1

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(
      ),
    ),
Adrian FG
  • 23
  • 1
  • 5

2 Answers2

4

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;
    });
  },
casraf
  • 21,085
  • 9
  • 56
  • 91
  • 1
    Hi, your solution is only partially correct, your situation is only an edge case when there is no translation nor rotation involved. The coordinate system is called "homogenous coordinates", and is used in perspective geometry and thus, OpenGL was well. In short, HC represents N-dimensions of space in (N+1)-dimensions, which the extra dimenion adds the perspective factor. It's a pretty fun and mindbending subject :) – rdnobrega Jun 08 '20 at 06:32
  • I am terrible at maths & vectors, if you have improvements to this answer feel free to suggest edits! – casraf Jun 16 '20 at 10:38
2

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.

rdnobrega
  • 721
  • 6
  • 16