mirror of
https://github.com/hanetzer/sdraw_mods_cvr.git
synced 2025-09-07 03:59:16 +00:00
Fix for PuppetMaster 1.4
Archived DesktopHeadTracking and ViveEyeTracking
This commit is contained in:
parent
b21d5497d9
commit
01a833f46d
24 changed files with 430 additions and 441 deletions
BIN
archived/ml_dht/.github/img_01.png
vendored
Normal file
BIN
archived/ml_dht/.github/img_01.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 291 KiB |
29
archived/ml_dht/DataParser.cs
Normal file
29
archived/ml_dht/DataParser.cs
Normal file
|
@ -0,0 +1,29 @@
|
|||
namespace ml_dht
|
||||
{
|
||||
class DataParser
|
||||
{
|
||||
MemoryMapReader m_mapReader = null;
|
||||
byte[] m_buffer = null;
|
||||
TrackingData m_trackingData;
|
||||
|
||||
public DataParser()
|
||||
{
|
||||
m_buffer = new byte[1024];
|
||||
m_mapReader = new MemoryMapReader();
|
||||
m_mapReader.Open("head/data");
|
||||
}
|
||||
~DataParser()
|
||||
{
|
||||
m_mapReader.Close();
|
||||
m_mapReader = null;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if(m_mapReader.Read(ref m_buffer))
|
||||
m_trackingData = TrackingData.ToObject(m_buffer);
|
||||
}
|
||||
|
||||
public ref TrackingData GetLatestTrackingData() => ref m_trackingData;
|
||||
}
|
||||
}
|
119
archived/ml_dht/GameEvents.cs
Normal file
119
archived/ml_dht/GameEvents.cs
Normal file
|
@ -0,0 +1,119 @@
|
|||
using ABI.CCK.Components;
|
||||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Core.Player.EyeMovement;
|
||||
using ABI_RC.Systems.IK;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace ml_dht
|
||||
{
|
||||
static class GameEvents
|
||||
{
|
||||
internal class EventResult
|
||||
{
|
||||
public bool m_result = false;
|
||||
}
|
||||
internal class GameEvent
|
||||
{
|
||||
event Action m_action;
|
||||
public void AddListener(Action p_listener) => m_action += p_listener;
|
||||
public void RemoveListener(Action p_listener) => m_action -= p_listener;
|
||||
public void Invoke() => m_action?.Invoke();
|
||||
}
|
||||
internal class GameEvent<T1>
|
||||
{
|
||||
event Action<T1> m_action;
|
||||
public void AddListener(Action<T1> p_listener) => m_action += p_listener;
|
||||
public void RemoveListener(Action<T1> p_listener) => m_action -= p_listener;
|
||||
public void Invoke(T1 p_obj) => m_action?.Invoke(p_obj);
|
||||
}
|
||||
internal class GameEvent<T1, T2>
|
||||
{
|
||||
event Action<T1, T2> m_action;
|
||||
public void AddListener(Action<T1, T2> p_listener) => m_action += p_listener;
|
||||
public void RemoveListener(Action<T1, T2> p_listener) => m_action -= p_listener;
|
||||
public void Invoke(T1 p_objA, T2 p_objB) => m_action?.Invoke(p_objA, p_objB);
|
||||
}
|
||||
|
||||
public static readonly GameEvent OnAvatarReuse = new GameEvent();
|
||||
public static readonly GameEvent<EyeMovementController> OnEyeControllerUpdate = new GameEvent<EyeMovementController>();
|
||||
public static readonly GameEvent<CVRFaceTracking, EventResult> OnFaceTrackingUpdate = new GameEvent<CVRFaceTracking, EventResult>();
|
||||
|
||||
static readonly EventResult ms_result = new EventResult();
|
||||
|
||||
internal static void InitA(HarmonyLib.Harmony p_instance)
|
||||
{
|
||||
try
|
||||
{
|
||||
p_instance.Patch(
|
||||
typeof(IKSystem).GetMethod(nameof(IKSystem.ReinitializeAvatar), BindingFlags.Instance | BindingFlags.Public),
|
||||
null,
|
||||
new HarmonyLib.HarmonyMethod(typeof(GameEvents).GetMethod(nameof(OnAvatarReinitialize_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
|
||||
);
|
||||
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
MelonLoader.MelonLogger.Error(e);
|
||||
}
|
||||
}
|
||||
internal static void InitB(HarmonyLib.Harmony p_instance)
|
||||
{
|
||||
try
|
||||
{
|
||||
p_instance.Patch(
|
||||
typeof(EyeMovementController).GetMethod("Update", BindingFlags.Instance | BindingFlags.NonPublic),
|
||||
null,
|
||||
new HarmonyLib.HarmonyMethod(typeof(GameEvents).GetMethod(nameof(OnEyeControllerUpdate_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
|
||||
);
|
||||
|
||||
p_instance.Patch(
|
||||
typeof(CVRFaceTracking).GetMethod("UpdateLocalData", BindingFlags.Instance | BindingFlags.NonPublic),
|
||||
new HarmonyLib.HarmonyMethod(typeof(GameEvents).GetMethod(nameof(OnFaceTrackingLocalUpdate_Prefix), BindingFlags.Static | BindingFlags.NonPublic))
|
||||
);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
MelonLoader.MelonLogger.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
static void OnAvatarReinitialize_Postfix()
|
||||
{
|
||||
try
|
||||
{
|
||||
OnAvatarReuse.Invoke();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
MelonLoader.MelonLogger.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
static void OnEyeControllerUpdate_Postfix(ref EyeMovementController __instance)
|
||||
{
|
||||
try
|
||||
{
|
||||
OnEyeControllerUpdate.Invoke(__instance);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
MelonLoader.MelonLogger.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
static bool OnFaceTrackingLocalUpdate_Prefix(ref CVRFaceTracking __instance)
|
||||
{
|
||||
try
|
||||
{
|
||||
ms_result.m_result = false;
|
||||
OnFaceTrackingUpdate.Invoke(__instance, ms_result);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
MelonLoader.MelonLogger.Error(e);
|
||||
}
|
||||
return !ms_result.m_result;
|
||||
}
|
||||
}
|
||||
}
|
252
archived/ml_dht/HeadTracked.cs
Normal file
252
archived/ml_dht/HeadTracked.cs
Normal file
|
@ -0,0 +1,252 @@
|
|||
using ABI.CCK.Components;
|
||||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Core.Player.EyeMovement;
|
||||
using ABI_RC.Systems.FaceTracking;
|
||||
using ABI_RC.Systems.GameEventSystem;
|
||||
using ABI_RC.Systems.IK;
|
||||
using ABI_RC.Systems.VRModeSwitch;
|
||||
using RootMotion.FinalIK;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using ViveSR.anipal.Lip;
|
||||
|
||||
namespace ml_dht
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
class HeadTracked : MonoBehaviour
|
||||
{
|
||||
static HeadTracked ms_instance = null;
|
||||
|
||||
CVRAvatar m_avatarDescriptor = null;
|
||||
Transform m_camera = null;
|
||||
LookAtIK m_lookIK = null;
|
||||
Transform m_headBone = null;
|
||||
|
||||
Vector3 m_headPosition;
|
||||
Quaternion m_headRotation;
|
||||
Vector2 m_gazeDirection;
|
||||
float m_blinkProgress = 0f;
|
||||
LipData_v2 m_lipData;
|
||||
bool m_lipDataSent = false;
|
||||
|
||||
Quaternion m_bindRotation;
|
||||
Quaternion m_lastHeadRotation;
|
||||
|
||||
DataParser m_dataParser = null;
|
||||
float m_smoothing = 0.5f;
|
||||
|
||||
internal HeadTracked()
|
||||
{
|
||||
m_lipData = new LipData_v2();
|
||||
m_lipData.frame = 0;
|
||||
m_lipData.time = 0;
|
||||
m_lipData.image = IntPtr.Zero;
|
||||
m_lipData.prediction_data = new PredictionData_v2();
|
||||
m_lipData.prediction_data.blend_shape_weight = new float[(int)LipShape_v2.Max];
|
||||
}
|
||||
|
||||
// Unity events
|
||||
void Awake()
|
||||
{
|
||||
if(ms_instance != null)
|
||||
{
|
||||
DestroyImmediate(this);
|
||||
return;
|
||||
}
|
||||
|
||||
ms_instance = this;
|
||||
DontDestroyOnLoad(this);
|
||||
|
||||
m_dataParser = new DataParser();
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
OnSmoothingChanged(Settings.Smoothing);
|
||||
|
||||
OnVRModeSwitch(true);
|
||||
|
||||
Settings.OnEnabledChanged.AddListener(this.OnEnabledOrHeadTrackingChanged);
|
||||
Settings.OnHeadTrackingChanged.AddListener(this.OnEnabledOrHeadTrackingChanged);
|
||||
Settings.OnSmoothingChanged.AddListener(this.OnSmoothingChanged);
|
||||
|
||||
CVRGameEventSystem.Avatar.OnLocalAvatarLoad.AddListener(this.OnAvatarSetup);
|
||||
CVRGameEventSystem.Avatar.OnLocalAvatarClear.AddListener(this.OnAvatarClear);
|
||||
GameEvents.OnAvatarReuse.AddListener(this.OnAvatarReuse);
|
||||
GameEvents.OnEyeControllerUpdate.AddListener(this.OnEyeControllerUpdate);
|
||||
GameEvents.OnFaceTrackingUpdate.AddListener(this.UpdateFaceTracking);
|
||||
|
||||
VRModeSwitchEvents.OnCompletedVRModeSwitch.AddListener(this.OnVRModeSwitch);
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
if(ms_instance == this)
|
||||
ms_instance = null;
|
||||
|
||||
m_dataParser = null;
|
||||
|
||||
Settings.OnEnabledChanged.RemoveListener(this.OnEnabledOrHeadTrackingChanged);
|
||||
Settings.OnHeadTrackingChanged.RemoveListener(this.OnEnabledOrHeadTrackingChanged);
|
||||
Settings.OnSmoothingChanged.RemoveListener(this.OnSmoothingChanged);
|
||||
|
||||
CVRGameEventSystem.Avatar.OnLocalAvatarLoad.RemoveListener(this.OnAvatarSetup);
|
||||
CVRGameEventSystem.Avatar.OnLocalAvatarClear.RemoveListener(this.OnAvatarClear);
|
||||
GameEvents.OnAvatarReuse.RemoveListener(this.OnAvatarReuse);
|
||||
GameEvents.OnEyeControllerUpdate.RemoveListener(this.OnEyeControllerUpdate);
|
||||
GameEvents.OnFaceTrackingUpdate.RemoveListener(this.UpdateFaceTracking);
|
||||
|
||||
VRModeSwitchEvents.OnCompletedVRModeSwitch.RemoveListener(this.OnVRModeSwitch);
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if(m_lipDataSent)
|
||||
m_lipDataSent = false;
|
||||
|
||||
if(Settings.Enabled && (m_dataParser != null))
|
||||
{
|
||||
m_dataParser.Update();
|
||||
UpdateTrackingData(ref m_dataParser.GetLatestTrackingData());
|
||||
}
|
||||
}
|
||||
|
||||
// Tracking updates
|
||||
public void UpdateTrackingData(ref TrackingData p_data)
|
||||
{
|
||||
m_headPosition.Set(p_data.m_headPositionX * (Settings.Mirrored ? -1f : 1f), p_data.m_headPositionY, p_data.m_headPositionZ);
|
||||
m_headRotation.Set(p_data.m_headRotationX, p_data.m_headRotationY * (Settings.Mirrored ? -1f : 1f), p_data.m_headRotationZ * (Settings.Mirrored ? -1f : 1f), p_data.m_headRotationW);
|
||||
m_gazeDirection.Set(Settings.Mirrored ? (1f - p_data.m_gazeX) : p_data.m_gazeX, p_data.m_gazeY);
|
||||
m_blinkProgress = p_data.m_blink;
|
||||
|
||||
float l_weight = Mathf.Clamp01(Mathf.InverseLerp(0.25f, 1f, Mathf.Abs(p_data.m_mouthShape)));
|
||||
m_lipData.prediction_data.blend_shape_weight[(int)LipShape_v2.Jaw_Open] = p_data.m_mouthOpen;
|
||||
m_lipData.prediction_data.blend_shape_weight[(int)LipShape_v2.Mouth_Pout] = ((p_data.m_mouthShape > 0f) ? l_weight : 0f);
|
||||
m_lipData.prediction_data.blend_shape_weight[(int)LipShape_v2.Mouth_Smile_Left] = ((p_data.m_mouthShape < 0f) ? l_weight : 0f);
|
||||
m_lipData.prediction_data.blend_shape_weight[(int)LipShape_v2.Mouth_Smile_Right] = ((p_data.m_mouthShape < 0f) ? l_weight : 0f);
|
||||
}
|
||||
|
||||
void OnLookIKPostUpdate()
|
||||
{
|
||||
if(Settings.Enabled && Settings.HeadTracking && (m_headBone != null))
|
||||
{
|
||||
m_lastHeadRotation = Quaternion.Slerp(m_lastHeadRotation, m_avatarDescriptor.transform.rotation * (m_headRotation * m_bindRotation), m_smoothing);
|
||||
|
||||
if(!PlayerSetup.Instance.IsEmotePlaying)
|
||||
m_headBone.rotation = m_lastHeadRotation;
|
||||
}
|
||||
}
|
||||
|
||||
// Game events
|
||||
internal void OnAvatarSetup(CVRAvatar p_avatar)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_camera = PlayerSetup.Instance.activeCam.transform;
|
||||
m_avatarDescriptor = PlayerSetup.Instance.AvatarObject.GetComponent<CVRAvatar>();
|
||||
|
||||
if(PlayerSetup.Instance.Animator.isHuman)
|
||||
{
|
||||
IKSystem.Instance.SetAvatarPose(IKSystem.AvatarPose.TPose);
|
||||
PlayerSetup.Instance.AvatarTransform.localPosition = Vector3.zero;
|
||||
PlayerSetup.Instance.AvatarTransform.localRotation = Quaternion.identity;
|
||||
|
||||
m_headBone = PlayerSetup.Instance.Animator.GetBoneTransform(HumanBodyBones.Head);
|
||||
if(m_headBone != null)
|
||||
m_bindRotation = Quaternion.Inverse(m_avatarDescriptor.transform.rotation) * m_headBone.rotation;
|
||||
|
||||
m_lookIK = PlayerSetup.Instance.AvatarObject.GetComponent<LookAtIK>();
|
||||
if(m_lookIK != null)
|
||||
m_lookIK.onPostSolverUpdate.AddListener(this.OnLookIKPostUpdate);
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
MelonLoader.MelonLogger.Error(e);
|
||||
}
|
||||
}
|
||||
void OnAvatarClear(CVRAvatar p_avatar)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_avatarDescriptor = null;
|
||||
m_lookIK = null;
|
||||
m_headBone = null;
|
||||
m_lastHeadRotation = Quaternion.identity;
|
||||
m_bindRotation = Quaternion.identity;
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
MelonLoader.MelonLogger.Error(e);
|
||||
}
|
||||
}
|
||||
void OnAvatarReuse()
|
||||
{
|
||||
m_camera = PlayerSetup.Instance.activeCam.transform;
|
||||
|
||||
m_lookIK = PlayerSetup.Instance.AvatarObject.GetComponent<LookAtIK>();
|
||||
if(m_lookIK != null)
|
||||
m_lookIK.onPostSolverUpdate.AddListener(this.OnLookIKPostUpdate);
|
||||
}
|
||||
|
||||
void OnEyeControllerUpdate(EyeMovementController p_component)
|
||||
{
|
||||
if(this.enabled && Settings.Enabled)
|
||||
{
|
||||
// Gaze
|
||||
if(Settings.EyeTracking && (m_camera != null))
|
||||
{
|
||||
p_component.manualViewTarget = true;
|
||||
p_component.targetViewPosition = m_camera.position + m_camera.rotation * new Vector3((m_gazeDirection.x - 0.5f) * 2f, (m_gazeDirection.y - 0.5f) * 2f, 1f);
|
||||
}
|
||||
|
||||
// Blink
|
||||
if(Settings.Blinking)
|
||||
{
|
||||
p_component.manualBlinking = true;
|
||||
p_component.blinkProgressLeft = m_blinkProgress;
|
||||
p_component.blinkProgressRight = m_blinkProgress;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateFaceTracking(CVRFaceTracking p_component, GameEvents.EventResult p_result)
|
||||
{
|
||||
if(this.enabled && Settings.Enabled && Settings.FaceTracking && p_component.isLocal && p_component.UseFacialTracking)
|
||||
{
|
||||
if(!m_lipDataSent)
|
||||
{
|
||||
FaceTrackingManager.Instance.SubmitNewFacialData(m_lipData);
|
||||
m_lipDataSent = true;
|
||||
}
|
||||
p_component.LipSyncWasUpdated = true;
|
||||
p_component.UpdateShapesLocal_Private();
|
||||
|
||||
p_result.m_result |= true;
|
||||
}
|
||||
}
|
||||
|
||||
void OnVRModeSwitch(bool p_state)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.enabled = !Utils.IsInVR();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
MelonLoader.MelonLogger.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Settings
|
||||
void OnEnabledOrHeadTrackingChanged(bool p_state)
|
||||
{
|
||||
if(Settings.Enabled && Settings.HeadTracking)
|
||||
m_lastHeadRotation = ((m_headBone != null) ? m_headBone.rotation : m_bindRotation);
|
||||
}
|
||||
void OnSmoothingChanged(float p_value)
|
||||
{
|
||||
m_smoothing = 1f - Mathf.Clamp(p_value, 0f, 0.99f);
|
||||
}
|
||||
}
|
||||
}
|
40
archived/ml_dht/Main.cs
Normal file
40
archived/ml_dht/Main.cs
Normal file
|
@ -0,0 +1,40 @@
|
|||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Core.Savior;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ml_dht
|
||||
{
|
||||
public class DesktopHeadTracking : MelonLoader.MelonMod
|
||||
{
|
||||
|
||||
HeadTracked m_tracked = null;
|
||||
|
||||
public override void OnInitializeMelon()
|
||||
{
|
||||
Settings.Init();
|
||||
GameEvents.InitA(HarmonyInstance);
|
||||
|
||||
MelonLoader.MelonCoroutines.Start(WaitForInstances());
|
||||
}
|
||||
|
||||
System.Collections.IEnumerator WaitForInstances()
|
||||
{
|
||||
while(MetaPort.Instance == null)
|
||||
yield return null;
|
||||
|
||||
while(PlayerSetup.Instance == null)
|
||||
yield return null;
|
||||
|
||||
GameEvents.InitB(HarmonyInstance);
|
||||
|
||||
m_tracked = new GameObject("[DesktopHeadTracking]").AddComponent<HeadTracked>();
|
||||
}
|
||||
|
||||
public override void OnDeinitializeMelon()
|
||||
{
|
||||
if(m_tracked != null)
|
||||
Object.Destroy(m_tracked.gameObject);
|
||||
m_tracked = null;
|
||||
}
|
||||
}
|
||||
}
|
58
archived/ml_dht/MemoryMapReader.cs
Normal file
58
archived/ml_dht/MemoryMapReader.cs
Normal file
|
@ -0,0 +1,58 @@
|
|||
using System.IO;
|
||||
using System.IO.MemoryMappedFiles;
|
||||
|
||||
namespace ml_dht
|
||||
{
|
||||
class MemoryMapReader
|
||||
{
|
||||
MemoryMappedFile m_file = null;
|
||||
MemoryMappedViewStream m_stream = null;
|
||||
int m_dataSize = 0;
|
||||
|
||||
public bool Open(string p_path, int p_dataSize = 1024)
|
||||
{
|
||||
if(m_file == null)
|
||||
{
|
||||
m_dataSize = p_dataSize;
|
||||
|
||||
m_file = MemoryMappedFile.CreateOrOpen(p_path, m_dataSize, MemoryMappedFileAccess.ReadWrite);
|
||||
m_stream = m_file.CreateViewStream(0, m_dataSize, MemoryMappedFileAccess.Read);
|
||||
}
|
||||
return (m_file != null);
|
||||
}
|
||||
|
||||
public bool Read(ref byte[] p_data)
|
||||
{
|
||||
bool l_result = false;
|
||||
if((m_stream != null) && m_stream.CanRead)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_stream.Seek(0, SeekOrigin.Begin);
|
||||
m_stream.Read(p_data, 0, (p_data.Length > m_dataSize) ? m_dataSize : p_data.Length);
|
||||
l_result = true;
|
||||
}
|
||||
catch(System.Exception e)
|
||||
{
|
||||
MelonLoader.MelonLogger.Error(e);
|
||||
}
|
||||
}
|
||||
return l_result;
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
if(m_file != null)
|
||||
{
|
||||
m_stream.Close();
|
||||
m_stream.Dispose();
|
||||
m_stream = null;
|
||||
|
||||
m_file.Dispose();
|
||||
m_file = null;
|
||||
|
||||
m_dataSize = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
4
archived/ml_dht/Properties/AssemblyInfo.cs
Normal file
4
archived/ml_dht/Properties/AssemblyInfo.cs
Normal file
|
@ -0,0 +1,4 @@
|
|||
[assembly: MelonLoader.MelonInfo(typeof(ml_dht.DesktopHeadTracking), "DesktopHeadTracking", "1.3.4", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
|
||||
[assembly: MelonLoader.MelonGame(null, "ChilloutVR")]
|
||||
[assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
|
||||
[assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
|
30
archived/ml_dht/README.md
Normal file
30
archived/ml_dht/README.md
Normal file
|
@ -0,0 +1,30 @@
|
|||
# Desktop Head Tracking
|
||||
This mod adds desktop head tracking based on data from memory-mapped file `head/data`.
|
||||
Refer to `TrackingData.cs` for reference in case of implementing own software.
|
||||
|
||||
[](https://youtu.be/jgcFhSNi9DM)
|
||||
|
||||
# Features
|
||||
* Head rotation
|
||||
* Eyes gaze direction
|
||||
* Basic mouth shapes: open, smile and pout
|
||||
* Blinking
|
||||
|
||||
# Installation
|
||||
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
|
||||
* Get [latest release DLL](../../../releases/latest):
|
||||
* Put `DesktopHeadTracking.dll` in `Mods` folder of game
|
||||
|
||||
# Usage
|
||||
Available mod's settings in `Settings - Implementation - Desktop Head Tracking`:
|
||||
* **Enabled:** enables mod's activity; default value - `false`.
|
||||
* **Use head tracking:** enables head tracking; default value - `true`.
|
||||
* **Use eyes tracking:** enables eyes tracking; default value - `true`.
|
||||
* **Use face tracking:** enables mouth shapes tracking; default value - `true`.
|
||||
* **Note:** Your avatar should have configured `CVR Face Tracking` component.
|
||||
* **Use blinking:** uses blinking from data; default value - `true`.
|
||||
* **Mirrored movement:** mirrors movement and gaze along 0YZ plane; default value - `false`.
|
||||
* **Movement smoothing:** smoothing factor between new and old movement data; default value - `50`.
|
||||
|
||||
# Known compatible tracking software
|
||||
* [VSeeFace](https://www.vseeface.icu) with [Tracking Data Parser mod](https://github.com/SDraw/ml_mods_vsf)
|
30
archived/ml_dht/ResourcesHandler.cs
Normal file
30
archived/ml_dht/ResourcesHandler.cs
Normal file
|
@ -0,0 +1,30 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
namespace ml_dht
|
||||
{
|
||||
static class ResourcesHandler
|
||||
{
|
||||
readonly static string ms_namespace = typeof(ResourcesHandler).Namespace;
|
||||
|
||||
public static string GetEmbeddedResource(string p_name)
|
||||
{
|
||||
string l_result = "";
|
||||
Assembly l_assembly = Assembly.GetExecutingAssembly();
|
||||
|
||||
try
|
||||
{
|
||||
Stream l_libraryStream = l_assembly.GetManifestResourceStream(ms_namespace + ".resources." + p_name);
|
||||
StreamReader l_streadReader = new StreamReader(l_libraryStream);
|
||||
l_result = l_streadReader.ReadToEnd();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
MelonLoader.MelonLogger.Error(e);
|
||||
}
|
||||
|
||||
return l_result;
|
||||
}
|
||||
}
|
||||
}
|
189
archived/ml_dht/Settings.cs
Normal file
189
archived/ml_dht/Settings.cs
Normal file
|
@ -0,0 +1,189 @@
|
|||
using ABI_RC.Core.InteractionSystem;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ml_dht
|
||||
{
|
||||
static class Settings
|
||||
{
|
||||
internal class SettingEvent<T>
|
||||
{
|
||||
event Action<T> m_action;
|
||||
public void AddListener(Action<T> p_listener) => m_action += p_listener;
|
||||
public void RemoveListener(Action<T> p_listener) => m_action -= p_listener;
|
||||
public void Invoke(T p_value) => m_action?.Invoke(p_value);
|
||||
}
|
||||
|
||||
enum ModSetting
|
||||
{
|
||||
Enabled = 0,
|
||||
HeadTracking,
|
||||
EyeTracking,
|
||||
FaceTracking,
|
||||
Blinking,
|
||||
Mirrored,
|
||||
Smoothing,
|
||||
}
|
||||
|
||||
public static bool Enabled { get; private set; } = false;
|
||||
public static bool HeadTracking { get; private set; } = true;
|
||||
public static bool EyeTracking { get; private set; } = true;
|
||||
public static bool FaceTracking { get; private set; } = true;
|
||||
public static bool Blinking { get; private set; } = true;
|
||||
public static bool Mirrored { get; private set; } = false;
|
||||
public static float Smoothing { get; private set; } = 0.5f;
|
||||
|
||||
static MelonLoader.MelonPreferences_Category ms_category = null;
|
||||
static List<MelonLoader.MelonPreferences_Entry> ms_entries = null;
|
||||
|
||||
public static readonly SettingEvent<bool> OnEnabledChanged = new SettingEvent<bool>();
|
||||
public static readonly SettingEvent<bool> OnHeadTrackingChanged = new SettingEvent<bool>();
|
||||
public static readonly SettingEvent<bool> OnEyeTrackingChanged = new SettingEvent<bool>();
|
||||
public static readonly SettingEvent<bool> OnFaceTrackingChanged = new SettingEvent<bool>();
|
||||
public static readonly SettingEvent<bool> OnBlinkingChanged = new SettingEvent<bool>();
|
||||
public static readonly SettingEvent<bool> OnMirroredChanged = new SettingEvent<bool>();
|
||||
public static readonly SettingEvent<float> OnSmoothingChanged = new SettingEvent<float>();
|
||||
|
||||
internal static void Init()
|
||||
{
|
||||
ms_category = MelonLoader.MelonPreferences.CreateCategory("DHT");
|
||||
|
||||
ms_entries = new List<MelonLoader.MelonPreferences_Entry>()
|
||||
{
|
||||
ms_category.CreateEntry(ModSetting.Enabled.ToString(), Enabled),
|
||||
ms_category.CreateEntry(ModSetting.HeadTracking.ToString(), HeadTracking),
|
||||
ms_category.CreateEntry(ModSetting.EyeTracking.ToString(), EyeTracking),
|
||||
ms_category.CreateEntry(ModSetting.FaceTracking.ToString(), FaceTracking),
|
||||
ms_category.CreateEntry(ModSetting.Blinking.ToString(), Blinking),
|
||||
ms_category.CreateEntry(ModSetting.Mirrored.ToString(), Mirrored),
|
||||
ms_category.CreateEntry(ModSetting.Smoothing.ToString(), (int)(Smoothing * 50f)),
|
||||
};
|
||||
|
||||
Enabled = (bool)ms_entries[(int)ModSetting.Enabled].BoxedValue;
|
||||
HeadTracking = (bool)ms_entries[(int)ModSetting.HeadTracking].BoxedValue;
|
||||
EyeTracking = (bool)ms_entries[(int)ModSetting.EyeTracking].BoxedValue;
|
||||
FaceTracking = (bool)ms_entries[(int)ModSetting.FaceTracking].BoxedValue;
|
||||
Blinking = (bool)ms_entries[(int)ModSetting.Blinking].BoxedValue;
|
||||
Mirrored = (bool)ms_entries[(int)ModSetting.Mirrored].BoxedValue;
|
||||
Smoothing = ((int)ms_entries[(int)ModSetting.Smoothing].BoxedValue) * 0.01f;
|
||||
|
||||
MelonLoader.MelonCoroutines.Start(WaitMainMenuUi());
|
||||
}
|
||||
|
||||
static System.Collections.IEnumerator WaitMainMenuUi()
|
||||
{
|
||||
while(ViewManager.Instance == null)
|
||||
yield return null;
|
||||
while(ViewManager.Instance.cohtmlView == null)
|
||||
yield return null;
|
||||
while(ViewManager.Instance.cohtmlView.Listener == null)
|
||||
yield return null;
|
||||
|
||||
ViewManager.Instance.cohtmlView.Listener.ReadyForBindings += () =>
|
||||
{
|
||||
ViewManager.Instance.cohtmlView.View.BindCall("OnToggleUpdate_" + ms_category.Identifier, new Action<string, string>(OnToggleUpdate));
|
||||
ViewManager.Instance.cohtmlView.View.BindCall("OnSliderUpdate_" + ms_category.Identifier, new Action<string, string>(OnSliderUpdate));
|
||||
};
|
||||
ViewManager.Instance.cohtmlView.Listener.FinishLoad += (_) =>
|
||||
{
|
||||
ViewManager.Instance.cohtmlView.View.ExecuteScript(ResourcesHandler.GetEmbeddedResource("mods_extension.js"));
|
||||
ViewManager.Instance.cohtmlView.View.ExecuteScript(ResourcesHandler.GetEmbeddedResource("mod_menu.js"));
|
||||
MelonLoader.MelonCoroutines.Start(UpdateMenuSettings());
|
||||
};
|
||||
}
|
||||
|
||||
static System.Collections.IEnumerator UpdateMenuSettings()
|
||||
{
|
||||
while(!ViewManager.Instance.IsReady || !ViewManager.Instance.IsViewShown)
|
||||
yield return null;
|
||||
|
||||
foreach(var l_entry in ms_entries)
|
||||
ViewManager.Instance.cohtmlView.View.TriggerEvent("updateModSetting", ms_category.Identifier, l_entry.DisplayName, l_entry.GetValueAsString());
|
||||
}
|
||||
|
||||
static void OnSliderUpdate(string p_name, string p_value)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(Enum.TryParse(p_name, out ModSetting l_setting) && int.TryParse(p_value, out int l_value))
|
||||
{
|
||||
switch(l_setting)
|
||||
{
|
||||
case ModSetting.Smoothing:
|
||||
{
|
||||
Smoothing = l_value * 0.01f;
|
||||
OnSmoothingChanged.Invoke(Smoothing);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
ms_entries[(int)l_setting].BoxedValue = l_value;
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
MelonLoader.MelonLogger.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
static void OnToggleUpdate(string p_name, string p_value)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(Enum.TryParse(p_name, out ModSetting l_setting) && bool.TryParse(p_value, out bool l_value))
|
||||
{
|
||||
switch(l_setting)
|
||||
{
|
||||
case ModSetting.Enabled:
|
||||
{
|
||||
Enabled = l_value;
|
||||
OnEnabledChanged.Invoke(Enabled);
|
||||
}
|
||||
break;
|
||||
|
||||
case ModSetting.HeadTracking:
|
||||
{
|
||||
HeadTracking = l_value;
|
||||
OnHeadTrackingChanged.Invoke(HeadTracking);
|
||||
}
|
||||
break;
|
||||
|
||||
case ModSetting.EyeTracking:
|
||||
{
|
||||
EyeTracking = l_value;
|
||||
OnEyeTrackingChanged.Invoke(EyeTracking);
|
||||
}
|
||||
break;
|
||||
|
||||
case ModSetting.FaceTracking:
|
||||
{
|
||||
FaceTracking = l_value;
|
||||
OnFaceTrackingChanged.Invoke(FaceTracking);
|
||||
}
|
||||
break;
|
||||
|
||||
case ModSetting.Blinking:
|
||||
{
|
||||
Blinking = l_value;
|
||||
OnBlinkingChanged.Invoke(Blinking);
|
||||
}
|
||||
break;
|
||||
|
||||
case ModSetting.Mirrored:
|
||||
{
|
||||
Mirrored = l_value;
|
||||
OnMirroredChanged.Invoke(Mirrored);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
ms_entries[(int)l_setting].BoxedValue = l_value;
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
MelonLoader.MelonLogger.Error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
46
archived/ml_dht/TrackingData.cs
Normal file
46
archived/ml_dht/TrackingData.cs
Normal file
|
@ -0,0 +1,46 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
struct TrackingData
|
||||
{
|
||||
public float m_headPositionX; // Not used yet
|
||||
public float m_headPositionY; // Not used yet
|
||||
public float m_headPositionZ; // Not used yet
|
||||
public float m_headRotationX;
|
||||
public float m_headRotationY;
|
||||
public float m_headRotationZ;
|
||||
public float m_headRotationW;
|
||||
public float m_gazeX; // Range - [0;1], 0.5 - center
|
||||
public float m_gazeY; // Range - [0;1], 0.5 - center
|
||||
public float m_blink; // Range - [0;1], 1.0 - closed
|
||||
public float m_mouthOpen; // Range - [0;1]
|
||||
public float m_mouthShape; // Range - [-1;1], -1 - wide, 1 - narrow
|
||||
public float m_brows; // Range - [-1;1], -1 - up, 1 - down; not used yet
|
||||
|
||||
public static byte[] ToBytes(TrackingData p_faceData)
|
||||
{
|
||||
int l_size = Marshal.SizeOf(p_faceData);
|
||||
byte[] l_arr = new byte[l_size];
|
||||
|
||||
IntPtr ptr = Marshal.AllocHGlobal(l_size);
|
||||
Marshal.StructureToPtr(p_faceData, ptr, true);
|
||||
Marshal.Copy(ptr, l_arr, 0, l_size);
|
||||
Marshal.FreeHGlobal(ptr);
|
||||
return l_arr;
|
||||
}
|
||||
|
||||
public static TrackingData ToObject(byte[] p_buffer)
|
||||
{
|
||||
TrackingData l_faceData = new TrackingData();
|
||||
|
||||
int l_size = Marshal.SizeOf(l_faceData);
|
||||
IntPtr l_ptr = Marshal.AllocHGlobal(l_size);
|
||||
|
||||
Marshal.Copy(p_buffer, 0, l_ptr, l_size);
|
||||
|
||||
l_faceData = (TrackingData)Marshal.PtrToStructure(l_ptr, l_faceData.GetType());
|
||||
Marshal.FreeHGlobal(l_ptr);
|
||||
|
||||
return l_faceData;
|
||||
}
|
||||
}
|
20
archived/ml_dht/Utils.cs
Normal file
20
archived/ml_dht/Utils.cs
Normal file
|
@ -0,0 +1,20 @@
|
|||
using ABI.CCK.Components;
|
||||
using ABI_RC.Core.Savior;
|
||||
using ABI_RC.Core.UI;
|
||||
using System.Reflection;
|
||||
|
||||
namespace ml_dht
|
||||
{
|
||||
static class Utils
|
||||
{
|
||||
static readonly object[] ms_emptyArray = new object[0];
|
||||
static readonly FieldInfo ms_view = typeof(CohtmlControlledViewWrapper).GetField("_view", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
static readonly MethodInfo ms_updateShapesLocal = typeof(CVRFaceTracking).GetMethod("UpdateShapesLocal", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
|
||||
public static bool IsInVR() => ((MetaPort.Instance != null) && MetaPort.Instance.isUsingVr);
|
||||
|
||||
public static void ExecuteScript(this CohtmlControlledViewWrapper p_instance, string p_script) => (ms_view?.GetValue(p_instance) as cohtml.Net.View)?.ExecuteScript(p_script);
|
||||
|
||||
public static void UpdateShapesLocal_Private(this CVRFaceTracking p_instance) => ms_updateShapesLocal?.Invoke(p_instance, ms_emptyArray);
|
||||
}
|
||||
}
|
85
archived/ml_dht/ml_dht.csproj
Normal file
85
archived/ml_dht/ml_dht.csproj
Normal file
|
@ -0,0 +1,85 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<PackageId>DesktopHeadTracking</PackageId>
|
||||
<Authors>SDraw</Authors>
|
||||
<Company>SDraw</Company>
|
||||
<Product>DesktopHeadTracking</Product>
|
||||
<Version>1.3.4</Version>
|
||||
<Platforms>x64</Platforms>
|
||||
<AssemblyName>DesktopHeadTracking</AssemblyName>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<DebugType>embedded</DebugType>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="DesktopHeadTracking.json" />
|
||||
<None Remove="resources\mod_menu.js" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="resources\mod_menu.js" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="..\js\mods_extension.js" Link="resources\mods_extension.js" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="0Harmony">
|
||||
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\0Harmony.dll</HintPath>
|
||||
<SpecificVersion>false</SpecificVersion>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<Reference Include="Assembly-CSharp">
|
||||
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
<SpecificVersion>false</SpecificVersion>
|
||||
</Reference>
|
||||
<Reference Include="Assembly-CSharp-firstpass">
|
||||
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
<SpecificVersion>false</SpecificVersion>
|
||||
</Reference>
|
||||
<Reference Include="cohtml.Net">
|
||||
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\cohtml.Net.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
<SpecificVersion>false</SpecificVersion>
|
||||
</Reference>
|
||||
<Reference Include="Cohtml.Runtime">
|
||||
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Cohtml.Runtime.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
<SpecificVersion>false</SpecificVersion>
|
||||
</Reference>
|
||||
<Reference Include="MelonLoader">
|
||||
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\MelonLoader.dll</HintPath>
|
||||
<SpecificVersion>false</SpecificVersion>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine">
|
||||
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
<SpecificVersion>false</SpecificVersion>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.AnimationModule">
|
||||
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AnimationModule.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
<SpecificVersion>false</SpecificVersion>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.CoreModule">
|
||||
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
<SpecificVersion>false</SpecificVersion>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
<Exec Command="copy /y "$(TargetPath)" "D:\Games\Steam\steamapps\common\ChilloutVR\Mods\"" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
70
archived/ml_dht/resources/mod_menu.js
Normal file
70
archived/ml_dht/resources/mod_menu.js
Normal file
|
@ -0,0 +1,70 @@
|
|||
// Add own menu
|
||||
{
|
||||
let l_block = document.createElement('div');
|
||||
l_block.innerHTML = `
|
||||
<div class ="settings-subcategory">
|
||||
<div class ="subcategory-name">Desktop Head Tracking</div>
|
||||
<div class ="subcategory-description"></div>
|
||||
</div>
|
||||
|
||||
<div class ="row-wrapper">
|
||||
<div class ="option-caption">Enabled: </div>
|
||||
<div class ="option-input">
|
||||
<div id="Enabled" class ="inp_toggle no-scroll" data-current="false"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class ="row-wrapper">
|
||||
<div class ="option-caption">Use head tracking: </div>
|
||||
<div class ="option-input">
|
||||
<div id="HeadTracking" class ="inp_toggle no-scroll" data-current="true"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class ="row-wrapper">
|
||||
<div class ="option-caption">Use eyes tracking: </div>
|
||||
<div class ="option-input">
|
||||
<div id="EyeTracking" class ="inp_toggle no-scroll" data-current="true"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class ="row-wrapper">
|
||||
<div class ="option-caption">Use face tracking: </div>
|
||||
<div class ="option-input">
|
||||
<div id="FaceTracking" class ="inp_toggle no-scroll" data-current="true"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class ="row-wrapper">
|
||||
<div class ="option-caption">Use blinking: </div>
|
||||
<div class ="option-input">
|
||||
<div id="Blinking" class ="inp_toggle no-scroll" data-current="true"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class ="row-wrapper">
|
||||
<div class ="option-caption">Mirrored movement: </div>
|
||||
<div class ="option-input">
|
||||
<div id="Mirrored" class ="inp_toggle no-scroll" data-current="false"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class ="row-wrapper">
|
||||
<div class ="option-caption">Movement smoothing: </div>
|
||||
<div class ="option-input">
|
||||
<div id="Smoothing" class ="inp_slider no-scroll" data-min="0" data-max="99" data-current="50"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
`;
|
||||
document.getElementById('settings-implementation').appendChild(l_block);
|
||||
|
||||
// Toggles
|
||||
for (let l_toggle of l_block.querySelectorAll('.inp_toggle'))
|
||||
modsExtension.addSetting('DHT', l_toggle.id, modsExtension.createToggle(l_toggle, 'OnToggleUpdate_DHT'));
|
||||
|
||||
// Sliders
|
||||
for (let l_slider of l_block.querySelectorAll('.inp_slider'))
|
||||
modsExtension.addSetting('DHT', l_slider.id, modsExtension.createSlider(l_slider, 'OnSliderUpdate_DHT'));
|
||||
}
|
274
archived/ml_vet/Main.cs
Normal file
274
archived/ml_vet/Main.cs
Normal file
|
@ -0,0 +1,274 @@
|
|||
using ABI_RC.Core;
|
||||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Core.Player.EyeMovement;
|
||||
using ABI_RC.Core.Savior;
|
||||
using ABI_RC.Systems.FaceTracking;
|
||||
using ABI_RC.Systems.FaceTracking.Impl;
|
||||
using ABI_RC.Systems.RuntimeDebug;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using UnityEngine;
|
||||
using ViveSR.anipal.Eye;
|
||||
|
||||
namespace ml_vet
|
||||
{
|
||||
public class ViveEyeTracking : MelonLoader.MelonMod
|
||||
{
|
||||
const string c_gameSettingName = "ImplementationVRViveFaceTracking";
|
||||
|
||||
static ViveEyeTracking ms_instance = null;
|
||||
|
||||
readonly object m_lock = new object();
|
||||
EyeData_v2 m_threadEyeData; // Shared between threads
|
||||
SingleEyeData m_combinedEyeData;
|
||||
SingleEyeData m_leftEyeData;
|
||||
SingleEyeData m_rightEyeData;
|
||||
|
||||
Vector2 m_openness = Vector2.one;
|
||||
Vector3 m_leftEyeGaze = new Vector3(1f, 0f, 1f).normalized;
|
||||
Vector3 m_rightEyeGaze = new Vector3(-1f, 0f, 1f).normalized;
|
||||
Vector3 m_combinedGaze = Vector3.forward;
|
||||
Vector3 m_leftEyeOrigin = new Vector3(-0.025f, 0f, 0f);
|
||||
Vector3 m_rightEyeOrigin = new Vector3(0.025f, 0f, 0f);
|
||||
Vector3 m_cominedGazeOrigin = Vector3.zero;
|
||||
bool m_leftEyeGazeValid = false;
|
||||
bool m_rightEyeGazeValid = false;
|
||||
bool m_combinedGazeValid = false;
|
||||
Vector3 m_gazePoint = Vector3.forward;
|
||||
Vector3 m_gazeVelocity = Vector3.zero;
|
||||
|
||||
bool m_faceTrackingEnabled = false;
|
||||
|
||||
public override void OnInitializeMelon()
|
||||
{
|
||||
ms_instance = this;
|
||||
|
||||
Settings.Init();
|
||||
|
||||
HarmonyInstance.Patch(
|
||||
typeof(SRanipalTrackingModule).GetMethod(nameof(SRanipalTrackingModule.Initialize), BindingFlags.Public | BindingFlags.Instance),
|
||||
new HarmonyLib.HarmonyMethod(typeof(ViveEyeTracking).GetMethod(nameof(SRanipalTrackingModuleUpdate_Prefix), BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
HarmonyInstance.Patch(typeof(EyeMovementController).GetMethod("Update", BindingFlags.NonPublic | BindingFlags.Instance),
|
||||
null,
|
||||
new HarmonyLib.HarmonyMethod(typeof(ViveEyeTracking).GetMethod(nameof(EyeMovementControllerUpdate_Postfix), BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
|
||||
MelonLoader.MelonCoroutines.Start(WaitForRootLogic(HarmonyInstance));
|
||||
}
|
||||
|
||||
System.Collections.IEnumerator WaitForRootLogic(HarmonyLib.Harmony p_harmony)
|
||||
{
|
||||
while(RootLogic.Instance == null)
|
||||
yield return null;
|
||||
while(MetaPort.Instance == null)
|
||||
yield return null;
|
||||
|
||||
p_harmony.Patch(
|
||||
typeof(FaceTrackingManager).GetMethod(nameof(FaceTrackingManager.SubmitNewEyeData), BindingFlags.Public | BindingFlags.Instance),
|
||||
null,
|
||||
new HarmonyLib.HarmonyMethod(typeof(ViveEyeTracking).GetMethod(nameof(FaceTrackingManagerSubmitNewEyeData_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
|
||||
);
|
||||
|
||||
m_faceTrackingEnabled = MetaPort.Instance.settings.GetSettingsBool(c_gameSettingName);
|
||||
MetaPort.Instance.settings.settingBoolChanged.AddListener(this.OnGameSettingBoolChanged);
|
||||
}
|
||||
|
||||
public override void OnDeinitializeMelon()
|
||||
{
|
||||
ms_instance = null;
|
||||
}
|
||||
|
||||
public override void OnUpdate()
|
||||
{
|
||||
if(Settings.Enabled && m_faceTrackingEnabled && Utils.IsInVR())
|
||||
{
|
||||
if(Monitor.TryEnter(m_lock))
|
||||
{
|
||||
m_combinedEyeData = m_threadEyeData.verbose_data.combined.eye_data;
|
||||
m_leftEyeData = m_threadEyeData.verbose_data.left;
|
||||
m_rightEyeData = m_threadEyeData.verbose_data.right;
|
||||
Monitor.Exit(m_lock);
|
||||
}
|
||||
|
||||
m_combinedGazeValid = m_combinedEyeData.GetValidity(SingleEyeDataValidity.SINGLE_EYE_DATA_GAZE_DIRECTION_VALIDITY);
|
||||
if(m_combinedGazeValid)
|
||||
{
|
||||
m_combinedGaze = m_combinedEyeData.gaze_direction_normalized;
|
||||
m_combinedGaze.x *= -1f;
|
||||
}
|
||||
if(m_combinedEyeData.GetValidity(SingleEyeDataValidity.SINGLE_EYE_DATA_GAZE_ORIGIN_VALIDITY))
|
||||
{
|
||||
m_cominedGazeOrigin = m_combinedEyeData.gaze_origin_mm * 0.001f;
|
||||
m_cominedGazeOrigin.x *= -1f;
|
||||
}
|
||||
|
||||
|
||||
m_leftEyeGazeValid = m_leftEyeData.GetValidity(SingleEyeDataValidity.SINGLE_EYE_DATA_GAZE_DIRECTION_VALIDITY);
|
||||
if(m_leftEyeGazeValid)
|
||||
{
|
||||
m_leftEyeGaze = m_leftEyeData.gaze_direction_normalized;
|
||||
m_leftEyeGaze.x *= -1f;
|
||||
}
|
||||
if(m_leftEyeData.GetValidity(SingleEyeDataValidity.SINGLE_EYE_DATA_GAZE_ORIGIN_VALIDITY))
|
||||
{
|
||||
m_leftEyeOrigin = m_leftEyeData.gaze_origin_mm * 0.001f;
|
||||
m_leftEyeOrigin.x *= -1f;
|
||||
}
|
||||
if(m_leftEyeData.GetValidity(SingleEyeDataValidity.SINGLE_EYE_DATA_EYE_OPENNESS_VALIDITY))
|
||||
m_openness.x = m_leftEyeData.eye_openness;
|
||||
|
||||
m_rightEyeGazeValid = m_rightEyeData.GetValidity(SingleEyeDataValidity.SINGLE_EYE_DATA_GAZE_DIRECTION_VALIDITY);
|
||||
if(m_rightEyeGazeValid)
|
||||
{
|
||||
m_rightEyeGaze = m_rightEyeData.gaze_direction_normalized;
|
||||
m_rightEyeGaze.x *= -1f;
|
||||
}
|
||||
if(m_rightEyeData.GetValidity(SingleEyeDataValidity.SINGLE_EYE_DATA_GAZE_ORIGIN_VALIDITY))
|
||||
{
|
||||
m_rightEyeOrigin = m_rightEyeData.gaze_origin_mm * 0.001f;
|
||||
m_rightEyeOrigin.x *= -1f;
|
||||
}
|
||||
if(m_rightEyeData.GetValidity(SingleEyeDataValidity.SINGLE_EYE_DATA_EYE_OPENNESS_VALIDITY))
|
||||
m_openness.y = m_rightEyeData.eye_openness;
|
||||
}
|
||||
}
|
||||
|
||||
void OnGameSettingBoolChanged(string p_name, bool p_state)
|
||||
{
|
||||
if(p_name == c_gameSettingName)
|
||||
m_faceTrackingEnabled = p_state;
|
||||
}
|
||||
|
||||
bool IsReadyForUpdate() => (Settings.Enabled && m_faceTrackingEnabled && FaceTrackingManager.Instance.IsEyeDataAvailable());
|
||||
|
||||
// Patches
|
||||
static void SRanipalTrackingModuleUpdate_Prefix(ref bool useEye, bool useLip)
|
||||
{
|
||||
// Hijack face tracking module to be eye tracking module as well, because it is SRanipal too and can initialize without issues
|
||||
if(useLip && Settings.Enabled)
|
||||
useEye = true;
|
||||
}
|
||||
|
||||
static void FaceTrackingManagerSubmitNewEyeData_Postfix(ref EyeData_v2 eyeData) => ms_instance?.OnEyeDataPostSubmit(ref eyeData);
|
||||
void OnEyeDataPostSubmit(ref EyeData_v2 p_data)
|
||||
{
|
||||
// This is called not in main thread
|
||||
try
|
||||
{
|
||||
Monitor.Enter(m_lock);
|
||||
m_threadEyeData = p_data;
|
||||
Monitor.Exit(m_lock);
|
||||
}
|
||||
catch(System.Exception e)
|
||||
{
|
||||
MelonLoader.MelonLogger.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
static void EyeMovementControllerUpdate_Postfix(ref EyeMovementController __instance) => ms_instance?.OnEyeMovementControllerPostUpdate(__instance);
|
||||
void OnEyeMovementControllerPostUpdate(EyeMovementController p_controller)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(p_controller.IsLocal && IsReadyForUpdate())
|
||||
{
|
||||
p_controller.manualBlinking = true;
|
||||
#if NIGHTLY_BUILD
|
||||
p_controller.blinkProgressLeft = 1f - m_openness.x;
|
||||
p_controller.blinkProgressRight = 1f - m_openness.y;
|
||||
#else
|
||||
p_controller.blinkProgress = 1f - (m_openness.x + m_openness.y) * 0.5f;
|
||||
#endif
|
||||
p_controller.manualViewTarget = true;
|
||||
|
||||
Vector3 l_gazePoint = m_gazePoint;
|
||||
float l_playspaceScale = PlayerSetup.Instance.GetPlaySpaceScale();
|
||||
|
||||
if(m_leftEyeGazeValid && m_rightEyeGazeValid)
|
||||
{
|
||||
// https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
|
||||
// Projecting on OXZ plane to intersect eyes
|
||||
float x1 = m_leftEyeOrigin.x;
|
||||
float x2 = m_leftEyeOrigin.x + m_leftEyeGaze.x;
|
||||
float y1 = m_leftEyeOrigin.z;
|
||||
float y2 = m_leftEyeOrigin.z + m_leftEyeGaze.z;
|
||||
|
||||
float x3 = m_rightEyeOrigin.x;
|
||||
float x4 = m_rightEyeOrigin.x + m_rightEyeGaze.x;
|
||||
float y3 = m_rightEyeOrigin.z;
|
||||
float y4 = m_rightEyeOrigin.z + m_rightEyeGaze.z;
|
||||
|
||||
float l_det = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
|
||||
Vector3 l_combineGazeOrigin = Vector3.Lerp(m_leftEyeOrigin, m_rightEyeOrigin, 0.5f); // SRanipal's combined gaze origin is unstable
|
||||
|
||||
if(!Mathf.Approximately(l_det, 0f))
|
||||
{
|
||||
float l_detZ = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4);
|
||||
float l_posZ = l_detZ / l_det;
|
||||
|
||||
float l_thetaLeft = (l_posZ - m_leftEyeOrigin.z) / m_leftEyeGaze.z;
|
||||
Vector3 l_leftEyePoint = m_leftEyeOrigin + l_thetaLeft * m_leftEyeGaze;
|
||||
|
||||
float l_thetaRight = (l_posZ - m_rightEyeOrigin.z) / m_rightEyeGaze.z;
|
||||
Vector3 l_rightEyePoint = m_rightEyeOrigin + l_thetaRight * m_rightEyeGaze;
|
||||
|
||||
Vector3 l_midPoint = Vector3.Lerp(l_leftEyePoint, l_rightEyePoint, 0.5f);
|
||||
if(l_midPoint.z > l_combineGazeOrigin.z)
|
||||
{
|
||||
Vector3 l_resultDir = l_midPoint - l_combineGazeOrigin;
|
||||
l_resultDir = Vector3.ClampMagnitude(l_resultDir, 1f);
|
||||
l_gazePoint = (l_combineGazeOrigin + l_resultDir) * l_playspaceScale;
|
||||
}
|
||||
else
|
||||
l_gazePoint = (l_combineGazeOrigin + m_combinedGaze) * l_playspaceScale;
|
||||
}
|
||||
else
|
||||
l_gazePoint = (l_combineGazeOrigin + m_combinedGaze) * l_playspaceScale;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(m_leftEyeGazeValid)
|
||||
l_gazePoint = (m_leftEyeOrigin + m_leftEyeGaze) * l_playspaceScale;
|
||||
if(m_rightEyeGazeValid)
|
||||
l_gazePoint = (m_rightEyeGaze + m_rightEyeGaze) * l_playspaceScale;
|
||||
}
|
||||
|
||||
l_gazePoint = Vector3.SmoothDamp(m_gazePoint, l_gazePoint, ref m_gazeVelocity, Settings.Smoothing);
|
||||
p_controller.targetViewPosition = p_controller.ViewPointTransform.position + p_controller.ViewPointTransform.rotation * l_gazePoint;
|
||||
|
||||
if(m_rightEyeGazeValid || m_leftEyeGazeValid)
|
||||
m_gazePoint = l_gazePoint;
|
||||
|
||||
if(Settings.Debug)
|
||||
{
|
||||
Vector3 l_pos = p_controller.ViewPointTransform.position;
|
||||
Quaternion l_rot = p_controller.ViewPointTransform.rotation;
|
||||
|
||||
RuntimeGizmos.DrawArrowFromTo(
|
||||
l_pos + l_rot * (m_leftEyeOrigin * l_playspaceScale),
|
||||
l_pos + l_rot * ((m_leftEyeOrigin + (m_leftEyeGazeValid ? m_leftEyeGaze : Vector3.forward)) * l_playspaceScale),
|
||||
0.01f * l_playspaceScale, Color.blue, CVRLayers.Default, 0.25f
|
||||
);
|
||||
RuntimeGizmos.DrawArrowFromTo(
|
||||
l_pos + l_rot * (m_rightEyeOrigin * l_playspaceScale),
|
||||
l_pos + l_rot * ((m_rightEyeOrigin + (m_rightEyeGazeValid ? m_rightEyeGaze : Vector3.forward)) * l_playspaceScale),
|
||||
0.01f * l_playspaceScale, Color.blue, CVRLayers.Default, 0.25f
|
||||
);
|
||||
RuntimeGizmos.DrawArrowFromTo(
|
||||
l_pos + l_rot * (m_cominedGazeOrigin * l_playspaceScale),
|
||||
l_pos + l_rot * ((m_cominedGazeOrigin + (m_combinedGazeValid ? m_combinedGaze : Vector3.forward)) * l_playspaceScale),
|
||||
0.01f * l_playspaceScale, Color.red, CVRLayers.Default, 0.25f
|
||||
);
|
||||
|
||||
RuntimeGizmos.DrawSphere(p_controller.targetViewPosition, 0.01f * l_playspaceScale, Color.cyan, CVRLayers.Default, 0.25f);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(System.Exception e)
|
||||
{
|
||||
MelonLoader.MelonLogger.Error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
4
archived/ml_vet/Properties/AssemblyInfo.cs
Normal file
4
archived/ml_vet/Properties/AssemblyInfo.cs
Normal file
|
@ -0,0 +1,4 @@
|
|||
[assembly: MelonLoader.MelonInfo(typeof(ml_vet.ViveEyeTracking), "ViveEyeTracking", "1.0.2", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
|
||||
[assembly: MelonLoader.MelonGame(null, "ChilloutVR")]
|
||||
[assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
|
||||
[assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
|
26
archived/ml_vet/README.md
Normal file
26
archived/ml_vet/README.md
Normal file
|
@ -0,0 +1,26 @@
|
|||
# Vive Eye Tracking
|
||||
This mod complements functionality of in-game SRanipal face tracking module and makes it work additionaly as eye tracking module.
|
||||
|
||||
# Why?
|
||||
* Game has unfinished/unused and forcibly disabled eye tracking code that actually uses SRanipal API instead of TobiiXR.
|
||||
* Implemented native TobiiXR eye tracking is very unreliable. It freezes main thread with 66% chance at game launch and has limited detection of eyes openness.
|
||||
|
||||
# Benefits?
|
||||
* SRanipal API supports ranged eyes openness detection, unlike TobiiXR. You can finally squint now.
|
||||
* There is no main thread freeze at game launch.
|
||||
* Ready for future game update with separated eyes blinking (currently only nightly).
|
||||
|
||||
# Installation
|
||||
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
|
||||
* Get [latest release DLL](../../../releases/latest):
|
||||
* Put `ViveEyeTracking.dll` in `Mods` folder of game
|
||||
|
||||
# Usage
|
||||
Available mod's settings in `Settings - Implementation - Vive Eye Tracking`:
|
||||
* **Enable eye tracking:** eye tracking state; `true` by default.
|
||||
* Note: If Vive face tracking is already running, enabling requires Vive face tracking restart.
|
||||
* **Gaze smoothing:** smoothing of gaze point; 5 by default.
|
||||
* **Debug gizmos:** show debug lines of SRanipal gazes directions and calculated gaze point; `false` by default.
|
||||
|
||||
# Notes
|
||||
* Made primarily for Vive Pro Eye and SRanipal tracking.
|
30
archived/ml_vet/ResourcesHandler.cs
Normal file
30
archived/ml_vet/ResourcesHandler.cs
Normal file
|
@ -0,0 +1,30 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
namespace ml_vet
|
||||
{
|
||||
static class ResourcesHandler
|
||||
{
|
||||
readonly static string ms_namespace = typeof(ResourcesHandler).Namespace;
|
||||
|
||||
public static string GetEmbeddedResource(string p_name)
|
||||
{
|
||||
string l_result = "";
|
||||
Assembly l_assembly = Assembly.GetExecutingAssembly();
|
||||
|
||||
try
|
||||
{
|
||||
Stream l_libraryStream = l_assembly.GetManifestResourceStream(ms_namespace + ".resources." + p_name);
|
||||
StreamReader l_streadReader = new StreamReader(l_libraryStream);
|
||||
l_result = l_streadReader.ReadToEnd();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
MelonLoader.MelonLogger.Warning(e);
|
||||
}
|
||||
|
||||
return l_result;
|
||||
}
|
||||
}
|
||||
}
|
141
archived/ml_vet/Settings.cs
Normal file
141
archived/ml_vet/Settings.cs
Normal file
|
@ -0,0 +1,141 @@
|
|||
using ABI_RC.Core.InteractionSystem;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ml_vet
|
||||
{
|
||||
static class Settings
|
||||
{
|
||||
internal class SettingEvent<T>
|
||||
{
|
||||
event Action<T> m_action;
|
||||
public void AddListener(Action<T> p_listener) => m_action += p_listener;
|
||||
public void RemoveListener(Action<T> p_listener) => m_action -= p_listener;
|
||||
public void Invoke(T p_value) => m_action?.Invoke(p_value);
|
||||
}
|
||||
|
||||
enum ModSetting
|
||||
{
|
||||
Enabled = 0,
|
||||
Smoothing,
|
||||
Debug
|
||||
}
|
||||
|
||||
public static bool Enabled { get; private set; } = true;
|
||||
public static float Smoothing { get; private set; } = 0f;
|
||||
public static bool Debug { get; private set; } = false;
|
||||
|
||||
static MelonLoader.MelonPreferences_Category ms_category = null;
|
||||
static List<MelonLoader.MelonPreferences_Entry> ms_entries = null;
|
||||
|
||||
public static readonly SettingEvent<bool> OnEnabledChanged = new SettingEvent<bool>();
|
||||
public static readonly SettingEvent<float> OnSmoothingChanged = new SettingEvent<float>();
|
||||
public static readonly SettingEvent<bool> OnDebugChanged = new SettingEvent<bool>();
|
||||
|
||||
internal static void Init()
|
||||
{
|
||||
ms_category = MelonLoader.MelonPreferences.CreateCategory("VET", null, true);
|
||||
|
||||
ms_entries = new List<MelonLoader.MelonPreferences_Entry>()
|
||||
{
|
||||
ms_category.CreateEntry(ModSetting.Enabled.ToString(), Enabled),
|
||||
ms_category.CreateEntry(ModSetting.Smoothing.ToString(), (int)(Smoothing * 100f)),
|
||||
ms_category.CreateEntry(ModSetting.Debug.ToString(), Debug),
|
||||
};
|
||||
|
||||
Enabled = (bool)ms_entries[(int)ModSetting.Enabled].BoxedValue;
|
||||
Smoothing = ((int)ms_entries[(int)ModSetting.Smoothing].BoxedValue) * 0.01f;
|
||||
Debug = (bool)ms_entries[(int)ModSetting.Debug].BoxedValue;
|
||||
|
||||
MelonLoader.MelonCoroutines.Start(WaitMainMenuUi());
|
||||
}
|
||||
|
||||
static System.Collections.IEnumerator WaitMainMenuUi()
|
||||
{
|
||||
while(ViewManager.Instance == null)
|
||||
yield return null;
|
||||
while(ViewManager.Instance.cohtmlView == null)
|
||||
yield return null;
|
||||
while(ViewManager.Instance.cohtmlView.Listener == null)
|
||||
yield return null;
|
||||
|
||||
ViewManager.Instance.cohtmlView.Listener.ReadyForBindings += () =>
|
||||
{
|
||||
ViewManager.Instance.cohtmlView.View.BindCall("OnToggleUpdate_" + ms_category.Identifier, new Action<string, string>(OnToggleUpdate));
|
||||
ViewManager.Instance.cohtmlView.View.BindCall("OnSliderUpdate_" + ms_category.Identifier, new Action<string, string>(OnSliderUpdate));
|
||||
};
|
||||
ViewManager.Instance.cohtmlView.Listener.FinishLoad += (_) =>
|
||||
{
|
||||
ViewManager.Instance.cohtmlView.View.ExecuteScript(ResourcesHandler.GetEmbeddedResource("mods_extension.js"));
|
||||
ViewManager.Instance.cohtmlView.View.ExecuteScript(ResourcesHandler.GetEmbeddedResource("mod_menu.js"));
|
||||
MelonLoader.MelonCoroutines.Start(UpdateMenuSettings());
|
||||
};
|
||||
}
|
||||
|
||||
static System.Collections.IEnumerator UpdateMenuSettings()
|
||||
{
|
||||
while(!ViewManager.Instance.IsReady || !ViewManager.Instance.IsViewShown)
|
||||
yield return null;
|
||||
|
||||
foreach(var l_entry in ms_entries)
|
||||
ViewManager.Instance.cohtmlView.View.TriggerEvent("updateModSetting", ms_category.Identifier, l_entry.DisplayName, l_entry.GetValueAsString());
|
||||
}
|
||||
|
||||
static void OnToggleUpdate(string p_name, string p_value)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(Enum.TryParse(p_name, out ModSetting l_setting) && bool.TryParse(p_value, out bool l_value))
|
||||
{
|
||||
switch(l_setting)
|
||||
{
|
||||
case ModSetting.Enabled:
|
||||
{
|
||||
Enabled = l_value;
|
||||
OnEnabledChanged.Invoke(Enabled);
|
||||
}
|
||||
break;
|
||||
|
||||
case ModSetting.Debug:
|
||||
{
|
||||
Debug = l_value;
|
||||
OnDebugChanged.Invoke(Debug);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
ms_entries[(int)l_setting].BoxedValue = l_value;
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
MelonLoader.MelonLogger.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
static void OnSliderUpdate(string p_name, string p_value)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(Enum.TryParse(p_name, out ModSetting l_setting) && int.TryParse(p_value, out int l_value))
|
||||
{
|
||||
switch(l_setting)
|
||||
{
|
||||
case ModSetting.Smoothing:
|
||||
{
|
||||
Smoothing = l_value * 0.01f;
|
||||
OnSmoothingChanged.Invoke(Smoothing);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
ms_entries[(int)l_setting].BoxedValue = l_value;
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
MelonLoader.MelonLogger.Error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
15
archived/ml_vet/Utils.cs
Normal file
15
archived/ml_vet/Utils.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
using ABI_RC.Core.Savior;
|
||||
using ABI_RC.Core.UI;
|
||||
using System.Reflection;
|
||||
|
||||
namespace ml_vet
|
||||
{
|
||||
public static class Utils
|
||||
{
|
||||
static readonly FieldInfo ms_view = typeof(CohtmlControlledViewWrapper).GetField("_view", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
|
||||
public static void ExecuteScript(this CohtmlControlledViewWrapper p_instance, string p_script) => (ms_view?.GetValue(p_instance) as cohtml.Net.View)?.ExecuteScript(p_script);
|
||||
|
||||
public static bool IsInVR() => ((MetaPort.Instance != null) && MetaPort.Instance.isUsingVr);
|
||||
}
|
||||
}
|
71
archived/ml_vet/ml_vet.csproj
Normal file
71
archived/ml_vet/ml_vet.csproj
Normal file
|
@ -0,0 +1,71 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<Platforms>x64</Platforms>
|
||||
<PackageId>ViveEyeTracking</PackageId>
|
||||
<Version>1.0.2</Version>
|
||||
<Authors>SDraw</Authors>
|
||||
<Company>SDraw</Company>
|
||||
<Product>ViveEyeTracking</Product>
|
||||
<AssemblyName>ViveEyeTracking</AssemblyName>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<DebugType>embedded</DebugType>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DefineConstants>TRACE;NIGHTLY_BUILD</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="resources\mod_menu.js" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="..\js\mods_extension.js" Link="resources\mods_extension.js" />
|
||||
<EmbeddedResource Include="resources\mod_menu.js" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="0Harmony">
|
||||
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\0Harmony.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
<SpecificVersion>false</SpecificVersion>
|
||||
</Reference>
|
||||
<Reference Include="Assembly-CSharp">
|
||||
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
<SpecificVersion>false</SpecificVersion>
|
||||
</Reference>
|
||||
<Reference Include="cohtml.Net">
|
||||
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\cohtml.Net.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
<SpecificVersion>false</SpecificVersion>
|
||||
</Reference>
|
||||
<Reference Include="Cohtml.Runtime">
|
||||
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Cohtml.Runtime.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
<SpecificVersion>false</SpecificVersion>
|
||||
</Reference>
|
||||
<Reference Include="MelonLoader">
|
||||
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\MelonLoader.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
<SpecificVersion>false</SpecificVersion>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine">
|
||||
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
<SpecificVersion>false</SpecificVersion>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.CoreModule">
|
||||
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
<SpecificVersion>false</SpecificVersion>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
<Exec Command="copy /y "$(TargetPath)" "D:\Games\Steam\steamapps\common\ChilloutVR\Mods\"" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
42
archived/ml_vet/resources/mod_menu.js
Normal file
42
archived/ml_vet/resources/mod_menu.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
let l_block = document.createElement('div');
|
||||
l_block.innerHTML = `
|
||||
<div class ="settings-subcategory">
|
||||
<div class ="subcategory-name">Vive Eye Tracking</div>
|
||||
<div class ="subcategory-description"></div>
|
||||
</div>
|
||||
|
||||
<div class ="row-wrapper">
|
||||
<div class ="option-caption">Enable eye tracking: </div>
|
||||
<div class ="option-input">
|
||||
<div id="Enabled" class ="inp_toggle no-scroll" data-current="true"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row-wrapper">
|
||||
<p>Requires Vive Face tracking restart at first.</p>
|
||||
</div>
|
||||
|
||||
<div class ="row-wrapper">
|
||||
<div class ="option-caption">Gaze smoothing: </div>
|
||||
<div class ="option-input">
|
||||
<div id="Smoothing" class ="inp_slider no-scroll" data-min="0" data-max="100" data-current="5"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class ="row-wrapper">
|
||||
<div class ="option-caption">Debug gizmos: </div>
|
||||
<div class ="option-input">
|
||||
<div id="Debug" class ="inp_toggle no-scroll" data-current="false"></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.getElementById('settings-implementation').appendChild(l_block);
|
||||
|
||||
// Toggles
|
||||
for (let l_toggle of l_block.querySelectorAll('.inp_toggle'))
|
||||
modsExtension.addSetting('VET', l_toggle.id, modsExtension.createToggle(l_toggle, 'OnToggleUpdate_VET'));
|
||||
|
||||
// Sliders
|
||||
for (let l_slider of l_block.querySelectorAll('.inp_slider'))
|
||||
modsExtension.addSetting('VET', l_slider.id, modsExtension.createSlider(l_slider, 'OnSliderUpdate_VET'));
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue