Skip to content

Instantly share code, notes, and snippets.

@ghthor
Created September 15, 2016 21:47
Show Gist options
  • Save ghthor/4a19aa6812a3ee869797750054b3dca7 to your computer and use it in GitHub Desktop.
Save ghthor/4a19aa6812a3ee869797750054b3dca7 to your computer and use it in GitHub Desktop.
Chordpad For Vive in Unity
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
[RequireComponent(typeof(SteamVR_TrackedObject))]
public class ChordInput : MonoBehaviour
{
SteamVR_TrackedObject controller;
enum ControllerHand { L, R }
enum ChordButtonID
{
Trigger_0,
Trigger_1,
// Used to represent 2 buttons using the trackpad
Hemisphere_0_0,
Hemisphere_0_1,
Hemisphere_1_0,
Hemisphere_1_1,
// Used to represent 3 buttons using the trackpad
Pie_0_0,
Pie_0_1,
Pie_0_2,
Pie_1_0,
Pie_1_1,
Pie_1_2,
};
class ChordMask
{
private Dictionary<ChordButtonID, ulong> masks;
public readonly ulong All;
public ChordMask(ChordButtonID[] inputs)
{
masks = new Dictionary<ChordButtonID, ulong>();
foreach (ChordButtonID b in inputs)
{
ulong mask = 1ul << masks.Count;
masks.Add(b, mask);
All |= mask;
}
}
public ulong For(ChordButtonID b)
{
return masks[b];
}
public string StringFor(ulong mask)
{
if (mask == 0) { return "[]"; }
List<string> buttonsPressed = new List<string>();
foreach (KeyValuePair<ChordButtonID, ulong> mapping in masks)
{
if ((mask & mapping.Value) == 0) { continue; }
buttonsPressed.Add(mapping.Key.ToString());
}
return "[" + string.Join(",", buttonsPressed.ToArray()) + "]";
}
}
// TODO: Implement 3-button support
class TrackpadButtonMask
{
// If the location's distance from center is <= this radius then both buttons are pressed.
const float crossoverRadius = 1.0F / 3.0F * 2.0F;
// Config for ButtonID => Mask
private ChordMask mask;
// The Set of ButtonID's for this Hand
private ChordButtonID[] buttons;
// A Mask for all the Trackpad ButtonID's attached the Hand
public readonly ulong All;
public TrackpadButtonMask(ChordMask config, ControllerHand hand)
{
mask = config;
buttons = new[]
{
ChordButtonID.Hemisphere_0_0,
ChordButtonID.Hemisphere_0_1,
};
if (hand == ControllerHand.R)
{
buttons = new[]
{
ChordButtonID.Hemisphere_1_0,
ChordButtonID.Hemisphere_1_1,
};
}
foreach (ChordButtonID b in buttons)
{
All |= mask.For(b);
}
}
public ulong MaskFor(SteamVR_Controller.Device dev)
{
// TODO: Add Support Vertical axis Mode
Vector2 v = dev.GetAxis(Valve.VR.EVRButtonId.k_EButton_SteamVR_Touchpad);
float radius = Mathf.Abs(v.x);
if (radius <= crossoverRadius)
{
return mask.For(buttons[0]) | mask.For(buttons[1]);
}
else if (v.x < 0)
{
return mask.For(buttons[0]);
}
else if (v.x > 0)
{
return mask.For(buttons[1]);
}
return 0;
}
}
class ChordMachine
{
public enum State
{
Building,
Playing,
Played,
}
public enum InputEvent
{
TriggerPressed,
TriggerReleased,
TouchpadPressed,
TouchpadValueModified,
TouchpadReleased,
ChordValueOutput,
}
public static State Transistion(State state, InputEvent inputEvent)
{
switch (state)
{
case State.Building:
if (inputEvent == InputEvent.TriggerReleased
|| inputEvent == InputEvent.TouchpadReleased)
{
return State.Playing;
}
break;
case State.Playing:
if (inputEvent == InputEvent.ChordValueOutput)
{
return State.Played;
}
break;
case State.Played:
if (inputEvent == InputEvent.TriggerPressed
|| inputEvent == InputEvent.TouchpadPressed
|| inputEvent == InputEvent.TouchpadValueModified)
{
return State.Building;
}
break;
}
return state;
}
}
// A Mapping of ChordButtonID's => Unique Mask Values
private ChordMask mask;
private ControllerHand hand;
// Unique set of ChordButtonID's for the current Hand
private ChordButtonID trigger;
private TrackpadButtonMask trackpad;
// Shared Button State between both hands
static private ulong prevKeys = 0;
static private ulong chordKeys = 0;
static private ChordMachine.State status = ChordMachine.State.Building;
ChordInput()
{
// TODO: Add Runtime Configuration of ChordingButton's used
mask = new ChordMask(new ChordButtonID[] {
ChordButtonID.Trigger_0,
ChordButtonID.Trigger_1,
ChordButtonID.Hemisphere_0_0,
ChordButtonID.Hemisphere_0_1,
ChordButtonID.Hemisphere_1_0,
ChordButtonID.Hemisphere_1_1,
});
}
void Awake()
{
controller = GetComponent<SteamVR_TrackedObject>();
setButtonIDs();
Debug.Log("Mask Config: " + mask.StringFor(mask.All));
Debug.Log(string.Join("\n", new[] {
controller.name + " setup as " + hand.ToString(),
"Trigger => " + trigger.ToString(),
"Trackpad => " + mask.StringFor(trackpad.All),
}));
}
private ControllerHand HandForName(string name)
{
// TODO: Generalize this resolution of GameObject.name => enum value
return controller.name.Contains("Left") ? ControllerHand.L : ControllerHand.R;
}
private void setButtonIDs()
{
hand = HandForName(controller.name);
trigger = (hand == ControllerHand.L ? ChordButtonID.Trigger_0 : ChordButtonID.Trigger_1);
trackpad = new TrackpadButtonMask(mask, hand);
}
void FixedUpdate()
{
SteamVR_Controller.Device dev = SteamVR_Controller.Input((int)controller.index);
// ## Update Phase - Additions to Chord Being Built
bool trackpadDown = dev.GetPress(SteamVR_Controller.ButtonMask.Touchpad)
|| dev.GetPressDown(SteamVR_Controller.ButtonMask.Touchpad);
bool triggerDown = dev.GetTouch(SteamVR_Controller.ButtonMask.Trigger)
|| dev.GetTouchDown(SteamVR_Controller.ButtonMask.Trigger);
// ### Update Trackpad
chordKeys &= ~trackpad.All;
chordKeys = trackpadDown ?
chordKeys | trackpad.MaskFor(dev) :
chordKeys;
// ### Update Trigger State
chordKeys = triggerDown ?
chordKeys | mask.For(trigger) :
chordKeys & ~mask.For(trigger);
}
void Update()
{
if (prevKeys == chordKeys) { return; }
ulong touchpadKeysDownPrev = (prevKeys & trackpad.All);
ulong touchpadKeysDownNow = (chordKeys & trackpad.All);
bool touchpadWasDownPrev = touchpadKeysDownPrev != 0;
bool touchpadIsDownNow = touchpadKeysDownNow != 0;
ulong triggerMask = mask.For(trigger);
bool triggerDownPrev = (prevKeys & triggerMask) != 0;
bool triggerDownNow = (chordKeys & triggerMask) != 0;
if ((touchpadWasDownPrev && touchpadIsDownNow)
&& (touchpadKeysDownPrev != touchpadKeysDownNow))
{
status = ChordMachine.Transistion(status, ChordMachine.InputEvent.TouchpadValueModified);
}
// Press Events
if (!triggerDownPrev && triggerDownNow)
{
status = ChordMachine.Transistion(status, ChordMachine.InputEvent.TriggerPressed);
}
if (!touchpadWasDownPrev && touchpadIsDownNow)
{
status = ChordMachine.Transistion(status, ChordMachine.InputEvent.TouchpadPressed);
}
// Release Events
if (triggerDownPrev && !triggerDownNow)
{
status = ChordMachine.Transistion(status, ChordMachine.InputEvent.TriggerReleased);
}
if (touchpadWasDownPrev && !touchpadIsDownNow)
{
status = ChordMachine.Transistion(status, ChordMachine.InputEvent.TouchpadReleased);
}
}
void LateUpdate()
{
if (status == ChordMachine.State.Playing)
{
// TODO: Modify Text View
Debug.Log(string.Format("Output Chord Value({0}) : {1}", prevKeys, mask.StringFor(prevKeys)));
status = ChordMachine.Transistion(status, ChordMachine.InputEvent.ChordValueOutput);
}
prevKeys = chordKeys;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment