Skip to content

Haptic throttle deadzone#159

Open
dhleong wants to merge 7 commits intodantman:masterfrom
dhleong:dhleong/haptic-throttle-deadzone
Open

Haptic throttle deadzone#159
dhleong wants to merge 7 commits intodantman:masterfrom
dhleong:dhleong/haptic-throttle-deadzone

Conversation

@dhleong
Copy link
Contributor

@dhleong dhleong commented Feb 29, 2020

Hello! Not sure how you'll feel about this approach but I think it feels pretty good. If you don't like the haptics but are interested in the controller behavior changes, there's another branch that's this but without the haptic stuff.

Here's an explanation from the commits:

The direction the throttle first leaves the deadzone from determines its
initial state, IE: Forward or Reverse. In this state, it is easy to fall
back into an associated "idle detent" state by moving the throttle
within the deadzone, eg: ForwardIdleDetent or ReverseIdleDetent.

From the "idle detent" state, it is easy to move into the associated
acceleration state, but "hard" to move to the opposite acceleration
state.

Transition between states can be made clear to the user by use of haptic
events.

This mechanism enables you to quickly move from forward to reverse if
you need to, while also making it easy to find (and not move past!)
idle.

This PR also makes it more clear when you're in the idle deadzone by keeping the throttle UI idle while within it, and adds some initial haptic feedback when you leave and enter the deadzone. The haptic API is designed to continue the work you've started to keep the controller interactions platform agnostic and keep the door open for a non-SteamVR implementation. I haven't spent a lot of time tweaking the actual haptic feedback, but there it's a place to start.

The direction the throttle first leaves the deadzone from determines its
initial state, IE: Forward or Reverse. In this state, it is easy to fall
back into an associated "idle detent" state by moving the throttle
within the deadzone, eg: ForwardIdleDetent or ReverseIdleDetent.

From the "idle detent" state, it is easy to move into the associated
acceleration state, but "hard" to move to the opposite acceleration
state.

Transition between states can be made clear to the user by use of haptic
events.

This mechanism enables you to quickly move from forward to reverse if
you need to, while also making it easy to find (and not move past!)
idle.
@dantman
Copy link
Owner

dantman commented Feb 29, 2020

As I mentioned in #143. I want to fix the Update/LateUpdate code to handle the deadzone inside VirtualThrotttle. That's how I want to fix the throttle marker to snap to zero when in the deadzone. I do not want to keep layering hacks on top of the current throttle handling.

I do want to add haptics at some point, but I need to do some investigation into them before I decide what the preferential API would be. I like that you kept it abstract (though SteamVrHaptics should be in the bindings folder), but I need an investigation and some thought to decide whether that's the exact API that would be best.

I'm not actually entirely clear on what you're doing, what the 3x reverse dent multiplier is for, or how making it "hard" to reverse the move to the opposite acceleration works. However I have a sneaking suspicion that CheckStateChange contains 10x the code it needs.

@dhleong
Copy link
Contributor Author

dhleong commented Feb 29, 2020

This does move deadzone handling into VirtualThrottle. I suppose the logic is currently duplicated by the vJoyInterface, but functionally VirtualThrottle controls the deadzone now, rather than the vJoyInterface.

This is a finite state machine with 5 states. In the IDLE state, the deadzone is the same size in both directions. IDLE can move to either FORWARD or REVERSE when you leave the deadzone in the appropriate direction. Conceptually, in eg FORWARD, the deadzone is from -reverseDetent to detent (EG: -15% to 5%); when you move into that detent, you enter FORWARD_IDLE_DETENT, which keeps the same deadzone (IE: it makes extra motion to switch to REVERSE, the normal amount to go back FORWARD).

It's probably possible to simplify this FSM, but this was the clearest model in my mind. I didn't want to invest too much time on this in case you weren't interested in this mechanic---I know there's a competing PR that uses a button to switch from FORWARD to REVERSE that's been sitting idle.

@dantman
Copy link
Owner

dantman commented Feb 29, 2020

This does move deadzone handling into VirtualThrottle. I suppose the logic is currently duplicated by the vJoyInterface, but functionally VirtualThrottle controls the deadzone now, rather than the vJoyInterface.

I want to completely move deadzone handling into VirtualThrottle. Basically instead of all the MoveThrottleToIdleDetent, the SetThrottle in Update, incomplete handle position calculations in FixedUpdate, and real deadzone calculations in vJoyInterface; one update step (possibly a coroutine instead of FixedUpdate/Update) would calculate the new throttle value first (not the new handle value first like the current code) and apply a deadzone directly to that, and then after that that throttle value would be used to update the handle and vJoyInterface throttle value. That way the handle's position implicitly reflects the deadzone because the deadzone is applied to the throttle value before the handle update instead of after. And there is no separate deadzone implementation. It'll also make it easier to set the throttle value to a specific number like 0, right now you'd have to update both the handle and vJoyInterface separately.

This is a finite state machine with 5 states. In the IDLE state, the deadzone is the same size in both directions. IDLE can move to either FORWARD or REVERSE when you leave the deadzone in the appropriate direction. Conceptually, in eg FORWARD, the deadzone is from -reverseDetent to detent (EG: -15% to 5%); when you move into that detent, you enter FORWARD_IDLE_DETENT, which keeps the same deadzone (IE: it makes extra motion to switch to REVERSE, the normal amount to go back FORWARD).

Ok I think I understand now. There is an extra deadzone area if you go from one throttle sign to the other. What kind of advantage is there to this method over other methods?

I'm also not sure how I feel about how this makes the "grab/move/release throttle" and "continually hold and move throttle" interactions with the throttle have slightly divergent functionality in how stopping then reversing feels. This also sounds like reversing from forward would result in a larger initial throttle jump because of the extra detent.

It's probably possible to simplify this FSM, but this was the clearest model in my mind.

It's more that I don't think a state machine is necessary. There's a lot of repeat code. This could probably be handled with some simpler math and logic. e.g. Stuff like bool isInDeadzone = throttle < deadzoneStart || throttle > deadzoneEnd; and if (prevThrottleZone != throttleZone) { EmitHapticBump();.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants