mirror of
https://github.com/NotAKidoS/NAK_CVR_Mods.git
synced 2025-09-02 14:29:25 +00:00
Merge remote-tracking branch 'DesktopVRIK/main'
Merged DesktopVRIK
This commit is contained in:
commit
d8ae68d34c
10 changed files with 1288 additions and 1 deletions
57
DesktopVRIK/DesktopVRIK.csproj
Normal file
57
DesktopVRIK/DesktopVRIK.csproj
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net472</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
|
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="0Harmony">
|
||||||
|
<HintPath>C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\0Harmony.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Assembly-CSharp">
|
||||||
|
<HintPath>C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\Assembly-CSharp.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Assembly-CSharp-firstpass">
|
||||||
|
<HintPath>C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\Assembly-CSharp-firstpass.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="BTKUILib">
|
||||||
|
<HintPath>..\..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\ChilloutVR\Mods\BTKUILib.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Cohtml.Runtime">
|
||||||
|
<HintPath>C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\Cohtml.Runtime.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="MelonLoader">
|
||||||
|
<HintPath>C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\MelonLoader.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="UnityEngine.AnimationModule">
|
||||||
|
<HintPath>C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\UnityEngine.AnimationModule.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="UnityEngine.AssetBundleModule">
|
||||||
|
<HintPath>C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\UnityEngine.AssetBundleModule.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="UnityEngine.CoreModule">
|
||||||
|
<HintPath>C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\UnityEngine.CoreModule.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="UnityEngine.InputLegacyModule">
|
||||||
|
<HintPath>C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\UnityEngine.InputLegacyModule.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="UnityEngine.PhysicsModule">
|
||||||
|
<HintPath>C:\Users\krist\Documents\GitHub\_ChilloutVR Modding\_ManagedLibs\UnityEngine.PhysicsModule.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Integrations\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<Target Name="Deploy" AfterTargets="Build">
|
||||||
|
<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" />
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
</Project>
|
25
DesktopVRIK/DesktopVRIK.sln
Normal file
25
DesktopVRIK/DesktopVRIK.sln
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.2.32630.192
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DesktopVRIK", "DesktopVRIK.csproj", "{07F06485-C387-470A-A43D-F3779A059F30}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{07F06485-C387-470A-A43D-F3779A059F30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{07F06485-C387-470A-A43D-F3779A059F30}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{07F06485-C387-470A-A43D-F3779A059F30}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{07F06485-C387-470A-A43D-F3779A059F30}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {05F83429-FD88-4C7A-B7D5-40516382B6FD}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
677
DesktopVRIK/DesktopVRIKSystem.cs
Normal file
677
DesktopVRIK/DesktopVRIKSystem.cs
Normal file
|
@ -0,0 +1,677 @@
|
||||||
|
using ABI.CCK.Components;
|
||||||
|
using ABI_RC.Core.Base;
|
||||||
|
using ABI_RC.Core.Player;
|
||||||
|
using ABI_RC.Systems.IK;
|
||||||
|
using ABI_RC.Systems.IK.SubSystems;
|
||||||
|
using ABI_RC.Systems.MovementSystem;
|
||||||
|
using RootMotion.FinalIK;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Events;
|
||||||
|
|
||||||
|
namespace NAK.Melons.DesktopVRIK;
|
||||||
|
|
||||||
|
internal class DesktopVRIKSystem : MonoBehaviour
|
||||||
|
{
|
||||||
|
public static DesktopVRIKSystem Instance;
|
||||||
|
public static Dictionary<HumanBodyBones, bool> BoneExists;
|
||||||
|
public 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
|
||||||
|
};
|
||||||
|
|
||||||
|
enum AvatarPose
|
||||||
|
{
|
||||||
|
Default = 0,
|
||||||
|
Initial = 1,
|
||||||
|
IKPose = 2,
|
||||||
|
TPose = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
// DesktopVRIK Settings
|
||||||
|
public bool Setting_Enabled = true;
|
||||||
|
public bool Setting_PlantFeet;
|
||||||
|
public bool Setting_ResetFootsteps;
|
||||||
|
public bool Setting_ProneThrusting;
|
||||||
|
public float Setting_BodyLeanWeight;
|
||||||
|
public float Setting_BodyHeadingLimit;
|
||||||
|
public float Setting_PelvisHeadingWeight;
|
||||||
|
public float Setting_ChestHeadingWeight;
|
||||||
|
public float Setting_IKLerpSpeed;
|
||||||
|
|
||||||
|
// Calibration Settings
|
||||||
|
public bool Setting_UseVRIKToes;
|
||||||
|
public bool Setting_FindUnmappedToes;
|
||||||
|
|
||||||
|
// Integration Settings
|
||||||
|
public bool Setting_IntegrationAMT;
|
||||||
|
|
||||||
|
// Avatar Components
|
||||||
|
public CVRAvatar avatarDescriptor = null;
|
||||||
|
public Animator avatarAnimator = null;
|
||||||
|
public Transform avatarTransform = null;
|
||||||
|
public LookAtIK avatarLookAtIK = null;
|
||||||
|
public VRIK avatarVRIK = null;
|
||||||
|
public IKSolverVR avatarIKSolver = null;
|
||||||
|
|
||||||
|
// ChilloutVR Player Components
|
||||||
|
PlayerSetup playerSetup;
|
||||||
|
MovementSystem movementSystem;
|
||||||
|
|
||||||
|
// Calibration Objects
|
||||||
|
HumanPose _humanPose;
|
||||||
|
HumanPose _humanPoseInitial;
|
||||||
|
HumanPoseHandler _humanPoseHandler;
|
||||||
|
|
||||||
|
// Animator Info
|
||||||
|
int _animLocomotionLayer = -1;
|
||||||
|
int _animIKPoseLayer = -1;
|
||||||
|
|
||||||
|
// VRIK Calibration Info
|
||||||
|
Vector3 _vrikKneeNormalLeft;
|
||||||
|
Vector3 _vrikKneeNormalRight;
|
||||||
|
Vector3 _vrikInitialFootPosLeft;
|
||||||
|
Vector3 _vrikInitialFootPosRight;
|
||||||
|
Quaternion _vrikInitialFootRotLeft;
|
||||||
|
Quaternion _vrikInitialFootRotRight;
|
||||||
|
float _vrikInitialFootDistance;
|
||||||
|
float _vrikInitialStepThreshold;
|
||||||
|
float _vrikInitialStepHeight;
|
||||||
|
bool _vrikFixTransformsRequired;
|
||||||
|
|
||||||
|
// Player Info
|
||||||
|
Transform _cameraTransform;
|
||||||
|
bool _ikEmotePlaying;
|
||||||
|
float _ikWeightLerp = 1f;
|
||||||
|
float _ikSimulatedRootAngle = 0f;
|
||||||
|
float _locomotionWeight = 1f;
|
||||||
|
float _scaleDifference = 1f;
|
||||||
|
|
||||||
|
// Last Movement Parent Info
|
||||||
|
Vector3 _movementPosition;
|
||||||
|
Quaternion _movementRotation;
|
||||||
|
|
||||||
|
DesktopVRIKSystem()
|
||||||
|
{
|
||||||
|
BoneExists = new Dictionary<HumanBodyBones, bool>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Start()
|
||||||
|
{
|
||||||
|
Instance = this;
|
||||||
|
|
||||||
|
playerSetup = GetComponent<PlayerSetup>();
|
||||||
|
movementSystem = GetComponent<MovementSystem>();
|
||||||
|
|
||||||
|
_cameraTransform = playerSetup.desktopCamera.transform;
|
||||||
|
|
||||||
|
DesktopVRIKMod.UpdateAllSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Update()
|
||||||
|
{
|
||||||
|
if (avatarVRIK == null) return;
|
||||||
|
|
||||||
|
HandleLocomotionTracking();
|
||||||
|
UpdateLocomotionWeight();
|
||||||
|
ApplyBodySystemWeights();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleLocomotionTracking()
|
||||||
|
{
|
||||||
|
bool shouldTrackLocomotion = ShouldTrackLocomotion();
|
||||||
|
|
||||||
|
if (shouldTrackLocomotion != BodySystem.TrackingLocomotionEnabled)
|
||||||
|
{
|
||||||
|
BodySystem.TrackingLocomotionEnabled = shouldTrackLocomotion;
|
||||||
|
avatarIKSolver.Reset();
|
||||||
|
ResetDesktopVRIK();
|
||||||
|
if (shouldTrackLocomotion) IKResetFootsteps();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShouldTrackLocomotion()
|
||||||
|
{
|
||||||
|
bool isMoving = movementSystem.movementVector.magnitude > 0f;
|
||||||
|
bool isGrounded = movementSystem._isGrounded;
|
||||||
|
bool isCrouching = movementSystem.crouching;
|
||||||
|
bool isProne = movementSystem.prone;
|
||||||
|
bool isFlying = movementSystem.flying;
|
||||||
|
bool isStanding = IsStanding();
|
||||||
|
|
||||||
|
return !(isMoving || isCrouching || isProne || isFlying || !isGrounded || !isStanding);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsStanding()
|
||||||
|
{
|
||||||
|
// Let AMT handle it if available
|
||||||
|
if (Setting_IntegrationAMT) return true;
|
||||||
|
|
||||||
|
// Get Upright value
|
||||||
|
Vector3 delta = avatarIKSolver.spine.headPosition - avatarTransform.position;
|
||||||
|
Vector3 deltaRotated = Quaternion.Euler(0, avatarTransform.rotation.eulerAngles.y, 0) * delta;
|
||||||
|
float upright = Mathf.InverseLerp(0f, avatarIKSolver.spine.headHeight * _scaleDifference, deltaRotated.y);
|
||||||
|
return upright > 0.85f;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void UpdateLocomotionWeight()
|
||||||
|
{
|
||||||
|
float targetWeight = BodySystem.TrackingEnabled && BodySystem.TrackingLocomotionEnabled ? 1.0f : 0.0f;
|
||||||
|
if (Setting_IKLerpSpeed > 0)
|
||||||
|
{
|
||||||
|
_ikWeightLerp = Mathf.Lerp(_ikWeightLerp, targetWeight, Time.deltaTime * Setting_IKLerpSpeed);
|
||||||
|
_locomotionWeight = Mathf.Lerp(_locomotionWeight, targetWeight, Time.deltaTime * Setting_IKLerpSpeed * 2f);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_ikWeightLerp = targetWeight;
|
||||||
|
_locomotionWeight = targetWeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplyBodySystemWeights()
|
||||||
|
{
|
||||||
|
void SetArmWeight(IKSolverVR.Arm arm, bool isTracked)
|
||||||
|
{
|
||||||
|
arm.positionWeight = isTracked ? 1f : 0f;
|
||||||
|
arm.rotationWeight = isTracked ? 1f : 0f;
|
||||||
|
arm.shoulderRotationWeight = isTracked ? 1f : 0f;
|
||||||
|
arm.shoulderTwistWeight = isTracked ? 1f : 0f;
|
||||||
|
}
|
||||||
|
void SetLegWeight(IKSolverVR.Leg leg, bool isTracked)
|
||||||
|
{
|
||||||
|
leg.positionWeight = isTracked ? 1f : 0f;
|
||||||
|
leg.rotationWeight = isTracked ? 1f : 0f;
|
||||||
|
}
|
||||||
|
if (BodySystem.TrackingEnabled)
|
||||||
|
{
|
||||||
|
avatarVRIK.enabled = true;
|
||||||
|
avatarIKSolver.IKPositionWeight = BodySystem.TrackingPositionWeight;
|
||||||
|
avatarIKSolver.locomotion.weight = _locomotionWeight;
|
||||||
|
|
||||||
|
SetArmWeight(avatarIKSolver.leftArm, BodySystem.TrackingLeftArmEnabled && avatarIKSolver.leftArm.target != null);
|
||||||
|
SetArmWeight(avatarIKSolver.rightArm, BodySystem.TrackingRightArmEnabled && avatarIKSolver.rightArm.target != null);
|
||||||
|
SetLegWeight(avatarIKSolver.leftLeg, BodySystem.TrackingLeftLegEnabled && avatarIKSolver.leftLeg.target != null);
|
||||||
|
SetLegWeight(avatarIKSolver.rightLeg, BodySystem.TrackingRightLegEnabled && avatarIKSolver.rightLeg.target != null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
avatarVRIK.enabled = false;
|
||||||
|
avatarIKSolver.IKPositionWeight = 0f;
|
||||||
|
avatarIKSolver.locomotion.weight = 0f;
|
||||||
|
|
||||||
|
SetArmWeight(avatarIKSolver.leftArm, false);
|
||||||
|
SetArmWeight(avatarIKSolver.rightArm, false);
|
||||||
|
SetLegWeight(avatarIKSolver.leftLeg, false);
|
||||||
|
SetLegWeight(avatarIKSolver.rightLeg, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnSetupAvatarDesktop()
|
||||||
|
{
|
||||||
|
if (!Setting_Enabled) return;
|
||||||
|
|
||||||
|
CalibrateDesktopVRIK();
|
||||||
|
ResetDesktopVRIK();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnSetupIKScaling(float scaleDifference)
|
||||||
|
{
|
||||||
|
if (avatarVRIK == null) return false;
|
||||||
|
|
||||||
|
VRIKUtils.ApplyScaleToVRIK
|
||||||
|
(
|
||||||
|
avatarVRIK,
|
||||||
|
_vrikInitialFootDistance,
|
||||||
|
_vrikInitialStepThreshold,
|
||||||
|
_vrikInitialStepHeight,
|
||||||
|
scaleDifference
|
||||||
|
);
|
||||||
|
|
||||||
|
_scaleDifference = scaleDifference;
|
||||||
|
|
||||||
|
avatarIKSolver.Reset();
|
||||||
|
ResetDesktopVRIK();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnPlayerSetupUpdate(bool isEmotePlaying)
|
||||||
|
{
|
||||||
|
if (avatarVRIK == null) return;
|
||||||
|
|
||||||
|
if (isEmotePlaying == _ikEmotePlaying) return;
|
||||||
|
_ikEmotePlaying = isEmotePlaying;
|
||||||
|
|
||||||
|
if (avatarLookAtIK != null)
|
||||||
|
avatarLookAtIK.enabled = !isEmotePlaying;
|
||||||
|
|
||||||
|
// Disable tracking completely while emoting
|
||||||
|
BodySystem.TrackingEnabled = !isEmotePlaying;
|
||||||
|
avatarIKSolver.Reset();
|
||||||
|
ResetDesktopVRIK();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnPlayerSetupResetIk()
|
||||||
|
{
|
||||||
|
if (avatarVRIK == null) return false;
|
||||||
|
|
||||||
|
// Check if PlayerSetup.ResetIk() was called for movement parent
|
||||||
|
CVRMovementParent currentParent = movementSystem._currentParent;
|
||||||
|
if (currentParent == null || currentParent._referencePoint == null) return false;
|
||||||
|
|
||||||
|
// Get current position
|
||||||
|
var currentPosition = currentParent._referencePoint.position;
|
||||||
|
var currentRotation = Quaternion.Euler(0f, currentParent.transform.rotation.eulerAngles.y, 0f);
|
||||||
|
|
||||||
|
// Convert to delta position (how much changed since last frame)
|
||||||
|
var deltaPosition = currentPosition - _movementPosition;
|
||||||
|
var deltaRotation = Quaternion.Inverse(_movementRotation) * currentRotation;
|
||||||
|
|
||||||
|
// desktop pivots from playerlocal transform
|
||||||
|
var platformPivot = transform.position;
|
||||||
|
|
||||||
|
// Add platform motion to IK solver
|
||||||
|
avatarIKSolver.AddPlatformMotion(deltaPosition, deltaRotation, platformPivot);
|
||||||
|
|
||||||
|
// Store for next frame
|
||||||
|
_movementPosition = currentPosition;
|
||||||
|
_movementRotation = currentRotation;
|
||||||
|
|
||||||
|
ResetDesktopVRIK();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnPreSolverUpdate()
|
||||||
|
{
|
||||||
|
// Reset avatar offset
|
||||||
|
avatarTransform.localPosition = Vector3.zero;
|
||||||
|
avatarTransform.localRotation = Quaternion.identity;
|
||||||
|
|
||||||
|
// Don't run during emotes
|
||||||
|
if (_ikEmotePlaying) return;
|
||||||
|
|
||||||
|
// Set plant feet
|
||||||
|
avatarIKSolver.plantFeet = Setting_PlantFeet;
|
||||||
|
|
||||||
|
// Apply custom VRIK solving effects
|
||||||
|
IKBodyLeaningOffset(_ikWeightLerp);
|
||||||
|
IKBodyHeadingOffset(_ikWeightLerp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IKBodyLeaningOffset(float weight)
|
||||||
|
{
|
||||||
|
// Emulate old VRChat hip movement
|
||||||
|
if (Setting_BodyLeanWeight <= 0) return;
|
||||||
|
|
||||||
|
if (Setting_ProneThrusting) weight = 1f;
|
||||||
|
float weightedAngle = Setting_BodyLeanWeight * weight;
|
||||||
|
float angle = _cameraTransform.localEulerAngles.x;
|
||||||
|
angle = angle > 180 ? angle - 360 : angle;
|
||||||
|
Quaternion rotation = Quaternion.AngleAxis(angle * weightedAngle, avatarTransform.right);
|
||||||
|
avatarIKSolver.spine.headRotationOffset *= rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IKBodyHeadingOffset(float weight)
|
||||||
|
{
|
||||||
|
// Make root heading follow within a set limit
|
||||||
|
if (Setting_BodyHeadingLimit <= 0) return;
|
||||||
|
|
||||||
|
float weightedAngleLimit = Setting_BodyHeadingLimit * weight;
|
||||||
|
float deltaAngleRoot = Mathf.DeltaAngle(transform.eulerAngles.y, _ikSimulatedRootAngle);
|
||||||
|
float absDeltaAngleRoot = Mathf.Abs(deltaAngleRoot);
|
||||||
|
|
||||||
|
if (absDeltaAngleRoot > weightedAngleLimit)
|
||||||
|
{
|
||||||
|
deltaAngleRoot = Mathf.Sign(deltaAngleRoot) * weightedAngleLimit;
|
||||||
|
_ikSimulatedRootAngle = Mathf.MoveTowardsAngle(_ikSimulatedRootAngle, transform.eulerAngles.y, absDeltaAngleRoot - weightedAngleLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
avatarIKSolver.spine.rootHeadingOffset = deltaAngleRoot;
|
||||||
|
|
||||||
|
if (Setting_PelvisHeadingWeight > 0)
|
||||||
|
{
|
||||||
|
avatarIKSolver.spine.pelvisRotationOffset *= Quaternion.Euler(0f, deltaAngleRoot * Setting_PelvisHeadingWeight, 0f);
|
||||||
|
avatarIKSolver.spine.chestRotationOffset *= Quaternion.Euler(0f, -deltaAngleRoot * Setting_PelvisHeadingWeight, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Setting_ChestHeadingWeight > 0)
|
||||||
|
{
|
||||||
|
avatarIKSolver.spine.chestRotationOffset *= Quaternion.Euler(0f, deltaAngleRoot * Setting_ChestHeadingWeight, 0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IKResetFootsteps()
|
||||||
|
{
|
||||||
|
// Reset footsteps immediatly to initial
|
||||||
|
if (!Setting_ResetFootsteps) return;
|
||||||
|
|
||||||
|
VRIKUtils.SetFootsteps
|
||||||
|
(
|
||||||
|
avatarVRIK,
|
||||||
|
_vrikInitialFootPosLeft,
|
||||||
|
_vrikInitialFootPosRight,
|
||||||
|
_vrikInitialFootRotLeft,
|
||||||
|
_vrikInitialFootRotRight
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResetDesktopVRIK()
|
||||||
|
{
|
||||||
|
_ikSimulatedRootAngle = transform.eulerAngles.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CalibrateDesktopVRIK()
|
||||||
|
{
|
||||||
|
ScanAvatar();
|
||||||
|
SetupVRIK();
|
||||||
|
CalibrateVRIK();
|
||||||
|
ConfigureVRIK();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScanAvatar()
|
||||||
|
{
|
||||||
|
// Find required avatar components
|
||||||
|
avatarDescriptor = playerSetup._avatarDescriptor;
|
||||||
|
avatarAnimator = playerSetup._animator;
|
||||||
|
avatarTransform = playerSetup._avatar.transform;
|
||||||
|
avatarLookAtIK = playerSetup.lookIK;
|
||||||
|
|
||||||
|
// Get animator layer inticies
|
||||||
|
_animIKPoseLayer = avatarAnimator.GetLayerIndex("IKPose");
|
||||||
|
_animLocomotionLayer = avatarAnimator.GetLayerIndex("Locomotion/Emotes");
|
||||||
|
|
||||||
|
// Dispose and create new _humanPoseHandler
|
||||||
|
_humanPoseHandler?.Dispose();
|
||||||
|
_humanPoseHandler = new HumanPoseHandler(avatarAnimator.avatar, avatarTransform);
|
||||||
|
|
||||||
|
// Get initial human poses
|
||||||
|
_humanPoseHandler.GetHumanPose(ref _humanPose);
|
||||||
|
_humanPoseHandler.GetHumanPose(ref _humanPoseInitial);
|
||||||
|
|
||||||
|
// Dumb fix for rare upload issue
|
||||||
|
_vrikFixTransformsRequired = !avatarAnimator.enabled;
|
||||||
|
|
||||||
|
// Find available HumanoidBodyBones
|
||||||
|
BoneExists.Clear();
|
||||||
|
foreach (HumanBodyBones bone in Enum.GetValues(typeof(HumanBodyBones)))
|
||||||
|
{
|
||||||
|
if (bone != HumanBodyBones.LastBone)
|
||||||
|
{
|
||||||
|
BoneExists.Add(bone, avatarAnimator.GetBoneTransform(bone) != null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetupVRIK()
|
||||||
|
{
|
||||||
|
// Add and configure VRIK
|
||||||
|
avatarVRIK = avatarTransform.AddComponentIfMissing<VRIK>();
|
||||||
|
avatarVRIK.AutoDetectReferences();
|
||||||
|
avatarIKSolver = avatarVRIK.solver;
|
||||||
|
|
||||||
|
VRIKUtils.ConfigureVRIKReferences(avatarVRIK, Setting_UseVRIKToes, Setting_FindUnmappedToes, out bool foundUnmappedToes);
|
||||||
|
|
||||||
|
// Fix animator issue or non-human mapped toes
|
||||||
|
avatarVRIK.fixTransforms = _vrikFixTransformsRequired || foundUnmappedToes;
|
||||||
|
|
||||||
|
// Default solver settings
|
||||||
|
avatarIKSolver.locomotion.weight = 0f;
|
||||||
|
avatarIKSolver.locomotion.angleThreshold = 30f;
|
||||||
|
avatarIKSolver.locomotion.maxLegStretch = 1f;
|
||||||
|
avatarIKSolver.spine.minHeadHeight = 0f;
|
||||||
|
avatarIKSolver.IKPositionWeight = 1f;
|
||||||
|
avatarIKSolver.spine.chestClampWeight = 0f;
|
||||||
|
avatarIKSolver.spine.maintainPelvisPosition = 0f;
|
||||||
|
|
||||||
|
// Body leaning settings
|
||||||
|
avatarIKSolver.spine.neckStiffness = 0.0001f;
|
||||||
|
avatarIKSolver.spine.bodyPosStiffness = 1f;
|
||||||
|
avatarIKSolver.spine.bodyRotStiffness = 0.2f;
|
||||||
|
|
||||||
|
// Disable locomotion
|
||||||
|
avatarIKSolver.locomotion.velocityFactor = 0f;
|
||||||
|
avatarIKSolver.locomotion.maxVelocity = 0f;
|
||||||
|
avatarIKSolver.locomotion.rootSpeed = 1000f;
|
||||||
|
|
||||||
|
// Disable chest rotation by hands
|
||||||
|
avatarIKSolver.spine.rotateChestByHands = 0f;
|
||||||
|
|
||||||
|
// Prioritize LookAtIK
|
||||||
|
avatarIKSolver.spine.headClampWeight = 0.2f;
|
||||||
|
|
||||||
|
// Disable going on tippytoes
|
||||||
|
avatarIKSolver.spine.positionWeight = 0f;
|
||||||
|
avatarIKSolver.spine.rotationWeight = 1f;
|
||||||
|
|
||||||
|
// Set so emotes play properly
|
||||||
|
avatarIKSolver.spine.maxRootAngle = 180f;
|
||||||
|
|
||||||
|
// We disable these ourselves now, as we no longer use BodySystem
|
||||||
|
avatarIKSolver.spine.maintainPelvisPosition = 1f;
|
||||||
|
avatarIKSolver.spine.positionWeight = 0f;
|
||||||
|
avatarIKSolver.spine.pelvisPositionWeight = 0f;
|
||||||
|
avatarIKSolver.leftArm.positionWeight = 0f;
|
||||||
|
avatarIKSolver.leftArm.rotationWeight = 0f;
|
||||||
|
avatarIKSolver.rightArm.positionWeight = 0f;
|
||||||
|
avatarIKSolver.rightArm.rotationWeight = 0f;
|
||||||
|
avatarIKSolver.leftLeg.positionWeight = 0f;
|
||||||
|
avatarIKSolver.leftLeg.rotationWeight = 0f;
|
||||||
|
avatarIKSolver.rightLeg.positionWeight = 0f;
|
||||||
|
avatarIKSolver.rightLeg.rotationWeight = 0f;
|
||||||
|
|
||||||
|
// This is now our master Locomotion weight
|
||||||
|
avatarIKSolver.locomotion.weight = 1f;
|
||||||
|
avatarIKSolver.IKPositionWeight = 1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CalibrateVRIK()
|
||||||
|
{
|
||||||
|
SetAvatarPose(AvatarPose.Default);
|
||||||
|
|
||||||
|
// Calculate bend normals with motorcycle pose
|
||||||
|
VRIKUtils.CalculateKneeBendNormals(avatarVRIK, out _vrikKneeNormalLeft, out _vrikKneeNormalRight);
|
||||||
|
|
||||||
|
SetAvatarPose(AvatarPose.IKPose);
|
||||||
|
|
||||||
|
// Calculate initial IK scaling values with IKPose
|
||||||
|
VRIKUtils.CalculateInitialIKScaling(avatarVRIK, out _vrikInitialFootDistance, out _vrikInitialStepThreshold, out _vrikInitialStepHeight);
|
||||||
|
|
||||||
|
// Calculate initial Footstep positions
|
||||||
|
VRIKUtils.CalculateInitialFootsteps(avatarVRIK, out _vrikInitialFootPosLeft, out _vrikInitialFootPosRight, out _vrikInitialFootRotLeft, out _vrikInitialFootRotRight);
|
||||||
|
|
||||||
|
// Setup HeadIKTarget
|
||||||
|
VRIKUtils.SetupHeadIKTarget(avatarVRIK);
|
||||||
|
|
||||||
|
// Initiate VRIK manually
|
||||||
|
VRIKUtils.InitiateVRIKSolver(avatarVRIK);
|
||||||
|
|
||||||
|
SetAvatarPose(AvatarPose.Initial);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigureVRIK()
|
||||||
|
{
|
||||||
|
// Reset scale diffrence
|
||||||
|
_scaleDifference = 1f;
|
||||||
|
VRIKUtils.ApplyScaleToVRIK
|
||||||
|
(
|
||||||
|
avatarVRIK,
|
||||||
|
_vrikInitialFootDistance,
|
||||||
|
_vrikInitialStepThreshold,
|
||||||
|
_vrikInitialStepHeight,
|
||||||
|
1f
|
||||||
|
);
|
||||||
|
VRIKUtils.ApplyKneeBendNormals(avatarVRIK, _vrikKneeNormalLeft, _vrikKneeNormalRight);
|
||||||
|
avatarVRIK.onPreSolverUpdate.AddListener(new UnityAction(DesktopVRIKSystem.Instance.OnPreSolverUpdate));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetAvatarPose(AvatarPose pose)
|
||||||
|
{
|
||||||
|
switch (pose)
|
||||||
|
{
|
||||||
|
case AvatarPose.Default:
|
||||||
|
SetMusclesToValue(0f);
|
||||||
|
break;
|
||||||
|
case AvatarPose.Initial:
|
||||||
|
if (HasCustomIKPose())
|
||||||
|
{
|
||||||
|
SetCustomLayersWeights(0f, 1f);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_humanPoseHandler.SetHumanPose(ref _humanPoseInitial);
|
||||||
|
break;
|
||||||
|
case AvatarPose.IKPose:
|
||||||
|
if (HasCustomIKPose())
|
||||||
|
{
|
||||||
|
SetCustomLayersWeights(1f, 0f);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SetMusclesToPose(IKPoseMuscles);
|
||||||
|
break;
|
||||||
|
case AvatarPose.TPose:
|
||||||
|
SetMusclesToPose(BodySystem.TPoseMuscles);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasCustomIKPose()
|
||||||
|
{
|
||||||
|
return _animLocomotionLayer != -1 && _animIKPoseLayer != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetCustomLayersWeights(float customIKPoseLayerWeight, float locomotionLayerWeight)
|
||||||
|
{
|
||||||
|
avatarAnimator.SetLayerWeight(_animIKPoseLayer, customIKPoseLayerWeight);
|
||||||
|
avatarAnimator.SetLayerWeight(_animLocomotionLayer, locomotionLayerWeight);
|
||||||
|
avatarAnimator.Update(0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetMusclesToValue(float value)
|
||||||
|
{
|
||||||
|
_humanPoseHandler.GetHumanPose(ref _humanPose);
|
||||||
|
|
||||||
|
for (int i = 0; i < _humanPose.muscles.Length; i++)
|
||||||
|
{
|
||||||
|
ApplyMuscleValue((MuscleIndex)i, value, ref _humanPose.muscles);
|
||||||
|
}
|
||||||
|
|
||||||
|
_humanPose.bodyRotation = Quaternion.identity;
|
||||||
|
_humanPoseHandler.SetHumanPose(ref _humanPose);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetMusclesToPose(float[] muscles)
|
||||||
|
{
|
||||||
|
_humanPoseHandler.GetHumanPose(ref _humanPose);
|
||||||
|
|
||||||
|
for (int i = 0; i < _humanPose.muscles.Length; i++)
|
||||||
|
{
|
||||||
|
ApplyMuscleValue((MuscleIndex)i, muscles[i], ref _humanPose.muscles);
|
||||||
|
}
|
||||||
|
|
||||||
|
_humanPose.bodyRotation = Quaternion.identity;
|
||||||
|
_humanPoseHandler.SetHumanPose(ref _humanPose);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplyMuscleValue(MuscleIndex index, float value, ref float[] muscles)
|
||||||
|
{
|
||||||
|
if (BoneExists.ContainsKey(IKSystem.MusclesToHumanBodyBones[(int)index]) && BoneExists[IKSystem.MusclesToHumanBodyBones[(int)index]])
|
||||||
|
{
|
||||||
|
muscles[(int)index] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
63
DesktopVRIK/HarmonyPatches.cs
Normal file
63
DesktopVRIK/HarmonyPatches.cs
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
using ABI_RC.Core.Player;
|
||||||
|
using HarmonyLib;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
/**
|
||||||
|
|
||||||
|
The process of calibrating VRIK is fucking painful.
|
||||||
|
|
||||||
|
Avatars of Note:
|
||||||
|
TurtleNeck Ferret- close feet, far shoulders, nonideal rig.
|
||||||
|
Space Robot Kyle- the worst bone rolls on the planet, tpose/headikcalibration fixed it mostly... ish.
|
||||||
|
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.
|
||||||
|
Small Cheese- Emotes are angled wrong due to maxRootAngle..???
|
||||||
|
|
||||||
|
Most other avatars play just fine.
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
namespace NAK.Melons.DesktopVRIK.HarmonyPatches;
|
||||||
|
|
||||||
|
class PlayerSetupPatches
|
||||||
|
{
|
||||||
|
[HarmonyPostfix]
|
||||||
|
[HarmonyPatch(typeof(PlayerSetup), "Start")]
|
||||||
|
static void Postfix_PlayerSetup_Start(ref PlayerSetup __instance)
|
||||||
|
{
|
||||||
|
__instance.gameObject.AddComponent<DesktopVRIKSystem>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPostfix]
|
||||||
|
[HarmonyPatch(typeof(PlayerSetup), "SetupAvatarDesktop")]
|
||||||
|
static void Postfix_PlayerSetup_SetupAvatarDesktop(ref Animator ____animator)
|
||||||
|
{
|
||||||
|
if (____animator != null && ____animator.avatar != null && ____animator.avatar.isHuman)
|
||||||
|
{
|
||||||
|
DesktopVRIKSystem.Instance?.OnSetupAvatarDesktop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPostfix]
|
||||||
|
[HarmonyPatch(typeof(PlayerSetup), "Update")]
|
||||||
|
static void Postfix_PlayerSetup_Update(ref bool ____emotePlaying)
|
||||||
|
{
|
||||||
|
DesktopVRIKSystem.Instance?.OnPlayerSetupUpdate(____emotePlaying);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(typeof(PlayerSetup), "SetupIKScaling")]
|
||||||
|
private static bool Prefix_PlayerSetup_SetupIKScaling(float height, ref Vector3 ___scaleDifference)
|
||||||
|
{
|
||||||
|
return !(bool)DesktopVRIKSystem.Instance?.OnSetupIKScaling(1f + ___scaleDifference.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(typeof(PlayerSetup), "ResetIk")]
|
||||||
|
static bool Prefix_PlayerSetup_ResetIk()
|
||||||
|
{
|
||||||
|
return !(bool)DesktopVRIKSystem.Instance?.OnPlayerSetupResetIk();
|
||||||
|
}
|
||||||
|
}
|
55
DesktopVRIK/Integrations/BTKUIAddon.cs
Normal file
55
DesktopVRIK/Integrations/BTKUIAddon.cs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
using BTKUILib;
|
||||||
|
using BTKUILib.UIObjects;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace NAK.Melons.DesktopVRIK;
|
||||||
|
|
||||||
|
public static class BTKUIAddon
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
public static void Init()
|
||||||
|
{
|
||||||
|
//Add myself to the Misc Menu
|
||||||
|
|
||||||
|
Page miscPage = QuickMenuAPI.MiscTabPage;
|
||||||
|
Category miscCategory = miscPage.AddCategory(DesktopVRIKMod.SettingsCategory);
|
||||||
|
|
||||||
|
AddMelonToggle(ref miscCategory, DesktopVRIKMod.EntryEnabled);
|
||||||
|
|
||||||
|
//Add my own page to not clog up Misc Menu
|
||||||
|
Page desktopVRIKPage = miscCategory.AddPage("DesktopVRIK Settings", "", "Configure the settings for DesktopVRIK.", "DesktopVRIK");
|
||||||
|
desktopVRIKPage.MenuTitle = "DesktopVRIK Settings";
|
||||||
|
Category desktopVRIKCategory = desktopVRIKPage.AddCategory(DesktopVRIKMod.SettingsCategory);
|
||||||
|
|
||||||
|
// General Settings
|
||||||
|
AddMelonToggle(ref desktopVRIKCategory, DesktopVRIKMod.EntryPlantFeet);
|
||||||
|
|
||||||
|
// Calibration Settings
|
||||||
|
AddMelonToggle(ref desktopVRIKCategory, DesktopVRIKMod.EntryUseVRIKToes);
|
||||||
|
AddMelonToggle(ref desktopVRIKCategory, DesktopVRIKMod.EntryFindUnmappedToes);
|
||||||
|
|
||||||
|
// Fine-tuning Settings
|
||||||
|
AddMelonToggle(ref desktopVRIKCategory, DesktopVRIKMod.EntryResetFootstepsOnIdle);
|
||||||
|
|
||||||
|
// Body Leaning Weight
|
||||||
|
AddMelonSlider(ref desktopVRIKPage, DesktopVRIKMod.EntryBodyLeanWeight, 0, 1f, 1);
|
||||||
|
|
||||||
|
// Max Root Heading Limit & Weights
|
||||||
|
AddMelonSlider(ref desktopVRIKPage, DesktopVRIKMod.EntryBodyHeadingLimit, 0, 90f, 0);
|
||||||
|
AddMelonSlider(ref desktopVRIKPage, DesktopVRIKMod.EntryPelvisHeadingWeight, 0, 1f, 1);
|
||||||
|
AddMelonSlider(ref desktopVRIKPage, DesktopVRIKMod.EntryChestHeadingWeight, 0, 1f, 1);
|
||||||
|
|
||||||
|
// Lerp Speed
|
||||||
|
AddMelonSlider(ref desktopVRIKPage, DesktopVRIKMod.EntryIKLerpSpeed, 0, 20f, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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, decimalPlaces).OnValueUpdated += f => entry.Value = f;
|
||||||
|
}
|
||||||
|
}
|
118
DesktopVRIK/Main.cs
Normal file
118
DesktopVRIK/Main.cs
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
using MelonLoader;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NAK.Melons.DesktopVRIK;
|
||||||
|
|
||||||
|
public class DesktopVRIKMod : MelonMod
|
||||||
|
{
|
||||||
|
internal static MelonLogger.Instance Logger;
|
||||||
|
public const string SettingsCategory = "DesktopVRIK";
|
||||||
|
public static readonly MelonPreferences_Category CategoryDesktopVRIK = MelonPreferences.CreateCategory(SettingsCategory);
|
||||||
|
|
||||||
|
public static readonly MelonPreferences_Entry<bool> EntryEnabled =
|
||||||
|
CategoryDesktopVRIK.CreateEntry("Enabled", true, description: "Toggle DesktopVRIK entirely. Requires avatar reload.");
|
||||||
|
|
||||||
|
public static readonly MelonPreferences_Entry<bool> EntryPlantFeet =
|
||||||
|
CategoryDesktopVRIK.CreateEntry("Enforce Plant Feet", true, description: "Forces VRIK Plant Feet enabled to prevent hovering when stopping movement.");
|
||||||
|
|
||||||
|
public static readonly MelonPreferences_Entry<bool> EntryResetFootstepsOnIdle =
|
||||||
|
CategoryDesktopVRIK.CreateEntry("Reset Footsteps on Idle", true, description: "Determins if the Locomotion Footsteps will be reset to their calibration position when entering idle.");
|
||||||
|
|
||||||
|
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 static readonly MelonPreferences_Entry<float> EntryIKLerpSpeed =
|
||||||
|
CategoryDesktopVRIK.CreateEntry("IK Lerp Speed", 10f, description: "Determines fast the IK & Locomotion weights blend after entering idle. Set to 0 to disable.");
|
||||||
|
|
||||||
|
public static readonly MelonPreferences_Entry<bool> EntryProneThrusting =
|
||||||
|
CategoryDesktopVRIK.CreateEntry("Prone Thrusting", false, description: "Allows Body Lean Weight to take affect while crouched or prone.");
|
||||||
|
|
||||||
|
public static readonly MelonPreferences_Entry<bool> EntryIntegrationAMT =
|
||||||
|
CategoryDesktopVRIK.CreateEntry("AMT Integration", true, description: "Relies on AvatarMotionTweaker to handle VRIK Locomotion weights if available.");
|
||||||
|
|
||||||
|
public static bool integration_AMT = false;
|
||||||
|
|
||||||
|
public override void OnInitializeMelon()
|
||||||
|
{
|
||||||
|
Logger = LoggerInstance;
|
||||||
|
|
||||||
|
CategoryDesktopVRIK.Entries.ForEach(e => e.OnEntryValueChangedUntyped.Subscribe(OnUpdateSettings));
|
||||||
|
|
||||||
|
ApplyPatches(typeof(HarmonyPatches.PlayerSetupPatches));
|
||||||
|
|
||||||
|
//BTKUILib Misc Tab
|
||||||
|
if (MelonMod.RegisteredMelons.Any(it => it.Info.Name == "BTKUILib"))
|
||||||
|
{
|
||||||
|
Logger.Msg("Initializing BTKUILib support.");
|
||||||
|
BTKUIAddon.Init();
|
||||||
|
}
|
||||||
|
//AvatarMotionTweaker Handling
|
||||||
|
if (MelonMod.RegisteredMelons.Any(it => it.Info.Name == "AvatarMotionTweaker"))
|
||||||
|
{
|
||||||
|
Logger.Msg("AvatarMotionTweaker was found. Relying on it to handle VRIK locomotion.");
|
||||||
|
integration_AMT = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Msg("AvatarMotionTweaker was not found. Using built-in VRIK locomotion handling.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void UpdateAllSettings()
|
||||||
|
{
|
||||||
|
if (!DesktopVRIKSystem.Instance) return;
|
||||||
|
// DesktopVRIK Settings
|
||||||
|
DesktopVRIKSystem.Instance.Setting_Enabled = EntryEnabled.Value;
|
||||||
|
DesktopVRIKSystem.Instance.Setting_PlantFeet = EntryPlantFeet.Value;
|
||||||
|
DesktopVRIKSystem.Instance.Setting_ResetFootsteps = EntryResetFootstepsOnIdle.Value;
|
||||||
|
|
||||||
|
DesktopVRIKSystem.Instance.Setting_BodyLeanWeight = Mathf.Clamp01(EntryBodyLeanWeight.Value);
|
||||||
|
DesktopVRIKSystem.Instance.Setting_BodyHeadingLimit = Mathf.Clamp(EntryBodyHeadingLimit.Value, 0f, 90f);
|
||||||
|
DesktopVRIKSystem.Instance.Setting_PelvisHeadingWeight = (1f - Mathf.Clamp01(EntryPelvisHeadingWeight.Value));
|
||||||
|
DesktopVRIKSystem.Instance.Setting_ChestHeadingWeight = (1f - Mathf.Clamp01(EntryChestHeadingWeight.Value));
|
||||||
|
DesktopVRIKSystem.Instance.Setting_ChestHeadingWeight = (1f - Mathf.Clamp01(EntryChestHeadingWeight.Value));
|
||||||
|
DesktopVRIKSystem.Instance.Setting_IKLerpSpeed = Mathf.Clamp(EntryIKLerpSpeed.Value, 0f, 20f);
|
||||||
|
|
||||||
|
// Calibration Settings
|
||||||
|
DesktopVRIKSystem.Instance.Setting_UseVRIKToes = EntryUseVRIKToes.Value;
|
||||||
|
DesktopVRIKSystem.Instance.Setting_FindUnmappedToes = EntryFindUnmappedToes.Value;
|
||||||
|
|
||||||
|
// Fine-tuning Settings
|
||||||
|
DesktopVRIKSystem.Instance.Setting_ResetFootsteps = EntryResetFootstepsOnIdle.Value;
|
||||||
|
|
||||||
|
// Integration Settings
|
||||||
|
DesktopVRIKSystem.Instance.Setting_IntegrationAMT = EntryIntegrationAMT.Value && integration_AMT;
|
||||||
|
|
||||||
|
// Funny Settings
|
||||||
|
DesktopVRIKSystem.Instance.Setting_ProneThrusting = EntryProneThrusting.Value;
|
||||||
|
}
|
||||||
|
void OnUpdateSettings(object arg1, object arg2) => UpdateAllSettings();
|
||||||
|
|
||||||
|
void ApplyPatches(Type type)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
HarmonyInstance.PatchAll(type);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Msg($"Failed while patching {type.Name}!");
|
||||||
|
Logger.Error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
DesktopVRIK/Properties/AssemblyInfo.cs
Normal file
31
DesktopVRIK/Properties/AssemblyInfo.cs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
using MelonLoader;
|
||||||
|
using NAK.Melons.DesktopVRIK.Properties;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
|
||||||
|
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
|
||||||
|
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
|
||||||
|
[assembly: AssemblyTitle(nameof(NAK.Melons.DesktopVRIK))]
|
||||||
|
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
|
||||||
|
[assembly: AssemblyProduct(nameof(NAK.Melons.DesktopVRIK))]
|
||||||
|
|
||||||
|
[assembly: MelonInfo(
|
||||||
|
typeof(NAK.Melons.DesktopVRIK.DesktopVRIKMod),
|
||||||
|
nameof(NAK.Melons.DesktopVRIK),
|
||||||
|
AssemblyInfoParams.Version,
|
||||||
|
AssemblyInfoParams.Author,
|
||||||
|
downloadLink: "https://github.com/NotAKidOnSteam/DesktopVRIK"
|
||||||
|
)]
|
||||||
|
|
||||||
|
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
|
||||||
|
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
|
||||||
|
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
|
||||||
|
[assembly: MelonOptionalDependencies("BTKUILib")]
|
||||||
|
[assembly: HarmonyDontPatchAll]
|
||||||
|
|
||||||
|
namespace NAK.Melons.DesktopVRIK.Properties;
|
||||||
|
internal static class AssemblyInfoParams
|
||||||
|
{
|
||||||
|
public const string Version = "4.1.5";
|
||||||
|
public const string Author = "NotAKidoS";
|
||||||
|
}
|
217
DesktopVRIK/VRIKUtils.cs
Normal file
217
DesktopVRIK/VRIKUtils.cs
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
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 easier math
|
||||||
|
vrik.solver.leftLeg.bendToTargetWeight = 0f;
|
||||||
|
vrik.solver.rightLeg.bendToTargetWeight = 0f;
|
||||||
|
|
||||||
|
var pelvis_localRotationInverse = Quaternion.Inverse(vrik.references.pelvis.localRotation);
|
||||||
|
vrik.solver.leftLeg.bendNormalRelToPelvis = pelvis_localRotationInverse * leftKneeNormal;
|
||||||
|
vrik.solver.rightLeg.bendNormalRelToPelvis = pelvis_localRotationInverse * rightKneeNormal;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 CalculateInitialFootsteps(VRIK vrik, out Vector3 initialFootPosLeft, out Vector3 initialFootPosRight, out Quaternion initialFootRotLeft, out Quaternion initialFootRotRight)
|
||||||
|
{
|
||||||
|
Transform root = vrik.references.root;
|
||||||
|
Transform leftFoot = vrik.references.leftFoot;
|
||||||
|
Transform rightFoot = vrik.references.rightFoot;
|
||||||
|
|
||||||
|
// Calculate the world rotation of the root bone at the current frame
|
||||||
|
Quaternion rootWorldRot = root.rotation;
|
||||||
|
|
||||||
|
// Calculate the world rotation of the left and right feet relative to the root bone
|
||||||
|
initialFootPosLeft = root.InverseTransformPoint(leftFoot.position);
|
||||||
|
initialFootPosRight = root.InverseTransformPoint(rightFoot.position);
|
||||||
|
initialFootRotLeft = Quaternion.Inverse(rootWorldRot) * leftFoot.rotation;
|
||||||
|
initialFootRotRight = Quaternion.Inverse(rootWorldRot) * rightFoot.rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetFootsteps(VRIK vrik, Vector3 footPosLeft, Vector3 footPosRight, Quaternion footRotLeft, Quaternion footRotRight)
|
||||||
|
{
|
||||||
|
var locomotionSolver = vrik.solver.locomotion;
|
||||||
|
|
||||||
|
var footsteps = locomotionSolver.footsteps;
|
||||||
|
var footstepLeft = footsteps[0];
|
||||||
|
var footstepRight = footsteps[1];
|
||||||
|
|
||||||
|
var rootWorldRot = vrik.references.root.rotation;
|
||||||
|
footstepLeft.Reset(rootWorldRot, vrik.transform.TransformPoint(footPosLeft), rootWorldRot * footRotLeft);
|
||||||
|
footstepRight.Reset(rootWorldRot, vrik.transform.TransformPoint(footPosRight), rootWorldRot * footRotRight);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetupHeadIKTarget(VRIK vrik)
|
||||||
|
{
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
24
DesktopVRIK/format.json
Normal file
24
DesktopVRIK/format.json
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"_id": 117,
|
||||||
|
"name": "DesktopVRIK",
|
||||||
|
"modversion": "4.1.5",
|
||||||
|
"gameversion": "2022r170",
|
||||||
|
"loaderversion": "0.5.7",
|
||||||
|
"modtype": "Mod",
|
||||||
|
"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\nOptional BTKUILib integration.",
|
||||||
|
"searchtags": [
|
||||||
|
"desktop",
|
||||||
|
"vrik",
|
||||||
|
"ik",
|
||||||
|
"feet",
|
||||||
|
"fish"
|
||||||
|
],
|
||||||
|
"requirements": [
|
||||||
|
"BTKUILib"
|
||||||
|
],
|
||||||
|
"downloadlink": "https://github.com/NotAKidOnSteam/DesktopVRIK/releases/download/v4.1.5/DesktopVRIK.dll",
|
||||||
|
"sourcelink": "https://github.com/NotAKidOnSteam/DesktopVRIK/",
|
||||||
|
"changelog": "- No longer requires AvatarMotionTweaker.\n- No longer piggybacks on IKSystem/PlayerSetup.\n- Tweaks to Locomotion & IKSolver weight blending.\n\n DesktopVRIK will now handle VRIK Locomotion weight instead of relying on CVR & AMT to handle it. This means LeapMotionExtension, PickupArmMovement, and CVRLimbGrabber will now work while in crouch/prone.",
|
||||||
|
"embedcolor": "9b59b6"
|
||||||
|
}
|
22
README.md
22
README.md
|
@ -65,6 +65,27 @@ There is no native method to clear notifications, so I force an immediate notifi
|
||||||
* Enter Drowsy Time - How many minutes without movement until enter drowsy mode.
|
* Enter Drowsy Time - How many minutes without movement until enter drowsy mode.
|
||||||
* Enter Sleep Time - How many seconds without movement until enter sleep mode.
|
* Enter Sleep Time - How many seconds without movement until enter sleep mode.
|
||||||
|
|
||||||
|
# DesktopVRIK
|
||||||
|
Adds VRIK to Desktop ChilloutVR avatars. No longer will you be a liveless sliding statue~!
|
||||||
|
|
||||||
|
(adds the small feet stepping when looking around on Desktop)
|
||||||
|
|
||||||
|
https://user-images.githubusercontent.com/37721153/221870123-fbe4f5e8-8d6e-4a43-aa5e-f2188e6491a9.mp4
|
||||||
|
|
||||||
|
## 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:
|
||||||
|
https://feedback.abinteractive.net/p/desktop-feet-ik-for-avatars
|
||||||
|
|
||||||
|
https://feedback.abinteractive.net/p/pivot-desktop-camera-with-head
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Here is the block of text where I tell you this mod is not affiliated or endorsed by ABI.
|
Here is the block of text where I tell you this mod is not affiliated or endorsed by ABI.
|
||||||
|
@ -75,4 +96,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.
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue