0025276: Visualization - Lighting is broken if some kinds of transformation applied...
authorduv <duv@opencascade.com>
Fri, 26 Sep 2014 10:41:51 +0000 (14:41 +0400)
committerbugmaster <bugmaster@opencascade.com>
Thu, 16 Oct 2014 10:58:33 +0000 (14:58 +0400)
In order to solve the problem, triangle vertices order is inverted in
mirrored mesh (triangulation). Mesh considered to be mirrored if its
transformation matrix determinant is less than 0.

To handle AIS object mirror transformations "Mirrored" flag stored in
OpenGl_Structure. If this flag is enabled, glFrontFace (GL_CW) applied
before the draw call.

New DRAW commands for visualization level transformations added.

src/Graphic3d/Graphic3d_Structure.cxx
src/OpenGl/OpenGl_Context.cxx
src/OpenGl/OpenGl_Context.hxx
src/OpenGl/OpenGl_Structure.cxx
src/OpenGl/OpenGl_Structure.hxx
src/OpenGl/OpenGl_View_2.cxx
src/StdPrs/StdPrs_ShadedShape.cxx
src/ViewerTest/ViewerTest_ObjectCommands.cxx
tests/bugs/vis/bug25276 [new file with mode: 0644]

index e66e2ec..3ec6316 100644 (file)
@@ -1616,7 +1616,7 @@ void Graphic3d_Structure::SetTransform (const TColStd_Array2OfReal&       theMat
     ReCompute();
   }
 
-  GraphicTransform (aNewTrsf);
+  myCStructure->UpdateTransformation();
   myStructureManager->SetTransform (this, aNewTrsf);
 
   Update();
index a936cd1..54c225e 100644 (file)
@@ -120,6 +120,7 @@ OpenGl_Context::OpenGl_Context (const Handle(OpenGl_Caps)& theCaps)
   myGlVerMinor (0),
   myIsInitialized (Standard_False),
   myIsStereoBuffers (Standard_False),
+  myIsGlNormalizeEnabled (Standard_False),
 #if !defined(GL_ES_VERSION_2_0)
   myRenderMode (GL_RENDER),
 #else
@@ -2126,3 +2127,32 @@ void OpenGl_Context::SetPointSize (const Standard_ShortReal theSize)
   }
 #endif
 }
+
+// =======================================================================
+// function : SetGlNormalizeEnabled
+// purpose  :
+// =======================================================================
+Standard_Boolean OpenGl_Context::SetGlNormalizeEnabled (Standard_Boolean isEnabled)
+{
+  if (isEnabled == myIsGlNormalizeEnabled)
+  {
+    return myIsGlNormalizeEnabled;
+  }
+
+  Standard_Boolean anOldGlNormalize = myIsGlNormalizeEnabled;
+
+  myIsGlNormalizeEnabled = isEnabled;
+
+#if !defined(GL_ES_VERSION_2_0)
+  if (isEnabled)
+  {
+    glEnable (GL_NORMALIZE);
+  }
+  else
+  {
+    glDisable (GL_NORMALIZE);
+  }
+#endif
+
+  return anOldGlNormalize;
+}
index 916502b..c41298a 100644 (file)
@@ -372,6 +372,13 @@ public:
        && !caps->vboDisable;
   }
 
+  //! @return cached state of GL_NORMALIZE.
+  Standard_Boolean IsGlNormalizeEnabled() const { return myIsGlNormalizeEnabled; }
+
+  //! Sets GL_NORMALIZE enabled or disabled.
+  //! @return old value of the flag
+  Standard_EXPORT Standard_Boolean SetGlNormalizeEnabled (Standard_Boolean isEnabled);
+
 public:
 
   //! @return messenger instance
@@ -527,17 +534,19 @@ private: // context info
 
   OpenGl_Clipping myClippingState; //!< state of clip planes
 
-  void*            myGlLibHandle;     //!< optional handle to GL library
+  void*            myGlLibHandle;          //!< optional handle to GL library
   NCollection_Handle<OpenGl_GlFunctions>
-                   myFuncs;           //!< mega structure for all GL functions
-  Standard_Integer myAnisoMax;        //!< maximum level of anisotropy texture filter
-  Standard_Integer myTexClamp;        //!< either GL_CLAMP_TO_EDGE (1.2+) or GL_CLAMP (1.1)
-  Standard_Integer myMaxTexDim;       //!< value for GL_MAX_TEXTURE_SIZE
-  Standard_Integer myMaxClipPlanes;   //!< value for GL_MAX_CLIP_PLANES
-  Standard_Integer myGlVerMajor;      //!< cached GL version major number
-  Standard_Integer myGlVerMinor;      //!< cached GL version minor number
-  Standard_Boolean myIsInitialized;   //!< flag indicates initialization state
-  Standard_Boolean myIsStereoBuffers; //!< context supports stereo buffering
+                   myFuncs;                //!< mega structure for all GL functions
+  Standard_Integer myAnisoMax;             //!< maximum level of anisotropy texture filter
+  Standard_Integer myTexClamp;             //!< either GL_CLAMP_TO_EDGE (1.2+) or GL_CLAMP (1.1)
+  Standard_Integer myMaxTexDim;            //!< value for GL_MAX_TEXTURE_SIZE
+  Standard_Integer myMaxClipPlanes;        //!< value for GL_MAX_CLIP_PLANES
+  Standard_Integer myGlVerMajor;           //!< cached GL version major number
+  Standard_Integer myGlVerMinor;           //!< cached GL version minor number
+  Standard_Boolean myIsInitialized;        //!< flag indicates initialization state
+  Standard_Boolean myIsStereoBuffers;      //!< context supports stereo buffering
+  Standard_Boolean myIsGlNormalizeEnabled; //!< GL_NORMALIZE flag
+                                           //!< Used to tell OpenGl that normals should be normalized
 
   Handle(OpenGl_ShaderManager) myShaderManager; //! support object for managing shader programs
 
index 57f7578..a6ededc 100644 (file)
@@ -147,7 +147,8 @@ OpenGl_Structure::OpenGl_Structure (const Handle(Graphic3d_StructureManager)& th
   myZLayer(0),
   myIsRaytracable (Standard_False),
   myModificationState (0),
-  myIsCulled (Standard_True)
+  myIsCulled (Standard_True),
+  myIsMirrored (Standard_False)
 {
   UpdateNamedStatus();
 }
@@ -195,6 +196,16 @@ void OpenGl_Structure::UpdateTransformation()
     myTransformation = new OpenGl_Matrix();
   }
 
+  Standard_ShortReal (*aMat)[4] = Graphic3d_CStructure::Transformation;
+
+  Standard_ShortReal aDet =
+    aMat[0][0] * (aMat[1][1] * aMat[2][2] - aMat[2][1] * aMat[1][2]) -
+    aMat[0][1] * (aMat[1][0] * aMat[2][2] - aMat[2][0] * aMat[1][2]) +
+    aMat[0][2] * (aMat[1][0] * aMat[2][1] - aMat[2][0] * aMat[1][1]);
+
+  // Determinant of transform matrix less then 0 means that mirror transform applied.
+  myIsMirrored = aDet < 0.0f;
+
   matcpy (myTransformation->mat, &Graphic3d_CStructure::Transformation[0][0]);
 
   if (myIsRaytracable)
@@ -296,11 +307,11 @@ void OpenGl_Structure::clearHighlightBox (const Handle(OpenGl_Context)& theGlCtx
 void OpenGl_Structure::HighlightWithColor (const Graphic3d_Vec3&  theColor,
                                            const Standard_Boolean theToCreate)
 {
-  const Handle(OpenGl_Context)& aCtx = GlDriver()->GetSharedContext();
+  const Handle(OpenGl_Context)& aContext = GlDriver()->GetSharedContext();
   if (theToCreate)
-    setHighlightColor   (aCtx, theColor);
+    setHighlightColor   (aContext, theColor);
   else
-    clearHighlightColor (aCtx);
+    clearHighlightColor (aContext);
 }
 
 // =======================================================================
@@ -310,16 +321,16 @@ void OpenGl_Structure::HighlightWithColor (const Graphic3d_Vec3&  theColor,
 void OpenGl_Structure::HighlightWithBndBox (const Handle(Graphic3d_Structure)& theStruct,
                                             const Standard_Boolean             theToCreate)
 {
-  const Handle(OpenGl_Context)& aCtx = GlDriver()->GetSharedContext();
+  const Handle(OpenGl_Context)& aContext = GlDriver()->GetSharedContext();
   if (!theToCreate)
   {
-    clearHighlightBox (aCtx);
+    clearHighlightBox (aContext);
     return;
   }
 
   if (!myHighlightBox.IsNull())
   {
-    myHighlightBox->Release (aCtx);
+    myHighlightBox->Release (aContext);
   }
   else
   {
@@ -612,13 +623,13 @@ void OpenGl_Structure::Clear (const Handle(OpenGl_Context)& theGlCtx)
 // function : RenderGeometry
 // purpose  :
 // =======================================================================
-void OpenGl_Structure::RenderGeometry (const Handle(OpenGl_Workspace) &AWorkspace) const
+void OpenGl_Structure::RenderGeometry (const Handle(OpenGl_Workspace) &theWorkspace) const
 {
   // Render groups
   const Graphic3d_SequenceOfGroup& aGroups = DrawGroups();
   for (OpenGl_Structure::GroupIterator aGroupIter (aGroups); aGroupIter.More(); aGroupIter.Next())
   {
-    aGroupIter.Value()->Render (AWorkspace);
+    aGroupIter.Value()->Render (theWorkspace);
   }
 }
 
@@ -626,35 +637,50 @@ void OpenGl_Structure::RenderGeometry (const Handle(OpenGl_Workspace) &AWorkspac
 // function : Render
 // purpose  :
 // =======================================================================
-void OpenGl_Structure::Render (const Handle(OpenGl_Workspace) &AWorkspace) const
+void OpenGl_Structure::Render (const Handle(OpenGl_Workspace) &theWorkspace) const
 {
   // Process the structure only if visible
-  if ( myNamedStatus & OPENGL_NS_HIDE )
+  if (myNamedStatus & OPENGL_NS_HIDE)
+  {
     return;
+  }
 
-  const Handle(OpenGl_Context)& aCtx = AWorkspace->GetGlContext();
+  const Handle(OpenGl_Context)& aContext = theWorkspace->GetGlContext();
 
   // Render named status
-  const Standard_Integer named_status = AWorkspace->NamedStatus;
-  AWorkspace->NamedStatus |= myNamedStatus;
+  const Standard_Integer aNamedStatus = theWorkspace->NamedStatus;
+  theWorkspace->NamedStatus |= myNamedStatus;
 
   // Is rendering in ADD or IMMEDIATE mode?
-  const Standard_Boolean isImmediate = (AWorkspace->NamedStatus & OPENGL_NS_IMMEDIATE) != 0;
+  const Standard_Boolean isImmediate = (theWorkspace->NamedStatus & OPENGL_NS_IMMEDIATE) != 0;
+
+  // Do we need to restore GL_NORMALIZE?
+  Standard_Boolean anOldGlNormalize = aContext->IsGlNormalizeEnabled();
 
   // Apply local transformation
-  GLint matrix_mode = 0;
-  const OpenGl_Matrix *local_trsf = NULL;
+  GLint aMatrixMode = 0;
+  const OpenGl_Matrix* aLocalTrsf = NULL;
   if (myTransformation)
   {
   #if !defined(GL_ES_VERSION_2_0)
+
+    Standard_ShortReal aScaleX = OpenGl_Vec3 (myTransformation->mat[0][0],
+                                              myTransformation->mat[0][1],
+                                              myTransformation->mat[0][2]).SquareModulus();
+    // Scale transform detected.
+    if (Abs (aScaleX - 1.f) > Precision::Confusion())
+    {
+      anOldGlNormalize = aContext->SetGlNormalizeEnabled (Standard_True);
+    }
+
     if (isImmediate)
     {
       Tmatrix3 aModelWorld;
       call_util_transpose_mat (*aModelWorld, myTransformation->mat);
 
-      glGetIntegerv (GL_MATRIX_MODE, &matrix_mode);
+      glGetIntegerv (GL_MATRIX_MODE, &aMatrixMode);
 
-      if (!aCtx->ShaderManager()->IsEmpty())
+      if (!aContext->ShaderManager()->IsEmpty())
       {
         Tmatrix3 aWorldView;
         glGetFloatv (GL_MODELVIEW_MATRIX, *aWorldView);
@@ -662,14 +688,14 @@ void OpenGl_Structure::Render (const Handle(OpenGl_Workspace) &AWorkspace) const
         Tmatrix3 aProjection;
         glGetFloatv (GL_PROJECTION_MATRIX, *aProjection);
 
-        aCtx->ShaderManager()->UpdateModelWorldStateTo (&aModelWorld);
-        aCtx->ShaderManager()->UpdateWorldViewStateTo (&aWorldView);
-        aCtx->ShaderManager()->UpdateProjectionStateTo (&aProjection);
+        aContext->ShaderManager()->UpdateModelWorldStateTo (&aModelWorld);
+        aContext->ShaderManager()->UpdateWorldViewStateTo (&aWorldView);
+        aContext->ShaderManager()->UpdateProjectionStateTo (&aProjection);
       }
 
       glMatrixMode (GL_MODELVIEW);
       glPushMatrix ();
-      glScalef (1.F, 1.F, 1.F);
+      glScalef (1.0f, 1.0f, 1.0f);
       glMultMatrixf (*aModelWorld);
     }
     else
@@ -677,58 +703,70 @@ void OpenGl_Structure::Render (const Handle(OpenGl_Workspace) &AWorkspace) const
       glMatrixMode (GL_MODELVIEW);
       glPushMatrix();
 
-      local_trsf = AWorkspace->SetStructureMatrix (myTransformation);
+      aLocalTrsf = theWorkspace->SetStructureMatrix (myTransformation);
     }
   #endif
   }
 
   // Apply transform persistence
-  const TEL_TRANSFORM_PERSISTENCE *trans_pers = NULL;
+  const TEL_TRANSFORM_PERSISTENCE *aTransPersistence = NULL;
   if ( myTransPers && myTransPers->mode != 0 )
   {
-    trans_pers = AWorkspace->ActiveView()->BeginTransformPersistence (aCtx, myTransPers);
+    aTransPersistence = theWorkspace->ActiveView()->BeginTransformPersistence (aContext, myTransPers);
   }
 
   // Apply aspects
-  const OpenGl_AspectLine *aspect_line = AWorkspace->AspectLine(Standard_False);
-  const OpenGl_AspectFace *aspect_face = AWorkspace->AspectFace(Standard_False);
-  const OpenGl_AspectMarker *aspect_marker = AWorkspace->AspectMarker(Standard_False);
-  const OpenGl_AspectText *aspect_text = AWorkspace->AspectText(Standard_False);
+  const OpenGl_AspectLine *anAspectLine = theWorkspace->AspectLine (Standard_False);
+  const OpenGl_AspectFace *anAspectFace = theWorkspace->AspectFace (Standard_False);
+  const OpenGl_AspectMarker *anAspectMarker = theWorkspace->AspectMarker (Standard_False);
+  const OpenGl_AspectText *anAspectText = theWorkspace->AspectText (Standard_False);
   if (myAspectLine)
-    AWorkspace->SetAspectLine(myAspectLine);
+  {
+    theWorkspace->SetAspectLine (myAspectLine);
+  }
   if (myAspectFace)
-    AWorkspace->SetAspectFace(myAspectFace);
+  {
+    theWorkspace->SetAspectFace (myAspectFace);
+  }
   if (myAspectMarker)
-    AWorkspace->SetAspectMarker(myAspectMarker);
+  {
+    theWorkspace->SetAspectMarker (myAspectMarker);
+  }
   if (myAspectText)
-    AWorkspace->SetAspectText(myAspectText);
+  {
+    theWorkspace->SetAspectText (myAspectText);
+  }
+
+  // Apply correction for mirror transform
+  if (myIsMirrored)
+  {
+    glFrontFace (GL_CW);
+  }
 
   // Apply highlight color
-  const TEL_COLOUR *highlight_color = AWorkspace->HighlightColor;
+  const TEL_COLOUR *aHighlightColor = theWorkspace->HighlightColor;
   if (myHighlightColor)
-    AWorkspace->HighlightColor = myHighlightColor;
+    theWorkspace->HighlightColor = myHighlightColor;
 
   // Render connected structures
-  OpenGl_ListOfStructure::Iterator its(myConnected);
-  while (its.More())
+  OpenGl_ListOfStructure::Iterator anIter (myConnected);
+  while (anIter.More())
   {
-    its.Value()->RenderGeometry (AWorkspace);
-    its.Next();
+    anIter.Value()->RenderGeometry (theWorkspace);
+    anIter.Next();
   }
 
   // Set up plane equations for non-structure transformed global model-view matrix
-  const Handle(OpenGl_Context)& aContext = AWorkspace->GetGlContext();
-
   // List of planes to be applied to context state
   Handle(Graphic3d_SequenceOfHClipPlane) aUserPlanes;
 
   // Collect clipping planes of structure scope
   if (!myClipPlanes.IsEmpty())
   {
-    Graphic3d_SequenceOfHClipPlane::Iterator aClippingIt (myClipPlanes);
-    for (; aClippingIt.More(); aClippingIt.Next())
+    Graphic3d_SequenceOfHClipPlane::Iterator aClippingIter (myClipPlanes);
+    for (; aClippingIter.More(); aClippingIter.Next())
     {
-      const Handle(Graphic3d_ClipPlane)& aClipPlane = aClippingIt.Value();
+      const Handle(Graphic3d_ClipPlane)& aClipPlane = aClippingIter.Value();
       if (!aClipPlane->IsOn())
       {
         continue;
@@ -746,7 +784,7 @@ void OpenGl_Structure::Render (const Handle(OpenGl_Workspace) &AWorkspace) const
   if (!aUserPlanes.IsNull() && !aUserPlanes->IsEmpty())
   {
     // add planes at loaded view matrix state
-    aContext->ChangeClipping().AddWorld (*aUserPlanes, AWorkspace);
+    aContext->ChangeClipping().AddWorld (*aUserPlanes, theWorkspace);
 
     // Set OCCT state uniform variables
     if (!aContext->ShaderManager()->IsEmpty())
@@ -759,13 +797,17 @@ void OpenGl_Structure::Render (const Handle(OpenGl_Workspace) &AWorkspace) const
   const Graphic3d_SequenceOfGroup& aGroups = DrawGroups();
   for (OpenGl_Structure::GroupIterator aGroupIter (aGroups); aGroupIter.More(); aGroupIter.Next())
   {
-    aGroupIter.Value()->Render (AWorkspace);
+    aGroupIter.Value()->Render (theWorkspace);
   }
 
+  // Reset correction for mirror transform
+  if (myIsMirrored)
+    glFrontFace (GL_CCW); // default
+
   // Render capping for structure groups
   if (!aContext->Clipping().Planes().IsEmpty())
   {
-    OpenGl_CappingAlgo::RenderCapping (AWorkspace, aGroups);
+    OpenGl_CappingAlgo::RenderCapping (theWorkspace, aGroups);
   }
 
   // Revert structure clippings
@@ -781,28 +823,31 @@ void OpenGl_Structure::Render (const Handle(OpenGl_Workspace) &AWorkspace) const
   }
 
   // Restore highlight color
-  AWorkspace->HighlightColor = highlight_color;
+  theWorkspace->HighlightColor = aHighlightColor;
 
   // Restore aspects
-  AWorkspace->SetAspectLine(aspect_line);
-  AWorkspace->SetAspectFace(aspect_face);
-  AWorkspace->SetAspectMarker(aspect_marker);
-  AWorkspace->SetAspectText(aspect_text);
+  theWorkspace->SetAspectLine (anAspectLine);
+  theWorkspace->SetAspectFace (anAspectFace);
+  theWorkspace->SetAspectMarker (anAspectMarker);
+  theWorkspace->SetAspectText (anAspectText);
 
   // Restore transform persistence
   if ( myTransPers && myTransPers->mode != 0 )
   {
-    AWorkspace->ActiveView()->BeginTransformPersistence (aContext, trans_pers);
+    theWorkspace->ActiveView()->BeginTransformPersistence (aContext, aTransPersistence);
   }
 
   // Restore local transformation
   if (myTransformation)
   {
   #if !defined(GL_ES_VERSION_2_0)
+
+    aContext->SetGlNormalizeEnabled (anOldGlNormalize);
+
     if (isImmediate)
     {
       glPopMatrix ();
-      glMatrixMode (matrix_mode);
+      glMatrixMode (aMatrixMode);
 
       Tmatrix3 aModelWorldState = { { 1.f, 0.f, 0.f, 0.f },
                                     { 0.f, 1.f, 0.f, 0.f },
@@ -813,7 +858,7 @@ void OpenGl_Structure::Render (const Handle(OpenGl_Workspace) &AWorkspace) const
     }
     else
     {
-      AWorkspace->SetStructureMatrix (local_trsf, true);
+      theWorkspace->SetStructureMatrix (aLocalTrsf, true);
 
       glMatrixMode (GL_MODELVIEW);
       glPopMatrix();
@@ -824,11 +869,11 @@ void OpenGl_Structure::Render (const Handle(OpenGl_Workspace) &AWorkspace) const
   // Apply highlight box
   if (!myHighlightBox.IsNull())
   {
-    myHighlightBox->Render (AWorkspace);
+    myHighlightBox->Render (theWorkspace);
   }
 
   // Restore named status
-  AWorkspace->NamedStatus = named_status;
+  theWorkspace->NamedStatus = aNamedStatus;
 }
 
 // =======================================================================
index d603cea..24ce516 100644 (file)
@@ -250,6 +250,8 @@ protected:
 
   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.
+
 public:
 
   DEFINE_STANDARD_RTTI(OpenGl_Structure) // Type definition
index f346cb4..91164cc 100644 (file)
@@ -483,12 +483,16 @@ void OpenGl_View::Render (const Handle(OpenGl_PrinterContext)& thePrintContext,
   // if the view is scaled normal vectors are scaled to unit
   // length for correct displaying of shaded objects
   const gp_Pnt anAxialScale = myCamera->AxialScale();
-  if(anAxialScale.X() != 1.F ||
-     anAxialScale.Y() != 1.F ||
-     anAxialScale.Z() != 1.F)
-    glEnable(GL_NORMALIZE);
-  else if(glIsEnabled(GL_NORMALIZE))
-    glDisable(GL_NORMALIZE);
+  if (anAxialScale.X() != 1.F ||
+      anAxialScale.Y() != 1.F ||
+      anAxialScale.Z() != 1.F)
+  {
+    aContext->SetGlNormalizeEnabled (Standard_True);
+  }
+  else
+  {
+    aContext->SetGlNormalizeEnabled (Standard_False);
+  }
 
   // Apply Fog
   if ( myFog.IsOn )
index cfbb5eb..160417b 100644 (file)
@@ -149,6 +149,10 @@ namespace
         continue;
       }
       const gp_Trsf& aTrsf = aLoc.Transformation();
+
+      // Determinant of transform matrix less then 0 means that mirror transform applied.
+      Standard_Boolean isMirrored = aTrsf.VectorialPart().Determinant() < 0;
+
       Poly_Connect aPolyConnect (aT);
       // Extracts vertices & normals from nodes
       const TColgp_Array1OfPnt&   aNodes   = aT->Nodes();
@@ -170,7 +174,8 @@ namespace
         if (!aLoc.IsIdentity())
         {
           aPoint.Transform (aTrsf);
-          aNormals (aNodeIter).Transform (aTrsf);
+
+          aNormals (aNodeIter) = aNormals (aNodeIter).Transformed (aTrsf);
         }
 
         if (theHasTexels && aUVNodes.Upper() == aNodes.Upper())
@@ -190,7 +195,7 @@ namespace
       Standard_Integer anIndex[3];
       for (Standard_Integer aTriIter = 1; aTriIter <= aT->NbTriangles(); ++aTriIter)
       {
-        if (aFace.Orientation() == TopAbs_REVERSED)
+        if ((aFace.Orientation() == TopAbs_REVERSED) ^ isMirrored)
         {
           aTriangles (aTriIter).Get (anIndex[0], anIndex[2], anIndex[1]);
         }
index 222e88d..7248b4b 100644 (file)
@@ -3542,6 +3542,166 @@ static Standard_Integer VSetLocation (Draw_Interpretor& /*di*/,
   return 0;
 }
 
+//=======================================================================
+//function : TransformPresentation
+//purpose  : Change transformation of AIS interactive object
+//=======================================================================
+static Standard_Integer LocalTransformPresentation (Draw_Interpretor& /*theDi*/,
+                                                    Standard_Integer theArgNb,
+                                                    const char** theArgVec)
+{
+  if (theArgNb <= 1)
+  {
+    std::cout << "Error: too few arguments.\n";
+    return 1;
+  }
+
+  Handle(AIS_InteractiveContext) aContext = ViewerTest::GetAISContext();
+  ViewerTest_AutoUpdater anUpdateTool(aContext, ViewerTest::CurrentView());
+  if (aContext.IsNull())
+  {
+    std::cout << "Error: no active view!\n";
+    return 1;
+  }
+
+  gp_Trsf aTrsf;
+  Standard_Integer aLast = theArgNb;
+  const char* aName = theArgVec[0];
+
+  Standard_Boolean isReset = Standard_False;
+  Standard_Boolean isMove = Standard_False;
+
+  // Prefix 'vloc'
+  aName += 4;
+
+  if (!strcmp (aName, "reset"))
+  {
+    isReset = Standard_True;
+  }
+  else if (!strcmp (aName, "move"))
+  {
+    if (theArgNb < 3)
+    {
+      std::cout << "Error: too few arguments.\n";
+      return 1;
+    }
+
+    const ViewerTest_DoubleMapOfInteractiveAndName& aMap = GetMapOfAIS();
+
+    Handle(AIS_InteractiveObject) anIObj;
+    if (aMap.IsBound2 (theArgVec[theArgNb - 1]))
+    {
+      anIObj = Handle(AIS_InteractiveObject)::DownCast (aMap.Find2 (theArgVec[theArgNb - 1]));
+    }
+
+    if (anIObj.IsNull())
+    {
+      std::cout << "Error: object '" << theArgVec[theArgNb - 1] << "' is not displayed!\n";
+      return 1;
+    }
+
+    isMove = Standard_True;
+
+    aTrsf = anIObj->Transformation();
+    aLast = theArgNb - 1;
+  }
+  else if (!strcmp (aName, "translate"))
+  {
+    if (theArgNb < 5)
+    {
+      std::cout << "Error: too few arguments.\n";
+      return 1;
+    }
+    aTrsf.SetTranslation (gp_Vec (Draw::Atof (theArgVec[theArgNb - 3]),
+                                  Draw::Atof (theArgVec[theArgNb - 2]),
+                                  Draw::Atof (theArgVec[theArgNb - 1])));
+    aLast = theArgNb - 3;
+  }
+  else if (!strcmp (aName, "rotate"))
+  {
+    if (theArgNb < 9)
+    {
+      std::cout << "Error: too few arguments.\n";
+      return 1;
+    }
+
+    aTrsf.SetRotation (
+      gp_Ax1 (gp_Pnt (Draw::Atof (theArgVec[theArgNb - 7]),
+                      Draw::Atof (theArgVec[theArgNb - 6]),
+                      Draw::Atof (theArgVec[theArgNb - 5])),
+              gp_Vec (Draw::Atof (theArgVec[theArgNb - 4]),
+                      Draw::Atof (theArgVec[theArgNb - 3]),
+                      Draw::Atof (theArgVec[theArgNb - 2]))),
+      Draw::Atof (theArgVec[theArgNb - 1]) * (M_PI / 180.0));
+
+    aLast = theArgNb - 7;
+  }
+  else if (!strcmp (aName, "mirror"))
+  {
+    if (theArgNb < 8)
+    {
+      std::cout << "Error: too few arguments.\n";
+      return 1;
+    }
+
+    aTrsf.SetMirror (gp_Ax2 (gp_Pnt (Draw::Atof(theArgVec[theArgNb - 6]),
+                                     Draw::Atof(theArgVec[theArgNb - 5]),
+                                     Draw::Atof(theArgVec[theArgNb - 4])),
+                             gp_Vec (Draw::Atof(theArgVec[theArgNb - 3]),
+                                     Draw::Atof(theArgVec[theArgNb - 2]),
+                                     Draw::Atof(theArgVec[theArgNb - 1]))));
+    aLast = theArgNb - 6;
+  }
+  else if (!strcmp (aName, "scale"))
+  {
+    if (theArgNb < 6)
+    {
+      std::cout << "Error: too few arguments.\n";
+      return 1;
+    }
+
+    aTrsf.SetScale (gp_Pnt (Draw::Atof(theArgVec[theArgNb - 4]),
+                            Draw::Atof(theArgVec[theArgNb - 3]),
+                            Draw::Atof(theArgVec[theArgNb - 2])),
+                    Draw::Atof(theArgVec[theArgNb - 1]));
+    aLast = theArgNb - 4;
+  }
+
+  for (Standard_Integer anIdx = 1; anIdx < aLast; anIdx++)
+  {
+    // find object
+    const ViewerTest_DoubleMapOfInteractiveAndName& aMap = GetMapOfAIS();
+    Handle(AIS_InteractiveObject) anIObj;
+    if (aMap.IsBound2 (theArgVec[anIdx]))
+    {
+      anIObj = Handle(AIS_InteractiveObject)::DownCast (aMap.Find2 (theArgVec[anIdx]));
+    }
+    if (anIObj.IsNull())
+    {
+      std::cout << "Error: object '" << theArgVec[anIdx] << "' is not displayed!\n";
+      return 1;
+    }
+    
+    if (isReset)
+    {
+      // aTrsf already identity
+    }
+    else if (isMove)
+    {
+      aTrsf = anIObj->LocalTransformation() * anIObj->Transformation().Inverted() * aTrsf;
+    }
+    else
+    {
+      aTrsf = anIObj->LocalTransformation() * aTrsf;
+    }
+
+    TopLoc_Location aLocation (aTrsf);
+    aContext->SetLocation (anIObj, aLocation);
+  }
+
+  return 0;
+}
+
 //===============================================================================================
 //function : VConnect
 //purpose  : Creates and displays AIS_ConnectedInteractive object from input object and location 
@@ -5818,4 +5978,34 @@ void ViewerTest::ObjectCommands(Draw_Interpretor& theCommands)
                    "\n\t\t:  -noNormals - do not generate normal per point"
                    "\n",
                    __FILE__, VPointCloud, group);
+
+  theCommands.Add("vlocreset",
+    "vlocreset name1 name2 ...\n\t\t  remove object local transformation",
+    __FILE__,
+    LocalTransformPresentation, group);
+
+  theCommands.Add("vlocmove",
+    "vlocmove name1 name2 ... name\n\t\t  set local transform to match transform of 'name'",
+    __FILE__,
+    LocalTransformPresentation, group);
+
+  theCommands.Add("vloctranslate",
+    "vloctranslate name1 name2 ... dx dy dz\n\t\t  applies translation to local transformation",
+    __FILE__,
+    LocalTransformPresentation, group);
+
+  theCommands.Add("vlocrotate",
+    "vlocrotate name1 name2 ... x y z dx dy dz angle\n\t\t  applies rotation to local transformation",
+    __FILE__,
+    LocalTransformPresentation, group);
+
+  theCommands.Add("vlocmirror",
+    "vlocmirror name x y z dx dy dz\n\t\t  applies mirror to local transformation",
+    __FILE__,
+    LocalTransformPresentation, group);
+
+  theCommands.Add("vlocscale",
+    "vlocscale name x y z scale\n\t\t  applies scale to local transformation",
+    __FILE__,
+    LocalTransformPresentation, group);
 }
diff --git a/tests/bugs/vis/bug25276 b/tests/bugs/vis/bug25276
new file mode 100644 (file)
index 0000000..20c19db
--- /dev/null
@@ -0,0 +1,40 @@
+puts "============"
+puts "OCC25276"
+puts "============"
+puts ""
+#######################################################################
+# Visualization - Lighting is broken if some kinds of transformation applied to a shape
+#######################################################################
+
+pload ALL
+vinit
+box b1 1 6 1
+vsetdispmode 1
+vdisplay b1
+vconnectto b2 6 0 0 b1
+box b3 7 1 1
+vdisplay b3
+vloctranslate b3 0 4 0
+vconnect z 0 0 0 b1 b2 b3
+
+vconnect z1 0 0 0 z
+vloctranslate z1 10 0 0
+
+vconnect z2 0 10 0 z
+vlocrotate z2 0 0 0 1 0 0 90
+
+vconnect z3 -10 0 0 z
+vlocscale z3 0 0 0 0.5
+
+vconnect z4 0 0 0 z
+vlocmove z4 z3
+
+psphere sp 3
+vdisplay sp
+vlocmove sp z3
+vlocreset sp
+
+vlocmirror z 0 -0.5 0 0 1 0
+vfit
+
+vdump $imagedir/${casename}.png