Skip to content

Commit

Permalink
feat(tracking): add finger tracking (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
Xenira committed Mar 11, 2024
1 parent a2ac00d commit cd60835
Show file tree
Hide file tree
Showing 10 changed files with 838 additions and 35 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"conventionalCommits.scopes": [
"release"
"release",
"tracking"
]
}
10 changes: 9 additions & 1 deletion README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ endif::[]
:icons: font
:source-highlighter: highlightjs
:repo: Xenira/TTIK
:game-version: 0.3.0-e

image:https://img.shields.io/github/v/release/{repo}["GitHub release (with filter)", link="https://github.com/{repo}/releases/latest"]
image:https://img.shields.io/github/release-date/{repo}["GitHub Release Date - Published_At", link="https://github.com/{repo}/releases/latest"]
Expand All @@ -33,7 +34,7 @@ image:https://img.shields.io/github/license/{repo}["License", link="https://gith
</p>
++++

Adds networked inverse kinematics to the game https://store.steampowered.com/app/1457320/Techtonica/[Techtonica]. This allows players to see their own and other players' full body movements. This only syncs movements of VR players, but VR is not required to use this mod.
Adds networked inverse kinematics and finger tracking to the game https://store.steampowered.com/app/1457320/Techtonica/[Techtonica]. This allows players to see their own and other players' full body and finger movements. This only syncs movements of VR players, but VR is not required to use this mod.

ifdef::env-github[]
____
Expand All @@ -60,6 +61,7 @@ endif::[]

== Prerequisites

* Version {game-version} of the game. As the game is still in early access, this mod may not work with future versions of the game. If you encounter any issues, please create an https://github.com/{repo}/issues[Issue].
* https://github.com/BepInEx/BepInEx[BepInEx] current 5.x BepInEx
* https://github.com/Xenira/PiUtils[PiUtils] mod. Is included in the github release and as a dependency in the thunderstore release.

Expand Down Expand Up @@ -101,6 +103,10 @@ To reset only a specific section, delete the section from the configuration file
.General
Enabed:: Enables or disables the mod. When using https://github.com/Xenira/TechtonicaVR[TechtonicaVR] disabling this mod might cause issues. Default: `true`

[horizontal]
.Sync
Finger Sync Deadzone:: Deadzone for finger sync. If the difference between the local and remote finger position is less than this value, the remote finger will be set to the local finger position. Default: `0.01`

== Privacy
This mod does not collect any personal data and it does not send any data to any server other than the connected coop server. That being said, movement data is sent to the coop server and any connected clients.

Expand All @@ -110,5 +116,7 @@ If you encounter any issues while using this mod, please check the BepInEx conso
== License
This mod is licensed under the GNU General Public License v3.0 (GPL-3.0).

Contents of the `unity`, `tools` and `libs` folders are licensed under their respective licenses.

== Disclaimer
This mod is not affiliated with the game's developer https://www.firehosegames.com[Firehose Games] or Unity Technologies. All trademarks are the property of their respective owners.
6 changes: 6 additions & 0 deletions plugin/src/ModConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,16 @@ public class ModConfig
// General
private static ConfigEntry<bool> modEnabled;

// Sync
public static ConfigEntry<float> fingerSyncDeadzone;

public static void Init(ConfigFile config)
{
// General
modEnabled = config.Bind("General", "Enabled", true, "Enable mod");

// Sync
fingerSyncDeadzone = config.Bind("Sync", "Finger Sync Deadzone", 0.01f, "Deadzone for finger sync. If the difference between the local and remote finger position is less than this value, the remote finger will be set to the local finger position.");
}

public static bool ModEnabled()
Expand Down
92 changes: 88 additions & 4 deletions plugin/src/IkPlayer.cs → plugin/src/ik/IkPlayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using PiUtils.Debug;
using PiUtils.Util;
using RootMotion.FinalIK;
using TTIK.Ik.FingerTracking;
using UnityEngine;

namespace TTIK.Ik;
Expand All @@ -30,10 +31,12 @@ public class IkPlayer : MonoBehaviour
private Transform head;
public Transform headTarget;
public Transform headTargetParent;
private Transform leftHand;
public Transform leftHand;
public Transform leftHandTarget;
private Transform rightHand;
internal CustomHandUpdater leftHandFingers;
public Transform rightHand;
public Transform rightHandTarget;
internal CustomHandUpdater rightHandFingers;

private static VRIK.References references;
public bool? calibrating = null;
Expand Down Expand Up @@ -96,6 +99,8 @@ private void init()
head = GameObjectFinder.FindChildObjectByName("head", avatar).transform;
leftHand = GameObjectFinder.FindChildObjectByName("hand_l", avatar).transform;
rightHand = GameObjectFinder.FindChildObjectByName("hand_r", avatar).transform;
leftHandFingers = AddFingerTracking(leftHand, "l");
rightHandFingers = AddFingerTracking(rightHand, "r");

setReferences();
avatarHeight = references.head.position.y - references.root.position.y;
Expand Down Expand Up @@ -219,8 +224,16 @@ internal void calibrate()
public void calibrated(float? scale)
{
trackingTargets.Values.ForEach(t => t.FinishCalibration());
var settings = new VRIKCalibrator.Settings();
setScale(this.scale ?? scale ?? 1f);

if (headTargetParent != null)
{
setScale(getScale(headTargetParent.position));
}
else
{
setScale(scale ?? 1f);
}

calibrating = false;
ik.enabled = true;
OnCalibrated?.Invoke();
Expand All @@ -230,4 +243,75 @@ private Transform TrackingTargetOrDefault(TrackingTargetType type, Transform def
{
return trackingTargets.GetValueOrDefault(type, null)?.transform ?? defaultTransform;
}

private static CustomHandUpdater AddFingerTracking(Transform handRoot, string boneName)
{
var thumb1 = GameObjectFinder.FindChildObjectByName($"thumb_01_{boneName}", handRoot);
var thumb2 = thumb1.transform.GetChild(0);
var thumb3 = thumb2.transform.GetChild(0);

var index1 = GameObjectFinder.FindChildObjectByName($"index_01_{boneName}", handRoot);
var index2 = index1.transform.GetChild(0);
var index3 = index2.transform.GetChild(0);
index2.gameObject.AddComponent<Gizmo>();

var middle1 = GameObjectFinder.FindChildObjectByName($"middle_01_{boneName}", handRoot);
var middle2 = middle1.transform.GetChild(0);
var middle3 = middle2.transform.GetChild(0);

var ring1 = GameObjectFinder.FindChildObjectByName($"ring_01_{boneName}", handRoot);
var ring2 = ring1.transform.GetChild(0);
var ring3 = ring2.transform.GetChild(0);

var pinky1 = GameObjectFinder.FindChildObjectByName($"pinky_01_{boneName}", handRoot);
var pinky2 = pinky1.transform.GetChild(0);
var pinky3 = pinky2.transform.GetChild(0);

var hand = new CustomHandUpdater();

var thumb = new FingerTracking.Finger(null, 110);
thumb.WithBone(thumb1.transform)
.WithBone(thumb2)
.WithBone(thumb3, 0.75f);
hand.fingers.Add(FingerType.Thumb, thumb);

var indexFinger = new FingerTracking.Finger(Vector3.back + (Vector3.down * 0.1f));
indexFinger.WithBone(index1.transform)
.WithBone(index2, 1.25f)
.WithBone(index3);
hand.fingers.Add(FingerType.Index, indexFinger);

var middleFinger = new FingerTracking.Finger();
middleFinger.WithBone(middle1.transform)
.WithBone(middle2, 1.25f)
.WithBone(middle3);
hand.fingers.Add(FingerType.Middle, middleFinger);

var ringFinger = new FingerTracking.Finger(Vector3.back + (Vector3.up * 0.2f));
ringFinger.WithBone(ring1.transform)
.WithBone(ring2, 1.25f)
.WithBone(ring3);
hand.fingers.Add(FingerType.Ring, ringFinger);

var pinkyFinger = new FingerTracking.Finger(Vector3.back + (Vector3.up * 0.3f));
pinkyFinger.WithBone(pinky1.transform)
.WithBone(pinky2, 1.25f)
.WithBone(pinky3);
hand.fingers.Add(FingerType.Pinky, pinkyFinger);

return hand;
}

internal void UpdateFingerCurl(HandType hand, FingerType finger, float curl)
{
switch (hand)
{
case HandType.Left:
leftHandFingers?.OnBoneTransformsUpdated(finger, curl);
break;
case HandType.Right:
rightHandFingers?.OnBoneTransformsUpdated(finger, curl);
break;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using PiUtils.Util;
using UnityEngine;

namespace TTIK;
namespace TTIK.Ik;

public class TrackingTarget : MonoBehaviour
{
Expand Down
62 changes: 62 additions & 0 deletions plugin/src/ik/finger_tracking/CustomHandUpdater.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using System.Collections.Generic;

namespace TTIK.Ik.FingerTracking;

class CustomHandUpdater
{
public static FingerType[] ALL_FINGER_TYPES = [FingerType.Thumb, FingerType.Index, FingerType.Middle, FingerType.Ring, FingerType.Pinky];

public Dictionary<FingerType, Finger> fingers = new Dictionary<FingerType, Finger>();

public void OnBoneTransformsUpdated(FingerType fingerType, float value)
{
if (fingers.TryGetValue(fingerType, out var finger))
{
finger.CurlFinger(value);
}
}
}

// Not currently used but kept for reference or future change from curl to actual finger movement
// https://github.com/ValveSoftware/openvr/wiki/Hand-Skeleton
enum Bone
{
Root = 0,
Wrist,

Thumb0,
Thumb1,
Thumb2,
Thumb3,

IndexFinger0,
IndexFinger1,
IndexFinger2,
IndexFinger3,
IndexFinger4,

MiddleFinger0,
MiddleFinger1,
MiddleFinger2,
MiddleFinger3,
MiddleFinger4,

RingFinger0,
RingFinger1,
RingFinger2,
RingFinger3,
RingFinger4,

PinkyFinger0,
PinkyFinger1,
PinkyFinger2,
PinkyFinger3,
PinkyFinger4,

Aux_Thumb,
Aux_IndexFinger,
Aux_MiddleFinger,
Aux_RingFinger,
Aux_PinkyFinger,
Count
}
59 changes: 59 additions & 0 deletions plugin/src/ik/finger_tracking/Finger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace TTIK.Ik.FingerTracking;

public class Finger
{
private Vector3 axis = Vector3.back;
private float totalCurl = 180;
private Dictionary<Transform, float> bones = new Dictionary<Transform, float>();
private Dictionary<Transform, Quaternion> originalRotations = new Dictionary<Transform, Quaternion>();

public Finger(Vector3? axis = null, float totalCurl = 180)
{
this.axis = axis ?? this.axis;
this.totalCurl = totalCurl;
}

public Finger(Dictionary<Transform, float> bones, Vector3? axis = null, float totalCurl = 180) : this(axis, totalCurl)
{
this.bones = bones;
}

public Finger WithBone(Transform transform)
{
AddBone(transform);
return this;
}

public Finger WithBone(Transform transform, float factor)
{
AddBone(transform, factor);
return this;
}

public void AddBone(Transform transform)
{
AddBone(transform, 1);
}

public void AddBone(Transform transform, float factor)
{
bones.Add(transform, factor);
originalRotations.Add(transform, transform.localRotation);
}

public void CurlFinger(float factor)
{
var relativeFactor = factor / bones.Values.Sum();
foreach (var (bone, boneFactor) in bones)
{
var originalRotation = originalRotations[bone];
var combinedFactor = boneFactor * relativeFactor;
// Rotate around forward axis
bone.localRotation = originalRotation * Quaternion.AngleAxis(totalCurl * combinedFactor, axis);
}
}
}
11 changes: 11 additions & 0 deletions plugin/src/ik/finger_tracking/FingerType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace TTIK.Ik.FingerTracking;

public enum FingerType
{
Thumb = 0,
Index,
Middle,
Ring,
Pinky
}

7 changes: 7 additions & 0 deletions plugin/src/ik/finger_tracking/HandType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace TTIK.Ik.FingerTracking;

public enum HandType
{
Left = 0,
Right
}
Loading

0 comments on commit cd60835

Please sign in to comment.