using System; using System.Collections.Generic; using System.Linq; using UnityEngine; namespace UDE_HAND_INTERACTION { [RequireComponent(typeof(InteractableObject))] public class InteractableObjectOutline : MonoBehaviour { private Renderer[] renderers; private MeshFilter[] meshFilters; private Material MaskOutlineMat; private Material FillOutlineMat; [SerializeField] private Color outlineColor = new Color(1, 0, 0, 1); [SerializeField, Range(0f, 10f)] private float outlineWidth = 6f; [SerializeField, HideInInspector] private List bakeKeys = new List(); [SerializeField, HideInInspector] private List> bakeValues = new List>(); public List ObjectRenderers { get; private set; } = new List(); public List ObjectMeshFilters { get; private set; } = new List(); private static HashSet registeredMeshes = new HashSet(); bool[] TwoHandEnable = new bool[2] { false, false }; void Awake() { FindObjectRenders(transform); renderers = ObjectRenderers.ToArray(); meshFilters = ObjectMeshFilters.ToArray(); MaskOutlineMat = Instantiate(Resources.Load(@"Mask")); FillOutlineMat = Instantiate(Resources.Load(@"Fill")); MaskOutlineMat.name = "Mask(TempClone)"; FillOutlineMat.name = "Fill(TempClone)"; foreach (var meshFilter in meshFilters) { if (!registeredMeshes.Add(meshFilter.sharedMesh)) continue; var index = bakeKeys.IndexOf(meshFilter.sharedMesh); var smoothNormals = (index >= 0) ? bakeValues[index] : SmoothNormals(meshFilter.sharedMesh); meshFilter.sharedMesh.SetUVs(3, smoothNormals); var renderer = meshFilter.GetComponent(); if (renderer != null) { CombineSubmeshes(meshFilter.sharedMesh, renderer.sharedMaterials); } } FillOutlineMat.SetColor("_OutlineColor", outlineColor); FillOutlineMat.SetFloat("_OutlineWidth", outlineWidth); enabled = false; } public void EnableWithHand(int hand, bool enable) { TwoHandEnable[hand] = enable; enabled = TwoHandEnable[0] || TwoHandEnable[1]; } void OnEnable() { foreach (var renderer in renderers) { var materials = renderer.sharedMaterials.ToList(); materials.Add(MaskOutlineMat); materials.Add(FillOutlineMat); renderer.materials = materials.ToArray(); } } void OnDisable() { foreach (var renderer in renderers) { var materials = renderer.sharedMaterials.ToList(); materials.Remove(MaskOutlineMat); materials.Remove(FillOutlineMat); renderer.materials = materials.ToArray(); } } void OnValidate() { if (bakeKeys.Count != bakeValues.Count) { bakeKeys.Clear(); bakeValues.Clear(); } if (bakeKeys.Count == 0) { var bakedMeshes = new HashSet(); foreach (var meshFilter in GetComponentsInChildren()) { if (!bakedMeshes.Add(meshFilter.sharedMesh)) continue; var smoothNormals = SmoothNormals(meshFilter.sharedMesh); bakeKeys.Add(meshFilter.sharedMesh); bakeValues.Add(smoothNormals); } } } void Update() { FillOutlineMat.SetColor("_OutlineColor", outlineColor); FillOutlineMat.SetFloat("_OutlineWidth", outlineWidth); } void OnDestroy() { Destroy(MaskOutlineMat); Destroy(FillOutlineMat); } void FindObjectRenders(Transform trans) { if (trans.TryGetComponent(out var render)) { ObjectRenderers.Add(render); } if (trans.TryGetComponent(out var filter)) { ObjectMeshFilters.Add(filter); } for (int i = 0; i < trans.childCount; ++i) { FindObjectRenders(trans.GetChild(i)); } } List SmoothNormals(Mesh mesh) { var groups = mesh.vertices.Select((vertex, index) => new KeyValuePair(vertex, index)).GroupBy(pair => pair.Key); var smoothNormals = new List(mesh.normals); foreach (var group in groups) { if (group.Count() == 1) continue; var smoothNormal = Vector3.zero; foreach (var pair in group) { smoothNormal += smoothNormals[pair.Value]; } smoothNormal.Normalize(); foreach (var pair in group) { smoothNormals[pair.Value] = smoothNormal; } } return smoothNormals; } void CombineSubmeshes(Mesh mesh, Material[] materials) { if (mesh.subMeshCount == 1 || mesh.subMeshCount > materials.Length) return; mesh.subMeshCount++; mesh.SetTriangles(mesh.triangles, mesh.subMeshCount - 1); } } }