How to snap rotation to nearest increment?

I’m making a level creator, and I’ve finished making the rotation tool. Now I want to implement rotation snapping; this seems like it should be easy but I can’t find a reliable way to do it.

I’m rotating objects by multiplying their rotation with the add rotation, and I’m getting the add rotation by seeing how much the controller moved between this frame and the last frame (this is a VR project).

    void RotateObject()
    {
        float delta = 0;
        directionGizmos.SetActive(true);

        Vector3 add = Vector3.zero;
        switch ( currentAxis )
        {
            case 0: // x
                delta = -(currentController.localPosition.y - dragStartPos.y);
                add = new Vector3(delta, 0, 0);

                directionGizmos.transform.localRotation = Quaternion.Euler(90, -90, 0);
                break;
            case 1: // y
                delta = currentController.localPosition.x - dragStartPos.x;
                add = new Vector3(0, delta, 0);

                directionGizmos.transform.localRotation = Quaternion.Euler(0, -90, 0);
                break;
            case 2: // z
                delta = -(currentController.localPosition.y - dragStartPos.y);
                add = new Vector3(0, 0, delta);

                directionGizmos.transform.localRotation = Quaternion.Euler(90, -90, 0);
                break;
        }

        add *= rotateSpeedMultiplier;

        if ( ToolSelector.IsLocal )
        {
            Selector.instance.currentSelected.transform.localRotation *= Quaternion.Euler(add);
        }

        else
        {
            Selector.instance.currentSelected.transform.rotation *= Quaternion.Euler(add);
            rotateGizmos.transform.rotation *= Quaternion.Euler(add);
        }

        dragStartPos = currentController.localPosition;
    }

Any help is appreciated, thanks!

Generally, any time you want to snap a floating point value to a stepped multiple you do it the following way:

float snappedValue = Mathf.Round(value / Snap) * Snap;

This of course would be in absolute terms. If you want relative terms (in other words, you want some offset that you then snap from) then you’d simply add your offset to the final value.

3 Likes

This helped me find a solution. I changed delta from being based on the position moved from last frame to this frame to being the distance moved from the startDragPos to the current position, and I fixed the bug where the rotation was constantly adding to itself by setting the rotation to the startRotation + add.

    void RotateObject()
    {
        float delta = 0;
        directionGizmos.SetActive(true);

        Vector3 add = Vector3.zero;
      
        switch ( currentAxis )
        {
            // x
            case 0: 
                delta = -(currentController.localPosition.y - dragStartPos.y);
                add = new Vector3(delta, 0, 0);

                directionGizmos.transform.localRotation = Quaternion.Euler(90, -90, 0);
                break;
            // y
            case 1: 
                delta = currentController.localPosition.x - dragStartPos.x;
                add = new Vector3(0, delta, 0);

                directionGizmos.transform.localRotation = Quaternion.Euler(0, -90, 0);
                break;
            // z
            case 2:
                delta = -(currentController.localPosition.y - dragStartPos.y);
                add = new Vector3(0, 0, delta);

                directionGizmos.transform.localRotation = Quaternion.Euler(90, -90, 0);
                break;
        }

        add *= rotateSpeedMultiplier;
        add = new Vector3(SnapValue(add.x), SnapValue(add.y), SnapValue(add.z));

        if ( ToolSelector.IsLocal )
        {
            Selector.instance.currentSelected.transform.localRotation = startLocalRotation * Quaternion.Euler(add);
        }

        else
        {
            Selector.instance.currentSelected.transform.rotation = startRotation * Quaternion.Euler(add);
            rotateGizmos.transform.rotation *= Quaternion.Euler(add);
        }
    }

    float SnapValue(float value)
    {
        return Mathf.Round(value / snap) * snap;
    }
1 Like