mixedreality/com.microsoft.mixedreality..../Core/Utilities/StandardShader/ProximityLight.cs

320 lines
12 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Microsoft.MixedReality.Toolkit.Utilities
{
/// <summary>
/// Utility component to animate and visualize a light that can be used with
/// the "MixedRealityToolkit/Standard" shader "_ProximityLight" feature.
/// </summary>
[ExecuteInEditMode]
[HelpURL("https://docs.microsoft.com/windows/mixed-reality/mrtk-unity/features/rendering/proximity-light")]
[AddComponentMenu("Scripts/MRTK/Core/ProximityLight")]
public class ProximityLight : MonoBehaviour
{
// Two proximity lights are supported at this time. Excess lights will
// be held in a queue and addded to the shader in a first-in, first-out
// manner.
private const int proximityLightCount = 2;
private const int proximityLightQueueSize = 6;
private const int proximityLightDataSize = 6;
private static List<ProximityLight> activeProximityLights = new List<ProximityLight>(proximityLightQueueSize);
private static Vector4[] proximityLightData = new Vector4[proximityLightCount * proximityLightDataSize];
private static int proximityLightDataID;
private static int lastProximityLightUpdate = -1;
[Serializable]
public class LightSettings
{
/// <summary>
/// Specifies the radius of the ProximityLight effect when near to a surface.
/// </summary>
public float NearRadius
{
get { return nearRadius; }
set { nearRadius = value; }
}
[Header("Proximity Settings")]
[Tooltip("Specifies the radius of the ProximityLight effect when near to a surface.")]
[SerializeField]
[Range(0.0f, 1.0f)]
private float nearRadius = 0.05f;
/// <summary>
/// Specifies the radius of the ProximityLight effect when far from a surface.
/// </summary>
public float FarRadius
{
get { return farRadius; }
set { farRadius = value; }
}
[Tooltip("Specifies the radius of the ProximityLight effect when far from a surface.")]
[SerializeField]
[Range(0.0f, 1.0f)]
private float farRadius = 0.2f;
/// <summary>
/// Specifies the distance a ProximityLight must be from a surface to be considered near.
/// </summary>
public float NearDistance
{
get { return nearDistance; }
set { nearDistance = value; }
}
[Tooltip("Specifies the distance a ProximityLight must be from a surface to be considered near.")]
[SerializeField]
[Range(0.0f, 1.0f)]
private float nearDistance = 0.02f;
/// <summary>
/// When a ProximityLight is near, the smallest size percentage from the far size it can shrink to.
/// </summary>
public float MinNearSizePercentage
{
get { return minNearSizePercentage; }
set { minNearSizePercentage = value; }
}
[Tooltip("When a ProximityLight is near, the smallest size percentage from the far size it can shrink to.")]
[SerializeField]
[Range(0.0f, 1.0f)]
private float minNearSizePercentage = 0.35f;
/// <summary>
/// The color of the ProximityLight gradient at the center (RGB) and (A) is gradient extent.
/// </summary>
public Color CenterColor
{
get { return centerColor; }
set { centerColor = value; }
}
[Header("Color Settings")]
[Tooltip("The color of the ProximityLight gradient at the center (RGB) and (A) is gradient extent.")]
[ColorUsageAttribute(true, true)]
[SerializeField]
private Color centerColor = new Color(54.0f / 255.0f, 142.0f / 255.0f, 250.0f / 255.0f, 0.0f / 255.0f);
/// <summary>
/// The color of the ProximityLight gradient at the center (RGB) and (A) is gradient extent.
/// </summary>
public Color MiddleColor
{
get { return middleColor; }
set { middleColor = value; }
}
[Tooltip("The color of the ProximityLight gradient at the middle (RGB) and (A) is gradient extent.")]
[SerializeField]
[ColorUsageAttribute(true, true)]
private Color middleColor = new Color(47.0f / 255.0f, 132.0f / 255.0f, 255.0f / 255.0f, 51.0f / 255.0f);
/// <summary>
/// The color of the ProximityLight gradient at the center (RGB) and (A) is gradient extent.
/// </summary>
public Color OuterColor
{
get { return outerColor; }
set { outerColor = value; }
}
[Tooltip("The color of the ProximityLight gradient at the outer (RGB) and (A) is gradient extent.")]
[SerializeField]
[ColorUsageAttribute(true, true)]
private Color outerColor = new Color((82.0f * 3.0f) / 255.0f, (31.0f * 3.0f) / 255.0f, (191.0f * 3.0f) / 255.0f, 255.0f / 255.0f);
}
public LightSettings Settings
{
get { return settings; }
set { settings = value; }
}
[SerializeField]
private LightSettings settings = new LightSettings();
private float pulseTime;
private float pulseFade;
/// <summary>
/// Initiates a pulse, if one is not already occurring, which simulates a user touching a surface.
/// </summary>
/// <param name="pulseDuration">How long in seconds should the pulse animate over.</param>
/// <param name="fadeBegin">At what point during the pulseDuration should the pulse begin to fade out as a percentage. Range should be [0, 1].</param>
/// <param name="fadeSpeed">The speed to fade in and out.</param>
public void Pulse(float pulseDuration = 0.2f, float fadeBegin = 0.8f, float fadeSpeed = 10.0f)
{
if (pulseTime <= 0.0f)
{
StartCoroutine(PulseRoutine(pulseDuration, fadeBegin, fadeSpeed));
}
}
private void OnEnable()
{
AddProximityLight(this);
UpdateProximityLights(true);
}
private void OnDisable()
{
RemoveProximityLight(this);
UpdateProximityLights(true);
}
#if UNITY_EDITOR
private void Update()
{
if (Application.isPlaying)
{
return;
}
Initialize();
UpdateProximityLights();
}
#endif // UNITY_EDITOR
private void LateUpdate()
{
UpdateProximityLights();
}
private void OnDrawGizmosSelected()
{
if (!enabled)
{
return;
}
Vector3[] directions = new Vector3[] { Vector3.right, Vector3.left, Vector3.up, Vector3.down, Vector3.forward, Vector3.back };
Gizmos.color = new Color(Settings.CenterColor.r, Settings.CenterColor.g, Settings.CenterColor.b);
Gizmos.DrawWireSphere(transform.position, Settings.NearRadius);
foreach (Vector3 direction in directions)
{
Gizmos.DrawIcon(transform.position + direction * Settings.NearRadius, string.Empty, false);
}
Gizmos.color = new Color(Settings.OuterColor.r, Settings.OuterColor.g, Settings.OuterColor.b);
Gizmos.DrawWireSphere(transform.position, Settings.FarRadius);
foreach (Vector3 direction in directions)
{
Gizmos.DrawIcon(transform.position + direction * Settings.FarRadius, string.Empty, false);
}
}
private static void AddProximityLight(ProximityLight light)
{
activeProximityLights.Add(light);
}
private static void RemoveProximityLight(ProximityLight light)
{
activeProximityLights.Remove(light);
}
private static void Initialize()
{
proximityLightDataID = Shader.PropertyToID("_ProximityLightData");
}
private static void UpdateProximityLights(bool forceUpdate = false)
{
if (lastProximityLightUpdate == -1)
{
Initialize();
}
if (!forceUpdate && (Time.frameCount == lastProximityLightUpdate))
{
return;
}
int lightIndex, queueIndex;
for (lightIndex = queueIndex = 0; queueIndex < activeProximityLights.Count && lightIndex < proximityLightCount; ++queueIndex)
{
ProximityLight light = activeProximityLights[queueIndex];
if (light)
{
int dataIndex = lightIndex++ * proximityLightDataSize;
proximityLightData[dataIndex] = new Vector4(light.transform.position.x,
light.transform.position.y,
light.transform.position.z,
1.0f);
float pulseScaler = 1.0f + light.pulseTime;
proximityLightData[dataIndex + 1] = new Vector4(light.Settings.NearRadius * pulseScaler,
1.0f / Mathf.Clamp(light.Settings.FarRadius * pulseScaler, 0.001f, 1.0f),
1.0f / Mathf.Clamp(light.Settings.NearDistance * pulseScaler, 0.001f, 1.0f),
Mathf.Clamp01(light.Settings.MinNearSizePercentage));
proximityLightData[dataIndex + 2] = new Vector4(light.Settings.NearDistance * light.pulseTime,
Mathf.Clamp01(1.0f - light.pulseFade),
0.0f,
0.0f);
proximityLightData[dataIndex + 3] = light.Settings.CenterColor;
proximityLightData[dataIndex + 4] = light.Settings.MiddleColor;
proximityLightData[dataIndex + 5] = light.Settings.OuterColor;
}
}
for (; lightIndex < proximityLightCount; ++lightIndex)
{
int dataIndex = lightIndex * proximityLightDataSize;
proximityLightData[dataIndex] = Vector4.zero;
}
Shader.SetGlobalVectorArray(proximityLightDataID, proximityLightData);
lastProximityLightUpdate = Time.frameCount;
}
private IEnumerator PulseRoutine(float pulseDuration, float fadeBegin, float fadeSpeed)
{
float pulseTimer = 0.0f;
while (pulseTimer < pulseDuration)
{
pulseTimer += Time.deltaTime;
pulseTime = pulseTimer / pulseDuration;
if (pulseTime > fadeBegin)
{
pulseFade += Time.deltaTime;
}
yield return null;
}
while (pulseFade < 1.0f)
{
pulseFade += Time.deltaTime * fadeSpeed;
yield return null;
}
pulseTime = 0.0f;
while (pulseFade > 0.0f)
{
pulseFade -= Time.deltaTime * fadeSpeed;
yield return null;
}
pulseFade = 0.0f;
}
}
}