0029295: Visualization, TKOpenGl - provide distance culling option
authorkgv <kgv@opencascade.com>
Sun, 5 Nov 2017 15:07:25 +0000 (18:07 +0300)
committerbugmaster <bugmaster@opencascade.com>
Thu, 9 Nov 2017 15:08:05 +0000 (18:08 +0300)
Graphic3d_ZLayerSettings::CullingDistance() and ::CullingSize() - added
new properties configuring culling of small and distant objects, disabled by default.
OpenGl_BVHTreeSelector now handles size culling and distance culling in addition to frustom culling.

src/Graphic3d/Graphic3d_ZLayerSettings.hxx
src/OpenGl/OpenGl_BVHTreeSelector.cxx
src/OpenGl/OpenGl_BVHTreeSelector.hxx
src/OpenGl/OpenGl_Layer.cxx
src/OpenGl/OpenGl_Layer.hxx
src/OpenGl/OpenGl_View_Redraw.cxx
src/ViewerTest/ViewerTest_ViewerCommands.cxx
tests/v3d/glsl/culldist [new file with mode: 0644]
tests/v3d/glsl/cullsize [new file with mode: 0644]

index e7bf523..8ca091d 100644 (file)
@@ -33,7 +33,9 @@ struct Graphic3d_ZLayerSettings
 
   //! Default settings.
   Graphic3d_ZLayerSettings()
-  : myIsImmediate       (Standard_False),
+  : myCullingDistance (Precision::Infinite()),
+    myCullingSize     (Precision::Infinite()),
+    myIsImmediate       (Standard_False),
     myUseEnvironmentTexture (Standard_True),
     myToEnableDepthTest (Standard_True),
     myToEnableDepthWrite(Standard_True),
@@ -62,6 +64,30 @@ struct Graphic3d_ZLayerSettings
     }
   }
 
+  //! Return TRUE, if culling of distant objects (distance culling) should be performed; FALSE by default.
+  //! @sa CullingDistance()
+  Standard_Boolean HasCullingDistance() const { return !Precision::IsInfinite (myCullingDistance) && myCullingDistance > 0.0; }
+
+  //! Return the distance to discard drawing of distant objects (distance from camera Eye point); by default it is Infinite (distance culling is disabled).
+  //! Since camera eye definition has no strong meaning within orthographic projection, option is considered only within perspective projection.
+  //! Note also that this option has effect only when frustum culling is enabled.
+  Standard_Real CullingDistance() const { return myCullingDistance; }
+
+  //! Set the distance to discard drawing objects.
+  void SetCullingDistance (Standard_Real theDistance) { myCullingDistance = theDistance; }
+
+  //! Return TRUE, if culling of small objects (size culling) should be performed; FALSE by default.
+  //! @sa CullingSize()
+  Standard_Boolean HasCullingSize() const { return !Precision::IsInfinite (myCullingSize) && myCullingSize > 0.0; }
+
+  //! Return the size to discard drawing of small objects; by default it is Infinite (size culling is disabled).
+  //! Current implementation checks the length of projected diagonal of bounding box in pixels for discarding.
+  //! Note that this option has effect only when frustum culling is enabled.
+  Standard_Real CullingSize() const { return myCullingSize; }
+
+  //! Set the distance to discard drawing objects.
+  void SetCullingSize (Standard_Real theSize) { myCullingSize = theSize; }
+
   //! Return true if this layer should be drawn after all normal (non-immediate) layers.
   Standard_Boolean IsImmediate() const { return myIsImmediate; }
 
@@ -162,6 +188,8 @@ protected:
   TCollection_AsciiString     myName;                  //!< user-provided name
   Handle(Geom_Transformation) myOriginTrsf;            //!< transformation to the origin
   gp_XYZ                      myOrigin;                //!< the origin of all objects within the layer
+  Standard_Real               myCullingDistance;       //!< distance to discard objects
+  Standard_Real               myCullingSize;           //!< size to discard objects
   Graphic3d_PolygonOffset     myPolygonOffset;         //!< glPolygonOffset() arguments
   Standard_Boolean            myIsImmediate;           //!< immediate layer will be drawn after all normal layers
   Standard_Boolean            myUseEnvironmentTexture; //!< flag to allow/prevent environment texture mapping usage for specific layer
index 21cfccb..644b429 100644 (file)
 // purpose  :
 // =======================================================================
 OpenGl_BVHTreeSelector::OpenGl_BVHTreeSelector()
-: myIsProjectionParallel (Standard_True)
+: myIsProjectionParallel (Standard_True),
+  myCamScaleInv (1.0),
+  myDistCull (-1.0),
+  myPixelSize (1.0),
+  mySizeCull2 (-1.0)
 {
   //
 }
@@ -44,6 +48,8 @@ void OpenGl_BVHTreeSelector::SetViewVolume (const Handle(Graphic3d_Camera)& theC
   myProjectionMat      = theCamera->ProjectionMatrix();
   myWorldViewMat       = theCamera->OrientationMatrix();
   myWorldViewProjState = theCamera->WorldViewProjState();
+  myCamEye.SetValues (theCamera->Eye().X(), theCamera->Eye().Y(), theCamera->Eye().Z());
+  myCamScaleInv = 1.0 / myCamera->Scale();
 
   Standard_Real nLeft = 0.0, nRight = 0.0, nTop = 0.0, nBottom = 0.0;
   Standard_Real fLeft = 0.0, fRight = 0.0, fTop = 0.0, fBottom = 0.0;
@@ -124,11 +130,14 @@ void OpenGl_BVHTreeSelector::SetViewVolume (const Handle(Graphic3d_Camera)& theC
 // function : SetViewportSize
 // purpose  :
 // =======================================================================
-void OpenGl_BVHTreeSelector::SetViewportSize (const Standard_Integer theViewportWidth,
-                                              const Standard_Integer theViewportHeight)
+void OpenGl_BVHTreeSelector::SetViewportSize (Standard_Integer theViewportWidth,
+                                              Standard_Integer theViewportHeight,
+                                              Standard_Real theResolutionRatio)
 {
   myViewportHeight = theViewportHeight;
-  myViewportWidth = theViewportWidth;
+  myViewportWidth  = theViewportWidth;
+  myPixelSize = Max (theResolutionRatio / theViewportHeight,
+                     theResolutionRatio / theViewportWidth);
 }
 
 // =======================================================================
@@ -154,9 +163,37 @@ Standard_Real OpenGl_BVHTreeSelector::SignedPlanePointDistance (const OpenGl_Vec
 }
 
 // =======================================================================
+// function : SetCullingDistance
+// purpose  :
+// =======================================================================
+void OpenGl_BVHTreeSelector::SetCullingDistance (Standard_Real theDistance)
+{
+  myDistCull = -1.0;
+  if (!myIsProjectionParallel)
+  {
+    myDistCull = theDistance > 0.0 && !Precision::IsInfinite (theDistance)
+               ? theDistance
+               : -1.0;
+  }
+}
+
+// =======================================================================
+// function : SetCullingSize
+// purpose  :
+// =======================================================================
+void OpenGl_BVHTreeSelector::SetCullingSize (Standard_Real theSize)
+{
+  mySizeCull2 = -1.0;
+  if (theSize > 0.0 && !Precision::IsInfinite (theSize))
+  {
+    mySizeCull2 = (myPixelSize * theSize) / myCamScaleInv;
+    mySizeCull2 *= mySizeCull2;
+  }
+}
+
+// =======================================================================
 // function : CacheClipPtsProjections
-// purpose  : Caches view volume's vertices projections along its normals and AABBs dimensions
-//            Must be called at the beginning of each BVH tree traverse loop
+// purpose  :
 // =======================================================================
 void OpenGl_BVHTreeSelector::CacheClipPtsProjections()
 {
@@ -254,5 +291,26 @@ Standard_Boolean OpenGl_BVHTreeSelector::Intersect (const OpenGl_Vec3d& theMinPt
     }
   }
 
+  // distance culling - discard node if distance to it's bounding box from camera eye is less than specified culling distance
+  if (myDistCull > 0.0)
+  {
+    // check distance to the bounding sphere as fast approximation
+    const Graphic3d_Vec3d aSphereCenter = (theMinPt + theMaxPt) * 0.5;
+    const Standard_Real   aSphereRadius = (theMaxPt - theMinPt).maxComp() * 0.5;
+    if ((aSphereCenter - myCamEye).Modulus() - aSphereRadius > myDistCull)
+    {
+      return Standard_False;
+    }
+  }
+
+  // size culling - discard node if diagonal of it's bounding box is less than specified culling size
+  if (mySizeCull2 > 0.0)
+  {
+    if ((theMaxPt - theMinPt).SquareModulus() < mySizeCull2)
+    {
+      return Standard_False;
+    }
+  }
+
   return Standard_True;
 }
index cddae0b..e0815f5 100644 (file)
@@ -33,7 +33,19 @@ public:
   //! Retrieves view volume's planes equations and its vertices from projection and world-view matrices.
   Standard_EXPORT void SetViewVolume (const Handle(Graphic3d_Camera)& theCamera);
 
-  Standard_EXPORT void SetViewportSize (const Standard_Integer theViewportWidth, const Standard_Integer theViewportHeight);
+  Standard_EXPORT void SetViewportSize (Standard_Integer theViewportWidth,
+                                        Standard_Integer theViewportHeight,
+                                        Standard_Real theResolutionRatio);
+
+  //! Setup distance culling.
+  Standard_EXPORT void SetCullingDistance (Standard_Real theDistance);
+
+  //! Setup size culling.
+  Standard_EXPORT void SetCullingSize (Standard_Real theSize);
+
+  //! Caches view volume's vertices projections along its normals and AABBs dimensions.
+  //! Must be called at the beginning of each BVH tree traverse loop.
+  Standard_EXPORT void CacheClipPtsProjections();
 
   //! Detects if AABB overlaps view volume using separating axis theorem (SAT).
   //! @param theMinPt [in] maximum point of AABB.
@@ -42,10 +54,6 @@ public:
   Standard_EXPORT Standard_Boolean Intersect (const OpenGl_Vec3d& theMinPt,
                                               const OpenGl_Vec3d& theMaxPt) const;
 
-  //! Caches view volume's vertices projections along its normals and AABBs dimensions.
-  //! Must be called at the beginning of each BVH tree traverse loop.
-  Standard_EXPORT void CacheClipPtsProjections();
-
   //! Return the camera definition.
   const Handle(Graphic3d_Camera)& Camera() const { return myCamera; }
 
@@ -139,6 +147,13 @@ protected:
   Standard_Integer myViewportHeight;
 
   Graphic3d_WorldViewProjState myWorldViewProjState; //!< State of world view projection matrices.
+
+  Graphic3d_Vec3d myCamEye;      //!< camera eye position for distance culling
+  Standard_Real   myCamScaleInv; //!< inverted camera scale for size culling
+  Standard_Real   myDistCull;    //!< culling distance
+  Standard_Real   myPixelSize;   //!< pixel size for size culling
+  Standard_Real   mySizeCull2;   //!< squared culling size
+
 };
 
 #endif // _OpenGl_BVHTreeSelector_HeaderFile
index d3d8601..f5bca3f 100644 (file)
@@ -489,9 +489,15 @@ void OpenGl_Layer::updateBVH() const
 void OpenGl_Layer::renderTraverse (const Handle(OpenGl_Workspace)& theWorkspace) const
 {
   updateBVH();
-
-  OpenGl_BVHTreeSelector& aSelector = theWorkspace->View()->BVHTreeSelector();
-  traverse (aSelector);
+  if (myBVHPrimitives        .Size() != 0
+   || myBVHPrimitivesTrsfPers.Size() != 0)
+  {
+    OpenGl_BVHTreeSelector& aSelector = theWorkspace->View()->BVHTreeSelector();
+    aSelector.SetCullingDistance (myLayerSettings.CullingDistance());
+    aSelector.SetCullingSize (myLayerSettings.CullingSize());
+    aSelector.CacheClipPtsProjections();
+    traverse (aSelector);
+  }
 
   const Standard_Integer aViewId = theWorkspace->View()->Identification();
   for (OpenGl_ArrayOfIndexedMapOfStructure::Iterator aMapIter (myArray); aMapIter.More(); aMapIter.Next())
@@ -516,15 +522,8 @@ void OpenGl_Layer::renderTraverse (const Handle(OpenGl_Workspace)& theWorkspace)
 // function : traverse
 // purpose  :
 // =======================================================================
-void OpenGl_Layer::traverse (OpenGl_BVHTreeSelector& theSelector) const
+void OpenGl_Layer::traverse (const OpenGl_BVHTreeSelector& theSelector) const
 {
-  // handle a case when all objects are infinite
-  if (myBVHPrimitives        .Size() == 0
-   && myBVHPrimitivesTrsfPers.Size() == 0)
-    return;
-
-  theSelector.CacheClipPtsProjections();
-
   opencascade::handle<BVH_Tree<Standard_Real, 3> > aBVHTree;
   for (Standard_Integer aBVHTreeIdx = 0; aBVHTreeIdx < 2; ++aBVHTreeIdx)
   {
@@ -595,9 +594,9 @@ void OpenGl_Layer::traverse (OpenGl_BVHTreeSelector& theSelector) const
       else
       {
         Standard_Integer aIdx = aBVHTree->BegPrimitive (aNode);
-        const OpenGl_Structure* aStruct =
-          isTrsfPers ? myBVHPrimitivesTrsfPers.GetStructureById (aIdx)
-                     : myBVHPrimitives.GetStructureById (aIdx);
+        const OpenGl_Structure* aStruct = isTrsfPers
+                                        ? myBVHPrimitivesTrsfPers.GetStructureById (aIdx)
+                                        : myBVHPrimitives.GetStructureById (aIdx);
         aStruct->MarkAsNotCulled();
         if (aHead < 0)
         {
@@ -696,7 +695,6 @@ void OpenGl_Layer::Render (const Handle(OpenGl_Workspace)&   theWorkspace,
   const Standard_Boolean hasLocalCS = !myLayerSettings.OriginTransformation().IsNull();
   const Handle(OpenGl_Context)&   aCtx         = theWorkspace->GetGlContext();
   const Handle(Graphic3d_Camera)& aWorldCamera = theWorkspace->View()->Camera();
-  Handle(Graphic3d_Camera) aCameraBack;
   if (hasLocalCS)
   {
     // Apply local camera transformation.
index f04a3a8..a74ea43 100644 (file)
@@ -135,7 +135,7 @@ protected:
   void updateBVH() const;
 
   //! Traverses through BVH tree to determine which structures are in view volume.
-  void traverse (OpenGl_BVHTreeSelector& theSelector) const;
+  void traverse (const OpenGl_BVHTreeSelector& theSelector) const;
 
   //! Iterates through the hierarchical list of existing structures and renders them all.
   void renderAll (const Handle(OpenGl_Workspace)& theWorkspace) const;
index 93f5640..b579c52 100644 (file)
@@ -877,9 +877,10 @@ void OpenGl_View::render (Graphic3d_Camera::Projection theProjection,
   }
 #endif
 
-  // Update states of OpenGl_BVHTreeSelector (frustum culling algorithm).
+  // update states of OpenGl_BVHTreeSelector (frustum culling algorithm);
+  // note that we pass here window dimensions ignoring Graphic3d_RenderingParams::RenderResolutionScale
   myBVHSelector.SetViewVolume (myCamera);
-  myBVHSelector.SetViewportSize (myWindow->Width(), myWindow->Height());
+  myBVHSelector.SetViewportSize (myWindow->Width(), myWindow->Height(), myRenderParams.ResolutionRatio());
 
   const Handle(OpenGl_ShaderManager)& aManager   = aContext->ShaderManager();
   if (StateInfo (myCurrLightSourceState, aManager->LightSourceState().Index()) != myLastLightSourceState)
index 37900c1..fcc5c5c 100644 (file)
@@ -4655,6 +4655,8 @@ inline void printZLayerInfo (Draw_Interpretor& theDI,
     theDI << "  Immediate: TRUE\n";
   }
   theDI << "  Origin: " << theLayer.Origin().X() << " " << theLayer.Origin().Y() << " " << theLayer.Origin().Z() << "\n";
+  theDI << "  Culling distance: "      << theLayer.CullingDistance() << "\n";
+  theDI << "  Culling size: "          << theLayer.CullingSize() << "\n";
   theDI << "  Depth test:   "          << (theLayer.ToEnableDepthTest() ? "enabled" : "disabled") << "\n";
   theDI << "  Depth write:  "          << (theLayer.ToEnableDepthWrite() ? "enabled" : "disabled") << "\n";
   theDI << "  Depth buffer clearing: " << (theLayer.ToClearDepth() ? "enabled" : "disabled") << "\n";
@@ -4885,6 +4887,33 @@ static int VZLayer (Draw_Interpretor& theDI,
       aSettings.SetOrigin (anOrigin);
       aViewer->SetZLayerSettings (aLayerId, aSettings);
     }
+    else if (aLayerId != Graphic3d_ZLayerId_UNKNOWN
+          && anArgIter + 1 < theArgNb
+          && (anArg == "-cullingdistance"
+           || anArg == "-cullingdist"
+           || anArg == "-culldistance"
+           || anArg == "-culldist"
+           || anArg == "-distcull"
+           || anArg == "-distculling"
+           || anArg == "-distanceculling"))
+    {
+      Graphic3d_ZLayerSettings aSettings = aViewer->ZLayerSettings (aLayerId);
+      const Standard_Real aDist = Draw::Atof (theArgVec[++anArgIter]);
+      aSettings.SetCullingDistance (aDist);
+      aViewer->SetZLayerSettings (aLayerId, aSettings);
+    }
+    else if (aLayerId != Graphic3d_ZLayerId_UNKNOWN
+          && anArgIter + 1 < theArgNb
+          && (anArg == "-cullingsize"
+           || anArg == "-cullsize"
+           || anArg == "-sizecull"
+           || anArg == "-sizeculling"))
+    {
+      Graphic3d_ZLayerSettings aSettings = aViewer->ZLayerSettings (aLayerId);
+      const Standard_Real aSize = Draw::Atof (theArgVec[++anArgIter]);
+      aSettings.SetCullingSize (aSize);
+      aViewer->SetZLayerSettings (aLayerId, aSettings);
+    }
     else if (anArg == "-settings"
           || anArg == "settings")
     {
@@ -11548,6 +11577,7 @@ void ViewerTest::ViewerCommands(Draw_Interpretor& theCommands)
   theCommands.Add("vzlayer",
               "vzlayer [layerId]"
       "\n\t\t:         [-add|-delete|-get|-settings]"
+      "\n\t\t:         [-origin X Y Z] [-cullDist Distance] [-cullSize Size]"
       "\n\t\t:         [-enable|-disable {depthTest|depthWrite|depthClear|depthoffset}]"
       "\n\t\t:         [-enable|-disable {positiveOffset|negativeOffset|textureenv}]"
       "\n\t\t: ZLayer list management:"
diff --git a/tests/v3d/glsl/culldist b/tests/v3d/glsl/culldist
new file mode 100644 (file)
index 0000000..255fdf2
--- /dev/null
@@ -0,0 +1,34 @@
+puts "========"
+puts "0029295: Visualization, TKOpenGl - provide distance culling option"
+puts "Check distance culling"
+puts "========"
+
+set THE_NB_BOXES 5
+set THE_COLORS { RED GREEN BLUE1 YELLOW PURPLE1 }
+set THE_PICK_PNTS1 { {  20  80 } {  60 110 } { 120 140 } { 200 180 } { 300 240 } }
+set THE_PICK_PNTS2 { { 110 140 } { 130 150 } { 160 170 } { 200 190 } { 240 210 } }
+
+pload MODELING VISUALIZATION
+vclear
+vinit View1
+vaxo
+vcamera -persp
+for { set x 0 } { $x < $THE_NB_BOXES } { incr x } { box b$x $x 0 0 0.5 0.2 0.4; vpoint p$x $x 0 0; vdisplay -mutable p$x; vdisplay -dispMode 1 b$x }
+for { set x 0 } { $x < $THE_NB_BOXES } { incr x } { vsetcolor b$x [lindex $THE_COLORS $x] }
+vline l 0 0 0 5 0 0
+vdisplay -mutable l
+vfit
+vzlayer default -culldist 7
+for { set x 0 } { $x < $THE_NB_BOXES } { incr x } { if { [vreadpixel {*}[lindex $THE_PICK_PNTS1 $x] rgb name] == "BLACK" } { puts "Error: object is culled" } }
+vdump $::imagedir/${::casename}_7.png
+
+vzlayer default -culldist 5
+for { set x 2 } { $x < $THE_NB_BOXES } { incr x } { if { [vreadpixel {*}[lindex $THE_PICK_PNTS1 $x] rgb name] == "BLACK" } { puts "Error: object is culled" } }
+for { set x 0 } { $x < 2 }             { incr x } { if { [vreadpixel {*}[lindex $THE_PICK_PNTS1 $x] rgb name] != "BLACK" } { puts "Error: object is NOT culled" } }
+vdump $::imagedir/${::casename}_5.png
+
+vzoom 0.5
+vzlayer default -culldist 10
+for { set x 1 } { $x < $THE_NB_BOXES } { incr x } { if { [vreadpixel {*}[lindex $THE_PICK_PNTS2 $x] rgb name] == "BLACK" } { puts "Error: object is culled" } }
+for { set x 0 } { $x < 1 }             { incr x } { if { [vreadpixel {*}[lindex $THE_PICK_PNTS2 $x] rgb name] != "BLACK" } { puts "Error: object is NOT culled" } }
+vdump $::imagedir/${::casename}_10.png
diff --git a/tests/v3d/glsl/cullsize b/tests/v3d/glsl/cullsize
new file mode 100644 (file)
index 0000000..892bcea
--- /dev/null
@@ -0,0 +1,51 @@
+puts "========"
+puts "0029295: Visualization, TKOpenGl - provide distance culling option"
+puts "Check size culling"
+puts "========"
+
+set THE_NB_BOXES 5
+set THE_COLORS { RED GREEN BLUE1 YELLOW PURPLE1 }
+set THE_PICK_PNTS1O { {  10 165 } { 100 200 } { 170 250 } { 250 250 } { 350 300 } }
+set THE_PICK_PNTS1P { {  40 170 } { 100 200 } { 170 250 } { 250 250 } { 380 350 } }
+set THE_PICK_PNTS2O { { 148 192 } { 175 200 } { 190 200 } { 200 220 } { 250 230 } }
+set THE_PICK_PNTS2P { { 152 192 } { 175 200 } { 190 200 } { 220 220 } { 250 230 } }
+
+pload MODELING VISUALIZATION
+vclear
+vinit View1
+vaxo
+for { set x 1 } { $x <= $THE_NB_BOXES } { incr x } { box b$x [expr $x * 1.0] 0 0 [expr $x * 0.2] [expr $x *0.1] [expr $x *0.3]; vdisplay -dispMode 1 b$x }
+for { set x 1 } { $x <= $THE_NB_BOXES } { incr x } { vsetcolor b$x [lindex $THE_COLORS [expr $x - 1]] }
+vline l 1 0 0 6 0 0
+vdisplay -mutable l
+vcamera -ortho
+vfit
+
+vzlayer default -cullsize 25
+vcamera -ortho
+for { set x 0 } { $x < $THE_NB_BOXES } { incr x } { if { [vreadpixel {*}[lindex $THE_PICK_PNTS1O $x] rgb name] == "BLACK" } { puts "Error: object $x is culled" } }
+vdump $::imagedir/${::casename}_25o.png
+vcamera -persp
+for { set x 0 } { $x < $THE_NB_BOXES } { incr x } { if { [vreadpixel {*}[lindex $THE_PICK_PNTS1P $x] rgb name] == "BLACK" } { puts "Error: object $x is culled" } }
+vdump $::imagedir/${::casename}_25p.png
+
+vzlayer default -cullsize 50
+vcamera -ortho
+for { set x 1 } { $x < $THE_NB_BOXES } { incr x } { if { [vreadpixel {*}[lindex $THE_PICK_PNTS1O $x] rgb name] == "BLACK" } { puts "Error: object $x is culled" } }
+for { set x 0 } { $x < 1 }             { incr x } { if { [vreadpixel {*}[lindex $THE_PICK_PNTS1O $x] rgb name] != "BLACK" } { puts "Error: object $x is NOT culled" } }
+vdump $::imagedir/${::casename}_50o.png
+vcamera -persp
+for { set x 1 } { $x < $THE_NB_BOXES } { incr x } { if { [vreadpixel {*}[lindex $THE_PICK_PNTS1P $x] rgb name] == "BLACK" } { puts "Error: object $x is culled" } }
+for { set x 0 } { $x < 1 }             { incr x } { if { [vreadpixel {*}[lindex $THE_PICK_PNTS1P $x] rgb name] != "BLACK" } { puts "Error: object $x is NOT culled" } }
+vdump $::imagedir/${::casename}_50p.png
+
+vzoom 0.25
+vzlayer default -cullsize 30
+vcamera -ortho
+for { set x 2 } { $x < $THE_NB_BOXES } { incr x } { if { [vreadpixel {*}[lindex $THE_PICK_PNTS2O $x] rgb name] == "BLACK" } { puts "Error: object $x is culled" } }
+for { set x 0 } { $x < 2 }             { incr x } { if { [vreadpixel {*}[lindex $THE_PICK_PNTS2O $x] rgb name] != "BLACK" } { puts "Error: object $x is NOT culled" } }
+vdump $::imagedir/${::casename}_30o.png
+vcamera -persp
+for { set x 2 } { $x < $THE_NB_BOXES } { incr x } { if { [vreadpixel {*}[lindex $THE_PICK_PNTS2P $x] rgb name] == "BLACK" } { puts "Error: object $x is culled" } }
+for { set x 0 } { $x < 2 }             { incr x } { if { [vreadpixel {*}[lindex $THE_PICK_PNTS2P $x] rgb name] != "BLACK" } { puts "Error: object $x is NOT culled" } }
+vdump $::imagedir/${::casename}_30p.png