0025760: Visualization - precision factor added to ZNear, ZFar in method ZFitAll...
authorapl <apl@opencascade.com>
Thu, 18 Jun 2015 10:44:54 +0000 (13:44 +0300)
committerbugmaster <bugmaster@opencascade.com>
Thu, 18 Jun 2015 10:46:00 +0000 (13:46 +0300)
Small correction of test cases for issue CR25760

src/Graphic3d/Graphic3d_Camera.cxx
tests/bugs/vis/bug25760_1 [new file with mode: 0644]
tests/bugs/vis/bug25760_2 [new file with mode: 0644]

index c994718..19ee995 100644 (file)
@@ -41,6 +41,20 @@ namespace
 
   // minimum camera distance
   static const Standard_Real MIN_DISTANCE = Pow (0.1, ShortRealDigits() - 2);
+
+  // z-range tolerance compatible with for floating point.
+  static Standard_Real zEpsilon()
+  {
+    return FLT_EPSILON;
+  }
+
+  // relative z-range tolerance compatible with for floating point.
+  static Standard_Real zEpsilon (const Standard_Real theValue)
+  {
+    Standard_Real aLogRadix = Log10 (Abs (theValue)) / Log10 (FLT_RADIX);
+    Standard_Real aExp = Floor (aLogRadix);
+    return FLT_EPSILON * Pow (FLT_RADIX, aExp);
+  };
 };
 
 // =======================================================================
@@ -989,45 +1003,20 @@ void Graphic3d_Camera::ZFitAll (const Standard_Real theScaleFactor, const Bnd_Bo
 {
   Standard_ASSERT_RAISE (theScaleFactor > 0.0, "Zero or negative scale factor is not allowed.");
 
-  // Method changes ZNear and ZFar planes of camera so as to fit the graphical structures
-  // by their real boundaries (computed ignoring infinite flag) into the viewing volume.
-  // In addition to the graphical boundaries, the usual min max used for fitting perspective
-  // camera. To avoid numeric errors for perspective camera the negative ZNear values are
-  // fixed using tolerance distance, relative to boundaries size. The tolerance distance
-  // should be computed using information on boundaries of primary application actors,
-  // (e.g. representing the displayed model) - to ensure that they are not unreasonably clipped.
-  const Standard_ShortReal anEpsilon = 1e-4f;
-
+  // Method changes zNear and zFar parameters of camera so as to fit graphical structures
+  // by their graphical boundaries. It precisely fits min max boundaries of primary application
+  // objects (second argument), while it can sacrifice the real graphical boundaries of the
+  // scene with infinite or helper objects (third argument) for the sake of perspective projection.
   if (theGraphicBB.IsVoid())
   {
-    // Precision factor used to add meaningful tolerance to
-    // ZNear, ZFar values in order to avoid equality after type conversion
-    // to ShortReal matrices type.
-
-    Standard_Real aZFar  = Distance() * 3.0;
-    Standard_Real aZNear = 0.0;
-
-    if (!IsOrthographic())
-    {
-      if (aZFar < anEpsilon)
-      {
-        aZNear = anEpsilon;
-        aZFar  = anEpsilon * 2.0;
-      }
-      else if (aZNear < aZFar * anEpsilon)
-      {
-        aZNear = aZFar * anEpsilon;
-      }
-    }
-
-    SetZRange (aZNear, aZFar);
+    SetZRange (DEFAULT_ZNEAR, DEFAULT_ZFAR);
     return;
   }
 
-  // Measure depth of boundary points from camera eye
+  // Measure depth of boundary points from camera eye.
   NCollection_Sequence<gp_Pnt> aPntsToMeasure;
 
-  Standard_Real aGraphicBB[6]; // real graphical boundaries (not accounting infinite flag).
+  Standard_Real aGraphicBB[6];
   theGraphicBB.Get (aGraphicBB[0], aGraphicBB[1], aGraphicBB[2], aGraphicBB[3], aGraphicBB[4], aGraphicBB[5]);
 
   aPntsToMeasure.Append (gp_Pnt (aGraphicBB[0], aGraphicBB[1], aGraphicBB[2]));
@@ -1041,7 +1030,7 @@ void Graphic3d_Camera::ZFitAll (const Standard_Real theScaleFactor, const Bnd_Bo
 
   if (!theMinMax.IsVoid() && !theMinMax.IsWhole())
   {
-    Standard_Real aMinMax[6]; // applicative min max boundaries
+    Standard_Real aMinMax[6];
     theMinMax.Get (aMinMax[0], aMinMax[1], aMinMax[2], aMinMax[3], aMinMax[4], aMinMax[5]);
 
     aPntsToMeasure.Append (gp_Pnt (aMinMax[0], aMinMax[1], aMinMax[2]));
@@ -1054,7 +1043,7 @@ void Graphic3d_Camera::ZFitAll (const Standard_Real theScaleFactor, const Bnd_Bo
     aPntsToMeasure.Append (gp_Pnt (aMinMax[3], aMinMax[4], aMinMax[5]));
   }
 
-  // Camera eye plane
+  // Camera eye plane.
   gp_Dir aCamDir = Direction();
   gp_Pnt aCamEye = myEye;
   gp_Pln aCamPln (aCamEye, aCamDir);
@@ -1066,7 +1055,7 @@ void Graphic3d_Camera::ZFitAll (const Standard_Real theScaleFactor, const Bnd_Bo
 
   const gp_XYZ& anAxialScale = myAxialScale;
 
-  // Get minimum and maximum distances to the eye plane
+  // Get minimum and maximum distances to the eye plane.
   Standard_Integer aCounter = 0;
   NCollection_Sequence<gp_Pnt>::Iterator aPntIt(aPntsToMeasure);
   for (; aPntIt.More(); aPntIt.Next())
@@ -1079,14 +1068,13 @@ void Graphic3d_Camera::ZFitAll (const Standard_Real theScaleFactor, const Bnd_Bo
 
     Standard_Real aDistance = aCamPln.Distance (aMeasurePnt);
 
-    // Check if the camera is intruded into the scene
+    // Check if the camera is intruded into the scene.
     if (aCamDir.IsOpposite (gp_Vec (aCamEye, aMeasurePnt), M_PI * 0.5))
     {
       aDistance *= -1;
     }
 
-    // the first eight points are from theGraphicBB, the last eight points are from theMinMax
-    // (they can be absent).
+    // The first eight points are from theGraphicBB, the last eight points are from theMinMax (can be absent).
     Standard_Real& aChangeMinDist = aCounter >= 8 ? aModelMinDist : aGraphicMinDist;
     Standard_Real& aChangeMaxDist = aCounter >= 8 ? aModelMaxDist : aGraphicMaxDist;
     aChangeMinDist = Min (aDistance, aChangeMinDist);
@@ -1094,50 +1082,85 @@ void Graphic3d_Camera::ZFitAll (const Standard_Real theScaleFactor, const Bnd_Bo
     aCounter++;
   }
 
-  // Compute depth of bounding box center
+  // Compute depth of bounding box center.
   Standard_Real aMidDepth  = (aGraphicMinDist + aGraphicMaxDist) * 0.5;
   Standard_Real aHalfDepth = (aGraphicMaxDist - aGraphicMinDist) * 0.5;
 
-  // Compute enlarged or shrank near and far z ranges
+  // Compute enlarged or shrank near and far z ranges.
   Standard_Real aZNear  = aMidDepth - aHalfDepth * theScaleFactor;
   Standard_Real aZFar   = aMidDepth + aHalfDepth * theScaleFactor;
-  Standard_Real aZRange = Abs (aZFar - aZNear);
-  Standard_Real aZConf  = Max (static_cast <Standard_Real> (anEpsilon * aZRange),
-                               static_cast <Standard_Real> (anEpsilon));
-
-  aZNear -= Abs (aZNear) * anEpsilon + aZConf;
-  aZFar  += Abs  (aZFar) * anEpsilon + aZConf;
 
   if (!IsOrthographic())
   {
-    if (aZFar > anEpsilon)
+    // Everything is behind the perspective camera.
+    if (aZFar < zEpsilon())
     {
-      // Choose between model distance and graphical distance, as the model boundaries
-      // might be infinite if all structures have infinite flag.
-      const Standard_Real aGraphicDepth = aGraphicMaxDist >= aGraphicMinDist
-        ? aGraphicMaxDist - aGraphicMinDist : RealLast();
-
-      const Standard_Real aModelDepth = aModelMaxDist >= aModelMinDist
-        ? aModelMaxDist - aModelMinDist : RealLast();
-
-      const Standard_Real aMinDepth = Min (aModelDepth, aGraphicDepth);
-      const Standard_Real aZTol     = Max (static_cast<Standard_Real> (anEpsilon * Abs (aMinDepth)),
-                                           static_cast<Standard_Real> (anEpsilon));
-      if (aZNear < aZTol)
-      {
-        aZNear = aZTol;
-      }
+      SetZRange (DEFAULT_ZNEAR, DEFAULT_ZFAR);
+      return;
     }
-    else
+
+    // For better perspective the zNear value should not be less than zEpsilon (zFar).
+    // If zNear computed by graphical boundaries do not meet the rule (e.g. it is negative
+    // when computing it for grid) it could be increased up to minimum depth computed by
+    // application min max values. This means that z-fit can sacrifice presentation of
+    // non primary application graphical objects in favor of better perspective projection;
+    if (aZNear < zEpsilon (aZFar))
     {
-      aZNear = anEpsilon;
-      aZFar  = anEpsilon * 2.0;
+      // Otherwise it should be increased up to zEpsilon (1.0) to avoid clipping of primary
+      // graphical objects.
+      if (aModelMinDist < zEpsilon (aZFar))
+      {
+        aMidDepth  = (aModelMinDist + aModelMaxDist) * 0.5;
+        aHalfDepth = (aModelMinDist - aModelMaxDist) * 0.5;
+        aZNear     = Max (zEpsilon(), aMidDepth - aHalfDepth * theScaleFactor);
+      }
+      else
+      {
+        aZNear = zEpsilon (aZFar);
+      }
     }
   }
 
-  if (aZFar < (aZNear + Abs (aZFar) * anEpsilon))
+  //
+  // Consider clipping errors due to double to single precision floating-point conversion.
+  //
+
+  // Model to view transformation performs translation of points against eye position
+  // in three dimensions. Both point coordinate and eye position values are converted from
+  // double to single precision floating point numbers producing conversion errors. 
+  // Epsilon (Mod) * 3.0 should safely compensate precision error for z coordinate after
+  // translation assuming that the:
+  // Epsilon (Eye.Mod()) * 3.0 > Epsilon (Eye.X()) + Epsilon (Eye.Y()) + Epsilon (Eye.Z()).
+  Standard_Real aEyeConf = 3.0 * zEpsilon (myEye.XYZ().Modulus());
+
+  // Model to view transformation performs rotation of points according to view direction.
+  // New z coordinate is computed as a multiplication of point's x, y, z coordinates by the
+  // "forward" direction vector's x, y, z coordinates. Both point's and "z" direction vector's
+  // values are converted from double to single precision floating point numbers producing
+  // conversion errors.
+  // Epsilon (Mod) * 6.0 should safely compensate the precision errors for the multiplication
+  // of point coordinates by direction vector.
+  gp_Pnt aGraphicMin = theGraphicBB.CornerMin();
+  gp_Pnt aGraphicMax = theGraphicBB.CornerMax();
+
+  Standard_Real aModelConf = 6.0 * zEpsilon (aGraphicMin.XYZ().Modulus()) +
+                             6.0 * zEpsilon (aGraphicMax.XYZ().Modulus());
+
+  // Compensate floating point conversion errors by increasing zNear, zFar to avoid clipping.
+  aZNear -= zEpsilon (aZNear) + aEyeConf + aModelConf;
+  aZFar  += zEpsilon (aZFar)  + aEyeConf + aModelConf;
+
+  if (!IsOrthographic())
   {
-    aZFar = aZNear + Abs (aZFar) * anEpsilon;
+    // Compensate zNear, zFar conversion errors for perspective projection.
+    aZNear -= aZFar * zEpsilon (aZNear) / (aZFar - zEpsilon (aZNear));
+    aZFar  += zEpsilon (aZFar);
+
+    // Ensure that after all the zNear is not a negative value.
+    if (aZNear < zEpsilon())
+    {
+      aZNear = zEpsilon();
+    }
   }
 
   SetZRange (aZNear, aZFar);
diff --git a/tests/bugs/vis/bug25760_1 b/tests/bugs/vis/bug25760_1
new file mode 100644 (file)
index 0000000..26d1ea5
--- /dev/null
@@ -0,0 +1,79 @@
+puts "============"
+puts "CR25760"
+puts "============"
+puts ""
+#######################################################################
+# Visualization - precision factor added to ZNear, ZFar in method ZFitAll() of Graphic3d_Camera is not enough
+#######################################################################
+
+vinit View1 w=409 h=409
+vclear
+
+proc test2d {} {
+  set pix1 {135 204}
+  set pix2 {204 187}
+
+  for {set i 8} {$i <= 8} {incr i} {
+    set min_z [expr pow (-10, $i)]
+    set max_z [expr $min_z + 1000]
+    plane p1 0 0 $min_z 0 0 1
+    plane p2 0 0 $max_z 0 0 1
+
+    mkface f1 p1 -1 0 -1 0
+    mkface f2 p2  0 1  0 1
+
+    vclear
+    vdisplay f1 f2
+    vtop
+    vfit
+
+    for {set z [expr $max_z + 1.0]} {$z <= 1e10} {set z [expr abs ($z) * 1.2]} {
+      vviewparams -eye 0 0 $z
+      vmoveto {*}$pix1
+      if { [checkcolor {*}$pix1 0 1 1] != 1 } {
+        puts "Error: 2D projection test failed with the following parameters:"
+        vviewparams
+        vzrange
+        puts ""
+        puts "z    : $z"
+        puts "min_z: $min_z"
+        puts "max_z: $max_z"
+        return 0
+      }
+      vmoveto {*}$pix2
+      if { [checkcolor {*}$pix2 0 1 1] != 1 } {
+        puts "Error: 2D projection test failed with the following parameters:"
+        vviewparams
+        vzrange
+        puts ""
+        puts "z    : $z"
+        puts "min_z: $min_z"
+        puts "max_z: $max_z"
+        return 0
+      }
+    }
+  }
+  return 1
+}
+
+set tcl_precision 16
+
+####################################################################
+# Test orthographic camera without frustum culling.                #
+####################################################################
+vcamera -ortho
+vfrustumculling 0
+
+if { [test2d] != 1 } {
+  puts "Error: 2D projection test failed: view frustum culling is OFF"
+}
+
+####################################################################
+# Test orthographic camera with frustum culling.                   #
+####################################################################
+vcamera -ortho
+vfrustumculling 1
+
+if { [test2d] != 1 } {
+  puts "Error: 2D projection test failed: view frustum culling is ON"
+}
diff --git a/tests/bugs/vis/bug25760_2 b/tests/bugs/vis/bug25760_2
new file mode 100644 (file)
index 0000000..dfb98e3
--- /dev/null
@@ -0,0 +1,129 @@
+puts "============"
+puts "CR25760"
+puts "============"
+puts ""
+#######################################################################
+# Visualization - precision factor added to ZNear, ZFar in method ZFitAll() of Graphic3d_Camera is not enough
+#######################################################################
+
+vinit View1 w=409 h=409
+vclear
+
+vclear
+vautozfit 0
+
+proc test3d {dstart} {
+
+  set proj1 { 0.47243081629544409 -0.39335870920278265 -0.78871924644244684}
+  set proj2 {-0.31828216872577886  0.17649241059446089 -0.93142197208020105}
+
+  for {set i 1} {$i <= 3} {incr i} {
+    for {set r 1} {$r <= 3} {incr r} {
+
+      set x    [expr pow(100, $i)]
+      set y    [expr pow( 70, $i)]
+      set z    [expr pow( 50, $i)]
+      set dist [expr pow(100, $r)]
+
+      vclear
+      vertex v0  $x $y $z
+      vertex v1 [expr "$x + ($dist * [lindex $proj1 0])"] [expr "$y + ($dist * [lindex $proj1 1])"] [expr "$z + ($dist * [lindex $proj1 2])"]
+      vertex v2 [expr "$x + ($dist * [lindex $proj2 0])"] [expr "$y + ($dist * [lindex $proj2 1])"] [expr "$z + ($dist * [lindex $proj2 2])"]
+
+      for {set d [expr $dstart * {max ($x,$y,$z,$dist)}]} {$d <= 1e7} {set d [expr "abs ($d) * 1.2E5"]} {
+        for {set p 1} {$p <= 2} {incr p} {
+          set proj [set proj$p]
+
+          vremove -all
+          vdisplay v0
+          vdisplay v$p
+          vviewparams -eye [expr "$x - ($d * [lindex $proj 0])"] [expr "$y - ($d * [lindex $proj 1])"] [expr "$z - ($d * [lindex $proj 2])"] -at $x $y $z
+          vzfit
+
+          vremove -all
+          vdisplay v0
+          if { [checkcolor 204 204 1 1 0] != 1 } {
+            puts "Error: 3D projection test failed with the following parameters:"
+            vviewparams
+            vzrange
+            puts ""
+            puts "v1 x: $x"
+            puts "v1 y: $y"
+            puts "v1 z: $z"
+            puts "v2 x: [expr $x + ($dist * [lindex $proj 0])]"
+            puts "v2 y: [expr $y + ($dist * [lindex $proj 1])]"
+            puts "v2 z: [expr $z + ($dist * [lindex $proj 2])]"
+            puts ""
+            return 0
+          }
+
+          vremove -all
+          vdisplay v$p
+          if { [checkcolor 204 204 1 1 0] != 1 } {
+            puts "Error: 3D projection test failed with the following parameters:"
+            vviewparams
+            vzrange
+            puts ""
+            puts "v1 x: $x"
+            puts "v1 y: $y"
+            puts "v1 z: $z"
+            puts "v2 x: [expr $x + ($dist * [lindex $proj 0])]"
+            puts "v2 y: [expr $y + ($dist * [lindex $proj 1])]"
+            puts "v2 z: [expr $z + ($dist * [lindex $proj 2])]"
+            puts ""
+            return 0
+          }
+        }
+      }
+    }
+  }
+  return 1
+}
+
+set tcl_precision 16
+
+####################################################################
+# Test orthographic camera without frustum culling.                #
+# Test camera with scale 1E-8 to avoid jittering.                  # 
+####################################################################
+vcamera -ortho
+vviewparams -scale 1e-8
+vfrustumculling 0
+
+if { [test3d 1e-7] != 1 } {
+  puts "Error: 3D projection test failed: camera is orthographic, view frustum culling is OFF"
+}
+
+####################################################################
+# Test orthographic camera with frustum culling.                   #
+# Test camera with scale 1E-8 to avoid jittering.                  # 
+####################################################################
+vcamera -ortho
+vviewparams -scale 1e-8
+vfrustumculling 1
+
+if { [test3d 1e-7] != 1 } {
+  puts "Error: 3D projection test failed: camera is orthographic, view frustum culling is ON"
+}
+
+####################################################################
+# Test perspective camera without frustum culling.                 #
+# Test camera with less starting distance 1.0 to avoid jittering. #
+####################################################################
+vcamera -persp
+vfrustumculling 0
+
+if { [test3d 1.0] != 1 } {
+  puts "Error: 3D projection test failed: camera is perspective, view frustum culling is OFF"
+}
+
+####################################################################
+# Test perspective camera with frustum culling.                    #
+# Test camera with less starting distance 1.0 to avoid jittering. #
+####################################################################
+vcamera -persp
+vfrustumculling 1
+
+if { [test3d 1.0] != 1 } {
+  puts "Error: 3D projection test failed: camera is perspective, view frustum culling is ON"
+}