Title: [116543] trunk
Revision
116543
Author
[email protected]
Date
2012-05-09 10:57:50 -0700 (Wed, 09 May 2012)

Log Message

Hit testing is incorrect in some cases with perspective transforms
https://bugs.webkit.org/show_bug.cgi?id=79136

Reviewed by Simon Fraser.

Source/WebCore:

Tests: transforms/3d/hit-testing/coplanar-with-camera.html
       transforms/3d/hit-testing/perspective-clipped.html

* platform/graphics/transforms/TransformationMatrix.cpp:
(WebCore::TransformationMatrix::projectPoint): Fix a
divide-by-zero error so that values do not become Inf or Nan. Also
fix an overflow error by using a large, but not-too-large constant
to represent infinity.

(WebCore::TransformationMatrix::projectQuad): Fix an error where
incorrect quads were being returned. Incorrect quads can occur
when projectPoint clamped==true after returning.

LayoutTests:

* transforms/3d/hit-testing/coplanar-with-camera-expected.txt: Added.
* transforms/3d/hit-testing/coplanar-with-camera.html: Added.
* transforms/3d/hit-testing/perspective-clipped-expected.txt: Added.
* transforms/3d/hit-testing/perspective-clipped.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (116542 => 116543)


--- trunk/LayoutTests/ChangeLog	2012-05-09 17:47:03 UTC (rev 116542)
+++ trunk/LayoutTests/ChangeLog	2012-05-09 17:57:50 UTC (rev 116543)
@@ -1,3 +1,15 @@
+2012-05-03  Shawn Singh  <[email protected]>
+
+        Hit testing is incorrect in some cases with perspective transforms
+        https://bugs.webkit.org/show_bug.cgi?id=79136
+
+        Reviewed by Simon Fraser.
+
+        * transforms/3d/hit-testing/coplanar-with-camera-expected.txt: Added.
+        * transforms/3d/hit-testing/coplanar-with-camera.html: Added.
+        * transforms/3d/hit-testing/perspective-clipped-expected.txt: Added.
+        * transforms/3d/hit-testing/perspective-clipped.html: Added.
+
 2012-05-09  Dominik Röttsches  <[email protected]>
 
         Textarea-placeholder-wrapping.html may fail because of scrollbars

Added: trunk/LayoutTests/transforms/3d/hit-testing/coplanar-with-camera-expected.txt (0 => 116543)


--- trunk/LayoutTests/transforms/3d/hit-testing/coplanar-with-camera-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/transforms/3d/hit-testing/coplanar-with-camera-expected.txt	2012-05-09 17:57:50 UTC (rev 116543)
@@ -0,0 +1,9 @@
+The text on this element should be selectable. Hovering on this element should cause a highlight. Element at 98, 200 has id "background": PASS
+Element at 302, 200 has id "background": PASS
+Element at 200, 98 has id "background": PASS
+Element at 200, 302 has id "background": PASS
+Element at 101, 200 has id "layer": PASS
+Element at 299, 200 has id "layer": PASS
+Element at 200, 101 has id "layer": PASS
+Element at 200, 299 has id "layer": PASS
+

Added: trunk/LayoutTests/transforms/3d/hit-testing/coplanar-with-camera.html (0 => 116543)


--- trunk/LayoutTests/transforms/3d/hit-testing/coplanar-with-camera.html	                        (rev 0)
+++ trunk/LayoutTests/transforms/3d/hit-testing/coplanar-with-camera.html	2012-05-09 17:57:50 UTC (rev 116543)
@@ -0,0 +1,84 @@
+<html>
+  <!-- This test reproduces a divide-by-zero error that is hopefully fixed by
+       https://bugs.webkit.org/show_bug.cgi?id=79136. In that bug, a layer that gets
+       translated by z so that it is coplanar with the camera origin. As a result, when
+       trying to project a point from the container space to the local space, the
+       implementation had a divide-by-zero which made hit-testing results incorrect. -->
+
+<head>
+  <style type="text/css">
+    /* Marquee content. */
+    #camera {
+        position: absolute;
+        top: 100px;
+        left: 100px;
+        -webkit-perspective: 800px;
+    }
+
+    #container {
+        -webkit-transform-style: preserve-3d;
+        -webkit-transform: translateZ(800px)
+    }
+
+    #layer {
+        position: absolute;
+        width: 200px;
+        height: 200px;
+        background-color: green;
+
+        /* This should theoretically cancel out the container's transform, and hit-testing should work. */
+        -webkit-transform: translateZ(-800px);
+    }
+
+    #background {
+        position: absolute;
+        width: 400px;
+        height: 400px;
+        background-color: gray;
+   }
+
+    #layer:hover {
+        background-color: orange;
+    }
+
+    #results {
+        position: absolute;
+        top: 420px;
+        left: 20px;
+    }
+  </style>
+
+  <script src=""
+  <script>
+    const hitTestData = [
+        { 'point': [98, 200], 'target' : 'background' },
+        { 'point': [302, 200], 'target' : 'background' },
+        { 'point': [200, 98], 'target' : 'background' },
+        { 'point': [200, 302], 'target' : 'background' },
+        { 'point': [101, 200], 'target' : 'layer' },
+        { 'point': [299, 200], 'target' : 'layer' },
+        { 'point': [200, 101], 'target' : 'layer' },
+        { 'point': [200, 299], 'target' : 'layer' },
+    ];
+
+    window.addEventListener('load', runTest, false);
+  </script>
+</head>
+
+<body>
+
+  <div id="background"></div>
+
+  <div id="camera">
+    <div id="container">
+      <div id="layer">
+        The text on this element should be selectable.
+        Hovering on this element should cause a highlight.
+      </div>
+    </div>
+  </div>
+
+  <div id="results"></div>
+
+</body>
+</html>

Added: trunk/LayoutTests/transforms/3d/hit-testing/perspective-clipped-expected.txt (0 => 116543)


--- trunk/LayoutTests/transforms/3d/hit-testing/perspective-clipped-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/transforms/3d/hit-testing/perspective-clipped-expected.txt	2012-05-09 17:57:50 UTC (rev 116543)
@@ -0,0 +1,13 @@
+Element at 35, 100 has id "topLayer": PASS
+Element at 370, 100 has id "topLayer": PASS
+Element at 40, 40 has id "topLayer": PASS
+Element at 365, 40 has id "topLayer": PASS
+Element at 35, 40 has id "backgroundLayer": PASS
+Element at 370, 40 has id "backgroundLayer": PASS
+Element at 40, 340 has id "bottomLayer": PASS
+Element at 365, 340 has id "bottomLayer": PASS
+Element at 35, 260 has id "bottomLayer": PASS
+Element at 370, 280 has id "bottomLayer": PASS
+Element at 35, 340 has id "backgroundLayer": PASS
+Element at 370, 340 has id "backgroundLayer": PASS
+

Added: trunk/LayoutTests/transforms/3d/hit-testing/perspective-clipped.html (0 => 116543)


--- trunk/LayoutTests/transforms/3d/hit-testing/perspective-clipped.html	                        (rev 0)
+++ trunk/LayoutTests/transforms/3d/hit-testing/perspective-clipped.html	2012-05-09 17:57:50 UTC (rev 116543)
@@ -0,0 +1,103 @@
+<html>
+  <!-- This test reproduces a perspective w < 0 error addressed in
+       https://bugs.webkit.org/show_bug.cgi?id=79136. In that bug, as a layer is being
+       transformed, it may get "clamped" by the homogeneous coordinate w < 0.  When
+       projecting individual points, this was handled correctly, but projecting quads was
+       ignoring this clamping, causing invalid quads to be generated, which ultimately did
+       not hit-test correctly.
+    -->
+<head>
+  <style type="text/css">
+    #container {
+        -webkit-perspective: 1000px;
+        -webkit-perspective-origin-x: 145px;
+        /* removing this property fixes the issue, but doesn't provide the desired rendering */
+        -webkit-perspective-origin-y: 189px;
+    }
+
+    #intermediate {
+        position: absolute;
+        left: 40px;
+        top: 189px;
+        -webkit-transform-style: preserve-3d;
+    }
+
+    #results {
+        position: absolute;
+        top: 420px;
+        left: 20px;
+    }
+
+    #backgroundLayer {
+        position: absolute;
+        width: 400px;
+        height: 400px;
+        background-color: gray;
+    }
+
+    .highlightOnHover:hover {
+        background-color: orange;
+    }
+
+    .rotatedUp {
+        -webkit-transform: rotateX(-240deg) translateZ(200px)
+    }
+
+    .rotatedDown {
+        -webkit-transform: rotateX(-300deg) translateZ(200px)
+    }
+
+    .green {
+        background-color: green;
+    }
+
+    .box {
+        position: absolute;
+        width: 300px;
+        height: 110px;
+    }
+
+  </style>
+  <script src=""
+  <script>
+      const hitTestData = [
+        // Points near the corners of the top layer
+        { 'point': [35, 100], 'target' : 'topLayer' },
+        { 'point': [370, 100], 'target' : 'topLayer' },
+        { 'point': [40, 40], 'target' : 'topLayer' },
+        { 'point': [365, 40], 'target' : 'topLayer' },
+
+        // Points within the axis-aligned bounding box of the top layer, but not actually on the layer itself
+        { 'point': [35, 40], 'target' : 'backgroundLayer' },
+        { 'point': [370, 40], 'target' : 'backgroundLayer' },
+
+        // Points near the corners of the top layer
+        { 'point': [40, 340], 'target' : 'bottomLayer' },
+        { 'point': [365, 340], 'target' : 'bottomLayer' },
+        { 'point': [35, 260], 'target' : 'bottomLayer' },
+        { 'point': [370, 280], 'target' : 'bottomLayer' },
+
+        // Points within the axis-aligned bounding box of the bottom layer, but not actually on the layer itself
+        { 'point': [35, 340], 'target' : 'backgroundLayer' },
+        { 'point': [370, 340], 'target' : 'backgroundLayer' },
+      ];
+
+      window.addEventListener('load', runTest, false);
+  </script>
+</head>
+
+<body>
+
+  <div id="backgroundLayer"></div>
+
+  <div id="container">
+    <div id="intermediate" class="host" style="-webkit-transform: rotate3d(1, 0, 0, 270deg)">
+      <div id="topLayer" class="highlightOnHover rotatedUp green box" style=""></div>
+      <div id="bottomLayer" class="highlightOnHover rotatedDown green box" style=""></div>
+    </div>
+  </div>
+
+  <div id="results"></div>
+
+</body>
+</html>

Modified: trunk/Source/WebCore/ChangeLog (116542 => 116543)


--- trunk/Source/WebCore/ChangeLog	2012-05-09 17:47:03 UTC (rev 116542)
+++ trunk/Source/WebCore/ChangeLog	2012-05-09 17:57:50 UTC (rev 116543)
@@ -1,3 +1,23 @@
+2012-05-03  Shawn Singh  <[email protected]>
+
+        Hit testing is incorrect in some cases with perspective transforms
+        https://bugs.webkit.org/show_bug.cgi?id=79136
+
+        Reviewed by Simon Fraser.
+
+        Tests: transforms/3d/hit-testing/coplanar-with-camera.html
+               transforms/3d/hit-testing/perspective-clipped.html
+
+        * platform/graphics/transforms/TransformationMatrix.cpp:
+        (WebCore::TransformationMatrix::projectPoint): Fix a
+        divide-by-zero error so that values do not become Inf or Nan. Also
+        fix an overflow error by using a large, but not-too-large constant
+        to represent infinity.
+
+        (WebCore::TransformationMatrix::projectQuad): Fix an error where
+        incorrect quads were being returned. Incorrect quads can occur
+        when projectPoint clamped==true after returning.
+
 2012-05-09  Caio Marcelo de Oliveira Filho  <[email protected]>
 
         Simplify CSSParser::parseSimpleLengthValue()

Modified: trunk/Source/WebCore/platform/graphics/transforms/TransformationMatrix.cpp (116542 => 116543)


--- trunk/Source/WebCore/platform/graphics/transforms/TransformationMatrix.cpp	2012-05-09 17:47:03 UTC (rev 116542)
+++ trunk/Source/WebCore/platform/graphics/transforms/TransformationMatrix.cpp	2012-05-09 17:57:50 UTC (rev 116543)
@@ -551,6 +551,12 @@
     // d = -dot (Pn', R0) / dot (Pn', Rd)
     if (clamped)
         *clamped = false;
+
+    if (m33() == 0) {
+        // In this case, the projection plane is parallel to the ray we are trying to
+        // trace, and there is no well-defined value for the projection.
+        return FloatPoint();
+    }
     
     double x = p.x();
     double y = p.y();
@@ -562,8 +568,12 @@
 
     double w = x * m14() + y * m24() + z * m34() + m44();
     if (w <= 0) {
-        outX = copysign(numeric_limits<int>::max(), outX);
-        outY = copysign(numeric_limits<int>::max(), outY);
+        // Using int max causes overflow when other code uses the projected point. To
+        // represent infinity yet reduce the risk of overflow, we use a large but
+        // not-too-large number here when clamping.
+        const int kLargeNumber = 100000000;
+        outX = copysign(kLargeNumber, outX);
+        outY = copysign(kLargeNumber, outY);
         if (clamped)
             *clamped = true;
     } else if (w != 1) {
@@ -577,11 +587,22 @@
 FloatQuad TransformationMatrix::projectQuad(const FloatQuad& q) const
 {
     FloatQuad projectedQuad;
-    projectedQuad.setP1(projectPoint(q.p1()));
-    projectedQuad.setP2(projectPoint(q.p2()));
-    projectedQuad.setP3(projectPoint(q.p3()));
-    projectedQuad.setP4(projectPoint(q.p4()));
-    
+
+    bool clamped1 = false;
+    bool clamped2 = false;
+    bool clamped3 = false;
+    bool clamped4 = false;
+
+    projectedQuad.setP1(projectPoint(q.p1(), &clamped1));
+    projectedQuad.setP2(projectPoint(q.p2(), &clamped2));
+    projectedQuad.setP3(projectPoint(q.p3(), &clamped3));
+    projectedQuad.setP4(projectPoint(q.p4(), &clamped4));
+
+    // If all points on the quad had w < 0, then the entire quad would not be visible to the projected surface.
+    bool everythingWasClipped = clamped1 && clamped2 && clamped3 && clamped4;
+    if (everythingWasClipped)
+        return FloatQuad();
+
     return projectedQuad;
 }
 
_______________________________________________
webkit-changes mailing list
[email protected]
http://lists.webkit.org/mailman/listinfo.cgi/webkit-changes

Reply via email to