Unverified Commit 53c470dd authored by xster's avatar xster Committed by GitHub

Add helper function to create cylindrical projection matrices (#13376)

* Add a cylindrical projection helper matrix

* specify tangential

* more doc clarifications

* reshuffle arguments

* more code comments

* add some sanity tests

* review

* review 2

* added one more caveat for consideration in docs
parent 045ee5fd
......@@ -169,6 +169,83 @@ class MatrixUtils {
transform = new Matrix4.copy(transform)..invert();
return transformRect(transform, rect);
}
/// 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.
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 = new 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
? new Matrix4.rotationY(angle)
: new Matrix4.rotationX(angle)
) * new Matrix4.translationValues(0.0, 0.0, radius);
// Essentially perspective * view * model.
return result;
}
}
/// Returns a list of strings representing the given transform in a format
......
......@@ -2,8 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/painting.dart';
import 'package:test/test.dart';
import 'package:vector_math/vector_math_64.dart';
void main() {
......@@ -39,4 +41,49 @@ void main() {
test.translate(4.0, 8.0);
expect(MatrixUtils.getAsTranslation(test), equals(const Offset(6.0, 6.0)));
});
test('cylindricalProjectionTransform identity', () {
final Matrix4 initialState = MatrixUtils.createCylindricalProjectionTransform(
radius: 0.0,
angle: 0.0,
perspective: 0.0,
);
expect(initialState, new Matrix4.identity());
});
test('cylindricalProjectionTransform rotate with no radius', () {
final Matrix4 simpleRotate = MatrixUtils.createCylindricalProjectionTransform(
radius: 0.0,
angle: pi / 2.0,
perspective: 0.0,
);
expect(simpleRotate, new Matrix4.rotationX(pi / 2.0));
});
test('cylindricalProjectionTransform radius does not change scale', () {
final Matrix4 noRotation = MatrixUtils.createCylindricalProjectionTransform(
radius: 1000000.0,
angle: 0.0,
perspective: 0.0,
);
expect(noRotation, new Matrix4.identity());
});
test('cylindricalProjectionTransform calculation spot check', () {
final Matrix4 actual = MatrixUtils.createCylindricalProjectionTransform(
radius: 100.0,
angle: pi / 3.0,
perspective: 0.001,
);
expect(actual.storage, <dynamic>[
1.0, 0.0, 0.0, 0.0,
0.0, moreOrLessEquals(0.5), moreOrLessEquals(0.8660254037844386), moreOrLessEquals(-0.0008660254037844386),
0.0, moreOrLessEquals(-0.8660254037844386), moreOrLessEquals(0.5), moreOrLessEquals(-0.0005),
0.0, moreOrLessEquals(-86.60254037844386), moreOrLessEquals(-50.0), 1.05,
]);
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment