/*
         * Draws the scene view helpers for IKSolverFullBody
         * */
        public static void AddScene(UnityEngine.Object target, IKSolverFullBody solver, Color color, bool modifiable, ref int selectedEffector, float size)
        {
            if (!modifiable)
            {
                return;
            }
            if (!solver.initiated)
            {
                return;
            }
            if (!Application.isPlaying && !solver.IsValid())
            {
                return;
            }

            // Effectors
            for (int i = 0; i < solver.effectors.Length; i++)
            {
                bool  rotate = solver.effectors[i].isEndEffector;
                float weight = rotate? Mathf.Max(solver.effectors[i].positionWeight, solver.effectors[i].rotationWeight): solver.effectors[i].positionWeight;

                if (weight > 0 && selectedEffector != i)
                {
                    Handles.color = color;

                    if (rotate)
                    {
                        if (Inspector.DotButton(solver.effectors[i].position, solver.effectors[i].rotation, size * 0.5f, size * 0.5f))
                        {
                            selectedEffector = i;
                            return;
                        }
                    }
                    else
                    {
                        if (Inspector.SphereButton(solver.effectors[i].position, solver.effectors[i].rotation, size, size))
                        {
                            selectedEffector = i;
                            return;
                        }
                    }
                }
            }

            for (int i = 0; i < solver.effectors.Length; i++)
            {
                IKEffectorInspector.AddScene(solver.effectors[i], color, modifiable && i == selectedEffector, size);
            }

            if (GUI.changed)
            {
                EditorUtility.SetDirty(target);
            }
        }
Ejemplo n.º 2
0
        private static void SelectChain(FABRIKChain[] chain, ref FABRIKChain selected, Color color)
        {
            foreach (FABRIKChain c in chain)
            {
                if (c.ik.solver.IKPositionWeight > 0 && selected != c)
                {
                    Handles.color = GetChainColor(c, color);

                    if (Inspector.DotButton(c.ik.solver.GetIKPosition(), Quaternion.identity, GetHandleSize(c.ik.solver.GetIKPosition()), GetHandleSize(c.ik.solver.GetIKPosition())))
                    {
                        selected = c;
                        return;
                    }
                }
            }
        }
Ejemplo n.º 3
0
        // Scene view handles to help with limb setup
        private static void AddLimbHelper(FBIKChain chain, float size, Transform root = null)
        {
            Vector3 cross = Vector3.Cross((chain.nodes[1].transform.position - chain.nodes[0].transform.position).normalized, (chain.nodes[2].transform.position - chain.nodes[0].transform.position).normalized);

            Vector3 bendDirection = -Vector3.Cross(cross.normalized, (chain.nodes[2].transform.position - chain.nodes[0].transform.position).normalized);

            if (bendDirection != Vector3.zero)
            {
                Color c        = Handles.color;
                bool  inverted = root != null && Vector3.Dot(root.forward, bendDirection.normalized) < 0f;

                // Inverted bend direction
                if (inverted)
                {
                    GUI.color     = new Color(1f, 0.75f, 0.75f);
                    Handles.color = Color.yellow;

                    if (Inspector.DotButton(chain.nodes[1].transform.position, Quaternion.identity, size * 0.5f, size))
                    {
                        Warning.logged = false;
                        Warning.Log("The bend direction of this limb appears to be inverted. Please rotate this bone so that the limb is bent in it's natural bending direction. If this limb is supposed to be bent in the direction pointed by the arrow, ignore this warning.", root, true);
                    }
                }

                Inspector.ArrowCap(0, chain.nodes[1].transform.position, Quaternion.LookRotation(bendDirection), size * 2f);

                GUI.color     = Color.white;
                Handles.color = c;
            }
            else
            {
                // The limb is completely stretched out
                Color c = Handles.color;
                Handles.color = Color.red;
                GUI.color     = new Color(1f, 0.75f, 0.75f);

                if (Inspector.DotButton(chain.nodes[1].transform.position, Quaternion.identity, size * 0.5f, size))
                {
                    Warning.logged = false;
                    Warning.Log("The limb is completely stretched out. Full Body Biped IK does not know which way the limb should be bent. Please rotate this bone slightly in it's bending direction.", root, true);
                }

                GUI.color     = Color.white;
                Handles.color = c;
            }
        }
Ejemplo n.º 4
0
        /*
         * Draws the scene view helpers for BipedIK.Solvers
         * */
        public static void AddScene(BipedIKSolvers solvers, ref int selected)
        {
            // Draw limbs
            for (int i = 0; i < solvers.limbs.Length; i++)
            {
                IKSolverLimbInspector.AddScene(solvers.limbs[i] as IKSolverLimb, GetSolverColor(i), selected == i);
            }

            // Draw spine
            IKSolverHeuristicInspector.AddScene(solvers.spine, GetSolverColor(4), selected == 4);

            // Draw look at
            IKSolverLookAtInspector.AddScene(solvers.lookAt, GetSolverColor(5), selected == 5);

            // Draw aim
            IKSolverAimInspector.AddScene(solvers.aim, GetSolverColor(6), selected == 6);

            // Draw constraints
            ConstraintsInspector.AddScene(solvers.pelvis, GetSolverColor(7), selected == 7);

            // Selecting solvers
            if (Application.isPlaying)
            {
                for (int i = 0; i < solvers.ikSolvers.Length; i++)
                {
                    Handles.color = GetSolverColor(i);
                    if (solvers.ikSolvers[i].GetIKPositionWeight() > 0 && selected != i && solvers.ikSolvers[i].initiated)
                    {
                        if (Inspector.DotButton(solvers.ikSolvers[i].GetIKPosition(), Quaternion.identity, GetHandleSize(solvers.ikSolvers[i].GetIKPosition()), GetHandleSize(solvers.ikSolvers[i].GetIKPosition())))
                        {
                            selected = i;
                        }
                    }
                }

                if ((solvers.pelvis.positionWeight > 0 || solvers.pelvis.rotationWeight > 0) && selected != solvers.ikSolvers.Length)
                {
                    Handles.color = GetSolverColor(7);
                    if (Inspector.DotButton(solvers.pelvis.position, Quaternion.identity, GetHandleSize(solvers.pelvis.position), GetHandleSize(solvers.pelvis.position)))
                    {
                        selected = solvers.ikSolvers.Length;
                    }
                }
            }
        }
Ejemplo n.º 5
0
        public void OnSceneGUI()
        {
            GUI.changed = false;

            // Set defaultLocalRotation so that the initial local rotation will be the zero point for the rotation limit
            if (!Application.isPlaying)
            {
                script.defaultLocalRotation = script.transform.localRotation;
            }
            if (script.axis == Vector3.zero)
            {
                return;
            }

            // Quick Editing Tools
            Handles.BeginGUI();
            GUILayout.BeginArea(new Rect(10, Screen.height - 180, 550, 130), "Rotation Limit Polygonal", "Window");

            // Cloning values from another RotationLimitPolygonal
            EditorGUILayout.BeginHorizontal();
            if (Inspector.Button("Clone From", "Make this rotation limit identical to another", script, GUILayout.Width(220)))
            {
                CloneLimit();
            }
            clone = (RotationLimitPolygonal)EditorGUILayout.ObjectField("", clone, typeof(RotationLimitPolygonal), true);
            EditorGUILayout.EndHorizontal();

            // Symmetry
            symmetry = (Symmetry)EditorGUILayout.EnumPopup("Symmetry", symmetry, GUILayout.Width(220));

            // Flipping
            EditorGUILayout.BeginHorizontal();
            if (Inspector.Button("Flip X", "Flip points along local X axis", script, GUILayout.Width(100)))
            {
                FlipLimit(0);
            }
            if (Inspector.Button("Flip Y", "Flip points along local Y axis", script, GUILayout.Width(100)))
            {
                FlipLimit(1);
            }
            if (Inspector.Button("Flip Z", "Flip points along local Z axis", script, GUILayout.Width(100)))
            {
                FlipLimit(2);
            }
            GUILayout.Label("Flip everything along axis");
            EditorGUILayout.EndHorizontal();

            // Rotating
            EditorGUILayout.BeginHorizontal();
            if (Inspector.Button("Rotate X", "Rotate points along X axis by Degrees", script, GUILayout.Width(100)))
            {
                RotatePoints(degrees, Vector3.right);
            }
            if (Inspector.Button("Rotate Y", "Rotate points along Y axis by Degrees", script, GUILayout.Width(100)))
            {
                RotatePoints(degrees, Vector3.up);
            }
            if (Inspector.Button("Rotate Z", "Rotate points along Z axis by Degrees", script, GUILayout.Width(100)))
            {
                RotatePoints(degrees, Vector3.forward);
            }

            degrees = EditorGUILayout.FloatField("Degrees", degrees, GUILayout.Width(200));
            EditorGUILayout.EndHorizontal();

            // Smooth/Optimize
            EditorGUILayout.BeginHorizontal();
            if (Inspector.Button("Smooth", "Double the points", script))
            {
                Smooth();
            }
            if (Inspector.Button("Optimize", "Delete every second point", script))
            {
                Optimize();
            }
            EditorGUILayout.EndHorizontal();

            GUILayout.EndArea();
            Handles.EndGUI();

            // Rebuild reach cones
            script.BuildReachCones();

            // Draw a white transparent sphere
            DrawRotationSphere(script.transform.position);

            // Draw Axis
            DrawArrow(script.transform.position, Direction(script.axis), colorDefault, "Axis", 0.02f);

            // Display limit points
            for (int i = 0; i < script.points.Length; i++)
            {
                Color color = GetColor(i);                 // Paint the point in green or red if it belongs to an invalid reach cone

                Handles.color = color;
                GUI.color     = color;

                // Line from the center to the point and the label
                Handles.DrawLine(script.transform.position, script.transform.position + Direction(script.points[i].point));
                Handles.Label(script.transform.position + Direction(script.points[i].point + new Vector3(-0.02f, 0, 0)), " " + i.ToString());

                // Selecting points
                Handles.color = colorHandles;
                if (Inspector.DotButton(script.transform.position + Direction(script.points[i].point), script.transform.rotation, 0.02f, 0.02f))
                {
                    selectedPoint = i;
                }

                Handles.color = Color.white;
                GUI.color     = Color.white;

                // Limit point GUI
                if (i == selectedPoint)
                {
                    Handles.BeginGUI();

                    GUILayout.BeginArea(new Rect(Screen.width - 240, Screen.height - 180, 230, 130), "Limit Point " + i.ToString(), "Window");

                    if (Inspector.Button("Delete", "Delete this point", script))
                    {
                        if (script.points.Length > 3)
                        {
                            // Using the deletePoint index here because we dont want to delete points from the array that we are iterating
                            deletePoint = i;
                        }
                        else if (!Warning.logged)
                        {
                            script.LogWarning("Polygonal Rotation Limit should have at least 3 limit points");
                        }
                    }
                    if (Inspector.Button("Add Point", "Add a new point next to this one", script))
                    {
                        addPoint = i;
                    }

                    // Store point for undo
                    Vector3 oldPoint = script.points[i].point;

                    // Manual input for the point position
                    Inspector.AddVector3(ref script.points[i].point, "Point", script, GUILayout.Width(210));

                    EditorGUILayout.Space();

                    // Tangent weight
                    Inspector.AddFloat(ref script.points[i].tangentWeight, "Tangent Weight", "Weight of this point's tangent. Used in smoothing.", script, -Mathf.Infinity, Mathf.Infinity, GUILayout.Width(150));

                    GUILayout.EndArea();

                    Handles.EndGUI();

                    // Moving Points
                    Vector3 pointWorld = Handles.PositionHandle(script.transform.position + Direction(script.points[i].point), Quaternion.identity);
                    Vector3 newPoint   = InverseDirection(pointWorld - script.transform.position);
                    if (newPoint != script.points[i].point)
                    {
                        if (!Application.isPlaying)
                        {
                            Undo.RecordObject(script, "Move Limit Point");
                        }
                        script.points[i].point = newPoint;
                    }

                    // Symmetry
                    if (symmetry != Symmetry.Off && script.points.Length > 3 && oldPoint != script.points[i].point)
                    {
                        RotationLimitPolygonal.LimitPoint symmetryPoint = GetClosestPoint(Symmetrize(oldPoint, symmetry));
                        if (symmetryPoint != script.points[i])
                        {
                            symmetryPoint.point = Symmetrize(script.points[i].point, symmetry);
                        }
                    }
                }

                // Normalize the point
                script.points[i].point = script.points[i].point.normalized;
            }

            // Display smoothed polygon
            for (int i = 0; i < script.P.Length; i++)
            {
                Color color = GetColor(i);

                // Smoothed triangles are transparent
                Handles.color = new Color(color.r, color.g, color.b, 0.25f);
                Handles.DrawLine(script.transform.position, script.transform.position + Direction(script.P[i]));

                Handles.color = color;

                if (i < script.P.Length - 1)
                {
                    Handles.DrawLine(script.transform.position + Direction(script.P[i]), script.transform.position + Direction(script.P[i + 1]));
                }
                else
                {
                    Handles.DrawLine(script.transform.position + Direction(script.P[i]), script.transform.position + Direction(script.P[0]));
                }

                Handles.color = Color.white;
            }

            // Deleting points
            if (deletePoint != -1)
            {
                DeletePoint(deletePoint);
                selectedPoint = -1;
                deletePoint   = -1;
            }

            // Adding points
            if (addPoint != -1)
            {
                AddPoint(addPoint);
                addPoint = -1;
            }

            if (GUI.changed)
            {
                EditorUtility.SetDirty(script);
            }
        }
Ejemplo n.º 6
0
        void OnSceneGUI()
        {
            if (!script.enabled)
            {
                return;
            }
            string message = string.Empty;

            if (!script.IsValid(ref message))
            {
                return;
            }
            if (Application.isPlaying && !script.initiated)
            {
                return;
            }

            Color color = Color.cyan;

            color.a = script.weight;

            Handles.color = color;
            GUI.color     = color;

            // Display the bones
            if (!Application.isPlaying)
            {
                for (int i = 0; i < script.fingers.Length; i++)
                {
                    Handles.DrawLine(script.fingers[i].bone1.position, script.fingers[i].bone2.position);
                    Inspector.SphereCap(0, script.fingers[i].bone1.position, Quaternion.identity, IKSolverInspector.GetHandleSize(script.fingers[i].bone1.position) * 0.5f);
                    Inspector.SphereCap(0, script.fingers[i].bone2.position, Quaternion.identity, IKSolverInspector.GetHandleSize(script.fingers[i].bone2.position) * 0.5f);

                    if (script.fingers[i].bone3 != null)
                    {
                        Handles.DrawLine(script.fingers[i].bone2.position, script.fingers[i].bone3.position);
                        Handles.DrawLine(script.fingers[i].bone3.position, script.fingers[i].tip.position);
                        Inspector.SphereCap(0, script.fingers[i].bone3.position, Quaternion.identity, IKSolverInspector.GetHandleSize(script.fingers[i].bone3.position) * 0.5f);
                    }
                    else
                    {
                        Handles.DrawLine(script.fingers[i].bone2.position, script.fingers[i].tip.position);
                    }

                    Inspector.SphereCap(0, script.fingers[i].tip.position, Quaternion.identity, IKSolverInspector.GetHandleSize(script.fingers[i].tip.position) * 0.5f);
                }
            }

            // Selecting solvers
            if (Application.isPlaying)
            {
                if (selected >= 0 && selected < script.fingers.Length)
                {
                    if (script.fingers[selected].weight > 0f)
                    {
                        color.a       = script.weight * script.fingers[selected].weight;
                        Handles.color = color;

                        float size = IKSolverInspector.GetHandleSize(script.fingers[selected].IKPosition);

                        Inspector.CubeCap(0, script.fingers[selected].IKPosition, script.fingers[selected].IKRotation, size);

                        if (script.fingers[selected].target == null)
                        {
                            switch (Tools.current)
                            {
                            case Tool.Move:
                                script.fingers[selected].IKPosition = Handles.PositionHandle(script.fingers[selected].IKPosition, Tools.pivotRotation == PivotRotation.Local? script.fingers[selected].IKRotation: Quaternion.identity);
                                break;

                            case Tool.Rotate:
                                script.fingers[selected].IKRotation = Handles.RotationHandle(script.fingers[selected].IKRotation, script.fingers[selected].IKPosition);
                                break;
                            }
                        }
                    }
                }

                for (int i = 0; i < script.fingers.Length; i++)
                {
                    color.a       = script.weight * script.fingers[i].weight;
                    Handles.color = color;
                    Handles.DrawLine(script.fingers[i].tip.position, script.fingers[i].IKPosition);

                    if (script.fingers[i].weight > 0 && selected != i && script.fingers[i].initiated)
                    {
                        float size = IKSolverInspector.GetHandleSize(script.fingers[i].IKPosition) * 0.5f;

                        if (Inspector.DotButton(script.fingers[i].IKPosition, Quaternion.identity, size, size))
                        {
                            selected = i;
                        }
                    }
                }
            }

            Handles.color = Color.white;
            GUI.color     = Color.white;
        }
Ejemplo n.º 7
0
        void OnSceneGUI()
        {
            Rigidbody[] rigidbodies = script.transform.GetComponentsInChildren <Rigidbody>();
            if (rigidbodies.Length == 0)
            {
                return;
            }

            Collider[] colliders = script.transform.GetComponentsInChildren <Collider>();

            if (script.selectedRigidbody == null)
            {
                script.selectedRigidbody = rigidbodies[0];
            }
            if (script.selectedCollider == null && colliders.Length > 0)
            {
                script.selectedCollider = colliders[0];
            }

            Rigidbody symmetricRigidbody = script.symmetry && script.selectedRigidbody != null?SymmetryTools.GetSymmetric(script.selectedRigidbody, rigidbodies, script.transform) : null;

            Collider symmetricCollider = script.symmetry && script.selectedCollider != null?SymmetryTools.GetSymmetric(script.selectedCollider, colliders, script.transform) : null;

            Joint joint = script.selectedRigidbody != null?script.selectedRigidbody.GetComponent <Joint>() : null;

            Joint symmetricJoint = joint != null && symmetricRigidbody != null?symmetricRigidbody.GetComponent <Joint>() : null;

            // Selected
            Transform selected = script.mode == RootMotion.Dynamics.RagdollEditor.Mode.Colliders? (script.selectedCollider != null? script.selectedCollider.transform: null): (script.selectedRigidbody != null? script.selectedRigidbody.transform: null);

            if (selected != null)
            {
                Handles.BeginGUI();
                GUILayout.BeginVertical(new GUIContent("Ragdoll Editor", string.Empty), "Window", GUILayout.Width(200), GUILayout.Height(20));
                GUILayout.BeginHorizontal();
                GUILayout.Label(selected.name);
                if (GUILayout.Button("Select GameObject", EditorStyles.miniButton))
                {
                    Selection.activeGameObject = selected.gameObject;
                }
                GUILayout.EndHorizontal();

                GUILayout.Space(10);

                //GUILayout.BeginVertical("Box");

                GUILayout.BeginHorizontal();
                GUILayout.Label("Edit Mode", GUILayout.Width(86));
                script.mode = (RagdollEditor.Mode)EditorGUILayout.EnumPopup(string.Empty, script.mode, GUILayout.Width(100));
                GUILayout.EndHorizontal();


                GUILayout.BeginHorizontal();
                GUILayout.Label("Symmetry", GUILayout.Width(86));
                script.symmetry = GUILayout.Toggle(script.symmetry, string.Empty);
                GUILayout.EndHorizontal();

                GUILayout.Space(10);

                // COLLIDERS
                if (script.mode == RagdollEditor.Mode.Colliders && selected != null)
                {
                    if (script.selectedCollider is CapsuleCollider)
                    {
                        var capsule  = script.selectedCollider as CapsuleCollider;
                        var capsuleS = symmetricCollider != null?symmetricCollider.transform.GetComponent <CapsuleCollider>() : null;

                        if (GUILayout.Button("Convert To Box Collider"))
                        {
                            ColliderTools.ConvertToBoxCollider(capsule);
                            script.selectedCollider = selected.GetComponent <Collider>();
                            if (capsuleS != null)
                            {
                                ColliderTools.ConvertToBoxCollider(capsuleS);
                            }
                            return;
                        }

                        if (GUILayout.Button("Convert To Sphere Collider"))
                        {
                            ColliderTools.ConvertToSphereCollider(capsule);
                            script.selectedCollider = selected.GetComponent <Collider>();
                            if (capsuleS != null)
                            {
                                ColliderTools.ConvertToSphereCollider(capsuleS);
                            }
                            return;
                        }

                        string capsuleDir = capsule.direction == 0? "X": (capsule.direction == 1? "Y": "Z");

                        if (GUILayout.Button("Direction: " + capsuleDir))
                        {
                            if (capsuleS == null)
                            {
                                Undo.RecordObject(capsule, "Change Capsule Direction");
                            }
                            else
                            {
                                Undo.RecordObjects(new Object[2] {
                                    capsule, capsuleS
                                }, "Change Capsule Direction");
                            }

                            capsule.direction++;
                            if (capsule.direction > 2)
                            {
                                capsule.direction = 0;
                            }
                            if (capsuleS != null)
                            {
                                capsuleS.direction = capsule.direction;
                            }
                        }
                    }
                    else if (script.selectedCollider is BoxCollider)
                    {
                        var box  = script.selectedCollider as BoxCollider;
                        var boxS = symmetricCollider != null?symmetricCollider.transform.GetComponent <BoxCollider>() : null;

                        if (GUILayout.Button("Convert To Capsule Collider"))
                        {
                            ColliderTools.ConvertToCapsuleCollider(box);
                            script.selectedCollider = selected.GetComponent <Collider>();
                            if (boxS != null)
                            {
                                ColliderTools.ConvertToCapsuleCollider(boxS);
                            }
                            return;
                        }

                        if (GUILayout.Button("Convert To Sphere Collider"))
                        {
                            ColliderTools.ConvertToSphereCollider(box);
                            script.selectedCollider = selected.GetComponent <Collider>();
                            if (boxS != null)
                            {
                                ColliderTools.ConvertToSphereCollider(boxS);
                            }
                            return;
                        }

                        if (GUILayout.Button("Rotate Collider"))
                        {
                            if (boxS == null)
                            {
                                Undo.RecordObject(box, "Rotate Collider");
                            }
                            else
                            {
                                Undo.RecordObjects(new Object[2] {
                                    box, boxS
                                }, "Rotate Collider");
                            }

                            box.size = new Vector3(box.size.y, box.size.z, box.size.x);
                            if (boxS != null)
                            {
                                boxS.size = box.size;
                            }
                        }
                    }
                    else if (script.selectedCollider is SphereCollider)
                    {
                        var sphere  = script.selectedCollider as SphereCollider;
                        var sphereS = symmetricCollider != null?symmetricCollider.transform.GetComponent <SphereCollider>() : null;

                        if (GUILayout.Button("Convert To Capsule Collider"))
                        {
                            ColliderTools.ConvertToCapsuleCollider(sphere);
                            script.selectedCollider = selected.GetComponent <Collider>();
                            if (sphereS != null)
                            {
                                ColliderTools.ConvertToCapsuleCollider(sphereS);
                            }
                            return;
                        }

                        if (GUILayout.Button("Convert To Box Collider"))
                        {
                            ColliderTools.ConvertToBoxCollider(sphere);
                            script.selectedCollider = selected.GetComponent <Collider>();
                            if (sphereS != null)
                            {
                                ColliderTools.ConvertToBoxCollider(sphereS);
                            }
                            return;
                        }
                    }
                }

                // JOINTS
                if (script.mode == RagdollEditor.Mode.Joints)
                {
                    if (joint != null && (joint is CharacterJoint || joint is ConfigurableJoint))
                    {
                        GUILayout.BeginHorizontal();
                        GUILayout.Label("Connected Body");

                        var lastConnectedBody = joint.connectedBody;
                        var newConnectedBody  = (Rigidbody)EditorGUILayout.ObjectField(joint.connectedBody, typeof(Rigidbody), true);
                        if (newConnectedBody != lastConnectedBody)
                        {
                            Undo.RecordObject(joint, "Changing Joint ConnectedBody");
                            joint.connectedBody = newConnectedBody;
                        }

                        GUILayout.EndHorizontal();

                        if (joint is CharacterJoint)
                        {
                            var j  = joint as CharacterJoint;
                            var sJ = symmetricJoint != null && symmetricJoint is CharacterJoint? symmetricJoint as CharacterJoint: null;

                            if (GUILayout.Button("Convert to Configurable"))
                            {
                                JointConverter.CharacterToConfigurable(j);

                                if (sJ != null)
                                {
                                    JointConverter.CharacterToConfigurable(sJ);
                                }
                            }

                            if (GUILayout.Button("Switch Yellow/Green"))
                            {
                                JointTools.SwitchXY(ref j);
                                if (sJ != null)
                                {
                                    JointTools.SwitchXY(ref sJ);
                                }
                            }

                            if (GUILayout.Button("Switch Yellow/Blue"))
                            {
                                JointTools.SwitchXZ(ref j);
                                if (sJ != null)
                                {
                                    JointTools.SwitchXZ(ref sJ);
                                }
                            }

                            if (GUILayout.Button("Switch Green/Blue"))
                            {
                                JointTools.SwitchYZ(ref j);
                                if (sJ != null)
                                {
                                    JointTools.SwitchYZ(ref sJ);
                                }
                            }

                            if (GUILayout.Button("Invert Yellow"))
                            {
                                JointTools.InvertAxis(ref joint);
                                if (sJ != null)
                                {
                                    JointTools.InvertAxis(ref symmetricJoint);
                                }
                            }
                        }

                        if (joint is ConfigurableJoint)
                        {
                            var j  = joint as ConfigurableJoint;
                            var sJ = symmetricJoint != null && symmetricJoint is ConfigurableJoint? symmetricJoint as ConfigurableJoint: null;

                            if (GUILayout.Button("Switch Yellow/Green"))
                            {
                                JointTools.SwitchXY(ref j);
                                if (sJ != null)
                                {
                                    JointTools.SwitchXY(ref sJ);
                                }
                            }

                            if (GUILayout.Button("Switch Yellow/Blue"))
                            {
                                JointTools.SwitchXZ(ref j);
                                if (sJ != null)
                                {
                                    JointTools.SwitchXZ(ref sJ);
                                }
                            }

                            if (GUILayout.Button("Switch Green/Blue"))
                            {
                                JointTools.SwitchYZ(ref j);
                                if (sJ != null)
                                {
                                    JointTools.SwitchYZ(ref sJ);
                                }
                            }

                            if (GUILayout.Button("Invert Yellow"))
                            {
                                JointTools.InvertAxis(ref joint);
                                if (sJ != null)
                                {
                                    JointTools.InvertAxis(ref symmetricJoint);
                                }
                            }
                        }
                    }
                }

                GUILayout.EndVertical();
                //GUILayout.EndArea();
                Handles.EndGUI();

                if (script.mode == RagdollEditor.Mode.Joints && joint != null)
                {
                    if (joint is CharacterJoint)
                    {
                        var j = joint as CharacterJoint;

                        SoftJointLimit lowTwistLimit  = j.lowTwistLimit;
                        SoftJointLimit highTwistLimit = j.highTwistLimit;
                        SoftJointLimit swing1Limit    = j.swing1Limit;
                        SoftJointLimit swing2Limit    = j.swing2Limit;

                        CharacterJointInspector.DrawJoint(j);

                        if (symmetricJoint != null && symmetricJoint is CharacterJoint)
                        {
                            var sJ = symmetricJoint as CharacterJoint;

                            CharacterJointInspector.DrawJoint(sJ, false, 0.5f);

                            // Low Twist
                            if (lowTwistLimit.limit != j.lowTwistLimit.limit)
                            {
                                Undo.RecordObject(sJ, "Change Joint Limits");

                                Vector3 lowXAxisWorld       = JointTools.GetLowXAxisWorld(j);
                                Vector3 lowXAxisWorldS      = JointTools.GetLowXAxisWorld(sJ);
                                Vector3 lowXAxisWorldMirror = SymmetryTools.Mirror(lowXAxisWorld, script.transform);
                                bool    low = Vector3.Dot(lowXAxisWorldMirror, lowXAxisWorldS) < 0f;

                                JointAxis sJointAxis = JointTools.GetSymmetricJointAxis(j, JointAxis.Primary, sJ, script.transform);
                                float     delta      = j.lowTwistLimit.limit - lowTwistLimit.limit;

                                JointTools.ApplyXDeltaToJointLimit(ref sJ, delta, sJointAxis, low);
                            }

                            // High Twist
                            if (highTwistLimit.limit != j.highTwistLimit.limit)
                            {
                                Undo.RecordObject(sJ, "Change Joint Limits");

                                Vector3 highXAxisWorld       = JointTools.GetHighXAxisWorld(j);
                                Vector3 highXAxisWorldS      = JointTools.GetHighXAxisWorld(sJ);
                                Vector3 highXAxisWorldMirror = SymmetryTools.Mirror(highXAxisWorld, script.transform);
                                bool    low = Vector3.Dot(highXAxisWorldMirror, highXAxisWorldS) > 0f;

                                JointAxis sJointAxis = JointTools.GetSymmetricJointAxis(j, JointAxis.Primary, sJ, script.transform);
                                float     delta      = j.highTwistLimit.limit - highTwistLimit.limit;

                                JointTools.ApplyXDeltaToJointLimit(ref sJ, -delta, sJointAxis, low);
                            }

                            // Swing 1
                            if (swing1Limit.limit != j.swing1Limit.limit)
                            {
                                Undo.RecordObject(sJ, "Change Joint Limits");
                                JointAxis sJointAxis = JointTools.GetSymmetricJointAxis(j, JointAxis.Secondary, sJ, script.transform);
                                float     delta      = j.swing1Limit.limit - swing1Limit.limit;

                                JointTools.ApplyDeltaToJointLimit(ref sJ, delta, sJointAxis);
                            }

                            // Swing 2
                            if (swing2Limit.limit != j.swing2Limit.limit)
                            {
                                Undo.RecordObject(sJ, "Change Joint Limits");
                                JointAxis sJointAxis = JointTools.GetSymmetricJointAxis(j, JointAxis.Tertiary, sJ, script.transform);
                                float     delta      = j.swing2Limit.limit - swing2Limit.limit;

                                JointTools.ApplyDeltaToJointLimit(ref sJ, delta, sJointAxis);
                            }
                        }
                    }
                    else if (joint is ConfigurableJoint)
                    {
                        var j = joint as ConfigurableJoint;

                        SoftJointLimit lowAngularXLimit  = j.lowAngularXLimit;
                        SoftJointLimit highAngularXLimit = j.highAngularXLimit;
                        SoftJointLimit angularYLimit     = j.angularYLimit;
                        SoftJointLimit angularZLimit     = j.angularZLimit;

                        ConfigurableJointInspector.DrawJoint(j);

                        if (symmetricJoint != null && symmetricJoint is ConfigurableJoint)
                        {
                            var sJ = symmetricJoint as ConfigurableJoint;

                            ConfigurableJointInspector.DrawJoint(sJ, false, 0.5f);

                            // Low X
                            if (lowAngularXLimit.limit != j.lowAngularXLimit.limit)
                            {
                                Undo.RecordObject(sJ, "Change Joint Limits");

                                Vector3 lowXAxisWorld       = JointTools.GetLowXAxisWorld(j);
                                Vector3 lowXAxisWorldS      = JointTools.GetLowXAxisWorld(sJ);
                                Vector3 lowXAxisWorldMirror = SymmetryTools.Mirror(lowXAxisWorld, script.transform);
                                bool    low = Vector3.Dot(lowXAxisWorldMirror, lowXAxisWorldS) < 0f;

                                JointAxis sJointAxis = JointTools.GetSymmetricJointAxis(j, JointAxis.Primary, sJ, script.transform);
                                float     delta      = j.lowAngularXLimit.limit - lowAngularXLimit.limit;

                                JointTools.ApplyXDeltaToJointLimit(ref sJ, delta, sJointAxis, low);
                            }

                            // High X
                            if (highAngularXLimit.limit != j.highAngularXLimit.limit)
                            {
                                Undo.RecordObject(sJ, "Change Joint Limits");

                                Vector3 highXAxisWorld       = JointTools.GetHighXAxisWorld(j);
                                Vector3 highXAxisWorldS      = JointTools.GetHighXAxisWorld(sJ);
                                Vector3 highXAxisWorldMirror = SymmetryTools.Mirror(highXAxisWorld, script.transform);
                                bool    low = Vector3.Dot(highXAxisWorldMirror, highXAxisWorldS) > 0f;

                                JointAxis sJointAxis = JointTools.GetSymmetricJointAxis(j, JointAxis.Primary, sJ, script.transform);
                                float     delta      = j.highAngularXLimit.limit - highAngularXLimit.limit;

                                JointTools.ApplyXDeltaToJointLimit(ref sJ, -delta, sJointAxis, low);
                            }

                            // Y
                            if (angularYLimit.limit != j.angularYLimit.limit)
                            {
                                Undo.RecordObject(sJ, "Change Joint Limits");
                                JointAxis sJointAxis = JointTools.GetSymmetricJointAxis(j, JointAxis.Secondary, sJ, script.transform);
                                float     delta      = j.angularYLimit.limit - angularYLimit.limit;

                                JointTools.ApplyDeltaToJointLimit(ref sJ, delta, sJointAxis);
                            }

                            // Z
                            if (angularZLimit.limit != j.angularZLimit.limit)
                            {
                                Undo.RecordObject(sJ, "Change Joint Limits");
                                JointAxis sJointAxis = JointTools.GetSymmetricJointAxis(j, JointAxis.Tertiary, sJ, script.transform);
                                float     delta      = j.angularZLimit.limit - angularZLimit.limit;

                                JointTools.ApplyDeltaToJointLimit(ref sJ, delta, sJointAxis);
                            }
                        }
                    }
                }
            }

            if (!Application.isPlaying)
            {
                foreach (Collider c in colliders)
                {
                    c.enabled = script.mode == RagdollEditor.Mode.Colliders;
                }
            }

            // HANDLES
            Color color = new Color(0.2f, 0.9f, 0.5f);

            Handles.color = color;

            if (Event.current.type == EventType.MouseUp)
            {
                isDragging = false;
            }
            if (Event.current.type == EventType.MouseDown)
            {
                isDragging = false;
            }

            if (script.mode == RagdollEditor.Mode.Colliders)
            {
                // Select/move scale colliders
                for (int i = 0; i < colliders.Length; i++)
                {
                    if (colliders[i] == script.selectedCollider)
                    {
                        // Moving and scaling selected colliders
                        switch (Tools.current)
                        {
                        case Tool.Move:
                            Vector3 oldPosition = ColliderTools.GetColliderCenterWorld(colliders[i]);
                            Vector3 newPosition = Handles.PositionHandle(oldPosition, colliders[i].transform.rotation);
                            if (newPosition != oldPosition)
                            {
                                if (!isDragging)
                                {
                                    Undo.RecordObjects(SymmetryTools.GetColliderPair(colliders[i], symmetricCollider), "Move Colliders");

                                    isDragging = true;
                                }

                                ColliderTools.SetColliderCenterWorld(colliders[i], symmetricCollider, newPosition, script.transform);
                            }
                            break;

                        case Tool.Scale:
                            Vector3 position = ColliderTools.GetColliderCenterWorld(colliders[i]);
                            Vector3 oldSize  = ColliderTools.GetColliderSize(colliders[i]);
                            Vector3 oldSizeS = ColliderTools.GetColliderSize(symmetricCollider);
                            Vector3 newSize  = Handles.ScaleHandle(oldSize, position, colliders[i].transform.rotation, HandleUtility.GetHandleSize(position));
                            if (newSize != oldSize)
                            {
                                if (!isDragging)
                                {
                                    Undo.RecordObjects(SymmetryTools.GetColliderPair(colliders[i], symmetricCollider), "Scale Colliders");

                                    isDragging = true;
                                }

                                ColliderTools.SetColliderSize(colliders[i], symmetricCollider, newSize, oldSize, oldSizeS, script.transform);
                            }

                            Handles.color = color;
                            break;
                        }

                        // Dot on selected collider
                        if (Tools.current != Tool.Scale)
                        {
                            Handles.color = new Color(0.8f, 0.8f, 0.8f);
                            Vector3 center = ColliderTools.GetColliderCenterWorld(colliders[i]);
                            float   size   = GetHandleSize(center);
                            Inspector.CubeCap(0, center, colliders[i].transform.rotation, size);
                            Handles.color = color;
                        }
                    }
                    else
                    {
                        // Selecting colliders
                        Vector3 center = ColliderTools.GetColliderCenterWorld(colliders[i]);
                        float   size   = GetHandleSize(center);

                        if (Inspector.DotButton(center, Quaternion.identity, size * 0.5f, size * 0.5f))
                        {
                            Undo.RecordObject(script, "Change RagdollEditor Selection");

                            script.selectedCollider = colliders[i];
                        }
                    }
                }
            }
            else
            {
                // Select joints
                for (int i = 0; i < rigidbodies.Length; i++)
                {
                    if (rigidbodies[i] == script.selectedRigidbody)
                    {
                        // Dots on selected joints
                        Handles.color = new Color(0.8f, 0.8f, 0.8f);
                        Vector3 center = rigidbodies[i].transform.position;
                        float   size   = GetHandleSize(center);
                        Inspector.CubeCap(0, center, rigidbodies[i].transform.rotation, size);
                        Handles.color = color;
                    }
                    else
                    {
                        // Selecting joints
                        Vector3 center = rigidbodies[i].transform.position;
                        float   size   = GetHandleSize(center);

                        if (Inspector.DotButton(center, Quaternion.identity, size * 0.5f, size * 0.5f))
                        {
                            Undo.RecordObject(script, "Change RagdollEditor Selection");

                            script.selectedRigidbody = rigidbodies[i];
                        }
                    }
                }
            }

            Handles.color = Color.white;
        }