createCylindricalProjectionTransform method

Matrix4 createCylindricalProjectionTransform ({@required double radius, @required double angle, double perspective: 0.001, Axis orientation: Axis.vertical })

Create a transformation matrix which mimics the effects of tangentially wrapping the plane on which this transform is applied around a cylinder and then looking at the cylinder from a point outside the cylinder.

The radius simulates the radius of the cylinder the plane is being wrapped onto. If the transformation is applied to a 0-dimensional dot instead of a plane, the dot would simply translate by +/- radius pixels along the orientation Axis when rotating from 0 to +/- 90 degrees.

A positive radius means the object is closest at 0 angle and a negative radius means the object is closest at π angle or 180 degrees.

The angle argument is the difference in angle in radians between the object and the viewing point. A positive angle on a positive radius moves the object up when orientation is vertical and right when horizontal.

The transformation is always done such that a 0 angle keeps the transformed object at exactly the same size as before regardless of radius and perspective when radius is positive.

The perspective argument is a number between 0 and 1 where 0 means looking at the object from infinitely far with an infinitely narrow field of view and 1 means looking at the object from infinitely close with an infinitely wide field of view. Defaults to a sane but arbitrary 0.001.

The orientation is the direction of the rotation axis.

Because the viewing position is a point, it's never possible to see the outer side of the cylinder at or past +/- π / 2 or 90 degrees and it's almost always possible to end up seeing the inner side of the cylinder or the back side of the transformed plane before π / 2 when perspective > 0.

Implementation

static Matrix4 createCylindricalProjectionTransform({
  @required double radius,
  @required double angle,
  double perspective = 0.001,
  Axis orientation = Axis.vertical,
}) {
  assert(radius != null);
  assert(angle != null);
  assert(perspective >= 0 && perspective <= 1.0);
  assert(orientation != null);

  // Pre-multiplied matrix of a projection matrix and a view matrix.
  //
  // Projection matrix is a simplified perspective matrix
  // http://web.iitd.ac.in/~hegde/cad/lecture/L9_persproj.pdf
  // in the form of
  // [[1.0, 0.0, 0.0, 0.0],
  //  [0.0, 1.0, 0.0, 0.0],
  //  [0.0, 0.0, 1.0, 0.0],
  //  [0.0, 0.0, -perspective, 1.0]]
  //
  // View matrix is a simplified camera view matrix.
  // Basically re-scales to keep object at original size at angle = 0 at
  // any radius in the form of
  // [[1.0, 0.0, 0.0, 0.0],
  //  [0.0, 1.0, 0.0, 0.0],
  //  [0.0, 0.0, 1.0, -radius],
  //  [0.0, 0.0, 0.0, 1.0]]
  Matrix4 result = Matrix4.identity()
      ..setEntry(3, 2, -perspective)
      ..setEntry(2, 3, -radius)
      ..setEntry(3, 3, perspective * radius + 1.0);

  // Model matrix by first translating the object from the origin of the world
  // by radius in the z axis and then rotating against the world.
  result *= (
      orientation == Axis.horizontal
          ? Matrix4.rotationY(angle)
          : Matrix4.rotationX(angle)
  ) * Matrix4.translationValues(0.0, 0.0, radius);

  // Essentially perspective * view * model.
  return result;
}