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;