implement knee bend normal calculation

implemented knee bend normal calculation to fix knee bending at extreme scales, as well as finally fix robot kyle...

tweaked ikpose method to also allow for setting to motorcycle pose, as well as resetting hip to default rotation
This commit is contained in:
NotAKidoS 2023-03-06 19:53:27 -06:00
parent 7164e36392
commit c6c9b712bd
3 changed files with 209 additions and 70 deletions

View file

@ -40,7 +40,7 @@ public class DesktopVRIK : MonoBehaviour
public void OnSetupAvatarDesktop() public void OnSetupAvatarDesktop()
{ {
if (!Setting_Enabled) return; if (!Setting_Enabled) return;
Calibrator.SetupDesktopVRIK(); Calibrator.CalibrateDesktopVRIK();
ResetDesktopVRIK(); ResetDesktopVRIK();
} }
@ -51,7 +51,7 @@ public class DesktopVRIK : MonoBehaviour
Calibrator.vrik.solver.locomotion.footDistance = Calibrator.initialFootDistance * scaleDifference; Calibrator.vrik.solver.locomotion.footDistance = Calibrator.initialFootDistance * scaleDifference;
Calibrator.vrik.solver.locomotion.stepThreshold = Calibrator.initialStepThreshold * scaleDifference; Calibrator.vrik.solver.locomotion.stepThreshold = Calibrator.initialStepThreshold * scaleDifference;
DesktopVRIK.ScaleStepHeight(Calibrator.vrik.solver.locomotion.stepHeight, Calibrator.initialStepHeight * scaleDifference); DesktopVRIK.ScaleStepHeight(Calibrator.vrik.solver.locomotion.stepHeight, Calibrator.initialStepHeight * scaleDifference);
Calibrator.vrik.solver.Reset(); //Calibrator.vrik.solver.Reset();
ResetDesktopVRIK(); ResetDesktopVRIK();
return true; return true;

View file

@ -13,6 +13,42 @@ namespace NAK.Melons.DesktopVRIK;
public class DesktopVRIKCalibrator public class DesktopVRIKCalibrator
{ {
// Settings
public bool Setting_UseVRIKToes = true;
public bool Setting_FindUnmappedToes = true;
public bool Setting_ExperimentalKneeBend = true;
public bool Setting_DebugCalibrationPose = false;
// Avatar Component References
public CVRAvatar avatar;
public Animator animator;
public Transform avatarTransform;
public VRIK vrik;
public LookAtIK lookAtIK;
// Calibrated Values
public float
initialFootDistance,
initialStepThreshold,
initialStepHeight;
// Calibration Internals
bool DebugCalibrationPose;
bool fixTransformsRequired;
Vector3 leftKneeNormal, rightKneeNormal;
HumanPose initialHumanPose;
HumanPoseHandler humanPoseHandler;
// Traverse
IKSystem ikSystem;
PlayerSetup playerSetup;
Traverse
_vrikTraverse,
_lookIKTraverse,
_avatarTraverse,
_animatorManagerTraverse,
_poseHandlerTraverse,
_avatarRootHeightTraverse;
public DesktopVRIKCalibrator() public DesktopVRIKCalibrator()
{ {
// Get base game scripts. // Get base game scripts.
@ -30,67 +66,102 @@ public class DesktopVRIKCalibrator
_lookIKTraverse = Traverse.Create(playerSetup).Field("lookIK"); _lookIKTraverse = Traverse.Create(playerSetup).Field("lookIK");
} }
// Settings public void CalibrateDesktopVRIK()
public bool Setting_UseVRIKToes = true;
public bool Setting_FindUnmappedToes = true;
// DesktopVRIK
public CVRAvatar avatar;
public Animator animator;
public Transform avatarTransform;
public VRIK vrik;
public LookAtIK lookAtIK;
// Calibration
public HumanPoseHandler humanPoseHandler;
public HumanPose initialHumanPose;
// Calibrator
public bool fixTransformsRequired;
public float initialFootDistance, initialStepThreshold, initialStepHeight;
// Traverse
private IKSystem ikSystem;
private PlayerSetup playerSetup;
private Traverse
_vrikTraverse,
_lookIKTraverse,
_avatarTraverse,
_animatorManagerTraverse,
_poseHandlerTraverse,
_avatarRootHeightTraverse;
public void SetupDesktopVRIK()
{ {
//store avatar root transform & center it PreInitialize();
avatar = playerSetup._avatar.GetComponent<CVRAvatar>();
animator = avatar.GetComponent<Animator>();
avatarTransform = avatar.transform;
avatarTransform.localPosition = Vector3.zero;
lookAtIK = _lookIKTraverse.GetValue<LookAtIK>();
//prepare for VRIK // Don't do anything else if just debugging calibration pose
DebugCalibrationPose = !DebugCalibrationPose;
if (Setting_DebugCalibrationPose && DebugCalibrationPose)
{
ForceCalibrationPose();
return;
}
// Add VRIK and configure
PrepareAvatarVRIK();
Initialize();
PostInitialize();
}
public void ForceCalibrationPose(bool toggle = true)
{
animator.enabled = !toggle;
SetHumanPose(0f);
//SetAvatarIKPose(toggle);
}
private void PreInitialize()
{
// Scan avatar for issues/references
ScanAvatarForCalibration();
// Prepare CVR IKSystem for external VRIK
PrepareIKSystem(); PrepareIKSystem();
CalibrateDesktopVRIK(); }
//add presolver update listener private void Initialize()
{
// Calculate bend normals with motorcycle pose
SetHumanPose(0f);
CalculateKneeBendNormals();
SetAvatarIKPose(true);
// Setup HeadIK target & calculate initial footstep values
SetupDesktopHeadIKTarget();
CalculateInitialIKScaling();
// Initiate VRIK manually
ForceInitiateVRIKSolver();
SetAvatarIKPose(false);
}
private void PostInitialize()
{
ApplyKneeBendNormals();
ApplyInitialIKScaling();
vrik.onPreSolverUpdate.AddListener(new UnityAction(DesktopVRIK.Instance.OnPreSolverUpdate)); vrik.onPreSolverUpdate.AddListener(new UnityAction(DesktopVRIK.Instance.OnPreSolverUpdate));
} }
private void CalibrateDesktopVRIK() private void ScanAvatarForCalibration()
{ {
//calibrate VRIK // Reset some stuff to default
PrepareAvatarVRIK(); fixTransformsRequired = false;
SetAvatarIKPose(true);
CalibrateHeadIK(); // Find required avatar components
ForceInitiateVRIKSolver(); avatar = playerSetup._avatar.GetComponent<CVRAvatar>();
CalculateInitialIKScaling(); animator = avatar.GetComponent<Animator>();
SetAvatarIKPose(false); avatarTransform = avatar.transform;
lookAtIK = _lookIKTraverse.GetValue<LookAtIK>();
// Apply some fixes for weird setups
if (!animator.enabled)
{
fixTransformsRequired = true;
DesktopVRIKMod.Logger.Error("Avatar has Animator disabled by default!");
}
// Center avatar local offsets
avatarTransform.localPosition = Vector3.zero;
//avatarTransform.localRotation = Quaternion.identity;
// Store original human pose
if (humanPoseHandler != null)
{
humanPoseHandler.Dispose();
}
humanPoseHandler = new HumanPoseHandler(animator.avatar, avatarTransform);
humanPoseHandler.GetHumanPose(ref initialHumanPose);
} }
private void PrepareIKSystem() private void PrepareIKSystem()
{ {
// Get the animator manager and human pose handler // Get the animator manager and human pose handler
var animatorManager = _animatorManagerTraverse.GetValue<CVRAnimatorManager>(); var animatorManager = _animatorManagerTraverse.GetValue<CVRAnimatorManager>();
humanPoseHandler = _poseHandlerTraverse.GetValue<HumanPoseHandler>(); var ikHumanPoseHandler = _poseHandlerTraverse.GetValue<HumanPoseHandler>();
// Store the avatar component // Store the avatar component
_avatarTraverse.SetValue(avatar); _avatarTraverse.SetValue(avatar);
@ -107,13 +178,13 @@ public class DesktopVRIKCalibrator
_avatarRootHeightTraverse.SetValue(avatarHeight); _avatarRootHeightTraverse.SetValue(avatarHeight);
// Create a new human pose handler and dispose the old one // Create a new human pose handler and dispose the old one
if (humanPoseHandler != null) if (ikHumanPoseHandler != null)
{ {
humanPoseHandler.Dispose(); ikHumanPoseHandler.Dispose();
_poseHandlerTraverse.SetValue(null); _poseHandlerTraverse.SetValue(null);
} }
humanPoseHandler = new HumanPoseHandler(ikSystem.animator.avatar, avatarTransform); ikHumanPoseHandler = new HumanPoseHandler(ikSystem.animator.avatar, avatarTransform);
_poseHandlerTraverse.SetValue(humanPoseHandler); _poseHandlerTraverse.SetValue(ikHumanPoseHandler);
// Find valid human bones // Find valid human bones
IKSystem.BoneExists.Clear(); IKSystem.BoneExists.Clear();
@ -160,14 +231,12 @@ public class DesktopVRIKCalibrator
vrik.solver.spine.bodyPosStiffness = 1f; vrik.solver.spine.bodyPosStiffness = 1f;
vrik.solver.spine.bodyRotStiffness = 0.2f; vrik.solver.spine.bodyRotStiffness = 0.2f;
//disable so avatar doesnt try and walk away //disable so avatar doesnt try and walk away
//fixes nameplate spazzing on remote
vrik.solver.locomotion.velocityFactor = 0f; vrik.solver.locomotion.velocityFactor = 0f;
vrik.solver.locomotion.maxVelocity = 0f; vrik.solver.locomotion.maxVelocity = 0f;
//fixes nameplate spazzing on remote & magicacloth
vrik.solver.locomotion.rootSpeed = 1000f;
//disable so PAM & BID dont make body shake //disable so PAM & BID dont make body shake
vrik.solver.spine.rotateChestByHands = 0f; vrik.solver.spine.rotateChestByHands = 0f;
//enable so knees on fucked models work better
vrik.solver.leftLeg.useAnimatedBendNormal = true;
vrik.solver.rightLeg.useAnimatedBendNormal = true;
//enable to prioritize LookAtIK //enable to prioritize LookAtIK
vrik.solver.spine.headClampWeight = 0.2f; vrik.solver.spine.headClampWeight = 0.2f;
//disable to not go on tippytoes //disable to not go on tippytoes
@ -206,6 +275,70 @@ public class DesktopVRIKCalibrator
//vrik.solver.spine.chestGoalWeight = 0f; //vrik.solver.spine.chestGoalWeight = 0f;
} }
private void CalculateKneeBendNormals()
{
// Get assumed left knee normal
Vector3[] leftVectors = new Vector3[]
{
vrik.references.leftThigh?.position ?? Vector3.zero,
vrik.references.leftCalf?.position ?? Vector3.zero,
vrik.references.leftFoot?.position ?? Vector3.zero,
};
leftKneeNormal = Quaternion.Inverse(vrik.references.root.rotation) * GetNormalFromArray(leftVectors);
// Get assumed right knee normal
Vector3[] rightVectors = new Vector3[]
{
vrik.references.rightThigh?.position ?? Vector3.zero,
vrik.references.rightCalf?.position ?? Vector3.zero,
vrik.references.rightFoot?.position ?? Vector3.zero,
};
rightKneeNormal = Quaternion.Inverse(vrik.references.root.rotation) * GetNormalFromArray(rightVectors);
}
private void ApplyKneeBendNormals()
{
if (!Setting_ExperimentalKneeBend)
{
//enable so knees on fucked models work better
vrik.solver.leftLeg.useAnimatedBendNormal = true;
vrik.solver.rightLeg.useAnimatedBendNormal = true;
return;
}
vrik.solver.leftLeg.bendToTargetWeight = 0f;
vrik.solver.rightLeg.bendToTargetWeight = 0f;
Traverse leftLeg_bendNormalRelToPelvisTraverse = Traverse.Create(vrik.solver.leftLeg).Field("bendNormalRelToPelvis");
Traverse rightLeg_bendNormalRelToPelvisTraverse = Traverse.Create(vrik.solver.rightLeg).Field("bendNormalRelToPelvis");
// Calculate knee normal without root rotation but with pelvis rotation
Quaternion pelvisLocalRotationInverse = Quaternion.Inverse(vrik.references.pelvis.localRotation);
Vector3 leftLegBendNormalRelToPelvis = pelvisLocalRotationInverse * leftKneeNormal;
Vector3 rightLegBendNormalRelToPelvis = pelvisLocalRotationInverse * rightKneeNormal;
//Quaternion rootRotation = vrik.references.root.rotation;
//Quaternion pelvisRotationRelativeToRoot = Quaternion.Inverse(rootRotation) * vrik.references.pelvis.rotation;
//Quaternion pelvisRotationInverse = Quaternion.Inverse(pelvisRotationRelativeToRoot);
leftLeg_bendNormalRelToPelvisTraverse.SetValue(leftLegBendNormalRelToPelvis);
rightLeg_bendNormalRelToPelvisTraverse.SetValue(rightLegBendNormalRelToPelvis);
}
private Vector3 GetNormalFromArray(Vector3[] positions)
{
Vector3 vector = Vector3.zero;
Vector3 vector2 = Vector3.zero;
for (int i = 0; i < positions.Length; i++)
{
vector2 += positions[i];
}
vector2 /= (float)positions.Length;
for (int j = 0; j < positions.Length - 1; j++)
{
vector += Vector3.Cross(positions[j] - vector2, positions[j + 1] - vector2).normalized;
}
return Vector3.Normalize(vector);
}
private void CalculateInitialIKScaling() private void CalculateInitialIKScaling()
{ {
// Get distance between feets and thighs // Get distance between feets and thighs
@ -213,7 +346,10 @@ public class DesktopVRIKCalibrator
initialFootDistance = footDistance * 0.5f; initialFootDistance = footDistance * 0.5f;
initialStepThreshold = footDistance * 0.4f; initialStepThreshold = footDistance * 0.4f;
initialStepHeight = Vector3.Distance(vrik.references.leftFoot.position, vrik.references.leftCalf.position) * 0.2f; initialStepHeight = Vector3.Distance(vrik.references.leftFoot.position, vrik.references.leftCalf.position) * 0.2f;
}
private void ApplyInitialIKScaling()
{
// Set initial values // Set initial values
vrik.solver.locomotion.footDistance = initialFootDistance; vrik.solver.locomotion.footDistance = initialFootDistance;
vrik.solver.locomotion.stepThreshold = initialStepThreshold; vrik.solver.locomotion.stepThreshold = initialStepThreshold;
@ -221,7 +357,7 @@ public class DesktopVRIKCalibrator
} }
private void CalibrateHeadIK() private void SetupDesktopHeadIKTarget()
{ {
// Lazy HeadIKTarget calibration // Lazy HeadIKTarget calibration
if (vrik.solver.spine.headTarget == null) if (vrik.solver.spine.headTarget == null)
@ -250,8 +386,7 @@ public class DesktopVRIKCalibrator
// Otherwise use DesktopVRIK IKPose & revert afterwards. // Otherwise use DesktopVRIK IKPose & revert afterwards.
if (enforceTPose) if (enforceTPose)
{ {
humanPoseHandler.GetHumanPose(ref initialHumanPose); SetHumanPose(1f);
SetCustomPose(IKPoseMuscles);
} }
else else
{ {
@ -259,13 +394,15 @@ public class DesktopVRIKCalibrator
} }
} }
private void SetCustomPose(float[] muscleValues) private void SetHumanPose(float ikPoseWeight = 1f)
{ {
humanPoseHandler.GetHumanPose(ref ikSystem.humanPose); humanPoseHandler.GetHumanPose(ref ikSystem.humanPose);
for (int i = 0; i < muscleValues.Length; i++) for (int i = 0; i < ikSystem.humanPose.muscles.Length; i++)
{ {
IKSystem.Instance.ApplyMuscleValue((MuscleIndex)i, muscleValues[i], ref ikSystem.humanPose.muscles); float weight = ikPoseWeight * IKPoseMuscles[i];
IKSystem.Instance.ApplyMuscleValue((MuscleIndex)i, weight, ref ikSystem.humanPose.muscles);
} }
ikSystem.humanPose.bodyRotation = Quaternion.identity;
humanPoseHandler.SetHumanPose(ref ikSystem.humanPose); humanPoseHandler.SetHumanPose(ref ikSystem.humanPose);
} }
@ -278,8 +415,6 @@ public class DesktopVRIKCalibrator
private void ConfigureVRIKReferences() private void ConfigureVRIKReferences()
{ {
fixTransformsRequired = false;
//might not work over netik //might not work over netik
FixChestAndSpineReferences(); FixChestAndSpineReferences();
@ -459,4 +594,3 @@ public class DesktopVRIKCalibrator
0.8110138f 0.8110138f
}; };
} }

View file

@ -5,6 +5,7 @@ namespace NAK.Melons.DesktopVRIK;
public class DesktopVRIKMod : MelonMod public class DesktopVRIKMod : MelonMod
{ {
internal static MelonLogger.Instance Logger;
internal const string SettingsCategory = "DesktopVRIK"; internal const string SettingsCategory = "DesktopVRIK";
internal static MelonPreferences_Category m_categoryDesktopVRIK; internal static MelonPreferences_Category m_categoryDesktopVRIK;
internal static MelonPreferences_Entry<bool> internal static MelonPreferences_Entry<bool>
@ -13,7 +14,8 @@ public class DesktopVRIKMod : MelonMod
m_entryResetIKOnLand, m_entryResetIKOnLand,
m_entryPlantFeet, m_entryPlantFeet,
m_entryUseVRIKToes, m_entryUseVRIKToes,
m_entryFindUnmappedToes; m_entryFindUnmappedToes,
m_entryExperimentalKneeBend;
internal static MelonPreferences_Entry<float> internal static MelonPreferences_Entry<float>
m_entryBodyLeanWeight, m_entryBodyLeanWeight,
m_entryBodyHeadingLimit, m_entryBodyHeadingLimit,
@ -21,12 +23,14 @@ public class DesktopVRIKMod : MelonMod
m_entryChestHeadingWeight; m_entryChestHeadingWeight;
public override void OnInitializeMelon() public override void OnInitializeMelon()
{ {
Logger = LoggerInstance;
m_categoryDesktopVRIK = MelonPreferences.CreateCategory(SettingsCategory); m_categoryDesktopVRIK = MelonPreferences.CreateCategory(SettingsCategory);
m_entryEnabled = m_categoryDesktopVRIK.CreateEntry<bool>("Enabled", true, description: "Toggle DesktopVRIK entirely. Requires avatar reload."); m_entryEnabled = m_categoryDesktopVRIK.CreateEntry<bool>("Enabled", true, description: "Toggle DesktopVRIK entirely. Requires avatar reload.");
//m_entryEnforceViewPosition = m_categoryDesktopVRIK.CreateEntry<bool>("Enforce View Position", false, description: "Corrects view position to use VRIK offsets."); //m_entryEnforceViewPosition = m_categoryDesktopVRIK.CreateEntry<bool>("Enforce View Position", false, description: "Corrects view position to use VRIK offsets.");
m_entryPlantFeet = m_categoryDesktopVRIK.CreateEntry<bool>("Enforce Plant Feet", true, description: "Forces VRIK Plant Feet enabled. This prevents the little hover when you stop moving."); m_entryPlantFeet = m_categoryDesktopVRIK.CreateEntry<bool>("Enforce Plant Feet", true, description: "Forces VRIK Plant Feet enabled. This prevents the little hover when you stop moving.");
m_entryUseVRIKToes = m_categoryDesktopVRIK.CreateEntry<bool>("Use VRIK Toes", false, description: "Should VRIK use your humanoid toes for IK solving? This can cause your feet to idle behind you."); m_entryUseVRIKToes = m_categoryDesktopVRIK.CreateEntry<bool>("Use VRIK Toes", false, description: "Should VRIK use your humanoid toes for IK solving? This can cause your feet to idle behind you.");
m_entryFindUnmappedToes = m_categoryDesktopVRIK.CreateEntry<bool>("Find Unmapped Toes", false, description: "Should DesktopVRIK look for unmapped toe bones if humanoid rig does not have any?"); m_entryFindUnmappedToes = m_categoryDesktopVRIK.CreateEntry<bool>("Find Unmapped Toes", false, description: "Should DesktopVRIK look for unmapped toe bones if humanoid rig does not have any?");
m_entryExperimentalKneeBend = m_categoryDesktopVRIK.CreateEntry<bool>("Experimental Knee Bend", true, description: "Experimental method to calculate knee bend normal. This may break avatars.");
m_entryBodyLeanWeight = m_categoryDesktopVRIK.CreateEntry<float>("Body Lean Weight", 0.5f, description: "Emulates old VRChat-like body leaning when looking up/down. Set to 0 to disable."); m_entryBodyLeanWeight = m_categoryDesktopVRIK.CreateEntry<float>("Body Lean Weight", 0.5f, description: "Emulates old VRChat-like body leaning when looking up/down. Set to 0 to disable.");
m_entryBodyHeadingLimit = m_categoryDesktopVRIK.CreateEntry<float>("Body Heading Limit", 20f, description: "Emulates VRChat-like body and head offset when rotating left/right. Set to 0 to disable."); m_entryBodyHeadingLimit = m_categoryDesktopVRIK.CreateEntry<float>("Body Heading Limit", 20f, description: "Emulates VRChat-like body and head offset when rotating left/right. Set to 0 to disable.");
@ -59,6 +63,7 @@ public class DesktopVRIKMod : MelonMod
// Calibration Settings // Calibration Settings
DesktopVRIK.Instance.Calibrator.Setting_UseVRIKToes = m_entryUseVRIKToes.Value; DesktopVRIK.Instance.Calibrator.Setting_UseVRIKToes = m_entryUseVRIKToes.Value;
DesktopVRIK.Instance.Calibrator.Setting_FindUnmappedToes = m_entryFindUnmappedToes.Value; DesktopVRIK.Instance.Calibrator.Setting_FindUnmappedToes = m_entryFindUnmappedToes.Value;
DesktopVRIK.Instance.Calibrator.Setting_ExperimentalKneeBend = m_entryExperimentalKneeBend.Value;
} }
private void OnUpdateSettings(object arg1, object arg2) => UpdateAllSettings(); private void OnUpdateSettings(object arg1, object arg2) => UpdateAllSettings();
@ -80,8 +85,8 @@ public class DesktopVRIKMod : MelonMod
} }
catch (Exception e) catch (Exception e)
{ {
LoggerInstance.Msg($"Failed while patching {type.Name}!"); Logger.Msg($"Failed while patching {type.Name}!");
LoggerInstance.Error(e); Logger.Error(e);
} }
} }
} }