#if UNITY_EDITOR using UnityEngine; using VRC.Dynamics; using UnityEditor; using System.Reflection; namespace VRC.SDK3.Dynamics.PhysBone { [AddComponentMenu("")] public class PhysBoneGrabHelper : MonoBehaviour { Camera currentCamera; System.Type gameViewType; FieldInfo targetDisplayField; void Update() { currentCamera = FindCamera(); //Process mouse input SetMouseDown(Input.GetMouseButton(0)); if (mouseIsDown && Input.GetMouseButtonDown(1)) { if(grab != null) { PhysBoneManager.Inst.ReleaseGrab(grab, true); grab = null; } } UpdateGrab(); } void Start() { var assembly = typeof(EditorWindow).Assembly; gameViewType = assembly.GetType("UnityEditor.PlayModeView"); targetDisplayField = gameViewType.GetField("m_TargetDisplay", BindingFlags.DeclaredOnly | BindingFlags.NonPublic | BindingFlags.Instance); } //In Unity 2021.2 this can be replaced with Display.activeEditorGameViewTarget, allowing us to remove all dependencies on UnityEditor Camera FindCamera() { //Get target display var gameView = UnityEditor.EditorWindow.focusedWindow; if(gameView == null || !gameViewType.IsInstanceOfType(gameView)) return null; var targetDisplay = (int)targetDisplayField.GetValue(gameView); //Find camera with matching target Camera result = null; var cameras = Camera.allCameras; foreach(var camera in cameras) //Get the last active camera { if(camera.isActiveAndEnabled && camera.targetDisplay == targetDisplay) result = camera; } return result; } bool mouseIsDown = false; PhysBoneManager.Grab grab; Vector3 grabOrigin; void SetMouseDown(bool state) { if (state == mouseIsDown) return; mouseIsDown = state; if (mouseIsDown) { var ray = GetMouseRay(); grab = PhysBoneManager.Inst.AttemptGrab(-1, ray, out grabOrigin); #if VERBOSE_LOGGING if (grab != null) { Debug.Log($"Grabbing - Chain:{grab.chainId} Bone:{grab.bone}"); } #endif } else { if (grab != null) { PhysBoneManager.Inst.ReleaseGrab(grab); grab = null; } } } Ray GetMouseRay() { if(currentCamera != null) return currentCamera.ScreenPointToRay(Input.mousePosition); else return default; } void UpdateGrab() { if(currentCamera == null) return; if (grab != null) { var ray = GetMouseRay(); Vector3 hit; if (PlaneLineIntersection(grabOrigin, -currentCamera.transform.forward, ray.origin, ray.origin + ray.direction * 1000f, out hit)) { grab.globalPosition = hit + (Vector3)grab.localOffset; } } } public static bool PlaneLineIntersection(Vector3 planeOrigin, Vector3 planeNormal, Vector3 lineA, Vector3 lineB, out Vector3 hit) { float delta; //Make sure the line is not parallel delta = Vector3.Dot(planeNormal, (lineB - lineA) - planeOrigin); if (delta == 0.0f) { hit = Vector3.zero; return false; } //Find the delta delta = Vector3.Dot(planeNormal, lineA - planeOrigin) / delta; hit = lineA + ((lineB - lineA) * -delta); return true; } } } #endif