Last active
November 3, 2022 07:01
-
-
Save Seneral/2e348684570b89ed50030adbd402eb0a to your computer and use it in GitHub Desktop.
Allows to save, export, import and restore a SkinnedMeshRenderer's bone transformation state (aka pose). This includes resetting to the initial model pose. Put PoseManagerEditor.cs in an editor folder.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using System; | |
using System.Linq; | |
using System.Collections; | |
using System.Collections.Generic; | |
public static class BoneManager | |
{ | |
/// <summary> | |
/// Searches for a transform with the given name in the given parent transformation recursively | |
/// </summary> | |
public static Transform Find (Transform parentTrans, string name) | |
{ | |
if (parentTrans.name == name) | |
return parentTrans; | |
foreach (Transform child in parentTrans) | |
{ | |
Transform foundChild = Find (child, name); | |
if (foundChild != null) | |
return foundChild; | |
} | |
return null; | |
} | |
// BindPose Utility | |
/// <summary> | |
/// Creates a bindPose for the bone in it's current pose | |
/// RootBone has to be the parent bone in most cases: The space of the bindPose | |
/// </summary> | |
public static Matrix4x4 CreateBoneBindPose (Transform bone, Transform rootBone) | |
{ | |
return bone.worldToLocalMatrix * rootBone.localToWorldMatrix; | |
} | |
/// <summary> | |
/// Gets the local transformation matrix from the given bindPose | |
/// </summary> | |
public static Matrix4x4 GetLocalMatrix (Matrix4x4 bindPose, Matrix4x4 parentBindPose) | |
{ | |
return (bindPose * parentBindPose.inverse).inverse; | |
} | |
/// <summary> | |
/// Gets the local transformation matrix from the given bindPose | |
/// </summary> | |
public static Matrix4x4 GetLocalMatrix (Matrix4x4 bindPose) | |
{ | |
return bindPose.inverse; | |
} | |
/// <summary> | |
/// Restores the bone pose from the given bindPose | |
/// </summary> | |
public static void RestoreBonePose (ref Transform bone, Matrix4x4 bindPose, Matrix4x4 parentBindPose) | |
{ | |
RestorePosition (ref bone, GetLocalMatrix (bindPose, parentBindPose)); | |
} | |
/// <summary> | |
/// Restores the bone pose from the given bindPose | |
/// </summary> | |
public static void RestoreBonePose (ref Transform bone, Matrix4x4 bindPose) | |
{ | |
RestorePosition (ref bone, bindPose.inverse); | |
} | |
private static void RestorePosition (ref Transform objTrans, Matrix4x4 localTrans) | |
{ | |
objTrans.localPosition = localTrans.DecodePosition (); | |
objTrans.localRotation = localTrans.DecodeRotation (); | |
objTrans.localScale = localTrans.DecodeScale (); | |
} | |
/// <summary> | |
/// Decodes the position from the given transformation matrix | |
/// </summary> | |
public static Vector3 DecodePosition (this Matrix4x4 matrix) | |
{ | |
return matrix.MultiplyPoint (Vector3.zero); | |
} | |
/// <summary> | |
/// Decodes the rotation from the given transformation matrix | |
/// </summary> | |
public static Quaternion DecodeRotation (this Matrix4x4 matrix) | |
{ | |
return Quaternion.LookRotation (matrix.GetColumn (2), matrix.GetColumn (1)); | |
} | |
/// <summary> | |
/// Decodes the scale from the given transformation matrix | |
/// </summary> | |
public static Vector3 DecodeScale (this Matrix4x4 matrix) | |
{ | |
return new Vector3 (matrix.GetColumn (0).magnitude, matrix.GetColumn (1).magnitude, matrix.GetColumn (2).magnitude); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using UnityEditor; | |
using System.IO; | |
using System.Collections.Generic; | |
using System.Linq; | |
[CustomEditor(typeof(PoseManager))] | |
public class PoseManagerEditor : Editor | |
{ | |
PoseManager poseManager; | |
public void OnEnable () | |
{ | |
poseManager = (PoseManager)target; | |
} | |
public override void OnInspectorGUI () | |
{ | |
if (poseManager.skinnedRenderer == null) | |
poseManager.skinnedRenderer = poseManager.GetComponentInChildren<SkinnedMeshRenderer> (); | |
poseManager.skinnedRenderer = EditorGUILayout.ObjectField ("Skinned Mesh", poseManager.skinnedRenderer, typeof(SkinnedMeshRenderer), true) as SkinnedMeshRenderer; | |
if (poseManager.skinnedRenderer == null || poseManager.skinnedRenderer.sharedMesh == null) | |
return; | |
EditorGUILayout.Space (); | |
// Display associated Poses | |
if (poseManager.poses.Count > 0) | |
{ | |
for (int poseCnts = 0; poseCnts < poseManager.poses.Count; poseCnts++) | |
{ | |
RigPose pose = poseManager.poses[poseCnts]; | |
if (pose == null) | |
{ | |
poseManager.poses.RemoveAt (poseCnts); | |
poseCnts--; | |
continue; | |
} | |
GUILayout.BeginHorizontal (); | |
GUILayout.Label (pose.name, new GUILayoutOption[] { GUILayout.MinWidth (100), GUILayout.MaxWidth (Screen.width/2) }); | |
if (GUILayout.Button ("Set Pose", GUILayout.ExpandWidth (true))) | |
poseManager.SetPose (pose); | |
GUILayout.FlexibleSpace (); | |
if (GUILayout.Button ("^", GUILayout.Width (20)) && poseCnts > 0) | |
{ | |
poseManager.poses.RemoveAt (poseCnts); | |
poseManager.poses.Insert (poseCnts-1, pose); | |
} | |
if (GUILayout.Button ("v", GUILayout.Width (20)) && poseCnts < poseManager.poses.Count-1) | |
{ | |
poseManager.poses.RemoveAt (poseCnts); | |
poseManager.poses.Insert (poseCnts+1, pose); | |
} | |
GUILayout.FlexibleSpace (); | |
if (GUILayout.Button ("X", GUILayout.Width (20))) | |
{ | |
poseManager.poses.RemoveAt (poseCnts); | |
poseCnts--; | |
} | |
GUILayout.EndHorizontal (); | |
} | |
} | |
else | |
GUILayout.Label ("No Poses for this mesh!"); | |
EditorGUILayout.Space (); | |
// Manage Poses | |
if (GUILayout.Button ("Load Pose")) | |
{ | |
string path = EditorUtility.OpenFilePanel ("Load Pose", Application.dataPath, "asset"); | |
if (!string.IsNullOrEmpty (path)) | |
{ | |
path = path.Replace (Application.dataPath, "Assets"); | |
RigPose pose = AssetDatabase.LoadAssetAtPath<RigPose> (path); | |
if (pose != null) | |
poseManager.LoadPose (pose); | |
else | |
Debug.Log ("Chosen asset is not a valid RigPose!"); | |
} | |
} | |
if (GUILayout.Button ("Save Current Pose")) | |
{ | |
string savePath = EditorUtility.SaveFilePanelInProject ("Save Current Pose", "Pose", "asset", "Choose path to save the current pose in!"); | |
if (!string.IsNullOrEmpty (savePath)) | |
{ | |
string poseName = Path.GetFileNameWithoutExtension (savePath); | |
RigPose curPose = poseManager.SaveCurrentPose (poseName); | |
AssetDatabase.CreateAsset (curPose, savePath); | |
} | |
} | |
if (GUILayout.Button ("Restore Rest Pose")) | |
{ | |
poseManager.RestoreRestPose (); | |
} | |
if (GUILayout.Button ("Fix Mesh")) | |
{ | |
poseManager.TryFixMesh (); | |
} | |
} | |
/// <summary> | |
/// Adds the item to the array | |
/// </summary> | |
public static T[] AddArrayItem<T> (T[] array, T item) | |
{ | |
System.Array.Resize<T> (ref array, array.Length+1); | |
array [array.Length-1] = item; | |
return array; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using System; | |
using System.Linq; | |
using System.Collections.Generic; | |
[Serializable] | |
public class RigPose : ScriptableObject | |
{ | |
public List<PoseTransform> bonePoses; | |
public static RigPose Create (string poseName, List<Transform> bones, Matrix4x4[] bindPoses) | |
{ | |
if (bones.Count != bindPoses.Length) | |
throw new UnityException ("Cannot create Pose as bone- (" + bones.Count + ") and bindPose count (" + bindPoses.Length + ") do not match!"); | |
RigPose rigPose = RigPose.CreateInstance<RigPose> (); | |
rigPose.name = poseName; | |
rigPose.bonePoses = new List<PoseTransform> (bones.Count); | |
for (int boneCnt = 0; boneCnt < bones.Count; boneCnt++) | |
{ | |
Transform bone = bones[boneCnt]; | |
Matrix4x4 localTrans; | |
if (bone.parent != null) | |
{ | |
int parentBoneIndex = bones.FindIndex ((Transform b) => b.name == bone.parent.name); | |
if (parentBoneIndex >= 0) | |
localTrans = BoneManager.GetLocalMatrix (bindPoses[boneCnt], bindPoses[parentBoneIndex]); | |
else | |
localTrans = bindPoses[boneCnt].inverse; | |
} | |
else | |
localTrans = bindPoses[boneCnt].inverse; | |
rigPose.bonePoses.Add (new PoseTransform (bones[boneCnt].name, localTrans)); | |
} | |
return rigPose; | |
} | |
public static RigPose Create (string poseName, Transform[] bones) | |
{ | |
RigPose rigPose = RigPose.CreateInstance<RigPose> (); | |
rigPose.name = poseName; | |
rigPose.bonePoses = new List<PoseTransform> (bones.Length); | |
for (int boneCnt = 0; boneCnt < bones.Length; boneCnt++) | |
rigPose.bonePoses.Add (new PoseTransform (bones[boneCnt])); | |
return rigPose; | |
} | |
} | |
[Serializable] | |
public struct PoseTransform | |
{ | |
public string name; | |
public Vector3 position; | |
public Quaternion rotation; | |
public Vector3 scale; | |
public PoseTransform (Transform trans) | |
{ | |
name = trans.name; | |
position = trans.localPosition; | |
rotation = trans.localRotation; | |
scale = trans.localScale; | |
} | |
public PoseTransform (string poseName, Matrix4x4 localTrans) | |
{ | |
name = poseName; | |
position = BoneManager.DecodePosition (localTrans); | |
rotation = BoneManager.DecodeRotation (localTrans); | |
scale = BoneManager.DecodeScale (localTrans); | |
} | |
public PoseTransform (string poseName, Vector3 localPos, Quaternion localRot, Vector3 localScale) | |
{ | |
name = poseName; | |
position = localPos; | |
rotation = localRot; | |
scale = localScale; | |
} | |
public void SetTransform (ref Transform trans) | |
{ | |
trans.localPosition = position; | |
trans.localRotation = rotation; | |
trans.localScale = scale; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using System; | |
using System.Linq; | |
using System.Collections.Generic; | |
[ExecuteInEditMode] | |
public class PoseManager : MonoBehaviour | |
{ | |
public SkinnedMeshRenderer skinnedRenderer; | |
public List<RigPose> poses = new List<RigPose> (); | |
public void OnEnable () | |
{ | |
if (skinnedRenderer == null) | |
skinnedRenderer = GetComponentInChildren<SkinnedMeshRenderer> (); | |
} | |
public void AssureSetup () | |
{ | |
if (skinnedRenderer == null) | |
skinnedRenderer = GetComponentInChildren<SkinnedMeshRenderer> (); | |
if (skinnedRenderer == null) | |
throw new UnityException ("Please select a SkinnedMeshRenderer!"); | |
} | |
public void TryFixMesh () | |
{ | |
if (skinnedRenderer.bones.Length != skinnedRenderer.sharedMesh.bindposes.Length) | |
{ | |
Transform[] newBones = new Transform[skinnedRenderer.sharedMesh.bindposes.Length]; | |
Array.Copy (skinnedRenderer.bones, newBones, skinnedRenderer.sharedMesh.bindposes.Length); | |
skinnedRenderer.bones = newBones; | |
} | |
} | |
public void RestoreRestPose () | |
{ | |
RigPose restPose = RigPose.Create ("Rest Pose", skinnedRenderer.bones.ToList (), skinnedRenderer.sharedMesh.bindposes); | |
SetPose (restPose); | |
} | |
public RigPose SaveCurrentPose (string name) | |
{ | |
AssureSetup (); | |
RigPose curPose = RigPose.Create (name, skinnedRenderer.bones); | |
poses.Add (curPose); | |
return curPose; | |
} | |
public void LoadPose (RigPose pose) | |
{ | |
AssureSetup (); | |
if (pose.bonePoses.Count == skinnedRenderer.bones.Length) | |
poses.Add (pose); | |
else | |
Debug.LogError ("Cannot load pose as bone count does not match: Mesh: " + skinnedRenderer.bones.Length + "; Pose: " + pose.bonePoses.Count); | |
} | |
public void SetPose (RigPose pose) | |
{ | |
if (pose == null) | |
throw new UnityException ("Trying to set null pose to PoseManager " + name + "!"); | |
AssureSetup (); | |
foreach (PoseTransform bonePose in pose.bonePoses) | |
{ | |
Transform poseBone = BoneManager.Find (skinnedRenderer.rootBone, bonePose.name); | |
if (poseBone != null) | |
{ | |
#if UNITY_EDITOR | |
UnityEditor.Undo.RecordObject (poseBone, "Set Pose " + pose.name + " on " + skinnedRenderer.name); | |
#endif | |
bonePose.SetTransform (ref poseBone); | |
} | |
else | |
Debug.LogError ("Pose bone " + bonePose.name + " has no valid associated bone!"); | |
} | |
#if UNITY_EDITOR | |
UnityEditor.Undo.FlushUndoRecordObjects (); | |
#endif | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment