Skip to content

Instantly share code, notes, and snippets.

@Seneral
Last active November 3, 2022 07:01
Show Gist options
  • Save Seneral/2e348684570b89ed50030adbd402eb0a to your computer and use it in GitHub Desktop.
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.
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);
}
}
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;
}
}
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;
}
}
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