Tanoda
HierarchyRecorder.cs
Go to the documentation of this file.
1/******************************************************************************
2 * Copyright (C) Ultraleap, Inc. 2011-2020. *
3 * *
4 * Use subject to the terms of the Apache License 2.0 available at *
5 * http://www.apache.org/licenses/LICENSE-2.0, or another agreement *
6 * between Ultraleap and you, your company or other organization. *
7 ******************************************************************************/
8
9using System;
10using System.IO;
11using System.Linq;
12using System.Collections.Generic;
13using UnityEngine;
14#if UNITY_EDITOR
15using UnityEditor;
16#endif
17using Leap.Unity.Query;
18
19namespace Leap.Unity.Recording {
20 using Attributes;
21
22 public class HierarchyRecorder : MonoBehaviour {
23 public static Action OnPreRecordFrame;
24 public static Action OnBeginRecording;
26
27 [EnumFlags]
28 public RecordOn recordWhen = 0;
29 public string recordingName;
31
32 [Header("Leap Recording Settings")]
34 public bool recordLeapData = false;
35
36 [Header("Key Bindings")]
37 public KeyCode beginRecordingKey = KeyCode.F5;
38 public KeyCode finishRecordingKey = KeyCode.F6;
39
40 protected AnimationClip _clip;
41
42 protected List<Component> _components;
43 protected List<PropertyRecorder> _recorders;
44
45 protected List<Behaviour> _tempBehaviour = new List<Behaviour>();
46 protected List<Renderer> _tempRenderer = new List<Renderer>();
47 protected List<Collider> _tempCollider = new List<Collider>();
48
49 protected HashSet<string> _takenNames = new HashSet<string>();
50
51 protected bool _isRecording = false;
52 protected float _startTime = 0;
53 protected int _startFrame = 0;
54
55 public bool isRecording {
56 get { return _isRecording; }
57 }
58
59 public float recordingTime {
60 get { return Time.time - _startTime; }
61 }
62
63 [Flags]
64 public enum RecordOn {
65 Start = 0x01,
66 HMDPresence = 0x02,
67 HandPresence = 0x04
68 }
69
70#if UNITY_EDITOR
71 protected List<Frame> _leapData;
72 protected List<CurveData> _curves;
73 protected Dictionary<AudioSource, RecordedAudio> _audioData;
74 protected Dictionary<Transform, List<TransformData>> _transformData;
75 protected Dictionary<Component, SerializedObject> _initialComponentData;
76 protected Dictionary<Component, List<ActivityData>> _behaviourActivity;
77
78 protected void Reset() {
79 recordingName = gameObject.name;
80 }
81
82 protected void Start() {
83 instance = this;
84
85 if ((recordWhen & RecordOn.Start) != 0) {
86 BeginRecording();
87 }
88 }
89
90 protected void LateUpdate() {
91 if (!_isRecording && (recordWhen & RecordOn.HMDPresence) != 0 && XRSupportUtil.IsUserPresent()) {
92 BeginRecording();
93 }
94
95 if ((Hands.Left != null || Hands.Right != null) && (recordWhen & RecordOn.HandPresence) != 0) {
96 BeginRecording();
97 }
98
99 if (Input.GetKeyDown(beginRecordingKey)) {
100 BeginRecording();
101 }
102
103 if (Input.GetKeyDown(finishRecordingKey)) {
104 finishRecording(new ProgressBar());
105 }
106
107 if (_isRecording) {
108 recordData();
109 }
110 }
111
115 public void BeginRecording() {
116 if (_isRecording) return;
117 _isRecording = true;
118 _startTime = Time.time;
119 _startFrame = Time.frameCount;
120
121 _components = new List<Component>();
122 _recorders = new List<PropertyRecorder>();
123 _leapData = new List<Frame>();
124
125 _curves = new List<CurveData>();
126 _audioData = new Dictionary<AudioSource, RecordedAudio>();
127 _transformData = new Dictionary<Transform, List<TransformData>>();
128 _initialComponentData = new Dictionary<Component, SerializedObject>();
129 _behaviourActivity = new Dictionary<Component, List<ActivityData>>();
130
131 if (OnBeginRecording != null) {
133 }
134 }
135
139 public void FinishRecording() {
140 finishRecording(new ProgressBar());
141 }
142
143 protected void finishRecording(ProgressBar progress) {
144
145 string targetFolderPath = targetFolder.Path;
146 if (targetFolderPath == null) {
147 if (gameObject.scene.IsValid() && !string.IsNullOrEmpty(gameObject.scene.path)) {
148 string sceneFolder = Path.GetDirectoryName(gameObject.scene.path);
149 targetFolderPath = Path.Combine(sceneFolder, "Recordings");
150 } else {
151 targetFolderPath = Path.Combine("Assets", "Recordings");
152 }
153 }
154
155 int folderSuffix = 1;
156 string finalSubFolder;
157 do {
158 finalSubFolder = Path.Combine(targetFolderPath, recordingName + " " + folderSuffix.ToString().PadLeft(2, '0'));
159 folderSuffix++;
160 } while (Directory.Exists(finalSubFolder));
161
162 string dataDirectory = Path.Combine(finalSubFolder, "_Data");
163
164 Directory.CreateDirectory(dataDirectory);
165 Directory.CreateDirectory(finalSubFolder);
166 AssetDatabase.Refresh();
167
168 progress.Begin(6, "Saving Recording", "", () => {
169 if (!_isRecording) return;
170 _isRecording = false;
171
172 //Turn on auto-pushing for all auto-proxy components
173 foreach (var autoProxy in GetComponentsInChildren<AutoValueProxy>()) {
174 autoProxy.autoPushingEnabled = true;
175 }
176
177 progress.Begin(3, "", "Reverting Scene State", () => {
178 //For all of our transform data, revert to the first piece recorded
179 progress.Begin(_transformData.Count, "", "", () => {
180 foreach (var pair in _transformData) {
181 progress.Step();
182 var transform = pair.Key;
183 var data = pair.Value;
184
185 if (transform == null || data.Count == 0) {
186 continue;
187 }
188
189 data[0].ApplyTo(transform);
190 }
191 });
192
193 //For all recorded curves, revert to start of curve
194 progress.Begin(_curves.Count, "", "", () => {
195 AnimationClip tempClip = new AnimationClip();
196 foreach (var data in _curves) {
197 progress.Step();
198 AnimationUtility.SetEditorCurve(tempClip, data.binding, data.curve);
199 }
200 tempClip.SampleAnimation(gameObject, 0);
201 });
202
203 //For all non-transform components, revert to original serialized values
204 progress.Begin(_initialComponentData.Count, "", "", () => {
205 foreach (var pair in _initialComponentData) {
206 progress.Step();
207 var component = pair.Key;
208 var sobj = pair.Value;
209
210 if (component == null || component is Transform) {
211 continue;
212 }
213
214
215 //We don't want to revert method recordings!
216 if (component is MethodRecording ||
217 component is RecordedAudio) {
218 continue;
219 }
220
221 var flags = sobj.FindProperty("m_ObjectHideFlags");
222 if (flags == null) {
223 Debug.LogError("Could not find hide flags for " + component);
224 continue;
225 }
226
227 //We have to dirty the serialized object somehow
228 //apparently there is no api to do this
229 //all objects have hide flags so we just touch them and revert them
230 int originalFlags = flags.intValue;
231 flags.intValue = ~originalFlags;
232 flags.intValue = originalFlags;
233
234 try {
235 //Applies previous state of entire component
236 sobj.ApplyModifiedProperties();
237 } catch (Exception e) {
238 Debug.LogError("Exception when trying to apply properties to " + component);
239 Debug.LogException(e);
240 }
241 }
242 });
243 });
244
245 progress.Begin(1, "", "Patching Materials: ", () => {
246 GetComponentsInChildren(true, _recorders);
247 foreach (var recorder in _recorders) {
248 DestroyImmediate(recorder);
249 }
250
251 //Patch up renderer references to materials
252 var allMaterials = Resources.FindObjectsOfTypeAll<Material>().
253 Query().
254 Where(AssetDatabase.IsMainAsset).
255 ToList();
256
257 var renderers = GetComponentsInChildren<Renderer>(includeInactive: true);
258
259 progress.Begin(renderers.Length, "", "", () => {
260 foreach (var renderer in renderers) {
261 progress.Step(renderer.name);
262
263 var materials = renderer.sharedMaterials;
264 for (int i = 0; i < materials.Length; i++) {
265 var material = materials[i];
266 if (material == null) {
267 continue;
268 }
269
270 if (!AssetDatabase.IsMainAsset(material)) {
271 var matchingMaterial = allMaterials.Query().FirstOrDefault(m => material.name.Contains(m.name) &&
272 material.shader == m.shader);
273
274 if (matchingMaterial != null) {
275 materials[i] = matchingMaterial;
276 }
277 }
278 }
279 renderer.sharedMaterials = materials;
280 }
281 });
282 });
283
284 progress.Begin(_behaviourActivity.Count, "", "Converting Activity Data: ", () => {
285 foreach (var pair in _behaviourActivity) {
286 var targetBehaviour = pair.Key;
287 var activityData = pair.Value;
288
289 if (targetBehaviour == null) {
290 continue;
291 }
292
293 progress.Step(targetBehaviour.name);
294
295 string path = AnimationUtility.CalculateTransformPath(targetBehaviour.transform, transform);
296 Type type = targetBehaviour.GetType();
297 string propertyName = "m_Enabled";
298
299 AnimationCurve curve = new AnimationCurve();
300 foreach (var dataPoint in activityData) {
301 int index = curve.AddKey(dataPoint.time, dataPoint.enabled ? 1 : 0);
302 AnimationUtility.SetKeyLeftTangentMode(curve, index, AnimationUtility.TangentMode.Constant);
303 AnimationUtility.SetKeyRightTangentMode(curve, index, AnimationUtility.TangentMode.Constant);
304 }
305
306 if (curve.IsConstant()) {
307 continue;
308 }
309
310 var binding = EditorCurveBinding.FloatCurve(path, type, propertyName);
311
312 if (_curves.Query().Any(c => c.binding == binding)) {
313 Debug.LogError("Binding already existed?");
314 Debug.LogError(binding.path + " : " + binding.propertyName);
315 continue;
316 }
317
318 _curves.Add(new CurveData() {
319 binding = binding,
320 curve = curve
321 });
322 }
323 });
324
325 progress.Begin(_transformData.Count, "", "Converting Transform Data: ", () => {
326 foreach (var pair in _transformData) {
327 var targetTransform = pair.Key;
328 var targetData = pair.Value;
329
330 progress.Step(targetTransform.name);
331
332 string path = AnimationUtility.CalculateTransformPath(targetTransform, transform);
333
334 bool isActivityConstant = true;
335 bool isPositionConstant = true;
336 bool isRotationConstant = true;
337 bool isScaleConstant = true;
338
339 {
340 bool startEnabled = targetData[0].enabled;
341 Vector3 startPosition = targetData[0].localPosition;
342 Quaternion startRotation = targetData[0].localRotation;
343 Vector3 startScale = targetData[0].localScale;
344 for (int i = 1; i < targetData.Count; i++) {
345 isActivityConstant &= targetData[i].enabled == startEnabled;
346 isPositionConstant &= targetData[i].localPosition == startPosition;
347 isRotationConstant &= targetData[i].localRotation == startRotation;
348 isScaleConstant &= targetData[i].localScale == startScale;
349 }
350 }
351
352 for (int i = 0; i < TransformData.CURVE_COUNT; i++) {
353 string propertyName = TransformData.GetName(i);
354 Type type = typeof(Transform);
355
356 AnimationCurve curve = new AnimationCurve();
357 var dataType = TransformData.GetDataType(i);
358
359 switch (dataType) {
360 case TransformDataType.Position:
361 if (isPositionConstant) continue;
362 break;
363 case TransformDataType.Rotation:
364 if (isRotationConstant) continue;
365 break;
366 case TransformDataType.Scale:
367 if (isScaleConstant) continue;
368 break;
369 case TransformDataType.Activity:
370 if (isActivityConstant) continue;
371 type = typeof(GameObject);
372 break;
373 }
374
375 for (int j = 0; j < targetData.Count; j++) {
376 int index = curve.AddKey(targetData[j].time, targetData[j].GetFloat(i));
377 if (dataType == TransformDataType.Activity) {
378 AnimationUtility.SetKeyLeftTangentMode(curve, index, AnimationUtility.TangentMode.Constant);
379 AnimationUtility.SetKeyRightTangentMode(curve, index, AnimationUtility.TangentMode.Constant);
380 }
381 }
382
383 var binding = EditorCurveBinding.FloatCurve(path, type, propertyName);
384
385 if (_curves.Query().Any(c => c.binding == binding)) {
386 Debug.LogError("Duplicate object was created??");
387 Debug.LogError("Named " + targetTransform.name + " : " + binding.path + " : " + binding.propertyName);
388 } else {
389 _curves.Add(new CurveData() {
390 binding = binding,
391 curve = curve
392 });
393 }
394 }
395 }
396 });
397
398 progress.Begin(_curves.Count, "", "Compressing Data: ", () => {
399 _curves.Sort((a, b) => a.binding.propertyName.CompareTo(b.binding.propertyName));
400 foreach (var data in _curves) {
401 using (new ProfilerSample("A")) {
402 EditorCurveBinding binding = data.binding;
403 AnimationCurve curve = data.curve;
404
405 progress.Step(binding.propertyName);
406
407 GameObject animationGameObject;
408 {
409 var animatedObj = AnimationUtility.GetAnimatedObject(gameObject, binding);
410 if (animatedObj is GameObject) {
411 animationGameObject = animatedObj as GameObject;
412 } else {
413 animationGameObject = (animatedObj as Component).gameObject;
414 }
415 }
416
417 bool isMatBinding = binding.propertyName.StartsWith("material.") &&
418 binding.type.IsSubclassOf(typeof(Renderer));
419
420 //But if the curve is constant, just get rid of it!
421 //Except for material curves, which we always need to keep
422 if (curve.IsConstant() && !isMatBinding) {
423 //Check to make sure there are no other matching curves that are
424 //non constant. If X and Y are constant but Z is not, we need to
425 //keep them all :(
426 if (_curves.Query().Where(p => p.binding.path == binding.path &&
427 p.binding.type == binding.type &&
428 p.binding.propertyName.TrimEnd(2) == binding.propertyName.TrimEnd(2)).
429 All(k => k.curve.IsConstant())) {
430 continue;
431 }
432 }
433
434 //First do a lossless compression
435 using (new ProfilerSample("B")) {
436 curve = AnimationCurveUtil.Compress(curve, Mathf.Epsilon, checkSteps: 3);
437 }
438
439 Transform targetTransform = null;
440 var targetObj = AnimationUtility.GetAnimatedObject(gameObject, binding);
441 if (targetObj is GameObject) {
442 targetTransform = (targetObj as GameObject).transform;
443 } else if (targetObj is Component) {
444 targetTransform = (targetObj as Component).transform;
445 } else {
446 Debug.LogError("Target obj was of type " + targetObj.GetType().Name);
447 }
448 }
449 }
450 });
451 });
452
453 progress.Begin(4, "Finalizing Assets", "", () => {
454 var postProcessComponent = gameObject.AddComponent<HierarchyPostProcess>();
455
456 GameObject myGameObject = gameObject;
457
458 DestroyImmediate(this);
459
460 //Create all the files for the method recording
461 progress.Step("Creating Method Recording Files...");
462 var methodRecordings = myGameObject.GetComponentsInChildren<MethodRecording>();
463 for (int i = 0; i < methodRecordings.Length; i++) {
464 var methodRecording = methodRecordings[i];
465 string fullPath = Path.Combine(finalSubFolder, "MethodRecording_" + i + ".asset");
466 methodRecording.ExitRecordingMode(fullPath);
467 }
468
469 postProcessComponent.dataFolder = new AssetFolder(dataDirectory);
470
471 //Create the asset that holds all of the curve data
472 progress.Begin(_curves.Count, "", "", () => {
473 string curveFile = Path.Combine(dataDirectory, "Curves.data");
474 using (var writer = File.CreateText(curveFile)) {
475 foreach (var data in _curves) {
476 progress.Step(data.binding.propertyName);
477
478 var bindingData = new EditorCurveBindingData() {
479 path = data.binding.path,
480 propertyName = data.binding.propertyName,
481 typeName = data.binding.type.Name,
482 curve = data.curve
483 };
484
485 writer.WriteLine(JsonUtility.ToJson(bindingData));
486 }
487 }
488 });
489
490 //Create the asset that holds all of the leap data
491 if (_leapData.Count > 0) {
492 progress.Begin(_leapData.Count, "", "", () => {
493 string leapFile = Path.Combine(dataDirectory, "Frames.data");
494
495 using (var writer = File.CreateText(leapFile)) {
496 for (int i = 0; i < _leapData.Count; i++) {
497 progress.Step("Frame " + i);
498 writer.WriteLine(JsonUtility.ToJson(_leapData[i]));
499 }
500 }
501 });
502 }
503
504 progress.Step("Creating Final Prefab...");
505 //Init the post process component
506 postProcessComponent.recordingName = recordingName;
507 postProcessComponent.assetFolder = new AssetFolder(finalSubFolder);
508
509 string prefabPath = Path.Combine(finalSubFolder, recordingName + " Raw.prefab");
510 #if UNITY_2018_3_OR_NEWER
511 var path = prefabPath.Replace('\\', '/');
512 PrefabUtility.SaveAsPrefabAsset(myGameObject, path, out bool didSucceed);
513 if (!didSucceed) { Debug.LogError($"Error saving prefab asset to {path}"); }
514 #else
515 PrefabUtility.CreatePrefab(prefabPath.Replace('\\', '/'), myGameObject);
516 #endif
517
518 AssetDatabase.Refresh();
519
520 EditorApplication.isPlaying = false;
521 });
522 }
523
524 protected void recordData() {
525 using (new ProfilerSample("Dispatch PreRecord Event")) {
526 if (OnPreRecordFrame != null) {
527 OnPreRecordFrame();
528 }
529 }
530
531 using (new ProfilerSample("Search For New Components")) {
532 using (new ProfilerSample("Get Components In Children")) {
533 GetComponentsInChildren(true, _components);
534 }
535
536 for (int i = 0; i < _components.Count; i++) {
537 var component = _components[i];
538
539 if (!_initialComponentData.ContainsKey(component)) {
540 using (new ProfilerSample("Handle New Component")) {
541 _initialComponentData[component] = new SerializedObject(component);
542
543 //First time experiencing a gameobject
544 if (component is Transform) {
545 using (new ProfilerSample("Handle New Transform")) {
546 var transform = component as Transform;
547
548 _transformData[transform] = new List<TransformData>();
549
550 var parent = transform.parent;
551 if (parent != null) {
552 var newName = transform.name;
553
554 for (int j = 0; j < parent.childCount; j++) {
555 var sibling = parent.GetChild(j);
556 if (sibling != transform && sibling.name == transform.name) {
557 transform.name = transform.name + " " + transform.gameObject.GetInstanceID();
558 break;
559 }
560 }
561 }
562 }
563 }
564
565 if (component is AudioSource) {
566 using (new ProfilerSample("Handle New AudioSource")) {
567 var source = component as AudioSource;
568 var data = source.gameObject.AddComponent<RecordedAudio>();
569 data.target = source;
570 _audioData[source] = data;
571 }
572 }
573
574 if (component is PropertyRecorder) {
575 using (new ProfilerSample("Handle New PropertyRecorder")) {
576 var recorder = component as PropertyRecorder;
577 foreach (var binding in recorder.GetBindings(gameObject)) {
578 _curves.Add(new CurveData() {
579 binding = binding,
580 curve = new AnimationCurve(),
581 accessor = new PropertyAccessor(gameObject, binding, failureIsZero: true)
582 });
583 }
584 }
585 }
586
587 if (((component is Behaviour) || (component is Renderer) || (component is Collider)) &&
588 !(component is PropertyRecorder)) {
589 using (new ProfilerSample("Handle New Behaviour")) {
590 _behaviourActivity[component] = new List<ActivityData>();
591 }
592 }
593 }
594 }
595
596 if (component is IValueProxy) (component as IValueProxy).OnPullValue();
597 }
598 }
599
600 using (new ProfilerSample("Record Custom Data")) {
601 float time = Time.time - _startTime;
602 for (int i = 0; i < _curves.Count; i++) {
603 _curves[i].SampleNow(time);
604 }
605 }
606
607 using (new ProfilerSample("Record Transform Data")) {
608 foreach (var pair in _transformData) {
609 var list = pair.Value;
610 var transform = pair.Key;
611
612 //If we have no data for this object BUT we also are not
613 //on the first frame of recording, this object must have
614 //been spawned, make sure to record a frame with it being
615 //disabled right before this
616 if (list.Count == 0 && Time.time > _startTime) {
617 list.Add(new TransformData() {
618 time = Time.time - _startTime - Time.deltaTime,
619 enabled = false,
620 localPosition = transform.localPosition,
621 localRotation = transform.localRotation,
622 localScale = transform.localScale
623 });
624 }
625
626 list.Add(new TransformData() {
627 time = Time.time - _startTime,
628 enabled = transform.gameObject.activeSelf,
629 localPosition = transform.localPosition,
630 localRotation = transform.localRotation,
631 localScale = transform.localScale
632 });
633 }
634 }
635
636 using (new ProfilerSample("Record Behaviour Activity Data")) {
637 foreach (var pair in _behaviourActivity) {
638 pair.Value.Add(new ActivityData() {
639 time = Time.time - _startTime,
640 enabled = EditorUtility.GetObjectEnabled(pair.Key) == 1
641 });
642 }
643 }
644
645 if (provider != null && recordLeapData) {
646 using (new ProfilerSample("Record Leap Data")) {
647 Frame newFrame = new Frame();
648 newFrame.CopyFrom(provider.CurrentFrame);
649 newFrame.CurrentFramesPerSecond = 1.0f / Time.smoothDeltaTime;
650 newFrame.Timestamp = (long)((Time.time - _startTime) * 1e6);
651 newFrame.Id = Time.frameCount - _startFrame;
652 _leapData.Add(newFrame);
653 }
654 }
655 }
656
657 protected struct ActivityData {
658 public float time;
659 public bool enabled;
660 }
661
662 protected enum TransformDataType {
663 Activity,
664 Position,
665 Rotation,
666 Scale
667 }
668
669 protected struct CurveData {
670 public EditorCurveBinding binding;
671 public AnimationCurve curve;
672 public PropertyAccessor accessor;
673
674 public void SampleNow(float time) {
675 curve.AddKey(time, accessor.Access());
676 }
677 }
678
679 protected struct TransformData {
680 public const int CURVE_COUNT = 11;
681
682 public float time;
683
684 public bool enabled;
685 public Vector3 localPosition;
686 public Quaternion localRotation;
687 public Vector3 localScale;
688
689 public float GetFloat(int index) {
690 switch (index) {
691 case 0:
692 case 1:
693 case 2:
694 return localPosition[index];
695 case 3:
696 case 4:
697 case 5:
698 case 6:
699 return localRotation[index - 3];
700 case 7:
701 case 8:
702 case 9:
703 return localScale[index - 7];
704 case 10:
705 return enabled ? 1 : 0;
706 }
707 throw new Exception();
708 }
709
710 public static string GetName(int index) {
711 switch (index) {
712 case 0:
713 case 1:
714 case 2:
715 return "m_LocalPosition." + "xyz"[index];
716 case 3:
717 case 4:
718 case 5:
719 case 6:
720 return "m_LocalRotation." + "xyzw"[index - 3];
721 case 7:
722 case 8:
723 case 9:
724 return "m_LocalScale" + "xyz"[index - 7];
725 case 10:
726 return "m_IsActive";
727 }
728 throw new Exception();
729 }
730
731 public static TransformDataType GetDataType(int index) {
732 switch (index) {
733 case 0:
734 case 1:
735 case 2:
736 return TransformDataType.Position;
737 case 3:
738 case 4:
739 case 5:
740 case 6:
741 return TransformDataType.Rotation;
742 case 7:
743 case 8:
744 case 9:
745 return TransformDataType.Scale;
746 case 10:
747 return TransformDataType.Activity;
748 }
749 throw new Exception();
750 }
751
752 public void ApplyTo(Transform transform) {
753 transform.localPosition = localPosition;
754 transform.localRotation = localRotation;
755 transform.localScale = localScale;
756 transform.gameObject.SetActive(enabled);
757 }
758 }
759
760 #region GUI
761
762 protected Vector2 _guiMargins = new Vector2(5F, 5F);
763 protected Vector2 _guiSize = new Vector2(175F, 60F);
764
765 protected void OnGUI() {
766 var guiRect = new Rect(_guiMargins.x, _guiMargins.y, _guiSize.x, _guiSize.y);
767
768 GUI.Box(guiRect, "");
769
770 GUILayout.BeginArea(guiRect.PadInner(5F));
771 GUILayout.BeginVertical();
772
773 if (!_isRecording) {
774 GUILayout.Label("Ready to record.");
775 if (GUILayout.Button("Start Recording (" + beginRecordingKey.ToString() + ")",
776 GUILayout.ExpandHeight(true))) {
777 BeginRecording();
778 }
779 } else {
780 GUILayout.Label("Recording.");
781 if (GUILayout.Button("Stop Recording (" + finishRecordingKey.ToString() + ")",
782 GUILayout.ExpandHeight(true))) {
783 finishRecording(new ProgressBar());
784 }
785 }
786
787 GUILayout.EndVertical();
788 GUILayout.EndArea();
789 }
790
791 #endregion
792
793#endif
794 }
795}
UnityEngine.Component Component
UnityEngine.Debug Debug
Definition: TanodaServer.cs:19
A convenient serializable representation of an asset folder. Only useful for editor scripts since ass...
Definition: AssetFolder.cs:26
virtual string Path
Gets or sets the folder path. This path will always be a path relative to the asset folder,...
Definition: AssetFolder.cs:43
Provides Frame object data to the Unity application by firing events as soon as Frame data is availab...
Definition: LeapProvider.cs:21
abstract Frame CurrentFrame
The current frame for this update cycle, in world space.
Definition: LeapProvider.cs:37
A Query object is a type of immutable ordered collection of elements that can be used to perform usef...
Definition: Query.cs:90