using UnityEngine; namespace VRC.SDK3.ClientSim { // This class is used for when an object has no usable mesh renderers to highlight. This will copy the collider on // the interact instead of any mesh on or under the interact. // An object will not have usable colliders if: // - There are no mesh renderers on or as a child of the object. // - The mesh renderers do not have a mesh // - The mesh renderers are part of a combined or static batched mesh. [AddComponentMenu("")] public class ClientSimHighlightProxy : ClientSimBehaviour { public Renderer Renderer { get; private set; } private Mesh _cubeMesh; private Mesh _capsuleMesh; private Mesh _sphereMesh; private MeshFilter _filter; private Transform _proxyObject; private Collider _proxyCollider; protected override void Awake() { base.Awake(); Renderer = GetComponent(); _filter = GetComponent(); } public void Initialize(Mesh cubeMesh, Mesh capsuleMesh, Mesh sphereMesh) { _cubeMesh = cubeMesh; _capsuleMesh = capsuleMesh; _sphereMesh = sphereMesh; } private void OnWillRenderObject() { UpdatePosition(); } private void UpdatePosition() { Vector3 position = _proxyObject.position; Quaternion rotation = _proxyObject.rotation; Vector3 scale = _proxyObject.lossyScale; if (_proxyCollider is BoxCollider boxCollider) { position += rotation * Vector3.Scale(boxCollider.center, scale); scale = Vector3.Scale(scale, boxCollider.size); _filter.sharedMesh = _cubeMesh; } else if (_proxyCollider is SphereCollider sphereCollider) { position += rotation * Vector3.Scale(sphereCollider.center, scale); // VRChatBug: Unity will always keep spheres round, but VRChat ignores this for the proxy rendering. // Sphere colliders are always round. Get the max component as the sphere size. float sphereScale = sphereCollider.radius * 2 * Mathf.Max(scale.x, scale.y, scale.z); scale = sphereScale * Vector3.one; _filter.sharedMesh = _sphereMesh; } else if (_proxyCollider is CapsuleCollider capsuleCollider) { position += rotation * Vector3.Scale(capsuleCollider.center, scale); float height = capsuleCollider.height * 0.5f; float radius = capsuleCollider.radius * 2; float sphereScale = radius; switch (capsuleCollider.direction) { case 0: // X rotation *= Quaternion.Euler(0, 0, 90); sphereScale = Mathf.Max(scale.y, scale.z) * radius; height *= scale.x; break; case 1: // Y // No need to rotate since mesh is already along y axis. sphereScale = Mathf.Max(scale.x, scale.z) * radius; height *= scale.y; break; case 2: // Z rotation *= Quaternion.Euler(90, 0, 0); sphereScale = Mathf.Max(scale.y, scale.x) * radius; height *= scale.z; break; } height = Mathf.Max(sphereScale * 0.5f, height); scale = new Vector3(sphereScale, height, sphereScale); _filter.sharedMesh = _capsuleMesh; } else if (_proxyCollider is MeshCollider meshCollider) { _filter.sharedMesh = meshCollider.sharedMesh; } transform.SetPositionAndRotation(position, rotation); transform.localScale = scale; } public void EnableProxy(Transform obj, Collider proxyCollider) { gameObject.SetActive(true); _filter.sharedMesh = null; _proxyObject = obj; _proxyCollider = proxyCollider; UpdatePosition(); } public void DisableProxy() { gameObject.SetActive(false); _filter.sharedMesh = null; _proxyObject = null; _proxyCollider = null; } } }