Skip to content

Commit

Permalink
Merge pull request #574 from thestonefox/feat/highlighters
Browse files Browse the repository at this point in the history
feat(Highlighters): add highlighter composition scripts - resolves #493
  • Loading branch information
thestonefox committed Sep 16, 2016
2 parents 0c4d5d5 + 5b850bb commit 6a480a0
Show file tree
Hide file tree
Showing 8 changed files with 452 additions and 113 deletions.
9 changes: 9 additions & 0 deletions Assets/VRTK/Scripts/Highlighters.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 36 additions & 0 deletions Assets/VRTK/Scripts/Highlighters/VRTK_BaseHighlighter.cs
@@ -0,0 +1,36 @@
// Base Highlighter|Highlighters|0010
namespace VRTK.Highlighters
{
using UnityEngine;
using System.Collections.Generic;

/// <summary>
/// The Base Highlighter is an abstract class that all other highlighters inherit and are required to implement the public methods.
/// </summary>
/// <remarks>
/// As this is an abstract class, it cannot be applied directly to a game object and performs no logic.
/// </remarks>
public abstract class VRTK_BaseHighlighter : MonoBehaviour
{
/// <summary>
/// The Initalise method is used to set up the state of the highlighter.
/// </summary>
/// <param name="color">An optional colour may be passed through at point of initialisation in case the highlighter requires it.</param>
/// <param name="options">An optional dictionary of highlighter specific options that may be differ with highlighter implementations.</param>
public abstract void Initialise(Color? color = null, Dictionary<string, object> options = null);

/// <summary>
/// The Highlight method is used to initiate the highlighting logic to apply to an object.
/// </summary>
/// <param name="color">An optional colour to highlight the game object to. The highlight colour may already have been set in the `Initialise` method so may not be required here.</param>
/// <param name="duration">An optional duration of how long before the highlight has occured. It can be used by highlighters to fade the colour if possible.</param>

public abstract void Highlight(Color? color = null, float duration = 0f);
/// <summary>
/// The Unhighlight method is used to initiate the logic that returns an object back to it's original appearance.
/// </summary>
/// <param name="color">An optional colour that could be used during the unhighlight phase. Usually will be left as null.</param>
/// <param name="duration">An optional duration of how long before the unhighlight has occured.</param>
public abstract void Unhighlight(Color? color = null, float duration = 0f);
}
}
12 changes: 12 additions & 0 deletions Assets/VRTK/Scripts/Highlighters/VRTK_BaseHighlighter.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

176 changes: 176 additions & 0 deletions Assets/VRTK/Scripts/Highlighters/VRTK_MaterialColorSwapHighlighter.cs
@@ -0,0 +1,176 @@
// Material Colour Swap|Highlighters|0020
namespace VRTK.Highlighters
{
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

/// <summary>
/// The Material Colour Swap Highlighter is a basic implementation that simply swaps the texture colour for the given highlight colour.
/// </summary>
/// <remarks>
/// Due to the way the object material is interacted with, changing the material colour will break Draw Call Batching in Unity whilst the object is highlighted.
///
/// The Draw Call Batching will resume on the original material when the item is no longer highlighted.
///
/// This is the default highlighter that is applied to any script that requires a highlighting component (e.g. `VRTK_Interactable_Object` or `VRTK_ControllerActions`).
/// </remarks>
public class VRTK_MaterialColorSwapHighlighter : VRTK_BaseHighlighter
{
/// <summary>
/// The emission colour of the texture will be the highlight colour but this percent darker.
/// </summary>
public float emissionDarken = 50f;

private Dictionary<string, Material[]> originalSharedRendererMaterials;
private Dictionary<string, Material[]> originalRendererMaterials;
private Dictionary<string, Coroutine> faderRoutines;
private bool resetMainTexture = false;

/// <summary>
/// The Initialise method sets up the highlighter for use.
/// </summary>
/// <param name="color">Not used.</param>
/// <param name="options">A dictionary array containing the highlighter options:\r * `&lt;'resetMainTexture', bool&gt;` - Determines if the default main texture should be cleared on highlight. `true` to reset the main default texture, `false` to not reset it.</param>
public override void Initialise(Color? color = null, Dictionary<string, object> options = null)
{
originalSharedRendererMaterials = new Dictionary<string, Material[]>();
originalRendererMaterials = new Dictionary<string, Material[]>();
faderRoutines = new Dictionary<string, Coroutine>();
StoreOriginalMaterials();

if (options != null && options.ContainsKey("resetMainTexture") && options["resetMainTexture"] != null && options["resetMainTexture"] is bool)
{
resetMainTexture = (bool)options["resetMainTexture"];
}
}

/// <summary>
/// The Highlight method initiates the change of colour on the object and will fade to that colour (from a base white colour) for the given duration.
/// </summary>
/// <param name="color">The colour to highlight to.</param>
/// <param name="duration">The time taken to fade to the highlighted colour.</param>
public override void Highlight(Color? color, float duration = 0f)
{
if (color == null)
{
return;
}
ChangeToHighlightColor((Color)color, duration);
}

/// <summary>
/// The Unhighlight method returns the object back to it's original colour.
/// </summary>
/// <param name="color">Not used.</param>
/// <param name="duration">Not used.</param>
public override void Unhighlight(Color? color = null, float duration = 0f)
{
if (originalRendererMaterials == null)
{
return;
}

foreach (var fadeRoutine in faderRoutines)
{
StopCoroutine(fadeRoutine.Value);
}
faderRoutines.Clear();

foreach (Renderer renderer in GetComponentsInChildren<Renderer>())
{
var objectReference = renderer.gameObject.GetInstanceID().ToString();
if (!originalRendererMaterials.ContainsKey(objectReference))
{
continue;
}

renderer.materials = originalRendererMaterials[objectReference];
renderer.sharedMaterials = originalSharedRendererMaterials[objectReference];
}
}

private void StoreOriginalMaterials()
{
originalSharedRendererMaterials.Clear();
originalRendererMaterials.Clear();
foreach (Renderer renderer in GetComponentsInChildren<Renderer>())
{
var objectReference = renderer.gameObject.GetInstanceID().ToString();
originalSharedRendererMaterials[objectReference] = renderer.sharedMaterials;
originalRendererMaterials[objectReference] = renderer.materials;
renderer.sharedMaterials = originalSharedRendererMaterials[objectReference];
}
}

private void ChangeToHighlightColor(Color color, float duration = 0f)
{
foreach (Renderer renderer in GetComponentsInChildren<Renderer>())
{
for (int i = 0; i < renderer.materials.Length; i++)
{
var material = renderer.materials[i];
var faderRoutineID = material.GetInstanceID().ToString();

if (faderRoutines.ContainsKey(faderRoutineID) && faderRoutines[faderRoutineID] != null)
{
StopCoroutine(faderRoutines[faderRoutineID]);
faderRoutines.Remove(faderRoutineID);
}

material.EnableKeyword("_EMISSION");

if (resetMainTexture && material.HasProperty("_MainTex"))
{
renderer.material.SetTexture("_MainTex", new Texture());
}

if (material.HasProperty("_Color"))
{
if (duration > 0f)
{
faderRoutines[faderRoutineID] = StartCoroutine(CycleColor(material, material.color, color, duration));
}
else
{
material.color = color;
if (material.HasProperty("_EmissionColor"))
{
material.SetColor("_EmissionColor", Darken(color, emissionDarken));
}
}
}
}
}
}

private IEnumerator CycleColor(Material material, Color startColor, Color endColor, float duration)
{
var elapsedTime = 0f;
while (elapsedTime <= duration)
{
elapsedTime += Time.deltaTime;
if (material.HasProperty("_Color"))
{
material.color = Color.Lerp(startColor, endColor, (elapsedTime / duration));
}
if (material.HasProperty("_EmissionColor"))
{
material.SetColor("_EmissionColor", Color.Lerp(startColor, Darken(endColor, emissionDarken), (elapsedTime / duration)));
}
yield return null;
}
}

private Color Darken(Color color, float percent)
{
return new Color(ColorPercent(color.r, percent), ColorPercent(color.g, percent), ColorPercent(color.b, percent), color.a);
}

private float ColorPercent(float value, float percent)
{
percent = Mathf.Clamp(percent, 0f, 100f);
return (percent == 0f ? value : (value - (percent / 100f)));
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

81 changes: 59 additions & 22 deletions Assets/VRTK/Scripts/VRTK_ControllerActions.cs
Expand Up @@ -4,6 +4,8 @@ namespace VRTK
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using Highlighters;

/// <summary>
/// Event Payload
Expand All @@ -24,6 +26,9 @@ public struct ControllerActionsEventArgs
/// <summary>
/// The Controller Actions script provides helper methods to deal with common controller actions. It deals with actions that can be done to the controller.
/// </summary>
/// <remarks>
/// The highlighting of the controller is defaulted to use the `VRTK_MaterialColorSwapHighlighter` if no other highlighter is applied to the Object.
/// </remarks>
/// <example>
/// `VRTK/Examples/016_Controller_HapticRumble` demonstrates the ability to hide a controller model and make the controller vibrate for a given length of time at a given intensity.
///
Expand All @@ -46,8 +51,9 @@ public class VRTK_ControllerActions : MonoBehaviour
private uint controllerIndex;
private ushort maxHapticVibration = 3999;
private bool controllerHighlighted = false;
private Dictionary<GameObject, Material> storedMaterials;
private Dictionary<string, Transform> cachedElements;
private VRTK_BaseHighlighter objectHighlighter;
private Dictionary<string, object> highlighterOptions;

public virtual void OnControllerModelVisible(ControllerActionsEventArgs e)
{
Expand Down Expand Up @@ -103,7 +109,7 @@ public void ToggleControllerModel(bool state, GameObject grabbedChildObject)
}

controllerVisible = state;
if(state)
if (state)
{
OnControllerModelVisible(SetActionEvent(controllerIndex));
}
Expand Down Expand Up @@ -168,18 +174,10 @@ public void HighlightControllerElement(GameObject element, Color? highlight, flo
return;
}

var renderer = element.GetComponent<Renderer>();
if (renderer && renderer.material)
var highlighter = element.GetComponent<VRTK_BaseHighlighter>();
if (highlighter)
{
if (!storedMaterials.ContainsKey(element))
{
storedMaterials.Add(element, new Material(renderer.material));
}
renderer.material.SetTexture("_MainTex", new Texture());
if (renderer.material.HasProperty("_Color"))
{
StartCoroutine(CycleColor(renderer.material, new Color(renderer.material.color.r, renderer.material.color.g, renderer.material.color.b), highlight ?? Color.white, fadeDuration));
}
highlighter.Highlight(highlight ?? Color.white, fadeDuration);
}
}

Expand All @@ -194,14 +192,10 @@ public void UnhighlightControllerElement(GameObject element)
return;
}

var renderer = element.GetComponent<Renderer>();
if (renderer && renderer.material)
var highlighter = element.GetComponent<VRTK_BaseHighlighter>();
if (highlighter)
{
if (storedMaterials.ContainsKey(element))
{
renderer.material = new Material(storedMaterials[element]);
storedMaterials.Remove(element);
}
highlighter.Unhighlight();
}
}

Expand Down Expand Up @@ -340,15 +334,58 @@ public void TriggerHapticPulse(ushort strength, float duration, float pulseInter
private void Awake()
{
gameObject.layer = LayerMask.NameToLayer("Ignore Raycast");
storedMaterials = new Dictionary<GameObject, Material>();
cachedElements = new Dictionary<string, Transform>();
}

private void OnEnable()
{
StartCoroutine(SetupHighlighter());
}

private void Update()
{
controllerIndex = VRTK_DeviceFinder.GetControllerIndex(gameObject);
}

private IEnumerator SetupHighlighter()
{
highlighterOptions = new Dictionary<string, object>();
highlighterOptions.Add("resetMainTexture", true);
while (GetElementTransform(VRTK_SDK_Bridge.defaultBodyModelPath) == null)
{
yield return null;
}

objectHighlighter = GetComponent<VRTK_BaseHighlighter>();
if (!objectHighlighter)
{
objectHighlighter = gameObject.AddComponent<VRTK_MaterialColorSwapHighlighter>();
}

objectHighlighter.Initialise(null, highlighterOptions);

AddHighlighterToElement(GetElementTransform(VRTK_SDK_Bridge.defaultApplicationMenuModelPath), objectHighlighter);
AddHighlighterToElement(GetElementTransform(VRTK_SDK_Bridge.defaultBodyModelPath), objectHighlighter);
AddHighlighterToElement(GetElementTransform(VRTK_SDK_Bridge.defaultGripLeftModelPath), objectHighlighter);
AddHighlighterToElement(GetElementTransform(VRTK_SDK_Bridge.defaultGripRightModelPath), objectHighlighter);
AddHighlighterToElement(GetElementTransform(VRTK_SDK_Bridge.defaultSystemModelPath), objectHighlighter);
AddHighlighterToElement(GetElementTransform(VRTK_SDK_Bridge.defaultTouchpadModelPath), objectHighlighter);
AddHighlighterToElement(GetElementTransform(VRTK_SDK_Bridge.defaultTriggerModelPath), objectHighlighter);
}

private void AddHighlighterToElement(Transform element, VRTK_BaseHighlighter highlighter)
{
if (element)
{
VRTK_BaseHighlighter tmpComponent = (VRTK_BaseHighlighter)element.gameObject.AddComponent(highlighter.GetType());
foreach (FieldInfo f in highlighter.GetType().GetFields())
{
f.SetValue(tmpComponent, f.GetValue(highlighter));
}
tmpComponent.Initialise(null, highlighterOptions);
}
}

private IEnumerator HapticPulse(float duration, ushort hapticPulseStrength, float pulseInterval)
{
if (pulseInterval <= 0)
Expand Down Expand Up @@ -399,7 +436,7 @@ private void ToggleHighlightAlias(bool state, string transformPath, Color? highl
private ControllerActionsEventArgs SetActionEvent(uint index)
{
ControllerActionsEventArgs e;
e.controllerIndex= index;
e.controllerIndex = index;
return e;
}
}
Expand Down

0 comments on commit 6a480a0

Please sign in to comment.