Skip to content
This repository has been archived by the owner on May 15, 2023. It is now read-only.

Commit

Permalink
IKT: hide inapplicable settings, add new knee positioner
Browse files Browse the repository at this point in the history
  • Loading branch information
knah committed Jun 17, 2022
1 parent 87c7bee commit 72bd9f0
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 4 deletions.
6 changes: 6 additions & 0 deletions IKTweaks/CachedSolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ public struct CachedSolver
public readonly IKSolverVR.Arm RightArm;
public readonly IKSolverVR.Locomotion Locomotion;

public readonly IKSolverVR.VirtualBone[] LeftLegBones;
public readonly IKSolverVR.VirtualBone[] RightLegBones;

public CachedSolver(IKSolverVR solver)
{
Solver = solver;
Expand All @@ -21,6 +24,9 @@ public CachedSolver(IKSolverVR solver)
RightArm = solver.rightArm;
RightLeg = solver.rightLeg;
Locomotion = solver.locomotion;

LeftLegBones = LeftLeg.bones;
RightLegBones = RightLeg.bones;
}
}
}
2 changes: 1 addition & 1 deletion IKTweaks/IKTweaks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>latest</LangVersion>
<VrcReferences>true</VrcReferences>
<AssemblyVersion>2.0.0</AssemblyVersion>
<AssemblyVersion>2.1.0</AssemblyVersion>
<UsesNativePatches>true</UsesNativePatches>
</PropertyGroup>
<ItemGroup>
Expand Down
29 changes: 28 additions & 1 deletion IKTweaks/IKTweaksMod.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
Expand All @@ -9,7 +10,7 @@
using UIExpansionKit.Components;
using UnityEngine;

[assembly:MelonInfo(typeof(IKTweaksMod), "IKTweaks", "2.0.0", "knah", "https://github.com/knah/VRCMods")]
[assembly:MelonInfo(typeof(IKTweaksMod), "IKTweaks", "2.1.0", "knah", "https://github.com/knah/VRCMods")]
[assembly:MelonGame("VRChat", "VRChat")]
[assembly:MelonOptionalDependencies("UIExpansionKit")]

Expand All @@ -33,6 +34,32 @@ public override void OnApplicationStart()
private static void AddUixActions()
{
ExpansionKitApi.GetExpandedMenu(ExpandedMenu.SettingsMenu).AddSimpleButton("More IKTweaks...", ShowIKTweaksMenu);

var settingNameList = new[]
{
nameof(IkTweaksSettings.StraightSpineAngle),
nameof(IkTweaksSettings.StraightSpinePower),
nameof(IkTweaksSettings.DoHipShifting),
nameof(IkTweaksSettings.PreStraightenSpine),
nameof(IkTweaksSettings.StraightenNeck),
nameof(IkTweaksSettings.PinHipRotation),
nameof(IkTweaksSettings.NeckPriority),
nameof(IkTweaksSettings.SpineRelaxIterations),
nameof(IkTweaksSettings.MaxNeckAngleBack),
nameof(IkTweaksSettings.MaxNeckAngleFwd),
nameof(IkTweaksSettings.MaxSpineAngleBack),
nameof(IkTweaksSettings.MaxSpineAngleFwd),
};
var updateCallbacks = new List<Action>();

foreach (var s in settingNameList)
updateCallbacks.Add(ExpansionKitApi.RegisterSettingsVisibilityCallback(
IkTweaksSettings.IkTweaksCategory, s, () => IkTweaksSettings.FullBodyVrIk.Value));

IkTweaksSettings.FullBodyVrIk.OnValueChangedUntyped += () =>
{
foreach (var it in updateCallbacks) it();
};
}

private static void ShowIKTweaksMenu()
Expand Down
13 changes: 13 additions & 0 deletions IKTweaks/IkTweaksSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ internal static void RegisterSettings()
NoWallFreeze = category.CreateEntry(nameof(NoWallFreeze), true, "Don't freeze head/hands inside walls");

DisableElbowAvoidance = category.CreateEntry(nameof(DisableElbowAvoidance), false, "Disable IK2 elbow-chest avoidance");
IktKneeMode = category.CreateEntry(nameof(IktKneeMode), KneeBendNormalMode.Natural, "Knee angle mode (with no trackers)");

ExperimentalSettingOne = category.CreateEntry(nameof(ExperimentalSettingOne), false, "Experimental setting", dont_save_default: true, is_hidden: true);

Expand Down Expand Up @@ -73,6 +74,8 @@ internal static void RegisterSettings()
public static MelonPreferences_Entry<bool> Unrestrict3PointHeadRotation;
public static MelonPreferences_Entry<bool> NoWallFreeze;
public static MelonPreferences_Entry<bool> DisableElbowAvoidance;
public static MelonPreferences_Entry<KneeBendNormalMode> IktKneeMode;

public static MelonPreferences_Entry<bool> ExperimentalSettingOne;

public static MelonPreferences_Entry<Vector3> HandAngleOffset;
Expand All @@ -97,4 +100,14 @@ public enum IgnoreAnimationsMode
[Description("Ignore all (always slide around)")]
All = HandAndHead | Others
}

public enum KneeBendNormalMode
{
[Description("IK2 Default")]
Default,
[Description("IKT Classic")]
Classic,
[Description("IKT Natural")]
Natural
}
}
3 changes: 3 additions & 0 deletions IKTweaks/Math.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ public static Float3 ProjectOnPlane(Float3 a, Float3 planeNormal)
planeNormal = planeNormal.normalized;
return a - planeNormal * Dot(planeNormal, a);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Float3 Lerp(Float3 a, Float3 b, float t) => a * (1 - t) + b * t;

public override string ToString()
{
Expand Down
57 changes: 56 additions & 1 deletion IKTweaks/VrIkHandling.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public static void HookVrIkInit()
NativePatchUtils.NativePatch(typeof(IKSolverVR.Spine).GetMethod(nameof(IKSolverVR.Spine.Solve))!, out ourOriginalSolveSpine, SolveSpinePatch);

NativePatchUtils.NativePatch(typeof(IKSolverVR.Arm).GetMethod(nameof(IKSolverVR.Arm.VrcAvoidElbowClipping))!, out ourOriginalElbowClipping, ElbowClippingPatch);

NativePatchUtils.NativePatch(typeof(IKSolverVR.Leg).GetMethod(nameof(IKSolverVR.Leg.Solve))!, out ourOriginalLegSolve, LegSolvePatch);
}

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
Expand All @@ -46,6 +48,7 @@ public static void HookVrIkInit()
private static VoidDelegate ourOriginalSolverVrLateUpdate;
private static VoidDelegate ourOriginalSolverUpdate;
private static BoolDelegate ourOriginalElbowClipping;
private static BoolDelegate ourOriginalLegSolve;

private static VoidDelegate ourOriginalSolvePelvis;
private static SolveSpineDelegate ourOriginalSolveSpine;
Expand Down Expand Up @@ -141,10 +144,62 @@ private static void SolveSpinePatch(IntPtr thisPtr, IntPtr rootBonePtr, IntPtr l

private static void ElbowClippingPatch(IntPtr thisPtr, byte boolValue, IntPtr methodPtr)
{
if (thisPtr != ourCachedSolver.LeftArm?.Pointer && thisPtr != ourCachedSolver.RightArm?.Pointer || !IkTweaksSettings.DisableElbowAvoidance.Value)
if (!IkTweaksSettings.DisableElbowAvoidance.Value || thisPtr != ourCachedSolver.LeftArm?.Pointer && thisPtr != ourCachedSolver.RightArm?.Pointer)
ourOriginalElbowClipping(thisPtr, boolValue, methodPtr);
}

private static void LegSolvePatch(IntPtr thisPtr, byte boolValue, IntPtr methodPtr)
{
var kneeMode = IkTweaksSettings.IktKneeMode.Value;

if (kneeMode != KneeBendNormalMode.Default && ourLastIkController.field_Private_IkType_0 == IkController.IkType.SixPoint && (thisPtr == ourCachedSolver.LeftLeg.Pointer || thisPtr == ourCachedSolver.RightLeg.Pointer))
{
var isLeftLeg = thisPtr == ourCachedSolver.LeftLeg.Pointer;
var currentLeg = isLeftLeg
? ourCachedSolver.LeftLeg
: ourCachedSolver.RightLeg;

var weight = currentLeg.bendGoalWeight;
var oldUseKneeTarget = currentLeg.vrcUseKneeTarget;
if (weight < 1)
{
Float3 newNormal;
var currentLegBones = isLeftLeg ? ourCachedSolver.LeftLegBones : ourCachedSolver.RightLegBones;
if (kneeMode == KneeBendNormalMode.Classic)
{
newNormal = (Quat)currentLeg.IKRotation * currentLeg.vrcBendNormalRelToFoot;
}
else
{
var baseFootUpDirectionG = currentLegBones[1].solverPosition - (Float3)currentLegBones[2].solverPosition;
var baseKneeBendGoalDirectionRelToFoot = (Quat)currentLeg.rotation * Quat.Inverse(currentLegBones[2].solverRotation) * baseFootUpDirectionG;
var legRootToFoot = (Float3)currentLeg.position - (Float3)currentLegBones[0].solverPosition;
var newNormalCandidate = Float3.Cross(baseKneeBendGoalDirectionRelToFoot.normalized, legRootToFoot.normalized);

// mix between "dumb" normal and "smart" one based on cross product length;
// longer cross = presumably higher precision so mix more of "smart" normal in
var newNormalLength = newNormalCandidate.magnitude;
var baseNormal = (Quat)currentLeg.IKRotation * currentLeg.vrcBendNormalRelToFoot;
newNormal = Float3.Lerp(baseNormal.normalized, newNormalCandidate, newNormalLength).normalized;
}

// this neat little thing seemingly makes VRC recalculate the normal in some weird way
// but also... we set it ourself? whatever
currentLeg.vrcUseKneeTarget = false;

currentLeg.bendNormal = Float3.Lerp(newNormal, currentLeg.bendNormal, weight);
}

ourOriginalLegSolve(thisPtr, boolValue, methodPtr);

currentLeg.vrcUseKneeTarget = oldUseKneeTarget;

return;
}

ourOriginalLegSolve(thisPtr, boolValue, methodPtr);
}

private static byte VrIkInitReplacement(IntPtr thisPtr, IntPtr vrcAnimController, IntPtr animatorPtr, IntPtr playerPtr, byte isLocalPlayer, IntPtr nativeMethod)
{
var __instance = new VRCVrIkController(thisPtr);
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,9 +232,10 @@ Features:
It's recommended to use a normal humanoid rig without any rig hacks (so no neck fix, no FBT fix, no inverted hip, no zero-length spine bones).

**Canny tickets**:
* [Add an option to disable locomotion animations on all avatars](https://feedback.vrchat.com/vrchat-ik-20/p/add-an-option-to-disable-locomotion-animations-on-all-avatars)
* [Stop locking avatars in place when players walk through walls](https://feedback.vrchat.com/vrchat-ik-20/p/stop-locking-avatars-in-place-when-players-walk-through-walls)
* [Provide in-depth IK configuration options for enthusiasts](https://feedback.vrchat.com/vrchat-ik-20/p/provide-in-depth-ik-configuration-options-for-enthusiasts)
* [Add calibration mirror or something similar.](https://vrchat.canny.io/vrchat-ik-20/p/feature-request-add-calibration-mirror-or-something-similar)
* [Add support for one-handed calibration](https://vrchat.canny.io/vrchat-ik-20/p/featureaccessibility-add-support-for-one-handed-calibration)
* ... and the rest of IK 2.0 Beta board (assuming it stays)

### Brief settings description
Expand All @@ -255,6 +256,7 @@ Given that this mod is still work in progress, these are subject to change.
* Allow more head rotation in 3/4-point tracking - ever tried to look up when close to the ground in 3/4-point tracking? Now you can, even if it breaks your neck.
* Don't freeze head/hands inside walls - prevents your hands/head from freezing if your head gets inside a wall.
* Disable IK2 elbow-chest avoidance - brings behavior of elbows closer to old 3-point IK, solving some issues with elbows spazzing out in some poses
* Knee angle mode - affects how knee angle is chosen when you have leg trackers but not knee trackers. "IKT classic" should be the old knee positioner, which is... Bad. "IKT Natural" attempts to bend knees into a more relaxed pose.
* Hand angles/offsets (found in VRChat Settings menu -> left blue panel -> More IKTweaks -> Adjust hand angles/offsets) - you can configure how avatar hands are positioned relative to controllers. Defaults were tuned for Index controllers, but should be applicable to most other controllers too.

## JoinNotifier
Expand Down
2 changes: 2 additions & 0 deletions ReleaseChangelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ Read the [Malicious Mods and you](https://github.com/knah/VRCMods/blob/master/Ma
Changes:
* Advanced Safety: updated BundleVerifier to catch more types of corrupted bundles
* FavCat: fixed some worlds not appearing in favorites. Re-fetch favorites via "More FavCat..." menu and restart the game to fix already-missing ones.
* IKTweaks: if IKT spine solver is disabled, settings relevant only to it will be hidden
* IKTweaks: added an option to change knee angles when not using knee trackers (enabled by default)
* ScaleGoesBrr: fixed shoulders moving wrong if shoulder trackers are in use

**USE IT AT YOUR OWN RISK.** Modding the client is against VRChat ToS. I am not responsible for any bans or other punishments you may get by using these mods!

0 comments on commit 72bd9f0

Please sign in to comment.