// 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))); }