Пример #1
0
    // manually specify direction, label, and color
    public static void Arrow(ref float val, Vector3 origin, bool isBlob, Vector3 direction, string label, Color col)
    {
        // set handle color
        Color oldColor = Handles.color;

        CustomHandleUtilities.SetHandleColor(col);

        // draw a label if requested
        if (label.Length > 0)
        {
            Handles.Label(origin + direction * val, label);
        }

        // create a slider
        direction.Normalize();
        Vector3 initialPosition = origin + direction * val;
        Vector3 vDelta          = Handles.Slider(initialPosition, direction, HandleUtility.GetHandleSize(initialPosition) * arrowSize, Handles.ArrowCap, 1f) - initialPosition;
        float   delta           = vDelta.magnitude;

        if (delta > requiredMinHandleChange)
        {
            val += delta * Mathf.Sign(Vector3.Dot(vDelta, direction));
        }

        // draw a blob
        if (isBlob)
        {
            Handles.SphereCap(-1, origin + direction * val, Quaternion.identity, blobSize);
        }

        // reset handle color
        CustomHandleUtilities.SetHandleColor(oldColor);
    }
Пример #2
0
    // manually specify an origin, direction, label, and color
    public static void ValueSlider(ref float val, Vector3 origin, Vector3 direction, string label, Color col)
    {
        // set handle color
        Color oldColor = Handles.color;

        CustomHandleUtilities.SetHandleColor(col);         // always need handles to be visible

        // draw a label if requested
        if (label.Length > 0)
        {
            Handles.Label(origin + direction * val, label);
        }

        // create a slider
        direction.Normalize();
        Vector3 initialPosition = origin + direction * val;
        Vector3 vDelta          = Handles.Slider(initialPosition, direction, HandleUtility.GetHandleSize(initialPosition) * dotHandleSize, Handles.DotCap, 1f) - initialPosition;
        float   delta           = vDelta.magnitude;

        if (delta > requiredMinHandleChange)
        {
            val += delta * Mathf.Sign(Vector3.Dot(vDelta, direction));
        }

        // reset handle color
        CustomHandleUtilities.SetHandleColor(oldColor);
    }
    /*
     * Draw the handle if it is enabled
     * */
    void OnSceneGUI()
    {
        // viewport gui controls
        Handles.BeginGUI();
        {
            ViewportControls.BeginArea(viewportControlsWidth, GUIAnchor.TopLeft);
            {
                GUILayout.BeginVertical();
                {
                    JointHandleToggle();
                    GUILayout.BeginHorizontal();
                    {
                        JointHandleSizeSlider(viewportControlsWidth);
                    }
                    GUILayout.EndHorizontal();
                    CustomHandleUtilities.ViewportIntegratorFidelityControls(viewportControlsWidth);
                }
                GUILayout.EndVertical();
            }
            ViewportControls.EndArea();
        }
        Handles.EndGUI();

        // handles
        if (!isHandleEnabled)
        {
            return;
        }

        Undo.SetSnapshotTarget(target, "Change Configurable Joint");
        JointHandles.JointLimit(joint, jointHandleSize);
        EditorUtility.SetDirty(target);
    }
Пример #4
0
    /*
     * Create a disc handle for val using the specified parameters
     * */
    private static void DiscHandle(ref float val, Vector3 origin, Quaternion orientation, string label, Color col, bool isFilled)
    {
        // compute handle locations based on disc orientation
        Vector3 right   = orientation * Vector3.right;
        Vector3 up      = orientation * Vector3.up;
        Vector3 forward = orientation * Vector3.forward;

        // set handle color
        Color oldColor = Handles.color;

        CustomHandleUtilities.SetHandleColor(col);         // ensure it will still render even if alpha is 0

        // create handles
        LinearHandles.ValueSlider(ref val, origin, forward, label, Handles.color);
        LinearHandles.ValueSlider(ref val, origin, right, Handles.color);
        LinearHandles.ValueSlider(ref val, origin, -forward, Handles.color);
        LinearHandles.ValueSlider(ref val, origin, -right, Handles.color);

        // draw disc
        CustomHandleUtilities.SetHandleColor(col, col.a * 0.5f);       // ensure it will still render even if alpha is 0
        Handles.DrawWireDisc(origin, up, val);

        // optionally fill the disc
        if (isFilled)
        {
            CustomHandleUtilities.SetHandleColor(col, col.a * 0.1f);
            Handles.DrawSolidDisc(origin, up, val);
        }

        // reset handle color
        CustomHandleUtilities.SetHandleColor(oldColor);
    }
Пример #5
0
    /*
     * Create an arc handle for setting an angular value
     * */
    public static void Arc(ref float val, Vector3 origin, float radius, Quaternion orientation, string label, Color col, bool isFilled, bool isRing)
    {
        // compute handle locations based on disc orientation
        Vector3 right   = orientation * Vector3.right;
        Vector3 up      = orientation * Vector3.up;
        Vector3 forward = orientation * Vector3.forward;

        // set handle color
        Color oldColor = Handles.color;

        CustomHandleUtilities.SetHandleColor(col);

        // create handle
        Vector3 handlePosition  = origin + Quaternion.AngleAxis(val, up) * forward * radius;
        Vector3 handleDirection = Quaternion.AngleAxis(val, up) * right;

        Handles.DrawLine(origin, handlePosition);
        Vector3 vDelta = Handles.Slider(handlePosition, handleDirection, HandleUtility.GetHandleSize(handlePosition) * LinearHandles.dotHandleSize, Handles.DotCap, 1f) - handlePosition;

        if (vDelta.magnitude > requiredMinAngleChange)
        {
            val += vDelta.magnitude * Mathf.Sign(Vector3.Dot(vDelta, handleDirection));
        }

        // draw disc
        if (isRing)
        {
            CustomHandleUtilities.SetHandleColor(col);
            Handles.DrawWireDisc(origin, up, radius);
        }

        // fill arc
        if (isFilled)
        {
            CustomHandleUtilities.SetHandleColor(col, col.a * 0.1f);
            Handles.DrawSolidArc(origin, up, forward, val, radius);
        }

        // draw the label if requested
        if (label.Length > 0)
        {
            Handles.Label(origin + Quaternion.AngleAxis(val, up) * forward * radius, label);
        }

        // reset handle color
        CustomHandleUtilities.SetHandleColor(oldColor);
    }
Пример #6
0
    // manually specify direction, label, and color
    public static void Line(ref float val, Vector3 origin, Vector3 direction, string label, Color col)
    {
        // set handle color
        Color oldColor = Handles.color;

        CustomHandleUtilities.SetHandleColor(col);

        // create handle
        ValueSlider(ref val, origin, direction, label, Handles.color);

        // draw line
        CustomHandleUtilities.SetHandleColor(col);
        Handles.DrawLine(origin, origin + direction * val);

        // reset handle color
        CustomHandleUtilities.SetHandleColor(oldColor);
    }
Пример #7
0
    // manually specify label and color
    public static void WireBox(ref Vector3 size, Vector3 center, Quaternion orientation,
                               string label, Color col)
    {
        // set handle color
        Color oldColor = Handles.color;

        CustomHandleUtilities.SetHandleColor(col);

        // TODO: do something with label?

        // create linear handles to adjust size
        LinearHandles.ValueSlider(ref size.x, center - orientation * Vector3.right * size.x * 0.5f, orientation * Vector3.right);
        LinearHandles.ValueSlider(ref size.x, center + orientation * Vector3.right * size.x * 0.5f, orientation * Vector3.left);
        LinearHandles.ValueSlider(ref size.y, center - orientation * Vector3.up * size.y * 0.5f, orientation * Vector3.up);
        LinearHandles.ValueSlider(ref size.y, center + orientation * Vector3.up * size.y * 0.5f, orientation * Vector3.down);
        LinearHandles.ValueSlider(ref size.z, center - orientation * Vector3.forward * size.z * 0.5f, orientation * Vector3.forward);
        LinearHandles.ValueSlider(ref size.z, center + orientation * Vector3.forward * size.z * 0.5f, orientation * Vector3.back);


        // draw the shape
        Vector3[] corners = new Vector3[8];
        corners[0] = center + orientation * (new Vector3(size.x * 0.5f, size.y * 0.5f, size.z * 0.5f));
        corners[1] = center + orientation * (new Vector3(size.x * 0.5f, -size.y * 0.5f, size.z * 0.5f));
        corners[2] = center + orientation * (new Vector3(size.x * 0.5f, size.y * 0.5f, -size.z * 0.5f));
        corners[3] = center + orientation * (new Vector3(size.x * 0.5f, -size.y * 0.5f, -size.z * 0.5f));
        corners[4] = center + orientation * (new Vector3(-size.x * 0.5f, -size.y * 0.5f, -size.z * 0.5f));
        corners[5] = center + orientation * (new Vector3(-size.x * 0.5f, size.y * 0.5f, size.z * 0.5f));
        corners[6] = center + orientation * (new Vector3(-size.x * 0.5f, -size.y * 0.5f, size.z * 0.5f));
        corners[7] = center + orientation * (new Vector3(-size.x * 0.5f, size.y * 0.5f, -size.z * 0.5f));
        Handles.DrawLine(corners[0], corners[1]);
        Handles.DrawLine(corners[0], corners[2]);
        Handles.DrawLine(corners[0], corners[5]);
        Handles.DrawLine(corners[1], corners[3]);
        Handles.DrawLine(corners[1], corners[6]);
        Handles.DrawLine(corners[2], corners[3]);
        Handles.DrawLine(corners[2], corners[7]);
        Handles.DrawLine(corners[3], corners[4]);
        Handles.DrawLine(corners[4], corners[6]);
        Handles.DrawLine(corners[4], corners[7]);
        Handles.DrawLine(corners[5], corners[6]);
        Handles.DrawLine(corners[5], corners[7]);

        // reset handle color
        CustomHandleUtilities.SetHandleColor(oldColor);
    }
Пример #8
0
 /*
  * Display viewport controls for messing with gizmos
  * */
 public static void ViewportCommonControls()
 {
     GUILayout.BeginVertical();
     {
         // only update values if they change to prevent constant updating of player pref keys
         bool bVal;
         bVal = ViewportControls.OnOffToggle("Symmetry Mode:", isSymmetrical);
         if (bVal != isSymmetrical)
         {
             isSymmetrical = bVal;
         }
         bVal = ViewportControls.OnOffToggle("Mirror Scale:", isScaleSymmetrical);
         if (bVal != isScaleSymmetrical)
         {
             isScaleSymmetrical = bVal;
         }
         bVal = ViewportControls.OnOffToggle("Shape Handles:", isShapeHandleEnabled);
         if (bVal != isShapeHandleEnabled)
         {
             isShapeHandleEnabled = bVal;
         }
         bVal = ViewportControls.OnOffToggle("Center Handles:", isCenterHandleEnabled);
         if (bVal != isCenterHandleEnabled)
         {
             isCenterHandleEnabled = bVal;
         }
         ConfigurableJointEditor.JointHandleToggle();
         GUILayout.BeginHorizontal();
         {
             ConfigurableJointEditor.JointHandleSizeSlider(viewportControlsWidth);
         }
         GUILayout.EndHorizontal();
         CustomHandleUtilities.ViewportIntegratorFidelityControls(viewportControlsWidth);
     }
     GUILayout.EndVertical();
 }
Пример #9
0
    /*
     * Create a disc handle for radius at t using the specified parameters
     * */
    private static void CylinderHandle(ref float radius, ref float height, Vector3 center, Quaternion orientation, string label, Color col, bool isCapsule)
    {
        // clamp values
        radius = Mathf.Abs(radius);
        height = Mathf.Abs(height);

        float oldRadius = radius;

        // TODO: do something with label?

        // set handle color
        Color oldColor = Handles.color;

        CustomHandleUtilities.SetHandleColor(col);

        // compute disc handle locations based on orientation
        Vector3 right   = orientation * Vector3.right * radius;
        Vector3 up      = orientation * Vector3.up * height * 0.5f;
        Vector3 forward = orientation * Vector3.forward * radius;

        // create a disc handle at each end of the cylinder
        Vector3 upperPoint = center + (orientation * Vector3.up) * (height * 0.5f - (isCapsule?radius:0f));
        Vector3 lowerPoint = center + (orientation * Vector3.up) * (height * -0.5f + (isCapsule?radius:0f));

        if (!isCapsule)
        {
            DiscHandles.WireDisc(ref radius, upperPoint, orientation, label, col);
            DiscHandles.WireDisc(ref radius, lowerPoint, orientation, label, col);
        }
        else
        {
            Handles.DrawWireDisc(upperPoint, up, radius);
            Handles.DrawWireDisc(lowerPoint, up, radius);
            DiscHandles.WireDisc(ref radius, center, orientation, label, col);
        }

        // draw a line connecting the handles to visualize the height
        Handles.DrawLine(upperPoint + forward, lowerPoint + forward);
        Handles.DrawLine(upperPoint - forward, lowerPoint - forward);
        Handles.DrawLine(upperPoint + right, lowerPoint + right);
        Handles.DrawLine(upperPoint - right, lowerPoint - right);

        // create a linear handles to adjust height
        LinearHandles.ValueSlider(ref height, center - up, up);
        LinearHandles.ValueSlider(ref height, center + up, -up);

        // create caps if requested
        if (isCapsule)
        {
            Handles.DrawWireArc(center - up + up.normalized * radius, right, forward, 180f, radius);
            Handles.DrawWireArc(center - up + up.normalized * radius, forward, right, -180f, radius);
            Handles.DrawWireArc(center + up - up.normalized * radius, right, forward, -180f, radius);
            Handles.DrawWireArc(center + up - up.normalized * radius, forward, right, 180f, radius);

            // ensure that height and radius values are valid
            if (radius > height * 0.5f)
            {
                // user was operating a radius handle
                if (radius > oldRadius)
                {
                    radius = height * 0.5f;
                }
                // user was operating a height handle
                else
                {
                    height = radius * 2f;
                }
            }
        }

        // reset handle color
        CustomHandleUtilities.SetHandleColor(oldColor);
    }
Пример #10
0
    // invocation using float values and specifying an alpha value
    public static void JointLimit(ref float xMin, ref float xMax, ref float yMax, ref float zMax,
                                  Vector3 origin, Quaternion orientation, Vector3 axis, Vector3 secondaryAxis,
                                  float scale, float alpha)
    {
        // ConfigurableJoint defaults to Vector3.right if axis is Vector3.zero - contrary to documentation
        axis = (axis.sqrMagnitude > 0f)?axis:Vector3.right;

        // if secondaryAxis is Vector3.zero, then it defaults to Vector.up
        secondaryAxis = (secondaryAxis.sqrMagnitude > 0f)?secondaryAxis:Vector3.up;

        // if both secondaryAxis and axis are the same
        secondaryAxis = (Mathf.Abs(Vector3.Dot(axis, secondaryAxis)) == 1f)?Vector3.right:secondaryAxis;

        // normalize axes
        axis.Normalize();
        secondaryAxis.Normalize();

        // on a ConfigurableJoint, secondary axis is used for nothing if it the same as primary axis
        bool isSecondaryAxisValid = !(Mathf.Abs(Vector3.Dot(axis, secondaryAxis)) == 1f);

        // on a ConfigurableJoint, secondary axis is re-orthogonalized from Vector3.up or Vector3.forward (if axis is Vector3.up)
        if (!isSecondaryAxisValid)
        {
            secondaryAxis = (Mathf.Abs(Vector3.Dot(axis, Vector3.up)) == 1f)?Vector3.forward:Vector3.up;
        }
        // compute the third axis
        Vector3 tertiaryAxis = Vector3.Cross(axis, secondaryAxis);

        // orthogonalize secondary axis
        secondaryAxis = Vector3.Cross(tertiaryAxis, axis);

        // colors for each handle
        Color xLimitColor = Color.red; xLimitColor.a = alpha;
        Color yLimitColor = Color.green; yLimitColor.a = alpha;
        Color zLimitColor = Color.blue; zLimitColor.a = alpha;

        CustomHandleUtilities.SetHandleColor(xLimitColor);
        Handles.ArrowCap(0, origin, Quaternion.LookRotation(orientation * axis), scale * 0.2f);
        CustomHandleUtilities.SetHandleColor(yLimitColor);
        Handles.ArrowCap(0, origin, Quaternion.LookRotation(orientation * secondaryAxis), scale * 0.2f);
        CustomHandleUtilities.SetHandleColor(zLimitColor);
        Handles.ArrowCap(0, origin, Quaternion.LookRotation(orientation * tertiaryAxis), scale * 0.2f);

        // xMin/xMax Handles
        Quaternion handleOffset      = Quaternion.LookRotation(tertiaryAxis, axis); // offset from orientation into handle's plane
        Quaternion handleOrientation = orientation * handleOffset;                  // composite orientation of the handle
        float      val = -xMin;

        DiscHandles.Arc(ref val, origin, scale, handleOrientation, "", xLimitColor, false, false);
        xMin = Mathf.Min(-val, xMax);
        val  = -xMax;
        DiscHandles.Arc(ref val, origin, scale, handleOrientation, "", xLimitColor, false, false);
        xMax = Mathf.Max(-val, xMin);
        CustomHandleUtilities.SetHandleColor(xLimitColor, xLimitColor.a * 0.1f);
        Vector3 xHandle1 = orientation * Quaternion.AngleAxis(-xMin, axis) * handleOffset * Vector3.forward;
        Vector3 xHandle2 = orientation * Quaternion.AngleAxis(-xMax, axis) * handleOffset * Vector3.forward;

        // yMax Handles
        handleOffset      = Quaternion.LookRotation(tertiaryAxis, secondaryAxis);
        handleOrientation = orientation * handleOffset;
        val = yMax;
        DiscHandles.Arc(ref val, origin, scale, handleOrientation, "", yLimitColor, false, false);
        yMax = Mathf.Max(val, 0f);
        val *= -1f;
        DiscHandles.Arc(ref val, origin, scale, handleOrientation, "", yLimitColor, false, false);
        yMax = Mathf.Max(-val, 0f);
        Vector3 yHandle1 = orientation * Quaternion.AngleAxis(-yMax, secondaryAxis) * handleOffset * Vector3.forward;
        Vector3 yHandle2 = orientation * Quaternion.AngleAxis(yMax, secondaryAxis) * handleOffset * Vector3.forward;

        // a quaternion to describe the orientation of each handle
        Quaternion qX1 = Quaternion.LookRotation(xHandle1, tertiaryAxis);
        Quaternion qX2 = Quaternion.LookRotation(xHandle2, tertiaryAxis);
        Quaternion qY1 = Quaternion.LookRotation(yHandle1, tertiaryAxis);
        Quaternion qY2 = Quaternion.LookRotation(yHandle2, tertiaryAxis);

        // draw lines to shade the cone
        Vector3[] pts = new Vector3[5];
        pts[0] = qX1 * Vector3.forward * scale;
        pts[1] = qY1 * Vector3.forward * scale;
        pts[2] = qX2 * Vector3.forward * scale;
        pts[3] = qY2 * Vector3.forward * scale;
        pts[4] = qX1 * Vector3.forward * scale;

        // use a catmull-rom spline to define the cone
        int last = pts.Length - 1;

        for (int current = 0; current < last; current++)
        {
            int previous = (current == 0)?last:current - 1;
            int start    = current;
            int end      = (current == last)?0:current + 1;
            int next     = (end == last)?0:end + 1;

            // determine slice count based on arc length between points
            int slices = (int)(CustomHandleUtilities.GetIntegratorStep(origin, scale) * 50f * Vector3.Angle(pts[start], pts[end]));

            // adding one guarantees yielding at least the end point
            int     stepCount        = slices + 1;
            float   oneOverStepCount = 1f / stepCount;
            Vector3 currentPt        = pts[current];
            Vector3 previousPt       = currentPt;
            for (int step = 1; step <= stepCount; step++)
            {
                // compute current color
                Color col = Color.Lerp(xLimitColor, yLimitColor, (current == 1 || current == 3)?1f - step * oneOverStepCount:step * oneOverStepCount);
                // lines to fill cone
                CustomHandleUtilities.SetHandleColor(col, col.a * 0.25f);
                currentPt = Interpolate.CatmullRom(pts[previous], pts[start], pts[end], pts[next], step, stepCount).normalized *scale;
                Handles.DrawLine(origin, origin + Interpolate.CatmullRom(pts[previous], pts[start], pts[end], pts[next], step, stepCount).normalized *scale);
                // lines to draw outer arc
                CustomHandleUtilities.SetHandleColor(col);
                Handles.DrawLine(origin + previousPt, origin + currentPt);
                // increment
                previousPt = currentPt;
            }
        }

        // zMax Handles
        handleOrientation = orientation * Quaternion.LookRotation(axis, tertiaryAxis);
        Quaternion oppositeHandleOrientation = orientation * Quaternion.AngleAxis(180f, tertiaryAxis) * Quaternion.LookRotation(axis, tertiaryAxis);

        val = zMax;
        DiscHandles.Arc(ref val, origin, scale * 0.5f, handleOrientation, "", zLimitColor, true, false);
        zMax = Mathf.Max(val, 0f);
        val *= -1f;
        DiscHandles.Arc(ref val, origin, scale * 0.5f, handleOrientation, "", zLimitColor, true, false);
        zMax = Mathf.Max(-val, 0f);
        val  = zMax;
        DiscHandles.Arc(ref val, origin, scale * 0.5f, oppositeHandleOrientation, "", zLimitColor, true, false);
        zMax = Mathf.Max(val, 0f);
        val *= -1f;
        DiscHandles.Arc(ref val, origin, scale * 0.5f, oppositeHandleOrientation, "", zLimitColor, true, false);
        zMax = Mathf.Max(-val, 0f);
    }
Пример #11
0
    /*
     * Draw a collider gizmo for a specified part
     * */
    public static void DrawShapeHandle(BodyPart part, bool drawOpposite)
    {
        // early out if the part is null or the part has no shape
        if (part == null || part.shapeType == ShapeType.None || !part.isCollider)
        {
            return;
        }

        // store the current color
        Color oldColor = Handles.color;

        // set undo snapshot
        BodyPart[] parts = new BodyPart[1];
        if (isSymmetrical && part.oppositePart != null)
        {
            parts    = new BodyPart[2];
            parts[1] = part.oppositePart;
            parts[1].oppositePart = part;             // BUG: No idea why I need to do this
        }
        parts[0] = part;
        Undo.SetSnapshotTarget(parts, string.Format("Change Shape"));

        // create shape handles
        if (isShapeHandleEnabled)
        {
            // use radius if shape is capsule or sphere
            float radius = 0f;

            // compute the size to draw the shape handle based on the part's scale
            Vector3 shapeHandleSize = GetShapeHandleSize(part);

            // draw the correct handle based on shapeType
            switch (part.shapeType)
            {
            case ShapeType.Box:
                // create handles
                ShapeHandles.WireBox(ref shapeHandleSize, part.bone.TransformPoint(part.shapeCenter), part.bone.rotation, "");

                // apply the result
                DoShapeSizeThresholdTest(part, shapeHandleSize);

                // handle symmetry
                if (parts.Length > 1)
                {
                    part.PasteShapeSizeToOpposite(isScaleSymmetrical);
                    if (drawOpposite)
                    {
                        // get the opposite part's dimensions in case its local scale is different
                        shapeHandleSize = GetShapeHandleSize(parts[1]);

                        // ghost the opposite part
                        CustomHandleUtilities.SetHandleColor(oldColor, symmetryAlpha);
                        ShapeHandles.WireBox(ref shapeHandleSize, parts[1].bone.TransformPoint(parts[1].shapeCenter), parts[1].bone.rotation, "");

                        // apply the result
                        DoShapeSizeThresholdTest(parts[1], shapeHandleSize);
                        parts[1].PasteShapeSizeToOpposite(isScaleSymmetrical);
                    }
                }
                break;

            case ShapeType.Capsule:
                // get handle properties
                float      height             = 0f;
                Quaternion capsuleOrientation = Quaternion.identity;
                GetCapsuleProperties(part, ref height, ref radius, ref capsuleOrientation);

                // draw handle
                ShapeHandles.WireCapsule(ref radius, ref height, part.bone.TransformPoint(part.shapeCenter), part.bone.rotation * part.shapeRotation * capsuleOrientation, "");

                // apply result
                DoShapeSizeThresholdTest(part, CapsuleToSize(part, height, radius));

                // handle symmetry
                if (parts.Length > 1)
                {
                    part.PasteShapeSizeToOpposite(isScaleSymmetrical);
                    if (drawOpposite)
                    {
                        // get the opposite part's dimensions in case its local scale is different
                        GetCapsuleProperties(parts[1], ref height, ref radius, ref capsuleOrientation);

                        // ghost the opposite part
                        CustomHandleUtilities.SetHandleColor(oldColor, symmetryAlpha);
                        ShapeHandles.WireCapsule(ref radius, ref height, parts[1].bone.TransformPoint(parts[1].shapeCenter), parts[1].bone.rotation * parts[1].shapeRotation * capsuleOrientation, "");

                        // apply the result
                        DoShapeSizeThresholdTest(parts[1], CapsuleToSize(parts[1], height, radius));
                        parts[1].PasteShapeSizeToOpposite(isScaleSymmetrical);
                    }
                }
                break;

            case ShapeType.Sphere:
                // create a simple radius handle
                float oldRadius = Mathf.Max(
                    part.shapeSize.x * part.bone.lossyScale.x * 0.5f,
                    part.shapeSize.y * part.bone.lossyScale.y * 0.5f,
                    part.shapeSize.z * part.bone.lossyScale.z * 0.5f);
                radius = Handles.RadiusHandle(part.bone.rotation * part.shapeRotation, part.bone.TransformPoint(part.shapeCenter), oldRadius);
                if (Mathf.Abs(radius - oldRadius) > changeThreshold)
                {
                    float scaleFactor = 1f / VectorHelpers.MaxValue(part.bone.lossyScale);
                    part.shapeSize.x = 2f * radius * scaleFactor;
                    part.shapeSize.y = 2f * radius * scaleFactor;
                    part.shapeSize.z = 2f * radius * scaleFactor;
                    oldRadius        = radius;
                }

                // handle symmetry
                if (parts.Length > 1)
                {
                    part.PasteShapeSizeToOpposite(isScaleSymmetrical);
                    if (drawOpposite)
                    {
                        // ghost the opposite part
                        CustomHandleUtilities.SetHandleColor(oldColor, symmetryAlpha);
                        oldRadius = Mathf.Max(
                            parts[1].shapeSize.x * parts[1].bone.lossyScale.x * 0.5f,
                            parts[1].shapeSize.y * parts[1].bone.lossyScale.y * 0.5f,
                            parts[1].shapeSize.z * parts[1].bone.lossyScale.z * 0.5f);
                        radius = Handles.RadiusHandle(parts[1].bone.rotation * part.shapeRotation, parts[1].bone.TransformPoint(parts[1].shapeCenter), oldRadius);
                        if (Mathf.Abs(radius - oldRadius) > changeThreshold)
                        {
                            parts[1].shapeSize.x = 2f * radius;
                            parts[1].shapeSize.y = 2f * radius;
                            parts[1].shapeSize.z = 2f * radius;
                            parts[1].PasteShapeSizeToOpposite(isScaleSymmetrical);
                        }
                    }
                }
                break;
            }
        }

        // center handles
        if (isCenterHandleEnabled)
        {
            // position handle for the center
            Vector3 center = part.bone.InverseTransformPoint(Handles.PositionHandle(part.bone.TransformPoint(part.shapeCenter), part.bone.rotation * part.shapeRotation));

            // rotation handle
//			Quaternion rotation = Quaternion.Inverse(part.bone.rotation)*Handles.RotationHandle(part.bone.rotation*part.shapeRotation, part.bone.TransformPoint(part.shapeCenter));

            // handle symmetry
            if (parts.Length > 1)
            {
                center = part.TransformPointToOpposite(center, isScaleSymmetrical);
//				rotation = part.TransformRotationToOpposite(rotation);
                if (drawOpposite)
                {
                    center = parts[1].bone.InverseTransformPoint(Handles.PositionHandle(parts[1].bone.TransformPoint(center), parts[1].bone.rotation * part.shapeRotation));
//					rotation = Quaternion.Inverse(parts[1].bone.rotation)*Handles.RotationHandle(parts[1].bone.rotation*parts[1].shapeRotation, parts[1].bone.TransformPoint(parts[1].shapeCenter));
                }
                center = parts[1].TransformPointToOpposite(center, isScaleSymmetrical);
//				rotation = parts[1].TransformRotationToOpposite(rotation);
            }

            // apply results
            if ((part.shapeCenter - center).sqrMagnitude > changeThreshold * changeThreshold)
            {
                part.shapeCenter = center;
            }
//			if (Quaternion.Angle(part.shapeRotation, rotation)>changeThreshold) part.shapeRotation = rotation;
        }

        if (parts.Length > 1)
        {
            // update values
            parts[1].shapeType = part.shapeType;

            Vector3 oldValue = parts[1].shapeCenter;
            Vector3 newValue = part.TransformPointToOpposite(part.shapeCenter, isScaleSymmetrical);
            if ((oldValue - newValue).sqrMagnitude > changeThreshold * changeThreshold)
            {
                parts[1].shapeCenter = newValue;
            }

            oldValue = parts[1].shapeSize;
            part.PasteShapeSizeToOpposite(isScaleSymmetrical);
            if ((oldValue - parts[1].shapeSize).sqrMagnitude < changeThreshold * changeThreshold)
            {
                parts[1].shapeSize = oldValue;
            }

            // TODO: mirror rotation if/when collider rotation is implemented
        }

        CustomHandleUtilities.SetHandleColor(oldColor);

        foreach (BodyPart p in parts)
        {
            EditorUtility.SetDirty(p);
        }
    }