233 lines
8.2 KiB
C#
233 lines
8.2 KiB
C#
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
using UnityEngine;
|
|
|
|
namespace Microsoft.MixedReality.Toolkit.Utilities.Solvers
|
|
{
|
|
/// <summary>
|
|
/// ConstantViewSize solver scales to maintain a constant size relative to the view (currently tied to the Camera)
|
|
/// </summary>
|
|
[AddComponentMenu("Scripts/MRTK/SDK/ConstantViewSize")]
|
|
public class ConstantViewSize : Solver
|
|
{
|
|
#region ConstantViewSize Parameters
|
|
|
|
[Range(0f, 1f)]
|
|
[SerializeField]
|
|
[Tooltip("The object take up this percent vertically in our view (not technically a percent use 0.5 for 50%)")]
|
|
private float targetViewPercentV = 0.5f;
|
|
|
|
/// <summary>
|
|
/// The object take up this percent vertically in our view (not technically a percent use 0.5 for 50%)
|
|
/// </summary>
|
|
public float TargetViewPercentV
|
|
{
|
|
get { return targetViewPercentV; }
|
|
set { targetViewPercentV = value; }
|
|
}
|
|
|
|
[SerializeField]
|
|
[Tooltip("If the object is closer than MinDistance, the distance used is clamped here")]
|
|
private float minDistance = 0.5f;
|
|
|
|
/// <summary>
|
|
/// If the object is closer than MinDistance, the distance used is clamped here
|
|
/// </summary>
|
|
public float MinDistance
|
|
{
|
|
get { return minDistance; }
|
|
set { minDistance = value; }
|
|
}
|
|
|
|
[SerializeField]
|
|
[Tooltip("If the object is farther than MaxDistance, the distance used is clamped here")]
|
|
private float maxDistance = 3.5f;
|
|
|
|
/// <summary>
|
|
/// If the object is farther than MaxDistance, the distance used is clamped here
|
|
/// </summary>
|
|
public float MaxDistance
|
|
{
|
|
get { return maxDistance; }
|
|
set { maxDistance = value; }
|
|
}
|
|
|
|
[SerializeField]
|
|
[Tooltip("Minimum scale value possible (world space scale)")]
|
|
private float minScale = 0.01f;
|
|
|
|
/// <summary>
|
|
/// Minimum scale value possible (world space scale)
|
|
/// </summary>
|
|
public float MinScale
|
|
{
|
|
get { return minScale; }
|
|
set { minScale = value; }
|
|
}
|
|
|
|
[SerializeField]
|
|
[Tooltip("Maximum scale value possible (world space scale)")]
|
|
private float maxScale = 100f;
|
|
|
|
/// <summary>
|
|
/// Maximum scale value possible (world space scale)
|
|
/// </summary>
|
|
public float MaxScale
|
|
{
|
|
get { return maxScale; }
|
|
set { maxScale = value; }
|
|
}
|
|
|
|
[SerializeField]
|
|
[Tooltip("Used for dead zone for scaling")]
|
|
private float scaleBuffer = 0.01f;
|
|
|
|
/// <summary>
|
|
/// Used for dead zone for scaling
|
|
/// </summary>
|
|
public float ScaleBuffer
|
|
{
|
|
get { return scaleBuffer; }
|
|
set { scaleBuffer = value; }
|
|
}
|
|
|
|
[SerializeField]
|
|
[Tooltip("Overrides auto size calculation with provided manual size. If 0, solver calculates size")]
|
|
private float manualObjectSize = 0;
|
|
|
|
/// <summary>
|
|
/// Overrides auto size calculation with provided manual size. If 0, solver calculates size
|
|
/// </summary>
|
|
public float ManualObjectSize
|
|
{
|
|
get { return manualObjectSize; }
|
|
set
|
|
{
|
|
manualObjectSize = value;
|
|
RecalculateBounds();
|
|
}
|
|
}
|
|
|
|
public ScaleState ScaleState { get; private set; } = ScaleState.Static;
|
|
|
|
/// <summary>
|
|
/// 0 to 1 between MinScale and MaxScale. If current is less than max, then scaling is being applied.
|
|
/// This value is subject to inaccuracies due to smoothing/interpolation/momentum.
|
|
/// </summary>
|
|
public float CurrentScalePercent { get; private set; } = 1f;
|
|
|
|
/// <summary>
|
|
/// 0 to 1 between MinDistance and MaxDistance. If current is less than max, object is potentially on a surface [or some other condition like interpolating] (since it may still be on surface, but scale percent may be clamped at max).
|
|
/// This value is subject to inaccuracies due to smoothing/interpolation/momentum.
|
|
/// </summary>
|
|
public float CurrentDistancePercent { get; private set; } = 1f;
|
|
|
|
/// <summary>
|
|
/// Returns the scale to be applied based on the FOV. This scale will be multiplied by distance as part of
|
|
/// the final scale calculation, so this is the ratio of vertical fov to distance.
|
|
/// </summary>
|
|
public float FovScale
|
|
{
|
|
get
|
|
{
|
|
float cameraFovRadians = (CameraCache.Main.aspect * CameraCache.Main.fieldOfView) * Mathf.Deg2Rad;
|
|
float sinFov = Mathf.Sin(cameraFovRadians * 0.5f);
|
|
return 2f * targetViewPercentV * sinFov / objectSize;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
private float fovScalar = 1f;
|
|
private float objectSize = 1f;
|
|
|
|
protected override void Start()
|
|
{
|
|
base.Start();
|
|
RecalculateBounds();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void SolverUpdate()
|
|
{
|
|
float lastScalePct = CurrentScalePercent;
|
|
|
|
if (SolverHandler.TransformTarget != null)
|
|
{
|
|
// Get current fov each time instead of trying to cache it. Can never count on init order these days
|
|
fovScalar = FovScale;
|
|
|
|
// Set the linked alt scale ahead of our work. This is an attempt to minimize jittering by having solvers work with an interpolated scale.
|
|
SolverHandler.AltScale.SetGoal(transform.localScale);
|
|
|
|
// Calculate scale based on distance from view. Do not interpolate so we can appear at a constant size if possible. Borrowed from greybox.
|
|
Vector3 targetPosition = SolverHandler.TransformTarget.position;
|
|
float distance = Mathf.Clamp(Vector3.Distance(transform.position, targetPosition), minDistance, maxDistance);
|
|
float scale = Mathf.Clamp(fovScalar * distance, minScale, maxScale);
|
|
GoalScale = Vector3.one * scale;
|
|
|
|
// Save some state information for external use
|
|
CurrentDistancePercent = Mathf.InverseLerp(minDistance, maxDistance, distance);
|
|
CurrentScalePercent = Mathf.InverseLerp(minScale, maxScale, scale);
|
|
}
|
|
|
|
float scaleDifference = (CurrentScalePercent - lastScalePct) / SolverHandler.DeltaTime;
|
|
|
|
if (scaleDifference > scaleBuffer)
|
|
{
|
|
ScaleState = ScaleState.Growing;
|
|
}
|
|
else if (scaleDifference < -scaleBuffer)
|
|
{
|
|
ScaleState = ScaleState.Shrinking;
|
|
}
|
|
else
|
|
{
|
|
ScaleState = ScaleState.Static;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to calculate the size of the bounds which contains all child renderers for attached GameObject. This information is used in the core solver calculations
|
|
/// </summary>
|
|
public void RecalculateBounds()
|
|
{
|
|
float baseSize;
|
|
|
|
// If user set object size override apply, otherwise compute baseSize
|
|
if (manualObjectSize > 0)
|
|
{
|
|
baseSize = manualObjectSize;
|
|
}
|
|
else
|
|
{
|
|
Vector3 cachedScale = transform.root.localScale;
|
|
transform.root.localScale = Vector3.one;
|
|
|
|
var combinedBounds = new Bounds(transform.position, Vector3.zero);
|
|
var renderers = GetComponentsInChildren<Renderer>();
|
|
|
|
for (var i = 0; i < renderers.Length; i++)
|
|
{
|
|
combinedBounds.Encapsulate(renderers[i].bounds);
|
|
}
|
|
|
|
baseSize = combinedBounds.extents.magnitude;
|
|
|
|
transform.root.localScale = cachedScale;
|
|
}
|
|
|
|
if (baseSize > 0)
|
|
{
|
|
objectSize = baseSize;
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning("ConstantViewSize: Object base size calculate was 0, defaulting to 1");
|
|
objectSize = 1f;
|
|
}
|
|
}
|
|
}
|
|
}
|