blob: 65b1381699f817b43ad31223ff4a5463c9766cc8 [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// 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/painting.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:vector_math/vector_math_64.dart';
void main() {
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);
final Rect transformedRect = MatrixUtils.transformRect(transform, evilRect);
expect(transformedRect.isFinite, true);
});
test('MatrixUtils.getAsTranslation()', () {
Matrix4 test;
test = Matrix4.identity();
expect(MatrixUtils.getAsTranslation(test), equals(Offset.zero));
test = Matrix4.zero();
expect(MatrixUtils.getAsTranslation(test), isNull);
test = Matrix4.rotationX(1.0);
expect(MatrixUtils.getAsTranslation(test), isNull);
test = Matrix4.rotationZ(1.0);
expect(MatrixUtils.getAsTranslation(test), isNull);
test = Matrix4.translationValues(1.0, 2.0, 0.0);
expect(MatrixUtils.getAsTranslation(test), equals(const Offset(1.0, 2.0)));
test = Matrix4.translationValues(1.0, 2.0, 3.0);
expect(MatrixUtils.getAsTranslation(test), isNull);
test = Matrix4.identity();
expect(MatrixUtils.getAsTranslation(test), equals(Offset.zero));
test.rotateX(2.0);
expect(MatrixUtils.getAsTranslation(test), isNull);
test = Matrix4.identity();
expect(MatrixUtils.getAsTranslation(test), equals(Offset.zero));
test.scale(2.0);
expect(MatrixUtils.getAsTranslation(test), isNull);
test = Matrix4.identity();
expect(MatrixUtils.getAsTranslation(test), equals(Offset.zero));
test.translate(2.0, -2.0);
expect(MatrixUtils.getAsTranslation(test), equals(const Offset(2.0, -2.0)));
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, 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, 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, Matrix4.identity());
});
test('cylindricalProjectionTransform calculation spot check', () {
final Matrix4 actual = MatrixUtils.createCylindricalProjectionTransform(
radius: 100.0,
angle: pi / 3.0,
);
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,
]);
});
test('forceToPoint', () {
const Offset forcedOffset = Offset(20, -30);
final Matrix4 forcedTransform = MatrixUtils.forceToPoint(forcedOffset);
expect(
MatrixUtils.transformPoint(forcedTransform, forcedOffset),
forcedOffset,
);
expect(
MatrixUtils.transformPoint(forcedTransform, Offset.zero),
forcedOffset,
);
expect(
MatrixUtils.transformPoint(forcedTransform, const Offset(1, 1)),
forcedOffset,
);
expect(
MatrixUtils.transformPoint(forcedTransform, const Offset(-1, -1)),
forcedOffset,
);
expect(
MatrixUtils.transformPoint(forcedTransform, const Offset(-20, 30)),
forcedOffset,
);
expect(
MatrixUtils.transformPoint(forcedTransform, const Offset(-1.2344, 1422434.23)),
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)));
}