Improved Transform Tools

Project description

After being dissapointed at the lack of (visual) information given to the user when using the default transform tools (Move, Rotate, Scale) in the Unity engine, I wrote my own tool that does show the information that I need. The visual feedback is mainly inspired by Unreal and CryEngine. This plugin gives the user more (visual) information about the action they are currently executing making it easier to move, rotate or scale objects without having to look at the inspector for information.
The plugin comes with an option menu that gives the user full customization control over Colors, whether or not to show labels and rulers. It's also compatible with Improved Handles which adds custom 3D handles to Unity.

UML Diagram
To get a better understanding of the structure of the tool you can view the UML diagram below. You can see that MoveTool, RotateTool and ScaleTool all extend from BaseTool, which provides them with some basic functionality. BaseTool extends Unity's Editor class and implements the IBaseTool interface which provides BaseTool with a set a functions it has to implement. BaseTool creates a new instance of SettingsSerializer to get access to the users customization settings.



All the tools inherit from BaseTool which provides some basic functionality that is used by all three classes, this is also useful as it allows me to easily extend my tool with more functionality if all of the tools require it. And it gives me a starting point to create new transform tools from.

public class BaseTool : Editor, IBaseTool {

    public SettingsSerializer settings = new SettingsSerializer();

    public virtual void OnEnable() {
        settings = SettingsSerializer.Load();
    }

    public virtual void OnSceneGUI() {
        // You can keep this empty, this is a building block for dirived Tools
    }

    public GUIStyle SetLabelStyle() {
        GUIStyle _style         = new GUIStyle();
        _style.normal.textColor = Color.white;
        _style.fontStyle        = FontStyle.Bold;
        _style.fontSize         = 14;

        return _style;
    }
}

For the rotate tool, I wanted to display a disc that was filled for the same amount of degrees as the object's rotation. I also wanted to display a label that would give the user an indication on how much he has rotated the object. Below you can find the Rotate Tool script.
[CustomEditor(typeof(RotateHandle))]
public class RotateTool : BaseTool {

    private Vector3 startWorldDirection, direction, rootForward, rootRight, rootUp;
    private bool isMouseDown = false;
    private float axisInfo = 0.0f;

    public override void OnEnable() {
        base.OnEnable();
    }

    public override void OnSceneGUI() {
        RotateHandle _handle = target as RotateHandle;

        if (_handle != null) {
            // On MouseDown event, set some variables
            if (Event.current.type == EventType.MouseDown && Event.current.button == 0) {
                isMouseDown = true;

                // Get starting values of the axis
                startWorldDirection = _handle.transform.up;
                rootForward = _handle.transform.forward;
                rootRight = _handle.transform.right;
                rootUp = _handle.transform.up;

            } else if (Event.current.type == EventType.MouseUp && Event.current.button == 0) {
                isMouseDown = false;
            }

            if (isMouseDown) {
                Vector3 _worldDirection = _handle.transform.up;

                // Get our rotation angle
                float _angle = Mathf.Acos(Vector3.Dot(startWorldDirection, _worldDirection)) * Mathf.Rad2Deg;

                // Get Cross product between 2 Vectors
                Vector3 _cross = Vector3.Cross(startWorldDirection, _worldDirection);

                // Get Dot product between 2 Vectors
                if (Vector3.Dot(_handle.transform.forward, _cross) < 0) {
                    _angle = -_angle;
                }

                Vector3 _newForward = _handle.transform.forward;
                Vector3 _newRight = _handle.transform.right;
                Vector3 _newUp = _handle.transform.up;

                bool _y, _x, _z;
                _y = rootUp == _newUp;
                _x = rootRight == _newRight;
                _z = rootForward == _newForward;

                //rotating over x
                if (_x && !(_y && _z)) {
                    Handles.color = settings.rotateTool.x;
                    direction = new Vector3(0, 1, 1);
                    axisInfo = CalculateDegrees(GetAngle(rootUp, _newUp, rootRight));
                }
                //rotating over y
                if (_y && !(_x && _z)) {
                    Handles.color = settings.rotateTool.y;
                    direction = Vector3.up;
                    axisInfo = CalculateDegrees(GetAngle(rootRight, _newRight, rootUp));
                }
                //rotating over z
                if (_z && !(_x && _y)) {
                    Handles.color = settings.rotateTool.z;
                    direction = Vector3.forward;
                    axisInfo = CalculateDegrees(GetAngle(rootUp, _newUp, rootForward));
                }

                if (settings.rotateTool.ruler) {
                    Handles.DrawSolidArc(
                        _handle.transform.position,
                        direction,
                        -Vector3.right,
                        axisInfo,
                        // make sure our disc stays the same size as our tool handle
                        HandleUtility.GetHandleSize(_handle.transform.position)
                    );
                }
                
                if (settings.rotateTool.label) {
                    // Show a label with useful rotation related information
                    Handles.Label(
                        _handle.transform.position,
                        axisInfo.ToString("F1") + " degrees",
                        SetLabelStyle()
                    );
                }
            }
        }
    }

    private float GetAngle(Vector3 a, Vector3 b, Vector3? customCross = null) {
        a.Normalize();
        b.Normalize();

        Vector3 _cross = customCross == null ? Vector3.Cross(a, b) : customCross.Value;

        Vector3 _aP = Quaternion.AngleAxis(90, _cross) * a;
        return (Mathf.Atan2(Vector3.Dot(_aP, b), Vector3.Dot(a, b)) / Mathf.PI) * 180;
    }

    // Dot product goes from -180 to 0 to 180
    // -180 now equals 360 degrees rotation
    private float CalculateDegrees(float value) {
        if (value < 0) {
            return 180 + 180 + value;
        }
        return value;
    }
}

Project details
  • Client : Personal project
  • Category : Game Development, Programming
  • Date Created : November 1, 2016

Share this Post: