// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using UnityEngine; namespace Microsoft.MixedReality.Toolkit.UI { /// /// A background with 'fake' inertia /// Useful for soft or liquid objects /// [AddComponentMenu("Scripts/MRTK/SDK/ToolTipBackgroundBlob")] public class ToolTipBackgroundBlob : MonoBehaviour, IToolTipBackground { // Which transforms to use for each type of distortion // See the ToolTipBalloon prefab for an example of which transforms to target #region Transform Targets [Header("Transform targets")] [SerializeField] private Transform positionTarget = null; [SerializeField] private Transform rotationTarget = null; [SerializeField] private Transform distortionTarget = null; [SerializeField] private Transform attachPointOffset = null; [SerializeField] private ToolTip toolTip = null; #endregion Transform Targets /// /// Determines whether background of Tooltip is visible. /// public bool IsVisible { set { if (BackgroundRenderer) { BackgroundRenderer.enabled = value; } } } [Header("Blob settings")] [SerializeField] [Range(0f, 5f)] private float blobInertia = 0.5f; public float BlobInertia { get { return blobInertia; } set { blobInertia = Mathf.Clamp(value, 0, maxInertia); } } [SerializeField] [Range(0f, 1f)] private float blobDistortion = 0.1f; public float BlobDistortion { get { return blobDistortion; } set { blobDistortion = Mathf.Clamp(value, 0, maxDistortion); } } [SerializeField] [Range(0f, 1f)] private float blobRotation = 0.1f; public float BlobRotation { get { return blobRotation; } set { blobRotation = Mathf.Clamp(value, 0, maxRotation); } } [SerializeField] [Range(0.1f, 5f)] private float positionCorrectionStrength = 1f; public float PositionCorrectionStrength { get { return positionCorrectionStrength; } set { positionCorrectionStrength = Mathf.Clamp(value, minPositionCorrection, maxPositionCorrection); } } [SerializeField] [Range(0.1f, 5f)] private float distortionCorrectionStrength = 1f; public float DistortionCorrectionStrength { get { return distortionCorrectionStrength; } set { distortionCorrectionStrength = Mathf.Clamp(value, minDistortionCorrection, maxDistortionCorrection); } } [SerializeField] [Range(0.1f, 5f)] private float rotationCorrectionStrength = 1f; public float RotationCorrectionStrength { get { return rotationCorrectionStrength; } set { rotationCorrectionStrength = Mathf.Clamp(value, minRotationCorrection, maxRotationCorrection); } } [SerializeField] private Vector3 blobOffset; public Vector3 BlobOffset { get { return blobOffset; } set { blobOffset = value; } } private const float maxInertia = 5f; private const float maxDistortion = 1f; private const float maxRotation = 1f; private const float minPositionCorrection = 0.1f; private const float minDistortionCorrection = 0.1f; private const float minRotationCorrection = 0.1f; private const float maxPositionCorrection = 5f; private const float maxDistortionCorrection = 5f; private const float maxRotationCorrection = 5f; private readonly Bounds defaultBounds = new Bounds(Vector3.zero, Vector3.one); private MeshFilter backgroundRendererMeshFilter; private Vector3 lastPosition; private Vector3 velocity; private Vector3 distortion; private Vector3 rotation; private Bounds localContentBounds; private Bounds inertialContentBounds; /// /// Mesh renderer for mesh background. /// public MeshRenderer BackgroundRenderer; private void OnEnable() { lastPosition = positionTarget.position; inertialContentBounds = defaultBounds; localContentBounds = defaultBounds; velocity = Vector3.zero; backgroundRendererMeshFilter = BackgroundRenderer.GetComponent(); } public void OnContentChange(Vector3 localContentSize, Vector3 localContentOffset, Transform contentParentTransform) { // Get the size of the mesh and use this to adjust the local content size on the x / y axis // This will accommodate meshes that aren't built to 1,1 scale Bounds meshBounds = backgroundRendererMeshFilter.sharedMesh.bounds; localContentSize.x /= meshBounds.size.x; localContentSize.y /= meshBounds.size.y; localContentSize.z = 1; localContentBounds.size = Vector3.one; // localContentSize; localContentBounds.center = localContentOffset; } private void Update() { // Adjust center and size by velocity Vector3 currentPosition = positionTarget.position; velocity = Vector3.Lerp(velocity, lastPosition - currentPosition, 1f / blobInertia * Time.deltaTime); Vector3 currentDistortion = -velocity * blobDistortion; distortion = Vector3.Lerp(distortion, currentDistortion, 1f / blobDistortion * Time.deltaTime); inertialContentBounds.center += velocity; Vector3 size = inertialContentBounds.size + distortion; inertialContentBounds.size = size; Vector3 currentRotation = Vector3.zero; currentRotation.x = velocity.x * 360; currentRotation.z = velocity.z * 360; currentRotation.y = velocity.y * 360; currentRotation = rotationTarget.TransformDirection(currentRotation); rotation = Vector3.Lerp(rotation, currentRotation, 1f / blobRotation * Time.deltaTime); // Correct the center and size inertialContentBounds.center = Vector3.Lerp(inertialContentBounds.center, localContentBounds.center, Time.deltaTime * positionCorrectionStrength); inertialContentBounds.size = Vector3.Lerp(inertialContentBounds.size, localContentBounds.size, Time.deltaTime * distortionCorrectionStrength); rotationTarget.localRotation = Quaternion.Lerp(positionTarget.localRotation, Quaternion.identity, Time.deltaTime * rotationCorrectionStrength); // Apply the center and size positionTarget.localPosition = inertialContentBounds.center + blobOffset; distortionTarget.localScale = inertialContentBounds.size; rotationTarget.Rotate(velocity * blobRotation * 360); // Adjust the tool tip attach position toolTip.AttachPointPosition = attachPointOffset.position; lastPosition = currentPosition; } } }