VR4RoboticArm2/VR4RoboticArm/Library/PackageCache/com.meta.xr.sdk.interaction/Editor/Utils/HandAnimationUtils.cs
IonutMocanu d7aba243a2 Main
2025-09-08 11:04:02 +03:00

374 lines
15 KiB
C#

/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Oculus.Interaction.Input;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
namespace Oculus.Interaction.Utils
{
public static class HandAnimationUtils
{
public const string localEulerKey = "localEulerAnglesRaw.";
public const string positionKey = "m_LocalPosition.";
public static void Compress(ref AnimationClip clip, float rotationThreshold, float positionThreshold)
{
EditorCurveBinding[] bindings = AnimationUtility.GetCurveBindings(clip);
foreach (EditorCurveBinding binding in bindings)
{
float threshold;
if (binding.propertyName.StartsWith(localEulerKey))
{
threshold = rotationThreshold;
}
else if (binding.propertyName.StartsWith(positionKey))
{
threshold = positionThreshold;
}
else
{
return;
}
AnimationCurve curve = AnimationUtility.GetEditorCurve(clip, binding);
for (int i = 2; i < curve.keys.Length; i++)
{
Keyframe prevFrame = curve.keys[i - 1];
Keyframe prevPrevFrame = curve.keys[i - 2];
Keyframe currentFrame = curve.keys[i];
Vector2 prevDelta = new Vector2(prevFrame.time - prevPrevFrame.time, prevFrame.value - prevPrevFrame.value);
Vector2 currDelta = new Vector2(currentFrame.time - prevFrame.time, currentFrame.value - prevFrame.value);
float slopeDifference = prevDelta.x * currDelta.y - prevDelta.y * currDelta.x;
if (Mathf.Abs(slopeDifference) < threshold)
{
curve.RemoveKey(i--);
}
}
SmoothCurveTangets(ref curve);
AnimationUtility.SetEditorCurve(clip, binding, curve);
}
}
public static void Trim(ref AnimationClip clip, float minTime, float maxTime)
{
EditorCurveBinding[] bindings = AnimationUtility.GetCurveBindings(clip);
float min = minTime * clip.length;
float max = maxTime * clip.length;
foreach (var binding in bindings)
{
AnimationCurve curve = AnimationUtility.GetEditorCurve(clip, binding);
float minValue = curve.Evaluate(min);
float maxValue = curve.Evaluate(max);
while (curve.length > 0
&& curve.keys[0].time < min)
{
curve.RemoveKey(0);
}
while (curve.length > 0
&& curve.keys[curve.keys.Length - 1].time > max)
{
curve.RemoveKey(curve.keys.Length - 1);
}
if (curve.length > 0)
{
if (curve.keys[0].time > min)
{
curve.AddKey(new Keyframe(min, minValue));
}
if (curve.keys[curve.keys.Length - 1].time != max)
{
curve.AddKey(new Keyframe(max, maxValue));
}
for (int i = 0; i < curve.keys.Length; i++)
{
Keyframe keyframe = curve.keys[i];
keyframe.time -= min;
curve.MoveKey(i, keyframe);
}
}
SmoothCurveTangets(ref curve);
AnimationUtility.SetEditorCurve(clip, binding, curve);
}
}
public static AnimationClip Mirror(AnimationClip clip,
IList<Transform> joints, Transform root, HandFingerJointFlags maskHandJointIds,
Handedness fromHandedness, string leftPrefix, string rightPrefix, bool includePosition)
{
AnimationClip mirrorClip = new AnimationClip();
mirrorClip.frameRate = clip.frameRate;
string fromPrefix = fromHandedness == Handedness.Left ? leftPrefix : rightPrefix;
string toPrefix = fromHandedness == Handedness.Left ? rightPrefix : leftPrefix;
var fromHandSpace = fromHandedness == Handedness.Left ?
HandMirroring.LeftHandSpace : HandMirroring.RightHandSpace;
var toHandSpace = fromHandedness == Handedness.Left ?
HandMirroring.RightHandSpace : HandMirroring.LeftHandSpace;
EditorCurveBinding[] curveBindings = AnimationUtility.GetCurveBindings(clip);
foreach (EditorCurveBinding curveBinding in curveBindings)
{
string path = curveBinding.path;
string mirrorPath = path.Replace(fromPrefix, toPrefix);
AnimationCurve curve = AnimationUtility.GetEditorCurve(clip, curveBinding);
mirrorClip.SetCurve(mirrorPath, curveBinding.type, curveBinding.propertyName, curve);
}
for (HandJointId jointId = HandJointId.HandStart; jointId < HandJointId.HandEnd; jointId++)
{
if (((int)maskHandJointIds & (1 << (int)jointId)) == 0)
{
continue;
}
string path = GetGameObjectPath(joints[(int)jointId], root);
JointRecord mirroredRecord = new JointRecord(jointId, path);
EditorCurveBinding bindingEulerX = curveBindings.First(cb => cb.path == path && cb.propertyName.StartsWith($"{localEulerKey}x"));
EditorCurveBinding bindingEulerY = curveBindings.First(cb => cb.path == path && cb.propertyName.StartsWith($"{localEulerKey}y"));
EditorCurveBinding bindingEulerZ = curveBindings.First(cb => cb.path == path && cb.propertyName.StartsWith($"{localEulerKey}z"));
AnimationCurve curveEulerX = AnimationUtility.GetEditorCurve(clip, bindingEulerX);
AnimationCurve curveEulerY = AnimationUtility.GetEditorCurve(clip, bindingEulerY);
AnimationCurve curveEulerZ = AnimationUtility.GetEditorCurve(clip, bindingEulerZ);
AnimationCurve curvePositionX = null;
AnimationCurve curvePositionY = null;
AnimationCurve curvePositionZ = null;
if (includePosition)
{
EditorCurveBinding bindingPositionX = curveBindings.First(cb => cb.path == path && cb.propertyName.StartsWith($"{positionKey}x"));
EditorCurveBinding bindingPositionY = curveBindings.First(cb => cb.path == path && cb.propertyName.StartsWith($"{positionKey}y"));
EditorCurveBinding bindingPositionZ = curveBindings.First(cb => cb.path == path && cb.propertyName.StartsWith($"{positionKey}z"));
curvePositionX = AnimationUtility.GetEditorCurve(clip, bindingPositionX);
curvePositionY = AnimationUtility.GetEditorCurve(clip, bindingPositionY);
curvePositionZ = AnimationUtility.GetEditorCurve(clip, bindingPositionZ);
}
for (float time = 0; time <= clip.length; time += 1f / clip.frameRate)
{
Quaternion rotation = Quaternion.Euler(
curveEulerX.Evaluate(time),
curveEulerY.Evaluate(time),
curveEulerZ.Evaluate(time));
Quaternion rotationMirrored = HandMirroring.TransformRotation(rotation,
fromHandSpace, toHandSpace);
Vector3 positionMirrored = Vector3.zero;
if (includePosition)
{
Vector3 position = new Vector3(
curvePositionX.Evaluate(time),
curvePositionY.Evaluate(time),
curvePositionZ.Evaluate(time));
positionMirrored = HandMirroring.TransformPosition(position,
fromHandSpace, toHandSpace);
}
Pose mirroredPose = new Pose(positionMirrored, rotationMirrored);
mirroredRecord.RecordPose(time, mirroredPose);
}
WriteAnimationCurves(ref mirrorClip, mirroredRecord, includePosition);
}
return mirrorClip;
}
public static void WriteAnimationCurves(ref AnimationClip clip, JointRecord record, bool includePosition)
{
WriteCurve(ref clip, record.Path, $"{localEulerKey}x", record.RotationX);
WriteCurve(ref clip, record.Path, $"{localEulerKey}y", record.RotationY);
WriteCurve(ref clip, record.Path, $"{localEulerKey}z", record.RotationZ);
if (includePosition)
{
WriteCurve(ref clip, record.Path, $"{positionKey}x", record.PositionX);
WriteCurve(ref clip, record.Path, $"{positionKey}y", record.PositionY);
WriteCurve(ref clip, record.Path, $"{positionKey}z", record.PositionZ);
}
}
public static void WriteCurve(ref AnimationClip clip, string path, string propertyName, List<Keyframe> frames)
{
AnimationCurve curve = new AnimationCurve(frames.ToArray());
clip.SetCurve(path, typeof(Transform), propertyName, curve);
}
public static bool TryGetClipHandedness(AnimationClip clip,
string leftPrefix, string rightPrefix,
out Handedness handedness)
{
if (clip == null)
{
handedness = Handedness.Left;
return false;
}
EditorCurveBinding[] curveBindings = AnimationUtility.GetCurveBindings(clip);
foreach (EditorCurveBinding curveBinding in curveBindings)
{
if (curveBinding.path.Contains(leftPrefix))
{
handedness = Handedness.Left;
return true;
}
else if (curveBinding.path.Contains(rightPrefix))
{
handedness = Handedness.Right;
return true;
}
}
handedness = Handedness.Left;
return false;
}
public static void StoreAsset(Object asset, string folder, string name)
{
string targetFolder = Path.Combine("Assets", folder);
CreateFolder(targetFolder);
string path = Path.Combine(targetFolder, name);
AssetDatabase.CreateAsset(asset, path);
AssetDatabase.Refresh();
Debug.Log($"Asset generated at {path}");
}
public static void CreateFolder(string targetFolder)
{
if (!Directory.Exists(targetFolder))
{
Directory.CreateDirectory(targetFolder);
}
}
public static string GetGameObjectPath(Transform transform, Transform root)
{
string path = transform.name;
while (transform.parent != null
&& transform.parent != root)
{
transform = transform.parent;
path = $"{transform.name}/{path}";
}
return path;
}
public static bool GenerateObjectField<T>(ref T obj, string label = "") where T : Object
{
EditorGUI.BeginChangeCheck();
obj = EditorGUILayout.ObjectField(label, obj, typeof(T), true) as T;
return EditorGUI.EndChangeCheck();
}
private static void SmoothCurveTangets(ref AnimationCurve curve)
{
for (int i = 0; i < curve.keys.Length; i++)
{
AnimationUtility.SetKeyLeftTangentMode(curve, i, AnimationUtility.TangentMode.ClampedAuto);
AnimationUtility.SetKeyRightTangentMode(curve, i, AnimationUtility.TangentMode.ClampedAuto);
}
}
}
public struct PoseFrame
{
public float time;
public Pose pose;
public PoseFrame(float time, Pose pose)
{
this.time = time;
this.pose = pose;
}
}
public class JointRecord
{
public HandJointId JointId { get; private set; }
public string Path { get; private set; }
private List<PoseFrame> _poseFrames = new List<PoseFrame>();
public List<Keyframe> RotationX => FilterAngles(_poseFrames.Select(pf => new Keyframe(pf.time, pf.pose.rotation.eulerAngles.x)).ToList());
public List<Keyframe> RotationY => FilterAngles(_poseFrames.Select(pf => new Keyframe(pf.time, pf.pose.rotation.eulerAngles.y)).ToList());
public List<Keyframe> RotationZ => FilterAngles(_poseFrames.Select(pf => new Keyframe(pf.time, pf.pose.rotation.eulerAngles.z)).ToList());
public List<Keyframe> PositionX => _poseFrames.Select(pf => new Keyframe(pf.time, pf.pose.position.x)).ToList();
public List<Keyframe> PositionY => _poseFrames.Select(pf => new Keyframe(pf.time, pf.pose.position.y)).ToList();
public List<Keyframe> PositionZ => _poseFrames.Select(pf => new Keyframe(pf.time, pf.pose.position.z)).ToList();
public JointRecord(HandJointId jointId, string path)
{
JointId = jointId;
Path = path;
}
public void RecordPose(float time, Pose pose)
{
_poseFrames.Add(new PoseFrame(time, pose));
}
private static List<Keyframe> FilterAngles(List<Keyframe> keyframes)
{
if (keyframes.Count < 1)
{
return keyframes;
}
List<Keyframe> filtered = new List<Keyframe>();
filtered.Add(keyframes[0]);
for (int i = 1; i < keyframes.Count; i++)
{
float prevValue = filtered[filtered.Count - 1].value;
float value = keyframes[i].value;
while (Mathf.Abs(prevValue - value) > 180f)
{
value += 360f * Mathf.Sign(prevValue - value);
}
filtered.Add(new Keyframe(keyframes[i].time, value));
}
return filtered;
}
}
}