createCylindricalProjectionTransform static method

Matrix4 createCylindricalProjectionTransform({
  1. required double radius,
  2. required double angle,
  3. double perspective = 0.001,
  4. 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 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(perspective >= 0 && perspective <= 1.0);

  // 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 = result * (switch (orientation) {
      Axis.horizontal => Matrix4.rotationY(angle),
      Axis.vertical   => Matrix4.rotationX(angle),
    } * Matrix4.translationValues(0.0, 0.0, radius)) as Matrix4;

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