Skip to content

Commit

Permalink
[EyeMovementFix] Added custom limits for the avatar's eye movement.
Browse files Browse the repository at this point in the history
  • Loading branch information
kafeijao committed Jan 28, 2023
1 parent 0f26f94 commit 9222560
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 15 deletions.
85 changes: 73 additions & 12 deletions EyeMovementFix/BetterEyeController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
using ABI_RC.Core.Player.AvatarTracking.Local;
using ABI_RC.Core.Player.AvatarTracking.Remote;
using ABI.CCK.Components;
using EyeMovementFix.CCK;
using HarmonyLib;
using MelonLoader;
using UnityEngine;

#if DEBUG
using CCK.Debugger.Components;
using CCKDebugger = CCK.Debugger;
#endif

namespace EyeMovementFix;
Expand All @@ -20,8 +22,7 @@ public class BetterEyeController : MonoBehaviour {
public static readonly Dictionary<CVRAvatar, Quaternion> OriginalLeftEyeLocalRotation = new();
public static readonly Dictionary<CVRAvatar, Quaternion> OriginalRightEyeLocalRotation = new();

private const float MaxVerticalAngle = 25f;
private const float MaxHorizontalAngle = 25f;
private EyeRotationLimits _eyeRotationLimits;

private CVRAvatar _avatar;

Expand Down Expand Up @@ -148,14 +149,17 @@ private static void CreateFake(BetterEye eye, Transform viewpoint, Transform hea
#endif
}

private static void Initialize(CVRAvatar avatar, CVREyeController eyeController, Transform head, Transform leftRealEye, Transform rightRealEye) {
private static void Initialize(CVRAvatar avatar, Animator animator, CVREyeController eyeController, Transform head, Transform leftRealEye, Transform rightRealEye) {

// Initialize our better eye controller
var betterEyeController = eyeController.gameObject.AddComponent<BetterEyeController>();
BetterControllers[eyeController] = betterEyeController;
betterEyeController.cvrEyeController = eyeController;
betterEyeController._avatar = avatar;

// Fetch and load the eye rotation limits
LoadRotationLimits(avatar, animator, betterEyeController);

// Todo: Improve this crap
if (eyeController.isLocal) {
var localHeadPoint = Traverse.Create(PlayerSetup.Instance).Field<LocalHeadPoint>("_viewPoint").Value;
Expand Down Expand Up @@ -208,6 +212,50 @@ private static void Initialize(CVRAvatar avatar, CVREyeController eyeController,
}
}

private static void LoadRotationLimits(CVRAvatar avatar, Animator animator, BetterEyeController betterEyeController) {

// Look for the EyeRotationLimit Script
betterEyeController._eyeRotationLimits = avatar.GetComponent<EyeRotationLimits>();

#if DEBUG
if (betterEyeController._eyeRotationLimits != null) MelonLogger.Msg($"Found avatar EyeRotationLimits in the avatar!");
#endif

if (betterEyeController._eyeRotationLimits != null) return;

// If there is no script, created with defaults (25)
betterEyeController._eyeRotationLimits = avatar.gameObject.AddComponent<EyeRotationLimits>();

// If there are bone muscle limits, get them and apply over the defaults!
var humanBones = animator.avatar.humanDescription.human;
foreach (var humanBone in humanBones) {

// Ignore default muscle values values
if (humanBone.limit.useDefaultValues) continue;

if (humanBone.humanName == HumanBodyBones.LeftEye.ToString()) {
betterEyeController._eyeRotationLimits.LeftEyeMinY = humanBone.limit.min.z;
betterEyeController._eyeRotationLimits.LeftEyeMaxY = humanBone.limit.max.z;
betterEyeController._eyeRotationLimits.LeftEyeMinX = -humanBone.limit.max.y;
betterEyeController._eyeRotationLimits.LeftEyeMaxX = -humanBone.limit.min.y;

#if DEBUG
MelonLogger.Msg($"Found avatar Left Eye Muscle Limits on the avatar!");
#endif
}
else if (humanBone.humanName == HumanBodyBones.RightEye.ToString()) {
betterEyeController._eyeRotationLimits.RightEyeMinY = humanBone.limit.min.z;
betterEyeController._eyeRotationLimits.RightEyeMaxY = humanBone.limit.max.z;
betterEyeController._eyeRotationLimits.RightEyeMinX = humanBone.limit.min.y;
betterEyeController._eyeRotationLimits.RightEyeMaxX = humanBone.limit.max.y;

#if DEBUG
MelonLogger.Msg($"Found avatar Right Eye Muscle Limits on the avatar!");
#endif
}
}
}

private void Start() {

#if DEBUG
Expand All @@ -227,7 +275,7 @@ private void Start() {
}


CCK.Debugger.Components.CohtmlMenuHandlers.AvatarCohtmlHandler.AvatarChangeEvent += OnCCKDebuggerAvatarChanged;
CCKDebugger.Components.CohtmlMenuHandlers.AvatarCohtmlHandler.AvatarChangeEvent += OnCCKDebuggerAvatarChanged;
#endif

if (enabled) initialized = true;
Expand Down Expand Up @@ -265,9 +313,13 @@ private void TargetHandler(CVREyeController controller) {
}
}

private static float NormalizeAngleToPercent(float angle, float maxAngle) {
var normal = Mathf.InverseLerp(-maxAngle, maxAngle, angle);
return Mathf.Lerp(-1f, 1f, normal);
private static float NormalizeAngleToPercent(float angle, float minAngle, float maxAngle) {
if (angle >= 0) {
return Mathf.Lerp(0, 1f, Mathf.InverseLerp(0, maxAngle, angle));
}
else {
return Mathf.Lerp(-1f, 0, Mathf.InverseLerp(minAngle, 0, angle));
}
}

private void UpdateEyeRotation(BetterEye eye, Quaternion lookRotation) {
Expand All @@ -277,8 +329,15 @@ private void UpdateEyeRotation(BetterEye eye, Quaternion lookRotation) {
var wrapperLocalRotation = eye.FakeEyeWrapper.localRotation.eulerAngles;
if (wrapperLocalRotation.x > 180f) wrapperLocalRotation.x -= 360f;
if (wrapperLocalRotation.y > 180f) wrapperLocalRotation.y -= 360f;
wrapperLocalRotation.x = Mathf.Clamp(wrapperLocalRotation.x, -MaxVerticalAngle, MaxVerticalAngle);
wrapperLocalRotation.y = Mathf.Clamp(wrapperLocalRotation.y, -MaxHorizontalAngle, MaxHorizontalAngle);

if (eye.IsLeft) {
wrapperLocalRotation.x = Mathf.Clamp(wrapperLocalRotation.x, _eyeRotationLimits.LeftEyeMinY, _eyeRotationLimits.LeftEyeMaxY);
wrapperLocalRotation.y = Mathf.Clamp(wrapperLocalRotation.y, _eyeRotationLimits.LeftEyeMinX, _eyeRotationLimits.LeftEyeMaxX);
}
else {
wrapperLocalRotation.x = Mathf.Clamp(wrapperLocalRotation.x, _eyeRotationLimits.RightEyeMinY, _eyeRotationLimits.RightEyeMaxY);
wrapperLocalRotation.y = Mathf.Clamp(wrapperLocalRotation.y, _eyeRotationLimits.RightEyeMinX, _eyeRotationLimits.RightEyeMaxX);
}

#if DEBUG
var previousLocal = eye.FakeEyeWrapper.localRotation;
Expand All @@ -296,7 +355,9 @@ private void UpdateEyeRotation(BetterEye eye, Quaternion lookRotation) {
// Set the eye angle (we're setting twice if we have 2 eyes, but the values should be the same anyway)
// This will give values different than cvr. I've opted to have the looking forward angle to be 0
// And then goes between [-1;0] and [0;+1], instead of [335-360] and [0-25] (cvr default)
cvrEyeController.eyeAngle.Set(NormalizeAngleToPercent(wrapperLocalRotation.y, MaxHorizontalAngle), NormalizeAngleToPercent(wrapperLocalRotation.x, MaxVerticalAngle));
cvrEyeController.eyeAngle.Set(
NormalizeAngleToPercent(wrapperLocalRotation.y, _eyeRotationLimits.LeftEyeMinX, _eyeRotationLimits.LeftEyeMaxX),
NormalizeAngleToPercent(wrapperLocalRotation.x, _eyeRotationLimits.LeftEyeMinY, _eyeRotationLimits.LeftEyeMaxY));
}

private void UpdateEyeRotations() {
Expand Down Expand Up @@ -375,7 +436,7 @@ private void OnDestroy() {
}

#if DEBUG
CCK.Debugger.Components.CohtmlMenuHandlers.AvatarCohtmlHandler.AvatarChangeEvent -= OnCCKDebuggerAvatarChanged;
CCKDebugger.Components.CohtmlMenuHandlers.AvatarCohtmlHandler.AvatarChangeEvent -= OnCCKDebuggerAvatarChanged;
#endif
}

Expand Down Expand Up @@ -583,7 +644,7 @@ private static void After_CVREyeControllerManager_Start(ref CVREyeController __i
if ((__instance.isLocal && head == null) || leftEye == null && rightEye == null) return;

// Initialize the controller
Initialize(___avatar, __instance, head, leftEye, rightEye);
Initialize(___avatar, animator, __instance, head, leftEye, rightEye);

}
catch (Exception e) {
Expand Down
16 changes: 16 additions & 0 deletions EyeMovementFix/CCK/EyeRotationLimits.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using UnityEngine;

namespace EyeMovementFix.CCK;

public class EyeRotationLimits : MonoBehaviour {

public float LeftEyeMinX = -25;
public float LeftEyeMaxX = 25;
public float LeftEyeMinY = -25;
public float LeftEyeMaxY = 25;

public float RightEyeMinX = -25;
public float RightEyeMaxX = 25;
public float RightEyeMinY = -25;
public float RightEyeMaxY = 25;
}
5 changes: 5 additions & 0 deletions EyeMovementFix/Main.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System.Reflection;
using System.Reflection.Emit;
using ABI_RC.Core.Player;
using ABI_RC.Core.Util.AssetFiltering;
using ABI.CCK.Components;
using EyeMovementFix.CCK;
using HarmonyLib;
using MelonLoader;
using UnityEngine;
Expand Down Expand Up @@ -30,6 +32,9 @@ public override void OnInitializeMelon() {
"are active, they create targets for the eye movement. But since I use the mirror to mostly " +
"see what I'm doing, makes no sense to target players in the portable mirror.");

// Add our CCK component to the whitelist
Traverse.Create(typeof(SharedFilter)).Field<HashSet<Type>>("_avatarWhitelist").Value.Add(typeof(EyeRotationLimits));

// Check for portable mirror
_hasPortableMirror = RegisteredMelons.FirstOrDefault(m => m.Info.Name == "PortableMirrorMod") != null;

Expand Down
2 changes: 1 addition & 1 deletion EyeMovementFix/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@

namespace EyeMovementFix.Properties;
internal static class AssemblyInfoParams {
public const string Version = "2.0.2";
public const string Version = "2.0.3";
public const string Author = "kafeijao";
}
17 changes: 15 additions & 2 deletions EyeMovementFix/README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
# EyeMovementFix

Mod to attempt to fix some of the current issues with the eye movement. This will affect the local and remote players
eye movement but only for the local client.
eye movement but only for the local client. Over time I also implemented some new features.

## Added Features
- Reworked the eye targeting system, now finding a target will require line of sight and gives priority to:
- Distance of the target and how close to the viewpoint center it is.
- How loud the target is talking (if the target is a player).
- Prefers the real players over their reflections on the mirror.
- Is the local player :)
- Allow to set the avatar eye rotation limits by:
- Using a Unity [Custom CCK Component](https://github.com/kafeijao/Kafe_CVR_CCKs/blob/master/EyeMovementFix)
- Using the Unity Muscle rig limits for the eyes

## Addressed Issues

- The Local Player look at target (local and in mirror) doesn't follow the viewpoint Up/Down when in FBT. (results in
players looking higher/lower when looking at you).
players looking higher/lower when looking at you). This seems to be because of some janky coded that during the
Update Loop makes the viewpoint default to the avatar height position. In FixedUpdate and LateUpdate it is on the
correct place.
- Some remote players don't have an instance of PuppetMaster attached to their CVRAvatar instance, which results in the
viewpoint location to be completely wrong (results in crossed eyes a lot of times).
- The math for rotating the eyes has some issues. Sometimes one of my eyes would go up, and the other to the left. I
redid that part with simpler code, it might not be the best, but the eyes won't move in opposite ways/axis anymore.
- The parameter stream fetches the Y eye angle from the X axis.

## Configuration

Expand Down

0 comments on commit 9222560

Please sign in to comment.