using System;
using System.Collections.Generic;
using UnityEngine;
using Utilities;
namespace SMPLModel {
///
/// Poses the body and adjust pose-dependent blendshapes accordingly.
///
public class CharacterPoser : MonoBehaviour {
SMPLCharacter smplCharacter;
SkinnedMeshRenderer skinnedMeshRenderer;
Transform[] bones;
ModelDefinition model;
[SerializeField] Quaternion[] poses;
void Awake() {
smplCharacter = GetComponent();
if (smplCharacter == null) throw new NullReferenceException("Can't find SMPLCharacter component");
skinnedMeshRenderer = GetComponentInChildren();
if (skinnedMeshRenderer == null) throw new NullReferenceException("Can't find SkinnedMeshRenderer component");
model = smplCharacter.Model;
bones = skinnedMeshRenderer.bones;
poses = new Quaternion[model.JointCount];
}
void OnDestroy() {
ResetToTPose();
}
void Update() {
if (smplCharacter == null) return;
if (smplCharacter.RenderSettings.UpdateBodyShapeLive) {
ResetToTPose();
smplCharacter.Body.UpdateBody();
}
if (smplCharacter.RenderSettings.AllowPoseManipulation) {
poses = GatherPosesFromBones();
UpdatePoses();
}
else if (smplCharacter.RenderSettings.UpdatePosesLive) UpdatePoses();
else ResetPoses();
if (smplCharacter.RenderSettings.UpdatePoseBlendshapesLive) AddPoseDependentBlendShapes(poses);
else ResetPoseDependentBlendShapesToZero();
}
Quaternion[] GatherPosesFromBones() {
Quaternion[] currentPoses = new Quaternion[model.JointCount];
foreach (Transform bone in skinnedMeshRenderer.bones) {
int poseIndex = Bones.NameToJointIndex[bone.name];
currentPoses[poseIndex] = bone.localRotation;
}
return currentPoses;
}
public void SetPoses(Quaternion[] newPoses) {
poses = newPoses;
}
///
/// Updates the bones based on new newPoses and translation of pelvis
///
///
void UpdatePoses() {
for (int boneIndex = 0; boneIndex < bones.Length; boneIndex++) {
string boneName = bones[boneIndex].name;
try {
//to deal with pelvis's rotation, it's rotated by -90 degrees on x axis because maya
//uses z=up rather than unity's y=up. So to deal with that we have to add this correction.
//But since the order matters for multiple rotations, this complicates things,
//meaning we have to reset the rotation to zero first, then apply them additively in a particular order.
bones[boneIndex].transform.localEulerAngles = Vector3.zero;
if (boneName == Bones.Pelvis) {
bones[boneIndex].transform.Rotate(-90, 0, 0, Space.Self);
}
int poseIndex = Bones.NameToJointIndex[boneName];
bones[boneIndex].localRotation = bones[boneIndex].localRotation * poses[poseIndex];
}
catch (KeyNotFoundException) {
throw new KeyNotFoundException($"Bone Not in dictionary: boneIndex: {boneIndex}, animationName: {boneName}");
}
}
}
[ContextMenu("ResetToTPose")]
public void ResetToTPose() {
ResetPoses();
ResetPoseDependentBlendShapesToZero();
}
void ResetPoses() {
foreach (Transform bone in bones) {
bone.localRotation = Quaternion.identity;
}
}
///
/// Updates all pose-dependent blendshapes this frame.
///
/// Pelvis has no blend shapes. So need to skip it when iterating through joints.
/// But then to index blendshapes need to subtract that 1 back to get blendshape index.
///
void AddPoseDependentBlendShapes(Quaternion[] posesToMatch) {
int startingJoint = model.FirstPoseIsPelvisTranslation ? 1 : 0;
for (int jointIndex = startingJoint; jointIndex < model.JointCount; jointIndex++) {
Quaternion jointPose = posesToMatch[jointIndex];
//convert to from Unity back to MPI's right-handed coords.
jointPose = jointPose.ToRightHanded();
float[] rotationMatrix = jointPose.To3X3Matrix();
rotationMatrix = MatrixUtilities.SubtractIdentity(rotationMatrix);
//rotationMatrix = MatrixUtilities.RotationMatrix3x3ToRightHanded(rotationMatrix);
for (int rotMatrixElement = 0; rotMatrixElement < SMPLConstants.RotationMatrixElementCount; rotMatrixElement++) {
float rawPoseDependentBlendshapeWeight = rotationMatrix[rotMatrixElement];
float scaledWeightBeta = ScalePoseBlendshapesFromBlenderToUnity(rawPoseDependentBlendshapeWeight);
//if (smplCharacter.Gender == Gender.Female && model.FemaleNegativeBlendshapes) scaledWeightBeta = -scaledWeightBeta;
//if (Mathf.Abs(scaledWeightBeta) < BlendShapeThreshold) continue;
int jointIndexNoPelvis = model.FirstPoseIsPelvisTranslation ? jointIndex - 1 : jointIndex; // no blendshapes for pelvis.
int blendShapeIndex = model.BodyShapeBetaCount + jointIndexNoPelvis * SMPLConstants.RotationMatrixElementCount + rotMatrixElement;
skinnedMeshRenderer.SetBlendShapeWeight(blendShapeIndex, scaledWeightBeta);
}
}
}
[ContextMenu("ResetPoseBlendshapes")]
public void ResetPoseDependentBlendShapesToZero() {
int startingJoint = model.FirstPoseIsPelvisTranslation ? 1 : 0;
for (int jointIndex = startingJoint; jointIndex < model.JointCount; jointIndex++) {
for (int rotMatrixElement = 0; rotMatrixElement < SMPLConstants.RotationMatrixElementCount; rotMatrixElement++) {
float scaledWeightBeta = 0;
int jointIndexNoPelvis = model.FirstPoseIsPelvisTranslation ? jointIndex - 1 : jointIndex; // no blendshapes for pelvis.
int blendShapeIndex = model.BodyShapeBetaCount + jointIndexNoPelvis * SMPLConstants.RotationMatrixElementCount + rotMatrixElement;
skinnedMeshRenderer.SetBlendShapeWeight(blendShapeIndex, scaledWeightBeta);
}
}
}
float ScalePoseBlendshapesFromBlenderToUnity(float rawWeight) {
float scaledWeight = rawWeight * model.PoseBlendshapeScalingFactor * model.UnityBlendShapeScaleFactor;
return scaledWeight;
}
public void ForceUpdate() {
Update();
}
}
}