Newer
Older
CGTrack / Assets / Oculus / SampleFramework / Usage / HandsTrainExample / Scripts / TrainButtonVisualController.cs
/************************************************************************************

Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.  

See SampleFramework license.txt for license terms.  Unless required by applicable law 
or agreed to in writing, the sample code is provided “AS IS” WITHOUT WARRANTIES OR 
CONDITIONS OF ANY KIND, either express or implied.  See the license for specific 
language governing permissions and limitations under the license.

************************************************************************************/

using System.Collections;
using UnityEngine;
using UnityEngine.Assertions;

namespace OculusSampleFramework
{
	/// <summary>
	/// An example visual controller for a button intended for the train sample scene.
	/// </summary>
	public class TrainButtonVisualController : MonoBehaviour
	{
		private const float LERP_TO_OLD_POS_DURATION = 1.0f;
		private const float LOCAL_SIZE_HALVED = 0.5f;

		[SerializeField] private MeshRenderer _meshRenderer = null;
		[SerializeField] private MeshRenderer _glowRenderer = null;
		[SerializeField] private ButtonController _buttonController = null;
		[SerializeField] private Color _buttonContactColor = new Color(0.51f, 0.78f, 0.92f, 1.0f);
		[SerializeField] private Color _buttonActionColor = new Color(0.24f, 0.72f, 0.98f, 1.0f);

		[SerializeField] private AudioSource _audioSource = null;
		[SerializeField] private AudioClip _actionSoundEffect = null;

		[SerializeField] private Transform _buttonContactTransform = null;
		[SerializeField] private float _contactMaxDisplacementDistance = 0.0141f;

		private Material _buttonMaterial;
		private Color _buttonDefaultColor;
		private int _materialColorId;

		private bool _buttonInContactOrActionStates = false;

		private Coroutine _lerpToOldPositionCr = null;
		private Vector3 _oldPosition;

		private void Awake()
		{
			Assert.IsNotNull(_meshRenderer);
			Assert.IsNotNull(_glowRenderer);
			Assert.IsNotNull(_buttonController);
			Assert.IsNotNull(_audioSource);
			Assert.IsNotNull(_actionSoundEffect);

			Assert.IsNotNull(_buttonContactTransform);
			_materialColorId = Shader.PropertyToID("_Color");
			_buttonMaterial = _meshRenderer.material;
			_buttonDefaultColor = _buttonMaterial.GetColor(_materialColorId);

			_oldPosition = transform.localPosition;
		}

		private void OnDestroy()
		{
			if (_buttonMaterial != null)
			{
				Destroy(_buttonMaterial);
			}
		}

		private void OnEnable()
		{
			_buttonController.InteractableStateChanged.AddListener(InteractableStateChanged);
			_buttonController.ContactZoneEvent += ActionOrInContactZoneStayEvent;
			_buttonController.ActionZoneEvent += ActionOrInContactZoneStayEvent;
			_buttonInContactOrActionStates = false;
		}

		private void OnDisable()
		{
			if (_buttonController != null)
			{
				_buttonController.InteractableStateChanged.RemoveListener(InteractableStateChanged);
				_buttonController.ContactZoneEvent -= ActionOrInContactZoneStayEvent;
				_buttonController.ActionZoneEvent -= ActionOrInContactZoneStayEvent;
			}
		}

		private void ActionOrInContactZoneStayEvent(ColliderZoneArgs collisionArgs)
		{
			if (!_buttonInContactOrActionStates || collisionArgs.CollidingTool.IsFarFieldTool)
			{
				return;
			}

			// calculate how much the button should be pushed inwards. based on contact zone.
			// assume collider is uniform 1x1x1 cube, and all scaling, etc is done on transform component
			// another way to test distances is to measure distance to plane that represents where
			// button translation must stop
			Vector3 buttonScale = _buttonContactTransform.localScale;
			Vector3 interactionPosition = collisionArgs.CollidingTool.InteractionPosition;
			Vector3 localSpacePosition = _buttonContactTransform.InverseTransformPoint(
			  interactionPosition);
			// calculate offset in local space. so bias coordinates from 0.5,-0.5 to 0, -1.
			// 0 is no offset, 1.0 in local space is max offset pushing inwards
			Vector3 offsetVector = localSpacePosition - LOCAL_SIZE_HALVED * Vector3.one;
			// affect offset by button scale. only care about y (since y goes inwards)
			float scaledLocalSpaceOffset = offsetVector.y * buttonScale.y;

			// restrict button movement. can only go so far in negative direction, and cannot
			// be positive (which would cause the button to "stick out")
			if (scaledLocalSpaceOffset > -_contactMaxDisplacementDistance && scaledLocalSpaceOffset
				<= 0.0f)
			{
				transform.localPosition = new Vector3(_oldPosition.x, _oldPosition.y +
				  scaledLocalSpaceOffset, _oldPosition.z);
			}
		}

		private void InteractableStateChanged(InteractableStateArgs obj)
		{
			_buttonInContactOrActionStates = false;
			_glowRenderer.gameObject.SetActive(obj.NewInteractableState >
			  InteractableState.Default);
			switch (obj.NewInteractableState)
			{
				case InteractableState.ContactState:
					StopResetLerping();
					_buttonMaterial.SetColor(_materialColorId, _buttonContactColor);
					_buttonInContactOrActionStates = true;
					break;
				case InteractableState.ProximityState:
					_buttonMaterial.SetColor(_materialColorId, _buttonDefaultColor);
					LerpToOldPosition();
					break;
				case InteractableState.ActionState:
					StopResetLerping();
					_buttonMaterial.SetColor(_materialColorId, _buttonActionColor);
					PlaySound(_actionSoundEffect);
					_buttonInContactOrActionStates = true;
					break;
				default:
					_buttonMaterial.SetColor(_materialColorId, _buttonDefaultColor);
					LerpToOldPosition();
					break;
			}
		}

		private void PlaySound(AudioClip clip)
		{
			_audioSource.timeSamples = 0;
			_audioSource.clip = clip;
			_audioSource.Play();
		}

		private void StopResetLerping()
		{
			if (_lerpToOldPositionCr != null)
			{
				StopCoroutine(_lerpToOldPositionCr);
			}
		}

		private void LerpToOldPosition()
		{
			if ((transform.localPosition - _oldPosition).sqrMagnitude < Mathf.Epsilon)
			{
				return;
			}

			StopResetLerping();
			_lerpToOldPositionCr = StartCoroutine(ResetPosition());
		}

		private IEnumerator ResetPosition()
		{
			var startTime = Time.time;
			var endTime = Time.time + LERP_TO_OLD_POS_DURATION;

			while (Time.time < endTime)
			{
				transform.localPosition = Vector3.Lerp(transform.localPosition, _oldPosition,
				  (Time.time - startTime) / LERP_TO_OLD_POS_DURATION);
				yield return null;
			}

			transform.localPosition = _oldPosition;
			_lerpToOldPositionCr = null;
		}
	}
}