0026029: Visualization - Poor performance of connected objects
authordbp <dbp@opencascade.com>
Thu, 7 May 2015 15:20:27 +0000 (18:20 +0300)
committerabv <abv@opencascade.com>
Fri, 8 May 2015 05:54:30 +0000 (08:54 +0300)
Fix performance issue with SelectMgr_SelectableObjectSet.

16 files changed:
src/Graphic3d/FILES
src/Graphic3d/Graphic3d.cdl
src/Graphic3d/Graphic3d_IndexedMapOfAddress.hxx [new file with mode: 0644]
src/Graphic3d/Graphic3d_Structure.cdl
src/Graphic3d/Graphic3d_Structure.cxx
src/OpenGl/OpenGl_Group.cxx
src/OpenGl/OpenGl_Group.hxx
src/OpenGl/OpenGl_SceneGeometry.cxx
src/OpenGl/OpenGl_Structure.cxx
src/OpenGl/OpenGl_Structure.hxx
src/OpenGl/OpenGl_View.hxx
src/OpenGl/OpenGl_View_Raytrace.cxx
src/SelectMgr/SelectMgr_SelectableObjectSet.cxx
src/SelectMgr/SelectMgr_SelectableObjectSet.hxx
src/ViewerTest/ViewerTest_ObjectCommands.cxx
tests/bugs/vis/bug26029 [new file with mode: 0644]

index f396243..14f6e96 100755 (executable)
@@ -32,6 +32,7 @@ Graphic3d_ShaderVariable.cxx
 Graphic3d_ShaderVariable.lxx
 Graphic3d_MapOfStructure.hxx
 Graphic3d_MapIteratorOfMapOfStructure.hxx
+Graphic3d_IndexedMapOfAddress.hxx
 Graphic3d_TypeOfShaderObject.hxx
 Graphic3d_DataStructure.pxx
 Graphic3d_StructureManager.pxx
index 05c5451..7ed61e2 100644 (file)
@@ -493,6 +493,7 @@ is
     imported transient class NMapOfTransient;
 
     imported MapOfStructure;
+    imported IndexedMapOfAddress;
     imported SequenceOfDisplayedStructures;
 
     ---------------------------------
diff --git a/src/Graphic3d/Graphic3d_IndexedMapOfAddress.hxx b/src/Graphic3d/Graphic3d_IndexedMapOfAddress.hxx
new file mode 100644 (file)
index 0000000..9296b68
--- /dev/null
@@ -0,0 +1,23 @@
+// Created on: 2015-04-2015
+// Created by: Denis BOGOLEPOV
+// Copyright (c) 2015 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _Graphic3d_IndexedMapOfAddress
+#define _Graphic3d_IndexedMapOfAddress
+
+#include <NCollection_DataMap.hxx>
+
+typedef NCollection_DataMap<const Standard_Address, Standard_Integer> Graphic3d_IndexedMapOfAddress;
+
+#endif // _Graphic3d_IndexedMapOfAddress
index 263108d..1710ac2 100644 (file)
@@ -40,42 +40,42 @@ class Structure from Graphic3d inherits TShared
 
 uses
 
-       Array2OfReal            from TColStd,
-       SequenceOfAddress       from TColStd,
-
-       Color                   from Quantity,
-
-       GenId                   from Aspect,
-       TypeOfHighlightMethod   from Aspect,
-
-       DataStructureManager    from Graphic3d,
-       AspectFillArea3d        from Graphic3d,
-       AspectLine3d            from Graphic3d,
-       AspectMarker3d          from Graphic3d,
-       AspectText3d            from Graphic3d,
-       CStructure          from Graphic3d,
-       CStructure   from Graphic3d,
-       GraphicDriver           from Graphic3d,
-       Group                   from Graphic3d,
-       SequenceOfGroup         from Graphic3d,
-       SequenceOfStructure     from Graphic3d,
-       HSequenceOfStructure    from Graphic3d,
-       MapOfStructure          from Graphic3d,
-       StructureManager        from Graphic3d,
-       StructureManagerPtr from Graphic3d,
-       TypeOfComposition       from Graphic3d,
-       TypeOfConnection        from Graphic3d,
-       TypeOfPrimitive         from Graphic3d,
-       TypeOfStructure         from Graphic3d,
-       Vector                  from Graphic3d,
-       Vertex                  from Graphic3d,
-       TransModeFlags          from Graphic3d,
-  ZLayerId from Graphic3d,
-       Pnt                     from gp,
-    SequenceOfHClipPlane from Graphic3d,
-    BndBox4f from Graphic3d,
-    BndBox4d from Graphic3d,
-    Box from Bnd
+    Array2OfReal            from TColStd,
+    SequenceOfAddress       from TColStd,
+
+    Color                   from Quantity,
+
+    GenId                   from Aspect,
+    TypeOfHighlightMethod   from Aspect,
+
+    DataStructureManager    from Graphic3d,
+    AspectFillArea3d        from Graphic3d,
+    AspectLine3d            from Graphic3d,
+    AspectMarker3d          from Graphic3d,
+    AspectText3d            from Graphic3d,
+    CStructure              from Graphic3d,
+    GraphicDriver           from Graphic3d,
+    Group                   from Graphic3d,
+    SequenceOfGroup         from Graphic3d,
+    SequenceOfStructure     from Graphic3d,
+    HSequenceOfStructure    from Graphic3d,
+    MapOfStructure          from Graphic3d,
+    IndexedMapOfAddress     from Graphic3d,
+    StructureManager        from Graphic3d,
+    StructureManagerPtr     from Graphic3d,
+    TypeOfComposition       from Graphic3d,
+    TypeOfConnection        from Graphic3d,
+    TypeOfPrimitive         from Graphic3d,
+    TypeOfStructure         from Graphic3d,
+    Vector                  from Graphic3d,
+    Vertex                  from Graphic3d,
+    TransModeFlags          from Graphic3d,
+    ZLayerId                from Graphic3d,
+    Pnt                     from gp,
+    SequenceOfHClipPlane    from Graphic3d,
+    BndBox4f                from Graphic3d,
+    BndBox4d                from Graphic3d,
+    Box                     from Bnd
 
 raises
 
@@ -957,6 +957,38 @@ is
   ---C++: return const &
   ---C++: inline
 
+  AppendDescendant (me : mutable;
+                    theDescendant : Address from Standard)
+    returns Boolean from Standard
+    is protected;
+  ---Level: Internal
+  ---Purpose: Appends new descendant structure.
+  ---Category: Private methods
+
+  RemoveDescendant (me : mutable;
+                    theDescendant : Address from Standard)
+    returns Boolean from Standard
+    is protected;
+  ---Level: Internal
+  ---Purpose: Removes the given descendant structure.
+  ---Category: Private methods
+
+  AppendAncestor (me : mutable;
+                  theAncestor : Address from Standard)
+    returns Boolean from Standard
+    is protected;
+  ---Level: Internal
+  ---Purpose: Appends new ancestor structure.
+  ---Category: Private methods
+
+  RemoveAncestor (me : mutable;
+                  theAncestor : Address from Standard)
+    returns Boolean from Standard
+    is protected;
+  ---Level: Internal
+  ---Purpose: Removes the given ancestor structure.
+  ---Category: Private methods
+
 fields
 
 --
@@ -968,14 +1000,20 @@ fields
 --                     It is a sequence of groups of primitives.
 --
 
-       -- the associated low-level structure
-  myCStructure : CStructure from Graphic3d;
+    -- the associated low-level structure
+    myCStructure : CStructure from Graphic3d;
+
+    -- the structures to which the structure is attached
+    myAncestors     :  SequenceOfAddress from TColStd;
+
+    -- the structures attached to the structure
+    myDescendants   :  SequenceOfAddress from TColStd;
 
-       -- the structures to which the structure is attached
-       myAncestors             :       SequenceOfAddress from TColStd;
+    -- the map of structures to which the structure is attached
+    myAncestorMap   :  IndexedMapOfAddress from Graphic3d;
 
-       -- the structures attached to the structure
-       myDescendants           :       SequenceOfAddress from TColStd;
+    -- the map of structures attached to the structure
+    myDescendantMap :  IndexedMapOfAddress from Graphic3d;
 
        -- the highlight method of the structure
        myHighlightColor        :       Color from Quantity;
index 053d0af..db64f66 100644 (file)
@@ -1312,6 +1312,90 @@ void Graphic3d_Structure::Descendants (Graphic3d_MapOfStructure& theSet) const
   }
 }
 
+//=============================================================================
+//function : AppendDescendant
+//purpose  :
+//=============================================================================
+Standard_Boolean Graphic3d_Structure::AppendDescendant (const Standard_Address theDescendant)
+{
+  if (myDescendantMap.IsBound (theDescendant))
+  {
+    return Standard_False; // already connected
+  }
+
+  myDescendantMap.Bind (theDescendant, myDescendants.Length() + 1);
+  myDescendants.Append (theDescendant);
+
+  return Standard_True;
+}
+
+//=============================================================================
+//function : RemoveDescendant
+//purpose  :
+//=============================================================================
+Standard_Boolean Graphic3d_Structure::RemoveDescendant (const Standard_Address theDescendant)
+{
+  Standard_Integer aStructIdx;
+  if (!myDescendantMap.Find (theDescendant, aStructIdx))
+  {
+    return Standard_False;
+  }
+
+  myDescendantMap.UnBind (theDescendant);
+
+  if (aStructIdx != myDescendants.Length())
+  {
+    myDescendants.Exchange (aStructIdx, myDescendants.Length());
+    myDescendantMap.Bind   (myDescendants (aStructIdx), aStructIdx);
+  }
+
+  myDescendants.Remove (myDescendants.Length());
+
+  return Standard_True;
+}
+
+//=============================================================================
+//function : AppendAncestor
+//purpose  :
+//=============================================================================
+Standard_Boolean Graphic3d_Structure::AppendAncestor (const Standard_Address theAncestor)
+{
+  if (myAncestorMap.IsBound (theAncestor))
+  {
+    return Standard_False; // already connected
+  }
+
+  myAncestorMap.Bind (theAncestor, myAncestors.Length() + 1);
+  myAncestors.Append (theAncestor);
+
+  return Standard_True;
+}
+
+//=============================================================================
+//function : RemoveAncestor
+//purpose  :
+//=============================================================================
+Standard_Boolean Graphic3d_Structure::RemoveAncestor (const Standard_Address theAncestor)
+{
+  Standard_Integer aStructIdx;
+  if (!myAncestorMap.Find (theAncestor, aStructIdx))
+  {
+    return Standard_False;
+  }
+
+  myAncestorMap.UnBind (theAncestor);
+
+  if (aStructIdx != myAncestors.Length())
+  {
+    myAncestors.Exchange (aStructIdx, myAncestors.Length());
+    myAncestorMap.Bind   (myAncestors (aStructIdx), aStructIdx);
+  }
+
+  myAncestors.Remove (myAncestors.Length());
+
+  return Standard_True;
+}
+
 //=============================================================================
 //function : Connect
 //purpose  :
@@ -1320,7 +1404,10 @@ void Graphic3d_Structure::Connect (const Handle(Graphic3d_Structure)& theStructu
                                    const Graphic3d_TypeOfConnection   theType,
                                    const Standard_Boolean             theWithCheck)
 {
-  if (IsDeleted()) return;
+  if (IsDeleted())
+  {
+    return;
+  }
 
   // cycle detection
   if (theWithCheck
@@ -1329,47 +1416,34 @@ void Graphic3d_Structure::Connect (const Handle(Graphic3d_Structure)& theStructu
     return;
   }
 
-  switch (theType)
+  const Standard_Address aStructure = theStructure.operator->();
+
+  if (theType == Graphic3d_TOC_DESCENDANT)
   {
-    case Graphic3d_TOC_DESCENDANT:
+    if (!AppendDescendant (aStructure))
     {
-      const Standard_Integer aNbDesc = myDescendants.Length();
-      for (Standard_Integer anIter = 1; anIter <= aNbDesc; ++anIter)
-      {
-        if (myDescendants.Value (anIter) == theStructure.operator->())
-        {
-          return;
-        }
-      }
+      return;
+    }
 
-      myDescendants.Append (theStructure.operator->());
-      CalculateBoundBox();
-      theStructure->Connect (this, Graphic3d_TOC_ANCESTOR);
+    CalculateBoundBox();
+    theStructure->Connect (this, Graphic3d_TOC_ANCESTOR);
 
-      GraphicConnect (theStructure);
-      myStructureManager->Connect (this, theStructure);
+    GraphicConnect (theStructure);
+    myStructureManager->Connect (this, theStructure);
 
-      Update();
+    Update();
+  }
+  else // Graphic3d_TOC_ANCESTOR
+  {
+    if (!AppendAncestor (aStructure))
+    {
       return;
     }
-    case Graphic3d_TOC_ANCESTOR:
-    {
-      const Standard_Integer aNbAnces = myAncestors.Length();
-      for (Standard_Integer anIter = 1; anIter <= aNbAnces; ++anIter)
-      {
-        if (myAncestors.Value (anIter) == theStructure.operator->())
-        {
-          return;
-        }
-      }
 
-      myAncestors.Append (theStructure.operator->());
-      CalculateBoundBox();
-      theStructure->Connect (this, Graphic3d_TOC_DESCENDANT);
+    CalculateBoundBox();
+    theStructure->Connect (this, Graphic3d_TOC_DESCENDANT);
 
-      // myGraphicDriver->Connect is called in case if connection between parent and child
-      return;
-    }
+    // myStructureManager->Connect is called in case if connection between parent and child
   }
 }
 
@@ -1379,37 +1453,29 @@ void Graphic3d_Structure::Connect (const Handle(Graphic3d_Structure)& theStructu
 //=============================================================================
 void Graphic3d_Structure::Disconnect (const Handle(Graphic3d_Structure)& theStructure)
 {
-  if (IsDeleted()) return;
-
-  const Standard_Integer aNbDesc = myDescendants.Length();
-  for (Standard_Integer anIter = 1; anIter <= aNbDesc; ++anIter)
+  if (IsDeleted())
   {
-    if (myDescendants.Value (anIter) == theStructure.operator->())
-    {
-      myDescendants.Remove (anIter);
-      theStructure->Disconnect (this);
+    return;
+  }
 
-      GraphicDisconnect (theStructure);
-      myStructureManager->Disconnect (this, theStructure);
+  const Standard_Address aStructure = theStructure.operator->();
 
-      CalculateBoundBox();
+  if (RemoveDescendant (aStructure))
+  {
+    theStructure->Disconnect (this);
 
-      Update();
-      return;
-    }
-  }
+    GraphicDisconnect (theStructure);
+    myStructureManager->Disconnect (this, theStructure);
 
-  const Standard_Integer aNbAnces = myAncestors.Length();
-  for (Standard_Integer anIter = 1; anIter <= aNbAnces; ++anIter)
+    CalculateBoundBox();
+    Update();
+  }
+  else if (RemoveAncestor (aStructure))
   {
-    if (myAncestors.Value (anIter) == theStructure.operator->())
-    {
-      myAncestors.Remove (anIter);
-      theStructure->Disconnect (this);
-      CalculateBoundBox();
-      // no call of myGraphicDriver->Disconnect in case of an ancestor
-      return;
-    }
+    theStructure->Disconnect (this);
+    CalculateBoundBox();
+
+    // no call of myStructureManager->Disconnect in case of an ancestor
   }
 }
 
@@ -1680,34 +1746,13 @@ gp_Pnt Graphic3d_Structure::TransformPersistencePoint() const
 void Graphic3d_Structure::Remove (const Standard_Address           thePtr,
                                   const Graphic3d_TypeOfConnection theType)
 {
-  switch (theType)
+  if (theType == Graphic3d_TOC_DESCENDANT)
   {
-    case Graphic3d_TOC_DESCENDANT:
-    {
-      const Standard_Integer aNbDesc = myDescendants.Length();
-      for (Standard_Integer anIter = 1; anIter <= aNbDesc; ++anIter)
-      {
-        if (myDescendants.Value (anIter) == thePtr)
-        {
-          myDescendants.Remove (anIter);
-          return;
-        }
-      }
-      break;
-    }
-    case Graphic3d_TOC_ANCESTOR:
-    {
-      const Standard_Integer aNbAncestors = myAncestors.Length();
-      for (Standard_Integer anIter = 1; anIter <= aNbAncestors; ++anIter)
-      {
-        if (myAncestors.Value (anIter) == thePtr)
-        {
-          myAncestors.Remove (anIter);
-          return;
-        }
-      }
-      break;
-    }
+    RemoveDescendant (thePtr);
+  }
+  else
+  {
+    RemoveAncestor (thePtr);
   }
 }
 
index aa5babb..8eda734 100644 (file)
@@ -42,8 +42,7 @@ OpenGl_Group::OpenGl_Group (const Handle(Graphic3d_Structure)& theStruct)
   myAspectText(NULL),
   myFirst(NULL),
   myLast(NULL),
-  myIsRaytracable (Standard_False),
-  myModificationState (0)
+  myIsRaytracable (Standard_False)
 {
   Handle(OpenGl_Structure) aStruct = Handle(OpenGl_Structure)::DownCast (myStructure->CStructure());
   if (aStruct == NULL)
@@ -116,11 +115,10 @@ void OpenGl_Group::UpdateAspectFace (const Standard_Boolean theIsGlobal)
 
   if (myIsRaytracable)
   {
-    ++myModificationState;
     OpenGl_Structure* aStruct = GlStruct();
     if (aStruct != NULL)
     {
-      aStruct->UpdateStateWithAncestorStructures();
+      aStruct->UpdateStateIfRaytracable (Standard_False);
     }
   }
 }
@@ -302,14 +300,12 @@ void OpenGl_Group::AddElement (OpenGl_Element* theElem)
 
   if (OpenGl_Raytrace::IsRaytracedElement (aNode))
   {
-    myModificationState++;
     myIsRaytracable = Standard_True;
 
     OpenGl_Structure* aStruct = GlStruct();
     if (aStruct != NULL)
     {
-      aStruct->UpdateStateWithAncestorStructures();
-      aStruct->SetRaytracableWithAncestorStructures();
+      aStruct->UpdateStateIfRaytracable (Standard_False);
     }
   }
 }
@@ -366,6 +362,8 @@ void OpenGl_Group::Clear (const Standard_Boolean theToUpdateStructureMgr)
 
   Release (aCtx);
   Graphic3d_Group::Clear (theToUpdateStructureMgr);
+
+  myIsRaytracable = Standard_False;
 }
 
 // =======================================================================
index 07e0c70..6406bd2 100644 (file)
@@ -104,9 +104,6 @@ public:
   //! Returns OpenGL face aspect.
   const OpenGl_AspectFace* AspectFace() const { return myAspectFace; }
 
-  //! Returns modification state for ray-tracing.
-  Standard_Size ModificationState() const { return myModificationState; }
-
   //! Is the group ray-tracable (contains ray-tracable elements)?
   Standard_Boolean IsRaytracable() const { return myIsRaytracable; }
 
@@ -128,7 +125,6 @@ protected:
   OpenGl_ElementNode*    myLast;
 
   Standard_Boolean       myIsRaytracable;
-  Standard_Size          myModificationState;
 
 public:
 
index 2411e11..1a4fd81 100755 (executable)
@@ -516,10 +516,9 @@ namespace OpenGl_Raytrace
   // function : IsRaytracedGroup
   // purpose  : Checks to see if the group contains ray-trace geometry
   // =======================================================================
-  Standard_Boolean IsRaytracedGroup (const OpenGl_Group *theGroup)
+  Standard_Boolean IsRaytracedGroup (const OpenGl_GrouptheGroup)
   {
-    const OpenGl_ElementNode* aNode;
-    for (aNode = theGroup->FirstNode(); aNode != NULL; aNode = aNode->next)
+    for (const OpenGl_ElementNode* aNode = theGroup->FirstNode(); aNode != NULL; aNode = aNode->next)
     {
       if (IsRaytracedElement (aNode))
       {
@@ -535,17 +534,12 @@ namespace OpenGl_Raytrace
   // =======================================================================
   Standard_Boolean IsRaytracedStructure (const OpenGl_Structure* theStructure)
   {
-    for (OpenGl_Structure::GroupIterator aGroupIter (theStructure->DrawGroups());
-         aGroupIter.More(); aGroupIter.Next())
+    for (OpenGl_Structure::GroupIterator anIter (theStructure->DrawGroups()); anIter.More(); anIter.Next())
     {
-      if (aGroupIter.Value()->IsRaytracable())
-        return Standard_True;
-    }
-    for (OpenGl_ListOfStructure::Iterator anIts (theStructure->ConnectedStructures());
-         anIts.More(); anIts.Next())
-    {
-      if (IsRaytracedStructure (anIts.Value()))
+      if (anIter.Value()->IsRaytracable())
+      {
         return Standard_True;
+      }
     }
     return Standard_False;
   }
index 3bac56e..df77dfc 100644 (file)
@@ -119,17 +119,18 @@ public:
 // =======================================================================
 OpenGl_Structure::OpenGl_Structure (const Handle(Graphic3d_StructureManager)& theManager)
 : Graphic3d_CStructure (theManager),
-  myTransformation(NULL),
-  myTransPers(NULL),
-  myAspectLine(NULL),
-  myAspectFace(NULL),
-  myAspectMarker(NULL),
-  myAspectText(NULL),
-  myHighlightColor(NULL),
-  myIsRaytracable (Standard_False),
-  myModificationState (0),
-  myIsCulled (Standard_True),
-  myIsMirrored (Standard_False)
+  myTransformation     (NULL),
+  myTransPers          (NULL),
+  myAspectLine         (NULL),
+  myAspectFace         (NULL),
+  myAspectMarker       (NULL),
+  myAspectText         (NULL),
+  myHighlightColor     (NULL),
+  myInstancedStructure (NULL),
+  myIsRaytracable      (Standard_False),
+  myModificationState  (0),
+  myIsCulled           (Standard_True),
+  myIsMirrored         (Standard_False)
 {
   //
 }
@@ -189,9 +190,9 @@ void OpenGl_Structure::UpdateTransformation()
 
   matcpy (myTransformation->mat, &Graphic3d_CStructure::Transformation[0][0]);
 
-  if (myIsRaytracable)
+  if (IsRaytracable())
   {
-    UpdateStateWithAncestorStructures();
+    ++myModificationState;
   }
 }
 
@@ -236,9 +237,9 @@ void OpenGl_Structure::SetAspectFace (const CALL_DEF_CONTEXTFILLAREA& theAspect)
   }
   myAspectFace->SetAspect (theAspect);
 
-  if (myIsRaytracable)
+  if (IsRaytracable())
   {
-    UpdateStateWithAncestorStructures();
+    ++myModificationState;
   }
 }
 
@@ -365,111 +366,41 @@ void OpenGl_Structure::clearHighlightColor (const Handle(OpenGl_Context)& theGlC
 // =======================================================================
 void OpenGl_Structure::OnVisibilityChanged()
 {
-  if (myIsRaytracable)
+  if (IsRaytracable())
   {
-    UpdateStateWithAncestorStructures();
+    ++myModificationState;
   }
 }
 
 // =======================================================================
-// function : RegisterAncestorStructure
+// function : IsRaytracable
 // purpose  :
 // =======================================================================
-void OpenGl_Structure::RegisterAncestorStructure (const OpenGl_Structure* theStructure) const
+Standard_Boolean OpenGl_Structure::IsRaytracable() const
 {
-  for (OpenGl_ListOfStructure::Iterator anIt (myAncestorStructures); anIt.More(); anIt.Next())
+  if (!myGroups.IsEmpty())
   {
-    if (anIt.Value() == theStructure)
-    {
-      return;
-    }
+    return myIsRaytracable; // geometry structure
   }
-
-  myAncestorStructures.Append (theStructure);
-}
-
-// =======================================================================
-// function : UnregisterAncestorStructure
-// purpose  :
-// =======================================================================
-void OpenGl_Structure::UnregisterAncestorStructure (const OpenGl_Structure* theStructure) const
-{
-  for (OpenGl_ListOfStructure::Iterator anIt (myAncestorStructures); anIt.More(); anIt.Next())
+  else if (myInstancedStructure != NULL)
   {
-    if (anIt.Value() == theStructure)
-    {
-      myAncestorStructures.Remove (anIt);
-      return;
-    }
+    return myInstancedStructure->IsRaytracable(); // instance structure
   }
-}
-
-// =======================================================================
-// function : UnregisterFromAncestorStructure
-// purpose  :
-// =======================================================================
-void OpenGl_Structure::UnregisterFromAncestorStructure() const
-{
-  for (OpenGl_ListOfStructure::Iterator anIta (myAncestorStructures); anIta.More(); anIta.Next())
-  {
-    OpenGl_Structure* anAncestor = const_cast<OpenGl_Structure*> (anIta.ChangeValue());
 
-    for (OpenGl_ListOfStructure::Iterator anIts (anAncestor->myConnected); anIts.More(); anIts.Next())
-    {
-      if (anIts.Value() == this)
-      {
-        anAncestor->myConnected.Remove (anIts);
-        return;
-      }
-    }
-  }
+  return Standard_False; // has no any groups or structures
 }
 
 // =======================================================================
-// function : UpdateStateWithAncestorStructures
+// function : UpdateRaytracableState
 // purpose  :
 // =======================================================================
-void OpenGl_Structure::UpdateStateWithAncestorStructures() const
+void OpenGl_Structure::UpdateStateIfRaytracable (const Standard_Boolean toCheck) const
 {
-  myModificationState++;
+  myIsRaytracable = !toCheck || OpenGl_Raytrace::IsRaytracedStructure (this);
 
-  for (OpenGl_ListOfStructure::Iterator anIt (myAncestorStructures); anIt.More(); anIt.Next())
+  if (IsRaytracable())
   {
-    anIt.Value()->UpdateStateWithAncestorStructures();
-  }
-}
-
-// =======================================================================
-// function : UpdateRaytracableWithAncestorStructures
-// purpose  :
-// =======================================================================
-void OpenGl_Structure::UpdateRaytracableWithAncestorStructures() const
-{
-  myIsRaytracable = OpenGl_Raytrace::IsRaytracedStructure (this);
-
-  if (!myIsRaytracable)
-  {
-    for (OpenGl_ListOfStructure::Iterator anIt (myAncestorStructures); anIt.More(); anIt.Next())
-    {
-      anIt.Value()->UpdateRaytracableWithAncestorStructures();
-    }
-  }
-}
-
-// =======================================================================
-// function : SetRaytracableWithAncestorStructures
-// purpose  :
-// =======================================================================
-void OpenGl_Structure::SetRaytracableWithAncestorStructures() const
-{
-  myIsRaytracable = Standard_True;
-
-  for (OpenGl_ListOfStructure::Iterator anIt (myAncestorStructures); anIt.More(); anIt.Next())
-  {
-    if (!anIt.Value()->IsRaytracable())
-    {
-      anIt.Value()->SetRaytracableWithAncestorStructures();
-    }
+    ++myModificationState;
   }
 }
 
@@ -479,17 +410,17 @@ void OpenGl_Structure::SetRaytracableWithAncestorStructures() const
 // =======================================================================
 void OpenGl_Structure::Connect (Graphic3d_CStructure& theStructure)
 {
-  OpenGl_Structure* aStruct = (OpenGl_Structure* )&theStructure;
-  Disconnect (theStructure);
-  myConnected.Append (aStruct);
+  OpenGl_Structure* aStruct = static_cast<OpenGl_Structure*> (&theStructure);
+
+  Standard_ASSERT_RAISE (myInstancedStructure == NULL || myInstancedStructure == aStruct,
+    "Error! Instanced structure is already defined");
+
+  myInstancedStructure = aStruct;
 
   if (aStruct->IsRaytracable())
   {
-    UpdateStateWithAncestorStructures();
-    SetRaytracableWithAncestorStructures();
+    UpdateStateIfRaytracable (Standard_False);
   }
-
-  aStruct->RegisterAncestorStructure (this);
 }
 
 // =======================================================================
@@ -498,22 +429,15 @@ void OpenGl_Structure::Connect (Graphic3d_CStructure& theStructure)
 // =======================================================================
 void OpenGl_Structure::Disconnect (Graphic3d_CStructure& theStructure)
 {
-  OpenGl_Structure* aStruct = (OpenGl_Structure* )&theStructure;
-  for (OpenGl_ListOfStructure::Iterator anIter (myConnected); anIter.More(); anIter.Next())
-  {
-    // Check for the given structure
-    if (anIter.Value() == aStruct)
-    {
-      myConnected.Remove (anIter);
+  OpenGl_Structure* aStruct = static_cast<OpenGl_Structure*> (&theStructure);
 
-      if (aStruct->IsRaytracable())
-      {
-        UpdateStateWithAncestorStructures();
-        UpdateRaytracableWithAncestorStructures();
-      }
+  if (myInstancedStructure == aStruct)
+  {
+    myInstancedStructure = NULL;
 
-      aStruct->UnregisterAncestorStructure (this);
-      return;
+    if (aStruct->IsRaytracable())
+    {
+      UpdateStateIfRaytracable();
     }
   }
 }
@@ -545,12 +469,14 @@ void OpenGl_Structure::RemoveGroup (const Handle(Graphic3d_Group)& theGroup)
     // Check for the given group
     if (aGroupIter.Value() == theGroup)
     {
+      const Standard_Boolean wasRaytracable =
+        static_cast<const OpenGl_Group&> (*theGroup).IsRaytracable();
+
       theGroup->Clear (Standard_False);
 
-      if (((OpenGl_Group* )theGroup.operator->())->IsRaytracable())
+      if (wasRaytracable)
       {
-        UpdateStateWithAncestorStructures();
-        UpdateRaytracableWithAncestorStructures();
+        UpdateStateIfRaytracable();
       }
 
       myGroups.Remove (aGroupIter);
@@ -588,8 +514,7 @@ void OpenGl_Structure::Clear (const Handle(OpenGl_Context)& theGlCtx)
 
   if (aRaytracableGroupDeleted)
   {
-    UpdateStateWithAncestorStructures();
-    UpdateRaytracableWithAncestorStructures();
+    myIsRaytracable = Standard_False;
   }
 
   Is2dText       = Standard_False;
@@ -695,12 +620,10 @@ void OpenGl_Structure::Render (const Handle(OpenGl_Workspace) &theWorkspace) con
   if (myHighlightColor)
     theWorkspace->HighlightColor = myHighlightColor;
 
-  // Render connected structures
-  OpenGl_ListOfStructure::Iterator anIter (myConnected);
-  while (anIter.More())
+  // Render instanced structure (if exists)
+  if (myInstancedStructure != NULL)
   {
-    anIter.Value()->RenderGeometry (theWorkspace);
-    anIter.Next();
+    myInstancedStructure->RenderGeometry (theWorkspace);
   }
 
   // Set up plane equations for non-structure transformed global model-view matrix
@@ -816,9 +739,6 @@ void OpenGl_Structure::Release (const Handle(OpenGl_Context)& theGlCtx)
   OpenGl_Element::Destroy (theGlCtx.operator->(), myAspectMarker);
   OpenGl_Element::Destroy (theGlCtx.operator->(), myAspectText);
   clearHighlightColor (theGlCtx);
-
-  // Remove from connected list of ancestor
-  UnregisterFromAncestorStructure();
 }
 
 // =======================================================================
index 3305645..ac96195 100644 (file)
@@ -175,8 +175,8 @@ public:
   //! and will lead to broken visualization due to loosed data.
   Standard_EXPORT void ReleaseGlResources (const Handle(OpenGl_Context)& theGlCtx);
 
-  //! Returns list of connected OpenGL structures.
-  const OpenGl_ListOfStructure& ConnectedStructures() const { return myConnected; }
+  //! Returns instanced OpenGL structure.
+  const OpenGl_Structure* InstancedStructure() const { return myInstancedStructure; }
 
   //! Returns OpenGL face aspect.
   const OpenGl_AspectFace* AspectFace() const { return myAspectFace; }
@@ -194,29 +194,14 @@ public:
   void ResetModificationState() const { myModificationState = 0; }
 
   //! Is the structure ray-tracable (contains ray-tracable elements)?
-  Standard_Boolean IsRaytracable() const { return myIsRaytracable; }
+  Standard_Boolean IsRaytracable() const;
 
 protected:
 
   Standard_EXPORT virtual ~OpenGl_Structure();
 
-  //! Registers ancestor connected structure (for updating ray-tracing state).
-  void RegisterAncestorStructure (const OpenGl_Structure* theStructure) const;
-
-  //! Unregisters ancestor connected structure (for updating ray-tracing state).
-  void UnregisterAncestorStructure (const OpenGl_Structure* theStructure) const;
-
-  //! Unregisters structure from ancestor structure (for updating ray-tracing state).
-  void UnregisterFromAncestorStructure() const;
-
-  //! Updates modification state for structure and its parents.
-  void UpdateStateWithAncestorStructures() const;
-
   //! Updates ray-tracable status for structure and its parents.
-  void UpdateRaytracableWithAncestorStructures() const;
-
-  //! Sets ray-tracable status for structure and its parents.
-  void SetRaytracableWithAncestorStructures() const;
+  void UpdateStateIfRaytracable (const Standard_Boolean toCheck = Standard_True) const;
 
 protected:
 
@@ -230,15 +215,14 @@ protected:
   Handle(OpenGl_Group)       myHighlightBox;
   TEL_COLOUR*                myHighlightColor;
 
-  OpenGl_ListOfStructure           myConnected;
+  OpenGl_Structure*          myInstancedStructure;
 
-  mutable OpenGl_ListOfStructure   myAncestorStructures;
-  mutable Standard_Boolean         myIsRaytracable;
-  mutable Standard_Size            myModificationState;
+  mutable Standard_Boolean   myIsRaytracable;
+  mutable Standard_Size      myModificationState;
 
-  mutable Standard_Boolean         myIsCulled; //!< A status specifying is structure needs to be rendered after BVH tree traverse.
+  mutable Standard_Boolean   myIsCulled; //!< A status specifying is structure needs to be rendered after BVH tree traverse.
 
-  Standard_Boolean                 myIsMirrored; //!< Used to tell OpenGl to interpret polygons in clockwise order.
+  Standard_Boolean           myIsMirrored; //!< Used to tell OpenGl to interpret polygons in clockwise order.
 
 public:
 
index 5f28f80..2a16e41 100644 (file)
@@ -448,6 +448,31 @@ protected: //! @name data types related to ray-tracing
     }
   };
 
+  //! Describes state of OpenGL structure.
+  struct StructState
+  {
+    Standard_Size StructureState;
+    Standard_Size InstancedState;
+
+    //! Creates new structure state.
+    StructState (const Standard_Size theStructureState = 0,
+                 const Standard_Size theInstancedState = 0)
+    : StructureState (theStructureState),
+      InstancedState (theInstancedState)
+    {
+      //
+    }
+
+    //! Creates new structure state.
+    StructState (const OpenGl_Structure* theStructure)
+    {
+      StructureState = theStructure->ModificationState();
+
+      InstancedState = theStructure->InstancedStructure() != NULL ?
+        theStructure->InstancedStructure()->ModificationState() : 0;
+    }
+  };
+
 protected: //! @name methods related to ray-tracing
 
   //! Updates 3D scene geometry for ray-tracing.
@@ -671,7 +696,7 @@ protected: //! @name fields related to ray-tracing
   Standard_Integer myUniformLocations[2][OpenGl_RT_NbVariables];
 
   //! State of OpenGL structures reflected to ray-tracing.
-  std::map<const OpenGl_Structure*, Standard_Size> myStructureStates;
+  std::map<const OpenGl_Structure*, StructState> myStructureStates;
 
   //! PrimitiveArray to TriangleSet map for scene partial update.
   std::map<Standard_Size, OpenGl_TriangleSet*> myArrayToTrianglesMap;
index 4a0196e..15e9f9d 100644 (file)
@@ -157,7 +157,7 @@ Standard_Boolean OpenGl_View::updateRaytraceGeometry (const RaytraceUpdateMode
   else if (theMode == OpenGl_GUM_REBUILD)
   {
     // Actualize the hash map of structures - remove out-of-date records
-    std::map<const OpenGl_Structure*, Standard_Size>::iterator anIter = myStructureStates.begin();
+    std::map<const OpenGl_Structure*, StructState>::iterator anIter = myStructureStates.begin();
 
     while (anIter != myStructureStates.end())
     {
@@ -209,14 +209,18 @@ Standard_Boolean OpenGl_View::toUpdateStructure (const OpenGl_Structure* theStru
     return Standard_False; // did not contain ray-trace elements
   }
 
-  std::map<const OpenGl_Structure*, Standard_Size>::iterator aStructState = myStructureStates.find (theStructure);
+  std::map<const OpenGl_Structure*, StructState>::iterator aStructState = myStructureStates.find (theStructure);
 
-  if (aStructState != myStructureStates.end())
+  if (aStructState == myStructureStates.end() || aStructState->second.StructureState != theStructure->ModificationState())
   {
-    return aStructState->second != theStructure->ModificationState();
+    return Standard_True;
+  }
+  else if (theStructure->InstancedStructure() != NULL)
+  {
+    return aStructState->second.InstancedState != theStructure->InstancedStructure()->ModificationState();
   }
 
-  return Standard_True;
+  return Standard_False;
 }
 
 // =======================================================================
@@ -354,7 +358,7 @@ Standard_Boolean OpenGl_View::addRaytraceStructure (const OpenGl_Structure*
 {
   if (!theStructure->IsVisible())
   {
-    myStructureStates[theStructure] = theStructure->ModificationState();
+    myStructureStates[theStructure] = StructState (theStructure);
 
     return Standard_True;
   }
@@ -388,16 +392,15 @@ Standard_Boolean OpenGl_View::addRaytraceStructure (const OpenGl_Structure*
     theStructure->Transformation()->mat ? aStructTransform : NULL, theGlContext);
 
   // Process all connected OpenGL structures
-  for (OpenGl_ListOfStructure::Iterator anIts (theStructure->ConnectedStructures()); anIts.More(); anIts.Next())
+  const OpenGl_Structure* anInstanced = theStructure->InstancedStructure();
+
+  if (anInstanced != NULL && anInstanced->IsRaytracable())
   {
-    if (anIts.Value()->IsRaytracable())
-    {
-      aResult &= addRaytraceGroups (anIts.Value(), aStructMatID,
-        theStructure->Transformation()->mat ? aStructTransform : NULL, theGlContext);
-    }
+    aResult &= addRaytraceGroups (anInstanced, aStructMatID,
+      theStructure->Transformation()->mat ? aStructTransform : NULL, theGlContext);
   }
 
-  myStructureStates[theStructure] = theStructure->ModificationState();
+  myStructureStates[theStructure] = StructState (theStructure);
 
   return aResult;
 }
index 4c5738c..ffd330b 100644 (file)
@@ -34,10 +34,10 @@ SelectMgr_SelectableObjectSet::SelectMgr_SelectableObjectSet()
 //=======================================================================
 void SelectMgr_SelectableObjectSet::Append (const Handle(SelectMgr_SelectableObject)& theObject)
 {
-  myObjects.Append (theObject);
-  myObjectIdxs.Append (myObjects.Size());
-
-  MarkDirty();
+  if (Size() < myObjects.Add (theObject))
+  {
+    MarkDirty();
+  }
 }
 
 //=======================================================================
@@ -47,19 +47,15 @@ void SelectMgr_SelectableObjectSet::Append (const Handle(SelectMgr_SelectableObj
 //=======================================================================
 void SelectMgr_SelectableObjectSet::Remove (const Handle(SelectMgr_SelectableObject)& theObject)
 {
-  for (Standard_Integer anObjectIdx = 1; anObjectIdx <= myObjects.Size(); ++anObjectIdx)
+  const Standard_Integer anIndex = myObjects.FindIndex (theObject);
+
+  if (anIndex != 0)
   {
-    if (myObjects.Value (anObjectIdx) == theObject)
-    {
-      myObjects.Remove (anObjectIdx);
-      myObjectIdxs.Clear();
-      for (Standard_Integer anObjIdxsIter = 1; anObjIdxsIter <= myObjects.Size(); ++anObjIdxsIter)
-      {
-        myObjectIdxs.Append (anObjIdxsIter);
-      }
-      MarkDirty();
-      break;
-    }
+    Swap (anIndex - 1, Size() - 1);
+
+    myObjects.RemoveLast();
+
+    MarkDirty();
   }
 }
 
@@ -88,11 +84,9 @@ Standard_Real SelectMgr_SelectableObjectSet::Center (const Standard_Integer theI
                                                      const Standard_Integer theAxis) const
 {
   Select3D_BndBox3d aBndBox = Box (theIndex);
-  Standard_Real aCenter = theAxis == 0 ? (aBndBox.CornerMin().x() + aBndBox.CornerMax().x()) * 0.5 :
-    (theAxis == 1 ? (aBndBox.CornerMin().y() + aBndBox.CornerMax().y()) * 0.5 :
-                    (aBndBox.CornerMin().z() + aBndBox.CornerMax().z()) * 0.5);
 
-  return aCenter;
+  return (aBndBox.CornerMin()[theAxis] +
+          aBndBox.CornerMax()[theAxis]) * 0.5;
 }
 
 //=======================================================================
@@ -102,10 +96,15 @@ Standard_Real SelectMgr_SelectableObjectSet::Center (const Standard_Integer theI
 void SelectMgr_SelectableObjectSet::Swap (const Standard_Integer theIndex1,
                                           const Standard_Integer theIndex2)
 {
-  Standard_Integer anObjectIdx1 = myObjectIdxs.Value (theIndex1 + 1);
-  Standard_Integer anObjectIdx2 = myObjectIdxs.Value (theIndex2 + 1);
-  myObjectIdxs.ChangeValue (theIndex1 + 1) = anObjectIdx2;
-  myObjectIdxs.ChangeValue (theIndex2 + 1) = anObjectIdx1;
+  const Standard_Integer aIndex1 = theIndex1 + 1;
+  const Standard_Integer aIndex2 = theIndex2 + 1;
+
+  Handle(SelectMgr_SelectableObject) anObject1 = myObjects.FindKey (aIndex1);
+  Handle(SelectMgr_SelectableObject) anObject2 = myObjects.FindKey (aIndex2);
+
+  myObjects.Substitute (aIndex1, EMPTY_OBJ);
+  myObjects.Substitute (aIndex2, anObject1);
+  myObjects.Substitute (aIndex1, anObject2);
 }
 
 //=======================================================================
@@ -114,18 +113,16 @@ void SelectMgr_SelectableObjectSet::Swap (const Standard_Integer theIndex1,
 //=======================================================================
 Standard_Integer SelectMgr_SelectableObjectSet::Size() const
 {
-  return myObjectIdxs.Size();
+  return myObjects.Size();
 }
 
 //=======================================================================
 // function : GetObjectById
 // purpose  : Returns object from set by theIndex given
 //=======================================================================
-const Handle(SelectMgr_SelectableObject)& SelectMgr_SelectableObjectSet::GetObjectById
-                                                                         (const Standard_Integer theIndex) const
+const Handle(SelectMgr_SelectableObject)& SelectMgr_SelectableObjectSet::GetObjectById (const Standard_Integer theIndex) const
 {
-  Standard_Integer anIdx = myObjectIdxs.Value (theIndex + 1);
-  return myObjects.Value (anIdx);
+  return myObjects.FindKey (theIndex + 1);
 }
 
 //=======================================================================
@@ -134,11 +131,5 @@ const Handle(SelectMgr_SelectableObject)& SelectMgr_SelectableObjectSet::GetObje
 //=======================================================================
 Standard_Boolean SelectMgr_SelectableObjectSet::Contains (const Handle(SelectMgr_SelectableObject)& theObject) const
 {
-  for (Standard_Integer anObjectIdx = 1; anObjectIdx <= myObjects.Size(); ++anObjectIdx)
-  {
-    if (myObjects.Value (anObjectIdx) == theObject)
-      return Standard_True;
-  }
-
-  return Standard_False;
+  return myObjects.Contains (theObject);
 }
index 2632439..671f996 100644 (file)
 #include <SelectMgr_SelectableObject.hxx>
 #include <SelectMgr_VectorTypes.hxx>
 
+#include <NCollection_IndexedMap.hxx>
+
 //! The purpose of this class is to organize all selectable objects into
 //! data structure, allowing to build BVH tree. For selectable objects
 //! binned BVH builder is used with 32 bins and 1 element per leaf.
 class SelectMgr_SelectableObjectSet : public BVH_PrimitiveSet<Standard_Real, 3>
 {
+  Handle(SelectMgr_SelectableObject) EMPTY_OBJ;
+
 public:
 
   //! Creates new empty objects set and initializes BVH tree
   //! builder to Binned builder with 1 element per list
   SelectMgr_SelectableObjectSet();
 
-  virtual ~SelectMgr_SelectableObjectSet() {};
+  //! Releases resources of selectable object set.
+  virtual ~SelectMgr_SelectableObjectSet() { }
 
   //! Adds new object to the set and marks BVH tree for rebuild
   void Append (const Handle(SelectMgr_SelectableObject)& theObject);
@@ -65,8 +70,7 @@ public:
 
 private:
 
-  NCollection_Sequence<Handle(SelectMgr_SelectableObject)> myObjects;
-  NCollection_Sequence<Standard_Integer>                   myObjectIdxs;
+  NCollection_IndexedMap<Handle(SelectMgr_SelectableObject)> myObjects;
 };
 
 #endif // _SelectMgr_SelectableObjectSet_HeaderFile
index e684515..0c59eee 100644 (file)
@@ -3834,7 +3834,7 @@ static Standard_Integer VConnect (Draw_Interpretor& /*di*/,
 //===============================================================================================
 //function : VConnectTo
 //purpose  : Creates and displays AIS_ConnectedInteractive object from input object and location 
-//Draw arg : vconnectto name Xo Yo Zo object
+//Draw arg : vconnectto name Xo Yo Zo object [-nodisplay|-noupdate|-update]
 //===============================================================================================
 
 static Standard_Integer VConnectTo (Draw_Interpretor& /*di*/, 
@@ -3843,6 +3843,7 @@ static Standard_Integer VConnectTo (Draw_Interpretor& /*di*/,
 {
   // Check the viewer
   Handle(AIS_InteractiveContext) aContext = ViewerTest::GetAISContext();
+  ViewerTest_AutoUpdater anUpdateTool (aContext, ViewerTest::CurrentView());
   if (aContext.IsNull())
   {
     std::cout << "vconnect error : call vinit before\n";
@@ -3925,10 +3926,15 @@ static Standard_Integer VConnectTo (Draw_Interpretor& /*di*/,
     anArg.LowerCase();
     if (anArg == "-nodisplay")
       return 0;
+
+    if (!anUpdateTool.parseRedrawMode (anArg))
+    {
+      std::cout << "Warning! Unknown argument '" << anArg << "' passed, -nodisplay|-noupdate|-update expected at this point.\n";
+    }
   }
 
   // Display connected object
-  TheAISContext()->Display (aConnected);
+  TheAISContext()->Display (aConnected, Standard_False);
 
   return 0;
 }
@@ -6026,7 +6032,7 @@ void ViewerTest::ObjectCommands(Draw_Interpretor& theCommands)
     __FILE__, VConnect, group);
 
   theCommands.Add("vconnectto",
-    "vconnectto : instance_name Xo Yo Zo object [-nodisplay]"
+    "vconnectto : instance_name Xo Yo Zo object [-nodisplay|-noupdate|-update]"
     "  Makes an instance 'instance_name' of 'object' with position (Xo Yo Zo)."
     "\n\t\t:   -nodisplay - only creates interactive object, but not displays it",
     __FILE__, VConnectTo,group);
diff --git a/tests/bugs/vis/bug26029 b/tests/bugs/vis/bug26029
new file mode 100644 (file)
index 0000000..162b5d3
--- /dev/null
@@ -0,0 +1,59 @@
+puts "============"
+puts "0026029: Visualization - Poor performance of connected objects"
+puts "============"
+puts ""
+###########################################################
+# Time spent on computation of large number of connected objects
+# should grow linearly with the number of objects
+###########################################################
+
+pload MODELING VISUALIZATION
+psphere s 0.5
+tclean  s
+incmesh s 0.1
+trinfo  s
+
+vinit View1
+vclear
+vaxo
+#vcaps -vbo 0
+vsetdispmode 1
+vdefaults -defl 1.0 -autoTriang off
+vdisplay s
+
+# disable output of commands
+decho off
+
+set aNb1 100
+
+# display 100x100 connected instances of single presentation
+puts "Creating [expr $aNb1*$aNb1] instances..."
+set t [time {for {set i 0} {$i < $aNb1} {incr i} {for {set j 0} {$j < $aNb1} {incr j} {vconnectto s_${i}_${j} ${i} ${j} 0 s -noupdate}}}]
+set d1 [lindex $t 0]
+puts "Done in $d1 microseconds!\n"
+vclear
+
+set aNb2 200
+
+# display 200x200 connected instances of single presentation
+puts "Creating [expr $aNb2*$aNb2] instances..."
+set t [time {for {set i 0} {$i < $aNb2} {incr i} {for {set j 0} {$j < $aNb2} {incr j} {vconnectto s_${i}_${j} ${i} ${j} 0 s -noupdate}}}]
+set d2 [lindex $t 0]
+puts "Done in $d2 microseconds!\n"
+vclear
+
+# compare two CPU times: the ratio should be quasi-linear
+set expected_ratio [expr double($aNb2 * $aNb2) / double($aNb1 * $aNb1)]
+set actual_ratio [expr double($d2) / double($d1)]
+puts "Comparing CPU time for the two test cases..."
+puts "============================================="
+puts "Expected ratio: $expected_ratio"
+puts "Actual ratio:   $actual_ratio"
+puts "============================================="
+
+# Allow 50% deviation from linear growth
+if {[expr $actual_ratio / $expected_ratio] > 1.5} {
+  puts "Error: non-linear time growth detected!"
+} else {
+  puts "Test passed!"
+}