Dee
Dee

Reputation: 1

Snap Matrix4 rotation to 90, 180, 270, 360 degrees, Flutter

I'm currently using the matrix_gesture_detector package to scale, transform and rotate a Transform widget.

Everything works fine but to improve UX I would like to snap the widget at 90, 180, 270 or 360 degrees once the user rotates the widget close enough to said angles.

Edit: To clarify I would like the user to be able to freely rotate the widget, but snap into the nearest 90 degree rotation within whichever quadrant it is, once it gets close enough. Hence, the solution should detect that "closeness" and then act accordingly. Please visit this link to see a GIF which shows the desired effect

How can I achieve this?

Below is the code snippet

Widget transformContainer() {
Matrix4 matrix;
GlobalKey matrixDetectorKey = GlobalKey();

return MatrixGestureDetector(
  key: matrixDetectorKey,
  onMatrixUpdate: (m, tm, sm, rm) {
    setState(() {
      matrix = MatrixGestureDetector.compose(matrix, tm, sm, rm);
    });
  },
  child: Transform(
    transform: matrix,
    child: Container(
      padding: EdgeInsets.all(24.0),
      width: 100.0,
      height: 200.0,
      color: Colors.teal,
    ),
  ),
);}

Upvotes: 0

Views: 2330

Answers (3)

Yusuph wickama
Yusuph wickama

Reputation: 450

For this to work, you have to check if the rotation of the matrix is closing in on a snapping point and rotate the Z-Axis to that point. I was able to write this code which works for this scenario. You may have to tune the threshold to adjust the "snappiness" to your taste.

import vector_math and use the following code:

import 'package:vector_math/vector_math_64.dart' as vec;

       onMatrixUpdate: (Matrix4 m, Matrix4 tm, Matrix4 sm, Matrix4 rm) {
          Matrix4 ogRm = rm.clone();
          double radian = MatrixGestureDetector.decomposeToValues(m).rotation;
          double degrees = vec.degrees(radian);

          double delta_0 = vec.absoluteError(degrees, 0);
          double delta_90 = vec.absoluteError(degrees, 90);
          double delta_180 = vec.absoluteError(degrees, 180);
          double delta_270 = vec.absoluteError(degrees, -90);
          double threshold = 4;

          if (delta_0 <= threshold) {
            rm.rotateZ(vec.radians(0) - radian);
          } else if (delta_90 <= threshold) {
            rm.rotateZ(vec.radians(90) - radian);
          } else if (delta_180 <= threshold) {
            rm.rotateZ(vec.radians(180) - radian);
          } else if (delta_270 <= threshold) {
            rm.rotateZ(vec.radians(270) - radian);
          }

          // update gesture matrix
          if (ogRm != rm) m = m * rm;

          setState(() {
            //transform your widget using this matrix
            matrix = m;
          });
        }

Note that 360 is the same as 0, so there's no need to check for it.

Upvotes: 1

AYUSH SINGH
AYUSH SINGH

Reputation: 31

You will have the rotate function inside the library. this code will work for snapping to 0 degrees. Basically what I am doing is if the difference in rotation and x-axis is less than 0.2 radians, I snap to 0 degrees while keeping the rotation in a separate variable. When the user moves beyond this, I add this value again and keep rotating normally.

 Matrix4 _rotate(double angle, Offset focalPoint) {
    double toBeRotated = 0;
    var array = matrix.applyToVector3Array([0, 0, 0, 1, 0, 0]);
    Offset delta = Offset(array[3] - array[0], array[4] - array[1]);
    double rotation = delta.direction;
    deltaAngle = deltaAngle + angle;

  
    if ((rotation + deltaAngle).abs() > 0.2) {
      toBeRotated = deltaAngle;
      deltaAngle = 0;
    } else if (rotation != 0 && (rotation + deltaAngle).abs() <= 0.2) {
      toBeRotated = -rotation;
      deltaAngle = deltaAngle + rotation;
    } else {
      toBeRotated = 0;
    }
      var c = cos(toBeRotated);
    var s = sin(toBeRotated);
    
    var dx = (1 - c) * focalPoint.dx + s * focalPoint.dy;
    var dy = (1 - c) * focalPoint.dy - s * focalPoint.dx;

    //  ..[0]  = c       # x scale
    //  ..[1]  = s       # y skew
    //  ..[4]  = -s      # x skew
    //  ..[5]  = c       # y scale
    //  ..[10] = 1       # diagonal "one"
    //  ..[12] = dx      # x translation
    //  ..[13] = dy      # y translation
    //  ..[15] = 1       # diagonal "one"
    return Matrix4(c, s, 0, 0, -s, c, 0, 0, 0, 0, 1, 0, dx, dy, 0, 1);
  }

Upvotes: 0

Josteve Adekanbi
Josteve Adekanbi

Reputation: 12673

MatrixGestureDetector(
        onMatrixUpdate: (m, tm, sm, rm) {
          setState(() {
            //270 is the angle
            m.rotate(m.getTranslation(), 270);
            matrix4 = m;
          });
        },
        child: Transform(
          transform: matrix4,
          child: Container(
            padding: EdgeInsets.all(24.0),
            width: 100,
            height: 200,
            color: Colors.teal,
          ),
        ),
      ),

Upvotes: 0

Related Questions