Skip to content

Commit

Permalink
Added VRCPimaxEyeTracker plugin source and embedded dependencies.
Browse files Browse the repository at this point in the history
  • Loading branch information
Genesis committed May 7, 2021
1 parent c1a70c8 commit de84912
Show file tree
Hide file tree
Showing 6 changed files with 779 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.vs
bin
obj
/VRCPimaxEyeTracker.csproj.user
392 changes: 392 additions & 0 deletions PimaxEyeTracker/PimaxEyeTracker.cs

Large diffs are not rendered by default.

Binary file added PimaxEyeTracker/PimaxEyeTrackerNative.dll
Binary file not shown.
215 changes: 215 additions & 0 deletions VRCPimaxEyeTracker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
using VRCPimaxEyeTracker;
using MelonLoader;
using UnityEngine;
using System;
using System.Collections;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using VRC.SDK3.Avatars.ScriptableObjects;

[assembly: MelonInfo(typeof(VRCPimaxEyeTrackerMod), "VRCPimaxEyeTracker", "1.0.0", "Genesis", "https://github.com/NGenesis/VRCPimaxEyeTracker")]
[assembly: MelonGame("VRChat", "VRChat")]

namespace VRCPimaxEyeTracker
{
public static class DependencyManager
{
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern IntPtr LoadLibrary(string lpFileName);

public static bool Init() => LoadDLL("VRCPimaxEyeTracker.PimaxEyeTracker", "PimaxEyeTrackerNative.dll");

private static bool LoadDLL(string resourcePath, string dllName)
{
var dllPath = Path.Combine(Path.GetTempPath(), dllName);

using (var resource = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourcePath + "." + dllName))
{
if (resource == null) return false;

using (Stream dllFile = File.Create(dllPath))
{
resource.Seek(0, SeekOrigin.Begin);
resource.CopyTo(dllFile);
}
}

return LoadLibrary(dllPath) != IntPtr.Zero;
}
}

public static class ExpressionParameterManager
{
private static AvatarAnimParamController AnimParamController => VRCPlayer.field_Internal_Static_VRCPlayer_0?.field_Private_AnimatorControllerManager_0?.field_Private_AvatarAnimParamController_0;
private static AvatarPlayableController PlayableController => AnimParamController?.field_Private_AvatarPlayableController_0;
private static VRCExpressionParameters.Parameter[] ExpressionParameterDescriptors => VRCPlayer.field_Internal_Static_VRCPlayer_0?.prop_VRCAvatarManager_0?.prop_VRCAvatarDescriptor_0?.expressionParameters?.parameters;
private static UnhollowerBaseLib.Il2CppReferenceArray<AvatarPlayableController.ObjectNPublicInObInPaInUnique> AnimatorParameters => PlayableController?.field_Private_ArrayOf_ObjectNPublicInObInPaInUnique_0;

public static void SetParameter(string name, float value, bool prioritise = false)
{
if (ExpressionParameterDescriptors == null || PlayableController == null) return;

for (var i = 0; i < ExpressionParameterDescriptors.Length; ++i)
{
var expressionParameter = ExpressionParameterDescriptors[i];
if (expressionParameter.name == name)
{
switch(expressionParameter.valueType) {
case VRCExpressionParameters.ValueType.Bool:
value = value <= float.Epsilon ? 0.0f : 1.0f;
break;

case VRCExpressionParameters.ValueType.Int:
value = (int)value;
break;
}

PlayableController?.Method_Public_Boolean_Int32_Single_0(i, value); // Set parameter at index
if (prioritise) PlayableController?.Method_Public_Void_Int32_0(i); // Set parameter priority
return;
}
}
}

public static void SetParameter(string name, bool value, bool prioritise = false) => SetParameter(name, value ? 1.0f : 0.0f, prioritise);
public static void SetParameter(string name, int value, bool prioritise = false) => SetParameter(name, (float)value, prioritise);

public static bool GetBoolParameter(string paramName)
{
if (AnimatorParameters != null && !string.IsNullOrEmpty(paramName))
{
foreach (var animatorParameter in AnimatorParameters)
{
VRCExpressionParameters.Parameter expressionParameter = animatorParameter.field_Public_Parameter_0;
if (expressionParameter.name == paramName && expressionParameter.valueType == VRCExpressionParameters.ValueType.Bool) return animatorParameter?.field_Public_AvatarParameter_0?.prop_Boolean_0 ?? false;
}
}

return false;
}

public static float GetFloatParameter(string paramName)
{
if (AnimatorParameters != null && !string.IsNullOrEmpty(paramName))
{
foreach (var animatorParameter in AnimatorParameters)
{
VRCExpressionParameters.Parameter expressionParameter = animatorParameter.field_Public_Parameter_0;
if (expressionParameter.name == paramName && expressionParameter.valueType == VRCExpressionParameters.ValueType.Float) return animatorParameter?.field_Public_AvatarParameter_0?.prop_Single_0 ?? 0.0f;
}
}

return 0.0f;
}

public static int GetIntParameter(string paramName)
{
if (AnimatorParameters != null && !string.IsNullOrEmpty(paramName))
{
foreach (var animatorParameter in AnimatorParameters)
{
VRCExpressionParameters.Parameter expressionParameter = animatorParameter.field_Public_Parameter_0;
if (expressionParameter.name == paramName && expressionParameter.valueType == VRCExpressionParameters.ValueType.Int) return animatorParameter?.field_Public_AvatarParameter_0?.prop_Int32_0 ?? 0;
}
}

return 0;
}
}

public class VRCPimaxEyeTrackerMod : MelonMod
{
private static Pimax.EyeTracking.EyeTracker eyeTracker;
private static bool isChangingActiveStatus = false;
private static bool needsExpressionUpdate = false;

public override void OnApplicationStart()
{
DependencyManager.Init();

eyeTracker = new Pimax.EyeTracking.EyeTracker();
eyeTracker.OnUpdate += OnEyeTrackerUpdate;
eyeTracker.OnStart += OnEyeTrackerStart;
eyeTracker.OnStop += OnEyeTrackerStop;

MelonCoroutines.Start(UpdateEyeTrackerState());
}

public override void OnApplicationQuit()
{
if (eyeTracker?.Active ?? false) eyeTracker.Stop();
}

private static void OnEyeTrackerStart()
{
MelonLogger.Msg("Eye Tracker Started");
isChangingActiveStatus = false;
}

private static void OnEyeTrackerStop()
{
MelonLogger.Msg("Eye Tracker Stopped");
isChangingActiveStatus = false;
}

private static void OnEyeTrackerUpdate()
{
needsExpressionUpdate = true;
}

private static void UpdateExpressionParameters()
{
if (eyeTracker?.Active ?? false)
{
ExpressionParameterManager.SetParameter("LeftEyeBlink", eyeTracker.LeftEye.Expression.Blink, true);
ExpressionParameterManager.SetParameter("RightEyeBlink", eyeTracker.RightEye.Expression.Blink, true);

ExpressionParameterManager.SetParameter("LeftEyeLid", eyeTracker.LeftEye.Expression.Openness, true);
ExpressionParameterManager.SetParameter("RightEyeLid", eyeTracker.RightEye.Expression.Openness, true);

ExpressionParameterManager.SetParameter("LeftEyeX", eyeTracker.LeftEye.Expression.PupilCenter.x);
ExpressionParameterManager.SetParameter("RightEyeX", eyeTracker.RightEye.Expression.PupilCenter.x);
ExpressionParameterManager.SetParameter("EyesX", eyeTracker.RightEye.Expression.Blink ? eyeTracker.LeftEye.Expression.PupilCenter.x : eyeTracker.RightEye.Expression.PupilCenter.x);

ExpressionParameterManager.SetParameter("LeftEyeY", eyeTracker.LeftEye.Expression.PupilCenter.y);
ExpressionParameterManager.SetParameter("RightEyeY", eyeTracker.RightEye.Expression.PupilCenter.y);
ExpressionParameterManager.SetParameter("EyesY", eyeTracker.RightEye.Expression.Blink ? eyeTracker.LeftEye.Expression.PupilCenter.y : eyeTracker.RightEye.Expression.PupilCenter.y);
}
}

private static IEnumerator UpdateEyeTrackerState()
{
while (true)
{
// Start or stop eye tracker from avatar quick menu
var useEyeTracker = ExpressionParameterManager.GetBoolParameter("UseEyeTracker");
var isEyeTrackerActive = eyeTracker?.Active ?? false;

if (isEyeTrackerActive && needsExpressionUpdate)
{
needsExpressionUpdate = false;
UpdateExpressionParameters();
}

if (!isChangingActiveStatus)
{
if (isEyeTrackerActive && !useEyeTracker)
{
MelonLogger.Msg("Eye Tracker Stopping");
isChangingActiveStatus = true;
eyeTracker.Stop();
}
else if (!isEyeTrackerActive && useEyeTracker)
{
MelonLogger.Msg("Eye Tracker Starting");
isChangingActiveStatus = true;
eyeTracker.Start();
}
}

yield return new WaitForSeconds(isEyeTrackerActive ? 0.01f : 0.5f);
}
}
}
}
143 changes: 143 additions & 0 deletions VRCPimaxEyeTracker.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" 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>{0767C09E-D536-464B-B0A1-46D5BFA19E98}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>VRCPimaxEyeTracker</RootNamespace>
<AssemblyName>VRCPimaxEyeTracker</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>x64</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>x64</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Compile Include="VRCPimaxEyeTracker.cs" />
<Compile Include="PimaxEyeTracker\PimaxEyeTracker.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="Assembly-CSharp, Version=3.7.1.6, Culture=neutral, PublicKeyToken=null">
<HintPath>F:\Steam\steamapps\common\VRChat\MelonLoader\Managed\Assembly-CSharp.dll</HintPath>
</Reference>
<Reference Include="Il2Cppmscorlib, Version=3.7.1.6, Culture=neutral, PublicKeyToken=null">
<HintPath>F:\Steam\steamapps\common\VRChat\MelonLoader\Managed\Il2Cppmscorlib.dll</HintPath>
</Reference>
<Reference Include="MelonLoader, Version=0.3.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>F:\Steam\steamapps\common\VRChat\MelonLoader\MelonLoader.dll</HintPath>
</Reference>
<Reference Include="UnhollowerBaseLib, Version=0.4.13.0, Culture=neutral, PublicKeyToken=null">
<HintPath>F:\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnhollowerBaseLib.dll</HintPath>
</Reference>
<Reference Include="UnityEngine, Version=3.7.1.6, Culture=neutral, PublicKeyToken=null">
<HintPath>F:\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule, Version=3.7.1.6, Culture=neutral, PublicKeyToken=null">
<HintPath>F:\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.CoreModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.ImageConversionModule, Version=3.7.1.6, Culture=neutral, PublicKeyToken=null">
<HintPath>F:\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.ImageConversionModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.PhysicsModule, Version=3.7.1.6, Culture=neutral, PublicKeyToken=null">
<HintPath>F:\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.PhysicsModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UI, Version=3.7.1.6, Culture=neutral, PublicKeyToken=null">
<HintPath>F:\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.UI.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UIElementsModule, Version=3.7.1.6, Culture=neutral, PublicKeyToken=null">
<HintPath>F:\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.UIElementsModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UIModule, Version=3.7.1.6, Culture=neutral, PublicKeyToken=null">
<HintPath>F:\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.UIModule.dll</HintPath>
</Reference>
<Reference Include="VRC.SDKBase.Editor.BuildPipeline, Version=3.7.1.6, Culture=neutral, PublicKeyToken=null">
<HintPath>F:\Steam\steamapps\common\VRChat\MelonLoader\Managed\VRC.SDKBase.Editor.BuildPipeline.dll</HintPath>
</Reference>
<Reference Include="VRC.Udon, Version=3.7.1.6, Culture=neutral, PublicKeyToken=null">
<HintPath>F:\Steam\steamapps\common\VRChat\MelonLoader\Managed\VRC.Udon.dll</HintPath>
</Reference>
<Reference Include="VRC.Udon.ClientBindings, Version=3.7.1.6, Culture=neutral, PublicKeyToken=null">
<HintPath>F:\Steam\steamapps\common\VRChat\MelonLoader\Managed\VRC.Udon.ClientBindings.dll</HintPath>
</Reference>
<Reference Include="VRC.Udon.Common, Version=3.7.1.6, Culture=neutral, PublicKeyToken=null">
<HintPath>F:\Steam\steamapps\common\VRChat\MelonLoader\Managed\VRC.Udon.Common.dll</HintPath>
</Reference>
<Reference Include="VRC.Udon.Security, Version=3.7.1.6, Culture=neutral, PublicKeyToken=null">
<HintPath>F:\Steam\steamapps\common\VRChat\MelonLoader\Managed\VRC.Udon.Security.dll</HintPath>
</Reference>
<Reference Include="VRC.Udon.Serialization.AOT, Version=3.7.1.6, Culture=neutral, PublicKeyToken=null">
<HintPath>F:\Steam\steamapps\common\VRChat\MelonLoader\Managed\VRC.Udon.Serialization.AOT.dll</HintPath>
</Reference>
<Reference Include="VRC.Udon.Serialization.OdinSerializer, Version=3.7.1.6, Culture=neutral, PublicKeyToken=null">
<HintPath>F:\Steam\steamapps\common\VRChat\MelonLoader\Managed\VRC.Udon.Serialization.OdinSerializer.dll</HintPath>
</Reference>
<Reference Include="VRC.Udon.VM, Version=3.7.1.6, Culture=neutral, PublicKeyToken=null">
<HintPath>F:\Steam\steamapps\common\VRChat\MelonLoader\Managed\VRC.Udon.VM.dll</HintPath>
</Reference>
<Reference Include="VRC.Udon.VRCWrapperModules, Version=3.7.1.6, Culture=neutral, PublicKeyToken=null">
<HintPath>F:\Steam\steamapps\common\VRChat\MelonLoader\Managed\VRC.Udon.VRCWrapperModules.dll</HintPath>
</Reference>
<Reference Include="VRC.Udon.Wrapper, Version=3.7.1.6, Culture=neutral, PublicKeyToken=null">
<HintPath>F:\Steam\steamapps\common\VRChat\MelonLoader\Managed\VRC.Udon.Wrapper.dll</HintPath>
</Reference>
<Reference Include="VRCCore-Editor, Version=3.7.1.6, Culture=neutral, PublicKeyToken=null">
<HintPath>F:\Steam\steamapps\common\VRChat\MelonLoader\Managed\VRCCore-Editor.dll</HintPath>
</Reference>
<Reference Include="VRCCore-Standalone, Version=3.7.1.6, Culture=neutral, PublicKeyToken=null">
<HintPath>F:\Steam\steamapps\common\VRChat\MelonLoader\Managed\VRCCore-Standalone.dll</HintPath>
</Reference>
<Reference Include="VRCLog, Version=3.7.1.6, Culture=neutral, PublicKeyToken=null">
<HintPath>F:\Steam\steamapps\common\VRChat\MelonLoader\Managed\VRCLog.dll</HintPath>
</Reference>
<Reference Include="VRCSDK2, Version=3.7.1.6, Culture=neutral, PublicKeyToken=null">
<HintPath>F:\Steam\steamapps\common\VRChat\MelonLoader\Managed\VRCSDK2.dll</HintPath>
</Reference>
<Reference Include="VRCSDK3, Version=3.7.1.6, Culture=neutral, PublicKeyToken=null">
<HintPath>F:\Steam\steamapps\common\VRChat\MelonLoader\Managed\VRCSDK3.dll</HintPath>
</Reference>
<Reference Include="VRCSDK3A, Version=3.7.1.6, Culture=neutral, PublicKeyToken=null">
<HintPath>F:\Steam\steamapps\common\VRChat\MelonLoader\Managed\VRCSDK3A.dll</HintPath>
</Reference>
<Reference Include="VRCSDKBase, Version=3.7.1.6, Culture=neutral, PublicKeyToken=null">
<HintPath>F:\Steam\steamapps\common\VRChat\MelonLoader\Managed\VRCSDKBase.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="PimaxEyeTracker\PimaxEyeTrackerNative.dll" />
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>xcopy "$(TargetDir)$(TargetName)$(TargetExt)" "F:\Steam\steamapps\common\VRChat\Mods" /Y</PostBuildEvent>
</PropertyGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
25 changes: 25 additions & 0 deletions VRCPimaxEyeTracker.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.28307.1433
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VRCPimaxEyeTracker", "VRCPimaxEyeTracker.csproj", "{0767C09E-D536-464B-B0A1-46D5BFA19E98}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0767C09E-D536-464B-B0A1-46D5BFA19E98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0767C09E-D536-464B-B0A1-46D5BFA19E98}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0767C09E-D536-464B-B0A1-46D5BFA19E98}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0767C09E-D536-464B-B0A1-46D5BFA19E98}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6A1C2751-5B21-42E8-BB52-330B46A53CAE}
EndGlobalSection
EndGlobal

0 comments on commit de84912

Please sign in to comment.