Implement correct orthographic projection (#22985)
diff --git a/packages/flutter/lib/src/rendering/box.dart b/packages/flutter/lib/src/rendering/box.dart
index d129acc..9fd85d8 100644
--- a/packages/flutter/lib/src/rendering/box.dart
+++ b/packages/flutter/lib/src/rendering/box.dart
@@ -1989,6 +1989,9 @@
/// Convert the given point from the global coordinate system in logical pixels
/// to the local coordinate system for this box.
///
+ /// This method will un-project the point from the screen onto the widget,
+ /// which makes it different from [MatrixUtils.transformPoint].
+ ///
/// If the transform from global coordinates to local coordinates is
/// degenerate, this function returns [Offset.zero].
///
@@ -1998,11 +2001,28 @@
///
/// This method is implemented in terms of [getTransformTo].
Offset globalToLocal(Offset point, { RenderObject ancestor }) {
+ // We want to find point (p) that corresponds to a given point on the
+ // screen (s), but that also physically resides on the local render plane,
+ // so that it is useful for visually accurate gesture processing in the
+ // local space. For that, we can't simply transform 2D screen point to
+ // the 3D local space since the screen space lacks the depth component |z|,
+ // and so there are many 3D points that correspond to the screen point.
+ // We must first unproject the screen point onto the render plane to find
+ // the true 3D point that corresponds to the screen point.
+ // We do orthogonal unprojection after undoing perspective, in local space.
+ // The render plane is specified by renderBox offset (o) and Z axis (n).
+ // Unprojection is done by finding the intersection of the view vector (d)
+ // with the local X-Y plane: (o-s).dot(n) == (p-s).dot(n), (p-s) == |z|*d.
final Matrix4 transform = getTransformTo(ancestor);
final double det = transform.invert();
if (det == 0.0)
return Offset.zero;
- return MatrixUtils.transformPoint(transform, point);
+ final Vector3 n = Vector3(0.0, 0.0, 1.0);
+ final Vector3 i = transform.perspectiveTransform(Vector3(0.0, 0.0, 0.0));
+ final Vector3 d = transform.perspectiveTransform(Vector3(0.0, 0.0, 1.0)) - i;
+ final Vector3 s = transform.perspectiveTransform(Vector3(point.dx, point.dy, 0.0));
+ final Vector3 p = s - d * (n.dot(s) / n.dot(d));
+ return Offset(p.x, p.y);
}
/// Convert the given point from the local coordinate system for this box to
diff --git a/packages/flutter/test/rendering/transform_test.dart b/packages/flutter/test/rendering/transform_test.dart
index 4ee5f53..f580886 100644
--- a/packages/flutter/test/rendering/transform_test.dart
+++ b/packages/flutter/test/rendering/transform_test.dart
@@ -143,10 +143,11 @@
expect(round(inner.globalToLocal(const Offset(25.0, 50.0))), equals(const Offset(25.0, 50.0)));
expect(inner.globalToLocal(const Offset(25.0, 17.0)).dy, greaterThan(0.0));
expect(inner.globalToLocal(const Offset(25.0, 17.0)).dy, lessThan(10.0));
- expect(inner.globalToLocal(const Offset(25.0, 73.0)).dy, greaterThan(90.0));
- expect(inner.globalToLocal(const Offset(25.0, 73.0)).dy, lessThan(100.0));
- expect(inner.globalToLocal(const Offset(25.0, 17.0)).dy, equals(-inner.globalToLocal(const Offset(25.0, 73.0)).dy));
- }, skip: true); // https://github.com/flutter/flutter/issues/6080
+ expect(inner.globalToLocal(const Offset(25.0, 83.0)).dy, greaterThan(90.0));
+ expect(inner.globalToLocal(const Offset(25.0, 83.0)).dy, lessThan(100.0));
+ expect(round(inner.globalToLocal(const Offset(25.0, 17.0))).dy,
+ equals(100 - round(inner.globalToLocal(const Offset(25.0, 83.0))).dy));
+ });
test('RenderTransform - perspective - localToGlobal', () {
RenderBox inner;