Merge branch 'master' into experimental

This commit is contained in:
SDraw 2024-01-26 19:18:36 +03:00
commit c169c7336a
No known key found for this signature in database
GPG key ID: BB95B4DAB2BB8BB5
155 changed files with 16128 additions and 11723 deletions

View file

@ -1,20 +1,24 @@
Merged set of MelonLoader mods for ChilloutVR.
**Table for game build 2022r171:**
**Table for game build 2023r173:**
| Full name | Short name | Latest version | Available in [CVRMA](https://github.com/knah/CVRMelonAssistant) |
|:---------:|:----------:|:--------------:| :----------------------------------------------------------------|
| [Avatar Motion Tweaker](/ml_amt/README.md) | ml_amt | - | ✔ Yes<br>:warning:Broken |
| [Desktop Head Tracking](/ml_dht/README.md)| ml_dht | - | ✔ Yes<br>:warning:Broken |
| [Desktop Reticle Switch](/ml_drs/README.md)| ml_drs | 1.0.1 [:arrow_down:](../../releases/latest/download/ml_drs.dll)| ✔ Yes<br>:hourglass_flowing_sand: Update review |
| [Extended Game Notifications](/ml_egn/README.md) | ml_egn | 1.0.3 [:arrow_down:](../../releases/latest/download/ml_egn.dll)| ✔ Yes<br>:hourglass_flowing_sand: Update review |
| [Leap Motion Extension](/ml_lme/README.md)| ml_lme | 1.4.0 [:arrow_down:](../../releases/latest/download/ml_lme.dll)| ✔ Yes<br>:hourglass_flowing_sand: Update review |
| [Pickup Arm Movement](/ml_pam/README.md)| ml_pam | 1.0.6 [:arrow_down:](../../releases/latest/download/ml_pam.dll)| ✔ Yes<br>:hourglass_flowing_sand: Update review |
| [Player Movement Copycat](/ml_pmc/README.md)| ml_pmc | 1.0.1 [:arrow_down:](../../releases/latest/download/ml_pmc.dll)| ✔ Yes<br>:hourglass_flowing_sand: Update review |
| [Player Ragdoll Mod](/ml_prm/README.md)| ml_prm | 1.0.6 [:arrow_down:](../../releases/latest/download/ml_prm.dll)| ✔ Yes<br>:hourglass_flowing_sand: Update review |
| [Avatar Motion Tweaker](/ml_amt/README.md) | ml_amt | 1.3.6 [:arrow_down:](../../releases/latest/download/ml_amt.dll)| ✔ Yes<br>:hourglass: Update review |
| [Avatar Synced Look](/ml_asl/README.md) | ml_asl | 1.0.0 [:arrow_down:](../../releases/latest/download/ml_asl.dll)| ✔ Yes |
| [Desktop Head Tracking](/ml_dht/README.md) | ml_dht | 1.2.0 [:arrow_down:](../../releases/latest/download/ml_dht.dll) | ✔ Yes (`Retired` group)<br>:hourglass: Update review |
| [Leap Motion Extension](/ml_lme/README.md)| ml_lme | 1.4.5 [:arrow_down:](../../releases/latest/download/ml_lme.dll)| ✔ Yes |
| [Pickup Arm Movement](/ml_pam/README.md)| ml_pam | 1.0.9 [:arrow_down:](../../releases/latest/download/ml_pam.dll)| ✔ Yes |
| [Player Movement Copycat](/ml_pmc/README.md)| ml_pmc | 1.0.4 [:arrow_down:](../../releases/latest/download/ml_pmc.dll)| ✔ Yes |
| [Player Ragdoll Mod](/ml_prm/README.md) | ml_prm | 1.1.2 [:arrow_down:](../../releases/latest/download/ml_prm.dll)| ✔ Yes<br>:hourglass: Update review |
| [Players Instance Notifier](/ml_pin/README.md) | ml_pin | 1.0.1 [:arrow_down:](../../releases/latest/download/ml_ml_pin.dll)| ✔ Yes<br>:hourglass: Update review |
| [Vive Extended Input](/ml_vei/README.md) | ml_vei | 1.0.0 [:arrow_down:](../../releases/latest/download/ml_vei.dll)| ✔ Yes |
**Archived mods:**
| Full name | Short name | Notes |
|:---------:|:----------:|-------|
| Avatar Change Info | ml_aci | Superseded by `Extended Game Notifications`
| Four Point Tracking | ml_fpt | In-game feature since 2022r170 update
| Avatar Change Info | ml_aci | Superseded by `Extended Game Notifications` |
| Desktop Reticle Switch | ml_drs | Boring functionality |
| Extended Game Notifications | ml_egn | In-game feature sine 2023r172 update |
| Four Point Tracking | ml_fpt | In-game feature since 2022r170 update |
| Game Main Fixes | ml_gmf | In-game feature sine 2023r172 update |
| Server Connection Info | ml_sci | Superseded by `Extended Game Notifications`

BIN
archived/ml_aci/.github/img_01.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

58
archived/ml_aci/Main.cs Normal file
View file

@ -0,0 +1,58 @@
using ABI_RC.Core.EventSystem;
using ABI_RC.Core.InteractionSystem;
using ABI_RC.Core.Networking;
using ABI_RC.Core.Util;
using DarkRift;
using System.Reflection;
namespace ml_aci
{
public class AvatarChangeInfo : MelonLoader.MelonMod
{
public override void OnInitializeMelon()
{
HarmonyInstance.Patch(
typeof(AssetManagement).GetMethod(nameof(AssetManagement.LoadLocalAvatar)),
null,
new HarmonyLib.HarmonyMethod(typeof(AvatarChangeInfo).GetMethod(nameof(OnLocalAvatarLoad), BindingFlags.NonPublic | BindingFlags.Static))
);
HarmonyInstance.Patch(
typeof(CVRSyncHelper).GetMethod(nameof(CVRSyncHelper.SpawnProp)),
null,
new HarmonyLib.HarmonyMethod(typeof(AvatarChangeInfo).GetMethod(nameof(OnPropSpawned), BindingFlags.NonPublic | BindingFlags.Static))
);
}
static void OnLocalAvatarLoad()
{
try
{
if(ViewManager.Instance != null)
ViewManager.Instance.TriggerPushNotification("Avatar changed", 1f);
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnPropSpawned()
{
try
{
if(ViewManager.Instance != null)
{
if((NetworkManager.Instance != null) && (NetworkManager.Instance.GameNetwork.ConnectionState == ConnectionState.Connected))
ViewManager.Instance.TriggerPushNotification("Prop spawned", 1f);
else
ViewManager.Instance.TriggerAlert("Prop Error", "Not connected to live instance", -1, true);
}
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
}
}

View file

@ -1,6 +1,10 @@
using System.Reflection;
[assembly: MelonLoader.MelonInfo(typeof(ml_egn.ExtendedGameNotifications), "ExtendedGameNotifications", "1.0.3", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: AssemblyTitle("AvatarChangeInfo")]
[assembly: AssemblyVersion("1.0.3")]
[assembly: AssemblyFileVersion("1.0.3")]
[assembly: MelonLoader.MelonInfo(typeof(ml_aci.AvatarChangeInfo), "AvatarChangeInfo", "1.0.3", "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)]

View file

@ -0,0 +1,9 @@
# Avatar Change Info
This mod shows simple main menu popup upon local player avatar change and prop spawn.
![](.github/img_01.png)
# Installation
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
* Get [latest release DLL](../../../releases/latest):
* Put `ml_aci.dll` in `Mods` folder of game

View file

@ -0,0 +1,77 @@
<?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>{1B5ACA07-6266-4C9A-BA30-D4BBE6634846}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ml_aci</RootNamespace>
<AssemblyName>ml_aci</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>
<HintPath>F:\games\Steam\common\ChilloutVR\MelonLoader\0Harmony.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Assembly-CSharp, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>F:\games\Steam\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="DarkRift">
<Private>False</Private>
</Reference>
<Reference Include="MelonLoader, Version=0.5.4.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>F:\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.CoreModule, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Main.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PreBuildEvent>
</PreBuildEvent>
</PropertyGroup>
<PropertyGroup>
<PostBuildEvent>copy /y "$(TargetPath)" "C:\Games\Steam\common\ChilloutVR\Mods\"</PostBuildEvent>
</PropertyGroup>
</Project>

View 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>

View file

@ -0,0 +1,4 @@
[assembly: MelonLoader.MelonInfo(typeof(ml_drs.DesktopReticleSwitch), "DesktopReticleSwitch", "1.0.1", "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)]

View file

@ -0,0 +1,4 @@
[assembly: MelonLoader.MelonInfo(typeof(ml_egn.ExtendedGameNotifications), "ExtendedGameNotifications", "1.0.3", "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)]

313
archived/ml_fpt/Main.cs Normal file
View file

@ -0,0 +1,313 @@
using ABI.CCK.Scripts;
using ABI_RC.Core.InteractionSystem;
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Core.UI;
using ABI_RC.Systems.IK;
using ABI_RC.Systems.IK.SubSystems;
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
namespace ml_fpt
{
public class FourPointTracking : MelonLoader.MelonMod
{
static FourPointTracking ms_instance = null;
bool m_ready = false;
IndexIK m_indexIK = null;
RootMotion.FinalIK.VRIK m_vrIK = null;
RuntimeAnimatorController m_runtimeAnimator = null;
List<CVRAdvancedSettingsFileProfileValue> m_aasParameters = null;
bool m_calibrationActive = false;
object m_calibrationTask = null;
int m_hipsTrackerIndex = -1;
Transform m_hips = null;
Dictionary<string, Tuple<Vector3, Quaternion>> m_avatarCalibrations = null;
public override void OnInitializeMelon()
{
if(ms_instance == null)
ms_instance = this;
m_avatarCalibrations = new Dictionary<string, Tuple<Vector3, Quaternion>>();
HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.ClearAvatar)),
null,
new HarmonyLib.HarmonyMethod(typeof(FourPointTracking).GetMethod(nameof(OnAvatarClear_Postfix), BindingFlags.NonPublic | BindingFlags.Static))
);
HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.SetupAvatar)),
null,
new HarmonyLib.HarmonyMethod(typeof(FourPointTracking).GetMethod(nameof(OnSetupAvatar_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
MelonLoader.MelonCoroutines.Start(WaitForMainMenuView());
MelonLoader.MelonCoroutines.Start(WaitForLocalPlayer());
}
System.Collections.IEnumerator WaitForMainMenuView()
{
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.RegisterForEvent("MelonMod_FPT_Action_Calibrate", new Action(this.StartCalibration));
};
ViewManager.Instance.gameMenuView.Listener.FinishLoad += (_) =>
{
ViewManager.Instance.gameMenuView.View.ExecuteScript(Scripts.GetEmbeddedScript("menu.js"));
};
}
System.Collections.IEnumerator WaitForLocalPlayer()
{
while(PlayerSetup.Instance == null)
yield return null;
m_indexIK = PlayerSetup.Instance.gameObject.GetComponent<IndexIK>();
m_ready = true;
}
public override void OnDeinitializeMelon()
{
if(ms_instance == this)
ms_instance = null;
m_ready = false;
m_aasParameters?.Clear();
m_aasParameters = null;
m_avatarCalibrations?.Clear();
m_avatarCalibrations = null;
m_hipsTrackerIndex = -1;
if(m_calibrationTask != null)
MelonLoader.MelonCoroutines.Stop(m_calibrationTask);
m_calibrationTask = null;
}
void StartCalibration()
{
if(m_ready && !m_calibrationActive && PlayerSetup.Instance._inVr && !PlayerSetup.Instance.avatarIsLoading && PlayerSetup.Instance._animator.isHuman && !BodySystem.isCalibrating && !BodySystem.isCalibratedAsFullBody)
{
m_hipsTrackerIndex = GetHipsTracker();
if(m_hipsTrackerIndex != -1)
{
m_avatarCalibrations.Remove(MetaPort.Instance.currentAvatarGuid);
m_runtimeAnimator = PlayerSetup.Instance._animator.runtimeAnimatorController;
m_aasParameters = PlayerSetup.Instance.animatorManager.GetAdditionalSettingsCurrent();
PlayerSetup.Instance._animator.runtimeAnimatorController = PlayerSetup.Instance.tPoseAnimatorController;
PlayerSetup.Instance.animatorManager.SetAnimator(PlayerSetup.Instance._animator, PlayerSetup.Instance.tPoseAnimatorController);
m_hips = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Hips);
m_vrIK = PlayerSetup.Instance._animator.GetComponent<RootMotion.FinalIK.VRIK>();
if(m_vrIK != null)
m_vrIK.solver.OnPreUpdate += this.OverrideIKWeight;
IKSystem.Instance.leftHandModel.SetActive(true);
IKSystem.Instance.rightHandModel.SetActive(true);
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].ShowTracker(true);
CVR_InteractableManager.enableInteractions = false;
m_calibrationActive = true;
m_calibrationTask = MelonLoader.MelonCoroutines.Start(CalibrationTask());
ViewManager.Instance.ForceUiStatus(false);
ShowHudNotification("Calibration started");
}
else
ShowMenuAlert("No hips tracker detected. Check if tracker has waist role in SteamVR settings.");
}
else
ShowMenuAlert("Calibraton requirements aren't met: be in VR, be not in FBT or avatar calibration, humanoid avatar");
}
System.Collections.IEnumerator CalibrationTask()
{
while(m_calibrationActive)
{
if(m_vrIK != null)
m_vrIK.enabled = false;
m_indexIK.enabled = false;
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].ShowLine(true, m_hips);
if((CVRInputManager.Instance.interactLeftValue > 0.9f) && (CVRInputManager.Instance.interactRightValue > 0.9f))
{
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].target.transform.position = m_hips.position;
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].target.transform.rotation = m_hips.rotation;
m_avatarCalibrations.Add(
MetaPort.Instance.currentAvatarGuid,
new Tuple<Vector3, Quaternion>(
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].target.transform.localPosition,
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].target.transform.localRotation
)
);
if(m_vrIK != null)
{
m_vrIK.solver.spine.pelvisTarget = PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].target;
m_vrIK.solver.spine.pelvisPositionWeight = 1f;
m_vrIK.solver.spine.pelvisRotationWeight = 1f;
m_vrIK.solver.OnPreUpdate -= this.OverrideIKWeight;
m_vrIK.solver.IKPositionWeight = 1f;
m_vrIK.enabled = true;
}
m_indexIK.enabled = true;
PlayerSetup.Instance._animator.runtimeAnimatorController = m_runtimeAnimator;
PlayerSetup.Instance.animatorManager.SetAnimator(PlayerSetup.Instance._animator, m_runtimeAnimator);
if(m_aasParameters != null)
{
foreach(var l_param in m_aasParameters)
{
PlayerSetup.Instance.animatorManager.SetAnimatorParameter(l_param.name, l_param.value);
}
}
IKSystem.Instance.leftHandModel.SetActive(false);
IKSystem.Instance.rightHandModel.SetActive(false);
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].ShowTracker(false);
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].ShowLine(false);
CVR_InteractableManager.enableInteractions = true;
Reset();
ShowHudNotification("Calibration completed");
}
yield return null;
}
m_calibrationTask = null; // Idk if it's safe or not
}
void OverrideIKWeight()
{
if(m_calibrationActive)
{
m_vrIK.solver.IKPositionWeight = 0f;
}
}
void Reset()
{
m_vrIK = null;
m_runtimeAnimator = null;
m_aasParameters = null;
m_calibrationActive = false;
m_calibrationTask = null;
m_hipsTrackerIndex = -1;
m_hips = null;
}
static void OnAvatarClear_Postfix() => ms_instance?.OnAvatarClear();
void OnAvatarClear()
{
try
{
if(m_calibrationActive)
{
if(m_calibrationTask != null)
MelonLoader.MelonCoroutines.Stop(m_calibrationTask);
m_indexIK.enabled = true;
IKSystem.Instance.leftHandModel.SetActive(false);
IKSystem.Instance.rightHandModel.SetActive(false);
if(m_hipsTrackerIndex != -1)
{
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].ShowTracker(false);
PlayerSetup.Instance._trackerManager.trackers[m_hipsTrackerIndex].ShowLine(false);
}
CVR_InteractableManager.enableInteractions = true;
Reset();
ShowHudNotification("Calibration canceled");
}
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnSetupAvatar_Postfix() => ms_instance?.OnSetupAvatar();
void OnSetupAvatar()
{
try
{
if(m_ready && PlayerSetup.Instance._inVr && PlayerSetup.Instance._animator.isHuman && !VRTrackerManager.Instance.CheckFullBody())
{
int l_hipsTracker = GetHipsTracker();
if((l_hipsTracker != -1) && m_avatarCalibrations.TryGetValue(MetaPort.Instance.currentAvatarGuid, out var l_stored))
{
var l_vrIK = PlayerSetup.Instance._animator.GetComponent<RootMotion.FinalIK.VRIK>();
if(l_vrIK != null)
{
l_vrIK.solver.spine.pelvisTarget = PlayerSetup.Instance._trackerManager.trackers[l_hipsTracker].target;
l_vrIK.solver.spine.pelvisPositionWeight = 1f;
l_vrIK.solver.spine.pelvisRotationWeight = 1f;
l_vrIK.solver.spine.pelvisTarget.localPosition = l_stored.Item1;
l_vrIK.solver.spine.pelvisTarget.localRotation = l_stored.Item2;
ShowHudNotification("Applied saved calibration");
}
}
}
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void ShowHudNotification(string p_message)
{
if(CohtmlHud.Instance != null)
CohtmlHud.Instance.ViewDropText("4-Point Tracking", p_message);
}
static void ShowMenuAlert(string p_message)
{
if(ViewManager.Instance != null)
ViewManager.Instance.TriggerAlert("4-Point Tracking", p_message, 0, false);
}
static int GetHipsTracker()
{
int l_result = -1;
for(int i = 0; i < PlayerSetup.Instance._trackerManager.trackerNames.Length; i++)
{
if((PlayerSetup.Instance._trackerManager.trackerNames[i] == "vive_tracker_waist") && PlayerSetup.Instance._trackerManager.trackers[i].active)
{
l_result = i;
break;
}
}
return l_result;
}
}
}

View file

@ -0,0 +1,10 @@
using System.Reflection;
[assembly: AssemblyTitle("FourPointTracking")]
[assembly: AssemblyVersion("1.0.9")]
[assembly: AssemblyFileVersion("1.0.9")]
[assembly: MelonLoader.MelonInfo(typeof(ml_fpt.FourPointTracking), "FourPointTracking", "1.0.9", "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)]

18
archived/ml_fpt/README.md Normal file
View file

@ -0,0 +1,18 @@
# Four Point Tracking
This mod adds ability to use 4-point tracking.
# Installation
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
* Get [latest release DLL](../../../releases/latest):
* Put `ml_fpt.dll` in `Mods` folder of game
# Usage
* Be sure that your tracker role is set to `Hips` in SteamVR
* Go to `Settings - Implementation - 4-Point Tracking` and press `Calibrate` button
* Adjust your tracker in a similar way as in FBT calibration
* Press trigger on both controllers
# Notes
* Will be deprecated soon
* Calibration is saved per avatar for game session.
* AAS parameters are restored after calibration.

View file

@ -2,7 +2,7 @@
using System.IO;
using System.Reflection;
namespace ml_amt
namespace ml_fpt
{
static class Scripts
{

View file

@ -0,0 +1,88 @@
<?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>{EC0A8C41-A429-42CD-B8FA-401A802D4BA6}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ml_fpt</RootNamespace>
<AssemblyName>ml_fpt</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup>
<StartupObject />
</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">
<Private>False</Private>
</Reference>
<Reference Include="Assembly-CSharp">
<HintPath>C:\Games\Steam\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll</HintPath>
<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">
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Main.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Scripts.cs" />
</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>

View 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>

View file

@ -0,0 +1,12 @@
{
let l_block = document.createElement('div');
l_block.innerHTML = `
<div class ="settings-subcategory">
<div class ="subcategory-name">4-Point Tracking</div>
<div class ="subcategory-description"></div>
</div>
<div class ="action-btn button" onclick="engine.trigger('MelonMod_FPT_Action_Calibrate');"><img src="gfx/recalibrate.svg">Calibrate</div>
`;
document.getElementById('settings-implementation').appendChild(l_block);
}

View file

@ -0,0 +1,34 @@
using ABI_RC.Core;
using System;
using System.Reflection;
namespace ml_gmf.Fixes
{
static class AnimationOverrides
{
internal static void Init(HarmonyLib.Harmony p_instance)
{
p_instance.Patch(
typeof(CVRAnimatorManager).GetMethod(nameof(CVRAnimatorManager.SetOverrideAnimation)),
new HarmonyLib.HarmonyMethod(typeof(AnimationOverrides).GetMethod(nameof(OnOverride_Prefix), BindingFlags.Static | BindingFlags.NonPublic))
);
p_instance.Patch(
typeof(CVRAnimatorManager).GetMethod(nameof(CVRAnimatorManager.RestoreOverrideAnimation)),
new HarmonyLib.HarmonyMethod(typeof(AnimationOverrides).GetMethod(nameof(OnOverride_Prefix), BindingFlags.Static | BindingFlags.NonPublic))
);
}
static void OnOverride_Prefix(ref CVRAnimatorManager __instance)
{
try
{
if(__instance.animator != null)
__instance.animator.WriteDefaultValues();
}
catch(Exception l_exception)
{
MelonLoader.MelonLogger.Error(l_exception);
}
}
}
}

View file

@ -0,0 +1,48 @@
using ABI.CCK.Components;
using ABI_RC.Core.Player;
using System;
using System.Reflection;
namespace ml_gmf.Fixes
{
static class AvatarOverrides
{
internal static void Init(HarmonyLib.Harmony p_instance)
{
p_instance.Patch(
typeof(PlayerSetup).GetMethod("SetupAvatarGeneral", BindingFlags.NonPublic | BindingFlags.Instance),
new HarmonyLib.HarmonyMethod(typeof(AvatarOverrides).GetMethod(nameof(OnSetupAvatarGeneral_Prefix), BindingFlags.NonPublic | BindingFlags.Static))
);
p_instance.Patch(
typeof(PuppetMaster).GetMethod(nameof(PuppetMaster.AvatarInstantiated), BindingFlags.Public | BindingFlags.Instance),
new HarmonyLib.HarmonyMethod(typeof(AvatarOverrides).GetMethod(nameof(OnPuppetAvatarInstantiated_Prefix), BindingFlags.NonPublic | BindingFlags.Static))
);
}
static void OnSetupAvatarGeneral_Prefix(CVRAvatar ____avatarDescriptor)
{
try
{
if(____avatarDescriptor.overrides != null)
____avatarDescriptor.overrides = UnityEngine.Object.Instantiate(____avatarDescriptor.overrides);
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnPuppetAvatarInstantiated_Prefix(ref PuppetMaster __instance)
{
try
{
CVRAvatar l_avatar = __instance.avatarObject.GetComponent<CVRAvatar>();
if((l_avatar != null) && (l_avatar.overrides != null))
l_avatar.overrides = UnityEngine.Object.Instantiate(l_avatar.overrides);
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
}
}

View file

@ -0,0 +1,31 @@
using ABI_RC.Core.Player;
using System.Collections;
namespace ml_gmf.Fixes
{
static class PostProccesVolumes
{
internal static void Init()
{
MelonLoader.MelonCoroutines.Start(FixVRCameraVolumeTarget());
}
static IEnumerator FixVRCameraVolumeTarget()
{
while(PlayerSetup.Instance == null)
yield return null;
while(PlayerSetup.Instance.vrCamera == null)
yield return null;
UnityEngine.Rendering.PostProcessing.PostProcessLayer l_layer = null;
while(l_layer == null)
{
l_layer = PlayerSetup.Instance.vrCamera.GetComponent<UnityEngine.Rendering.PostProcessing.PostProcessLayer>();
yield return null;
}
l_layer.volumeTrigger = PlayerSetup.Instance.vrCamera.transform;
}
}
}

View file

@ -0,0 +1,60 @@
using ABI_RC.Systems.InputManagement;
using ABI_RC.Systems.InputManagement.XR;
using System;
using System.Reflection;
namespace ml_gmf.Fixes
{
static class ViveControls
{
internal static void Init(HarmonyLib.Harmony p_instance)
{
p_instance.Patch(
typeof(CVRXRModule).GetMethod("Update_Gestures_Vive", BindingFlags.NonPublic | BindingFlags.Instance),
null,
new HarmonyLib.HarmonyMethod(typeof(ViveControls).GetMethod(nameof(OnViveGesturesUpdate_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
p_instance.Patch(
typeof(CVRXRModule).GetMethod(nameof(CVRXRModule.Reset), BindingFlags.Public | BindingFlags.Instance),
new HarmonyLib.HarmonyMethod(typeof(ViveControls).GetMethod(nameof(OnCVRXRModuleReset_Prefix), BindingFlags.NonPublic | BindingFlags.Static)),
new HarmonyLib.HarmonyMethod(typeof(ViveControls).GetMethod(nameof(OnCVRXRModuleReset_Postfix), BindingFlags.NonPublic | BindingFlags.Static))
);
}
static void OnViveGesturesUpdate_Postfix(ref CVRXRModule __instance)
{
try
{
float l_mag = ((!__instance.HasEmoteOverride) ? __instance.Primary2DAxis : __instance.EmoteOverride).magnitude;
if(__instance.ViveDirectionPressed && (l_mag >= CVRInputManager.VrViveGestureDeadZone))
{
if(__instance.Grip > 0.5f)
{
__instance.GestureRaw = -1f;
__instance.Gesture = -1f;
}
else
{
__instance.GestureRaw = __instance.Trigger;
__instance.Gesture = __instance.Trigger;
}
}
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnCVRXRModuleReset_Prefix(ref CVRXRModule __instance, out bool __state)
{
__state = __instance.ViveDirectionPressed;
}
static void OnCVRXRModuleReset_Postfix(ref CVRXRModule __instance, bool __state)
{
if((__instance.Type == EXRControllerType.Vive) && CVRInputManager._moduleXR.ViveAdvancedControls)
__instance.ViveDirectionPressed = __state;
}
}
}

13
archived/ml_gmf/Main.cs Normal file
View file

@ -0,0 +1,13 @@
namespace ml_gmf
{
public class GameMainFixes : MelonLoader.MelonMod
{
public override void OnInitializeMelon()
{
Fixes.ViveControls.Init(HarmonyInstance);
Fixes.AvatarOverrides.Init(HarmonyInstance);
Fixes.PostProccesVolumes.Init();
Fixes.AnimationOverrides.Init(HarmonyInstance);
}
}
}

View file

@ -0,0 +1,4 @@
[assembly: MelonLoader.MelonInfo(typeof(ml_gmf.GameMainFixes), "GameMainFixes", "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)]

17
archived/ml_gmf/README.md Normal file
View file

@ -0,0 +1,17 @@
# Game Main Fixes
This mod fixes some issues that are present in game
# Installation
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
* Get [latest release DLL](../../../releases/latest):
* Put `ml_gmf.dll` in `Mods` folder of game
# Implemented fixes
* Fix of broken `Vive Advanced Controls` game input option
* Additional feature: Disables gestures when moving with Vive controllers
* Fix of post-processing layer volume trigger for VR camera ([feedback post](https://feedback.abinteractive.net/p/2023r171ex1-post-process-volume-effects-are-applied-based-on-playspace-center-instead-of-camera-s-in-vr-mode))
* Fix of shared `AnimatorOverrideController` between same avatars that leads to broken avatar animator
* Fix of animation replacement (chairs, etc.) that leads to broken avatar animator ([feedback post](https://feedback.abinteractive.net/p/gestures-getting-stuck-locally-upon-entering-vehicles-chairs))
# Notes
All these fixes are implemented natively in 2023r172ex4 build.

View file

@ -0,0 +1,52 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Platforms>x64</Platforms>
<PackageId>GameMainFixes</PackageId>
<Version>1.0.0</Version>
<Authors>SDraw</Authors>
<Company>None</Company>
<Product>GameMainFixes</Product>
</PropertyGroup>
<ItemGroup>
<None Remove="PlayerRagdollMod.json" />
</ItemGroup>
<ItemGroup>
<Reference Include="0Harmony">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\0Harmony.dll</HintPath>
<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>
</Reference>
<Reference Include="MelonLoader">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\MelonLoader.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Unity.Postprocessing.Runtime">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Unity.Postprocessing.Runtime.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.AnimationModule">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AnimationModule.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="copy /y &quot;$(TargetPath)&quot; &quot;D:\Games\Steam\steamapps\common\ChilloutVR\Mods\&quot;" />
</Target>
</Project>

BIN
archived/ml_sci/.github/img_01.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

31
archived/ml_sci/Main.cs Normal file
View file

@ -0,0 +1,31 @@
using ABI_RC.Core.UI;
using DarkRift.Client;
using System.Reflection;
namespace ml_sci
{
public class ServerConnectionInfo : MelonLoader.MelonMod
{
public override void OnInitializeMelon()
{
HarmonyInstance.Patch(
typeof(ABI_RC.Core.Networking.NetworkManager).GetMethod("OnGameNetworkConnectionClosed", BindingFlags.NonPublic | BindingFlags.Instance),
null,
new HarmonyLib.HarmonyMethod(typeof(ServerConnectionInfo).GetMethod(nameof(OnGameNetworkConnectionClosed), BindingFlags.NonPublic | BindingFlags.Static))
);
}
static void OnGameNetworkConnectionClosed(object __0, DisconnectedEventArgs __1)
{
try
{
if((CohtmlHud.Instance != null) && (__1 != null) && (!__1.LocalDisconnect))
CohtmlHud.Instance.ViewDropTextImmediate("(Local) Client", "Connection lost", (__1.Error != System.Net.Sockets.SocketError.Success) ? ("Reason: " + __1.Error.ToString()) : "");
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
}
}

View file

@ -1,6 +1,10 @@
using System.Reflection;
[assembly: MelonLoader.MelonInfo(typeof(ml_drs.DesktopReticleSwitch), "DesktopReticleSwitch", "1.0.1", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: AssemblyTitle("ServerConnectionInfo")]
[assembly: AssemblyVersion("1.0.2")]
[assembly: AssemblyFileVersion("1.0.2")]
[assembly: MelonLoader.MelonInfo(typeof(ml_sci.ServerConnectionInfo), "ServerConnectionInfo", "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)]

View file

@ -0,0 +1,9 @@
# Server Connection Info
This mod shows HUD notification upon server disconnection.
![](.github/img_01.png)
# Installation
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
* Get [latest release DLL](../../../releases/latest):
* Put `ml_sci.dll` in `Mods` folder of game

View file

@ -0,0 +1,79 @@
<?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>{E5481D41-196C-4241-AF26-6595EF1863C1}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ml_sci</RootNamespace>
<AssemblyName>ml_sci</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>
<HintPath>F:\games\Steam\common\ChilloutVR\MelonLoader\0Harmony.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Assembly-CSharp, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>F:\games\Steam\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="DarkRift.Client, Version=2.4.5.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>C:\Games\Steam\common\ChilloutVR\ChilloutVR_Data\Managed\DarkRift.Client.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="MelonLoader, Version=0.5.4.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>F:\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.CoreModule, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Main.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PreBuildEvent>
</PreBuildEvent>
</PropertyGroup>
<PropertyGroup>
<PostBuildEvent>copy /y "$(TargetPath)" "C:\Games\Steam\common\ChilloutVR\Mods\"</PostBuildEvent>
</PropertyGroup>
</Project>

View 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>

283
js/mods_extension.js Normal file
View file

@ -0,0 +1,283 @@
if (typeof modsExtension === 'undefined') {
window.modsExtension = []
// UI elements, modified from original `inp` types, because I have no js knowledge to hook stuff
modsExtension.createToggle = function (_obj, _callbackName) {
let uiElement = {};
uiElement.obj = _obj;
uiElement.callbackName = _callbackName;
uiElement.value = _obj.getAttribute('data-current');
uiElement.name = _obj.id;
uiElement.type = _obj.getAttribute('data-type');
var self = uiElement;
uiElement.mouseDown = function (_e) {
self.value = self.value == "True" ? "False" : "True";
self.updateState();
}
uiElement.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', uiElement.mouseDown);
uiElement.getValue = function () {
return self.value;
}
uiElement.updateValue = function (value) {
self.value = value;
self.obj.classList.remove("checked");
if (self.value == "True") {
self.obj.classList.add("checked");
}
}
uiElement.updateValue(uiElement.value);
return {
name: uiElement.name,
value: uiElement.getValue,
updateValue: uiElement.updateValue
}
};
modsExtension.createSlider = function (_obj, _callbackName) {
let uiElement = {};
uiElement.obj = _obj;
uiElement.callbackName = _callbackName;
uiElement.minValue = parseFloat(_obj.getAttribute('data-min'));
uiElement.maxValue = parseFloat(_obj.getAttribute('data-max'));
uiElement.percent = 0;
uiElement.value = parseFloat(_obj.getAttribute('data-current'));
uiElement.dragActive = false;
uiElement.name = _obj.id;
uiElement.type = _obj.getAttribute('data-type');
uiElement.stepSize = _obj.getAttribute('data-stepSize') || 0;
uiElement.format = _obj.getAttribute('data-format') || '{value}';
var self = uiElement;
if (uiElement.stepSize != 0)
uiElement.value = Math.round(uiElement.value / uiElement.stepSize) * uiElement.stepSize;
else
uiElement.value = Math.round(uiElement.value);
uiElement.valueLabelBackground = document.createElement('div');
uiElement.valueLabelBackground.className = 'valueLabel background';
uiElement.valueLabelBackground.innerHTML = uiElement.format.replace('{value}', uiElement.value);
uiElement.obj.appendChild(uiElement.valueLabelBackground);
uiElement.valueBar = document.createElement('div');
uiElement.valueBar.className = 'valueBar';
uiElement.valueBar.setAttribute('style', 'width: ' + (((uiElement.value - uiElement.minValue) / (uiElement.maxValue - uiElement.minValue)) * 100) + '%;');
uiElement.obj.appendChild(uiElement.valueBar);
uiElement.valueLabelForeground = document.createElement('div');
uiElement.valueLabelForeground.className = 'valueLabel foreground';
uiElement.valueLabelForeground.innerHTML = uiElement.format.replace('{value}', uiElement.value);
uiElement.valueLabelForeground.setAttribute('style', 'width: ' + (1.0 / ((uiElement.value - uiElement.minValue) / (uiElement.maxValue - uiElement.minValue)) * 100) + '%;');
uiElement.valueBar.appendChild(uiElement.valueLabelForeground);
uiElement.mouseDown = function (_e) {
self.dragActive = true;
self.mouseMove(_e, false);
}
uiElement.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();
}
}
uiElement.mouseUp = function (_e) {
self.mouseMove(_e, true);
self.dragActive = false;
}
_obj.addEventListener('mousedown', uiElement.mouseDown);
document.addEventListener('mousemove', uiElement.mouseMove);
document.addEventListener('mouseup', uiElement.mouseUp);
uiElement.getValue = function () {
return self.value;
}
uiElement.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();
}
uiElement.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 + "&apos;" + inches + '&apos;&apos;';
}
}
}
return {
name: uiElement.name,
value: uiElement.getValue,
updateValue: uiElement.updateValue
}
};
modsExtension.createDropdown = function (_obj, _callbackName) {
let uiElement = {};
uiElement.obj = _obj;
uiElement.callbackName = _callbackName;
uiElement.value = _obj.getAttribute('data-current');
uiElement.options = _obj.getAttribute('data-options').split(',');
uiElement.name = _obj.id;
uiElement.opened = false;
uiElement.keyValue = [];
uiElement.type = _obj.getAttribute('data-type');
uiElement.optionElements = [];
var self = uiElement;
uiElement.SelectValue = function (_e) {
self.value = _e.target.getAttribute('data-key');
self.valueElement.innerHTML = _e.target.getAttribute('data-value');
self.globalClose();
engine.call(self.callbackName, self.name, self.value);
}
uiElement.openClick = function (_e) {
if (self.obj.classList.contains('open')) {
self.obj.classList.remove('open');
self.list.setAttribute('style', 'display: none;');
} else {
self.obj.classList.add('open');
self.list.setAttribute('style', 'display: block;');
self.opened = true;
window.setTimeout(function () { self.opened = false; }, 10);
}
}
uiElement.globalClose = function (_e) {
if (self.opened) return;
self.obj.classList.remove('open');
self.list.setAttribute('style', 'display: none;');
}
uiElement.list = document.createElement('div');
uiElement.list.className = 'valueList';
uiElement.updateOptions = function () {
self.list.innerHTML = "";
for (var i = 0; i < self.options.length; i++) {
self.optionElements[i] = document.createElement('div');
self.optionElements[i].className = 'listValue';
var valuePair = Array.isArray(self.options[i]) ? self.options[i] : self.options[i].split(':');
var key = "";
var value = "";
if (valuePair.length == 1) {
key = valuePair[0];
value = valuePair[0];
} else {
key = valuePair[0];
value = valuePair[1];
}
self.keyValue[key] = value;
self.optionElements[i].innerHTML = value;
self.optionElements[i].setAttribute('data-value', value);
self.optionElements[i].setAttribute('data-key', key);
self.list.appendChild(self.optionElements[i]);
self.optionElements[i].addEventListener('mousedown', self.SelectValue);
}
self.valueElement.innerHTML = self.keyValue[self.value];
}
uiElement.valueElement = document.createElement('div');
uiElement.valueElement.className = 'dropdown-value';
uiElement.updateOptions();
uiElement.obj.appendChild(uiElement.valueElement);
uiElement.obj.appendChild(uiElement.list);
uiElement.valueElement.addEventListener('mousedown', uiElement.openClick);
document.addEventListener('mousedown', uiElement.globalClose);
uiElement.getValue = function () {
return self.value;
}
uiElement.updateValue = function (value) {
self.value = value;
self.valueElement.innerHTML = self.keyValue[value];
}
uiElement.setOptions = function (options) {
self.options = options;
}
return {
name: uiElement.name,
value: uiElement.getValue,
updateValue: uiElement.updateValue,
updateOptions: uiElement.updateOptions,
setOptions: uiElement.setOptions
}
};
modsExtension.settings = []
modsExtension.settings.data = []; // [category] -> [entry]
modsExtension.addSetting = function (_category, _entry, _obj) {
if (modsExtension.settings.data[_category] === undefined)
modsExtension.settings.data[_category] = []
modsExtension.settings.data[_category][_entry] = _obj
};
modsExtension.updateSetting = function (_category, _entry, _value) {
if ((modsExtension.settings.data[_category] !== undefined) && (modsExtension.settings.data[_category][_entry] !== undefined))
modsExtension.settings.data[_category][_entry].updateValue(_value);
};
engine.on('updateModSetting', modsExtension.updateSetting);
}

View file

@ -1,85 +1,80 @@
using ABI_RC.Core;
using System.Text.RegularExpressions;
using UnityEngine;
namespace ml_amt
{
class AvatarParameter
{
public enum ParameterType
{
Upright,
GroundedRaw,
Moving
}
readonly ParameterType m_type;
readonly string m_name;
readonly int m_hash = 0;
readonly bool m_sync;
readonly AnimatorControllerParameterType m_innerType;
readonly CVRAnimatorManager m_manager = null;
public AvatarParameter(ParameterType p_type, CVRAnimatorManager p_manager)
{
m_type = p_type;
m_name = p_type.ToString();
m_manager = p_manager;
Regex l_regex = new Regex("^#?" + m_name + '$');
foreach(var l_param in m_manager.animator.parameters)
{
if(l_regex.IsMatch(l_param.name))
{
m_hash = l_param.nameHash;
m_sync = (l_param.name[0] != '#');
m_innerType = l_param.type;
break;
}
}
}
public void Update(MotionTweaker p_tweaker)
{
switch(m_type)
{
case ParameterType.Upright:
SetFloat(p_tweaker.GetUpright());
break;
case ParameterType.GroundedRaw:
SetBoolean(p_tweaker.GetGroundedRaw());
break;
case ParameterType.Moving:
SetBoolean(p_tweaker.GetMoving());
break;
}
}
public bool IsValid() => (m_hash != 0);
public ParameterType GetParameterType() => m_type;
void SetFloat(float p_value)
{
if(m_innerType == AnimatorControllerParameterType.Float)
{
if(m_sync)
m_manager.SetAnimatorParameterFloat(m_name, p_value);
else
m_manager.animator.SetFloat(m_hash, p_value);
}
}
void SetBoolean(bool p_value)
{
if(m_innerType == AnimatorControllerParameterType.Bool)
{
if(m_sync)
m_manager.SetAnimatorParameterBool(m_name, p_value);
else
m_manager.animator.SetBool(m_hash, p_value);
}
}
}
}
using ABI_RC.Core;
using System.Text.RegularExpressions;
using UnityEngine;
namespace ml_amt
{
class AvatarParameter
{
public enum ParameterType
{
GroundedRaw,
Moving
}
readonly ParameterType m_type;
readonly string m_name;
readonly int m_hash = 0;
readonly bool m_sync;
readonly AnimatorControllerParameterType m_innerType;
readonly CVRAnimatorManager m_manager = null;
public AvatarParameter(ParameterType p_type, CVRAnimatorManager p_manager)
{
m_type = p_type;
m_name = p_type.ToString();
m_manager = p_manager;
Regex l_regex = new Regex("^#?" + m_name + '$');
foreach(var l_param in m_manager.animator.parameters)
{
if(l_regex.IsMatch(l_param.name))
{
m_hash = l_param.nameHash;
m_sync = !l_param.name.StartsWith('#');
m_innerType = l_param.type;
break;
}
}
}
public void Update(MotionTweaker p_tweaker)
{
switch(m_type)
{
case ParameterType.GroundedRaw:
SetBoolean(p_tweaker.GetGroundedRaw());
break;
case ParameterType.Moving:
SetBoolean(p_tweaker.GetMoving());
break;
}
}
public bool IsValid() => (m_hash != 0);
public ParameterType GetParameterType() => m_type;
void SetFloat(float p_value)
{
if(m_innerType == AnimatorControllerParameterType.Float)
{
if(m_sync)
m_manager.SetAnimatorParameterFloat(m_name, p_value);
else
m_manager.animator.SetFloat(m_hash, p_value);
}
}
void SetBoolean(bool p_value)
{
if(m_innerType == AnimatorControllerParameterType.Bool)
{
if(m_sync)
m_manager.SetAnimatorParameterBool(m_name, p_value);
else
m_manager.animator.SetBool(m_hash, p_value);
}
}
}
}

View file

@ -1,71 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace ml_amt.Fixes
{
class AnimatorAnalyzer
{
bool m_enabled = true;
List<AnimatorControllerParameter> m_parameters = null;
public void AnalyzeFrom(Animator p_animator)
{
m_enabled = p_animator.enabled;
m_parameters = p_animator.parameters?.ToList();
if(m_parameters != null)
{
foreach(var l_param in m_parameters)
{
switch(l_param.type)
{
case AnimatorControllerParameterType.Bool:
case AnimatorControllerParameterType.Trigger:
l_param.defaultBool = p_animator.GetBool(l_param.nameHash);
break;
case AnimatorControllerParameterType.Float:
l_param.defaultFloat = p_animator.GetFloat(l_param.nameHash);
break;
case AnimatorControllerParameterType.Int:
l_param.defaultInt = p_animator.GetInteger(l_param.nameHash);
break;
}
}
}
}
public void ApplyTo(Animator p_animator)
{
p_animator.enabled = m_enabled;
if(m_parameters != null)
{
foreach(var l_param in m_parameters)
{
switch(l_param.type)
{
case AnimatorControllerParameterType.Bool:
p_animator.SetBool(l_param.nameHash, l_param.defaultBool);
break;
case AnimatorControllerParameterType.Float:
p_animator.SetFloat(l_param.nameHash, l_param.defaultFloat);
break;
case AnimatorControllerParameterType.Int:
p_animator.SetInteger(l_param.nameHash, l_param.defaultInt);
break;
case AnimatorControllerParameterType.Trigger:
{
if(l_param.defaultBool)
p_animator.SetTrigger(l_param.nameHash);
}
break;
}
}
}
}
public bool IsEnabled() => m_enabled;
}
}

View file

@ -1,60 +0,0 @@
using ABI_RC.Core;
using System;
using System.Reflection;
namespace ml_amt.Fixes
{
static class AnimatorOverrideControllerFix
{
internal static void Init(HarmonyLib.Harmony p_instance)
{
// AAS overriding fix
p_instance.Patch(
typeof(CVRAnimatorManager).GetMethod(nameof(CVRAnimatorManager.SetOverrideAnimation)),
new HarmonyLib.HarmonyMethod(typeof(AnimatorOverrideControllerFix).GetMethod(nameof(OnOverride_Prefix), BindingFlags.Static | BindingFlags.NonPublic)),
new HarmonyLib.HarmonyMethod(typeof(AnimatorOverrideControllerFix).GetMethod(nameof(OnOverride_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
p_instance.Patch(
typeof(CVRAnimatorManager).GetMethod(nameof(CVRAnimatorManager.RestoreOverrideAnimation)),
new HarmonyLib.HarmonyMethod(typeof(AnimatorOverrideControllerFix).GetMethod(nameof(OnOverride_Prefix), BindingFlags.Static | BindingFlags.NonPublic)),
new HarmonyLib.HarmonyMethod(typeof(AnimatorOverrideControllerFix).GetMethod(nameof(OnOverride_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
}
// AnimatorOverrideController runtime animation replacement fix
static void OnOverride_Prefix(ref CVRAnimatorManager __instance, out AnimatorAnalyzer __state)
{
__state = new AnimatorAnalyzer();
try
{
if(Settings.OverrideFix && (__instance.animator != null))
{
__state.AnalyzeFrom(__instance.animator);
if(__state.IsEnabled())
__instance.animator.enabled = false;
__instance.animator.WriteDefaultValues();
}
}
catch(Exception l_exception)
{
MelonLoader.MelonLogger.Error(l_exception);
}
}
static void OnOverride_Postfix(ref CVRAnimatorManager __instance, AnimatorAnalyzer __state)
{
try
{
if(Settings.OverrideFix && (__instance.animator != null))
{
__state.ApplyTo(__instance.animator);
if(__state.IsEnabled())
__instance.animator.Update(0f);
}
}
catch(Exception l_exception)
{
MelonLoader.MelonLogger.Error(l_exception);
}
}
}
}

View file

@ -1,114 +0,0 @@
using ABI.CCK.Components;
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Systems.MovementSystem;
using System;
using System.Collections;
using System.Reflection;
using UnityEngine;
namespace ml_amt.Fixes
{
static class MovementJumpFix
{
static FieldInfo ms_avatarHeight = typeof(PlayerSetup).GetField("_avatarHeight", BindingFlags.NonPublic | BindingFlags.Instance);
static float ms_playerHeight = 1f;
internal static void Init(HarmonyLib.Harmony p_instance)
{
p_instance.Patch(
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.SetupAvatar)),
null,
new HarmonyLib.HarmonyMethod(typeof(MovementJumpFix).GetMethod(nameof(OnSetupAvatar_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
p_instance.Patch(
typeof(CVRWorld).GetMethod("SetupWorldRules", BindingFlags.NonPublic | BindingFlags.Instance),
null,
new HarmonyLib.HarmonyMethod(typeof(MovementJumpFix).GetMethod(nameof(OnWorldRulesSetup_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
p_instance.Patch(
typeof(PlayerSetup).GetMethod("SetupIKScaling", BindingFlags.NonPublic | BindingFlags.Instance),
null,
new HarmonyLib.HarmonyMethod(typeof(MovementJumpFix).GetMethod(nameof(OnSetupIKScaling_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
Settings.ScaledJumpChange += OnScaledJumpChange;
MelonLoader.MelonCoroutines.Start(WaitForGameSettings());
}
static IEnumerator WaitForGameSettings()
{
while(MetaPort.Instance == null)
yield return null;
while(MetaPort.Instance.settings == null)
yield return null;
ms_playerHeight = MetaPort.Instance.settings.GetSettingInt("GeneralPlayerHeight") * 0.01f;
MetaPort.Instance.settings.settingIntChanged.AddListener(OnGameSettingIntChange);
}
// Patches
static void OnSetupAvatar_Postfix()
{
try
{
SetScaledJump(Settings.ScaledJump);
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnWorldRulesSetup_Postfix()
{
try
{
SetScaledJump(Settings.ScaledJump);
}
catch(Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnSetupIKScaling_Postfix()
{
try
{
SetScaledJump(Settings.ScaledJump);
}
catch(Exception l_exception)
{
MelonLoader.MelonLogger.Error(l_exception);
}
}
// Mod settings
static void OnScaledJumpChange(bool p_state)
{
SetScaledJump(p_state);
}
// Game settings
static void OnGameSettingIntChange(string p_name, int p_value)
{
if(p_name == "GeneralPlayerHeight")
{
ms_playerHeight = p_value * 0.01f;
}
}
// Arbitrary
static void SetScaledJump(bool p_state)
{
if(Utils.IsWorldSafe())
{
if(p_state)
MovementSystem.Instance.jumpHeight = Mathf.Clamp(Utils.GetWorldJumpHeight() * ((float)ms_avatarHeight.GetValue(PlayerSetup.Instance) / ms_playerHeight), float.MinValue, Utils.GetWorldMovementLimit());
else
MovementSystem.Instance.jumpHeight = Utils.GetWorldJumpHeight();
}
}
}
}

View file

@ -1,111 +1,120 @@
using ABI.CCK.Components;
using ABI_RC.Core.Player;
using ABI_RC.Systems.IK.SubSystems;
using System;
using System.Collections;
using System.Reflection;
namespace ml_amt
{
public class AvatarMotionTweaker : MelonLoader.MelonMod
{
static AvatarMotionTweaker ms_instance = null;
MotionTweaker m_localTweaker = null;
public override void OnInitializeMelon()
{
if(ms_instance == null)
ms_instance = this;
Settings.Init();
HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.ClearAvatar)),
null,
new HarmonyLib.HarmonyMethod(typeof(AvatarMotionTweaker).GetMethod(nameof(OnAvatarClear_Postfix), BindingFlags.NonPublic | BindingFlags.Static))
);
HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.SetupAvatar)),
null,
new HarmonyLib.HarmonyMethod(typeof(AvatarMotionTweaker).GetMethod(nameof(OnSetupAvatar_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
HarmonyInstance.Patch(
typeof(BodySystem).GetMethod(nameof(BodySystem.Calibrate)),
null,
new HarmonyLib.HarmonyMethod(typeof(AvatarMotionTweaker).GetMethod(nameof(OnCalibrate_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
// Fixes
Fixes.AnimatorOverrideControllerFix.Init(HarmonyInstance);
Fixes.MovementJumpFix.Init(HarmonyInstance);
ModSupporter.Init();
MelonLoader.MelonCoroutines.Start(WaitForLocalPlayer());
}
IEnumerator WaitForLocalPlayer()
{
while(PlayerSetup.Instance == null)
yield return null;
m_localTweaker = PlayerSetup.Instance.gameObject.AddComponent<MotionTweaker>();
m_localTweaker.SetCrouchLimit(Settings.CrouchLimit);
m_localTweaker.SetProneLimit(Settings.ProneLimit);
m_localTweaker.SetIKOverrideFly(Settings.IKOverrideFly);
m_localTweaker.SetIKOverrideJump(Settings.IKOverrideJump);
m_localTweaker.SetDetectEmotes(Settings.DetectEmotes);
m_localTweaker.SetFollowHips(Settings.FollowHips);
}
public override void OnDeinitializeMelon()
{
if(ms_instance == this)
ms_instance = null;
m_localTweaker = null;
}
static void OnAvatarClear_Postfix() => ms_instance?.OnAvatarClear();
void OnAvatarClear()
{
try
{
if(m_localTweaker != null)
m_localTweaker.OnAvatarClear();
}
catch(Exception l_exception)
{
MelonLoader.MelonLogger.Error(l_exception);
}
}
static void OnSetupAvatar_Postfix() => ms_instance?.OnSetupAvatar();
void OnSetupAvatar()
{
try
{
if(m_localTweaker != null)
m_localTweaker.OnSetupAvatar();
}
catch(Exception l_exception)
{
MelonLoader.MelonLogger.Error(l_exception);
}
}
static void OnCalibrate_Postfix() => ms_instance?.OnCalibrate();
void OnCalibrate()
{
try
{
if(m_localTweaker != null)
m_localTweaker.OnCalibrate();
}
catch(Exception l_exception)
{
MelonLoader.MelonLogger.Error(l_exception);
}
}
}
}
using ABI_RC.Core.Player;
using ABI_RC.Systems.IK.SubSystems;
using System;
using System.Collections;
using System.Reflection;
namespace ml_amt
{
public class AvatarMotionTweaker : MelonLoader.MelonMod
{
static AvatarMotionTweaker ms_instance = null;
MotionTweaker m_localTweaker = null;
public override void OnInitializeMelon()
{
if(ms_instance == null)
ms_instance = this;
Settings.Init();
HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.ClearAvatar)),
null,
new HarmonyLib.HarmonyMethod(typeof(AvatarMotionTweaker).GetMethod(nameof(OnAvatarClear_Postfix), BindingFlags.NonPublic | BindingFlags.Static))
);
HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.SetupAvatar)),
null,
new HarmonyLib.HarmonyMethod(typeof(AvatarMotionTweaker).GetMethod(nameof(OnSetupAvatar_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
HarmonyInstance.Patch(
typeof(BodySystem).GetMethod(nameof(BodySystem.Calibrate)),
null,
new HarmonyLib.HarmonyMethod(typeof(AvatarMotionTweaker).GetMethod(nameof(OnCalibrate_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod("SetPlaySpaceScale", BindingFlags.NonPublic | BindingFlags.Instance),
null,
new HarmonyLib.HarmonyMethod(typeof(AvatarMotionTweaker).GetMethod(nameof(OnPlayspaceScale_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
MelonLoader.MelonCoroutines.Start(WaitForLocalPlayer());
}
IEnumerator WaitForLocalPlayer()
{
while(PlayerSetup.Instance == null)
yield return null;
m_localTweaker = PlayerSetup.Instance.gameObject.AddComponent<MotionTweaker>();
}
public override void OnDeinitializeMelon()
{
if(ms_instance == this)
ms_instance = null;
if(m_localTweaker != null)
UnityEngine.Object.Destroy(m_localTweaker);
m_localTweaker = null;
}
static void OnAvatarClear_Postfix() => ms_instance?.OnAvatarClear();
void OnAvatarClear()
{
try
{
if(m_localTweaker != null)
m_localTweaker.OnAvatarClear();
}
catch(Exception l_exception)
{
MelonLoader.MelonLogger.Error(l_exception);
}
}
static void OnSetupAvatar_Postfix() => ms_instance?.OnSetupAvatar();
void OnSetupAvatar()
{
try
{
if(m_localTweaker != null)
m_localTweaker.OnSetupAvatar();
}
catch(Exception l_exception)
{
MelonLoader.MelonLogger.Error(l_exception);
}
}
static void OnCalibrate_Postfix() => ms_instance?.OnCalibrate();
void OnCalibrate()
{
try
{
if(m_localTweaker != null)
m_localTweaker.OnCalibrate();
}
catch(Exception l_exception)
{
MelonLoader.MelonLogger.Error(l_exception);
}
}
static void OnPlayspaceScale_Postfix() => ms_instance?.OnPlayspaceScale();
void OnPlayspaceScale()
{
try
{
if(m_localTweaker != null)
m_localTweaker.OnPlayspaceScale();
}
catch(Exception l_exception)
{
MelonLoader.MelonLogger.Error(l_exception);
}
}
}
}

View file

@ -1,49 +0,0 @@
using System.Collections;
using System.Linq;
namespace ml_amt
{
static class ModSupporter
{
static bool ms_ragdollMod = false;
static bool ms_copycatMod = false;
public static void Init()
{
if(MelonLoader.MelonMod.RegisteredMelons.FirstOrDefault(m => m.Info.Name == "PlayerRagdollMod") != null)
MelonLoader.MelonCoroutines.Start(WaitForRagdollInstance());
if(MelonLoader.MelonMod.RegisteredMelons.FirstOrDefault(m => m.Info.Name == "PlayerMovementCopycat") != null)
MelonLoader.MelonCoroutines.Start(WaitForCopycatInstance());
}
// PlayerRagdollMod support
static IEnumerator WaitForRagdollInstance()
{
while(ml_prm.RagdollController.Instance == null)
yield return null;
ms_ragdollMod = true;
}
static bool IsRagdolled() => ml_prm.RagdollController.Instance.IsRagdolled();
// PlayerMovementCopycat support
static IEnumerator WaitForCopycatInstance()
{
while(ml_pmc.PoseCopycat.Instance == null)
yield return null;
ms_copycatMod = true;
}
static bool IsCopycating() => ml_pmc.PoseCopycat.Instance.IsActive();
public static bool SkipHipsOverride()
{
bool l_result = false;
l_result |= (ms_ragdollMod && IsRagdolled());
l_result |= (ms_copycatMod && IsCopycating());
return l_result;
}
}
}

View file

@ -1,305 +1,315 @@
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Systems.IK;
using ABI_RC.Systems.IK.SubSystems;
using ABI_RC.Systems.MovementSystem;
using RootMotion.FinalIK;
using System.Collections.Generic;
using UnityEngine;
namespace ml_amt
{
[DisallowMultipleComponent]
class MotionTweaker : MonoBehaviour
{
struct IKState
{
public float m_weight;
public float m_locomotionWeight;
public bool m_plantFeet;
public bool m_bendNormalLeft;
public bool m_bendNormalRight;
}
static readonly Vector4 ms_pointVector = new Vector4(0f, 0f, 0f, 1f);
static readonly int ms_emoteHash = Animator.StringToHash("Emote");
IKState m_ikState;
VRIK m_vrIk = null;
int m_locomotionLayer = 0;
float m_avatarScale = 1f;
Vector3 m_locomotionOffset = Vector3.zero; // Original locomotion offset
Transform m_avatarHips = null;
bool m_inVR = false;
bool m_avatarReady = false;
bool m_grounded = false;
bool m_groundedRaw = false;
bool m_moving = false;
bool m_locomotionOverride = false;
bool m_ikOverrideFly = true;
bool m_ikOverrideJump = true;
bool m_detectEmotes = true;
bool m_emoteActive = false;
bool m_followHips = true;
Vector3 m_hipsToPlayer = Vector3.zero;
Vector3 m_massCenter = Vector3.zero;
readonly List<AvatarParameter> m_parameters = null;
internal MotionTweaker()
{
m_parameters = new List<AvatarParameter>();
}
// Unity events
void Start()
{
m_inVR = Utils.IsInVR();
Settings.CrouchLimitChange += this.SetCrouchLimit;
Settings.ProneLimitChange += this.SetProneLimit;
Settings.IKOverrideFlyChange += this.SetIKOverrideFly;
Settings.IKOverrideJumpChange += this.SetIKOverrideJump;
Settings.DetectEmotesChange += this.SetDetectEmotes;
Settings.FollowHipsChange += this.SetFollowHips;
Settings.MassCenterChange += this.OnMassCenterChange;
SetCrouchLimit(Settings.CrouchLimit);
SetProneLimit(Settings.ProneLimit);
}
void OnDestroy()
{
Settings.CrouchLimitChange -= this.SetCrouchLimit;
Settings.ProneLimitChange -= this.SetProneLimit;
Settings.IKOverrideFlyChange -= this.SetIKOverrideFly;
Settings.IKOverrideJumpChange -= this.SetIKOverrideJump;
Settings.DetectEmotesChange -= this.SetDetectEmotes;
Settings.FollowHipsChange -= this.SetFollowHips;
Settings.MassCenterChange -= this.OnMassCenterChange;
}
void Update()
{
if(m_avatarReady)
{
m_grounded = MovementSystem.Instance.IsGrounded();
m_groundedRaw = MovementSystem.Instance.IsGroundedRaw();
m_moving = !Mathf.Approximately(MovementSystem.Instance.movementVector.magnitude, 0f);
if(m_avatarHips != null)
{
Vector4 l_hipsToPoint = (PlayerSetup.Instance.transform.GetMatrix().inverse * m_avatarHips.GetMatrix()) * ms_pointVector;
m_hipsToPlayer.Set(l_hipsToPoint.x, 0f, l_hipsToPoint.z);
}
m_emoteActive = false;
if(m_detectEmotes && (m_locomotionLayer >= 0))
{
AnimatorStateInfo l_animState = PlayerSetup.Instance._animator.GetCurrentAnimatorStateInfo(m_locomotionLayer);
m_emoteActive = (l_animState.tagHash == ms_emoteHash);
}
if(m_parameters.Count > 0)
{
foreach(AvatarParameter l_param in m_parameters)
l_param.Update(this);
}
}
}
// Game events
internal void OnAvatarClear()
{
m_vrIk = null;
m_locomotionLayer = -1;
m_grounded = false;
m_groundedRaw = false;
m_avatarReady = false;
m_avatarScale = 1f;
m_locomotionOffset = Vector3.zero;
m_emoteActive = false;
m_moving = false;
m_locomotionOverride = false;
m_hipsToPlayer = Vector3.zero;
m_avatarHips = null;
m_massCenter = Vector3.zero;
m_parameters.Clear();
}
internal void OnSetupAvatar()
{
m_inVR = Utils.IsInVR();
m_vrIk = PlayerSetup.Instance._avatar.GetComponent<VRIK>();
m_locomotionLayer = PlayerSetup.Instance._animator.GetLayerIndex("Locomotion/Emotes");
m_avatarHips = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Hips);
m_avatarScale = Mathf.Abs(PlayerSetup.Instance._avatar.transform.localScale.y);
// Parse animator parameters
m_parameters.Add(new AvatarParameter(AvatarParameter.ParameterType.Upright, PlayerSetup.Instance.animatorManager));
m_parameters.Add(new AvatarParameter(AvatarParameter.ParameterType.GroundedRaw, PlayerSetup.Instance.animatorManager));
m_parameters.Add(new AvatarParameter(AvatarParameter.ParameterType.Moving, PlayerSetup.Instance.animatorManager));
m_parameters.RemoveAll(p => !p.IsValid());
// Apply VRIK tweaks
if(m_vrIk != null)
{
m_locomotionOffset = m_vrIk.solver.locomotion.offset;
m_massCenter = m_locomotionOffset;
if(m_vrIk.solver.HasToes())
{
Transform l_foot = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftFoot);
if(l_foot == null)
l_foot = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightFoot);
Transform l_toe = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftToes);
if(l_toe == null)
l_toe = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightToes);
if((l_foot != null) && (l_toe != null))
{
Vector3 l_footPos = (PlayerSetup.Instance._avatar.transform.GetMatrix().inverse * l_foot.GetMatrix()) * ms_pointVector;
Vector3 l_toePos = (PlayerSetup.Instance._avatar.transform.GetMatrix().inverse * l_toe.GetMatrix()) * ms_pointVector;
m_massCenter = new Vector3(0f, 0f, l_toePos.z - l_footPos.z);
}
}
m_vrIk.solver.locomotion.offset = (Settings.MassCenter ? m_massCenter : m_locomotionOffset);
m_vrIk.onPreSolverUpdate.AddListener(this.OnIKPreUpdate);
m_vrIk.onPostSolverUpdate.AddListener(this.OnIKPostUpdate);
}
m_avatarReady = true;
}
internal void OnCalibrate()
{
if(m_avatarReady && (m_vrIk != null) && (m_vrIk.solver.spine.pelvisTarget != null) && (m_vrIk.solver.leftLeg.target == null) && (m_vrIk.solver.rightLeg.target == null))
{
// Do not consider 4PT as FBT (!!!)
m_vrIk.solver.spine.bodyPosStiffness = 0.55f;
m_vrIk.solver.spine.bodyRotStiffness = 0.1f;
m_vrIk.solver.spine.neckStiffness = 0.5f;
m_vrIk.solver.spine.chestClampWeight = 0.55f;
m_vrIk.solver.spine.moveBodyBackWhenCrouching = 0.5f;
m_vrIk.solver.spine.maxRootAngle = 25f;
m_vrIk.fixTransforms = false;
BodySystem.isCalibratedAsFullBody = false;
BodySystem.TrackingLeftLegEnabled = false;
BodySystem.TrackingRightLegEnabled = false;
BodySystem.TrackingLocomotionEnabled = true;
}
}
// IK events
void OnIKPreUpdate()
{
bool l_locomotionOverride = false;
m_ikState.m_weight = m_vrIk.solver.IKPositionWeight;
m_ikState.m_locomotionWeight = m_vrIk.solver.locomotion.weight;
m_ikState.m_plantFeet = m_vrIk.solver.plantFeet;
m_ikState.m_bendNormalLeft = m_vrIk.solver.leftLeg.useAnimatedBendNormal;
m_ikState.m_bendNormalRight = m_vrIk.solver.rightLeg.useAnimatedBendNormal;
if(m_detectEmotes && m_emoteActive)
m_vrIk.solver.IKPositionWeight = 0f;
if(!BodySystem.isCalibratedAsFullBody)
{
if(PlayerSetup.Instance.avatarUpright <= PlayerSetup.Instance.avatarCrouchLimit)
{
m_vrIk.solver.leftLeg.useAnimatedBendNormal = true;
m_vrIk.solver.rightLeg.useAnimatedBendNormal = true;
l_locomotionOverride = true;
}
if(m_ikOverrideFly && MovementSystem.Instance.flying)
{
m_vrIk.solver.locomotion.weight = 0f;
m_vrIk.solver.leftLeg.useAnimatedBendNormal = true;
m_vrIk.solver.rightLeg.useAnimatedBendNormal = true;
l_locomotionOverride = true;
}
if(m_ikOverrideJump && !m_grounded && !MovementSystem.Instance.flying)
{
m_vrIk.solver.locomotion.weight = 0f;
m_vrIk.solver.leftLeg.useAnimatedBendNormal = true;
m_vrIk.solver.rightLeg.useAnimatedBendNormal = true;
l_locomotionOverride = true;
}
}
bool l_solverActive = !Mathf.Approximately(m_vrIk.solver.IKPositionWeight, 0f);
if(l_locomotionOverride && l_solverActive && m_followHips && (!m_moving || (PlayerSetup.Instance.avatarUpright <= PlayerSetup.Instance.avatarProneLimit)) && m_inVR && !BodySystem.isCalibratedAsFullBody && !ModSupporter.SkipHipsOverride())
{
m_vrIk.solver.plantFeet = false;
IKSystem.VrikRootController.enabled = false;
PlayerSetup.Instance._avatar.transform.localPosition = m_hipsToPlayer;
}
if(m_locomotionOverride && !l_locomotionOverride)
m_vrIk.solver.Reset();
m_locomotionOverride = l_locomotionOverride;
}
void OnIKPostUpdate()
{
m_vrIk.solver.IKPositionWeight = m_ikState.m_weight;
m_vrIk.solver.locomotion.weight = m_ikState.m_locomotionWeight;
m_vrIk.solver.plantFeet = m_ikState.m_plantFeet;
m_vrIk.solver.leftLeg.useAnimatedBendNormal = m_ikState.m_bendNormalLeft;
m_vrIk.solver.rightLeg.useAnimatedBendNormal = m_ikState.m_bendNormalRight;
}
// Settings
internal void SetCrouchLimit(float p_value)
{
PlayerSetup.Instance.avatarCrouchLimit = Mathf.Max(Mathf.Clamp01(p_value), PlayerSetup.Instance.avatarProneLimit);
}
internal void SetProneLimit(float p_value)
{
PlayerSetup.Instance.avatarProneLimit = Mathf.Min(Mathf.Clamp01(p_value), PlayerSetup.Instance.avatarCrouchLimit);
}
internal void SetIKOverrideFly(bool p_state)
{
m_ikOverrideFly = p_state;
}
internal void SetIKOverrideJump(bool p_state)
{
m_ikOverrideJump = p_state;
}
internal void SetDetectEmotes(bool p_state)
{
m_detectEmotes = p_state;
}
internal void SetFollowHips(bool p_state)
{
m_followHips = p_state;
}
void OnMassCenterChange(bool p_state)
{
if(m_vrIk != null)
m_vrIk.solver.locomotion.offset = (Settings.MassCenter ? (m_massCenter * GetRelativeScale()) : m_locomotionOffset);
}
// Arbitrary
float GetRelativeScale()
{
return ((m_avatarScale > 0f) ? (PlayerSetup.Instance._avatar.transform.localScale.y / m_avatarScale) : 0f);
}
// Parameters access
public float GetUpright() => PlayerSetup.Instance.avatarUpright;
public bool GetGroundedRaw() => m_groundedRaw;
public bool GetMoving() => m_moving;
}
}
using ABI_RC.Core.Player;
using ABI_RC.Systems.IK;
using ABI_RC.Systems.IK.SubSystems;
using ABI_RC.Systems.MovementSystem;
using RootMotion.FinalIK;
using System.Collections.Generic;
using UnityEngine;
namespace ml_amt
{
[DisallowMultipleComponent]
class MotionTweaker : MonoBehaviour
{
struct IKState
{
public float m_weight;
public float m_locomotionWeight;
public bool m_plantFeet;
public bool m_bendNormalLeft;
public bool m_bendNormalRight;
}
static readonly Vector4 ms_pointVector = new Vector4(0f, 0f, 0f, 1f);
static readonly int ms_emoteHash = Animator.StringToHash("Emote");
IKState m_ikState;
VRIK m_vrIk = null;
int m_locomotionLayer = 0;
float m_avatarScale = 1f;
Vector3 m_locomotionOffset = Vector3.zero; // Original locomotion offset
bool m_inVR = false;
bool m_avatarReady = false;
bool m_grounded = false;
bool m_groundedRaw = false;
bool m_moving = false;
bool m_locomotionOverride = false;
bool m_ikOverrideFly = true;
bool m_ikOverrideJump = true;
bool m_detectEmotes = true;
bool m_emoteActive = false;
Vector3 m_massCenter = Vector3.zero;
Transform m_ikLimits = null;
readonly List<AvatarParameter> m_parameters = null;
internal MotionTweaker()
{
m_parameters = new List<AvatarParameter>();
}
// Unity events
void Start()
{
m_inVR = Utils.IsInVR();
SetCrouchLimit(Settings.CrouchLimit);
SetProneLimit(Settings.ProneLimit);
SetIKOverrideFly(Settings.IKOverrideFly);
SetIKOverrideJump(Settings.IKOverrideJump);
SetDetectEmotes(Settings.DetectEmotes);
Settings.CrouchLimitChange += this.SetCrouchLimit;
Settings.ProneLimitChange += this.SetProneLimit;
Settings.IKOverrideFlyChange += this.SetIKOverrideFly;
Settings.IKOverrideJumpChange += this.SetIKOverrideJump;
Settings.DetectEmotesChange += this.SetDetectEmotes;
Settings.MassCenterChange += this.OnMassCenterChange;
}
void OnDestroy()
{
m_vrIk = null;
m_ikLimits = null;
m_parameters.Clear();
Settings.CrouchLimitChange -= this.SetCrouchLimit;
Settings.ProneLimitChange -= this.SetProneLimit;
Settings.IKOverrideFlyChange -= this.SetIKOverrideFly;
Settings.IKOverrideJumpChange -= this.SetIKOverrideJump;
Settings.DetectEmotesChange -= this.SetDetectEmotes;
Settings.MassCenterChange -= this.OnMassCenterChange;
}
void Update()
{
if(m_avatarReady)
{
m_grounded = MovementSystem.Instance.IsGrounded();
m_groundedRaw = MovementSystem.Instance.IsGroundedRaw();
m_moving = !Mathf.Approximately(MovementSystem.Instance.movementVector.magnitude, 0f);
UpdateIKLimits();
m_emoteActive = false;
if(m_detectEmotes && (m_locomotionLayer >= 0))
{
AnimatorStateInfo l_animState = PlayerSetup.Instance._animator.GetCurrentAnimatorStateInfo(m_locomotionLayer);
m_emoteActive = (l_animState.tagHash == ms_emoteHash);
}
if(m_parameters.Count > 0)
{
foreach(AvatarParameter l_param in m_parameters)
l_param.Update(this);
}
}
}
// Game events
internal void OnAvatarClear()
{
m_vrIk = null;
m_locomotionLayer = -1;
m_grounded = false;
m_groundedRaw = false;
m_avatarReady = false;
m_avatarScale = 1f;
m_locomotionOffset = Vector3.zero;
m_emoteActive = false;
m_moving = false;
m_locomotionOverride = false;
m_massCenter = Vector3.zero;
m_ikLimits = null;
m_parameters.Clear();
PlayerSetup.Instance.avatarCrouchLimit = Mathf.Clamp01(Settings.CrouchLimit);
PlayerSetup.Instance.avatarProneLimit = Mathf.Clamp01(Settings.ProneLimit);
}
internal void OnSetupAvatar()
{
m_inVR = Utils.IsInVR();
m_vrIk = PlayerSetup.Instance._avatar.GetComponent<VRIK>();
m_locomotionLayer = PlayerSetup.Instance._animator.GetLayerIndex("Locomotion/Emotes");
m_avatarScale = Mathf.Abs(PlayerSetup.Instance._avatar.transform.localScale.y);
// Parse animator parameters
m_parameters.Add(new AvatarParameter(AvatarParameter.ParameterType.GroundedRaw, PlayerSetup.Instance.animatorManager));
m_parameters.Add(new AvatarParameter(AvatarParameter.ParameterType.Moving, PlayerSetup.Instance.animatorManager));
m_parameters.RemoveAll(p => !p.IsValid());
// Avatar custom IK limits
m_ikLimits = PlayerSetup.Instance._avatar.transform.Find("[IKLimits]");
UpdateIKLimits();
// Apply VRIK tweaks
if(m_vrIk != null)
{
m_locomotionOffset = m_vrIk.solver.locomotion.offset;
m_massCenter = m_locomotionOffset;
if(m_vrIk.solver.HasToes())
{
Transform l_foot = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftFoot);
if(l_foot == null)
l_foot = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightFoot);
Transform l_toe = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftToes);
if(l_toe == null)
l_toe = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightToes);
if((l_foot != null) && (l_toe != null))
{
Vector3 l_footPos = (PlayerSetup.Instance._avatar.transform.GetMatrix().inverse * l_foot.GetMatrix()) * ms_pointVector;
Vector3 l_toePos = (PlayerSetup.Instance._avatar.transform.GetMatrix().inverse * l_toe.GetMatrix()) * ms_pointVector;
m_massCenter = new Vector3(0f, 0f, l_toePos.z - l_footPos.z);
}
}
m_vrIk.solver.locomotion.offset = (Settings.MassCenter ? m_massCenter : m_locomotionOffset);
m_vrIk.onPreSolverUpdate.AddListener(this.OnIKPreUpdate);
m_vrIk.onPostSolverUpdate.AddListener(this.OnIKPostUpdate);
}
m_avatarReady = true;
}
internal void OnCalibrate()
{
if(m_avatarReady && (m_vrIk != null) && (m_vrIk.solver.spine.pelvisTarget != null) && (m_vrIk.solver.leftLeg.target == null) && (m_vrIk.solver.rightLeg.target == null))
{
// Do not consider 4PT as FBT (!!!)
m_vrIk.solver.spine.bodyPosStiffness = 0.55f;
m_vrIk.solver.spine.bodyRotStiffness = 0.1f;
m_vrIk.solver.spine.neckStiffness = 0.5f;
m_vrIk.solver.spine.chestClampWeight = 0.55f;
m_vrIk.solver.spine.moveBodyBackWhenCrouching = 0.5f;
m_vrIk.solver.spine.maxRootAngle = 25f;
m_vrIk.fixTransforms = false;
BodySystem.isCalibratedAsFullBody = false;
BodySystem.TrackingLeftLegEnabled = false;
BodySystem.TrackingRightLegEnabled = false;
BodySystem.TrackingLocomotionEnabled = true;
IKSystem.Instance.applyOriginalHipRotation = true;
}
}
internal void OnPlayspaceScale()
{
if((m_vrIk != null) && Settings.MassCenter)
m_vrIk.solver.locomotion.offset = m_massCenter * GetRelativeScale();
}
// IK events
void OnIKPreUpdate()
{
bool l_locomotionOverride = false;
m_ikState.m_weight = m_vrIk.solver.IKPositionWeight;
m_ikState.m_locomotionWeight = m_vrIk.solver.locomotion.weight;
m_ikState.m_plantFeet = m_vrIk.solver.plantFeet;
m_ikState.m_bendNormalLeft = m_vrIk.solver.leftLeg.useAnimatedBendNormal;
m_ikState.m_bendNormalRight = m_vrIk.solver.rightLeg.useAnimatedBendNormal;
if(m_detectEmotes && m_emoteActive)
m_vrIk.solver.IKPositionWeight = 0f;
if(!BodySystem.isCalibratedAsFullBody)
{
if(PlayerSetup.Instance.avatarUpright <= PlayerSetup.Instance.avatarCrouchLimit)
{
m_vrIk.solver.leftLeg.useAnimatedBendNormal = true;
m_vrIk.solver.rightLeg.useAnimatedBendNormal = true;
l_locomotionOverride = true;
}
if(m_ikOverrideFly && MovementSystem.Instance.flying)
{
m_vrIk.solver.locomotion.weight = 0f;
m_vrIk.solver.leftLeg.useAnimatedBendNormal = true;
m_vrIk.solver.rightLeg.useAnimatedBendNormal = true;
l_locomotionOverride = true;
}
if(m_ikOverrideJump && !m_grounded && !MovementSystem.Instance.flying)
{
m_vrIk.solver.locomotion.weight = 0f;
m_vrIk.solver.leftLeg.useAnimatedBendNormal = true;
m_vrIk.solver.rightLeg.useAnimatedBendNormal = true;
l_locomotionOverride = true;
}
}
if(m_locomotionOverride && !l_locomotionOverride)
m_vrIk.solver.Reset();
m_locomotionOverride = l_locomotionOverride;
}
void OnIKPostUpdate()
{
m_vrIk.solver.IKPositionWeight = m_ikState.m_weight;
m_vrIk.solver.locomotion.weight = m_ikState.m_locomotionWeight;
m_vrIk.solver.plantFeet = m_ikState.m_plantFeet;
m_vrIk.solver.leftLeg.useAnimatedBendNormal = m_ikState.m_bendNormalLeft;
m_vrIk.solver.rightLeg.useAnimatedBendNormal = m_ikState.m_bendNormalRight;
}
// Settings
internal void SetCrouchLimit(float p_value)
{
if(m_ikLimits == null)
PlayerSetup.Instance.avatarCrouchLimit = Mathf.Clamp01(p_value);
}
internal void SetProneLimit(float p_value)
{
if(m_ikLimits == null)
PlayerSetup.Instance.avatarProneLimit = Mathf.Clamp01(p_value);
}
internal void SetIKOverrideFly(bool p_state)
{
m_ikOverrideFly = p_state;
}
internal void SetIKOverrideJump(bool p_state)
{
m_ikOverrideJump = p_state;
}
internal void SetDetectEmotes(bool p_state)
{
m_detectEmotes = p_state;
}
void OnMassCenterChange(bool p_state)
{
if(m_vrIk != null)
m_vrIk.solver.locomotion.offset = (Settings.MassCenter ? (m_massCenter * GetRelativeScale()) : m_locomotionOffset);
}
// Arbitrary
float GetRelativeScale()
{
return ((m_avatarScale > 0f) ? (PlayerSetup.Instance._avatar.transform.localScale.y / m_avatarScale) : 0f);
}
void UpdateIKLimits()
{
if(m_ikLimits != null)
{
Vector3 l_values = m_ikLimits.localPosition;
PlayerSetup.Instance.avatarCrouchLimit = Mathf.Clamp01(l_values.x);
PlayerSetup.Instance.avatarProneLimit = Mathf.Clamp01(l_values.y);
}
}
// Parameters access
public float GetUpright() => PlayerSetup.Instance.avatarUpright;
public bool GetGroundedRaw() => m_groundedRaw;
public bool GetMoving() => m_moving;
}
}

View file

@ -1,7 +1,4 @@
using System.Reflection;
[assembly: MelonLoader.MelonInfo(typeof(ml_amt.AvatarMotionTweaker), "AvatarMotionTweaker", "1.2.9", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonGame(null, "ChilloutVR")]
[assembly: MelonLoader.MelonOptionalDependencies("ml_prm", "ml_pmc")]
[assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
[assembly: MelonLoader.MelonInfo(typeof(ml_amt.AvatarMotionTweaker), "AvatarMotionTweaker", "1.3.6", "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)]

View file

@ -1,43 +1,33 @@
# Avatar Motion Tweaker
This mod adds features for AAS animator and avatar locomotion behaviour.
![](.github/img_01.png)
# Installation
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
* Get [latest release DLL](../../../releases/latest):
* Put `ml_amt.dll` in `Mods` folder of game
# Usage
Available mod's settings in `Settings - IK - Avatar Motion Tweaker`:
* **Crouch limit:** defines crouch limit; default value - `75`.
* **Prone limit:** defines prone limit; default value - `40`.
* **IK override while flying:** disables legs locomotion/autostep in fly mode; default value - `true`.
* **IK override while jumping:** disables legs locomotion/autostep in jump; default value - `true`.
* **Follow hips on IK override:** adjusts avatar position to overcome animation snapping on IK override; default value - `true`.
* Note: Works best with animations that have root transform position (XZ) based on center of mass.
* Note: Made for four point tracking (head, hands and hips) in mind.
* **Detect animations emote tag:** disables avatar's IK entirely if current animator state has `Emote` tag; default value - `true`.
* Note: Created as example for [propoused game feature](https://feedback.abinteractive.net/p/disabling-vr-ik-for-emotes-via-animator-state-tag-7b80d963-053a-41c0-86ac-e3d53c61c1e2).
* **Adjusted locomotion mass center:** automatically changes IK locomotion center if avatar has toe bones; default value - `true`.
* Note: Compatible with [DesktopVRIK](https://github.com/NotAKidOnSteam/DesktopVRIK) and [FuckToes](https://github.com/NotAKidOnSteam/FuckToes).
#### Fixes/overhauls options
* **Scaled locomotion jump:** scales locomotion jump according to relation between your player settings height and current avatar height (includes avatar scale); default value - `false`.
* Note: Disabled in worlds that don't allow flight.
* **Fix animation overrides (chairs, etc.):** fixes animations overriding for avatars with AAS; default value - `true`.
* Note: This option is made to address [broken animator in chairs and combat worlds issue](https://feedback.abinteractive.net/p/gestures-getting-stuck-locally-upon-entering-vehicles-chairs).
Available additional parameters for AAS animator:
* **`Upright`:** defines linear coefficient between current viewpoint height and avatar's viewpoint height; float, range - [0.0, 1.0].
* Note: Can be set as local-only (not synced) if starts with `#` character.
* Note: Shouldn't be used for transitions between poses in desktop mode. In desktop mode its value is driven by avatar animations. Use `CVR Parameter Stream` for detecting desktop/VR modes and change AAS animator transitions accordingly.
* **`GroundedRaw`:** defines instant grounding state of player instead of delayed default parameter `Grounded`; boolean.
* Note: Can be set as local-only (not synced) if starts with `#` character.
* **`Moving`:** defines movement state of player; boolean.
* Note: Can be set as local-only (not synced) if starts with `#` character.
Additional mod's behaviour:
* Overrides and fixes IK behaviour in 4PT mode (head, hands and hips).
# NOTE
This is testing update for game build r171, not ready for massive usage yet!
# Avatar Motion Tweaker
This mod adds features for AAS animator and avatar locomotion behaviour.
![](.github/img_01.png)
# Installation
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
* Get [latest release DLL](../../../releases/latest):
* Put `ml_amt.dll` in `Mods` folder of game
# Usage
Available mod's settings in `Settings - IK - Avatar Motion Tweaker`:
* **Crouch limit:** defines crouch limit; default value - `75`.
* **Prone limit:** defines prone limit; default value - `40`.
* **IK override while flying:** disables legs locomotion/autostep in fly mode; default value - `true`.
* **IK override while jumping:** disables legs locomotion/autostep in jump; default value - `true`.
* **Detect animations emote tag:** disables avatar's IK entirely if current animator state has `Emote` tag; default value - `true`.
* Note: Created as example for [propoused game feature](https://feedback.abinteractive.net/p/disabling-vr-ik-for-emotes-via-animator-state-tag-7b80d963-053a-41c0-86ac-e3d53c61c1e2).
* **Adjusted locomotion mass center:** automatically changes IK locomotion center if avatar has toe bones; default value - `true`.
* Note: Compatible with [DesktopVRIK](https://github.com/NotAKidOnSteam/DesktopVRIK) and [FuckToes](https://github.com/NotAKidOnSteam/FuckToes).
Available additional parameters for AAS animator:
* **`Upright`:** defines linear coefficient between current viewpoint height and avatar's viewpoint height; float, range - [0.0, 1.0].
* Note: Can be set as local-only (not synced) if starts with `#` character.
* Note: Shouldn't be used for transitions between poses in desktop mode. In desktop mode its value is driven by avatar animations. Use `CVR Parameter Stream` for detecting desktop/VR modes and change AAS animator transitions accordingly.
* **`GroundedRaw`:** defines instant grounding state of player instead of delayed default parameter `Grounded`; boolean.
* Note: Can be set as local-only (not synced) if starts with `#` character.
* **`Moving`:** defines movement state of player; boolean.
* Note: Can be set as local-only (not synced) if starts with `#` character.
Additional mod's behaviour:
* Overrides and fixes IK behaviour in 4PT mode (head, hands and hips).
* Avatars can have controlled IK crouch and prone limits. For that create `[IKLimits]` GameObject parented to avatar's root. Its local X and Y positions will be used as crouch and prone limits respectively and can be changed via animations. Values should be in range of [0;1].

View file

@ -0,0 +1,26 @@
using System;
using System.IO;
using System.Reflection;
namespace ml_amt
{
static class ResourcesHandler
{
public static string GetEmbeddedResource(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;
}
}
}

View file

@ -1,183 +1,147 @@
using ABI_RC.Core.InteractionSystem;
using cohtml;
using System;
using System.Collections.Generic;
namespace ml_amt
{
static class Settings
{
enum ModSetting
{
CrouchLimit,
ProneLimit,
IKOverrideFly,
IKOverrideJump,
DetectEmotes,
FollowHips,
ScaledJump,
MassCenter,
OverrideFix
};
public static float CrouchLimit { get; private set; } = 0.75f;
public static float ProneLimit { get; private set; } = 0.4f;
public static bool IKOverrideFly { get; private set; } = true;
public static bool IKOverrideJump { get; private set; } = true;
public static bool DetectEmotes { get; private set; } = true;
public static bool FollowHips { get; private set; } = true;
public static bool MassCenter { get; private set; } = true;
public static bool ScaledJump { get; private set; } = false;
public static bool OverrideFix { get; private set; } = true;
static MelonLoader.MelonPreferences_Category ms_category = null;
static List<MelonLoader.MelonPreferences_Entry> ms_entries = null;
static public event Action<float> CrouchLimitChange;
static public event Action<float> ProneLimitChange;
static public event Action<bool> IKOverrideFlyChange;
static public event Action<bool> IKOverrideJumpChange;
static public event Action<bool> DetectEmotesChange;
static public event Action<bool> FollowHipsChange;
static public event Action<bool> MassCenterChange;
static public event Action<bool> ScaledJumpChange;
static public event Action<bool> OverrideFixChange;
internal static void Init()
{
ms_category = MelonLoader.MelonPreferences.CreateCategory("AMT", null, true);
ms_entries = new List<MelonLoader.MelonPreferences_Entry>()
{
ms_category.CreateEntry(ModSetting.CrouchLimit.ToString(), (int)(CrouchLimit * 100f)),
ms_category.CreateEntry(ModSetting.ProneLimit.ToString(), (int)(ProneLimit * 100f)),
ms_category.CreateEntry(ModSetting.IKOverrideFly.ToString(), IKOverrideFly),
ms_category.CreateEntry(ModSetting.IKOverrideJump.ToString(), IKOverrideJump),
ms_category.CreateEntry(ModSetting.DetectEmotes.ToString(), DetectEmotes),
ms_category.CreateEntry(ModSetting.FollowHips.ToString(), FollowHips),
ms_category.CreateEntry(ModSetting.MassCenter.ToString(), MassCenter),
ms_category.CreateEntry(ModSetting.ScaledJump.ToString(), ScaledJump),
ms_category.CreateEntry(ModSetting.OverrideFix.ToString(), OverrideFix)
};
CrouchLimit = ((int)ms_entries[(int)ModSetting.CrouchLimit].BoxedValue) * 0.01f;
ProneLimit = ((int)ms_entries[(int)ModSetting.ProneLimit].BoxedValue) * 0.01f;
IKOverrideFly = (bool)ms_entries[(int)ModSetting.IKOverrideFly].BoxedValue;
IKOverrideJump = (bool)ms_entries[(int)ModSetting.IKOverrideJump].BoxedValue;
DetectEmotes = (bool)ms_entries[(int)ModSetting.DetectEmotes].BoxedValue;
FollowHips = (bool)ms_entries[(int)ModSetting.FollowHips].BoxedValue;
MassCenter = (bool)ms_entries[(int)ModSetting.MassCenter].BoxedValue;
ScaledJump = (bool)ms_entries[(int)ModSetting.ScaledJump].BoxedValue;
OverrideFix = (bool)ms_entries[(int)ModSetting.OverrideFix].BoxedValue;
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_AMT_Call_InpSlider", new Action<string, string>(OnSliderUpdate));
ViewManager.Instance.gameMenuView.View.BindCall("MelonMod_AMT_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("updateModSettingAMT", l_entry.DisplayName, l_entry.GetValueAsString());
};
}
static void OnSliderUpdate(string p_name, string p_value)
{
if(Enum.TryParse(p_name, out ModSetting l_setting))
{
switch(l_setting)
{
case ModSetting.CrouchLimit:
{
CrouchLimit = int.Parse(p_value) * 0.01f;
CrouchLimitChange?.Invoke(CrouchLimit);
}
break;
case ModSetting.ProneLimit:
{
ProneLimit = int.Parse(p_value) * 0.01f;
ProneLimitChange?.Invoke(ProneLimit);
}
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.IKOverrideFly:
{
IKOverrideFly = bool.Parse(p_value);
IKOverrideFlyChange?.Invoke(IKOverrideFly);
}
break;
case ModSetting.IKOverrideJump:
{
IKOverrideJump = bool.Parse(p_value);
IKOverrideJumpChange?.Invoke(IKOverrideJump);
}
break;
case ModSetting.DetectEmotes:
{
DetectEmotes = bool.Parse(p_value);
DetectEmotesChange?.Invoke(DetectEmotes);
}
break;
case ModSetting.FollowHips:
{
FollowHips = bool.Parse(p_value);
FollowHipsChange?.Invoke(FollowHips);
}
break;
case ModSetting.MassCenter:
{
MassCenter = bool.Parse(p_value);
MassCenterChange?.Invoke(MassCenter);
}
break;
case ModSetting.ScaledJump:
{
ScaledJump = bool.Parse(p_value);
ScaledJumpChange?.Invoke(ScaledJump);
}
break;
case ModSetting.OverrideFix:
{
OverrideFix = bool.Parse(p_value);
OverrideFixChange?.Invoke(OverrideFix);
}
break;
}
ms_entries[(int)l_setting].BoxedValue = bool.Parse(p_value);
}
}
}
}
using ABI_RC.Core.InteractionSystem;
using System;
using System.Collections.Generic;
namespace ml_amt
{
static class Settings
{
enum ModSetting
{
CrouchLimit,
ProneLimit,
IKOverrideFly,
IKOverrideJump,
DetectEmotes,
MassCenter
};
public static float CrouchLimit { get; private set; } = 0.75f;
public static float ProneLimit { get; private set; } = 0.4f;
public static bool IKOverrideFly { get; private set; } = true;
public static bool IKOverrideJump { get; private set; } = true;
public static bool DetectEmotes { get; private set; } = true;
public static bool MassCenter { get; private set; } = true;
static MelonLoader.MelonPreferences_Category ms_category = null;
static List<MelonLoader.MelonPreferences_Entry> ms_entries = null;
static public event Action<float> CrouchLimitChange;
static public event Action<float> ProneLimitChange;
static public event Action<bool> IKOverrideFlyChange;
static public event Action<bool> IKOverrideJumpChange;
static public event Action<bool> DetectEmotesChange;
static public event Action<bool> MassCenterChange;
internal static void Init()
{
ms_category = MelonLoader.MelonPreferences.CreateCategory("AMT", null, true);
ms_entries = new List<MelonLoader.MelonPreferences_Entry>()
{
ms_category.CreateEntry(ModSetting.CrouchLimit.ToString(), (int)(CrouchLimit * 100f)),
ms_category.CreateEntry(ModSetting.ProneLimit.ToString(), (int)(ProneLimit * 100f)),
ms_category.CreateEntry(ModSetting.IKOverrideFly.ToString(), IKOverrideFly),
ms_category.CreateEntry(ModSetting.IKOverrideJump.ToString(), IKOverrideJump),
ms_category.CreateEntry(ModSetting.DetectEmotes.ToString(), DetectEmotes),
ms_category.CreateEntry(ModSetting.MassCenter.ToString(), MassCenter)
};
CrouchLimit = ((int)ms_entries[(int)ModSetting.CrouchLimit].BoxedValue) * 0.01f;
ProneLimit = ((int)ms_entries[(int)ModSetting.ProneLimit].BoxedValue) * 0.01f;
IKOverrideFly = (bool)ms_entries[(int)ModSetting.IKOverrideFly].BoxedValue;
IKOverrideJump = (bool)ms_entries[(int)ModSetting.IKOverrideJump].BoxedValue;
DetectEmotes = (bool)ms_entries[(int)ModSetting.DetectEmotes].BoxedValue;
MassCenter = (bool)ms_entries[(int)ModSetting.MassCenter].BoxedValue;
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("OnSliderUpdate_" + ms_category.Identifier, new Action<string, string>(OnSliderUpdate));
ViewManager.Instance.gameMenuView.View.BindCall("OnToggleUpdate_" + ms_category.Identifier, new Action<string, string>(OnToggleUpdate));
};
ViewManager.Instance.gameMenuView.Listener.FinishLoad += (_) =>
{
ViewManager.Instance.gameMenuView.View.ExecuteScript(ResourcesHandler.GetEmbeddedResource("mods_extension.js"));
ViewManager.Instance.gameMenuView.View.ExecuteScript(ResourcesHandler.GetEmbeddedResource("mod_menu.js"));
foreach(var l_entry in ms_entries)
ViewManager.Instance.gameMenuView.View.TriggerEvent("updateModSetting", ms_category.Identifier, l_entry.DisplayName, l_entry.GetValueAsString());
};
}
static void OnSliderUpdate(string p_name, string p_value)
{
if(Enum.TryParse(p_name, out ModSetting l_setting))
{
switch(l_setting)
{
case ModSetting.CrouchLimit:
{
CrouchLimit = int.Parse(p_value) * 0.01f;
CrouchLimitChange?.Invoke(CrouchLimit);
}
break;
case ModSetting.ProneLimit:
{
ProneLimit = int.Parse(p_value) * 0.01f;
ProneLimitChange?.Invoke(ProneLimit);
}
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.IKOverrideFly:
{
IKOverrideFly = bool.Parse(p_value);
IKOverrideFlyChange?.Invoke(IKOverrideFly);
}
break;
case ModSetting.IKOverrideJump:
{
IKOverrideJump = bool.Parse(p_value);
IKOverrideJumpChange?.Invoke(IKOverrideJump);
}
break;
case ModSetting.DetectEmotes:
{
DetectEmotes = bool.Parse(p_value);
DetectEmotesChange?.Invoke(DetectEmotes);
}
break;
case ModSetting.MassCenter:
{
MassCenter = bool.Parse(p_value);
MassCenterChange?.Invoke(MassCenter);
}
break;
}
ms_entries[(int)l_setting].BoxedValue = bool.Parse(p_value);
}
}
}
}

View file

@ -1,58 +1,45 @@
using ABI.CCK.Components;
using ABI_RC.Core.UI;
using ABI_RC.Systems.MovementSystem;
using RootMotion.FinalIK;
using System.Reflection;
using UnityEngine;
namespace ml_amt
{
static class Utils
{
static readonly FieldInfo ms_grounded = typeof(MovementSystem).GetField("_isGrounded", BindingFlags.NonPublic | BindingFlags.Instance);
static readonly FieldInfo ms_groundedRaw = typeof(MovementSystem).GetField("_isGroundedRaw", BindingFlags.NonPublic | BindingFlags.Instance);
static readonly FieldInfo ms_hasToes = typeof(IKSolverVR).GetField("hasToes", BindingFlags.NonPublic | BindingFlags.Instance);
static MethodInfo ms_getSineKeyframes = typeof(IKSolverVR).GetMethod("GetSineKeyframes", BindingFlags.NonPublic | BindingFlags.Static);
static FieldInfo ms_cohtmlView = typeof(CohtmlControlledViewDisposable).GetField("_view", BindingFlags.NonPublic | BindingFlags.Instance);
public static bool IsInVR() => ((ABI_RC.Core.Savior.CheckVR.Instance != null) && ABI_RC.Core.Savior.CheckVR.Instance.hasVrDeviceLoaded);
public static bool IsGrounded(this MovementSystem p_instance) => (bool)ms_grounded.GetValue(MovementSystem.Instance);
public static bool IsGroundedRaw(this MovementSystem p_instance) => (bool)ms_groundedRaw.GetValue(MovementSystem.Instance);
public static bool HasToes(this IKSolverVR p_instance) => (bool)ms_hasToes.GetValue(p_instance);
public static Keyframe[] GetSineKeyframes(float p_mag)
{
return (Keyframe[])ms_getSineKeyframes.Invoke(null, new object[] { p_mag });
}
public static bool IsWorldSafe() => ((CVRWorld.Instance != null) && CVRWorld.Instance.allowFlying);
public static float GetWorldJumpHeight()
{
float l_result = 1f;
if(CVRWorld.Instance != null)
l_result = CVRWorld.Instance.jumpHeight;
return l_result;
}
public static float GetWorldMovementLimit()
{
float l_result = 1f;
if(CVRWorld.Instance != null)
{
l_result = CVRWorld.Instance.baseMovementSpeed;
l_result *= CVRWorld.Instance.sprintMultiplier;
l_result *= CVRWorld.Instance.inAirMovementMultiplier;
l_result *= CVRWorld.Instance.flyMultiplier;
}
return l_result;
}
public static void ExecuteScript(this CohtmlControlledViewDisposable p_instance, string p_script) => ((cohtml.Net.View)ms_cohtmlView.GetValue(p_instance))?.ExecuteScript(p_script);
// Engine extensions
public static Matrix4x4 GetMatrix(this Transform p_transform, bool p_pos = true, bool p_rot = true, bool p_scl = false)
{
return Matrix4x4.TRS(p_pos ? p_transform.position : Vector3.zero, p_rot ? p_transform.rotation : Quaternion.identity, p_scl ? p_transform.localScale : Vector3.one);
}
}
}
using ABI.CCK.Components;
using ABI_RC.Core.UI;
using ABI_RC.Systems.MovementSystem;
using RootMotion.FinalIK;
using System.Reflection;
using UnityEngine;
namespace ml_amt
{
static class Utils
{
static readonly FieldInfo ms_grounded = typeof(MovementSystem).GetField("_isGrounded", BindingFlags.NonPublic | BindingFlags.Instance);
static readonly FieldInfo ms_groundedRaw = typeof(MovementSystem).GetField("_isGroundedRaw", BindingFlags.NonPublic | BindingFlags.Instance);
static readonly FieldInfo ms_hasToes = typeof(IKSolverVR).GetField("hasToes", BindingFlags.NonPublic | BindingFlags.Instance);
static readonly FieldInfo ms_view = typeof(CohtmlControlledViewWrapper).GetField("_view", BindingFlags.NonPublic | BindingFlags.Instance);
public static bool IsInVR() => ((ABI_RC.Core.Savior.CheckVR.Instance != null) && ABI_RC.Core.Savior.CheckVR.Instance.hasVrDeviceLoaded);
public static bool IsGroundedRaw(this MovementSystem p_instance) => (bool)ms_groundedRaw.GetValue(MovementSystem.Instance);
public static bool HasToes(this IKSolverVR p_instance) => (bool)ms_hasToes.GetValue(p_instance);
public static bool IsWorldSafe() => ((CVRWorld.Instance != null) && CVRWorld.Instance.allowFlying);
public static float GetWorldMovementLimit()
{
float l_result = 1f;
if(CVRWorld.Instance != null)
{
l_result = CVRWorld.Instance.baseMovementSpeed;
l_result *= CVRWorld.Instance.sprintMultiplier;
l_result *= CVRWorld.Instance.inAirMovementMultiplier;
l_result *= CVRWorld.Instance.flyMultiplier;
}
return l_result;
}
static public void ExecuteScript(this CohtmlControlledViewWrapper p_instance, string p_script) => ((cohtml.Net.View)ms_view.GetValue(p_instance)).ExecuteScript(p_script);
// Engine extensions
public static Matrix4x4 GetMatrix(this Transform p_transform, bool p_pos = true, bool p_rot = true, bool p_scl = false)
{
return Matrix4x4.TRS(p_pos ? p_transform.position : Vector3.zero, p_rot ? p_transform.rotation : Quaternion.identity, p_scl ? p_transform.localScale : Vector3.one);
}
}
}

View file

@ -1,89 +1,97 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Authors>SDraw</Authors>
<Company>None</Company>
<Product>AvatarMotionTweaker</Product>
<PackageId>AvatarMotionTweaker</PackageId>
<Version>1.2.9</Version>
<Platforms>x64</Platforms>
<AssemblyName>ml_amt</AssemblyName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<PlatformTarget>x64</PlatformTarget>
<WarningLevel>4</WarningLevel>
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Test.cs" />
</ItemGroup>
<ItemGroup>
<None Remove="AvatarMotionTweaker.json" />
<None Remove="resources\menu.js" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="resources\menu.js" />
</ItemGroup>
<ItemGroup>
<Reference Include="0Harmony">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\0Harmony.dll</HintPath>
<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>
</Reference>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="cohtml.Net">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\cohtml.Net.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Cohtml.Runtime">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Cohtml.Runtime.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="MelonLoader">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\MelonLoader.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="ml_pmc">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\Mods\ml_pmc.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="ml_prm">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\Mods\ml_prm.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.AnimationModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AnimationModule.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.PhysicsModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.PhysicsModule.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="copy /y &quot;$(TargetPath)&quot; &quot;D:\Games\Steam\steamapps\common\ChilloutVR\Mods\&quot;" />
</Target>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Authors>SDraw</Authors>
<Company>None</Company>
<Product>AvatarMotionTweaker</Product>
<PackageId>AvatarMotionTweaker</PackageId>
<Version>1.3.6</Version>
<Platforms>x64</Platforms>
<AssemblyName>ml_amt</AssemblyName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<PlatformTarget>x64</PlatformTarget>
<WarningLevel>4</WarningLevel>
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<DefineConstants>TRACE</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Test.cs" />
</ItemGroup>
<ItemGroup>
<None Remove="AvatarMotionTweaker.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>
<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="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>
<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.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>
<Reference Include="UnityEngine.PhysicsModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.PhysicsModule.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="copy /y &quot;$(TargetPath)&quot; &quot;D:\Games\Steam\steamapps\common\ChilloutVR\Mods\&quot;" />
</Target>
</Project>

View file

@ -1,261 +0,0 @@
// Add settings
var g_modSettingsAMT = [];
engine.on('updateModSettingAMT', function (_name, _value) {
for (var i = 0; i < g_modSettingsAMT.length; i++) {
if (g_modSettingsAMT[i].name == _name) {
g_modSettingsAMT[i].updateValue(_value);
break;
}
}
});
// Modified from original `inp` types, because I have no js knowledge to hook stuff
function inp_slider_mod_amt(_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 + "&apos;" + inches + '&apos;&apos;';
}
}
}
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_amt(_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">Avatar Motion Tweaker</div>
<div class ="subcategory-description"></div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Crouch limit: </div>
<div class ="option-input">
<div id="CrouchLimit" class ="inp_slider no-scroll" data-min="0" data-max="100" data-current="75"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Prone limit: </div>
<div class ="option-input">
<div id="ProneLimit" class ="inp_slider no-scroll" data-min="0" data-max="100" data-current="40"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">IK override while flying: </div>
<div class ="option-input">
<div id="IKOverrideFly" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">IK override while jumping: </div>
<div class ="option-input">
<div id="IKOverrideJump" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Follow hips on IK override: </div>
<div class ="option-input">
<div id="FollowHips" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Detect animations emote tag: </div>
<div class ="option-input">
<div id="DetectEmotes" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Adjusted locomotion mass center: </div>
<div class ="option-input">
<div id="MassCenter" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<h4><p style="color: #7F7F7F">Avatar independent game fixes/overhauls</p></h4><br>
<div class ="row-wrapper">
<div class ="option-caption">Scaled locomotion jump: </div>
<div class ="option-input">
<div id="ScaledJump" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Fix animator overrides (chairs, etc.): </div>
<div class ="option-input">
<div id="OverrideFix" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
`;
document.getElementById('settings-ik').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_modSettingsAMT[g_modSettingsAMT.length] = new inp_slider_mod_amt(l_sliders[i], 'MelonMod_AMT_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_modSettingsAMT[g_modSettingsAMT.length] = new inp_toggle_mod_amt(l_toggles[i], 'MelonMod_AMT_Call_InpToggle');
}
}

View file

@ -0,0 +1,54 @@
// Add own menu
{
let l_block = document.createElement('div');
l_block.innerHTML = `
<div class ="settings-subcategory">
<div class ="subcategory-name">Avatar Motion Tweaker</div>
<div class ="subcategory-description"></div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Crouch limit: </div>
<div class ="option-input">
<div id="CrouchLimit" class ="inp_slider no-scroll" data-min="0" data-max="100" data-current="75"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Prone limit: </div>
<div class ="option-input">
<div id="ProneLimit" class ="inp_slider no-scroll" data-min="0" data-max="100" data-current="40"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">IK override while flying: </div>
<div class ="option-input">
<div id="IKOverrideFly" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Detect animations emote tag: </div>
<div class ="option-input">
<div id="DetectEmotes" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Adjusted locomotion mass center: </div>
<div class ="option-input">
<div id="MassCenter" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
`;
document.getElementById('settings-ik').appendChild(l_block);
// Toggles
for (let l_toggle of l_block.querySelectorAll('.inp_toggle'))
modsExtension.addSetting('AMT', l_toggle.id, modsExtension.createToggle(l_toggle, 'OnToggleUpdate_AMT'));
// Sliders
for (let l_slider of l_block.querySelectorAll('.inp_slider'))
modsExtension.addSetting('AMT', l_slider.id, modsExtension.createSlider(l_slider, 'OnSliderUpdate_AMT'));
}

25
ml_asl/Main.cs Normal file
View file

@ -0,0 +1,25 @@
using ABI_RC.Core.Player;
using System.Reflection;
namespace ml_asl
{
public class AvatarSyncedLook : MelonLoader.MelonMod
{
public override void OnInitializeMelon()
{
Settings.Init();
HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod("UpdatePlayerAvatarMovementData", BindingFlags.NonPublic | BindingFlags.Instance),
null,
new HarmonyLib.HarmonyMethod(typeof(AvatarSyncedLook).GetMethod(nameof(OnPlayerAvatarMovementDataUpdate_Postfix), BindingFlags.NonPublic | BindingFlags.Static))
);
}
static void OnPlayerAvatarMovementDataUpdate_Postfix(ref PlayerSetup __instance, PlayerAvatarMovementData ____playerAvatarMovementData)
{
if(Settings.Enabled && (__instance.eyeMovement != null))
____playerAvatarMovementData.EyeTrackingOverride = true;
}
}
}

View file

@ -0,0 +1,4 @@
[assembly: MelonLoader.MelonInfo(typeof(ml_asl.AvatarSyncedLook), "AvatarSyncedLook", "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)]

14
ml_asl/README.md Normal file
View file

@ -0,0 +1,14 @@
# Avatar Synced Look
This mod Forces local player's eyes look direction to be synced for remote players.
# Installation
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
* Get [latest release DLL](../../../releases/latest):
* Put `ml_asl.dll` in `Mods` folder of game
# Usage
Available mod's settings in `Settings - Interactions - Avatar Synced Look`:
* **Enabled:** sets eyes look direction to be synced or locally generated on remote users side; `true` by default.
# Notes
* Remote users with [EyeMovementFix](https://github.com/kafeijao/Kafe_CVR_Mods/tree/master/EyeMovementFix) installed can't see synced look direction.

View file

@ -0,0 +1,26 @@
using System;
using System.IO;
using System.Reflection;
namespace ml_asl
{
static class ResourcesHandler
{
public static string GetEmbeddedResource(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;
}
}
}

75
ml_asl/Settings.cs Normal file
View file

@ -0,0 +1,75 @@
using ABI_RC.Core.InteractionSystem;
using System;
using System.Collections.Generic;
namespace ml_asl
{
static class Settings
{
public enum ModSetting
{
Enabled = 0
}
public static bool Enabled { get; private set; } = true;
static MelonLoader.MelonPreferences_Category ms_category = null;
static List<MelonLoader.MelonPreferences_Entry> ms_entries = null;
static public event Action<bool> EnabledChange;
internal static void Init()
{
ms_category = MelonLoader.MelonPreferences.CreateCategory("ASL", null, true);
ms_entries = new List<MelonLoader.MelonPreferences_Entry>()
{
ms_category.CreateEntry(ModSetting.Enabled.ToString(), Enabled)
};
Enabled = (bool)ms_entries[(int)ModSetting.Enabled].BoxedValue;
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("OnToggleUpdate_" + ms_category.Identifier, new Action<string, string>(OnToggleUpdate));
};
ViewManager.Instance.gameMenuView.Listener.FinishLoad += (_) =>
{
ViewManager.Instance.gameMenuView.View.ExecuteScript(ResourcesHandler.GetEmbeddedResource("mods_extension.js"));
ViewManager.Instance.gameMenuView.View.ExecuteScript(ResourcesHandler.GetEmbeddedResource("mod_menu.js"));
foreach(var l_entry in ms_entries)
ViewManager.Instance.gameMenuView.View.TriggerEvent("updateModSetting", ms_category.Identifier, l_entry.DisplayName, l_entry.GetValueAsString());
};
}
static void OnToggleUpdate(string p_name, string p_value)
{
if(Enum.TryParse(p_name, out ModSetting l_setting))
{
switch(l_setting)
{
case ModSetting.Enabled:
{
Enabled = bool.Parse(p_value);
EnabledChange?.Invoke(Enabled);
}
break;
}
ms_entries[(int)l_setting].BoxedValue = bool.Parse(p_value);
}
}
}
}

12
ml_asl/Utils.cs Normal file
View file

@ -0,0 +1,12 @@
using ABI_RC.Core.UI;
using System.Reflection;
namespace ml_asl
{
static class Utils
{
static readonly FieldInfo ms_view = typeof(CohtmlControlledViewWrapper).GetField("_view", BindingFlags.NonPublic | BindingFlags.Instance);
static public void ExecuteScript(this CohtmlControlledViewWrapper p_instance, string p_script) => ((cohtml.Net.View)ms_view.GetValue(p_instance)).ExecuteScript(p_script);
}
}

63
ml_asl/ml_asl.csproj Normal file
View file

@ -0,0 +1,63 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Platforms>x64</Platforms>
<PackageId>AvatarSyncedLook</PackageId>
<Authors>SDraw</Authors>
<Company>None</Company>
<Product>AvatarSyncedLook</Product>
</PropertyGroup>
<ItemGroup>
<None Remove="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="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>
<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>
<ItemGroup>
<EmbeddedResource Include="..\js\mods_extension.js" Link="resources\mods_extension.js" />
<EmbeddedResource Include="resources\mod_menu.js" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="copy /y &quot;$(TargetPath)&quot; &quot;D:\Games\Steam\steamapps\common\ChilloutVR\Mods\&quot;" />
</Target>
</Project>

View file

@ -0,0 +1,22 @@
// Add own menu
{
let l_block = document.createElement('div');
l_block.innerHTML = `
<div class ="settings-subcategory">
<div class ="subcategory-name">Avatar Synced Look</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="true"></div>
</div>
</div>
`;
document.getElementById('settings-interaction').appendChild(l_block);
// Toggles
for (let l_toggle of l_block.querySelectorAll('.inp_toggle'))
modsExtension.addSetting('ASL', l_toggle.id, modsExtension.createToggle(l_toggle, 'OnToggleUpdate_ASL'));
}

View file

@ -14,13 +14,10 @@ namespace ml_dht
bool m_enabled = false;
bool m_headTracking = true;
bool m_blinking = true;
bool m_eyeTracking = true;
float m_smoothing = 0.5f;
bool m_mirrored = false;
bool m_faceOverride = true;
CVRAvatar m_avatarDescriptor = null;
Transform m_camera = null;
LookAtIK m_lookIK = null;
Transform m_headBone = null;
@ -28,8 +25,6 @@ namespace ml_dht
Quaternion m_headRotation;
Vector2 m_gazeDirection;
float m_blinkProgress = 0f;
Vector2 m_mouthShapes;
float m_eyebrowsProgress = 0f;
Quaternion m_bindRotation;
Quaternion m_lastHeadRotation;
@ -37,35 +32,29 @@ namespace ml_dht
// Unity events
void Start()
{
SetEnabled(Settings.Enabled);
SetHeadTracking(Settings.HeadTracking);
SetSmoothing(Settings.Smoothing);
Settings.EnabledChange += this.SetEnabled;
Settings.HeadTrackingChange += this.SetHeadTracking;
Settings.EyeTrackingChange += this.SetEyeTracking;
Settings.BlinkingChange += this.SetBlinking;
Settings.SmoothingChange += this.SetSmoothing;
Settings.MirroredChange += this.SetMirrored;
Settings.FaceOverrideChange += this.SetFaceOverride;
}
void OnDestroy()
{
Settings.EnabledChange -= this.SetEnabled;
Settings.HeadTrackingChange -= this.SetHeadTracking;
Settings.EyeTrackingChange -= this.SetEyeTracking;
Settings.BlinkingChange -= this.SetBlinking;
Settings.SmoothingChange -= this.SetSmoothing;
Settings.MirroredChange -= this.SetMirrored;
Settings.FaceOverrideChange -= this.SetFaceOverride;
}
// Tracking updates
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_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;
m_mouthShapes.Set(p_data.m_mouthOpen, p_data.m_mouthShape);
m_eyebrowsProgress = p_data.m_brows;
}
void OnLookIKPostUpdate()
@ -80,48 +69,9 @@ namespace ml_dht
}
// Game events
internal void OnEyeControllerUpdate(CVREyeController p_component)
{
if(m_enabled)
{
// Gaze
if(m_eyeTracking)
{
Transform l_camera = PlayerSetup.Instance.GetActiveCamera().transform;
p_component.manualViewTarget = true;
p_component.targetViewPosition = l_camera.position + l_camera.rotation * new Vector3((m_gazeDirection.x - 0.5f) * 2f, (m_gazeDirection.y - 0.5f) * 2f, 1f);
}
// Blink
if(m_blinking)
{
p_component.manualBlinking = true;
p_component.blinkProgress = m_blinkProgress;
}
}
}
internal void OnFaceTrackingUpdate(CVRFaceTracking p_component)
{
if(m_enabled && m_faceOverride)
{
if(m_avatarDescriptor != null)
m_avatarDescriptor.useVisemeLipsync = false;
float l_weight = Mathf.Clamp01(Mathf.InverseLerp(0.25f, 1f, Mathf.Abs(m_mouthShapes.y))) * 100f;
p_component.BlendShapeValues[(int)LipShape_v2.Jaw_Open] = m_mouthShapes.x * 100f;
p_component.BlendShapeValues[(int)LipShape_v2.Mouth_Pout] = ((m_mouthShapes.y > 0f) ? l_weight : 0f);
p_component.BlendShapeValues[(int)LipShape_v2.Mouth_Smile_Left] = ((m_mouthShapes.y < 0f) ? l_weight : 0f);
p_component.BlendShapeValues[(int)LipShape_v2.Mouth_Smile_Right] = ((m_mouthShapes.y < 0f) ? l_weight : 0f);
p_component.LipSyncWasUpdated = true;
p_component.UpdateLipShapes();
}
}
internal void OnSetupAvatar()
{
m_camera = PlayerSetup.Instance.GetActiveCamera().transform;
m_avatarDescriptor = PlayerSetup.Instance._avatar.GetComponent<CVRAvatar>();
m_headBone = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Head);
m_lookIK = PlayerSetup.Instance._avatar.GetComponent<LookAtIK>();
@ -142,44 +92,53 @@ namespace ml_dht
m_bindRotation = Quaternion.identity;
}
internal void OnEyeControllerUpdate(CVREyeController p_component)
{
if(m_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.blinkProgress = m_blinkProgress;
}
}
}
// Settings
internal void SetEnabled(bool p_state)
void SetEnabled(bool p_state)
{
if(m_enabled != p_state)
{
m_enabled = p_state;
if(m_enabled && m_headTracking)
m_lastHeadRotation = ((m_headBone != null) ? m_headBone.rotation : m_bindRotation);
TryRestoreHeadRotation();
}
}
internal void SetHeadTracking(bool p_state)
void SetHeadTracking(bool p_state)
{
if(m_headTracking != p_state)
{
m_headTracking = p_state;
if(m_enabled && m_headTracking)
m_lastHeadRotation = ((m_headBone != null) ? m_headBone.rotation : m_bindRotation);
TryRestoreHeadRotation();
}
}
internal void SetEyeTracking(bool p_state)
{
m_eyeTracking = p_state;
}
internal void SetBlinking(bool p_state)
{
m_blinking = p_state;
}
internal void SetSmoothing(float p_value)
void SetSmoothing(float p_value)
{
m_smoothing = 1f - Mathf.Clamp(p_value, 0f, 0.99f);
}
internal void SetMirrored(bool p_state)
// Arbitrary
void TryRestoreHeadRotation()
{
m_mirrored = p_state;
}
internal void SetFaceOverride(bool p_state)
{
m_faceOverride = p_state;
if(m_enabled && m_headTracking)
m_lastHeadRotation = ((m_headBone != null) ? m_headBone.rotation : m_bindRotation);
}
}
}

View file

@ -1,5 +1,6 @@
using ABI.CCK.Components;
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Systems.FaceTracking;
using System.Reflection;
namespace ml_dht
@ -8,10 +9,7 @@ namespace ml_dht
{
static DesktopHeadTracking ms_instance = null;
MemoryMapReader m_mapReader = null;
byte[] m_buffer = null;
TrackingData m_trackingData;
TrackingModule m_trackingModule = null;
HeadTracked m_localTracked = null;
public override void OnInitializeMelon()
@ -21,10 +19,7 @@ namespace ml_dht
Settings.Init();
m_mapReader = new MemoryMapReader();
m_buffer = new byte[1024];
m_mapReader.Open("head/data");
m_trackingModule = new TrackingModule();
// Patches
HarmonyInstance.Patch(
@ -37,33 +32,27 @@ namespace ml_dht
null,
new HarmonyLib.HarmonyMethod(typeof(DesktopHeadTracking).GetMethod(nameof(OnSetupAvatar_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
MelonLoader.MelonCoroutines.Start(WaitForInstances());
}
System.Collections.IEnumerator WaitForInstances()
{
while(MetaPort.Instance == null)
yield return null;
while(PlayerSetup.Instance == null)
yield return null;
m_localTracked = PlayerSetup.Instance.gameObject.AddComponent<HeadTracked>();
FaceTrackingManager.Instance.RegisterModule(m_trackingModule);
// If you think it's a joke to put patch here, go on, try to put it in OnInitializeMelon, you melon :>
HarmonyInstance.Patch(
typeof(CVREyeController).GetMethod("Update", BindingFlags.Instance | BindingFlags.NonPublic),
null,
new HarmonyLib.HarmonyMethod(typeof(DesktopHeadTracking).GetMethod(nameof(OnEyeControllerUpdate_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
HarmonyInstance.Patch(
typeof(CVRFaceTracking).GetMethod("Update", BindingFlags.Instance | BindingFlags.NonPublic),
null,
new HarmonyLib.HarmonyMethod(typeof(DesktopHeadTracking).GetMethod(nameof(OnFaceTrackingUpdate_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
MelonLoader.MelonCoroutines.Start(WaitForPlayer());
}
System.Collections.IEnumerator WaitForPlayer()
{
while(PlayerSetup.Instance == null)
yield return null;
m_localTracked = PlayerSetup.Instance.gameObject.AddComponent<HeadTracked>();
m_localTracked.SetEnabled(Settings.Enabled);
m_localTracked.SetHeadTracking(Settings.HeadTracking);
m_localTracked.SetEyeTracking(Settings.EyeTracking);
m_localTracked.SetBlinking(Settings.Blinking);
m_localTracked.SetMirrored(Settings.Mirrored);
m_localTracked.SetSmoothing(Settings.Smoothing);
m_localTracked.SetFaceOverride(Settings.FaceOverride);
}
public override void OnDeinitializeMelon()
@ -71,19 +60,17 @@ namespace ml_dht
if(ms_instance == this)
ms_instance = null;
m_mapReader?.Close();
m_mapReader = null;
m_buffer = null;
m_trackingModule = null;
m_localTracked = null;
}
public override void OnUpdate()
{
if(Settings.Enabled && m_mapReader.Read(ref m_buffer))
if(Settings.Enabled && (m_trackingModule != null))
{
m_trackingData = TrackingData.ToObject(m_buffer);
m_trackingModule.Update();
if(m_localTracked != null)
m_localTracked.UpdateTrackingData(ref m_trackingData);
m_localTracked.UpdateTrackingData(ref m_trackingModule.GetLatestTrackingData());
}
}
@ -128,19 +115,5 @@ namespace ml_dht
MelonLoader.MelonLogger.Error(e);
}
}
static void OnFaceTrackingUpdate_Postfix(ref CVRFaceTracking __instance) => ms_instance?.OnFaceTrackingUpdate(__instance);
void OnFaceTrackingUpdate(CVRFaceTracking p_component)
{
try
{
if(p_component.isLocal && (m_localTracked != null))
m_localTracked.OnFaceTrackingUpdate(p_component);
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
}
}

View file

@ -1,4 +1,4 @@
[assembly: MelonLoader.MelonInfo(typeof(ml_dht.DesktopHeadTracking), "DesktopHeadTracking", "1.1.2-ex", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonInfo(typeof(ml_dht.DesktopHeadTracking), "DesktopHeadTracking", "1.2.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)]

View file

@ -7,8 +7,8 @@ Refer to `TrackingData.cs` for reference in case of implementing own software.
# Features
* Head rotation
* Eyes gaze direction
* Basic mouth shapes: open, smile and pout
* Blinking
* Basic mouth shapes
# Installation
* Install [latest MelonLoader](https://github.com/LavaGang/MelonLoader)
@ -19,11 +19,13 @@ Refer to `TrackingData.cs` for reference in case of implementing own software.
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:** uses eyes tracking from data; default value - `true`.
* **Use eyes tracking:** enables eyes tracking; default value - `true`.
* **Use face tracking:** enables mouth shapes tracking; default value - `true`.
* **Note:** You need to enable desktop tracking of `Vive Face tracking` in `Settings - Implementation` menu page.
* **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`.
* **Override face tracking:** overrides and activates avatar's `VRC Face Tracking` components. List of used shapes: `Jaw_Open`, `Mouth_Pout`, `Mouth_Smile_Left`, `Mouth_Smile_Right`; default value - `true`.
# Known compatible tracking software
* [VSeeFace](https://www.vseeface.icu) with [Tracking Data Parser mod](https://github.com/SDraw/ml_mods_vsf)

View file

@ -4,9 +4,9 @@ using System.Reflection;
namespace ml_dht
{
static class Scripts
static class ResourcesHandler
{
public static string GetEmbeddedScript(string p_name)
public static string GetEmbeddedResource(string p_name)
{
string l_result = "";
Assembly l_assembly = Assembly.GetExecutingAssembly();

View file

@ -11,19 +11,19 @@ namespace ml_dht
Enabled = 0,
HeadTracking,
EyeTracking,
FaceTracking,
Blinking,
Mirrored,
Smoothing,
FaceOverride
}
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;
public static bool FaceOverride { get; private set; } = true;
static MelonLoader.MelonPreferences_Category ms_category = null;
static List<MelonLoader.MelonPreferences_Entry> ms_entries = null;
@ -31,10 +31,11 @@ namespace ml_dht
static public event Action<bool> EnabledChange;
static public event Action<bool> HeadTrackingChange;
static public event Action<bool> EyeTrackingChange;
static public event Action<bool> FaceTrackingChange;
static public event Action<bool> BlinkingChange;
static public event Action<bool> MirroredChange;
static public event Action<float> SmoothingChange;
static public event Action<bool> FaceOverrideChange;
internal static void Init()
{
@ -45,13 +46,19 @@ namespace ml_dht
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)),
ms_category.CreateEntry(ModSetting.FaceOverride.ToString(), FaceOverride)
};
Load();
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());
}
@ -67,28 +74,18 @@ namespace ml_dht
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.View.BindCall("OnToggleUpdate_" + ms_category.Identifier, new Action<string, string>(OnToggleUpdate));
ViewManager.Instance.gameMenuView.View.BindCall("OnSliderUpdate_" + ms_category.Identifier, new Action<string, string>(OnSliderUpdate));
};
ViewManager.Instance.gameMenuView.Listener.FinishLoad += (_) =>
{
ViewManager.Instance.gameMenuView.View.ExecuteScript(Scripts.GetEmbeddedScript("menu.js"));
ViewManager.Instance.gameMenuView.View.ExecuteScript(ResourcesHandler.GetEmbeddedResource("mods_extension.js"));
ViewManager.Instance.gameMenuView.View.ExecuteScript(ResourcesHandler.GetEmbeddedResource("mod_menu.js"));
foreach(var l_entry in ms_entries)
ViewManager.Instance.gameMenuView.View.TriggerEvent("updateModSettingDHT", l_entry.DisplayName, l_entry.GetValueAsString());
ViewManager.Instance.gameMenuView.View.TriggerEvent("updateModSetting", ms_category.Identifier, l_entry.DisplayName, l_entry.GetValueAsString());
};
}
static void Load()
{
Enabled = (bool)ms_entries[(int)ModSetting.Enabled].BoxedValue;
HeadTracking = (bool)ms_entries[(int)ModSetting.HeadTracking].BoxedValue;
EyeTracking = (bool)ms_entries[(int)ModSetting.EyeTracking].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;
FaceOverride = (bool)ms_entries[(int)ModSetting.FaceOverride].BoxedValue;
}
static void OnSliderUpdate(string p_name, string p_value)
{
if(Enum.TryParse(p_name, out ModSetting l_setting))
@ -134,6 +131,13 @@ namespace ml_dht
}
break;
case ModSetting.FaceTracking:
{
FaceTracking = bool.Parse(p_value);
FaceTrackingChange?.Invoke(FaceTracking);
}
break;
case ModSetting.Blinking:
{
Blinking = bool.Parse(p_value);
@ -147,13 +151,6 @@ namespace ml_dht
MirroredChange?.Invoke(Mirrored);
}
break;
case ModSetting.FaceOverride:
{
FaceOverride = bool.Parse(p_value);
FaceOverrideChange?.Invoke(FaceOverride);
}
break;
}
ms_entries[(int)l_setting].BoxedValue = bool.Parse(p_value);

70
ml_dht/TrackingModule.cs Normal file
View file

@ -0,0 +1,70 @@
using ABI_RC.Systems.FaceTracking;
using System;
using UnityEngine;
using ViveSR.anipal.Lip;
namespace ml_dht
{
class TrackingModule : ITrackingModule
{
bool m_registered = false;
bool m_activeAsModule = false;
MemoryMapReader m_mapReader = null;
byte[] m_buffer = null;
TrackingData m_trackingData;
LipData_v2 m_lipData;
public TrackingModule()
{
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];
m_buffer = new byte[1024];
m_mapReader = new MemoryMapReader();
m_mapReader.Open("head/data");
}
~TrackingModule()
{
m_mapReader.Close();
m_mapReader = null;
}
public (bool, bool) Initialize(bool useEye, bool useLip)
{
m_registered = true;
m_activeAsModule = true;
return (false, true);
}
public void Shutdown()
{
m_activeAsModule = false;
}
public bool IsEyeDataAvailable() => false;
public bool IsLipDataAvailable() => true;
internal void Update()
{
if(m_mapReader.Read(ref m_buffer))
{
m_trackingData = TrackingData.ToObject(m_buffer);
float l_weight = Mathf.Clamp01(Mathf.InverseLerp(0.25f, 1f, Mathf.Abs(m_trackingData.m_mouthShape)));
m_lipData.prediction_data.blend_shape_weight[(int)LipShape_v2.Jaw_Open] = m_trackingData.m_mouthOpen;
m_lipData.prediction_data.blend_shape_weight[(int)LipShape_v2.Mouth_Pout] = ((m_trackingData.m_mouthShape > 0f) ? l_weight : 0f);
m_lipData.prediction_data.blend_shape_weight[(int)LipShape_v2.Mouth_Smile_Left] = ((m_trackingData.m_mouthShape < 0f) ? l_weight : 0f);
m_lipData.prediction_data.blend_shape_weight[(int)LipShape_v2.Mouth_Smile_Right] = ((m_trackingData.m_mouthShape < 0f) ? l_weight : 0f);
if(m_registered && m_activeAsModule && Settings.FaceTracking)
FaceTrackingManager.Instance.SubmitNewFacialData(m_lipData);
}
}
internal ref TrackingData GetLatestTrackingData() => ref m_trackingData;
}
}

View file

@ -6,13 +6,13 @@ namespace ml_dht
{
static class Utils
{
static FieldInfo ms_cohtmlView = typeof(CohtmlControlledViewDisposable).GetField("_view", BindingFlags.NonPublic | BindingFlags.Instance);
static readonly FieldInfo ms_view = typeof(CohtmlControlledViewWrapper).GetField("_view", BindingFlags.NonPublic | BindingFlags.Instance);
static public void ExecuteScript(this CohtmlControlledViewWrapper p_instance, string p_script) => ((cohtml.Net.View)ms_view.GetValue(p_instance)).ExecuteScript(p_script);
public static Matrix4x4 GetMatrix(this Transform p_transform, bool p_pos = true, bool p_rot = true, bool p_scl = false)
{
return Matrix4x4.TRS(p_pos ? p_transform.position : Vector3.zero, p_rot ? p_transform.rotation : Quaternion.identity, p_scl ? p_transform.localScale : Vector3.one);
}
public static void ExecuteScript(this CohtmlControlledViewDisposable p_viewDisposable, string p_script) => ((cohtml.Net.View)ms_cohtmlView.GetValue(p_viewDisposable))?.ExecuteScript(p_script);
}
}

View file

@ -6,7 +6,7 @@
<Authors>SDraw</Authors>
<Company>None</Company>
<Product>DesktopHeadTracking</Product>
<Version>1.1.2</Version>
<Version>1.2.0</Version>
<Platforms>x64</Platforms>
</PropertyGroup>
@ -18,49 +18,62 @@
<ItemGroup>
<None Remove="DesktopHeadTracking.json" />
<None Remove="resources\menu.js" />
<None Remove="resources\mod_menu.js" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="resources\menu.js" />
<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\0Harmony.dll</HintPath>
<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\MelonLoader.dll</HintPath>
<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>

View file

@ -1,245 +0,0 @@
// 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 + "&apos;" + inches + '&apos;&apos;';
}
}
}
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">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 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>
<div class ="row-wrapper">
<div class ="option-caption">Override face tracking: </div>
<div class ="option-input">
<div id="FaceOverride" class ="inp_toggle no-scroll" data-current="true"></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');
}
}

View 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'));
}

View file

@ -13,8 +13,8 @@ namespace ml_lme
"leapmotion_hands.asset"
};
static Dictionary<string, AssetBundle> ms_loadedAssets = new Dictionary<string, AssetBundle>();
static Dictionary<string, GameObject> ms_loadedObjects = new Dictionary<string, GameObject>();
static readonly Dictionary<string, AssetBundle> ms_loadedAssets = new Dictionary<string, AssetBundle>();
static readonly Dictionary<string, GameObject> ms_loadedObjects = new Dictionary<string, GameObject>();
public static void Load()
{

View file

@ -20,7 +20,7 @@ namespace ml_lme
foreach(string l_library in ms_libraries)
{
Stream l_libraryStream = l_assembly.GetManifestResourceStream(l_assemblyName + "." + l_library);
Stream l_libraryStream = l_assembly.GetManifestResourceStream(l_assemblyName + ".resources." + l_library);
if(!File.Exists(l_library))
{

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,4 @@
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Systems.InputManagement;
using System.Collections;
using UnityEngine;
@ -12,7 +11,7 @@ namespace ml_lme
public static LeapManager Instance { get; private set; } = null;
Leap.Controller m_leapController = null;
GestureMatcher.LeapData m_leapData = null;
LeapParser.LeapData m_leapData = null;
LeapTracking m_leapTracking = null;
LeapTracked m_leapTracked = null;
@ -24,7 +23,7 @@ namespace ml_lme
Instance = this;
m_leapController = new Leap.Controller();
m_leapData = new GestureMatcher.LeapData();
m_leapData = new LeapParser.LeapData();
DontDestroyOnLoad(this);
@ -60,6 +59,23 @@ namespace ml_lme
m_leapController.Dispose();
m_leapController = null;
if(m_leapTracking != null)
Object.Destroy(m_leapTracking);
m_leapTracking = null;
if(m_leapTracked != null)
Object.Destroy(m_leapTracked);
m_leapTracked = null;
if(m_leapInput != null)
{
if(CVRInputManager.Instance != null)
CVRInputManager.Instance.DestroyInputModule(m_leapInput);
else
m_leapInput.ModuleDestroyed();
}
m_leapInput = null;
Settings.EnabledChange -= this.OnEnableChange;
Settings.TrackingModeChange -= this.OnTrackingModeChange;
}
@ -90,12 +106,12 @@ namespace ml_lme
if(m_leapController.IsConnected)
{
Leap.Frame l_frame = m_leapController.Frame();
GestureMatcher.GetFrameData(l_frame, m_leapData);
LeapParser.ParseFrame(l_frame, m_leapData);
}
}
}
public GestureMatcher.LeapData GetLatestData() => m_leapData;
public LeapParser.LeapData GetLatestData() => m_leapData;
// Device events
void OnLeapDeviceInitialized(object p_sender, Leap.DeviceEventArgs p_args)
@ -169,12 +185,6 @@ namespace ml_lme
m_leapTracked.OnAvatarSetup();
}
internal void OnCalibrate()
{
if(m_leapTracked != null)
m_leapTracked.OnCalibrate();
}
internal void OnRayScale(float p_scale)
{
m_leapInput?.OnRayScale(p_scale);

View file

@ -2,15 +2,24 @@
namespace ml_lme
{
static class GestureMatcher
static class LeapParser
{
readonly static Vector2[] ms_fingerLimits =
readonly static Vector2[] ms_bendLimits =
{
new Vector2(-50f, 0f),
new Vector2(-20f, 30f),
new Vector2(-15f, 15f),
new Vector2(-10f, 20f),
new Vector2(-10f, 25f)
new Vector2(0f, 90f),
new Vector2(0f, 180f),
new Vector2(0f, 180f),
new Vector2(0f, 180f),
new Vector2(0f, 180f)
};
readonly static Vector2[] ms_spreadLimits =
{
new Vector2(-25f, 25f), // Unity's default limits
new Vector2(-20f, 20f),
new Vector2(-7.5f, 7.5f),
new Vector2(-7.5f, 7.5f),
new Vector2(-20f, 20f)
};
public class HandData
@ -68,7 +77,7 @@ namespace ml_lme
}
}
public static void GetFrameData(Leap.Frame p_frame, LeapData p_data)
public static void ParseFrame(Leap.Frame p_frame, LeapData p_data)
{
p_data.Reset();
@ -93,30 +102,31 @@ namespace ml_lme
// Bends
foreach(Leap.Finger l_finger in p_hand.Fingers)
{
Quaternion l_prevSegment = Quaternion.identity;
Quaternion l_parentRot = Quaternion.identity;
float l_angle = 0f;
foreach(Leap.Bone l_bone in l_finger.bones)
{
p_data.m_fingerPosition[(int)l_finger.Type * 4 + (int)l_bone.Type] = l_bone.PrevJoint;
p_data.m_fingerRotation[(int)l_finger.Type * 4 + (int)l_bone.Type] = l_bone.Rotation;
int l_index = (int)l_finger.Type * 4 + (int)l_bone.Type;
p_data.m_fingerPosition[l_index] = l_bone.PrevJoint;
p_data.m_fingerRotation[l_index] = l_bone.Rotation;
if(l_bone.Type == Leap.Bone.BoneType.TYPE_METACARPAL)
{
l_prevSegment = l_bone.Rotation;
l_parentRot = l_bone.Rotation;
continue;
}
Quaternion l_diff = Quaternion.Inverse(l_prevSegment) * l_bone.Rotation;
l_prevSegment = l_bone.Rotation;
float l_angleDiff = l_diff.eulerAngles.x;
Quaternion l_localRot = Quaternion.Inverse(l_parentRot) * l_bone.Rotation;
float l_angleDiff = l_localRot.eulerAngles.x;
if(l_angleDiff > 180f)
l_angleDiff -= 360f;
l_angle += l_angleDiff;
l_parentRot = l_bone.Rotation;
}
p_data.m_bends[(int)l_finger.Type] = Utils.InverseLerpUnclamped(0f, (l_finger.Type == Leap.Finger.FingerType.TYPE_THUMB) ? 90f : 180f, l_angle);
p_data.m_bends[(int)l_finger.Type] = Utils.InverseLerpUnclamped(ms_bendLimits[(int)l_finger.Type].x, ms_bendLimits[(int)l_finger.Type].y, l_angle);
}
// Spreads
@ -126,24 +136,17 @@ namespace ml_lme
Leap.Bone l_child = l_finger.Bone(Leap.Bone.BoneType.TYPE_PROXIMAL);
Quaternion l_diff = Quaternion.Inverse(l_parent.Rotation) * l_child.Rotation;
// Spread - local Y rotation, but thumb is obnoxious
float l_angle = 360f - l_diff.eulerAngles.y;
// Spread - local Y rotation
float l_angle = l_diff.eulerAngles.y;
if(l_angle > 180f)
l_angle -= 360f;
// Pain
if(p_hand.IsRight)
l_angle *= -1f;
if(l_finger.Type != Leap.Finger.FingerType.TYPE_THUMB)
{
if(l_angle < 0f)
p_data.m_spreads[(int)l_finger.Type] = 0.5f * Utils.InverseLerpUnclamped(ms_fingerLimits[(int)l_finger.Type].x, 0f, l_angle);
else
p_data.m_spreads[(int)l_finger.Type] = 0.5f + 0.5f * Utils.InverseLerpUnclamped(0f, ms_fingerLimits[(int)l_finger.Type].y, l_angle);
}
else
p_data.m_spreads[(int)l_finger.Type] = Utils.InverseLerpUnclamped(ms_fingerLimits[(int)l_finger.Type].x, ms_fingerLimits[(int)l_finger.Type].y, l_angle);
p_data.m_spreads[(int)l_finger.Type] = Utils.InverseLerpUnclamped(ms_spreadLimits[(int)l_finger.Type].x, ms_spreadLimits[(int)l_finger.Type].y, l_angle) * 2f - 1f;
if((l_finger.Type != Leap.Finger.FingerType.TYPE_THUMB) && (p_data.m_bends[(int)l_finger.Type] >= 0.8f))
p_data.m_spreads[(int)l_finger.Type] = Mathf.Lerp(p_data.m_spreads[(int)l_finger.Type], 0f, (p_data.m_bends[(int)l_finger.Type] - 0.8f) * 5f);
}
p_data.m_grabStrength = Mathf.Clamp01((p_data.m_bends[1] + p_data.m_bends[2] + p_data.m_bends[3] + p_data.m_bends[4]) * 0.25f);

View file

@ -1,429 +1,412 @@
using ABI_RC.Core.Player;
using ABI_RC.Systems.IK;
using RootMotion.FinalIK;
using System.Reflection;
using UnityEngine;
namespace ml_lme
{
[DisallowMultipleComponent]
class LeapTracked : MonoBehaviour
{
static readonly float[] ms_tposeMuscles = typeof(ABI_RC.Systems.IK.SubSystems.BodySystem).GetField("TPoseMuscles", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null) as float[];
static readonly Quaternion ms_offsetLeft = Quaternion.Euler(0f, 0f, 270f);
static readonly Quaternion ms_offsetRight = Quaternion.Euler(0f, 0f, 90f);
static readonly Quaternion ms_offsetLeftDesktop = Quaternion.Euler(0f, 90f, 0f);
static readonly Quaternion ms_offsetRightDesktop = Quaternion.Euler(0f, 270f, 0f);
VRIK m_vrIK = null;
Vector4 m_armsWeights = Vector2.zero;
bool m_inVR = false;
Transform m_hips = null;
Transform m_origLeftHand = null;
Transform m_origRightHand = null;
Transform m_origLeftElbow = null;
Transform m_origRightElbow = null;
bool m_enabled = true;
bool m_fingersOnly = false;
bool m_trackElbows = true;
ArmIK m_leftArmIK = null;
ArmIK m_rightArmIK = null;
HumanPoseHandler m_poseHandler = null;
HumanPose m_pose;
Transform m_leftHandTarget = null;
Transform m_rightHandTarget = null;
bool m_leftTargetActive = false;
bool m_rightTargetActive = false;
// Unity events
void Start()
{
m_inVR = Utils.IsInVR();
m_leftHandTarget = new GameObject("RotationTarget").transform;
m_leftHandTarget.parent = LeapTracking.Instance.GetLeftHand();
m_leftHandTarget.localPosition = Vector3.zero;
m_leftHandTarget.localRotation = Quaternion.identity;
m_rightHandTarget = new GameObject("RotationTarget").transform;
m_rightHandTarget.parent = LeapTracking.Instance.GetRightHand();
m_rightHandTarget.localPosition = Vector3.zero;
m_rightHandTarget.localRotation = Quaternion.identity;
Settings.EnabledChange += this.OnEnabledChange;
Settings.FingersOnlyChange += this.OnFingersOnlyChange;
Settings.TrackElbowsChange += this.OnTrackElbowsChange;
OnEnabledChange(Settings.Enabled);
OnFingersOnlyChange(Settings.FingersOnly);
OnTrackElbowsChange(Settings.TrackElbows);
}
void OnDestroy()
{
Settings.EnabledChange -= this.OnEnabledChange;
Settings.FingersOnlyChange -= this.OnFingersOnlyChange;
Settings.TrackElbowsChange -= this.OnTrackElbowsChange;
}
void Update()
{
if(m_enabled)
{
GestureMatcher.LeapData l_data = LeapManager.Instance.GetLatestData();
if((m_leftArmIK != null) && (m_rightArmIK != null))
{
m_leftArmIK.solver.IKPositionWeight = Mathf.Lerp(m_leftArmIK.solver.IKPositionWeight, (l_data.m_leftHand.m_present && !m_fingersOnly) ? 1f : 0f, 0.25f);
m_leftArmIK.solver.IKRotationWeight = Mathf.Lerp(m_leftArmIK.solver.IKRotationWeight, (l_data.m_leftHand.m_present && !m_fingersOnly) ? 1f : 0f, 0.25f);
if(m_trackElbows)
m_leftArmIK.solver.arm.bendGoalWeight = Mathf.Lerp(m_leftArmIK.solver.arm.bendGoalWeight, (l_data.m_leftHand.m_present && !m_fingersOnly) ? 1f : 0f, 0.25f);
m_rightArmIK.solver.IKPositionWeight = Mathf.Lerp(m_rightArmIK.solver.IKPositionWeight, (l_data.m_rightHand.m_present && !m_fingersOnly) ? 1f : 0f, 0.25f);
m_rightArmIK.solver.IKRotationWeight = Mathf.Lerp(m_rightArmIK.solver.IKRotationWeight, (l_data.m_rightHand.m_present && !m_fingersOnly) ? 1f : 0f, 0.25f);
if(m_trackElbows)
m_rightArmIK.solver.arm.bendGoalWeight = Mathf.Lerp(m_rightArmIK.solver.arm.bendGoalWeight, (l_data.m_rightHand.m_present && !m_fingersOnly) ? 1f : 0f, 0.25f);
}
if((m_vrIK != null) && !m_fingersOnly)
{
if(l_data.m_leftHand.m_present && !m_leftTargetActive)
{
m_vrIK.solver.leftArm.target = m_leftHandTarget;
m_vrIK.solver.leftArm.bendGoal = LeapTracking.Instance.GetLeftElbow();
m_vrIK.solver.leftArm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
m_leftTargetActive = true;
}
if(!l_data.m_leftHand.m_present && m_leftTargetActive)
{
m_vrIK.solver.leftArm.target = m_origLeftHand;
m_vrIK.solver.leftArm.bendGoal = m_origLeftElbow;
m_vrIK.solver.leftArm.bendGoalWeight = ((m_origLeftElbow != null) ? 1f : 0f);
m_leftTargetActive = false;
}
if(l_data.m_rightHand.m_present && !m_rightTargetActive)
{
m_vrIK.solver.rightArm.target = m_rightHandTarget;
m_vrIK.solver.rightArm.bendGoal = LeapTracking.Instance.GetRightElbow();
m_vrIK.solver.rightArm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
m_rightTargetActive = true;
}
if(!l_data.m_rightHand.m_present && m_rightTargetActive)
{
m_vrIK.solver.rightArm.target = m_origRightHand;
m_vrIK.solver.rightArm.bendGoal = m_origRightElbow;
m_vrIK.solver.rightArm.bendGoalWeight = ((m_origRightElbow != null) ? 1f : 0f);
m_rightTargetActive = false;
}
}
}
}
void LateUpdate()
{
if(m_enabled && !m_inVR && (m_poseHandler != null))
{
GestureMatcher.LeapData l_data = LeapManager.Instance.GetLatestData();
Vector3 l_hipsLocalPos = m_hips.localPosition;
Quaternion l_hipsLocalRot = m_hips.localRotation;
m_poseHandler.GetHumanPose(ref m_pose);
UpdateFingers(l_data);
m_poseHandler.SetHumanPose(ref m_pose);
m_hips.localPosition = l_hipsLocalPos;
m_hips.localRotation = l_hipsLocalRot;
}
}
// Tracking update
void UpdateFingers(GestureMatcher.LeapData p_data)
{
if(p_data.m_leftHand.m_present)
{
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftThumb1Stretched, Mathf.LerpUnclamped(0.85f, -0.85f, p_data.m_leftHand.m_bends[0]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftThumb2Stretched, Mathf.LerpUnclamped(0.85f, -0.85f, p_data.m_leftHand.m_bends[0]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftThumb3Stretched, Mathf.LerpUnclamped(0.85f, -0.85f, p_data.m_leftHand.m_bends[0]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftThumbSpread, Mathf.LerpUnclamped(-1.5f, 1.0f, p_data.m_leftHand.m_spreads[0])); // Ok
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftIndex1Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[1]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftIndex2Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[1]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftIndex3Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[1]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftIndexSpread, Mathf.LerpUnclamped(1f, -1f, p_data.m_leftHand.m_spreads[1])); // Ok
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftMiddle1Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftMiddle2Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftMiddle3Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftMiddleSpread, Mathf.LerpUnclamped(2f, -2f, p_data.m_leftHand.m_spreads[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftRing1Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftRing2Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftRing3Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftRingSpread, Mathf.LerpUnclamped(-2f, 2f, p_data.m_leftHand.m_spreads[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftLittle1Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[4]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftLittle2Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[4]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftLittle3Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_leftHand.m_bends[4]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftLittleSpread, Mathf.LerpUnclamped(-0.5f, 1f, p_data.m_leftHand.m_spreads[4]));
}
if(p_data.m_rightHand.m_present)
{
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightThumb1Stretched, Mathf.LerpUnclamped(0.85f, -0.85f, p_data.m_rightHand.m_bends[0]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightThumb2Stretched, Mathf.LerpUnclamped(0.85f, -0.85f, p_data.m_rightHand.m_bends[0]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightThumb3Stretched, Mathf.LerpUnclamped(0.85f, -0.85f, p_data.m_rightHand.m_bends[0]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightThumbSpread, Mathf.LerpUnclamped(-1.5f, 1.0f, p_data.m_rightHand.m_spreads[0])); // Ok
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightIndex1Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[1]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightIndex2Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[1]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightIndex3Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[1]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightIndexSpread, Mathf.LerpUnclamped(1f, -1f, p_data.m_rightHand.m_spreads[1])); // Ok
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightMiddle1Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightMiddle2Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightMiddle3Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightMiddleSpread, Mathf.LerpUnclamped(2f, -2f, p_data.m_rightHand.m_spreads[2]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightRing1Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightRing2Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightRing3Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightRingSpread, Mathf.LerpUnclamped(-2f, 2f, p_data.m_rightHand.m_spreads[3]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightLittle1Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[4]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightLittle2Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[4]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightLittle3Stretched, Mathf.LerpUnclamped(0.7f, -1f, p_data.m_rightHand.m_bends[4]));
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightLittleSpread, Mathf.LerpUnclamped(-0.5f, 1f, p_data.m_rightHand.m_spreads[4]));
}
}
// Game events
internal void OnAvatarClear()
{
m_vrIK = null;
m_origLeftHand = null;
m_origRightHand = null;
m_origLeftElbow = null;
m_origRightElbow = null;
m_hips = null;
m_armsWeights = Vector2.zero;
m_leftArmIK = null;
m_rightArmIK = null;
m_leftTargetActive = false;
m_rightTargetActive = false;
if(!m_inVR)
m_poseHandler?.Dispose();
m_poseHandler = null;
m_leftHandTarget.localPosition = Vector3.zero;
m_leftHandTarget.localRotation = Quaternion.identity;
m_rightHandTarget.localPosition = Vector3.zero;
m_rightHandTarget.localRotation = Quaternion.identity;
}
internal void OnAvatarSetup()
{
m_inVR = Utils.IsInVR();
m_vrIK = PlayerSetup.Instance._animator.GetComponent<VRIK>();
if(PlayerSetup.Instance._animator.isHuman)
{
Vector3 l_hipsPos = Vector3.zero;
m_hips = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Hips);
if(m_hips != null)
l_hipsPos = m_hips.localPosition;
if(!m_inVR)
{
// Force desktop avatar into T-Pose
m_poseHandler = new HumanPoseHandler(PlayerSetup.Instance._animator.avatar, PlayerSetup.Instance._avatar.transform);
m_poseHandler.GetHumanPose(ref m_pose);
HumanPose l_tPose = new HumanPose
{
bodyPosition = m_pose.bodyPosition,
bodyRotation = m_pose.bodyRotation,
muscles = new float[m_pose.muscles.Length]
};
for(int i = 0; i < l_tPose.muscles.Length; i++)
l_tPose.muscles[i] = ms_tposeMuscles[i];
m_poseHandler.SetHumanPose(ref l_tPose);
}
Transform l_hand = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftHand);
if(l_hand != null)
m_leftHandTarget.localRotation = (m_inVR ? ms_offsetLeft : ms_offsetLeftDesktop) * (PlayerSetup.Instance._avatar.transform.GetMatrix().inverse * l_hand.GetMatrix()).rotation;
l_hand = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightHand);
if(l_hand != null)
m_rightHandTarget.localRotation = (m_inVR ? ms_offsetRight : ms_offsetRightDesktop) * (PlayerSetup.Instance._avatar.transform.GetMatrix().inverse * l_hand.GetMatrix()).rotation;
if(m_vrIK == null)
{
Transform l_chest = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.UpperChest);
if(l_chest == null)
l_chest = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Chest);
if(l_chest == null)
l_chest = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Spine);
m_leftArmIK = PlayerSetup.Instance._avatar.AddComponent<ArmIK>();
m_leftArmIK.solver.isLeft = true;
m_leftArmIK.solver.SetChain(
l_chest,
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftShoulder),
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftUpperArm),
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftLowerArm),
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftHand),
PlayerSetup.Instance._animator.transform
);
m_leftArmIK.solver.arm.target = m_leftHandTarget;
m_leftArmIK.solver.arm.bendGoal = LeapTracking.Instance.GetLeftElbow();
m_leftArmIK.solver.arm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
m_leftArmIK.enabled = (m_enabled && !m_fingersOnly);
m_rightArmIK = PlayerSetup.Instance._avatar.AddComponent<ArmIK>();
m_rightArmIK.solver.isLeft = false;
m_rightArmIK.solver.SetChain(
l_chest,
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightShoulder),
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightUpperArm),
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightLowerArm),
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightHand),
PlayerSetup.Instance._animator.transform
);
m_rightArmIK.solver.arm.target = m_rightHandTarget;
m_rightArmIK.solver.arm.bendGoal = LeapTracking.Instance.GetRightElbow();
m_rightArmIK.solver.arm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
m_rightArmIK.enabled = (m_enabled && !m_fingersOnly);
m_poseHandler?.SetHumanPose(ref m_pose);
}
else
{
m_origLeftHand = m_vrIK.solver.leftArm.target;
m_origRightHand = m_vrIK.solver.rightArm.target;
m_origLeftElbow = m_vrIK.solver.leftArm.bendGoal;
m_origRightElbow = m_vrIK.solver.rightArm.bendGoal;
m_vrIK.solver.OnPreUpdate += this.OnIKPreUpdate;
m_vrIK.solver.OnPostUpdate += this.OnIKPostUpdate;
}
if(m_hips != null)
m_hips.localPosition = l_hipsPos;
}
}
internal void OnCalibrate()
{
if(m_vrIK != null)
{
m_origLeftHand = m_vrIK.solver.leftArm.target;
m_origRightHand = m_vrIK.solver.rightArm.target;
m_origLeftElbow = m_vrIK.solver.leftArm.bendGoal;
m_origRightElbow = m_vrIK.solver.rightArm.bendGoal;
}
}
// IK updates
void OnIKPreUpdate()
{
m_armsWeights.Set(
m_vrIK.solver.leftArm.positionWeight,
m_vrIK.solver.leftArm.rotationWeight,
m_vrIK.solver.rightArm.positionWeight,
m_vrIK.solver.rightArm.rotationWeight
);
if(m_leftTargetActive && (Mathf.Approximately(m_armsWeights.x, 0f) || Mathf.Approximately(m_armsWeights.y, 0f)))
{
m_vrIK.solver.leftArm.positionWeight = 1f;
m_vrIK.solver.leftArm.rotationWeight = 1f;
}
if(m_rightTargetActive && (Mathf.Approximately(m_armsWeights.z, 0f) || Mathf.Approximately(m_armsWeights.w, 0f)))
{
m_vrIK.solver.rightArm.positionWeight = 1f;
m_vrIK.solver.rightArm.rotationWeight = 1f;
}
}
void OnIKPostUpdate()
{
m_vrIK.solver.leftArm.positionWeight = m_armsWeights.x;
m_vrIK.solver.leftArm.rotationWeight = m_armsWeights.y;
m_vrIK.solver.rightArm.positionWeight = m_armsWeights.z;
m_vrIK.solver.rightArm.rotationWeight = m_armsWeights.w;
}
// Settings
void OnEnabledChange(bool p_state)
{
m_enabled = p_state;
RefreshArmIK();
if(!m_enabled || m_fingersOnly)
RestoreVRIK();
}
void OnFingersOnlyChange(bool p_state)
{
m_fingersOnly = p_state;
RefreshArmIK();
if(!m_enabled || m_fingersOnly)
RestoreVRIK();
}
void OnTrackElbowsChange(bool p_state)
{
m_trackElbows = p_state;
if((m_leftArmIK != null) && (m_rightArmIK != null))
{
m_leftArmIK.solver.arm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
m_rightArmIK.solver.arm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
}
RestoreVRIK();
}
// Arbitrary
void RestoreVRIK()
{
if(m_vrIK != null)
{
if(m_leftTargetActive)
{
m_vrIK.solver.leftArm.target = m_origLeftHand;
m_vrIK.solver.leftArm.bendGoal = m_origLeftElbow;
m_vrIK.solver.leftArm.bendGoalWeight = ((m_origLeftElbow != null) ? 1f : 0f);
m_leftTargetActive = false;
}
if(m_rightTargetActive)
{
m_vrIK.solver.rightArm.target = m_origRightHand;
m_vrIK.solver.rightArm.bendGoal = m_origRightElbow;
m_vrIK.solver.rightArm.bendGoalWeight = ((m_origRightElbow != null) ? 1f : 0f);
m_rightTargetActive = false;
}
}
}
void RefreshArmIK()
{
if((m_leftArmIK != null) && (m_rightArmIK != null))
{
m_leftArmIK.enabled = (m_enabled && !m_fingersOnly);
m_rightArmIK.enabled = (m_enabled && !m_fingersOnly);
}
}
static void UpdatePoseMuscle(ref HumanPose p_pose, int p_index, float p_value)
{
if(p_pose.muscles.Length > p_index)
p_pose.muscles[p_index] = p_value;
}
}
}
using ABI_RC.Core.Player;
using ABI_RC.Systems.IK;
using RootMotion.FinalIK;
using System.Reflection;
using UnityEngine;
namespace ml_lme
{
[DisallowMultipleComponent]
class LeapTracked : MonoBehaviour
{
struct IKInfo
{
public Vector4 m_armsWeights;
public Vector2 m_elbowsWeights;
public Transform m_leftHandTarget;
public Transform m_rightHandTarget;
public Transform m_leftElbowTarget;
public Transform m_rightElbowTarget;
}
static readonly float[] ms_tposeMuscles = typeof(ABI_RC.Systems.IK.SubSystems.BodySystem).GetField("TPoseMuscles", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null) as float[];
static readonly Quaternion ms_offsetLeft = Quaternion.Euler(0f, 90f, 0f);
static readonly Quaternion ms_offsetRight = Quaternion.Euler(0f, 270f, 0f);
bool m_inVR = false;
VRIK m_vrIK = null;
Transform m_hips = null;
bool m_enabled = true;
bool m_fingersOnly = false;
bool m_trackElbows = true;
IKInfo m_vrIKInfo;
ArmIK m_leftArmIK = null;
ArmIK m_rightArmIK = null;
HumanPoseHandler m_poseHandler = null;
HumanPose m_pose;
Transform m_leftHandTarget = null;
Transform m_rightHandTarget = null;
bool m_leftTargetActive = false; // VRIK only
bool m_rightTargetActive = false; // VRIK only
// Unity events
void Start()
{
m_inVR = Utils.IsInVR();
m_leftHandTarget = new GameObject("RotationTarget").transform;
m_leftHandTarget.parent = LeapTracking.Instance.GetLeftHand();
m_leftHandTarget.localPosition = Vector3.zero;
m_leftHandTarget.localRotation = Quaternion.identity;
m_rightHandTarget = new GameObject("RotationTarget").transform;
m_rightHandTarget.parent = LeapTracking.Instance.GetRightHand();
m_rightHandTarget.localPosition = Vector3.zero;
m_rightHandTarget.localRotation = Quaternion.identity;
Settings.EnabledChange += this.OnEnabledChange;
Settings.FingersOnlyChange += this.OnFingersOnlyChange;
Settings.TrackElbowsChange += this.OnTrackElbowsChange;
OnEnabledChange(Settings.Enabled);
OnFingersOnlyChange(Settings.FingersOnly);
OnTrackElbowsChange(Settings.TrackElbows);
}
void OnDestroy()
{
if(m_leftArmIK != null)
Destroy(m_leftArmIK);
m_leftArmIK = null;
if(m_rightArmIK != null)
Destroy(m_rightArmIK);
m_rightArmIK = null;
if(m_leftHandTarget != null)
Destroy(m_leftHandTarget);
m_leftHandTarget = null;
if(m_rightHandTarget != null)
Destroy(m_rightHandTarget);
m_rightHandTarget = null;
m_poseHandler?.Dispose();
m_poseHandler = null;
m_vrIK = null;
Settings.EnabledChange -= this.OnEnabledChange;
Settings.FingersOnlyChange -= this.OnFingersOnlyChange;
Settings.TrackElbowsChange -= this.OnTrackElbowsChange;
}
void Update()
{
if(m_enabled)
{
LeapParser.LeapData l_data = LeapManager.Instance.GetLatestData();
if((m_leftArmIK != null) && (m_rightArmIK != null))
{
m_leftArmIK.solver.IKPositionWeight = Mathf.Lerp(m_leftArmIK.solver.IKPositionWeight, (l_data.m_leftHand.m_present && !m_fingersOnly) ? 1f : 0f, 0.25f);
m_leftArmIK.solver.IKRotationWeight = Mathf.Lerp(m_leftArmIK.solver.IKRotationWeight, (l_data.m_leftHand.m_present && !m_fingersOnly) ? 1f : 0f, 0.25f);
if(m_trackElbows)
m_leftArmIK.solver.arm.bendGoalWeight = Mathf.Lerp(m_leftArmIK.solver.arm.bendGoalWeight, (l_data.m_leftHand.m_present && !m_fingersOnly) ? 1f : 0f, 0.25f);
m_rightArmIK.solver.IKPositionWeight = Mathf.Lerp(m_rightArmIK.solver.IKPositionWeight, (l_data.m_rightHand.m_present && !m_fingersOnly) ? 1f : 0f, 0.25f);
m_rightArmIK.solver.IKRotationWeight = Mathf.Lerp(m_rightArmIK.solver.IKRotationWeight, (l_data.m_rightHand.m_present && !m_fingersOnly) ? 1f : 0f, 0.25f);
if(m_trackElbows)
m_rightArmIK.solver.arm.bendGoalWeight = Mathf.Lerp(m_rightArmIK.solver.arm.bendGoalWeight, (l_data.m_rightHand.m_present && !m_fingersOnly) ? 1f : 0f, 0.25f);
}
if((m_vrIK != null) && !m_fingersOnly)
{
m_leftTargetActive = l_data.m_leftHand.m_present;
m_rightTargetActive = l_data.m_rightHand.m_present;
}
}
}
void LateUpdate()
{
if(m_enabled && !m_inVR && (m_poseHandler != null))
{
LeapParser.LeapData l_data = LeapManager.Instance.GetLatestData();
Vector3 l_hipsLocalPos = m_hips.localPosition;
Quaternion l_hipsLocalRot = m_hips.localRotation;
m_poseHandler.GetHumanPose(ref m_pose);
UpdateFingers(l_data);
m_poseHandler.SetHumanPose(ref m_pose);
m_hips.localPosition = l_hipsLocalPos;
m_hips.localRotation = l_hipsLocalRot;
}
}
// Game events
internal void OnAvatarClear()
{
m_vrIK = null;
m_hips = null;
m_leftArmIK = null;
m_rightArmIK = null;
m_leftTargetActive = false;
m_rightTargetActive = false;
if(!m_inVR)
m_poseHandler?.Dispose();
m_poseHandler = null;
m_leftHandTarget.localPosition = Vector3.zero;
m_leftHandTarget.localRotation = Quaternion.identity;
m_rightHandTarget.localPosition = Vector3.zero;
m_rightHandTarget.localRotation = Quaternion.identity;
}
internal void OnAvatarSetup()
{
m_inVR = Utils.IsInVR();
m_vrIK = PlayerSetup.Instance._animator.GetComponent<VRIK>();
if(PlayerSetup.Instance._animator.isHuman)
{
Vector3 l_hipsPos = Vector3.zero;
m_hips = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Hips);
if(m_hips != null)
l_hipsPos = m_hips.localPosition;
if(!m_inVR)
{
// Force desktop avatar into T-Pose
m_poseHandler = new HumanPoseHandler(PlayerSetup.Instance._animator.avatar, PlayerSetup.Instance._avatar.transform);
m_poseHandler.GetHumanPose(ref m_pose);
HumanPose l_tPose = new HumanPose
{
bodyPosition = m_pose.bodyPosition,
bodyRotation = m_pose.bodyRotation,
muscles = new float[m_pose.muscles.Length]
};
for(int i = 0; i < l_tPose.muscles.Length; i++)
l_tPose.muscles[i] = ms_tposeMuscles[i];
m_poseHandler.SetHumanPose(ref l_tPose);
}
Transform l_hand = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftHand);
if(l_hand != null)
m_leftHandTarget.localRotation = ms_offsetLeft * (PlayerSetup.Instance._avatar.transform.GetMatrix().inverse * l_hand.GetMatrix()).rotation;
l_hand = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightHand);
if(l_hand != null)
m_rightHandTarget.localRotation = ms_offsetRight * (PlayerSetup.Instance._avatar.transform.GetMatrix().inverse * l_hand.GetMatrix()).rotation;
if(m_vrIK == null)
{
Transform l_chest = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.UpperChest);
if(l_chest == null)
l_chest = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Chest);
if(l_chest == null)
l_chest = PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.Spine);
m_leftArmIK = PlayerSetup.Instance._avatar.AddComponent<ArmIK>();
m_leftArmIK.solver.isLeft = true;
m_leftArmIK.solver.SetChain(
l_chest,
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftShoulder),
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftUpperArm),
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftLowerArm),
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.LeftHand),
PlayerSetup.Instance._animator.transform
);
m_leftArmIK.solver.arm.target = m_leftHandTarget;
m_leftArmIK.solver.arm.bendGoal = LeapTracking.Instance.GetLeftElbow();
m_leftArmIK.solver.arm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
m_leftArmIK.enabled = (m_enabled && !m_fingersOnly);
m_rightArmIK = PlayerSetup.Instance._avatar.AddComponent<ArmIK>();
m_rightArmIK.solver.isLeft = false;
m_rightArmIK.solver.SetChain(
l_chest,
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightShoulder),
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightUpperArm),
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightLowerArm),
PlayerSetup.Instance._animator.GetBoneTransform(HumanBodyBones.RightHand),
PlayerSetup.Instance._animator.transform
);
m_rightArmIK.solver.arm.target = m_rightHandTarget;
m_rightArmIK.solver.arm.bendGoal = LeapTracking.Instance.GetRightElbow();
m_rightArmIK.solver.arm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
m_rightArmIK.enabled = (m_enabled && !m_fingersOnly);
m_poseHandler?.SetHumanPose(ref m_pose);
}
else
{
m_vrIK.solver.OnPreUpdate += this.OnIKPreUpdate;
m_vrIK.solver.OnPostUpdate += this.OnIKPostUpdate;
}
if(m_hips != null)
m_hips.localPosition = l_hipsPos;
}
}
// VRIK updates
void OnIKPreUpdate()
{
if(m_leftTargetActive)
{
m_vrIKInfo.m_leftHandTarget = m_vrIK.solver.leftArm.target;
m_vrIKInfo.m_armsWeights.x = m_vrIK.solver.leftArm.positionWeight;
m_vrIKInfo.m_armsWeights.y = m_vrIK.solver.leftArm.rotationWeight;
m_vrIKInfo.m_leftElbowTarget = m_vrIK.solver.leftArm.bendGoal;
m_vrIKInfo.m_elbowsWeights.x = m_vrIK.solver.leftArm.bendGoalWeight;
m_vrIK.solver.leftArm.target = m_leftHandTarget;
m_vrIK.solver.leftArm.positionWeight = 1f;
m_vrIK.solver.leftArm.rotationWeight = 1f;
m_vrIK.solver.leftArm.bendGoal = LeapTracking.Instance.GetLeftElbow();
m_vrIK.solver.leftArm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
}
if(m_rightTargetActive)
{
m_vrIKInfo.m_rightHandTarget = m_vrIK.solver.rightArm.target;
m_vrIKInfo.m_armsWeights.z = m_vrIK.solver.rightArm.positionWeight;
m_vrIKInfo.m_armsWeights.w = m_vrIK.solver.rightArm.rotationWeight;
m_vrIKInfo.m_rightElbowTarget = m_vrIK.solver.rightArm.bendGoal;
m_vrIKInfo.m_elbowsWeights.y = m_vrIK.solver.rightArm.bendGoalWeight;
m_vrIK.solver.rightArm.target = m_rightHandTarget;
m_vrIK.solver.rightArm.positionWeight = 1f;
m_vrIK.solver.rightArm.rotationWeight = 1f;
m_vrIK.solver.rightArm.bendGoal = LeapTracking.Instance.GetRightElbow();
m_vrIK.solver.rightArm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
}
}
void OnIKPostUpdate()
{
if(m_leftTargetActive)
{
m_vrIK.solver.leftArm.target = m_vrIKInfo.m_leftHandTarget;
m_vrIK.solver.leftArm.positionWeight = m_vrIKInfo.m_armsWeights.x;
m_vrIK.solver.leftArm.rotationWeight = m_vrIKInfo.m_armsWeights.y;
m_vrIK.solver.leftArm.bendGoal = m_vrIKInfo.m_leftElbowTarget;
m_vrIK.solver.leftArm.bendGoalWeight = m_vrIKInfo.m_elbowsWeights.x;
}
if(m_rightTargetActive)
{
m_vrIK.solver.rightArm.target = m_vrIKInfo.m_rightHandTarget;
m_vrIK.solver.rightArm.positionWeight = m_vrIKInfo.m_armsWeights.z;
m_vrIK.solver.rightArm.rotationWeight = m_vrIKInfo.m_armsWeights.w;
m_vrIK.solver.rightArm.bendGoal = m_vrIKInfo.m_rightElbowTarget;
m_vrIK.solver.rightArm.bendGoalWeight = m_vrIKInfo.m_elbowsWeights.y;
}
}
// Settings
void OnEnabledChange(bool p_state)
{
m_enabled = p_state;
RefreshArmIK();
ResetTargetsStates();
}
void OnFingersOnlyChange(bool p_state)
{
m_fingersOnly = p_state;
RefreshArmIK();
ResetTargetsStates();
}
void OnTrackElbowsChange(bool p_state)
{
m_trackElbows = p_state;
if((m_leftArmIK != null) && (m_rightArmIK != null))
{
m_leftArmIK.solver.arm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
m_rightArmIK.solver.arm.bendGoalWeight = (m_trackElbows ? 1f : 0f);
}
ResetTargetsStates();
}
// Arbitrary
void ResetTargetsStates()
{
m_leftTargetActive = false;
m_rightTargetActive = false;
}
void RefreshArmIK()
{
if((m_leftArmIK != null) && (m_rightArmIK != null))
{
m_leftArmIK.enabled = (m_enabled && !m_fingersOnly);
m_rightArmIK.enabled = (m_enabled && !m_fingersOnly);
}
}
void UpdateFingers(LeapParser.LeapData p_data)
{
if(p_data.m_leftHand.m_present)
{
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftThumb1Stretched, -0.5f - p_data.m_leftHand.m_bends[0]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftThumb2Stretched, 0.7f - p_data.m_leftHand.m_bends[0] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftThumb3Stretched, 0.7f - p_data.m_leftHand.m_bends[0] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftThumbSpread, -p_data.m_leftHand.m_spreads[0]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftIndex1Stretched, 0.5f - p_data.m_leftHand.m_bends[1]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftIndex2Stretched, 0.7f - p_data.m_leftHand.m_bends[1] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftIndex3Stretched, 0.7f - p_data.m_leftHand.m_bends[1] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftIndexSpread, p_data.m_leftHand.m_spreads[1]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftMiddle1Stretched, 0.5f - p_data.m_leftHand.m_bends[2]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftMiddle2Stretched, 0.7f - p_data.m_leftHand.m_bends[2] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftMiddle3Stretched, 0.7f - p_data.m_leftHand.m_bends[2] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftMiddleSpread, p_data.m_leftHand.m_spreads[2]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftRing1Stretched, 0.5f - p_data.m_leftHand.m_bends[3]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftRing2Stretched, 0.7f - p_data.m_leftHand.m_bends[3] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftRing3Stretched, 0.7f - p_data.m_leftHand.m_bends[3] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftRingSpread, -p_data.m_leftHand.m_spreads[3]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftLittle1Stretched, 0.5f - p_data.m_leftHand.m_bends[4]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftLittle2Stretched, 0.7f - p_data.m_leftHand.m_bends[4] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftLittle3Stretched, 0.7f - p_data.m_leftHand.m_bends[4] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.LeftLittleSpread, -p_data.m_leftHand.m_spreads[4]);
}
if(p_data.m_rightHand.m_present)
{
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightThumb1Stretched, -0.5f - p_data.m_rightHand.m_bends[0]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightThumb2Stretched, 0.7f - p_data.m_rightHand.m_bends[0] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightThumb3Stretched, 0.7f - p_data.m_rightHand.m_bends[0] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightThumbSpread, -p_data.m_rightHand.m_spreads[0]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightIndex1Stretched, 0.5f - p_data.m_rightHand.m_bends[1]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightIndex2Stretched, 0.7f - p_data.m_rightHand.m_bends[1] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightIndex3Stretched, 0.7f - p_data.m_rightHand.m_bends[1] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightIndexSpread, p_data.m_rightHand.m_spreads[1]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightMiddle1Stretched, 0.5f - p_data.m_rightHand.m_bends[2]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightMiddle2Stretched, 0.7f - p_data.m_rightHand.m_bends[2] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightMiddle3Stretched, 0.7f - p_data.m_rightHand.m_bends[2] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightMiddleSpread, p_data.m_rightHand.m_spreads[2]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightRing1Stretched, 0.5f - p_data.m_rightHand.m_bends[3]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightRing2Stretched, 0.7f - p_data.m_rightHand.m_bends[3] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightRing3Stretched, 0.7f - p_data.m_rightHand.m_bends[3] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightRingSpread, -p_data.m_rightHand.m_spreads[3]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightLittle1Stretched, 0.5f - p_data.m_rightHand.m_bends[4]);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightLittle2Stretched, 0.7f - p_data.m_rightHand.m_bends[4] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightLittle3Stretched, 0.7f - p_data.m_rightHand.m_bends[4] * 2f);
UpdatePoseMuscle(ref m_pose, (int)MuscleIndex.RightLittleSpread, -p_data.m_rightHand.m_spreads[4]);
}
}
static void UpdatePoseMuscle(ref HumanPose p_pose, int p_index, float p_value)
{
if(p_pose.muscles.Length > p_index)
p_pose.muscles[p_index] = p_value;
}
}
}

View file

@ -102,8 +102,36 @@ namespace ml_lme
if(Instance == this)
Instance = null;
if(m_leapHandLeft != null)
Object.Destroy(m_leapHandLeft);
m_leapHandLeft = null;
if(m_leapHandRight != null)
Object.Destroy(m_leapHandRight);
m_leapHandRight = null;
if(m_leapElbowLeft != null)
Object.Destroy(m_leapElbowLeft);
m_leapElbowLeft = null;
if(m_leapElbowRight != null)
Object.Destroy(m_leapElbowRight);
m_leapElbowRight = null;
if(m_leapControllerModel != null)
Object.Destroy(m_leapControllerModel);
m_leapControllerModel = null;
if(m_visualHands != null)
Object.Destroy(m_visualHands);
m_visualHands = null;
m_visualHandLeft = null;
m_visualHandRight = null;
Settings.DesktopOffsetChange -= this.OnDesktopOffsetChange;
Settings.ModelVisibilityChange -= this.OnModelVisibilityChange;
Settings.VisualHandsChange -= this.OnVisualHandsChange;
Settings.TrackingModeChange -= this.OnTrackingModeChange;
Settings.RootAngleChange -= this.OnRootAngleChange;
Settings.HeadAttachChange -= this.OnHeadAttachChange;
@ -114,7 +142,7 @@ namespace ml_lme
{
if(Settings.Enabled)
{
GestureMatcher.LeapData l_data = LeapManager.Instance.GetLatestData();
LeapParser.LeapData l_data = LeapManager.Instance.GetLatestData();
if(l_data.m_leftHand.m_present)
{

View file

@ -1,5 +1,4 @@
using ABI_RC.Core.Player;
using ABI_RC.Systems.IK.SubSystems;
using System.Collections;
using System.Reflection;
using UnityEngine;
@ -33,11 +32,6 @@ namespace ml_lme
null,
new HarmonyLib.HarmonyMethod(typeof(LeapMotionExtension).GetMethod(nameof(OnSetupAvatar_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
HarmonyInstance.Patch(
typeof(BodySystem).GetMethod(nameof(BodySystem.Calibrate)),
null,
new HarmonyLib.HarmonyMethod(typeof(LeapMotionExtension).GetMethod(nameof(OnCalibrate_Postfix), BindingFlags.Static | BindingFlags.NonPublic))
);
HarmonyInstance.Patch(
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.SetControllerRayScale)),
null,
@ -57,6 +51,10 @@ namespace ml_lme
{
if(ms_instance == this)
ms_instance = null;
if(m_leapManager != null)
Object.Destroy(m_leapManager);
m_leapManager = null;
}
IEnumerator WaitForRootLogic()
@ -96,20 +94,6 @@ namespace ml_lme
}
}
static void OnCalibrate_Postfix() => ms_instance?.OnCalibrate();
void OnCalibrate()
{
try
{
if(m_leapManager != null)
m_leapManager.OnCalibrate();
}
catch(System.Exception e)
{
MelonLoader.MelonLogger.Error(e);
}
}
static void OnRayScale_Postfix(float __0) => ms_instance?.OnRayScale(__0);
void OnRayScale(float p_scale)
{

View file

@ -1,9 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ml_lme
{

View file

@ -1,7 +1,6 @@
using System.Reflection;
[assembly: MelonLoader.MelonInfo(typeof(ml_lme.LeapMotionExtension), "LeapMotionExtension", "1.4.0", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonGame(null, "ChilloutVR")]
[assembly: MelonLoader.MelonOptionalDependencies("ml_pmc")]
[assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
[assembly: MelonLoader.MelonInfo(typeof(ml_lme.LeapMotionExtension), "LeapMotionExtension", "1.4.5", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonGame(null, "ChilloutVR")]
[assembly: MelonLoader.MelonOptionalDependencies("ml_pmc")]
[assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
[assembly: MelonLoader.MelonAdditionalCredits("NotAKidOnSteam")]

View file

@ -4,9 +4,9 @@ using System.Reflection;
namespace ml_lme
{
static class Scripts
static class ResourcesHandler
{
public static string GetEmbeddedScript(string p_name)
public static string GetEmbeddedResource(string p_name)
{
string l_result = "";
Assembly l_assembly = Assembly.GetExecutingAssembly();

View file

@ -1,5 +1,4 @@
using ABI_RC.Core.InteractionSystem;
using cohtml;
using System;
using System.Collections.Generic;
using UnityEngine;
@ -32,7 +31,7 @@ namespace ml_lme
HeadY,
HeadZ,
TrackElbows,
Input,
Interaction,
Gestures,
InteractThreadhold,
GripThreadhold,
@ -48,7 +47,7 @@ namespace ml_lme
public static bool HeadAttach { get; private set; } = false;
public static Vector3 HeadOffset { get; private set; } = new Vector3(0f, -0.3f, 0.15f);
public static bool TrackElbows { get; private set; } = true;
public static bool Input { get; private set; } = true;
public static bool Interaction { get; private set; } = true;
public static bool Gestures { get; private set; } = false;
public static float InteractThreadhold { get; private set; } = 0.8f;
public static float GripThreadhold { get; private set; } = 0.4f;
@ -66,7 +65,7 @@ namespace ml_lme
static public event Action<bool> HeadAttachChange;
static public event Action<Vector3> HeadOffsetChange;
static public event Action<bool> TrackElbowsChange;
static public event Action<bool> InputChange;
static public event Action<bool> InteractionChange;
static public event Action<bool> GesturesChange;
static public event Action<float> InteractThreadholdChange;
static public event Action<float> GripThreadholdChange;
@ -93,7 +92,7 @@ namespace ml_lme
ms_category.CreateEntry(ModSetting.HeadY.ToString(), (int)(HeadOffset.y * 100f)),
ms_category.CreateEntry(ModSetting.HeadZ.ToString(), (int)(HeadOffset.z * 100f)),
ms_category.CreateEntry(ModSetting.TrackElbows.ToString(), TrackElbows),
ms_category.CreateEntry(ModSetting.Input.ToString(), Input),
ms_category.CreateEntry(ModSetting.Interaction.ToString(), Interaction),
ms_category.CreateEntry(ModSetting.Gestures.ToString(), Gestures),
ms_category.CreateEntry(ModSetting.InteractThreadhold.ToString(), (int)(InteractThreadhold * 100f)),
ms_category.CreateEntry(ModSetting.GripThreadhold.ToString(), (int)(GripThreadhold * 100f)),
@ -116,15 +115,16 @@ namespace ml_lme
ViewManager.Instance.gameMenuView.Listener.ReadyForBindings += () =>
{
ViewManager.Instance.gameMenuView.View.BindCall("MelonMod_LME_Call_InpToggle", new Action<string, string>(OnToggleUpdate));
ViewManager.Instance.gameMenuView.View.BindCall("MelonMod_LME_Call_InpSlider", new Action<string, string>(OnSliderUpdate));
ViewManager.Instance.gameMenuView.View.BindCall("MelonMod_LME_Call_InpDropdown", new Action<string, string>(OnDropdownUpdate));
ViewManager.Instance.gameMenuView.View.BindCall("OnToggleUpdate_" + ms_category.Identifier, new Action<string, string>(OnToggleUpdate));
ViewManager.Instance.gameMenuView.View.BindCall("OnSliderUpdate_" + ms_category.Identifier, new Action<string, string>(OnSliderUpdate));
ViewManager.Instance.gameMenuView.View.BindCall("OnDropdownUpdate_" + ms_category.Identifier, new Action<string, string>(OnDropdownUpdate));
};
ViewManager.Instance.gameMenuView.Listener.FinishLoad += (_) =>
{
ViewManager.Instance.gameMenuView.View.ExecuteScript(Scripts.GetEmbeddedScript("menu.js"));
ViewManager.Instance.gameMenuView.View.ExecuteScript(ResourcesHandler.GetEmbeddedResource("mods_extension.js"));
ViewManager.Instance.gameMenuView.View.ExecuteScript(ResourcesHandler.GetEmbeddedResource("mod_menu.js"));
foreach(var l_entry in ms_entries)
ViewManager.Instance.gameMenuView.View.TriggerEvent("updateModSettingLME", l_entry.DisplayName, l_entry.GetValueAsString());
ViewManager.Instance.gameMenuView.View.TriggerEvent("updateModSetting", ms_category.Identifier, l_entry.DisplayName, l_entry.GetValueAsString());
};
}
@ -151,7 +151,7 @@ namespace ml_lme
(int)ms_entries[(int)ModSetting.HeadZ].BoxedValue
) * 0.01f;
TrackElbows = (bool)ms_entries[(int)ModSetting.TrackElbows].BoxedValue;
Input = (bool)ms_entries[(int)ModSetting.Input].BoxedValue;
Interaction = (bool)ms_entries[(int)ModSetting.Interaction].BoxedValue;
Gestures = (bool)ms_entries[(int)ModSetting.Gestures].BoxedValue;
InteractThreadhold = (int)ms_entries[(int)ModSetting.InteractThreadhold].BoxedValue * 0.01f;
GripThreadhold = (int)ms_entries[(int)ModSetting.GripThreadhold].BoxedValue * 0.01f;
@ -199,10 +199,10 @@ namespace ml_lme
}
break;
case ModSetting.Input:
case ModSetting.Interaction:
{
Input = bool.Parse(p_value);
InputChange?.Invoke(Input);
Interaction = bool.Parse(p_value);
InteractionChange?.Invoke(Interaction);
}
break;

View file

@ -1,48 +1,50 @@
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Core.UI;
using ABI_RC.Systems.InputManagement;
using System.Linq;
using System.Reflection;
using UnityEngine;
namespace ml_lme
{
static class Utils
{
public static bool IsInVR() => ((CheckVR.Instance != null) && CheckVR.Instance.hasVrDeviceLoaded);
public static bool AreKnucklesInUse() => ((CVRInputManager.Instance._leftController == ABI_RC.Systems.InputManagement.XR.EXRControllerType.Index) || (CVRInputManager.Instance._rightController == ABI_RC.Systems.InputManagement.XR.EXRControllerType.Index));
public static bool IsLeftHandTracked() => (CVRInputManager.Instance._leftController != ABI_RC.Systems.InputManagement.XR.EXRControllerType.None);
public static bool IsRightHandTracked() => (CVRInputManager.Instance._rightController != ABI_RC.Systems.InputManagement.XR.EXRControllerType.None);
public static Matrix4x4 GetMatrix(this Transform p_transform, bool p_pos = true, bool p_rot = true, bool p_scl = false)
{
return Matrix4x4.TRS(p_pos ? p_transform.position : Vector3.zero, p_rot ? p_transform.rotation : Quaternion.identity, p_scl ? p_transform.lossyScale : Vector3.one);
}
public static void ShowHUDNotification(string p_title, string p_message, string p_small = "", bool p_immediate = false)
{
if(CohtmlHud.Instance != null)
{
if(p_immediate)
CohtmlHud.Instance.ViewDropTextImmediate(p_title, p_message, p_small);
else
CohtmlHud.Instance.ViewDropText(p_title, p_message, p_small);
}
}
public static void Swap<T>(ref T lhs, ref T rhs)
{
T temp = lhs;
lhs = rhs;
rhs = temp;
}
public static float InverseLerpUnclamped(float a, float b, float value)
{
if(a != b)
return (value - a) / (b - a);
return 0f;
}
}
}
using ABI_RC.Core.Savior;
using ABI_RC.Core.UI;
using ABI_RC.Systems.InputManagement;
using System.Reflection;
using UnityEngine;
namespace ml_lme
{
static class Utils
{
static readonly FieldInfo ms_view = typeof(CohtmlControlledViewWrapper).GetField("_view", BindingFlags.NonPublic | BindingFlags.Instance);
public static bool IsInVR() => ((CheckVR.Instance != null) && CheckVR.Instance.hasVrDeviceLoaded);
public static bool AreKnucklesInUse() => ((CVRInputManager.Instance._leftController == ABI_RC.Systems.InputManagement.XR.eXRControllerType.Index) || (CVRInputManager.Instance._rightController == ABI_RC.Systems.InputManagement.XR.eXRControllerType.Index));
public static bool IsLeftHandTracked() => (CVRInputManager.Instance._leftController != ABI_RC.Systems.InputManagement.XR.eXRControllerType.None);
public static bool IsRightHandTracked() => (CVRInputManager.Instance._rightController != ABI_RC.Systems.InputManagement.XR.eXRControllerType.None);
public static Matrix4x4 GetMatrix(this Transform p_transform, bool p_pos = true, bool p_rot = true, bool p_scl = false)
{
return Matrix4x4.TRS(p_pos ? p_transform.position : Vector3.zero, p_rot ? p_transform.rotation : Quaternion.identity, p_scl ? p_transform.lossyScale : Vector3.one);
}
public static void ShowHUDNotification(string p_title, string p_message, string p_small = "", bool p_immediate = false)
{
if(CohtmlHud.Instance != null)
{
if(p_immediate)
CohtmlHud.Instance.ViewDropTextImmediate(p_title, p_message, p_small);
else
CohtmlHud.Instance.ViewDropText(p_title, p_message, p_small);
}
}
static public void ExecuteScript(this CohtmlControlledViewWrapper p_instance, string p_script) => ((cohtml.Net.View)ms_view.GetValue(p_instance)).ExecuteScript(p_script);
public static void Swap<T>(ref T lhs, ref T rhs)
{
T temp = lhs;
lhs = rhs;
rhs = temp;
}
public static float InverseLerpUnclamped(float a, float b, float value)
{
if(a != b)
return (value - a) / (b - a);
return 0f;
}
}
}

View file

@ -4,17 +4,14 @@ namespace ml_lme
{
class VisualHand
{
Transform m_root = null;
Transform m_wrist = null;
Transform[] m_fingers = null;
readonly Transform m_wrist = null;
readonly Transform[] m_fingers = null;
public VisualHand(Transform p_root, bool p_left)
{
m_root = p_root;
if(m_root != null)
if(p_root != null)
{
m_wrist = m_root.Find(p_left ? "LeftHand/Wrist" : "RightHand/Wrist");
m_wrist = p_root.Find(p_left ? "LeftHand/Wrist" : "RightHand/Wrist");
if(m_wrist != null)
{
m_fingers = new Transform[20];
@ -47,7 +44,7 @@ namespace ml_lme
}
}
public void Update(GestureMatcher.HandData p_data)
public void Update(LeapParser.HandData p_data)
{
if(m_wrist != null)
{

View file

@ -1,99 +1,115 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Platforms>x64</Platforms>
<PackageId>LeapMotionExtension</PackageId>
<Version>1.4.0</Version>
<Authors>SDraw</Authors>
<Company>None</Company>
<Product>LeapMotionExtension</Product>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<Compile Remove="vendor\LeapSDK\**" />
<EmbeddedResource Remove="vendor\LeapSDK\**" />
<None Remove="vendor\LeapSDK\**" />
</ItemGroup>
<ItemGroup>
<None Remove="LeapMotionExtension.json" />
<None Remove="resources\leapmotion_controller.asset" />
<None Remove="resources\leapmotion_hands.asset" />
<None Remove="resources\menu.js" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="resources\leapmotion_controller.asset" />
<EmbeddedResource Include="resources\leapmotion_hands.asset" />
<EmbeddedResource Include="resources\menu.js" />
<EmbeddedResource Include="vendor\LeapSDK\lib\x64\LeapC.dll">
<Link>LeapC.dll</Link>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Reference Include="0Harmony">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\0Harmony.dll</HintPath>
<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>
</Reference>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="cohtml.Net">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\cohtml.Net.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Cohtml.Runtime">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Cohtml.Runtime.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="MelonLoader">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\MelonLoader.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="ml_pmc">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\Mods\ml_pmc.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.AnimationModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AnimationModule.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.AssetBundleModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AssetBundleModule.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.XRModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.XRModule.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Folder Include="vendor\LeapCSharp\" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="copy /y &quot;$(TargetPath)&quot; &quot;D:\Games\Steam\steamapps\common\ChilloutVR\Mods\&quot;" />
</Target>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Platforms>x64</Platforms>
<PackageId>LeapMotionExtension</PackageId>
<Version>1.4.5</Version>
<Authors>SDraw</Authors>
<Company>None</Company>
<Product>LeapMotionExtension</Product>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<Compile Remove="vendor\LeapSDK\**" />
<EmbeddedResource Remove="vendor\LeapSDK\**" />
<None Remove="vendor\LeapSDK\**" />
</ItemGroup>
<ItemGroup>
<None Remove="LeapMotionExtension.json" />
<None Remove="resources\leapmotion_controller.asset" />
<None Remove="resources\leapmotion_hands.asset" />
<None Remove="resources\mod_menu.js" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="resources\leapmotion_controller.asset" />
<EmbeddedResource Include="resources\leapmotion_hands.asset" />
<EmbeddedResource Include="resources\mod_menu.js" />
<EmbeddedResource Include="vendor\LeapSDK\lib\x64\LeapC.dll">
<Link>resources/LeapC.dll</Link>
</EmbeddedResource>
</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="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>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
<Reference Include="ml_pmc">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\Mods\ml_pmc.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.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.AssetBundleModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AssetBundleModule.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>
<Reference Include="UnityEngine.XRModule">
<HintPath>D:\games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.XRModule.dll</HintPath>
<Private>false</Private>
<SpecificVersion>false</SpecificVersion>
</Reference>
</ItemGroup>
<ItemGroup>
<Folder Include="vendor\LeapCSharp\" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="..\js\mods_extension.js" Link="resources\mods_extension.js" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="copy /y &quot;$(TargetPath)&quot; &quot;D:\Games\Steam\steamapps\common\ChilloutVR\Mods\&quot;" />
</Target>
</Project>

View file

@ -1,442 +0,0 @@
// Add settings
var g_modSettingsLME = [];
engine.on('updateModSettingLME', function (_name, _value) {
for (var i = 0; i < g_modSettingsLME.length; i++) {
if (g_modSettingsLME[i].name == _name) {
g_modSettingsLME[i].updateValue(_value);
break;
}
}
});
// Modified from original `inp` types, because I have no js knowledge to hook stuff
function inp_toggle_mod_lme(_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
}
}
function inp_slider_mod_lme(_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 + "&apos;" + inches + '&apos;&apos;';
}
}
}
return {
name: this.name,
value: this.getValue,
updateValue: this.updateValue
}
}
function inp_dropdown_mod_lme(_obj, _callbackName) {
this.obj = _obj;
this.callbackName = _callbackName;
this.value = _obj.getAttribute('data-current');
this.options = _obj.getAttribute('data-options').split(',');
this.name = _obj.id;
this.opened = false;
this.keyValue = [];
this.type = _obj.getAttribute('data-type');
this.optionElements = [];
var self = this;
this.SelectValue = function (_e) {
self.value = _e.target.getAttribute('data-key');
self.valueElement.innerHTML = _e.target.getAttribute('data-value');
self.globalClose();
engine.call(self.callbackName, self.name, self.value);
}
this.openClick = function (_e) {
if (self.obj.classList.contains('open')) {
self.obj.classList.remove('open');
self.list.setAttribute('style', 'display: none;');
} else {
self.obj.classList.add('open');
self.list.setAttribute('style', 'display: block;');
self.opened = true;
window.setTimeout(function () { self.opened = false; }, 10);
}
}
this.globalClose = function (_e) {
if (self.opened) return;
self.obj.classList.remove('open');
self.list.setAttribute('style', 'display: none;');
}
this.list = document.createElement('div');
this.list.className = 'valueList';
this.updateOptions = function () {
self.list.innerHTML = "";
for (var i = 0; i < self.options.length; i++) {
self.optionElements[i] = document.createElement('div');
self.optionElements[i].className = 'listValue';
var valuePair = Array.isArray(self.options[i]) ? self.options[i] : self.options[i].split(':');
var key = "";
var value = "";
if (valuePair.length == 1) {
key = valuePair[0];
value = valuePair[0];
} else {
key = valuePair[0];
value = valuePair[1];
}
self.keyValue[key] = value;
self.optionElements[i].innerHTML = value;
self.optionElements[i].setAttribute('data-value', value);
self.optionElements[i].setAttribute('data-key', key);
self.list.appendChild(self.optionElements[i]);
self.optionElements[i].addEventListener('mousedown', self.SelectValue);
}
self.valueElement.innerHTML = self.keyValue[self.value];
}
this.valueElement = document.createElement('div');
this.valueElement.className = 'dropdown-value';
this.updateOptions();
this.obj.appendChild(this.valueElement);
this.obj.appendChild(this.list);
this.valueElement.addEventListener('mousedown', this.openClick);
document.addEventListener('mousedown', this.globalClose);
this.getValue = function () {
return self.value;
}
this.updateValue = function (value) {
self.value = value;
self.valueElement.innerHTML = self.keyValue[value];
}
this.setOptions = function (options) {
self.options = options;
}
return {
name: this.name,
value: this.getValue,
updateValue: this.updateValue,
updateOptions: this.updateOptions,
setOptions: this.setOptions
}
}
// Add own menu
{
let l_block = document.createElement('div');
l_block.innerHTML = `
<div class ="settings-subcategory">
<div class ="subcategory-name">Leap Motion tracking</div>
<div class ="subcategory-description"></div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Enable tracking: </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">Tracking mode: </div>
<div class ="option-input">
<div id="Mode" class ="inp_dropdown no-scroll" data-options="0:Screentop,1:Desktop,2:HMD" data-current="1"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Desktop offset X: </div>
<div class ="option-input">
<div id="DesktopX" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="0"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Desktop offset Y: </div>
<div class ="option-input">
<div id="DesktopY" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="-45"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Desktop offset Z: </div>
<div class ="option-input">
<div id="DesktopZ" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="30"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Attach to head: </div>
<div class ="option-input">
<div id="Head" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Head offset X: </div>
<div class ="option-input">
<div id="HeadX" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="0"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Head offset Y: </div>
<div class ="option-input">
<div id="HeadY" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="-30"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Head offset Z: </div>
<div class ="option-input">
<div id="HeadZ" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="15"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Offset angle X: </div>
<div class ="option-input">
<div id="AngleX" class ="inp_slider no-scroll" data-min="-180" data-max="180" data-current="0"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Offset angle Y: </div>
<div class ="option-input">
<div id="AngleY" class ="inp_slider no-scroll" data-min="-180" data-max="180" data-current="0"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Offset angle Z: </div>
<div class ="option-input">
<div id="AngleZ" class ="inp_slider no-scroll" data-min="-180" data-max="180" data-current="0"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Track elbows: </div>
<div class ="option-input">
<div id="TrackElbows" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Fingers tracking only: </div>
<div class ="option-input">
<div id="FingersOnly" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Model visibility: </div>
<div class ="option-input">
<div id="Model" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Visualize hands: </div>
<div class ="option-input">
<div id="VisualHands" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Interaction input: </div>
<div class ="option-input">
<div id="Input" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Recognize Gestures: </div>
<div class ="option-input">
<div id="Gestures" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Interact gesture threadhold: </div>
<div class ="option-input">
<div id="InteractThreadhold" class ="inp_slider no-scroll" data-min="0" data-max="100" data-current="80"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Grip gesture threadhold: </div>
<div class ="option-input">
<div id="GripThreadhold" class ="inp_slider no-scroll" data-min="0" data-max="100" data-current="40"></div>
</div>
</div>
`;
document.getElementById('settings-implementation').appendChild(l_block);
// Update toggles in new menu block
let l_toggles = l_block.querySelectorAll('.inp_toggle');
for (var i = 0; i < l_toggles.length; i++) {
g_modSettingsLME[g_modSettingsLME.length] = new inp_toggle_mod_lme(l_toggles[i], 'MelonMod_LME_Call_InpToggle');
}
// Update sliders in new menu block
let l_sliders = l_block.querySelectorAll('.inp_slider');
for (var i = 0; i < l_sliders.length; i++) {
g_modSettingsLME[g_modSettingsLME.length] = new inp_slider_mod_lme(l_sliders[i], 'MelonMod_LME_Call_InpSlider');
}
//Update dropdowns in new menu block
let l_dropdowns = l_block.querySelectorAll('.inp_dropdown');
for (var i = 0; i < l_dropdowns.length; i++) {
g_modSettingsLME[g_modSettingsLME.length] = new inp_dropdown_mod_lme(l_dropdowns[i], 'MelonMod_LME_Call_InpDropdown');
}
}

View file

@ -0,0 +1,162 @@
{
let l_block = document.createElement('div');
l_block.innerHTML = `
<div class ="settings-subcategory">
<div class ="subcategory-name">Leap Motion tracking</div>
<div class ="subcategory-description"></div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Enable tracking: </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">Tracking mode: </div>
<div class ="option-input">
<div id="Mode" class ="inp_dropdown no-scroll" data-options="0:Screentop,1:Desktop,2:HMD" data-current="1"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Desktop offset X: </div>
<div class ="option-input">
<div id="DesktopX" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="0"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Desktop offset Y: </div>
<div class ="option-input">
<div id="DesktopY" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="-45"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Desktop offset Z: </div>
<div class ="option-input">
<div id="DesktopZ" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="30"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Attach to head: </div>
<div class ="option-input">
<div id="Head" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Head offset X: </div>
<div class ="option-input">
<div id="HeadX" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="0"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Head offset Y: </div>
<div class ="option-input">
<div id="HeadY" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="-30"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Head offset Z: </div>
<div class ="option-input">
<div id="HeadZ" class ="inp_slider no-scroll" data-min="-100" data-max="100" data-current="15"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Offset angle X: </div>
<div class ="option-input">
<div id="AngleX" class ="inp_slider no-scroll" data-min="-180" data-max="180" data-current="0"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Offset angle Y: </div>
<div class ="option-input">
<div id="AngleY" class ="inp_slider no-scroll" data-min="-180" data-max="180" data-current="0"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Offset angle Z: </div>
<div class ="option-input">
<div id="AngleZ" class ="inp_slider no-scroll" data-min="-180" data-max="180" data-current="0"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Track elbows: </div>
<div class ="option-input">
<div id="TrackElbows" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Fingers tracking only: </div>
<div class ="option-input">
<div id="FingersOnly" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Model visibility: </div>
<div class ="option-input">
<div id="Model" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Visualize hands: </div>
<div class ="option-input">
<div id="VisualHands" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Interaction input: </div>
<div class ="option-input">
<div id="Interaction" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Recognize gestures: </div>
<div class ="option-input">
<div id="Gestures" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Interact gesture threadhold: </div>
<div class ="option-input">
<div id="InteractThreadhold" class ="inp_slider no-scroll" data-min="0" data-max="100" data-current="80"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Grip gesture threadhold: </div>
<div class ="option-input">
<div id="GripThreadhold" class ="inp_slider no-scroll" data-min="0" data-max="100" data-current="40"></div>
</div>
</div>
`;
document.getElementById('settings-implementation').appendChild(l_block);
// Toggles
for (let l_toggle of l_block.querySelectorAll('.inp_toggle'))
modsExtension.addSetting('LME', l_toggle.id, modsExtension.createToggle(l_toggle, 'OnToggleUpdate_LME'));
// Sliders
for (let l_slider of l_block.querySelectorAll('.inp_slider'))
modsExtension.addSetting('LME', l_slider.id, modsExtension.createSlider(l_slider, 'OnSliderUpdate_LME'));
// Dropdowns
for (let l_dropdown of l_block.querySelectorAll('.inp_dropdown'))
modsExtension.addSetting('LME', l_dropdown.id, modsExtension.createDropdown(l_dropdown, 'OnDropdownUpdate_LME'));
}

View file

@ -18,6 +18,7 @@ namespace Leap
///
/// @since 1.0
/// </summary>
[Obsolete("Config.cs is not used in Ultraleap's Tracking Service 5.X+. This will be removed in the next Major release")]
public class Config
{
private Connection _connection;
@ -29,12 +30,15 @@ namespace Leap
/// Note that the Controller.Config provides a properly initialized Config object already.
/// @since 3.0
/// </summary>
[Obsolete("Config.cs is not used in Ultraleap's Tracking Service 5.X+. This will be removed in the next Major release")]
public Config(Connection.Key connectionKey)
{
_connection = Connection.GetConnection(connectionKey);
_connection.LeapConfigChange += handleConfigChange;
_connection.LeapConfigResponse += handleConfigResponse;
}
[Obsolete("Config.cs is not used in Ultraleap's Tracking Service 5.X+. This will be removed in the next Major release")]
public Config(int connectionId) : this(new Connection.Key(connectionId)) { }
private void handleConfigChange(object sender, ConfigChangeEventArgs eventArgs)
@ -87,6 +91,7 @@ namespace Leap
///
/// @since 3.0
/// </summary>
[Obsolete("Config.cs is not used in Ultraleap's Tracking Service 5.X+. This will be removed in the next Major release")]
public bool Get<T>(string key, Action<T> onResult)
{
uint requestId = _connection.GetConfigValue(key);
@ -107,6 +112,7 @@ namespace Leap
///
/// @since 3.0
/// </summary>
[Obsolete("Config.cs is not used in Ultraleap's Tracking Service 5.X+. This will be removed in the next Major release")]
public bool Set<T>(string key, T value, Action<bool> onResult) where T : IConvertible
{
uint requestId = _connection.SetConfigValue<T>(key, value);
@ -123,6 +129,7 @@ namespace Leap
/// Enumerates the possible data types for configuration values.
/// @since 1.0
/// </summary>
[Obsolete("Config.cs is not used in Ultraleap's Tracking Service 5.X+. This will be removed in the next Major release")]
public enum ValueType
{
TYPE_UNKNOWN = 0,

View file

@ -359,9 +359,6 @@ namespace LeapInternal
StructMarshal<LEAP_CONFIG_CHANGE_EVENT>.PtrToStruct(_msg.eventStructPtr, out config_change_evt);
handleConfigChange(ref config_change_evt);
break;
case eLeapEventType.eLeapEventType_ConfigResponse:
handleConfigResponse(ref _msg);
break;
case eLeapEventType.eLeapEventType_DroppedFrame:
LEAP_DROPPED_FRAME_EVENT dropped_frame_evt;
StructMarshal<LEAP_DROPPED_FRAME_EVENT>.PtrToStruct(_msg.eventStructPtr, out dropped_frame_evt);
@ -749,55 +746,6 @@ namespace LeapInternal
}
}
private void handleConfigResponse(ref LEAP_CONNECTION_MESSAGE configMsg)
{
LEAP_CONFIG_RESPONSE_EVENT config_response_evt;
StructMarshal<LEAP_CONFIG_RESPONSE_EVENT>.PtrToStruct(configMsg.eventStructPtr, out config_response_evt);
string config_key = "";
_configRequests.TryGetValue(config_response_evt.requestId, out config_key);
if (config_key != null)
_configRequests.Remove(config_response_evt.requestId);
Config.ValueType dataType;
object value;
uint requestId = config_response_evt.requestId;
if (config_response_evt.value.type != eLeapValueType.eLeapValueType_String)
{
switch (config_response_evt.value.type)
{
case eLeapValueType.eLeapValueType_Boolean:
dataType = Config.ValueType.TYPE_BOOLEAN;
value = config_response_evt.value.boolValue;
break;
case eLeapValueType.eLeapValueType_Int32:
dataType = Config.ValueType.TYPE_INT32;
value = config_response_evt.value.intValue;
break;
case eLeapValueType.eLeapValueType_Float:
dataType = Config.ValueType.TYPE_FLOAT;
value = config_response_evt.value.floatValue;
break;
default:
dataType = Config.ValueType.TYPE_UNKNOWN;
value = new object();
break;
}
}
else
{
LEAP_CONFIG_RESPONSE_EVENT_WITH_REF_TYPE config_ref_value;
StructMarshal<LEAP_CONFIG_RESPONSE_EVENT_WITH_REF_TYPE>.PtrToStruct(configMsg.eventStructPtr, out config_ref_value);
dataType = Config.ValueType.TYPE_STRING;
value = config_ref_value.value.stringValue;
}
SetConfigResponseEventArgs args = new SetConfigResponseEventArgs(config_key, dataType, value, requestId);
if (LeapConfigResponse != null)
{
LeapConfigResponse.DispatchOnContext(this, EventContext, args);
}
}
private void reportLogMessage(ref LEAP_LOG_EVENT logMsg)
{
if (LeapLogEvent != null)

View file

@ -43,7 +43,6 @@ namespace Leap
bool _disposed = false;
bool _supportsMultipleDevices = true;
string _serverNamespace = "Leap Service";
Config _config;
/// <summary>
/// The SynchronizationContext used for dispatching events.
@ -871,13 +870,12 @@ namespace Leap
///
/// @since 1.0
/// </summary>
[Obsolete("Config.cs is not used in Ultraleap's Tracking Service 5.X+. This will be removed in the next Major release")]
public Config Config
{
get
{
if (_config == null)
_config = new Config(this._connection.ConnectionKey);
return _config;
return null;
}
}

View file

@ -121,14 +121,6 @@ namespace Leap
IsStreaming = true;
else
IsStreaming = false;
if ((status & eLeapDeviceStatus.eLeapDeviceStatus_Smudged) == eLeapDeviceStatus.eLeapDeviceStatus_Smudged)
IsSmudged = true;
else
IsSmudged = false;
if ((status & eLeapDeviceStatus.eLeapDeviceStatus_Robust) == eLeapDeviceStatus.eLeapDeviceStatus_Robust)
IsLightingBad = true;
else
IsLightingBad = false;
if ((status & eLeapDeviceStatus.eLeapDeviceStatus_LowResource) == eLeapDeviceStatus.eLeapDeviceStatus_LowResource)
IsLowResource = true;
else
@ -263,33 +255,61 @@ namespace Leap
return devicePose;
}
//float[] data = new float[16];
//eLeapRS result = LeapC.GetDeviceTransform(Handle, data);
bool deviceTransformAvailable = LeapC.GetDeviceTransformAvailable(Handle);
//if (result != eLeapRS.eLeapRS_Success || data == null)
//{
// devicePose = Pose.identity;
// return devicePose;
//}
if (!deviceTransformAvailable)
{
devicePose = Pose.identity;
poseSet = true;
return Pose.identity;
}
//// Get transform matrix and convert to unity space by inverting Z.
//Matrix4x4 transformMatrix = new Matrix4x4(
// new Vector4(data[0], data[1], data[2], data[3]),
// new Vector4(data[4], data[5], data[6], data[7]),
// new Vector4(data[8], data[9], data[10], data[11]),
// new Vector4(data[12], data[13], data[14], data[15]));
//Matrix4x4 toUnity = Matrix4x4.Scale(new Vector3(1, 1, -1));
//transformMatrix = toUnity * transformMatrix;
float[] data = new float[16];
eLeapRS result = LeapC.GetDeviceTransform(Handle, data);
//// Identity matrix here means we have no device transform, also check validity.
//if (transformMatrix.isIdentity || !transformMatrix.ValidTRS())
//{
// devicePose = Pose.identity;
// return devicePose;
//}
if (result != eLeapRS.eLeapRS_Success || data == null)
{
devicePose = Pose.identity;
poseSet = true;
return Pose.identity;
}
//// Return the valid pose
//devicePose = new Pose(transformMatrix.GetColumn(3), transformMatrix.rotation);
// Using the LEAP->OPENXR device transform matrix
// Unitys matrices are generated as 4 columns:
Matrix4x4 deviceTransform = new Matrix4x4(
new Vector4(data[0], data[1], data[2], data[3]),
new Vector4(data[4], data[5], data[6], data[7]),
new Vector4(data[8], data[9], data[10], data[11]),
new Vector4(data[12], data[13], data[14], data[15]));
// An example of the expected matrix if it were 8cm forward from the head origin
// Unitys matrices are generated as 4 columns:
//Matrix4x4 deviceTransform = new Matrix4x4(
// new Vector4(-0.001f, 0, 0, 0),
// new Vector4(0, 0, -0.001f, 0),
// new Vector4(0, -0.001f, 0, 0),
// new Vector4(0, 0, -0.08f, 1));
if (deviceTransform == Matrix4x4.identity)
{
devicePose = Pose.identity;
poseSet = true;
return Pose.identity;
}
Matrix4x4 openXRToUnity = new Matrix4x4(
new Vector4(1f, 0, 0, 0),
new Vector4(0, 1f, 0, 0),
new Vector4(0, 0, -1f, 0),
new Vector4(0, 0, 0, 1));
deviceTransform = openXRToUnity * deviceTransform;
Vector3 outputPos = deviceTransform.GetPosition();
//Quaternion outputRot = deviceTransform.rotation; // Note: the matrices we receive are not rotatrion matrices. This produces unexpected results
devicePose = new Pose(outputPos, Quaternion.identity);
poseSet = true;
return devicePose;
@ -321,6 +341,7 @@ namespace Leap
/// over the Leap Motion cameras.
/// @since 3.0
/// </summary>
[Obsolete("IsSmudged is not used in Ultraleap's Tracking Service 5.X+. This will be removed in the next Major release")]
public bool IsSmudged { get; internal set; }
/// <summary>
@ -335,6 +356,7 @@ namespace Leap
/// isLightingBad() is true.
/// @since 3.0
/// </summary>
[Obsolete("IsLightingBad is not used in Ultraleap's Tracking Service 5.X+. This will be removed in the next Major release")]
public bool IsLightingBad { get; internal set; }
/// <summary>

View file

@ -179,6 +179,7 @@ namespace Leap
/// Provides the configuration key, whether the change was successful, and the id of the original change request.
/// @since 3.0
/// </summary>
[Obsolete("Config.cs is not used in Ultraleap's Tracking Service 5.X+. This will be removed in the next Major release")]
public class SetConfigResponseEventArgs : LeapEventArgs
{
public SetConfigResponseEventArgs(string config_key, Config.ValueType dataType, object value, uint requestId) : base(LeapEvent.EVENT_CONFIG_RESPONSE)

View file

@ -24,7 +24,7 @@ namespace Leap
long Now();
bool IsConnected { get; }
Config Config { get; }
DeviceList Devices { get; }
event EventHandler<ConnectionEventArgs> Connect;

View file

@ -1052,6 +1052,9 @@ namespace LeapInternal
[DllImport("LeapC", EntryPoint = "LeapGetDeviceInfo", CharSet = CharSet.Ansi)]
public static extern eLeapRS GetDeviceInfo(IntPtr hDevice, ref LEAP_DEVICE_INFO info);
[DllImport("LeapC", EntryPoint = "LeapDeviceTransformAvailable")]
public static extern bool GetDeviceTransformAvailable(IntPtr hDevice);
[DllImport("LeapC", EntryPoint = "LeapGetDeviceTransform")]
public static extern eLeapRS GetDeviceTransform(IntPtr hDevice, [MarshalAs(UnmanagedType.LPArray, SizeConst = 16)] float[] transform);

View file

@ -41,6 +41,17 @@ namespace Leap
return new Frame().CopyFrom(frame).Transform(transform);
}
/**
* Returns a new frame that is a copy of a frame, with an additional rigid
* transformation applied to it.
*
* @param transform The transformation to be applied to the copied frame.
*/
public static Frame TransformedCopy(this Frame frame, Vector3 position, Quaternion rotation)
{
return new Frame().CopyFrom(frame).Transform(new LeapTransform(position, rotation));
}
/**
* Does an in-place rigid transformation of a Hand.
*
@ -79,6 +90,17 @@ namespace Leap
return new Hand().CopyFrom(hand).Transform(transform);
}
/**
* Returns a new hand that is a copy of a hand, with an additional rigid
* transformation applied to it.
*
* @param transform The transformation to be applied to the copied hand.
*/
public static Hand TransformedCopy(this Hand hand, Vector3 position, Quaternion rotation)
{
return new Hand().CopyFrom(hand).Transform(new LeapTransform(position, rotation));
}
/**
* Does an in-place rigid transformation of a Finger.
*

View file

@ -1,482 +1,482 @@
ULTRALEAP TRACKING SDK AGREEMENT
Updated: 22 March 2022
Permitted uses
This SDK Agreement (“Agreement”) covers use of the Ultraleap hand tracking SDK (the “SDK”) by
individuals and businesses for the following purposes:
1. Your personal, non-commercial use (for the avoidance of doubt, excluding use for the design or
manufacture of a commercial or distributable product (e.g in design studios)); or
2. Commercial use for the development and sale consumer facing games, made available for sale to
be purchased by consumers for personal use either at retail or through app stores (excluding,
without limitation, location-based entertainment and arcade applications); or
3. Demonstration of your application to internal and external stakeholders and customers where
there is no transaction, no sale of tickets specifically for the application, or any other form of
compensation for you or your organisation,
but in all cases excluding applications relating to the following: (a) the production of or trade in tobacco,
alcoholic beverages, and related products, (b) the production or trade in weapons of any kind or any
military applications, (c) casinos, gambling and equivalent enterprises, (d) human cloning, human
embryos, or stem cells, or (e) nuclear energy.
Any other uses, or applications using third party hardware are “Specialised Applications” and will require
a separate license agreement. Please contact Ultraleap info@ultraleap.com for more information.
In each case, the SDK may only be used with Ultraleap Hardware and Ultraleap Software.
1. Parties
1.1. This Agreement is made between the individual or entity (“you” or the “Developer”) that accepts
it and Ultraleap Limited (“Ultraleap”). You accept this Agreement by (a) accepting it on download
of the SDK, or (b) if you use or access the SDK or any part of the SDK. Your entry into this
Agreement also binds your authorized users, and your company or organisation.
1.2. If you do not agree to the terms of this Agreement you must not use the SDK.
1.3. Capitalized terms bear the meanings given in the “Definitions” section of this Agreement.
1.4. This Agreement incorporates the terms of the Ultraleap Hand Tracking End User License
Agreement (“EULA”), which is available at https://developer.leapmotion.com/end-user-license-
agreement or from Ultraleap on request. In the event of a conflict between these terms and the
EULA, these terms will prevail.
2. License
Development License
2.1. Conditional on your compliance with the terms and conditions of this Agreement, Ultraleap
hereby grants you a limited, non-exclusive, personal, revocable, non-sublicensable, and non-
transferable license to:
2.1.1. install and use a reasonable number of copies of the SDK on computers owned or
controlled by you for the purpose of developing and testing applications that (a) are not
Specialised Applications and (b) are intended for use solely in connection with Ultraleap
Hardware and Ultraleap Software (each being an “Ultraleap Enabled Application”); and
2.1.2. modify and incorporate into your Ultraleap Enabled Application any sample code
provided in the SDK.
Distribution License
2.2. Conditional on your compliance with the terms and conditions of this Agreement, Ultraleap
hereby grants you a limited, non-exclusive, personal, revocable, non-transferrable license of
Ultraleaps intellectual property rights to the extent necessary to:
2.2.1. copy and distribute (or have copied and distributed) the Ultraleap Redistributables,
solely as compiled with, incorporated into, or packaged with, your Ultraleap Enabled
Application; and
2.2.2. to make (but not have made), use, sell, offer for sale, and import your Ultraleap Enabled
Application.
3. Restrictions
3.1. The license granted to you in section 2.1 and section 2.2 is subject to the following restrictions,
as well as others listed in this Agreement:
3.1.1. Except as expressly permitted in section 2.1, (a) you may not publish, distribute, or copy
the SDK, and (b) you may not modify or create derivative works of the SDK;
3.1.2. Except as expressly permitted in section 2.2, you may not, and may not allow any third
party, directly or indirectly, to publish, post, or otherwise make available, the Ultraleap
Redistributables;
3.1.3. You may not, and may not enable others to, distributed the Non-Redistributable
Materials;
3.1.4. You may use the SDK solely in connection with Ultraleap Hardware and/or Ultraleap
Software;
3.1.5. You may not use the SDK to create, or aid in the creation, directly or indirectly, of any
software or hardware which provides hand tracking functionality or which is otherwise
substantially similar to the features or functionality of Ultraleap products;
3.1.6. You may not, and may not enable others to, directly or indirectly, reverse engineer,
decompile, disassemble, or otherwise attempt to reconstruct, identify, or discover any
source code, underlying ideas, techniques, or algorithms in the Ultraleap Software, the
Ultraleap Hardware, or any software which forms part of the SDK, nor attempt to
circumvent any related security measures (except as and only to the extent any
foregoing restriction is prohibited by applicable law notwithstanding the foregoing
restriction, or to the extent as may be permitted by licensing terms governing the use of
any open source software components or sample code contained within the SDK;
3.1.7. You may not remove, obscure, or alter any proprietary rights or confidentiality notices
within the SDK or any software, documentation, or other materials in it or supplied with
it;
3.1.8. You must not allow the Ultraleap Software or SDK to fall under the terms of any license
which would obligate you or Ultraleap to make available or publish any part of the
Ultraleap Software or SDK.
3.1.9. You may not create Ultraleap Enabled Applications or other software that prevent or
degrade the interaction of applications developed by others with the Ultraleap Software;
3.1.10. You may not represent functionality provided by any Ultraleap hardware or software as
your technology or the technology of any third party. For example (without limitation)
you may not describe any application, technology, or feature developed or distributed
by you that incorporates Ultraleap technology as your gesture or touchless control
technology without providing attribution to Ultraleap; and
3.1.11. You may not allow your Ultraleap Enabled Application to be used for a High Risk Use.
4. Updates
4.1. The terms of this Agreement will apply to any Updates which Ultraleap (in its sole discretion)
makes available to you. You agree that Updates may require you to change or update your
Ultraleap Enabled Application, and may affect your ability to use, access, or interact with the
Ultraleap Software, the Ultraleap Hardware, and/or the SDK. You are solely responsible for
turning off any auto-update functionality of the Ultraleap Software.
5. Trademarks and Marketing
5.1. Conditioned upon compliance with the terms and conditions of this Agreement, Ultraleap grants
you a limited, non-exclusive, personal, license to reproduce and use Ultraleap trademarks solely
to (a) mark the Ultraleap Enabled Application, (b) produce and make available related collateral,
and (c) to promote and market your Ultraleap Enabled Application, in each case solely in
accordance with the Ultraleap trademark guidelines that Ultraleap may provide to you from time
to time.
5.2. For so long as Ultraleap technology is included with the Ultraleap Enabled Application, you must
identify on the packaging of the Ultraleap Enabled Application, the loading screen and start-up
messages for the Ultraleap Enabled Application, and list on your website and marketing collateral
(in each case, where applicable), as prominently as other listed features and functionality, that
Ultraleap technology is included with the Ultraleap Enabled Application, in accordance with the
Ultraleap trademark guidelines that Ultraleap may provide to you from time to time. All
references to Ultraleap or Ultraleap Technology will be subject to Ultraleaps prior approval,
which will not be unreasonably withheld.
5.3. Ultraleap may at its option mention you and your products using Ultraleap technology in
Ultraleaps press releases, press briefings, social media accounts, and/or website, and may use
your trademarks for such purpose. You grant to Ultraleap and its affiliates a non-exclusive,
worldwide and royalty-free limited license to use, reproduce, display, perform, publish and
distribute screenshots, elements, assets, photographic, graphic or video reproductions or
fragments of your Ultraleap Enabled Application in any medium or media, solely for purposes of
promotion of your Ultraleap Enabled Application or of Ultraleap and its technology and business.
The rights set out in this section 5.3 will survive termination of this Agreement in respect of
materials already in existence as at the date of termination.
6. EULA and Other Licenses
6.1. Example code made publicly available by Ultraleap on its developer web site may be provided
subject to the Apache 2.0 license, this Agreement, or other licenses, as specified in the notice or
readme files distributed with the example or in related documentation. The SDK may otherwise
include software or other materials that are provided under a separate license agreement, and
that separate license will govern the use of such software or other materials in the event of a
conflict with this Agreement. Any such separate license agreement may be indicated in the
license, notice, or readme files distributed with the applicable software or other materials or in
related documentation.
6.2. You must either require end users of your Ultraleap Enabled Application to affirmatively agree to
the Ultraleap EULA, or require its End Users to affirmatively agree to your own end user license
agreement that protects Ultraleap at least as much as the Ultraleap EULA.
7. High Risk Uses and Waiver
7.1. Notwithstanding anything in this Agreement, you are not licensed to, and you agree not to, use,
copy, sell, offer for sale, or distribute the SDK, Ultraleap Hardware, Ultraleap Software or
Ultraleap Redistributables (whether compiled with, incorporated into, or packaged with your
Ultraleap Enabled Application or otherwise), for or in connection with uses where failure or fault
of the Ultraleap Hardware, Ultraleap Software, Ultraleap Redistributables or your Ultraleap
Enabled Application could lead to death or serious bodily injury of any person, or to severe
physical or environmental damage (“High Risk Use”). Any such use is strictly prohibited.
7.2. You acknowledge the SDK may allow you to develop Ultraleap Enabled Applications that enable
the control of motorized or mechanical equipment, or other systems, machines or devices. If you
elect to use the SDK in such a way, you must take steps to design and test your Ultraleap Enabled
Applications to ensure that your Ultraleap Enabled Applications do not present risks of personal
injury or death, property damage, or other losses. The Ultraleap Hardware, the Ultraleap
Software, the Ultraleap Redistributables and other software in the SDK may not always function
as intended. You must design your Ultraleap Enabled Applications so that any failure of Ultraleap
Technology and/or such other software as Ultraleap may make available from time to time does
not cause personal injury or death, property damage, or other losses. If you choose to use the
SDK, (i) you assume all risk that use of the Ultraleap Technology and/or such other software by
you or by any others causes any harm or loss, including to the end users of your Ultraleap
Enabled Applications or to third parties, (ii) you hereby waive, on behalf of yourself and your
Authorized Users, all claims against Ultraleap and its affiliates related to such use, harm or loss
(including, but not limited to, any claim that Ultraleap Technology or such other software is
defective), and (iii) you agree to hold Ultraleap and its affiliates harmless from such claims.
8. Confidentiality and Data Protection
8.1. Beta Software etc. Obligations. You acknowledge and agree that Ultraleap may share alpha or
beta software or hardware with you that it identifies as non-public. If so, you agree not to
disclose such software or hardware to others without the prior written consent of Ultraleap
until the time, if any, it is made public by Ultraleap, and to use such software or hardware only
as expressly permitted by Ultraleap. Without limitation to the foregoing, the distribution license
set out in section 2.2 shall not apply to any alpha or beta software which may be shared with
you.
8.2. Your Information. Ultraleap may collect personal information provided by you or your
Authorized Users to Ultraleap or any group company of Ultraleap in connection with the SDK,
and may collect other information from you or your Authorized Users, including technical, non-
personally identifiable and/or aggregated information such as usage statistics, hardware
configuration, problem / fault data, IP addresses, version number of the SDK, information about
which tools and/or services in the SDK are being used and how they are being used, and any
other information described in Ultraleaps privacy policy, currently available at
https://www.ultraleap.com/privacy-policy/. Ultraleap may use the information collected to
facilitate the provision of Updates and other services to you, to verify compliance with, and
enforce, the terms of this Agreement, to improve the SDK and Ultraleaps other products, and
for any other purposes set out in Ultraleaps privacy policy (these uses, collectively, are
“Permitted Uses”). The information collected may be transferred to, stored, and processed in a
destination outside the European Economic Area, including (without limitation) by our staff in
the USA, China, Japan, and Hong Kong. By submitting information about you and/or your
Authorized Users to Ultraleap through your access and use of the SDK, you consent to
Ultraleaps collection and use of the information for the Permitted Uses and represent that you
have obtained all consents and permits necessary under applicable law to disclose your
Authorized Users information to Ultraleap for the Permitted Uses. You further agree that
Ultraleap may provide any information collected under this Section 8.2, including your or your
Authorized Users user name, IP address or other identifying information to law enforcement
authorities or as required by applicable law or regulation.
9. Ownership and Feedback
9.1. As between you and Ultraleap, Ultraleap owns all right, title, and interest, including all
intellectual property rights, in and to the SDK, the Ultraleap Software, Ultraleap Hardware, the
Ultraleap Redistributables, and all documentation associated with the foregoing, other than any
third party software or materials incorporated into the SDK. You agree not to contest Ultraleaps
ownership of any of the foregoing.
9.2. Subject to Section 9.1, Ultraleap agrees that it obtains no right, title, or interest from you (or
your licensors) under this Agreement in or to your Ultraleap Enabled Applications, including any
intellectual property rights which subsist in those Ultraleap Enabled Applications.
9.3. Feedback. You may (but are not required to) provide feedback, comments, and suggestions
(collectively “Feedback”) to Ultraleap. You hereby grant to Ultraleap a non-exclusive, perpetual,
irrevocable, paid-up, transferrable, sub-licensable, worldwide license under all intellectual
property rights covering such Feedback to use, disclose, and exploit all such Feedback for any
purpose.
10. Your Obligations and Warranties
In addition to your other obligations under this Agreement, you warrant and agree that:
10.1. you are at least 18 years of age and have the right and authority to enter into this Agreement on
your own behalf and that of your Authorized Users. If you are entering into this Agreement on
behalf of your company or organization, you warrant that you have the right and authority to
legally bind your company or organization and its Authorized Users;
10.2. you will use the SDK only in accordance with all accompanying documentation, and in the
manner expressly permitted by this Agreement; and
10.3. your use of the SDK, and the marketing, sales and distribution of your Ultraleap Enabled
Application, will be in compliance with all applicable laws and regulations and all UK, U.S. and
local or foreign export and re-export restrictions applicable to the technology and
documentation provided under this Agreement (including privacy and data security laws and
regulations), and you will not develop any Ultraleap Enabled Application which would commit or
facilitate the commission of a crime, or other tortious, unlawful, or illegal act.
11. Agreement and Development Program
11.1. We reserve the right to change this Agreement, the SDK or the Ultraleap development and
licensing program at any time in our discretion. Ultraleap may require that you either accept
and agree to the new terms of this Agreement, or, if you do not agree to the new terms, cease
or terminate your use of the SDK. Your continued use of the SDK after changes to this
Agreement take effect will constitute your acceptance of the changes. If you do not agree to a
change, you must stop using the SDK and terminate this Agreement. Any termination of this
Agreement by you under this Section 11 (and only this Section 11) will not affect your right,
subject to your continued compliance with your obligations under this Agreement, to continue
to distribute versions of your Ultraleap Enabled Application created and first distributed before
termination, and will not affect the right of your End Users to continue using such versions of
your Ultraleap Enabled Application, both of which rights will survive termination.
12. Term and Termination
12.1. Term. This Agreement will continue to apply until terminated by either you or Ultraleap as set
out below.
12.2. Termination by You. If you want to terminate this Agreement, you may terminate it by
uninstalling and destroying all copies of the SDK that are in the possession, custody or control of
you, your Authorized Users and your organization.
12.3. Termination by Ultraleap. Ultraleap may at any time, terminate this Agreement with you for
any reason or for no reason in Ultraleaps sole discretion, including as a result of non-
compliance by you with the restrictions in in this Agreement, or for other reasons.
12.4. Effect of Termination. Upon termination of this Agreement, all rights granted to you under this
Agreement will immediately terminate and you must immediately cease all use and destroy all
copies of the SDK in your and your Authorized Users possession, custody or control, and, except
as specifically set out in Section 11, cease your distribution of Ultraleap Enabled Applications.
Sections 3, 8.1, 8.2, 9, 12.4, 14-16, and 17, will survive termination of this Agreement.
Termination of this Agreement will not affect the right of your End Users who have downloaded
your Ultraleap Enabled Application prior to termination to continue using it.
13. Indemnification.
13.1. You agree to indemnify, hold harmless and, at Ultraleaps option, defend Ultraleap and its
affiliates and their respective officers, directors, employees, agents, and representatives
harmless from any and all judgments, awards, settlements, liabilities, damages, costs, penalties,
fines and other expenses (including court costs and reasonable attorneys fees) incurred by
them arising out of or relating to any third party claim (a) with respect to your Ultraleap Enabled
Application, including products liability, privacy, or intellectual property infringement claims, or
(b) based upon your negligence or wilful misconduct or any breach or alleged breach of your
representations, warranties, and covenants under this Agreement. In no event may you enter
into any settlement or like agreement with a third party that affects Ultraleaps rights or binds
Ultraleap or its affiliates in any way, without the prior written consent of Ultraleap.
14. Warranty Disclaimer.
14.1. THE SDK, THE ULTRALEAP SOFTWARE AND THE ULTRALEAP REDISTRIBUTABLES ARE PROVIDED
"AS IS" WITHOUT WARRANTY OF ANY KIND. ULTRALEAP, ON BEHALF OF ITSELF AND ITS
SUPPLIERS, HEREBY DISCLAIMS ALL REPRESENTATIONS, PROMISES, OR WARRANTIES, WHETHER
EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT TO THE SDK, THE ULTRALEAP
SOFTWARE AND THE ULTRALEAP REDISTRIBUTABLES, INCLUDING THEIR CONDITION,
AVAILABILITY, OR THE EXISTENCE OF ANY LATENT DEFECTS, AND ULTRALEAP SPECIFICALLY
DISCLAIMS ALL IMPLIED WARRANTIES OF MERCHANTABILITY, TITLE, NONINFRINGEMENT,
SUITABILITY, AND FITNESS FOR ANY PURPOSE. ULTRALEAP DOES NOT WARRANT THAT THE SDK,
THE ULTRALEAP SOFTWARE OR THE ULTALEAP REDISTRIBUTABLES WILL BE ERROR-FREE OR
THAT THEY WILL WORK WITHOUT INTERRUPTION.
15. Limitation of Liability.
15.1. ULTRALEAP SHALL NOT IN ANY CIRCUMSTANCES WHATEVER BE LIABLE TO YOU, WHETHER IN
CONTRACT, TORT (INCLUDING NEGLIGENCE), BREACH OF STATUTORY DUTY, OR OTHERWISE,
ARISING UNDER OR IN CONNECTION WITH THE AGREEMENT FOR:
15.1.1. LOSS OF PROFITS, SALES, BUSINESS, OR REVENUE;
15.1.2. BUSINESS INTERRUPTION;
15.1.3. LOSS OF ANTICIPATED SAVINGS;
15.1.4. LOSS OR CORRUPTION OF DATA OR INFORMATION;
15.1.5. LOSS OF BUSINESS OPPORTUNITY, GOODWILL OR REPUTATION; OR
15.1.6. ANY INDIRECT OR CONSEQUENTIAL LOSS OR DAMAGE.
15.2. OTHER THAN THE LOSSES SET OUT ABOVE (FOR WHICH ULTRALEAP IS NOT LIABLE),
ULTRALEAPS MAXIMUM AGGREGATE LIABILITY UNDER OR IN CONNECTION WITH THE
AGREEMENT WHETHER IN CONTRACT, TORT (INCLUDING NEGLIGENCE), BREACH OF STATUTORY
DUTY, OR OTHERWISE, SHALL IN ALL CIRCUMSTANCES BE LIMITED TO $1,000 (ONE THOUSAND
US DOLLARS). THIS MAXIMUM CAP DOES NOT APPLY TO DEATH OR PERSONAL INJURY
RESULTING FROM ULTRALEAP'S NEGLIGENCE; FRAUD OR FRAUDULENT MISREPRESENTATION;
OR ANY OTHER LIABILITY THAT CANNOT BE EXCLUDED OR LIMITED BY APPLICABLE LAW.
15.3. THE AGREEMENT SETS OUT THE FULL EXTENT OF ULTRALEAPS OBLIGATIONS AND LIABILITIES IN
RESPECT OF THE SUPPLY OF THE ULTRALEAP DEVICES, DELIVERABLES AND SOFTWARE. EXCEPT
AS EXPRESSLY STATED IN THE AGREEMENT, THERE ARE NO CONDITIONS, WARRANTIES,
REPRESENTATIONS OR OTHER TERMS, EXPRESS OR IMPLIED, THAT ARE BINDING ON
ULTRALEAP. ANY CONDITION, WARRANTY, REPRESENTATION OR OTHER TERM CONCERNING
THE SUPPLY OF THE ULTRALEAP HARDWARE, ULTRALEAP SOFTWARE, THE SDK, THE ULTRALEAP
REDISTRIBUTABLES, OR ANY OTHER ULTRALEAP TECHNOLOGY WHICH MIGHT OTHERWISE BE
IMPLIED INTO, OR INCORPORATED IN THE AGREEMENT WHETHER BY STATUTE, COMMON LAW
OR OTHERWISE, INCLUDING ANY WARRANTY OR CONDITION OF MERCHANTABILITY OR FITNESS
FOR A PARTICULAR PURPOSE, IS EXCLUDED TO THE FULLEST EXTENT PERMITTED BY LAW. THESE
LIMITATIONS WILL APPLY NOTWITHSTANDING ANY FAILURE OF ESSENTIAL PURPOSE OF ANY
LIMITED REMEDY. THE PARTIES AGREE THAT THE FOREGOING LIMITATIONS REPRESENT A
REASONABLE ALLOCATION OF RISK UNDER THIS AGREEMENT.
16. Miscellaneous.
16.1. Assignment. You may not assign this Agreement without the prior written consent of Ultraleap.
Any assignment without such consent is void and of no effect. Ultraleap may assign this
Agreement without your consent in connection with (a) a merger or consolidation of Ultraleap,
(b) a sale or assignment of substantially all its assets, or (c) any other transaction which results
in another entity or person owning substantially all of the assets of Ultraleap, or (d) to any of its
affiliates. In the event of a permitted assignment, this Agreement will inure to the benefit of and
be binding upon the parties and their respective successors and permitted assigns.
16.2. Waiver; Severability. The failure of the other party to enforce any rights under this Agreement
will not be deemed a waiver of any rights. The rights and remedies of the parties in this
Agreement are not exclusive and are in addition to any other rights and remedies provided by
law. If any provision of this Agreement is held by a court of competent jurisdiction to be
contrary to law, the remaining provisions of this Agreement will remain in full force and effect.
16.3. Reservation. All licenses not expressly granted in this Agreement are reserved and no other
licenses, immunity or rights, express or implied, are granted by Ultraleap, by implication,
estoppel, or otherwise. The software in the SDK is licensed, not sold.
16.4. Export Restrictions. The Ultraleap Software is subject to United States and UK export laws and
regulations. You must comply with all domestic and international export laws and regulations
that apply to the Ultraleap Software. These laws include restrictions on destinations, end users,
and end use.
16.5. Governing Law and Jurisdiction. This Agreement will be exclusively governed by and construed
under the laws of the England and Wales, without reference to or application of rules governing
choice of laws. All disputes arising out of or related to this Agreement will be subject to the
exclusive jurisdiction of courts of England and you hereby consent to such jurisdiction. However,
Ultraleap may apply to any court or tribunal worldwide, including but not limited to those
having jurisdiction over you or your Authorized Users, to seek injunctive relief.
16.6. Relationship of the Parties. This Agreement does not create any agency, partnership, or joint
venture relationship between Ultraleap and you. This Agreement is for the sole benefit of
Ultraleap and you (and indemnified parties), and no other persons will have any right or remedy
under this Agreement.
16.7. Notices. The address for notice to Ultraleap under this Agreement is:
Ultraleap Limited
The West Wing
Glass Wharf
Bristol, BS2 0EL
United Kingdom
Ultraleap may provide you notice under this Agreement by email or other electronic
communication or by posting communications to its development community on the Ultraleap
developer portal. You consent to receive such notices in any of the foregoing manners and
agree that any such notices by Ultraleap will satisfy any legal communication requirements.
16.8. Entire Agreement. This Agreement is the entire understanding of the parties with respect to its
subject matter and supersedes any previous or contemporaneous communications, whether
oral or written with respect to such subject matter.
17. Definitions
Whenever capitalized in this Agreement:
“Authorized Users” means your employees and contractors, members of your organization or, if you
are an educational institution, your faculty, staff and registered students, who (a) have a
demonstrable need to know or use the SDK in order to develop and test Ultraleap Enabled
Applications on your behalf and (b) each have written and binding agreements with you to protect
against the unauthorized use and disclosure of the SDK consistent with the terms and conditions of
this Agreement. Authorized Users do not include End Users.
“End User” means your end user customer(s) or licensee(s).
“Non-Redistributable Materials” means the Ultraleap Software, and any other code, files or
materials that are not specifically designated in the SDK as made available for incorporation into
Ultraleap Enabled Applications or that are specifically designated in the SDK as not subject to
distribution.
“SDK” means, collectively, the Ultraleap Redistributables, tools, APIs, sample code, software,
documentation, other materials and any updates to the foregoing that may be provided or made
available to you by Ultraleap in connection with this Agreement, via the Ultraleap developer portal or
otherwise for use in connection with the Ultraleap development program to develop Ultraleap
Enabled Applications.
“Specialized Application” means an Ultraleap Enabled Application which does not fall within the
permitted uses set out in this Agreement.
“Ultraleap” “we” or “us” means Ultraleap Limited, a company registered in England with company
number 08781720, with a principal place of business at The West Wing, Glass Wharf, Bristol, BS2 0EL,
United Kingdom.
“Ultraleap Hardware” means the Leap Motion Controller, Stereo IR 170, Stereo IR 170 EK or Ultraleap 3Di each being a device that
detects and reads movements within a 3-D interaction space to precisely interact with and control
software on a computing device, or an Ultraleap-authorized embedded optical module.
“Ultraleap Redistributables” means any .lib code, .dll files, .so files, sample code, or other materials
we specifically designate in the SDK as made available for incorporation into or distribution with
Ultraleap Enabled Applications.
“Ultraleap Software” means the Ultraleap core services application and related applications that
interact with Ultraleap Hardware and an operating system to make motion control functionality
available to Ultraleap Enabled Applications, and includes any Updates thereto.
“Updates” means updates, upgrades, modifications, enhancements, revisions, new releases or new
versions to the SDK that Ultraleap may make available to you in connection with this Agreement.
Other capitalized terms used in this Agreement have the meaning given them elsewhere in this
Agreement.
18. Supplemental Terms Applicable to the Use of Image API
18.1. Purpose. You and/or your Ultraleap Enabled Application may access the Image API and use
image data available through the Image API only for the purpose of developing and testing
Ultraleap Enabled Applications, and only for use with Ultraleap Hardware. You may not use the
Image API to develop or aid development of competing motion tracking hardware or software.
Any use of the Image API is subject to the terms of the Agreement.
18.2. Data Protection.
18.2.1. If you or your Ultraleap Enabled Application collects, uploads, stores, transmits, or
shares images, videos, or other personal information available through the Image API,
either through or in connection with your Ultraleap Enabled Application, you must
expressly provide users with your privacy policy and adhere to it.
18.2.2. You must obtain specific, opt-in consent from the user for any use that is beyond the
limited and express purpose of your Ultraleap Enabled Application.
18.2.3. You and your Ultraleap Enabled Application must use and store information collected
form users securely and only for as long as it is required.
18.2.4. You agree that you will protect the privacy and legal rights of users, and will comply with
all applicable criminal, civil, and statutory privacy and data protection laws and
regulations.
ULTRALEAP TRACKING SDK AGREEMENT
Updated: 22 March 2022
Permitted uses
This SDK Agreement (“Agreement”) covers use of the Ultraleap hand tracking SDK (the “SDK”) by
individuals and businesses for the following purposes:
1. Your personal, non-commercial use (for the avoidance of doubt, excluding use for the design or
manufacture of a commercial or distributable product (e.g in design studios)); or
2. Commercial use for the development and sale consumer facing games, made available for sale to
be purchased by consumers for personal use either at retail or through app stores (excluding,
without limitation, location-based entertainment and arcade applications); or
3. Demonstration of your application to internal and external stakeholders and customers where
there is no transaction, no sale of tickets specifically for the application, or any other form of
compensation for you or your organisation,
but in all cases excluding applications relating to the following: (a) the production of or trade in tobacco,
alcoholic beverages, and related products, (b) the production or trade in weapons of any kind or any
military applications, (c) casinos, gambling and equivalent enterprises, (d) human cloning, human
embryos, or stem cells, or (e) nuclear energy.
Any other uses, or applications using third party hardware are “Specialised Applications” and will require
a separate license agreement. Please contact Ultraleap info@ultraleap.com for more information.
In each case, the SDK may only be used with Ultraleap Hardware and Ultraleap Software.
1. Parties
1.1. This Agreement is made between the individual or entity (“you” or the “Developer”) that accepts
it and Ultraleap Limited (“Ultraleap”). You accept this Agreement by (a) accepting it on download
of the SDK, or (b) if you use or access the SDK or any part of the SDK. Your entry into this
Agreement also binds your authorized users, and your company or organisation.
1.2. If you do not agree to the terms of this Agreement you must not use the SDK.
1.3. Capitalized terms bear the meanings given in the “Definitions” section of this Agreement.
1.4. This Agreement incorporates the terms of the Ultraleap Hand Tracking End User License
Agreement (“EULA”), which is available at https://developer.leapmotion.com/end-user-license-
agreement or from Ultraleap on request. In the event of a conflict between these terms and the
EULA, these terms will prevail.
2. License
Development License
2.1. Conditional on your compliance with the terms and conditions of this Agreement, Ultraleap
hereby grants you a limited, non-exclusive, personal, revocable, non-sublicensable, and non-
transferable license to:
2.1.1. install and use a reasonable number of copies of the SDK on computers owned or
controlled by you for the purpose of developing and testing applications that (a) are not
Specialised Applications and (b) are intended for use solely in connection with Ultraleap
Hardware and Ultraleap Software (each being an “Ultraleap Enabled Application”); and
2.1.2. modify and incorporate into your Ultraleap Enabled Application any sample code
provided in the SDK.
Distribution License
2.2. Conditional on your compliance with the terms and conditions of this Agreement, Ultraleap
hereby grants you a limited, non-exclusive, personal, revocable, non-transferrable license of
Ultraleaps intellectual property rights to the extent necessary to:
2.2.1. copy and distribute (or have copied and distributed) the Ultraleap Redistributables,
solely as compiled with, incorporated into, or packaged with, your Ultraleap Enabled
Application; and
2.2.2. to make (but not have made), use, sell, offer for sale, and import your Ultraleap Enabled
Application.
3. Restrictions
3.1. The license granted to you in section 2.1 and section 2.2 is subject to the following restrictions,
as well as others listed in this Agreement:
3.1.1. Except as expressly permitted in section 2.1, (a) you may not publish, distribute, or copy
the SDK, and (b) you may not modify or create derivative works of the SDK;
3.1.2. Except as expressly permitted in section 2.2, you may not, and may not allow any third
party, directly or indirectly, to publish, post, or otherwise make available, the Ultraleap
Redistributables;
3.1.3. You may not, and may not enable others to, distributed the Non-Redistributable
Materials;
3.1.4. You may use the SDK solely in connection with Ultraleap Hardware and/or Ultraleap
Software;
3.1.5. You may not use the SDK to create, or aid in the creation, directly or indirectly, of any
software or hardware which provides hand tracking functionality or which is otherwise
substantially similar to the features or functionality of Ultraleap products;
3.1.6. You may not, and may not enable others to, directly or indirectly, reverse engineer,
decompile, disassemble, or otherwise attempt to reconstruct, identify, or discover any
source code, underlying ideas, techniques, or algorithms in the Ultraleap Software, the
Ultraleap Hardware, or any software which forms part of the SDK, nor attempt to
circumvent any related security measures (except as and only to the extent any
foregoing restriction is prohibited by applicable law notwithstanding the foregoing
restriction, or to the extent as may be permitted by licensing terms governing the use of
any open source software components or sample code contained within the SDK;
3.1.7. You may not remove, obscure, or alter any proprietary rights or confidentiality notices
within the SDK or any software, documentation, or other materials in it or supplied with
it;
3.1.8. You must not allow the Ultraleap Software or SDK to fall under the terms of any license
which would obligate you or Ultraleap to make available or publish any part of the
Ultraleap Software or SDK.
3.1.9. You may not create Ultraleap Enabled Applications or other software that prevent or
degrade the interaction of applications developed by others with the Ultraleap Software;
3.1.10. You may not represent functionality provided by any Ultraleap hardware or software as
your technology or the technology of any third party. For example (without limitation)
you may not describe any application, technology, or feature developed or distributed
by you that incorporates Ultraleap technology as your gesture or touchless control
technology without providing attribution to Ultraleap; and
3.1.11. You may not allow your Ultraleap Enabled Application to be used for a High Risk Use.
4. Updates
4.1. The terms of this Agreement will apply to any Updates which Ultraleap (in its sole discretion)
makes available to you. You agree that Updates may require you to change or update your
Ultraleap Enabled Application, and may affect your ability to use, access, or interact with the
Ultraleap Software, the Ultraleap Hardware, and/or the SDK. You are solely responsible for
turning off any auto-update functionality of the Ultraleap Software.
5. Trademarks and Marketing
5.1. Conditioned upon compliance with the terms and conditions of this Agreement, Ultraleap grants
you a limited, non-exclusive, personal, license to reproduce and use Ultraleap trademarks solely
to (a) mark the Ultraleap Enabled Application, (b) produce and make available related collateral,
and (c) to promote and market your Ultraleap Enabled Application, in each case solely in
accordance with the Ultraleap trademark guidelines that Ultraleap may provide to you from time
to time.
5.2. For so long as Ultraleap technology is included with the Ultraleap Enabled Application, you must
identify on the packaging of the Ultraleap Enabled Application, the loading screen and start-up
messages for the Ultraleap Enabled Application, and list on your website and marketing collateral
(in each case, where applicable), as prominently as other listed features and functionality, that
Ultraleap technology is included with the Ultraleap Enabled Application, in accordance with the
Ultraleap trademark guidelines that Ultraleap may provide to you from time to time. All
references to Ultraleap or Ultraleap Technology will be subject to Ultraleaps prior approval,
which will not be unreasonably withheld.
5.3. Ultraleap may at its option mention you and your products using Ultraleap technology in
Ultraleaps press releases, press briefings, social media accounts, and/or website, and may use
your trademarks for such purpose. You grant to Ultraleap and its affiliates a non-exclusive,
worldwide and royalty-free limited license to use, reproduce, display, perform, publish and
distribute screenshots, elements, assets, photographic, graphic or video reproductions or
fragments of your Ultraleap Enabled Application in any medium or media, solely for purposes of
promotion of your Ultraleap Enabled Application or of Ultraleap and its technology and business.
The rights set out in this section 5.3 will survive termination of this Agreement in respect of
materials already in existence as at the date of termination.
6. EULA and Other Licenses
6.1. Example code made publicly available by Ultraleap on its developer web site may be provided
subject to the Apache 2.0 license, this Agreement, or other licenses, as specified in the notice or
readme files distributed with the example or in related documentation. The SDK may otherwise
include software or other materials that are provided under a separate license agreement, and
that separate license will govern the use of such software or other materials in the event of a
conflict with this Agreement. Any such separate license agreement may be indicated in the
license, notice, or readme files distributed with the applicable software or other materials or in
related documentation.
6.2. You must either require end users of your Ultraleap Enabled Application to affirmatively agree to
the Ultraleap EULA, or require its End Users to affirmatively agree to your own end user license
agreement that protects Ultraleap at least as much as the Ultraleap EULA.
7. High Risk Uses and Waiver
7.1. Notwithstanding anything in this Agreement, you are not licensed to, and you agree not to, use,
copy, sell, offer for sale, or distribute the SDK, Ultraleap Hardware, Ultraleap Software or
Ultraleap Redistributables (whether compiled with, incorporated into, or packaged with your
Ultraleap Enabled Application or otherwise), for or in connection with uses where failure or fault
of the Ultraleap Hardware, Ultraleap Software, Ultraleap Redistributables or your Ultraleap
Enabled Application could lead to death or serious bodily injury of any person, or to severe
physical or environmental damage (“High Risk Use”). Any such use is strictly prohibited.
7.2. You acknowledge the SDK may allow you to develop Ultraleap Enabled Applications that enable
the control of motorized or mechanical equipment, or other systems, machines or devices. If you
elect to use the SDK in such a way, you must take steps to design and test your Ultraleap Enabled
Applications to ensure that your Ultraleap Enabled Applications do not present risks of personal
injury or death, property damage, or other losses. The Ultraleap Hardware, the Ultraleap
Software, the Ultraleap Redistributables and other software in the SDK may not always function
as intended. You must design your Ultraleap Enabled Applications so that any failure of Ultraleap
Technology and/or such other software as Ultraleap may make available from time to time does
not cause personal injury or death, property damage, or other losses. If you choose to use the
SDK, (i) you assume all risk that use of the Ultraleap Technology and/or such other software by
you or by any others causes any harm or loss, including to the end users of your Ultraleap
Enabled Applications or to third parties, (ii) you hereby waive, on behalf of yourself and your
Authorized Users, all claims against Ultraleap and its affiliates related to such use, harm or loss
(including, but not limited to, any claim that Ultraleap Technology or such other software is
defective), and (iii) you agree to hold Ultraleap and its affiliates harmless from such claims.
8. Confidentiality and Data Protection
8.1. Beta Software etc. Obligations. You acknowledge and agree that Ultraleap may share alpha or
beta software or hardware with you that it identifies as non-public. If so, you agree not to
disclose such software or hardware to others without the prior written consent of Ultraleap
until the time, if any, it is made public by Ultraleap, and to use such software or hardware only
as expressly permitted by Ultraleap. Without limitation to the foregoing, the distribution license
set out in section 2.2 shall not apply to any alpha or beta software which may be shared with
you.
8.2. Your Information. Ultraleap may collect personal information provided by you or your
Authorized Users to Ultraleap or any group company of Ultraleap in connection with the SDK,
and may collect other information from you or your Authorized Users, including technical, non-
personally identifiable and/or aggregated information such as usage statistics, hardware
configuration, problem / fault data, IP addresses, version number of the SDK, information about
which tools and/or services in the SDK are being used and how they are being used, and any
other information described in Ultraleaps privacy policy, currently available at
https://www.ultraleap.com/privacy-policy/. Ultraleap may use the information collected to
facilitate the provision of Updates and other services to you, to verify compliance with, and
enforce, the terms of this Agreement, to improve the SDK and Ultraleaps other products, and
for any other purposes set out in Ultraleaps privacy policy (these uses, collectively, are
“Permitted Uses”). The information collected may be transferred to, stored, and processed in a
destination outside the European Economic Area, including (without limitation) by our staff in
the USA, China, Japan, and Hong Kong. By submitting information about you and/or your
Authorized Users to Ultraleap through your access and use of the SDK, you consent to
Ultraleaps collection and use of the information for the Permitted Uses and represent that you
have obtained all consents and permits necessary under applicable law to disclose your
Authorized Users information to Ultraleap for the Permitted Uses. You further agree that
Ultraleap may provide any information collected under this Section 8.2, including your or your
Authorized Users user name, IP address or other identifying information to law enforcement
authorities or as required by applicable law or regulation.
9. Ownership and Feedback
9.1. As between you and Ultraleap, Ultraleap owns all right, title, and interest, including all
intellectual property rights, in and to the SDK, the Ultraleap Software, Ultraleap Hardware, the
Ultraleap Redistributables, and all documentation associated with the foregoing, other than any
third party software or materials incorporated into the SDK. You agree not to contest Ultraleaps
ownership of any of the foregoing.
9.2. Subject to Section 9.1, Ultraleap agrees that it obtains no right, title, or interest from you (or
your licensors) under this Agreement in or to your Ultraleap Enabled Applications, including any
intellectual property rights which subsist in those Ultraleap Enabled Applications.
9.3. Feedback. You may (but are not required to) provide feedback, comments, and suggestions
(collectively “Feedback”) to Ultraleap. You hereby grant to Ultraleap a non-exclusive, perpetual,
irrevocable, paid-up, transferrable, sub-licensable, worldwide license under all intellectual
property rights covering such Feedback to use, disclose, and exploit all such Feedback for any
purpose.
10. Your Obligations and Warranties
In addition to your other obligations under this Agreement, you warrant and agree that:
10.1. you are at least 18 years of age and have the right and authority to enter into this Agreement on
your own behalf and that of your Authorized Users. If you are entering into this Agreement on
behalf of your company or organization, you warrant that you have the right and authority to
legally bind your company or organization and its Authorized Users;
10.2. you will use the SDK only in accordance with all accompanying documentation, and in the
manner expressly permitted by this Agreement; and
10.3. your use of the SDK, and the marketing, sales and distribution of your Ultraleap Enabled
Application, will be in compliance with all applicable laws and regulations and all UK, U.S. and
local or foreign export and re-export restrictions applicable to the technology and
documentation provided under this Agreement (including privacy and data security laws and
regulations), and you will not develop any Ultraleap Enabled Application which would commit or
facilitate the commission of a crime, or other tortious, unlawful, or illegal act.
11. Agreement and Development Program
11.1. We reserve the right to change this Agreement, the SDK or the Ultraleap development and
licensing program at any time in our discretion. Ultraleap may require that you either accept
and agree to the new terms of this Agreement, or, if you do not agree to the new terms, cease
or terminate your use of the SDK. Your continued use of the SDK after changes to this
Agreement take effect will constitute your acceptance of the changes. If you do not agree to a
change, you must stop using the SDK and terminate this Agreement. Any termination of this
Agreement by you under this Section 11 (and only this Section 11) will not affect your right,
subject to your continued compliance with your obligations under this Agreement, to continue
to distribute versions of your Ultraleap Enabled Application created and first distributed before
termination, and will not affect the right of your End Users to continue using such versions of
your Ultraleap Enabled Application, both of which rights will survive termination.
12. Term and Termination
12.1. Term. This Agreement will continue to apply until terminated by either you or Ultraleap as set
out below.
12.2. Termination by You. If you want to terminate this Agreement, you may terminate it by
uninstalling and destroying all copies of the SDK that are in the possession, custody or control of
you, your Authorized Users and your organization.
12.3. Termination by Ultraleap. Ultraleap may at any time, terminate this Agreement with you for
any reason or for no reason in Ultraleaps sole discretion, including as a result of non-
compliance by you with the restrictions in in this Agreement, or for other reasons.
12.4. Effect of Termination. Upon termination of this Agreement, all rights granted to you under this
Agreement will immediately terminate and you must immediately cease all use and destroy all
copies of the SDK in your and your Authorized Users possession, custody or control, and, except
as specifically set out in Section 11, cease your distribution of Ultraleap Enabled Applications.
Sections 3, 8.1, 8.2, 9, 12.4, 14-16, and 17, will survive termination of this Agreement.
Termination of this Agreement will not affect the right of your End Users who have downloaded
your Ultraleap Enabled Application prior to termination to continue using it.
13. Indemnification.
13.1. You agree to indemnify, hold harmless and, at Ultraleaps option, defend Ultraleap and its
affiliates and their respective officers, directors, employees, agents, and representatives
harmless from any and all judgments, awards, settlements, liabilities, damages, costs, penalties,
fines and other expenses (including court costs and reasonable attorneys fees) incurred by
them arising out of or relating to any third party claim (a) with respect to your Ultraleap Enabled
Application, including products liability, privacy, or intellectual property infringement claims, or
(b) based upon your negligence or wilful misconduct or any breach or alleged breach of your
representations, warranties, and covenants under this Agreement. In no event may you enter
into any settlement or like agreement with a third party that affects Ultraleaps rights or binds
Ultraleap or its affiliates in any way, without the prior written consent of Ultraleap.
14. Warranty Disclaimer.
14.1. THE SDK, THE ULTRALEAP SOFTWARE AND THE ULTRALEAP REDISTRIBUTABLES ARE PROVIDED
"AS IS" WITHOUT WARRANTY OF ANY KIND. ULTRALEAP, ON BEHALF OF ITSELF AND ITS
SUPPLIERS, HEREBY DISCLAIMS ALL REPRESENTATIONS, PROMISES, OR WARRANTIES, WHETHER
EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT TO THE SDK, THE ULTRALEAP
SOFTWARE AND THE ULTRALEAP REDISTRIBUTABLES, INCLUDING THEIR CONDITION,
AVAILABILITY, OR THE EXISTENCE OF ANY LATENT DEFECTS, AND ULTRALEAP SPECIFICALLY
DISCLAIMS ALL IMPLIED WARRANTIES OF MERCHANTABILITY, TITLE, NONINFRINGEMENT,
SUITABILITY, AND FITNESS FOR ANY PURPOSE. ULTRALEAP DOES NOT WARRANT THAT THE SDK,
THE ULTRALEAP SOFTWARE OR THE ULTALEAP REDISTRIBUTABLES WILL BE ERROR-FREE OR
THAT THEY WILL WORK WITHOUT INTERRUPTION.
15. Limitation of Liability.
15.1. ULTRALEAP SHALL NOT IN ANY CIRCUMSTANCES WHATEVER BE LIABLE TO YOU, WHETHER IN
CONTRACT, TORT (INCLUDING NEGLIGENCE), BREACH OF STATUTORY DUTY, OR OTHERWISE,
ARISING UNDER OR IN CONNECTION WITH THE AGREEMENT FOR:
15.1.1. LOSS OF PROFITS, SALES, BUSINESS, OR REVENUE;
15.1.2. BUSINESS INTERRUPTION;
15.1.3. LOSS OF ANTICIPATED SAVINGS;
15.1.4. LOSS OR CORRUPTION OF DATA OR INFORMATION;
15.1.5. LOSS OF BUSINESS OPPORTUNITY, GOODWILL OR REPUTATION; OR
15.1.6. ANY INDIRECT OR CONSEQUENTIAL LOSS OR DAMAGE.
15.2. OTHER THAN THE LOSSES SET OUT ABOVE (FOR WHICH ULTRALEAP IS NOT LIABLE),
ULTRALEAPS MAXIMUM AGGREGATE LIABILITY UNDER OR IN CONNECTION WITH THE
AGREEMENT WHETHER IN CONTRACT, TORT (INCLUDING NEGLIGENCE), BREACH OF STATUTORY
DUTY, OR OTHERWISE, SHALL IN ALL CIRCUMSTANCES BE LIMITED TO $1,000 (ONE THOUSAND
US DOLLARS). THIS MAXIMUM CAP DOES NOT APPLY TO DEATH OR PERSONAL INJURY
RESULTING FROM ULTRALEAP'S NEGLIGENCE; FRAUD OR FRAUDULENT MISREPRESENTATION;
OR ANY OTHER LIABILITY THAT CANNOT BE EXCLUDED OR LIMITED BY APPLICABLE LAW.
15.3. THE AGREEMENT SETS OUT THE FULL EXTENT OF ULTRALEAPS OBLIGATIONS AND LIABILITIES IN
RESPECT OF THE SUPPLY OF THE ULTRALEAP DEVICES, DELIVERABLES AND SOFTWARE. EXCEPT
AS EXPRESSLY STATED IN THE AGREEMENT, THERE ARE NO CONDITIONS, WARRANTIES,
REPRESENTATIONS OR OTHER TERMS, EXPRESS OR IMPLIED, THAT ARE BINDING ON
ULTRALEAP. ANY CONDITION, WARRANTY, REPRESENTATION OR OTHER TERM CONCERNING
THE SUPPLY OF THE ULTRALEAP HARDWARE, ULTRALEAP SOFTWARE, THE SDK, THE ULTRALEAP
REDISTRIBUTABLES, OR ANY OTHER ULTRALEAP TECHNOLOGY WHICH MIGHT OTHERWISE BE
IMPLIED INTO, OR INCORPORATED IN THE AGREEMENT WHETHER BY STATUTE, COMMON LAW
OR OTHERWISE, INCLUDING ANY WARRANTY OR CONDITION OF MERCHANTABILITY OR FITNESS
FOR A PARTICULAR PURPOSE, IS EXCLUDED TO THE FULLEST EXTENT PERMITTED BY LAW. THESE
LIMITATIONS WILL APPLY NOTWITHSTANDING ANY FAILURE OF ESSENTIAL PURPOSE OF ANY
LIMITED REMEDY. THE PARTIES AGREE THAT THE FOREGOING LIMITATIONS REPRESENT A
REASONABLE ALLOCATION OF RISK UNDER THIS AGREEMENT.
16. Miscellaneous.
16.1. Assignment. You may not assign this Agreement without the prior written consent of Ultraleap.
Any assignment without such consent is void and of no effect. Ultraleap may assign this
Agreement without your consent in connection with (a) a merger or consolidation of Ultraleap,
(b) a sale or assignment of substantially all its assets, or (c) any other transaction which results
in another entity or person owning substantially all of the assets of Ultraleap, or (d) to any of its
affiliates. In the event of a permitted assignment, this Agreement will inure to the benefit of and
be binding upon the parties and their respective successors and permitted assigns.
16.2. Waiver; Severability. The failure of the other party to enforce any rights under this Agreement
will not be deemed a waiver of any rights. The rights and remedies of the parties in this
Agreement are not exclusive and are in addition to any other rights and remedies provided by
law. If any provision of this Agreement is held by a court of competent jurisdiction to be
contrary to law, the remaining provisions of this Agreement will remain in full force and effect.
16.3. Reservation. All licenses not expressly granted in this Agreement are reserved and no other
licenses, immunity or rights, express or implied, are granted by Ultraleap, by implication,
estoppel, or otherwise. The software in the SDK is licensed, not sold.
16.4. Export Restrictions. The Ultraleap Software is subject to United States and UK export laws and
regulations. You must comply with all domestic and international export laws and regulations
that apply to the Ultraleap Software. These laws include restrictions on destinations, end users,
and end use.
16.5. Governing Law and Jurisdiction. This Agreement will be exclusively governed by and construed
under the laws of the England and Wales, without reference to or application of rules governing
choice of laws. All disputes arising out of or related to this Agreement will be subject to the
exclusive jurisdiction of courts of England and you hereby consent to such jurisdiction. However,
Ultraleap may apply to any court or tribunal worldwide, including but not limited to those
having jurisdiction over you or your Authorized Users, to seek injunctive relief.
16.6. Relationship of the Parties. This Agreement does not create any agency, partnership, or joint
venture relationship between Ultraleap and you. This Agreement is for the sole benefit of
Ultraleap and you (and indemnified parties), and no other persons will have any right or remedy
under this Agreement.
16.7. Notices. The address for notice to Ultraleap under this Agreement is:
Ultraleap Limited
The West Wing
Glass Wharf
Bristol, BS2 0EL
United Kingdom
Ultraleap may provide you notice under this Agreement by email or other electronic
communication or by posting communications to its development community on the Ultraleap
developer portal. You consent to receive such notices in any of the foregoing manners and
agree that any such notices by Ultraleap will satisfy any legal communication requirements.
16.8. Entire Agreement. This Agreement is the entire understanding of the parties with respect to its
subject matter and supersedes any previous or contemporaneous communications, whether
oral or written with respect to such subject matter.
17. Definitions
Whenever capitalized in this Agreement:
“Authorized Users” means your employees and contractors, members of your organization or, if you
are an educational institution, your faculty, staff and registered students, who (a) have a
demonstrable need to know or use the SDK in order to develop and test Ultraleap Enabled
Applications on your behalf and (b) each have written and binding agreements with you to protect
against the unauthorized use and disclosure of the SDK consistent with the terms and conditions of
this Agreement. Authorized Users do not include End Users.
“End User” means your end user customer(s) or licensee(s).
“Non-Redistributable Materials” means the Ultraleap Software, and any other code, files or
materials that are not specifically designated in the SDK as made available for incorporation into
Ultraleap Enabled Applications or that are specifically designated in the SDK as not subject to
distribution.
“SDK” means, collectively, the Ultraleap Redistributables, tools, APIs, sample code, software,
documentation, other materials and any updates to the foregoing that may be provided or made
available to you by Ultraleap in connection with this Agreement, via the Ultraleap developer portal or
otherwise for use in connection with the Ultraleap development program to develop Ultraleap
Enabled Applications.
“Specialized Application” means an Ultraleap Enabled Application which does not fall within the
permitted uses set out in this Agreement.
“Ultraleap” “we” or “us” means Ultraleap Limited, a company registered in England with company
number 08781720, with a principal place of business at The West Wing, Glass Wharf, Bristol, BS2 0EL,
United Kingdom.
“Ultraleap Hardware” means the Leap Motion Controller, Stereo IR 170, Stereo IR 170 EK or Ultraleap 3Di each being a device that
detects and reads movements within a 3-D interaction space to precisely interact with and control
software on a computing device, or an Ultraleap-authorized embedded optical module.
“Ultraleap Redistributables” means any .lib code, .dll files, .so files, sample code, or other materials
we specifically designate in the SDK as made available for incorporation into or distribution with
Ultraleap Enabled Applications.
“Ultraleap Software” means the Ultraleap core services application and related applications that
interact with Ultraleap Hardware and an operating system to make motion control functionality
available to Ultraleap Enabled Applications, and includes any Updates thereto.
“Updates” means updates, upgrades, modifications, enhancements, revisions, new releases or new
versions to the SDK that Ultraleap may make available to you in connection with this Agreement.
Other capitalized terms used in this Agreement have the meaning given them elsewhere in this
Agreement.
18. Supplemental Terms Applicable to the Use of Image API
18.1. Purpose. You and/or your Ultraleap Enabled Application may access the Image API and use
image data available through the Image API only for the purpose of developing and testing
Ultraleap Enabled Applications, and only for use with Ultraleap Hardware. You may not use the
Image API to develop or aid development of competing motion tracking hardware or software.
Any use of the Image API is subject to the terms of the Agreement.
18.2. Data Protection.
18.2.1. If you or your Ultraleap Enabled Application collects, uploads, stores, transmits, or
shares images, videos, or other personal information available through the Image API,
either through or in connection with your Ultraleap Enabled Application, you must
expressly provide users with your privacy policy and adhere to it.
18.2.2. You must obtain specific, opt-in consent from the user for any use that is beyond the
limited and express purpose of your Ultraleap Enabled Application.
18.2.3. You and your Ultraleap Enabled Application must use and store information collected
form users securely and only for as long as it is required.
18.2.4. You agree that you will protect the privacy and legal rights of users, and will comply with
all applicable criminal, civil, and statutory privacy and data protection laws and
regulations.

View file

@ -1,137 +1,159 @@
# Ultraleap SDK
--------------------------------------------------------------------------------
## Package contents:
LeapSDK
- include
* API headers.
- lib
* dynamic API library and CMake scripts.
- samples
* Various samples demonstrating several different usages.
- LICENSE.md
* Ultraleap Tracking SDK license.
- README.md
* Ultraleap Tracking SDK readme.
- ThirdPartyNotices.md
* Ultraleap Tracking SDK third party licenses.
## Requirements:
1. Running requires
* Ultraleap Tracking Software https://developer.leapmotion.com/get-started/
2. Building Samples requires
* CMake 3.16.3+ (https://cmake.org/)
* Microsoft Visual Studio 15+ (Windows)
* GCC (Linux - tested on v9.4.0)
## Installation:
The LeapSDK is installed with Ultraleap Tracking.
## Usage:
1. For CMake projects
* Ensure LeapSDK is in a directory considered as a prefix by find_package.
(https://cmake.org/cmake/help/v3.16/command/find_package.html)
* Or : directly set LeapSDK_DIR to <install_dir>/LeapSDK/lib/cmake/LeapSDK
* Or : Pass the LeapSDK's path to find_package with the PATHS option.
* call find_package(LeapSDK 5 [PATHS ...]).
* call target_link_libraries(<your project> PUBLIC|PRIVATE LeapSDK::LeapC).
* Ensure LeapC.dll/LeapC.so is in your dynamic library search path.
* A popular option is to add a post-build step that copies it to your project's output directory.
2. For non-CMake projects
* Use a C/C++ compiler such as MSVC, Clang or GCC.
* Add LeapSDK/include to the compiler include search paths.
* Either add a linker reference to LeapC.lib or dynamically load LeapC.dll/LeapC.so.
## Building Samples:
### Windows
1. Open CMake using LeapSDK/samples as the source directory
2. Select a build directory (often LeapSDK/samples/build) to use
3. Configure & Generate CMake with the generator of your choice
4. Open and build the CMake generated project files. For more help, see the CMake documentation.
* An example script would be :
```powershell
$env:BUILD_TYPE = 'Release'
$env:REPOS_BUILD_ROOT = 'C:/build'
$env:REPOS_INSTALL_ROOT = 'C:/Program Files'
cmake -S "C:/Program Files/Ultraleap/LeapSDK/samples" -B $env:REPOS_BUILD_ROOT/$env:BUILD_TYPE/LeapSDK/leapc_example `
-DCMAKE_INSTALL_PREFIX="$env:REPOS_INSTALL_ROOT/leapc_example" `
-DCMAKE_BUILD_TYPE="$env:BUILD_TYPE"
cmake --build $env:REPOS_BUILD_ROOT/$env:BUILD_TYPE/LeapSDK/leapc_example -j --config $env:BUILD_TYPE
```
### x64 Linux
1. Open CMake using /usr/share/doc/ultraleap-hand-tracking-service/samples as the source directory
2. Select a build directory (eg. ~/ultraleap-tracking-samples/build) to use
3. Configure & Generate CMake with the generator of your choice
4. Open and build the CMake generated project files. For more help, see the CMake documentation.
* An example script would be :
```bash
SRC_DIR=/usr/share/doc/ultraleap-hand-tracking-service/samples
BUILD_TYPE='Release'
REPOS_BUILD_ROOT=~/ultraleap-tracking-samples/build
REPOS_INSTALL_ROOT=/usr/bin/ultraleap-tracking-samples
cmake -S ${SRC_DIR} -B ${REPOS_BUILD_ROOT}/${BUILD_TYPE}/LeapSDK/leapc_example `
-DCMAKE_INSTALL_PREFIX="${REPOS_INSTALL_ROOT}/leapc_example" `
-DCMAKE_BUILD_TYPE="${BUILD_TYPE}"
cmake --build ${REPOS_BUILD_ROOT}/${BUILD_TYPE}/LeapSDK/leapc_example -j --config ${BUILD_TYPE}
```
### MacOS
1. Open CMake using /Library/Application Support/Ultraleap/LeapSDK/samples as the source directory
2. Select a build directory (eg. ~/ultraleap-tracking-samples/build) to use
3. Configure & Generate CMake with the generator of your choice
4. Open and build the CMake generated project files. For more help, see the CMake documentation.
* An example script would be :
```bash
SRC_DIR='/Library/Application Support/Ultraleap/LeapSDK/samples'
BUILD_TYPE='Release'
REPOS_BUILD_ROOT=~/ultraleap-tracking-samples/build
REPOS_INSTALL_ROOT=~/ultraleap-tracking-samples/bin
cmake -S ${SRC_DIR} -B ${REPOS_BUILD_ROOT}/${BUILD_TYPE}/LeapSDK/leapc_example `
-DCMAKE_INSTALL_PREFIX="${REPOS_INSTALL_ROOT}/leapc_example" `
-DCMAKE_BUILD_TYPE="${BUILD_TYPE}"
cmake --build ${REPOS_BUILD_ROOT}/${BUILD_TYPE}/LeapSDK/leapc_example -j --config ${BUILD_TYPE}
```
## Resources:
1. Ultraleap For Developers Site (https://developer.leapmotion.com)
provides examples, community forums, Ultraleap news, and documentation
to help you to learn how to develop applications using the Ultraleap Tracking
SDK.
2. C# and Unity bindings (https://github.com/leapmotion/UnityModules)
3. C++ bindings matching the old API (https://github.com/leapmotion/LeapCxx)
--------------------------------------------------------------------------------
Copyright © 2012-2020 Ultraleap Ltd. All rights reserved.
Use subject to the terms of the Ultraleap Tracking SDK Agreement `LICENSE.md` next to this `README.md` file.
# Ultraleap SDK
--------------------------------------------------------------------------------
## Package contents:
LeapSDK
- include
* API headers.
- lib
* dynamic API library and CMake scripts.
- samples
* Various samples demonstrating several different usages.
- LICENSE.md
* Ultraleap Tracking SDK license.
- README.md
* Ultraleap Tracking SDK readme.
- ThirdPartyNotices.md
* Ultraleap Tracking SDK third party licenses.
## Requirements:
1. Running requires
* Ultraleap Tracking Software https://developer.leapmotion.com/get-started/
2. Building Samples requires
* CMake 3.16.3+ (https://cmake.org/)
* Microsoft Visual Studio 15+ (Windows)
* GCC (Linux - tested on v9.4.0)
## Installation:
The LeapSDK is installed with Ultraleap Tracking.
## Usage:
1. For CMake projects
* Ensure LeapSDK is in a directory considered as a prefix by find_package.
(https://cmake.org/cmake/help/v3.16/command/find_package.html)
* Or : directly set LeapSDK_DIR to <install_dir>/LeapSDK/lib/cmake/LeapSDK
* Or : Pass the LeapSDK's path to find_package with the PATHS option.
* call find_package(LeapSDK 5 [PATHS ...]).
* call target_link_libraries(<your project> PUBLIC|PRIVATE LeapSDK::LeapC).
* Ensure LeapC.dll/LeapC.so is in your dynamic library search path.
* A popular option is to add a post-build step that copies it to your project's output directory.
2. For non-CMake projects
* Use a C/C++ compiler such as MSVC, Clang or GCC.
* Add LeapSDK/include to the compiler include search paths.
* Either add a linker reference to LeapC.lib or dynamically load LeapC.dll/LeapC.so.
## Building Samples:
### Windows
1. Open CMake using LeapSDK/samples as the source directory
2. Select a build directory (often LeapSDK/samples/build) to use
3. Configure & Generate CMake with the generator of your choice
4. Open and build the CMake generated project files. For more help, see the CMake documentation.
* An example script would be :
```powershell
$env:BUILD_TYPE = 'Release'
$env:REPOS_BUILD_ROOT = 'C:/build'
$env:REPOS_INSTALL_ROOT = 'C:/Program Files'
cmake -S "C:/Program Files/Ultraleap/LeapSDK/samples" -B $env:REPOS_BUILD_ROOT/$env:BUILD_TYPE/LeapSDK/leapc_example `
-DCMAKE_INSTALL_PREFIX="$env:REPOS_INSTALL_ROOT/leapc_example" `
-DCMAKE_BUILD_TYPE="$env:BUILD_TYPE"
cmake --build $env:REPOS_BUILD_ROOT/$env:BUILD_TYPE/LeapSDK/leapc_example -j --config $env:BUILD_TYPE
```
### x64 Linux
1. Open CMake using /usr/share/doc/ultraleap-hand-tracking-service/samples as the source directory
2. Select a build directory (eg. ~/ultraleap-tracking-samples/build) to use
3. Configure & Generate CMake with the generator of your choice
4. Open and build the CMake generated project files. For more help, see the CMake documentation.
* An example script would be :
```bash
SRC_DIR=/usr/share/doc/ultraleap-hand-tracking-service/samples
BUILD_TYPE='Release'
REPOS_BUILD_ROOT=~/ultraleap-tracking-samples/build
REPOS_INSTALL_ROOT=/usr/bin/ultraleap-tracking-samples
cmake -S ${SRC_DIR} -B ${REPOS_BUILD_ROOT}/${BUILD_TYPE}/LeapSDK/leapc_example `
-DCMAKE_INSTALL_PREFIX="${REPOS_INSTALL_ROOT}/leapc_example" `
-DCMAKE_BUILD_TYPE="${BUILD_TYPE}"
cmake --build ${REPOS_BUILD_ROOT}/${BUILD_TYPE}/LeapSDK/leapc_example -j --config ${BUILD_TYPE}
```
### ARM64 Linux
1. Open the top level directory of the untarred release and select a build directory (eg. ~/ultraleap-tracking-samples/build) to use
2. Set the CMAKE_PREFIX_PATH directory to the absolute location of LeapSDK
3. Configure & Generate CMake with the generator of your choice
4. Open and build the CMake generated project files. For more help, see the CMake documentation.
* An example script would be :
```bash
SRC_DIR='LeapSDK/samples'
BUILD_TYPE='Release'
REPOS_BUILD_ROOT=~/ultraleap-tracking-samples/build
REPOS_INSTALL_ROOT=~/ultraleap-tracking-samples/bin
PREFIX_PATH=$(pwd)/LeapSDK
cmake -S ${SRC_DIR} -B ${REPOS_BUILD_ROOT}/${BUILD_TYPE}/LeapSDK/leapc_example `
-DCMAKE_INSTALL_PREFIX="${REPOS_INSTALL_ROOT}/leapc_example"`
-DCMAKE_BUILD_TYPE="${BUILD_TYPE}" -DCMAKE_PREFIX_PATH="${PREFIX_PATH}"
cmake --build ${REPOS_BUILD_ROOT}/${BUILD_TYPE}/LeapSDK/leapc_example -j --config ${BUILD_TYPE}
```
### MacOS
1. Open CMake using /Applications/Ultraleap\ Hand\ Tracking.app/Contents/LeapSDK/samples as the source directory
2. Select a build directory (eg. ~/ultraleap-tracking-samples/build) to use
3. Configure & Generate CMake with the generator of your choice
4. Open and build the CMake generated project files. For more help, see the CMake documentation.
* An example script would be :
```bash
SRC_DIR='/Applications/Ultraleap\ Hand\ Tracking.app/Contents/LeapSDK/samples'
BUILD_TYPE='Release'
REPOS_BUILD_ROOT=~/ultraleap-tracking-samples/build
REPOS_INSTALL_ROOT=~/ultraleap-tracking-samples/bin
cmake -S ${SRC_DIR} -B ${REPOS_BUILD_ROOT}/${BUILD_TYPE}/LeapSDK/leapc_example `
-DCMAKE_INSTALL_PREFIX="${REPOS_INSTALL_ROOT}/leapc_example" `
-DCMAKE_BUILD_TYPE="${BUILD_TYPE}"
cmake --build ${REPOS_BUILD_ROOT}/${BUILD_TYPE}/LeapSDK/leapc_example -j --config ${BUILD_TYPE}
```
## Resources:
1. Ultraleap For Developers Site (https://developer.leapmotion.com)
provides examples, community forums, Ultraleap news, and documentation
to help you to learn how to develop applications using the Ultraleap Tracking
SDK.
2. C# and Unity bindings (https://github.com/leapmotion/UnityModules)
3. C++ bindings matching the old API (https://github.com/leapmotion/LeapCxx)
--------------------------------------------------------------------------------
Copyright © 2012-2020 Ultraleap Ltd. All rights reserved.
Use subject to the terms of the Ultraleap Tracking SDK Agreement `LICENSE.md` next to this `README.md` file.

Some files were not shown because too many files have changed in this diff Show more