Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
/******************************************************************************
* Copyright (C) Leap Motion, Inc. 2011-2018. *
* Leap Motion proprietary and confidential. *
* *
* Use subject to the terms of the Leap Motion SDK Agreement available at *
* https://developer.leapmotion.com/sdk_agreement, or another agreement *
* between Leap Motion and you, your company or other organization. *
******************************************************************************/
using System;
using System.IO;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;
#if UNITY_EDITOR
using UnityEditor;
#endif
using Leap.Unity;
using Leap.Unity.Query;
using Leap.Unity.Attributes;
using Leap.Unity.GraphicalRenderer;
namespace Leap.Unity.Recording {
public class HierarchyPostProcess : MonoBehaviour {
[Header("Recording Settings")]
public string recordingName;
public AssetFolder assetFolder;
public AssetFolder dataFolder;
[SerializeField, ImplementsTypeNameDropdown(typeof(LeapRecording))]
private string _leapRecordingType;
[Header("Compression Settings")]
[MinValue(0)]
public float positionMaxError = 0.005f;
[Range(0, 90)]
public float rotationMaxError = 1;
[MinValue(1)]
public float scaleMaxError = 1.02f;
[Range(0, 1)]
public float colorHueMaxError = 0.05f;
[Range(0, 1)]
public float colorSaturationMaxError = 0.05f;
[Range(0, 1)]
public float colorValueMaxError = 0.05f;
[Range(0, 1)]
public float colorAlphaMaxError = 0.05f;
[MinValue(0)]
public float genericMaxError = 0.05f;
#if UNITY_EDITOR
public void BuildPlaybackPrefab(ProgressBar progress) {
var timeline = ScriptableObject.CreateInstance<TimelineAsset>();
var animationTrack = timeline.CreateTrack<AnimationTrack>(null, "Playback Animation");
var clip = generateCompressedClip(progress);
var playableAsset = ScriptableObject.CreateInstance<AnimationPlayableAsset>();
playableAsset.clip = clip;
playableAsset.hideFlags = HideFlags.HideInInspector | HideFlags.HideInHierarchy;
playableAsset.name = "Recorded Animation";
var timelineClip = animationTrack.CreateClip(clip);
timelineClip.duration = clip.length;
timelineClip.asset = playableAsset;
timelineClip.displayName = "Recorded Animation";
//If a clip is not recordable, it will not show up as editable in the timeline view.
//For whatever reason unity decided that imported clips are not recordable, so we hack a
//private variable to force them to be! This seems to have no ill effects but if things go
//wrong we can just revert this line
timelineClip.GetType().GetField("m_Recordable", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(timelineClip, true);
//Try to generate a leap recording if we have leap data
RecordingTrack recordingTrack = null;
LeapRecording leapRecording = null;
string framesPath = Path.Combine(dataFolder.Path, "Frames.data");
if (File.Exists(framesPath)) {
List<Frame> frames = new List<Frame>();
progress.Begin(1, "Loading Leap Data", "", () => {
progress.Step();
using (var reader = File.OpenText(framesPath)) {
while (true) {
string line = reader.ReadLine();
if (string.IsNullOrEmpty(line)) {
break;
}
frames.Add(JsonUtility.FromJson<Frame>(line));
}
}
});
leapRecording = ScriptableObject.CreateInstance(_leapRecordingType) as LeapRecording;
if (leapRecording != null) {
leapRecording.name = "Recorded Leap Data";
leapRecording.LoadFrames(frames);
} else {
Debug.LogError("Unable to create Leap recording: Invalid type specification for "
+ "LeapRecording implementation.", this);
}
}
string assetPath = Path.Combine(assetFolder.Path, recordingName + ".asset");
AssetDatabase.CreateAsset(timeline, assetPath);
AssetDatabase.AddObjectToAsset(playableAsset, timeline);
AssetDatabase.AddObjectToAsset(animationTrack, timeline);
AssetDatabase.AddObjectToAsset(clip, timeline);
//If we do have a leap recording, create a recording track to house it
if (leapRecording != null) {
recordingTrack = timeline.CreateTrack<RecordingTrack>(null, "Leap Recording");
var recordingClip = recordingTrack.CreateDefaultClip();
recordingClip.duration = leapRecording.length;
var recordingAsset = recordingClip.asset as RecordingClip;
recordingAsset.recording = leapRecording;
AssetDatabase.AddObjectToAsset(leapRecording, timeline);
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
//Create the playable director and link it to the new timeline
var director = gameObject.AddComponent<PlayableDirector>();
director.playableAsset = timeline;
//Create the animator
gameObject.AddComponent<Animator>();
//Link the animation track to the animator
//(it likes to point to gameobject instead of the animator directly)
director.SetGenericBinding(animationTrack.outputs.Query().First().sourceObject, gameObject);
//Destroy existing provider
var provider = gameObject.GetComponentInChildren<LeapProvider>();
if (provider != null) {
GameObject providerObj = provider.gameObject;
DestroyImmediate(provider);
//If a leap recording track exists, spawn a playable provider and link it to the track
if (recordingTrack != null) {
var playableProvider = providerObj.AddComponent<LeapPlayableProvider>();
director.SetGenericBinding(recordingTrack.outputs.Query().First().sourceObject, playableProvider);
}
}
buildAudioTracks(progress, director, timeline);
buildMethodRecordingTracks(progress, director, timeline);
progress.Begin(1, "", "Finalizing Prefab", () => {
GameObject myGameObject = gameObject;
DestroyImmediate(this);
string prefabPath = Path.Combine(assetFolder.Path, recordingName + ".prefab");
PrefabUtility.CreatePrefab(prefabPath.Replace('\\', '/'), myGameObject);
});
}
private void buildMethodRecordingTracks(ProgressBar progress, PlayableDirector director, TimelineAsset timeline) {
var recordings = GetComponentsInChildren<MethodRecording>();
if (recordings.Length > 0) {
progress.Begin(recordings.Length, "", "Building Method Tracks: ", () => {
foreach (var recording in recordings) {
progress.Step(recording.gameObject.name);
try {
var track = timeline.CreateTrack<MethodRecordingTrack>(null, recording.gameObject.name);
director.SetGenericBinding(track.outputs.Query().First().sourceObject, recording);
var clip = track.CreateClip<MethodRecordingClip>();
clip.duration = recording.GetDuration();
} catch (Exception e) {
Debug.LogException(e);
}
}
});
}
}
private void buildAudioTracks(ProgressBar progress, PlayableDirector director, TimelineAsset timeline) {
var audioData = GetComponentsInChildren<RecordedAudio>(includeInactive: true);
var sourceToData = audioData.Query().ToDictionary(a => a.target, a => a);
progress.Begin(sourceToData.Count, "", "Building Audio Track: ", () => {
foreach (var pair in sourceToData) {
var track = timeline.CreateTrack<AudioTrack>(null, pair.Value.name);
director.SetGenericBinding(track.outputs.Query().First().sourceObject, pair.Key);
progress.Begin(pair.Value.data.Count, "", "", () => {
foreach (var clipData in pair.Value.data) {
progress.Step(clipData.clip.name);
var clip = track.CreateClip(clipData.clip);
clip.start = clipData.startTime;
clip.timeScale = clipData.pitch;
clip.duration = clipData.clip.length;
}
});
}
});
}
private AnimationClip generateCompressedClip(ProgressBar progress) {
var clip = new AnimationClip();
clip.name = "Recorded Animation";
List<EditorCurveBindingData> curveData = new List<EditorCurveBindingData>();
progress.Begin(1, "Opening Curve Files...", "", () => {
progress.Step();
using (var reader = File.OpenText(Path.Combine(dataFolder.Path, "Curves.data"))) {
while (true) {
string line = reader.ReadLine();
if (string.IsNullOrEmpty(line)) {
break;
}
curveData.Add(JsonUtility.FromJson<EditorCurveBindingData>(line));
}
}
});
progress.Begin(2, "", "", () => {
var bindingMap = new Dictionary<EditorCurveBinding, AnimationCurve>();
Dictionary<string, Type> nameToType = new Dictionary<string, Type>();
foreach (var component in GetComponentsInChildren<Component>()) {
nameToType[component.GetType().Name] = component.GetType();
}
nameToType[typeof(GameObject).Name] = typeof(GameObject);
var toCompress = new Dictionary<EditorCurveBinding, AnimationCurve>();
var targetObjects = new HashSet<UnityEngine.Object>();
foreach (var data in curveData) {
Type type;
if (!nameToType.TryGetValue(data.typeName, out type)) {
continue;
}
var binding = EditorCurveBinding.FloatCurve(data.path, type, data.propertyName);
var targetObj = AnimationUtility.GetAnimatedObject(gameObject, binding);
if (targetObj == null) {
continue;
}
toCompress[binding] = data.curve;
targetObjects.Add(targetObj);
}
progress.Begin(targetObjects.Count, "Compressing Curves", "", () => {
foreach (var targetObj in targetObjects) {
var filteredCurves = new Dictionary<EditorCurveBinding, AnimationCurve>();
foreach (var curve in toCompress) {
if (AnimationUtility.GetAnimatedObject(gameObject, curve.Key) == targetObj) {
filteredCurves[curve.Key] = curve.Value;
}
}
doCompression(progress, targetObj, filteredCurves, bindingMap);
}
});
progress.Begin(bindingMap.Count, "Assigning Curves", "", () => {
foreach (var binding in bindingMap) {
progress.Step(binding.Key.propertyName);
AnimationUtility.SetEditorCurve(clip, binding.Key, binding.Value);
}
});
});
return clip;
}
private void doCompression(ProgressBar progress,
UnityEngine.Object targetObject,
Dictionary<EditorCurveBinding, AnimationCurve> toCompress,
Dictionary<EditorCurveBinding, AnimationCurve> bindingMap) {
var propertyToMaxError = calculatePropertyErrors(targetObject);
List<EditorCurveBinding> bindings;
progress.Begin(6, "", targetObject.name, () => {
//First do rotations
bindings = toCompress.Keys.Query().ToList();
progress.Begin(bindings.Count, "", "", () => {
foreach (var wBinding in bindings) {
progress.Step();
if (!wBinding.propertyName.EndsWith(".w")) {
continue;
}
string property = wBinding.propertyName.Substring(0, wBinding.propertyName.Length - 2);
string xProp = property + ".x";
string yProp = property + ".y";
string zProp = property + ".z";
var xMaybe = toCompress.Keys.Query().FirstOrNone(t => t.propertyName == xProp);
var yMaybe = toCompress.Keys.Query().FirstOrNone(t => t.propertyName == yProp);
var zMaybe = toCompress.Keys.Query().FirstOrNone(t => t.propertyName == zProp);
Maybe.MatchAll(xMaybe, yMaybe, zMaybe, (xBinding, yBinding, zBinding) => {
float maxAngleError;
if (!propertyToMaxError.TryGetValue(property, out maxAngleError)) {
maxAngleError = rotationMaxError;
}
AnimationCurve compressedX, compressedY, compressedZ, compressedW;
AnimationCurveUtil.CompressRotations(toCompress[xBinding],
toCompress[yBinding],
toCompress[zBinding],
toCompress[wBinding],
out compressedX,
out compressedY,
out compressedZ,
out compressedW,
maxAngleError);
bindingMap[xBinding] = compressedX;
bindingMap[yBinding] = compressedY;
bindingMap[zBinding] = compressedZ;
bindingMap[wBinding] = compressedW;
toCompress.Remove(xBinding);
toCompress.Remove(yBinding);
toCompress.Remove(zBinding);
toCompress.Remove(wBinding);
});
}
});
//Next do scales
bindings = toCompress.Keys.Query().ToList();
progress.Begin(bindings.Count, "", "", () => {
foreach (var binding in bindings) {
progress.Step();
if (!binding.propertyName.EndsWith(".x") &&
!binding.propertyName.EndsWith(".y") &&
!binding.propertyName.EndsWith(".z")) {
continue;
}
if (!binding.propertyName.Contains("LocalScale")) {
continue;
}
bindingMap[binding] = AnimationCurveUtil.CompressScale(toCompress[binding], scaleMaxError);
toCompress.Remove(binding);
}
});
//Next do positions
bindings = toCompress.Keys.Query().ToList();
progress.Begin(bindings.Count, "", "", () => {
foreach (var xBinding in bindings) {
progress.Step();
if (!xBinding.propertyName.EndsWith(".x")) {
continue;
}
string property = xBinding.propertyName.Substring(0, xBinding.propertyName.Length - 2);
string yProp = property + ".y";
string zProp = property + ".z";
var yMaybe = toCompress.Keys.Query().FirstOrNone(t => t.propertyName == yProp);
var zMaybe = toCompress.Keys.Query().FirstOrNone(t => t.propertyName == zProp);
Maybe.MatchAll(yMaybe, zMaybe, (yBinding, zBinding) => {
float maxDistanceError;
if (!propertyToMaxError.TryGetValue(property, out maxDistanceError)) {
maxDistanceError = positionMaxError;
}
AnimationCurve compressedX, compressedY, compressedZ;
AnimationCurveUtil.CompressPositions(toCompress[xBinding],
toCompress[yBinding],
toCompress[zBinding],
out compressedX,
out compressedY,
out compressedZ,
maxDistanceError);
bindingMap[xBinding] = compressedX;
bindingMap[yBinding] = compressedY;
bindingMap[zBinding] = compressedZ;
toCompress.Remove(xBinding);
toCompress.Remove(yBinding);
toCompress.Remove(zBinding);
});
}
});
//Next do colors
bindings = toCompress.Keys.Query().ToList();
progress.Begin(bindings.Count, "", "", () => {
foreach (var rBinding in bindings) {
progress.Step();
if (!rBinding.propertyName.EndsWith(".r")) {
continue;
}
string property = rBinding.propertyName.Substring(0, rBinding.propertyName.Length - 2);
string gProp = property + ".g";
string bProp = property + ".b";
var gMaybe = toCompress.Keys.Query().FirstOrNone(t => t.propertyName == gProp);
var bMaybe = toCompress.Keys.Query().FirstOrNone(t => t.propertyName == bProp);
Maybe.MatchAll(gMaybe, bMaybe, (gBinding, bBinding) => {
AnimationCurve compressedR, compressedG, compressedB;
AnimationCurveUtil.CompressColorsHSV(toCompress[rBinding],
toCompress[gBinding],
toCompress[bBinding],
out compressedR,
out compressedG,
out compressedB,
colorHueMaxError,
colorSaturationMaxError,
colorValueMaxError);
bindingMap[rBinding] = compressedR;
bindingMap[gBinding] = compressedG;
bindingMap[bBinding] = compressedB;
});
}
});
//Then do color alpha
bindings = toCompress.Keys.Query().ToList();
progress.Begin(bindings.Count, "", "", () => {
foreach (var aBinding in bindings) {
progress.Step();
if (!aBinding.propertyName.EndsWith(".a")) {
continue;
}
var compressedA = AnimationCurveUtil.Compress(toCompress[aBinding], colorAlphaMaxError);
toCompress.Remove(aBinding);
bindingMap[aBinding] = compressedA;
}
});
//Then everything else
bindings = toCompress.Keys.Query().ToList();
progress.Begin(bindings.Count, "", "", () => {
foreach (var binding in bindings) {
progress.Step();
float maxError;
if (!propertyToMaxError.TryGetValue(binding.propertyName, out maxError)) {
maxError = genericMaxError;
}
var compressedCurve = AnimationCurveUtil.Compress(toCompress[binding], maxError);
toCompress.Remove(binding);
bindingMap[binding] = compressedCurve;
}
});
});
}
private Dictionary<string, float> calculatePropertyErrors(UnityEngine.Object targetObject) {
Transform targetTransform;
if (targetObject is GameObject) {
targetTransform = (targetObject as GameObject).transform;
} else if (targetObject is Component) {
targetTransform = (targetObject as Component).transform;
} else {
throw new InvalidOperationException("Unexpected target object type " + targetObject);
}
var propertyToMaxError = new Dictionary<string, float>();
Transform currTransform = targetTransform;
while (currTransform != null) {
var compressionSettings = currTransform.GetComponent<PropertyCompression>();
if (compressionSettings != null) {
foreach (var setting in compressionSettings.compressionOverrides) {
if (!propertyToMaxError.ContainsKey(setting.propertyName)) {
propertyToMaxError.Add(setting.propertyName, setting.maxError);
}
}
}
currTransform = currTransform.parent;
}
return propertyToMaxError;
}
#endif
}
}
3142d0420This file is pinned to the selected release. Download the original to open it in your design tool.