// Outline Object Copy|Highlighters|40030 namespace VRTK.Highlighters { using UnityEngine; using System; using System.Collections.Generic; using System.Linq; /// /// Creates a mesh copy and applies an outline shader which is toggled on and off when highlighting the object. /// /// /// > A valid mesh must be found or provided for the clone mesh to be created. /// /// **Script Usage:** /// * Place the `VRTK_OutlineObjectCopyHighlighter` script on either: /// * The GameObject of the Interactable Object to highlight. /// * Any other scene GameObject and then link that GameObject to the Interactable Objects `Object Highlighter` parameter to denote use of the highlighter. /// * Ensure the `Active` parameter is checked. /// /// /// `VRTK/Examples/005_Controller_BasicObjectGrabbing` demonstrates the outline highlighting on the green sphere when the controller touches it. /// /// `VRTK/Examples/035_Controller_OpacityAndHighlighting` demonstrates the outline highlighting if the left controller collides with the green box. /// [AddComponentMenu("VRTK/Scripts/Interactions/Highlighters/VRTK_OutlineObjectCopyHighlighter")] public class VRTK_OutlineObjectCopyHighlighter : VRTK_BaseHighlighter { [Tooltip("The thickness of the outline effect")] public float thickness = 1f; [Tooltip("The GameObjects to use as the model to outline. If one isn't provided then the first GameObject with a valid Renderer in the current GameObject hierarchy will be used.")] public GameObject[] customOutlineModels; [Tooltip("A path to a GameObject to find at runtime, if the GameObject doesn't exist at edit time.")] public string[] customOutlineModelPaths; [Tooltip("If the mesh has multiple sub-meshes to highlight then this should be checked, otherwise only the first mesh will be highlighted.")] public bool enableSubmeshHighlight = false; protected Material stencilOutline; protected Renderer[] highlightModels; protected string[] copyComponents = new string[] { "UnityEngine.MeshFilter", "UnityEngine.MeshRenderer" }; /// /// The Initialise method sets up the highlighter for use. /// /// Not used. /// An optional GameObject to specify which object to apply the highlighting to. /// A dictionary array containing the highlighter options:\r * `<'thickness', float>` - Same as `thickness` inspector parameter.\r * `<'customOutlineModels', GameObject[]>` - Same as `customOutlineModels` inspector parameter.\r * `<'customOutlineModelPaths', string[]>` - Same as `customOutlineModelPaths` inspector parameter. public override void Initialise(Color? color = null, GameObject affectObject = null, Dictionary options = null) { objectToAffect = (affectObject != null ? affectObject : gameObject); usesClonedObject = true; if (stencilOutline == null) { stencilOutline = Instantiate((Material)Resources.Load("OutlineBasic")); } SetOptions(options); ResetHighlighter(); } /// /// The ResetHighlighter method creates the additional model to use as the outline highlighted object. /// public override void ResetHighlighter() { DeleteExistingHighlightModels(); //First try and use the paths if they have been set ResetHighlighterWithCustomModelPaths(); //If the custom models have been set then use these to override any set paths. ResetHighlighterWithCustomModels(); //if no highlights set then try falling back ResetHighlightersWithCurrentGameObject(); } /// /// The Highlight method initiates the outline object to be enabled and display the outline colour. /// /// The colour to outline with. /// Not used. public override void Highlight(Color? color, float duration = 0f) { if (highlightModels != null && highlightModels.Length > 0 && stencilOutline != null) { stencilOutline.SetFloat("_Thickness", thickness); stencilOutline.SetColor("_OutlineColor", (Color)color); for (int i = 0; i < highlightModels.Length; i++) { if (highlightModels[i] != null) { highlightModels[i].gameObject.SetActive(true); highlightModels[i].material = stencilOutline; } } } } /// /// The Unhighlight method hides the outline object and removes the outline colour. /// /// Not used. /// Not used. public override void Unhighlight(Color? color = null, float duration = 0f) { if (objectToAffect == null) { return; } if (highlightModels != null) { for (int i = 0; i < highlightModels.Length; i++) { if (highlightModels[i] != null) { highlightModels[i].gameObject.SetActive(false); } } } } protected virtual void OnEnable() { if (customOutlineModels == null) { customOutlineModels = new GameObject[0]; } if (customOutlineModelPaths == null) { customOutlineModelPaths = new string[0]; } } protected virtual void OnDestroy() { if (highlightModels != null) { for (int i = 0; i < highlightModels.Length; i++) { if (highlightModels[i] != null) { Destroy(highlightModels[i]); } } } Destroy(stencilOutline); } protected virtual void ResetHighlighterWithCustomModels() { if (customOutlineModels != null && customOutlineModels.Length > 0) { highlightModels = new Renderer[customOutlineModels.Length]; for (int i = 0; i < customOutlineModels.Length; i++) { highlightModels[i] = CreateHighlightModel(customOutlineModels[i], ""); } } } protected virtual void ResetHighlighterWithCustomModelPaths() { if (customOutlineModelPaths != null && customOutlineModelPaths.Length > 0) { highlightModels = new Renderer[customOutlineModelPaths.Length]; for (int i = 0; i < customOutlineModelPaths.Length; i++) { highlightModels[i] = CreateHighlightModel(null, customOutlineModelPaths[i]); } } } protected virtual void ResetHighlightersWithCurrentGameObject() { if (highlightModels == null || highlightModels.Length == 0) { highlightModels = new Renderer[1]; highlightModels[0] = CreateHighlightModel(null, ""); } } protected virtual void SetOptions(Dictionary options = null) { float tmpThickness = GetOption(options, "thickness"); if (tmpThickness > 0f) { thickness = tmpThickness; } GameObject[] tmpCustomModels = GetOption(options, "customOutlineModels"); if (tmpCustomModels != null) { customOutlineModels = tmpCustomModels; } string[] tmpCustomModelPaths = GetOption(options, "customOutlineModelPaths"); if (tmpCustomModelPaths != null) { customOutlineModelPaths = tmpCustomModelPaths; } } protected virtual void DeleteExistingHighlightModels() { VRTK_PlayerObject[] existingHighlighterObjects = objectToAffect.GetComponentsInChildren(true); for (int i = 0; i < existingHighlighterObjects.Length; i++) { if (existingHighlighterObjects[i].objectType == VRTK_PlayerObject.ObjectTypes.Highlighter) { Destroy(existingHighlighterObjects[i].gameObject); } } highlightModels = new Renderer[0]; } protected virtual Renderer CreateHighlightModel(GameObject givenOutlineModel, string givenOutlineModelPath) { if (givenOutlineModel != null) { givenOutlineModel = (givenOutlineModel.GetComponent() ? givenOutlineModel : givenOutlineModel.GetComponentInChildren().gameObject); } else if (givenOutlineModelPath != "") { Transform getChildModel = objectToAffect.transform.Find(givenOutlineModelPath); givenOutlineModel = (getChildModel ? getChildModel.gameObject : null); } GameObject copyModel = givenOutlineModel; if (copyModel == null) { Renderer copyModelRenderer = objectToAffect.GetComponentInChildren(); copyModel = (copyModelRenderer != null ? copyModelRenderer.gameObject : null); } if (copyModel == null) { VRTK_Logger.Error(VRTK_Logger.GetCommonMessage(VRTK_Logger.CommonMessageKeys.REQUIRED_COMPONENT_MISSING_FROM_GAMEOBJECT, "VRTK_OutlineObjectCopyHighlighter", "Renderer", "the same or child", " to add the highlighter to")); return null; } GameObject highlightModel = new GameObject(objectToAffect.name + "_HighlightModel"); highlightModel.transform.SetParent(copyModel.transform.parent, false); highlightModel.transform.localPosition = copyModel.transform.localPosition; highlightModel.transform.localRotation = copyModel.transform.localRotation; highlightModel.transform.localScale = copyModel.transform.localScale; highlightModel.transform.SetParent(objectToAffect.transform); Component[] copyModelComponents = copyModel.GetComponents(); for (int i = 0; i < copyModelComponents.Length; i++) { Component copyModelComponent = copyModelComponents[i]; if (Array.IndexOf(copyComponents, copyModelComponent.GetType().ToString()) >= 0) { VRTK_SharedMethods.CloneComponent(copyModelComponent, highlightModel); } } MeshFilter copyMesh = copyModel.GetComponent(); MeshFilter highlightMesh = highlightModel.GetComponent(); Renderer returnHighlightModel = highlightModel.GetComponent(); if (highlightMesh != null) { if (enableSubmeshHighlight) { HashSet combine = new HashSet(); for (int i = 0; i < copyMesh.mesh.subMeshCount; i++) { CombineInstance ci = new CombineInstance(); ci.mesh = copyMesh.mesh; ci.subMeshIndex = i; ci.transform = copyMesh.transform.localToWorldMatrix; combine.Add(ci); } highlightMesh.mesh = new Mesh(); highlightMesh.mesh.CombineMeshes(combine.ToArray(), true, false); } else { highlightMesh.mesh = copyMesh.mesh; } returnHighlightModel.material = stencilOutline; returnHighlightModel.shadowCastingMode = copyModel.transform.GetComponent().shadowCastingMode; } highlightModel.SetActive(false); VRTK_PlayerObject.SetPlayerObject(highlightModel, VRTK_PlayerObject.ObjectTypes.Highlighter); return returnHighlightModel; } } }