mirror of
https://github.com/hanetzer/sdraw_mods_cvr.git
synced 2025-09-03 18:39:23 +00:00
Desktop Head Tracking mod
This commit is contained in:
parent
0ebafccb33
commit
b116b14843
14 changed files with 847 additions and 0 deletions
BIN
ml_dht/.github/img_01.png
vendored
Normal file
BIN
ml_dht/.github/img_01.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 291 KiB |
101
ml_dht/FaceTracked.cs
Normal file
101
ml_dht/FaceTracked.cs
Normal file
|
@ -0,0 +1,101 @@
|
|||
using ABI_RC.Core.Player;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ml_dht
|
||||
{
|
||||
class FaceTracked : MonoBehaviour
|
||||
{
|
||||
bool m_enabled = true;
|
||||
float m_smoothing = 0.5f;
|
||||
bool m_mirrored = false;
|
||||
|
||||
RootMotion.FinalIK.LookAtIK m_lookIK = null;
|
||||
Transform m_camera = null;
|
||||
Transform m_headBone = null;
|
||||
|
||||
Vector3 m_headPosition;
|
||||
Quaternion m_headRotation;
|
||||
Vector2 m_gazeDirection;
|
||||
float m_blinkProgress = 0f;
|
||||
Vector2 m_mouthShapes;
|
||||
float m_eyebrowsProgress = 0f;
|
||||
|
||||
Quaternion m_bindRotation;
|
||||
Quaternion m_lastHeadRotation;
|
||||
|
||||
void Start()
|
||||
{
|
||||
m_camera = PlayerSetup.Instance.desktopCamera.transform;
|
||||
}
|
||||
|
||||
public void UpdateTrackingData(ref TrackingData p_data)
|
||||
{
|
||||
m_headPosition.Set(p_data.m_headPositionX * (m_mirrored ? -1f : 1f), p_data.m_headPositionY, p_data.m_headPositionZ);
|
||||
m_headRotation.Set(p_data.m_headRotationX, p_data.m_headRotationY * (m_mirrored ? -1f : 1f), p_data.m_headRotationZ * (m_mirrored ? -1f : 1f), p_data.m_headRotationW);
|
||||
m_gazeDirection.Set(m_mirrored ? (1f - p_data.m_gazeX) : p_data.m_gazeX, p_data.m_gazeY);
|
||||
m_blinkProgress = p_data.m_blink;
|
||||
m_mouthShapes.Set(p_data.m_mouthOpen, p_data.m_mouthShape);
|
||||
m_eyebrowsProgress = p_data.m_brows;
|
||||
}
|
||||
|
||||
public void OnEyeControllerUpdate()
|
||||
{
|
||||
if(m_enabled)
|
||||
{
|
||||
// Gaze
|
||||
PlayerSetup.Instance.eyeMovement.manualViewTarget = true;
|
||||
PlayerSetup.Instance.eyeMovement.targetViewPosition = m_camera.position + m_camera.rotation * new Vector3((m_gazeDirection.x - 0.5f) * -2f, (m_gazeDirection.y - 0.5f) * 2f, 1f);
|
||||
|
||||
// Blink
|
||||
PlayerSetup.Instance.eyeMovement.manualBlinking = true;
|
||||
PlayerSetup.Instance.eyeMovement.blinkProgress = m_blinkProgress;
|
||||
}
|
||||
}
|
||||
|
||||
void OnLookIKPostUpdate()
|
||||
{
|
||||
if(m_enabled && (m_headBone != null))
|
||||
{
|
||||
m_lastHeadRotation = Quaternion.Slerp(m_lastHeadRotation, m_headRotation * m_bindRotation, m_smoothing);
|
||||
m_headBone.localRotation = m_lastHeadRotation;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnSetupAvatarGeneral()
|
||||
{
|
||||
m_headBone = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Head);
|
||||
m_lookIK = PlayerSetup.Instance._avatar.GetComponent<RootMotion.FinalIK.LookAtIK>();
|
||||
|
||||
if(m_headBone != null)
|
||||
m_bindRotation = m_headBone.localRotation;
|
||||
|
||||
if(m_lookIK != null)
|
||||
m_lookIK.solver.OnPostUpdate += this.OnLookIKPostUpdate;
|
||||
}
|
||||
public void OnAvatarClear()
|
||||
{
|
||||
m_lookIK = null;
|
||||
m_headBone = null;
|
||||
m_lastHeadRotation = Quaternion.identity;
|
||||
m_bindRotation = Quaternion.identity;
|
||||
}
|
||||
|
||||
public void SetEnabled(bool p_state)
|
||||
{
|
||||
if(m_enabled != p_state)
|
||||
{
|
||||
m_enabled = p_state;
|
||||
if(m_enabled)
|
||||
m_lastHeadRotation = m_bindRotation;
|
||||
}
|
||||
}
|
||||
public void SetSmoothing(float p_value)
|
||||
{
|
||||
m_smoothing = 1f - Mathf.Clamp(p_value, 0f, 0.99f);
|
||||
}
|
||||
public void SetMirrored(bool p_state)
|
||||
{
|
||||
m_mirrored = p_state;
|
||||
}
|
||||
}
|
||||
}
|
133
ml_dht/Main.cs
Normal file
133
ml_dht/Main.cs
Normal file
|
@ -0,0 +1,133 @@
|
|||
using ABI_RC.Core.Player;
|
||||
|
||||
namespace ml_dht
|
||||
{
|
||||
public class DesktopHeadTracking : MelonLoader.MelonMod
|
||||
{
|
||||
static DesktopHeadTracking ms_instance = null;
|
||||
|
||||
MemoryMapReader m_mapReader = null;
|
||||
byte[] m_buffer = null;
|
||||
TrackingData m_trackingData;
|
||||
|
||||
FaceTracked m_localTracked = null;
|
||||
|
||||
public override void OnApplicationStart()
|
||||
{
|
||||
if(ms_instance == null)
|
||||
ms_instance = this;
|
||||
|
||||
Settings.Init();
|
||||
Settings.EnabledChange += this.OnEnabledChanged;
|
||||
Settings.MirroredChange += this.OnMirroredChanged;
|
||||
Settings.SmoothingChange += this.OnSmoothingChanged;
|
||||
|
||||
m_mapReader = new MemoryMapReader();
|
||||
m_buffer = new byte[1024];
|
||||
|
||||
m_mapReader.Open("head/data");
|
||||
|
||||
// Patches
|
||||
HarmonyInstance.Patch(
|
||||
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.ClearAvatar)),
|
||||
null,
|
||||
new HarmonyLib.HarmonyMethod(typeof(DesktopHeadTracking).GetMethod(nameof(OnAvatarClear_Postfix), System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic))
|
||||
);
|
||||
HarmonyInstance.Patch(
|
||||
typeof(PlayerSetup).GetMethod("SetupAvatarGeneral", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic),
|
||||
null,
|
||||
new HarmonyLib.HarmonyMethod(typeof(DesktopHeadTracking).GetMethod(nameof(OnSetupAvatarGeneral_Postfix), System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic))
|
||||
);
|
||||
HarmonyInstance.Patch(
|
||||
typeof(CVREyeController).GetMethod("Update", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic),
|
||||
null,
|
||||
new HarmonyLib.HarmonyMethod(typeof(DesktopHeadTracking).GetMethod(nameof(OnEyeControllerUpdate_Postfix), System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic))
|
||||
);
|
||||
|
||||
MelonLoader.MelonCoroutines.Start(WaitForPlayer());
|
||||
}
|
||||
|
||||
System.Collections.IEnumerator WaitForPlayer()
|
||||
{
|
||||
while(PlayerSetup.Instance == null)
|
||||
yield return null;
|
||||
|
||||
m_localTracked = PlayerSetup.Instance.gameObject.AddComponent<FaceTracked>();
|
||||
m_localTracked.SetEnabled(Settings.Enabled);
|
||||
m_localTracked.SetMirrored(Settings.Mirrored);
|
||||
m_localTracked.SetSmoothing(Settings.Smoothing);
|
||||
}
|
||||
|
||||
public override void OnUpdate()
|
||||
{
|
||||
if(Settings.Enabled && m_mapReader.Read(ref m_buffer))
|
||||
{
|
||||
m_trackingData = TrackingData.ToObject(m_buffer);
|
||||
if(m_localTracked != null)
|
||||
m_localTracked.UpdateTrackingData(ref m_trackingData);
|
||||
}
|
||||
}
|
||||
|
||||
void OnEnabledChanged(bool p_state)
|
||||
{
|
||||
if(m_localTracked != null)
|
||||
m_localTracked.SetEnabled(p_state);
|
||||
}
|
||||
void OnMirroredChanged(bool p_state)
|
||||
{
|
||||
if(m_localTracked != null)
|
||||
m_localTracked.SetMirrored(p_state);
|
||||
}
|
||||
void OnSmoothingChanged(float p_value)
|
||||
{
|
||||
if(m_localTracked != null)
|
||||
m_localTracked.SetSmoothing(p_value);
|
||||
}
|
||||
|
||||
static void OnSetupAvatarGeneral_Postfix() => ms_instance?.OnSetupAvatarGeneral();
|
||||
void OnSetupAvatarGeneral()
|
||||
{
|
||||
try
|
||||
{
|
||||
if(m_localTracked != null)
|
||||
m_localTracked.OnSetupAvatarGeneral();
|
||||
}
|
||||
catch(System.Exception e)
|
||||
{
|
||||
MelonLoader.MelonLogger.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
static void OnAvatarClear_Postfix() => ms_instance?.OnAvatarClear();
|
||||
void OnAvatarClear()
|
||||
{
|
||||
try
|
||||
{
|
||||
if(m_localTracked != null)
|
||||
m_localTracked.OnAvatarClear();
|
||||
}
|
||||
catch(System.Exception e)
|
||||
{
|
||||
MelonLoader.MelonLogger.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
static void OnEyeControllerUpdate_Postfix(ref CVREyeController __instance)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(__instance == PlayerSetup.Instance.eyeMovement)
|
||||
ms_instance?.OnEyeControllerUpdate();
|
||||
}
|
||||
catch(System.Exception e)
|
||||
{
|
||||
MelonLoader.MelonLogger.Error(e);
|
||||
}
|
||||
}
|
||||
void OnEyeControllerUpdate()
|
||||
{
|
||||
if(m_localTracked != null)
|
||||
m_localTracked.OnEyeControllerUpdate();
|
||||
}
|
||||
}
|
||||
}
|
55
ml_dht/MemoryMapReader.cs
Normal file
55
ml_dht/MemoryMapReader.cs
Normal file
|
@ -0,0 +1,55 @@
|
|||
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) { }
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
10
ml_dht/Properties/AssemblyInfo.cs
Normal file
10
ml_dht/Properties/AssemblyInfo.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyTitle("DesktopHeadTracking")]
|
||||
[assembly: AssemblyVersion("1.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0")]
|
||||
|
||||
[assembly: MelonLoader.MelonInfo(typeof(ml_dht.DesktopHeadTracking), "DesktopHeadTracking", "1.0.0", "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)]
|
27
ml_dht/README.md
Normal file
27
ml_dht/README.md
Normal file
|
@ -0,0 +1,27 @@
|
|||
# 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
|
||||
* Blinking
|
||||
|
||||
# Installation
|
||||
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
|
||||
* Get [latest release DLL](../../../releases/latest):
|
||||
* Put `ml_dht.dll` in `Mods` folder of game
|
||||
|
||||
# Usage
|
||||
Available mod's settings in `Settings - Implementation - Desktop Head Tracking`:
|
||||
* **Enabled:** enabled head tracking; default value - `false`.
|
||||
* **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 with [Tracking Data Parser mod](https://github.com/SDraw/ml_mods_vsf)
|
||||
|
||||
# Notes
|
||||
* Blinking doesn't work for remote players to due [game's bug](https://feedback.abinteractive.net/p/overrided-blinking-state-isn-t-copied-to-movement-data-from-network).
|
26
ml_dht/Scripts.cs
Normal file
26
ml_dht/Scripts.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
namespace ml_dht
|
||||
{
|
||||
static class Scripts
|
||||
{
|
||||
public static string GetEmbeddedScript(string p_name)
|
||||
{
|
||||
string l_result = "";
|
||||
Assembly l_assembly = Assembly.GetExecutingAssembly();
|
||||
string l_assemblyName = l_assembly.GetName().Name;
|
||||
|
||||
try
|
||||
{
|
||||
Stream l_libraryStream = l_assembly.GetManifestResourceStream(l_assemblyName + ".resources." + p_name);
|
||||
StreamReader l_streadReader = new StreamReader(l_libraryStream);
|
||||
l_result = l_streadReader.ReadToEnd();
|
||||
}
|
||||
catch(Exception) { }
|
||||
|
||||
return l_result;
|
||||
}
|
||||
}
|
||||
}
|
127
ml_dht/Settings.cs
Normal file
127
ml_dht/Settings.cs
Normal file
|
@ -0,0 +1,127 @@
|
|||
using ABI_RC.Core.InteractionSystem;
|
||||
using cohtml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ml_dht
|
||||
{
|
||||
static class Settings
|
||||
{
|
||||
enum ModSetting
|
||||
{
|
||||
Enabled = 0,
|
||||
Mirrored,
|
||||
Smoothing
|
||||
}
|
||||
|
||||
static bool ms_enabled = false;
|
||||
static bool ms_mirrored = false;
|
||||
static float ms_smoothing = 0.5f;
|
||||
|
||||
static MelonLoader.MelonPreferences_Category ms_category = null;
|
||||
static List<MelonLoader.MelonPreferences_Entry> ms_entries = null;
|
||||
|
||||
static public event Action<bool> EnabledChange;
|
||||
static public event Action<bool> MirroredChange;
|
||||
static public event Action<float> SmoothingChange;
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
ms_category = MelonLoader.MelonPreferences.CreateCategory("DHT");
|
||||
|
||||
ms_entries = new List<MelonLoader.MelonPreferences_Entry>();
|
||||
ms_entries.Add(ms_category.CreateEntry(ModSetting.Enabled.ToString(), false));
|
||||
ms_entries.Add(ms_category.CreateEntry(ModSetting.Mirrored.ToString(), false));
|
||||
ms_entries.Add(ms_category.CreateEntry(ModSetting.Smoothing.ToString(), 50));
|
||||
|
||||
Load();
|
||||
|
||||
MelonLoader.MelonCoroutines.Start(WaitMainMenuUi());
|
||||
}
|
||||
|
||||
static System.Collections.IEnumerator WaitMainMenuUi()
|
||||
{
|
||||
while(ViewManager.Instance == null)
|
||||
yield return null;
|
||||
while(ViewManager.Instance.gameMenuView == null)
|
||||
yield return null;
|
||||
while(ViewManager.Instance.gameMenuView.Listener == null)
|
||||
yield return null;
|
||||
|
||||
ViewManager.Instance.gameMenuView.Listener.ReadyForBindings += () =>
|
||||
{
|
||||
ViewManager.Instance.gameMenuView.View.BindCall("MelonMod_DHT_Call_InpSlider", new Action<string, string>(OnSliderUpdate));
|
||||
ViewManager.Instance.gameMenuView.View.BindCall("MelonMod_DHT_Call_InpToggle", new Action<string, string>(OnToggleUpdate));
|
||||
};
|
||||
ViewManager.Instance.gameMenuView.Listener.FinishLoad += (_) =>
|
||||
{
|
||||
ViewManager.Instance.gameMenuView.View.ExecuteScript(Scripts.GetEmbeddedScript("menu.js"));
|
||||
foreach(var l_entry in ms_entries)
|
||||
ViewManager.Instance.gameMenuView.View.TriggerEvent("updateModSettingDHT", l_entry.DisplayName, l_entry.GetValueAsString());
|
||||
};
|
||||
}
|
||||
|
||||
static void Load()
|
||||
{
|
||||
ms_enabled = (bool)ms_entries[(int)ModSetting.Enabled].BoxedValue;
|
||||
ms_mirrored = (bool)ms_entries[(int)ModSetting.Mirrored].BoxedValue;
|
||||
ms_smoothing = ((int)ms_entries[(int)ModSetting.Smoothing].BoxedValue) * 0.01f;
|
||||
}
|
||||
|
||||
static void OnSliderUpdate(string p_name, string p_value)
|
||||
{
|
||||
if(Enum.TryParse(p_name, out ModSetting l_setting))
|
||||
{
|
||||
switch(l_setting)
|
||||
{
|
||||
case ModSetting.Smoothing:
|
||||
{
|
||||
ms_smoothing = int.Parse(p_value) * 0.01f;
|
||||
SmoothingChange?.Invoke(ms_smoothing);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
ms_entries[(int)l_setting].BoxedValue = int.Parse(p_value);
|
||||
}
|
||||
}
|
||||
|
||||
static void OnToggleUpdate(string p_name, string p_value)
|
||||
{
|
||||
if(Enum.TryParse(p_name, out ModSetting l_setting))
|
||||
{
|
||||
switch(l_setting)
|
||||
{
|
||||
case ModSetting.Enabled:
|
||||
{
|
||||
ms_enabled = bool.Parse(p_value);
|
||||
EnabledChange?.Invoke(ms_enabled);
|
||||
}
|
||||
break;
|
||||
|
||||
case ModSetting.Mirrored:
|
||||
{
|
||||
ms_mirrored = bool.Parse(p_value);
|
||||
MirroredChange?.Invoke(ms_mirrored);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
ms_entries[(int)l_setting].BoxedValue = bool.Parse(p_value);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Enabled
|
||||
{
|
||||
get => ms_enabled;
|
||||
}
|
||||
public static bool Mirrored
|
||||
{
|
||||
get => ms_mirrored;
|
||||
}
|
||||
public static float Smoothing
|
||||
{
|
||||
get => ms_smoothing;
|
||||
}
|
||||
}
|
||||
}
|
46
ml_dht/TrackingData.cs
Normal file
46
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], not used yet
|
||||
public float m_mouthShape; // Range - [0;1], not used yet
|
||||
public float m_brows; // Range - [0;1], not used yet
|
||||
|
||||
static public 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;
|
||||
}
|
||||
|
||||
static public 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;
|
||||
}
|
||||
}
|
92
ml_dht/ml_dht.csproj
Normal file
92
ml_dht/ml_dht.csproj
Normal file
|
@ -0,0 +1,92 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{6DD89FC3-A974-4C39-A3EE-F60C24B17B5B}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>ml_dht</RootNamespace>
|
||||
<AssemblyName>ml_dht</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<Deterministic>true</Deterministic>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x64\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<OutputPath>bin\x64\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="0Harmony, Version=2.9.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Assembly-CSharp, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Assembly-CSharp-firstpass, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>C:\Games\Steam\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="cohtml.Net">
|
||||
<HintPath>C:\Games\Steam\common\ChilloutVR\ChilloutVR_Data\Managed\cohtml.Net.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Cohtml.Runtime">
|
||||
<HintPath>C:\Games\Steam\common\ChilloutVR\ChilloutVR_Data\Managed\Cohtml.Runtime.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="MelonLoader">
|
||||
<HintPath>C:\Games\Steam\common\ChilloutVR\MelonLoader\MelonLoader.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="UnityEngine.AnimationModule, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.CoreModule">
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="FaceTracked.cs" />
|
||||
<Compile Include="Main.cs" />
|
||||
<Compile Include="MemoryMapReader.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Scripts.cs" />
|
||||
<Compile Include="Settings.cs" />
|
||||
<Compile Include="TrackingData.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="resources\menu.js" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
<PostBuildEvent>copy /y "$(TargetPath)" "C:\Games\Steam\common\ChilloutVR\Mods\"</PostBuildEvent>
|
||||
</PropertyGroup>
|
||||
</Project>
|
6
ml_dht/ml_dht.csproj.user
Normal file
6
ml_dht/ml_dht.csproj.user
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<ReferencePath>C:\Games\Steam\common\ChilloutVR\MelonLoader\;C:\Games\Steam\common\ChilloutVR\ChilloutVR_Data\Managed\</ReferencePath>
|
||||
</PropertyGroup>
|
||||
</Project>
|
217
ml_dht/resources/menu.js
Normal file
217
ml_dht/resources/menu.js
Normal file
|
@ -0,0 +1,217 @@
|
|||
// Add settings
|
||||
var g_modSettingsDHT = [];
|
||||
|
||||
engine.on('updateModSettingDHT', function (_name, _value) {
|
||||
for (var i = 0; i < g_modSettingsDHT.length; i++) {
|
||||
if (g_modSettingsDHT[i].name == _name) {
|
||||
g_modSettingsDHT[i].updateValue(_value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Modified from original `inp` types, because I have no js knowledge to hook stuff
|
||||
function inp_slider_mod_dht(_obj, _callbackName) {
|
||||
this.obj = _obj;
|
||||
this.callbackName = _callbackName;
|
||||
this.minValue = parseFloat(_obj.getAttribute('data-min'));
|
||||
this.maxValue = parseFloat(_obj.getAttribute('data-max'));
|
||||
this.percent = 0;
|
||||
this.value = parseFloat(_obj.getAttribute('data-current'));
|
||||
this.dragActive = false;
|
||||
this.name = _obj.id;
|
||||
this.type = _obj.getAttribute('data-type');
|
||||
this.stepSize = _obj.getAttribute('data-stepSize') || 0;
|
||||
this.format = _obj.getAttribute('data-format') || '{value}';
|
||||
|
||||
var self = this;
|
||||
|
||||
if (this.stepSize != 0)
|
||||
this.value = Math.round(this.value / this.stepSize) * this.stepSize;
|
||||
else
|
||||
this.value = Math.round(this.value);
|
||||
|
||||
this.valueLabelBackground = document.createElement('div');
|
||||
this.valueLabelBackground.className = 'valueLabel background';
|
||||
this.valueLabelBackground.innerHTML = this.format.replace('{value}', this.value);
|
||||
this.obj.appendChild(this.valueLabelBackground);
|
||||
|
||||
this.valueBar = document.createElement('div');
|
||||
this.valueBar.className = 'valueBar';
|
||||
this.valueBar.setAttribute('style', 'width: ' + (((this.value - this.minValue) / (this.maxValue - this.minValue)) * 100) + '%;');
|
||||
this.obj.appendChild(this.valueBar);
|
||||
|
||||
this.valueLabelForeground = document.createElement('div');
|
||||
this.valueLabelForeground.className = 'valueLabel foreground';
|
||||
this.valueLabelForeground.innerHTML = this.format.replace('{value}', this.value);
|
||||
this.valueLabelForeground.setAttribute('style', 'width: ' + (1.0 / ((this.value - this.minValue) / (this.maxValue - this.minValue)) * 100) + '%;');
|
||||
this.valueBar.appendChild(this.valueLabelForeground);
|
||||
|
||||
this.mouseDown = function (_e) {
|
||||
self.dragActive = true;
|
||||
self.mouseMove(_e, false);
|
||||
}
|
||||
|
||||
this.mouseMove = function (_e, _write) {
|
||||
if (self.dragActive) {
|
||||
var rect = _obj.getBoundingClientRect();
|
||||
var start = rect.left;
|
||||
var end = rect.right;
|
||||
self.percent = Math.min(Math.max((_e.clientX - start) / rect.width, 0), 1);
|
||||
var value = self.percent;
|
||||
value *= (self.maxValue - self.minValue);
|
||||
value += self.minValue;
|
||||
if (self.stepSize != 0) {
|
||||
value = Math.round(value / self.stepSize);
|
||||
self.value = value * self.stepSize;
|
||||
self.percent = (self.value - self.minValue) / (self.maxValue - self.minValue);
|
||||
}
|
||||
else
|
||||
self.value = Math.round(value);
|
||||
|
||||
self.valueBar.setAttribute('style', 'width: ' + (self.percent * 100) + '%;');
|
||||
self.valueLabelForeground.setAttribute('style', 'width: ' + (1.0 / self.percent * 100) + '%;');
|
||||
self.valueLabelBackground.innerHTML = self.valueLabelForeground.innerHTML = self.format.replace('{value}', self.value);
|
||||
|
||||
engine.call(self.callbackName, self.name, "" + self.value);
|
||||
self.displayImperial();
|
||||
}
|
||||
}
|
||||
|
||||
this.mouseUp = function (_e) {
|
||||
self.mouseMove(_e, true);
|
||||
self.dragActive = false;
|
||||
}
|
||||
|
||||
_obj.addEventListener('mousedown', this.mouseDown);
|
||||
document.addEventListener('mousemove', this.mouseMove);
|
||||
document.addEventListener('mouseup', this.mouseUp);
|
||||
|
||||
this.getValue = function () {
|
||||
return self.value;
|
||||
}
|
||||
|
||||
this.updateValue = function (value) {
|
||||
if (self.stepSize != 0)
|
||||
self.value = Math.round(value * self.stepSize) / self.stepSize;
|
||||
else
|
||||
self.value = Math.round(value);
|
||||
self.percent = (self.value - self.minValue) / (self.maxValue - self.minValue);
|
||||
self.valueBar.setAttribute('style', 'width: ' + (self.percent * 100) + '%;');
|
||||
self.valueLabelForeground.setAttribute('style', 'width: ' + (1.0 / self.percent * 100) + '%;');
|
||||
self.valueLabelBackground.innerHTML = self.valueLabelForeground.innerHTML = self.format.replace('{value}', self.value);
|
||||
self.displayImperial();
|
||||
}
|
||||
|
||||
this.displayImperial = function () {
|
||||
var displays = document.querySelectorAll('.imperialDisplay');
|
||||
for (var i = 0; i < displays.length; i++) {
|
||||
var binding = displays[i].getAttribute('data-binding');
|
||||
if (binding == self.name) {
|
||||
var realFeet = ((self.value * 0.393700) / 12);
|
||||
var feet = Math.floor(realFeet);
|
||||
var inches = Math.floor((realFeet - feet) * 12);
|
||||
displays[i].innerHTML = feet + "'" + inches + '''';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name: this.name,
|
||||
value: this.getValue,
|
||||
updateValue: this.updateValue
|
||||
}
|
||||
}
|
||||
|
||||
// Modified from original `inp` types, because I have no js knowledge to hook stuff
|
||||
function inp_toggle_mod_dht(_obj, _callbackName) {
|
||||
this.obj = _obj;
|
||||
this.callbackName = _callbackName;
|
||||
this.value = _obj.getAttribute('data-current');
|
||||
this.name = _obj.id;
|
||||
this.type = _obj.getAttribute('data-type');
|
||||
|
||||
var self = this;
|
||||
|
||||
this.mouseDown = function (_e) {
|
||||
self.value = self.value == "True" ? "False" : "True";
|
||||
self.updateState();
|
||||
}
|
||||
|
||||
this.updateState = function () {
|
||||
self.obj.classList.remove("checked");
|
||||
if (self.value == "True") {
|
||||
self.obj.classList.add("checked");
|
||||
}
|
||||
|
||||
engine.call(self.callbackName, self.name, self.value);
|
||||
}
|
||||
|
||||
_obj.addEventListener('mousedown', this.mouseDown);
|
||||
|
||||
this.getValue = function () {
|
||||
return self.value;
|
||||
}
|
||||
|
||||
this.updateValue = function (value) {
|
||||
self.value = value;
|
||||
|
||||
self.obj.classList.remove("checked");
|
||||
if (self.value == "True") {
|
||||
self.obj.classList.add("checked");
|
||||
}
|
||||
}
|
||||
|
||||
this.updateValue(this.value);
|
||||
|
||||
return {
|
||||
name: this.name,
|
||||
value: this.getValue,
|
||||
updateValue: this.updateValue
|
||||
}
|
||||
}
|
||||
|
||||
// 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">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);
|
||||
|
||||
// Update sliders in new menu block
|
||||
let l_sliders = l_block.querySelectorAll('.inp_slider');
|
||||
for (var i = 0; i < l_sliders.length; i++) {
|
||||
g_modSettingsDHT[g_modSettingsDHT.length] = new inp_slider_mod_dht(l_sliders[i], 'MelonMod_DHT_Call_InpSlider');
|
||||
}
|
||||
|
||||
// Update toggles in new menu block
|
||||
let l_toggles = l_block.querySelectorAll('.inp_toggle');
|
||||
for (var i = 0; i < l_toggles.length; i++) {
|
||||
g_modSettingsDHT[g_modSettingsDHT.length] = new inp_toggle_mod_dht(l_toggles[i], 'MelonMod_DHT_Call_InpToggle');
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue