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