Skip to content

Commit

Permalink
feat(Highlighters): add highlighter composition scripts
Browse files Browse the repository at this point in the history
A new Highlighter composition concept has been added that allows a
separate component to deal with highlighting of objects.

Previously, the Interactable Object and the Controller highlighting
were done with the same logic in two different scripts and it also
meant that there was only one way to highlight these elements.

The new highlighter scripts mean the logic for highlighting is in
one script but other highlighters can be created and added to an
object to provide alternative highlighting methods.
  • Loading branch information
thestonefox committed Sep 16, 2016
1 parent 0c4d5d5 commit 5b850bb
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Loading

0 comments on commit 5b850bb

Please sign in to comment.