Unverified Commit 76d13ab3 authored by Jim Graham's avatar Jim Graham Committed by GitHub

Reland: Optimize the transformRect and transformPoint methods in matrix_utils. (#36396) (#37275)

Primarily these methods no longer allocate any objects other than their
return values.

Additionally, the math in the methods is reduced compared to the general
case math based on known input conditions.

Modified to no longer generate infinite values in some finite cases.
parent 852bcba5
......@@ -9,7 +9,7 @@ import 'package:flutter/painting.dart';
import 'package:vector_math/vector_math_64.dart';
void main() {
test('MatrixUtils.transformRect handles very small values', () {
test('MatrixUtils.transformRect handles very large finite values', () {
const Rect evilRect = Rect.fromLTRB(0.0, -1.7976931348623157e+308, 800.0, 1.7976931348623157e+308);
final Matrix4 transform = Matrix4.identity()..translate(10.0, 0.0);
final Rect transformedRect = MatrixUtils.transformRect(transform, evilRect);
......@@ -128,4 +128,78 @@ void main() {
forcedOffset,
);
});
test('transformRect with no perspective (w = 1)', () {
const Rect rectangle20x20 = Rect.fromLTRB(10, 20, 30, 40);
// Identity
expect(
MatrixUtils.transformRect(Matrix4.identity(), rectangle20x20),
rectangle20x20,
);
// 2D Scaling
expect(
MatrixUtils.transformRect(Matrix4.diagonal3Values(2, 2, 2), rectangle20x20),
const Rect.fromLTRB(20, 40, 60, 80),
);
// Rotation
expect(
MatrixUtils.transformRect(Matrix4.rotationZ(pi / 2.0), rectangle20x20),
within<Rect>(distance: 0.00001, from: const Rect.fromLTRB(-40.0, 10.0, -20.0, 30.0)),
);
});
test('transformRect with perspective (w != 1)', () {
final Matrix4 transform = MatrixUtils.createCylindricalProjectionTransform(
radius: 10.0,
angle: pi / 8.0,
perspective: 0.3,
);
for (int i = 1; i < 10000; i++) {
final Rect rect = Rect.fromLTRB(11.0 * i, 12.0 * i, 15.0 * i, 18.0 * i);
final Rect golden = _vectorWiseTransformRect(transform, rect);
expect(
MatrixUtils.transformRect(transform, rect),
within<Rect>(distance: 0.00001, from: golden),
);
}
});
}
// Produces the same computation as `MatrixUtils.transformPoint` but it uses
// the built-in perspective transform methods in the Matrix4 class as a
// golden implementation of the optimized `MatrixUtils.transformPoint`
// to make sure optimizations do not contain bugs.
Offset _transformPoint(Matrix4 transform, Offset point) {
final Vector3 position3 = Vector3(point.dx, point.dy, 0.0);
final Vector3 transformed3 = transform.perspectiveTransform(position3);
return Offset(transformed3.x, transformed3.y);
}
// Produces the same computation as `MatrixUtils.transformRect` but it does this
// one point at a time. This function is used as the golden implementation of the
// optimized `MatrixUtils.transformRect` to make sure optimizations do not contain
// bugs.
Rect _vectorWiseTransformRect(Matrix4 transform, Rect rect) {
final Offset point1 = _transformPoint(transform, rect.topLeft);
final Offset point2 = _transformPoint(transform, rect.topRight);
final Offset point3 = _transformPoint(transform, rect.bottomLeft);
final Offset point4 = _transformPoint(transform, rect.bottomRight);
return Rect.fromLTRB(
_min4(point1.dx, point2.dx, point3.dx, point4.dx),
_min4(point1.dy, point2.dy, point3.dy, point4.dy),
_max4(point1.dx, point2.dx, point3.dx, point4.dx),
_max4(point1.dy, point2.dy, point3.dy, point4.dy),
);
}
double _min4(double a, double b, double c, double d) {
return min(a, min(b, min(c, d)));
}
double _max4(double a, double b, double c, double d) {
return max(a, max(b, max(c, d)));
}
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