// Created on: 1998-09-01
// Created by: Robert COUBLANC
// Copyright (c) 1998-1999 Matra Datavision
// Copyright (c) 1999-2014 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.

#if defined(_WIN32)
  #include <windows.h>
#endif

#include <ViewerTest.hxx>

#include <Aspect_CircularGrid.hxx>
#include <Aspect_GridParams.hxx>
#include <AIS_AnimationAxisRotation.hxx>
#include <AIS_AnimationCamera.hxx>
#include <AIS_AnimationObject.hxx>
#include <AIS_Axis.hxx>
#include <AIS_CameraFrustum.hxx>
#include <AIS_ColorScale.hxx>
#include <AIS_InteractiveContext.hxx>
#include <AIS_LightSource.hxx>
#include <AIS_InteractiveObject.hxx>
#include <NCollection_LinearVector.hxx>
#include <NCollection_List.hxx>
#include <AIS_Manipulator.hxx>
#include <AIS_ViewCube.hxx>
#include <AIS_Shape.hxx>
#include <AIS_Point.hxx>
#include <Aspect_DisplayConnection.hxx>
#include <Aspect_Grid.hxx>
#include <Aspect_TypeOfLine.hxx>
#include <Draw_Appli.hxx>
#include <Draw_Interpretor.hxx>
#include <Draw_ProgressIndicator.hxx>
#include <gp_Dir.hxx>
#include <gp_Pln.hxx>
#include <gp_Pnt.hxx>
#include <Geom_Axis2Placement.hxx>
#include <Geom_CartesianPoint.hxx>
#include <Graphic3d_ArrayOfPolylines.hxx>
#include <Graphic3d_AspectFillArea3d.hxx>
#include <Graphic3d_ClipPlane.hxx>
#include <Graphic3d_CubeMapPacked.hxx>
#include <Graphic3d_CubeMapSeparate.hxx>
#include <Graphic3d_GraduatedTrihedron.hxx>
#include <Graphic3d_GraphicDriver.hxx>
#include <Graphic3d_GraphicDriverFactory.hxx>
#include <Graphic3d_NameOfTextureEnv.hxx>
#include <Graphic3d_Texture2D.hxx>
#include <Graphic3d_TextureEnv.hxx>
#include <Graphic3d_TextureParams.hxx>
#include <Graphic3d_TypeOfTextureFilter.hxx>
#include <Image_AlienPixMap.hxx>
#include <Image_Diff.hxx>
#include <Image_VideoRecorder.hxx>
#include <Message.hxx>
#include <Message_ProgressScope.hxx>
#include <NCollection_DataMap.hxx>
#include <NCollection_LocalArray.hxx>
#include <OSD_Parallel.hxx>
#include <OSD_Timer.hxx>
#include <Prs3d_ShadingAspect.hxx>
#include <Prs3d_DatumAspect.hxx>
#include <Prs3d_Drawer.hxx>
#include <Prs3d_LineAspect.hxx>
#include <Prs3d_Text.hxx>
#include <Select3D_SensitivePrimitiveArray.hxx>
#include <TCollection_AsciiString.hxx>
#include <NCollection_Sequence.hxx>
#include <NCollection_HSequence.hxx>
#include <Standard_Integer.hxx>
#include <ViewerTest_AutoUpdater.hxx>
#include <ViewerTest_ContinuousRedrawer.hxx>
#include <ViewerTest_EventManager.hxx>
#include <Standard_Transient.hxx>
#include <NCollection_DoubleMap.hxx>
#include <ViewerTest_CmdParser.hxx>
#include <ViewerTest_V3dView.hxx>
#include <V3d_AmbientLight.hxx>
#include <V3d_DirectionalLight.hxx>
#include <V3d_PositionalLight.hxx>
#include <V3d_SpotLight.hxx>
#include <V3d_Trihedron.hxx>
#include <V3d_Viewer.hxx>
#include <UnitsAPI.hxx>

#include <tcl.h>

#if defined(_WIN32)
  #include <WNT_WClass.hxx>
  #include <WNT_Window.hxx>
#elif defined(HAVE_XLIB)
  #include <Xw_Window.hxx>
  #include <X11/Xlib.h>
  #include <X11/Xutil.h>
#elif defined(__APPLE__)
  #include <Cocoa_Window.hxx>
#elif defined(__EMSCRIPTEN__)
  #include <Wasm_Window.hxx>
  #include <emscripten/emscripten.h>
#else
  #include <Aspect_NeutralWindow.hxx>

class AIS_InteractiveObject;
#endif

//==============================================================================
//  VIEWER GLOBAL VARIABLES
//==============================================================================

Standard_IMPORT bool Draw_VirtualWindows;
Standard_IMPORT bool Draw_Interprete(const char* theCommand);

Standard_EXPORT int ViewerMainLoop(int, const char** argv);
extern NCollection_DoubleMap<occ::handle<AIS_InteractiveObject>, TCollection_AsciiString>&
  GetMapOfAIS();

#if defined(_WIN32)
typedef WNT_Window ViewerTest_Window;
#elif defined(HAVE_XLIB)
typedef Xw_Window ViewerTest_Window;
static void       VProcessEvents(ClientData, int);
#elif defined(__APPLE__)
typedef Cocoa_Window ViewerTest_Window;
extern void ViewerTest_SetCocoaEventManagerView(const occ::handle<Cocoa_Window>& theWindow);
extern void GetCocoaScreenResolution(int& theWidth, int& theHeight);
#elif defined(__EMSCRIPTEN__)
typedef Wasm_Window ViewerTest_Window;
#else
typedef Aspect_NeutralWindow ViewerTest_Window;
#endif

#if defined(__EMSCRIPTEN__)
  #if defined(_LP64)
EM_JS(char*, occJSNumberToPtr, (double thePtr), { return BigInt(thePtr); });
  #else
EM_JS(char*, occJSNumberToPtr, (double thePtr), { return thePtr; });
  #endif

//! Return DOM id of default WebGL canvas from Module.canvas.
EM_JS(char*, occJSModuleCanvasId, (), {
  const aCanvasId = Module.canvas.id;
  const aNbBytes  = lengthBytesUTF8(aCanvasId) + 1;
  const aStrPtr   = Module._malloc(aNbBytes);
  stringToUTF8(aCanvasId, aStrPtr, aNbBytes);
  return occJSNumberToPtr(aStrPtr);
});

//! Return DOM id of default WebGL canvas from Module.canvas.
static TCollection_AsciiString getModuleCanvasId()
{
  char*                   aRawId = occJSModuleCanvasId();
  TCollection_AsciiString anId(aRawId != NULL ? aRawId : "");
  free(aRawId);
  return anId;
}
#endif

namespace
{
static occ::handle<ViewerTest_Window>& VT_GetWindow()
{
  static occ::handle<ViewerTest_Window> aWindow;
  return aWindow;
}

static occ::handle<Aspect_DisplayConnection>& GetDisplayConnection()
{
  static occ::handle<Aspect_DisplayConnection> aDisplayConnection;
  return aDisplayConnection;
}

using ViewerTest_ViewerCommandsViewMap =
  NCollection_DoubleMap<TCollection_AsciiString, occ::handle<V3d_View>>;
using ViewerTest_ViewerCommandsInteractiveContextMap =
  NCollection_DoubleMap<TCollection_AsciiString, occ::handle<AIS_InteractiveContext>>;
using ViewerTest_ViewerCommandsGraphicDriverMap =
  NCollection_DoubleMap<TCollection_AsciiString, occ::handle<Graphic3d_GraphicDriver>>;

static void SetDisplayConnection(const occ::handle<Aspect_DisplayConnection>& theDisplayConnection)
{
  GetDisplayConnection() = theDisplayConnection;
}

static ViewerTest_ViewerCommandsInteractiveContextMap ViewerTest_myContexts;
static ViewerTest_ViewerCommandsGraphicDriverMap      ViewerTest_myDrivers;
} // namespace

ViewerTest_ViewerCommandsViewMap ViewerTest_myViews;

static struct
{
  Quantity_Color            FlatColor;
  Quantity_Color            GradientColor1;
  Quantity_Color            GradientColor2;
  Aspect_GradientFillMethod FillMethod;

  //! Sets the gradient filling for a background in a default viewer.
  void SetDefaultGradient()
  {
    for (ViewerTest_ViewerCommandsInteractiveContextMap::Iterator aCtxIter(ViewerTest_myContexts);
         aCtxIter.More();
         aCtxIter.Next())
    {
      const occ::handle<V3d_Viewer>& aViewer = aCtxIter.Key2()->CurrentViewer();
      aViewer->SetDefaultBgGradientColors(GradientColor1, GradientColor2, FillMethod);
    }
  }

  //! Sets the color used for filling a background in a default viewer.
  void SetDefaultColor()
  {
    for (ViewerTest_ViewerCommandsInteractiveContextMap::Iterator aCtxIter(ViewerTest_myContexts);
         aCtxIter.More();
         aCtxIter.Next())
    {
      const occ::handle<V3d_Viewer>& aViewer = aCtxIter.Key2()->CurrentViewer();
      aViewer->SetDefaultBackgroundColor(FlatColor);
    }
  }

} ViewerTest_DefaultBackground = {Quantity_NOC_BLACK,
                                  Quantity_NOC_BLACK,
                                  Quantity_NOC_BLACK,
                                  Aspect_GradientFillMethod_None};

//==============================================================================
//  EVENT GLOBAL VARIABLES
//==============================================================================

#ifdef _WIN32
static LRESULT WINAPI AdvViewerWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
#endif

//=================================================================================================

const occ::handle<WNT_WClass>& ViewerTest::WClass()
{
  static occ::handle<WNT_WClass> theWClass;
#if defined(_WIN32)
  if (theWClass.IsNull())
  {
    theWClass = new WNT_WClass("GW3D_Class",
                               (void*)AdvViewerWindowProc,
                               CS_VREDRAW | CS_HREDRAW,
                               0,
                               0,
                               ::LoadCursor(NULL, IDC_ARROW));
  }
#endif
  return theWClass;
}

//==============================================================================
// function : CreateName
// purpose  : Create numerical name for new object in theMap
//==============================================================================
template <typename ObjectType>
TCollection_AsciiString CreateName(
  const NCollection_DoubleMap<TCollection_AsciiString, ObjectType>& theObjectMap,
  const TCollection_AsciiString&                                    theDefaultString)
{
  if (theObjectMap.IsEmpty())
  {
    return theDefaultString + TCollection_AsciiString(1);
  }

  int  aNextKey = 1;
  bool isFound  = false;
  while (!isFound)
  {
    TCollection_AsciiString aStringKey = theDefaultString + TCollection_AsciiString(aNextKey);
    // Look for objects with default names
    if (theObjectMap.IsBound1(aStringKey))
    {
      aNextKey++;
    }
    else
    {
      isFound = true;
    }
  }

  return theDefaultString + TCollection_AsciiString(aNextKey);
}

//==============================================================================
// structure : ViewerTest_Names
// purpose   : Allow to operate with full view name: driverName/viewerName/viewName
//==============================================================================
struct ViewerTest_Names
{
private:
  TCollection_AsciiString myDriverName;
  TCollection_AsciiString myViewerName;
  TCollection_AsciiString myViewName;

public:
  const TCollection_AsciiString& GetDriverName() const { return myDriverName; }

  void SetDriverName(const TCollection_AsciiString& theDriverName) { myDriverName = theDriverName; }

  const TCollection_AsciiString& GetViewerName() const { return myViewerName; }

  void SetViewerName(const TCollection_AsciiString& theViewerName) { myViewerName = theViewerName; }

  const TCollection_AsciiString& GetViewName() const { return myViewName; }

  void SetViewName(const TCollection_AsciiString& theViewName) { myViewName = theViewName; }

  //===========================================================================
  // function : Constructor for ViewerTest_Names
  // purpose  : Get view, viewer, driver names from custom string
  //===========================================================================

  ViewerTest_Names(const TCollection_AsciiString& theInputString)
  {
    TCollection_AsciiString aName(theInputString);
    if (theInputString.IsEmpty())
    {
      // Get current configuration
      if (ViewerTest_myDrivers.IsEmpty())
      {
        myDriverName =
          CreateName<occ::handle<Graphic3d_GraphicDriver>>(ViewerTest_myDrivers,
                                                           TCollection_AsciiString("Driver"));
      }
      else
      {
        myDriverName =
          ViewerTest_myDrivers.Find2(ViewerTest::GetAISContext()->CurrentViewer()->Driver());
      }

      if (ViewerTest_myContexts.IsEmpty())
      {
        myViewerName = CreateName<occ::handle<AIS_InteractiveContext>>(
          ViewerTest_myContexts,
          TCollection_AsciiString(myDriverName + "/Viewer"));
      }
      else
      {
        myViewerName = ViewerTest_myContexts.Find2(ViewerTest::GetAISContext());
      }

      myViewName =
        CreateName<occ::handle<V3d_View>>(ViewerTest_myViews,
                                          TCollection_AsciiString(myViewerName + "/View"));
    }
    else
    {
      // There is at least view name
      int aParserNumber = 0;
      for (int i = 0; i < 3; ++i)
      {
        int aParserPos = aName.SearchFromEnd("/");
        if (aParserPos != -1)
        {
          aParserNumber++;
          aName.Split(aParserPos - 1);
        }
        else
        {
          break;
        }
      }
      if (aParserNumber == 0)
      {
        // Only view name
        if (!ViewerTest::GetAISContext().IsNull())
        {
          myDriverName =
            ViewerTest_myDrivers.Find2(ViewerTest::GetAISContext()->CurrentViewer()->Driver());
          myViewerName = ViewerTest_myContexts.Find2(ViewerTest::GetAISContext());
        }
        else
        {
          // There is no opened contexts here, need to create names for viewer and driver
          myDriverName =
            CreateName<occ::handle<Graphic3d_GraphicDriver>>(ViewerTest_myDrivers,
                                                             TCollection_AsciiString("Driver"));

          myViewerName = CreateName<occ::handle<AIS_InteractiveContext>>(
            ViewerTest_myContexts,
            TCollection_AsciiString(myDriverName + "/Viewer"));
        }
        myViewName = TCollection_AsciiString(myViewerName + "/" + theInputString);
      }
      else if (aParserNumber == 1)
      {
        // Here is viewerName/viewName
        if (!ViewerTest::GetAISContext().IsNull())
        {
          myDriverName =
            ViewerTest_myDrivers.Find2(ViewerTest::GetAISContext()->CurrentViewer()->Driver());
        }
        else
        {
          // There is no opened contexts here, need to create name for driver
          myDriverName =
            CreateName<occ::handle<Graphic3d_GraphicDriver>>(ViewerTest_myDrivers,
                                                             TCollection_AsciiString("Driver"));
        }
        myViewerName = TCollection_AsciiString(myDriverName + "/" + aName);

        myViewName = TCollection_AsciiString(myDriverName + "/" + theInputString);
      }
      else
      {
        // Here is driverName/viewerName/viewName
        myDriverName = TCollection_AsciiString(aName);

        TCollection_AsciiString aViewerName(theInputString);
        aViewerName.Split(aViewerName.SearchFromEnd("/") - 1);
        myViewerName = TCollection_AsciiString(aViewerName);

        myViewName = TCollection_AsciiString(theInputString);
      }
    }
  }
};

//==============================================================================
// function : FindContextByView
// purpose  : Find AIS_InteractiveContext by View
//==============================================================================

occ::handle<AIS_InteractiveContext> FindContextByView(const occ::handle<V3d_View>& theView)
{
  occ::handle<AIS_InteractiveContext> anAISContext;

  for (ViewerTest_ViewerCommandsInteractiveContextMap::Iterator anIter(ViewerTest_myContexts);
       anIter.More();
       anIter.Next())
  {
    if (anIter.Key2()->CurrentViewer() == theView->Viewer())
    {
      return anIter.Key2();
    }
  }
  return anAISContext;
}

//==============================================================================
// function : IsWindowOverlapped
// purpose  : Check if theWindow overlap another view
//==============================================================================

bool IsWindowOverlapped(const int                thePxLeft,
                        const int                thePxTop,
                        const int                thePxRight,
                        const int                thePxBottom,
                        TCollection_AsciiString& theViewId)
{
  for (ViewerTest_ViewerCommandsViewMap::Iterator anIter(ViewerTest_myViews); anIter.More();
       anIter.Next())
  {
    int aTop = 0, aLeft = 0, aRight = 0, aBottom = 0;
    anIter.Key2()->Window()->Position(aLeft, aTop, aRight, aBottom);
    if ((thePxLeft >= aLeft && thePxLeft <= aRight && thePxTop >= aTop && thePxTop <= aBottom)
        || (thePxLeft >= aLeft && thePxLeft <= aRight && thePxBottom >= aTop
            && thePxBottom <= aBottom)
        || (thePxRight >= aLeft && thePxRight <= aRight && thePxTop >= aTop && thePxTop <= aBottom)
        || (thePxRight >= aLeft && thePxRight <= aRight && thePxBottom >= aTop
            && thePxBottom <= aBottom))
    {
      theViewId = anIter.Key1();
      return true;
    }
  }
  return false;
}

// Workaround: to create and delete non-orthographic views outside ViewerTest
void ViewerTest::RemoveViewName(const TCollection_AsciiString& theName)
{
  ViewerTest_myViews.UnBind1(theName);
}

void ViewerTest::InitViewName(const TCollection_AsciiString& theName,
                              const occ::handle<V3d_View>&   theView)
{
  ViewerTest_myViews.Bind(theName, theView);
}

TCollection_AsciiString ViewerTest::GetCurrentViewName()
{
  return ViewerTest_myViews.Find2(ViewerTest::CurrentView());
}

//==============================================================================
// function : ViewerInit
// purpose  : Create the window viewer and initialize all the global variable
//==============================================================================

TCollection_AsciiString ViewerTest::ViewerInit(const ViewerTest_VinitParams& theParams)
{
  // Default position and dimension of the viewer window.
  // Note that left top corner is set to be sufficiently small to have
  // window fit in the small screens (actual for remote desktops, see #23003).
  // The position corresponds to the window's client area, thus some
  // gap is added for window frame to be visible.
  NCollection_Vec2<double> aPxTopLeft(20, 40);
  NCollection_Vec2<double> aPxSize(409, 409);
  bool                     isDefViewSize  = true;
  bool                     toCreateViewer = false;
  const bool               isVirtual      = Draw_VirtualWindows || theParams.IsVirtual;
  if (!theParams.ViewToClone.IsNull())
  {
    NCollection_Vec2<int> aCloneSize;
    theParams.ViewToClone->Window()->Size(aCloneSize.x(), aCloneSize.y());
    aPxSize       = NCollection_Vec2<double>(aCloneSize);
    isDefViewSize = false;
#if !defined(__EMSCRIPTEN__)
    (void)isDefViewSize;
#endif
  }

  occ::handle<Graphic3d_GraphicDriverFactory> aFactory =
    Graphic3d_GraphicDriverFactory::DefaultDriverFactory();
  if (aFactory.IsNull())
  {
    Draw::GetInterpretor().Eval("pload OPENGL");
    aFactory = Graphic3d_GraphicDriverFactory::DefaultDriverFactory();
    if (aFactory.IsNull())
    {
      Draw::GetInterpretor().Eval("pload GLES");
      aFactory = Graphic3d_GraphicDriverFactory::DefaultDriverFactory();
      if (aFactory.IsNull())
      {
        throw Standard_ProgramError("Error: no graphic driver factory found");
      }
    }
  }

  occ::handle<Graphic3d_GraphicDriver> aGraphicDriver;
  ViewerTest_Names                     aViewNames(theParams.ViewName);
  if (ViewerTest_myViews.IsBound1(aViewNames.GetViewName()))
  {
    aViewNames.SetViewName(aViewNames.GetViewerName() + "/"
                           + CreateName<occ::handle<V3d_View>>(ViewerTest_myViews, "View"));
  }

  // Get graphic driver (create it or get from another view)
  const bool isNewDriver = !ViewerTest_myDrivers.IsBound1(aViewNames.GetDriverName());
  if (isNewDriver)
  {
    // Get connection string
#if defined(HAVE_XLIB)
    if (!theParams.DisplayName.IsEmpty())
    {
      SetDisplayConnection(new Aspect_DisplayConnection(theParams.DisplayName));
    }
    else
    {
      Aspect_XDisplay* aDispX = nullptr;
      // create dedicated display connection instead of reusing Tk connection
      // so that to proceed events independently through VProcessEvents()/ViewerMainLoop() callbacks
      /*Draw_Interpretor& aCommands = Draw::GetInterpretor();
      Tcl_Interp* aTclInterp = aCommands.Interp();
      Tk_Window aMainWindow = Tk_MainWindow (aTclInterp);
      aDispX = aMainWindow != NULL ? Tk_Display (aMainWindow) : NULL;*/
      SetDisplayConnection(new Aspect_DisplayConnection(aDispX));
    }
#else
    SetDisplayConnection(new Aspect_DisplayConnection());
#endif

    aGraphicDriver = aFactory->CreateDriver(GetDisplayConnection());
    if (isVirtual)
    {
      // don't waste the time waiting for VSync when window is not displayed on the screen
      aGraphicDriver->SetVerticalSync(false);
    }

    ViewerTest_myDrivers.Bind(aViewNames.GetDriverName(), aGraphicDriver);
    toCreateViewer = true;
  }
  else
  {
    aGraphicDriver = ViewerTest_myDrivers.Find1(aViewNames.GetDriverName());
  }

  // Get screen resolution
  NCollection_Vec2<int> aScreenSize;
#if defined(_WIN32)
  RECT aWindowSize;
  GetClientRect(GetDesktopWindow(), &aWindowSize);
  aScreenSize.SetValues(aWindowSize.right, aWindowSize.bottom);
#elif defined(HAVE_XLIB)
  ::Display* aDispX  = (::Display*)GetDisplayConnection()->GetDisplayAspect();
  Screen*    aScreen = DefaultScreenOfDisplay(aDispX);
  aScreenSize.x()    = WidthOfScreen(aScreen);
  aScreenSize.y()    = HeightOfScreen(aScreen);
#elif defined(__APPLE__)
  GetCocoaScreenResolution(aScreenSize.x(), aScreenSize.y());
#else
  // not implemented
#endif

  if (!theParams.ParentView.IsNull())
  {
    aPxTopLeft.SetValues(0, 0);
  }
  if (theParams.Offset.x() != 0)
  {
    aPxTopLeft.x() = theParams.Offset.x();
  }
  if (theParams.Offset.y() != 0)
  {
    aPxTopLeft.y() = theParams.Offset.y();
  }
  if (theParams.Size.x() != 0)
  {
    isDefViewSize = false;
    aPxSize.x()   = theParams.Size.x();
    if (aPxSize.x() <= 1.0 && aScreenSize.x() > 0 && theParams.ParentView.IsNull())
    {
      aPxSize.x() = aPxSize.x() * double(aScreenSize.x());
    }
  }
  if (theParams.Size.y() != 0)
  {
    isDefViewSize = false;
    aPxSize.y()   = theParams.Size.y();
    if (aPxSize.y() <= 1.0 && aScreenSize.y() > 0 && theParams.ParentView.IsNull())
    {
      aPxSize.y() = aPxSize.y() * double(aScreenSize.y());
    }
  }

  // Dispose the window if input parameters are default
  if (!ViewerTest_myViews.IsEmpty() && theParams.ParentView.IsNull() && theParams.Offset.x() == 0
      && theParams.Offset.y() == 0)
  {
    int                     aTop = 0, aLeft = 0, aRight = 0, aBottom = 0;
    TCollection_AsciiString anOverlappedViewId("");
    while (IsWindowOverlapped((int)aPxTopLeft.x(),
                              (int)aPxTopLeft.y(),
                              (int)aPxTopLeft.x() + (int)aPxSize.x(),
                              (int)aPxTopLeft.y() + (int)aPxSize.y(),
                              anOverlappedViewId))
    {
      ViewerTest_myViews.Find1(anOverlappedViewId)
        ->Window()
        ->Position(aLeft, aTop, aRight, aBottom);

      if (IsWindowOverlapped(aRight + 20,
                             (int)aPxTopLeft.y(),
                             aRight + 20 + (int)aPxSize.x(),
                             (int)aPxTopLeft.y() + (int)aPxSize.y(),
                             anOverlappedViewId)
          && aRight + 2 * aPxSize.x() + 40 > aScreenSize.x())
      {
        if (aBottom + aPxSize.y() + 40 > aScreenSize.y())
        {
          aPxTopLeft.x() = 20;
          aPxTopLeft.y() = 40;
          break;
        }
        aPxTopLeft.x() = 20;
        aPxTopLeft.y() = aBottom + 40;
      }
      else
      {
        aPxTopLeft.x() = aRight + 20;
      }
    }
  }

  // Get viewer name
  TCollection_AsciiString aTitle("3D View - ");
  aTitle = aTitle + aViewNames.GetViewName() + "(*)";

  // Change name of current active window
  if (const occ::handle<V3d_View>& aCurrentView = ViewerTest::CurrentView())
  {
    aCurrentView->Window()->SetTitle(TCollection_AsciiString("3D View - ")
                                     + ViewerTest_myViews.Find2(aCurrentView));
  }

  // Create viewer
  occ::handle<V3d_Viewer> a3DViewer;
  // If it's the single view, we first look for empty context
  if (ViewerTest_myViews.IsEmpty() && !ViewerTest_myContexts.IsEmpty())
  {
    ViewerTest_ViewerCommandsInteractiveContextMap::Iterator anIter(ViewerTest_myContexts);
    if (anIter.More())
    {
      ViewerTest::SetAISContext(anIter.Key2());
    }
    a3DViewer = ViewerTest::GetAISContext()->CurrentViewer();
  }
  else if (ViewerTest_myContexts.IsBound1(aViewNames.GetViewerName()))
  {
    ViewerTest::SetAISContext(ViewerTest_myContexts.Find1(aViewNames.GetViewerName()));
    a3DViewer = ViewerTest::GetAISContext()->CurrentViewer();
  }
  else if (a3DViewer.IsNull())
  {
    toCreateViewer = true;
    a3DViewer      = new V3d_Viewer(aGraphicDriver);
    a3DViewer->SetDefaultBackgroundColor(ViewerTest_DefaultBackground.FlatColor);
    a3DViewer->SetDefaultBgGradientColors(ViewerTest_DefaultBackground.GradientColor1,
                                          ViewerTest_DefaultBackground.GradientColor2,
                                          ViewerTest_DefaultBackground.FillMethod);
  }

  // AIS context setup
  if (ViewerTest::GetAISContext().IsNull()
      || !(ViewerTest_myContexts.IsBound1(aViewNames.GetViewerName())))
  {
    occ::handle<AIS_InteractiveContext> aContext = new AIS_InteractiveContext(a3DViewer);
    ViewerTest::SetAISContext(aContext);
    ViewerTest_myContexts.Bind(aViewNames.GetViewerName(), ViewerTest::GetAISContext());
  }
  else
  {
    ViewerTest::ResetEventManager();
  }

  // Create window
  if (!theParams.ParentView.IsNull())
  {
    VT_GetWindow() = occ::down_cast<ViewerTest_Window>(theParams.ParentView->Window());
  }
  else
  {
#if defined(_WIN32)
    VT_GetWindow() = new WNT_Window(aTitle.ToCString(),
                                    WClass(),
                                    isVirtual ? WS_POPUP : WS_OVERLAPPEDWINDOW,
                                    (int)aPxTopLeft.x(),
                                    (int)aPxTopLeft.y(),
                                    (int)aPxSize.x(),
                                    (int)aPxSize.y(),
                                    Quantity_NOC_BLACK);
    VT_GetWindow()->RegisterRawInputDevices(WNT_Window::RawInputMask_SpaceMouse);
#elif defined(HAVE_XLIB)
    VT_GetWindow() = new Xw_Window(aGraphicDriver->GetDisplayConnection(),
                                   aTitle.ToCString(),
                                   (int)aPxTopLeft.x(),
                                   (int)aPxTopLeft.y(),
                                   (int)aPxSize.x(),
                                   (int)aPxSize.y());
#elif defined(__APPLE__)
    VT_GetWindow() = new Cocoa_Window(aTitle.ToCString(),
                                      (int)aPxTopLeft.x(),
                                      (int)aPxTopLeft.y(),
                                      (int)aPxSize.x(),
                                      (int)aPxSize.y());
    ViewerTest_SetCocoaEventManagerView(VT_GetWindow());
#elif defined(__EMSCRIPTEN__)
    // current EGL implementation in Emscripten supports only one global WebGL canvas returned by
    // Module.canvas property; the code should be revised for handling multiple canvas elements
    // (which is technically also possible)
    TCollection_AsciiString aCanvasId = getModuleCanvasId();
    if (!aCanvasId.IsEmpty())
    {
      aCanvasId = TCollection_AsciiString("#") + aCanvasId;
    }

    VT_GetWindow() = new Wasm_Window(aCanvasId);
    NCollection_Vec2<int> aRealSize;
    VT_GetWindow()->Size(aRealSize.x(), aRealSize.y());
    if (!isDefViewSize || (aRealSize.x() <= 0 && aRealSize.y() <= 0))
    {
      // Wasm_Window wraps an existing HTML element without creating a new one.
      // Keep size defined on a web page instead of defaulting to 409x409 (as in case of other
      // platform), but resize canvas if vinit has been called with explicitly specified dimensions.
      VT_GetWindow()->SetSizeLogical(NCollection_Vec2<double>(aPxSize));
    }
#else
    // not implemented
    VT_GetWindow() = new Aspect_NeutralWindow();
    VT_GetWindow()->SetSize((int)aPxSize.x(), (int)aPxSize.y());
#endif
    VT_GetWindow()->SetVirtual(isVirtual);
  }

  // View setup
  occ::handle<V3d_View> aView;
  if (!theParams.ViewToClone.IsNull())
  {
    aView = new ViewerTest_V3dView(a3DViewer, theParams.ViewToClone);
  }
  else
  {
    aView = new ViewerTest_V3dView(a3DViewer, a3DViewer->DefaultTypeOfView());
  }

  aView->View()->SetSubviewComposer(theParams.IsComposer);
  if (!theParams.ParentView.IsNull())
  {
    aView->SetWindow(theParams.ParentView,
                     aPxSize,
                     theParams.Corner,
                     aPxTopLeft,
                     theParams.SubviewMargins);
  }
  else
  {
    aView->SetWindow(VT_GetWindow());
  }
  ViewerTest::GetAISContext()->RedrawImmediate(a3DViewer);

  ViewerTest::CurrentView(aView);
  ViewerTest_myViews.Bind(aViewNames.GetViewName(), aView);

  // Setup for X11 or NT
  SetDisplayConnection(ViewerTest::CurrentView()->Viewer()->Driver()->GetDisplayConnection());
  ViewerTest_EventManager::SetupWindowCallbacks(VT_GetWindow());

  // Set parameters for V3d_View and V3d_Viewer
  const occ::handle<V3d_View> aV3dView = ViewerTest::CurrentView();
  aV3dView->SetComputedMode(false);

  a3DViewer->SetDefaultBackgroundColor(Quantity_NOC_BLACK);
  if (toCreateViewer)
  {
    a3DViewer->SetDefaultLights();
    a3DViewer->SetLightOn();
  }

#if defined(HAVE_XLIB)
  if (isNewDriver)
  {
    ::Display* aDispX = (::Display*)GetDisplayConnection()->GetDisplayAspect();
    Tcl_CreateFileHandler(XConnectionNumber(aDispX),
                          TCL_READABLE,
                          VProcessEvents,
                          (ClientData)aDispX);
  }
#endif

  VT_GetWindow()->Map();

  // Set the handle of created view in the event manager
  ViewerTest::ResetEventManager();

  ViewerTest::CurrentView()->Redraw();

  aView.Nullify();
  a3DViewer.Nullify();

  return aViewNames.GetViewName();
}

//=================================================================================================

void ViewerTest::RedrawAllViews()
{
  ViewerTest_ViewerCommandsViewMap::Iterator aViewIt(ViewerTest_myViews);
  for (; aViewIt.More(); aViewIt.Next())
  {
    const occ::handle<V3d_View>& aView = aViewIt.Key2();
    aView->Redraw();
  }
}

//=================================================================================================

static int VDriver(Draw_Interpretor& theDi, int theArgsNb, const char** theArgVec)
{
  if (theArgsNb == 1)
  {
    theDi << "Registered: ";
    for (NCollection_List<occ::handle<Graphic3d_GraphicDriverFactory>>::Iterator aFactoryIter(
           Graphic3d_GraphicDriverFactory::DriverFactories());
         aFactoryIter.More();
         aFactoryIter.Next())
    {
      const occ::handle<Graphic3d_GraphicDriverFactory>& aFactory = aFactoryIter.Value();
      theDi << aFactory->Name() << " ";
    }

    theDi << "\n";
    theDi << "Default: ";
    if (occ::handle<Graphic3d_GraphicDriverFactory> aFactory =
          Graphic3d_GraphicDriverFactory::DefaultDriverFactory())
    {
      theDi << aFactory->Name();
    }
    else
    {
      theDi << "NONE";
    }
    return 0;
  }

  TCollection_AsciiString aNewActive;
  bool                    toLoad = false;
  for (int anArgIter = 1; anArgIter < theArgsNb; ++anArgIter)
  {
    TCollection_AsciiString anArgCase(theArgVec[anArgIter]);
    anArgCase.LowerCase();
    if (anArgCase == "-list")
    {
      for (NCollection_List<occ::handle<Graphic3d_GraphicDriverFactory>>::Iterator aFactoryIter(
             Graphic3d_GraphicDriverFactory::DriverFactories());
           aFactoryIter.More();
           aFactoryIter.Next())
      {
        const occ::handle<Graphic3d_GraphicDriverFactory>& aFactory = aFactoryIter.Value();
        theDi << aFactory->Name() << " ";
      }
    }
    else if ((anArgCase == "-default" || anArgCase == "-load") && aNewActive.IsEmpty())
    {
      toLoad = (anArgCase == "-load");
      if (anArgIter + 1 < theArgsNb)
      {
        aNewActive = theArgVec[++anArgIter];
      }
      else if (toLoad)
      {
        theDi << "Syntax error at '" << theArgVec[anArgIter] << "'";
        return 1;
      }
      else
      {
        if (occ::handle<Graphic3d_GraphicDriverFactory> aFactory =
              Graphic3d_GraphicDriverFactory::DefaultDriverFactory())
        {
          theDi << aFactory->Name();
        }
        else
        {
          theDi << "NONE";
        }
      }
    }
    else if (aNewActive.IsEmpty())
    {
      aNewActive = theArgVec[anArgIter];
    }
    else
    {
      theDi << "Syntax error: unknown argument '" << theArgVec[anArgIter] << "'";
      return 1;
    }
  }

  if (!aNewActive.IsEmpty())
  {
    const TCollection_AsciiString aNameCopy = aNewActive;
    if (TCollection_AsciiString::IsSameString(aNewActive, "gl", false)
        || TCollection_AsciiString::IsSameString(aNewActive, "opengl", false)
        || TCollection_AsciiString::IsSameString(aNewActive, "tkopengl", false))
    {
      aNewActive = "tkopengl";
    }
    else if (TCollection_AsciiString::IsSameString(aNewActive, "gles", false)
             || TCollection_AsciiString::IsSameString(aNewActive, "opengles", false)
             || TCollection_AsciiString::IsSameString(aNewActive, "tkopengles", false))
    {
      aNewActive = "tkopengles";
    }
    else if (TCollection_AsciiString::IsSameString(aNewActive, "d3d", false)
             || TCollection_AsciiString::IsSameString(aNewActive, "d3dhost", false)
             || TCollection_AsciiString::IsSameString(aNewActive, "tkd3dhost", false))
    {
      aNewActive = "tkd3dhost";
    }

    if (toLoad)
    {
      if (aNewActive == "tkopengl")
      {
        Draw::GetInterpretor().Eval("pload OPENGL");
      }
      else if (aNewActive == "tkopengles")
      {
        Draw::GetInterpretor().Eval("pload GLES");
      }
      else if (aNewActive == "tkd3dhost")
      {
        Draw::GetInterpretor().Eval("pload D3DHOST");
      }
      else
      {
        theDi << "Syntax error: unable to load plugin for unknown driver factory '" << aNameCopy
              << "'";
        return 1;
      }
    }

    bool isFound = false;
    for (NCollection_List<occ::handle<Graphic3d_GraphicDriverFactory>>::Iterator aFactoryIter(
           Graphic3d_GraphicDriverFactory::DriverFactories());
         aFactoryIter.More();
         aFactoryIter.Next())
    {
      occ::handle<Graphic3d_GraphicDriverFactory> aFactory = aFactoryIter.Value();
      if (TCollection_AsciiString::IsSameString(aFactory->Name(), aNewActive, false))
      {
        Graphic3d_GraphicDriverFactory::RegisterFactory(aFactory, true);
        isFound = true;
        break;
      }
    }

    if (!isFound)
    {
      theDi << "Syntax error: driver factory '" << aNameCopy << "' not found";
      return 1;
    }
  }

  return 0;
}

//==============================================================================
// function : Vinit
// purpose  : Create the window viewer and initialize all the global variable
//    Use Tcl_CreateFileHandler on UNIX to catch the X11 Viewer event
//==============================================================================
static int VInit(Draw_Interpretor& theDi, int theArgsNb, const char** theArgVec)
{
  ViewerTest_VinitParams  aParams;
  TCollection_AsciiString aName, aValue;
  int                     is2dMode = -1, aDpiAware = -1;
  for (int anArgIt = 1; anArgIt < theArgsNb; ++anArgIt)
  {
    const TCollection_AsciiString anArg     = theArgVec[anArgIt];
    TCollection_AsciiString       anArgCase = anArg;
    anArgCase.LowerCase();
    if (anArgIt + 1 < theArgsNb && anArgCase == "-name")
    {
      aParams.ViewName = theArgVec[++anArgIt];
    }
    else if (anArgIt + 1 < theArgsNb && (anArgCase == "-left" || anArgCase == "-l")
             && Draw::ParseReal(theArgVec[anArgIt + 1], aParams.Offset.x()))
    {
      ++anArgIt;
    }
    else if (anArgIt + 1 < theArgsNb && (anArgCase == "-top" || anArgCase == "-t")
             && Draw::ParseReal(theArgVec[anArgIt + 1], aParams.Offset.y()))
    {
      ++anArgIt;
    }
    else if (anArgIt + 1 < theArgsNb && (anArgCase == "-width" || anArgCase == "-w")
             && Draw::ParseReal(theArgVec[anArgIt + 1], aParams.Size.x()))
    {
      ++anArgIt;
    }
    else if (anArgIt + 1 < theArgsNb && (anArgCase == "-height" || anArgCase == "-h")
             && Draw::ParseReal(theArgVec[anArgIt + 1], aParams.Size.y()))
    {
      ++anArgIt;
    }
    else if (anArgIt + 1 < theArgsNb
             && (anArgCase == "-pos" || anArgCase == "-position" || anArgCase == "-corner")
             && ViewerTest::ParseCorner(theArgVec[anArgIt + 1], aParams.Corner))
    {
      ++anArgIt;
    }
    else if (anArgIt + 2 < theArgsNb && anArgCase == "-margins"
             && Draw::ParseInteger(theArgVec[anArgIt + 1], aParams.SubviewMargins.x())
             && Draw::ParseInteger(theArgVec[anArgIt + 2], aParams.SubviewMargins.y()))
    {
      anArgIt += 2;
    }
    else if (anArgCase == "-virtual" || anArgCase == "-offscreen")
    {
      aParams.IsVirtual = Draw::ParseOnOffIterator(theArgsNb, theArgVec, anArgIt);
      ;
    }
    else if (anArgCase == "-composer")
    {
      aParams.IsComposer = Draw::ParseOnOffIterator(theArgsNb, theArgVec, anArgIt);
    }
    else if (anArgCase == "-exitonclose")
    {
      ViewerTest_EventManager::ToExitOnCloseView() =
        Draw::ParseOnOffIterator(theArgsNb, theArgVec, anArgIt);
      ;
    }
    else if (anArgCase == "-closeonescape" || anArgCase == "-closeonesc")
    {
      ViewerTest_EventManager::ToCloseViewOnEscape() =
        Draw::ParseOnOffIterator(theArgsNb, theArgVec, anArgIt);
      ;
    }
    else if (anArgCase == "-2d_mode" || anArgCase == "-2dmode" || anArgCase == "-2d")
    {
      bool toEnable = Draw::ParseOnOffIterator(theArgsNb, theArgVec, anArgIt);
      ;
      is2dMode = toEnable ? 1 : 0;
    }
    else if (anArgIt + 1 < theArgsNb && (anArgCase == "-disp" || anArgCase == "-display"))
    {
      aParams.DisplayName = theArgVec[++anArgIt];
    }
    else if (anArgCase == "-dpiaware")
    {
      aDpiAware = Draw::ParseOnOffIterator(theArgsNb, theArgVec, anArgIt) ? 1 : 0;
    }
    else if (!ViewerTest::CurrentView().IsNull() && aParams.ViewToClone.IsNull()
             && (anArgCase == "-copy" || anArgCase == "-clone" || anArgCase == "-cloneactive"
                 || anArgCase == "-cloneactiveview"))
    {
      aParams.ViewToClone = ViewerTest::CurrentView();
    }
    else if (!ViewerTest::CurrentView().IsNull() && aParams.ParentView.IsNull()
             && anArgCase == "-subview")
    {
      aParams.ParentView = ViewerTest::CurrentView();
      if (aParams.ParentView.IsNull())
      {
        Message::SendFail() << "Syntax error: cannot create of subview without parent";
        return 1;
      }
      if (aParams.ParentView->IsSubview())
      {
        aParams.ParentView = aParams.ParentView->ParentView();
      }
    }
    else if (!ViewerTest::CurrentView().IsNull() && aParams.ParentView.IsNull()
             && anArgCase == "-parent" && anArgIt + 1 < theArgsNb)
    {
      TCollection_AsciiString aParentStr(theArgVec[++anArgIt]);
      ViewerTest_Names        aViewNames(aParentStr);
      if (!ViewerTest_myViews.IsBound1(aViewNames.GetViewName()))
      {
        Message::SendFail() << "Syntax error: parent view '" << aParentStr << "' not found";
        return 1;
      }

      aParams.ParentView = ViewerTest_myViews.Find1(aViewNames.GetViewName());
      if (aParams.ParentView->IsSubview())
      {
        aParams.ParentView = aParams.ParentView->ParentView();
      }
    }
    // old syntax
    else if (ViewerTest::SplitParameter(anArg, aName, aValue))
    {
      aName.LowerCase();
      if (aName == "name")
      {
        aParams.ViewName = aValue;
      }
      else if (aName == "l" || aName == "left")
      {
        aParams.Offset.x() = (float)aValue.RealValue();
      }
      else if (aName == "t" || aName == "top")
      {
        aParams.Offset.y() = (float)aValue.RealValue();
      }
      else if (aName == "disp" || aName == "display")
      {
        aParams.DisplayName = aValue;
      }
      else if (aName == "w" || aName == "width")
      {
        aParams.Size.x() = (float)aValue.RealValue();
      }
      else if (aName == "h" || aName == "height")
      {
        aParams.Size.y() = (float)aValue.RealValue();
      }
      else
      {
        Message::SendFail() << "Syntax error: unknown argument " << anArg;
        return 1;
      }
    }
    else if (aParams.ViewName.IsEmpty())
    {
      aParams.ViewName = anArg;
    }
    else
    {
      Message::SendFail() << "Syntax error: unknown argument " << anArg;
      return 1;
    }
  }

#if defined(_WIN32)
  if (aDpiAware != -1)
  {
    typedef void*(WINAPI * SetThreadDpiAwarenessContext_t)(void*);
    if (HMODULE aUser32Module = GetModuleHandleW(L"User32"))
    {
      SetThreadDpiAwarenessContext_t aSetDpiAware =
        (SetThreadDpiAwarenessContext_t)GetProcAddress(aUser32Module,
                                                       "SetThreadDpiAwarenessContext");
      if (aDpiAware == 1)
      {
        // DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
        if (aSetDpiAware((void*)-4) == NULL)
        {
          // DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE for older systems
          if (aSetDpiAware((void*)-3) == NULL)
          {
            Message::SendFail() << "Error: unable to enable DPI awareness";
          }
        }
      }
      else
      {
        // DPI_AWARENESS_CONTEXT_UNAWARE
        if (aSetDpiAware((void*)-1) == NULL)
        {
          Message::SendFail() << "Error: unable to disable DPI awareness";
        }
      }
    }
  }
#else
  (void)aDpiAware;
  #if !defined(HAVE_XLIB)
  if (!aParams.DisplayName.IsEmpty())
  {
    aParams.DisplayName.Clear();
    Message::SendWarning() << "Warning: display parameter will be ignored.\n";
  }
  #endif
#endif

  ViewerTest_Names aViewNames(aParams.ViewName);
  if (ViewerTest_myViews.IsBound1(aViewNames.GetViewName()))
  {
    TCollection_AsciiString aCommand =
      TCollection_AsciiString("vactivate ") + aViewNames.GetViewName();
    theDi.Eval(aCommand.ToCString());
    if (is2dMode != -1)
    {
      ViewerTest_V3dView::SetCurrentView2DMode(is2dMode == 1);
    }
    return 0;
  }

  TCollection_AsciiString aViewId = ViewerTest::ViewerInit(aParams);
  if (is2dMode != -1)
  {
    ViewerTest_V3dView::SetCurrentView2DMode(is2dMode == 1);
  }
  theDi << aViewId;
  return 0;
}

//! Parse HLR algo type.
static bool parseHlrAlgoType(const char* theName, Prs3d_TypeOfHLR& theType)
{
  TCollection_AsciiString aName(theName);
  aName.LowerCase();
  if (aName == "polyalgo")
  {
    theType = Prs3d_TOH_PolyAlgo;
  }
  else if (aName == "algo")
  {
    theType = Prs3d_TOH_Algo;
  }
  else
  {
    return false;
  }
  return true;
}

//==============================================================================
// function : VHLR
// purpose  : hidden lines removal algorithm
//==============================================================================

static int VHLR(Draw_Interpretor& di, int argc, const char** argv)
{
  const occ::handle<V3d_View>               aView = ViewerTest::CurrentView();
  const occ::handle<AIS_InteractiveContext> aCtx  = ViewerTest::GetAISContext();
  if (aView.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  bool                   hasHlrOnArg      = false;
  bool                   hasShowHiddenArg = false;
  bool                   isHLROn          = false;
  bool                   toShowHidden     = aCtx->DefaultDrawer()->DrawHiddenLine();
  Prs3d_TypeOfHLR        aTypeOfHLR       = Prs3d_TOH_NotSet;
  ViewerTest_AutoUpdater anUpdateTool(occ::handle<AIS_InteractiveContext>(), aView);
  for (int anArgIter = 1; anArgIter < argc; ++anArgIter)
  {
    TCollection_AsciiString anArg(argv[anArgIter]);
    anArg.LowerCase();
    if (anUpdateTool.parseRedrawMode(anArg))
    {
      continue;
    }
    else if (anArg == "-showhidden" && anArgIter + 1 < argc
             && Draw::ParseOnOff(argv[anArgIter + 1], toShowHidden))
    {
      ++anArgIter;
      hasShowHiddenArg = true;
      continue;
    }
    else if ((anArg == "-type" || anArg == "-algo" || anArg == "-algotype") && anArgIter + 1 < argc
             && parseHlrAlgoType(argv[anArgIter + 1], aTypeOfHLR))
    {
      ++anArgIter;
      continue;
    }
    else if (!hasHlrOnArg && Draw::ParseOnOff(argv[anArgIter], isHLROn))
    {
      hasHlrOnArg = true;
      continue;
    }
    // old syntax
    else if (!hasShowHiddenArg && Draw::ParseOnOff(argv[anArgIter], toShowHidden))
    {
      hasShowHiddenArg = true;
      continue;
    }
    else
    {
      Message::SendFail() << "Syntax error at '" << argv[anArgIter] << "'";
      return 1;
    }
  }
  if (!hasHlrOnArg)
  {
    di << "HLR:        " << aView->ComputedMode() << "\n";
    di << "HiddenLine: " << aCtx->DefaultDrawer()->DrawHiddenLine() << "\n";
    di << "HlrAlgo:    ";
    switch (aCtx->DefaultDrawer()->TypeOfHLR())
    {
      case Prs3d_TOH_NotSet:
        di << "NotSet\n";
        break;
      case Prs3d_TOH_PolyAlgo:
        di << "PolyAlgo\n";
        break;
      case Prs3d_TOH_Algo:
        di << "Algo\n";
        break;
    }
    anUpdateTool.Invalidate();
    return 0;
  }

  bool toRecompute = false;
  if (aTypeOfHLR != Prs3d_TOH_NotSet && aTypeOfHLR != aCtx->DefaultDrawer()->TypeOfHLR())
  {
    toRecompute = true;
    aCtx->DefaultDrawer()->SetTypeOfHLR(aTypeOfHLR);
  }
  if (toShowHidden != aCtx->DefaultDrawer()->DrawHiddenLine())
  {
    toRecompute = true;
    if (toShowHidden)
    {
      aCtx->DefaultDrawer()->EnableDrawHiddenLine();
    }
    else
    {
      aCtx->DefaultDrawer()->DisableDrawHiddenLine();
    }
  }

  // redisplay shapes
  if (aView->ComputedMode() && isHLROn && toRecompute)
  {
    NCollection_List<occ::handle<AIS_InteractiveObject>> aListOfShapes;
    aCtx->DisplayedObjects(aListOfShapes);
    for (NCollection_List<occ::handle<AIS_InteractiveObject>>::Iterator anIter(aListOfShapes);
         anIter.More();
         anIter.Next())
    {
      if (occ::handle<AIS_Shape> aShape = occ::down_cast<AIS_Shape>(anIter.Value()))
      {
        aCtx->Redisplay(aShape, false);
      }
    }
  }

  aView->SetComputedMode(isHLROn);
  return 0;
}

//==============================================================================
// function : VHLRType
// purpose  : change type of using HLR algorithm
//==============================================================================

static int VHLRType(Draw_Interpretor&, int argc, const char** argv)
{
  const occ::handle<V3d_View>               aView = ViewerTest::CurrentView();
  const occ::handle<AIS_InteractiveContext> aCtx  = ViewerTest::GetAISContext();
  if (aView.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  Prs3d_TypeOfHLR        aTypeOfHLR = Prs3d_TOH_NotSet;
  ViewerTest_AutoUpdater anUpdateTool(occ::handle<AIS_InteractiveContext>(), aView);
  NCollection_List<occ::handle<AIS_InteractiveObject>> aListOfShapes;
  for (int anArgIter = 1; anArgIter < argc; ++anArgIter)
  {
    TCollection_AsciiString anArg(argv[anArgIter]);
    anArg.LowerCase();
    if (anUpdateTool.parseRedrawMode(anArg))
    {
      continue;
    }
    else if ((anArg == "-type" || anArg == "-algo" || anArg == "-algotype") && anArgIter + 1 < argc
             && parseHlrAlgoType(argv[anArgIter + 1], aTypeOfHLR))
    {
      ++anArgIter;
      continue;
    }
    // old syntax
    else if (aTypeOfHLR == Prs3d_TOH_NotSet && parseHlrAlgoType(argv[anArgIter], aTypeOfHLR))
    {
      continue;
    }
    else
    {
      NCollection_DoubleMap<occ::handle<AIS_InteractiveObject>, TCollection_AsciiString>& aMap =
        GetMapOfAIS();
      TCollection_AsciiString aName(argv[anArgIter]);
      if (!aMap.IsBound2(aName))
      {
        Message::SendFail() << "Syntax error: Wrong shape name '" << aName << "'";
        return 1;
      }

      occ::handle<AIS_Shape> aShape = occ::down_cast<AIS_Shape>(aMap.Find2(aName));
      if (aShape.IsNull())
      {
        Message::SendFail() << "Syntax error: '" << aName << "' is not a shape presentation";
        return 1;
      }
      aListOfShapes.Append(aShape);
      continue;
    }
  }
  if (aTypeOfHLR == Prs3d_TOH_NotSet)
  {
    Message::SendFail("Syntax error: wrong number of arguments");
    return 1;
  }

  const bool isGlobal = aListOfShapes.IsEmpty();
  if (isGlobal)
  {
    aCtx->DisplayedObjects(aListOfShapes);
    aCtx->DefaultDrawer()->SetTypeOfHLR(aTypeOfHLR);
  }

  for (NCollection_List<occ::handle<AIS_InteractiveObject>>::Iterator anIter(aListOfShapes);
       anIter.More();
       anIter.Next())
  {
    occ::handle<AIS_Shape> aShape = occ::down_cast<AIS_Shape>(anIter.Value());
    if (aShape.IsNull())
    {
      continue;
    }

    const bool toUpdateShape = aShape->TypeOfHLR() != aTypeOfHLR && aView->ComputedMode();
    if (!isGlobal || aShape->TypeOfHLR() != aTypeOfHLR)
    {
      aShape->SetTypeOfHLR(aTypeOfHLR);
    }
    if (toUpdateShape)
    {
      aCtx->Redisplay(aShape, false);
    }
  }
  return 0;
}

//==============================================================================
// function : FindViewIdByWindowHandle
// purpose  : Find theView Id in the map of views by window handle
//==============================================================================
#if defined(_WIN32) || defined(HAVE_XLIB)
static TCollection_AsciiString FindViewIdByWindowHandle(Aspect_Drawable theWindowHandle)
{
  for (ViewerTest_ViewerCommandsViewMap::Iterator anIter(ViewerTest_myViews); anIter.More();
       anIter.Next())
  {
    Aspect_Drawable aWindowHandle = anIter.Key2()->Window()->NativeHandle();
    if (aWindowHandle == theWindowHandle)
    {
      return anIter.Key1();
    }
  }
  return TCollection_AsciiString("");
}
#endif

//! Make the view active
void ActivateView(const TCollection_AsciiString& theViewName, bool theToUpdate = true)
{
  if (const occ::handle<V3d_View>& aView = ViewerTest_myViews.Find1(theViewName))
  {
    ViewerTest::ActivateView(aView, theToUpdate);
  }
}

//=================================================================================================

void ViewerTest::ActivateView(const occ::handle<V3d_View>& theView, bool theToUpdate)
{
  const occ::handle<V3d_View>&   aView     = theView;
  const TCollection_AsciiString* aViewName = ViewerTest_myViews.Seek2(aView);
  if (aViewName == nullptr)
  {
    return;
  }

  occ::handle<AIS_InteractiveContext> anAISContext = FindContextByView(aView);
  if (anAISContext.IsNull())
  {
    return;
  }

  if (const occ::handle<V3d_View>& aCurrentView = ViewerTest::CurrentView())
  {
    if (!aCurrentView->Window().IsNull())
    {
      aCurrentView->Window()->SetTitle(TCollection_AsciiString("3D View - ")
                                       + ViewerTest_myViews.Find2(aCurrentView));
    }
  }

  ViewerTest::CurrentView(aView);
  ViewerTest::SetAISContext(anAISContext);
  if (aView->IsSubview())
  {
    aView->ParentView()->Window()->SetTitle(TCollection_AsciiString("3D View - ") + *aViewName
                                            + "(*)");
    VT_GetWindow() = occ::down_cast<ViewerTest_Window>(aView->View()->ParentView()->Window());
  }
  else
  {
    VT_GetWindow() = occ::down_cast<ViewerTest_Window>(aView->Window());
  }
  if (!VT_GetWindow().IsNull())
  {
    VT_GetWindow()->SetTitle(TCollection_AsciiString("3D View - ") + *aViewName + "(*)");
  }
  SetDisplayConnection(aView->Viewer()->Driver()->GetDisplayConnection());
  if (theToUpdate)
  {
    aView->Redraw();
  }
}

//=================================================================================================

void ViewerTest::RemoveView(const occ::handle<V3d_View>& theView, const bool theToRemoveContext)
{
  if (!ViewerTest_myViews.IsBound2(theView))
  {
    return;
  }

  const TCollection_AsciiString& aViewName = ViewerTest_myViews.Find2(theView);
  RemoveView(aViewName, theToRemoveContext);
}

//==============================================================================
// function : RemoveView
// purpose  : Close and remove view from display, clear maps if necessary
//==============================================================================
void ViewerTest::RemoveView(const TCollection_AsciiString& theViewName, const bool isContextRemoved)
{
  if (!ViewerTest_myViews.IsBound1(theViewName))
  {
    Message::SendFail() << "Wrong view name";
    return;
  }

  occ::handle<V3d_View>               aView           = ViewerTest_myViews.Find1(theViewName);
  occ::handle<AIS_InteractiveContext> aCurrentContext = FindContextByView(aView);
  ViewerTest_ContinuousRedrawer&      aRedrawer       = ViewerTest_ContinuousRedrawer::Instance();
  aRedrawer.Stop(aView);
  if (!aView->Subviews().IsEmpty())
  {
    NCollection_Sequence<occ::handle<V3d_View>> aSubviews = aView->Subviews();
    for (const occ::handle<V3d_View>& aSubviewIter : aSubviews)
    {
      RemoveView(aSubviewIter, isContextRemoved);
    }
  }

  // Activate another view if it's active now
  if (ViewerTest_myViews.Find1(theViewName) == ViewerTest::CurrentView())
  {
    if (ViewerTest_myViews.Extent() > 1)
    {
      for (ViewerTest_ViewerCommandsViewMap::Iterator anIter(ViewerTest_myViews); anIter.More();
           anIter.Next())
      {
        if (anIter.Key1() != theViewName)
        {
          ActivateView(anIter.Key2(), true);
          break;
        }
      }
    }
    else
    {
      VT_GetWindow().Nullify();
      ViewerTest::CurrentView(occ::handle<V3d_View>());
      if (isContextRemoved)
      {
        occ::handle<AIS_InteractiveContext> anEmptyContext;
        ViewerTest::SetAISContext(anEmptyContext);
      }
    }
  }

  // Delete view, name will be removed too
  const TCollection_AsciiString aCopyString(theViewName);
  ViewerTest_myViews.UnBind1(theViewName);
  if (!aView->Window().IsNull())
  {
    aView->Window()->Unmap();
  }
  aView->Remove();

#if defined(HAVE_XLIB)
  XFlush((::Display*)GetDisplayConnection()->GetDisplayAspect());
#endif

  // Keep context opened only if the closed view is last to avoid
  // unused empty contexts
  if (!aCurrentContext.IsNull())
  {
    // Check if there are more defined views in the viewer
    if ((isContextRemoved || ViewerTest_myContexts.Size() != 1)
        && aCurrentContext->CurrentViewer()->DefinedViews().IsEmpty())
    {
      // Remove driver if there is no viewers that use it
      bool isRemoveDriver = true;
      for (ViewerTest_ViewerCommandsInteractiveContextMap::Iterator anIter(ViewerTest_myContexts);
           anIter.More();
           anIter.Next())
      {
        if (aCurrentContext != anIter.Key2()
            && aCurrentContext->CurrentViewer()->Driver()
                 == anIter.Key2()->CurrentViewer()->Driver())
        {
          isRemoveDriver = false;
          break;
        }
      }

      aCurrentContext->RemoveAll(false);
      if (isRemoveDriver)
      {
        ViewerTest_myDrivers.UnBind2(aCurrentContext->CurrentViewer()->Driver());
#if defined(HAVE_XLIB)
        Tcl_DeleteFileHandler(XConnectionNumber((::Display*)aCurrentContext->CurrentViewer()
                                                  ->Driver()
                                                  ->GetDisplayConnection()
                                                  ->GetDisplayAspect()));
#endif
      }

      ViewerTest_myContexts.UnBind2(aCurrentContext);
    }
  }
  Message::SendInfo() << "3D View - " << aCopyString << " was deleted.\n";
  if (ViewerTest_EventManager::ToExitOnCloseView())
  {
    Draw_Interprete("exit");
  }
}

//==============================================================================
// function : VClose
// purpose  : Remove the view defined by its name
//==============================================================================

static int VClose(Draw_Interpretor& /*theDi*/, int theArgsNb, const char** theArgVec)
{
  NCollection_List<TCollection_AsciiString> aViewList;
  if (theArgsNb > 1)
  {
    TCollection_AsciiString anArg(theArgVec[1]);
    anArg.UpperCase();
    if (anArg.IsEqual("ALL") || anArg.IsEqual("*"))
    {
      for (ViewerTest_ViewerCommandsViewMap::Iterator anIter(ViewerTest_myViews); anIter.More();
           anIter.Next())
      {
        aViewList.Append(anIter.Key1());
      }
      if (aViewList.IsEmpty())
      {
        std::cout << "No view to close\n";
        return 0;
      }
    }
    else
    {
      ViewerTest_Names aViewName(theArgVec[1]);
      if (!ViewerTest_myViews.IsBound1(aViewName.GetViewName()))
      {
        Message::SendFail() << "Error: the view with name '" << theArgVec[1] << "' does not exist";
        return 1;
      }
      aViewList.Append(aViewName.GetViewName());
    }
  }
  else
  {
    // close active view
    if (ViewerTest::CurrentView().IsNull())
    {
      Message::SendFail("Error: no active view");
      return 1;
    }
    aViewList.Append(ViewerTest_myViews.Find2(ViewerTest::CurrentView()));
  }

  bool toRemoveContext = (theArgsNb != 3 || Draw::Atoi(theArgVec[2]) != 1);
  for (NCollection_List<TCollection_AsciiString>::Iterator anIter(aViewList); anIter.More();
       anIter.Next())
  {
    ViewerTest::RemoveView(anIter.Value(), toRemoveContext);
  }

  return 0;
}

//==============================================================================
// function : VActivate
// purpose  : Activate the view defined by its ID
//==============================================================================

static int VActivate(Draw_Interpretor& theDi, int theArgsNb, const char** theArgVec)
{
  if (theArgsNb == 1)
  {
    theDi.Eval("vviewlist");
    return 0;
  }

  TCollection_AsciiString aNameString;
  bool                    toUpdate   = true;
  bool                    toActivate = true;
  for (int anArgIter = 1; anArgIter < theArgsNb; ++anArgIter)
  {
    TCollection_AsciiString anArg(theArgVec[anArgIter]);
    anArg.LowerCase();
    if (toUpdate && anArg == "-noupdate")
    {
      toUpdate = false;
    }
    else if (toActivate && aNameString.IsEmpty() && anArg == "none")
    {
      ViewerTest::CurrentView()->Window()->SetTitle(
        TCollection_AsciiString("3D View - ")
        + ViewerTest_myViews.Find2(ViewerTest::CurrentView()));
      VT_GetWindow().Nullify();
      ViewerTest::CurrentView(occ::handle<V3d_View>());
      ViewerTest::ResetEventManager();
      theDi << theArgVec[0] << ": all views are inactive\n";
      toActivate = false;
    }
    else if (toActivate && aNameString.IsEmpty())
    {
      aNameString = theArgVec[anArgIter];
    }
    else
    {
      Message::SendFail() << "Syntax error at '" << theArgVec[anArgIter] << "'";
      return 1;
    }
  }

  if (!toActivate)
  {
    return 0;
  }
  else if (aNameString.IsEmpty())
  {
    Message::SendFail("Syntax error: wrong number of arguments");
    return 1;
  }

  // Check if this view exists in the viewer with the driver
  ViewerTest_Names aViewNames(aNameString);
  if (!ViewerTest_myViews.IsBound1(aViewNames.GetViewName()))
  {
    theDi << "Syntax error: wrong view name '" << aNameString << "'\n";
    return 1;
  }

  // Check if it is active already
  if (ViewerTest::CurrentView() == ViewerTest_myViews.Find1(aViewNames.GetViewName()))
  {
    theDi << theArgVec[0] << ": the view is active already\n";
    return 0;
  }

  ActivateView(aViewNames.GetViewName(), toUpdate);
  return 0;
}

//==============================================================================
// function : VViewList
// purpose  : Print current list of views per viewer and graphic driver ID
//           shared between viewers
//==============================================================================

static int VViewList(Draw_Interpretor& theDi, int theArgsNb, const char** theArgVec)
{
  if (theArgsNb > 2)
  {
    theDi << theArgVec[0] << ": Wrong number of command arguments\n"
          << "Usage: " << theArgVec[0] << " name";
    return 1;
  }
  if (ViewerTest_myContexts.Size() < 1)
  {
    return 0;
  }

  bool isTreeView = ((theArgsNb == 1) || (strcasecmp(theArgVec[1], "long") != 0));

  if (isTreeView)
  {
    theDi << theArgVec[0] << ":\n";
  }

  for (ViewerTest_ViewerCommandsGraphicDriverMap::Iterator aDriverIter(ViewerTest_myDrivers);
       aDriverIter.More();
       aDriverIter.Next())
  {
    if (isTreeView)
    {
      theDi << aDriverIter.Key1() << ":\n";
    }

    for (ViewerTest_ViewerCommandsInteractiveContextMap::Iterator aContextIter(
           ViewerTest_myContexts);
         aContextIter.More();
         aContextIter.Next())
    {
      if (aContextIter.Key1().Search(aDriverIter.Key1()) != -1)
      {
        if (isTreeView)
        {
          TCollection_AsciiString aContextName(aContextIter.Key1());
          theDi << " " << aContextName.Split(aDriverIter.Key1().Length() + 1) << ":\n";
        }

        for (ViewerTest_ViewerCommandsViewMap::Iterator aViewIter(ViewerTest_myViews);
             aViewIter.More();
             aViewIter.Next())
        {
          if (aViewIter.Key1().Search(aContextIter.Key1()) != -1)
          {
            TCollection_AsciiString aViewName(aViewIter.Key1());
            if (isTreeView)
            {
              if (aViewIter.Key2() == ViewerTest::CurrentView())
              {
                theDi << "  " << aViewName.Split(aContextIter.Key1().Length() + 1) << "(*)\n";
              }
              else
              {
                theDi << "  " << aViewName.Split(aContextIter.Key1().Length() + 1) << "\n";
              }
            }
            else
            {
              theDi << aViewName << " ";
            }
          }
        }
      }
    }
  }
  return 0;
}

//=================================================================================================

void ViewerTest::GetMousePosition(int& theX, int& theY)
{
  if (occ::handle<ViewerTest_EventManager> aViewCtrl = ViewerTest::CurrentEventManager())
  {
    theX = aViewCtrl->LastMousePosition().x();
    theY = aViewCtrl->LastMousePosition().y();
  }
}

//=================================================================================================

static int VViewProj(Draw_Interpretor&, int theNbArgs, const char** theArgVec)
{
  static bool                  isYup = false;
  const occ::handle<V3d_View>& aView = ViewerTest::CurrentView();
  if (aView.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  TCollection_AsciiString aCmdName(theArgVec[0]);
  bool                    isGeneralCmd = false;
  if (aCmdName == "vfront")
  {
    aView->SetProj(isYup ? V3d_TypeOfOrientation_Yup_Front : V3d_TypeOfOrientation_Zup_Front,
                   isYup);
  }
  else if (aCmdName == "vback")
  {
    aView->SetProj(isYup ? V3d_TypeOfOrientation_Yup_Back : V3d_TypeOfOrientation_Zup_Back, isYup);
  }
  else if (aCmdName == "vtop")
  {
    aView->SetProj(isYup ? V3d_TypeOfOrientation_Yup_Top : V3d_TypeOfOrientation_Zup_Top, isYup);
  }
  else if (aCmdName == "vbottom")
  {
    aView->SetProj(isYup ? V3d_TypeOfOrientation_Yup_Bottom : V3d_TypeOfOrientation_Zup_Bottom,
                   isYup);
  }
  else if (aCmdName == "vleft")
  {
    aView->SetProj(isYup ? V3d_TypeOfOrientation_Yup_Left : V3d_TypeOfOrientation_Zup_Left, isYup);
  }
  else if (aCmdName == "vright")
  {
    aView->SetProj(isYup ? V3d_TypeOfOrientation_Yup_Right : V3d_TypeOfOrientation_Zup_Right,
                   isYup);
  }
  else if (aCmdName == "vaxo")
  {
    aView->SetProj(isYup ? V3d_TypeOfOrientation_Yup_AxoRight : V3d_TypeOfOrientation_Zup_AxoRight,
                   isYup);
  }
  else
  {
    isGeneralCmd = true;
    for (int anArgIter = 1; anArgIter < theNbArgs; ++anArgIter)
    {
      TCollection_AsciiString anArgCase(theArgVec[anArgIter]);
      anArgCase.LowerCase();
      if (anArgCase == "-zup")
      {
        isYup = false;
      }
      else if (anArgCase == "-yup")
      {
        isYup = true;
      }
      else if (anArgCase == "-front" || anArgCase == "front" || anArgCase == "-f"
               || anArgCase == "f")
      {
        aView->SetProj(isYup ? V3d_TypeOfOrientation_Yup_Front : V3d_TypeOfOrientation_Zup_Front,
                       isYup);
      }
      else if (anArgCase == "-back" || anArgCase == "back" || anArgCase == "-b" || anArgCase == "b")
      {
        aView->SetProj(isYup ? V3d_TypeOfOrientation_Yup_Back : V3d_TypeOfOrientation_Zup_Back,
                       isYup);
      }
      else if (anArgCase == "-top" || anArgCase == "top" || anArgCase == "-t" || anArgCase == "t")
      {
        aView->SetProj(isYup ? V3d_TypeOfOrientation_Yup_Top : V3d_TypeOfOrientation_Zup_Top,
                       isYup);
      }
      else if (anArgCase == "-bottom" || anArgCase == "bottom" || anArgCase == "-bot"
               || anArgCase == "bot" || anArgCase == "-b" || anArgCase == "b")
      {
        aView->SetProj(isYup ? V3d_TypeOfOrientation_Yup_Bottom : V3d_TypeOfOrientation_Zup_Bottom,
                       isYup);
      }
      else if (anArgCase == "-left" || anArgCase == "left" || anArgCase == "-l" || anArgCase == "l")
      {
        aView->SetProj(isYup ? V3d_TypeOfOrientation_Yup_Left : V3d_TypeOfOrientation_Zup_Left,
                       isYup);
      }
      else if (anArgCase == "-right" || anArgCase == "right" || anArgCase == "-r"
               || anArgCase == "r")
      {
        aView->SetProj(isYup ? V3d_TypeOfOrientation_Yup_Right : V3d_TypeOfOrientation_Zup_Right,
                       isYup);
      }
      else if (anArgCase == "-axoleft" || anArgCase == "-leftaxo" || anArgCase == "axoleft"
               || anArgCase == "leftaxo")
      {
        aView->SetProj(isYup ? V3d_TypeOfOrientation_Yup_AxoLeft
                             : V3d_TypeOfOrientation_Zup_AxoLeft,
                       isYup);
      }
      else if (anArgCase == "-axo" || anArgCase == "axo" || anArgCase == "-a" || anArgCase == "a"
               || anArgCase == "-axoright" || anArgCase == "-rightaxo" || anArgCase == "axoright"
               || anArgCase == "rightaxo")
      {
        aView->SetProj(isYup ? V3d_TypeOfOrientation_Yup_AxoRight
                             : V3d_TypeOfOrientation_Zup_AxoRight,
                       isYup);
      }
      else if (anArgCase == "+x")
      {
        aView->SetProj(V3d_Xpos, isYup);
      }
      else if (anArgCase == "-x")
      {
        aView->SetProj(V3d_Xneg, isYup);
      }
      else if (anArgCase == "+y")
      {
        aView->SetProj(V3d_Ypos, isYup);
      }
      else if (anArgCase == "-y")
      {
        aView->SetProj(V3d_Yneg, isYup);
      }
      else if (anArgCase == "+z")
      {
        aView->SetProj(V3d_Zpos, isYup);
      }
      else if (anArgCase == "-z")
      {
        aView->SetProj(V3d_Zneg, isYup);
      }
      else if (anArgCase == "+x+y+z")
      {
        aView->SetProj(V3d_XposYposZpos, isYup);
      }
      else if (anArgCase == "+x+y-z")
      {
        aView->SetProj(V3d_XposYposZneg, isYup);
      }
      else if (anArgCase == "+x-y+z")
      {
        aView->SetProj(V3d_XposYnegZpos, isYup);
      }
      else if (anArgCase == "+x-y-z")
      {
        aView->SetProj(V3d_XposYnegZneg, isYup);
      }
      else if (anArgCase == "-x+y+z")
      {
        aView->SetProj(V3d_XnegYposZpos, isYup);
      }
      else if (anArgCase == "-x+y-z")
      {
        aView->SetProj(V3d_XnegYposZneg, isYup);
      }
      else if (anArgCase == "-x-y+z")
      {
        aView->SetProj(V3d_XnegYnegZpos, isYup);
      }
      else if (anArgCase == "-x-y-z")
      {
        aView->SetProj(V3d_XnegYnegZneg, isYup);
      }
      else if (anArgCase == "+x+y")
      {
        aView->SetProj(V3d_XposYpos, isYup);
      }
      else if (anArgCase == "+x-y")
      {
        aView->SetProj(V3d_XposYneg, isYup);
      }
      else if (anArgCase == "-x+y")
      {
        aView->SetProj(V3d_XnegYpos, isYup);
      }
      else if (anArgCase == "-x-y")
      {
        aView->SetProj(V3d_XnegYneg, isYup);
      }
      else if (anArgCase == "+x+z")
      {
        aView->SetProj(V3d_XposZpos, isYup);
      }
      else if (anArgCase == "+x-z")
      {
        aView->SetProj(V3d_XposZneg, isYup);
      }
      else if (anArgCase == "-x+z")
      {
        aView->SetProj(V3d_XnegZpos, isYup);
      }
      else if (anArgCase == "-x-z")
      {
        aView->SetProj(V3d_XnegZneg, isYup);
      }
      else if (anArgCase == "+y+z")
      {
        aView->SetProj(V3d_YposZpos, isYup);
      }
      else if (anArgCase == "+y-z")
      {
        aView->SetProj(V3d_YposZneg, isYup);
      }
      else if (anArgCase == "-y+z")
      {
        aView->SetProj(V3d_YnegZpos, isYup);
      }
      else if (anArgCase == "-y-z")
      {
        aView->SetProj(V3d_YnegZneg, isYup);
      }
      else if (anArgIter + 1 < theNbArgs && anArgCase == "-frame"
               && TCollection_AsciiString(theArgVec[anArgIter + 1]).Length() == 4)
      {
        TCollection_AsciiString aFrameDef(theArgVec[++anArgIter]);
        aFrameDef.LowerCase();
        gp_Dir aRight, anUp;
        if (aFrameDef.Value(2) == aFrameDef.Value(4))
        {
          Message::SendFail() << "Syntax error at '" << theArgVec[anArgIter] << "'";
          return 1;
        }

        if (aFrameDef.Value(2) == 'x')
        {
          aRight = aFrameDef.Value(1) == '+' ? gp::DX() : -gp::DX();
        }
        else if (aFrameDef.Value(2) == 'y')
        {
          aRight = aFrameDef.Value(1) == '+' ? gp::DY() : -gp::DY();
        }
        else if (aFrameDef.Value(2) == 'z')
        {
          aRight = aFrameDef.Value(1) == '+' ? gp::DZ() : -gp::DZ();
        }
        else
        {
          Message::SendFail() << "Syntax error at '" << theArgVec[anArgIter] << "'";
          return 1;
        }

        if (aFrameDef.Value(4) == 'x')
        {
          anUp = aFrameDef.Value(3) == '+' ? gp::DX() : -gp::DX();
        }
        else if (aFrameDef.Value(4) == 'y')
        {
          anUp = aFrameDef.Value(3) == '+' ? gp::DY() : -gp::DY();
        }
        else if (aFrameDef.Value(4) == 'z')
        {
          anUp = aFrameDef.Value(3) == '+' ? gp::DZ() : -gp::DZ();
        }
        else
        {
          Message::SendFail() << "Syntax error at '" << theArgVec[anArgIter] << "'";
          return 1;
        }

        const occ::handle<Graphic3d_Camera>& aCamera     = aView->Camera();
        const gp_Pnt                         anOriginVCS = aCamera->ConvertWorld2View(gp::Origin());
        const gp_Dir                         aDir        = anUp.Crossed(aRight);
        aCamera->SetCenter(gp_Pnt(0, 0, 0));
        aCamera->SetDirection(aDir);
        aCamera->SetUp(anUp);
        aCamera->OrthogonalizeUp();

        aView->Panning(anOriginVCS.X(), anOriginVCS.Y());
        aView->Update();
      }
      else
      {
        Message::SendFail() << "Syntax error at '" << theArgVec[anArgIter] << "'";
        return 1;
      }
    }
  }

  if (!isGeneralCmd && theNbArgs != 1)
  {
    Message::SendFail("Syntax error: wrong number of arguments");
    return 1;
  }
  return 0;
}

//==============================================================================
// function : VHelp
// purpose  : Dsiplay help on viewer Keyboead and mouse commands
// Draw arg : No args
//==============================================================================

static int VHelp(Draw_Interpretor& di, int, const char**)
{
  di << "=========================\n";
  di << "F : FitAll\n";
  di << "T : TopView\n";
  di << "B : BottomView\n";
  di << "R : RightView\n";
  di << "L : LeftView\n";
  di << "Backspace : AxonometricView\n";

  di << "=========================\n";
  di << "W, S : Fly   forward/backward\n";
  di << "A, D : Slide left/right\n";
  di << "Q, E : Bank  left/right\n";
  di << "-, + : Change flying speed\n";
  di << "Arrows : look left/right/up/down\n";
  di << "Arrows+Shift : slide left/right/up/down\n";

  di << "=========================\n";
  di << "S + Ctrl : Shading\n";
  di << "W + Ctrl : Wireframe\n";
  di << "H : HiddenLineRemoval\n";
  di << "U : Unset display mode\n";
  di << "Delete : Remove selection from viewer\n";

  di << "=========================\n";
  di << "Selection mode \n";
  di << "0 : Shape\n";
  di << "1 : Vertex\n";
  di << "2 : Edge\n";
  di << "3 : Wire\n";
  di << "4 : Face\n";
  di << "5 : Shell\n";
  di << "6 : Solid\n";
  di << "7 : Compound\n";

  di << "=========================\n";
  di << "< : Hilight next detected\n";
  di << "> : Hilight previous detected\n";

  return 0;
}

#ifdef _WIN32

static LRESULT WINAPI AdvViewerWindowProc(HWND   theWinHandle,
                                          UINT   theMsg,
                                          WPARAM wParam,
                                          LPARAM lParam)
{
  if (ViewerTest_myViews.IsEmpty())
  {
    return DefWindowProcW(theWinHandle, theMsg, wParam, lParam);
  }

  switch (theMsg)
  {
    case WM_CLOSE: {
      // Delete view from map of views
      ViewerTest::RemoveView(FindViewIdByWindowHandle(theWinHandle));
      return 0;
    }
    case WM_ACTIVATE: {
      if (LOWORD(wParam) == WA_CLICKACTIVE || LOWORD(wParam) == WA_ACTIVE
          || ViewerTest::CurrentView().IsNull())
      {
        // Activate inactive window
        if (VT_GetWindow().IsNull() || (HWND)VT_GetWindow()->HWindow() != theWinHandle)
        {
          ActivateView(FindViewIdByWindowHandle(theWinHandle));
        }
      }
      return 0;
    }
    default: {
      const occ::handle<V3d_View>& aView = ViewerTest::CurrentView();
      if (!aView.IsNull() && !VT_GetWindow().IsNull())
      {
        MSG aMsg     = {};
        aMsg.hwnd    = theWinHandle;
        aMsg.message = theMsg;
        aMsg.wParam  = wParam;
        aMsg.lParam  = lParam;
        if (VT_GetWindow()->ProcessMessage(*ViewerTest::CurrentEventManager(), aMsg))
        {
          return 0;
        }
      }
    }
  }
  return DefWindowProcW(theWinHandle, theMsg, wParam, lParam);
}

//==============================================================================
// function : ViewerMainLoop
// purpose  : Get a Event on the view and dispatch it
//==============================================================================

int ViewerMainLoop(int theNbArgs, const char** theArgVec)
{
  occ::handle<ViewerTest_EventManager> aViewCtrl = ViewerTest::CurrentEventManager();
  if (aViewCtrl.IsNull() || theNbArgs < 4)
  {
    return 0;
  }

  aViewCtrl->StartPickPoint(theArgVec[1], theArgVec[2], theArgVec[3]);

  std::cout << "Start picking\n";

  MSG aMsg;
  aMsg.wParam = 1;
  while (aViewCtrl->ToPickPoint())
  {
    // Wait for a VT_ProcessButton1Press() to toggle pick to 1 or 0
    if (GetMessageW(&aMsg, NULL, 0, 0))
    {
      TranslateMessage(&aMsg);
      DispatchMessageW(&aMsg);
    }
  }

  std::cout << "Picking done\n";
  return 0;
}

#elif defined(HAVE_XLIB)

int ViewerMainLoop(int theNbArgs, const char** theArgVec)
{
  static XEvent aReport;
  const bool    toPick = theNbArgs > 0;
  if (theNbArgs > 0)
  {
    if (ViewerTest::CurrentEventManager().IsNull())
    {
      return 0;
    }
    ViewerTest::CurrentEventManager()->StartPickPoint(theArgVec[1], theArgVec[2], theArgVec[3]);
  }

  Display* aDisplay = (Display*)GetDisplayConnection()->GetDisplayAspect();
  XNextEvent(aDisplay, &aReport);

  // Handle event for the chosen display connection
  switch (aReport.type)
  {
    case ClientMessage: {
      if ((Atom)aReport.xclient.data.l[0]
          == GetDisplayConnection()->GetAtom(Aspect_XA_DELETE_WINDOW))
      {
        // Close the window
        ViewerTest::RemoveView(FindViewIdByWindowHandle(aReport.xclient.window));
        return toPick ? 0 : 1;
      }
      break;
    }
    case FocusIn: {
      // Activate inactive view
      Window aWindow = !VT_GetWindow().IsNull() ? VT_GetWindow()->XWindow() : 0;
      if (aWindow != aReport.xfocus.window)
      {
        ActivateView(FindViewIdByWindowHandle(aReport.xfocus.window));
      }
      break;
    }
    default: {
      const occ::handle<V3d_View>& aView = ViewerTest::CurrentView();
      if (!aView.IsNull() && !VT_GetWindow().IsNull())
      {
        VT_GetWindow()->ProcessMessage(*ViewerTest::CurrentEventManager(), aReport);
      }
      break;
    }
  }
  return (!toPick || ViewerTest::CurrentEventManager()->ToPickPoint()) ? 1 : 0;
}

//==============================================================================
// function : VProcessEvents
// purpose  : manage the event in the Viewer window (see Tcl_CreateFileHandler())
//==============================================================================
static void VProcessEvents(ClientData theDispX, int)
{
  Display*                              aDispX = (Display*)theDispX;
  occ::handle<Aspect_DisplayConnection> aDispConn;
  for (ViewerTest_ViewerCommandsGraphicDriverMap::Iterator aDriverIter(ViewerTest_myDrivers);
       aDriverIter.More();
       aDriverIter.Next())
  {
    const occ::handle<Aspect_DisplayConnection>& aDispConnTmp =
      aDriverIter.Key2()->GetDisplayConnection();
    if ((Display*)aDispConnTmp->GetDisplayAspect() == aDispX)
    {
      aDispConn = aDispConnTmp;
      break;
    }
  }
  if (aDispConn.IsNull())
  {
    Message::SendFail("Error: ViewerTest is unable processing messages for unknown X Display");
    return;
  }

  // process new events in queue
  SetDisplayConnection(aDispConn);
  int aNbRemain = 0;
  for (int aNbEventsMax = XPending(aDispX), anEventIter(0);;)
  {
    const int anEventResult = ViewerMainLoop(0, nullptr);
    if (anEventResult == 0)
    {
      return;
    }

    aNbRemain = XPending(aDispX);
    if (++anEventIter >= aNbEventsMax || aNbRemain <= 0)
    {
      break;
    }
  }

  // Listening X events through Tcl_CreateFileHandler() callback is fragile,
  // it is possible that new events will arrive to queue before the end of this callback
  // so that either this callback should go into an infinite loop (blocking processing of other
  // events) or to keep unprocessed events till the next queue update (which can arrive not soon).
  // Sending a dummy event in this case is a simple workaround (still, it is possible that new event
  // will be queued in-between).
  if (aNbRemain != 0)
  {
    XEvent aDummyEvent;
    memset(&aDummyEvent, 0, sizeof(aDummyEvent));
    aDummyEvent.type           = ClientMessage;
    aDummyEvent.xclient.format = 32;
    XSendEvent(aDispX, InputFocus, False, 0, &aDummyEvent);
    XFlush(aDispX);
  }

  if (const occ::handle<AIS_InteractiveContext>& anActiveCtx = ViewerTest::GetAISContext())
  {
    SetDisplayConnection(anActiveCtx->CurrentViewer()->Driver()->GetDisplayConnection());
  }
}
#elif !defined(__APPLE__)
//=================================================================================================

int ViewerMainLoop(int, const char**)
{
  // unused
  return 0;
}
#endif

//=================================================================================================

static int VFit(Draw_Interpretor& /*theDi*/, int theArgNb, const char** theArgv)
{
  const occ::handle<V3d_View> aView = ViewerTest::CurrentView();
  if (aView.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  bool                   toFit = true;
  ViewerTest_AutoUpdater anUpdateTool(occ::handle<AIS_InteractiveContext>(), aView);
  for (int anArgIter = 1; anArgIter < theArgNb; ++anArgIter)
  {
    TCollection_AsciiString anArg(theArgv[anArgIter]);
    anArg.LowerCase();
    if (anUpdateTool.parseRedrawMode(anArg))
    {
      continue;
    }
    else if (anArg == "-selected")
    {
      ViewerTest::GetAISContext()->FitSelected(aView, 0.01, false);
      toFit = false;
    }
    else
    {
      Message::SendFail() << "Syntax error at '" << anArg << "'";
    }
  }

  if (toFit)
  {
    aView->FitAll(0.01, false);
  }
  return 0;
}

//=======================================================================
// function : VFitArea
// purpose  : Fit view to show area located between two points
//         : given in world 2D or 3D coordinates.
//=======================================================================
static int VFitArea(Draw_Interpretor& theDI, int theArgNb, const char** theArgVec)
{
  occ::handle<V3d_View> aView = ViewerTest::CurrentView();
  if (aView.IsNull())
  {
    Message::SendFail("Error: No active viewer");
    return 1;
  }

  // Parse arguments.
  gp_Pnt aWorldPnt1(0.0, 0.0, 0.0);
  gp_Pnt aWorldPnt2(0.0, 0.0, 0.0);

  if (theArgNb == 5)
  {
    aWorldPnt1.SetX(Draw::Atof(theArgVec[1]));
    aWorldPnt1.SetY(Draw::Atof(theArgVec[2]));
    aWorldPnt2.SetX(Draw::Atof(theArgVec[3]));
    aWorldPnt2.SetY(Draw::Atof(theArgVec[4]));
  }
  else if (theArgNb == 7)
  {
    aWorldPnt1.SetX(Draw::Atof(theArgVec[1]));
    aWorldPnt1.SetY(Draw::Atof(theArgVec[2]));
    aWorldPnt1.SetZ(Draw::Atof(theArgVec[3]));
    aWorldPnt2.SetX(Draw::Atof(theArgVec[4]));
    aWorldPnt2.SetY(Draw::Atof(theArgVec[5]));
    aWorldPnt2.SetZ(Draw::Atof(theArgVec[6]));
  }
  else
  {
    Message::SendFail("Syntax error: Invalid number of arguments");
    theDI.PrintHelp(theArgVec[0]);
    return 1;
  }

  // Convert model coordinates to view space
  occ::handle<Graphic3d_Camera> aCamera   = aView->Camera();
  gp_Pnt                        aViewPnt1 = aCamera->ConvertWorld2View(aWorldPnt1);
  gp_Pnt                        aViewPnt2 = aCamera->ConvertWorld2View(aWorldPnt2);

  // Determine fit area
  gp_Pnt2d aMinCorner(std::min(aViewPnt1.X(), aViewPnt2.X()),
                      std::min(aViewPnt1.Y(), aViewPnt2.Y()));
  gp_Pnt2d aMaxCorner(std::max(aViewPnt1.X(), aViewPnt2.X()),
                      std::max(aViewPnt1.Y(), aViewPnt2.Y()));

  double aDiagonal = aMinCorner.Distance(aMaxCorner);

  if (aDiagonal < Precision::Confusion())
  {
    Message::SendFail("Error: view area is too small");
    return 1;
  }

  aView->FitAll(aMinCorner.X(), aMinCorner.Y(), aMaxCorner.X(), aMaxCorner.Y());
  return 0;
}

//==============================================================================
// function : VZFit
// purpose  : ZFitall, no DRAW arguments
// Draw arg : No args
//==============================================================================
static int VZFit(Draw_Interpretor& /*theDi*/, int theArgsNb, const char** theArgVec)
{
  const occ::handle<V3d_View>& aCurrentView = ViewerTest::CurrentView();

  if (aCurrentView.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  if (theArgsNb == 1)
  {
    aCurrentView->ZFitAll();
    aCurrentView->Redraw();
    return 0;
  }

  double aScale = 1.0;

  if (theArgsNb >= 2)
  {
    aScale = Draw::Atoi(theArgVec[1]);
  }

  aCurrentView->ZFitAll(aScale);
  aCurrentView->Redraw();

  return 0;
}

//=================================================================================================

static int VRepaint(Draw_Interpretor&, int theArgNb, const char** theArgVec)
{
  occ::handle<V3d_View> aView = ViewerTest::CurrentView();
  if (aView.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  bool isImmediateUpdate = false;
  for (int anArgIter = 1; anArgIter < theArgNb; ++anArgIter)
  {
    TCollection_AsciiString anArg(theArgVec[anArgIter]);
    anArg.LowerCase();
    if (anArg == "-immediate" || anArg == "-imm")
    {
      isImmediateUpdate = true;
      if (anArgIter + 1 < theArgNb && Draw::ParseOnOff(theArgVec[anArgIter + 1], isImmediateUpdate))
      {
        ++anArgIter;
      }
    }
    else if (anArg == "-continuous" || anArg == "-cont" || anArg == "-fps" || anArg == "-framerate")
    {
      double aFps = -1.0;
      if (anArgIter + 1 < theArgNb
          && TCollection_AsciiString(theArgVec[anArgIter + 1]).IsRealValue(true))
      {
        aFps = Draw::Atof(theArgVec[++anArgIter]);
      }

      ViewerTest_ContinuousRedrawer& aRedrawer = ViewerTest_ContinuousRedrawer::Instance();
      ViewerTest::CurrentEventManager()->SetContinuousRedraw(false);
      if (aFps >= 1.0)
      {
        aRedrawer.Start(aView, aFps);
      }
      else if (aFps < 0.0)
      {
        if (ViewerTest::GetViewerFromContext()->ActiveViews().Extent() == 1)
        {
          aRedrawer.Stop();
          ViewerTest::CurrentEventManager()->SetContinuousRedraw(true);
          ViewerTest::CurrentEventManager()->FlushViewEvents(ViewerTest::GetAISContext(),
                                                             ViewerTest::CurrentView(),
                                                             true);
          continue;
        }
        aRedrawer.Start(aView, aFps);
      }
      else
      {
        aRedrawer.Stop();
      }
    }
    else
    {
      Message::SendFail() << "Syntax error at '" << anArg << "'";
      return 1;
    }
  }

  if (isImmediateUpdate)
  {
    aView->RedrawImmediate();
  }
  else
  {
    aView->Redraw();
  }
  return 0;
}

//==============================================================================
// function : VClear
// purpose  : Remove all the object from the viewer
// Draw arg : No args
//==============================================================================

static int VClear(Draw_Interpretor&, int, const char**)
{
  occ::handle<V3d_View> V = ViewerTest::CurrentView();
  if (!V.IsNull())
  {
    ViewerTest::Clear();
  }
  return 0;
}

//=================================================================================================

static int VPick(Draw_Interpretor&, int theNbArgs, const char** theArgVec)
{
  if (ViewerTest::CurrentView().IsNull())
  {
    return 1;
  }

  if (theNbArgs < 4)
  {
    Message::SendFail("Syntax error: wrong number of arguments");
    return 1;
  }

  while (ViewerMainLoop(theNbArgs, theArgVec))
  {
    //
  }

  return 0;
}

//! Parse image fill method.
static bool parseImageMode(const TCollection_AsciiString& theName, Aspect_FillMethod& theMode)
{
  TCollection_AsciiString aName = theName;
  aName.LowerCase();
  if (aName == "none")
  {
    theMode = Aspect_FM_NONE;
  }
  else if (aName == "centered")
  {
    theMode = Aspect_FM_CENTERED;
  }
  else if (aName == "tiled")
  {
    theMode = Aspect_FM_TILED;
  }
  else if (aName == "stretch")
  {
    theMode = Aspect_FM_STRETCH;
  }
  else
  {
    return false;
  }
  return true;
}

//! Parse gradient fill method.
static bool parseGradientMode(const TCollection_AsciiString& theName,
                              Aspect_GradientFillMethod&     theMode)
{
  TCollection_AsciiString aName = theName;
  aName.LowerCase();
  if (aName == "none")
  {
    theMode = Aspect_GradientFillMethod_None;
  }
  else if (aName == "hor" || aName == "horizontal")
  {
    theMode = Aspect_GradientFillMethod_Horizontal;
  }
  else if (aName == "ver" || aName == "vert" || aName == "vertical")
  {
    theMode = Aspect_GradientFillMethod_Vertical;
  }
  else if (aName == "diag" || aName == "diagonal" || aName == "diag1" || aName == "diagonal1")
  {
    theMode = Aspect_GradientFillMethod_Diagonal1;
  }
  else if (aName == "diag2" || aName == "diagonal2")
  {
    theMode = Aspect_GradientFillMethod_Diagonal2;
  }
  else if (aName == "corner1")
  {
    theMode = Aspect_GradientFillMethod_Corner1;
  }
  else if (aName == "corner2")
  {
    theMode = Aspect_GradientFillMethod_Corner2;
  }
  else if (aName == "corner3")
  {
    theMode = Aspect_GradientFillMethod_Corner3;
  }
  else if (aName == "corner4")
  {
    theMode = Aspect_GradientFillMethod_Corner4;
  }
  else if (aName == "ellip" || aName == "elliptical")
  {
    theMode = Aspect_GradientFillMethod_Elliptical;
  }
  else
  {
    return false;
  }
  return true;
}

//=================================================================================================

static int VBackground(Draw_Interpretor& theDI, int theNbArgs, const char** theArgVec)
{
  if (theNbArgs < 2)
  {
    theDI << "Syntax error: wrong number of arguments";
    return 1;
  }

  const TCollection_AsciiString aCmdName(theArgVec[0]);
  bool                          isDefault = aCmdName == "vsetdefaultbg";
  int                           aNbColors = 0;
  Quantity_ColorRGBA            aColors[2];

  Aspect_GradientFillMethod aGradientMode   = Aspect_GradientFillMethod_None;
  bool                      hasGradientMode = false;

  TCollection_AsciiString anImagePath;
  Aspect_FillMethod       anImageMode  = Aspect_FM_CENTERED;
  bool                    hasImageMode = false;

  bool                     isSkydomeBg = false;
  Aspect_SkydomeBackground aSkydomeAspect;

  NCollection_Sequence<TCollection_AsciiString> aCubeMapSeq;
  Graphic3d_CubeMapOrder                        aCubeOrder      = Graphic3d_CubeMapOrder::Default();
  bool                                          isCubeZInverted = false;
  bool                                          isSRgb          = true;

  int toUseIBL = 1;

  occ::handle<V3d_View>  aView = ViewerTest::CurrentView();
  ViewerTest_AutoUpdater anUpdateTool(ViewerTest::GetAISContext(), aView);
  for (int anArgIter = 1; anArgIter < theNbArgs; ++anArgIter)
  {
    TCollection_AsciiString anArg(theArgVec[anArgIter]);
    anArg.LowerCase();
    if (anUpdateTool.parseRedrawMode(anArg))
    {
      continue;
    }
    else if (anArg == "-default" || anArg == "-def")
    {
      isDefault = true;
    }
    else if (anArgIter + 1 < theNbArgs
             && (anArg == "-imagefile" || anArg == "-imgfile" || anArg == "-image"
                 || anArg == "-img"))
    {
      anImagePath = theArgVec[++anArgIter];
    }
    else if (anArg == "-skydome" || anArg == "-sky")
    {
      isSkydomeBg = true;
    }
    else if (anArgIter + 3 < theNbArgs && isSkydomeBg && anArg == "-sundir")
    {
      float aX = (float)Draw::Atof(theArgVec[++anArgIter]);
      float aY = (float)Draw::Atof(theArgVec[++anArgIter]);
      float aZ = (float)Draw::Atof(theArgVec[++anArgIter]);
      aSkydomeAspect.SetSunDirection(gp_Dir(aX, aY, aZ));
    }
    else if (anArgIter + 1 < theNbArgs && isSkydomeBg && anArg == "-cloud")
    {
      float aCloudy = (float)Draw::Atof(theArgVec[++anArgIter]);
      aSkydomeAspect.SetCloudiness(aCloudy);
    }
    else if (anArgIter + 1 < theNbArgs && isSkydomeBg && anArg == "-time")
    {
      float aTime = (float)Draw::Atof(theArgVec[++anArgIter]);
      aSkydomeAspect.SetTimeParameter(aTime);
    }
    else if (anArgIter + 1 < theNbArgs && isSkydomeBg && anArg == "-fog")
    {
      float aFoggy = (float)Draw::Atof(theArgVec[++anArgIter]);
      aSkydomeAspect.SetFogginess(aFoggy);
    }
    else if (anArgIter + 1 < theNbArgs && isSkydomeBg && anArg == "-size")
    {
      int aSize = Draw::Atoi(theArgVec[++anArgIter]);
      aSkydomeAspect.SetSize(aSize);
    }
    else if (anArgIter + 1 < theNbArgs && aCubeMapSeq.IsEmpty()
             && (anArg == "-cubemap" || anArg == "-cmap" || anArg == "-cm"))
    {
      aCubeMapSeq.Append(theArgVec[++anArgIter]);
      for (int aCubeSideIter = 1; anArgIter + aCubeSideIter < theNbArgs; ++aCubeSideIter)
      {
        TCollection_AsciiString aSideArg(theArgVec[anArgIter + aCubeSideIter]);
        if (!aSideArg.IsEmpty() && aSideArg.Value(1) == '-')
        {
          break;
        }

        aCubeMapSeq.Append(aSideArg);
        if (aCubeMapSeq.Size() == 6)
        {
          anArgIter += 5;
          break;
        }
      }

      if (aCubeMapSeq.Size() > 1 && aCubeMapSeq.Size() < 6)
      {
        aCubeMapSeq.Remove(2, aCubeMapSeq.Length());
      }
    }
    else if (anArgIter + 6 < theNbArgs && anArg == "-order")
    {
      for (int aCubeSideIter = 0; aCubeSideIter < 6; ++aCubeSideIter)
      {
        int aSideArg = 0;
        if (!Draw::ParseInteger(theArgVec[anArgIter + aCubeSideIter + 1], aSideArg) || aSideArg < 0
            || aSideArg > 5)
        {
          theDI << "Syntax error at '" << theArgVec[anArgIter] << "'";
          return 1;
        }
        aCubeOrder.Set((Graphic3d_CubeMapSide)aCubeSideIter, (unsigned char)aSideArg);
      }
      if (!aCubeOrder.IsValid())
      {
        theDI << "Syntax error at '" << theArgVec[anArgIter] << "'";
        return 1;
      }
      anArgIter += 6;
    }
    else if (anArg == "-invertedz" || anArg == "-noinvertedz" || anArg == "-invz"
             || anArg == "-noinvz")
    {
      isCubeZInverted = Draw::ParseOnOffNoIterator(theNbArgs, theArgVec, anArgIter);
    }
    else if (anArg == "-pbrenv" || anArg == "-nopbrenv" || anArg == "-ibl" || anArg == "-noibl")
    {
      toUseIBL = !anArg.StartsWith("-no") ? 1 : 0;
      if (anArgIter + 1 < theNbArgs)
      {
        TCollection_AsciiString anIblArg(theArgVec[anArgIter + 1]);
        anIblArg.LowerCase();
        if (anIblArg == "keep" || anIblArg == "-1")
        {
          toUseIBL = -1;
          ++anArgIter;
        }
        else if (anIblArg == "ibl" || anIblArg == "1" || anIblArg == "on")
        {
          toUseIBL = !anArg.StartsWith("-no") ? 1 : 0;
          ++anArgIter;
        }
        else if (anIblArg == "noibl" || anIblArg == "0" || anIblArg == "off")
        {
          toUseIBL = !anArg.StartsWith("-no") ? 0 : 1;
          ++anArgIter;
        }
      }
    }
    else if (anArg == "-srgb" || anArg == "-nosrgb")
    {
      isSRgb = Draw::ParseOnOffNoIterator(theNbArgs, theArgVec, anArgIter);
    }
    else if (aNbColors < 2 && (anArg == "-color" || anArg == "-col"))
    {
      int aNbParsed = Draw::ParseColor(theNbArgs - (anArgIter + 1),
                                       theArgVec + (anArgIter + 1),
                                       aColors[aNbColors].ChangeRGB());
      if (aNbParsed == 0)
      {
        theDI << "Syntax error at '" << theArgVec[anArgIter] << "'";
        return 1;
      }
      anArgIter += aNbParsed;
      ++aNbColors;
    }
    else if (anArgIter + 1 < theNbArgs
             && (anArg == "-gradientmode" || anArg == "-gradmode" || anArg == "-gradmd"
                 || anArg == "-grmode" || anArg == "-grmd")
             && parseGradientMode(theArgVec[anArgIter + 1], aGradientMode))
    {
      ++anArgIter;
      hasGradientMode = true;
    }
    else if (anArgIter + 1 < theNbArgs
             && (anArg == "-imagemode" || anArg == "-imgmode" || anArg == "-imagemd"
                 || anArg == "-imgmd")
             && parseImageMode(theArgVec[anArgIter + 1], anImageMode))
    {
      ++anArgIter;
      hasImageMode = true;
    }
    else if (aNbColors == 0 && anArgIter + 2 < theNbArgs
             && (anArg == "-gradient" || anArg == "-grad" || anArg == "-gr"))
    {
      int aNbParsed1 = Draw::ParseColor(theNbArgs - (anArgIter + 1),
                                        theArgVec + (anArgIter + 1),
                                        aColors[aNbColors].ChangeRGB());
      anArgIter += aNbParsed1;
      ++aNbColors;
      if (aNbParsed1 == 0)
      {
        theDI << "Syntax error at '" << theArgVec[anArgIter] << "'";
        return 1;
      }
      int aNbParsed2 = Draw::ParseColor(theNbArgs - (anArgIter + 1),
                                        theArgVec + (anArgIter + 1),
                                        aColors[aNbColors].ChangeRGB());
      anArgIter += aNbParsed2;
      ++aNbColors;
      if (aNbParsed2 == 0)
      {
        theDI << "Syntax error at '" << theArgVec[anArgIter] << "'";
        return 1;
      }
    }
    else if (parseGradientMode(theArgVec[anArgIter], aGradientMode))
    {
      hasGradientMode = true;
    }
    else if (aNbColors < 2
             && (Quantity_ColorRGBA::ColorFromName(theArgVec[anArgIter], aColors[aNbColors])
                 || Quantity_ColorRGBA::ColorFromHex(theArgVec[anArgIter], aColors[aNbColors])))
    {
      ++aNbColors;
    }
    else if (anImagePath.IsEmpty() && aNbColors == 0 && !hasGradientMode && aCubeMapSeq.IsEmpty())
    {
      anImagePath = theArgVec[anArgIter];
    }
    else
    {
      theDI << "Syntax error at '" << theArgVec[anArgIter] << "'";
      return 1;
    }
  }

  if (!isDefault && aView.IsNull())
  {
    theDI << "Error: no active viewer";
    return 1;
  }
  else if (isDefault && aNbColors == 0 && !hasGradientMode)
  {
    theDI << "Syntax error at '-default'";
    return 1;
  }

  if (aNbColors == 1)
  {
    if (isDefault)
    {
      ViewerTest_DefaultBackground.GradientColor1 = Quantity_Color();
      ViewerTest_DefaultBackground.GradientColor2 = Quantity_Color();
      ViewerTest_DefaultBackground.FillMethod     = Aspect_GradientFillMethod_None;
      ViewerTest_DefaultBackground.FlatColor      = aColors[0].GetRGB();
      ViewerTest_DefaultBackground.SetDefaultGradient();
      ViewerTest_DefaultBackground.SetDefaultColor();
    }
    else
    {
      aView->SetBgGradientStyle(hasGradientMode ? aGradientMode : Aspect_GradientFillMethod_None);
      aView->SetBackgroundColor(aColors[0].GetRGB());
      if (toUseIBL != -1)
      {
        aView->SetBackgroundCubeMap(occ::handle<Graphic3d_CubeMap>(), true);
      }
    }
  }
  else if (aNbColors == 2)
  {
    if (isDefault)
    {
      ViewerTest_DefaultBackground.GradientColor1 = aColors[0].GetRGB();
      ViewerTest_DefaultBackground.GradientColor2 = aColors[1].GetRGB();
      if (hasGradientMode)
      {
        ViewerTest_DefaultBackground.FillMethod = aGradientMode;
      }
      else if (ViewerTest_DefaultBackground.FillMethod == Aspect_GradientFillMethod_None)
      {
        ViewerTest_DefaultBackground.FillMethod = Aspect_GradientFillMethod_Vertical;
      }
      ViewerTest_DefaultBackground.SetDefaultGradient();
    }
    else
    {
      if (!hasGradientMode)
      {
        aGradientMode = aView->GradientBackground().BgGradientFillMethod();
        if (aGradientMode == Aspect_GradientFillMethod_None)
        {
          aGradientMode = Aspect_GradientFillMethod_Vertical;
        }
      }
      aView->SetBgGradientColors(aColors[0].GetRGB(), aColors[1].GetRGB(), aGradientMode);
      if (toUseIBL != -1)
      {
        aView->SetBackgroundCubeMap(occ::handle<Graphic3d_CubeMap>(), true);
      }
    }
  }
  else if (hasGradientMode)
  {
    if (isDefault)
    {
      ViewerTest_DefaultBackground.FillMethod = aGradientMode;
      ViewerTest_DefaultBackground.SetDefaultGradient();
    }
    else
    {
      aView->SetBgGradientStyle(aGradientMode);
    }
  }

  if (!anImagePath.IsEmpty())
  {
    occ::handle<Graphic3d_Texture2D> aTextureMap = new Graphic3d_Texture2D(anImagePath);
    aTextureMap->DisableModulate();
    aTextureMap->SetColorMap(isSRgb);
    if (!aTextureMap->IsDone())
    {
      theDI << "Syntax error at '" << anImagePath << "'";
      return 1;
    }
    aView->SetBackgroundImage(aTextureMap, anImageMode);
  }
  else if (hasImageMode)
  {
    aView->SetBgImageStyle(anImageMode);
  }

  if (isSkydomeBg)
  {
    aView->SetBackgroundSkydome(aSkydomeAspect, toUseIBL != -1);
  }

  if (!aCubeMapSeq.IsEmpty())
  {
    occ::handle<Graphic3d_CubeMap> aCubeMap;
    if (aCubeMapSeq.Size() == 1)
    {
      aCubeMap = new Graphic3d_CubeMapPacked(aCubeMapSeq.First(), aCubeOrder.Validated());
    }
    else
    {
      NCollection_Array1<TCollection_AsciiString> aCubeMapArr(0, 5);
      int                                         aCubeSide = 0;
      for (NCollection_Sequence<TCollection_AsciiString>::Iterator aFileIter(aCubeMapSeq);
           aFileIter.More();
           aFileIter.Next(), ++aCubeSide)
      {
        aCubeMapArr[aCubeSide] = aFileIter.Value();
      }
      aCubeMap = new Graphic3d_CubeMapSeparate(aCubeMapArr);
    }

    aCubeMap->SetZInversion(isCubeZInverted);
    aCubeMap->SetColorMap(isSRgb);

    aCubeMap->GetParams()->SetFilter(Graphic3d_TOTF_BILINEAR);
    aCubeMap->GetParams()->SetRepeat(false);
    aCubeMap->GetParams()->SetTextureUnit(Graphic3d_TextureUnit_EnvMap);

    aView->SetBackgroundCubeMap(aCubeMap, toUseIBL != -1);
  }
  if (toUseIBL != -1 && !aView.IsNull())
  {
    aView->SetImageBasedLighting(toUseIBL == 1);
  }

  return 0;
}

//=================================================================================================

static int VScale(Draw_Interpretor& di, int argc, const char** argv)
{
  occ::handle<V3d_View> V3dView = ViewerTest::CurrentView();
  if (V3dView.IsNull())
  {
    return 1;
  }

  if (argc != 4)
  {
    di << argv[0] << "Invalid number of arguments\n";
    return 1;
  }
  V3dView->SetAxialScale(Draw::Atof(argv[1]), Draw::Atof(argv[2]), Draw::Atof(argv[3]));
  return 0;
}

//=================================================================================================

static int VZBuffTrihedron(Draw_Interpretor& /*theDI*/, int theArgNb, const char** theArgVec)
{
  occ::handle<V3d_View> aView = ViewerTest::CurrentView();
  if (aView.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  ViewerTest_AutoUpdater anUpdateTool(ViewerTest::GetAISContext(), aView);

  Aspect_TypeOfTriedronPosition aPosition     = Aspect_TOTP_LEFT_LOWER;
  V3d_TypeOfVisualization       aVisType      = V3d_ZBUFFER;
  Quantity_Color                aLabelsColorX = Quantity_NOC_WHITE;
  Quantity_Color                aLabelsColorY = Quantity_NOC_WHITE;
  Quantity_Color                aLabelsColorZ = Quantity_NOC_WHITE;
  Quantity_Color                anArrowColorX = Quantity_NOC_RED;
  Quantity_Color                anArrowColorY = Quantity_NOC_GREEN;
  Quantity_Color                anArrowColorZ = Quantity_NOC_BLUE1;
  double                        aScale        = 0.1;
  double                        aSizeRatio    = 0.8;
  double                        anArrowDiam   = 0.05;
  int                           aNbFacets     = 12;
  for (int anArgIter = 1; anArgIter < theArgNb; ++anArgIter)
  {
    const char*             anArg = theArgVec[anArgIter];
    TCollection_AsciiString aFlag(anArg);
    aFlag.LowerCase();
    if (anUpdateTool.parseRedrawMode(aFlag))
    {
      continue;
    }
    else if (aFlag == "-on")
    {
      continue;
    }
    else if (aFlag == "-off")
    {
      aView->TriedronErase();
      return 0;
    }
    else if (aFlag == "-pos" || aFlag == "-position" || aFlag == "-corner")
    {
      if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at '" << anArg << "'";
        return 1;
      }

      if (!ViewerTest::ParseCorner(theArgVec[anArgIter], aPosition))
      {
        Message::SendFail() << "Syntax error at '" << anArg << "' - unknown position '"
                            << theArgVec[anArgIter] << "'";
        return 1;
      }
    }
    else if (aFlag == "-type")
    {
      if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Error: wrong syntax at '" << anArg << "'";
        return 1;
      }

      TCollection_AsciiString aTypeName(theArgVec[anArgIter]);
      aTypeName.LowerCase();
      if (aTypeName == "wireframe" || aTypeName == "wire")
      {
        aVisType = V3d_WIREFRAME;
      }
      else if (aTypeName == "zbuffer" || aTypeName == "shaded")
      {
        aVisType = V3d_ZBUFFER;
      }
      else
      {
        Message::SendFail() << "Error: wrong syntax at '" << anArg << "' - unknown type '"
                            << aTypeName << "'";
      }
    }
    else if (aFlag == "-scale")
    {
      if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Error: wrong syntax at '" << anArg << "'";
        return 1;
      }

      aScale = Draw::Atof(theArgVec[anArgIter]);
    }
    else if (aFlag == "-size" || aFlag == "-sizeratio")
    {
      if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Error: wrong syntax at '" << anArg << "'";
        return 1;
      }

      aSizeRatio = Draw::Atof(theArgVec[anArgIter]);
    }
    else if (aFlag == "-arrowdiam" || aFlag == "-arrowdiameter")
    {
      if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Error: wrong syntax at '" << anArg << "'";
        return 1;
      }

      anArrowDiam = Draw::Atof(theArgVec[anArgIter]);
    }
    else if (aFlag == "-nbfacets")
    {
      if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Error: wrong syntax at '" << anArg << "'";
        return 1;
      }

      aNbFacets = Draw::Atoi(theArgVec[anArgIter]);
    }
    else if (aFlag == "-colorlabel" || aFlag == "-colorlabels" || aFlag == "-colorlabelx"
             || aFlag == "-colorlabely" || aFlag == "-colorlabelz" || aFlag == "-colorarrowx"
             || aFlag == "-colorarrowy" || aFlag == "-colorarrowz")
    {
      Quantity_Color aColor;
      int aNbParsed = Draw::ParseColor(theArgNb - anArgIter - 1, theArgVec + anArgIter + 1, aColor);
      if (aNbParsed == 0)
      {
        Message::SendFail() << "Error: wrong syntax at '" << anArg << "'";
        return 1;
      }

      if (aFlag == "-colorarrowx")
      {
        anArrowColorX = aColor;
      }
      else if (aFlag == "-colorarrowy")
      {
        anArrowColorY = aColor;
      }
      else if (aFlag == "-colorarrowz")
      {
        anArrowColorZ = aColor;
      }
      else if (aFlag == "-colorlabelx")
      {
        aLabelsColorX = aColor;
      }
      else if (aFlag == "-colorlabely")
      {
        aLabelsColorY = aColor;
      }
      else if (aFlag == "-colorlabelz")
      {
        aLabelsColorZ = aColor;
      }
      else
      {
        aLabelsColorZ = aLabelsColorY = aLabelsColorX = aColor;
      }
      anArgIter += aNbParsed;
    }
    else
    {
      Message::SendFail() << "Error: wrong syntax at '" << anArg << "'";
      return 1;
    }
  }

  const occ::handle<V3d_Trihedron>& aTrihedron = aView->Trihedron();
  aTrihedron->SetArrowsColor(anArrowColorX, anArrowColorY, anArrowColorZ);
  aTrihedron->SetLabelsColor(aLabelsColorX, aLabelsColorY, aLabelsColorZ);
  aTrihedron->SetSizeRatio(aSizeRatio);
  aTrihedron->SetNbFacets(aNbFacets);
  aTrihedron->SetArrowDiameter(anArrowDiam);
  aTrihedron->SetScale(aScale);
  aTrihedron->SetPosition(aPosition);
  aTrihedron->SetWireframe(aVisType == V3d_WIREFRAME);
  aTrihedron->Display(aView);

  aView->ZFitAll();
  return 0;
}

//=================================================================================================

static int VRotate(Draw_Interpretor& /*theDi*/, int theArgNb, const char** theArgVec)
{
  occ::handle<V3d_View> aView = ViewerTest::CurrentView();
  if (aView.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  bool hasFlags = false;
  for (int anArgIter = 1; anArgIter < theArgNb; ++anArgIter)
  {
    const char*             anArg(theArgVec[anArgIter]);
    TCollection_AsciiString aFlag(anArg);
    aFlag.LowerCase();
    if (aFlag == "-mousestart" || aFlag == "-mousefrom")
    {
      hasFlags = true;
      if (anArgIter + 2 >= theArgNb)
      {
        Message::SendFail() << "Error: wrong syntax at '" << anArg << "'";
        return 1;
      }

      int anX = Draw::Atoi(theArgVec[++anArgIter]);
      int anY = Draw::Atoi(theArgVec[++anArgIter]);
      aView->StartRotation(anX, anY);
    }
    else if (aFlag == "-mousemove")
    {
      hasFlags = true;
      if (anArgIter + 2 >= theArgNb)
      {
        Message::SendFail() << "Error: wrong syntax at '" << anArg << "'";
        return 1;
      }

      int anX = Draw::Atoi(theArgVec[++anArgIter]);
      int anY = Draw::Atoi(theArgVec[++anArgIter]);
      aView->Rotation(anX, anY);
    }
    else if (theArgNb != 4 && theArgNb != 7)
    {
      Message::SendFail() << "Error: wrong syntax at '" << anArg << "'";
      return 1;
    }
  }

  if (hasFlags)
  {
    return 0;
  }
  else if (theArgNb == 4)
  {
    double anAX = Draw::Atof(theArgVec[1]);
    double anAY = Draw::Atof(theArgVec[2]);
    double anAZ = Draw::Atof(theArgVec[3]);
    aView->Rotate(anAX, anAY, anAZ);
    return 0;
  }
  else if (theArgNb == 7)
  {
    double anAX = Draw::Atof(theArgVec[1]);
    double anAY = Draw::Atof(theArgVec[2]);
    double anAZ = Draw::Atof(theArgVec[3]);

    double anX = Draw::Atof(theArgVec[4]);
    double anY = Draw::Atof(theArgVec[5]);
    double anZ = Draw::Atof(theArgVec[6]);

    aView->Rotate(anAX, anAY, anAZ, anX, anY, anZ);
    return 0;
  }

  Message::SendFail("Error: Invalid number of arguments");
  return 1;
}

//==============================================================================
// function : VZoom
// purpose  : View zoom in / out (relative to current zoom)
//==============================================================================

static int VZoom(Draw_Interpretor& di, int argc, const char** argv)
{
  occ::handle<V3d_View> V3dView = ViewerTest::CurrentView();
  if (V3dView.IsNull())
  {
    return 1;
  }

  if (argc == 2)
  {
    double coef = Draw::Atof(argv[1]);
    if (coef <= 0.0)
    {
      di << argv[1] << "Invalid value\n";
      return 1;
    }
    V3dView->SetZoom(Draw::Atof(argv[1]));
    return 0;
  }
  else
  {
    di << argv[0] << " Invalid number of arguments\n";
    return 1;
  }
}

//==============================================================================
// function : VPan
// purpose  : View panning (in pixels)
//==============================================================================

static int VPan(Draw_Interpretor& di, int argc, const char** argv)
{
  occ::handle<V3d_View> V3dView = ViewerTest::CurrentView();
  if (V3dView.IsNull())
  {
    return 1;
  }

  if (argc == 3)
  {
    V3dView->Pan(Draw::Atoi(argv[1]), Draw::Atoi(argv[2]));
    return 0;
  }
  else
  {
    di << argv[0] << " Invalid number of arguments\n";
    return 1;
  }
}

//==============================================================================
// function : VPlace
// purpose  : Place the point (in pixels) at the center of the window
//==============================================================================
static int VPlace(Draw_Interpretor& /*theDi*/, int theArgNb, const char** theArgs)
{
  occ::handle<V3d_View> aView = ViewerTest::CurrentView();
  if (aView.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  if (theArgNb != 3)
  {
    Message::SendFail("Syntax error: wrong number of arguments");
    return 1;
  }

  aView->Place(Draw::Atoi(theArgs[1]), Draw::Atoi(theArgs[2]), aView->Scale());

  return 0;
}

static int VColorScale(Draw_Interpretor& theDI, int theArgNb, const char** theArgVec)
{
  occ::handle<AIS_InteractiveContext> aContext = ViewerTest::GetAISContext();
  occ::handle<V3d_View>               aView    = ViewerTest::CurrentView();
  if (aContext.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }
  if (theArgNb <= 1)
  {
    Message::SendFail() << "Error: wrong syntax at command '" << theArgVec[0] << "'";
    return 1;
  }

  occ::handle<AIS_ColorScale> aColorScale;
  if (GetMapOfAIS().IsBound2(theArgVec[1]))
  {
    // find existing object
    aColorScale = occ::down_cast<AIS_ColorScale>(GetMapOfAIS().Find2(theArgVec[1]));
    if (aColorScale.IsNull())
    {
      Message::SendFail() << "Error: object '" << theArgVec[1]
                          << "'is already defined and is not a color scale";
      return 1;
    }
  }

  if (theArgNb <= 2)
  {
    if (aColorScale.IsNull())
    {
      Message::SendFail() << "Syntax error: colorscale with a given name does not exist";
      return 1;
    }

    theDI << "Color scale parameters for '" << theArgVec[1] << "':\n"
          << "Min range: " << aColorScale->GetMin() << "\n"
          << "Max range: " << aColorScale->GetMax() << "\n"
          << "Number of intervals: " << aColorScale->GetNumberOfIntervals() << "\n"
          << "Text height: " << aColorScale->GetTextHeight() << "\n"
          << "Color scale position: " << aColorScale->GetXPosition() << " "
          << aColorScale->GetYPosition() << "\n"
          << "Color scale title: " << aColorScale->GetTitle() << "\n"
          << "Label position: ";
    switch (aColorScale->GetLabelPosition())
    {
      case Aspect_TOCSP_NONE:
        theDI << "None\n";
        break;
      case Aspect_TOCSP_LEFT:
        theDI << "Left\n";
        break;
      case Aspect_TOCSP_RIGHT:
        theDI << "Right\n";
        break;
      case Aspect_TOCSP_CENTER:
        theDI << "Center\n";
        break;
    }
    return 0;
  }

  if (aColorScale.IsNull())
  {
    aColorScale = new AIS_ColorScale();
    aColorScale->SetZLayer(Graphic3d_ZLayerId_TopOSD);
    aContext->SetTransformPersistence(
      aColorScale,
      new Graphic3d_TransformPers(Graphic3d_TMF_2d, Aspect_TOTP_LEFT_LOWER));
  }

  ViewerTest_AutoUpdater anUpdateTool(aContext, aView);
  for (int anArgIter = 2; anArgIter < theArgNb; ++anArgIter)
  {
    const char*             anArg = theArgVec[anArgIter];
    TCollection_AsciiString aFlag(anArg);
    aFlag.LowerCase();
    if (anUpdateTool.parseRedrawMode(aFlag))
    {
      continue;
    }
    else if (aFlag == "-range")
    {
      if (anArgIter + 3 >= theArgNb)
      {
        Message::SendFail() << "Error: wrong syntax at argument '" << anArg << "'";
        return 1;
      }

      const TCollection_AsciiString aRangeMin(theArgVec[++anArgIter]);
      const TCollection_AsciiString aRangeMax(theArgVec[++anArgIter]);
      const TCollection_AsciiString aNbIntervals(theArgVec[++anArgIter]);
      if (!aRangeMin.IsRealValue(true) || !aRangeMax.IsRealValue(true))
      {
        Message::SendFail("Syntax error: the range values should be real");
        return 1;
      }
      else if (!aNbIntervals.IsIntegerValue())
      {
        Message::SendFail("Syntax error: the number of intervals should be integer");
        return 1;
      }

      aColorScale->SetRange(aRangeMin.RealValue(), aRangeMax.RealValue());
      aColorScale->SetNumberOfIntervals(aNbIntervals.IntegerValue());
    }
    else if (aFlag == "-font")
    {
      if (anArgIter + 1 >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }
      TCollection_AsciiString aFontArg(theArgVec[anArgIter + 1]);
      if (!aFontArg.IsIntegerValue())
      {
        Message::SendFail("Syntax error: HeightFont value should be integer");
        return 1;
      }

      aColorScale->SetTextHeight(aFontArg.IntegerValue());
      anArgIter += 1;
    }
    else if (aFlag == "-textpos")
    {
      if (anArgIter + 1 >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }

      TCollection_AsciiString aTextPosArg(theArgVec[++anArgIter]);
      aTextPosArg.LowerCase();
      Aspect_TypeOfColorScalePosition aLabPosition = Aspect_TOCSP_NONE;
      if (aTextPosArg == "none")
      {
        aLabPosition = Aspect_TOCSP_NONE;
      }
      else if (aTextPosArg == "left")
      {
        aLabPosition = Aspect_TOCSP_LEFT;
      }
      else if (aTextPosArg == "right")
      {
        aLabPosition = Aspect_TOCSP_RIGHT;
      }
      else if (aTextPosArg == "center")
      {
        aLabPosition = Aspect_TOCSP_CENTER;
      }
      else
      {
        Message::SendFail() << "Syntax error: unknown position '" << aTextPosArg << "'";
        return 1;
      }
      aColorScale->SetLabelPosition(aLabPosition);
    }
    else if (aFlag == "-logarithmic" || aFlag == "-log")
    {
      if (anArgIter + 1 >= theArgNb)
      {
        Message::SendFail() << "Synta error at argument '" << anArg << "'";
        return 1;
      }

      bool IsLog;
      if (!Draw::ParseOnOff(theArgVec[++anArgIter], IsLog))
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }
      aColorScale->SetLogarithmic(IsLog);
    }
    else if (aFlag == "-huerange" || aFlag == "-hue")
    {
      if (anArgIter + 2 >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }

      const double aHueMin = Draw::Atof(theArgVec[++anArgIter]);
      const double aHueMax = Draw::Atof(theArgVec[++anArgIter]);
      aColorScale->SetHueRange(aHueMin, aHueMax);
    }
    else if (aFlag == "-colorrange")
    {
      Quantity_Color aColorMin, aColorMax;
      int            aNbParsed1 =
        Draw::ParseColor(theArgNb - (anArgIter + 1), theArgVec + (anArgIter + 1), aColorMin);
      anArgIter += aNbParsed1;
      int aNbParsed2 =
        Draw::ParseColor(theArgNb - (anArgIter + 1), theArgVec + (anArgIter + 1), aColorMax);
      anArgIter += aNbParsed2;
      if (aNbParsed1 == 0 || aNbParsed2 == 0)
      {
        Message::SendFail() << "Error: wrong syntax at '" << anArg << "'";
        return 1;
      }

      aColorScale->SetColorRange(aColorMin, aColorMax);
    }
    else if (aFlag == "-reversed" || aFlag == "-inverted" || aFlag == "-topdown"
             || aFlag == "-bottomup")
    {
      bool toEnable = true;
      if (anArgIter + 1 < theArgNb && Draw::ParseOnOff(theArgVec[anArgIter + 1], toEnable))
      {
        ++anArgIter;
      }
      aColorScale->SetReversed((aFlag == "-topdown") ? !toEnable : toEnable);
    }
    else if (aFlag == "-smooth" || aFlag == "-smoothtransition")
    {
      bool toEnable = true;
      if (anArgIter + 1 < theArgNb && Draw::ParseOnOff(theArgVec[anArgIter + 1], toEnable))
      {
        ++anArgIter;
      }
      aColorScale->SetSmoothTransition(toEnable);
    }
    else if (aFlag == "-xy")
    {
      if (anArgIter + 2 >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }

      const TCollection_AsciiString anX(theArgVec[++anArgIter]);
      const TCollection_AsciiString anY(theArgVec[++anArgIter]);
      if (!anX.IsIntegerValue() || !anY.IsIntegerValue())
      {
        Message::SendFail("Syntax error: coordinates should be integer values");
        return 1;
      }

      aColorScale->SetPosition(anX.IntegerValue(), anY.IntegerValue());
    }
    else if (aFlag == "-width" || aFlag == "-w" || aFlag == "-breadth")
    {
      if (anArgIter + 1 >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }

      const TCollection_AsciiString aBreadth(theArgVec[++anArgIter]);
      if (!aBreadth.IsIntegerValue())
      {
        Message::SendFail("Syntax error: a width should be an integer value");
        return 1;
      }
      aColorScale->SetBreadth(aBreadth.IntegerValue());
    }
    else if (aFlag == "-height" || aFlag == "-h")
    {
      if (anArgIter + 1 >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }

      const TCollection_AsciiString aHeight(theArgVec[++anArgIter]);
      if (!aHeight.IsIntegerValue())
      {
        Message::SendFail("Syntax error: a width should be an integer value");
        return 1;
      }
      aColorScale->SetHeight(aHeight.IntegerValue());
    }
    else if (aFlag == "-color")
    {
      if (aColorScale->GetColorType() != Aspect_TOCSD_USER)
      {
        Message::SendFail(
          "Syntax error: wrong color type. Call -colors before to set user-specified colors");
        return 1;
      }
      else if (anArgIter + 2 >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }

      const TCollection_AsciiString anInd(theArgVec[++anArgIter]);
      if (!anInd.IsIntegerValue())
      {
        Message::SendFail("Syntax error: Index value should be integer");
        return 1;
      }
      const int anIndex = anInd.IntegerValue();
      if (anIndex <= 0 || anIndex > aColorScale->GetNumberOfIntervals())
      {
        Message::SendFail() << "Syntax error: Index value should be within range 1.."
                            << aColorScale->GetNumberOfIntervals();
        return 1;
      }

      Quantity_Color aColor;
      int            aNbParsed =
        Draw::ParseColor(theArgNb - (anArgIter + 1), theArgVec + (anArgIter + 1), aColor);
      if (aNbParsed == 0)
      {
        Message::SendFail() << "Error: wrong syntax at '" << anArg << "'";
        return 1;
      }
      aColorScale->SetIntervalColor(aColor, anIndex);
      aColorScale->SetColorType(Aspect_TOCSD_USER);
      anArgIter += aNbParsed;
    }
    else if (aFlag == "-label")
    {
      if (aColorScale->GetColorType() != Aspect_TOCSD_USER)
      {
        Message::SendFail(
          "Syntax error: wrong label type. Call -labels before to set user-specified labels");
        return 1;
      }
      else if (anArgIter + 2 >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }

      int anIndex = Draw::Atoi(theArgVec[anArgIter + 1]);
      if (anIndex <= 0 || anIndex > aColorScale->GetNumberOfIntervals() + 1)
      {
        Message::SendFail() << "Syntax error: Index value should be within range 1.."
                            << (aColorScale->GetNumberOfIntervals() + 1);
        return 1;
      }

      TCollection_ExtendedString aText(theArgVec[anArgIter + 2], true);
      aColorScale->SetLabel(aText, anIndex);
      aColorScale->SetLabelType(Aspect_TOCSD_USER);
      anArgIter += 2;
    }
    else if (aFlag == "-labelat" || aFlag == "-labat" || aFlag == "-labelatborder"
             || aFlag == "-labatborder" || aFlag == "-labelatcenter" || aFlag == "-labatcenter")
    {
      bool toEnable = true;
      if (aFlag == "-labelat" || aFlag == "-labat")
      {
        int aLabAtBorder = -1;
        if (++anArgIter >= theArgNb)
        {
          TCollection_AsciiString anAtBorder(theArgVec[anArgIter]);
          anAtBorder.LowerCase();
          if (anAtBorder == "border")
          {
            aLabAtBorder = 1;
          }
          else if (anAtBorder == "center")
          {
            aLabAtBorder = 0;
          }
        }
        if (aLabAtBorder == -1)
        {
          Message::SendFail() << "Syntax error at argument '" << anArg << "'";
          return 1;
        }
        toEnable = (aLabAtBorder == 1);
      }
      else if (anArgIter + 1 < theArgNb && Draw::ParseOnOff(theArgVec[anArgIter + 1], toEnable))
      {
        ++anArgIter;
      }
      aColorScale->SetLabelAtBorder(
        aFlag == "-labelatcenter" || aFlag == "-labatcenter" ? !toEnable : toEnable);
    }
    else if (aFlag == "-colors")
    {
      NCollection_Sequence<Quantity_Color> aSeq;
      for (;;)
      {
        Quantity_Color aColor;
        int            aNbParsed =
          Draw::ParseColor(theArgNb - (anArgIter + 1), theArgVec + (anArgIter + 1), aColor);
        if (aNbParsed == 0)
        {
          break;
        }
        anArgIter += aNbParsed;
        aSeq.Append(aColor);
      }
      if (aSeq.Length() != aColorScale->GetNumberOfIntervals())
      {
        Message::SendFail() << "Error: not enough arguments! You should provide color names or RGB "
                               "color values for every interval of the "
                            << aColorScale->GetNumberOfIntervals() << " intervals";
        return 1;
      }

      aColorScale->SetColors(aSeq);
      aColorScale->SetColorType(Aspect_TOCSD_USER);
    }
    else if (aFlag == "-uniform")
    {
      const double aLightness = Draw::Atof(theArgVec[++anArgIter]);
      const double aHueStart  = Draw::Atof(theArgVec[++anArgIter]);
      const double aHueEnd    = Draw::Atof(theArgVec[++anArgIter]);
      aColorScale->SetUniformColors(aLightness, aHueStart, aHueEnd);
      aColorScale->SetColorType(Aspect_TOCSD_USER);
    }
    else if (aFlag == "-labels" || aFlag == "-freelabels")
    {
      if (anArgIter + 1 >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }

      int aNbLabels = aColorScale->IsLabelAtBorder() ? aColorScale->GetNumberOfIntervals() + 1
                                                     : aColorScale->GetNumberOfIntervals();
      if (aFlag == "-freelabels")
      {
        ++anArgIter;
        aNbLabels = Draw::Atoi(theArgVec[anArgIter]);
      }
      if (anArgIter + aNbLabels >= theArgNb)
      {
        Message::SendFail() << "Syntax error: not enough arguments. " << aNbLabels
                            << " text labels are expected";
        return 1;
      }

      NCollection_Sequence<TCollection_ExtendedString> aSeq;
      for (int aLabelIter = 0; aLabelIter < aNbLabels; ++aLabelIter)
      {
        aSeq.Append(TCollection_ExtendedString(theArgVec[++anArgIter], true));
      }
      aColorScale->SetLabels(aSeq);
      aColorScale->SetLabelType(Aspect_TOCSD_USER);
    }
    else if (aFlag == "-title")
    {
      if (anArgIter + 1 >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }

      bool isTwoArgs = false;
      if (anArgIter + 2 < theArgNb)
      {
        TCollection_AsciiString aSecondArg(theArgVec[anArgIter + 2]);
        aSecondArg.LowerCase();
        Standard_DISABLE_DEPRECATION_WARNINGS if (aSecondArg == "none")
        {
          aColorScale->SetTitlePosition(Aspect_TOCSP_NONE);
          isTwoArgs = true;
        }
        else if (aSecondArg == "left")
        {
          aColorScale->SetTitlePosition(Aspect_TOCSP_LEFT);
          isTwoArgs = true;
        }
        else if (aSecondArg == "right")
        {
          aColorScale->SetTitlePosition(Aspect_TOCSP_RIGHT);
          isTwoArgs = true;
        }
        else if (aSecondArg == "center")
        {
          aColorScale->SetTitlePosition(Aspect_TOCSP_CENTER);
          isTwoArgs = true;
        }
        Standard_ENABLE_DEPRECATION_WARNINGS
      }

      TCollection_ExtendedString aTitle(theArgVec[anArgIter + 1], true);
      aColorScale->SetTitle(aTitle);
      if (isTwoArgs)
      {
        anArgIter += 1;
      }
      anArgIter += 1;
    }
    else if (aFlag == "-demoversion" || aFlag == "-demo")
    {
      aColorScale->SetPosition(0, 0);
      aColorScale->SetTextHeight(16);
      aColorScale->SetRange(0.0, 100.0);
      aColorScale->SetNumberOfIntervals(10);
      aColorScale->SetBreadth(0);
      aColorScale->SetHeight(0);
      aColorScale->SetLabelPosition(Aspect_TOCSP_RIGHT);
      aColorScale->SetColorType(Aspect_TOCSD_AUTO);
      aColorScale->SetLabelType(Aspect_TOCSD_AUTO);
    }
    else if (aFlag == "-findcolor")
    {
      if (anArgIter + 1 >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }

      TCollection_AsciiString anArg1(theArgVec[++anArgIter]);

      if (!anArg1.IsRealValue(true))
      {
        Message::SendFail("Syntax error: the value should be real");
        return 1;
      }

      Quantity_Color aColor;
      aColorScale->FindColor(anArg1.RealValue(), aColor);
      theDI << Quantity_Color::StringName(aColor.Name());
      return 0;
    }
    else
    {
      Message::SendFail() << "Syntax error at " << anArg << " - unknown argument";
      return 1;
    }
  }

  int aWinWidth = 0, aWinHeight = 0;
  aView->Window()->Size(aWinWidth, aWinHeight);
  if (aColorScale->GetBreadth() == 0)
  {
    aColorScale->SetBreadth(aWinWidth);
  }
  if (aColorScale->GetHeight() == 0)
  {
    aColorScale->SetHeight(aWinHeight);
  }
  aColorScale->SetToUpdate();
  ViewerTest::Display(theArgVec[1], aColorScale, false, true);
  return 0;
}

//==============================================================================
// function : VGraduatedTrihedron
// purpose  : Displays or hides a graduated trihedron
//==============================================================================
static bool GetColor(const TCollection_AsciiString& theValue, Quantity_Color& theColor)
{
  Quantity_NameOfColor    aColorName;
  TCollection_AsciiString aVal = theValue;
  aVal.UpperCase();
  if (!Quantity_Color::ColorFromName(aVal.ToCString(), aColorName))
  {
    return false;
  }
  theColor = Quantity_Color(aColorName);
  return true;
}

static int VGraduatedTrihedron(Draw_Interpretor& /*theDi*/, int theArgNum, const char** theArgs)
{
  if (theArgNum < 2)
  {
    Message::SendFail() << "Syntax error: wrong number of parameters. Type 'help" << theArgs[0]
                        << "' for more information";
    return 1;
  }

  NCollection_DataMap<TCollection_AsciiString,
                      occ::handle<NCollection_HSequence<TCollection_AsciiString>>>
                          aMapOfArgs;
  TCollection_AsciiString aParseKey;
  for (int anArgIt = 1; anArgIt < theArgNum; ++anArgIt)
  {
    TCollection_AsciiString anArg(theArgs[anArgIt]);

    if (anArg.Value(1) == '-' && !anArg.IsRealValue(true))
    {
      aParseKey = anArg;
      aParseKey.Remove(1);
      aParseKey.LowerCase();
      aMapOfArgs.Bind(aParseKey, new NCollection_HSequence<TCollection_AsciiString>);
      continue;
    }

    if (aParseKey.IsEmpty())
    {
      continue;
    }

    aMapOfArgs(aParseKey)->Append(anArg);
  }

  // Check parameters
  for (NCollection_DataMap<TCollection_AsciiString,
                           occ::handle<NCollection_HSequence<TCollection_AsciiString>>>::Iterator
         aMapIt(aMapOfArgs);
       aMapIt.More();
       aMapIt.Next())
  {
    const TCollection_AsciiString&                                     aKey   = aMapIt.Key();
    const occ::handle<NCollection_HSequence<TCollection_AsciiString>>& anArgs = aMapIt.Value();

    // Bool key, without arguments
    if ((aKey.IsEqual("on") || aKey.IsEqual("off")) && anArgs->IsEmpty())
    {
      continue;
    }

    // One argument
    if ((aKey.IsEqual("xname") || aKey.IsEqual("yname") || aKey.IsEqual("zname"))
        && anArgs->Length() == 1)
    {
      continue;
    }

    // On/off arguments
    if ((aKey.IsEqual("xdrawname") || aKey.IsEqual("ydrawname") || aKey.IsEqual("zdrawname")
         || aKey.IsEqual("xdrawticks") || aKey.IsEqual("ydrawticks") || aKey.IsEqual("zdrawticks")
         || aKey.IsEqual("xdrawvalues") || aKey.IsEqual("ydrawvalues")
         || aKey.IsEqual("zdrawvalues") || aKey.IsEqual("drawgrid") || aKey.IsEqual("drawaxes"))
        && anArgs->Length() == 1
        && (anArgs->Value(1).IsEqual("on") || anArgs->Value(1).IsEqual("off")))
    {
      continue;
    }

    // One string argument
    if ((aKey.IsEqual("xnamecolor") || aKey.IsEqual("ynamecolor") || aKey.IsEqual("znamecolor")
         || aKey.IsEqual("xcolor") || aKey.IsEqual("ycolor") || aKey.IsEqual("zcolor"))
        && anArgs->Length() == 1 && !anArgs->Value(1).IsIntegerValue()
        && !anArgs->Value(1).IsRealValue(true))
    {
      continue;
    }

    // One integer argument
    if ((aKey.IsEqual("xticks") || aKey.IsEqual("yticks") || aKey.IsEqual("zticks")
         || aKey.IsEqual("xticklength") || aKey.IsEqual("yticklength")
         || aKey.IsEqual("zticklength") || aKey.IsEqual("xnameoffset")
         || aKey.IsEqual("ynameoffset") || aKey.IsEqual("znameoffset")
         || aKey.IsEqual("xvaluesoffset") || aKey.IsEqual("yvaluesoffset")
         || aKey.IsEqual("zvaluesoffset"))
        && anArgs->Length() == 1 && anArgs->Value(1).IsIntegerValue())
    {
      continue;
    }

    // One real argument
    if (aKey.IsEqual("arrowlength") && anArgs->Length() == 1
        && (anArgs->Value(1).IsIntegerValue() || anArgs->Value(1).IsRealValue(true)))
    {
      continue;
    }

    // Two string arguments
    if ((aKey.IsEqual("namefont") || aKey.IsEqual("valuesfont")) && anArgs->Length() == 1
        && !anArgs->Value(1).IsIntegerValue() && !anArgs->Value(1).IsRealValue(true))
    {
      continue;
    }

    TCollection_AsciiString aLowerKey;
    aLowerKey = "-";
    aLowerKey += aKey;
    aLowerKey.LowerCase();
    Message::SendFail() << "Syntax error: " << aLowerKey
                        << " is unknown option, or the arguments are unacceptable.\n"
                        << "Type help for more information";
    return 1;
  }

  occ::handle<AIS_InteractiveContext> anAISContext = ViewerTest::GetAISContext();
  if (anAISContext.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  bool                         toDisplay = true;
  Quantity_Color               aColor;
  Graphic3d_GraduatedTrihedron aTrihedronData;
  // Process parameters
  occ::handle<NCollection_HSequence<TCollection_AsciiString>> aValues;
  if (aMapOfArgs.Find("off", aValues))
  {
    toDisplay = false;
  }

  // AXES NAMES
  if (aMapOfArgs.Find("xname", aValues))
  {
    aTrihedronData.ChangeXAxisAspect().SetName(aValues->Value(1));
  }
  if (aMapOfArgs.Find("yname", aValues))
  {
    aTrihedronData.ChangeYAxisAspect().SetName(aValues->Value(1));
  }
  if (aMapOfArgs.Find("zname", aValues))
  {
    aTrihedronData.ChangeZAxisAspect().SetName(aValues->Value(1));
  }
  if (aMapOfArgs.Find("xdrawname", aValues))
  {
    aTrihedronData.ChangeXAxisAspect().SetDrawName(aValues->Value(1).IsEqual("on"));
  }
  if (aMapOfArgs.Find("ydrawname", aValues))
  {
    aTrihedronData.ChangeYAxisAspect().SetDrawName(aValues->Value(1).IsEqual("on"));
  }
  if (aMapOfArgs.Find("zdrawname", aValues))
  {
    aTrihedronData.ChangeZAxisAspect().SetDrawName(aValues->Value(1).IsEqual("on"));
  }
  if (aMapOfArgs.Find("xnameoffset", aValues))
  {
    aTrihedronData.ChangeXAxisAspect().SetNameOffset(aValues->Value(1).IntegerValue());
  }
  if (aMapOfArgs.Find("ynameoffset", aValues))
  {
    aTrihedronData.ChangeYAxisAspect().SetNameOffset(aValues->Value(1).IntegerValue());
  }
  if (aMapOfArgs.Find("znameoffset", aValues))
  {
    aTrihedronData.ChangeZAxisAspect().SetNameOffset(aValues->Value(1).IntegerValue());
  }

  // COLORS
  if (aMapOfArgs.Find("xnamecolor", aValues))
  {
    if (!GetColor(aValues->Value(1), aColor))
    {
      Message::SendFail("Syntax error: -xnamecolor wrong color name");
      return 1;
    }
    aTrihedronData.ChangeXAxisAspect().SetNameColor(aColor);
  }
  if (aMapOfArgs.Find("ynamecolor", aValues))
  {
    if (!GetColor(aValues->Value(1), aColor))
    {
      Message::SendFail("Syntax error: -ynamecolor wrong color name");
      return 1;
    }
    aTrihedronData.ChangeYAxisAspect().SetNameColor(aColor);
  }
  if (aMapOfArgs.Find("znamecolor", aValues))
  {
    if (!GetColor(aValues->Value(1), aColor))
    {
      Message::SendFail("Syntax error: -znamecolor wrong color name");
      return 1;
    }
    aTrihedronData.ChangeZAxisAspect().SetNameColor(aColor);
  }
  if (aMapOfArgs.Find("xcolor", aValues))
  {
    if (!GetColor(aValues->Value(1), aColor))
    {
      Message::SendFail("Syntax error: -xcolor wrong color name");
      return 1;
    }
    aTrihedronData.ChangeXAxisAspect().SetColor(aColor);
  }
  if (aMapOfArgs.Find("ycolor", aValues))
  {
    if (!GetColor(aValues->Value(1), aColor))
    {
      Message::SendFail("Syntax error: -ycolor wrong color name");
      return 1;
    }
    aTrihedronData.ChangeYAxisAspect().SetColor(aColor);
  }
  if (aMapOfArgs.Find("zcolor", aValues))
  {
    if (!GetColor(aValues->Value(1), aColor))
    {
      Message::SendFail("Syntax error: -zcolor wrong color name");
      return 1;
    }
    aTrihedronData.ChangeZAxisAspect().SetColor(aColor);
  }

  // TICKMARKS
  if (aMapOfArgs.Find("xticks", aValues))
  {
    aTrihedronData.ChangeXAxisAspect().SetTickmarksNumber(aValues->Value(1).IntegerValue());
  }
  if (aMapOfArgs.Find("yticks", aValues))
  {
    aTrihedronData.ChangeYAxisAspect().SetTickmarksNumber(aValues->Value(1).IntegerValue());
  }
  if (aMapOfArgs.Find("zticks", aValues))
  {
    aTrihedronData.ChangeZAxisAspect().SetTickmarksNumber(aValues->Value(1).IntegerValue());
  }
  if (aMapOfArgs.Find("xticklength", aValues))
  {
    aTrihedronData.ChangeXAxisAspect().SetTickmarksLength(aValues->Value(1).IntegerValue());
  }
  if (aMapOfArgs.Find("yticklength", aValues))
  {
    aTrihedronData.ChangeYAxisAspect().SetTickmarksLength(aValues->Value(1).IntegerValue());
  }
  if (aMapOfArgs.Find("zticklength", aValues))
  {
    aTrihedronData.ChangeZAxisAspect().SetTickmarksLength(aValues->Value(1).IntegerValue());
  }
  if (aMapOfArgs.Find("xdrawticks", aValues))
  {
    aTrihedronData.ChangeXAxisAspect().SetDrawTickmarks(aValues->Value(1).IsEqual("on"));
  }
  if (aMapOfArgs.Find("ydrawticks", aValues))
  {
    aTrihedronData.ChangeYAxisAspect().SetDrawTickmarks(aValues->Value(1).IsEqual("on"));
  }
  if (aMapOfArgs.Find("zdrawticks", aValues))
  {
    aTrihedronData.ChangeZAxisAspect().SetDrawTickmarks(aValues->Value(1).IsEqual("on"));
  }

  // VALUES
  if (aMapOfArgs.Find("xdrawvalues", aValues))
  {
    aTrihedronData.ChangeXAxisAspect().SetDrawValues(aValues->Value(1).IsEqual("on"));
  }
  if (aMapOfArgs.Find("ydrawvalues", aValues))
  {
    aTrihedronData.ChangeYAxisAspect().SetDrawValues(aValues->Value(1).IsEqual("on"));
  }
  if (aMapOfArgs.Find("zdrawvalues", aValues))
  {
    aTrihedronData.ChangeZAxisAspect().SetDrawValues(aValues->Value(1).IsEqual("on"));
  }
  if (aMapOfArgs.Find("xvaluesoffset", aValues))
  {
    aTrihedronData.ChangeXAxisAspect().SetValuesOffset(aValues->Value(1).IntegerValue());
  }
  if (aMapOfArgs.Find("yvaluesoffset", aValues))
  {
    aTrihedronData.ChangeYAxisAspect().SetValuesOffset(aValues->Value(1).IntegerValue());
  }
  if (aMapOfArgs.Find("zvaluesoffset", aValues))
  {
    aTrihedronData.ChangeZAxisAspect().SetValuesOffset(aValues->Value(1).IntegerValue());
  }

  // ARROWS
  if (aMapOfArgs.Find("arrowlength", aValues))
  {
    aTrihedronData.SetArrowsLength((float)aValues->Value(1).RealValue());
  }

  // FONTS
  if (aMapOfArgs.Find("namefont", aValues))
  {
    aTrihedronData.SetNamesFont(aValues->Value(1));
  }
  if (aMapOfArgs.Find("valuesfont", aValues))
  {
    aTrihedronData.SetValuesFont(aValues->Value(1));
  }

  if (aMapOfArgs.Find("drawgrid", aValues))
  {
    aTrihedronData.SetDrawGrid(aValues->Value(1).IsEqual("on"));
  }
  if (aMapOfArgs.Find("drawaxes", aValues))
  {
    aTrihedronData.SetDrawAxes(aValues->Value(1).IsEqual("on"));
  }

  // The final step: display of erase trihedron
  if (toDisplay)
  {
    ViewerTest::CurrentView()->GraduatedTrihedronDisplay(aTrihedronData);
  }
  else
  {
    ViewerTest::CurrentView()->GraduatedTrihedronErase();
  }

  ViewerTest::GetAISContext()->UpdateCurrentViewer();
  ViewerTest::CurrentView()->Redraw();

  return 0;
}

//=================================================================================================

static int VTile(Draw_Interpretor& theDI, int theArgNb, const char** theArgVec)
{
  occ::handle<V3d_View> aView = ViewerTest::CurrentView();
  if (aView.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  Graphic3d_CameraTile aTile = aView->Camera()->Tile();
  if (theArgNb < 2)
  {
    theDI << "Total size: " << aTile.TotalSize.x() << " " << aTile.TotalSize.y() << "\n"
          << "Tile  size: " << aTile.TileSize.x() << " " << aTile.TileSize.y() << "\n"
          << "Lower left: " << aTile.Offset.x() << " " << aTile.Offset.y() << "\n";
    return 0;
  }

  aView->Window()->Size(aTile.TileSize.x(), aTile.TileSize.y());
  for (int anArgIter = 1; anArgIter < theArgNb; ++anArgIter)
  {
    TCollection_AsciiString anArg(theArgVec[anArgIter]);
    anArg.LowerCase();
    if (anArg == "-lowerleft" || anArg == "-upperleft")
    {
      if (anArgIter + 3 < theArgNb)
      {
        Message::SendFail() << "Syntax error at '" << theArgVec[anArgIter] << "'";
        return 1;
      }
      aTile.IsTopDown  = anArg == "-upperleft";
      aTile.Offset.x() = Draw::Atoi(theArgVec[anArgIter + 1]);
      aTile.Offset.y() = Draw::Atoi(theArgVec[anArgIter + 2]);
    }
    else if (anArg == "-total" || anArg == "-totalsize" || anArg == "-viewsize")
    {
      if (anArgIter + 3 < theArgNb)
      {
        Message::SendFail() << "Syntax error at '" << theArgVec[anArgIter] << "'";
        return 1;
      }
      aTile.TotalSize.x() = Draw::Atoi(theArgVec[anArgIter + 1]);
      aTile.TotalSize.y() = Draw::Atoi(theArgVec[anArgIter + 2]);
      if (aTile.TotalSize.x() < 1 || aTile.TotalSize.y() < 1)
      {
        Message::SendFail("Error: total size is incorrect");
        return 1;
      }
    }
    else if (anArg == "-tilesize")
    {
      if (anArgIter + 3 < theArgNb)
      {
        Message::SendFail() << "Syntax error at '" << theArgVec[anArgIter] << "'";
        return 1;
      }

      aTile.TileSize.x() = Draw::Atoi(theArgVec[anArgIter + 1]);
      aTile.TileSize.y() = Draw::Atoi(theArgVec[anArgIter + 2]);
      if (aTile.TileSize.x() < 1 || aTile.TileSize.y() < 1)
      {
        Message::SendFail("Error: tile size is incorrect");
        return 1;
      }
    }
    else if (anArg == "-unset")
    {
      aView->Camera()->SetTile(Graphic3d_CameraTile());
      aView->Redraw();
      return 0;
    }
  }

  if (aTile.TileSize.x() < 1 || aTile.TileSize.y() < 1)
  {
    Message::SendFail("Error: tile size is undefined");
    return 1;
  }
  else if (aTile.TotalSize.x() < 1 || aTile.TotalSize.y() < 1)
  {
    Message::SendFail("Error: total size is undefined");
    return 1;
  }

  aView->Camera()->SetTile(aTile);
  aView->Redraw();
  return 0;
}

//! Format ZLayer ID.
inline const char* formZLayerId(const int theLayerId)
{
  switch (theLayerId)
  {
    case Graphic3d_ZLayerId_UNKNOWN:
      return "[INVALID]";
    case Graphic3d_ZLayerId_Default:
      return "[DEFAULT]";
    case Graphic3d_ZLayerId_Top:
      return "[TOP]";
    case Graphic3d_ZLayerId_Topmost:
      return "[TOPMOST]";
    case Graphic3d_ZLayerId_TopOSD:
      return "[OVERLAY]";
    case Graphic3d_ZLayerId_BotOSD:
      return "[UNDERLAY]";
  }
  return "";
}

//! Print the ZLayer information.
inline void printZLayerInfo(Draw_Interpretor& theDI, const Graphic3d_ZLayerSettings& theLayer)
{
  if (!theLayer.Name().IsEmpty())
  {
    theDI << "  Name: " << theLayer.Name() << "\n";
  }
  if (theLayer.IsImmediate())
  {
    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";
  if (theLayer.PolygonOffset().Mode != Aspect_POM_None)
  {
    theDI << "  Depth offset: " << theLayer.PolygonOffset().Factor << " "
          << theLayer.PolygonOffset().Units << "\n";
  }
}

//==============================================================================
// function : VZLayer
// purpose  : Test z layer operations for v3d viewer
//==============================================================================
static int VZLayer(Draw_Interpretor& theDI, int theArgNb, const char** theArgVec)
{
  occ::handle<AIS_InteractiveContext> aContextAIS = ViewerTest::GetAISContext();
  if (aContextAIS.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  const occ::handle<V3d_Viewer>& aViewer = aContextAIS->CurrentViewer();
  if (theArgNb < 2)
  {
    NCollection_Sequence<int> aLayers;
    aViewer->GetAllZLayers(aLayers);
    for (NCollection_Sequence<int>::Iterator aLayeriter(aLayers); aLayeriter.More();
         aLayeriter.Next())
    {
      theDI << "ZLayer " << aLayeriter.Value() << " " << formZLayerId(aLayeriter.Value()) << "\n";
      Graphic3d_ZLayerSettings aSettings = aViewer->ZLayerSettings(aLayeriter.Value());
      printZLayerInfo(theDI, aSettings);
    }
    return 0;
  }

  int                    anArgIter = 1;
  int                    aLayerId  = Graphic3d_ZLayerId_UNKNOWN;
  ViewerTest_AutoUpdater anUpdateTool(aContextAIS, ViewerTest::CurrentView());
  if (anUpdateTool.parseRedrawMode(theArgVec[anArgIter]))
  {
    ++anArgIter;
  }

  {
    TCollection_AsciiString aFirstArg(theArgVec[anArgIter]);
    if (aFirstArg.IsIntegerValue())
    {
      ++anArgIter;
      aLayerId = aFirstArg.IntegerValue();
    }
    else
    {
      if (ViewerTest::ParseZLayerName(aFirstArg.ToCString(), aLayerId))
      {
        ++anArgIter;
      }
    }
  }

  Graphic3d_ZLayerId anOtherLayerId = Graphic3d_ZLayerId_UNKNOWN;
  for (; anArgIter < theArgNb; ++anArgIter)
  {
    // perform operation
    TCollection_AsciiString anArg(theArgVec[anArgIter]);
    anArg.LowerCase();
    if (anUpdateTool.parseRedrawMode(anArg))
    {
      //
    }
    else if (anArg == "-add" || anArg == "add")
    {
      aLayerId = Graphic3d_ZLayerId_UNKNOWN;
      if (!aViewer->AddZLayer(aLayerId))
      {
        Message::SendFail("Error: can not add a new z layer");
        return 0;
      }

      theDI << aLayerId;
    }
    else if (anArg == "-insertbefore" && anArgIter + 1 < theArgNb
             && ViewerTest::ParseZLayer(theArgVec[anArgIter + 1], anOtherLayerId))
    {
      ++anArgIter;
      aLayerId = Graphic3d_ZLayerId_UNKNOWN;
      if (!aViewer->InsertLayerBefore(aLayerId, Graphic3d_ZLayerSettings(), anOtherLayerId))
      {
        Message::SendFail("Error: can not add a new z layer");
        return 0;
      }

      theDI << aLayerId;
    }
    else if (anArg == "-insertafter" && anArgIter + 1 < theArgNb
             && ViewerTest::ParseZLayer(theArgVec[anArgIter + 1], anOtherLayerId))
    {
      ++anArgIter;
      aLayerId = Graphic3d_ZLayerId_UNKNOWN;
      if (!aViewer->InsertLayerAfter(aLayerId, Graphic3d_ZLayerSettings(), anOtherLayerId))
      {
        Message::SendFail("Error: can not add a new z layer");
        return 0;
      }

      theDI << aLayerId;
    }
    else if (anArg == "-del" || anArg == "-delete" || anArg == "del")
    {
      if (aLayerId == Graphic3d_ZLayerId_UNKNOWN)
      {
        if (++anArgIter >= theArgNb)
        {
          Message::SendFail("Syntax error: id of z layer to remove is missing");
          return 1;
        }

        aLayerId = Draw::Atoi(theArgVec[anArgIter]);
      }

      if (aLayerId == Graphic3d_ZLayerId_UNKNOWN || aLayerId == Graphic3d_ZLayerId_Default
          || aLayerId == Graphic3d_ZLayerId_Top || aLayerId == Graphic3d_ZLayerId_Topmost
          || aLayerId == Graphic3d_ZLayerId_TopOSD || aLayerId == Graphic3d_ZLayerId_BotOSD)
      {
        Message::SendFail("Syntax error: standard Z layer can not be removed");
        return 1;
      }

      // move all object displayed in removing layer to default layer
      for (NCollection_DoubleMap<occ::handle<AIS_InteractiveObject>,
                                 TCollection_AsciiString>::Iterator anObjIter(GetMapOfAIS());
           anObjIter.More();
           anObjIter.Next())
      {
        const occ::handle<AIS_InteractiveObject>& aPrs = anObjIter.Key1();
        if (aPrs.IsNull() || aPrs->ZLayer() != aLayerId)
        {
          continue;
        }
        aPrs->SetZLayer(Graphic3d_ZLayerId_Default);
      }

      if (!aViewer->RemoveZLayer(aLayerId))
      {
        Message::SendFail("Z layer can not be removed");
      }
      else
      {
        theDI << aLayerId << " ";
      }
    }
    else if (anArg == "-get" || anArg == "get")
    {
      NCollection_Sequence<int> aLayers;
      aViewer->GetAllZLayers(aLayers);
      for (NCollection_Sequence<int>::Iterator aLayeriter(aLayers); aLayeriter.More();
           aLayeriter.Next())
      {
        theDI << aLayeriter.Value() << " ";
      }

      theDI << "\n";
    }
    else if (anArg == "-name")
    {
      if (aLayerId == Graphic3d_ZLayerId_UNKNOWN)
      {
        Message::SendFail("Syntax error: id of Z layer is missing");
        return 1;
      }

      if (++anArgIter >= theArgNb)
      {
        Message::SendFail("Syntax error: name is missing");
        return 1;
      }

      Graphic3d_ZLayerSettings aSettings = aViewer->ZLayerSettings(aLayerId);
      aSettings.SetName(theArgVec[anArgIter]);
      aViewer->SetZLayerSettings(aLayerId, aSettings);
    }
    else if (anArg == "-origin")
    {
      if (aLayerId == Graphic3d_ZLayerId_UNKNOWN)
      {
        Message::SendFail("Syntax error: id of Z layer is missing");
        return 1;
      }

      if (anArgIter + 2 >= theArgNb)
      {
        Message::SendFail("Syntax error: origin coordinates are missing");
        return 1;
      }

      Graphic3d_ZLayerSettings aSettings = aViewer->ZLayerSettings(aLayerId);
      gp_XYZ                   anOrigin;
      anOrigin.SetX(Draw::Atof(theArgVec[anArgIter + 1]));
      anOrigin.SetY(Draw::Atof(theArgVec[anArgIter + 2]));
      anOrigin.SetZ(0.0);
      if (anArgIter + 3 < theArgNb)
      {
        anOrigin.SetZ(Draw::Atof(theArgVec[anArgIter + 3]));
        anArgIter += 3;
      }
      else
      {
        anArgIter += 2;
      }
      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 double             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 double             aSize     = Draw::Atof(theArgVec[++anArgIter]);
      aSettings.SetCullingSize(aSize);
      aViewer->SetZLayerSettings(aLayerId, aSettings);
    }
    else if (anArg == "-settings" || anArg == "settings")
    {
      if (aLayerId == Graphic3d_ZLayerId_UNKNOWN)
      {
        if (++anArgIter >= theArgNb)
        {
          Message::SendFail("Syntax error: id of Z layer is missing");
          return 1;
        }

        aLayerId = Draw::Atoi(theArgVec[anArgIter]);
      }

      Graphic3d_ZLayerSettings aSettings = aViewer->ZLayerSettings(aLayerId);
      printZLayerInfo(theDI, aSettings);
    }
    else if (anArg == "-enable" || anArg == "enable" || anArg == "-disable" || anArg == "disable")
    {
      const bool toEnable = anArg == "-enable" || anArg == "enable";
      if (++anArgIter >= theArgNb)
      {
        Message::SendFail("Syntax error: option name is missing");
        return 1;
      }

      TCollection_AsciiString aSubOp(theArgVec[anArgIter]);
      aSubOp.LowerCase();
      if (aLayerId == Graphic3d_ZLayerId_UNKNOWN)
      {
        if (++anArgIter >= theArgNb)
        {
          Message::SendFail("Syntax error: id of Z layer is missing");
          return 1;
        }

        aLayerId = Draw::Atoi(theArgVec[anArgIter]);
      }

      Graphic3d_ZLayerSettings aSettings = aViewer->ZLayerSettings(aLayerId);
      if (aSubOp == "depthtest" || aSubOp == "test")
      {
        aSettings.SetEnableDepthTest(toEnable);
      }
      else if (aSubOp == "depthwrite" || aSubOp == "write")
      {
        aSettings.SetEnableDepthWrite(toEnable);
      }
      else if (aSubOp == "depthclear" || aSubOp == "clear")
      {
        aSettings.SetClearDepth(toEnable);
      }
      else if (aSubOp == "depthoffset" || aSubOp == "offset")
      {
        Graphic3d_PolygonOffset aParams;
        aParams.Mode = toEnable ? Aspect_POM_Fill : Aspect_POM_None;
        if (toEnable)
        {
          if (anArgIter + 2 >= theArgNb)
          {
            Message::SendFail("Syntax error: factor and units values for depth offset are missing");
            return 1;
          }

          aParams.Factor = static_cast<float>(Draw::Atof(theArgVec[++anArgIter]));
          aParams.Units  = static_cast<float>(Draw::Atof(theArgVec[++anArgIter]));
        }
        aSettings.SetPolygonOffset(aParams);
      }
      else if (aSubOp == "positiveoffset" || aSubOp == "poffset")
      {
        if (toEnable)
        {
          aSettings.SetDepthOffsetPositive();
        }
        else
        {
          aSettings.SetPolygonOffset(Graphic3d_PolygonOffset());
        }
      }
      else if (aSubOp == "negativeoffset" || aSubOp == "noffset")
      {
        if (toEnable)
        {
          aSettings.SetDepthOffsetNegative();
        }
        else
        {
          aSettings.SetPolygonOffset(Graphic3d_PolygonOffset());
        }
      }
      else if (aSubOp == "textureenv")
      {
        aSettings.SetEnvironmentTexture(toEnable);
      }
      else if (aSubOp == "raytracing")
      {
        aSettings.SetRaytracable(toEnable);
      }

      aViewer->SetZLayerSettings(aLayerId, aSettings);
    }
    else
    {
      Message::SendFail() << "Syntax error: unknown option " << theArgVec[anArgIter];
      return 1;
    }
  }

  return 0;
}

// The interactive presentation of 2d layer item
// for "vlayerline" command it provides a presentation of
// line with user-defined linewidth, linetype and transparency.
class V3d_LineItem : public AIS_InteractiveObject
{
public:
  // CASCADE RTTI
  DEFINE_STANDARD_RTTI_INLINE(V3d_LineItem, AIS_InteractiveObject)

  // constructor
  Standard_EXPORT V3d_LineItem(double            X1,
                               double            Y1,
                               double            X2,
                               double            Y2,
                               Aspect_TypeOfLine theType   = Aspect_TOL_SOLID,
                               double            theWidth  = 0.5,
                               double            theTransp = 1.0);

private:
  void Compute(const occ::handle<PrsMgr_PresentationManager>& thePrsMgr,
               const occ::handle<Prs3d_Presentation>&         thePrs,
               const int                                      theMode) override;

  void ComputeSelection(const occ::handle<SelectMgr_Selection>&, const int) override {}

private:
  double            myX1, myY1, myX2, myY2;
  Aspect_TypeOfLine myType;
  double            myWidth;
};

// default constructor for line item
V3d_LineItem::V3d_LineItem(double            X1,
                           double            Y1,
                           double            X2,
                           double            Y2,
                           Aspect_TypeOfLine theType,
                           double            theWidth,
                           double            theTransp)
    : myX1(X1),
      myY1(Y1),
      myX2(X2),
      myY2(Y2),
      myType(theType),
      myWidth(theWidth)
{
  SetTransparency(1 - theTransp);
}

// render line
void V3d_LineItem::Compute(const occ::handle<PrsMgr_PresentationManager>&,
                           const occ::handle<Prs3d_Presentation>& thePresentation,
                           const int)
{
  thePresentation->Clear();
  Quantity_Color aColor(Quantity_NOC_RED);
  int            aWidth, aHeight;
  ViewerTest::CurrentView()->Window()->Size(aWidth, aHeight);
  occ::handle<Graphic3d_Group>            aGroup = thePresentation->CurrentGroup();
  occ::handle<Graphic3d_ArrayOfPolylines> aPrim  = new Graphic3d_ArrayOfPolylines(5);
  aPrim->AddVertex(myX1, aHeight - myY1, 0.);
  aPrim->AddVertex(myX2, aHeight - myY2, 0.);
  occ::handle<Prs3d_LineAspect> anAspect =
    new Prs3d_LineAspect(aColor, (Aspect_TypeOfLine)myType, myWidth);
  aGroup->SetPrimitivesAspect(anAspect->Aspect());
  aGroup->AddPrimitiveArray(aPrim);
}

//=============================================================================
// function : VLayerLine
// purpose  : Draws line in the v3d view layer with given attributes: linetype,
//         : linewidth, transparency coefficient
//============================================================================
static int VLayerLine(Draw_Interpretor& di, int argc, const char** argv)
{
  // get the active view
  occ::handle<V3d_View> aView = ViewerTest::CurrentView();
  if (aView.IsNull())
  {
    di << "Call vinit before!\n";
    return 1;
  }
  else if (argc < 5)
  {
    di << "Use: " << argv[0];
    di << " x1 y1 x2 y2 [linewidth = 0.5] [linetype = 0] [transparency = 1]\n";
    di << " linetype : { 0 | 1 | 2 | 3 } \n";
    di << "              0 - solid  \n";
    di << "              1 - dashed \n";
    di << "              2 - dot    \n";
    di << "              3 - dashdot\n";
    di << " transparency : { 0.0 - 1.0 } \n";
    di << "                  0.0 - transparent\n";
    di << "                  1.0 - visible    \n";
    return 1;
  }

  occ::handle<AIS_InteractiveContext> aContext = ViewerTest::GetAISContext();
  // get the input params
  double X1 = Draw::Atof(argv[1]);
  double Y1 = Draw::Atof(argv[2]);
  double X2 = Draw::Atof(argv[3]);
  double Y2 = Draw::Atof(argv[4]);

  double aWidth        = 0.5;
  double aTransparency = 1.0;

  // has width
  if (argc > 5)
  {
    aWidth = Draw::Atof(argv[5]);
  }

  // select appropriate line type
  Aspect_TypeOfLine aLineType = Aspect_TOL_SOLID;
  if (argc > 6 && !ViewerTest::ParseLineType(argv[6], aLineType))
  {
    Message::SendFail() << "Syntax error: unknown line type '" << argv[6] << "'";
    return 1;
  }

  // has transparency
  if (argc > 7)
  {
    aTransparency = Draw::Atof(argv[7]);
    if (aTransparency < 0 || aTransparency > 1.0)
    {
      aTransparency = 1.0;
    }
  }

  static occ::handle<V3d_LineItem> aLine;
  if (!aLine.IsNull())
  {
    aContext->Erase(aLine, false);
  }
  aLine = new V3d_LineItem(X1, Y1, X2, Y2, aLineType, aWidth, aTransparency);

  aContext->SetTransformPersistence(
    aLine,
    new Graphic3d_TransformPers(Graphic3d_TMF_2d, Aspect_TOTP_LEFT_LOWER));
  aLine->SetZLayer(Graphic3d_ZLayerId_TopOSD);
  aLine->SetToUpdate();
  aContext->Display(aLine, true);

  return 0;
}

//=================================================================================================

static int VGrid(Draw_Interpretor& /*theDI*/, int theArgNb, const char** theArgVec)
{
  occ::handle<V3d_View>   aView   = ViewerTest::CurrentView();
  occ::handle<V3d_Viewer> aViewer = ViewerTest::GetViewerFromContext();
  if (aView.IsNull() || aViewer.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  Aspect_GridType          aType = aViewer->GridType();
  Aspect_GridDrawMode      aMode = aViewer->GridDrawMode();
  NCollection_Vec2<double> aNewOriginXY, aNewStepXY, aNewSizeXY;
  double                   aNewRadius = 0.0, aNewRotAngle = 0.0, aNewZOffset = 0.0;
  double                   aNewArcStart = 0.0, aNewArcEnd = 0.0;
  Quantity_Color           aNewColor, aNewTenthColor;
  bool hasOrigin = false, hasStep = false, hasRotAngle = false, hasSize = false, hasRadius = false,
       hasZOffset = false;
  bool isGpuGrid = false, hasGridOff = false, hasScale = false, hasArc = false;
  bool hasColor = false, hasTenthColor = false;
  // Tracks whether any GPU-grid-only option was passed; used to warn when
  // such options are silently ignored on the CPU path.
  bool                   hasGpuOnlyOpt = false;
  Aspect_GridParams      aGridParams;
  ViewerTest_AutoUpdater anUpdateTool(ViewerTest::GetAISContext(), aView);
  for (int anArgIter = 1; anArgIter < theArgNb; ++anArgIter)
  {
    TCollection_AsciiString anArg(theArgVec[anArgIter]);
    anArg.LowerCase();
    if (anUpdateTool.parseRedrawMode(theArgVec[anArgIter]))
    {
      continue;
    }
    else if (anArgIter + 1 < theArgNb && anArg == "-type")
    {
      TCollection_AsciiString anArgNext(theArgVec[++anArgIter]);
      anArgNext.LowerCase();
      if (anArgNext == "r" || anArgNext == "rect" || anArgNext == "rectangular")
      {
        aType = Aspect_GT_Rectangular;
      }
      else if (anArgNext == "c" || anArgNext == "circ" || anArgNext == "circular")
      {
        aType = Aspect_GT_Circular;
      }
      else if (anArgNext == "gpu" || anArgNext == "shader")
      {
        isGpuGrid = true;
      }
      else
      {
        Message::SendFail() << "Syntax error at '" << anArgNext << "'";
        return 1;
      }
    }
    else if (anArgIter + 1 < theArgNb && anArg == "-mode")
    {
      TCollection_AsciiString anArgNext(theArgVec[++anArgIter]);
      anArgNext.LowerCase();
      if (anArgNext == "l" || anArgNext == "line" || anArgNext == "lines")
      {
        aMode = Aspect_GDM_Lines;
      }
      else if (anArgNext == "p" || anArgNext == "point" || anArgNext == "points")
      {
        aMode = Aspect_GDM_Points;
      }
      else
      {
        Message::SendFail() << "Syntax error at '" << anArgNext << "'";
        return 1;
      }
    }
    else if (anArgIter + 2 < theArgNb && (anArg == "-origin" || anArg == "-orig"))
    {
      hasOrigin = true;
      aNewOriginXY.SetValues(Draw::Atof(theArgVec[anArgIter + 1]),
                             Draw::Atof(theArgVec[anArgIter + 2]));
      anArgIter += 2;
    }
    else if (anArgIter + 2 < theArgNb && anArg == "-step")
    {
      hasStep = true;
      aNewStepXY.SetValues(Draw::Atof(theArgVec[anArgIter + 1]),
                           Draw::Atof(theArgVec[anArgIter + 2]));
      if (aNewStepXY.x() <= 0.0 || aNewStepXY.y() <= 0.0)
      {
        Message::SendFail() << "Syntax error: wrong step '" << theArgVec[anArgIter + 1] << " "
                            << theArgVec[anArgIter + 2] << "'";
        return 1;
      }
      anArgIter += 2;
    }
    else if (anArgIter + 1 < theArgNb
             && (anArg == "-angle" || anArg == "-rotangle" || anArg == "-rotationangle"))
    {
      hasRotAngle  = true;
      aNewRotAngle = Draw::Atof(theArgVec[++anArgIter]);
    }
    else if (anArgIter + 1 < theArgNb && (anArg == "-zoffset" || anArg == "-dz"))
    {
      hasZOffset  = true;
      aNewZOffset = Draw::Atof(theArgVec[++anArgIter]);
    }
    else if (anArgIter + 1 < theArgNb && anArg == "-radius")
    {
      hasRadius = true;
      ++anArgIter;
      aNewRadius = Draw::Atof(theArgVec[anArgIter]);
      if (aNewRadius <= 0.0)
      {
        Message::SendFail() << "Syntax error: wrong size '" << theArgVec[anArgIter] << "'";
        return 1;
      }
    }
    else if (anArgIter + 2 < theArgNb && anArg == "-size")
    {
      hasSize = true;
      aNewSizeXY.SetValues(Draw::Atof(theArgVec[anArgIter + 1]),
                           Draw::Atof(theArgVec[anArgIter + 2]));
      if (aNewSizeXY.x() <= 0.0 || aNewSizeXY.y() <= 0.0)
      {
        Message::SendFail() << "Syntax error: wrong size '" << theArgVec[anArgIter + 1] << " "
                            << theArgVec[anArgIter + 2] << "'";
        return 1;
      }
      anArgIter += 2;
    }
    else if (anArgIter + 3 < theArgNb && (anArg == "-color"))
    {
      // Colors feed both backends through different sinks: aGridParams here
      // for the GPU path, and Aspect_Grid::SetColors at the end of this
      // function for the CPU path (so V3d_Viewer::syncViews picks them up).
      hasColor  = true;
      aNewColor = Quantity_Color(Draw::Atof(theArgVec[anArgIter + 1]),
                                 Draw::Atof(theArgVec[anArgIter + 2]),
                                 Draw::Atof(theArgVec[anArgIter + 3]),
                                 Quantity_TOC_RGB);
      aGridParams.SetColor(aNewColor);
      anArgIter += 3;
    }
    else if (anArgIter + 3 < theArgNb && (anArg == "-tenthcolor" || anArg == "-accentcolor"))
    {
      hasTenthColor  = true;
      aNewTenthColor = Quantity_Color(Draw::Atof(theArgVec[anArgIter + 1]),
                                      Draw::Atof(theArgVec[anArgIter + 2]),
                                      Draw::Atof(theArgVec[anArgIter + 3]),
                                      Quantity_TOC_RGB);
      aGridParams.SetAccentColor(aNewTenthColor);
      anArgIter += 3;
    }
    else if (anArgIter + 1 < theArgNb && anArg == "-scale")
    {
      hasScale      = true;
      hasGpuOnlyOpt = true;
      aGridParams.SetScale(Draw::Atof(theArgVec[++anArgIter]));
    }
    else if (anArgIter + 1 < theArgNb && (anArg == "-linethickness" || anArg == "-thickness"))
    {
      hasGpuOnlyOpt = true;
      aGridParams.SetLineThickness(Draw::Atof(theArgVec[++anArgIter]));
    }
    else if (anArgIter + 1 < theArgNb && anArg == "-background")
    {
      hasGpuOnlyOpt  = true;
      const int aVal = Draw::Atoi(theArgVec[++anArgIter]);
      aGridParams.SetIsBackground(aVal != 0);
    }
    else if (anArgIter + 1 < theArgNb && anArg == "-drawaxis")
    {
      hasGpuOnlyOpt  = true;
      const int aVal = Draw::Atoi(theArgVec[++anArgIter]);
      aGridParams.SetIsDrawAxis(aVal != 0);
    }
    else if (anArgIter + 1 < theArgNb && (anArg == "-viewadaptive" || anArg == "-adaptive"))
    {
      hasGpuOnlyOpt  = true;
      const int aVal = Draw::Atoi(theArgVec[++anArgIter]);
      aGridParams.SetIsViewAdaptive(aVal != 0);
    }
    else if (anArg == "-gpu" || anArg == "-shader")
    {
      isGpuGrid = true;
    }
    else if (anArgIter + 2 < theArgNb && anArg == "-arc")
    {
      // Angular range for circular grids (radians). Equal start/end = full circle.
      hasArc       = true;
      aNewArcStart = Draw::Atof(theArgVec[anArgIter + 1]);
      aNewArcEnd   = Draw::Atof(theArgVec[anArgIter + 2]);
      aGridParams.SetArcRange(aNewArcStart, aNewArcEnd);
      anArgIter += 2;
    }
    else if (anArg == "r" || anArg == "rect" || anArg == "rectangular")
    {
      aType = Aspect_GT_Rectangular;
    }
    else if (anArg == "c" || anArg == "circ" || anArg == "circular")
    {
      aType = Aspect_GT_Circular;
    }
    else if (anArg == "l" || anArg == "line" || anArg == "lines")
    {
      aMode = Aspect_GDM_Lines;
    }
    else if (anArg == "p" || anArg == "point" || anArg == "points")
    {
      aMode = Aspect_GDM_Points;
    }
    else if (anArg == "off")
    {
      hasGridOff = true;
    }
    else
    {
      Message::SendFail() << "Syntax error at '" << anArg << "'";
      return 1;
    }
  }

  if (isGpuGrid && hasGridOff)
  {
    Message::SendFail("Syntax error: 'off' cannot be combined with GPU grid display");
    return 1;
  }

  // GPU-only options (-scale, -lineThickness, -background, -drawAxis,
  // -viewAdaptive) are stored on Aspect_GridParams and consumed only by the
  // shader path. Silently ignoring them on the CPU path leaves the user
  // wondering why nothing changed; warn explicitly.
  if (hasGpuOnlyOpt && !isGpuGrid && !hasGridOff)
  {
    Message::SendWarning("vgrid: -scale / -lineThickness / -background / -drawAxis / "
                         "-viewAdaptive are GPU-grid only; pass -gpu (or -type gpu) to apply.");
  }

  if (isGpuGrid || hasGridOff)
  {
    if (hasGridOff)
    {
      aView->GridErase();
    }
    if (isGpuGrid)
    {
      // The shader grid keeps a real Aspect_Grid to back snap selection, so we
      // route through ActivateGrid(rect|circ) first and override the display
      // with shader-specific params afterwards. Decide the shape from aType
      // or from the presence of circular-only options.
      const bool   isGpuCircular = aType == Aspect_GT_Circular || hasRadius || hasArc;
      const double anOrigX       = hasOrigin ? aNewOriginXY.x() : 0.0;
      const double anOrigY       = hasOrigin ? aNewOriginXY.y() : 0.0;
      const double aRotAngle     = hasRotAngle ? aNewRotAngle : 0.0;

      if (isGpuCircular)
      {
        // Radial step + angular divisions. `-step R N` supplies both; otherwise
        // use sensible defaults that keep snap consistent with the visible grid.
        double aRadiusStep    = 1.0;
        int    aDivisionCount = 16;
        if (hasStep)
        {
          aRadiusStep    = aNewStepXY.x();
          aDivisionCount = int(aNewStepXY.y() > 0 ? aNewStepXY.y() : aDivisionCount);
        }
        else if (hasScale && aGridParams.Scale() > 0.0)
        {
          aRadiusStep = 1.0 / aGridParams.Scale();
        }
        aViewer->SetCircularGridValues(anOrigX, anOrigY, aRadiusStep, aDivisionCount, aRotAngle);
        // Arc range reaches the circular grid base before ActivateGrid's first
        // syncViews fires (otherwise syncViews would overwrite our override).
        if (hasArc)
        {
          if (occ::handle<Aspect_CircularGrid> aCircGrid =
                occ::down_cast<Aspect_CircularGrid>(aViewer->Grid(true)))
          {
            aCircGrid->SetArcRange(aNewArcStart, aNewArcEnd);
          }
        }
        aViewer->ActivateGrid(Aspect_GT_Circular, aMode);

        aGridParams.SetScale(1.0 / aRadiusStep);
        aGridParams.SetScaleY(0.0); // unused in circular mode
        aGridParams.SetAngularDivisions(aDivisionCount);
      }
      else
      {
        // Rectangular shader grid. Derive step from explicit -step or from the
        // Aspect_GridParams scale; fall back to 1 world unit so the default is
        // immediately useful (Aspect_GridParams::Scale defaults to 0.01 which
        // would give step 100 - too coarse for typical scenes).
        if (!hasScale)
        {
          if (hasStep)
          {
            aGridParams.SetScale(1.0 / aNewStepXY.x());
            aGridParams.SetScaleY(1.0 / aNewStepXY.y());
          }
          else
          {
            aGridParams.SetScale(1.0);
            aGridParams.SetScaleY(1.0);
          }
        }
        const double aInfStepX = 1.0 / aGridParams.Scale();
        const double aInfStepY = 1.0 / aGridParams.EffectiveScaleY();
        aViewer->SetRectangularGridValues(anOrigX, anOrigY, aInfStepX, aInfStepY, aRotAngle);
        aViewer->ActivateGrid(Aspect_GT_Rectangular, aMode);

        aGridParams.SetAngularDivisions(0);
      }

      // Convert origin to the same world-offset convention used by V3d
      // syncViews so the shader's aPlaneOrigin matches snap's aPnt0.
      const gp_Ax3 aPlane = aViewer->PrivilegedPlane();
      const gp_XYZ aOriginOffset =
        aPlane.XDirection().XYZ() * -anOrigX + aPlane.YDirection().XYZ() * -anOrigY;
      aGridParams.SetOrigin(gp_Pnt(aOriginOffset));
      aGridParams.SetDrawMode(aMode);
      aGridParams.SetRotationAngle(aRotAngle);
      if (hasSize && !isGpuCircular)
      {
        aGridParams.SetSizeX(aNewSizeXY.x());
        aGridParams.SetSizeY(aNewSizeXY.y());
      }
      if (hasRadius)
      {
        aGridParams.SetRadius(aNewRadius);
      }
      if (hasZOffset)
      {
        aGridParams.SetZOffset(aNewZOffset);
      }
      aView->GridDisplay(aGridParams);
    }
    if (hasGridOff && !isGpuGrid)
    {
      // plain 'vgrid off' still deactivates the classical grid
      aViewer->DeactivateGrid();
    }
    return 0;
  }

  if (aType == Aspect_GT_Rectangular)
  {
    NCollection_Vec2<double> anOrigXY, aStepXY;
    double                   aRotAngle = 0.0;
    aViewer->RectangularGridValues(anOrigXY.x(), anOrigXY.y(), aStepXY.x(), aStepXY.y(), aRotAngle);
    if (hasOrigin)
    {
      anOrigXY = aNewOriginXY;
    }
    if (hasStep)
    {
      aStepXY = aNewStepXY;
    }
    if (hasRotAngle)
    {
      aRotAngle = aNewRotAngle;
    }
    aViewer->SetRectangularGridValues(anOrigXY.x(),
                                      anOrigXY.y(),
                                      aStepXY.x(),
                                      aStepXY.y(),
                                      aRotAngle);
    if (hasSize || hasZOffset)
    {
      NCollection_Vec3<double> aSize;
      aViewer->RectangularGridGraphicValues(aSize.x(), aSize.y(), aSize.z());
      if (hasSize)
      {
        aSize.x() = aNewSizeXY.x();
        aSize.y() = aNewSizeXY.y();
      }
      if (hasZOffset)
      {
        aSize.z() = aNewZOffset;
      }
      aViewer->SetRectangularGridGraphicValues(aSize.x(), aSize.y(), aSize.z());
    }
  }
  else if (aType == Aspect_GT_Circular)
  {
    NCollection_Vec2<double> anOrigXY;
    double                   aRadiusStep;
    int                      aDivisionNumber;
    double                   aRotAngle = 0.0;
    aViewer->CircularGridValues(anOrigXY.x(),
                                anOrigXY.y(),
                                aRadiusStep,
                                aDivisionNumber,
                                aRotAngle);
    if (hasOrigin)
    {
      anOrigXY = aNewOriginXY;
    }
    if (hasStep)
    {
      aRadiusStep     = aNewStepXY[0];
      aDivisionNumber = (int)aNewStepXY[1];
      if (aDivisionNumber < 1)
      {
        Message::SendFail() << "Syntax error: invalid division number '" << aNewStepXY[1] << "'";
        return 1;
      }
    }
    if (hasRotAngle)
    {
      aRotAngle = aNewRotAngle;
    }

    aViewer->SetCircularGridValues(anOrigXY.x(),
                                   anOrigXY.y(),
                                   aRadiusStep,
                                   aDivisionNumber,
                                   aRotAngle);
    if (hasSize)
    {
      Message::SendFail("Syntax error: circular size should be specified as radius");
      return 1;
    }
    if (hasRadius || hasZOffset)
    {
      double aRadius = 0.0, aZOffset = 0.0;
      aViewer->CircularGridGraphicValues(aRadius, aZOffset);
      if (hasRadius)
      {
        aRadius = aNewRadius;
      }
      if (hasZOffset)
      {
        aZOffset = aNewZOffset;
      }
      aViewer->SetCircularGridGraphicValues(aRadius, aZOffset);
    }
    // Angular range must hit the Aspect_CircularGrid base before ActivateGrid
    // fires syncViews - syncViews copies AngleStart/End from the grid, so any
    // value set earlier only on aGridParams would get overwritten.
    if (hasArc)
    {
      if (occ::handle<Aspect_CircularGrid> aCircGrid =
            occ::down_cast<Aspect_CircularGrid>(aViewer->Grid(true)))
      {
        aCircGrid->SetArcRange(aNewArcStart, aNewArcEnd);
      }
    }
  }
  // Apply -color / -tenthColor to the active grid so V3d syncViews picks
  // them up on the next display. GPU-grid display drives these through
  // aGridParams directly (set earlier in the parser loop).
  if (hasColor || hasTenthColor)
  {
    if (occ::handle<Aspect_Grid> aGrid = aViewer->Grid(true))
    {
      Quantity_Color aMainColor, aTenthColor;
      aGrid->Colors(aMainColor, aTenthColor);
      if (hasColor)
      {
        aMainColor = aNewColor;
      }
      if (hasTenthColor)
      {
        aTenthColor = aNewTenthColor;
      }
      aGrid->SetColors(aMainColor, aTenthColor);
    }
  }
  aViewer->ActivateGrid(aType, aMode);
  return 0;
}

//=================================================================================================

static int VPriviledgedPlane(Draw_Interpretor& theDI, int theArgNb, const char** theArgVec)
{
  if (theArgNb != 1 && theArgNb != 7 && theArgNb != 10)
  {
    Message::SendFail("Error: wrong number of arguments! See usage:");
    theDI.PrintHelp(theArgVec[0]);
    return 1;
  }

  // get the active viewer
  occ::handle<V3d_Viewer> aViewer = ViewerTest::GetViewerFromContext();
  if (aViewer.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  if (theArgNb == 1)
  {
    gp_Ax3        aPriviledgedPlane = aViewer->PrivilegedPlane();
    const gp_Pnt& anOrig            = aPriviledgedPlane.Location();
    const gp_Dir& aNorm             = aPriviledgedPlane.Direction();
    const gp_Dir& aXDir             = aPriviledgedPlane.XDirection();
    theDI << "Origin: " << anOrig.X() << " " << anOrig.Y() << " " << anOrig.Z() << " "
          << "Normal: " << aNorm.X() << " " << aNorm.Y() << " " << aNorm.Z() << " "
          << "X-dir: " << aXDir.X() << " " << aXDir.Y() << " " << aXDir.Z() << "\n";
    return 0;
  }

  int    anArgIdx = 1;
  double anOrigX  = Draw::Atof(theArgVec[anArgIdx++]);
  double anOrigY  = Draw::Atof(theArgVec[anArgIdx++]);
  double anOrigZ  = Draw::Atof(theArgVec[anArgIdx++]);
  double aNormX   = Draw::Atof(theArgVec[anArgIdx++]);
  double aNormY   = Draw::Atof(theArgVec[anArgIdx++]);
  double aNormZ   = Draw::Atof(theArgVec[anArgIdx++]);

  gp_Ax3 aPriviledgedPlane;
  gp_Pnt anOrig(anOrigX, anOrigY, anOrigZ);
  gp_Dir aNorm(aNormX, aNormY, aNormZ);
  if (theArgNb > 7)
  {
    double aXDirX = Draw::Atof(theArgVec[anArgIdx++]);
    double aXDirY = Draw::Atof(theArgVec[anArgIdx++]);
    double aXDirZ = Draw::Atof(theArgVec[anArgIdx++]);
    gp_Dir aXDir(aXDirX, aXDirY, aXDirZ);
    aPriviledgedPlane = gp_Ax3(anOrig, aNorm, aXDir);
  }
  else
  {
    aPriviledgedPlane = gp_Ax3(anOrig, aNorm);
  }

  aViewer->SetPrivilegedPlane(aPriviledgedPlane);

  return 0;
}

//=================================================================================================

static int VConvert(Draw_Interpretor& theDI, int theArgNb, const char** theArgVec)
{
  // get the active view
  occ::handle<V3d_View> aView = ViewerTest::CurrentView();
  if (aView.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  enum
  {
    Model,
    Ray,
    View,
    Window,
    Grid
  } aMode = Model;

  // access coordinate arguments
  NCollection_Sequence<double> aCoord;
  int                          anArgIdx = 1;
  for (; anArgIdx < 4 && anArgIdx < theArgNb; ++anArgIdx)
  {
    TCollection_AsciiString anArg(theArgVec[anArgIdx]);
    if (!anArg.IsRealValue(true))
    {
      break;
    }
    aCoord.Append(anArg.RealValue());
  }

  // non-numeric argument too early
  if (aCoord.IsEmpty())
  {
    Message::SendFail("Error: wrong number of arguments! See usage:");
    theDI.PrintHelp(theArgVec[0]);
    return 1;
  }

  // collect all other arguments and options
  for (; anArgIdx < theArgNb; ++anArgIdx)
  {
    TCollection_AsciiString anArg(theArgVec[anArgIdx]);
    anArg.LowerCase();
    if (anArg == "window")
    {
      aMode = Window;
    }
    else if (anArg == "view")
    {
      aMode = View;
    }
    else if (anArg == "grid")
    {
      aMode = Grid;
    }
    else if (anArg == "ray")
    {
      aMode = Ray;
    }
    else
    {
      Message::SendFail() << "Error: wrong argument " << anArg << "! See usage:";
      theDI.PrintHelp(theArgVec[0]);
      return 1;
    }
  }

  // complete input checks
  if ((aCoord.Length() == 1 && theArgNb > 3) || (aCoord.Length() == 2 && theArgNb > 4)
      || (aCoord.Length() == 3 && theArgNb > 5))
  {
    Message::SendFail("Error: wrong number of arguments! See usage:");
    theDI.PrintHelp(theArgVec[0]);
    return 1;
  }

  double aXYZ[6] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
  int    aXYp[2] = {0, 0};

  // convert one-dimensional coordinate
  if (aCoord.Length() == 1)
  {
    switch (aMode)
    {
      case View:
        theDI << "View Vv: " << aView->Convert((int)aCoord(1));
        return 0;
      case Window:
        theDI << "Window Vp: " << aView->Convert(aCoord(1));
        return 0;
      default:
        Message::SendFail("Error: wrong arguments! See usage:");
        theDI.PrintHelp(theArgVec[0]);
        return 1;
    }
  }

  // convert 2D coordinates from projection or view reference space
  if (aCoord.Length() == 2)
  {
    switch (aMode)
    {
      case Model:
        aView->Convert((int)aCoord(1), (int)aCoord(2), aXYZ[0], aXYZ[1], aXYZ[2]);
        theDI << "Model X,Y,Z: " << aXYZ[0] << " " << aXYZ[1] << " " << aXYZ[2] << "\n";
        return 0;

      case View:
        aView->Convert((int)aCoord(1), (int)aCoord(2), aXYZ[0], aXYZ[1]);
        theDI << "View Xv,Yv: " << aXYZ[0] << " " << aXYZ[1] << "\n";
        return 0;

      case Window:
        aView->Convert(aCoord(1), aCoord(2), aXYp[0], aXYp[1]);
        theDI << "Window Xp,Yp: " << aXYp[0] << " " << aXYp[1] << "\n";
        return 0;

      case Grid:
        aView->Convert((int)aCoord(1), (int)aCoord(2), aXYZ[0], aXYZ[1], aXYZ[2]);
        aView->ConvertToGrid(aXYZ[0], aXYZ[1], aXYZ[2], aXYZ[3], aXYZ[4], aXYZ[5]);
        theDI << "Model X,Y,Z: " << aXYZ[3] << " " << aXYZ[4] << " " << aXYZ[5] << "\n";
        return 0;

      case Ray:
        aView->ConvertWithProj((int)aCoord(1),
                               (int)aCoord(2),
                               aXYZ[0],
                               aXYZ[1],
                               aXYZ[2],
                               aXYZ[3],
                               aXYZ[4],
                               aXYZ[5]);
        theDI << "Model DX,DY,DZ: " << aXYZ[3] << " " << aXYZ[4] << " " << aXYZ[5] << "\n";
        return 0;

      default:
        Message::SendFail("Error: wrong arguments! See usage:");
        theDI.PrintHelp(theArgVec[0]);
        return 1;
    }
  }

  // convert 3D coordinates from view reference space
  else if (aCoord.Length() == 3)
  {
    switch (aMode)
    {
      case Window:
        aView->Convert(aCoord(1), aCoord(2), aCoord(3), aXYp[0], aXYp[1]);
        theDI << "Window Xp,Yp: " << aXYp[0] << " " << aXYp[1] << "\n";
        return 0;

      case Grid:
        aView->ConvertToGrid(aCoord(1), aCoord(2), aCoord(3), aXYZ[0], aXYZ[1], aXYZ[2]);
        theDI << "Model X,Y,Z: " << aXYZ[0] << " " << aXYZ[1] << " " << aXYZ[2] << "\n";
        return 0;

      default:
        Message::SendFail("Error: wrong arguments! See usage:");
        theDI.PrintHelp(theArgVec[0]);
        return 1;
    }
  }

  return 0;
}

//=================================================================================================

static int VFps(Draw_Interpretor& theDI, int theArgNb, const char** theArgVec)
{
  // get the active view
  occ::handle<V3d_View> aView = ViewerTest::CurrentView();
  if (aView.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  int    aFramesNb = -1;
  double aDuration = -1.0;
  for (int anArgIter = 1; anArgIter < theArgNb; ++anArgIter)
  {
    TCollection_AsciiString anArg(theArgVec[anArgIter]);
    anArg.LowerCase();
    if (aDuration < 0.0 && anArgIter + 1 < theArgNb
        && (anArg == "-duration" || anArg == "-dur" || anArg == "-time"))
    {
      aDuration = Draw::Atof(theArgVec[++anArgIter]);
    }
    else if (aFramesNb < 0 && anArg.IsIntegerValue())
    {
      aFramesNb = anArg.IntegerValue();
      if (aFramesNb <= 0)
      {
        Message::SendFail() << "Syntax error at '" << anArg << "'";
        return 1;
      }
    }
    else
    {
      Message::SendFail() << "Syntax error at '" << anArg << "'";
      return 1;
    }
  }
  if (aFramesNb < 0 && aDuration < 0.0)
  {
    aFramesNb = 100;
  }

  // the time is meaningless for first call
  // due to async OpenGl rendering
  aView->Redraw();

  // redraw view in loop to estimate average values
  OSD_Timer aTimer;
  aTimer.Start();
  int aFrameIter = 1;
  for (;; ++aFrameIter)
  {
    aView->Redraw();
    if ((aFramesNb > 0 && aFrameIter >= aFramesNb)
        || (aDuration > 0.0 && aTimer.ElapsedTime() >= aDuration))
    {
      break;
    }
  }
  aTimer.Stop();
  double       aCpu;
  const double aTime = aTimer.ElapsedTime();
  aTimer.OSD_Chronometer::Show(aCpu);

  const double aFpsAver = double(aFrameIter) / aTime;
  const double aCpuAver = aCpu / double(aFrameIter);

  // return statistics
  theDI << "FPS: " << aFpsAver << "\n"
        << "CPU: " << (1000.0 * aCpuAver) << " msec\n";

  // compute additional statistics in ray-tracing mode
  const Graphic3d_RenderingParams& aParams = aView->RenderingParams();
  if (aParams.Method == Graphic3d_RM_RAYTRACING)
  {
    NCollection_Vec2<int> aWinSize(0, 0);
    aView->Window()->Size(aWinSize.x(), aWinSize.y());

    // 1 shadow ray and 1 secondary ray pew each bounce
    const double aMRays =
      aWinSize.x() * aWinSize.y() * aFpsAver * aParams.RaytracingDepth * 2 / 1.0e6f;
    theDI << "MRays/sec (upper bound): " << aMRays << "\n";
  }

  return 0;
}

//=================================================================================================

static int VMemGpu(Draw_Interpretor& theDI, int theArgNb, const char** theArgVec)
{
  // get the context
  occ::handle<AIS_InteractiveContext> aContextAIS = ViewerTest::GetAISContext();
  if (aContextAIS.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  occ::handle<Graphic3d_GraphicDriver> aDriver = aContextAIS->CurrentViewer()->Driver();
  if (aDriver.IsNull())
  {
    Message::SendFail("Error: graphic driver not available");
    return 1;
  }

  size_t                  aFreeBytes = 0;
  TCollection_AsciiString anInfo;
  if (!aDriver->MemoryInfo(aFreeBytes, anInfo))
  {
    Message::SendFail("Error: information not available");
    return 1;
  }

  if (theArgNb > 1 && *theArgVec[1] == 'f')
  {
    theDI << double(aFreeBytes);
  }
  else
  {
    theDI << anInfo;
  }

  return 0;
}

//=================================================================================================

static int VReadPixel(Draw_Interpretor& theDI, int theArgNb, const char** theArgVec)
{
  // get the active view
  occ::handle<V3d_View> aView = ViewerTest::CurrentView();
  if (aView.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }
  else if (theArgNb < 3)
  {
    Message::SendFail() << "Syntax error: wrong number of arguments.\n"
                        << "Usage: " << theArgVec[0]
                        << " xPixel yPixel [{rgb|rgba|depth|hls|rgbf|rgbaf}=rgba] [name]";
    return 1;
  }

  Image_Format         aFormat     = Image_Format_RGBA;
  Graphic3d_BufferType aBufferType = Graphic3d_BT_RGBA;

  int aWidth, aHeight;
  aView->Window()->Size(aWidth, aHeight);
  const int anX = Draw::Atoi(theArgVec[1]);
  const int anY = Draw::Atoi(theArgVec[2]);
  if (anX < 0 || anX >= aWidth || anY < 0 || anY > aHeight)
  {
    Message::SendFail() << "Error: pixel coordinates (" << anX << "; " << anY
                        << ") are out of view (" << aWidth << " x " << aHeight << ")";
    return 1;
  }

  bool toShowName = false, toShowHls = false, toShowHex = false, toShow_sRGB = false;
  for (int anIter = 3; anIter < theArgNb; ++anIter)
  {
    TCollection_AsciiString aParam(theArgVec[anIter]);
    aParam.LowerCase();
    if (aParam == "-rgb" || aParam == "rgb" || aParam == "-srgb" || aParam == "srgb")
    {
      aFormat     = Image_Format_RGB;
      aBufferType = Graphic3d_BT_RGB;
      toShow_sRGB = aParam == "-srgb" || aParam == "srgb";
    }
    else if (aParam == "-hls" || aParam == "hls")
    {
      aFormat     = Image_Format_RGB;
      aBufferType = Graphic3d_BT_RGB;
      toShowHls   = true;
    }
    else if (aParam == "-rgbf" || aParam == "rgbf")
    {
      aFormat     = Image_Format_RGBF;
      aBufferType = Graphic3d_BT_RGB;
    }
    else if (aParam == "-rgba" || aParam == "rgba" || aParam == "-srgba" || aParam == "srgba")
    {
      aFormat     = Image_Format_RGBA;
      aBufferType = Graphic3d_BT_RGBA;
      toShow_sRGB = aParam == "-srgba" || aParam == "srgba";
    }
    else if (aParam == "-rgbaf" || aParam == "rgbaf")
    {
      aFormat     = Image_Format_RGBAF;
      aBufferType = Graphic3d_BT_RGBA;
    }
    else if (aParam == "-depth" || aParam == "depth")
    {
      aFormat     = Image_Format_GrayF;
      aBufferType = Graphic3d_BT_Depth;
    }
    else if (aParam == "-name" || aParam == "name")
    {
      toShowName = true;
    }
    else if (aParam == "-hex" || aParam == "hex")
    {
      toShowHex = true;
    }
    else
    {
      Message::SendFail() << "Syntax error at '" << aParam << "'";
      return 1;
    }
  }

  Image_PixMap anImage;
  if (!anImage.InitTrash(aFormat, aWidth, aHeight))
  {
    Message::SendFail("Error: image allocation failed");
    return 1;
  }
  else if (!aView->ToPixMap(anImage, aWidth, aHeight, aBufferType))
  {
    Message::SendFail("Error: image dump failed");
    return 1;
  }

  // redirect possible warning messages that could have been added by ToPixMap
  // into the Tcl interpreter (via DefaultMessenger) to cout, so that they do not
  // contaminate result of the command
  const char* aWarnLog = theDI.Result();
  if (aWarnLog != nullptr && aWarnLog[0] != '\0')
  {
    std::cout << aWarnLog << '\n';
  }
  theDI.Reset();

  Quantity_ColorRGBA aColor = anImage.PixelColor(anX, anY, true);
  if (toShowName)
  {
    if (aBufferType == Graphic3d_BT_RGBA)
    {
      theDI << Quantity_Color::StringName(aColor.GetRGB().Name()) << " " << aColor.Alpha();
    }
    else
    {
      theDI << Quantity_Color::StringName(aColor.GetRGB().Name());
    }
  }
  else if (toShowHex)
  {
    if (aBufferType == Graphic3d_BT_RGBA)
    {
      theDI << Quantity_ColorRGBA::ColorToHex(aColor);
    }
    else
    {
      theDI << Quantity_Color::ColorToHex(aColor.GetRGB());
    }
  }
  else
  {
    switch (aBufferType)
    {
      default:
      case Graphic3d_BT_RGB: {
        if (toShowHls)
        {
          theDI << aColor.GetRGB().Hue() << " " << aColor.GetRGB().Light() << " "
                << aColor.GetRGB().Saturation();
        }
        else if (toShow_sRGB)
        {
          const NCollection_Vec4<float> aColor_sRGB =
            Quantity_ColorRGBA::Convert_LinearRGB_To_sRGB((NCollection_Vec4<float>)aColor);
          theDI << aColor_sRGB.r() << " " << aColor_sRGB.g() << " " << aColor_sRGB.b();
        }
        else
        {
          theDI << aColor.GetRGB().Red() << " " << aColor.GetRGB().Green() << " "
                << aColor.GetRGB().Blue();
        }
        break;
      }
      case Graphic3d_BT_RGBA: {
        const NCollection_Vec4<float> aVec4 =
          toShow_sRGB
            ? Quantity_ColorRGBA::Convert_LinearRGB_To_sRGB((NCollection_Vec4<float>)aColor)
            : (NCollection_Vec4<float>)aColor;
        theDI << aVec4.r() << " " << aVec4.g() << " " << aVec4.b() << " " << aVec4.a();
        break;
      }
      case Graphic3d_BT_Depth: {
        theDI << aColor.GetRGB().Red();
        break;
      }
    }
  }

  return 0;
}

//! Auxiliary presentation for an image plane.
class ViewerTest_ImagePrs : public AIS_InteractiveObject
{
public:
  //! Main constructor.
  ViewerTest_ImagePrs(const occ::handle<Image_PixMap>& theImage,
                      const double                     theWidth,
                      const double                     theHeight,
                      const TCollection_AsciiString&   theLabel)
      : myLabel(theLabel),
        myWidth(theWidth),
        myHeight(theHeight)
  {
    SetDisplayMode(0);
    SetHilightMode(1);
    myDynHilightDrawer->SetZLayer(Graphic3d_ZLayerId_Topmost);
    {
      myDrawer->SetShadingAspect(new Prs3d_ShadingAspect());
      const occ::handle<Graphic3d_AspectFillArea3d>& aFillAspect =
        myDrawer->ShadingAspect()->Aspect();
      Graphic3d_MaterialAspect aMat;
      aMat.SetMaterialType(Graphic3d_MATERIAL_PHYSIC);
      aMat.SetAmbientColor(Quantity_NOC_BLACK);
      aMat.SetDiffuseColor(Quantity_NOC_WHITE);
      aMat.SetSpecularColor(Quantity_NOC_BLACK);
      aMat.SetEmissiveColor(Quantity_NOC_BLACK);
      aFillAspect->SetFrontMaterial(aMat);
      aFillAspect->SetTextureMap(new Graphic3d_Texture2D(theImage));
      aFillAspect->SetTextureMapOn();
    }
    {
      occ::handle<Prs3d_TextAspect> aTextAspect = new Prs3d_TextAspect();
      aTextAspect->SetHorizontalJustification(Graphic3d_HTA_CENTER);
      aTextAspect->SetVerticalJustification(Graphic3d_VTA_CENTER);
      myDrawer->SetTextAspect(aTextAspect);
    }
    {
      const gp_Dir aNorm(gp_Dir::D::Z);
      myTris = new Graphic3d_ArrayOfTriangles(4, 6, true, false, true);
      myTris->AddVertex(gp_Pnt(-myWidth * 0.5, -myHeight * 0.5, 0.0), aNorm, gp_Pnt2d(0.0, 0.0));
      myTris->AddVertex(gp_Pnt(myWidth * 0.5, -myHeight * 0.5, 0.0), aNorm, gp_Pnt2d(1.0, 0.0));
      myTris->AddVertex(gp_Pnt(-myWidth * 0.5, myHeight * 0.5, 0.0), aNorm, gp_Pnt2d(0.0, 1.0));
      myTris->AddVertex(gp_Pnt(myWidth * 0.5, myHeight * 0.5, 0.0), aNorm, gp_Pnt2d(1.0, 1.0));
      myTris->AddEdge(1);
      myTris->AddEdge(2);
      myTris->AddEdge(3);
      myTris->AddEdge(3);
      myTris->AddEdge(2);
      myTris->AddEdge(4);

      myRect = new Graphic3d_ArrayOfPolylines(4);
      myRect->AddVertex(myTris->Vertice(1));
      myRect->AddVertex(myTris->Vertice(3));
      myRect->AddVertex(myTris->Vertice(4));
      myRect->AddVertex(myTris->Vertice(2));
    }
  }

  //! Returns TRUE for accepted display modes.
  bool AcceptDisplayMode(const int theMode) const override { return theMode == 0 || theMode == 1; }

  //! Compute presentation.
  void Compute(const occ::handle<PrsMgr_PresentationManager>&,
               const occ::handle<Prs3d_Presentation>& thePrs,
               const int                              theMode) override
  {
    switch (theMode)
    {
      case 0: {
        occ::handle<Graphic3d_Group> aGroup = thePrs->NewGroup();
        aGroup->AddPrimitiveArray(myTris);
        aGroup->SetGroupPrimitivesAspect(myDrawer->ShadingAspect()->Aspect());
        aGroup->AddPrimitiveArray(myRect);
        aGroup->SetGroupPrimitivesAspect(myDrawer->LineAspect()->Aspect());
        return;
      }
      case 1: {
        Prs3d_Text::Draw(thePrs->NewGroup(),
                         myDrawer->TextAspect(),
                         myLabel,
                         gp_Pnt(0.0, 0.0, 0.0));
        occ::handle<Graphic3d_Group> aGroup = thePrs->NewGroup();
        aGroup->AddPrimitiveArray(myRect);
        aGroup->SetGroupPrimitivesAspect(myDrawer->LineAspect()->Aspect());
        return;
      }
    }
  }

  //! Compute selection.
  void ComputeSelection(const occ::handle<SelectMgr_Selection>& theSel, const int theMode) override
  {
    if (theMode == 0)
    {
      occ::handle<SelectMgr_EntityOwner> anEntityOwner = new SelectMgr_EntityOwner(this, 5);
      occ::handle<Select3D_SensitivePrimitiveArray> aSensitive =
        new Select3D_SensitivePrimitiveArray(anEntityOwner);
      aSensitive->InitTriangulation(myTris->Attributes(), myTris->Indices(), TopLoc_Location());
      theSel->Add(aSensitive);
    }
  }

private:
  occ::handle<Graphic3d_ArrayOfTriangles> myTris;
  occ::handle<Graphic3d_ArrayOfPolylines> myRect;
  TCollection_AsciiString                 myLabel;
  double                                  myWidth;
  double                                  myHeight;
};

//==============================================================================
// function : VDiffImage
// purpose  : The draw-command compares two images.
//==============================================================================

static int VDiffImage(Draw_Interpretor& theDI, int theArgNb, const char** theArgVec)
{
  if (theArgNb < 3)
  {
    Message::SendFail("Syntax error: not enough arguments");
    return 1;
  }

  int                     anArgIter = 1;
  TCollection_AsciiString anImgPathRef(theArgVec[anArgIter++]);
  TCollection_AsciiString anImgPathNew(theArgVec[anArgIter++]);
  TCollection_AsciiString aDiffImagePath;
  double                  aTolColor        = -1.0;
  int                     toBlackWhite     = -1;
  int                     isBorderFilterOn = -1;
  bool                    isOldSyntax      = false;
  TCollection_AsciiString aViewName, aPrsNameRef, aPrsNameNew, aPrsNameDiff;
  for (; anArgIter < theArgNb; ++anArgIter)
  {
    TCollection_AsciiString anArg(theArgVec[anArgIter]);
    anArg.LowerCase();
    if (anArgIter + 1 < theArgNb
        && (anArg == "-toleranceofcolor" || anArg == "-tolerancecolor" || anArg == "-tolerance"
            || anArg == "-toler"))
    {
      aTolColor = Atof(theArgVec[++anArgIter]);
      if (aTolColor < 0.0 || aTolColor > 1.0)
      {
        Message::SendFail() << "Syntax error at '" << anArg << " " << theArgVec[anArgIter] << "'";
        return 1;
      }
    }
    else if (anArg == "-blackwhite")
    {
      bool toEnable = true;
      if (anArgIter + 1 < theArgNb && Draw::ParseOnOff(theArgVec[anArgIter + 1], toEnable))
      {
        ++anArgIter;
      }
      toBlackWhite = toEnable ? 1 : 0;
    }
    else if (anArg == "-borderfilter")
    {
      bool toEnable = true;
      if (anArgIter + 1 < theArgNb && Draw::ParseOnOff(theArgVec[anArgIter + 1], toEnable))
      {
        ++anArgIter;
      }
      isBorderFilterOn = toEnable ? 1 : 0;
    }
    else if (anArg == "-exitonclose")
    {
      ViewerTest_EventManager::ToExitOnCloseView() = true;
      if (anArgIter + 1 < theArgNb
          && Draw::ParseOnOff(theArgVec[anArgIter + 1],
                              ViewerTest_EventManager::ToExitOnCloseView()))
      {
        ++anArgIter;
      }
    }
    else if (anArg == "-closeonescape" || anArg == "-closeonesc")
    {
      ViewerTest_EventManager::ToCloseViewOnEscape() = true;
      if (anArgIter + 1 < theArgNb
          && Draw::ParseOnOff(theArgVec[anArgIter + 1],
                              ViewerTest_EventManager::ToCloseViewOnEscape()))
      {
        ++anArgIter;
      }
    }
    else if (anArgIter + 3 < theArgNb && anArg == "-display")
    {
      aViewName   = theArgVec[++anArgIter];
      aPrsNameRef = theArgVec[++anArgIter];
      aPrsNameNew = theArgVec[++anArgIter];
      if (anArgIter + 1 < theArgNb && *theArgVec[anArgIter + 1] != '-')
      {
        aPrsNameDiff = theArgVec[++anArgIter];
      }
    }
    else if (aTolColor < 0.0 && anArg.IsRealValue(true))
    {
      isOldSyntax = true;
      aTolColor   = anArg.RealValue();
      if (aTolColor < 0.0 || aTolColor > 1.0)
      {
        Message::SendFail() << "Syntax error at '" << anArg << " " << theArgVec[anArgIter] << "'";
        return 1;
      }
    }
    else if (isOldSyntax && toBlackWhite == -1 && (anArg == "0" || anArg == "1"))
    {
      toBlackWhite = anArg == "1" ? 1 : 0;
    }
    else if (isOldSyntax && isBorderFilterOn == -1 && (anArg == "0" || anArg == "1"))
    {
      isBorderFilterOn = anArg == "1" ? 1 : 0;
    }
    else if (aDiffImagePath.IsEmpty())
    {
      aDiffImagePath = theArgVec[anArgIter];
    }
    else
    {
      Message::SendFail() << "Syntax error at '" << theArgVec[anArgIter] << "'";
      return 1;
    }
  }

  occ::handle<Image_AlienPixMap> anImgRef = new Image_AlienPixMap();
  occ::handle<Image_AlienPixMap> anImgNew = new Image_AlienPixMap();
  if (!anImgRef->Load(anImgPathRef))
  {
    Message::SendFail() << "Error: image file '" << anImgPathRef << "' cannot be read";
    return 1;
  }
  if (!anImgNew->Load(anImgPathNew))
  {
    Message::SendFail() << "Error: image file '" << anImgPathNew << "' cannot be read";
    return 1;
  }

  // compare the images
  Image_Diff aComparer;
  int        aDiffColorsNb = -1;
  if (aComparer.Init(anImgRef, anImgNew, toBlackWhite == 1))
  {
    aComparer.SetColorTolerance(aTolColor >= 0.0 ? aTolColor : 0.0);
    aComparer.SetBorderFilterOn(isBorderFilterOn == 1);
    aDiffColorsNb = aComparer.Compare();
    theDI << aDiffColorsNb << "\n";
  }

  // save image of difference
  occ::handle<Image_AlienPixMap> aDiff;
  if (aDiffColorsNb > 0 && (!aDiffImagePath.IsEmpty() || !aPrsNameDiff.IsEmpty()))
  {
    aDiff = new Image_AlienPixMap();
    if (!aDiff->InitTrash(Image_Format_Gray, anImgRef->SizeX(), anImgRef->SizeY()))
    {
      Message::SendFail() << "Error: cannot allocate memory for diff image " << anImgRef->SizeX()
                          << "x" << anImgRef->SizeY();
      return 1;
    }
    aComparer.SaveDiffImage(*aDiff);
    if (!aDiffImagePath.IsEmpty() && !aDiff->Save(aDiffImagePath))
    {
      Message::SendFail() << "Error: diff image file '" << aDiffImagePath << "' cannot be written";
      return 1;
    }
  }

  if (aViewName.IsEmpty())
  {
    return 0;
  }

  ViewerTest_Names aViewNames(aViewName);
  if (ViewerTest_myViews.IsBound1(aViewNames.GetViewName()))
  {
    TCollection_AsciiString aCommand =
      TCollection_AsciiString("vclose ") + aViewNames.GetViewName();
    theDI.Eval(aCommand.ToCString());
  }

  ViewerTest_VinitParams aParams;
  aParams.ViewName = aViewName;
  aParams.Size.x() = float(anImgRef->SizeX() * 2);
  aParams.Size.y() = !aDiff.IsNull() && !aPrsNameDiff.IsEmpty() ? float(anImgRef->SizeY() * 2)
                                                                : float(anImgRef->SizeY());
  TCollection_AsciiString aViewId = ViewerTest::ViewerInit(aParams);

  double aRatio = anImgRef->Ratio();
  double aSizeX = 1.0;
  double aSizeY = aSizeX / aRatio;
  {
    OSD_Path                aPath(anImgPathRef);
    TCollection_AsciiString aLabelRef;
    if (!aPath.Name().IsEmpty())
    {
      aLabelRef = aPath.Name() + aPath.Extension();
    }
    aLabelRef +=
      TCollection_AsciiString() + "\n" + int(anImgRef->SizeX()) + "x" + int(anImgRef->SizeY());

    occ::handle<ViewerTest_ImagePrs> anImgRefPrs =
      new ViewerTest_ImagePrs(anImgRef, aSizeX, aSizeY, aLabelRef);
    gp_Trsf aTrsfRef;
    aTrsfRef.SetTranslationPart(gp_Vec(-aSizeX * 0.5, 0.0, 0.0));
    anImgRefPrs->SetLocalTransformation(aTrsfRef);
    ViewerTest::Display(aPrsNameRef, anImgRefPrs, false, true);
  }
  {
    OSD_Path                aPath(anImgPathNew);
    TCollection_AsciiString aLabelNew;
    if (!aPath.Name().IsEmpty())
    {
      aLabelNew = aPath.Name() + aPath.Extension();
    }
    aLabelNew +=
      TCollection_AsciiString() + "\n" + int(anImgNew->SizeX()) + "x" + int(anImgNew->SizeY());

    occ::handle<ViewerTest_ImagePrs> anImgNewPrs =
      new ViewerTest_ImagePrs(anImgNew, aSizeX, aSizeY, aLabelNew);
    gp_Trsf aTrsfRef;
    aTrsfRef.SetTranslationPart(gp_Vec(aSizeX * 0.5, 0.0, 0.0));
    anImgNewPrs->SetLocalTransformation(aTrsfRef);
    ViewerTest::Display(aPrsNameNew, anImgNewPrs, false, true);
  }
  occ::handle<ViewerTest_ImagePrs> anImgDiffPrs;
  if (!aDiff.IsNull())
  {
    anImgDiffPrs = new ViewerTest_ImagePrs(aDiff,
                                           aSizeX,
                                           aSizeY,
                                           TCollection_AsciiString()
                                             + "Difference: " + aDiffColorsNb + " pixels");
    gp_Trsf aTrsfDiff;
    aTrsfDiff.SetTranslationPart(gp_Vec(0.0, -aSizeY, 0.0));
    anImgDiffPrs->SetLocalTransformation(aTrsfDiff);
  }
  if (!aPrsNameDiff.IsEmpty())
  {
    ViewerTest::Display(aPrsNameDiff, anImgDiffPrs, false, true);
  }
  ViewerTest::CurrentView()->SetProj(V3d_Zpos);
  ViewerTest::CurrentView()->FitAll();
  return 0;
}

//=======================================================================
// function : VSelect
// purpose  : Emulates different types of selection by mouse:
//           1) single click selection
//           2) selection with rectangle having corners at pixel positions (x1,y1) and (x2,y2)
//           3) selection with polygon having corners at
//           pixel positions (x1,y1),...,(xn,yn)
//           4) any of these selections with shift button pressed
//=======================================================================
static int VSelect(Draw_Interpretor&, int theNbArgs, const char** theArgVec)
{
  const occ::handle<AIS_InteractiveContext>& aCtx = ViewerTest::GetAISContext();
  if (aCtx.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  NCollection_Sequence<NCollection_Vec2<int>> aPnts;
  bool                                        toAllowOverlap = false;
  AIS_SelectionScheme                         aSelScheme     = AIS_SelectionScheme_Replace;
  for (int anArgIter = 1; anArgIter < theNbArgs; ++anArgIter)
  {
    TCollection_AsciiString anArg(theArgVec[anArgIter]);
    anArg.LowerCase();
    if (anArg == "-allowoverlap")
    {
      toAllowOverlap = true;
      if (anArgIter + 1 < theNbArgs && Draw::ParseOnOff(theArgVec[anArgIter + 1], toAllowOverlap))
      {
        ++anArgIter;
      }
    }
    else if (anArg == "-replace")
    {
      aSelScheme = AIS_SelectionScheme_Replace;
    }
    else if (anArg == "-replaceextra")
    {
      aSelScheme = AIS_SelectionScheme_ReplaceExtra;
    }
    else if (anArg == "-xor" || anArg == "-shift")
    {
      aSelScheme = AIS_SelectionScheme_XOR;
    }
    else if (anArg == "-add")
    {
      aSelScheme = AIS_SelectionScheme_Add;
    }
    else if (anArg == "-remove")
    {
      aSelScheme = AIS_SelectionScheme_Remove;
    }
    else if (anArgIter + 1 < theNbArgs && anArg.IsIntegerValue()
             && TCollection_AsciiString(theArgVec[anArgIter + 1]).IsIntegerValue())
    {
      const TCollection_AsciiString anArgNext(theArgVec[++anArgIter]);
      aPnts.Append(NCollection_Vec2<int>(anArg.IntegerValue(), anArgNext.IntegerValue()));
    }
    else if (anArgIter + 1 == theNbArgs && anArg.IsIntegerValue())
    {
      if (anArg.IntegerValue() == 1)
      {
        aSelScheme = AIS_SelectionScheme_XOR;
      }
    }
    else
    {
      Message::SendFail() << "Syntax error at '" << anArg << "'";
      return 1;
    }
  }

  if (toAllowOverlap)
  {
    aCtx->MainSelector()->AllowOverlapDetection(toAllowOverlap);
  }

  occ::handle<ViewerTest_EventManager> aCurrentEventManager = ViewerTest::CurrentEventManager();
  if (aPnts.IsEmpty())
  {
    aCtx->SelectDetected(aSelScheme);
    aCtx->CurrentViewer()->Invalidate();
  }
  else if (aPnts.Length() == 2)
  {
    if (toAllowOverlap && aPnts.First().y() < aPnts.Last().y())
    {
      std::swap(aPnts.ChangeFirst(), aPnts.ChangeLast());
    }
    else if (!toAllowOverlap && aPnts.First().y() > aPnts.Last().y())
    {
      std::swap(aPnts.ChangeFirst(), aPnts.ChangeLast());
    }

    aCurrentEventManager->SelectInViewer(aPnts, aSelScheme);
  }
  else
  {
    aCurrentEventManager->SelectInViewer(aPnts, aSelScheme);
  }
  aCurrentEventManager->FlushViewEvents(aCtx, ViewerTest::CurrentView(), true);
  return 0;
}

//=======================================================================
// function : VSelectPriority
// purpose  : Prints or sets the selection priority for an object
//=======================================================================
static int VSelectPriority(Draw_Interpretor& theDI, int theNbArgs, const char** theArgVec)
{
  occ::handle<AIS_InteractiveContext> aContext = ViewerTest::GetAISContext();
  ViewerTest_AutoUpdater              anUpdateTool(aContext, ViewerTest::CurrentView());
  if (aContext.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  TCollection_AsciiString aLastArg(theArgVec[theNbArgs - 1]);
  int                     aPriority = -1;
  int                     aNbArgs   = theNbArgs;
  if (aNbArgs < 2 || aNbArgs > 3)
  {
    Message::SendFail("Syntax error: wrong number of arguments! See usage:");
    theDI.PrintHelp(theArgVec[0]);
    return 1;
  }

  if (aLastArg.IsIntegerValue())
  {
    TCollection_AsciiString aFocusArg(theArgVec[1]);
    aPriority = aLastArg.IntegerValue();
    --aNbArgs;
    if (aPriority < 0)
    {
      Message::SendFail() << "Syntax error: the specified selection priority value '" << aLastArg
                          << "' is invalid ]";
      return 1;
    }
  }
  else
  {
    anUpdateTool.Invalidate();
  }

  for (int anArgIter = 1; anArgIter < aNbArgs; ++anArgIter)
  {
    TCollection_AsciiString            aName(theArgVec[anArgIter]);
    occ::handle<AIS_InteractiveObject> anIObj;
    GetMapOfAIS().Find2(aName, anIObj);
    if (anIObj.IsNull())
    {
      Message::SendFail() << "Error: the object '" << theArgVec[1] << "' is not displayed";
      return 1;
    }

    occ::handle<SelectMgr_EntityOwner> anOwner = anIObj->GlobalSelOwner();
    if (!anOwner.IsNull())
    {
      if (aPriority == Graphic3d_DisplayPriority_INVALID)
      {
        theDI << anOwner->Priority() << " ";
      }
      else
      {
        anOwner->SetPriority(aPriority);
      }
    }
    else
    {
      Message::SendFail() << "Error: the object '" << theArgVec[1]
                          << "' is does not have a selection priority attached.";
      return 1;
    }
  }
  return 0;
}

//=======================================================================
// function : VMoveTo
// purpose  : Emulates cursor movement to defined pixel position
//=======================================================================
static int VMoveTo(Draw_Interpretor& theDI, int theNbArgs, const char** theArgVec)
{
  const occ::handle<AIS_InteractiveContext>& aContext = ViewerTest::GetAISContext();
  const occ::handle<V3d_View>&               aView    = ViewerTest::CurrentView();
  if (aContext.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  NCollection_Vec2<int> aMousePos(IntegerLast(), IntegerLast());
  for (int anArgIter = 1; anArgIter < theNbArgs; ++anArgIter)
  {
    TCollection_AsciiString anArgStr(theArgVec[anArgIter]);
    anArgStr.LowerCase();
    if (anArgStr == "-reset" || anArgStr == "-clear")
    {
      if (anArgIter + 1 < theNbArgs)
      {
        Message::SendFail() << "Syntax error at '" << theArgVec[anArgIter + 1] << "'";
        return 1;
      }

      const bool toEchoGrid =
        aContext->CurrentViewer()->IsGridActive() && aContext->CurrentViewer()->GridEcho();
      if (toEchoGrid)
      {
        aContext->CurrentViewer()->HideGridEcho(aView);
      }
      if (aContext->ClearDetected() || toEchoGrid)
      {
        aContext->CurrentViewer()->RedrawImmediate();
      }
      return 0;
    }
    else if (aMousePos.x() == IntegerLast() && anArgStr.IsIntegerValue())
    {
      aMousePos.x() = anArgStr.IntegerValue();
    }
    else if (aMousePos.y() == IntegerLast() && anArgStr.IsIntegerValue())
    {
      aMousePos.y() = anArgStr.IntegerValue();
    }
    else
    {
      Message::SendFail() << "Syntax error at '" << theArgVec[anArgIter] << "'";
      return 1;
    }
  }

  if (aMousePos.x() == IntegerLast() || aMousePos.y() == IntegerLast())
  {
    Message::SendFail("Syntax error: wrong number of arguments");
    return 1;
  }

  ViewerTest::CurrentEventManager()->ResetPreviousMoveTo();
  ViewerTest::CurrentEventManager()->UpdateMousePosition(aMousePos,
                                                         Aspect_VKeyMouse_NONE,
                                                         Aspect_VKeyFlags_NONE,
                                                         false);
  ViewerTest::CurrentEventManager()->FlushViewEvents(ViewerTest::GetAISContext(), aView, true);

  gp_Pnt                                    aTopPnt(RealLast(), RealLast(), RealLast());
  const occ::handle<SelectMgr_EntityOwner>& aDetOwner = aContext->DetectedOwner();
  for (int aDetIter = 1; aDetIter <= aContext->MainSelector()->NbPicked(); ++aDetIter)
  {
    if (aContext->MainSelector()->Picked(aDetIter) == aDetOwner)
    {
      aTopPnt = aContext->MainSelector()->PickedPoint(aDetIter);
      break;
    }
  }
  theDI << aTopPnt.X() << " " << aTopPnt.Y() << " " << aTopPnt.Z();
  return 0;
}

//=================================================================================================

static int VSelectByAxis(Draw_Interpretor& theDI, int theNbArgs, const char** theArgVec)
{
  const occ::handle<AIS_InteractiveContext>& aContext = ViewerTest::GetAISContext();
  const occ::handle<V3d_View>&               aView    = ViewerTest::CurrentView();
  if (aContext.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  TCollection_AsciiString aName;
  gp_XYZ                  anAxisLocation(RealLast(), RealLast(), RealLast());
  gp_XYZ                  anAxisDirection(RealLast(), RealLast(), RealLast());
  bool                    isOnlyTop    = true;
  bool                    toShowNormal = false;
  for (int anArgIter = 1; anArgIter < theNbArgs; ++anArgIter)
  {
    TCollection_AsciiString anArgStr(theArgVec[anArgIter]);
    anArgStr.LowerCase();
    if (anArgStr == "-display")
    {
      if (anArgIter + 1 >= theNbArgs)
      {
        Message::SendFail() << "Syntax error at argument '" << anArgStr << "'";
        return 1;
      }
      aName = theArgVec[++anArgIter];
    }
    else if (anArgStr == "-onlytop")
    {
      isOnlyTop = true;
      if (anArgIter + 1 < theNbArgs && Draw::ParseOnOff(theArgVec[anArgIter + 1], isOnlyTop))
      {
        ++anArgIter;
      }
    }
    else if (anArgStr == "-shownormal")
    {
      toShowNormal = true;
      if (anArgIter + 1 < theNbArgs && Draw::ParseOnOff(theArgVec[anArgIter + 1], toShowNormal))
      {
        ++anArgIter;
      }
    }
    else if (Precision::IsInfinite(anAxisLocation.X()) && anArgStr.IsRealValue())
    {
      anAxisLocation.SetX(anArgStr.RealValue());
    }
    else if (Precision::IsInfinite(anAxisLocation.Y()) && anArgStr.IsRealValue())
    {
      anAxisLocation.SetY(anArgStr.RealValue());
    }
    else if (Precision::IsInfinite(anAxisLocation.Z()) && anArgStr.IsRealValue())
    {
      anAxisLocation.SetZ(anArgStr.RealValue());
    }
    else if (Precision::IsInfinite(anAxisDirection.X()) && anArgStr.IsRealValue())
    {
      anAxisDirection.SetX(anArgStr.RealValue());
    }
    else if (Precision::IsInfinite(anAxisDirection.Y()) && anArgStr.IsRealValue())
    {
      anAxisDirection.SetY(anArgStr.RealValue());
    }
    else if (Precision::IsInfinite(anAxisDirection.Z()) && anArgStr.IsRealValue())
    {
      anAxisDirection.SetZ(anArgStr.RealValue());
    }
    else
    {
      Message::SendFail() << "Syntax error at '" << theArgVec[anArgIter] << "'";
      return 1;
    }
  }

  if (Precision::IsInfinite(anAxisLocation.X()) || Precision::IsInfinite(anAxisLocation.Y())
      || Precision::IsInfinite(anAxisLocation.Z()) || Precision::IsInfinite(anAxisDirection.X())
      || Precision::IsInfinite(anAxisDirection.Y()) || Precision::IsInfinite(anAxisDirection.Z()))
  {
    Message::SendFail() << "Invalid axis location and direction";
    return 1;
  }

  gp_Ax1 anAxis(anAxisLocation, anAxisDirection);
  gp_Pnt aTopPnt;
  if (!ViewerTest::CurrentEventManager()->PickAxis(aTopPnt, aContext, aView, anAxis))
  {
    theDI << "There are no any intersections with this axis.";
    return 0;
  }
  NCollection_Sequence<gp_Pnt>                  aPoints;
  NCollection_Sequence<NCollection_Vec3<float>> aNormals;
  NCollection_Sequence<double>                  aNormalLengths;
  for (int aPickIter = 1; aPickIter <= aContext->MainSelector()->NbPicked(); ++aPickIter)
  {
    const SelectMgr_SortCriterion& aPickedData = aContext->MainSelector()->PickedData(aPickIter);
    aPoints.Append(aPickedData.Point);
    aNormals.Append(aPickedData.Normal);
    double aNormalLength = 1.0;
    if (!aPickedData.Entity.IsNull())
    {
      aNormalLength = 0.2 * aPickedData.Entity->BoundingBox().Size().maxComp();
    }
    aNormalLengths.Append(aNormalLength);
  }
  if (!aName.IsEmpty())
  {
    bool wasAuto = aContext->GetAutoActivateSelection();
    aContext->SetAutoActivateSelection(false);

    // Display axis
    Quantity_Color                   anAxisColor = Quantity_NOC_GREEN;
    occ::handle<Geom_Axis2Placement> anAx2Axis =
      new Geom_Axis2Placement(gp_Ax2(anAxisLocation, anAxisDirection));
    occ::handle<AIS_Axis> anAISAxis = new AIS_Axis(gp_Ax1(anAxisLocation, anAxisDirection));
    anAISAxis->SetColor(anAxisColor);
    ViewerTest::Display(TCollection_AsciiString(aName) + "_axis", anAISAxis, false);

    // Display axis start point
    occ::handle<AIS_Point> anAISStartPnt = new AIS_Point(new Geom_CartesianPoint(anAxisLocation));
    anAISStartPnt->SetMarker(Aspect_TOM_O);
    anAISStartPnt->SetColor(anAxisColor);
    ViewerTest::Display(TCollection_AsciiString(aName) + "_start", anAISStartPnt, false);

    int anIndex = 0;
    for (NCollection_Sequence<gp_Pnt>::Iterator aPntIter(aPoints); aPntIter.More();
         aPntIter.Next(), anIndex++)
    {
      const gp_Pnt& aPoint = aPntIter.Value();

      // Display normals in intersection points
      if (toShowNormal)
      {
        const NCollection_Vec3<float>& aNormal       = aNormals.Value(anIndex + 1);
        double                         aNormalLength = aNormalLengths.Value(anIndex + 1);
        if (aNormal.SquareModulus() > ShortRealEpsilon())
        {
          gp_Dir aNormalDir((double)aNormal.x(), (double)aNormal.y(), (double)aNormal.z());
          occ::handle<AIS_Axis> anAISNormal =
            new AIS_Axis(gp_Ax1(aPoint, aNormalDir), aNormalLength);
          anAISNormal->SetColor(Quantity_NOC_BLUE);
          anAISNormal->SetInfiniteState(false);
          ViewerTest::Display(TCollection_AsciiString(aName) + "_normal_" + anIndex,
                              anAISNormal,
                              false);
        }
      }

      // Display intersection points
      occ::handle<Geom_CartesianPoint> anIntersectPnt      = new Geom_CartesianPoint(aPoint);
      occ::handle<AIS_Point>           anAISIntersectPoint = new AIS_Point(anIntersectPnt);
      anAISIntersectPoint->SetMarker(Aspect_TOM_PLUS);
      anAISIntersectPoint->SetColor(Quantity_NOC_RED);
      ViewerTest::Display(TCollection_AsciiString(aName) + "_intersect_" + anIndex,
                          anAISIntersectPoint,
                          true);
    }

    aContext->SetAutoActivateSelection(wasAuto);
  }

  int anIndex = 0;
  for (NCollection_Sequence<gp_Pnt>::Iterator anIter(aPoints); anIter.More();
       anIter.Next(), anIndex++)
  {
    const gp_Pnt& aPnt = anIter.Value();
    theDI << aPnt.X() << " " << aPnt.Y() << " " << aPnt.Z() << "\n";
  }
  return 0;
}

namespace
{
//! Global map storing all animations registered in ViewerTest.
static NCollection_DataMap<TCollection_AsciiString, occ::handle<AIS_Animation>>
  ViewerTest_AnimationTimelineMap;

//! The animation calling the Draw Harness command.
class ViewerTest_AnimationProc : public AIS_Animation
{
  DEFINE_STANDARD_RTTI_INLINE(ViewerTest_AnimationProc, AIS_Animation)
public:
  //! Main constructor.
  ViewerTest_AnimationProc(const TCollection_AsciiString& theAnimationName,
                           Draw_Interpretor*              theDI,
                           const TCollection_AsciiString& theCommand)
      : AIS_Animation(theAnimationName),
        myDrawInter(theDI),
        myCommand(theCommand)
  {
    //
  }

protected:
  //! Evaluate the command.
  void update(const AIS_AnimationProgress& theProgress) override
  {
    TCollection_AsciiString aCmd = myCommand;
    replace(aCmd, "%pts", TCollection_AsciiString(theProgress.Pts));
    replace(aCmd, "%localpts", TCollection_AsciiString(theProgress.LocalPts));
    replace(aCmd, "%ptslocal", TCollection_AsciiString(theProgress.LocalPts));
    replace(aCmd, "%normalized", TCollection_AsciiString(theProgress.LocalNormalized));
    replace(aCmd, "%localnormalized", TCollection_AsciiString(theProgress.LocalNormalized));
    myDrawInter->Eval(aCmd.ToCString());
  }

  //! Find the keyword in the command and replace it with value.
  //! @return the position of the keyword to pass value
  void replace(TCollection_AsciiString&       theCmd,
               const TCollection_AsciiString& theKey,
               const TCollection_AsciiString& theVal)
  {
    TCollection_AsciiString aCmd(theCmd);
    aCmd.LowerCase();
    const int aPos = aCmd.Search(theKey);
    if (aPos == -1)
    {
      return;
    }

    TCollection_AsciiString aPart1, aPart2;
    int                     aPart1To = aPos - 1;
    if (aPart1To >= 1 && aPart1To <= theCmd.Length())
    {
      aPart1 = theCmd.SubString(1, aPart1To);
    }

    int aPart2From = aPos + theKey.Length();
    if (aPart2From >= 1 && aPart2From <= theCmd.Length())
    {
      aPart2 = theCmd.SubString(aPart2From, theCmd.Length());
    }

    theCmd = aPart1 + theVal + aPart2;
  }

protected:
  Draw_Interpretor*       myDrawInter;
  TCollection_AsciiString myCommand;
};

//! Auxiliary animation holder.
class ViewerTest_AnimationHolder : public AIS_AnimationCamera
{
  DEFINE_STANDARD_RTTI_INLINE(ViewerTest_AnimationHolder, AIS_AnimationCamera)
public:
  ViewerTest_AnimationHolder(const occ::handle<AIS_Animation>& theAnim,
                             const occ::handle<V3d_View>&      theView,
                             const bool                        theIsFreeView)
      : AIS_AnimationCamera("ViewerTest_AnimationHolder", occ::handle<V3d_View>())
  {
    if (theAnim->Timer().IsNull())
    {
      theAnim->SetTimer(new Media_Timer());
    }
    myTimer = theAnim->Timer();
    myView  = theView;
    if (theIsFreeView)
    {
      myCamStart = new Graphic3d_Camera(theView->Camera());
    }
    Add(theAnim);
  }

  //! Start playback.
  void StartTimer(const double theStartPts,
                  const double thePlaySpeed,
                  const bool   theToUpdate,
                  const bool   theToStopTimer) override
  {
    base_type::StartTimer(theStartPts, thePlaySpeed, theToUpdate, theToStopTimer);
    if (theToStopTimer)
    {
      abortPlayback();
    }
  }

  //! Pause animation.
  void Pause() override
  {
    myState = AnimationState_Paused;
    // default implementation would stop all children,
    // but we want to keep wrapped animation paused
    myAnimations.First()->Pause();
    abortPlayback();
  }

  //! Stop animation.
  void Stop() override
  {
    base_type::Stop();
    abortPlayback();
  }

  //! Process one step of the animation according to the input time progress, including all
  //! children.
  void updateWithChildren(const AIS_AnimationProgress& thePosition) override
  {
    occ::handle<V3d_View> aView = myView;
    if (!aView.IsNull() && !myCamStart.IsNull())
    {
      myCamStart->Copy(aView->Camera());
    }
    base_type::updateWithChildren(thePosition);
    if (!aView.IsNull() && !myCamStart.IsNull())
    {
      aView->Camera()->Copy(myCamStart);
    }
  }

private:
  void abortPlayback()
  {
    if (!myView.IsNull())
    {
      myView.Nullify();
    }
  }
};

//! Replace the animation with the new one.
static void replaceAnimation(const occ::handle<AIS_Animation>& theParentAnimation,
                             occ::handle<AIS_Animation>&       theAnimation,
                             const occ::handle<AIS_Animation>& theAnimationNew)
{
  theAnimationNew->CopyFrom(theAnimation);
  if (!theParentAnimation.IsNull())
  {
    theParentAnimation->Replace(theAnimation, theAnimationNew);
  }
  else
  {
    ViewerTest_AnimationTimelineMap.UnBind(theAnimationNew->Name());
    ViewerTest_AnimationTimelineMap.Bind(theAnimationNew->Name(), theAnimationNew);
  }
  theAnimation = theAnimationNew;
}

//! Parse the point.
static bool parseXYZ(const char** theArgVec, gp_XYZ& thePnt)
{
  const TCollection_AsciiString anXYZ[3] = {theArgVec[0], theArgVec[1], theArgVec[2]};
  if (!anXYZ[0].IsRealValue(true) || !anXYZ[1].IsRealValue(true) || !anXYZ[2].IsRealValue(true))
  {
    return false;
  }

  thePnt.SetCoord(anXYZ[0].RealValue(), anXYZ[1].RealValue(), anXYZ[2].RealValue());
  return true;
}

//! Parse the quaternion.
static bool parseQuaternion(const char** theArgVec, gp_Quaternion& theQRot)
{
  const TCollection_AsciiString anXYZW[4] = {theArgVec[0],
                                             theArgVec[1],
                                             theArgVec[2],
                                             theArgVec[3]};
  if (!anXYZW[0].IsRealValue(true) || !anXYZW[1].IsRealValue(true) || !anXYZW[2].IsRealValue(true)
      || !anXYZW[3].IsRealValue(true))
  {
    return false;
  }

  theQRot.Set(anXYZW[0].RealValue(),
              anXYZW[1].RealValue(),
              anXYZW[2].RealValue(),
              anXYZW[3].RealValue());
  return true;
}

//! Auxiliary class for flipping image upside-down.
class ImageFlipper
{
public:
  //! Empty constructor.
  ImageFlipper()
      : myTmp(NCollection_BaseAllocator::CommonBaseAllocator())
  {
  }

  //! Perform flipping.
  bool FlipY(Image_PixMap& theImage)
  {
    if (theImage.IsEmpty() || theImage.SizeX() == 0 || theImage.SizeY() == 0)
    {
      return false;
    }

    const size_t aRowSize = theImage.SizeRowBytes();
    if (myTmp.Size() < aRowSize && !myTmp.Allocate(aRowSize))
    {
      return false;
    }

    // for odd height middle row should be left as is
    size_t aNbRowsHalf = theImage.SizeY() / 2;
    for (size_t aRowT = 0, aRowB = theImage.SizeY() - 1; aRowT < aNbRowsHalf; ++aRowT, --aRowB)
    {
      uint8_t* aTop = theImage.ChangeRow(aRowT);
      uint8_t* aBot = theImage.ChangeRow(aRowB);
      memcpy(myTmp.ChangeData(), aTop, aRowSize);
      memcpy(aTop, aBot, aRowSize);
      memcpy(aBot, myTmp.Data(), aRowSize);
    }
    return true;
  }

private:
  NCollection_Buffer myTmp;
};

} // namespace

//=================================================================================================
// function : VViewParams
// purpose  : Gets or sets AIS View characteristics
//=================================================================================================
static int VViewParams(Draw_Interpretor& theDi, int theArgsNb, const char** theArgVec)
{
  occ::handle<V3d_View> aView = ViewerTest::CurrentView();
  if (aView.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  bool                  toSetProj     = false;
  bool                  toSetUp       = false;
  bool                  toSetAt       = false;
  bool                  toSetEye      = false;
  bool                  toSetScale    = false;
  bool                  toSetSize     = false;
  bool                  toSetCenter2d = false;
  double                aViewScale    = aView->Scale();
  double                aViewAspect   = aView->Camera()->Aspect();
  double                aViewSize     = 1.0;
  NCollection_Vec2<int> aCenter2d;
  gp_XYZ                aViewProj, aViewUp, aViewAt, aViewEye;
  aView->Proj(aViewProj.ChangeCoord(1), aViewProj.ChangeCoord(2), aViewProj.ChangeCoord(3));
  aView->Up(aViewUp.ChangeCoord(1), aViewUp.ChangeCoord(2), aViewUp.ChangeCoord(3));
  aView->At(aViewAt.ChangeCoord(1), aViewAt.ChangeCoord(2), aViewAt.ChangeCoord(3));
  aView->Eye(aViewEye.ChangeCoord(1), aViewEye.ChangeCoord(2), aViewEye.ChangeCoord(3));
  const NCollection_Mat4<double>& anOrientMat = aView->Camera()->OrientationMatrix();
  const NCollection_Mat4<double>& aProjMat    = aView->Camera()->ProjectionMatrix();
  if (theArgsNb == 1)
  {
    // print all of the available view parameters
    char aText[4096];
    Sprintf(aText,
            "Scale:  %g\n"
            "Aspect: %g\n"
            "Proj:   %12g %12g %12g\n"
            "Up:     %12g %12g %12g\n"
            "At:     %12g %12g %12g\n"
            "Eye:    %12g %12g %12g\n"
            "OrientMat:    %12g %12g %12g %12g\n"
            "              %12g %12g %12g %12g\n"
            "              %12g %12g %12g %12g\n"
            "              %12g %12g %12g %12g\n"
            "ProjMat:      %12g %12g %12g %12g\n"
            "              %12g %12g %12g %12g\n"
            "              %12g %12g %12g %12g\n"
            "              %12g %12g %12g %12g\n",
            aViewScale,
            aViewAspect,
            aViewProj.X(),
            aViewProj.Y(),
            aViewProj.Z(),
            aViewUp.X(),
            aViewUp.Y(),
            aViewUp.Z(),
            aViewAt.X(),
            aViewAt.Y(),
            aViewAt.Z(),
            aViewEye.X(),
            aViewEye.Y(),
            aViewEye.Z(),
            anOrientMat.GetValue(0, 0),
            anOrientMat.GetValue(0, 1),
            anOrientMat.GetValue(0, 2),
            anOrientMat.GetValue(0, 3),
            anOrientMat.GetValue(1, 0),
            anOrientMat.GetValue(1, 1),
            anOrientMat.GetValue(1, 2),
            anOrientMat.GetValue(1, 3),
            anOrientMat.GetValue(2, 0),
            anOrientMat.GetValue(2, 1),
            anOrientMat.GetValue(2, 2),
            anOrientMat.GetValue(2, 3),
            anOrientMat.GetValue(3, 0),
            anOrientMat.GetValue(3, 1),
            anOrientMat.GetValue(3, 2),
            anOrientMat.GetValue(3, 3),
            aProjMat.GetValue(0, 0),
            aProjMat.GetValue(0, 1),
            aProjMat.GetValue(0, 2),
            aProjMat.GetValue(0, 3),
            aProjMat.GetValue(1, 0),
            aProjMat.GetValue(1, 1),
            aProjMat.GetValue(1, 2),
            aProjMat.GetValue(1, 3),
            aProjMat.GetValue(2, 0),
            aProjMat.GetValue(2, 1),
            aProjMat.GetValue(2, 2),
            aProjMat.GetValue(2, 3),
            aProjMat.GetValue(3, 0),
            aProjMat.GetValue(3, 1),
            aProjMat.GetValue(3, 2),
            aProjMat.GetValue(3, 3));
    theDi << aText;
    return 0;
  }

  ViewerTest_AutoUpdater anUpdateTool(ViewerTest::GetAISContext(), aView);
  for (int anArgIter = 1; anArgIter < theArgsNb; ++anArgIter)
  {
    TCollection_AsciiString anArg(theArgVec[anArgIter]);
    anArg.LowerCase();
    if (anUpdateTool.parseRedrawMode(anArg))
    {
      continue;
    }
    else if (anArg == "-cmd" || anArg == "-command" || anArg == "-args")
    {
      char aText[4096];
      Sprintf(aText,
              "-scale %g "
              "-proj %g %g %g "
              "-up %g %g %g "
              "-at %g %g %g\n",
              aViewScale,
              aViewProj.X(),
              aViewProj.Y(),
              aViewProj.Z(),
              aViewUp.X(),
              aViewUp.Y(),
              aViewUp.Z(),
              aViewAt.X(),
              aViewAt.Y(),
              aViewAt.Z());
      theDi << aText;
    }
    else if (anArg == "-scale" || anArg == "-size")
    {
      if (anArgIter + 1 < theArgsNb && *theArgVec[anArgIter + 1] != '-')
      {
        const TCollection_AsciiString aValueArg(theArgVec[anArgIter + 1]);
        if (aValueArg.IsRealValue(true))
        {
          ++anArgIter;
          if (anArg == "-scale")
          {
            toSetScale = true;
            aViewScale = aValueArg.RealValue();
          }
          else if (anArg == "-size")
          {
            toSetSize = true;
            aViewSize = aValueArg.RealValue();
          }
          continue;
        }
      }
      if (anArg == "-scale")
      {
        theDi << "Scale: " << aView->Scale() << "\n";
      }
      else if (anArg == "-size")
      {
        NCollection_Vec2<double> aSizeXY;
        aView->Size(aSizeXY.x(), aSizeXY.y());
        theDi << "Size: " << aSizeXY.x() << " " << aSizeXY.y() << "\n";
      }
    }
    else if (anArg == "-eye" || anArg == "-at" || anArg == "-up" || anArg == "-proj")
    {
      if (anArgIter + 3 < theArgsNb)
      {
        gp_XYZ anXYZ;
        if (parseXYZ(theArgVec + anArgIter + 1, anXYZ))
        {
          anArgIter += 3;
          if (anArg == "-eye")
          {
            toSetEye = true;
            aViewEye = anXYZ;
          }
          else if (anArg == "-at")
          {
            toSetAt = true;
            aViewAt = anXYZ;
          }
          else if (anArg == "-up")
          {
            toSetUp = true;
            aViewUp = anXYZ;
          }
          else if (anArg == "-proj")
          {
            toSetProj = true;
            aViewProj = anXYZ;
          }
          continue;
        }
      }

      if (anArg == "-eye")
      {
        theDi << "Eye:  " << aViewEye.X() << " " << aViewEye.Y() << " " << aViewEye.Z() << "\n";
      }
      else if (anArg == "-at")
      {
        theDi << "At:   " << aViewAt.X() << " " << aViewAt.Y() << " " << aViewAt.Z() << "\n";
      }
      else if (anArg == "-up")
      {
        theDi << "Up:   " << aViewUp.X() << " " << aViewUp.Y() << " " << aViewUp.Z() << "\n";
      }
      else if (anArg == "-proj")
      {
        theDi << "Proj: " << aViewProj.X() << " " << aViewProj.Y() << " " << aViewProj.Z() << "\n";
      }
    }
    else if (anArg == "-center")
    {
      if (anArgIter + 2 < theArgsNb)
      {
        const TCollection_AsciiString anX(theArgVec[anArgIter + 1]);
        const TCollection_AsciiString anY(theArgVec[anArgIter + 2]);
        if (anX.IsIntegerValue() && anY.IsIntegerValue())
        {
          toSetCenter2d = true;
          aCenter2d     = NCollection_Vec2<int>(anX.IntegerValue(), anY.IntegerValue());
        }
      }
    }
    else
    {
      Message::SendFail() << "Syntax error at '" << anArg << "'";
      return 1;
    }
  }

  // change view parameters in proper order
  if (toSetScale)
  {
    aView->SetScale(aViewScale);
  }
  if (toSetSize)
  {
    aView->SetSize(aViewSize);
  }
  if (toSetEye)
  {
    aView->SetEye(aViewEye.X(), aViewEye.Y(), aViewEye.Z());
  }
  if (toSetAt)
  {
    aView->SetAt(aViewAt.X(), aViewAt.Y(), aViewAt.Z());
  }
  if (toSetProj)
  {
    aView->SetProj(aViewProj.X(), aViewProj.Y(), aViewProj.Z());
  }
  if (toSetUp)
  {
    aView->SetUp(aViewUp.X(), aViewUp.Y(), aViewUp.Z());
  }
  if (toSetCenter2d)
  {
    aView->SetCenter(aCenter2d.x(), aCenter2d.y());
  }

  return 0;
}

//=================================================================================================

static int V2DMode(Draw_Interpretor&, int theArgsNb, const char** theArgVec)
{
  bool                            is2dMode = true;
  occ::handle<ViewerTest_V3dView> aV3dView =
    occ::down_cast<ViewerTest_V3dView>(ViewerTest::CurrentView());
  if (aV3dView.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }
  for (int anArgIt = 1; anArgIt < theArgsNb; ++anArgIt)
  {
    const TCollection_AsciiString anArg     = theArgVec[anArgIt];
    TCollection_AsciiString       anArgCase = anArg;
    anArgCase.LowerCase();
    if (anArgIt + 1 < theArgsNb && anArgCase == "-name")
    {
      ViewerTest_Names               aViewNames(theArgVec[++anArgIt]);
      const TCollection_AsciiString& aViewName = aViewNames.GetViewName();
      if (!ViewerTest_myViews.IsBound1(aViewName))
      {
        Message::SendFail() << "Syntax error: unknown view '" << theArgVec[anArgIt - 1] << "'";
        return 1;
      }
      aV3dView = occ::down_cast<ViewerTest_V3dView>(ViewerTest_myViews.Find1(aViewName));
    }
    else if (anArgCase == "-mode")
    {
      if (anArgIt + 1 < theArgsNb && Draw::ParseOnOff(theArgVec[anArgIt + 1], is2dMode))
      {
        ++anArgIt;
      }
    }
    else if (Draw::ParseOnOff(theArgVec[anArgIt], is2dMode))
    {
      //
    }
    else
    {
      Message::SendFail() << "Syntax error: unknown argument " << anArg;
      return 1;
    }
  }

  aV3dView->SetView2DMode(is2dMode);
  return 0;
}

//=================================================================================================

static int VAnimation(Draw_Interpretor& theDI, int theArgNb, const char** theArgVec)
{
  occ::handle<AIS_InteractiveContext> aCtx = ViewerTest::GetAISContext();
  if (theArgNb < 2)
  {
    for (NCollection_DataMap<TCollection_AsciiString, occ::handle<AIS_Animation>>::Iterator
           anAnimIter(ViewerTest_AnimationTimelineMap);
         anAnimIter.More();
         anAnimIter.Next())
    {
      theDI << anAnimIter.Key() << " " << anAnimIter.Value()->Duration() << " sec\n";
    }
    return 0;
  }
  if (aCtx.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  int                     anArgIter = 1;
  TCollection_AsciiString aNameArg(theArgVec[anArgIter++]);
  if (aNameArg.IsEmpty())
  {
    Message::SendFail("Syntax error: animation name is not defined");
    return 1;
  }

  TCollection_AsciiString aNameArgLower = aNameArg;
  aNameArgLower.LowerCase();
  if (aNameArgLower == "-reset" || aNameArgLower == "-clear")
  {
    ViewerTest_AnimationTimelineMap.Clear();
    return 0;
  }
  else if (aNameArg.Value(1) == '-')
  {
    Message::SendFail() << "Syntax error: invalid animation name '" << aNameArg << "'";
    return 1;
  }

  const char* aNameSplitter = "/";
  int         aSplitPos     = aNameArg.Search(aNameSplitter);
  if (aSplitPos == -1)
  {
    aNameSplitter = ".";
    aSplitPos     = aNameArg.Search(aNameSplitter);
  }

  // find existing or create a new animation by specified name within syntax "parent.child".
  occ::handle<AIS_Animation> aRootAnimation, aParentAnimation, anAnimation;
  for (; !aNameArg.IsEmpty();)
  {
    TCollection_AsciiString aNameParent;
    if (aSplitPos != -1)
    {
      if (aSplitPos == aNameArg.Length())
      {
        Message::SendFail("Syntax error: animation name is not defined");
        return 1;
      }

      aNameParent = aNameArg.SubString(1, aSplitPos - 1);
      aNameArg    = aNameArg.SubString(aSplitPos + 1, aNameArg.Length());

      aSplitPos = aNameArg.Search(aNameSplitter);
    }
    else
    {
      aNameParent = aNameArg;
      aNameArg.Clear();
    }

    if (anAnimation.IsNull())
    {
      if (!ViewerTest_AnimationTimelineMap.Find(aNameParent, anAnimation))
      {
        anAnimation = new AIS_Animation(aNameParent);
        ViewerTest_AnimationTimelineMap.Bind(aNameParent, anAnimation);
      }
      aRootAnimation = anAnimation;
    }
    else
    {
      aParentAnimation = anAnimation;
      anAnimation      = aParentAnimation->Find(aNameParent);
      if (anAnimation.IsNull())
      {
        anAnimation = new AIS_Animation(aNameParent);
        aParentAnimation->Add(anAnimation);
      }
    }
  }
  if (anAnimation.IsNull())
  {
    Message::SendFail() << "Syntax error: wrong number of arguments";
    return 1;
  }

  if (anArgIter >= theArgNb)
  {
    // just print the list of children
    for (NCollection_Sequence<occ::handle<AIS_Animation>>::Iterator anAnimIter(
           anAnimation->Children());
         anAnimIter.More();
         anAnimIter.Next())
    {
      theDI << anAnimIter.Value()->Name() << " " << anAnimIter.Value()->Duration() << " sec\n";
    }
    return 0;
  }

  // animation parameters
  bool   toPlay             = false;
  double aPlaySpeed         = 1.0;
  double aPlayStartTime     = anAnimation->StartPts();
  double aPlayDuration      = anAnimation->Duration();
  bool   isFreeCamera       = false;
  bool   toPauseOnClick     = true;
  bool   isLockLoop         = false;
  bool   toPrintElapsedTime = false;

  // video recording parameters
  TCollection_AsciiString aRecFile;
  Image_VideoParams       aRecParams;

  occ::handle<V3d_View> aView = ViewerTest::CurrentView();
  for (; anArgIter < theArgNb; ++anArgIter)
  {
    TCollection_AsciiString anArg(theArgVec[anArgIter]);
    anArg.LowerCase();
    // general options
    if (anArg == "-reset" || anArg == "-clear")
    {
      anAnimation->Clear();
    }
    else if (anArg == "-remove" || anArg == "-del" || anArg == "-delete")
    {
      if (aParentAnimation.IsNull())
      {
        ViewerTest_AnimationTimelineMap.UnBind(anAnimation->Name());
      }
      else
      {
        aParentAnimation->Remove(anAnimation);
      }
    }
    // playback options
    else if (anArg == "-play")
    {
      toPlay = true;
      if (++anArgIter < theArgNb)
      {
        if (*theArgVec[anArgIter] == '-')
        {
          --anArgIter;
          continue;
        }
        aPlayStartTime = Draw::Atof(theArgVec[anArgIter]);

        if (++anArgIter < theArgNb)
        {
          if (*theArgVec[anArgIter] == '-')
          {
            --anArgIter;
            continue;
          }
          aPlayDuration = Draw::Atof(theArgVec[anArgIter]);
        }
      }
    }
    else if (anArg == "-elapsedtime" || anArg == "-elapsed")
    {
      toPrintElapsedTime = true;
    }
    else if (anArg == "-resume")
    {
      toPlay         = true;
      aPlayStartTime = anAnimation->ElapsedTime();
      if (++anArgIter < theArgNb)
      {
        if (*theArgVec[anArgIter] == '-')
        {
          --anArgIter;
          continue;
        }

        aPlayDuration = Draw::Atof(theArgVec[anArgIter]);
      }
    }
    else if (anArg == "-pause")
    {
      anAnimation->Pause();
    }
    else if (anArg == "-stop")
    {
      anAnimation->Stop();
    }
    else if (anArg == "-playspeed" || anArg == "-speed")
    {
      if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at " << anArg << "";
        return 1;
      }
      aPlaySpeed = Draw::Atof(theArgVec[anArgIter]);
    }
    else if (anArg == "-lock" || anArg == "-lockloop" || anArg == "-playlockloop")
    {
      isLockLoop = Draw::ParseOnOffIterator(theArgNb, theArgVec, anArgIter);
    }
    else if (anArg == "-freecamera" || anArg == "-nofreecamera" || anArg == "-playfreecamera"
             || anArg == "-noplayfreecamera" || anArg == "-freelook" || anArg == "-nofreelook")
    {
      isFreeCamera = Draw::ParseOnOffNoIterator(theArgNb, theArgVec, anArgIter);
    }
    else if (anArg == "-pauseonclick" || anArg == "-nopauseonclick" || anArg == "-nopause")
    {
      toPauseOnClick = Draw::ParseOnOffNoIterator(theArgNb, theArgVec, anArgIter);
    }
    // video recodring options
    else if (anArg == "-rec" || anArg == "-record")
    {
      if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at " << anArg;
        return 1;
      }

      aRecFile = theArgVec[anArgIter];
      if (aRecParams.FpsNum <= 0)
      {
        aRecParams.FpsNum = 24;
      }

      if (anArgIter + 2 < theArgNb && *theArgVec[anArgIter + 1] != '-'
          && *theArgVec[anArgIter + 2] != '-')
      {
        TCollection_AsciiString aWidthArg(theArgVec[anArgIter + 1]);
        TCollection_AsciiString aHeightArg(theArgVec[anArgIter + 2]);
        if (aWidthArg.IsIntegerValue() && aHeightArg.IsIntegerValue())
        {
          aRecParams.Width  = aWidthArg.IntegerValue();
          aRecParams.Height = aHeightArg.IntegerValue();
          anArgIter += 2;
        }
      }
    }
    else if (anArg == "-fps")
    {
      if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at " << anArg;
        return 1;
      }

      TCollection_AsciiString aFpsArg(theArgVec[anArgIter]);
      int                     aSplitIndex = aFpsArg.FirstLocationInSet("/", 1, aFpsArg.Length());
      if (aSplitIndex == 0)
      {
        aRecParams.FpsNum = aFpsArg.IntegerValue();
      }
      else
      {
        const TCollection_AsciiString aDenStr = aFpsArg.Split(aSplitIndex);
        aFpsArg.Split(aFpsArg.Length() - 1);
        const TCollection_AsciiString aNumStr = aFpsArg;
        aRecParams.FpsNum                     = aNumStr.IntegerValue();
        aRecParams.FpsDen                     = aDenStr.IntegerValue();
        if (aRecParams.FpsDen < 1)
        {
          Message::SendFail() << "Syntax error at " << anArg;
          return 1;
        }
      }
    }
    else if (anArg == "-format")
    {
      if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at " << anArg;
        return 1;
      }
      aRecParams.Format = theArgVec[anArgIter];
    }
    else if (anArg == "-pix_fmt" || anArg == "-pixfmt" || anArg == "-pixelformat")
    {
      if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at " << anArg;
        return 1;
      }
      aRecParams.PixelFormat = theArgVec[anArgIter];
    }
    else if (anArg == "-codec" || anArg == "-vcodec" || anArg == "-videocodec")
    {
      if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at " << anArg;
        return 1;
      }
      aRecParams.VideoCodec = theArgVec[anArgIter];
    }
    else if (anArg == "-crf" || anArg == "-preset" || anArg == "-qp")
    {
      const TCollection_AsciiString aParamName = anArg.SubString(2, anArg.Length());
      if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at " << anArg;
        return 1;
      }

      aRecParams.VideoCodecParams.Bind(aParamName, theArgVec[anArgIter]);
    }
    // animation definition options
    else if (anArg == "-start" || anArg == "-starttime" || anArg == "-startpts")
    {
      if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at " << anArg;
        return 1;
      }

      anAnimation->SetStartPts(Draw::Atof(theArgVec[anArgIter]));
      aRootAnimation->UpdateTotalDuration();
    }
    else if (anArg == "-end" || anArg == "-endtime" || anArg == "-endpts")
    {
      if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at " << anArg;
        return 1;
      }

      anAnimation->SetOwnDuration(Draw::Atof(theArgVec[anArgIter]) - anAnimation->StartPts());
      aRootAnimation->UpdateTotalDuration();
    }
    else if (anArg == "-dur" || anArg == "-duration")
    {
      if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at " << anArg;
        return 1;
      }

      anAnimation->SetOwnDuration(Draw::Atof(theArgVec[anArgIter]));
      aRootAnimation->UpdateTotalDuration();
    }
    else if (anArg == "-command" || anArg == "-cmd" || anArg == "-invoke" || anArg == "-eval"
             || anArg == "-proc")
    {
      if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at " << anArg;
        return 1;
      }

      occ::handle<ViewerTest_AnimationProc> aCmdAnimation =
        new ViewerTest_AnimationProc(anAnimation->Name(), &theDI, theArgVec[anArgIter]);
      replaceAnimation(aParentAnimation, anAnimation, aCmdAnimation);
    }
    else if (anArg == "-objecttrsf" || anArg == "-objectransformation"
             || anArg == "-objtransformation" || anArg == "-objtrsf" || anArg == "-object"
             || anArg == "-obj")
    {
      if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at " << anArg;
        return 1;
      }

      TCollection_AsciiString anObjName(theArgVec[anArgIter]);
      const NCollection_DoubleMap<occ::handle<AIS_InteractiveObject>, TCollection_AsciiString>&
                                         aMapOfAIS = GetMapOfAIS();
      occ::handle<AIS_InteractiveObject> anObject;
      if (!aMapOfAIS.Find2(anObjName, anObject))
      {
        Message::SendFail() << "Syntax error: wrong object name at " << anArg;
        return 1;
      }

      gp_Trsf       aTrsfs[2] = {anObject->LocalTransformation(), anObject->LocalTransformation()};
      gp_Quaternion aRotQuats[2] = {aTrsfs[0].GetRotation(), aTrsfs[1].GetRotation()};
      gp_XYZ        aLocPnts[2]  = {aTrsfs[0].TranslationPart(), aTrsfs[1].TranslationPart()};
      double        aScales[2]   = {aTrsfs[0].ScaleFactor(), aTrsfs[1].ScaleFactor()};
      bool          isTrsfSet    = false;

      gp_Ax1 anAxis;
      double anAngles[2]       = {0.0, 0.0};
      bool   isAxisRotationSet = false;

      int aTrsfArgIter = anArgIter + 1;
      for (; aTrsfArgIter < theArgNb; ++aTrsfArgIter)
      {
        TCollection_AsciiString aTrsfArg(theArgVec[aTrsfArgIter]);
        aTrsfArg.LowerCase();
        const int anIndex = aTrsfArg.EndsWith("1") ? 0 : 1;
        if (aTrsfArg.StartsWith("-rotation") || aTrsfArg.StartsWith("-rot"))
        {
          isTrsfSet = true;
          if (aTrsfArgIter + 4 >= theArgNb
              || !parseQuaternion(theArgVec + aTrsfArgIter + 1, aRotQuats[anIndex]))
          {
            Message::SendFail() << "Syntax error at " << aTrsfArg;
            return 1;
          }
          aTrsfArgIter += 4;
        }
        else if (aTrsfArg.StartsWith("-location") || aTrsfArg.StartsWith("-loc"))
        {
          isTrsfSet = true;
          if (aTrsfArgIter + 3 >= theArgNb
              || !parseXYZ(theArgVec + aTrsfArgIter + 1, aLocPnts[anIndex]))
          {
            Message::SendFail() << "Syntax error at " << aTrsfArg;
            return 1;
          }
          aTrsfArgIter += 3;
        }
        else if (aTrsfArg.StartsWith("-scale"))
        {
          isTrsfSet = true;
          if (++aTrsfArgIter >= theArgNb)
          {
            Message::SendFail() << "Syntax error at " << aTrsfArg;
            return 1;
          }

          const TCollection_AsciiString aScaleStr(theArgVec[aTrsfArgIter]);
          if (!aScaleStr.IsRealValue(true))
          {
            Message::SendFail() << "Syntax error at " << aTrsfArg;
            return 1;
          }
          aScales[anIndex] = aScaleStr.RealValue();
        }
        else if (aTrsfArg == "-axis")
        {
          isAxisRotationSet = true;
          gp_XYZ anOrigin, aDirection;
          if (aTrsfArgIter + 6 >= theArgNb || !parseXYZ(theArgVec + aTrsfArgIter + 1, anOrigin)
              || !parseXYZ(theArgVec + aTrsfArgIter + 4, aDirection))
          {
            Message::SendFail() << "Syntax error at " << aTrsfArg;
            return 1;
          }
          anAxis.SetLocation(anOrigin);
          anAxis.SetDirection(aDirection);
          aTrsfArgIter += 6;
        }
        else if (aTrsfArg.StartsWith("-ang"))
        {
          isAxisRotationSet = true;
          if (++aTrsfArgIter >= theArgNb)
          {
            Message::SendFail() << "Syntax error at " << aTrsfArg;
            return 1;
          }

          const TCollection_AsciiString anAngleStr(theArgVec[aTrsfArgIter]);
          if (!anAngleStr.IsRealValue(true))
          {
            Message::SendFail() << "Syntax error at " << aTrsfArg;
            return 1;
          }
          anAngles[anIndex] = anAngleStr.RealValue();
        }
        else
        {
          anArgIter = aTrsfArgIter - 1;
          break;
        }
      }
      if (!isTrsfSet && !isAxisRotationSet)
      {
        Message::SendFail() << "Syntax error at " << anArg;
        return 1;
      }
      else if (aTrsfArgIter >= theArgNb)
      {
        anArgIter = theArgNb;
      }
      occ::handle<AIS_BaseAnimationObject> anObjAnimation;
      if (isTrsfSet)
      {
        aTrsfs[0].SetRotation(aRotQuats[0]);
        aTrsfs[1].SetRotation(aRotQuats[1]);
        aTrsfs[0].SetTranslationPart(aLocPnts[0]);
        aTrsfs[1].SetTranslationPart(aLocPnts[1]);
        aTrsfs[0].SetScaleFactor(aScales[0]);
        aTrsfs[1].SetScaleFactor(aScales[1]);

        anObjAnimation =
          new AIS_AnimationObject(anAnimation->Name(), aCtx, anObject, aTrsfs[0], aTrsfs[1]);
      }
      else
      {
        anObjAnimation = new AIS_AnimationAxisRotation(anAnimation->Name(),
                                                       aCtx,
                                                       anObject,
                                                       anAxis,
                                                       anAngles[0] * (M_PI / 180.0),
                                                       anAngles[1] * (M_PI / 180.0));
      }
      replaceAnimation(aParentAnimation, anAnimation, anObjAnimation);
    }
    else if (anArg == "-viewtrsf" || anArg == "-view")
    {
      occ::handle<AIS_AnimationCamera> aCamAnimation =
        occ::down_cast<AIS_AnimationCamera>(anAnimation);
      if (aCamAnimation.IsNull())
      {
        aCamAnimation = new AIS_AnimationCamera(anAnimation->Name(), aView);
        replaceAnimation(aParentAnimation, anAnimation, aCamAnimation);
      }

      occ::handle<Graphic3d_Camera> aCams[2] = {
        new Graphic3d_Camera(aCamAnimation->View()->Camera()),
        new Graphic3d_Camera(aCamAnimation->View()->Camera())};

      bool isTrsfSet    = false;
      int  aViewArgIter = anArgIter + 1;
      for (; aViewArgIter < theArgNb; ++aViewArgIter)
      {
        TCollection_AsciiString aViewArg(theArgVec[aViewArgIter]);
        aViewArg.LowerCase();
        const int anIndex = aViewArg.EndsWith("1") ? 0 : 1;
        if (aViewArg.StartsWith("-scale"))
        {
          isTrsfSet = true;
          if (++aViewArgIter >= theArgNb)
          {
            Message::SendFail() << "Syntax error at " << anArg;
            return 1;
          }

          const TCollection_AsciiString aScaleStr(theArgVec[aViewArgIter]);
          if (!aScaleStr.IsRealValue(true))
          {
            Message::SendFail() << "Syntax error at " << aViewArg;
            return 1;
          }
          double aScale = aScaleStr.RealValue();
          aScale        = aCamAnimation->View()->DefaultCamera()->Scale() / aScale;
          aCams[anIndex]->SetScale(aScale);
        }
        else if (aViewArg.StartsWith("-eye") || aViewArg.StartsWith("-center")
                 || aViewArg.StartsWith("-at") || aViewArg.StartsWith("-up"))
        {
          isTrsfSet = true;
          gp_XYZ anXYZ;
          if (aViewArgIter + 3 >= theArgNb || !parseXYZ(theArgVec + aViewArgIter + 1, anXYZ))
          {
            Message::SendFail() << "Syntax error at " << aViewArg;
            return 1;
          }
          aViewArgIter += 3;

          if (aViewArg.StartsWith("-eye"))
          {
            aCams[anIndex]->SetEye(anXYZ);
          }
          else if (aViewArg.StartsWith("-center") || aViewArg.StartsWith("-at"))
          {
            aCams[anIndex]->SetCenter(anXYZ);
          }
          else if (aViewArg.StartsWith("-up"))
          {
            aCams[anIndex]->SetUp(anXYZ);
          }
        }
        else
        {
          anArgIter = aViewArgIter - 1;
          break;
        }
      }
      if (!isTrsfSet)
      {
        Message::SendFail() << "Syntax error at " << anArg;
        return 1;
      }
      else if (aViewArgIter >= theArgNb)
      {
        anArgIter = theArgNb;
      }

      aCamAnimation->SetCameraStart(aCams[0]);
      aCamAnimation->SetCameraEnd(aCams[1]);
    }
    else
    {
      Message::SendFail() << "Syntax error at " << anArg;
      return 1;
    }
  }

  if (anAnimation.IsNull() || anAnimation->IsStopped())
  {
    ViewerTest::CurrentEventManager()->AbortViewAnimation();
    ViewerTest::CurrentEventManager()->SetObjectsAnimation(occ::handle<AIS_Animation>());
  }

  if (toPrintElapsedTime)
  {
    theDI << "Elapsed Time: " << anAnimation->ElapsedTime() << " s\n";
  }

  if (!toPlay && aRecFile.IsEmpty())
  {
    return 0;
  }

  // Start animation timeline and process frame updating.
  if (aRecParams.FpsNum <= 0 && !isLockLoop)
  {
    occ::handle<ViewerTest_AnimationHolder> aHolder =
      new ViewerTest_AnimationHolder(anAnimation, aView, isFreeCamera);
    aHolder->StartTimer(aPlayStartTime, aPlaySpeed, true, aPlayDuration <= 0.0);
    ViewerTest::CurrentEventManager()->SetPauseObjectsAnimation(toPauseOnClick);
    ViewerTest::CurrentEventManager()->SetObjectsAnimation(aHolder);
    ViewerTest::CurrentEventManager()->ProcessExpose();
    return 0;
  }

  // Perform video recording
  const bool   wasImmediateUpdate = aView->SetImmediateUpdate(false);
  const double anUpperPts         = aPlayStartTime + aPlayDuration;
  anAnimation->StartTimer(aPlayStartTime, aPlaySpeed, true, aPlayDuration <= 0.0);

  OSD_Timer aPerfTimer;
  aPerfTimer.Start();

  occ::handle<Image_VideoRecorder>    aRecorder;
  ImageFlipper                        aFlipper;
  occ::handle<Draw_ProgressIndicator> aProgress;
  if (!aRecFile.IsEmpty())
  {
    if (aRecParams.Width <= 0 || aRecParams.Height <= 0)
    {
      aView->Window()->Size(aRecParams.Width, aRecParams.Height);
    }

    aRecorder = new Image_VideoRecorder();
    if (!aRecorder->Open(aRecFile.ToCString(), aRecParams))
    {
      Message::SendFail("Error: failed to open video file for recording");
      return 0;
    }

    aProgress = new Draw_ProgressIndicator(theDI, 1);
  }

  // Manage frame-rated animation here
  double                aPts      = aPlayStartTime;
  int64_t               aNbFrames = 0;
  Message_ProgressScope aPS(Message_ProgressIndicator::Start(aProgress),
                            "Video recording, sec",
                            std::max(1, int(aPlayDuration / aPlaySpeed)));
  int                   aSecondsProgress = 0;
  for (; aPts <= anUpperPts && aPS.More();)
  {
    double aRecPts = 0.0;
    if (aRecParams.FpsNum > 0)
    {
      aRecPts =
        aPlaySpeed * ((double(aRecParams.FpsDen) / double(aRecParams.FpsNum)) * double(aNbFrames));
    }
    else
    {
      aRecPts = aPlaySpeed * aPerfTimer.ElapsedTime();
    }

    aPts = aPlayStartTime + aRecPts;
    ++aNbFrames;
    if (!anAnimation->Update(aPts))
    {
      break;
    }

    if (!aRecorder.IsNull())
    {
      V3d_ImageDumpOptions aDumpParams;
      aDumpParams.Width          = aRecParams.Width;
      aDumpParams.Height         = aRecParams.Height;
      aDumpParams.BufferType     = Graphic3d_BT_RGBA;
      aDumpParams.StereoOptions  = V3d_SDO_MONO;
      aDumpParams.ToAdjustAspect = true;
      if (!aView->ToPixMap(aRecorder->ChangeFrame(), aDumpParams))
      {
        Message::SendFail("Error: view dump is failed");
        return 0;
      }
      aFlipper.FlipY(aRecorder->ChangeFrame());
      if (!aRecorder->PushFrame())
      {
        return 0;
      }
    }
    else
    {
      aView->Redraw();
    }

    while (aSecondsProgress < int(aRecPts / aPlaySpeed))
    {
      aPS.Next();
      ++aSecondsProgress;
    }
  }

  aPerfTimer.Stop();
  anAnimation->Stop();
  const double aRecFps = double(aNbFrames) / aPerfTimer.ElapsedTime();
  theDI << "Average FPS: " << aRecFps << "\n"
        << "Nb. Frames: " << double(aNbFrames);

  aView->Redraw();
  aView->SetImmediateUpdate(wasImmediateUpdate);
  return 0;
}

//=======================================================================
// function : VChangeSelected
// purpose  : Adds the shape to selection or remove one from it
//=======================================================================
static int VChangeSelected(Draw_Interpretor& di, int argc, const char** argv)
{
  if (argc != 2)
  {
    di << "Usage : " << argv[0] << " shape \n";
    return 1;
  }
  // get AIS_Shape:
  TCollection_AsciiString            aName(argv[1]);
  occ::handle<AIS_InteractiveObject> anAISObject;
  if (!GetMapOfAIS().Find2(aName, anAISObject) || anAISObject.IsNull())
  {
    di << "Use 'vdisplay' before";
    return 1;
  }

  ViewerTest::GetAISContext()->AddOrRemoveSelected(anAISObject, true);
  return 0;
}

//=======================================================================
// function : VNbSelected
// purpose  : Returns number of selected objects
//=======================================================================
static int VNbSelected(Draw_Interpretor& di, int argc, const char** argv)
{
  if (argc != 1)
  {
    di << "Usage : " << argv[0] << "\n";
    return 1;
  }
  occ::handle<AIS_InteractiveContext> aContext = ViewerTest::GetAISContext();
  if (aContext.IsNull())
  {
    di << "use 'vinit' command before " << argv[0] << "\n";
    return 1;
  }
  di << aContext->NbSelected() << "\n";
  return 0;
}

//=================================================================================================

static int VSetViewSize(Draw_Interpretor& di, int argc, const char** argv)
{
  occ::handle<AIS_InteractiveContext> aContext = ViewerTest::GetAISContext();
  if (aContext.IsNull())
  {
    di << "use 'vinit' command before " << argv[0] << "\n";
    return 1;
  }
  if (argc != 2)
  {
    di << "Usage : " << argv[0] << " Size\n";
    return 1;
  }
  double aSize = Draw::Atof(argv[1]);
  if (aSize <= 0.)
  {
    di << "Bad Size value  : " << aSize << "\n";
    return 1;
  }

  occ::handle<V3d_View> aView = ViewerTest::CurrentView();
  aView->SetSize(aSize);
  return 0;
}

//=================================================================================================

static int VMoveView(Draw_Interpretor& di, int argc, const char** argv)
{
  occ::handle<AIS_InteractiveContext> aContext = ViewerTest::GetAISContext();
  if (aContext.IsNull())
  {
    di << "use 'vinit' command before " << argv[0] << "\n";
    return 1;
  }
  if (argc < 4 || argc > 5)
  {
    di << "Usage : " << argv[0] << " Dx Dy Dz [Start = 1|0]\n";
    return 1;
  }
  double Dx     = Draw::Atof(argv[1]);
  double Dy     = Draw::Atof(argv[2]);
  double Dz     = Draw::Atof(argv[3]);
  bool   aStart = true;
  if (argc == 5)
  {
    aStart = (Draw::Atoi(argv[4]) > 0);
  }

  occ::handle<V3d_View> aView = ViewerTest::CurrentView();
  aView->Move(Dx, Dy, Dz, aStart);
  return 0;
}

//=================================================================================================

static int VTranslateView(Draw_Interpretor& di, int argc, const char** argv)
{
  occ::handle<AIS_InteractiveContext> aContext = ViewerTest::GetAISContext();
  if (aContext.IsNull())
  {
    di << "use 'vinit' command before " << argv[0] << "\n";
    return 1;
  }
  if (argc < 4 || argc > 5)
  {
    di << "Usage : " << argv[0] << " Dx Dy Dz [Start = 1|0]\n";
    return 1;
  }
  double Dx     = Draw::Atof(argv[1]);
  double Dy     = Draw::Atof(argv[2]);
  double Dz     = Draw::Atof(argv[3]);
  bool   aStart = true;
  if (argc == 5)
  {
    aStart = (Draw::Atoi(argv[4]) > 0);
  }

  occ::handle<V3d_View> aView = ViewerTest::CurrentView();
  aView->Translate(Dx, Dy, Dz, aStart);
  return 0;
}

//=================================================================================================

static int VTurnView(Draw_Interpretor& di, int argc, const char** argv)
{
  occ::handle<AIS_InteractiveContext> aContext = ViewerTest::GetAISContext();
  if (aContext.IsNull())
  {
    di << "use 'vinit' command before " << argv[0] << "\n";
    return 1;
  }
  if (argc < 4 || argc > 5)
  {
    di << "Usage : " << argv[0] << " Ax Ay Az [Start = 1|0]\n";
    return 1;
  }
  double Ax     = Draw::Atof(argv[1]);
  double Ay     = Draw::Atof(argv[2]);
  double Az     = Draw::Atof(argv[3]);
  bool   aStart = true;
  if (argc == 5)
  {
    aStart = (Draw::Atoi(argv[4]) > 0);
  }

  occ::handle<V3d_View> aView = ViewerTest::CurrentView();
  aView->Turn(Ax, Ay, Az, aStart);
  return 0;
}

//==============================================================================
// function : VTextureEnv
// purpose  : ENables or disables environment mapping
//==============================================================================
class OCC_TextureEnv : public Graphic3d_TextureEnv
{
public:
  OCC_TextureEnv(const char* const FileName);
  OCC_TextureEnv(const Graphic3d_NameOfTextureEnv aName);
  void SetTextureParameters(const bool                          theRepeatFlag,
                            const bool                          theModulateFlag,
                            const Graphic3d_TypeOfTextureFilter theFilter,
                            const float                         theXScale,
                            const float                         theYScale,
                            const float                         theXShift,
                            const float                         theYShift,
                            const float                         theAngle);
  DEFINE_STANDARD_RTTI_INLINE(OCC_TextureEnv, Graphic3d_TextureEnv)
};

OCC_TextureEnv::OCC_TextureEnv(const char* const theFileName)
    : Graphic3d_TextureEnv(theFileName)
{
}

OCC_TextureEnv::OCC_TextureEnv(const Graphic3d_NameOfTextureEnv theTexId)
    : Graphic3d_TextureEnv(theTexId)
{
}

void OCC_TextureEnv::SetTextureParameters(const bool                          theRepeatFlag,
                                          const bool                          theModulateFlag,
                                          const Graphic3d_TypeOfTextureFilter theFilter,
                                          const float                         theXScale,
                                          const float                         theYScale,
                                          const float                         theXShift,
                                          const float                         theYShift,
                                          const float                         theAngle)
{
  myParams->SetRepeat(theRepeatFlag);
  myParams->SetModulate(theModulateFlag);
  myParams->SetFilter(theFilter);
  myParams->SetScale(NCollection_Vec2<float>(theXScale, theYScale));
  myParams->SetTranslation(NCollection_Vec2<float>(theXShift, theYShift));
  myParams->SetRotation(theAngle);
}

static int VTextureEnv(Draw_Interpretor& /*theDI*/, int theArgNb, const char** theArgVec)
{
  // get the active view
  occ::handle<V3d_View> aView = ViewerTest::CurrentView();
  if (aView.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  // Checking the input arguments
  bool anEnableFlag = false;
  bool isOk         = theArgNb >= 2;
  if (isOk)
  {
    TCollection_AsciiString anEnableOpt(theArgVec[1]);
    anEnableFlag = anEnableOpt.IsEqual("on");
    isOk         = anEnableFlag || anEnableOpt.IsEqual("off");
  }
  if (anEnableFlag)
  {
    isOk = (theArgNb == 3 || theArgNb == 11);
    if (isOk)
    {
      TCollection_AsciiString aTextureOpt(theArgVec[2]);
      isOk = (!aTextureOpt.IsIntegerValue()
              || (aTextureOpt.IntegerValue() >= 0
                  && aTextureOpt.IntegerValue() < Graphic3d_NOT_ENV_UNKNOWN));

      if (isOk && theArgNb == 11)
      {
        TCollection_AsciiString aRepeatOpt(theArgVec[3]), aModulateOpt(theArgVec[4]),
          aFilterOpt(theArgVec[5]), aSScaleOpt(theArgVec[6]), aTScaleOpt(theArgVec[7]),
          aSTransOpt(theArgVec[8]), aTTransOpt(theArgVec[9]), anAngleOpt(theArgVec[10]);
        isOk = ((aRepeatOpt.IsEqual("repeat") || aRepeatOpt.IsEqual("clamp"))
                && (aModulateOpt.IsEqual("modulate") || aModulateOpt.IsEqual("decal"))
                && (aFilterOpt.IsEqual("nearest") || aFilterOpt.IsEqual("bilinear")
                    || aFilterOpt.IsEqual("trilinear"))
                && aSScaleOpt.IsRealValue(true) && aTScaleOpt.IsRealValue(true)
                && aSTransOpt.IsRealValue(true) && aTTransOpt.IsRealValue(true)
                && anAngleOpt.IsRealValue(true));
      }
    }
  }

  if (!isOk)
  {
    Message::SendFail() << "Usage:\n"
                        << theArgVec[0] << " off\n"
                        << theArgVec[0]
                        << " on {index_of_std_texture(0..7)|texture_file_name} [{clamp|repeat} "
                           "{decal|modulate} {nearest|bilinear|trilinear} scale_s scale_t "
                           "translation_s translation_t rotation_degrees]";
    return 1;
  }

  if (anEnableFlag)
  {
    TCollection_AsciiString     aTextureOpt(theArgVec[2]);
    occ::handle<OCC_TextureEnv> aTexEnv =
      aTextureOpt.IsIntegerValue()
        ? new OCC_TextureEnv((Graphic3d_NameOfTextureEnv)aTextureOpt.IntegerValue())
        : new OCC_TextureEnv(theArgVec[2]);

    if (theArgNb == 11)
    {
      TCollection_AsciiString aRepeatOpt(theArgVec[3]), aModulateOpt(theArgVec[4]),
        aFilterOpt(theArgVec[5]);
      aTexEnv->SetTextureParameters(aRepeatOpt.IsEqual("repeat"),
                                    aModulateOpt.IsEqual("modulate"),
                                    aFilterOpt.IsEqual("nearest")    ? Graphic3d_TOTF_NEAREST
                                    : aFilterOpt.IsEqual("bilinear") ? Graphic3d_TOTF_BILINEAR
                                                                     : Graphic3d_TOTF_TRILINEAR,
                                    (float)Draw::Atof(theArgVec[6]),
                                    (float)Draw::Atof(theArgVec[7]),
                                    (float)Draw::Atof(theArgVec[8]),
                                    (float)Draw::Atof(theArgVec[9]),
                                    (float)Draw::Atof(theArgVec[10]));
    }
    aView->SetTextureEnv(aTexEnv);
  }
  else // Disabling environment mapping
  {
    occ::handle<Graphic3d_TextureEnv> aTexture;
    aView->SetTextureEnv(aTexture); // Passing null handle to clear the texture data
  }

  aView->Redraw();
  return 0;
}

namespace
{
typedef NCollection_DataMap<TCollection_AsciiString, occ::handle<Graphic3d_ClipPlane>> MapOfPlanes;

//! Remove registered clipping plane from all views and objects.
static void removePlane(MapOfPlanes& theRegPlanes, const TCollection_AsciiString& theName)
{
  occ::handle<Graphic3d_ClipPlane> aClipPlane;
  if (!theRegPlanes.Find(theName, aClipPlane))
  {
    Message::SendWarning("Warning: no such plane");
    return;
  }

  theRegPlanes.UnBind(theName);
  for (NCollection_DoubleMap<occ::handle<AIS_InteractiveObject>, TCollection_AsciiString>::Iterator
         anIObjIt(GetMapOfAIS());
       anIObjIt.More();
       anIObjIt.Next())
  {
    const occ::handle<AIS_InteractiveObject>& aPrs = anIObjIt.Key1();
    aPrs->RemoveClipPlane(aClipPlane);
  }

  for (ViewerTest_ViewerCommandsViewMap::Iterator aViewIt(ViewerTest_myViews); aViewIt.More();
       aViewIt.Next())
  {
    const occ::handle<V3d_View>& aView = aViewIt.Key2();
    aView->RemoveClipPlane(aClipPlane);
  }

  ViewerTest::RedrawAllViews();
}
} // namespace

//=================================================================================================

static int VClipPlane(Draw_Interpretor& theDi, int theArgsNb, const char** theArgVec)
{
  // use short-cut for created clip planes map of created (or "registered by name") clip planes
  static MapOfPlanes aRegPlanes;

  if (theArgsNb < 2)
  {
    for (MapOfPlanes::Iterator aPlaneIter(aRegPlanes); aPlaneIter.More(); aPlaneIter.Next())
    {
      theDi << aPlaneIter.Key() << " ";
    }
    return 0;
  }

  TCollection_AsciiString aCommand(theArgVec[1]);
  aCommand.LowerCase();
  const occ::handle<V3d_View>& anActiveView = ViewerTest::CurrentView();
  if (anActiveView.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  // print maximum number of planes for current viewer
  if (aCommand == "-maxplanes" || aCommand == "maxplanes")
  {
    theDi << anActiveView->Viewer()->Driver()->InquirePlaneLimit()
          << " plane slots provided by driver.\n";
    return 0;
  }

  // create / delete plane instance
  if (aCommand == "-create" || aCommand == "create" || aCommand == "-delete" || aCommand == "delete"
      || aCommand == "-clone" || aCommand == "clone")
  {
    if (theArgsNb < 3)
    {
      Message::SendFail("Syntax error: plane name is required");
      return 1;
    }

    bool                    toCreate = aCommand == "-create" || aCommand == "create";
    bool                    toClone  = aCommand == "-clone" || aCommand == "clone";
    bool                    toDelete = aCommand == "-delete" || aCommand == "delete";
    TCollection_AsciiString aPlane(theArgVec[2]);

    if (toCreate)
    {
      if (aRegPlanes.IsBound(aPlane))
      {
        std::cout << "Warning: existing plane has been overridden.\n";
        toDelete = true;
      }
      else
      {
        aRegPlanes.Bind(aPlane, new Graphic3d_ClipPlane());
        return 0;
      }
    }
    else if (toClone) // toClone
    {
      if (!aRegPlanes.IsBound(aPlane))
      {
        Message::SendFail("Error: no such plane");
        return 1;
      }
      else if (theArgsNb < 4)
      {
        Message::SendFail("Syntax error: enter name for new plane");
        return 1;
      }

      TCollection_AsciiString aClone(theArgVec[3]);
      if (aRegPlanes.IsBound(aClone))
      {
        Message::SendFail("Error: plane name is in use");
        return 1;
      }

      const occ::handle<Graphic3d_ClipPlane>& aClipPlane = aRegPlanes.Find(aPlane);

      aRegPlanes.Bind(aClone, aClipPlane->Clone());
      return 0;
    }

    if (toDelete)
    {
      if (aPlane == "ALL" || aPlane == "all" || aPlane == "*")
      {
        for (MapOfPlanes::Iterator aPlaneIter(aRegPlanes); aPlaneIter.More();)
        {
          aPlane = aPlaneIter.Key();
          removePlane(aRegPlanes, aPlane);
          aPlaneIter = MapOfPlanes::Iterator(aRegPlanes);
        }
      }
      else
      {
        removePlane(aRegPlanes, aPlane);
      }
    }

    if (toCreate)
    {
      aRegPlanes.Bind(aPlane, new Graphic3d_ClipPlane());
    }
    return 0;
  }

  // set / unset plane command
  if (aCommand == "set" || aCommand == "unset")
  {
    if (theArgsNb < 5)
    {
      Message::SendFail("Syntax error: need more arguments");
      return 1;
    }

    // redirect to new syntax
    NCollection_Array1<const char*> anArgVec(1, theArgsNb - 1);
    anArgVec.SetValue(1, theArgVec[0]);
    anArgVec.SetValue(2, theArgVec[2]);
    anArgVec.SetValue(3, aCommand == "set" ? "-set" : "-unset");
    for (int anIt = 4; anIt < theArgsNb; ++anIt)
    {
      anArgVec.SetValue(anIt, theArgVec[anIt]);
    }

    return VClipPlane(theDi, anArgVec.Length(), &anArgVec.ChangeFirst());
  }

  // change plane command
  TCollection_AsciiString          aPlaneName;
  occ::handle<Graphic3d_ClipPlane> aClipPlane;
  int                              anArgIter = 0;
  if (aCommand == "-change" || aCommand == "change")
  {
    // old syntax support
    if (theArgsNb < 3)
    {
      Message::SendFail("Syntax error: need more arguments");
      return 1;
    }

    anArgIter  = 3;
    aPlaneName = theArgVec[2];
    if (!aRegPlanes.Find(aPlaneName, aClipPlane))
    {
      Message::SendFail() << "Error: no such plane '" << aPlaneName << "'";
      return 1;
    }
  }
  else if (aRegPlanes.Find(theArgVec[1], aClipPlane))
  {
    anArgIter  = 2;
    aPlaneName = theArgVec[1];
  }
  else
  {
    anArgIter  = 2;
    aPlaneName = theArgVec[1];
    aClipPlane = new Graphic3d_ClipPlane();
    aRegPlanes.Bind(aPlaneName, aClipPlane);
    theDi << "Created new plane " << aPlaneName << ".\n";
  }

  if (theArgsNb - anArgIter < 1)
  {
    Message::SendFail("Syntax error: need more arguments");
    return 1;
  }

  for (; anArgIter < theArgsNb; ++anArgIter)
  {
    const char**            aChangeArgs   = theArgVec + anArgIter;
    int                     aNbChangeArgs = theArgsNb - anArgIter;
    TCollection_AsciiString aChangeArg(aChangeArgs[0]);
    aChangeArg.LowerCase();

    bool toEnable = true;
    if (Draw::ParseOnOff(aChangeArgs[0], toEnable))
    {
      aClipPlane->SetOn(toEnable);
    }
    else if (aChangeArg.StartsWith("-equation") || aChangeArg.StartsWith("equation"))
    {
      if (aNbChangeArgs < 5)
      {
        Message::SendFail("Syntax error: need more arguments");
        return 1;
      }

      int aSubIndex  = 1;
      int aPrefixLen = 8 + (aChangeArg.Value(1) == '-' ? 1 : 0);
      if (aPrefixLen < aChangeArg.Length())
      {
        TCollection_AsciiString aSubStr = aChangeArg.SubString(aPrefixLen + 1, aChangeArg.Length());
        if (!aSubStr.IsIntegerValue() || aSubStr.IntegerValue() <= 0)
        {
          Message::SendFail() << "Syntax error: unknown argument '" << aChangeArg << "'";
          return 1;
        }
        aSubIndex = aSubStr.IntegerValue();
      }

      double                           aCoeffA = Draw::Atof(aChangeArgs[1]);
      double                           aCoeffB = Draw::Atof(aChangeArgs[2]);
      double                           aCoeffC = Draw::Atof(aChangeArgs[3]);
      double                           aCoeffD = Draw::Atof(aChangeArgs[4]);
      occ::handle<Graphic3d_ClipPlane> aSubPln = aClipPlane;
      for (int aSubPlaneIter = 1; aSubPlaneIter < aSubIndex; ++aSubPlaneIter)
      {
        if (aSubPln->ChainNextPlane().IsNull())
        {
          aSubPln->SetChainNextPlane(new Graphic3d_ClipPlane(*aSubPln));
        }
        aSubPln = aSubPln->ChainNextPlane();
      }
      aSubPln->SetChainNextPlane(occ::handle<Graphic3d_ClipPlane>());
      aSubPln->SetEquation(gp_Pln(aCoeffA, aCoeffB, aCoeffC, aCoeffD));
      anArgIter += 4;
    }
    else if ((aChangeArg == "-boxinterior" || aChangeArg == "-boxint" || aChangeArg == "-box")
             && aNbChangeArgs >= 7)
    {
      Graphic3d_BndBox3d aBndBox;
      aBndBox.Add(NCollection_Vec3<double>(Draw::Atof(aChangeArgs[1]),
                                           Draw::Atof(aChangeArgs[2]),
                                           Draw::Atof(aChangeArgs[3])));
      aBndBox.Add(NCollection_Vec3<double>(Draw::Atof(aChangeArgs[4]),
                                           Draw::Atof(aChangeArgs[5]),
                                           Draw::Atof(aChangeArgs[6])));
      anArgIter += 6;

      int                            aNbSubPlanes = 6;
      const NCollection_Vec3<double> aDirArray[6] = {
        NCollection_Vec3<double>(-1, 0, 0),
        NCollection_Vec3<double>(1, 0, 0),
        NCollection_Vec3<double>(0, -1, 0),
        NCollection_Vec3<double>(0, 1, 0),
        NCollection_Vec3<double>(0, 0, -1),
        NCollection_Vec3<double>(0, 0, 1),
      };
      occ::handle<Graphic3d_ClipPlane> aSubPln = aClipPlane;
      for (int aSubPlaneIter = 0; aSubPlaneIter < aNbSubPlanes; ++aSubPlaneIter)
      {
        const NCollection_Vec3<double>& aDir = aDirArray[aSubPlaneIter];
        const double                    aW =
          -aDir.Dot((aSubPlaneIter % 2 == 1) ? aBndBox.CornerMax() : aBndBox.CornerMin());
        aSubPln->SetEquation(gp_Pln(aDir.x(), aDir.y(), aDir.z(), aW));
        if (aSubPlaneIter + 1 == aNbSubPlanes)
        {
          aSubPln->SetChainNextPlane(occ::handle<Graphic3d_ClipPlane>());
        }
        else
        {
          aSubPln->SetChainNextPlane(new Graphic3d_ClipPlane(*aSubPln));
        }
        aSubPln = aSubPln->ChainNextPlane();
      }
    }
    else if (aChangeArg == "-capping" || aChangeArg == "capping")
    {
      if (aNbChangeArgs < 2)
      {
        Message::SendFail("Syntax error: need more arguments");
        return 1;
      }

      if (Draw::ParseOnOff(aChangeArgs[1], toEnable))
      {
        aClipPlane->SetCapping(toEnable);
        anArgIter += 1;
      }
      else
      {
        // just skip otherwise (old syntax)
      }
    }
    else if (aChangeArg == "-useobjectmaterial" || aChangeArg == "-useobjectmat"
             || aChangeArg == "-useobjmat" || aChangeArg == "-useobjmaterial")
    {
      if (aNbChangeArgs < 2)
      {
        Message::SendFail("Syntax error: need more arguments");
        return 1;
      }

      if (Draw::ParseOnOff(aChangeArgs[1], toEnable))
      {
        aClipPlane->SetUseObjectMaterial(toEnable);
        anArgIter += 1;
      }
    }
    else if (aChangeArg == "-useobjecttexture" || aChangeArg == "-useobjecttex"
             || aChangeArg == "-useobjtexture" || aChangeArg == "-useobjtex")
    {
      if (aNbChangeArgs < 2)
      {
        Message::SendFail("Syntax error: need more arguments");
        return 1;
      }

      if (Draw::ParseOnOff(aChangeArgs[1], toEnable))
      {
        aClipPlane->SetUseObjectTexture(toEnable);
        anArgIter += 1;
      }
    }
    else if (aChangeArg == "-useobjectshader" || aChangeArg == "-useobjshader")
    {
      if (aNbChangeArgs < 2)
      {
        Message::SendFail("Syntax error: need more arguments");
        return 1;
      }

      if (Draw::ParseOnOff(aChangeArgs[1], toEnable))
      {
        aClipPlane->SetUseObjectShader(toEnable);
        anArgIter += 1;
      }
    }
    else if (aChangeArg == "-color" || aChangeArg == "color")
    {
      Quantity_Color aColor;
      int            aNbParsed = Draw::ParseColor(aNbChangeArgs - 1, aChangeArgs + 1, aColor);
      if (aNbParsed == 0)
      {
        Message::SendFail("Syntax error: need more arguments");
        return 1;
      }
      aClipPlane->SetCappingColor(aColor);
      anArgIter += aNbParsed;
    }
    else if (aNbChangeArgs >= 1 && (aChangeArg == "-material" || aChangeArg == "material"))
    {
      ++anArgIter;
      Graphic3d_NameOfMaterial aMatName;
      if (!Graphic3d_MaterialAspect::MaterialFromName(aChangeArgs[1], aMatName))
      {
        Message::SendFail() << "Syntax error: unknown material '" << aChangeArgs[1] << "'";
        return 1;
      }
      aClipPlane->SetCappingMaterial(aMatName);
    }
    else if ((aChangeArg == "-transparency" || aChangeArg == "-transp") && aNbChangeArgs >= 2)
    {
      TCollection_AsciiString                 aValStr(aChangeArgs[1]);
      occ::handle<Graphic3d_AspectFillArea3d> anAspect = aClipPlane->CappingAspect();
      if (aValStr.IsRealValue(true))
      {
        Graphic3d_MaterialAspect aMat = aClipPlane->CappingMaterial();
        aMat.SetTransparency((float)aValStr.RealValue());
        anAspect->SetAlphaMode(Graphic3d_AlphaMode_BlendAuto);
        aClipPlane->SetCappingMaterial(aMat);
      }
      else
      {
        aValStr.LowerCase();
        Graphic3d_AlphaMode aMode = Graphic3d_AlphaMode_BlendAuto;
        if (aValStr == "opaque")
        {
          aMode = Graphic3d_AlphaMode_Opaque;
        }
        else if (aValStr == "mask")
        {
          aMode = Graphic3d_AlphaMode_Mask;
        }
        else if (aValStr == "blend")
        {
          aMode = Graphic3d_AlphaMode_Blend;
        }
        else if (aValStr == "maskblend" || aValStr == "blendmask")
        {
          aMode = Graphic3d_AlphaMode_MaskBlend;
        }
        else if (aValStr == "blendauto")
        {
          aMode = Graphic3d_AlphaMode_BlendAuto;
        }
        else
        {
          Message::SendFail() << "Syntax error at '" << aValStr << "'";
          return 1;
        }
        anAspect->SetAlphaMode(aMode);
        aClipPlane->SetCappingAspect(anAspect);
      }
      anArgIter += 1;
    }
    else if (aChangeArg == "-texname" || aChangeArg == "texname")
    {
      if (aNbChangeArgs < 2)
      {
        Message::SendFail("Syntax error: need more arguments");
        return 1;
      }

      TCollection_AsciiString          aTextureName(aChangeArgs[1]);
      occ::handle<Graphic3d_Texture2D> aTexture = new Graphic3d_Texture2D(aTextureName);
      if (!aTexture->IsDone())
      {
        aClipPlane->SetCappingTexture(nullptr);
      }
      else
      {
        aTexture->EnableModulate();
        aTexture->EnableRepeat();
        aClipPlane->SetCappingTexture(aTexture);
      }
      anArgIter += 1;
    }
    else if (aChangeArg == "-texscale" || aChangeArg == "texscale")
    {
      if (aClipPlane->CappingTexture().IsNull())
      {
        Message::SendFail("Error: no texture is set");
        return 1;
      }

      if (aNbChangeArgs < 3)
      {
        Message::SendFail("Syntax error: need more arguments");
        return 1;
      }

      float aSx = (float)Draw::Atof(aChangeArgs[1]);
      float aSy = (float)Draw::Atof(aChangeArgs[2]);
      aClipPlane->CappingTexture()->GetParams()->SetScale(NCollection_Vec2<float>(aSx, aSy));
      anArgIter += 2;
    }
    else if (aChangeArg == "-texorigin" || aChangeArg == "texorigin") // texture origin
    {
      if (aClipPlane->CappingTexture().IsNull())
      {
        Message::SendFail("Error: no texture is set");
        return 1;
      }

      if (aNbChangeArgs < 3)
      {
        Message::SendFail("Syntax error: need more arguments");
        return 1;
      }

      float aTx = (float)Draw::Atof(aChangeArgs[1]);
      float aTy = (float)Draw::Atof(aChangeArgs[2]);

      aClipPlane->CappingTexture()->GetParams()->SetTranslation(NCollection_Vec2<float>(aTx, aTy));
      anArgIter += 2;
    }
    else if (aChangeArg == "-texrotate" || aChangeArg == "texrotate") // texture rotation
    {
      if (aClipPlane->CappingTexture().IsNull())
      {
        Message::SendFail("Error: no texture is set");
        return 1;
      }

      if (aNbChangeArgs < 2)
      {
        Message::SendFail("Syntax error: need more arguments");
        return 1;
      }

      float aRot = (float)Draw::Atof(aChangeArgs[1]);
      aClipPlane->CappingTexture()->GetParams()->SetRotation(aRot);
      anArgIter += 1;
    }
    else if (aChangeArg == "-hatch" || aChangeArg == "hatch")
    {
      if (aNbChangeArgs < 2)
      {
        Message::SendFail("Syntax error: need more arguments");
        return 1;
      }

      TCollection_AsciiString aHatchStr(aChangeArgs[1]);
      aHatchStr.LowerCase();
      if (aHatchStr == "on")
      {
        aClipPlane->SetCappingHatchOn();
      }
      else if (aHatchStr == "off")
      {
        aClipPlane->SetCappingHatchOff();
      }
      else
      {
        aClipPlane->SetCappingHatch((Aspect_HatchStyle)Draw::Atoi(aChangeArgs[1]));
      }
      anArgIter += 1;
    }
    else if (aChangeArg == "-delete" || aChangeArg == "delete")
    {
      removePlane(aRegPlanes, aPlaneName);
      return 0;
    }
    else if (aChangeArg == "-set" || aChangeArg == "-unset" || aChangeArg == "-setoverrideglobal")
    {
      // set / unset plane command
      const bool toSet            = aChangeArg.StartsWith("-set");
      const bool toOverrideGlobal = aChangeArg == "-setoverrideglobal";
      int        anIt             = 1;
      for (; anIt < aNbChangeArgs; ++anIt)
      {
        TCollection_AsciiString anEntityName(aChangeArgs[anIt]);
        if (anEntityName.IsEmpty() || anEntityName.Value(1) == '-')
        {
          break;
        }
        else if (!toOverrideGlobal && ViewerTest_myViews.IsBound1(anEntityName))
        {
          const occ::handle<V3d_View>& aView = ViewerTest_myViews.Find1(anEntityName);
          if (toSet)
          {
            aView->AddClipPlane(aClipPlane);
          }
          else
          {
            aView->RemoveClipPlane(aClipPlane);
          }
          continue;
        }
        else if (GetMapOfAIS().IsBound2(anEntityName))
        {
          occ::handle<AIS_InteractiveObject> aIObj = GetMapOfAIS().Find2(anEntityName);
          if (toSet)
          {
            aIObj->AddClipPlane(aClipPlane);
          }
          else
          {
            aIObj->RemoveClipPlane(aClipPlane);
          }
          if (!aIObj->ClipPlanes().IsNull())
          {
            aIObj->ClipPlanes()->SetOverrideGlobal(toOverrideGlobal);
          }
        }
        else
        {
          Message::SendFail() << "Error: object/view '" << anEntityName << "' is not found";
          return 1;
        }
      }

      if (anIt == 1)
      {
        // apply to active view
        if (toSet)
        {
          anActiveView->AddClipPlane(aClipPlane);
        }
        else
        {
          anActiveView->RemoveClipPlane(aClipPlane);
        }
      }
      else
      {
        anArgIter = anArgIter + anIt - 1;
      }
    }
    else
    {
      Message::SendFail() << "Syntax error: unknown argument '" << aChangeArg << "'";
      return 1;
    }
  }

  ViewerTest::RedrawAllViews();
  return 0;
}

//=================================================================================================

static int VZRange(Draw_Interpretor& theDi, int theArgsNb, const char** theArgVec)
{
  const occ::handle<V3d_View>& aCurrentView = ViewerTest::CurrentView();

  if (aCurrentView.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  occ::handle<Graphic3d_Camera> aCamera = aCurrentView->Camera();

  if (theArgsNb < 2)
  {
    theDi << "ZNear: " << aCamera->ZNear() << "\n";
    theDi << "ZFar: " << aCamera->ZFar() << "\n";
    return 0;
  }

  if (theArgsNb == 3)
  {
    double aNewZNear = Draw::Atof(theArgVec[1]);
    double aNewZFar  = Draw::Atof(theArgVec[2]);

    if (aNewZNear >= aNewZFar)
    {
      Message::SendFail("Syntax error: invalid arguments: znear should be less than zfar");
      return 1;
    }

    if (!aCamera->IsOrthographic() && (aNewZNear <= 0.0 || aNewZFar <= 0.0))
    {
      Message::SendFail(
        "Syntax error: invalid arguments: znear, zfar should be positive for perspective camera");
      return 1;
    }

    aCamera->SetZRange(aNewZNear, aNewZFar);
  }
  else
  {
    Message::SendFail("Syntax error: wrong command arguments");
    return 1;
  }

  aCurrentView->Redraw();

  return 0;
}

//=================================================================================================

static int VAutoZFit(Draw_Interpretor& theDi, int theArgsNb, const char** theArgVec)
{
  const occ::handle<V3d_View>& aCurrentView = ViewerTest::CurrentView();

  if (aCurrentView.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  double aScale = aCurrentView->AutoZFitScaleFactor();

  if (theArgsNb > 3)
  {
    Message::SendFail("Syntax error: wrong command arguments");
    return 1;
  }

  if (theArgsNb < 2)
  {
    theDi << "Auto z-fit mode: \n"
          << "On: " << (aCurrentView->AutoZFitMode() ? "enabled" : "disabled") << "\n"
          << "Scale: " << aScale << "\n";
    return 0;
  }

  bool isOn = Draw::Atoi(theArgVec[1]) == 1;

  if (theArgsNb >= 3)
  {
    aScale = Draw::Atoi(theArgVec[2]);
  }

  aCurrentView->SetAutoZFitMode(isOn, aScale);
  aCurrentView->Redraw();
  return 0;
}

//! Auxiliary function to print projection type
inline const char* projTypeName(Graphic3d_Camera::Projection theProjType)
{
  switch (theProjType)
  {
    case Graphic3d_Camera::Projection_Orthographic:
      return "orthographic";
    case Graphic3d_Camera::Projection_Perspective:
      return "perspective";
    case Graphic3d_Camera::Projection_Stereo:
      return "stereoscopic";
    case Graphic3d_Camera::Projection_MonoLeftEye:
      return "monoLeftEye";
    case Graphic3d_Camera::Projection_MonoRightEye:
      return "monoRightEye";
  }
  return "UNKNOWN";
}

//=================================================================================================

static int VCamera(Draw_Interpretor& theDI, int theArgsNb, const char** theArgVec)
{
  occ::handle<V3d_View> aView = ViewerTest::CurrentView();
  if (aView.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  occ::handle<Graphic3d_Camera> aCamera = aView->Camera();
  if (theArgsNb < 2)
  {
    theDI << "ProjType:   " << projTypeName(aCamera->ProjectionType()) << "\n";
    theDI << "FOVy:       " << aCamera->FOVy() << "\n";
    theDI << "FOVx:       " << aCamera->FOVx() << "\n";
    theDI << "FOV2d:      " << aCamera->FOV2d() << "\n";
    theDI << "Distance:   " << aCamera->Distance() << "\n";
    theDI << "IOD:        " << aCamera->IOD() << "\n";
    theDI << "IODType:    "
          << (aCamera->GetIODType() == Graphic3d_Camera::IODType_Absolute ? "absolute" : "relative")
          << "\n";
    theDI << "ZFocus:     " << aCamera->ZFocus() << "\n";
    theDI << "ZFocusType: "
          << (aCamera->ZFocusType() == Graphic3d_Camera::FocusType_Absolute ? "absolute"
                                                                            : "relative")
          << "\n";
    theDI << "ZNear:      " << aCamera->ZNear() << "\n";
    theDI << "ZFar:       " << aCamera->ZFar() << "\n";
    return 0;
  }

  TCollection_AsciiString aPrsName;
  for (int anArgIter = 1; anArgIter < theArgsNb; ++anArgIter)
  {
    const char*             anArg = theArgVec[anArgIter];
    TCollection_AsciiString anArgCase(anArg);
    anArgCase.LowerCase();
    if (anArgCase == "-proj" || anArgCase == "-projection" || anArgCase == "-projtype"
        || anArgCase == "-projectiontype")
    {
      theDI << projTypeName(aCamera->ProjectionType()) << " ";
    }
    else if (anArgCase == "-ortho" || anArgCase == "-orthographic")
    {
      aCamera->SetProjectionType(Graphic3d_Camera::Projection_Orthographic);
    }
    else if (anArgCase == "-persp" || anArgCase == "-perspective" || anArgCase == "-perspmono"
             || anArgCase == "-perspectivemono" || anArgCase == "-mono")
    {
      aCamera->SetProjectionType(Graphic3d_Camera::Projection_Perspective);
    }
    else if (anArgCase == "-stereo" || anArgCase == "-stereoscopic" || anArgCase == "-perspstereo"
             || anArgCase == "-perspectivestereo")
    {
      aCamera->SetProjectionType(Graphic3d_Camera::Projection_Stereo);
    }
    else if (anArgCase == "-left" || anArgCase == "-lefteye" || anArgCase == "-monoleft"
             || anArgCase == "-monolefteye" || anArgCase == "-perpsleft"
             || anArgCase == "-perpslefteye")
    {
      aCamera->SetProjectionType(Graphic3d_Camera::Projection_MonoLeftEye);
    }
    else if (anArgCase == "-right" || anArgCase == "-righteye" || anArgCase == "-monoright"
             || anArgCase == "-monorighteye" || anArgCase == "-perpsright")
    {
      aCamera->SetProjectionType(Graphic3d_Camera::Projection_MonoRightEye);
    }
    else if (anArgCase == "-dist" || anArgCase == "-distance")
    {
      const char* anArgValue = (anArgIter + 1 < theArgsNb) ? theArgVec[anArgIter + 1] : nullptr;
      if (anArgValue != nullptr && *anArgValue != '-')
      {
        ++anArgIter;
        aCamera->SetDistance(Draw::Atof(anArgValue));
        continue;
      }
      theDI << aCamera->Distance() << " ";
    }
    else if (anArgCase == "-iod")
    {
      const char* anArgValue = (anArgIter + 1 < theArgsNb) ? theArgVec[anArgIter + 1] : nullptr;
      if (anArgValue != nullptr && *anArgValue != '-')
      {
        ++anArgIter;
        aCamera->SetIOD(aCamera->GetIODType(), Draw::Atof(anArgValue));
        continue;
      }
      theDI << aCamera->IOD() << " ";
    }
    else if (anArgCase == "-iodtype")
    {
      const char* anArgValue = (anArgIter + 1 < theArgsNb) ? theArgVec[anArgIter + 1] : "";
      TCollection_AsciiString anValueCase(anArgValue);
      anValueCase.LowerCase();
      if (anValueCase == "abs" || anValueCase == "absolute")
      {
        ++anArgIter;
        aCamera->SetIOD(Graphic3d_Camera::IODType_Absolute, aCamera->IOD());
        continue;
      }
      else if (anValueCase == "rel" || anValueCase == "relative")
      {
        ++anArgIter;
        aCamera->SetIOD(Graphic3d_Camera::IODType_Relative, aCamera->IOD());
        continue;
      }
      else if (*anArgValue != '-')
      {
        Message::SendFail() << "Error: unknown IOD type '" << anArgValue << "'";
        return 1;
      }
      switch (aCamera->GetIODType())
      {
        case Graphic3d_Camera::IODType_Absolute:
          theDI << "absolute ";
          break;
        case Graphic3d_Camera::IODType_Relative:
          theDI << "relative ";
          break;
      }
    }
    else if (anArgCase == "-zfocus")
    {
      const char* anArgValue = (anArgIter + 1 < theArgsNb) ? theArgVec[anArgIter + 1] : nullptr;
      if (anArgValue != nullptr && *anArgValue != '-')
      {
        ++anArgIter;
        aCamera->SetZFocus(aCamera->ZFocusType(), Draw::Atof(anArgValue));
        continue;
      }
      theDI << aCamera->ZFocus() << " ";
    }
    else if (anArgCase == "-zfocustype")
    {
      const char* anArgValue = (anArgIter + 1 < theArgsNb) ? theArgVec[anArgIter + 1] : "";
      TCollection_AsciiString anValueCase(anArgValue);
      anValueCase.LowerCase();
      if (anValueCase == "abs" || anValueCase == "absolute")
      {
        ++anArgIter;
        aCamera->SetZFocus(Graphic3d_Camera::FocusType_Absolute, aCamera->ZFocus());
        continue;
      }
      else if (anValueCase == "rel" || anValueCase == "relative")
      {
        ++anArgIter;
        aCamera->SetZFocus(Graphic3d_Camera::FocusType_Relative, aCamera->ZFocus());
        continue;
      }
      else if (*anArgValue != '-')
      {
        Message::SendFail() << "Error: unknown ZFocus type '" << anArgValue << "'";
        return 1;
      }
      switch (aCamera->ZFocusType())
      {
        case Graphic3d_Camera::FocusType_Absolute:
          theDI << "absolute ";
          break;
        case Graphic3d_Camera::FocusType_Relative:
          theDI << "relative ";
          break;
      }
    }
    else if (anArgCase == "-lockzup" || anArgCase == "-turntable")
    {
      bool toLockUp = true;
      if (++anArgIter < theArgsNb && !Draw::ParseOnOff(theArgVec[anArgIter], toLockUp))
      {
        --anArgIter;
      }
      ViewerTest::CurrentEventManager()->SetLockOrbitZUp(toLockUp);
    }
    else if (anArgCase == "-rotationmode" || anArgCase == "-rotmode")
    {
      AIS_RotationMode        aRotMode = AIS_RotationMode_BndBoxActive;
      TCollection_AsciiString aRotStr((anArgIter + 1 < theArgsNb) ? theArgVec[anArgIter + 1] : "");
      aRotStr.LowerCase();
      if (aRotStr == "bndboxactive" || aRotStr == "active")
      {
        aRotMode = AIS_RotationMode_BndBoxActive;
      }
      else if (aRotStr == "picklast" || aRotStr == "pick")
      {
        aRotMode = AIS_RotationMode_PickLast;
      }
      else if (aRotStr == "pickcenter")
      {
        aRotMode = AIS_RotationMode_PickCenter;
      }
      else if (aRotStr == "cameraat" || aRotStr == "cameracenter")
      {
        aRotMode = AIS_RotationMode_CameraAt;
      }
      else if (aRotStr == "bndboxscene" || aRotStr == "boxscene")
      {
        aRotMode = AIS_RotationMode_BndBoxScene;
      }
      else
      {
        Message::SendFail() << "Syntax error at '" << theArgVec[anArgIter] << "'";
        return 1;
      }

      ViewerTest::CurrentEventManager()->SetRotationMode(aRotMode);
      ++anArgIter;
    }
    else if (anArgCase == "-navigationmode" || anArgCase == "-navmode")
    {
      AIS_NavigationMode      aNavMode = AIS_NavigationMode_Orbit;
      TCollection_AsciiString aNavStr((anArgIter + 1 < theArgsNb) ? theArgVec[anArgIter + 1] : "");
      aNavStr.LowerCase();
      if (aNavStr == "orbit")
      {
        aNavMode = AIS_NavigationMode_Orbit;
      }
      else if (aNavStr == "flight" || aNavStr == "fly" || aNavStr == "copter"
               || aNavStr == "helicopter")
      {
        aNavMode = AIS_NavigationMode_FirstPersonFlight;
      }
      else if (aNavStr == "walk" || aNavStr == "shooter")
      {
        aNavMode = AIS_NavigationMode_FirstPersonWalk;
      }
      else
      {
        Message::SendFail() << "Syntax error at '" << theArgVec[anArgIter] << "'";
        return 1;
      }

      occ::handle<ViewerTest_EventManager> aViewMgr = ViewerTest::CurrentEventManager();
      aViewMgr->SetNavigationMode(aNavMode);
      if (aNavMode == AIS_NavigationMode_Orbit)
      {
        aViewMgr->ChangeMouseGestureMap().Bind(Aspect_VKeyMouse_LeftButton,
                                               AIS_MouseGesture_RotateOrbit);
      }
      else
      {
        aViewMgr->ChangeMouseGestureMap().Bind(Aspect_VKeyMouse_LeftButton,
                                               AIS_MouseGesture_RotateView);
      }
      ++anArgIter;
    }
    else if (anArgCase == "-fov" || anArgCase == "-fovy" || anArgCase == "-fovx"
             || anArgCase == "-fov2d")
    {
      const char* anArgValue = (anArgIter + 1 < theArgsNb) ? theArgVec[anArgIter + 1] : nullptr;
      if (anArgValue != nullptr && *anArgValue != '-')
      {
        ++anArgIter;
        if (anArgCase == "-fov2d")
        {
          aCamera->SetFOV2d(Draw::Atof(anArgValue));
        }
        else if (anArgCase == "-fovx")
        {
          aCamera->SetFOVy(Draw::Atof(anArgValue) / aCamera->Aspect()); ///
        }
        else
        {
          aCamera->SetFOVy(Draw::Atof(anArgValue));
        }
        continue;
      }
      if (anArgCase == "-fov2d")
      {
        theDI << aCamera->FOV2d() << " ";
      }
      else if (anArgCase == "-fovx")
      {
        theDI << aCamera->FOVx() << " ";
      }
      else
      {
        theDI << aCamera->FOVy() << " ";
      }
    }
    else if (anArgIter + 1 < theArgsNb && anArgCase == "-xrpose")
    {
      TCollection_AsciiString anXRArg(theArgVec[++anArgIter]);
      anXRArg.LowerCase();
      if (anXRArg == "base")
      {
        aCamera = aView->View()->BaseXRCamera();
      }
      else if (anXRArg == "head")
      {
        aCamera = aView->View()->PosedXRCamera();
      }
      else
      {
        Message::SendFail() << "Syntax error: unknown XR pose '" << anXRArg << "'";
        return 1;
      }
      if (aCamera.IsNull())
      {
        Message::SendFail() << "Error: undefined XR pose";
        return 0;
      }
      if (aView->AutoZFitMode())
      {
        const Bnd_Box aMinMaxBox  = aView->View()->MinMaxValues(false);
        const Bnd_Box aGraphicBox = aView->View()->MinMaxValues(true);
        aCamera->ZFitAll(aView->AutoZFitScaleFactor(), aMinMaxBox, aGraphicBox);
      }
    }
    else if (aPrsName.IsEmpty() && !anArgCase.StartsWith("-"))
    {
      aPrsName = anArg;
    }
    else
    {
      Message::SendFail() << "Error: unknown argument '" << anArg << "'";
      return 1;
    }
  }

  if (aPrsName.IsEmpty() || theArgsNb > 2)
  {
    aView->Redraw();
  }

  if (!aPrsName.IsEmpty())
  {
    occ::handle<AIS_CameraFrustum> aCameraFrustum;
    if (GetMapOfAIS().IsBound2(aPrsName))
    {
      // find existing object
      aCameraFrustum = occ::down_cast<AIS_CameraFrustum>(GetMapOfAIS().Find2(theArgVec[1]));
      if (aCameraFrustum.IsNull())
      {
        Message::SendFail() << "Error: object '" << aPrsName
                            << "'is already defined and is not a camera frustum";
        return 1;
      }
    }

    if (aCameraFrustum.IsNull())
    {
      aCameraFrustum = new AIS_CameraFrustum();
    }
    else
    {
      // not include displayed object of old camera frustum in the new one.
      ViewerTest::GetAISContext()->Erase(aCameraFrustum, false);
      aView->ZFitAll();
    }
    aCameraFrustum->SetCameraFrustum(aCamera);

    ViewerTest::Display(aPrsName, aCameraFrustum);
  }

  return 0;
}

//! Parse stereo output mode
inline bool parseStereoMode(const char* theArg, Graphic3d_StereoMode& theMode)
{
  TCollection_AsciiString aFlag(theArg);
  aFlag.LowerCase();
  if (aFlag == "quadbuffer")
  {
    theMode = Graphic3d_StereoMode_QuadBuffer;
  }
  else if (aFlag == "anaglyph")
  {
    theMode = Graphic3d_StereoMode_Anaglyph;
  }
  else if (aFlag == "row" || aFlag == "rowinterlaced")
  {
    theMode = Graphic3d_StereoMode_RowInterlaced;
  }
  else if (aFlag == "col" || aFlag == "colinterlaced" || aFlag == "columninterlaced")
  {
    theMode = Graphic3d_StereoMode_ColumnInterlaced;
  }
  else if (aFlag == "chess" || aFlag == "chessboard")
  {
    theMode = Graphic3d_StereoMode_ChessBoard;
  }
  else if (aFlag == "sbs" || aFlag == "sidebyside")
  {
    theMode = Graphic3d_StereoMode_SideBySide;
  }
  else if (aFlag == "ou" || aFlag == "overunder")
  {
    theMode = Graphic3d_StereoMode_OverUnder;
  }
  else if (aFlag == "pageflip" || aFlag == "softpageflip")
  {
    theMode = Graphic3d_StereoMode_SoftPageFlip;
  }
  else if (aFlag == "openvr" || aFlag == "vr")
  {
    theMode = Graphic3d_StereoMode_OpenVR;
  }
  else
  {
    return false;
  }
  return true;
}

//! Parse anaglyph filter
inline bool parseAnaglyphFilter(const char* theArg, Graphic3d_RenderingParams::Anaglyph& theFilter)
{
  TCollection_AsciiString aFlag(theArg);
  aFlag.LowerCase();
  if (aFlag == "redcyansimple")
  {
    theFilter = Graphic3d_RenderingParams::Anaglyph_RedCyan_Simple;
  }
  else if (aFlag == "redcyan" || aFlag == "redcyanoptimized")
  {
    theFilter = Graphic3d_RenderingParams::Anaglyph_RedCyan_Optimized;
  }
  else if (aFlag == "yellowbluesimple")
  {
    theFilter = Graphic3d_RenderingParams::Anaglyph_YellowBlue_Simple;
  }
  else if (aFlag == "yellowblue" || aFlag == "yellowblueoptimized")
  {
    theFilter = Graphic3d_RenderingParams::Anaglyph_YellowBlue_Optimized;
  }
  else if (aFlag == "greenmagenta" || aFlag == "greenmagentasimple")
  {
    theFilter = Graphic3d_RenderingParams::Anaglyph_GreenMagenta_Simple;
  }
  else
  {
    return false;
  }
  return true;
}

//=================================================================================================

static int VStereo(Draw_Interpretor& theDI, int theArgNb, const char** theArgVec)
{
  occ::handle<V3d_View> aView = ViewerTest::CurrentView();
  if (aView.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 0;
  }

  occ::handle<Graphic3d_Camera> aCamera = aView->Camera();
  Graphic3d_RenderingParams*    aParams = &aView->ChangeRenderingParams();
  if (theArgNb < 2)
  {
    bool isActive = aCamera->ProjectionType() == Graphic3d_Camera::Projection_Stereo;
    theDI << "Stereo " << (isActive ? "ON" : "OFF") << "\n";
    if (isActive)
    {
      TCollection_AsciiString aMode;
      switch (aView->RenderingParams().StereoMode)
      {
        case Graphic3d_StereoMode_QuadBuffer: {
          aMode = "quadBuffer";
          break;
        }
        case Graphic3d_StereoMode_RowInterlaced: {
          aMode = "rowInterlaced";
          if (aView->RenderingParams().ToSmoothInterlacing)
          {
            aMode.AssignCat(" (smoothed)");
          }
          break;
        }
        case Graphic3d_StereoMode_ColumnInterlaced: {
          aMode = "columnInterlaced";
          if (aView->RenderingParams().ToSmoothInterlacing)
          {
            aMode.AssignCat(" (smoothed)");
          }
          break;
        }
        case Graphic3d_StereoMode_ChessBoard: {
          aMode = "chessBoard";
          if (aView->RenderingParams().ToSmoothInterlacing)
          {
            aMode.AssignCat(" (smoothed)");
          }
          break;
        }
        case Graphic3d_StereoMode_SideBySide: {
          aMode = "sideBySide";
          break;
        }
        case Graphic3d_StereoMode_OverUnder: {
          aMode = "overUnder";
          break;
        }
        case Graphic3d_StereoMode_SoftPageFlip: {
          aMode = "softPageFlip";
          break;
        }
        case Graphic3d_StereoMode_OpenVR: {
          aMode = "openVR";
          break;
        }
        case Graphic3d_StereoMode_Anaglyph: {
          aMode = "anaglyph";
          switch (aView->RenderingParams().AnaglyphFilter)
          {
            case Graphic3d_RenderingParams::Anaglyph_RedCyan_Simple:
              aMode.AssignCat(" (redCyanSimple)");
              break;
            case Graphic3d_RenderingParams::Anaglyph_RedCyan_Optimized:
              aMode.AssignCat(" (redCyan)");
              break;
            case Graphic3d_RenderingParams::Anaglyph_YellowBlue_Simple:
              aMode.AssignCat(" (yellowBlueSimple)");
              break;
            case Graphic3d_RenderingParams::Anaglyph_YellowBlue_Optimized:
              aMode.AssignCat(" (yellowBlue)");
              break;
            case Graphic3d_RenderingParams::Anaglyph_GreenMagenta_Simple:
              aMode.AssignCat(" (greenMagentaSimple)");
              break;
            case Graphic3d_RenderingParams::Anaglyph_UserDefined:
              aMode.AssignCat(" (userDefined)");
              break;
          }
        }
      }
      theDI << "Mode " << aMode << "\n";
    }
    return 0;
  }

  Graphic3d_StereoMode   aMode = aParams->StereoMode;
  ViewerTest_AutoUpdater anUpdateTool(ViewerTest::GetAISContext(), aView);
  for (int anArgIter = 1; anArgIter < theArgNb; ++anArgIter)
  {
    const char*             anArg = theArgVec[anArgIter];
    TCollection_AsciiString aFlag(anArg);
    aFlag.LowerCase();
    if (anUpdateTool.parseRedrawMode(aFlag))
    {
      continue;
    }
    else if (aFlag == "0" || aFlag == "off")
    {
      if (++anArgIter < theArgNb)
      {
        Message::SendFail("Error: wrong number of arguments");
        return 1;
      }

      if (aCamera->ProjectionType() == Graphic3d_Camera::Projection_Stereo)
      {
        aCamera->SetProjectionType(Graphic3d_Camera::Projection_Perspective);
      }
      return 0;
    }
    else if (aFlag == "1" || aFlag == "on")
    {
      if (++anArgIter < theArgNb)
      {
        Message::SendFail("Error: wrong number of arguments");
        return 1;
      }

      aCamera->SetProjectionType(Graphic3d_Camera::Projection_Stereo);
      if (aParams->StereoMode != Graphic3d_StereoMode_OpenVR)
      {
        return 0;
      }
    }
    else if (aFlag == "-reverse" || aFlag == "-noreverse" || aFlag == "-reversed"
             || aFlag == "-swap" || aFlag == "-noswap")
    {
      aParams->ToReverseStereo = Draw::ParseOnOffNoIterator(theArgNb, theArgVec, anArgIter);
    }
    else if (aFlag == "-mode" || aFlag == "-stereomode")
    {
      if (++anArgIter >= theArgNb || !parseStereoMode(theArgVec[anArgIter], aMode))
      {
        Message::SendFail() << "Syntax error at '" << anArg << "'";
        return 1;
      }

      if (aMode == Graphic3d_StereoMode_QuadBuffer)
      {
        Message::SendInfo()
          << "Warning: make sure to call 'vcaps -stereo 1' before creating a view";
      }
    }
    else if (aFlag == "-anaglyph" || aFlag == "-anaglyphfilter")
    {
      Graphic3d_RenderingParams::Anaglyph aFilter =
        Graphic3d_RenderingParams::Anaglyph_RedCyan_Simple;
      if (++anArgIter >= theArgNb || !parseAnaglyphFilter(theArgVec[anArgIter], aFilter))
      {
        Message::SendFail() << "Syntax error at '" << anArg << "'";
        return 1;
      }

      aMode                   = Graphic3d_StereoMode_Anaglyph;
      aParams->AnaglyphFilter = aFilter;
    }
    else if (parseStereoMode(anArg, aMode)) // short syntax
    {
      if (aMode == Graphic3d_StereoMode_QuadBuffer)
      {
        Message::SendInfo()
          << "Warning: make sure to call 'vcaps -stereo 1' before creating a view";
      }
    }
    else if (anArgIter + 1 < theArgNb && aFlag == "-hmdfov2d")
    {
      aParams->HmdFov2d = (float)Draw::Atof(theArgVec[++anArgIter]);
      if (aParams->HmdFov2d < 10.0f || aParams->HmdFov2d > 180.0f)
      {
        Message::SendFail() << "Error: FOV is out of range";
        return 1;
      }
    }
    else if (aFlag == "-mirror" || aFlag == "-mirrorcomposer")
    {
      aParams->ToMirrorComposer = Draw::ParseOnOffNoIterator(theArgNb, theArgVec, anArgIter);
      ;
    }
    else if (aFlag == "-smooth" || aFlag == "-nosmooth" || aFlag == "-smoothinterlacing"
             || aFlag == "-nosmoothinterlacing")
    {
      aParams->ToSmoothInterlacing = Draw::ParseOnOffNoIterator(theArgNb, theArgVec, anArgIter);
    }
    else if (anArgIter + 1 < theArgNb && (aFlag == "-unitfactor" || aFlag == "-unitscale"))
    {
      aView->View()->SetUnitFactor(Draw::Atof(theArgVec[++anArgIter]));
    }
    else
    {
      Message::SendFail() << "Syntax error at '" << anArg << "'";
      return 1;
    }
  }

  aParams->StereoMode = aMode;
  aCamera->SetProjectionType(Graphic3d_Camera::Projection_Stereo);
  if (aParams->StereoMode == Graphic3d_StereoMode_OpenVR)
  {
    // initiate implicit continuous rendering
    ViewerTest::CurrentEventManager()->FlushViewEvents(ViewerTest::GetAISContext(), aView, true);
  }
  return 0;
}

//=================================================================================================

static int VDefaults(Draw_Interpretor& theDi, int theArgsNb, const char** theArgVec)
{
  const occ::handle<AIS_InteractiveContext>& aCtx = ViewerTest::GetAISContext();
  if (aCtx.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  occ::handle<Prs3d_Drawer> aDefParams = aCtx->DefaultDrawer();
  if (theArgsNb < 2)
  {
    if (aDefParams->TypeOfDeflection() == Aspect_TOD_RELATIVE)
    {
      theDi << "DeflType:           relative\n"
            << "DeviationCoeff:     " << aDefParams->DeviationCoefficient() << "\n";
    }
    else
    {
      theDi << "DeflType:           absolute\n"
            << "AbsoluteDeflection: " << aDefParams->MaximalChordialDeviation() << "\n";
    }
    theDi << "AngularDeflection:  " << (180.0 * aDefParams->DeviationAngle() / M_PI) << "\n";
    theDi << "AutoTriangulation:  " << (aDefParams->IsAutoTriangulation() ? "on" : "off") << "\n";
    return 0;
  }

  for (int anArgIter = 1; anArgIter < theArgsNb; ++anArgIter)
  {
    TCollection_AsciiString anArg(theArgVec[anArgIter]);
    anArg.UpperCase();
    if (anArg == "-ABSDEFL" || anArg == "-ABSOLUTEDEFLECTION" || anArg == "-DEFL"
        || anArg == "-DEFLECTION")
    {
      if (++anArgIter >= theArgsNb)
      {
        Message::SendFail() << "Syntax error at " << anArg;
        return 1;
      }
      aDefParams->SetTypeOfDeflection(Aspect_TOD_ABSOLUTE);
      aDefParams->SetMaximalChordialDeviation(Draw::Atof(theArgVec[anArgIter]));
    }
    else if (anArg == "-RELDEFL" || anArg == "-RELATIVEDEFLECTION" || anArg == "-DEVCOEFF"
             || anArg == "-DEVIATIONCOEFF" || anArg == "-DEVIATIONCOEFFICIENT")
    {
      if (++anArgIter >= theArgsNb)
      {
        Message::SendFail() << "Syntax error at " << anArg;
        return 1;
      }
      aDefParams->SetTypeOfDeflection(Aspect_TOD_RELATIVE);
      aDefParams->SetDeviationCoefficient(Draw::Atof(theArgVec[anArgIter]));
    }
    else if (anArg == "-ANGDEFL" || anArg == "-ANGULARDEFL" || anArg == "-ANGULARDEFLECTION")
    {
      if (++anArgIter >= theArgsNb)
      {
        Message::SendFail() << "Syntax error at " << anArg;
        return 1;
      }
      aDefParams->SetDeviationAngle(M_PI * Draw::Atof(theArgVec[anArgIter]) / 180.0);
    }
    else if (anArg == "-AUTOTR" || anArg == "-AUTOTRIANG" || anArg == "-AUTOTRIANGULATION")
    {
      ++anArgIter;
      bool toTurnOn = true;
      if (anArgIter >= theArgsNb || !Draw::ParseOnOff(theArgVec[anArgIter], toTurnOn))
      {
        Message::SendFail() << "Syntax error at '" << anArg << "'";
        return 1;
      }
      aDefParams->SetAutoTriangulation(toTurnOn);
    }
    else
    {
      Message::SendFail() << "Syntax error: unknown argument '" << anArg << "'";
      return 1;
    }
  }

  return 0;
}

//! Parse light source type from string.
static bool parseLightSourceType(const TCollection_AsciiString& theTypeName,
                                 Graphic3d_TypeOfLightSource&   theType)
{
  TCollection_AsciiString aType(theTypeName);
  aType.LowerCase();
  if (aType == "amb" || aType == "ambient" || aType == "amblight")
  {
    theType = Graphic3d_TypeOfLightSource_Ambient;
  }
  else if (aType == "directional" || aType == "dirlight")
  {
    theType = Graphic3d_TypeOfLightSource_Directional;
  }
  else if (aType == "spot" || aType == "spotlight")
  {
    theType = Graphic3d_TypeOfLightSource_Spot;
  }
  else if (aType == "poslight" || aType == "positional" || aType == "point" || aType == "pnt")
  {
    theType = Graphic3d_TypeOfLightSource_Positional;
  }
  else
  {
    return false;
  }
  return true;
}

//! Find existing light by name or index.
static occ::handle<V3d_Light> findLightSource(const TCollection_AsciiString& theName)
{
  occ::handle<V3d_Light> aLight;
  int                    aLightIndex = -1;
  Draw::ParseInteger(theName.ToCString(), aLightIndex);
  int                   aLightIt = 0;
  occ::handle<V3d_View> aView    = ViewerTest::CurrentView();
  for (NCollection_List<occ::handle<Graphic3d_CLight>>::Iterator aLightIter(
         aView->ActiveLightIterator());
       aLightIter.More();
       aLightIter.Next(), ++aLightIt)
  {
    if (aLightIndex != -1)
    {
      if (aLightIt == aLightIndex)
      {
        return aLightIter.Value();
      }
    }
    else if (aLightIter.Value()->GetId() == theName || aLightIter.Value()->Name() == theName)
    {
      if (!aLight.IsNull())
      {
        Message::SendWarning() << "Warning: ambiguous light name '" << theName << "'";
        break;
      }
      aLight = aLightIter.Value();
    }
  }
  return aLight;
}

//=================================================================================================

static int VLight(Draw_Interpretor& theDi, int theArgsNb, const char** theArgVec)
{
  occ::handle<V3d_View>                      aView   = ViewerTest::CurrentView();
  occ::handle<V3d_Viewer>                    aViewer = ViewerTest::GetViewerFromContext();
  const occ::handle<AIS_InteractiveContext>& aCtx    = ViewerTest::GetAISContext();
  if (aView.IsNull() || aViewer.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  if (theArgsNb < 2)
  {
    // print lights info
    int aLightId = 0;
    for (NCollection_List<occ::handle<Graphic3d_CLight>>::Iterator aLightIter(
           aView->ActiveLightIterator());
         aLightIter.More();
         aLightIter.Next(), ++aLightId)
    {
      occ::handle<V3d_Light> aLight = aLightIter.Value();
      const Quantity_Color   aColor = aLight->Color();
      theDi << "Light #" << aLightId
            << (!aLight->Name().IsEmpty() ? (TCollection_AsciiString(" ") + aLight->Name()) : "")
            << " [" << aLight->GetId() << "] " << (aLight->IsEnabled() ? "ON" : "OFF") << "\n";
      switch (aLight->Type())
      {
        case Graphic3d_TypeOfLightSource_Ambient: {
          theDi << "  Type:       Ambient\n"
                << "  Intensity:  " << aLight->Intensity() << "\n";
          break;
        }
        case Graphic3d_TypeOfLightSource_Directional: {
          theDi << "  Type:       Directional\n"
                << "  Intensity:  " << aLight->Intensity() << "\n"
                << "  Headlight:  " << (aLight->Headlight() ? "TRUE" : "FALSE") << "\n"
                << "  CastShadows:" << (aLight->ToCastShadows() ? "TRUE" : "FALSE") << "\n"
                << "  Smoothness: " << aLight->Smoothness() << "\n"
                << "  Direction:  " << aLight->PackedDirection().x() << " "
                << aLight->PackedDirection().y() << " " << aLight->PackedDirection().z() << "\n";
          break;
        }
        case Graphic3d_TypeOfLightSource_Positional: {
          theDi << "  Type:       Positional\n"
                << "  Intensity:  " << aLight->Intensity() << "\n"
                << "  Headlight:  " << (aLight->Headlight() ? "TRUE" : "FALSE") << "\n"
                << "  CastShadows:" << (aLight->ToCastShadows() ? "TRUE" : "FALSE") << "\n"
                << "  Smoothness: " << aLight->Smoothness() << "\n"
                << "  Position:   " << aLight->Position().X() << " " << aLight->Position().Y()
                << " " << aLight->Position().Z() << "\n"
                << "  Atten.:     " << aLight->ConstAttenuation() << " "
                << aLight->LinearAttenuation() << "\n"
                << "  Range:      " << aLight->Range() << "\n";
          break;
        }
        case Graphic3d_TypeOfLightSource_Spot: {
          theDi << "  Type:       Spot\n"
                << "  Intensity:  " << aLight->Intensity() << "\n"
                << "  Headlight:  " << (aLight->Headlight() ? "TRUE" : "FALSE") << "\n"
                << "  CastShadows:" << (aLight->ToCastShadows() ? "TRUE" : "FALSE") << "\n"
                << "  Position:   " << aLight->Position().X() << " " << aLight->Position().Y()
                << " " << aLight->Position().Z() << "\n"
                << "  Direction:  " << aLight->PackedDirection().x() << " "
                << aLight->PackedDirection().y() << " " << aLight->PackedDirection().z() << "\n"
                << "  Atten.:     " << aLight->ConstAttenuation() << " "
                << aLight->LinearAttenuation() << "\n"
                << "  Angle:      " << (aLight->Angle() * 180.0 / M_PI) << "\n"
                << "  Exponent:   " << aLight->Concentration() << "\n"
                << "  Range:      " << aLight->Range() << "\n";
          break;
        }
        default: {
          theDi << "  Type:       UNKNOWN\n";
          break;
        }
      }
      theDi << "  Color:      " << aColor.Red() << " " << aColor.Green() << " " << aColor.Blue()
            << " [" << Quantity_Color::StringName(aColor.Name()) << "]\n";
    }
  }

  occ::handle<V3d_Light>       aLightOld, aLightNew;
  Graphic3d_ZLayerId           aLayer   = Graphic3d_ZLayerId_UNKNOWN;
  bool                         isGlobal = true;
  ViewerTest_AutoUpdater       anUpdateTool(aCtx, aView);
  occ::handle<AIS_LightSource> aLightPrs;
  for (int anArgIt = 1; anArgIt < theArgsNb; ++anArgIt)
  {
    const TCollection_AsciiString anArg(theArgVec[anArgIt]);
    TCollection_AsciiString       anArgCase(anArg);
    anArgCase.LowerCase();
    if (anUpdateTool.parseRedrawMode(anArg))
    {
      continue;
    }
    else if (anArgCase == "-new" || anArgCase == "-add" || anArgCase == "-create"
             || anArgCase == "-type" || (anArgCase == "-reset" && !aLightNew.IsNull())
             || (anArgCase == "-defaults" && !aLightNew.IsNull()) || anArgCase == "add"
             || anArgCase == "new" || anArgCase == "create")
    {
      Graphic3d_TypeOfLightSource aType = Graphic3d_TypeOfLightSource_Ambient;
      if (anArgCase == "-reset")
      {
        aType = aLightNew->Type();
      }
      else if (anArgIt + 1 >= theArgsNb || !parseLightSourceType(theArgVec[++anArgIt], aType))
      {
        theDi << "Syntax error at '" << theArgVec[anArgIt] << "'\n";
        return 1;
      }

      TCollection_AsciiString aName;
      if (!aLightNew.IsNull())
      {
        aName = aLightNew->Name();
      }
      switch (aType)
      {
        case Graphic3d_TypeOfLightSource_Ambient: {
          aLightNew = new V3d_AmbientLight();
          break;
        }
        case Graphic3d_TypeOfLightSource_Directional: {
          aLightNew = new V3d_DirectionalLight();
          break;
        }
        case Graphic3d_TypeOfLightSource_Spot: {
          aLightNew = new V3d_SpotLight(gp_Pnt(0.0, 0.0, 0.0));
          break;
        }
        case Graphic3d_TypeOfLightSource_Positional: {
          aLightNew = new V3d_PositionalLight(gp_Pnt(0.0, 0.0, 0.0));
          break;
        }
      }

      if (anArgCase == "-type" && !aLightOld.IsNull())
      {
        aLightNew->CopyFrom(aLightOld);
      }
      aLightNew->SetName(aName);
    }
    else if ((anArgCase == "-layer" || anArgCase == "-zlayer") && anArgIt + 1 < theArgsNb)
    {
      if (!ViewerTest::ParseZLayer(theArgVec[++anArgIt], aLayer)
          || aLayer == Graphic3d_ZLayerId_UNKNOWN)
      {
        Message::SendFail() << "Error: wrong syntax at '" << theArgVec[anArgIt] << "'";
        return 1;
      }
    }
    else if (anArgCase == "-glob" || anArgCase == "-global" || anArgCase == "-loc"
             || anArgCase == "-local")
    {
      isGlobal = anArgCase.StartsWith("-glob");
    }
    else if (anArgCase == "-def" || anArgCase == "-defaults" || anArgCase == "-reset")
    {
      aViewer->SetDefaultLights();
      aLightOld.Nullify();
      aLightNew.Nullify();
      aLightPrs.Nullify();
    }
    else if (anArgCase == "-clr" || anArgCase == "-clear" || anArgCase == "clear")
    {
      NCollection_Sequence<int> aLayers;
      aViewer->GetAllZLayers(aLayers);
      for (NCollection_Sequence<int>::Iterator aLayeriter(aLayers); aLayeriter.More();
           aLayeriter.Next())
      {
        if (aLayeriter.Value() == aLayer || aLayer == Graphic3d_ZLayerId_UNKNOWN)
        {
          Graphic3d_ZLayerSettings aSettings = aViewer->ZLayerSettings(aLayeriter.Value());
          aSettings.SetLights(occ::handle<Graphic3d_LightSet>());
          aViewer->SetZLayerSettings(aLayeriter.Value(), aSettings);
          if (aLayer != Graphic3d_ZLayerId_UNKNOWN)
          {
            break;
          }
        }
      }

      if (aLayer == Graphic3d_ZLayerId_UNKNOWN)
      {
        NCollection_DoubleMap<occ::handle<AIS_InteractiveObject>, TCollection_AsciiString>& aMap =
          GetMapOfAIS();
        for (NCollection_List<occ::handle<Graphic3d_CLight>>::Iterator aLightIter(
               aView->ActiveLightIterator());
             aLightIter.More();)
        {
          occ::handle<V3d_Light>             aLight = aLightIter.Value();
          occ::handle<AIS_InteractiveObject> aPrsObject;
          GetMapOfAIS().Find2(aLight->Name(), aPrsObject);
          if (occ::handle<AIS_LightSource> aLightSourceDel =
                occ::down_cast<AIS_LightSource>(aPrsObject))
          {
            aCtx->Remove(aLightSourceDel, false);
            aMap.UnBind1(aLightSourceDel);
          }
          aViewer->DelLight(aLight);
          aLightIter = aView->ActiveLightIterator();
        }
      }

      aLightOld.Nullify();
      aLightNew.Nullify();
      aLightPrs.Nullify();
    }
    else if (!aLightNew.IsNull()
             && (anArgCase == "-display" || anArgCase == "-disp" || anArgCase == "-presentation"
                 || anArgCase == "-prs"))
    {
      TCollection_AsciiString aLightName = aLightNew->Name();
      if (anArgIt + 1 < theArgsNb && theArgVec[anArgIt + 1][0] != '-')
      {
        // old syntax
        aLightName = theArgVec[++anArgIt];
        if (aLightNew->Name() != aLightName)
        {
          if (occ::handle<V3d_Light> anOtherLight = findLightSource(aLightName))
          {
            theDi << "Syntax error: light with name '" << aLightName << "' is already defined";
            return 1;
          }
          aLightNew->SetName(aLightName);
        }
      }
      if (aLightName.IsEmpty())
      {
        Message::SendFail() << "Error: nameless light source cannot be displayed";
        return 1;
      }
      if (aLightPrs.IsNull())
      {
        aLightPrs = new AIS_LightSource(aLightNew);
      }
      theDi << aLightName << " ";
    }
    else if (!aLightNew.IsNull()
             && (anArgCase == "-disable" || anArgCase == "-disabled" || anArgCase == "-enable"
                 || anArgCase == "-enabled"))
    {
      bool toEnable = Draw::ParseOnOffIterator(theArgsNb, theArgVec, anArgIt);
      if (anArgCase == "-disable" || anArgCase == "-disabled")
      {
        toEnable = !toEnable;
      }
      aLightNew->SetEnabled(toEnable);
    }
    else if (!aLightNew.IsNull()
             && (anArgCase == "-color" || anArgCase == "-colour" || anArgCase == "color"))
    {
      Quantity_Color aColor;
      int aNbParsed = Draw::ParseColor(theArgsNb - anArgIt - 1, theArgVec + anArgIt + 1, aColor);
      anArgIt += aNbParsed;
      if (aNbParsed == 0)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }
      aLightNew->SetColor(aColor);
    }
    else if (!aLightNew.IsNull()
             && (anArgCase == "-pos" || anArgCase == "-position" || anArgCase == "-prsposition"
                 || anArgCase == "-prspos" || anArgCase == "pos" || anArgCase == "position")
             && (anArgIt + 3) < theArgsNb)
    {
      gp_XYZ aPosXYZ;
      if (!parseXYZ(theArgVec + anArgIt + 1, aPosXYZ))
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }

      anArgIt += 3;
      if (anArgCase == "-prsposition" || anArgCase == "-prspos")
      {
        aLightNew->SetDisplayPosition(aPosXYZ);
      }
      else
      {
        if (aLightNew->Type() != Graphic3d_TypeOfLightSource_Positional
            && aLightNew->Type() != Graphic3d_TypeOfLightSource_Spot)
        {
          Message::SendFail() << "Syntax error at argument '" << anArg << "'";
          return 1;
        }

        aLightNew->SetPosition(aPosXYZ);
      }
    }
    else if (!aLightNew.IsNull()
             && (aLightNew->Type() == Graphic3d_TypeOfLightSource_Directional
                 || aLightNew->Type() == Graphic3d_TypeOfLightSource_Spot)
             && (anArgCase == "-dir" || anArgCase == "-direction") && (anArgIt + 3) < theArgsNb)
    {
      gp_XYZ aDirXYZ;
      if (!parseXYZ(theArgVec + anArgIt + 1, aDirXYZ))
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }

      anArgIt += 3;
      aLightNew->SetDirection(gp_Dir(aDirXYZ));
    }
    else if (!aLightNew.IsNull()
             && (anArgCase == "-smoothangle" || anArgCase == "-smoothradius" || anArgCase == "-sm"
                 || anArgCase == "-smoothness")
             && anArgIt + 1 < theArgsNb)
    {
      float aSmoothness = (float)Atof(theArgVec[++anArgIt]);
      if (aLightNew->Type() == Graphic3d_TypeOfLightSource_Directional)
      {
        aSmoothness = float(aSmoothness * M_PI / 180.0);
      }
      if (std::abs(aSmoothness) <= ShortRealEpsilon())
      {
        aLightNew->SetIntensity(1.f);
      }
      else if (std::abs(aLightNew->Smoothness()) <= ShortRealEpsilon())
      {
        aLightNew->SetIntensity((aSmoothness * aSmoothness) / 3.f);
      }
      else
      {
        float aSmoothnessRatio = static_cast<float>(aSmoothness / aLightNew->Smoothness());
        aLightNew->SetIntensity(aLightNew->Intensity() / (aSmoothnessRatio * aSmoothnessRatio));
      }

      if (aLightNew->Type() == Graphic3d_TypeOfLightSource_Positional)
      {
        aLightNew->SetSmoothRadius(aSmoothness);
      }
      else if (aLightNew->Type() == Graphic3d_TypeOfLightSource_Directional)
      {
        aLightNew->SetSmoothAngle(aSmoothness);
      }
    }
    else if (!aLightNew.IsNull() && (anArgCase == "-int" || anArgCase == "-intensity")
             && anArgIt + 1 < theArgsNb)
    {
      float aIntensity = (float)Atof(theArgVec[++anArgIt]);
      aLightNew->SetIntensity(aIntensity);
    }
    else if (!aLightNew.IsNull() && aLightNew->Type() == Graphic3d_TypeOfLightSource_Spot
             && (anArgCase == "-spotangle" || anArgCase == "-ang" || anArgCase == "-angle")
             && anArgIt + 1 < theArgsNb)
    {
      float anAngle = (float)Atof(theArgVec[++anArgIt]);
      anAngle       = (float(anAngle / 180.0 * M_PI));
      aLightNew->SetAngle(anAngle);
    }
    else if (!aLightNew.IsNull()
             && (aLightNew->Type() == Graphic3d_TypeOfLightSource_Positional
                 || aLightNew->Type() == Graphic3d_TypeOfLightSource_Spot)
             && (anArgCase == "-constatten" || anArgCase == "-constattenuation")
             && anArgIt + 1 < theArgsNb)
    {
      const float aConstAtten = (float)Atof(theArgVec[++anArgIt]);
      aLightNew->SetAttenuation(aConstAtten, aLightNew->LinearAttenuation());
    }
    else if (!aLightNew.IsNull()
             && (aLightNew->Type() == Graphic3d_TypeOfLightSource_Positional
                 || aLightNew->Type() == Graphic3d_TypeOfLightSource_Spot)
             && (anArgCase == "-linatten" || anArgCase == "-linearatten"
                 || anArgCase == "-linearattenuation")
             && anArgIt + 1 < theArgsNb)
    {
      const float aLinAtten = (float)Atof(theArgVec[++anArgIt]);
      aLightNew->SetAttenuation(aLightNew->ConstAttenuation(), aLinAtten);
    }
    else if (!aLightNew.IsNull() && aLightNew->Type() == Graphic3d_TypeOfLightSource_Spot
             && (anArgCase == "-spotexp" || anArgCase == "-spotexponent" || anArgCase == "-exp"
                 || anArgCase == "-exponent")
             && anArgIt + 1 < theArgsNb)
    {
      aLightNew->SetConcentration((float)Atof(theArgVec[++anArgIt]));
    }
    else if (!aLightNew.IsNull() && aLightNew->Type() != Graphic3d_TypeOfLightSource_Ambient
             && aLightNew->Type() != Graphic3d_TypeOfLightSource_Directional
             && anArgCase == "-range" && anArgIt + 1 < theArgsNb)
    {
      float aRange((float)Atof(theArgVec[++anArgIt]));
      aLightNew->SetRange(aRange);
    }
    else if (!aLightNew.IsNull() && aLightNew->Type() != Graphic3d_TypeOfLightSource_Ambient
             && (anArgCase == "-head" || anArgCase == "-headlight"))
    {
      bool isHeadLight = true;
      if (anArgIt + 1 < theArgsNb && Draw::ParseOnOff(theArgVec[anArgIt + 1], isHeadLight))
      {
        ++anArgIt;
      }
      aLightNew->SetHeadlight(isHeadLight);
    }
    else if (!aLightNew.IsNull() && anArgCase == "-name" && anArgIt + 1 < theArgsNb)
    {
      const TCollection_AsciiString aName = theArgVec[++anArgIt];
      if (aLightNew->Name() == aName)
      {
        continue;
      }

      if (occ::handle<V3d_Light> anOtherLight = findLightSource(aName))
      {
        theDi << "Syntax error: light with name '" << aName << "' is already defined";
        return 1;
      }
      aLightNew->SetName(aName);
    }
    else if (!aLightPrs.IsNull()
             && (anArgCase == "-showzoomable" || anArgCase == "-prszoomable"
                 || anArgCase == "-zoomable"))
    {
      const bool isZoomable = Draw::ParseOnOffIterator(theArgsNb, theArgVec, anArgIt);
      aLightPrs->SetZoomable(isZoomable);
    }
    else if (!aLightPrs.IsNull()
             && (anArgCase == "-showdraggable" || anArgCase == "-prsdraggable"
                 || anArgCase == "-draggable"))
    {
      const bool isDraggable = Draw::ParseOnOffIterator(theArgsNb, theArgVec, anArgIt);
      aLightPrs->SetDraggable(isDraggable);
    }
    else if (!aLightPrs.IsNull() && (anArgCase == "-showname" || anArgCase == "-prsname"))
    {
      const bool toDisplay = Draw::ParseOnOffIterator(theArgsNb, theArgVec, anArgIt);
      aLightPrs->SetDisplayName(toDisplay);
    }
    else if (!aLightPrs.IsNull()
             && (aLightNew->Type() == Graphic3d_TypeOfLightSource_Spot
                 || aLightNew->Type() == Graphic3d_TypeOfLightSource_Positional)
             && (anArgCase == "-showrange" || anArgCase == "-prsrange"))
    {
      const bool toDisplay = Draw::ParseOnOffIterator(theArgsNb, theArgVec, anArgIt);
      aLightPrs->SetDisplayRange(toDisplay);
    }
    else if (!aLightPrs.IsNull() && (anArgCase == "-showsize" || anArgCase == "-prssize")
             && anArgIt + 1 < theArgsNb)
    {
      double aSize = 0.0;
      if (!Draw::ParseReal(theArgVec[++anArgIt], aSize) || aSize <= 0.0 || aLightPrs.IsNull())
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }

      aLightPrs->SetSize(aSize);
    }
    else if (!aLightPrs.IsNull()
             && (anArgCase == "-dirarcsize" || anArgCase == "-arcsize" || anArgCase == "-arc")
             && anArgIt + 1 < theArgsNb)
    {
      int aSize = 0;
      if (!Draw::ParseInteger(theArgVec[anArgIt + 1], aSize) || aSize <= 0
          || aLightPrs->Light()->Type() != Graphic3d_TypeOfLightSource_Directional)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }
      ++anArgIt;
      aLightPrs->SetArcSize(aSize);
    }
    else if (!aLightNew.IsNull() && aLightNew->Type() != Graphic3d_TypeOfLightSource_Ambient
             && (anArgCase == "-castshadow" || anArgCase == "-castshadows"
                 || anArgCase == "-shadows"))
    {
      bool toCastShadows = true;
      if (anArgIt + 1 < theArgsNb && Draw::ParseOnOff(theArgVec[anArgIt + 1], toCastShadows))
      {
        ++anArgIt;
      }
      aLightNew->SetCastShadows(toCastShadows);
    }
    else if (anArgCase == "-del" || anArgCase == "-delete" || anArgCase == "-remove"
             || anArgCase == "del" || anArgCase == "delete" || anArgCase == "remove")
    {
      if (aLightOld.IsNull())
      {
        if (!aLightNew.IsNull())
        {
          aLightNew.Nullify();
          continue;
        }

        if (++anArgIt >= theArgsNb)
        {
          Message::SendFail() << "Syntax error at argument '" << anArg << "'";
          return 1;
        }

        const TCollection_AsciiString anOldName(theArgVec[anArgIt]);
        aLightOld = findLightSource(anOldName);
        if (aLightOld.IsNull())
        {
          Message::SendWarning() << "Warning: light '" << anOldName << "' not found";
          continue;
        }
      }

      aLightNew.Nullify();
      aLightPrs.Nullify();
    }
    else if (anArgCase == "-change" || anArgCase == "change")
    {
      // just skip old syntax
    }
    else if (aLightNew.IsNull() && !anArgCase.StartsWith("-"))
    {
      if (!aLightNew.IsNull())
      {
        continue;
      }

      const TCollection_AsciiString anOldName(theArgVec[anArgIt]);
      aLightOld = findLightSource(anOldName);
      if (!aLightOld.IsNull())
      {
        aLightNew = aLightOld;

        occ::handle<AIS_InteractiveObject> aPrsObject;
        GetMapOfAIS().Find2(aLightOld->Name(), aPrsObject);
        aLightPrs = occ::down_cast<AIS_LightSource>(aPrsObject);
      }
      else
      {
        int aLightIndex = -1;
        Draw::ParseInteger(anOldName.ToCString(), aLightIndex);
        if (aLightIndex != -1)
        {
          Message::SendFail() << "Syntax error: light source with index '" << aLightIndex
                              << "' is not found";
          return 1;
        }

        aLightNew = new V3d_AmbientLight();
        aLightNew->SetName(anOldName);
      }
    }
    else
    {
      Message::SendFail() << "Warning: unknown argument '" << anArg << "'";
      return 1;
    }
  }

  // delete old light source
  if (!aLightOld.IsNull() && aLightOld != aLightNew)
  {
    NCollection_Sequence<int> aLayers;
    aViewer->GetAllZLayers(aLayers);
    for (NCollection_Sequence<int>::Iterator aLayerIter(aLayers); aLayerIter.More();
         aLayerIter.Next())
    {
      if (aLayerIter.Value() == aLayer || aLayer == Graphic3d_ZLayerId_UNKNOWN)
      {
        Graphic3d_ZLayerSettings aSettings = aViewer->ZLayerSettings(aLayerIter.Value());
        if (!aSettings.Lights().IsNull())
        {
          aSettings.Lights()->Remove(aLightOld);
          if (aSettings.Lights()->IsEmpty())
          {
            aSettings.SetLights(occ::handle<Graphic3d_LightSet>());
          }
        }
        aViewer->SetZLayerSettings(aLayerIter.Value(), aSettings);
        if (aLayer != Graphic3d_ZLayerId_UNKNOWN)
        {
          break;
        }
      }
    }

    if (aLayer == Graphic3d_ZLayerId_UNKNOWN)
    {
      occ::handle<AIS_InteractiveObject> aPrsObject;
      GetMapOfAIS().Find2(aLightOld->Name(), aPrsObject);
      if (occ::handle<AIS_LightSource> aLightSourceDel =
            occ::down_cast<AIS_LightSource>(aPrsObject))
      {
        aCtx->Remove(aLightSourceDel, false);
        GetMapOfAIS().UnBind1(aLightSourceDel);
      }
      aViewer->DelLight(aLightOld);
    }
    aLightOld.Nullify();
  }

  // add new light source
  if (!aLightNew.IsNull())
  {
    if (aLayer == Graphic3d_ZLayerId_UNKNOWN)
    {
      aViewer->AddLight(aLightNew);
      if (isGlobal)
      {
        aViewer->SetLightOn(aLightNew);
      }
      else
      {
        aView->SetLightOn(aLightNew);
      }
    }
    else
    {
      Graphic3d_ZLayerSettings aSettings = aViewer->ZLayerSettings(aLayer);
      if (aSettings.Lights().IsNull())
      {
        aSettings.SetLights(new Graphic3d_LightSet());
      }
      aSettings.Lights()->Add(aLightNew);
      aViewer->SetZLayerSettings(aLayer, aSettings);
    }

    if (!aLightPrs.IsNull())
    {
      aLightPrs->SetLight(aLightNew);
      ViewerTest::Display(aLightNew->Name(), aLightPrs, false);
    }
  }

  // manage presentations
  struct LightPrsSort
  {
    bool operator()(const occ::handle<AIS_LightSource>& theLeft,
                    const occ::handle<AIS_LightSource>& theRight)
    {
      return theLeft->Light()->GetId() < theRight->Light()->GetId();
    }
  };

  NCollection_List<occ::handle<AIS_InteractiveObject>> aPrsList;
  aCtx->DisplayedObjects(AIS_KindOfInteractive_LightSource, -1, aPrsList);
  if (!aPrsList.IsEmpty())
  {
    // update light source presentations
    NCollection_LinearVector<occ::handle<AIS_LightSource>> aLightPrsVec;
    for (NCollection_List<occ::handle<AIS_InteractiveObject>>::Iterator aPrsIter(aPrsList);
         aPrsIter.More();
         aPrsIter.Next())
    {
      if (occ::handle<AIS_LightSource> aLightPrs2 =
            occ::down_cast<AIS_LightSource>(aPrsIter.Value()))
      {
        aLightPrsVec.Append(aLightPrs2);
      }
    }

    // sort objects by id as AIS_InteractiveContext stores them in unordered map
    std::sort(aLightPrsVec.begin(), aLightPrsVec.end(), LightPrsSort());

    int aTopStack = 0;
    for (const occ::handle<AIS_LightSource>& aLightPrs2 : aLightPrsVec)
    {
      if (!aLightPrs2->TransformPersistence().IsNull()
          && aLightPrs2->TransformPersistence()->IsTrihedronOr2d())
      {
        const int aPrsSize = (int)aLightPrs2->Size();
        aLightPrs2->TransformPersistence()->SetOffset2d(
          NCollection_Vec2<int>(aTopStack + aPrsSize, aPrsSize));
        aTopStack += aPrsSize + aPrsSize / 2;
      }
      aCtx->Redisplay(aLightPrs2, false);
      aCtx->SetTransformPersistence(aLightPrs2, aLightPrs2->TransformPersistence());
    }
  }

  return 0;
}

//=================================================================================================

static int VPBREnvironment(Draw_Interpretor&, int theArgsNb, const char** theArgVec)
{
  if (theArgsNb > 2)
  {
    Message::SendFail("Syntax error: 'vpbrenv' command has only one argument");
    return 1;
  }

  occ::handle<V3d_View> aView = ViewerTest::CurrentView();
  if (aView.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  TCollection_AsciiString anArg = TCollection_AsciiString(theArgVec[1]);
  anArg.LowerCase();

  if (anArg == "-generate" || anArg == "-gen")
  {
    aView->GeneratePBREnvironment(true);
  }
  else if (anArg == "-clear")
  {
    aView->ClearPBREnvironment(true);
  }
  else
  {
    Message::SendFail() << "Syntax error: unknown argument [" << theArgVec[1]
                        << "] for 'vpbrenv' command";
    return 1;
  }

  return 0;
}

//! Read Graphic3d_RenderingParams::PerfCounters flag.
static bool parsePerfStatsFlag(const TCollection_AsciiString&           theValue,
                               bool&                                    theToReset,
                               Graphic3d_RenderingParams::PerfCounters& theFlagsRem,
                               Graphic3d_RenderingParams::PerfCounters& theFlagsAdd)
{
  Graphic3d_RenderingParams::PerfCounters aFlag     = Graphic3d_RenderingParams::PerfCounters_NONE;
  TCollection_AsciiString                 aVal      = theValue;
  bool                                    toReverse = false;
  if (aVal == "none")
  {
    theToReset = true;
    return true;
  }
  else if (aVal.StartsWith("-"))
  {
    toReverse = true;
    aVal      = aVal.SubString(2, aVal.Length());
  }
  else if (aVal.StartsWith("no"))
  {
    toReverse = true;
    aVal      = aVal.SubString(3, aVal.Length());
  }
  else if (aVal.StartsWith("+"))
  {
    aVal = aVal.SubString(2, aVal.Length());
  }
  else
  {
    theToReset = true;
  }

  if (aVal == "fps" || aVal == "framerate")
  {
    aFlag = Graphic3d_RenderingParams::PerfCounters_FrameRate;
  }
  else if (aVal == "cpu")
  {
    aFlag = Graphic3d_RenderingParams::PerfCounters_CPU;
  }
  else if (aVal == "layers")
  {
    aFlag = Graphic3d_RenderingParams::PerfCounters_Layers;
  }
  else if (aVal == "structs" || aVal == "structures" || aVal == "objects")
  {
    aFlag = Graphic3d_RenderingParams::PerfCounters_Structures;
  }
  else if (aVal == "groups")
  {
    aFlag = Graphic3d_RenderingParams::PerfCounters_Groups;
  }
  else if (aVal == "arrays")
  {
    aFlag = Graphic3d_RenderingParams::PerfCounters_GroupArrays;
  }
  else if (aVal == "tris" || aVal == "triangles")
  {
    aFlag = Graphic3d_RenderingParams::PerfCounters_Triangles;
  }
  else if (aVal == "pnts" || aVal == "points")
  {
    aFlag = Graphic3d_RenderingParams::PerfCounters_Points;
  }
  else if (aVal == "lines")
  {
    aFlag = Graphic3d_RenderingParams::PerfCounters_Lines;
  }
  else if (aVal == "mem" || aVal == "gpumem" || aVal == "estimmem")
  {
    aFlag = Graphic3d_RenderingParams::PerfCounters_EstimMem;
  }
  else if (aVal == "skipimmediate" || aVal == "noimmediate")
  {
    aFlag = Graphic3d_RenderingParams::PerfCounters_SkipImmediate;
  }
  else if (aVal == "frametime" || aVal == "frametimers" || aVal == "time")
  {
    aFlag = Graphic3d_RenderingParams::PerfCounters_FrameTime;
  }
  else if (aVal == "basic")
  {
    aFlag = Graphic3d_RenderingParams::PerfCounters_Basic;
  }
  else if (aVal == "extended" || aVal == "verbose" || aVal == "extra")
  {
    aFlag = Graphic3d_RenderingParams::PerfCounters_Extended;
  }
  else if (aVal == "full" || aVal == "all")
  {
    aFlag = Graphic3d_RenderingParams::PerfCounters_All;
  }
  else
  {
    return false;
  }

  if (toReverse)
  {
    theFlagsRem = Graphic3d_RenderingParams::PerfCounters(theFlagsRem | aFlag);
  }
  else
  {
    theFlagsAdd = Graphic3d_RenderingParams::PerfCounters(theFlagsAdd | aFlag);
  }
  return true;
}

//! Read Graphic3d_RenderingParams::PerfCounters flags.
static bool convertToPerfStatsFlags(const TCollection_AsciiString&           theValue,
                                    Graphic3d_RenderingParams::PerfCounters& theFlags)
{
  TCollection_AsciiString                 aValue    = theValue;
  Graphic3d_RenderingParams::PerfCounters aFlagsRem = Graphic3d_RenderingParams::PerfCounters_NONE;
  Graphic3d_RenderingParams::PerfCounters aFlagsAdd = Graphic3d_RenderingParams::PerfCounters_NONE;
  bool                                    toReset   = false;
  for (;;)
  {
    int aSplitPos = aValue.Search("|");
    if (aSplitPos <= 0)
    {
      if (!parsePerfStatsFlag(aValue, toReset, aFlagsRem, aFlagsAdd))
      {
        return false;
      }
      if (toReset)
      {
        theFlags = Graphic3d_RenderingParams::PerfCounters_NONE;
      }
      theFlags = Graphic3d_RenderingParams::PerfCounters(theFlags | aFlagsAdd);
      theFlags = Graphic3d_RenderingParams::PerfCounters(theFlags & ~aFlagsRem);
      return true;
    }

    if (aSplitPos > 1)
    {
      TCollection_AsciiString aSubValue = aValue.SubString(1, aSplitPos - 1);
      if (!parsePerfStatsFlag(aSubValue, toReset, aFlagsRem, aFlagsAdd))
      {
        return false;
      }
    }
    aValue = aValue.SubString(aSplitPos + 1, aValue.Length());
  }
}

//=======================================================================
// function : VRenderParams
// purpose  : Enables/disables rendering features
//=======================================================================

static int VRenderParams(Draw_Interpretor& theDI, int theArgNb, const char** theArgVec)
{
  occ::handle<V3d_View> aView = ViewerTest::CurrentView();
  if (aView.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  Graphic3d_RenderingParams& aParams = aView->ChangeRenderingParams();
  TCollection_AsciiString    aCmdName(theArgVec[0]);
  aCmdName.LowerCase();
  if (aCmdName == "vraytrace")
  {
    if (theArgNb == 1)
    {
      theDI << (aParams.Method == Graphic3d_RM_RAYTRACING ? "on" : "off") << " ";
      return 0;
    }
    else if (theArgNb == 2)
    {
      TCollection_AsciiString aValue(theArgVec[1]);
      aValue.LowerCase();
      if (aValue == "on" || aValue == "1")
      {
        aParams.Method = Graphic3d_RM_RAYTRACING;
        aView->Redraw();
        return 0;
      }
      else if (aValue == "off" || aValue == "0")
      {
        aParams.Method = Graphic3d_RM_RASTERIZATION;
        aView->Redraw();
        return 0;
      }
      else
      {
        Message::SendFail() << "Syntax error: unknown argument '" << theArgVec[1] << "'";
        return 1;
      }
    }
    else
    {
      Message::SendFail("Syntax error: wrong number of arguments");
      return 1;
    }
  }

  if (theArgNb < 2)
  {
    theDI << "renderMode:  ";
    switch (aParams.Method)
    {
      case Graphic3d_RM_RASTERIZATION:
        theDI << "rasterization ";
        break;
      case Graphic3d_RM_RAYTRACING:
        theDI << "raytrace ";
        break;
    }
    theDI << "\n";
    theDI << "transparency:  ";
    switch (aParams.TransparencyMethod)
    {
      case Graphic3d_RTM_BLEND_UNORDERED:
        theDI << "Basic blended transparency with non-commuting operator ";
        break;
      case Graphic3d_RTM_BLEND_OIT:
        theDI << "Weighted Blended Order-Independent Transparency, depth weight factor: "
              << TCollection_AsciiString(aParams.OitDepthFactor);
        break;
      case Graphic3d_RTM_DEPTH_PEELING_OIT:
        theDI << "Depth Peeling Order-Independent Transparency, Nb.Layers: "
              << TCollection_AsciiString(aParams.NbOitDepthPeelingLayers);
        break;
    }
    theDI << "\n";
    theDI << "msaa:           " << aParams.NbMsaaSamples << "\n";
    theDI << "rendScale:      " << aParams.RenderResolutionScale << "\n";
    theDI << "rayDepth:       " << aParams.RaytracingDepth << "\n";
    theDI << "fsaa:           " << (aParams.IsAntialiasingEnabled ? "on" : "off") << "\n";
    theDI << "shadows:        " << (aParams.IsShadowEnabled ? "on" : "off") << "\n";
    theDI << "shadowMapRes:   " << aParams.ShadowMapResolution << "\n";
    theDI << "shadowMapBias:  " << aParams.ShadowMapBias << "\n";
    theDI << "reflections:    " << (aParams.IsReflectionEnabled ? "on" : "off") << "\n";
    theDI << "gleam:          " << (aParams.IsTransparentShadowEnabled ? "on" : "off") << "\n";
    theDI << "GI:             " << (aParams.IsGlobalIlluminationEnabled ? "on" : "off") << "\n";
    theDI << "blocked RNG:    " << (aParams.CoherentPathTracingMode ? "on" : "off") << "\n";
    theDI << "iss:            " << (aParams.AdaptiveScreenSampling ? "on" : "off") << "\n";
    theDI << "iss debug:      " << (aParams.ShowSamplingTiles ? "on" : "off") << "\n";
    theDI << "two-sided BSDF: " << (aParams.TwoSidedBsdfModels ? "on" : "off") << "\n";
    theDI << "max radiance:   " << aParams.RadianceClampingValue << "\n";
    theDI << "nb tiles (iss): " << aParams.NbRayTracingTiles << "\n";
    theDI << "tile size (iss):" << aParams.RayTracingTileSize << "x" << aParams.RayTracingTileSize
          << "\n";
    theDI << "shadingModel: ";
    switch (aView->ShadingModel())
    {
      case Graphic3d_TypeOfShadingModel_DEFAULT:
        theDI << "default";
        break;
      case Graphic3d_TypeOfShadingModel_Unlit:
        theDI << "unlit";
        break;
      case Graphic3d_TypeOfShadingModel_PhongFacet:
        theDI << "flat";
        break;
      case Graphic3d_TypeOfShadingModel_Gouraud:
        theDI << "gouraud";
        break;
      case Graphic3d_TypeOfShadingModel_Phong:
        theDI << "phong";
        break;
      case Graphic3d_TypeOfShadingModel_Pbr:
        theDI << "pbr";
        break;
      case Graphic3d_TypeOfShadingModel_PbrFacet:
        theDI << "pbr_facet";
        break;
    }
    theDI << "\n";
    {
      theDI << "perfCounters:";
      if ((aParams.CollectedStats & Graphic3d_RenderingParams::PerfCounters_FrameRate) != 0)
      {
        theDI << " fps";
      }
      if ((aParams.CollectedStats & Graphic3d_RenderingParams::PerfCounters_CPU) != 0)
      {
        theDI << " cpu";
      }
      if ((aParams.CollectedStats & Graphic3d_RenderingParams::PerfCounters_Structures) != 0)
      {
        theDI << " structs";
      }
      if ((aParams.CollectedStats & Graphic3d_RenderingParams::PerfCounters_Groups) != 0)
      {
        theDI << " groups";
      }
      if ((aParams.CollectedStats & Graphic3d_RenderingParams::PerfCounters_GroupArrays) != 0)
      {
        theDI << " arrays";
      }
      if ((aParams.CollectedStats & Graphic3d_RenderingParams::PerfCounters_Triangles) != 0)
      {
        theDI << " tris";
      }
      if ((aParams.CollectedStats & Graphic3d_RenderingParams::PerfCounters_Lines) != 0)
      {
        theDI << " lines";
      }
      if ((aParams.CollectedStats & Graphic3d_RenderingParams::PerfCounters_Points) != 0)
      {
        theDI << " pnts";
      }
      if ((aParams.CollectedStats & Graphic3d_RenderingParams::PerfCounters_EstimMem) != 0)
      {
        theDI << " gpumem";
      }
      if ((aParams.CollectedStats & Graphic3d_RenderingParams::PerfCounters_FrameTime) != 0)
      {
        theDI << " frameTime";
      }
      if ((aParams.CollectedStats & Graphic3d_RenderingParams::PerfCounters_SkipImmediate) != 0)
      {
        theDI << " skipimmediate";
      }
      if (aParams.CollectedStats == Graphic3d_RenderingParams::PerfCounters_NONE)
      {
        theDI << " none";
      }
      theDI << "\n";
    }
    theDI << "depth pre-pass: " << (aParams.ToEnableDepthPrepass ? "on" : "off") << "\n";
    theDI << "alpha to coverage: " << (aParams.ToEnableAlphaToCoverage ? "on" : "off") << "\n";
    theDI << "frustum culling: "
          << (aParams.FrustumCullingState == Graphic3d_RenderingParams::FrustumCulling_On ? "on"
              : aParams.FrustumCullingState == Graphic3d_RenderingParams::FrustumCulling_Off
                ? "off"
                : "noUpdate")
          << "\n";
    theDI << "\n";
    return 0;
  }

  bool                   toPrint = false, toSyncDefaults = false, toSyncAllViews = false;
  ViewerTest_AutoUpdater anUpdateTool(ViewerTest::GetAISContext(), aView);
  for (int anArgIter = 1; anArgIter < theArgNb; ++anArgIter)
  {
    const char*             anArg(theArgVec[anArgIter]);
    TCollection_AsciiString aFlag(anArg);
    aFlag.LowerCase();
    if (anUpdateTool.parseRedrawMode(aFlag))
    {
      continue;
    }
    else if (aFlag == "-echo" || aFlag == "-print")
    {
      toPrint = true;
      anUpdateTool.Invalidate();
    }
    else if (aFlag == "-reset")
    {
      aParams = ViewerTest::GetViewerFromContext()->DefaultRenderingParams();
    }
    else if (aFlag == "-sync" && (anArgIter + 1 < theArgNb))
    {
      TCollection_AsciiString aSyncFlag(theArgVec[++anArgIter]);
      aSyncFlag.LowerCase();
      if (aSyncFlag == "default" || aSyncFlag == "defaults" || aSyncFlag == "viewer")
      {
        toSyncDefaults = true;
      }
      else if (aSyncFlag == "allviews" || aSyncFlag == "views")
      {
        toSyncAllViews = true;
      }
      else
      {
        Message::SendFail("Syntax error: unknown parameter to -sync argument");
        return 1;
      }
    }
    else if (aFlag == "-mode" || aFlag == "-rendermode" || aFlag == "-render_mode")
    {
      if (toPrint)
      {
        switch (aParams.Method)
        {
          case Graphic3d_RM_RASTERIZATION:
            theDI << "rasterization ";
            break;
          case Graphic3d_RM_RAYTRACING:
            theDI << "ray-tracing ";
            break;
        }
        continue;
      }
      else
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }
    }
    else if (aFlag == "-ray" || aFlag == "-raytrace")
    {
      if (toPrint)
      {
        theDI << (aParams.Method == Graphic3d_RM_RAYTRACING ? "true" : "false") << " ";
        continue;
      }

      bool isRayTrace = true;
      if (anArgIter + 1 < theArgNb && Draw::ParseOnOff(theArgVec[anArgIter + 1], isRayTrace))
      {
        ++anArgIter;
      }
      aParams.Method = isRayTrace ? Graphic3d_RM_RAYTRACING : Graphic3d_RM_RASTERIZATION;
    }
    else if (aFlag == "-rast" || aFlag == "-raster" || aFlag == "-rasterization")
    {
      if (toPrint)
      {
        theDI << (aParams.Method == Graphic3d_RM_RASTERIZATION ? "true" : "false") << " ";
        continue;
      }

      bool isRaster = true;
      if (anArgIter + 1 < theArgNb && Draw::ParseOnOff(theArgVec[anArgIter + 1], isRaster))
      {
        ++anArgIter;
      }
      aParams.Method = isRaster ? Graphic3d_RM_RASTERIZATION : Graphic3d_RM_RAYTRACING;
    }
    else if (aFlag == "-msaa")
    {
      if (toPrint)
      {
        theDI << aParams.NbMsaaSamples << " ";
        continue;
      }
      else if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }

      const int aNbSamples = Draw::Atoi(theArgVec[anArgIter]);
      if (aNbSamples < 0)
      {
        Message::SendFail() << "Syntax error: invalid number of MSAA samples " << aNbSamples << "";
        return 1;
      }
      else
      {
        aParams.NbMsaaSamples = aNbSamples;
      }
    }
    else if (aFlag == "-linefeather" || aFlag == "-edgefeather" || aFlag == "-feather")
    {
      if (toPrint)
      {
        theDI << " " << aParams.LineFeather << " ";
        continue;
      }
      else if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }

      TCollection_AsciiString aParam   = theArgVec[anArgIter];
      const float             aFeather = (float)Draw::Atof(theArgVec[anArgIter]);
      if (aFeather <= 0.0f)
      {
        Message::SendFail() << "Syntax error: invalid value of line width feather " << aFeather
                            << ". Should be > 0";
        return 1;
      }
      aParams.LineFeather = aFeather;
    }
    else if (aFlag == "-oit")
    {
      if (toPrint)
      {
        if (aParams.TransparencyMethod == Graphic3d_RTM_BLEND_OIT)
        {
          theDI << "on, depth weight factor: " << TCollection_AsciiString(aParams.OitDepthFactor)
                << " ";
        }
        else if (aParams.TransparencyMethod == Graphic3d_RTM_DEPTH_PEELING_OIT)
        {
          theDI << "on, depth peeling layers: "
                << TCollection_AsciiString(aParams.NbOitDepthPeelingLayers) << " ";
        }
        else
        {
          theDI << "off"
                << " ";
        }
        continue;
      }
      else if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }

      TCollection_AsciiString aParam = theArgVec[anArgIter];
      aParam.LowerCase();
      if (aParam == "peeling" || aParam == "peel")
      {
        aParams.TransparencyMethod = Graphic3d_RTM_DEPTH_PEELING_OIT;
        if (anArgIter + 1 < theArgNb
            && TCollection_AsciiString(theArgVec[anArgIter + 1]).IsIntegerValue())
        {
          ++anArgIter;
          const int aNbLayers = TCollection_AsciiString(theArgVec[anArgIter]).IntegerValue();
          if (aNbLayers < 2)
          {
            Message::SendFail()
              << "Syntax error: invalid layers number specified for Depth Peeling OIT "
              << aNbLayers;
            return 1;
          }
          aParams.NbOitDepthPeelingLayers =
            TCollection_AsciiString(theArgVec[anArgIter]).IntegerValue();
        }
      }
      else if (aParam == "weighted" || aParam == "weight")
      {
        aParams.TransparencyMethod = Graphic3d_RTM_BLEND_OIT;
        if (anArgIter + 1 < theArgNb
            && TCollection_AsciiString(theArgVec[anArgIter + 1]).IsRealValue())
        {
          ++anArgIter;
          const float aWeight = (float)TCollection_AsciiString(theArgVec[anArgIter]).RealValue();
          if (aWeight < 0.f || aWeight > 1.f)
          {
            Message::SendFail() << "Syntax error: invalid value of Weighted Order-Independent "
                                   "Transparency depth weight factor "
                                << aWeight << ". Should be within range [0.0; 1.0]";
            return 1;
          }
          aParams.OitDepthFactor = aWeight;
        }
      }
      else if (aParam.IsRealValue())
      {
        const float aWeight = (float)Draw::Atof(theArgVec[anArgIter]);
        if (aWeight < 0.f || aWeight > 1.f)
        {
          Message::SendFail() << "Syntax error: invalid value of Weighted Order-Independent "
                                 "Transparency depth weight factor "
                              << aWeight << ". Should be within range [0.0; 1.0]";
          return 1;
        }

        aParams.TransparencyMethod = Graphic3d_RTM_BLEND_OIT;
        aParams.OitDepthFactor     = aWeight;
      }
      else if (aParam == "off")
      {
        aParams.TransparencyMethod = Graphic3d_RTM_BLEND_UNORDERED;
      }
      else
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }
    }
    else if (aFlag == "-fonthinting" || aFlag == "-fonthint")
    {
      if (toPrint)
      {
        if ((aParams.FontHinting & Font_Hinting_Normal) != 0)
        {
          theDI << "normal"
                << " ";
        }
        else if ((aParams.FontHinting & Font_Hinting_Normal) != 0)
        {
          theDI << "light"
                << " ";
        }
        else
        {
          theDI << "off"
                << " ";
        }
        continue;
      }
      else if (anArgIter + 1 >= theArgNb)
      {
        theDI << "Syntax error at '" << theArgVec[anArgIter] << "'";
        return 1;
      }

      TCollection_AsciiString aHintStyle(theArgVec[++anArgIter]);
      aHintStyle.LowerCase();
      if (aHintStyle == "normal" || aHintStyle == "on" || aHintStyle == "1")
      {
        aParams.FontHinting = Font_Hinting(aParams.FontHinting & ~Font_Hinting_Light);
        aParams.FontHinting = Font_Hinting(aParams.FontHinting | Font_Hinting_Normal);
      }
      else if (aHintStyle == "light")
      {
        aParams.FontHinting = Font_Hinting(aParams.FontHinting & ~Font_Hinting_Normal);
        aParams.FontHinting = Font_Hinting(aParams.FontHinting | Font_Hinting_Light);
      }
      else if (aHintStyle == "no" || aHintStyle == "off" || aHintStyle == "0")
      {
        aParams.FontHinting = Font_Hinting(aParams.FontHinting & ~Font_Hinting_Normal);
        aParams.FontHinting = Font_Hinting(aParams.FontHinting & ~Font_Hinting_Light);
      }
      else
      {
        theDI << "Syntax error at '" << theArgVec[anArgIter] << "'";
        return 1;
      }
    }
    else if (aFlag == "-fontautohinting" || aFlag == "-fontautohint")
    {
      if (toPrint)
      {
        if ((aParams.FontHinting & Font_Hinting_ForceAutohint) != 0)
        {
          theDI << "force"
                << " ";
        }
        else if ((aParams.FontHinting & Font_Hinting_NoAutohint) != 0)
        {
          theDI << "disallow"
                << " ";
        }
        else
        {
          theDI << "auto"
                << " ";
        }
        continue;
      }
      else if (anArgIter + 1 >= theArgNb)
      {
        theDI << "Syntax error at '" << theArgVec[anArgIter] << "'";
        return 1;
      }

      TCollection_AsciiString aHintStyle(theArgVec[++anArgIter]);
      aHintStyle.LowerCase();
      if (aHintStyle == "force")
      {
        aParams.FontHinting = Font_Hinting(aParams.FontHinting & ~Font_Hinting_NoAutohint);
        aParams.FontHinting = Font_Hinting(aParams.FontHinting | Font_Hinting_ForceAutohint);
      }
      else if (aHintStyle == "disallow" || aHintStyle == "no")
      {
        aParams.FontHinting = Font_Hinting(aParams.FontHinting & ~Font_Hinting_ForceAutohint);
        aParams.FontHinting = Font_Hinting(aParams.FontHinting | Font_Hinting_NoAutohint);
      }
      else if (aHintStyle == "auto")
      {
        aParams.FontHinting = Font_Hinting(aParams.FontHinting & ~Font_Hinting_ForceAutohint);
        aParams.FontHinting = Font_Hinting(aParams.FontHinting & ~Font_Hinting_NoAutohint);
      }
      else
      {
        theDI << "Syntax error at '" << theArgVec[anArgIter] << "'";
        return 1;
      }
    }
    else if (aFlag == "-depthprepass")
    {
      if (toPrint)
      {
        theDI << (aParams.ToEnableDepthPrepass ? "on " : "off ");
        continue;
      }
      aParams.ToEnableDepthPrepass = true;
      if (anArgIter + 1 < theArgNb
          && Draw::ParseOnOff(theArgVec[anArgIter + 1], aParams.ToEnableDepthPrepass))
      {
        ++anArgIter;
      }
    }
    else if (aFlag == "-samplealphatocoverage" || aFlag == "-alphatocoverage")
    {
      if (toPrint)
      {
        theDI << (aParams.ToEnableAlphaToCoverage ? "on " : "off ");
        continue;
      }
      aParams.ToEnableAlphaToCoverage = true;
      if (anArgIter + 1 < theArgNb
          && Draw::ParseOnOff(theArgVec[anArgIter + 1], aParams.ToEnableAlphaToCoverage))
      {
        ++anArgIter;
      }
    }
    else if (aFlag == "-flip")
    {
      if (toPrint)
      {
        theDI << (aView->View()->ToFlipOutput() ? "on " : "off ");
        continue;
      }
      bool isToFlipOutput = false;
      if (anArgIter + 1 < theArgNb && Draw::ParseOnOff(theArgVec[anArgIter + 1], isToFlipOutput))
      {
        aView->View()->SetToFlipOutput(isToFlipOutput);
        ++anArgIter;
      }
    }
    else if (aFlag == "-rendscale" || aFlag == "-renderscale" || aFlag == "-renderresolutionscale")
    {
      if (toPrint)
      {
        theDI << aParams.RenderResolutionScale << " ";
        continue;
      }
      else if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }

      const double aScale = Draw::Atof(theArgVec[anArgIter]);
      if (aScale < 0.01)
      {
        Message::SendFail() << "Syntax error: invalid rendering resolution scale " << aScale << "";
        return 1;
      }
      else
      {
        aParams.RenderResolutionScale = float(aScale);
      }
    }
    else if (aFlag == "-raydepth" || aFlag == "-ray_depth")
    {
      if (toPrint)
      {
        theDI << aParams.RaytracingDepth << " ";
        continue;
      }
      else if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }

      const int aDepth = Draw::Atoi(theArgVec[anArgIter]);

      // We allow RaytracingDepth be more than 10 in case of GI enabled
      if (aDepth < 1 || (aDepth > 10 && !aParams.IsGlobalIlluminationEnabled))
      {
        Message::SendFail() << "Syntax error: invalid ray-tracing depth " << aDepth
                            << ". Should be within range [1; 10]";
        return 1;
      }
      else
      {
        aParams.RaytracingDepth = aDepth;
      }
    }
    else if (aFlag == "-shad" || aFlag == "-shadows")
    {
      if (toPrint)
      {
        theDI << (aParams.IsShadowEnabled ? "on" : "off") << " ";
        continue;
      }

      bool toEnable = true;
      if (++anArgIter < theArgNb && !Draw::ParseOnOff(theArgVec[anArgIter], toEnable))
      {
        --anArgIter;
      }
      aParams.IsShadowEnabled = toEnable;
    }
    else if (aFlag == "-shadowmapresolution" || aFlag == "-shadowmap")
    {
      if (toPrint)
      {
        theDI << aParams.ShadowMapResolution << " ";
        continue;
      }
      else if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }

      aParams.ShadowMapResolution = Draw::Atoi(theArgVec[anArgIter]);
    }
    else if (aFlag == "-shadowmapbias")
    {
      if (toPrint)
      {
        theDI << aParams.ShadowMapBias << " ";
        continue;
      }
      else if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }

      aParams.ShadowMapBias = (float)Draw::Atof(theArgVec[anArgIter]);
    }
    else if (aFlag == "-refl" || aFlag == "-reflections")
    {
      if (toPrint)
      {
        theDI << (aParams.IsReflectionEnabled ? "on" : "off") << " ";
        continue;
      }

      bool toEnable = true;
      if (++anArgIter < theArgNb && !Draw::ParseOnOff(theArgVec[anArgIter], toEnable))
      {
        --anArgIter;
      }
      aParams.IsReflectionEnabled = toEnable;
    }
    else if (aFlag == "-fsaa")
    {
      if (toPrint)
      {
        theDI << (aParams.IsAntialiasingEnabled ? "on" : "off") << " ";
        continue;
      }

      bool toEnable = true;
      if (++anArgIter < theArgNb && !Draw::ParseOnOff(theArgVec[anArgIter], toEnable))
      {
        --anArgIter;
      }
      aParams.IsAntialiasingEnabled = toEnable;
    }
    else if (aFlag == "-gleam")
    {
      if (toPrint)
      {
        theDI << (aParams.IsTransparentShadowEnabled ? "on" : "off") << " ";
        continue;
      }

      bool toEnable = true;
      if (++anArgIter < theArgNb && !Draw::ParseOnOff(theArgVec[anArgIter], toEnable))
      {
        --anArgIter;
      }
      aParams.IsTransparentShadowEnabled = toEnable;
    }
    else if (aFlag == "-gi")
    {
      if (toPrint)
      {
        theDI << (aParams.IsGlobalIlluminationEnabled ? "on" : "off") << " ";
        continue;
      }

      bool toEnable = true;
      if (++anArgIter < theArgNb && !Draw::ParseOnOff(theArgVec[anArgIter], toEnable))
      {
        --anArgIter;
      }
      aParams.IsGlobalIlluminationEnabled = toEnable;
      if (!toEnable)
      {
        aParams.RaytracingDepth = std::min(aParams.RaytracingDepth, 10);
      }
    }
    else if (aFlag == "-blockedrng" || aFlag == "-brng")
    {
      if (toPrint)
      {
        theDI << (aParams.CoherentPathTracingMode ? "on" : "off") << " ";
        continue;
      }

      bool toEnable = true;
      if (++anArgIter < theArgNb && !Draw::ParseOnOff(theArgVec[anArgIter], toEnable))
      {
        --anArgIter;
      }
      aParams.CoherentPathTracingMode = toEnable;
    }
    else if (aFlag == "-maxrad")
    {
      if (toPrint)
      {
        theDI << aParams.RadianceClampingValue << " ";
        continue;
      }
      else if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }

      const TCollection_AsciiString aMaxRadStr = theArgVec[anArgIter];
      if (!aMaxRadStr.IsRealValue(true))
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }

      const double aMaxRadiance = aMaxRadStr.RealValue();
      if (aMaxRadiance <= 0.0)
      {
        Message::SendFail() << "Syntax error: invalid radiance clamping value " << aMaxRadiance;
        return 1;
      }
      else
      {
        aParams.RadianceClampingValue = static_cast<float>(aMaxRadiance);
      }
    }
    else if (aFlag == "-iss")
    {
      if (toPrint)
      {
        theDI << (aParams.AdaptiveScreenSampling ? "on" : "off") << " ";
        continue;
      }

      bool toEnable = true;
      if (++anArgIter < theArgNb && !Draw::ParseOnOff(theArgVec[anArgIter], toEnable))
      {
        --anArgIter;
      }
      aParams.AdaptiveScreenSampling = toEnable;
    }
    else if (aFlag == "-issatomic")
    {
      if (toPrint)
      {
        theDI << (aParams.AdaptiveScreenSamplingAtomic ? "on" : "off") << " ";
        continue;
      }

      bool toEnable = true;
      if (++anArgIter < theArgNb && !Draw::ParseOnOff(theArgVec[anArgIter], toEnable))
      {
        --anArgIter;
      }
      aParams.AdaptiveScreenSamplingAtomic = toEnable;
    }
    else if (aFlag == "-issd")
    {
      if (toPrint)
      {
        theDI << (aParams.ShowSamplingTiles ? "on" : "off") << " ";
        continue;
      }

      bool toEnable = true;
      if (++anArgIter < theArgNb && !Draw::ParseOnOff(theArgVec[anArgIter], toEnable))
      {
        --anArgIter;
      }
      aParams.ShowSamplingTiles = toEnable;
    }
    else if (aFlag == "-tilesize")
    {
      if (toPrint)
      {
        theDI << aParams.RayTracingTileSize << " ";
        continue;
      }
      else if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }

      const int aTileSize = Draw::Atoi(theArgVec[anArgIter]);
      if (aTileSize < 1)
      {
        Message::SendFail() << "Syntax error: invalid size of ISS tile " << aTileSize;
        return 1;
      }
      aParams.RayTracingTileSize = aTileSize;
    }
    else if (aFlag == "-nbtiles")
    {
      if (toPrint)
      {
        theDI << aParams.NbRayTracingTiles << " ";
        continue;
      }
      else if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }

      const int aNbTiles = Draw::Atoi(theArgVec[anArgIter]);
      if (aNbTiles < -1)
      {
        Message::SendFail() << "Syntax error: invalid number of ISS tiles " << aNbTiles;
        return 1;
      }
      else if (aNbTiles > 0 && (aNbTiles < 64 || aNbTiles > 1024))
      {
        Message::SendWarning() << "Warning: suboptimal number of ISS tiles " << aNbTiles
                               << ". Recommended range: [64, 1024].";
      }
      aParams.NbRayTracingTiles = aNbTiles;
    }
    else if (aFlag == "-env")
    {
      if (toPrint)
      {
        theDI << (aParams.UseEnvironmentMapBackground ? "on" : "off") << " ";
        continue;
      }

      bool toEnable = true;
      if (++anArgIter < theArgNb && !Draw::ParseOnOff(theArgVec[anArgIter], toEnable))
      {
        --anArgIter;
      }
      aParams.UseEnvironmentMapBackground = toEnable;
    }
    else if (aFlag == "-ignorenormalmap")
    {
      if (toPrint)
      {
        theDI << (aParams.ToIgnoreNormalMapInRayTracing ? "on" : "off") << " ";
        continue;
      }

      bool toEnable = true;
      if (++anArgIter < theArgNb && !Draw::ParseOnOff(theArgVec[anArgIter], toEnable))
      {
        --anArgIter;
      }
      aParams.ToIgnoreNormalMapInRayTracing = toEnable;
    }
    else if (aFlag == "-twoside")
    {
      if (toPrint)
      {
        theDI << (aParams.TwoSidedBsdfModels ? "on" : "off") << " ";
        continue;
      }

      bool toEnable = true;
      if (++anArgIter < theArgNb && !Draw::ParseOnOff(theArgVec[anArgIter], toEnable))
      {
        --anArgIter;
      }
      aParams.TwoSidedBsdfModels = toEnable;
    }
    else if (aFlag == "-shademodel" || aFlag == "-shadingmodel" || aFlag == "-shading")
    {
      if (toPrint)
      {
        switch (aView->ShadingModel())
        {
          case Graphic3d_TypeOfShadingModel_DEFAULT:
            theDI << "default";
            break;
          case Graphic3d_TypeOfShadingModel_Unlit:
            theDI << "unlit ";
            break;
          case Graphic3d_TypeOfShadingModel_PhongFacet:
            theDI << "flat ";
            break;
          case Graphic3d_TypeOfShadingModel_Gouraud:
            theDI << "gouraud ";
            break;
          case Graphic3d_TypeOfShadingModel_Phong:
            theDI << "phong ";
            break;
          case Graphic3d_TypeOfShadingModel_Pbr:
            theDI << "pbr";
            break;
          case Graphic3d_TypeOfShadingModel_PbrFacet:
            theDI << "pbr_facet";
            break;
        }
        continue;
      }

      if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
      }

      Graphic3d_TypeOfShadingModel aModel = Graphic3d_TypeOfShadingModel_DEFAULT;
      if (ViewerTest::ParseShadingModel(theArgVec[anArgIter], aModel)
          && aModel != Graphic3d_TypeOfShadingModel_DEFAULT)
      {
        aView->SetShadingModel(aModel);
      }
      else
      {
        Message::SendFail() << "Syntax error: unknown shading model '" << theArgVec[anArgIter]
                            << "'";
        return 1;
      }
    }
    else if (aFlag == "-pbrenvpow2size" || aFlag == "-pbrenvp2s" || aFlag == "-pep2s")
    {
      if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }

      const int aPbrEnvPow2Size = Draw::Atoi(theArgVec[anArgIter]);
      if (aPbrEnvPow2Size < 1)
      {
        Message::SendFail(
          "Syntax error: 'Pow2Size' of PBR Environment has to be greater or equal 1");
        return 1;
      }
      aParams.PbrEnvPow2Size = aPbrEnvPow2Size;
    }
    else if (aFlag == "-pbrenvspecmaplevelsnumber" || aFlag == "-pbrenvspecmapnblevels"
             || aFlag == "-pbrenvspecmaplevels" || aFlag == "-pbrenvsmln" || aFlag == "-pesmln")
    {
      if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }

      const int aPbrEnvSpecMapNbLevels = Draw::Atoi(theArgVec[anArgIter]);
      if (aPbrEnvSpecMapNbLevels < 2)
      {
        Message::SendFail(
          "Syntax error: 'SpecMapLevelsNumber' of PBR Environment has to be greater or equal 2");
        return 1;
      }
      aParams.PbrEnvSpecMapNbLevels = aPbrEnvSpecMapNbLevels;
    }
    else if (aFlag == "-pbrenvbakngdiffsamplesnumber" || aFlag == "-pbrenvbakingdiffsamples"
             || aFlag == "-pbrenvbdsn")
    {
      if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }

      const int aPbrEnvBakingDiffNbSamples = Draw::Atoi(theArgVec[anArgIter]);
      if (aPbrEnvBakingDiffNbSamples < 1)
      {
        Message::SendFail("Syntax error: 'BakingDiffSamplesNumber' of PBR Environment has to be "
                          "greater or equal 1");
        return 1;
      }
      aParams.PbrEnvBakingDiffNbSamples = aPbrEnvBakingDiffNbSamples;
    }
    else if (aFlag == "-pbrenvbakngspecsamplesnumber" || aFlag == "-pbrenvbakingspecsamples"
             || aFlag == "-pbrenvbssn")
    {
      if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }

      const int aPbrEnvBakingSpecNbSamples = Draw::Atoi(theArgVec[anArgIter]);
      if (aPbrEnvBakingSpecNbSamples < 1)
      {
        Message::SendFail("Syntax error: 'BakingSpecSamplesNumber' of PBR Environment has to be "
                          "greater or equal 1");
        return 1;
      }
      aParams.PbrEnvBakingSpecNbSamples = aPbrEnvBakingSpecNbSamples;
    }
    else if (aFlag == "-pbrenvbakingprobability" || aFlag == "-pbrenvbp")
    {
      if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }
      const float aPbrEnvBakingProbability = static_cast<float>(Draw::Atof(theArgVec[anArgIter]));
      if (aPbrEnvBakingProbability < 0.f || aPbrEnvBakingProbability > 1.f)
      {
        Message::SendFail(
          "Syntax error: 'BakingProbability' of PBR Environment has to be in range of [0, 1]");
        return 1;
      }
      aParams.PbrEnvBakingProbability = aPbrEnvBakingProbability;
    }
    else if (aFlag == "-resolution")
    {
      if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }

      TCollection_AsciiString aResolution(theArgVec[anArgIter]);
      if (aResolution.IsIntegerValue())
      {
        aView->ChangeRenderingParams().Resolution =
          static_cast<unsigned int>(Draw::Atoi(aResolution.ToCString()));
      }
      else
      {
        Message::SendFail() << "Syntax error: wrong syntax at argument'" << anArg << "'";
        return 1;
      }
    }
    else if (aFlag == "-rebuildglsl" || aFlag == "-rebuild")
    {
      if (toPrint)
      {
        theDI << (aParams.RebuildRayTracingShaders ? "on" : "off") << " ";
        continue;
      }

      bool toEnable = true;
      if (++anArgIter < theArgNb && !Draw::ParseOnOff(theArgVec[anArgIter], toEnable))
      {
        --anArgIter;
      }
      aParams.RebuildRayTracingShaders = toEnable;
    }
    else if (aFlag == "-focal")
    {
      if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }

      TCollection_AsciiString aParam(theArgVec[anArgIter]);
      if (aParam.IsRealValue(true))
      {
        float aFocalDist = static_cast<float>(aParam.RealValue());
        if (aFocalDist < 0)
        {
          Message::SendFail() << "Error: parameter can't be negative at argument '" << anArg << "'";
          return 1;
        }
        aView->ChangeRenderingParams().CameraFocalPlaneDist = aFocalDist;
      }
      else
      {
        Message::SendFail() << "Syntax error at argument'" << anArg << "'";
        return 1;
      }
    }
    else if (aFlag == "-aperture")
    {
      if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }

      TCollection_AsciiString aParam(theArgVec[anArgIter]);
      if (aParam.IsRealValue(true))
      {
        float aApertureSize = static_cast<float>(aParam.RealValue());
        if (aApertureSize < 0)
        {
          Message::SendFail() << "Error: parameter can't be negative at argument '" << anArg << "'";
          return 1;
        }
        aView->ChangeRenderingParams().CameraApertureRadius = aApertureSize;
      }
      else
      {
        Message::SendFail() << "Syntax error at argument'" << anArg << "'";
        return 1;
      }
    }
    else if (aFlag == "-exposure")
    {
      if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }

      TCollection_AsciiString anExposure(theArgVec[anArgIter]);
      if (anExposure.IsRealValue(true))
      {
        aView->ChangeRenderingParams().Exposure = static_cast<float>(anExposure.RealValue());
      }
      else
      {
        Message::SendFail() << "Syntax error at argument'" << anArg << "'";
        return 1;
      }
    }
    else if (aFlag == "-whitepoint")
    {
      if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }

      TCollection_AsciiString aWhitePoint(theArgVec[anArgIter]);
      if (aWhitePoint.IsRealValue(true))
      {
        aView->ChangeRenderingParams().WhitePoint = static_cast<float>(aWhitePoint.RealValue());
      }
      else
      {
        Message::SendFail() << "Syntax error at argument'" << anArg << "'";
        return 1;
      }
    }
    else if (aFlag == "-tonemapping")
    {
      if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }

      TCollection_AsciiString aMode(theArgVec[anArgIter]);
      aMode.LowerCase();

      if (aMode == "disabled")
      {
        aView->ChangeRenderingParams().ToneMappingMethod = Graphic3d_ToneMappingMethod_Disabled;
      }
      else if (aMode == "filmic")
      {
        aView->ChangeRenderingParams().ToneMappingMethod = Graphic3d_ToneMappingMethod_Filmic;
      }
      else
      {
        Message::SendFail() << "Syntax error at argument'" << anArg << "'";
        return 1;
      }
    }
    else if (aFlag == "-performancestats" || aFlag == "-performancecounters"
             || aFlag == "-perfstats" || aFlag == "-perfcounters" || aFlag == "-stats")
    {
      if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }

      TCollection_AsciiString aFlagsStr(theArgVec[anArgIter]);
      aFlagsStr.LowerCase();
      Graphic3d_RenderingParams::PerfCounters aFlags =
        aView->ChangeRenderingParams().CollectedStats;
      if (!convertToPerfStatsFlags(aFlagsStr, aFlags))
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }
      aView->ChangeRenderingParams().CollectedStats = aFlags;
      aView->ChangeRenderingParams().ToShowStats =
        aFlags != Graphic3d_RenderingParams::PerfCounters_NONE;
    }
    else if (aFlag == "-perfupdateinterval" || aFlag == "-statsupdateinterval")
    {
      if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }
      aView->ChangeRenderingParams().StatsUpdateInterval = (float)Draw::Atof(theArgVec[anArgIter]);
    }
    else if (aFlag == "-perfchart" || aFlag == "-statschart")
    {
      if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }
      aView->ChangeRenderingParams().StatsNbFrames = Draw::Atoi(theArgVec[anArgIter]);
    }
    else if (aFlag == "-perfchartmax" || aFlag == "-statschartmax")
    {
      if (++anArgIter >= theArgNb)
      {
        Message::SendFail() << "Syntax error at argument '" << anArg << "'";
        return 1;
      }
      aView->ChangeRenderingParams().StatsMaxChartTime = (float)Draw::Atof(theArgVec[anArgIter]);
    }
    else if (aFlag == "-frustumculling" || aFlag == "-culling")
    {
      if (toPrint)
      {
        theDI << ((aParams.FrustumCullingState == Graphic3d_RenderingParams::FrustumCulling_On)
                    ? "on"
                  : (aParams.FrustumCullingState == Graphic3d_RenderingParams::FrustumCulling_Off)
                    ? "off"
                    : "noUpdate")
              << " ";
        continue;
      }

      Graphic3d_RenderingParams::FrustumCulling aState =
        Graphic3d_RenderingParams::FrustumCulling_On;
      if (++anArgIter < theArgNb)
      {
        TCollection_AsciiString aStateStr(theArgVec[anArgIter]);
        aStateStr.LowerCase();
        bool toEnable = true;
        if (Draw::ParseOnOff(aStateStr.ToCString(), toEnable))
        {
          aState = toEnable ? Graphic3d_RenderingParams::FrustumCulling_On
                            : Graphic3d_RenderingParams::FrustumCulling_Off;
        }
        else if (aStateStr == "noupdate" || aStateStr == "freeze")
        {
          aState = Graphic3d_RenderingParams::FrustumCulling_NoUpdate;
        }
        else
        {
          --anArgIter;
        }
      }
      aParams.FrustumCullingState = aState;
    }
    else
    {
      Message::SendFail() << "Syntax error: unknown flag '" << anArg << "'";
      return 1;
    }
  }

  // set current view parameters as defaults
  if (toSyncDefaults)
  {
    ViewerTest::GetViewerFromContext()->SetDefaultRenderingParams(aParams);
  }
  if (toSyncAllViews)
  {
    for (NCollection_List<occ::handle<V3d_View>>::Iterator aViewIter =
           ViewerTest::GetViewerFromContext()->DefinedViewIterator();
         aViewIter.More();
         aViewIter.Next())
    {
      aViewIter.Value()->ChangeRenderingParams() = aParams;
    }
  }
  return 0;
}

//=================================================================================================

inline TCollection_AsciiString searchInfo(
  const NCollection_IndexedDataMap<TCollection_AsciiString, TCollection_AsciiString>& theDict,
  const TCollection_AsciiString&                                                      theKey)
{
  for (NCollection_IndexedDataMap<TCollection_AsciiString, TCollection_AsciiString>::Iterator
         anIter(theDict);
       anIter.More();
       anIter.Next())
  {
    if (TCollection_AsciiString::IsSameString(anIter.Key(), theKey, false))
    {
      return anIter.Value();
    }
  }
  return TCollection_AsciiString();
}

//=================================================================================================

static int VStatProfiler(Draw_Interpretor& theDI, int theArgNb, const char** theArgVec)
{
  occ::handle<V3d_View> aView = ViewerTest::CurrentView();
  if (aView.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  bool                                    toRedraw = true;
  Graphic3d_RenderingParams::PerfCounters aPrevCounters =
    aView->ChangeRenderingParams().CollectedStats;
  float aPrevUpdInterval = aView->ChangeRenderingParams().StatsUpdateInterval;
  Graphic3d_RenderingParams::PerfCounters aRenderParams =
    Graphic3d_RenderingParams::PerfCounters_NONE;
  for (int anArgIter = 1; anArgIter < theArgNb; ++anArgIter)
  {
    const char*             anArg(theArgVec[anArgIter]);
    TCollection_AsciiString aFlag(anArg);
    aFlag.LowerCase();
    if (aFlag == "-noredraw")
    {
      toRedraw = false;
    }
    else
    {
      Graphic3d_RenderingParams::PerfCounters aParam = Graphic3d_RenderingParams::PerfCounters_NONE;
      if (aFlag == "fps")
      {
        aParam = Graphic3d_RenderingParams::PerfCounters_FrameRate;
      }
      else if (aFlag == "cpu")
      {
        aParam = Graphic3d_RenderingParams::PerfCounters_CPU;
      }
      else if (aFlag == "alllayers" || aFlag == "layers")
      {
        aParam = Graphic3d_RenderingParams::PerfCounters_Layers;
      }
      else if (aFlag == "allstructs" || aFlag == "allstructures" || aFlag == "structs"
               || aFlag == "structures")
      {
        aParam = Graphic3d_RenderingParams::PerfCounters_Structures;
      }
      else if (aFlag == "groups")
      {
        aParam = Graphic3d_RenderingParams::PerfCounters_Groups;
      }
      else if (aFlag == "allarrays" || aFlag == "fillarrays" || aFlag == "linearrays"
               || aFlag == "pointarrays" || aFlag == "textarrays")
      {
        aParam = Graphic3d_RenderingParams::PerfCounters_GroupArrays;
      }
      else if (aFlag == "triangles")
      {
        aParam = Graphic3d_RenderingParams::PerfCounters_Triangles;
      }
      else if (aFlag == "lines")
      {
        aParam = Graphic3d_RenderingParams::PerfCounters_Lines;
      }
      else if (aFlag == "points")
      {
        aParam = Graphic3d_RenderingParams::PerfCounters_Points;
      }
      else if (aFlag == "geommem" || aFlag == "texturemem" || aFlag == "framemem")
      {
        aParam = Graphic3d_RenderingParams::PerfCounters_EstimMem;
      }
      else if (aFlag == "elapsedframe" || aFlag == "cpuframeaverage" || aFlag == "cpupickingaverage"
               || aFlag == "cpucullingaverage" || aFlag == "cpudynaverage" || aFlag == "cpuframemax"
               || aFlag == "cpupickingmax" || aFlag == "cpucullingmax" || aFlag == "cpudynmax")
      {
        aParam = Graphic3d_RenderingParams::PerfCounters_FrameTime;
      }
      else
      {
        Message::SendFail() << "Error: unknown argument '" << theArgVec[anArgIter] << "'";
        continue;
      }

      aRenderParams = Graphic3d_RenderingParams::PerfCounters(aRenderParams | aParam);
    }
  }

  if (aRenderParams != Graphic3d_RenderingParams::PerfCounters_NONE)
  {
    aView->ChangeRenderingParams().CollectedStats = Graphic3d_RenderingParams::PerfCounters(
      aView->RenderingParams().CollectedStats | aRenderParams);

    if (toRedraw)
    {
      aView->ChangeRenderingParams().StatsUpdateInterval = -1;
      aView->Redraw();
      aView->ChangeRenderingParams().StatsUpdateInterval = aPrevUpdInterval;
    }

    NCollection_IndexedDataMap<TCollection_AsciiString, TCollection_AsciiString> aDict;
    aView->StatisticInformation(aDict);

    aView->ChangeRenderingParams().CollectedStats = aPrevCounters;

    for (int anArgIter = 1; anArgIter < theArgNb; ++anArgIter)
    {
      const char*             anArg(theArgVec[anArgIter]);
      TCollection_AsciiString aFlag(anArg);
      aFlag.LowerCase();
      if (aFlag == "fps")
      {
        theDI << searchInfo(aDict, "FPS") << " ";
      }
      else if (aFlag == "cpu")
      {
        theDI << searchInfo(aDict, "CPU FPS") << " ";
      }
      else if (aFlag == "alllayers")
      {
        theDI << searchInfo(aDict, "Layers") << " ";
      }
      else if (aFlag == "layers")
      {
        theDI << searchInfo(aDict, "Rendered layers") << " ";
      }
      else if (aFlag == "allstructs" || aFlag == "allstructures")
      {
        theDI << searchInfo(aDict, "Structs") << " ";
      }
      else if (aFlag == "structs" || aFlag == "structures")
      {
        TCollection_AsciiString aRend = searchInfo(aDict, "Rendered structs");
        if (aRend.IsEmpty()) // all structures rendered
        {
          aRend = searchInfo(aDict, "Structs");
        }
        theDI << aRend << " ";
      }
      else if (aFlag == "groups")
      {
        theDI << searchInfo(aDict, "Rendered groups") << " ";
      }
      else if (aFlag == "allarrays")
      {
        theDI << searchInfo(aDict, "Rendered arrays") << " ";
      }
      else if (aFlag == "fillarrays")
      {
        theDI << searchInfo(aDict, "Rendered [fill] arrays") << " ";
      }
      else if (aFlag == "linearrays")
      {
        theDI << searchInfo(aDict, "Rendered [line] arrays") << " ";
      }
      else if (aFlag == "pointarrays")
      {
        theDI << searchInfo(aDict, "Rendered [point] arrays") << " ";
      }
      else if (aFlag == "textarrays")
      {
        theDI << searchInfo(aDict, "Rendered [text] arrays") << " ";
      }
      else if (aFlag == "triangles")
      {
        theDI << searchInfo(aDict, "Rendered triangles") << " ";
      }
      else if (aFlag == "points")
      {
        theDI << searchInfo(aDict, "Rendered points") << " ";
      }
      else if (aFlag == "geommem")
      {
        theDI << searchInfo(aDict, "GPU Memory [geometry]") << " ";
      }
      else if (aFlag == "texturemem")
      {
        theDI << searchInfo(aDict, "GPU Memory [textures]") << " ";
      }
      else if (aFlag == "framemem")
      {
        theDI << searchInfo(aDict, "GPU Memory [frames]") << " ";
      }
      else if (aFlag == "elapsedframe")
      {
        theDI << searchInfo(aDict, "Elapsed Frame (average)") << " ";
      }
      else if (aFlag == "cpuframe_average")
      {
        theDI << searchInfo(aDict, "CPU Frame (average)") << " ";
      }
      else if (aFlag == "cpupicking_average")
      {
        theDI << searchInfo(aDict, "CPU Picking (average)") << " ";
      }
      else if (aFlag == "cpuculling_average")
      {
        theDI << searchInfo(aDict, "CPU Culling (average)") << " ";
      }
      else if (aFlag == "cpudyn_average")
      {
        theDI << searchInfo(aDict, "CPU Dynamics (average)") << " ";
      }
      else if (aFlag == "cpuframe_max")
      {
        theDI << searchInfo(aDict, "CPU Frame (max)") << " ";
      }
      else if (aFlag == "cpupicking_max")
      {
        theDI << searchInfo(aDict, "CPU Picking (max)") << " ";
      }
      else if (aFlag == "cpuculling_max")
      {
        theDI << searchInfo(aDict, "CPU Culling (max)") << " ";
      }
      else if (aFlag == "cpudyn_max")
      {
        theDI << searchInfo(aDict, "CPU Dynamics (max)") << " ";
      }
    }
  }
  else
  {
    if (toRedraw)
    {
      aView->ChangeRenderingParams().StatsUpdateInterval = -1;
      aView->Redraw();
      aView->ChangeRenderingParams().StatsUpdateInterval = aPrevUpdInterval;
    }
    theDI << "Statistic info:\n" << aView->StatisticInformation();
  }
  return 0;
}

//=================================================================================================

static int VXRotate(Draw_Interpretor& di, int argc, const char** argv)
{
  occ::handle<AIS_InteractiveContext> aContext = ViewerTest::GetAISContext();
  if (aContext.IsNull())
  {
    di << argv[0] << "ERROR : use 'vinit' command before \n";
    return 1;
  }

  if (argc != 3)
  {
    di << "ERROR : Usage : " << argv[0] << " name angle\n";
    return 1;
  }

  TCollection_AsciiString aName(argv[1]);
  double                  anAngle = Draw::Atof(argv[2]);

  // find object
  NCollection_DoubleMap<occ::handle<AIS_InteractiveObject>, TCollection_AsciiString>& aMap =
    GetMapOfAIS();
  occ::handle<AIS_InteractiveObject> anIObj;
  if (!aMap.Find2(aName, anIObj))
  {
    di << "Use 'vdisplay' before\n";
    return 1;
  }

  gp_Trsf aTransform;
  aTransform.SetRotation(gp_Ax1(gp_Pnt(0.0, 0.0, 0.0), gp_Vec(1.0, 0.0, 0.0)), anAngle);
  aTransform.SetTranslationPart(anIObj->LocalTransformation().TranslationPart());

  aContext->SetLocation(anIObj, aTransform);
  aContext->UpdateCurrentViewer();
  return 0;
}

namespace
{
//! Structure for setting AIS_Manipulator::SetPart() property.
struct ManipAxisModeOnOff
{
  int                 Axis;
  AIS_ManipulatorMode Mode;
  bool                ToEnable;

  ManipAxisModeOnOff()
      : Axis(-1),
        Mode(AIS_MM_None),
        ToEnable(false)
  {
  }
};

enum ManipAjustPosition
{
  ManipAjustPosition_Off,
  ManipAjustPosition_Center,
  ManipAjustPosition_Location,
  ManipAjustPosition_ShapeLocation,
};
} // namespace

//=================================================================================================

static int VManipulator(Draw_Interpretor& theDi, int theArgsNb, const char** theArgVec)
{
  occ::handle<V3d_View>   aCurrentView = ViewerTest::CurrentView();
  occ::handle<V3d_Viewer> aViewer      = ViewerTest::GetViewerFromContext();
  if (aCurrentView.IsNull() || aViewer.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  ViewerTest_AutoUpdater       anUpdateTool(ViewerTest::GetAISContext(), ViewerTest::CurrentView());
  int                          anArgIter = 1;
  occ::handle<AIS_Manipulator> aManipulator;
  NCollection_DoubleMap<occ::handle<AIS_InteractiveObject>, TCollection_AsciiString>& aMapAIS =
    GetMapOfAIS();
  TCollection_AsciiString aName;
  // parameters
  int toAutoActivate = -1, toFollowTranslation = -1, toFollowRotation = -1, toFollowDragging = -1,
      isZoomable = -1, isFlat = -1;
  double                                   aGap = -1.0, aSize = -1.0;
  NCollection_Sequence<ManipAxisModeOnOff> aParts;
  gp_XYZ aLocation(RealLast(), RealLast(), RealLast()), aVDir, anXDir;
  //
  bool                               toDetach    = false;
  bool                               toAddObject = false;
  AIS_Manipulator::OptionsForAttach  anAttachOptions;
  occ::handle<AIS_InteractiveObject> anAttachObject;
  occ::handle<V3d_View>              aViewAffinity;
  ManipAjustPosition                 anAttachPos = ManipAjustPosition_Off;
  //
  NCollection_Vec2<int> aMousePosFrom(IntegerLast(), IntegerLast());
  NCollection_Vec2<int> aMousePosTo(IntegerLast(), IntegerLast());
  int                   toStopMouseTransform = -1;
  // explicit transformation
  gp_Trsf aTrsf;
  gp_XYZ  aTmpXYZ;
  double  aTmpReal = 0.0;
  gp_XYZ  aRotPnt, aRotAxis;
  for (; anArgIter < theArgsNb; ++anArgIter)
  {
    TCollection_AsciiString anArg(theArgVec[anArgIter]);
    anArg.LowerCase();
    if (anUpdateTool.parseRedrawMode(anArg))
    {
      continue;
    }
    else if (anArg == "-help")
    {
      theDi.PrintHelp(theArgVec[0]);
      return 0;
    }
    //
    else if (anArg == "-autoactivate" || anArg == "-noautoactivate")
    {
      toAutoActivate = Draw::ParseOnOffNoIterator(theArgsNb, theArgVec, anArgIter) ? 1 : 0;
    }
    else if (anArg == "-followtranslation" || anArg == "-nofollowtranslation")
    {
      toFollowTranslation = Draw::ParseOnOffNoIterator(theArgsNb, theArgVec, anArgIter) ? 1 : 0;
    }
    else if (anArg == "-followrotation" || anArg == "-nofollowrotation")
    {
      toFollowRotation = Draw::ParseOnOffNoIterator(theArgsNb, theArgVec, anArgIter) ? 1 : 0;
    }
    else if (anArg == "-followdragging" || anArg == "-nofollowdragging")
    {
      toFollowDragging = Draw::ParseOnOffNoIterator(theArgsNb, theArgVec, anArgIter) ? 1 : 0;
    }
    else if (anArg == "-gap" && anArgIter + 1 < theArgsNb
             && Draw::ParseReal(theArgVec[anArgIter + 1], aGap) && aGap >= 0.0)
    {
      ++anArgIter;
    }
    else if (anArg == "-size" && anArgIter + 1 < theArgsNb
             && Draw::ParseReal(theArgVec[anArgIter + 1], aSize) && aSize > 0.0)
    {
      ++anArgIter;
    }
    else if ((anArg == "-part" && anArgIter + 3 < theArgsNb)
             || (anArg == "-parts" && anArgIter + 2 < theArgsNb))
    {
      ManipAxisModeOnOff aPart;
      int                aMode = 0;
      if (anArg == "-part")
      {
        if (!Draw::ParseInteger(theArgVec[++anArgIter], aPart.Axis) || aPart.Axis < 0
            || aPart.Axis > 3)
        {
          Message::SendFail() << "Syntax error: -part axis '" << theArgVec[anArgIter]
                              << "' is out of range [1, 3]";
          return 1;
        }
      }
      if (!Draw::ParseInteger(theArgVec[++anArgIter], aMode) || aMode < 1 || aMode > 4)
      {
        Message::SendFail() << "Syntax error: -part mode '" << theArgVec[anArgIter]
                            << "' is out of range [1, 4]";
        return 1;
      }
      if (!Draw::ParseOnOff(theArgVec[++anArgIter], aPart.ToEnable))
      {
        Message::SendFail() << "Syntax error: -part value on/off '" << theArgVec[anArgIter]
                            << "' is incorrect";
        return 1;
      }
      aPart.Mode = static_cast<AIS_ManipulatorMode>(aMode);
      aParts.Append(aPart);
    }
    else if (anArg == "-pos" && anArgIter + 3 < theArgsNb
             && parseXYZ(theArgVec + anArgIter + 1, aLocation))
    {
      anArgIter += 3;
      if (anArgIter + 3 < theArgsNb && parseXYZ(theArgVec + anArgIter + 1, aVDir)
          && aVDir.Modulus() > Precision::Confusion())
      {
        anArgIter += 3;
      }
      if (anArgIter + 3 < theArgsNb && parseXYZ(theArgVec + anArgIter + 1, anXDir)
          && anXDir.Modulus() > Precision::Confusion())
      {
        anArgIter += 3;
      }
    }
    else if (anArg == "-zoomable" || anArg == "-notzoomable")
    {
      isZoomable = Draw::ParseOnOffNoIterator(theArgsNb, theArgVec, anArgIter) ? 1 : 0;
    }
    else if (anArg == "-flat")
    {
      isFlat = Draw::ParseOnOffNoIterator(theArgsNb, theArgVec, anArgIter) ? 1 : 0;
    }
    //
    else if (anArg == "-adjustposition" || anArg == "-noadjustposition")
    {
      anAttachPos = ManipAjustPosition_Center;
      if (anArgIter + 1 < theArgsNb)
      {
        TCollection_AsciiString aPosName(theArgVec[++anArgIter]);
        aPosName.LowerCase();
        if (aPosName == "0")
        {
          anAttachPos = ManipAjustPosition_Off;
        }
        else if (aPosName == "1" || aPosName == "center")
        {
          anAttachPos = ManipAjustPosition_Center;
        }
        else if (aPosName == "transformation" || aPosName == "trsf" || aPosName == "location"
                 || aPosName == "loc")
        {
          anAttachPos = ManipAjustPosition_Location;
        }
        else if (aPosName == "shapelocation" || aPosName == "shapeloc")
        {
          anAttachPos = ManipAjustPosition_ShapeLocation;
        }
        else
        {
          --anArgIter;
        }
      }
      anAttachOptions.SetAdjustPosition(anAttachPos == ManipAjustPosition_Center);
    }
    else if (anArg == "-adjustsize" || anArg == "-noadjustsize")
    {
      anAttachOptions.SetAdjustSize(Draw::ParseOnOffNoIterator(theArgsNb, theArgVec, anArgIter));
    }
    else if (anArg == "-enablemodes")
    {
      anAttachOptions.SetEnableModes(Draw::ParseOnOffNoIterator(theArgsNb, theArgVec, anArgIter));
    }
    //
    else if (anArg == "-starttransform" && anArgIter + 2 < theArgsNb
             && Draw::ParseInteger(theArgVec[anArgIter + 1], aMousePosFrom.x())
             && Draw::ParseInteger(theArgVec[anArgIter + 2], aMousePosFrom.y()))
    {
      anArgIter += 2;
    }
    else if (anArg == "-transform" && anArgIter + 2 < theArgsNb
             && Draw::ParseInteger(theArgVec[anArgIter + 1], aMousePosTo.x())
             && Draw::ParseInteger(theArgVec[anArgIter + 2], aMousePosTo.y()))
    {
      anArgIter += 2;
    }
    else if (anArg == "-stoptransform")
    {
      toStopMouseTransform = 1;
      if (anArgIter + 1 < theArgsNb
          && TCollection_AsciiString::IsSameString(theArgVec[anArgIter + 1], "abort", false))
      {
        ++anArgIter;
        toStopMouseTransform = 0;
      }
    }
    //
    else if (anArg == "-move" && anArgIter + 3 < theArgsNb
             && parseXYZ(theArgVec + anArgIter + 1, aTmpXYZ))
    {
      anArgIter += 3;
      aTrsf.SetTranslationPart(aTmpXYZ);
    }
    else if (anArg == "-scale" && anArgIter + 1 < theArgsNb
             && Draw::ParseReal(theArgVec[anArgIter + 1], aTmpReal))
    {
      ++anArgIter;
      aTrsf.SetScale(gp_Pnt(), aTmpReal);
    }
    else if (anArg == "-rotate" && anArgIter + 7 < theArgsNb
             && parseXYZ(theArgVec + anArgIter + 1, aRotPnt)
             && parseXYZ(theArgVec + anArgIter + 4, aRotAxis)
             && Draw::ParseReal(theArgVec[anArgIter + 7], aTmpReal))
    {
      anArgIter += 7;
      aTrsf.SetRotation(gp_Ax1(gp_Pnt(aRotPnt), gp_Dir(aRotAxis)), aTmpReal);
    }
    //
    else if (anArg == "-addobject")
    {
      toAddObject = true;
    }
    else if (anArg == "-detach")
    {
      toDetach = true;
    }
    else if (anArg == "-attach" && anArgIter + 1 < theArgsNb)
    {
      TCollection_AsciiString anObjName(theArgVec[++anArgIter]);
      if (!aMapAIS.Find2(anObjName, anAttachObject))
      {
        Message::SendFail() << "Syntax error: AIS object '" << anObjName << "' does not exist";
        return 1;
      }

      for (NCollection_DoubleMap<occ::handle<AIS_InteractiveObject>,
                                 TCollection_AsciiString>::Iterator anIter(aMapAIS);
           anIter.More();
           anIter.Next())
      {
        occ::handle<AIS_Manipulator> aManip = occ::down_cast<AIS_Manipulator>(anIter.Key1());
        if (!aManip.IsNull() && aManip->IsAttached() && aManip->Object() == anAttachObject)
        {
          Message::SendFail() << "Syntax error: AIS object '" << anObjName
                              << "' already has manipulator";
          return 1;
        }
      }
    }
    else if (anArg == "-view" && anArgIter + 1 < theArgsNb && aViewAffinity.IsNull())
    {
      TCollection_AsciiString aViewString(theArgVec[++anArgIter]);
      if (aViewString == "active")
      {
        aViewAffinity = ViewerTest::CurrentView();
      }
      else // Check view name
      {
        ViewerTest_Names aViewNames(aViewString);
        if (!ViewerTest_myViews.IsBound1(aViewNames.GetViewName()))
        {
          Message::SendFail() << "Syntax error: wrong view name '" << aViewString << "'";
          return 1;
        }
        aViewAffinity = ViewerTest_myViews.Find1(aViewNames.GetViewName());
        if (aViewAffinity.IsNull())
        {
          Message::SendFail() << "Syntax error: cannot find view with name '" << aViewString << "'";
          return 1;
        }
      }
    }
    else if (aName.IsEmpty())
    {
      aName = theArgVec[anArgIter];
      if (!aMapAIS.IsBound2(aName))
      {
        aManipulator = new AIS_Manipulator();
        aManipulator->SetModeActivationOnDetection(true);
        aMapAIS.Bind(aManipulator, aName);
      }
      else
      {
        aManipulator = occ::down_cast<AIS_Manipulator>(aMapAIS.Find2(aName));
        if (aManipulator.IsNull())
        {
          Message::SendFail() << "Syntax error: \"" << aName << "\" is not an AIS manipulator";
          return 1;
        }
      }
    }
    else
    {
      theDi << "Syntax error: unknown argument '" << theArgVec[anArgIter] << "'";
    }
  }

  if (aName.IsEmpty())
  {
    Message::SendFail("Syntax error: please specify AIS manipulator's name as the first argument");
    return 1;
  }
  if (!toDetach && aManipulator.IsNull())
  {
    aManipulator = new AIS_Manipulator();
    aManipulator->SetModeActivationOnDetection(true);
    aMapAIS.Bind(aManipulator, aName);
  }

  // -----------------------------------------
  // change properties of manipulator instance
  // -----------------------------------------

  if (toAutoActivate != -1)
  {
    aManipulator->SetModeActivationOnDetection(toAutoActivate == 1);
  }
  if (toFollowTranslation != -1)
  {
    aManipulator->ChangeTransformBehavior().SetFollowTranslation(toFollowTranslation == 1);
  }
  if (toFollowRotation != -1)
  {
    aManipulator->ChangeTransformBehavior().SetFollowRotation(toFollowRotation == 1);
  }
  if (toFollowDragging != -1)
  {
    aManipulator->ChangeTransformBehavior().SetFollowDragging(toFollowDragging == 1);
  }
  if (aGap >= 0.0f)
  {
    aManipulator->SetGap((float)aGap);
  }

  for (NCollection_Sequence<ManipAxisModeOnOff>::Iterator aPartIter(aParts); aPartIter.More();
       aPartIter.Next())
  {
    const ManipAxisModeOnOff& aPart = aPartIter.Value();
    if (aPart.Axis == -1)
    {
      aManipulator->SetPart(aPart.Mode, aPart.ToEnable);
    }
    else
    {
      aManipulator->SetPart(aPart.Axis, aPart.Mode, aPart.ToEnable);
    }
  }

  if (aSize > 0.0)
  {
    aManipulator->SetSize((float)aSize);
  }
  if (isZoomable != -1)
  {
    aManipulator->SetZoomPersistence(isZoomable == 0);

    if (ViewerTest::GetAISContext()->IsDisplayed(aManipulator))
    {
      ViewerTest::GetAISContext()->Remove(aManipulator, false);
      ViewerTest::GetAISContext()->Display(aManipulator, false);
    }
  }
  if (isFlat != -1)
  {
    aManipulator->SetSkinMode((AIS_Manipulator::ManipulatorSkin)isFlat);

    if (ViewerTest::GetAISContext()->IsDisplayed(aManipulator))
    {
      ViewerTest::GetAISContext()->Remove(aManipulator, false);
      ViewerTest::GetAISContext()->Display(aManipulator, false);
    }
  }

  // ----------------------------------
  // detach existing manipulator object
  // ----------------------------------

  if (toDetach)
  {
    aManipulator->Detach();
    aMapAIS.UnBind2(aName);
    ViewerTest::GetAISContext()->Remove(aManipulator, false);
  }

  // ---------------------------------------------------
  // attach, detach or access manipulator from an object
  // ---------------------------------------------------

  if (!anAttachObject.IsNull())
  {
    if (toAddObject && aManipulator->IsAttached())
    {
      occ::handle<NCollection_HSequence<occ::handle<AIS_InteractiveObject>>> anAttachObjects =
        aManipulator->Objects();
      anAttachObjects->Append(anAttachObject);
      aManipulator->Attach(anAttachObjects, anAttachOptions);
    }
    else
    {
      aManipulator->Attach(anAttachObject, anAttachOptions);
    }
  }
  if (!aViewAffinity.IsNull())
  {
    for (ViewerTest_ViewerCommandsViewMap::Iterator anIter(ViewerTest_myViews); anIter.More();
         anIter.Next())
    {
      ViewerTest::GetAISContext()->SetViewAffinity(aManipulator, anIter.Key2(), false);
    }
    ViewerTest::GetAISContext()->SetViewAffinity(aManipulator, aViewAffinity, true);
  }

  if (anAttachPos != ManipAjustPosition_Off && aManipulator->IsAttached()
      && (anAttachObject.IsNull() || anAttachPos != ManipAjustPosition_Center))
  {
    gp_Ax2        aPosition = gp::XOY();
    const gp_Trsf aBaseTrsf = aManipulator->Object()->LocalTransformation();
    switch (anAttachPos)
    {
      case ManipAjustPosition_Off: {
        break;
      }
      case ManipAjustPosition_Location: {
        aPosition = gp::XOY().Transformed(aBaseTrsf);
        break;
      }
      case ManipAjustPosition_ShapeLocation: {
        if (occ::handle<AIS_Shape> aShapePrs = occ::down_cast<AIS_Shape>(aManipulator->Object()))
        {
          aPosition = gp::XOY().Transformed(aBaseTrsf * aShapePrs->Shape().Location());
        }
        else
        {
          Message::SendFail() << "Syntax error: manipulator is not attached to shape";
          return 1;
        }
        break;
      }
      case ManipAjustPosition_Center: {
        Bnd_Box aBox;
        for (NCollection_HSequence<occ::handle<AIS_InteractiveObject>>::Iterator anObjIter(
               *aManipulator->Objects());
             anObjIter.More();
             anObjIter.Next())
        {
          Bnd_Box anObjBox;
          anObjIter.Value()->BoundingBox(anObjBox);
          aBox.Add(anObjBox);
        }
        aBox = aBox.FinitePart();
        if (!aBox.IsVoid())
        {
          const gp_Pnt aCenter = (aBox.CornerMin().XYZ() + aBox.CornerMax().XYZ()) * 0.5;
          aPosition.SetLocation(aCenter);
        }
        break;
      }
    }
    aManipulator->SetPosition(aPosition);
  }
  if (!Precision::IsInfinite(aLocation.X()))
  {
    if (aVDir.Modulus() <= Precision::Confusion())
    {
      aVDir = aManipulator->Position().Direction().XYZ();
    }
    if (anXDir.Modulus() <= Precision::Confusion())
    {
      anXDir = aManipulator->Position().XDirection().XYZ();
    }
    aManipulator->SetPosition(gp_Ax2(gp_Pnt(aLocation), gp_Dir(aVDir), gp_Dir(anXDir)));
  }

  // --------------------------------------
  // apply transformation using manipulator
  // --------------------------------------

  if (aMousePosFrom.x() != IntegerLast())
  {
    aManipulator->StartTransform(aMousePosFrom.x(), aMousePosFrom.y(), ViewerTest::CurrentView());
  }
  if (aMousePosTo.x() != IntegerLast())
  {
    aManipulator->Transform(aMousePosTo.x(), aMousePosTo.y(), ViewerTest::CurrentView());
  }
  if (toStopMouseTransform != -1)
  {
    aManipulator->StopTransform(toStopMouseTransform == 1);
  }

  if (aTrsf.Form() != gp_Identity)
  {
    aManipulator->Transform(aTrsf);
  }

  if (ViewerTest::GetAISContext()->IsDisplayed(aManipulator))
  {
    ViewerTest::GetAISContext()->Redisplay(aManipulator, true);
  }
  return 0;
}

//=================================================================================================

static int VSelectionProperties(Draw_Interpretor& theDi, int theArgsNb, const char** theArgVec)
{
  const occ::handle<AIS_InteractiveContext>& aCtx = ViewerTest::GetAISContext();
  if (aCtx.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  if (TCollection_AsciiString(theArgVec[0]) == "vhighlightselected")
  {
    // handle obsolete alias
    bool toEnable = true;
    if (theArgsNb < 2)
    {
      theDi << (aCtx->ToHilightSelected() ? "on" : "off");
      return 0;
    }
    else if (theArgsNb != 2 || !Draw::ParseOnOff(theArgVec[1], toEnable))
    {
      Message::SendFail("Syntax error: wrong number of parameters");
      return 1;
    }
    if (toEnable != aCtx->ToHilightSelected())
    {
      aCtx->ClearDetected();
      aCtx->SetToHilightSelected(toEnable);
    }
    return 0;
  }

  bool                  toPrint   = theArgsNb == 1;
  bool                  toRedraw  = false;
  int                   anArgIter = 1;
  Prs3d_TypeOfHighlight aType     = Prs3d_TypeOfHighlight_None;
  if (anArgIter < theArgsNb)
  {
    TCollection_AsciiString anArgFirst(theArgVec[anArgIter]);
    anArgFirst.LowerCase();
    ++anArgIter;
    if (anArgFirst == "dynhighlight" || anArgFirst == "dynhilight"
        || anArgFirst == "dynamichighlight" || anArgFirst == "dynamichilight")
    {
      aType = Prs3d_TypeOfHighlight_Dynamic;
    }
    else if (anArgFirst == "localdynhighlight" || anArgFirst == "localdynhilight"
             || anArgFirst == "localdynamichighlight" || anArgFirst == "localdynamichilight")
    {
      aType = Prs3d_TypeOfHighlight_LocalDynamic;
    }
    else if (anArgFirst == "selhighlight" || anArgFirst == "selhilight"
             || anArgFirst == "selectedhighlight" || anArgFirst == "selectedhilight")
    {
      aType = Prs3d_TypeOfHighlight_Selected;
    }
    else if (anArgFirst == "localselhighlight" || anArgFirst == "localselhilight"
             || anArgFirst == "localselectedhighlight" || anArgFirst == "localselectedhilight")
    {
      aType = Prs3d_TypeOfHighlight_LocalSelected;
    }
    else
    {
      --anArgIter;
    }
  }
  for (; anArgIter < theArgsNb; ++anArgIter)
  {
    TCollection_AsciiString anArg(theArgVec[anArgIter]);
    anArg.LowerCase();
    if (anArg == "-help")
    {
      theDi.PrintHelp(theArgVec[0]);
      return 0;
    }
    else if (anArg == "-print")
    {
      toPrint = true;
    }
    else if (anArg == "-autoactivate")
    {
      bool toEnable = true;
      if (anArgIter + 1 < theArgsNb && Draw::ParseOnOff(theArgVec[anArgIter + 1], toEnable))
      {
        ++anArgIter;
      }
      aCtx->SetAutoActivateSelection(toEnable);
    }
    else if (anArg == "-automatichighlight" || anArg == "-automatichilight"
             || anArg == "-autohighlight" || anArg == "-autohilight")
    {
      bool toEnable = true;
      if (anArgIter + 1 < theArgsNb && Draw::ParseOnOff(theArgVec[anArgIter + 1], toEnable))
      {
        ++anArgIter;
      }
      aCtx->ClearSelected(false);
      aCtx->ClearDetected();
      aCtx->SetAutomaticHilight(toEnable);
      toRedraw = true;
    }
    else if (anArg == "-highlightselected" || anArg == "-hilightselected")
    {
      bool toEnable = true;
      if (anArgIter + 1 < theArgsNb && Draw::ParseOnOff(theArgVec[anArgIter + 1], toEnable))
      {
        ++anArgIter;
      }
      aCtx->ClearDetected();
      aCtx->SetToHilightSelected(toEnable);
      toRedraw = true;
    }
    else if (anArg == "-pickstrategy" || anArg == "-pickingstrategy")
    {
      if (++anArgIter >= theArgsNb)
      {
        Message::SendFail("Syntax error: type of highlighting is undefined");
        return 1;
      }

      SelectMgr_PickingStrategy aStrategy = SelectMgr_PickingStrategy_FirstAcceptable;
      TCollection_AsciiString   aVal(theArgVec[anArgIter]);
      aVal.LowerCase();
      if (aVal == "first" || aVal == "firstaccepted" || aVal == "firstacceptable")
      {
        aStrategy = SelectMgr_PickingStrategy_FirstAcceptable;
      }
      else if (aVal == "topmost" || aVal == "onlyTopmost")
      {
        aStrategy = SelectMgr_PickingStrategy_OnlyTopmost;
      }
      else
      {
        Message::SendFail() << "Syntax error: unknown picking strategy '" << aVal << "'";
        return 1;
      }

      aCtx->SetPickingStrategy(aStrategy);
    }
    else if (anArg == "-pixtol" && anArgIter + 1 < theArgsNb)
    {
      aCtx->SetPixelTolerance(Draw::Atoi(theArgVec[++anArgIter]));
    }
    else if (anArg == "-preferclosest")
    {
      bool toPreferClosest = true;
      if (anArgIter + 1 < theArgsNb && Draw::ParseOnOff(theArgVec[anArgIter + 1], toPreferClosest))
      {
        ++anArgIter;
      }
      aCtx->MainSelector()->SetPickClosest(toPreferClosest);
    }
    else if ((anArg == "-depthtol" || anArg == "-depthtolerance") && anArgIter + 1 < theArgsNb)
    {
      TCollection_AsciiString aTolType(theArgVec[++anArgIter]);
      aTolType.LowerCase();
      if (aTolType == "uniform")
      {
        if (anArgIter + 1 >= theArgsNb)
        {
          Message::SendFail() << "Syntax error: wrong number of arguments";
          return 1;
        }
        aCtx->MainSelector()->SetDepthTolerance(SelectMgr_TypeOfDepthTolerance_Uniform,
                                                Draw::Atof(theArgVec[++anArgIter]));
      }
      else if (aTolType == "uniformpx")
      {
        if (anArgIter + 1 >= theArgsNb)
        {
          Message::SendFail() << "Syntax error: wrong number of arguments";
          return 1;
        }
        aCtx->MainSelector()->SetDepthTolerance(SelectMgr_TypeOfDepthTolerance_UniformPixels,
                                                Draw::Atof(theArgVec[++anArgIter]));
      }
      else if (aTolType == "sensfactor")
      {
        aCtx->MainSelector()->SetDepthTolerance(SelectMgr_TypeOfDepthTolerance_SensitivityFactor,
                                                0.0);
      }
      else
      {
        Message::SendFail() << "Syntax error at '" << aTolType << "'";
        return 1;
      }
    }
    else if ((anArg == "-mode" || anArg == "-dispmode") && anArgIter + 1 < theArgsNb)
    {
      if (aType == Prs3d_TypeOfHighlight_None)
      {
        Message::SendFail("Syntax error: type of highlighting is undefined");
        return 1;
      }

      const int                        aDispMode = Draw::Atoi(theArgVec[++anArgIter]);
      const occ::handle<Prs3d_Drawer>& aStyle    = aCtx->HighlightStyle(aType);
      aStyle->SetDisplayMode(aDispMode);
      toRedraw = true;
    }
    else if (anArg == "-layer" && anArgIter + 1 < theArgsNb)
    {
      if (aType == Prs3d_TypeOfHighlight_None)
      {
        Message::SendFail("Syntax error: type of highlighting is undefined");
        return 1;
      }

      ++anArgIter;
      Graphic3d_ZLayerId aNewLayer = Graphic3d_ZLayerId_UNKNOWN;
      if (!ViewerTest::ParseZLayer(theArgVec[anArgIter], aNewLayer))
      {
        Message::SendFail() << "Syntax error at " << theArgVec[anArgIter];
        return 1;
      }

      const occ::handle<Prs3d_Drawer>& aStyle = aCtx->HighlightStyle(aType);
      aStyle->SetZLayer(aNewLayer);
      toRedraw = true;
    }
    else if (anArg == "-hicolor" || anArg == "-selcolor" || anArg == "-color")
    {
      if (anArg.StartsWith("-hi"))
      {
        aType = Prs3d_TypeOfHighlight_Dynamic;
      }
      else if (anArg.StartsWith("-sel"))
      {
        aType = Prs3d_TypeOfHighlight_Selected;
      }
      else if (aType == Prs3d_TypeOfHighlight_None)
      {
        Message::SendFail("Syntax error: type of highlighting is undefined");
        return 1;
      }

      Quantity_Color aColor;
      int            aNbParsed =
        Draw::ParseColor(theArgsNb - anArgIter - 1, theArgVec + anArgIter + 1, aColor);
      if (aNbParsed == 0)
      {
        Message::SendFail("Syntax error: need more arguments");
        return 1;
      }
      anArgIter += aNbParsed;

      const occ::handle<Prs3d_Drawer>& aStyle = aCtx->HighlightStyle(aType);
      aStyle->SetColor(aColor);
      toRedraw = true;
    }
    else if ((anArg == "-transp" || anArg == "-transparency" || anArg == "-hitransp"
              || anArg == "-seltransp" || anArg == "-hitransplocal" || anArg == "-seltransplocal")
             && anArgIter + 1 < theArgsNb)
    {
      if (anArg.StartsWith("-hi"))
      {
        aType = Prs3d_TypeOfHighlight_Dynamic;
      }
      else if (anArg.StartsWith("-sel"))
      {
        aType = Prs3d_TypeOfHighlight_Selected;
      }
      else if (aType == Prs3d_TypeOfHighlight_None)
      {
        Message::SendFail("Syntax error: type of highlighting is undefined");
        return 1;
      }

      const double                     aTransp = Draw::Atof(theArgVec[++anArgIter]);
      const occ::handle<Prs3d_Drawer>& aStyle  = aCtx->HighlightStyle(aType);
      aStyle->SetTransparency((float)aTransp);
      toRedraw = true;
    }
    else if ((anArg == "-mat" || anArg == "-material") && anArgIter + 1 < theArgsNb)
    {
      if (aType == Prs3d_TypeOfHighlight_None)
      {
        Message::SendFail("Syntax error: type of highlighting is undefined");
        return 1;
      }

      const occ::handle<Prs3d_Drawer>& aStyle = aCtx->HighlightStyle(aType);
      Graphic3d_NameOfMaterial         aMatName =
        Graphic3d_MaterialAspect::MaterialFromName(theArgVec[anArgIter + 1]);
      if (aMatName != Graphic3d_NameOfMaterial_DEFAULT)
      {
        ++anArgIter;
        occ::handle<Graphic3d_AspectFillArea3d> anAspect = new Graphic3d_AspectFillArea3d();
        *anAspect = *aCtx->DefaultDrawer()->ShadingAspect()->Aspect();
        Graphic3d_MaterialAspect aMat(aMatName);
        aMat.SetColor(aStyle->Color());
        aMat.SetTransparency(aStyle->Transparency());
        anAspect->SetFrontMaterial(aMat);
        anAspect->SetInteriorColor(aStyle->Color());
        aStyle->SetBasicFillAreaAspect(anAspect);
      }
      else
      {
        aStyle->SetBasicFillAreaAspect(occ::handle<Graphic3d_AspectFillArea3d>());
      }
      toRedraw = true;
    }
    else
    {
      Message::SendFail() << "Syntax error at '" << theArgVec[anArgIter] << "'";
      return 1;
    }
  }

  if (toPrint)
  {
    const occ::handle<Prs3d_Drawer>& aHiStyle  = aCtx->HighlightStyle();
    const occ::handle<Prs3d_Drawer>& aSelStyle = aCtx->SelectionStyle();
    theDi << "Auto-activation                : "
          << (aCtx->GetAutoActivateSelection() ? "On" : "Off") << "\n";
    theDi << "Auto-highlight                 : " << (aCtx->AutomaticHilight() ? "On" : "Off")
          << "\n";
    theDi << "Highlight selected             : " << (aCtx->ToHilightSelected() ? "On" : "Off")
          << "\n";
    theDi << "Selection pixel tolerance      : " << aCtx->MainSelector()->PixelTolerance() << "\n";
    theDi << "Selection color                : "
          << Quantity_Color::StringName(aSelStyle->Color().Name()) << "\n";
    theDi << "Dynamic highlight color        : "
          << Quantity_Color::StringName(aHiStyle->Color().Name()) << "\n";
    theDi << "Selection transparency         : " << aSelStyle->Transparency() << "\n";
    theDi << "Dynamic highlight transparency : " << aHiStyle->Transparency() << "\n";
    theDi << "Selection mode                 : " << aSelStyle->DisplayMode() << "\n";
    theDi << "Dynamic highlight mode         : " << aHiStyle->DisplayMode() << "\n";
    theDi << "Selection layer                : " << aSelStyle->ZLayer() << "\n";
    theDi << "Dynamic layer                  : " << aHiStyle->ZLayer() << "\n";
  }

  if (aCtx->NbSelected() != 0 && toRedraw)
  {
    aCtx->HilightSelected(true);
  }

  return 0;
}

//=================================================================================================

static int VDumpSelectionImage(Draw_Interpretor& /*theDi*/, int theArgsNb, const char** theArgVec)
{
  const occ::handle<AIS_InteractiveContext>& aContext = ViewerTest::GetAISContext();
  const occ::handle<V3d_View>&               aView    = ViewerTest::CurrentView();
  if (aContext.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  TCollection_AsciiString        aFile;
  StdSelect_TypeOfSelectionImage aType = StdSelect_TypeOfSelectionImage_NormalizedDepth;
  occ::handle<Graphic3d_Camera>  aCustomCam;
  Image_Format                   anImgFormat  = Image_Format_BGR;
  int                            aPickedIndex = 1;
  for (int anArgIter = 1; anArgIter < theArgsNb; ++anArgIter)
  {
    TCollection_AsciiString aParam(theArgVec[anArgIter]);
    aParam.LowerCase();
    if (aParam == "-type")
    {
      if (++anArgIter >= theArgsNb)
      {
        Message::SendFail("Syntax error: wrong number parameters of flag '-type'");
        return 1;
      }

      TCollection_AsciiString aValue(theArgVec[anArgIter]);
      aValue.LowerCase();
      if (aValue == "depth" || aValue == "normdepth" || aValue == "normalizeddepth")
      {
        aType       = StdSelect_TypeOfSelectionImage_NormalizedDepth;
        anImgFormat = Image_Format_GrayF;
      }
      else if (aValue == "depthinverted" || aValue == "normdepthinverted"
               || aValue == "normalizeddepthinverted" || aValue == "inverted")
      {
        aType       = StdSelect_TypeOfSelectionImage_NormalizedDepthInverted;
        anImgFormat = Image_Format_GrayF;
      }
      else if (aValue == "unnormdepth" || aValue == "unnormalizeddepth")
      {
        aType       = StdSelect_TypeOfSelectionImage_UnnormalizedDepth;
        anImgFormat = Image_Format_GrayF;
      }
      else if (aValue == "objectcolor" || aValue == "object" || aValue == "color")
      {
        aType = StdSelect_TypeOfSelectionImage_ColoredDetectedObject;
      }
      else if (aValue == "entitycolor" || aValue == "entity")
      {
        aType = StdSelect_TypeOfSelectionImage_ColoredEntity;
      }
      else if (aValue == "entitytypecolor" || aValue == "entitytype")
      {
        aType = StdSelect_TypeOfSelectionImage_ColoredEntityType;
      }
      else if (aValue == "ownercolor" || aValue == "owner")
      {
        aType = StdSelect_TypeOfSelectionImage_ColoredOwner;
      }
      else if (aValue == "selectionmodecolor" || aValue == "selectionmode"
               || aValue == "selmodecolor" || aValue == "selmode")
      {
        aType = StdSelect_TypeOfSelectionImage_ColoredSelectionMode;
      }
      else if (aValue == "surfnormal" || aValue == "surfacenormal" || aValue == "normal")
      {
        aType = StdSelect_TypeOfSelectionImage_SurfaceNormal;
      }
      else
      {
        Message::SendFail() << "Syntax error: unknown type '" << aValue << "'";
        return 1;
      }
    }
    else if (aParam == "-picked" || aParam == "-pickeddepth" || aParam == "-pickedindex")
    {
      if (++anArgIter >= theArgsNb)
      {
        Message::SendFail() << "Syntax error: wrong number parameters at '" << aParam << "'";
        return 1;
      }

      aPickedIndex = Draw::Atoi(theArgVec[anArgIter]);
    }
    else if (anArgIter + 1 < theArgsNb && aParam == "-xrpose")
    {
      TCollection_AsciiString anXRArg(theArgVec[++anArgIter]);
      anXRArg.LowerCase();
      if (anXRArg == "base")
      {
        aCustomCam = aView->View()->BaseXRCamera();
      }
      else if (anXRArg == "head")
      {
        aCustomCam = aView->View()->PosedXRCamera();
      }
      else
      {
        Message::SendFail() << "Syntax error: unknown XR pose '" << anXRArg << "'";
        return 1;
      }
      if (aCustomCam.IsNull())
      {
        Message::SendFail() << "Error: undefined XR pose";
        return 0;
      }
    }
    else if (aFile.IsEmpty())
    {
      aFile = theArgVec[anArgIter];
    }
    else
    {
      Message::SendFail() << "Syntax error: unknown argument '" << theArgVec[anArgIter] << "'";
      return 1;
    }
  }
  if (aFile.IsEmpty())
  {
    Message::SendFail("Syntax error: image file name is missing");
    return 1;
  }

  int aWidth = 0, aHeight = 0;
  aView->Window()->Size(aWidth, aHeight);

  Image_AlienPixMap aPixMap;
  if (!aPixMap.InitZero(anImgFormat, aWidth, aHeight))
  {
    Message::SendFail("Error: can't allocate image");
    return 1;
  }

  const bool                    wasImmUpdate = aView->SetImmediateUpdate(false);
  occ::handle<Graphic3d_Camera> aCamBack     = aView->Camera();
  if (!aCustomCam.IsNull())
  {
    aView->SetCamera(aCustomCam);
  }
  if (!aContext->MainSelector()->ToPixMap(aPixMap, aView, aType, aPickedIndex))
  {
    Message::SendFail("Error: can't generate selection image");
    return 1;
  }
  if (!aCustomCam.IsNull())
  {
    aView->SetCamera(aCamBack);
  }
  aView->SetImmediateUpdate(wasImmUpdate);

  if (!aPixMap.Save(aFile))
  {
    Message::SendFail("Error: can't save selection image");
    return 0;
  }
  return 0;
}

//=================================================================================================

static int VViewCube(Draw_Interpretor&, int theNbArgs, const char** theArgVec)
{
  const occ::handle<AIS_InteractiveContext>& aContext = ViewerTest::GetAISContext();
  const occ::handle<V3d_View>&               aView    = ViewerTest::CurrentView();
  if (aContext.IsNull() || aView.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }
  else if (theNbArgs < 2)
  {
    Message::SendFail("Syntax error: wrong number arguments");
    return 1;
  }

  occ::handle<AIS_ViewCube> aViewCube;
  ViewerTest_AutoUpdater    anUpdateTool(aContext, aView);
  Quantity_Color            aColorRgb;
  TCollection_AsciiString   aName;
  for (int anArgIter = 1; anArgIter < theNbArgs; ++anArgIter)
  {
    TCollection_AsciiString anArg(theArgVec[anArgIter]);
    anArg.LowerCase();
    if (anUpdateTool.parseRedrawMode(anArg))
    {
      //
    }
    else if (aViewCube.IsNull())
    {
      aName = theArgVec[anArgIter];
      if (aName.StartsWith("-"))
      {
        Message::SendFail("Syntax error: object name should be specified");
        return 1;
      }
      occ::handle<AIS_InteractiveObject> aPrs;
      GetMapOfAIS().Find2(aName, aPrs);
      aViewCube = occ::down_cast<AIS_ViewCube>(aPrs);
      if (aViewCube.IsNull())
      {
        aViewCube = new AIS_ViewCube();
        aViewCube->SetBoxColor(Quantity_NOC_GRAY50);
        aViewCube->SetViewAnimation(ViewerTest::CurrentEventManager()->ViewAnimation());
        aViewCube->SetFixedAnimationLoop(false);
      }
    }
    else if (anArg == "-reset")
    {
      aViewCube->ResetStyles();
    }
    else if (anArg == "-color" || anArg == "-boxcolor" || anArg == "-boxsidecolor"
             || anArg == "-sidecolor" || anArg == "-boxedgecolor" || anArg == "-edgecolor"
             || anArg == "-boxcornercolor" || anArg == "-cornercolor" || anArg == "-innercolor"
             || anArg == "-textcolor" || anArg == "-xaxistextcolor" || anArg == "-yaxistextcolor"
             || anArg == "-zaxistextcolor")
    {
      int aNbParsed =
        Draw::ParseColor(theNbArgs - anArgIter - 1, theArgVec + anArgIter + 1, aColorRgb);
      if (aNbParsed == 0)
      {
        Message::SendFail() << "Syntax error at '" << anArg << "'";
        return 1;
      }
      anArgIter += aNbParsed;
      if (anArg == "-boxcolor")
      {
        aViewCube->SetBoxColor(aColorRgb);
      }
      else if (anArg == "-boxsidecolor" || anArg == "-sidecolor")
      {
        aViewCube->BoxSideStyle()->SetColor(aColorRgb);
        aViewCube->SynchronizeAspects();
      }
      else if (anArg == "-boxedgecolor" || anArg == "-edgecolor")
      {
        aViewCube->BoxEdgeStyle()->SetColor(aColorRgb);
        aViewCube->SynchronizeAspects();
      }
      else if (anArg == "-boxcornercolor" || anArg == "-cornercolor")
      {
        aViewCube->BoxCornerStyle()->SetColor(aColorRgb);
        aViewCube->SynchronizeAspects();
      }
      else if (anArg == "-innercolor")
      {
        aViewCube->SetInnerColor(aColorRgb);
      }
      else if (anArg == "-textcolor")
      {
        aViewCube->SetTextColor(aColorRgb);
      }
      else if (anArg == "-xaxistextcolor" || anArg == "-yaxistextcolor"
               || anArg == "-zaxistextcolor")
      {
        Prs3d_DatumParts aDatum =
          anArg.Value(2) == 'x'
            ? Prs3d_DatumParts_XAxis
            : (anArg.Value(2) == 'y' ? Prs3d_DatumParts_YAxis : Prs3d_DatumParts_ZAxis);
        aViewCube->Attributes()->SetOwnDatumAspects();
        aViewCube->Attributes()->DatumAspect()->TextAspect(aDatum)->SetColor(aColorRgb);
      }
      else
      {
        aViewCube->SetColor(aColorRgb);
      }
    }
    else if (anArgIter + 1 < theNbArgs && (anArg == "-transparency" || anArg == "-boxtransparency"))
    {
      const double aValue = Draw::Atof(theArgVec[++anArgIter]);
      if (aValue < 0.0 || aValue > 1.0)
      {
        Message::SendFail() << "Syntax error: invalid transparency value " << theArgVec[anArgIter];
        return 1;
      }

      if (anArg == "-boxtransparency")
      {
        aViewCube->SetBoxTransparency(aValue);
      }
      else
      {
        aViewCube->SetTransparency(aValue);
      }
    }
    else if (anArg == "-axes" || anArg == "-edges" || anArg == "-vertices" || anArg == "-vertexes"
             || anArg == "-fixedanimation")
    {
      bool toShow = true;
      if (anArgIter + 1 < theNbArgs && Draw::ParseOnOff(theArgVec[anArgIter + 1], toShow))
      {
        ++anArgIter;
      }
      if (anArg == "-fixedanimation")
      {
        aViewCube->SetFixedAnimationLoop(toShow);
      }
      else if (anArg == "-axes")
      {
        aViewCube->SetDrawAxes(toShow);
      }
      else if (anArg == "-edges")
      {
        aViewCube->SetDrawEdges(toShow);
      }
      else
      {
        aViewCube->SetDrawVertices(toShow);
      }
    }
    else if (anArg == "-yup" || anArg == "-zup")
    {
      bool isOn = true;
      if (anArgIter + 1 < theNbArgs && Draw::ParseOnOff(theArgVec[anArgIter + 1], isOn))
      {
        ++anArgIter;
      }
      if (anArg == "-yup")
      {
        aViewCube->SetYup(isOn);
      }
      else
      {
        aViewCube->SetYup(!isOn);
      }
    }
    else if (anArgIter + 1 < theNbArgs && anArg == "-font")
    {
      aViewCube->SetFont(theArgVec[++anArgIter]);
    }
    else if (anArgIter + 1 < theNbArgs && anArg == "-fontheight")
    {
      aViewCube->SetFontHeight(Draw::Atof(theArgVec[++anArgIter]));
    }
    else if (anArgIter + 1 < theNbArgs && (anArg == "-size" || anArg == "-boxsize"))
    {
      aViewCube->SetSize(Draw::Atof(theArgVec[++anArgIter]), anArg != "-boxsize");
    }
    else if (anArgIter + 1 < theNbArgs
             && (anArg == "-boxfacet" || anArg == "-boxfacetextension" || anArg == "-facetextension"
                 || anArg == "-extension"))
    {
      aViewCube->SetBoxFacetExtension(Draw::Atof(theArgVec[++anArgIter]));
    }
    else if (anArgIter + 1 < theNbArgs && (anArg == "-boxedgegap" || anArg == "-edgegap"))
    {
      aViewCube->SetBoxEdgeGap(Draw::Atof(theArgVec[++anArgIter]));
    }
    else if (anArgIter + 1 < theNbArgs && (anArg == "-boxedgeminsize" || anArg == "-edgeminsize"))
    {
      aViewCube->SetBoxEdgeMinSize(Draw::Atof(theArgVec[++anArgIter]));
    }
    else if (anArgIter + 1 < theNbArgs
             && (anArg == "-boxcornerminsize" || anArg == "-cornerminsize"))
    {
      aViewCube->SetBoxCornerMinSize(Draw::Atof(theArgVec[++anArgIter]));
    }
    else if (anArgIter + 1 < theNbArgs && anArg == "-axespadding")
    {
      aViewCube->SetAxesPadding(Draw::Atof(theArgVec[++anArgIter]));
    }
    else if (anArgIter + 1 < theNbArgs && anArg == "-roundradius")
    {
      aViewCube->SetRoundRadius(Draw::Atof(theArgVec[++anArgIter]));
    }
    else if (anArgIter + 1 < theNbArgs && anArg == "-duration")
    {
      aViewCube->SetDuration(Draw::Atof(theArgVec[++anArgIter]));
    }
    else if (anArgIter + 1 < theNbArgs && anArg == "-axesradius")
    {
      aViewCube->SetAxesRadius(Draw::Atof(theArgVec[++anArgIter]));
    }
    else if (anArgIter + 1 < theNbArgs && anArg == "-axesconeradius")
    {
      aViewCube->SetAxesConeRadius(Draw::Atof(theArgVec[++anArgIter]));
    }
    else if (anArgIter + 1 < theNbArgs && anArg == "-axessphereradius")
    {
      aViewCube->SetAxesSphereRadius(Draw::Atof(theArgVec[++anArgIter]));
    }
    else if (anArg == "-orthopers")
    {
      const occ::handle<Graphic3d_TransformPers>& aTrsfPers = aViewCube->TransformPersistence();
      occ::handle<Graphic3d_TransformPers>        anOrthoPers =
        new Graphic3d_TransformPers(Graphic3d_TMF_TriedronPers | Graphic3d_TMF_OrthoPers,
                                    aTrsfPers->Corner2d(),
                                    aTrsfPers->Offset2d());
      aViewCube->SetTransformPersistence(anOrthoPers);
    }
    else
    {
      Message::SendFail() << "Syntax error: unknown argument '" << anArg << "'";
      return 1;
    }
  }
  if (aViewCube.IsNull())
  {
    Message::SendFail("Syntax error: wrong number of arguments");
    return 1;
  }

  ViewerTest::Display(aName, aViewCube, false);
  return 0;
}

//! Parse color type argument.
static bool parseColorType(const char* theString, Quantity_TypeOfColor& theType)
{
  TCollection_AsciiString aType(theString);
  aType.LowerCase();
  if (aType == "rgb")
  {
    theType = Quantity_TOC_RGB;
  }
  else if (aType == "srgb")
  {
    theType = Quantity_TOC_sRGB;
  }
  else if (aType == "hex")
  {
    theType = Quantity_TOC_sRGB;
  }
  else if (aType == "name")
  {
    theType = Quantity_TOC_sRGB;
  }
  else if (aType == "hls")
  {
    theType = Quantity_TOC_HLS;
  }
  else if (aType == "lab")
  {
    theType = Quantity_TOC_CIELab;
  }
  else if (aType == "lch")
  {
    theType = Quantity_TOC_CIELch;
  }
  else
  {
    return false;
  }
  return true;
}

//=================================================================================================

static int VColorConvert(Draw_Interpretor& theDI, int theNbArgs, const char** theArgVec)
{
  Quantity_TypeOfColor aTypeFrom = Quantity_TOC_RGB, aTypeTo = Quantity_TOC_RGB;
  double               anInput[4] = {};
  Quantity_ColorRGBA   aColor(0.0f, 0.0f, 0.0f, 1.0f);
  bool                 toPrintHex = false, toPrintName = false, hasAlpha = false;
  for (int anArgIter = 1; anArgIter < theNbArgs; ++anArgIter)
  {
    TCollection_AsciiString anArgCase(theArgVec[anArgIter]);
    anArgCase.LowerCase();
    if ((anArgCase == "-from" || anArgCase == "from") && anArgIter + 1 < theNbArgs
        && parseColorType(theArgVec[anArgIter + 1], aTypeFrom))
    {
      ++anArgIter;
    }
    else if ((anArgCase == "-to" || anArgCase == "to") && anArgIter + 1 < theNbArgs
             && parseColorType(theArgVec[anArgIter + 1], aTypeTo))
    {
      TCollection_AsciiString aToStr(theArgVec[++anArgIter]);
      aToStr.LowerCase();
      toPrintHex  = (aToStr == "hex");
      toPrintName = (aToStr == "name");
    }
    else if (Quantity_ColorRGBA::ColorFromHex(theArgVec[anArgIter], aColor))
    {
      hasAlpha = anArgCase.Length() >= 8;
    }
    else if (Quantity_Color::ColorFromName(theArgVec[anArgIter], aColor.ChangeRGB()))
    {
      //
    }
    else if (anArgIter + 2 < theNbArgs && Draw::ParseReal(theArgVec[anArgIter + 0], anInput[0])
             && Draw::ParseReal(theArgVec[anArgIter + 1], anInput[1])
             && Draw::ParseReal(theArgVec[anArgIter + 2], anInput[2]))
    {
      if (anArgIter + 3 < theNbArgs && Draw::ParseReal(theArgVec[anArgIter + 3], anInput[3]))
      {
        anArgIter += 1;
        aColor.SetAlpha((float)anInput[3]);
        hasAlpha = true;
      }
      anArgIter += 2;
      aColor.ChangeRGB().SetValues(anInput[0], anInput[1], anInput[2], aTypeFrom);
    }
    else
    {
      theDI << "Syntax error: unknown argument '" << theArgVec[anArgIter] << "'";
      return 1;
    }
  }

  if (toPrintHex)
  {
    if (hasAlpha || aColor.Alpha() < 1.0f)
    {
      theDI << Quantity_ColorRGBA::ColorToHex(aColor);
    }
    else
    {
      theDI << Quantity_Color::ColorToHex(aColor.GetRGB());
    }
  }
  else if (toPrintName)
  {
    theDI << Quantity_Color::StringName(aColor.GetRGB().Name());
  }
  else
  {
    double anOutput[3] = {};
    aColor.GetRGB().Values(anOutput[0], anOutput[1], anOutput[2], aTypeTo);

    // print values with 6 decimal digits
    char aBuffer[1024];
    if (hasAlpha || aColor.Alpha() < 1.0f)
    {
      Sprintf(aBuffer,
              "%.6f %.6f %.6f %.6f",
              anOutput[0],
              anOutput[1],
              anOutput[2],
              aColor.Alpha());
    }
    else
    {
      Sprintf(aBuffer, "%.6f %.6f %.6f", anOutput[0], anOutput[1], anOutput[2]);
    }
    theDI << aBuffer;
  }
  return 0;
}

//=================================================================================================

static int VColorDiff(Draw_Interpretor& theDI, int theNbArgs, const char** theArgVec)
{
  if (theNbArgs != 7)
  {
    std::cerr << "Error: command syntax is incorrect, see help" << '\n';
    return 1;
  }

  double aR1 = Draw::Atof(theArgVec[1]);
  double aG1 = Draw::Atof(theArgVec[2]);
  double aB1 = Draw::Atof(theArgVec[3]);
  double aR2 = Draw::Atof(theArgVec[4]);
  double aG2 = Draw::Atof(theArgVec[5]);
  double aB2 = Draw::Atof(theArgVec[6]);

  Quantity_Color aColor1(aR1, aG1, aB1, Quantity_TOC_RGB);
  Quantity_Color aColor2(aR2, aG2, aB2, Quantity_TOC_RGB);

  theDI << aColor1.DeltaE2000(aColor2);

  return 0;
}

//=================================================================================================

static int VSelBvhBuild(Draw_Interpretor& /*theDI*/, int theNbArgs, const char** theArgVec)
{
  const occ::handle<AIS_InteractiveContext> aCtx = ViewerTest::GetAISContext();
  if (aCtx.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  if (theNbArgs < 2)
  {
    Message::SendFail("Error: command syntax is incorrect, see help");
    return 1;
  }

  int  toEnable   = -1;
  int  aThreadsNb = -1;
  bool toWait     = false;

  for (int anArgIter = 1; anArgIter < theNbArgs; ++anArgIter)
  {
    TCollection_AsciiString anArg(theArgVec[anArgIter]);
    anArg.LowerCase();

    if (anArg == "-nbthreads" && anArgIter + 1 < theNbArgs)
    {
      aThreadsNb = Draw::Atoi(theArgVec[++anArgIter]);
      if (aThreadsNb < 1)
      {
        aThreadsNb = std::max(1, OSD_Parallel::NbLogicalProcessors() - 1);
      }
    }
    else if (anArg == "-wait")
    {
      toWait = true;
    }
    else if (toEnable == -1)
    {
      bool toEnableValue = true;
      if (Draw::ParseOnOff(anArg.ToCString(), toEnableValue))
      {
        toEnable = toEnableValue ? 1 : 0;
      }
      else
      {
        Message::SendFail() << "Syntax error: unknown argument '" << anArg << "'";
        return 1;
      }
    }
    else
    {
      Message::SendFail() << "Syntax error: unknown argument '" << anArg << "'";
      return 1;
    }
  }

  if (aThreadsNb == -1)
  {
    aThreadsNb = 1;
  }
  if (toEnable != -1)
  {
    aCtx->MainSelector()->SetToPrebuildBVH(toEnable == 1, aThreadsNb);
  }
  if (toWait)
  {
    aCtx->MainSelector()->WaitForBVHBuild();
  }

  return 0;
}

//=================================================================================================

static int VChangeMouseGesture(Draw_Interpretor&, int theArgsNb, const char** theArgVec)
{
  occ::handle<V3d_View> aView = ViewerTest::CurrentView();
  if (aView.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  NCollection_DoubleMap<TCollection_AsciiString, AIS_MouseGesture> aGestureMap;
  {
    aGestureMap.Bind("none", AIS_MouseGesture_NONE);
    aGestureMap.Bind("selectrectangle", AIS_MouseGesture_SelectRectangle);
    aGestureMap.Bind("selectlasso", AIS_MouseGesture_SelectLasso);
    aGestureMap.Bind("zoom", AIS_MouseGesture_Zoom);
    aGestureMap.Bind("zoomwindow", AIS_MouseGesture_ZoomWindow);
    aGestureMap.Bind("pan", AIS_MouseGesture_Pan);
    aGestureMap.Bind("rotateorbit", AIS_MouseGesture_RotateOrbit);
    aGestureMap.Bind("rotateview", AIS_MouseGesture_RotateView);
    aGestureMap.Bind("drag", AIS_MouseGesture_Drag);
  }
  NCollection_DoubleMap<TCollection_AsciiString, unsigned int> aMouseButtonMap;
  {
    aMouseButtonMap.Bind("none", (unsigned int)Aspect_VKeyMouse_NONE);
    aMouseButtonMap.Bind("left", (unsigned int)Aspect_VKeyMouse_LeftButton);
    aMouseButtonMap.Bind("middle", (unsigned int)Aspect_VKeyMouse_MiddleButton);
    aMouseButtonMap.Bind("right", (unsigned int)Aspect_VKeyMouse_RightButton);
  }

  unsigned int     aButton  = (unsigned int)Aspect_VKeyMouse_LeftButton;
  AIS_MouseGesture aGesture = AIS_MouseGesture_RotateOrbit;
  for (int anArgIter = 1; anArgIter < theArgsNb; ++anArgIter)
  {
    const char*             anArg = theArgVec[anArgIter];
    TCollection_AsciiString anArgCase(anArg);
    anArgCase.LowerCase();
    if (anArgCase == "-button")
    {
      TCollection_AsciiString aButtonStr = theArgVec[++anArgIter];
      aButtonStr.LowerCase();
      aButton = aMouseButtonMap.Find1(aButtonStr);
    }
    else if (anArgCase == "-gesture")
    {
      TCollection_AsciiString aGestureStr = theArgVec[++anArgIter];
      aGestureStr.LowerCase();
      aGesture = aGestureMap.Find1(aGestureStr);
    }
    else
    {
      Message::SendFail() << "Error: unknown argument '" << anArg << "'";
      return 1;
    }
  }

  occ::handle<ViewerTest_EventManager> aViewMgr = ViewerTest::CurrentEventManager();
  aViewMgr->ChangeMouseGestureMap().Bind(aButton, aGesture);

  return 0;
}

//=================================================================================================

static void ViewerTest_ExitProc(ClientData)
{
  NCollection_List<TCollection_AsciiString> aViewList;
  for (NCollection_DoubleMap<TCollection_AsciiString, occ::handle<V3d_View>>::Iterator anIter(
         ViewerTest_myViews);
       anIter.More();
       anIter.Next())
  {
    aViewList.Append(anIter.Key1());
  }

  for (NCollection_List<TCollection_AsciiString>::Iterator anIter(aViewList); anIter.More();
       anIter.Next())
  {
    ViewerTest::RemoveView(anIter.Value(), true);
  }
}

//=================================================================================================

void ViewerTest::ViewerCommands(Draw_Interpretor& theCommands)
{
  static bool TheIsInitialized = false;
  if (TheIsInitialized)
  {
    return;
  }

  TheIsInitialized = true;
  // define destruction callback to destroy views in a well-defined order
  Tcl_CreateExitHandler(ViewerTest_ExitProc, nullptr);

  const char* aGroup    = "AIS Viewer";
  const char* aFileName = __FILE__;
  auto        addCmd =
    [&](const char* theName, Draw_Interpretor::CommandFunction theFunc, const char* theHelp) {
      theCommands.Add(theName, theHelp, aFileName, theFunc, aGroup);
    };

  addCmd("vdriver", VDriver, /* [vdriver] */ R"(
vdriver [-list] [-default DriverName] [-load DriverName]
Manages active graphic driver factory.
Prints current active driver when called without arguments.
Makes specified driver active when ActiveName argument is specified.
 -list    print registered factories
 -default define which factory should be used by default (to be used by next vinit call)
 -load    try loading factory plugin and set it as default one
)" /* [vdriver] */);

  addCmd("vinit", VInit, /* [vinit] */ R"(
vinit [-name viewName] [-left leftPx] [-top topPx] [-width widthPx] [-height heightPx]
      [-exitOnClose] [-closeOnEscape] [-cloneActive] [-virtual {0|1}]=0 [-2d_mode {0|1}]=0
      [-display displayName] [-dpiAware {0|1}]=0
      [-subview] [-parent OtherView] [-composer {0|1}]=0 [-margins DX DY]=0
Creates new View window with specified name viewName.
By default the new view is created in the viewer and in graphic driver shared with active view.
 -name {driverName/viewerName/viewName | viewerName/viewName | viewName}
       if driverName isn't specified the driver will be shared with active view;
       if viewerName isn't specified the viewer will be shared with active view.
 -display HostName.DisplayNumber[:ScreenNumber]

Display name will be used within creation of graphic driver, when specified.
 -left,  -top    pixel position of left top corner of the window.
 -width, -height width and height of window respectively.
 -cloneActive flag to copy camera and dimensions of active view.
 -exitOnClose when specified, closing the view will exit application.
 -closeOnEscape when specified, view will be closed on pressing Escape.
 -virtual create an offscreen window within interactive session
 -subview create a subview within another view
 -2d_mode when on, view will not react on rotate scene events
 -dpiAware override dpi aware hint (Windows platform)
Additional commands for operations with views: vclose, vactivate, vviewlist.
)" /* [vinit] */);

  addCmd("vclose", VClose, /* [vclose] */ R"(
vclose [view_id [keep_context=0|1]]
or vclose ALL - to remove all created views
 - removes view(viewer window) defined by its view_id.
 - keep_context: by default 0; if 1 and the last view is deleted the current context is not removed.
)" /* [vclose] */);

  addCmd("vactivate", VActivate, /* [vactivate] */ R"(
vactivate view_id [-noUpdate]
Activates view(viewer window) defined by its view_id.
)" /* [vactivate] */);

  addCmd("vviewlist", VViewList, /* [vviewlist] */ R"(
vviewlist [format={tree, long}]=tree
Prints current list of views per viewer and graphic_driver ID shared between viewers
 - format: format of result output, if tree the output is a tree view;
           otherwise it's a list of full view names.
)" /* [vviewlist] */);

  addCmd("vhelp", VHelp, /* [vhelp] */ R"(
vhelp : display help on the viewer commands and list of hotkeys.
)" /* [vhelp] */);

  addCmd("vviewproj", VViewProj, /* [vviewproj] */ R"(
vviewproj [top|bottom|left|right|front|back|axoLeft|axoRight]
          [+-X+-Y+-Z] [-Zup|-Yup] [-frame +-X+-Y]
Setup view direction
 -Yup      use Y-up convention instead of Zup (which is default).
 +-X+-Y+-Z define direction as combination of DX, DY and DZ;
           for example '+Z' will show front of the model,
           '-X-Y+Z' will define left axonometric view.
 -frame    define camera Up and Right directions (regardless Up convention);
           for example '+X+Z' will show front of the model with Z-up.
)" /* [vviewproj] */);

  addCmd("vtop", VViewProj, /* [vtop] */ R"(
vtop or <T> : Display top view (+X+Y) in the 3D viewer window.
)" /* [vtop] */);

  addCmd("vbottom", VViewProj, /* [vbottom] */ R"(
vbottom : Display bottom view (+X-Y) in the 3D viewer window.
)" /* [vbottom] */);

  addCmd("vleft", VViewProj, /* [vleft] */ R"(
vleft : Display left view (-Y+Z) in the 3D viewer window.
)" /* [vleft] */);

  addCmd("vright", VViewProj, /* [vright] */ R"(
vright : Display right view (+Y+Z) in the 3D viewer window.
)" /* [vright] */);

  addCmd("vaxo", VViewProj, /* [vaxo] */ R"(
vaxo or <A> : Display axonometric view (+X-Y+Z) in the 3D viewer window.
)" /* [vaxo] */);

  addCmd("vfront", VViewProj, /* [vfront] */ R"(
vfront : Display front view (+X+Z) in the 3D viewer window.
)" /* [vfront] */);

  addCmd("vback", VViewProj, /* [vfront] */ R"(
vback : Display back view (-X+Z) in the 3D viewer window.
)" /* [vback] */);

  addCmd("vpick", VPick, /* [vpick] */ R"(
vpick X Y Z [shape subshape]
)" /* [vpick] */);

  addCmd("vfit", VFit, /* [vfit] */ R"(
vfit or <F> [-selected] [-noupdate]
Fit all / selected. Objects in the view are visualized to occupy the maximum surface.
)" /* [vfit] */);

  addCmd("vfitarea", VFitArea, /* [vfitarea] */ R"(
vfitarea [x1 y1 x2 y2] [x1 y1 z1 x2 y2 z2]
Fit view to show area located between two points
given in world 2D or 3D coordinates.
)" /* [vfitarea] */);

  addCmd("vzfit", VZFit, /* [vzfit] */ R"(
vzfit [scale]
Automatic depth panning.
Matches Z near, Z far view volume planes to the displayed objects.
 - "scale" specifies factor to scale computed z range.
)" /* [vzfit] */);

  addCmd("vrepaint", VRepaint, /* [vrepaint] */ R"(
vrepaint [-immediate] [-continuous FPS]
Force redraw of active View.
 -immediate  flag performs redraw of immediate layers only;
 -continuous activates/deactivates continuous redraw of active View,
             0 means no continuous rendering,
            -1 means non-stop redraws,
            >0 specifies target framerate.
)" /* [vrepaint] */);

  addCmd("vclear", VClear, /* [vclear] */ R"(
vclear : Remove all the object from the viewer
)" /* [vclear] */);

  addCmd("vbackground", VBackground, /* [vbackground] */ R"(
vbackground [-color Color [-default]]
    [-gradient Color1 Color2 [-default]
    [-gradientMode {NONE|HORIZONTAL|VERTICAL|DIAG1|DIAG2|CORNER1|CORNER2|CORNER3|ELLIPTICAL}]=VERT]
    [-imageFile ImageFile [-imageMode {CENTERED|TILED|STRETCH|NONE}]=CENTERED [-srgb {0|1}]=1]
    [-cubemap CubemapFile1 [CubeMapFiles2-5] [-order TilesIndexes1-6] [-invertedz]=0]
    [-skydome [-sunDir X Y Z=0 1 0] [-cloud Cloudy=0.2] [-time Time=0.0]
              [-fog Haze=0.0] [-size SizePx=512]]
    [-pbrEnv {ibl|noibl|keep}]
Changes background or some background settings.
 -color        sets background color
 -gradient     sets background gradient starting and ending colors
 -gradientMode sets gradient fill method
 -default      sets background default gradient or color
 -imageFile    sets filename of image used as background
 -imageMode    sets image fill type
 -cubemap      sets environment cubemap as background
 -invertedz    sets inversion of Z axis for background cubemap rendering; FALSE when unspecified
 -pbrEnv       sets on/off Image Based Lighting (IBL) from background cubemap for PBR
 -srgb         prefer sRGB texture format when applicable; TRUE when unspecified"
 -order        defines order of tiles in one image cubemap
               TileIndexi defubes an index in range [0, 5] for i tile of one image packed cubemap
               (has no effect in case of multi-image cubemaps).
Skydome background parameters (generated cubemap):
 -skydome      sets procedurally generated skydome as background
 -sunDir       sets direction to the sun, direction with negative y component represents moon direction (-x, -y, -z)
 -cloud        sets cloud intensity (0.0 - clear sky, 1.0 - very high cloudy)
 -time         might be tweaked to slightly change appearance of clouds
 -fog          sets mist intensity (0.0 - no mist at all, 1.0 - high mist)
 -size         sets size in pixels of cubemap side
)" /* [vbackground] */);

  addCmd("vsetbg", VBackground, /* [vsetbg] */ R"(
Alias for 'vbackground -imageFile ImageFile [-imageMode FillType]'.
)" /* [vsetbg] */);

  addCmd("vsetbgmode", VBackground, /* [vsetbgmode] */ R"(
Alias for 'vbackground -imageMode FillType'.
)" /* [vsetbgmode] */);

  addCmd("vsetgradientbg", VBackground, /* [vsetgradientbg] */ R"(
Alias for 'vbackground -gradient Color1 Color2 -gradientMode FillMethod'.
)" /* [vsetgradientbg] */);

  addCmd("vsetgrbgmode", VBackground, /* [vsetgrbgmode] */ R"(
Alias for 'vbackground -gradientMode FillMethod'.
)" /* [vsetgrbgmode] */);

  addCmd("vsetcolorbg", VBackground, /* [vsetcolorbg] */ R"(
Alias for 'vbackground -color Color'.
)" /* [vsetcolorbg] */);

  addCmd("vsetdefaultbg", VBackground, /* [vsetdefaultbg] */ R"(
Alias for 'vbackground -default -gradient Color1 Color2 [-gradientMode FillMethod]'
  and for 'vbackground -default -color Color'.
)" /* [vsetdefaultbg] */);

  addCmd("vscale", VScale, /* [vscale] */ R"(
vscale X Y Z
)" /* [vscale] */);

  addCmd("vzbufftrihedron", VZBuffTrihedron, /* [vzbufftrihedron] */ R"(
vzbufftrihedron [{-on|-off}=-on] [-type {wireframe|zbuffer}=zbuffer]
       [-position center|left_lower|left_upper|right_lower|right_upper]
       [-scale value=0.1] [-size value=0.8] [-arrowDiam value=0.05]
       [-colorArrowX color=RED] [-colorArrowY color=GREEN] [-colorArrowZ color=BLUE]
       [-nbfacets value=12] [-colorLabels color=WHITE]
       [-colorLabelX color] [-colorLabelY color] [-colorLabelZ color]
Displays a trihedron.
)" /* [vzbufftrihedron] */);

  addCmd("vrotate", VRotate, /* [vrotate] */ R"(
vrotate [[-mouseStart X Y] [-mouseMove X Y]]|[AX AY AZ [X Y Z]]
 -mouseStart start rotation according to the mouse position;
 -mouseMove  continue rotation with angle computed
             from last and new mouse position.
)" /* [vrotate] */);

  addCmd("vzoom", VZoom, /* [vzoom] */ R"(
vzoom coef
)" /* [vzoom] */);

  addCmd("vpan", VPan, /* [vpan] */ R"(
vpan dx dy
)" /* [vpan] */);

  addCmd("vcolorscale", VColorScale, /* [vcolorscale] */ R"(
vcolorscale name [-noupdate|-update] [-demo]
      [-range RangeMin=0 RangeMax=1 NbIntervals=10]
      [-font HeightFont=20]
      [-logarithmic {on|off}=off] [-reversed {on|off}=off]
      [-smoothTransition {on|off}=off]
      [-hueRange MinAngle=230 MaxAngle=0]
      [-colorRange MinColor=BLUE1 MaxColor=RED]
      [-textPos {left|right|center|none}=right]
      [-labelAtBorder {on|off}=on]
      [-colors Color1 Color2 ...] [-color Index Color]
      [-labels Label1 Label2 ...] [-label Index Label]
      [-freeLabels NbOfLabels Label1 Label2 ...]
      [-xy Left=0 Bottom=0]
      [-uniform lightness hue_from hue_to]
 -demo       display a color scale with demonstration values
 -colors     set colors for all intervals
 -color      set color for specific interval
 -uniform    generate colors with the same lightness
 -textpos    horizontal label position relative to color scale bar
 -labelAtBorder vertical label position relative to color interval;
             at border means the value in between neighbor intervals,
             at center means the center value within current interval
 -labels     set labels for all intervals
 -freeLabels same as -labels but does not require
             matching the number of intervals
 -label      set label for specific interval
 -title      set title
 -reversed   setup smooth color transition between intervals
 -smoothTransition swap colorscale direction
 -hueRange   set hue angles corresponding to minimum and maximum values
)" /* [vcolorscale] */);

  addCmd("vgraduatedtrihedron", VGraduatedTrihedron, /* [vgraduatedtrihedron] */ R"(
vgraduatedtrihedron : -on/-off [-xname Name] [-yname Name] [-zname Name] [-arrowlength Value]
    [-namefont Name] [-valuesfont Name]
    [-xdrawname on/off] [-ydrawname on/off] [-zdrawname on/off]
    [-xnameoffset IntVal] [-ynameoffset IntVal] [-znameoffset IntVal]
    [-xnamecolor Color] [-ynamecolor Color] [-znamecolor Color]
    [-xdrawvalues on/off] [-ydrawvalues on/off] [-zdrawvalues on/off]
    [-xvaluesoffset IntVal] [-yvaluesoffset IntVal] [-zvaluesoffset IntVal]
    [-xcolor Color] [-ycolor Color] [-zcolor Color]
    [-xdrawticks on/off] [-ydrawticks on/off] [-zdrawticks on/off]
    [-xticks Number] [-yticks Number] [-zticks Number]
    [-xticklength IntVal] [-yticklength IntVal] [-zticklength IntVal]
    [-drawgrid on/off] [-drawaxes on/off]
Display or erase graduated trihedron
 - xname, yname, zname - names of axes, default: X, Y, Z
 - namefont - font of axes names. Default: Arial
 - xnameoffset, ynameoffset, znameoffset - offset of name
   from values or tickmarks or axis. Default: 30
 - xnamecolor, ynamecolor, znamecolor - colors of axes names
 - xvaluesoffset, yvaluesoffset, zvaluesoffset - offset of values
   from tickmarks or axis. Default: 10
 - valuesfont - font of axes values. Default: Arial
 - xcolor, ycolor, zcolor - color of axis and values
 - xticks, yticks, xzicks - number of tickmark on axes. Default: 5
 - xticklength, yticklength, xzicklength - length of tickmark on axes. Default: 10
)" /* [vgraduatedtrihedron] */);

  addCmd("vtile", VTile, /* [vtile] */ R"(
vtile [-totalSize W H] [-lowerLeft X Y] [-upperLeft X Y] [-tileSize W H]
Setup view to draw a tile (a part of virtual bigger viewport).
 -totalSize the size of virtual bigger viewport
 -tileSize  tile size (the view size will be used if omitted)
 -lowerLeft tile offset as lower left corner
 -upperLeft tile offset as upper left corner
)" /* [vtile] */);

  addCmd("vzlayer", VZLayer, /* [vzlayer] */ R"(
vzlayer [layerId]
        [-add|-delete|-get|-settings] [-insertBefore AnotherLayer] [-insertAfter AnotherLayer]
        [-origin X Y Z] [-cullDist Distance] [-cullSize Size]
        [-enable|-disable {depthTest|depthWrite|depthClear|depthoffset}]
        [-enable|-disable {positiveOffset|negativeOffset|textureenv|rayTracing}]
ZLayer list management
 -add      add new z layer to viewer and print its id
 -insertBefore add new z layer and insert it before existing one
 -insertAfter  add new z layer and insert it after  existing one
 -delete   delete z layer
 -get      print sequence of z layers
 -settings print status of z layer settings
 -disable  disables given setting
 -enable   enables  given setting
)" /* [vzlayer] */);

  addCmd("vlayerline", VLayerLine, /* [vlayerline] */ R"(
vlayerline x1 y1 x2 y2 [linewidth=0.5] [linetype=0] [transparency=1.0]
)" /* [vlayerline] */);

  addCmd("vgrid", VGrid, /* [vgrid] */ R"(
vgrid [off] [-type {rect|circ|gpu|shader}] [-gpu] [-mode {line|point}] [-origin X Y] [-rotAngle Angle] [-zoffset DZ]
      [-step X Y] [-size DX DY]
      [-step StepRadius NbDivisions] [-radius Radius] [-arc AngleStart AngleEnd]
      [-color R G B] [-tenthColor R G B] [-scale N] [-lineThickness T]
      [-background {0|1}] [-drawAxis {0|1}] [-viewAdaptive {0|1}]
Two render backends are available:
  - '-type rect' / '-type circ' (default) draws the legacy CPU grid into a
    bounded Graphic3d_Structure shared by every active view.
  - '-type gpu' / '-gpu' activates the shader-rendered grid on the active view.
    Without '-size' or '-radius' it is unbounded; combine with '-size DX DY'
    for a rectangle, '-radius R' for a disc, '-arc S E' for a wedge.
'-color', '-tenthColor' apply to both backends. '-scale', '-lineThickness',
'-background', '-drawAxis', '-viewAdaptive {0|1}' are GPU-grid only. Switching
to GPU-grid display hides the CPU grid in the viewer; switching back to a CPU
grid type erases the shader grid in the active view. 'off' deactivates whichever
grid is currently visible.
)" /* [vgrid] */);

  addCmd("vpriviledgedplane", VPriviledgedPlane, /* [vpriviledgedplane] */ R"(
vpriviledgedplane [Ox Oy Oz Nx Ny Nz [Xx Xy Xz]]
Sets or prints viewer's privileged plane geometry:
  Ox, Oy, Oz - plane origin;
  Nx, Ny, Nz - plane normal direction;
  Xx, Xy, Xz - plane x-reference axis direction.
)" /* [vpriviledgedplane] */);

  addCmd("vconvert", VConvert, /* [vconvert] */ R"(
vconvert v [Mode={window|view}]
vconvert x y [Mode={window|view|grid|ray}]
vconvert x y z [Mode={window|grid}]
Convert the given coordinates to window/view/model space:
 - window - convert to window coordinates, pixels;
 - view   - convert to view projection plane;
 - grid   - convert to model coordinates, given on grid;
 - ray    - convert projection ray to model coordinates.
)" /* [vconvert] */);

  addCmd("vfps", VFps, /* [vfps] */ R"(
vfps [framesNb=100] [-duration seconds] : estimate average frame rate for active view.
)" /* [vfps] */);

  addCmd("vstereo", VStereo, /* [vstereo] */ R"(
vstereo [0|1] [-mode Mode] [-reverse {0|1}]
        [-mirrorComposer] [-hmdfov2d AngleDegrees] [-unitFactor MetersFactor]
        [-anaglyph Filter] [-smoothInterlacing]
Control stereo output mode. Available modes for -mode:
  quadBuffer       OpenGL QuadBuffer stereo;
    requires driver support;
    should be called BEFORE vinit!
  anaglyph         Anaglyph glasses, filters for -anaglyph:
    redCyan, redCyanSimple, yellowBlue, yellowBlueSimple, greenMagentaSimple.
  rowInterlaced    row-interlaced display
    smooth         smooth interlaced output for better text readability
  columnInterlaced column-interlaced display
  chessBoard       chess-board output
  sideBySide       horizontal pair
  overUnder        vertical   pair
  openVR           OpenVR (HMD), extra options:
    -mirrorComposer flag to mirror VR frame in the window (debug);
    -unitFactor     specifies meters scale factor for mapping VR input.
)" /* [vstereo] */);

  addCmd("vmemgpu", VMemGpu, /* [vmemgpu] */ R"(
vmemgpu [f]: print system-dependent GPU memory information if available;
with f option returns free memory in bytes.
)" /* [vmemgpu] */);

  addCmd("vreadpixel", VReadPixel, /* [vreadpixel] */ R"(
vreadpixel xPixel yPixel [{rgb|rgba|sRGB|sRGBa|depth|hls|rgbf|rgbaf}=rgba] [-name|-hex]
Read pixel value for active view.
)" /* [vreadpixel] */);

  addCmd("diffimage", VDiffImage, /* [diffimage] */ R"(
diffimage imageFile1 imageFile2 [diffImageFile]
          [-toleranceOfColor {0..1}=0] [-blackWhite {on|off}=off] [-borderFilter {on|off}=off]
          [-display viewName prsName1 prsName2 prsNameDiff] [-exitOnClose] [-closeOnEscape]
Compare two images by content and generate difference image.
When -exitOnClose is specified, closing the view will exit application.
When -closeOnEscape is specified, view will be closed on pressing Escape.
)" /* [diffimage] */);

  addCmd("vselect", VSelect, /* [vselect] */ R"(
vselect x1 y1 [x2 y2 [x3 y3 ... xn yn]] [-allowoverlap 0|1]
        [-replace|-replaceextra|-xor|-add|-remove]
Emulate different types of selection:
 1) Single click selection.
 2) Selection with rectangle having corners at pixel positions (x1,y1) and (x2,y2).
 3) Selection with polygon having corners in pixel positions (x1,y1), (x2,y2),...,(xn,yn).
 4) -allowoverlap manages overlap and inclusion detection in rectangular and polygonal selection.
    If the flag is set to 1, both sensitives that were included completely
    and overlapped partially by defined rectangle or polygon will be detected,
    otherwise algorithm will chose only fully included sensitives.
    Default behavior is to detect only full inclusion
    (partial inclusion - overlap - is not allowed by default).
 5) Selection scheme replace, replaceextra, xor, add or remove (replace by default).
)" /* [vselect] */);

  addCmd("vselectpriority", VSelectPriority, /* [vselectpriority] */ R"(
vselectpriority name [value]
Prints or sets the selection priority for an object.
)" /* [vselectpriority] */);

  addCmd("vmoveto", VMoveTo, /* [vmoveto] */ R"(
vmoveto [x y] [-reset]
Emulate cursor movement to pixel position (x,y).
 -reset resets current highlighting.
)" /* [vmoveto] */);

  addCmd("vselaxis", VSelectByAxis, /* [vselaxis] */ R"(
vselaxis x y z dx dy dz [-onlyTop 0|1] [-display Name] [-showNormal 0|1]"
Provides intersection by given axis and print result intersection points.
 -onlyTop       switches On/Off mode to find only top point or all;
 -display Name  displays intersecting axis and result intersection points for debug goals;
 -showNormal    adds displaying of normal in intersection point or not.
)" /* [vselaxis] */);

  addCmd("vviewparams", VViewParams, /* [vviewparams] */ R"(
vviewparams [-args] [-scale [s]]
            [-eye [x y z]] [-at [x y z]] [-up [x y z]]
            [-proj [x y z]] [-center x y] [-size sx]
Manage current view parameters (camera orientation) or prints all
current values when called without argument.
 -scale [s]    prints or sets viewport relative scale
 -eye  [x y z] prints or sets eye location
 -at   [x y z] prints or sets center of look
 -up   [x y z] prints or sets direction of up vector
 -proj [x y z] prints or sets direction of look
 -center x y   sets location of center of the screen in pixels
 -size [sx]    prints viewport projection width and height sizes
               or changes the size of its maximum dimension
 -args         prints vviewparams arguments for restoring current view
)" /* [vviewparams] */);

  addCmd("v2dmode", V2DMode, /* [v2dmode] */ R"(
v2dmode [-name viewName] [-mode {-on|-off}=-on]
  name - name of existing view, if not defined, the active view is changed;
  mode - switches On/Off rotation mode.
Set 2D mode of the active viewer manipulating. The following mouse and key actions are disabled:
 - rotation of the view by 3rd mouse button with Ctrl active
 - set view projection using key buttons: A/D/T/B/L/R for AXO, Reset, Top, Bottom, Left, Right
View camera position might be changed only by commands.
)" /* [v2dmode] */);

  addCmd("vanimation", VAnimation, /* [vanimation] */ R"(
Alias for vanim
)" /* [vanimation] */);

  addCmd("vanim", VAnimation, /* [vanim] */ R"(
List existing animations:
  vanim

Animation playback:
  vanim name {-play|-resume|-pause|-stop} [playFrom [playDuration]] [-speed Coeff]
             [-freeLook] [-noPauseOnClick] [-lockLoop] [-elapsedTime]

  -speed          playback speed (1.0 is normal speed)
  -freeLook       skip camera animations
  -noPauseOnClick do not pause animation on mouse click
  -lockLoop       disable any interactions
  -elapsedTime    prints elapsed time in seconds"

Animation definition:
  vanim Name/sub/name [-clear] [-delete]
        [-start TimeSec] [-duration TimeSec] [-end TimeSec]

Animation name defined in path-style (anim/name or anim.name)
specifies nested animations.
There is no syntax to explicitly add new animation,
and all non-existing animations within the name will be
implicitly created on first use (including parents).

Each animation might define the SINGLE action (see below),
like camera transition, object transformation or custom callback.
Child animations can be used for defining concurrent actions.

Camera animation:
  vanim name -view [-eye1 X Y Z] [-eye2 X Y Z]
                   [-at1  X Y Z] [-at2  X Y Z]
                   [-up1  X Y Z] [-up2  X Y Z]
                   [-scale1 Scale] [-scale2 Scale]
  -eyeX   camera Eye positions pair (start and end)
  -atX    camera Center positions pair
  -upX    camera Up directions pair
  -scaleX camera Scale factors pair

Object animation:
  vanim name -object [-loc1 X Y Z] [-loc2 X Y Z]
                     [-rot1 QX QY QZ QW] [-rot2 QX QY QZ QW]
                     [-scale1 Scale] [-scale2 Scale]
 -locX   object Location points pair (translation)
 -rotX   object Orientations pair (quaternions)
 -scaleX object Scale factors pair (quaternions)

  vanim name -object [-axis OX OY OZ DX DY DZ] [-ang1 A] [-ang2 A]
 -axis   rotation axis
 -ang1   start rotation angle in degrees
 -ang2   end   rotation angle in degrees

Custom callback:
  vanim name -invoke "Command Arg1 Arg2 %Pts %LocalPts %Normalized ArgN"

  %Pts        overall animation presentation timestamp
  %LocalPts   local animation timestamp
  %Normalized local animation normalized value in range 0..1

Video recording:
  vanim name -record FileName [Width Height] [-fps FrameRate=24]
        [-format Format] [-vcodec Codec] [-pix_fmt PixelFormat]
        [-crf Value] [-preset Preset]
  -fps     video framerate
  -format  file format, container (matroska, etc.)
  -vcodec  video codec identifier (ffv1, mjpeg, etc.)
  -pix_fmt image pixel format (yuv420p, rgb24, etc.)
  -crf     constant rate factor (specific to codec)
  -preset  codec parameters preset (specific to codec)
)" /* [vanim] */);

  addCmd("vchangeselected", VChangeSelected, /* [vchangeselected] */ R"(
vchangeselected shape : Add shape to selection or remove one from it.
)" /* [vchangeselected] */);

  addCmd("vnbselected", VNbSelected, /* [vnbselected] */ R"(
vnbselected : Returns number of selected objects in the interactive context.
)" /* [vnbselected] */);

  addCmd("vcamera", VCamera, /* [vcamera] */ R"(
vcamera [PrsName] [-ortho] [-projtype]
        [-persp]
        [-fovy   [Angle]] [-distance [Distance]]
        [-stereo] [-leftEye] [-rightEye]
        [-iod [Distance]] [-iodType    [absolute|relative]]
        [-zfocus [Value]] [-zfocusType [absolute|relative]]
        [-fov2d  [Angle]] [-lockZup {0|1}]
        [-rotationMode {active|pick|pickCenter|cameraAt|scene}]
        [-navigationMode {orbit|walk|flight}]
        [-xrPose base|head=base]
Manages camera parameters.
Displays frustum when presentation name PrsName is specified.
Prints current value when option called without argument.

Orthographic camera:
 -ortho      activate orthographic projection.

Perspective camera:
 -persp      activate perspective  projection (mono);
 -fovy       field of view in y axis, in degrees;
 -fov2d      field of view limit for 2d on-screen elements;
 -distance   distance of eye from camera center;
 -lockZup    lock Z up (turntable mode);
 -rotationMode rotation mode (gravity point);
 -navigationMode navigation mode.

Stereoscopic camera:
 -stereo     perspective  projection (stereo);
 -leftEye    perspective  projection (left  eye);
 -rightEye   perspective  projection (right eye);
 -iod        intraocular distance value;
 -iodType    distance type, absolute or relative;
 -zfocus     stereographic focus value;
 -zfocusType focus type, absolute or relative.
)" /* [vcamera] */);

  addCmd("vautozfit", VAutoZFit, /* [vautozfit] */ R"(
vautozfit [on={1|0}] [scale]
Prints or changes parameters of automatic z-fit mode:
 "on" - turns automatic z-fit on or off;
 "scale" - specifies factor to scale computed z range.
)" /* [vautozfit] */);

  addCmd("vzrange", VZRange, /* [vzrange] */ R"(
vzrange [znear] [zfar]
Applies provided znear/zfar to view or prints current values.
)" /* [vzrange] */);

  addCmd("vsetviewsize", VSetViewSize, /* [vsetviewsize] */ R"(
vsetviewsize size
)" /* [vsetviewsize] */);

  addCmd("vmoveview", VMoveView, /* [vmoveview] */ R"(
vmoveview Dx Dy Dz [Start = 1|0]
)" /* [vmoveview] */);

  addCmd("vtranslateview", VTranslateView, /* [vtranslateview] */ R"(
vtranslateview Dx Dy Dz [Start = 1|0)]
)" /* [vtranslateview] */);

  addCmd("vturnview", VTurnView, /* [vturnview] */ R"(
vturnview Ax Ay Az [Start = 1|0]
)" /* [vturnview] */);

  addCmd("vtextureenv", VTextureEnv, /* [vtextureenv] */ R"(
vtextureenv {on|off} {image_file}
            [{clamp|repeat} {decal|modulate} {nearest|bilinear|trilinear} ss st ts tt rot]
Enables or disables environment mapping in the 3D view, loading the texture from the given standard
or user-defined file and optionally applying texture mapping parameters.
 ss, st - scale factors for s and t texture coordinates;
 ts, tt - translation for s and t texture coordinates;
 rot    - texture rotation angle in degrees.
)" /* [vtextureenv] */);

  addCmd("vhlr", VHLR, /* [vhlr] */ R"(
vhlr {on|off} [-showHidden={1|0}] [-algoType={algo|polyAlgo}] [-noupdate]
Hidden Line Removal algorithm.
 -showHidden if set ON, hidden lines are drawn as dotted ones;
 -algoType   type of HLR algorithm:
            'algo' - exact HLR algorithm is applied;
            'polyAlgo' - polygonal HLR algorithm is applied.
)" /* [vhlr] */);

  addCmd("vhlrtype", VHLRType, /* [vhlrtype] */ R"(
vhlrtype {algo|polyAlgo} [shape_1 ... shape_n] [-noupdate]
Changes the type of HLR algorithm using for shapes:
 'algo' - exact HLR algorithm is applied;
 'polyAlgo' - polygonal HLR algorithm is applied.
If shapes are not given - option is applied to all shapes in the view.
)" /* [vhlrtype] */);

  addCmd("vclipplane", VClipPlane, /* [vclipplane] */ R"(
vclipplane planeName [{0|1}]
    [-equation1 A B C D]
    [-equation2 A B C D]
    [-boxInterior MinX MinY MinZ MaxX MaxY MaxZ]
    [-set|-unset|-setOverrideGlobal [objects|views]]
    [-maxPlanes]
    [-capping {0|1}]
      [-color R G B] [-transparency Value] [-hatch {on|off|ID}]
      [-texName Texture] [-texScale SX SY] [-texOrigin TX TY]
        [-texRotate Angle]
      [-useObjMaterial {0|1}] [-useObjTexture {0|1}]
        [-useObjShader {0|1}]

Clipping planes management:
 -maxPlanes   print plane limit for view;
 -delete      delete plane with given name;
 {off|on|0|1} turn clipping on/off;
 -set|-unset  set/unset plane for Object or View list;
              applied to active View when list is omitted;
 -equation A B C D change plane equation;
 -clone SourcePlane NewPlane clone the plane definition.

Capping options:
 -capping {off|on|0|1} turn capping on/off;
 -color R G B          set capping color;
 -transparency Value   set capping transparency 0..1;
 -texName Texture      set capping texture;
 -texScale SX SY       set capping tex scale;
 -texOrigin TX TY      set capping tex origin;
 -texRotate Angle      set capping tex rotation;
 -hatch {on|off|ID}    set capping hatching mask;
 -useObjMaterial {off|on|0|1} use material of clipped object;
 -useObjTexture  {off|on|0|1} use texture of clipped object;
 -useObjShader   {off|on|0|1} use shader program of object.
)" /* [vclipplane] */);

  addCmd("vdefaults", VDefaults, /* [vdefaults] */ R"(
vdefaults [-absDefl value] [-devCoeff value] [-angDefl value]
          [-autoTriang {off/on | 0/1}]
)" /* [vdefaults] */);

  addCmd("vlight", VLight, /* [vlight] */ R"(
vlight [lightName] [-noupdate]
       [-clear|-defaults] [-layer Id] [-local|-global] [-disable|-enable]
       [-type {ambient|directional|spotlight|positional}] [-name value]
       [-position X Y Z] [-direction X Y Z] [-color colorName] [-intensity value]
       [-headlight 0|1] [-castShadows 0|1]
       [-range value] [-constAttenuation value] [-linearAttenuation value]
       [-spotExponent value] [-spotAngle angleDeg]
       [-smoothAngle value] [-smoothRadius value]
       [-display] [-showName 1|0] [-showRange 1|0] [-prsZoomable 1|0] [-prsSize Value]
       [-arcSize Value]

Command manages light sources. Without arguments shows list of lights.
Arguments affecting the list of defined/active lights:
 -clear       remove all light sources;
 -defaults    defines two standard light sources;
 -reset       resets light source parameters to default values;
 -type        sets type of light source;
 -name        sets new name to light source;
 -global      assigns light source to all views (default state);
 -local       assigns light source to active view;
 -zlayer      assigns light source to specified Z-Layer.

Ambient light parameters:
 -color       sets (normalized) light color;
 -intensity   sets intensity of light source, 1.0 by default;
              affects also environment cubemap intensity.

Point light parameters:
 -color       sets (normalized) light color;
 -intensity   sets PBR intensity;
 -range       sets clamping distance;
 -constAtten  (obsolete) sets constant attenuation factor;
 -linearAtten (obsolete) sets linear   attenuation factor;
 -smoothRadius sets PBR smoothing radius.

Directional light parameters:
 -color       sets (normalized) light color;
 -intensity   sets PBR intensity;
 -direction   sets direction;
 -headlight   sets headlight flag;
 -castShadows enables/disables shadow casting;
 -smoothAngle sets PBR smoothing angle (in degrees) within 0..90 range.

Spot light parameters:
 -color       sets (normalized) light color;
 -intensity   sets PBR intensity;
 -range       sets clamping distance;
 -position    sets position;
 -direction   sets direction;
 -spotAngle   sets spotlight angle;
 -spotExp     sets spotlight exponenta;
 -headlight   sets headlight flag;
 -castShadows enables/disables shadow casting;
 -constAtten  (obsolete) sets constant attenuation factor;
 -linearAtten (obsolete) sets linear   attenuation factor.

Light presentation parameters:
 -display     adds light source presentation;
 -showName    shows/hides the name of light source; 1 by default;
 -showRange   shows/hides the range of spot/positional light source; 1 by default;
 -prsZoomable makes light presentation zoomable/non-zoomable;
 -prsDraggable makes light presentation draggable/non-draggable;
 -prsSize     sets light presentation size;
 -arcSize     sets arc presentation size(in pixels)
              for rotation directional light source; 25 by default.

Examples:
 vlight redlight -type POSITIONAL -headlight 1 -pos 0 1 1 -color RED
 vlight redlight -delete
)" /* [vlight] */);

  addCmd("vpbrenv", VPBREnvironment, /* [vpbrenv] */ R"(
vpbrenv -clear|-generate
Clears or generates PBR environment map of active view.
 -clear clears PBR environment (fills by white color);
 -generate generates PBR environment from current background cubemap.
)" /* [vpbrenv] */);

  addCmd("vraytrace", VRenderParams, /* [vraytrace] */ R"(
vraytrace [0|1] : Turns on/off ray-tracing renderer.
 'vraytrace 0' alias for 'vrenderparams -raster'.
 'vraytrace 1' alias for 'vrenderparams -rayTrace'.
)" /* [vraytrace] */);

  addCmd("vrenderparams", VRenderParams, /* [vrenderparams] */ R"(
Manages rendering parameters, affecting visual appearance, quality and performance.
Should be applied taking into account GPU hardware capabilities and performance.

Common parameters:
vrenderparams [-raster] [-shadingModel {unlit|facet|gouraud|phong|pbr|pbr_facet}=gouraud]
              [-msaa 0..8=0] [-rendScale scale=1]
              [-resolution value=72] [-fontHinting {off|normal|light}=off]
              [-fontAutoHinting {auto|force|disallow}=auto]
              [-oit {off|weight|peel}] [-oit weighted [depthFactor=0.0]] [-oit peeling [nbLayers=4]]
              [-shadows {on|off}=on] [-shadowMapResolution value=1024] [-shadowMapBias value=0.005]
              [-depthPrePass {on|off}=off] [-alphaToCoverage {on|off}=on]
              [-frustumCulling {on|off|noupdate}=on] [-lineFeather width=1.0]
              [-sync {default|views}] [-reset] [-flip {on|off}=off]
 -raster          Disables GPU ray-tracing.
 -shadingModel    Controls shading model.
 -msaa            Specifies number of samples for MSAA.
 -rendScale       Rendering resolution scale factor (supersampling, alternative to MSAA).
 -resolution      Sets new pixels density (PPI) used as text scaling factor.
 -fontHinting     Enables/disables font hinting for better readability on low-resolution screens.
 -fontAutoHinting Manages font autohinting.
 -lineFeather     Sets line feather factor while displaying mesh edges.
 -alphaToCoverage Enables/disables alpha to coverage (needs MSAA).
 -oit             Enables/disables order-independent transparency (OIT) rendering;
      off         unordered transparency (but opaque objects implicitly draw first);
      weighted    weight OIT is managed by depth weight factor 0.0..1.0;
      peeling     depth peeling OIT is managed by number of peeling layers.
  -shadows         Enables/disables shadows rendering.
  -shadowMapResolution Shadow texture map resolution.
  -shadowMapBias   Shadow map bias.
  -depthPrePass    Enables/disables depth pre-pass.
  -frustumCulling  Enables/disables objects frustum clipping or
                   sets state to check structures culled previously.
  -sync            Sets active View parameters as Viewer defaults / to other Views.
  -reset           Resets active View parameters to Viewer defaults.
  -flip            Flips View Vertically.

Diagnostic output (on-screen overlay):
vrenderparams [-perfCounters none|fps|cpu|layers|structures|groups|arrays|triangles|points
                                 |gpuMem|frameTime|basic|extended|full|nofps|skipImmediate]
              [-perfUpdateInterval nbSeconds=1] [-perfChart nbFrames=1] [-perfChartMax seconds=0.1]
 -perfCounters       Show/hide performance counters (flags can be combined).
 -perfUpdateInterval Performance counters update interval.
 -perfChart          Show frame timers chart limited by specified number of frames.
 -perfChartMax       Maximum time in seconds with the chart.

Ray-Tracing options:
vrenderparams [-rayTrace] [-rayDepth {0..10}=3] [-reflections {on|off}=off]
              [-fsaa {on|off}=off] [-gleam {on|off}=off] [-env {on|off}=off]
              [-gi {on|off}=off] [-brng {on|off}=off]
              [-iss {on|off}=off] [-tileSize {1..4096}=32] [-nbTiles {64..1024}=256]
              [-ignoreNormalMap {on|off}=off] [-twoSide {on|off}=off]
              [-maxRad {value>0}=30.0]
              [-aperture {value>=0}=0.0] [-focal {value>=0.0}=1.0]
              [-exposure value=0.0] [-whitePoint value=1.0] [-toneMapping {disabled|filmic}=disabled]
 -rayTrace     Enables  GPU ray-tracing.
 -rayDepth     Defines maximum ray-tracing depth.
 -reflections  Enables/disables specular reflections.
 -fsaa         Enables/disables adaptive anti-aliasing.
 -gleam        Enables/disables transparency shadow effects.
 -gi           Enables/disables global illumination effects (Path-Tracing).
 -env          Enables/disables environment map background.
 -ignoreNormalMap Enables/disables normal map ignoring during path tracing.
 -twoSide      Enables/disables two-sided BSDF models (PT mode).
 -iss          Enables/disables adaptive screen sampling (PT mode).
 -maxRad       Value used for clamping radiance estimation (PT mode).
 -tileSize     Specifies   size of screen tiles in ISS mode (32 by default).
 -nbTiles      Specifies number of screen tiles per Redraw in ISS mode (256 by default).
 -aperture     Aperture size  of perspective camera for depth-of-field effect (0 disables DOF).
 -focal        Focal distance of perspective camera for depth-of-field effect.
 -exposure     Exposure value for tone mapping (0.0 value disables the effect).
 -whitePoint   White point value for filmic tone mapping.
 -toneMapping  Tone mapping mode (disabled, filmic).

PBR environment baking parameters (advanced/debug):
vrenderparams [-pbrEnvPow2size {power>0}=9] [-pbrEnvSMLN {levels>1}=6] [-pbrEnvBP {0..1}=0.99]
              [-pbrEnvBDSN {samples>0}=1024] [-pbrEnvBSSN {samples>0}=256]
 -pbrEnvPow2size Controls size of IBL maps (real size can be calculates as 2^pbrenvpow2size).
 -pbrEnvSMLN     Controls number of mipmap levels used in specular IBL map.
 -pbrEnvBDSN     Controls number of samples in Monte-Carlo integration during
                 diffuse IBL map's sherical harmonics calculation.
 -pbrEnvBSSN     Controls maximum number of samples per mipmap level
                 in Monte-Carlo integration during specular IBL maps generation.
 -pbrEnvBP       Controls strength of samples number reducing
                 during specular IBL maps generation (1 disables reducing).

Debug options:
vrenderparams [-issd {on|off}=off] [-rebuildGlsl on|off]
 -issd         Shows screen sampling distribution in ISS mode.
 -rebuildGlsl  Rebuild Ray-Tracing GLSL programs (for debugging).
 -brng         Enables/disables blocked RNG (fast coherent PT).
)" /* [vrenderparams] */);

  addCmd("vstatprofiler", VStatProfiler, /* [vstatprofiler] */ R"(
vstatprofiler [fps|cpu|allLayers|layers|allstructures|structures|groups
                |allArrays|fillArrays|lineArrays|pointArrays|textArrays
                |triangles|points|geomMem|textureMem|frameMem
                |elapsedFrame|cpuFrameAverage|cpuPickingAverage|cpuCullingAverage|cpuDynAverage
                |cpuFrameMax|cpuPickingMax|cpuCullingMax|cpuDynMax]
              [-noredraw]
Prints rendering statistics for specified counters or for all when unspecified.
Set '-noredraw' flag to avoid additional redraw call and use already collected values.
)" /* [vstatprofiler] */);

  addCmd("vplace", VPlace, /* [vplace] */ R"(
vplace dx dy : Places the point (in pixels) at the center of the window
)" /* [vplace] */);

  addCmd("vxrotate", VXRotate, /* [vxrotate] */ R"(
vxrotate
)" /* [vxrotate] */);

  addCmd("vmanipulator", VManipulator, /* [vmanipulator] */ R"(
vmanipulator Name [-attach AISObject | -detach | ...]
Tool to create and manage AIS manipulators.
Options:
 '-attach AISObject'                 attach manipulator to AISObject
 '-adjustPosition {0|center|location|shapeLocation}' adjust position when attaching
 '-adjustSize     {0|1}'             adjust size when attaching
 '-enableModes    {0|1}'             enable modes when attaching
 '-view  {active | [name of view]}'  display manipulator only in defined view,
                                     by default it is displayed in all views of the current viewer
 '-addObject                         allows attach manipulator to multiple objects (replace by default)
 '-detach'                           detach manipulator
 '-startTransform mouse_x mouse_y' - invoke start of transformation
 '-transform      mouse_x mouse_y' - invoke transformation
 '-stopTransform  [abort]'         - invoke stop of transformation
 '-move x y z'                     - move attached object
 '-rotate x y z dx dy dz angle'    - rotate attached object
 '-scale factor'                   - scale attached object
 '-autoActivate      {0|1}'        - set activation on detection
 '-followTranslation {0|1}'        - set following translation transform
 '-followRotation    {0|1}'        - set following rotation transform
 '-followDragging    {0|1}'        - set following dragging transform
 '-gap value'                      - set gap between sub-parts
 '-part axis mode    {0|1}'        - set visual part
 '-parts axis mode   {0|1}'        - set visual part
 '-pos x y z [nx ny nz [xx xy xz]' - set position of manipulator
 '-size value'                     - set size of manipulator
 '-zoomable {0|1}'                 - set zoom persistence
 '-flat {0|1}'                     - set flat skin mode",
)" /* [vmanipulator] */);

  addCmd("vselprops", VSelectionProperties, /* [vselprops] */ R"(
vselprops [dynHighlight|localDynHighlight|selHighlight|localSelHighlight] [options]
Customizes selection and dynamic highlight parameters for the whole interactive context:
 -autoActivate {0|1}     disables|enables default computation
                         and activation of global selection mode
 -autoHighlight {0|1}    disables|enables automatic highlighting in 3D Viewer
 -highlightSelected {0|1} disables|enables highlighting of detected object in selected state
 -pickStrategy {first|topmost} : defines picking strategy
               'first'   to pick first acceptable (default)
               'topmost' to pick only topmost (and nothing, if topmost is rejected by filters)
 -pixTol    value        sets up pixel tolerance
 -depthTol {uniform|uniformpx} value : sets tolerance for sorting results by depth
 -depthTol {sensfactor}  use sensitive factor for sorting results by depth
 -preferClosest {0|1}    sets if depth should take precedence over priority while sorting results
 -dispMode  dispMode     sets display mode for highlighting
 -layer     ZLayer       sets ZLayer for highlighting
 -color     {name|r g b} sets highlight color
 -transp    value        sets transparency coefficient for highlight
 -material  material     sets highlight material
 -print                  prints current state of all mentioned parameters
)" /* [vselprops] */);

  addCmd("vhighlightselected", VSelectionProperties, /* [vhighlightselected] */ R"(
vhighlightselected [0|1] : alias for vselprops -highlightSelected.
)" /* [vhighlightselected] */);

  addCmd("vseldump", VDumpSelectionImage, /* [vseldump] */ R"(
vseldump file -type {depth|unnormDepth|object|owner|selMode|entity|entityType|surfNormal}=depth
         -pickedIndex Index=1
         [-xrPose base|head=base]
Generate an image based on detection results:
  depth       normalized depth values
  unnormDepth unnormalized depth values
  object      color of detected object
  owner       color of detected owner
  selMode     color of selection mode
  entity      color of detected entity
  entityType  color of detected entity type
  surfNormal  normal direction values
)" /* [vseldump] */);

  addCmd("vviewcube", VViewCube, /* [vviewcube] */ R"(
vviewcube name
Displays interactive view manipulation object. Options:
 -reset                   reset geometric and visual attributes
 -size Size               adapted size of View Cube
 -boxSize Size            box size
 -axes  {0|1}             show/hide axes (trihedron)
 -edges {0|1}             show/hide edges of View Cube
 -vertices {0|1}          show/hide vertices of View Cube
 -Yup {0|1} -Zup {0|1}    set Y-up or Z-up view orientation
 -color Color             color of View Cube
 -boxColor Color          box color
 -boxSideColor Color      box sides color
 -boxEdgeColor Color      box edges color
 -boxCornerColor Color    box corner color
 -textColor Color         color of side text of view cube
 -innerColor Color        inner box color
 -transparency Value      transparency of object within [0, 1] range
 -boxTransparency Value   transparency of box    within [0, 1] range
 -xAxisTextColor Color    color of X axis label
 -yAxisTextColor Color    color of Y axis label
 -zAxisTextColor Color    color of Z axis label
 -font Name               font name
 -fontHeight Value        font height
 -boxFacetExtension Value box facet extension
 -boxEdgeGap Value        gap between box edges and box sides
 -boxEdgeMinSize Value    minimal box edge size
 -boxCornerMinSize Value  minimal box corner size
 -axesPadding Value       padding between box and arrows
 -roundRadius Value       relative radius of corners of sides within [0.0, 0.5] range
 -axesRadius Value        radius of axes of the trihedron
 -axesConeRadius Value    radius of the cone (arrow) of the trihedron
 -axesSphereRadius Value  radius of the sphere (central point) of trihedron
 -fixedAnimation {0|1}    uninterruptible animation loop
 -duration Seconds        animation duration in seconds
 -orthoPers               force orthographic projection persistence.
)" /* [vviewcube] */);

  addCmd("vcolorconvert", VColorConvert, /* [vcolorconvert] */ R"(
vcolorconvert [-from {sRGB|HLS|Lab|Lch|RGB}]=RGB [-to {sRGB|HLS|Lab|Lch|RGB|hex|name}]=RGB C1 C2 C2
To convert color from specified color space to linear RGB:
  vcolorconvert -from {sRGB|HLS|Lab|Lch|RGB} C1 C2 C2
To convert linear RGB color to specified color space:
  vcolorconvert -to {sRGB|HLS|Lab|Lch|RGB|hex|name} R G B
)" /* [vcolorconvert] */);

  addCmd("vcolordiff", VColorDiff, /* [vcolordiff] */ R"(
vcolordiff R1 G1 B1 R2 G2 B2 : returns CIEDE2000 color difference between two RGB colors.
)" /* [vcolordiff] */);

  addCmd("vselbvhbuild", VSelBvhBuild, /* [vselbvhbuild] */ R"(
vselbvhbuild [{0|1}] [-nbThreads value] [-wait]
Turns on/off prebuilding of BVH within background thread(s).
 -nbThreads   number of threads, 1 by default; if < 1 then used (NbLogicalProcessors - 1);
 -wait        waits for building all of BVH.
)" /* [vselbvhbuild] */);

  addCmd("vchangemousegesture", VChangeMouseGesture, /* [vchangemousegesture] */ R"(
vchangemousegesture -button  {none|left|middle|right}=left
                    -gesture {none|selectRectangle|selectLasso|zoom|zoomWindow|pan|rotateOrbit|rotateView|drag}=rotateOrbit
Changes the gesture for the mouse button.
 -button  the mouse button;
 -gesture the new gesture for the button.
)" /* [vchangemousegesture] */);
}
