Jichael
Jichael

Reputation: 820

Detecting controller movement in same direction as VR button

I'm working on a VR project and I'm struggling to do something that should not be that hard, but I really am bad at math :x

What I want to do, is have "physical" buttons on the world that I can push with my finger to press/unpress it, this part works really well and the feeling is perfect, BUT :

Currently, to "push" the button, I'm using the z delta position of my controller, so I can't push button from the top for example, because I would need the y delta.

I'm trying to be able to have buttons facing any direction, and be able to push them using the right axis of my controller, but can't get my head around it :x

I'd need to be able to push the button depending on it's position/rotation.

I don't know if it's really understandable (probably not), maybe I just missed something really obvious.

Here is my code for my button, the enum part is probably useless, I'm just trying things lol

using System;
using UnityEngine;

public class JVRButton : MonoBehaviour, IJVRFingerInteract
{

    [SerializeField] private Vector3 pressedPosition;
    [SerializeField] private Vector3 defaultPosition;
    [SerializeField] private Vector3 unpressPosition;

    [SerializeField] private LinearDragNormal direction;

    public bool Pressed { get; private set; }
    public event Action<bool> OnStateChange;

    private bool _changedState;
    private Transform _transform;
    private Vector3 _tmp;
    private float _delay;

    private float _delta; 

    private void Awake()
    {
        _transform = transform;
        _transform.localPosition = Pressed ? pressedPosition : defaultPosition;
    }

    private void Update()
    {
        _delay += Time.deltaTime;
        if (_delay < 0.1f) return;

        // "Spring" effect
        _transform.localPosition = Vector3.MoveTowards(_transform.localPosition, Pressed ? pressedPosition : defaultPosition, Time.deltaTime / 2);
    }

    public void JVRFingerInteract(JVRFinger jvrFinger)
    {
        Vector3 test = Quaternion.FromToRotation(jvrFinger.transform.forward, _transform.forward).eulerAngles;

        Debug.Log(test);

        switch(direction)
        {
            case LinearDragNormal.XPositive:
                _delta = Mathf.Min(jvrFinger.JVRController.DeltaPositionLocal.z, 0);
                break;
            case LinearDragNormal.XNegative:
                _delta = Mathf.Max(jvrFinger.JVRController.DeltaPositionLocal.z, 0);
                break;
            case LinearDragNormal.YPositive:
                _delta = Mathf.Max(jvrFinger.JVRController.DeltaPositionLocal.y, 0);
                break;
            case LinearDragNormal.YNegative:
                _delta = Mathf.Min(jvrFinger.JVRController.DeltaPositionLocal.y, 0);
                break;
             case LinearDragNormal.ZPositive:
                _delta = Mathf.Min(jvrFinger.JVRController.DeltaPositionLocal.x, 0);
                break;
            case LinearDragNormal.ZNegative:
                _delta = Mathf.Max(jvrFinger.JVRController.DeltaPositionLocal.x, 0);
                break;
            default:
                throw new ArgumentOutOfRangeException();
        }

        MoveButton(Pressed ? unpressPosition : pressedPosition);
    }

    private void MoveButton(Vector3 position)
    {
        if (_changedState && _delay < 0.5f) return;
        _delay = 0;
        _changedState = false;            
        _tmp = _transform.localPosition;
        _tmp = Vector3.MoveTowards(_tmp, position, _delta);
        if (_tmp.x < position.x) _tmp.x = position.x;
        if (_tmp.y < position.y) _tmp.y = position.y;
        if (_tmp.z < position.z) _tmp.z = position.z;
        _transform.localPosition = _tmp;

        if (_transform.localPosition == pressedPosition)
        {
            Pressed = true;
            _changedState = true;
            OnStateChange?.Invoke(Pressed);
        }
        else if (_transform.localPosition == unpressPosition)
        {
            Pressed = false;
            _changedState = true;
            OnStateChange?.Invoke(Pressed);
        }
    }    
}

public enum LinearDragNormal
{
    XPositive,
    XNegative,
    YPositive,
    YNegative,
    ZPositive,
    ZNegative
}

JVRFingerInteract is called every frame my finger is touching the button, I'm simply doing a overlapsphere in my Finger to get interactable objects.

The pushing axis of the button is the button's local Z axis, and positive Z points out of the surface of the button.

Upvotes: 0

Views: 342

Answers (2)

Ruzihm
Ruzihm

Reputation: 20249

Assuming the surface of your button faces the button's local up direction:

local axes of button

Then you can use a dot product between the button's up in world space and the delta in world space to determine how much the finger is moving against that direction:

Vector3 worldFingerDelta;
Transform buttonTransform;

float deltaInButtonDown = Vector3.Dot(worldFingerDelta, 
        -buttonTransform.up);

// when deltainButtonDown is positive, the finger is moving in the 
// same direction as "pushing the button" 
// When it is negative, the finger is moving in the opposite direction.
// The magnitude of deltaInButtonDown is how much the finger is 
// moving in that direction.

local axes of button

If a different local direction is pointing out of the surface of the button, such as its negative forward, you would just use the corresponding local vector in world space:

negative forward is pointing out of the button

float deltaInButtonDown = Vector3.Dot(worldFingerDelta, 
        buttonTransform.forward); // negatives cancel out

Also, if the surface the button is on is moving in world space, you'll want to use Vector3.Dot(worldFingerDelta - worldButtonSurfaceDelta, buttonTransform.forward); in order to allow for things like the button pressing itself by moving into a still finger.


About your answer....

_delta = (_forward.x * jvrPos.z + _forward.y * -jvrPos.y 
        + _forward.z * -jvrPos.x);
if (_delta < 0) _delta = 0; 

_forward being the button forward, and jvrPos being the local delta of my controller/finger

As far as the vector math goes, you're doing a dot product between the button's forward and the local delta after some kind of inversion/reflection transformation is applied to it. So another way to write your answer is this:

new Vector3 vec = new Vector3(jvrPos.z, -jvrPos.y, -jvrPos.x);
_delta = Vector3.Dot(vec, buttonTransform.forward);

As we discovered in the comments of your answer, that transformation, based on the world rotation of the parent of the controller, turns your local delta into the negative of the world delta. So by taking the negative of what you have for the vector and making that negative in the Dot call, another way to write your answer is this:

new Vector3 calculatedWorldFingerDelta = new Vector3(-jvrPos.z, jvrPos.y, jvrPos.x);
_delta = Vector3.Dot(-calculatedWorldFingerDelta, buttonTransform.forward);

And since the dot product of A and -B equals the dot product of -A and B, your answer is equivalent to:

new Vector3 calculatedWorldFingerDelta = new Vector3(-jvrPos.z, jvrPos.y, jvrPos.x);
_delta = Vector3.Dot(calculatedWorldFingerDelta, -buttonTransform.forward);

Which, if calculatedWorldFingerDelta equals worldFingerDelta, is the same formula above for a button with the positive z direction pointing out of the surface of the button.

I would use worldFingerDelta directly because if the world rotation or position of the parent of the finger changes, then the transformation you're using now won't be guaranteed to give you calculatedWorldFingerDelta that equals worldFingerDelta, and your answer won't work anymore.

Upvotes: 0

Jichael
Jichael

Reputation: 820

Ok, I found the solution by trying, I don't understand HOW and WHY it works, but it does...

_delta = (_forward.x * jvrPos.z + _forward.y * -jvrPos.y + _forward.z * -jvrPos.x);
if (_delta < 0) _delta = 0;

_forward being the button forward, and jvrPos being the local delta of my controller/finger

Upvotes: 1

Related Questions