Merge pull request #2 from NotAKidOnSteam/rewrite

Rewrite
This commit is contained in:
NotAKidoS 2023-03-07 15:08:33 -06:00 committed by GitHub
commit 7feb806a7a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 776 additions and 528 deletions

View file

@ -1,169 +1,145 @@
using ABI.CCK.Components; using ABI_RC.Core.Player;
using ABI_RC.Core.Player;
using ABI_RC.Systems.IK;
using ABI_RC.Systems.IK.SubSystems; using ABI_RC.Systems.IK.SubSystems;
using ABI_RC.Systems.MovementSystem; using ABI_RC.Systems.MovementSystem;
using RootMotion.FinalIK; using RootMotion.FinalIK;
using System.Reflection;
using UnityEngine; using UnityEngine;
using UnityEngine.Events;
namespace NAK.Melons.DesktopVRIK; namespace NAK.Melons.DesktopVRIK;
public class DesktopVRIK : MonoBehaviour public class DesktopVRIK : MonoBehaviour
{ {
public static DesktopVRIK Instance; public static DesktopVRIK Instance;
public DesktopVRIKCalibrator Calibrator;
public static bool // DesktopVRIK Settings
Setting_Enabled, public bool
Setting_EnforceViewPosition, Setting_Enabled = true,
Setting_EmoteVRIK, Setting_PlantFeet = true;
Setting_EmoteLookAtIK; public float
Setting_BodyLeanWeight,
Setting_BodyHeadingLimit,
Setting_PelvisHeadingWeight,
Setting_ChestHeadingWeight;
public static float // Internal Stuff
Setting_BodyLeanWeight = 0.5f, bool ps_emoteIsPlaying;
Setting_BodyAngleLimit = 0f; float ik_SimulatedRootAngle;
Transform desktopCameraTransform;
public Transform viewpoint; static readonly FieldInfo ms_isGrounded = typeof(MovementSystem).GetField("_isGrounded", BindingFlags.NonPublic | BindingFlags.Instance);
public Vector3 eyeOffset; bool forceSteps;
bool forceStepsNow;
void Start() void Start()
{ {
desktopCameraTransform = PlayerSetup.Instance.desktopCamera.transform;
Calibrator = new DesktopVRIKCalibrator();
Instance = this; Instance = this;
DesktopVRIKMod.UpdateAllSettings();
} }
public void ChangeViewpointHandling(bool enabled) public void OnSetupAvatarDesktop()
{ {
if (Setting_EnforceViewPosition == enabled) return; if (!Setting_Enabled) return;
Setting_EnforceViewPosition = enabled; Calibrator.CalibrateDesktopVRIK();
if (enabled) ResetDesktopVRIK();
{
PlayerSetup.Instance.desktopCamera.transform.localPosition = Vector3.zero;
return;
}
PlayerSetup.Instance.desktopCamera.transform.localPosition = eyeOffset;
} }
public void AlternativeOnPreSolverUpdate() public bool OnSetupIKScaling(float scaleDifference)
{ {
//this order matters, rotation offset will be choppy if avatar is not cenetered first if (Calibrator.vrik == null) return false;
DesktopVRIK_Helper.Instance?.OnUpdateVRIK(); VRIKUtils.ApplyScaleToVRIK
(
Calibrator.vrik,
Calibrator.initialFootDistance,
Calibrator.initialStepThreshold,
Calibrator.initialStepHeight,
scaleDifference
);
//Reset avatar offset (VRIK will literally make you walk away from root otherwise) ResetDesktopVRIK();
IKSystem.vrik.transform.localPosition = Vector3.zero; return true;
IKSystem.vrik.transform.localRotation = Quaternion.identity;
IKSystem.vrik.solver.plantFeet = true;
} }
public Animator animator; public void OnPlayerSetupUpdate(bool isEmotePlaying)
public VRIK AlternativeCalibration(CVRAvatar avatar)
{ {
animator = avatar.GetComponent<Animator>(); bool changed = isEmotePlaying != ps_emoteIsPlaying;
Transform avatarHeadBone = animator.GetBoneTransform(HumanBodyBones.Head); if (!changed) return;
//Stuff to make bad armatures work (Fuck you Default Robot Kyle) ps_emoteIsPlaying = isEmotePlaying;
avatar.transform.localPosition = Vector3.zero;
//ikpose layer (specified by avatar author) Calibrator.avatarTransform.localPosition = Vector3.zero;
int ikposeLayerIndex = animator.GetLayerIndex("IKPose"); Calibrator.avatarTransform.localRotation = Quaternion.identity;
int locoLayerIndex = animator.GetLayerIndex("Locomotion/Emotes");
if (ikposeLayerIndex != -1) if (Calibrator.lookAtIK != null)
Calibrator.lookAtIK.enabled = !isEmotePlaying;
BodySystem.TrackingEnabled = !isEmotePlaying;
Calibrator.vrik.solver?.Reset();
ResetDesktopVRIK();
}
public void ResetDesktopVRIK()
{
ik_SimulatedRootAngle = transform.eulerAngles.y;
}
public void OnPreSolverUpdate()
{
if (ps_emoteIsPlaying) return;
var movementSystem = MovementSystem.Instance;
var vrikSolver = Calibrator.vrik.solver;
var avatarTransform = Calibrator.avatarTransform;
bool isGrounded = (bool)ms_isGrounded.GetValue(movementSystem);
// Calculate weight
float weight = vrikSolver.IKPositionWeight;
weight *= 1f - movementSystem.movementVector.magnitude;
weight *= isGrounded ? 1f : 0f;
// Reset avatar offset
avatarTransform.localPosition = Vector3.zero;
avatarTransform.localRotation = Quaternion.identity;
// Set plant feet
vrikSolver.plantFeet = Setting_PlantFeet;
// Emulate old VRChat hip movement
if (Setting_BodyLeanWeight > 0)
{ {
animator.SetLayerWeight(ikposeLayerIndex, 1f); float weightedAngle = Setting_BodyLeanWeight * weight;
if (locoLayerIndex != -1) float angle = desktopCameraTransform.localEulerAngles.x;
{ angle = angle > 180 ? angle - 360 : angle;
animator.SetLayerWeight(locoLayerIndex, 0f); Quaternion rotation = Quaternion.AngleAxis(angle * weightedAngle, avatarTransform.right);
} vrikSolver.AddRotationOffset(IKSolverVR.RotationOffset.Head, rotation);
animator.Update(0f);
} }
VRIK vrik = avatar.gameObject.AddComponent<VRIK>(); // Make root heading follow within a set limit
vrik.AutoDetectReferences(); if (Setting_BodyHeadingLimit > 0)
//fuck toes
vrik.references.leftToes = null;
vrik.references.rightToes = null;
vrik.fixTransforms = true;
vrik.solver.plantFeet = false;
vrik.solver.locomotion.angleThreshold = 30f;
vrik.solver.locomotion.maxLegStretch = 0.75f;
vrik.solver.spine.minHeadHeight = -100f;
vrik.solver.spine.bodyRotStiffness = 0.15f;
vrik.solver.spine.headClampWeight = 1f;
vrik.solver.spine.maintainPelvisPosition = 1f;
vrik.solver.spine.neckStiffness = 0f;
vrik.solver.locomotion.weight = 0f;
vrik.solver.spine.bodyPosStiffness = 0f;
vrik.solver.spine.positionWeight = 0f;
vrik.solver.spine.pelvisPositionWeight = 0f;
vrik.solver.leftArm.positionWeight = 0f;
vrik.solver.leftArm.rotationWeight = 0f;
vrik.solver.rightArm.positionWeight = 0f;
vrik.solver.rightArm.rotationWeight = 0f;
vrik.solver.leftLeg.positionWeight = 0f;
vrik.solver.leftLeg.rotationWeight = 0f;
vrik.solver.rightLeg.positionWeight = 0f;
vrik.solver.rightLeg.rotationWeight = 0f;
vrik.solver.IKPositionWeight = 0f;
BodySystem.TrackingLeftArmEnabled = false;
BodySystem.TrackingRightArmEnabled = false;
BodySystem.TrackingLeftLegEnabled = false;
BodySystem.TrackingRightLegEnabled = false;
BodySystem.TrackingPositionWeight = 0f;
//Custom funky AF head ik shit
foreach (Transform transform in DesktopVRIK_Helper.Instance.ik_HeadFollower)
{ {
if (transform.name == "Head IK Target") float weightedAngleLimit = Setting_BodyHeadingLimit * weight;
float currentAngle = Mathf.DeltaAngle(transform.eulerAngles.y, ik_SimulatedRootAngle);
float angleMaxDelta = Mathf.Abs(currentAngle);
if (angleMaxDelta > weightedAngleLimit)
{ {
Destroy(transform.gameObject); currentAngle = Mathf.Sign(currentAngle) * weightedAngleLimit;
ik_SimulatedRootAngle = Mathf.MoveTowardsAngle(ik_SimulatedRootAngle, transform.eulerAngles.y, angleMaxDelta - weightedAngleLimit);
}
vrikSolver.spine.rootHeadingOffset = currentAngle;
if (Setting_PelvisHeadingWeight > 0)
{
vrikSolver.AddRotationOffset(IKSolverVR.RotationOffset.Pelvis, new Vector3(0f, currentAngle * Setting_PelvisHeadingWeight, 0f));
vrikSolver.AddRotationOffset(IKSolverVR.RotationOffset.Chest, new Vector3(0f, -currentAngle * Setting_PelvisHeadingWeight, 0f));
}
if (Setting_ChestHeadingWeight > 0)
{
vrikSolver.AddRotationOffset(IKSolverVR.RotationOffset.Chest, new Vector3(0f, currentAngle * Setting_ChestHeadingWeight, 0f));
} }
} }
DesktopVRIK_Helper.Instance.avatar_HeadBone = avatarHeadBone;
DesktopVRIK_Helper.Instance.ik_HeadFollower.position = avatarHeadBone.position;
DesktopVRIK_Helper.Instance.ik_HeadFollower.rotation = Quaternion.identity;
VRIKCalibrator.CalibrateHead(vrik, DesktopVRIK_Helper.Instance.ik_HeadFollower.transform, IKSystem.Instance.headAnchorPositionOffset, IKSystem.Instance.headAnchorRotationOffset);
DesktopVRIK_Helper.Instance.ik_HeadFollower.localRotation = Quaternion.identity;
//force immediate calibration before animator decides to fuck us
vrik.solver.SetToReferences(vrik.references);
vrik.solver.Initiate(vrik.transform);
if (ikposeLayerIndex != -1)
{
animator.SetLayerWeight(ikposeLayerIndex, 0f);
if (locoLayerIndex != -1)
{
animator.SetLayerWeight(locoLayerIndex, 1f);
}
}
//Find eyeoffset
eyeOffset = PlayerSetup.Instance.desktopCamera.transform.localPosition;
viewpoint = avatarHeadBone.Find("LocalHeadPoint");
ChangeViewpointHandling(Setting_EnforceViewPosition);
//reset ikpose layer
if (ikposeLayerIndex != -1)
{
animator.SetLayerWeight(ikposeLayerIndex, 0f);
if (locoLayerIndex != -1)
{
animator.SetLayerWeight(locoLayerIndex, 1f);
}
}
vrik?.onPreSolverUpdate.AddListener(new UnityAction(this.AlternativeOnPreSolverUpdate));
DesktopVRIK_Helper.Instance?.OnResetIK();
return vrik;
} }
} }

View file

@ -19,7 +19,7 @@
<HintPath>C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath> <HintPath>C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath>
</Reference> </Reference>
<Reference Include="BTKUILib"> <Reference Include="BTKUILib">
<HintPath>C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\Mods\BTKUILib.dll</HintPath> <HintPath>..\..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\ChilloutVR\Mods\BTKUILib.dll</HintPath>
</Reference> </Reference>
<Reference Include="Cohtml.Runtime"> <Reference Include="Cohtml.Runtime">
<HintPath>C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Cohtml.Runtime.dll</HintPath> <HintPath>C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Cohtml.Runtime.dll</HintPath>
@ -44,6 +44,10 @@
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Integrations\" />
</ItemGroup>
<Target Name="Deploy" AfterTargets="Build"> <Target Name="Deploy" AfterTargets="Build">
<Copy SourceFiles="$(TargetPath)" DestinationFolder="C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\Mods\" /> <Copy SourceFiles="$(TargetPath)" DestinationFolder="C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\Mods\" />
<Message Text="Copied $(TargetPath) to C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\Mods\" Importance="high" /> <Message Text="Copied $(TargetPath) to C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\Mods\" Importance="high" />

View file

@ -0,0 +1,367 @@
using ABI.CCK.Components;
using ABI_RC.Core;
using ABI_RC.Core.Base;
using ABI_RC.Core.Player;
using ABI_RC.Systems.IK;
using ABI_RC.Systems.IK.SubSystems;
using HarmonyLib;
using RootMotion.FinalIK;
using UnityEngine;
using UnityEngine.Events;
namespace NAK.Melons.DesktopVRIK;
public class DesktopVRIKCalibrator
{
// Settings
public bool Setting_UseVRIKToes = true;
public bool Setting_FindUnmappedToes = true;
// 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 fixTransformsRequired;
Vector3 leftKneeNormal, rightKneeNormal;
HumanPose initialHumanPose;
HumanPoseHandler humanPoseHandler;
// Traverse
IKSystem ikSystem;
PlayerSetup playerSetup;
Traverse
_vrikTraverse,
_lookIKTraverse,
_avatarTraverse,
_animatorManagerTraverse,
_poseHandlerTraverse,
_avatarRootHeightTraverse;
public DesktopVRIKCalibrator()
{
// Get base game scripts.
ikSystem = IKSystem.Instance;
playerSetup = PlayerSetup.Instance;
// Get traverse to private shit in iksystem.
_vrikTraverse = Traverse.Create(ikSystem).Field("_vrik");
_avatarTraverse = Traverse.Create(ikSystem).Field("_avatar");
_animatorManagerTraverse = Traverse.Create(ikSystem).Field("_animatorManager");
_poseHandlerTraverse = Traverse.Create(ikSystem).Field("_poseHandler");
_avatarRootHeightTraverse = Traverse.Create(ikSystem).Field("_avatarRootHeight");
// Get traverse to private shit in playersetup.
_lookIKTraverse = Traverse.Create(playerSetup).Field("lookIK");
}
public void CalibrateDesktopVRIK()
{
// Scan avatar for issues/references
ScanAvatarForCalibration();
// Prepare CVR IKSystem for external VRIK
PrepareIKSystem();
// Add VRIK and configure
PrepareAvatarVRIK();
Initialize();
PostInitialize();
}
private void Initialize()
{
// Calculate bend normals with motorcycle pose
SetHumanPose(0f);
VRIKUtils.CalculateKneeBendNormals(vrik, out leftKneeNormal, out rightKneeNormal);
// Calculate initial IK scaling values with IKPose
SetAvatarIKPose(true);
VRIKUtils.CalculateInitialIKScaling(vrik, out initialFootDistance, out initialStepThreshold, out initialStepHeight);
// Setup HeadIK target & calculate initial footstep values
SetupDesktopHeadIKTarget();
// Initiate VRIK manually
VRIKUtils.InitiateVRIKSolver(vrik);
// Return avatar to original pose
SetAvatarIKPose(false);
}
private void PostInitialize()
{
VRIKUtils.ApplyScaleToVRIK
(
vrik,
initialFootDistance,
initialStepThreshold,
initialStepHeight,
1f
);
VRIKUtils.ApplyKneeBendNormals(vrik, leftKneeNormal, rightKneeNormal);
vrik.onPreSolverUpdate.AddListener(new UnityAction(DesktopVRIK.Instance.OnPreSolverUpdate));
}
private void ScanAvatarForCalibration()
{
// Find required avatar components
avatar = playerSetup._avatar.GetComponent<CVRAvatar>();
animator = avatar.GetComponent<Animator>();
avatarTransform = avatar.transform;
lookAtIK = _lookIKTraverse.GetValue<LookAtIK>();
// Apply some fixes for weird setups
fixTransformsRequired = !animator.enabled;
// Center avatar local position
avatarTransform.localPosition = Vector3.zero;
// Create a new human pose handler and dispose the old one
humanPoseHandler?.Dispose();
humanPoseHandler = new HumanPoseHandler(animator.avatar, avatarTransform);
// Store original human pose
humanPoseHandler.GetHumanPose(ref initialHumanPose);
}
private void PrepareIKSystem()
{
// Get the animator manager and human pose handler
var animatorManager = _animatorManagerTraverse.GetValue<CVRAnimatorManager>();
var ikHumanPoseHandler = _poseHandlerTraverse.GetValue<HumanPoseHandler>();
// Store the avatar component
_avatarTraverse.SetValue(avatar);
// Set the animator for the IK system
ikSystem.animator = animator;
animatorManager.SetAnimator(ikSystem.animator, ikSystem.animator.runtimeAnimatorController);
// Set the avatar height float
_avatarRootHeightTraverse.SetValue(ikSystem.vrPlaySpace.transform.InverseTransformPoint(avatarTransform.position).y);
// Create a new human pose handler and dispose the old one
ikHumanPoseHandler?.Dispose();
ikHumanPoseHandler = new HumanPoseHandler(ikSystem.animator.avatar, avatarTransform);
_poseHandlerTraverse.SetValue(ikHumanPoseHandler);
// Find valid human bones
IKSystem.BoneExists.Clear();
foreach (HumanBodyBones bone in Enum.GetValues(typeof(HumanBodyBones)))
{
if (bone != HumanBodyBones.LastBone)
{
IKSystem.BoneExists.Add(bone, ikSystem.animator.GetBoneTransform(bone) != null);
}
}
// Prepare BodySystem for calibration
BodySystem.TrackingLeftArmEnabled = false;
BodySystem.TrackingRightArmEnabled = false;
BodySystem.TrackingLeftLegEnabled = false;
BodySystem.TrackingRightLegEnabled = false;
BodySystem.TrackingPositionWeight = 0f;
}
private void PrepareAvatarVRIK()
{
// Add and configure VRIK
vrik = avatar.gameObject.AddComponentIfMissing<VRIK>();
vrik.AutoDetectReferences();
VRIKUtils.ConfigureVRIKReferences(vrik, Setting_UseVRIKToes, Setting_FindUnmappedToes, out bool foundUnmappedToes);
// Fix animator issue or non-human mapped toes
vrik.fixTransforms = fixTransformsRequired || foundUnmappedToes;
// Default solver settings
vrik.solver.locomotion.weight = 0f;
vrik.solver.locomotion.angleThreshold = 30f;
vrik.solver.locomotion.maxLegStretch = 1f;
vrik.solver.spine.minHeadHeight = 0f;
vrik.solver.IKPositionWeight = 1f;
vrik.solver.spine.chestClampWeight = 0f;
vrik.solver.spine.maintainPelvisPosition = 0f;
// Body leaning settings
vrik.solver.spine.neckStiffness = 0.0001f;
vrik.solver.spine.bodyPosStiffness = 1f;
vrik.solver.spine.bodyRotStiffness = 0.2f;
// Disable locomotion
vrik.solver.locomotion.velocityFactor = 0f;
vrik.solver.locomotion.maxVelocity = 0f;
vrik.solver.locomotion.rootSpeed = 1000f;
// Disable chest rotation by hands
vrik.solver.spine.rotateChestByHands = 0f;
// Prioritize LookAtIK
vrik.solver.spine.headClampWeight = 0.2f;
// Disable going on tippytoes
vrik.solver.spine.positionWeight = 0f;
vrik.solver.spine.rotationWeight = 1f;
// Tell IKSystem about new VRIK
_vrikTraverse.SetValue(vrik);
}
private void SetupDesktopHeadIKTarget()
{
// Lazy HeadIKTarget calibration
if (vrik.solver.spine.headTarget == null)
{
vrik.solver.spine.headTarget = new GameObject("Head IK Target").transform;
}
vrik.solver.spine.headTarget.parent = vrik.references.head;
vrik.solver.spine.headTarget.localPosition = Vector3.zero;
vrik.solver.spine.headTarget.localRotation = Quaternion.identity;
}
private void SetAvatarIKPose(bool enforceTPose)
{
int ikposeLayerIndex = animator.GetLayerIndex("IKPose");
int locoLayerIndex = animator.GetLayerIndex("Locomotion/Emotes");
// Use custom IKPose if found.
if (ikposeLayerIndex != -1 && locoLayerIndex != -1)
{
animator.SetLayerWeight(ikposeLayerIndex, enforceTPose ? 1f : 0f);
animator.SetLayerWeight(locoLayerIndex, enforceTPose ? 0f : 1f);
animator.Update(0f);
return;
}
// Otherwise use DesktopVRIK IKPose & revert afterwards.
if (enforceTPose)
{
SetHumanPose(1f);
}
else
{
humanPoseHandler.SetHumanPose(ref initialHumanPose);
}
}
private void SetHumanPose(float ikPoseWeight = 1f)
{
humanPoseHandler.GetHumanPose(ref ikSystem.humanPose);
for (int i = 0; i < ikSystem.humanPose.muscles.Length; i++)
{
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);
}
private static readonly float[] IKPoseMuscles = new float[]
{
0.00133321f,
8.195831E-06f,
8.537738E-07f,
-0.002669832f,
-7.651234E-06f,
-0.001659694f,
0f,
0f,
0f,
0.04213953f,
0.0003007996f,
-0.008032114f,
-0.03059979f,
-0.0003182998f,
0.009640567f,
0f,
0f,
0f,
0f,
0f,
0f,
0.5768794f,
0.01061097f,
-0.1127839f,
0.9705755f,
0.07972051f,
-0.0268422f,
0.007237188f,
0f,
0.5768792f,
0.01056608f,
-0.1127519f,
0.9705756f,
0.07971933f,
-0.02682396f,
0.007229362f,
0f,
-5.651802E-06f,
-3.034899E-07f,
0.4100508f,
0.3610304f,
-0.0838329f,
0.9262537f,
0.1353517f,
-0.03578902f,
0.06005657f,
-4.95989E-06f,
-1.43007E-06f,
0.4096187f,
0.363263f,
-0.08205152f,
0.9250782f,
0.1345718f,
-0.03572125f,
0.06055461f,
-1.079177f,
0.2095419f,
0.6140652f,
0.6365265f,
0.6683931f,
-0.4764312f,
0.8099416f,
0.8099371f,
0.6658203f,
-0.7327053f,
0.8113618f,
0.8114051f,
0.6643661f,
-0.40341f,
0.8111364f,
0.8111367f,
0.6170399f,
-0.2524227f,
0.8138723f,
0.8110135f,
-1.079171f,
0.2095456f,
0.6140658f,
0.6365255f,
0.6683878f,
-0.4764301f,
0.8099402f,
0.8099376f,
0.6658241f,
-0.7327023f,
0.8113653f,
0.8113793f,
0.664364f,
-0.4034042f,
0.811136f,
0.8111364f,
0.6170469f,
-0.2524345f,
0.8138595f,
0.8110138f
};
}

View file

@ -1,108 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using ABI_RC.Core.Player;
using ABI_RC.Systems.IK;
using ABI_RC.Systems.IK.SubSystems;
using ABI_RC.Systems.MovementSystem;
using RootMotion.FinalIK;
namespace NAK.Melons.DesktopVRIK;
internal class DesktopVRIK_Helper : MonoBehaviour
{
public static DesktopVRIK_Helper Instance;
//Avatar
public Transform avatar_HeadBone;
//DesktopVRIK
public Transform ik_HeadFollower;
public Quaternion ik_HeadRotation;
public static void CreateInstance()
{
Transform helper = new GameObject("[DesktopVRIK] Virtual Rig").transform;
helper.parent = PlayerSetup.Instance.transform;
helper.localPosition = Vector3.zero;
helper.localRotation = Quaternion.identity;
helper.gameObject.AddComponent<DesktopVRIK_Helper>();
}
void Start()
{
Instance = this;
Transform headFollower = new GameObject("HeadBone_Follower").transform;
headFollower.parent = transform;
headFollower.localPosition = new Vector3(0f, 1.8f, 0f);
headFollower.localRotation = Quaternion.identity;
ik_HeadFollower = headFollower;
}
public void OnUpdateVRIK()
{
if (avatar_HeadBone != null)
{
float globalWeight = (1 - MovementSystem.Instance.movementVector.magnitude);
globalWeight *= IKSystem.vrik.solver.locomotion.weight;
//the most important thing ever
//IKSystem.vrik.solver.spine.rotationWeight = globalWeight;
HeadIK_FollowPosition();
HeadIK_RotateWithWeight(globalWeight);
HeadIK_FollowWithinAngle(globalWeight);
ik_HeadFollower.rotation = ik_HeadRotation;
}
}
public void OnResetIK()
{
ik_HeadRotation = Quaternion.Euler(transform.eulerAngles.x, transform.eulerAngles.y, transform.eulerAngles.z);
}
public void HeadIK_FollowPosition()
{
ik_HeadFollower.position = new Vector3(transform.position.x, avatar_HeadBone.position.y, transform.position.z);
}
public void HeadIK_FollowWithinAngle(float weight)
{
if (DesktopVRIK.Setting_BodyAngleLimit != 0)
{
float weightedAngle = DesktopVRIK.Setting_BodyAngleLimit * weight;
float currentAngle = Mathf.DeltaAngle(transform.eulerAngles.y, ik_HeadRotation.eulerAngles.y);
if (Mathf.Abs(currentAngle) > weightedAngle)
{
float fixedCurrentAngle = currentAngle > 0 ? currentAngle : -currentAngle;
float clampedAngle = Mathf.MoveTowardsAngle(ik_HeadRotation.eulerAngles.y, transform.eulerAngles.y, fixedCurrentAngle - weightedAngle);
ik_HeadRotation = Quaternion.Euler(ik_HeadRotation.eulerAngles.x, clampedAngle, 0);
}
}
else
{
ik_HeadRotation = Quaternion.Euler(ik_HeadRotation.eulerAngles.x, transform.eulerAngles.y, transform.eulerAngles.z);
}
}
public void HeadIK_RotateWithWeight(float weight)
{
//VRChat hip movement emulation
if (DesktopVRIK.Setting_BodyLeanWeight != 0)
{
float angle = PlayerSetup.Instance.desktopCamera.transform.localEulerAngles.x;
if (angle > 180) angle -= 360;
float leanAmount = angle * weight * DesktopVRIK.Setting_BodyLeanWeight;
ik_HeadRotation = Quaternion.Euler(leanAmount * 0.33f, ik_HeadRotation.eulerAngles.y, 0);
}
else
{
ik_HeadRotation = Quaternion.Euler(transform.eulerAngles.x, ik_HeadRotation.eulerAngles.y, transform.eulerAngles.z);
}
}
}

View file

@ -1,34 +1,21 @@
using ABI.CCK.Components; using ABI_RC.Core.Player;
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Systems.IK; using ABI_RC.Systems.IK;
using ABI_RC.Systems.IK.SubSystems;
using ABI_RC.Systems.MovementSystem;
using HarmonyLib; using HarmonyLib;
using RootMotion.FinalIK;
using UnityEngine; using UnityEngine;
/** /**
The process of calibrating VRIK is fucking painful. The process of calibrating VRIK is fucking painful.
Immediatly doing GetHumanPose() and then SetHumanPose() fixed heels in ground for all avatars.
Setting the avatars rotation to identity, head rotation offset to head bone world rotation, and then calibrating head IK target (kinda) fixed only robot kyle.
Enforcing a TPose only fixed my ferret avatars right shoulder.
Mix and matching these, and changing order, fucks with random specific avatars with fucky armatures.
MOST AVATARS DONT EVEN CHANGE, ITS JUST THESE FEW SPECIFIC ONES
I NEED to look into an IKPose controller...
Avatars of Note: Avatars of Note:
TurtleNeck Ferret- broken/inverted right shoulder TurtleNeck Ferret- close feet, far shoulders, nonideal rig.
Space Robot Kyle- head ik target is rotated -90 90 0, so body/neck is fucked (Fuck you Default Robot Kyle) Space Robot Kyle- the worst bone rolls on the planet, tpose/headikcalibration fixed it mostly... ish.
Exteratta- the knees bend backwards like a fucking chicken... what the fuck im enforcing a tpose nowww Exteratta- knees bend backwards without proper tpose.
Chito- left foot is far back without proper tpose & foot ik distance, was uploaded in falling anim state.
Atlas (portal2)- Wide stance, proper feet distance needed to be calculated.
Freddy (gmod)- Doesn't have any fingers, wristToPalmAxis & palmToThumbAxis needed to be set manually.
Most other avatars play just fine. Never changes even when adding Tpose, rotating the avatar, headikrotationoffset, ect... Most other avatars play just fine.
WHY (Fuck you Default Robot Kyle)
**/ **/
@ -36,210 +23,37 @@ namespace NAK.Melons.DesktopVRIK.HarmonyPatches;
class PlayerSetupPatches class PlayerSetupPatches
{ {
private static bool emotePlayed = false;
[HarmonyPostfix] [HarmonyPostfix]
[HarmonyPatch(typeof(PlayerSetup), "SetupAvatarGeneral")] [HarmonyPatch(typeof(PlayerSetup), "SetupAvatarDesktop")]
static void SetupDesktopIKSystem(ref CVRAvatar ____avatarDescriptor, ref Animator ____animator) static void Postfix_PlayerSetup_SetupAvatarDesktop(ref Animator ____animator)
{ {
if (!MetaPort.Instance.isUsingVr && DesktopVRIK.Setting_Enabled) if (____animator != null && ____animator.avatar != null && ____animator.avatar.isHuman)
{ {
if (____avatarDescriptor != null && ____animator != null && ____animator.isHuman) DesktopVRIK.Instance?.OnSetupAvatarDesktop();
{
//this will stop at the useless isVr return (the function is only ever called by vr anyways...)
IKSystem.Instance.InitializeAvatar(____avatarDescriptor);
}
} }
} }
[HarmonyPostfix] [HarmonyPostfix]
[HarmonyPatch(typeof(PlayerSetup), "Update")] [HarmonyPatch(typeof(PlayerSetup), "Update")]
private static void CorrectVRIK(ref bool ____emotePlaying, ref LookAtIK ___lookIK) static void Postfix_PlayerSetup_Update(ref bool ____emotePlaying)
{ {
if (!MetaPort.Instance.isUsingVr && DesktopVRIK.Setting_Enabled) DesktopVRIK.Instance?.OnPlayerSetupUpdate(____emotePlaying);
{
bool changed = ____emotePlaying != emotePlayed;
if (changed)
{
emotePlayed = ____emotePlaying;
IKSystem.vrik.transform.localPosition = Vector3.zero;
IKSystem.vrik.transform.localRotation = Quaternion.identity;
if (DesktopVRIK.Setting_EmoteLookAtIK && ___lookIK != null)
{
___lookIK.enabled = !____emotePlaying;
}
if (DesktopVRIK.Setting_EmoteVRIK)
{
BodySystem.TrackingEnabled = !____emotePlaying;
IKSystem.vrik.solver?.Reset();
DesktopVRIK_Helper.Instance?.OnResetIK();
}
}
}
} }
//should probably patch movement system instead
[HarmonyPrefix] [HarmonyPrefix]
[HarmonyPatch(typeof(PlayerSetup), "HandleDesktopCameraPosition")] [HarmonyPatch(typeof(PlayerSetup), "SetupIKScaling")]
private static void Prefix_PlayerSetup_HandleDesktopCameraPosition private static bool Prefix_PlayerSetup_SetupIKScaling(float height, ref Vector3 ___scaleDifference)
(
bool ignore,
ref PlayerSetup __instance,
ref MovementSystem
____movementSystem,
ref int ___headBobbingLevel
)
{ {
if (___headBobbingLevel != 2) return !(bool)DesktopVRIK.Instance?.OnSetupIKScaling(1f + ___scaleDifference.y);
{
return;
}
if (!DesktopVRIK.Setting_Enabled || !DesktopVRIK.Setting_EnforceViewPosition)
{
return;
}
if (____movementSystem.disableCameraControl && !ignore)
{
return;
}
if (DesktopVRIK.Instance.viewpoint == null)
{
return;
}
__instance.desktopCamera.transform.position = DesktopVRIK.Instance.viewpoint.position;
return;
} }
} }
class IKSystemPatches class IKSystemPatches
{ {
[HarmonyPostfix] [HarmonyPostfix]
[HarmonyPatch(typeof(IKSystem), "InitializeAvatar")] [HarmonyPatch(typeof(IKSystem), "Start")]
private static void InitializeDesktopAvatarVRIK(CVRAvatar avatar, ref VRIK ____vrik, ref HumanPoseHandler ____poseHandler, ref HumanPose ___humanPose) private static void Postfix_IKSystem_Start(ref IKSystem __instance)
{ {
if (!MetaPort.Instance.isUsingVr && DesktopVRIK.Setting_Enabled) __instance.gameObject.AddComponent<DesktopVRIK>();
{
if (IKSystem.Instance.animator != null && IKSystem.Instance.animator.avatar != null && IKSystem.Instance.animator.avatar.isHuman)
{
if (____poseHandler == null)
{
____poseHandler = new HumanPoseHandler(IKSystem.Instance.animator.avatar, IKSystem.Instance.animator.transform);
}
____poseHandler.GetHumanPose(ref ___humanPose);
for (int i = 0; i < IKPoseMuscles.Length; i++)
{
IKSystem.Instance.ApplyMuscleValue((MuscleIndex)i, IKPoseMuscles[i], ref ___humanPose.muscles);
}
____poseHandler.SetHumanPose(ref ___humanPose);
____vrik = DesktopVRIK.Instance.AlternativeCalibration(avatar);
IKSystem.Instance.ApplyAvatarScaleToIk(avatar.viewPosition.y);
}
}
} }
private static readonly float[] IKPoseMuscles = new float[]
{
0.00133321f,
8.195831E-06f,
8.537738E-07f,
-0.002669832f,
-7.651234E-06f,
-0.001659694f,
0f,
0f,
0f,
0.04213953f,
0.0003007996f,
-0.008032114f,
-0.03059979f,
-0.0003182998f,
0.009640567f,
0f,
0f,
0f,
0f,
0f,
0f,
0.5768794f,
0.01061097f,
-0.1127839f,
0.9705755f,
0.07972051f,
-0.0268422f,
0.007237188f,
0f,
0.5768792f,
0.01056608f,
-0.1127519f,
0.9705756f,
0.07971933f,
-0.02682396f,
0.007229362f,
0f,
-5.651802E-06f,
-3.034899E-07f,
0.4100508f,
0.3610304f,
-0.0838329f,
0.9262537f,
0.1353517f,
-0.03578902f,
0.06005657f,
-4.95989E-06f,
-1.43007E-06f,
0.4096187f,
0.363263f,
-0.08205152f,
0.9250782f,
0.1345718f,
-0.03572125f,
0.06055461f,
-1.079177f,
0.2095419f,
0.6140652f,
0.6365265f,
0.6683931f,
-0.4764312f,
0.8099416f,
0.8099371f,
0.6658203f,
-0.7327053f,
0.8113618f,
0.8114051f,
0.6643661f,
-0.40341f,
0.8111364f,
0.8111367f,
0.6170399f,
-0.2524227f,
0.8138723f,
0.8110135f,
-1.079171f,
0.2095456f,
0.6140658f,
0.6365255f,
0.6683878f,
-0.4764301f,
0.8099402f,
0.8099376f,
0.6658241f,
-0.7327023f,
0.8113653f,
0.8113793f,
0.664364f,
-0.4034042f,
0.811136f,
0.8111364f,
0.6170469f,
-0.2524345f,
0.8138595f,
0.8110138f
};
} }

View file

@ -14,35 +14,38 @@ public static class BTKUIAddon
Page miscPage = QuickMenuAPI.MiscTabPage; Page miscPage = QuickMenuAPI.MiscTabPage;
Category miscCategory = miscPage.AddCategory(DesktopVRIKMod.SettingsCategory); Category miscCategory = miscPage.AddCategory(DesktopVRIKMod.SettingsCategory);
AddMelonToggle(ref miscCategory, DesktopVRIKMod.m_entryEnabled); AddMelonToggle(ref miscCategory, DesktopVRIKMod.EntryEnabled);
//Add my own page to not clog up Misc Menu //Add my own page to not clog up Misc Menu
Page desktopVRIKPage = miscCategory.AddPage("DesktopVRIK Settings", "", "Configure the settings for DesktopVRIK.", "DesktopVRIK"); Page desktopVRIKPage = miscCategory.AddPage("DesktopVRIK Settings", "", "Configure the settings for DesktopVRIK.", "DesktopVRIK");
desktopVRIKPage.MenuTitle = "DesktopVRIK Settings"; desktopVRIKPage.MenuTitle = "DesktopVRIK Settings";
desktopVRIKPage.MenuSubtitle = "Simplified settings for VRIK on Desktop.";
Category desktopVRIKCategory = desktopVRIKPage.AddCategory("DesktopVRIK"); Category desktopVRIKCategory = desktopVRIKPage.AddCategory(DesktopVRIKMod.SettingsCategory);
AddMelonToggle(ref desktopVRIKCategory, DesktopVRIKMod.m_entryEnabled); // General Settings
AddMelonToggle(ref desktopVRIKCategory, DesktopVRIKMod.EntryEnabled);
AddMelonToggle(ref desktopVRIKCategory, DesktopVRIKMod.EntryPlantFeet);
AddMelonToggle(ref desktopVRIKCategory, DesktopVRIKMod.m_entryEnforceViewPosition); // Calibration Settings
AddMelonToggle(ref desktopVRIKCategory, DesktopVRIKMod.EntryUseVRIKToes);
AddMelonToggle(ref desktopVRIKCategory, DesktopVRIKMod.EntryFindUnmappedToes);
AddMelonToggle(ref desktopVRIKCategory, DesktopVRIKMod.m_entryEmoteVRIK); // Body Leaning Weight
AddMelonSlider(ref desktopVRIKPage, DesktopVRIKMod.EntryBodyLeanWeight, 0, 1f, 1);
AddMelonToggle(ref desktopVRIKCategory, DesktopVRIKMod.m_entryEmoteLookAtIK); // Max Root Heading Limit & Weights
AddMelonSlider(ref desktopVRIKPage, DesktopVRIKMod.EntryBodyHeadingLimit, 0, 90f, 0);
AddMelonSlider(ref desktopVRIKPage, DesktopVRIKMod.m_entryBodyLeanWeight, 0, 1f); AddMelonSlider(ref desktopVRIKPage, DesktopVRIKMod.EntryPelvisHeadingWeight, 0, 1f, 1);
AddMelonSlider(ref desktopVRIKPage, DesktopVRIKMod.EntryChestHeadingWeight, 0, 1f, 1);
AddMelonSlider(ref desktopVRIKPage, DesktopVRIKMod.m_entryBodyAngleLimit, 0, 90f);
} }
private static void AddMelonToggle(ref Category category, MelonLoader.MelonPreferences_Entry<bool> entry) private static void AddMelonToggle(ref Category category, MelonLoader.MelonPreferences_Entry<bool> entry)
{ {
category.AddToggle(entry.DisplayName, entry.Description, entry.Value).OnValueUpdated += b => entry.Value = b; category.AddToggle(entry.DisplayName, entry.Description, entry.Value).OnValueUpdated += b => entry.Value = b;
} }
private static void AddMelonSlider(ref Page page, MelonLoader.MelonPreferences_Entry<float> entry, float min, float max) private static void AddMelonSlider(ref Page page, MelonLoader.MelonPreferences_Entry<float> entry, float min, float max, int decimalPlaces = 2)
{ {
page.AddSlider(entry.DisplayName, entry.Description, entry.Value, min, max).OnValueUpdated += f => entry.Value = f; page.AddSlider(entry.DisplayName, entry.Description, entry.Value, min, max, decimalPlaces).OnValueUpdated += f => entry.Value = f;
} }
} }

View file

@ -1,76 +1,78 @@
using ABI_RC.Core.Player; using MelonLoader;
using MelonLoader;
using UnityEngine; using UnityEngine;
namespace NAK.Melons.DesktopVRIK; namespace NAK.Melons.DesktopVRIK;
public class DesktopVRIKMod : MelonMod public class DesktopVRIKMod : MelonMod
{ {
internal const string SettingsCategory = "DesktopVRIK"; internal static MelonLogger.Instance Logger;
internal static MelonPreferences_Category m_categoryDesktopVRIK; public const string SettingsCategory = "DesktopVRIK";
internal static MelonPreferences_Entry<bool> m_entryEnabled, public static readonly MelonPreferences_Category CategoryDesktopVRIK = MelonPreferences.CreateCategory(SettingsCategory);
m_entryEnforceViewPosition,
m_entryEmoteVRIK, public static readonly MelonPreferences_Entry<bool> EntryEnabled =
m_entryEmoteLookAtIK; CategoryDesktopVRIK.CreateEntry("Enabled", true, description: "Toggle DesktopVRIK entirely. Requires avatar reload.");
internal static MelonPreferences_Entry<float>
m_entryBodyLeanWeight, public static readonly MelonPreferences_Entry<bool> EntryPlantFeet =
m_entryBodyAngleLimit; CategoryDesktopVRIK.CreateEntry("Enforce Plant Feet", true, description: "Forces VRIK Plant Feet enabled to prevent hovering when stopping movement.");
public static readonly MelonPreferences_Entry<bool> EntryUseVRIKToes =
CategoryDesktopVRIK.CreateEntry("Use VRIK Toes", false, description: "Determines if VRIK uses humanoid toes for IK solving, which can cause feet to idle behind the avatar.");
public static readonly MelonPreferences_Entry<bool> EntryFindUnmappedToes =
CategoryDesktopVRIK.CreateEntry("Find Unmapped Toes", false, description: "Determines if DesktopVRIK should look for unmapped toe bones if the humanoid rig does not have any.");
public static readonly MelonPreferences_Entry<float> EntryBodyLeanWeight =
CategoryDesktopVRIK.CreateEntry("Body Lean Weight", 0.5f, description: "Adds rotational influence to the body solver when looking up/down. Set to 0 to disable.");
public static readonly MelonPreferences_Entry<float> EntryBodyHeadingLimit =
CategoryDesktopVRIK.CreateEntry("Body Heading Limit", 20f, description: "Specifies the maximum angle the lower body can have relative to the head when rotating. Set to 0 to disable.");
public static readonly MelonPreferences_Entry<float> EntryPelvisHeadingWeight =
CategoryDesktopVRIK.CreateEntry("Pelvis Heading Weight", 0.25f, description: "Determines how much the pelvis will face the Body Heading Limit. Set to 0 to align with head.");
public static readonly MelonPreferences_Entry<float> EntryChestHeadingWeight =
CategoryDesktopVRIK.CreateEntry("Chest Heading Weight", 0.75f, description: "Determines how much the chest will face the Body Heading Limit. Set to 0 to align with head.");
public override void OnInitializeMelon() public override void OnInitializeMelon()
{ {
m_categoryDesktopVRIK = MelonPreferences.CreateCategory(SettingsCategory); Logger = LoggerInstance;
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_entryEmoteVRIK = m_categoryDesktopVRIK.CreateEntry<bool>("Disable Emote VRIK", true, description: "Disable VRIK while emoting. Only disable if you are ok with looking dumb.");
m_entryEmoteLookAtIK = m_categoryDesktopVRIK.CreateEntry<bool>("Disable Emote LookAtIK", true, description: "Disable LookAtIK while emoting. This setting doesn't really matter, as LookAtIK isn't networked while doing an emote.");
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."); CategoryDesktopVRIK.Entries.ForEach(e => e.OnEntryValueChangedUntyped.Subscribe(OnUpdateSettings));
m_entryBodyAngleLimit = m_categoryDesktopVRIK.CreateEntry<float>("Body Angle Limit", 0f, description: "Emulates VRChat-like body and head offset when rotating left/right. Set to 0 to disable. (this setting only affects the feet due to chillout not setting up the player controller for VRIK)");
foreach (var setting in m_categoryDesktopVRIK.Entries)
{
setting.OnEntryValueChangedUntyped.Subscribe(OnUpdateSettings);
}
//BTKUILib Misc Support
if (MelonMod.RegisteredMelons.Any(it => it.Info.Name == "BTKUILib"))
{
MelonLogger.Msg("Initializing BTKUILib support.");
BTKUIAddon.Init();
}
//Apply patches (i stole)
ApplyPatches(typeof(HarmonyPatches.PlayerSetupPatches)); ApplyPatches(typeof(HarmonyPatches.PlayerSetupPatches));
ApplyPatches(typeof(HarmonyPatches.IKSystemPatches)); ApplyPatches(typeof(HarmonyPatches.IKSystemPatches));
MelonLoader.MelonCoroutines.Start(WaitForLocalPlayer()); InitializeIntegrations();
} }
System.Collections.IEnumerator WaitForLocalPlayer() internal static void UpdateAllSettings()
{
while (PlayerSetup.Instance == null)
yield return null;
DesktopVRIK_Helper.CreateInstance();
PlayerSetup.Instance.gameObject.AddComponent<DesktopVRIK>();
while (DesktopVRIK.Instance == null)
yield return null;
UpdateAllSettings();
}
private void UpdateAllSettings()
{ {
if (!DesktopVRIK.Instance) return; if (!DesktopVRIK.Instance) return;
DesktopVRIK.Setting_Enabled = m_entryEnabled.Value; // DesktopVRIK Settings
DesktopVRIK.Setting_BodyLeanWeight = Mathf.Clamp01(m_entryBodyLeanWeight.Value); DesktopVRIK.Instance.Setting_Enabled = EntryEnabled.Value;
DesktopVRIK.Setting_BodyAngleLimit = Mathf.Clamp(m_entryBodyAngleLimit.Value, 0f, 90f); DesktopVRIK.Instance.Setting_PlantFeet = EntryPlantFeet.Value;
DesktopVRIK.Setting_EmoteVRIK = m_entryEmoteVRIK.Value;
DesktopVRIK.Setting_EmoteLookAtIK = m_entryEmoteLookAtIK.Value;
DesktopVRIK.Instance.ChangeViewpointHandling(m_entryEnforceViewPosition.Value);
}
DesktopVRIK.Instance.Setting_BodyLeanWeight = Mathf.Clamp01(EntryBodyLeanWeight.Value);
DesktopVRIK.Instance.Setting_BodyHeadingLimit = Mathf.Clamp(EntryBodyHeadingLimit.Value, 0f, 90f);
DesktopVRIK.Instance.Setting_PelvisHeadingWeight = (1f - Mathf.Clamp01(EntryPelvisHeadingWeight.Value));
DesktopVRIK.Instance.Setting_ChestHeadingWeight = (1f - Mathf.Clamp01(EntryChestHeadingWeight.Value));
// Calibration Settings
DesktopVRIK.Instance.Calibrator.Setting_UseVRIKToes = EntryUseVRIKToes.Value;
DesktopVRIK.Instance.Calibrator.Setting_FindUnmappedToes = EntryFindUnmappedToes.Value;
}
private void OnUpdateSettings(object arg1, object arg2) => UpdateAllSettings(); private void OnUpdateSettings(object arg1, object arg2) => UpdateAllSettings();
private static void InitializeIntegrations()
{
//BTKUILib Misc Tab
if (MelonMod.RegisteredMelons.Any(it => it.Info.Name == "BTKUILib"))
{
Logger.Msg("Initializing BTKUILib support.");
BTKUIAddon.Init();
}
}
private void ApplyPatches(Type type) private void ApplyPatches(Type type)
{ {
try try
@ -79,8 +81,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);
} }
} }
} }

View file

@ -1,8 +1,7 @@
using DesktopVRIK.Properties; using MelonLoader;
using MelonLoader; using NAK.Melons.DesktopVRIK.Properties;
using System.Reflection; using System.Reflection;
[assembly: AssemblyVersion(AssemblyInfoParams.Version)] [assembly: AssemblyVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] [assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] [assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
@ -24,9 +23,9 @@ using System.Reflection;
[assembly: MelonOptionalDependencies("BTKUILib")] [assembly: MelonOptionalDependencies("BTKUILib")]
[assembly: HarmonyDontPatchAll] [assembly: HarmonyDontPatchAll]
namespace DesktopVRIK.Properties; namespace NAK.Melons.DesktopVRIK.Properties;
internal static class AssemblyInfoParams internal static class AssemblyInfoParams
{ {
public const string Version = "2.0.5"; public const string Version = "4.0.1";
public const string Author = "NotAKidoS"; public const string Author = "NotAKidoS";
} }

183
DesktopVRIK/VRIKUtils.cs Normal file
View file

@ -0,0 +1,183 @@
using HarmonyLib;
using RootMotion.FinalIK;
using UnityEngine;
namespace NAK.Melons.DesktopVRIK;
public static class VRIKUtils
{
public static void ConfigureVRIKReferences(VRIK vrik, bool useVRIKToes, bool findUnmappedToes, out bool foundUnmappedToes)
{
foundUnmappedToes = false;
//might not work over netik
FixChestAndSpineReferences(vrik);
if (!useVRIKToes)
{
vrik.references.leftToes = null;
vrik.references.rightToes = null;
}
else if (findUnmappedToes)
{
//doesnt work with netik, but its toes...
FindAndSetUnmappedToes(vrik, out foundUnmappedToes);
}
//bullshit fix to not cause death
FixFingerBonesError(vrik);
}
private static void FixChestAndSpineReferences(VRIK vrik)
{
Transform leftShoulderBone = vrik.references.leftShoulder;
Transform rightShoulderBone = vrik.references.rightShoulder;
Transform assumedChest = leftShoulderBone?.parent;
if (assumedChest != null && rightShoulderBone.parent == assumedChest &&
vrik.references.chest != assumedChest)
{
vrik.references.chest = assumedChest;
vrik.references.spine = assumedChest.parent;
}
}
private static void FindAndSetUnmappedToes(VRIK vrik, out bool foundUnmappedToes)
{
foundUnmappedToes = false;
Transform leftToes = vrik.references.leftToes;
Transform rightToes = vrik.references.rightToes;
if (leftToes == null && rightToes == null)
{
leftToes = FindUnmappedToe(vrik.references.leftFoot);
rightToes = FindUnmappedToe(vrik.references.rightFoot);
if (leftToes != null && rightToes != null)
{
vrik.references.leftToes = leftToes;
vrik.references.rightToes = rightToes;
foundUnmappedToes = true;
}
}
}
private static Transform FindUnmappedToe(Transform foot)
{
foreach (Transform bone in foot)
{
if (bone.name.ToLowerInvariant().Contains("toe") ||
bone.name.ToLowerInvariant().EndsWith("_end"))
{
return bone;
}
}
return null;
}
private static void FixFingerBonesError(VRIK vrik)
{
FixFingerBones(vrik, vrik.references.leftHand, vrik.solver.leftArm);
FixFingerBones(vrik, vrik.references.rightHand, vrik.solver.rightArm);
}
private static void FixFingerBones(VRIK vrik, Transform hand, IKSolverVR.Arm armSolver)
{
if (hand.childCount == 0)
{
armSolver.wristToPalmAxis = Vector3.up;
armSolver.palmToThumbAxis = hand == vrik.references.leftHand ? -Vector3.forward : Vector3.forward;
}
}
public static void CalculateKneeBendNormals(VRIK vrik, out Vector3 leftKneeNormal, out Vector3 rightKneeNormal)
{
// Helper function to get position or default to Vector3.zero
Vector3 GetPositionOrDefault(Transform transform) => transform?.position ?? Vector3.zero;
// Get assumed left knee normal
Vector3[] leftVectors = {
GetPositionOrDefault(vrik.references.leftThigh),
GetPositionOrDefault(vrik.references.leftCalf),
GetPositionOrDefault(vrik.references.leftFoot)
};
leftKneeNormal = Quaternion.Inverse(vrik.references.root.rotation) * GetNormalFromArray(leftVectors);
// Get assumed right knee normal
Vector3[] rightVectors = {
GetPositionOrDefault(vrik.references.rightThigh),
GetPositionOrDefault(vrik.references.rightCalf),
GetPositionOrDefault(vrik.references.rightFoot)
};
rightKneeNormal = Quaternion.Inverse(vrik.references.root.rotation) * GetNormalFromArray(rightVectors);
}
public static void ApplyKneeBendNormals(VRIK vrik, Vector3 leftKneeNormal, Vector3 rightKneeNormal)
{
// 0 uses bendNormalRelToPelvis, 1 is bendNormalRelToTarget
// modifying pelvis normal weight is better math
vrik.solver.leftLeg.bendToTargetWeight = 0f;
vrik.solver.rightLeg.bendToTargetWeight = 0f;
var leftLeg_bendNormalRelToPelvisTraverse = Traverse.Create(vrik.solver.leftLeg).Field("bendNormalRelToPelvis");
var rightLeg_bendNormalRelToPelvisTraverse = Traverse.Create(vrik.solver.rightLeg).Field("bendNormalRelToPelvis");
var pelvis_localRotationInverse = Quaternion.Inverse(vrik.references.pelvis.localRotation);
var leftLeg_bendNormalRelToPelvis = pelvis_localRotationInverse * leftKneeNormal;
var rightLeg_bendNormalRelToPelvis = pelvis_localRotationInverse * rightKneeNormal;
leftLeg_bendNormalRelToPelvisTraverse.SetValue(leftLeg_bendNormalRelToPelvis);
rightLeg_bendNormalRelToPelvisTraverse.SetValue(rightLeg_bendNormalRelToPelvis);
}
private static Vector3 GetNormalFromArray(Vector3[] positions)
{
Vector3 centroid = Vector3.zero;
for (int i = 0; i < positions.Length; i++)
{
centroid += positions[i];
}
centroid /= positions.Length;
Vector3 normal = Vector3.zero;
for (int i = 0; i < positions.Length - 2; i++)
{
Vector3 side1 = positions[i] - centroid;
Vector3 side2 = positions[i + 1] - centroid;
normal += Vector3.Cross(side1, side2);
}
return normal.normalized;
}
public static void CalculateInitialIKScaling(VRIK vrik, out float initialFootDistance, out float initialStepThreshold, out float initialStepHeight)
{
// Get distance between feet and thighs
float scaleModifier = Mathf.Max(1f, vrik.references.pelvis.lossyScale.x);
float footDistance = Vector3.Distance(vrik.references.leftFoot.position, vrik.references.rightFoot.position);
initialFootDistance = footDistance * 0.5f;
initialStepThreshold = footDistance * scaleModifier;
initialStepHeight = Vector3.Distance(vrik.references.leftFoot.position, vrik.references.leftCalf.position) * 0.2f;
}
public static void ApplyScaleToVRIK(VRIK vrik, float footDistance, float stepThreshold, float stepHeight, float modifier)
{
vrik.solver.locomotion.footDistance = footDistance * modifier;
vrik.solver.locomotion.stepThreshold = stepThreshold * modifier;
ScaleStepHeight(vrik.solver.locomotion.stepHeight, stepHeight * modifier);
}
private static void ScaleStepHeight(AnimationCurve stepHeightCurve, float mag)
{
Keyframe[] keyframes = stepHeightCurve.keys;
keyframes[1].value = mag;
stepHeightCurve.keys = keyframes;
}
public static void InitiateVRIKSolver(VRIK vrik)
{
vrik.solver.SetToReferences(vrik.references);
vrik.solver.Initiate(vrik.transform);
}
}

View file

@ -1,23 +1,25 @@
{ {
"_id": 117, "_id": 117,
"name": "DesktopVRIK", "name": "DesktopVRIK",
"modversion": "2.0.5", "modversion": "4.0.1",
"gameversion": "2022r170", "gameversion": "2022r170",
"loaderversion": "0.5.7", "loaderversion": "0.5.7",
"modtype": "Mod", "modtype": "Mod",
"author": "NotAKidoS", "author": "NotAKidoS",
"description": "Adds VRIK to Desktop avatars. No longer will you be a liveless sliding statue~!\nAdds the small feet stepping when looking around on Desktop.\n\nAdditional option to emulate VRChat-like hip movement if you like being fish.", "description": "Adds VRIK to Desktop avatars. No longer will you be a liveless sliding statue~!\nAdds the small feet stepping when looking around on Desktop.\n\nIt is highly recommended to use AvatarMotionTweaker alongside this mod.",
"searchtags": [ "searchtags": [
"desktop", "desktop",
"vrik", "vrik",
"ik", "ik",
"feet" "feet",
"fish"
], ],
"requirements": [ "requirements": [
"BTKUILib" "BTKUILib",
"AvatarMotionTweaker"
], ],
"downloadlink": "https://github.com/NotAKidOnSteam/DesktopVRIK/releases/download/v2.0.5/DesktopVRIK.dll", "downloadlink": "https://github.com/NotAKidOnSteam/DesktopVRIK/releases/download/v4.0.1/DesktopVRIK.dll",
"sourcelink": "https://github.com/NotAKidOnSteam/DesktopVRIK/", "sourcelink": "https://github.com/NotAKidOnSteam/DesktopVRIK/",
"changelog": "- Tweaks to VRIK settings so BetterInteractDesktop & PickupArmMovement better behave alongside VRIK.\n- Tweaks to BTKUI integration.", "changelog": "- Implemented fixes for terrible armatures that had broken knee bending, initial hip rotation, and initial step calculations.\n- Added pelvis & chest weight sliders for heading angle limit option.\n- Implemented bandaid fixes for disabled animator and avatars without fingers.\n- Added options to use or remove toes from VRIK and find unmapped non-humanoid toe bones.\n- Contact me if you find an avatar that is still broken.",
"embedcolor": "9b59b6" "embedcolor": "9b59b6"
} }

View file

@ -3,9 +3,16 @@ Adds VRIK to Desktop ChilloutVR avatars. No longer will you be a liveless slidin
(adds the small feet stepping when looking around on Desktop) (adds the small feet stepping when looking around on Desktop)
Additional option to emulate VRChat-like hip movement if you like becoming a fish. https://user-images.githubusercontent.com/37721153/221870123-fbe4f5e8-8d6e-4a43-aa5e-f2188e6491a9.mp4
Control over disabling VRIK & LookAtIK during Emotes. (this can cause funky behavior) ## Configuration:
* Configurable body lean weight. Set to 0 to disable.
* Configurable max root heading angle with chest/pelvis weight settings. Set to 0 to disable.
* Options to disable VRIK from using mapped toes &/or find unmapped (non-human) toe bones.
* Autofixes for avatars without fingers & incorrect chest/spine bone mapping (might not play well with netik).
## Relevant Feedback Posts: ## Relevant Feedback Posts:
https://feedback.abinteractive.net/p/desktop-feet-ik-for-avatars https://feedback.abinteractive.net/p/desktop-feet-ik-for-avatars
@ -22,4 +29,3 @@ https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games
> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. > Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use.
> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. > To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive.