Adrian FG
Adrian FG

Reputation: 23

Flutter: How to set a max/min scale using matrix_gesture_detector

Im trying to make a Column scale/movable. But how can I set a max zoom and a min zoom? So that you don´t zoom to infinity.

Using this method right now:

 Matrix4 matrix = Matrix4.identity();

     MatrixGestureDetector(
      shouldRotate: false
      onMatrixUpdate: (Matrix4 m, Matrix4 tm, Matrix4 sm, Matrix4 rm) {
        setState(() {
          matrix = m;
        });
      },
      child: Transform(
        transform: matrix,
        child: Column(
      ),
    ),

Upvotes: 1

Views: 3370

Answers (2)

casraf
casraf

Reputation: 21684

Update: As @rdnobrega kindly explained, this is not an ideal solution by any means. It will only work when the only transformed Matrix is scale, and will break with other transformations. I am no math expert or Matrix4 expert, so the solution below is at your own risk.

I had the same problem and took a while but came across the solution.

After digging around just a bit in Flutter's Transform.scale source reveals this line which gives us a hint:

transform = Matrix4.diagonal3Values(scale, scale, 1.0)

It's using the diagonal values from the Matrix4 you receive in onMatrixUpdate. So it takes x from the 1st Vector4, y from the 2nd, and z from the 3rd. (the 4th is fixed from what I can tell). So those are the values you need to limit. In this example I've made a small _minMax method which bounds the scale to the relevant min/max when relevant (they can be passed null to ignore either side of the limit).

I used this to limit the scale:

typedef MathF<T extends num> = T Function(T, T);
typedef VFn = Vector4 Function(double x, double y, double z, double w);

double _minMax(num _min, num _max, num actual) {
  if (_min == null && _max == null) {
    return actual.toDouble();
  }

  if (_min == null) {
    return min(_max.toDouble(), actual.toDouble());
  }

  if (_max == null) {
    return max(_min.toDouble(), actual.toDouble());
  }

  return min(_max.toDouble(), max(_min.toDouble(), actual.toDouble()));
}

// ... ... ...

onMatrixUpdate: (Matrix4 m, Matrix4 tm, Matrix4 sm, Matrix4 rm) {
    var finalM = Matrix4.copy(m);
    Map<int, VFn> colmap = {
      0: (x, y, z, w) {
        x = _minMax(widget.minScale, widget.maxScale, x);
        return Vector4(x, y, z, w);
      },
      1: (x, y, z, w) {
        y = _minMax(widget.minScale, widget.maxScale, y);
        return Vector4(x, y, z, w);
      },
      2: (x, y, z, w) {
        z = _minMax(widget.minScale, widget.maxScale, z);
        return Vector4(x, y, z, w);
      },
    };
    for (var col in colmap.keys) {
      var oldCol = m.getColumn(col);
      var colD = colmap[col];
      if (colD != null) {
        finalM.setColumn(col, colD(oldCol.x, oldCol.y, oldCol.z, oldCol.w));
      }
    }
    setState(() {
      matrix = finalM;
    });
  },

Upvotes: 4

rdnobrega
rdnobrega

Reputation: 789

Just to complement @casraf's answer.

The coordinate system used by Flutter (and other graphic languages such as OpenGL) is the homogeous coordinates. This is just a fancy name for a helper dimension that exists in these Matrices for perspective calculations, like when a thing is closer it gets bigger, if it is far it gets smaller.

So, in these coordinates, unlike cartesian, we need one extra dimension. If we are trying to represent 3 dimensions of space in perspective, we need a 4-dimension matrix.

For curiosity only, isometric games and 2d-like 3d games actually mess with this extra W dimension (4th row of the matrix) for applying no perspective at all.

That said, Flutter/openGL does the calculation of final vertices in the screen multiplying your raw coordinate in the form of a 1-line matrix (x, y, z, 1) by a transformation matrix Matrix4. If you do the math, the result still is a 1-line matrix, but translated/rotated/scaled/skewed from the original vertex, by the matrix.

That means a bulky matrix multiplication behind the scenes, and altering specific coefficients manually may lead to unexpected behaviors. In @casraf's case, it works for there is no translation nor rotation involved, so the changed coefficients are equal to the scale coefficients. If you apply any other transformation, the result may vary a lot.

TL;DR

Always use the proper built-in methods of Matrix4 to do the transformations, unless you really know what you are doing.

Also, keep in mind that the order of transformations matter. Doing a translation then a rotation is diferent from doing rotation -> translation.

Upvotes: 2

Related Questions