0029384: Visualization, TKOpenGl - basic integration with OpenVR CR0-WEEK-18
authorkgv <kgv@opencascade.com>
Thu, 16 Apr 2020 15:44:50 +0000 (18:44 +0300)
committerkgv <kgv@opencascade.com>
Wed, 6 May 2020 17:45:53 +0000 (20:45 +0300)
V3d_View::AutoZFit() is now called only before redraw
within methods V3d_View::Redraw() and V3d_View::Update().

Graphic3d_CView now holds Aspect_ExtendedRealitySession object.
Aspect_OpenVRSession implements new interface via optional OpenVR library.
Graphic3d_CView::ProcessInput() - added new interface method
which should be called for processing positional input (head tracking).

Graphic3d_Camera now allows setting custom stereoscopic Projection matrices.

OpenGl_Context::Camera() - context now holds Camera object
in addition to active camera matrices.

genproj.tcl has been extended to handle optional CSF_OpenVR dependency.

69 files changed:
adm/RESOURCES
adm/UDLIST
adm/genconf.tcl
adm/genconfdeps.tcl
adm/genproj.tcl
adm/templates/env.bat
adm/templates/env.sh
src/AIS/AIS_ViewController.cxx
src/AIS/AIS_ViewController.hxx
src/AIS/AIS_XRTrackedDevice.cxx [new file with mode: 0644]
src/AIS/AIS_XRTrackedDevice.hxx [new file with mode: 0644]
src/AIS/FILES
src/Aspect/Aspect_ColorSpace.hxx [new file with mode: 0644]
src/Aspect/Aspect_Eye.hxx [new file with mode: 0644]
src/Aspect/Aspect_FrustumLRBT.hxx [new file with mode: 0644]
src/Aspect/Aspect_GraphicsLibrary.hxx [new file with mode: 0644]
src/Aspect/Aspect_OpenVRSession.cxx [new file with mode: 0644]
src/Aspect/Aspect_OpenVRSession.hxx [new file with mode: 0644]
src/Aspect/Aspect_TrackedDevicePose.hxx [new file with mode: 0644]
src/Aspect/Aspect_XRAction.hxx [new file with mode: 0644]
src/Aspect/Aspect_XRActionSet.hxx [new file with mode: 0644]
src/Aspect/Aspect_XRActionType.hxx [new file with mode: 0644]
src/Aspect/Aspect_XRAnalogActionData.hxx [new file with mode: 0644]
src/Aspect/Aspect_XRDigitalActionData.hxx [new file with mode: 0644]
src/Aspect/Aspect_XRGenericAction.hxx [new file with mode: 0644]
src/Aspect/Aspect_XRHapticActionData.hxx [new file with mode: 0644]
src/Aspect/Aspect_XRPoseActionData.hxx [new file with mode: 0644]
src/Aspect/Aspect_XRSession.cxx [new file with mode: 0644]
src/Aspect/Aspect_XRSession.hxx [new file with mode: 0644]
src/Aspect/Aspect_XRTrackedDeviceRole.hxx [new file with mode: 0644]
src/Aspect/FILES
src/Draw/Draw_BasicCommands.cxx
src/Graphic3d/Graphic3d_CView.cxx
src/Graphic3d/Graphic3d_CView.hxx
src/Graphic3d/Graphic3d_Camera.cxx
src/Graphic3d/Graphic3d_Camera.hxx
src/Graphic3d/Graphic3d_RenderingParams.hxx
src/Graphic3d/Graphic3d_StereoMode.hxx
src/Graphic3d/Graphic3d_TransformPers.hxx
src/OS/Visualization.tcl
src/OpenGl/OpenGl_Context.cxx
src/OpenGl/OpenGl_Context.hxx
src/OpenGl/OpenGl_FrameStatsPrs.cxx
src/OpenGl/OpenGl_GraduatedTrihedron.cxx
src/OpenGl/OpenGl_ShaderManager.cxx
src/OpenGl/OpenGl_Structure.cxx
src/OpenGl/OpenGl_Text.cxx
src/OpenGl/OpenGl_View.cxx
src/OpenGl/OpenGl_View.hxx
src/OpenGl/OpenGl_View_Redraw.cxx
src/OpenGl/OpenGl_Window.cxx
src/OpenGl/OpenGl_Window.hxx
src/OpenGl/OpenGl_Window_1.mm
src/SelectMgr/SelectMgr_ViewerSelector.hxx
src/TKService/EXTERNLIB
src/V3d/V3d_View.cxx
src/V3d/V3d_View_3.cxx
src/ViewerTest/ViewerTest.cxx
src/ViewerTest/ViewerTest_ViewerCommands.cxx
src/XRResources/FILES [new file with mode: 0644]
src/XRResources/occtvr_actions.json [new file with mode: 0644]
src/XRResources/occtvr_bindings_generic.json [new file with mode: 0644]
src/XRResources/occtvr_bindings_holographic_hmd.json [new file with mode: 0644]
src/XRResources/occtvr_bindings_index_hmd.json [new file with mode: 0644]
src/XRResources/occtvr_bindings_rift.json [new file with mode: 0644]
src/XRResources/occtvr_bindings_vive.json [new file with mode: 0644]
src/XRResources/occtvr_bindings_vive_controller.json [new file with mode: 0644]
src/XRResources/occtvr_bindings_vive_cosmos.json [new file with mode: 0644]
src/XRResources/occtvr_bindings_vive_pro.json [new file with mode: 0644]

index 4ee7b15..a0dd9bf 100644 (file)
@@ -3,6 +3,7 @@ StdResource
 SHMessage
 Textures
 Shaders
+XRResources
 XSMessage
 XSTEPResource
 XmlOcafResource
index bb83ccd..9952c01 100644 (file)
@@ -222,6 +222,7 @@ n Xw
 n Cocoa
 r Textures
 r Shaders
+r XRResources
 t TKMeshVS
 t TKOpenGl
 t TKD3DHost
index 0bad141..ce39bc4 100644 (file)
@@ -187,6 +187,9 @@ proc wokdep:gui:UpdateList {} {
   if { "$::HAVE_FFMPEG" == "true" } {
     wokdep:SearchFFmpeg  anIncErrs anLib32Errs anLib64Errs anBin32Errs anBin64Errs
   }
+  if { "$::HAVE_OPENVR" == "true" } {
+    wokdep:SearchOpenVR  anIncErrs anLib32Errs anLib64Errs anBin32Errs anBin64Errs
+  }
   if { "$::HAVE_TBB" == "true" } {
     wokdep:SearchTBB     anIncErrs anLib32Errs anLib64Errs anBin32Errs anBin64Errs
   }
@@ -472,6 +475,8 @@ checkbutton   .myFrame.myChecks.myFImageCheck   -offvalue "false" -onvalue "true
 ttk::label    .myFrame.myChecks.myFImageLbl     -text "Use FreeImage"
 checkbutton   .myFrame.myChecks.myTbbCheck      -offvalue "false" -onvalue "true" -variable HAVE_TBB       -command wokdep:gui:UpdateList
 ttk::label    .myFrame.myChecks.myTbbLbl        -text "Use Intel TBB"
+checkbutton   .myFrame.myChecks.myOpenVrCheck   -offvalue "false" -onvalue "true" -variable HAVE_OPENVR    -command wokdep:gui:UpdateList
+ttk::label    .myFrame.myChecks.myOpenVrLbl     -text "Use OpenVR"
 if { "$::tcl_platform(os)" != "Darwin" } {
   checkbutton .myFrame.myChecks.myGlesCheck     -offvalue "false" -onvalue "true" -variable HAVE_GLES2     -command wokdep:gui:UpdateList
   ttk::label  .myFrame.myChecks.myGlesLbl       -text "Use OpenGL ES"
@@ -635,6 +640,8 @@ grid .myFrame.myChecks.myJDKLbl        -row $aCheckRowIter -column 13 -sticky w
 incr aCheckRowIter
 grid .myFrame.myChecks.myRapidJsonCheck -row $aCheckRowIter -column 0 -sticky e
 grid .myFrame.myChecks.myRapidJsonLbl   -row $aCheckRowIter -column 1 -sticky w
+grid .myFrame.myChecks.myOpenVrCheck   -row $aCheckRowIter -column 4 -sticky e
+grid .myFrame.myChecks.myOpenVrLbl     -row $aCheckRowIter -column 5 -sticky w
 grid .myFrame.myChecks.myE57Check      -row $aCheckRowIter -column 6 -sticky e
 grid .myFrame.myChecks.myE57Lbl        -row $aCheckRowIter -column 7 -sticky w
 
index 1b15ac0..85715cf 100644 (file)
@@ -68,7 +68,7 @@ if { [info exists ::env(SHORTCUT_HEADERS)] } {
 }
 
 # fetch environment variables (e.g. set by custom.sh or custom.bat) and set them as tcl variables with the same name
-set THE_ENV_VARIABLES {HAVE_FREEIMAGE HAVE_FFMPEG HAVE_TBB HAVE_GLES2 HAVE_D3D HAVE_VTK HAVE_ZLIB HAVE_LIBLZMA HAVE_E57 HAVE_RAPIDJSON HAVE_OPENCL CHECK_QT4 CHECK_JDK MACOSX_USE_GLX HAVE_RelWithDebInfo BUILD_Inspector}
+set THE_ENV_VARIABLES {HAVE_FREEIMAGE HAVE_FFMPEG HAVE_TBB HAVE_GLES2 HAVE_D3D HAVE_VTK HAVE_ZLIB HAVE_LIBLZMA HAVE_E57 HAVE_RAPIDJSON HAVE_OPENVR HAVE_OPENCL CHECK_QT4 CHECK_JDK MACOSX_USE_GLX HAVE_RelWithDebInfo BUILD_Inspector}
 foreach anEnvIter $THE_ENV_VARIABLES {
   set ${anEnvIter} "false"
   if { [info exists ::env(${anEnvIter})] } {
@@ -625,6 +625,59 @@ proc wokdep:SearchFFmpeg {theErrInc theErrLib32 theErrLib64 theErrBin32 theErrBi
   return "$isFound"
 }
 
+# Search OpenVR SDK placement
+proc wokdep:SearchOpenVR {theErrInc theErrLib32 theErrLib64 theErrBin32 theErrBin64} {
+  upvar $theErrInc   anErrInc
+  upvar $theErrLib32 anErrLib32
+  upvar $theErrLib64 anErrLib64
+  upvar $theErrBin32 anErrBin32
+  upvar $theErrBin64 anErrBin64
+
+  set isFound "true"
+  set anOpenVrHPath [wokdep:SearchHeader "openvr.h"]
+  if { "$anOpenVrHPath"  == "" } {
+    set aPath [wokdep:Preferred [glob -nocomplain -directory "$::PRODUCTS_PATH" -type d *{openvr}*] "$::VCVER" "$::ARCH" ]
+    if { "$aPath" != "" && [file exists "$aPath/include/openvr.h"] } {
+      lappend ::CSF_OPT_INC "$aPath/include"
+    } elseif { "$aPath" != "" && [file exists "$aPath/headers/openvr.h"] } {
+      lappend ::CSF_OPT_INC "$aPath/headers"
+    } else {
+      lappend anErrInc "Error: 'openvr.h' not found (OpenVR)"
+      set isFound "false"
+    }
+  }
+
+  set aPlatform "unknown"
+  if { "$::tcl_platform(platform)" == "windows" } {
+    set aPlatform "win"
+  } elseif { "$::tcl_platform(os)" == "Darwin" } {
+    set aPlatform "osx"
+  } elseif { "$::tcl_platform(os)" == "Linux" } {
+    set aPlatform "linux"
+  }
+
+  foreach anArchIter {64 32} {
+    set anOpenVrLibPath [wokdep:SearchLib "openvr_api" "$anArchIter"]
+    if { "$anOpenVrLibPath" == "" } {
+      set aPath [wokdep:Preferred [glob -nocomplain -directory "$::PRODUCTS_PATH" -type d *{openvr}*] "$::VCVER" "$anArchIter" ]
+      set anOpenVrLibPath  [wokdep:SearchLib "openvr_api" "$anArchIter" "$aPath/lib/${aPlatform}${anArchIter}"]
+      set anOpenVrLibPath2 [wokdep:SearchLib "openvr_api" "$anArchIter" "$aPath/lib"]
+      if { "$anOpenVrLibPath" != "" } {
+        lappend ::CSF_OPT_LIB$anArchIter "$aPath/lib/${aPlatform}${anArchIter}"
+        lappend ::CSF_OPT_BIN$anArchIter "$aPath/bin/${aPlatform}${anArchIter}"
+      } elseif { "$anOpenVrLibPath2" != "" } {
+        lappend ::CSF_OPT_LIB$anArchIter "$aPath/lib"
+        lappend ::CSF_OPT_BIN$anArchIter "$aPath/bin"
+      } else {
+        lappend anErrLib$anArchIter "Error: '${::SYS_LIB_PREFIX}openvr_api.${::SYS_LIB_SUFFIX}' not found (OpenVR)"
+        if { "$::ARCH" == "$anArchIter"} { set isFound "false" }
+      }
+    }
+  }
+
+  return "$isFound"
+}
+
 # Search TBB library placement
 proc wokdep:SearchTBB {theErrInc theErrLib32 theErrLib64 theErrBin32 theErrBin64} {
   upvar $theErrInc   anErrInc
index 961c42e..501bf1c 100644 (file)
@@ -1440,6 +1440,9 @@ proc osutils:csfList { theOS theCsfLibsMap theCsfFrmsMap theRelease} {
   if { "$::HAVE_LIBLZMA" == "true" } {
     set aLibsMap(CSF_LIBLZMA) "liblzma"
   }
+  if { "$::HAVE_OPENVR" == "true" } {
+    set aLibsMap(CSF_OpenVR) "openvr_api"
+  }
   if { "$::HAVE_E57" == "true" && "$theOS" != "wnt" } {
     # exclude wnt, as there are different pragma lib depending on debug/release
     set aLibsMap(CSF_E57)    "E57RefImpl"
index b262e10..203e972 100644 (file)
@@ -25,6 +25,7 @@ set "HAVE_D3D=false"
 set "HAVE_ZLIB=false"
 set "HAVE_LIBLZMA=false"
 set "HAVE_RAPIDJSON=false"
+set "HAVE_OPENVR=false"
 set "HAVE_E57=false"
 set "CSF_OPT_INC="
 set "CSF_OPT_LIB32="
@@ -189,6 +190,7 @@ if ["%HAVE_D3D%"]       == ["true"] set "PRODUCTS_DEFINES=%PRODUCTS_DEFINES% -DH
 if ["%HAVE_ZLIB%"]      == ["true"] set "PRODUCTS_DEFINES=%PRODUCTS_DEFINES% -DHAVE_ZLIB"      & set "CSF_DEFINES=HAVE_ZLIB;%CSF_DEFINES%"
 if ["%HAVE_LIBLZMA%"]   == ["true"] set "PRODUCTS_DEFINES=%PRODUCTS_DEFINES% -DHAVE_LIBLZMA"   & set "CSF_DEFINES=HAVE_LIBLZMA;%CSF_DEFINES%"
 if ["%HAVE_RAPIDJSON%"] == ["true"] set "PRODUCTS_DEFINES=%PRODUCTS_DEFINES% -DHAVE_RAPIDJSON" & set "CSF_DEFINES=HAVE_RAPIDJSON;%CSF_DEFINES%"
+if ["%HAVE_OPENVR%"]    == ["true"] set "PRODUCTS_DEFINES=%PRODUCTS_DEFINES% -DHAVE_OPENVR"    & set "CSF_DEFINES=HAVE_OPENVR;%CSF_DEFINES%"
 if ["%HAVE_E57%"]       == ["true"] set "PRODUCTS_DEFINES=%PRODUCTS_DEFINES% -DHAVE_E57"       & set "CSF_DEFINES=HAVE_E57;%CSF_DEFINES%"
 
 rem Eliminate VS warning
index b761534..578b3ef 100644 (file)
@@ -16,6 +16,7 @@ export HAVE_GLES2="false";
 export HAVE_ZLIB="false";
 export HAVE_LIBLZMA="false";
 export HAVE_RAPIDJSON="false";
+export HAVE_OPENVR="false";
 export HAVE_E57="false";
 export MACOSX_USE_GLX="false";
 export CSF_OPT_INC=""
@@ -106,6 +107,7 @@ if [ "$HAVE_VTK"       == "true" ]; then export CSF_OPT_CMPL="${CSF_OPT_CMPL} -D
 if [ "$HAVE_ZLIB"      == "true" ]; then export CSF_OPT_CMPL="${CSF_OPT_CMPL} -DHAVE_ZLIB"; fi
 if [ "$HAVE_LIBLZMA"   == "true" ]; then export CSF_OPT_CMPL="${CSF_OPT_CMPL} -DHAVE_LIBLZMA"; fi
 if [ "$HAVE_RAPIDJSON" == "true" ]; then export CSF_OPT_CMPL="${CSF_OPT_CMPL} -DHAVE_RAPIDJSON"; fi
+if [ "$HAVE_OPENVR"    == "true" ]; then export CSF_OPT_CMPL="${CSF_OPT_CMPL} -DHAVE_OPENVR"; fi
 if [ "$HAVE_E57"       == "true" ]; then export CSF_OPT_CMPL="${CSF_OPT_CMPL} -DHAVE_E57"; fi
 # Option to compile OCCT with X11 libs on Mac OS X
 if [ "$MACOSX_USE_GLX" == "true" ]; then export CSF_OPT_CMPL="${CSF_OPT_CMPL} -DMACOSX_USE_GLX"; fi
index 0d34520..83fbaf0 100644 (file)
 #include <AIS_Manipulator.hxx>
 #include <AIS_Point.hxx>
 #include <AIS_RubberBand.hxx>
+#include <AIS_XRTrackedDevice.hxx>
+#include <Aspect_XRSession.hxx>
 #include <Aspect_Grid.hxx>
 #include <Geom_CartesianPoint.hxx>
+#include <Graphic3d_ArrayOfSegments.hxx>
+#include <Graphic3d_Texture2Dmanual.hxx>
 #include <Message.hxx>
 #include <Message_Messenger.hxx>
 #include <gp_Quaternion.hxx>
@@ -60,6 +64,17 @@ AIS_ViewController::AIS_ViewController()
   myPrevMoveTo (-1, -1),
   myHasHlrOnBeforeRotation (false),
   //
+  myXRPrsDevices (0, 0),
+  myXRLaserTeleColor (Quantity_NOC_GREEN),
+  myXRLaserPickColor (Quantity_NOC_BLUE),
+  myXRLastTeleportHand(Aspect_XRTrackedDeviceRole_Other),
+  myXRLastPickingHand (Aspect_XRTrackedDeviceRole_Other),
+  myXRLastPickDepthLeft (Precision::Infinite()),
+  myXRLastPickDepthRight(Precision::Infinite()),
+  myXRTurnAngle (M_PI_4),
+  myToDisplayXRAuxDevices (false),
+  myToDisplayXRHands (true),
+  //
   myMouseClickThreshold (3.0),
   myMouseDoubleClickInt (0.4),
   myScrollZoomRatio     (15.0f),
@@ -111,6 +126,18 @@ AIS_ViewController::AIS_ViewController()
 
   myMouseGestureMap.Bind (Aspect_VKeyMouse_MiddleButton,                         AIS_MouseGesture_Pan);
   myMouseGestureMap.Bind (Aspect_VKeyMouse_MiddleButton | Aspect_VKeyFlags_CTRL, AIS_MouseGesture_Pan);
+
+  myXRTeleportHaptic.Duration  = 3600.0f;
+  myXRTeleportHaptic.Frequency = 0.1f;
+  myXRTeleportHaptic.Amplitude = 0.2f;
+
+  myXRPickingHaptic.Duration  = 0.1f;
+  myXRPickingHaptic.Frequency = 4.0f;
+  myXRPickingHaptic.Amplitude = 0.1f;
+
+  myXRSelectHaptic.Duration  = 0.2f;
+  myXRSelectHaptic.Frequency = 4.0f;
+  myXRSelectHaptic.Amplitude = 0.5f;
 }
 
 // =======================================================================
@@ -1287,6 +1314,7 @@ void AIS_ViewController::handlePanning (const Handle(V3d_View)& theView)
   {
     theView->Pan (myGL.Panning.Delta.x(), myGL.Panning.Delta.y());
     theView->Invalidate();
+    theView->View()->SynchronizeXRPosedToBaseCamera();
     return;
   }
 
@@ -1307,6 +1335,7 @@ void AIS_ViewController::handlePanning (const Handle(V3d_View)& theView)
   aPanTrsf.SetTranslation (aCameraPan);
   aCam->Transform (aPanTrsf);
   theView->Invalidate();
+  theView->View()->SynchronizeXRPosedToBaseCamera();
 }
 
 // =======================================================================
@@ -1331,6 +1360,7 @@ void AIS_ViewController::handleZRotate (const Handle(V3d_View)& theView)
   aRotPnt.y() += myGL.ZRotate.Angle * aViewPort.y();
   theView->Rotation (int(aRotPnt.x()), int(aRotPnt.y()));
   theView->Invalidate();
+  theView->View()->SynchronizeXRPosedToBaseCamera();
 }
 
 // =======================================================================
@@ -1361,6 +1391,7 @@ void AIS_ViewController::handleZoom (const Handle(V3d_View)& theView,
     aCoeff = theParams.Delta > 0.0 ? aCoeff : 1.0 / aCoeff;
     theView->SetZoom (aCoeff, true);
     theView->Invalidate();
+    theView->View()->SynchronizeXRPosedToBaseCamera();
     return;
   }
 
@@ -1432,6 +1463,7 @@ void AIS_ViewController::handleZoom (const Handle(V3d_View)& theView,
   aPanTrsf.SetTranslation (aCameraPan);
   aCam->Transform (aPanTrsf);
   theView->Invalidate();
+  theView->View()->SynchronizeXRPosedToBaseCamera();
 }
 
 // =======================================================================
@@ -1452,7 +1484,7 @@ void AIS_ViewController::handleZFocusScroll (const Handle(V3d_View)& theView,
    && aFocus < 2.0)
   {
     theView->Camera()->SetZFocus (theView->Camera()->ZFocusType(), aFocus);
-    theView->Redraw();
+    theView->Invalidate();
   }
 }
 
@@ -1469,7 +1501,9 @@ void AIS_ViewController::handleOrbitRotation (const Handle(V3d_View)& theView,
     return;
   }
 
-  const Handle(Graphic3d_Camera)& aCam = theView->Camera();
+  const Handle(Graphic3d_Camera)& aCam = theView->View()->IsActiveXR()
+                                       ? theView->View()->BaseXRCamera()
+                                       : theView->Camera();
   if (myGL.OrbitRotation.ToStart)
   {
     // default alternatives
@@ -1508,9 +1542,13 @@ void AIS_ViewController::handleOrbitRotation (const Handle(V3d_View)& theView,
     theView->Window()->Size (aWinXY.x(), aWinXY.y());
     double aYawAngleDelta   =  ((myGL.OrbitRotation.PointStart.x() - myGL.OrbitRotation.PointTo.x()) / double (aWinXY.x())) * (M_PI * 0.5);
     double aPitchAngleDelta = -((myGL.OrbitRotation.PointStart.y() - myGL.OrbitRotation.PointTo.y()) / double (aWinXY.y())) * (M_PI * 0.5);
-    const double aPitchAngleNew = Max (Min (myRotateStartYawPitchRoll[1] + aPitchAngleDelta, M_PI * 0.5 - M_PI / 180.0), -M_PI * 0.5 + M_PI / 180.0);
-    const double aYawAngleNew   = myRotateStartYawPitchRoll[0] + aYawAngleDelta;
-    const double aRoll = 0.0;
+    double aPitchAngleNew = 0.0, aRoll = 0.0;
+    const double aYawAngleNew = myRotateStartYawPitchRoll[0] + aYawAngleDelta;
+    if (!theView->View()->IsActiveXR())
+    {
+      aPitchAngleNew = Max (Min (myRotateStartYawPitchRoll[1] + aPitchAngleDelta, M_PI * 0.5 - M_PI / 180.0), -M_PI * 0.5 + M_PI / 180.0);
+      aRoll = 0.0;
+    }
 
     gp_Quaternion aRot;
     aRot.SetEulerAngles (gp_YawPitchRoll, aYawAngleNew, aPitchAngleNew, aRoll);
@@ -1564,6 +1602,7 @@ void AIS_ViewController::handleOrbitRotation (const Handle(V3d_View)& theView,
   }
 
   theView->Invalidate();
+  theView->View()->SynchronizeXRBaseToPosedCamera();
 }
 
 // =======================================================================
@@ -1934,6 +1973,251 @@ void AIS_ViewController::handleCameraActions (const Handle(AIS_InteractiveContex
 }
 
 // =======================================================================
+// function : handleXRInput
+// purpose  :
+// =======================================================================
+void AIS_ViewController::handleXRInput (const Handle(AIS_InteractiveContext)& theCtx,
+                                        const Handle(V3d_View)& theView,
+                                        const AIS_WalkDelta& )
+{
+  theView->View()->ProcessXRInput();
+  if (!theView->View()->IsActiveXR())
+  {
+    return;
+  }
+  if (myXRCameraTmp.IsNull())
+  {
+    myXRCameraTmp = new Graphic3d_Camera();
+  }
+  handleXRTurnPad (theCtx, theView);
+  handleXRTeleport(theCtx, theView);
+  handleXRPicking (theCtx, theView);
+}
+
+// =======================================================================
+// function : handleXRTurnPad
+// purpose  :
+// =======================================================================
+void AIS_ViewController::handleXRTurnPad (const Handle(AIS_InteractiveContext)& ,
+                                          const Handle(V3d_View)& theView)
+{
+  if (myXRTurnAngle <= 0.0
+  || !theView->View()->IsActiveXR())
+  {
+    return;
+  }
+
+  // turn left/right at 45 degrees on left/right trackpad clicks
+  for (int aHand = 0; aHand < 2; ++aHand)
+  {
+    const Aspect_XRTrackedDeviceRole aRole = aHand == 0 ? Aspect_XRTrackedDeviceRole_RightHand : Aspect_XRTrackedDeviceRole_LeftHand;
+    const Handle(Aspect_XRAction)& aPadClickAct = theView->View()->XRSession()->GenericAction (aRole, Aspect_XRGenericAction_InputTrackPadClick);
+    const Handle(Aspect_XRAction)& aPadPosAct   = theView->View()->XRSession()->GenericAction (aRole, Aspect_XRGenericAction_InputTrackPadPosition);
+    if (aPadClickAct.IsNull()
+    ||  aPadPosAct.IsNull())
+    {
+      continue;
+    }
+
+    const Aspect_XRDigitalActionData aPadClick = theView->View()->XRSession()->GetDigitalActionData (aPadClickAct);
+    const Aspect_XRAnalogActionData  aPadPos   = theView->View()->XRSession()->GetAnalogActionData (aPadPosAct);
+    if (aPadClick.IsActive
+     && aPadClick.IsPressed
+     && aPadClick.IsChanged
+     && aPadPos.IsActive
+     && Abs (aPadPos.VecXYZ.y()) < 0.5f
+     && Abs (aPadPos.VecXYZ.x()) > 0.7f)
+    {
+      gp_Trsf aTrsfTurn;
+      aTrsfTurn.SetRotation (gp_Ax1 (gp::Origin(), theView->View()->BaseXRCamera()->Up()), aPadPos.VecXYZ.x() < 0.0f ? myXRTurnAngle : -myXRTurnAngle);
+      theView->View()->TurnViewXRCamera (aTrsfTurn);
+      break;
+    }
+  }
+}
+
+// =======================================================================
+// function : handleXRTeleport
+// purpose  :
+// =======================================================================
+void AIS_ViewController::handleXRTeleport (const Handle(AIS_InteractiveContext)& theCtx,
+                                           const Handle(V3d_View)& theView)
+{
+  if (!theView->View()->IsActiveXR())
+  {
+    return;
+  }
+
+  // teleport on forward trackpad unclicks
+  const Aspect_XRTrackedDeviceRole aTeleOld = myXRLastTeleportHand;
+  myXRLastTeleportHand = Aspect_XRTrackedDeviceRole_Other;
+  for (int aHand = 0; aHand < 2; ++aHand)
+  {
+    const Aspect_XRTrackedDeviceRole aRole = aHand == 0 ? Aspect_XRTrackedDeviceRole_RightHand : Aspect_XRTrackedDeviceRole_LeftHand;
+    const Standard_Integer aDeviceId = theView->View()->XRSession()->NamedTrackedDevice (aRole);
+    if (aDeviceId == -1)
+    {
+      continue;
+    }
+
+    const Handle(Aspect_XRAction)& aPadClickAct = theView->View()->XRSession()->GenericAction (aRole, Aspect_XRGenericAction_InputTrackPadClick);
+    const Handle(Aspect_XRAction)& aPadPosAct   = theView->View()->XRSession()->GenericAction (aRole, Aspect_XRGenericAction_InputTrackPadPosition);
+    if (aPadClickAct.IsNull()
+    ||  aPadPosAct.IsNull())
+    {
+      continue;
+    }
+
+    const Aspect_XRDigitalActionData aPadClick = theView->View()->XRSession()->GetDigitalActionData (aPadClickAct);
+    const Aspect_XRAnalogActionData  aPadPos   = theView->View()->XRSession()->GetAnalogActionData (aPadPosAct);
+    const bool isPressed =  aPadClick.IsPressed;
+    const bool isClicked = !aPadClick.IsPressed
+                        &&  aPadClick.IsChanged;
+    if (aPadClick.IsActive
+     && (isPressed || isClicked)
+     && aPadPos.IsActive
+     && aPadPos.VecXYZ.y() > 0.6f
+     && Abs (aPadPos.VecXYZ.x()) < 0.5f)
+    {
+      const Aspect_TrackedDevicePose& aPose = theView->View()->XRSession()->TrackedPoses()[aDeviceId];
+      if (!aPose.IsValidPose)
+      {
+        continue;
+      }
+
+      myXRLastTeleportHand = aRole;
+      Standard_Real& aPickDepth = aRole == Aspect_XRTrackedDeviceRole_LeftHand ? myXRLastPickDepthLeft : myXRLastPickDepthRight;
+      aPickDepth = Precision::Infinite();
+      Graphic3d_Vec3 aPickNorm;
+      const gp_Trsf aHandBase = theView->View()->PoseXRToWorld (aPose.Orientation);
+      const Standard_Real aHeadHeight = theView->View()->XRSession()->HeadPose().TranslationPart().Y();
+      {
+        const Standard_Integer aPickedId = handleXRMoveTo (theCtx, theView, aPose.Orientation, false);
+        if (aPickedId >= 1)
+        {
+          const SelectMgr_SortCriterion& aPickedData = theCtx->MainSelector()->PickedData (aPickedId);
+          aPickNorm = aPickedData.Normal;
+          if (aPickNorm.SquareModulus() > ShortRealEpsilon())
+          {
+            aPickDepth = aPickedData.Point.Distance (aHandBase.TranslationPart());
+          }
+        }
+      }
+      if (isClicked)
+      {
+        myXRLastTeleportHand = Aspect_XRTrackedDeviceRole_Other;
+        if (!Precision::IsInfinite (aPickDepth))
+        {
+          const gp_Dir aTeleDir = -gp::DZ().Transformed (aHandBase);
+          const gp_Dir anUpDir  = theView->View()->BaseXRCamera()->Up();
+
+          bool isHorizontal = false;
+          gp_Dir aPickNormDir (aPickNorm.x(), aPickNorm.y(), aPickNorm.z());
+          if (anUpDir.IsEqual ( aPickNormDir, M_PI_4)
+           || anUpDir.IsEqual (-aPickNormDir, M_PI_4))
+          {
+            isHorizontal = true;
+          }
+
+          gp_Pnt aNewEye = aHandBase.TranslationPart();
+          if (isHorizontal)
+          {
+            aNewEye  = aHandBase.TranslationPart()
+                     + aTeleDir.XYZ() * aPickDepth
+                     + anUpDir.XYZ() * aHeadHeight;
+          }
+          else
+          {
+            if (aPickNormDir.Dot (aTeleDir) < 0.0)
+            {
+              aPickNormDir.Reverse();
+            }
+            aNewEye  = aHandBase.TranslationPart()
+                     + aTeleDir.XYZ() * aPickDepth
+                     - aPickNormDir.XYZ() * aHeadHeight / 4;
+          }
+
+          theView->View()->PosedXRCamera()->MoveEyeTo (aNewEye);
+          theView->View()->ComputeXRBaseCameraFromPosed (theView->View()->PosedXRCamera(), theView->View()->XRSession()->HeadPose());
+        }
+      }
+      break;
+    }
+  }
+
+  if (myXRLastTeleportHand != aTeleOld)
+  {
+    if (aTeleOld != Aspect_XRTrackedDeviceRole_Other)
+    {
+      if (const Handle(Aspect_XRAction)& aHaptic = theView->View()->XRSession()->GenericAction (aTeleOld, Aspect_XRGenericAction_OutputHaptic))
+      {
+        theView->View()->XRSession()->AbortHapticVibrationAction (aHaptic);
+      }
+    }
+    if (myXRLastTeleportHand != Aspect_XRTrackedDeviceRole_Other)
+    {
+      if (const Handle(Aspect_XRAction)& aHaptic = theView->View()->XRSession()->GenericAction (myXRLastTeleportHand, Aspect_XRGenericAction_OutputHaptic))
+      {
+        theView->View()->XRSession()->TriggerHapticVibrationAction (aHaptic, myXRTeleportHaptic);
+      }
+    }
+  }
+}
+
+// =======================================================================
+// function : handleXRPicking
+// purpose  :
+// =======================================================================
+void AIS_ViewController::handleXRPicking (const Handle(AIS_InteractiveContext)& theCtx,
+                                          const Handle(V3d_View)& theView)
+{
+  if (!theView->View()->IsActiveXR())
+  {
+    return;
+  }
+
+  // handle selection on trigger clicks
+  Aspect_XRTrackedDeviceRole aPickDevOld = myXRLastPickingHand;
+  myXRLastPickingHand = Aspect_XRTrackedDeviceRole_Other;
+  for (int aHand = 0; aHand < 2; ++aHand)
+  {
+    const Aspect_XRTrackedDeviceRole aRole = aHand == 0 ? Aspect_XRTrackedDeviceRole_RightHand : Aspect_XRTrackedDeviceRole_LeftHand;
+    const Handle(Aspect_XRAction)& aTrigClickAct = theView->View()->XRSession()->GenericAction (aRole, Aspect_XRGenericAction_InputTriggerClick);
+    const Handle(Aspect_XRAction)& aTrigPullAct  = theView->View()->XRSession()->GenericAction (aRole, Aspect_XRGenericAction_InputTriggerPull);
+    if (aTrigClickAct.IsNull()
+    ||  aTrigPullAct.IsNull())
+    {
+      continue;
+    }
+
+    const Aspect_XRDigitalActionData aTrigClick = theView->View()->XRSession()->GetDigitalActionData (aTrigClickAct);
+    const Aspect_XRAnalogActionData  aTrigPos   = theView->View()->XRSession()->GetAnalogActionData (aTrigPullAct);
+    if (aTrigPos.IsActive
+     && Abs (aTrigPos.VecXYZ.x()) > 0.1f)
+    {
+      myXRLastPickingHand = aRole;
+      handleXRHighlight (theCtx, theView);
+      if (aTrigClick.IsActive
+       && aTrigClick.IsPressed
+       && aTrigClick.IsChanged)
+      {
+        theCtx->Select (false);
+        OnSelectionChanged (theCtx, theView);
+        if (const Handle(Aspect_XRAction)& aHaptic = theView->View()->XRSession()->GenericAction (myXRLastPickingHand, Aspect_XRGenericAction_OutputHaptic))
+        {
+          theView->View()->XRSession()->TriggerHapticVibrationAction (aHaptic, myXRSelectHaptic);
+        }
+      }
+      break;
+    }
+  }
+  if (myXRLastPickingHand != aPickDevOld)
+  {
+    theCtx->ClearDetected();
+  }
+}
+
+// =======================================================================
 // function : OnSelectionChanged
 // purpose  :
 // =======================================================================
@@ -2041,6 +2325,7 @@ void AIS_ViewController::contextLazyMoveTo (const Handle(AIS_InteractiveContext)
   myPrevMoveTo = thePnt;
 
   Handle(SelectMgr_EntityOwner) aLastPicked = theCtx->DetectedOwner();
+  theView->AutoZFit();
   theCtx->MoveTo (thePnt.x(), thePnt.y(), theView, false);
   Handle(SelectMgr_EntityOwner) aNewPicked = theCtx->DetectedOwner();
 
@@ -2272,9 +2557,10 @@ void AIS_ViewController::handleDynamicHighlight (const Handle(AIS_InteractiveCon
     else if (myToAllowHighlight)
     {
       if (myPrevMoveTo != aMoveToPnt
-       || myGL.OrbitRotation.ToRotate
-       || myGL.ViewRotation.ToRotate
-       || theView->IsInvalidated())
+       || (!theView->View()->IsActiveXR()
+        && (myGL.OrbitRotation.ToRotate
+         || myGL.ViewRotation.ToRotate
+         || theView->IsInvalidated())))
       {
         ResetPreviousMoveTo();
         contextLazyMoveTo (theCtx, theView, aMoveToPnt);
@@ -2340,6 +2626,12 @@ void AIS_ViewController::handleViewRedraw (const Handle(AIS_InteractiveContext)&
     setAskNextFrame();
   }
 
+  if (theView->View()->IsActiveXR())
+  {
+    // VR requires continuous rendering
+    myToAskNextFrame = true;
+  }
+
   for (V3d_ListOfViewIterator aViewIter (theView->Viewer()->ActiveViewIterator()); aViewIter.More(); aViewIter.Next())
   {
     const Handle(V3d_View)& aView = aViewIter.Value();
@@ -2369,17 +2661,289 @@ void AIS_ViewController::handleViewRedraw (const Handle(AIS_InteractiveContext)&
 }
 
 // =======================================================================
+// function : handleXRMoveTo
+// purpose  :
+// =======================================================================
+Standard_Integer AIS_ViewController::handleXRMoveTo (const Handle(AIS_InteractiveContext)& theCtx,
+                                                     const Handle(V3d_View)& theView,
+                                                     const gp_Trsf& thePose,
+                                                     const Standard_Boolean theToHighlight)
+{
+  //ResetPreviousMoveTo();
+  Standard_Integer aPickResult = 0;
+
+  Handle(Graphic3d_Camera) aCamBack = theView->Camera();
+  myXRCameraTmp->Copy (aCamBack);
+  theView->View()->ComputeXRPosedCameraFromBase (*myXRCameraTmp, thePose);
+  theView->SetCamera (myXRCameraTmp);
+  Graphic3d_Vec2i aPickPixel;
+  theView->Window()->Size (aPickPixel.x(), aPickPixel.y());
+  aPickPixel /= 2;
+  const Standard_Integer aSelTolerBack = theCtx->MainSelector()->CustomPixelTolerance();
+  theCtx->MainSelector()->SetPixelTolerance (1);
+  theView->AutoZFit();
+  if (theToHighlight)
+  {
+    theCtx->MoveTo (aPickPixel.x(), aPickPixel.y(), theView, false);
+    if (!theCtx->DetectedOwner().IsNull())
+    {
+      // ignore 2D objects
+      for (aPickResult = 1; !theCtx->DetectedOwner()->Selectable()->TransformPersistence().IsNull(); ++aPickResult)
+      {
+        if (theCtx->HilightNextDetected (theView, false) <= 1)
+        {
+          theCtx->ClearDetected();
+          aPickResult = 0;
+          break;
+        }
+      }
+    }
+  }
+  else
+  {
+    theCtx->MainSelector()->Pick (aPickPixel.x(), aPickPixel.y(), theView);
+    for (Standard_Integer aPickIter = 1; aPickIter <= theCtx->MainSelector()->NbPicked(); ++aPickIter)
+    {
+      const SelectMgr_SortCriterion& aPickedData = theCtx->MainSelector()->PickedData (aPickIter);
+      if (!aPickedData.Entity->OwnerId()->Selectable()->TransformPersistence().IsNull())
+      {
+        // skip 2d objects
+        continue;
+      }
+
+      aPickResult = aPickIter;
+      break;
+    }
+  }
+  theCtx->MainSelector()->SetPixelTolerance (aSelTolerBack);
+  theView->SetCamera (aCamBack);
+  return aPickResult;
+}
+
+// =======================================================================
+// function : handleXRHighlight
+// purpose  :
+// =======================================================================
+void AIS_ViewController::handleXRHighlight (const Handle(AIS_InteractiveContext)& theCtx,
+                                            const Handle(V3d_View)& theView)
+{
+  if (myXRLastPickingHand != Aspect_XRTrackedDeviceRole_LeftHand
+   && myXRLastPickingHand != Aspect_XRTrackedDeviceRole_RightHand)
+  {
+    return;
+  }
+
+  const Standard_Integer aDeviceId = theView->View()->XRSession()->NamedTrackedDevice (myXRLastPickingHand);
+  if (aDeviceId == -1)
+  {
+    return;
+  }
+
+  const Aspect_TrackedDevicePose& aPose = theView->View()->XRSession()->TrackedPoses()[aDeviceId];
+  if (!aPose.IsValidPose)
+  {
+    return;
+  }
+
+  Handle(SelectMgr_EntityOwner) aDetOld = theCtx->DetectedOwner();
+  handleXRMoveTo (theCtx, theView, aPose.Orientation, true);
+  if (!theCtx->DetectedOwner().IsNull()
+    && theCtx->DetectedOwner() != aDetOld)
+  {
+    if (const Handle(Aspect_XRAction)& aHaptic = theView->View()->XRSession()->GenericAction (myXRLastPickingHand, Aspect_XRGenericAction_OutputHaptic))
+    {
+      theView->View()->XRSession()->TriggerHapticVibrationAction (aHaptic, myXRPickingHaptic);
+    }
+  }
+
+  Standard_Real& aPickDepth = myXRLastPickingHand == Aspect_XRTrackedDeviceRole_LeftHand ? myXRLastPickDepthLeft : myXRLastPickDepthRight;
+  aPickDepth = Precision::Infinite();
+  if (theCtx->MainSelector()->NbPicked() > 0)
+  {
+    const gp_Trsf aHandBase = theView->View()->PoseXRToWorld (aPose.Orientation);
+    const SelectMgr_SortCriterion& aPicked = theCtx->MainSelector()->PickedData (1);
+    aPickDepth = aPicked.Point.Distance (aHandBase.TranslationPart());
+  }
+}
+
+// =======================================================================
+// function : handleXRPresentations
+// purpose  :
+// =======================================================================
+void AIS_ViewController::handleXRPresentations (const Handle(AIS_InteractiveContext)& theCtx,
+                                                const Handle(V3d_View)& theView)
+{
+  if (!theView->View()->IsActiveXR()
+   || (!myToDisplayXRAuxDevices
+    && !myToDisplayXRHands))
+  {
+    for (NCollection_Array1<Handle(AIS_XRTrackedDevice)>::Iterator aPrsIter (myXRPrsDevices); aPrsIter.More(); aPrsIter.Next())
+    {
+      if (!aPrsIter.Value().IsNull()
+        && aPrsIter.Value()->HasInteractiveContext())
+      {
+        theCtx->Remove (aPrsIter.Value(), false);
+      }
+      aPrsIter.ChangeValue().Nullify();
+    }
+    return;
+  }
+
+  if (myXRPrsDevices.Length() != theView->View()->XRSession()->TrackedPoses().Length())
+  {
+    for (NCollection_Array1<Handle(AIS_XRTrackedDevice)>::Iterator aPrsIter (myXRPrsDevices); aPrsIter.More(); aPrsIter.Next())
+    {
+      if (!aPrsIter.Value().IsNull())
+      {
+        theCtx->Remove (aPrsIter.Value(), false);
+      }
+    }
+    myXRPrsDevices.Resize (theView->View()->XRSession()->TrackedPoses().Lower(), theView->View()->XRSession()->TrackedPoses().Upper(), false);
+  }
+
+  const Standard_Integer aHeadDevice  = theView->View()->XRSession()->NamedTrackedDevice (Aspect_XRTrackedDeviceRole_Head);
+  const Standard_Integer aLeftDevice  = theView->View()->XRSession()->NamedTrackedDevice (Aspect_XRTrackedDeviceRole_LeftHand);
+  const Standard_Integer aRightDevice = theView->View()->XRSession()->NamedTrackedDevice (Aspect_XRTrackedDeviceRole_RightHand);
+  for (Standard_Integer aDeviceIter = theView->View()->XRSession()->TrackedPoses().Lower(); aDeviceIter <= theView->View()->XRSession()->TrackedPoses().Upper(); ++aDeviceIter)
+  {
+    const Aspect_TrackedDevicePose& aPose = theView->View()->XRSession()->TrackedPoses()[aDeviceIter];
+    Handle(AIS_XRTrackedDevice)& aPosePrs = myXRPrsDevices[aDeviceIter];
+    if (!aPose.IsValidPose)
+    {
+      continue;
+    }
+
+    const bool isHand = aDeviceIter == aLeftDevice
+                     || aDeviceIter == aRightDevice;
+    if ((!myToDisplayXRHands && isHand)
+     || (!myToDisplayXRAuxDevices && !isHand))
+    {
+      if (!aPosePrs.IsNull()
+        && aPosePrs->HasInteractiveContext())
+      {
+        theCtx->Remove (aPosePrs, false);
+      }
+      continue;
+    }
+
+    Aspect_XRTrackedDeviceRole aRole = Aspect_XRTrackedDeviceRole_Other;
+    if (aDeviceIter == aLeftDevice)
+    {
+      aRole = Aspect_XRTrackedDeviceRole_LeftHand;
+    }
+    else if (aDeviceIter == aRightDevice)
+    {
+      aRole = Aspect_XRTrackedDeviceRole_RightHand;
+    }
+
+    if (!aPosePrs.IsNull()
+      && aPosePrs->UnitFactor() != (float )theView->View()->UnitFactor())
+    {
+      theCtx->Remove (aPosePrs, false);
+      aPosePrs.Nullify();
+    }
+
+    if (aPosePrs.IsNull())
+    {
+      Handle(Image_Texture) aTexture;
+      Handle(Graphic3d_ArrayOfTriangles) aTris;
+      if (aDeviceIter != aHeadDevice)
+      {
+        aTris = theView->View()->XRSession()->LoadRenderModel (aDeviceIter, aTexture);
+      }
+      if (!aTris.IsNull())
+      {
+        aPosePrs = new AIS_XRTrackedDevice (aTris, aTexture);
+      }
+      else
+      {
+        aPosePrs = new AIS_XRTrackedDevice();
+      }
+      aPosePrs->SetUnitFactor ((float )theView->View()->UnitFactor());
+      aPosePrs->SetMutable (true);
+      aPosePrs->SetInfiniteState (true);
+    }
+    aPosePrs->SetRole (aRole);
+
+    if (!aPosePrs->HasInteractiveContext())
+    {
+      theCtx->Display (aPosePrs, 0, -1, false);
+    }
+
+    gp_Trsf aPoseLocal = aPose.Orientation;
+    if (aDeviceIter == aHeadDevice)
+    {
+      // show headset position on floor level
+      aPoseLocal.SetTranslationPart (gp_Vec (aPoseLocal.TranslationPart().X(), 0.0, aPoseLocal.TranslationPart().Z()));
+    }
+    const gp_Trsf aPoseWorld = theView->View()->PoseXRToWorld (aPoseLocal);
+    theCtx->SetLocation (aPosePrs, aPoseWorld);
+
+    Standard_Real aLaserLen = 0.0;
+    if (isHand
+      && aPosePrs->Role() == myXRLastPickingHand)
+    {
+      aLaserLen = myXRLastPickingHand == Aspect_XRTrackedDeviceRole_LeftHand ? myXRLastPickDepthLeft : myXRLastPickDepthRight;
+      if (Precision::IsInfinite (aLaserLen))
+      {
+        const Bnd_Box aViewBox = theView->View()->MinMaxValues (true);
+        if (!aViewBox.IsVoid())
+        {
+          aLaserLen = Sqrt (aViewBox.SquareExtent());
+        }
+        else
+        {
+          aLaserLen = 100.0;
+        }
+      }
+      aPosePrs->SetLaserColor (myXRLaserPickColor);
+    }
+    else if (isHand
+          && aPosePrs->Role() == myXRLastTeleportHand)
+    {
+      aLaserLen = myXRLastTeleportHand == Aspect_XRTrackedDeviceRole_LeftHand ? myXRLastPickDepthLeft : myXRLastPickDepthRight;
+      if (Precision::IsInfinite (aLaserLen))
+      {
+        const Bnd_Box aViewBox = theView->View()->MinMaxValues (true);
+        if (!aViewBox.IsVoid())
+        {
+          aLaserLen = Sqrt (aViewBox.SquareExtent());
+        }
+        else
+        {
+          aLaserLen = 100.0;
+        }
+      }
+      aPosePrs->SetLaserColor (myXRLaserTeleColor);
+    }
+    aPosePrs->SetLaserLength ((float )aLaserLen);
+  }
+}
+
+// =======================================================================
 // function : HandleViewEvents
 // purpose  :
 // =======================================================================
 void AIS_ViewController::HandleViewEvents (const Handle(AIS_InteractiveContext)& theCtx,
                                            const Handle(V3d_View)& theView)
 {
-  handleMoveTo (theCtx, theView);
+  const bool wasImmediateUpdate = theView->SetImmediateUpdate (false);
 
   const AIS_WalkDelta aWalk = FetchNavigationKeys (1.0, 1.0);
+  handleXRInput (theCtx, theView, aWalk);
+  if (theView->View()->IsActiveXR())
+  {
+    theView->View()->SetupXRPosedCamera();
+  }
   handleCameraActions (theCtx, theView, aWalk);
+  theView->View()->SynchronizeXRPosedToBaseCamera(); // handleCameraActions() may modify posed camera position - copy this modifications also to the base camera
+  handleXRPresentations (theCtx, theView);
+
+  handleMoveTo (theCtx, theView);
   handleViewRedraw (theCtx, theView);
+  theView->View()->UnsetXRPosedCamera();
+
+  theView->SetImmediateUpdate (wasImmediateUpdate);
 
   // make sure to not process the same events twice
   myGL.Reset();
index dc3fb2b..119fa8c 100644 (file)
@@ -16,6 +16,8 @@
 
 #include <Aspect_VKeySet.hxx>
 #include <Aspect_TouchMap.hxx>
+#include <Aspect_XRHapticActionData.hxx>
+#include <Aspect_XRTrackedDeviceRole.hxx>
 #include <AIS_DragAction.hxx>
 #include <AIS_MouseGesture.hxx>
 #include <AIS_NavigationMode.hxx>
@@ -28,6 +30,7 @@
 #include <NCollection_Array1.hxx>
 #include <OSD_Timer.hxx>
 #include <Precision.hxx>
+#include <Quantity_ColorRGBA.hxx>
 #include <Standard_Mutex.hxx>
 
 class AIS_AnimationCamera;
@@ -35,6 +38,8 @@ class AIS_InteractiveObject;
 class AIS_InteractiveContext;
 class AIS_Point;
 class AIS_RubberBand;
+class AIS_XRTrackedDevice;
+class Graphic3d_Camera;
 class V3d_View;
 
 //! Auxiliary structure for handling viewer events between GUI and Rendering threads.
@@ -200,6 +205,18 @@ public: //! @name global parameters
   //! Reset previous position of MoveTo.
   void ResetPreviousMoveTo() { myPrevMoveTo = Graphic3d_Vec2i (-1); }
 
+  //! Return TRUE to display auxiliary tracked XR devices (like tracking stations).
+  bool ToDisplayXRAuxDevices() const { return myToDisplayXRAuxDevices; }
+
+  //! Set if auxiliary tracked XR devices should be displayed.
+  void SetDisplayXRAuxDevices (bool theToDisplay) { myToDisplayXRAuxDevices = theToDisplay; }
+
+  //! Return TRUE to display XR hand controllers.
+  bool ToDisplayXRHands() const { return myToDisplayXRHands; }
+
+  //! Set if tracked XR hand controllers should be displayed.
+  void SetDisplayXRHands (bool theToDisplay) { myToDisplayXRHands = theToDisplay; }
+
 public: //! @name keyboard input
 
   //! Return keyboard state.
@@ -548,6 +565,40 @@ public:
   Standard_EXPORT virtual void handleViewRedraw (const Handle(AIS_InteractiveContext)& theCtx,
                                                  const Handle(V3d_View)& theView);
 
+public:
+
+  //! Perform XR input.
+  //! This method is expected to be called from rendering thread.
+  Standard_EXPORT virtual void handleXRInput (const Handle(AIS_InteractiveContext)& theCtx,
+                                              const Handle(V3d_View)& theView,
+                                              const AIS_WalkDelta& theWalk);
+
+  //! Handle trackpad view turn action.
+  Standard_EXPORT virtual void handleXRTurnPad (const Handle(AIS_InteractiveContext)& theCtx,
+                                                const Handle(V3d_View)& theView);
+
+  //! Handle trackpad teleportation action.
+  Standard_EXPORT virtual void handleXRTeleport (const Handle(AIS_InteractiveContext)& theCtx,
+                                                 const Handle(V3d_View)& theView);
+
+  //! Handle picking on trigger click.
+  Standard_EXPORT virtual void handleXRPicking (const Handle(AIS_InteractiveContext)& theCtx,
+                                                const Handle(V3d_View)& theView);
+
+  //! Perform dynamic highlighting for active hand.
+  Standard_EXPORT virtual void handleXRHighlight (const Handle(AIS_InteractiveContext)& theCtx,
+                                                  const Handle(V3d_View)& theView);
+
+  //! Display auxiliary XR presentations.
+  Standard_EXPORT virtual void handleXRPresentations (const Handle(AIS_InteractiveContext)& theCtx,
+                                                      const Handle(V3d_View)& theView);
+
+  //! Perform picking with/without dynamic highlighting for XR pose.
+  Standard_EXPORT virtual Standard_Integer handleXRMoveTo (const Handle(AIS_InteractiveContext)& theCtx,
+                                                           const Handle(V3d_View)& theView,
+                                                           const gp_Trsf& thePose,
+                                                           const Standard_Boolean theToHighlight);
+
 protected:
 
   //! Flush buffers.
@@ -629,6 +680,23 @@ protected:
   Graphic3d_Vec2i     myPrevMoveTo;               //!< previous position of MoveTo event in 3D viewer
   Standard_Boolean    myHasHlrOnBeforeRotation;   //!< flag for restoring Computed mode after rotation
 
+protected: //! @name XR input variables
+
+  NCollection_Array1<Handle(AIS_XRTrackedDevice)> myXRPrsDevices; //!< array of XR tracked devices presentations
+  Handle(Graphic3d_Camera)   myXRCameraTmp;       //!< temporary camera
+  Quantity_Color             myXRLaserTeleColor;  //!< color of teleport laser
+  Quantity_Color             myXRLaserPickColor;  //!< color of picking  laser
+  Aspect_XRTrackedDeviceRole myXRLastTeleportHand;//!< active hand for teleport
+  Aspect_XRTrackedDeviceRole myXRLastPickingHand; //!< active hand for picking objects
+  Aspect_XRHapticActionData  myXRTeleportHaptic;  //!< vibration on picking teleport destination
+  Aspect_XRHapticActionData  myXRPickingHaptic;   //!< vibration on dynamic highlighting
+  Aspect_XRHapticActionData  myXRSelectHaptic;    //!< vibration on selection
+  Standard_Real       myXRLastPickDepthLeft;      //!< last picking depth for left  hand
+  Standard_Real       myXRLastPickDepthRight;     //!< last picking depth for right hand
+  Standard_Real       myXRTurnAngle;              //!< discrete turn angle for XR trackpad
+  Standard_Boolean    myToDisplayXRAuxDevices;    //!< flag to display auxiliary tracked XR devices
+  Standard_Boolean    myToDisplayXRHands;         //!< flag to display XR hands
+
 protected: //! @name keyboard input variables
 
   Aspect_VKeySet      myKeys;                     //!< keyboard state
diff --git a/src/AIS/AIS_XRTrackedDevice.cxx b/src/AIS/AIS_XRTrackedDevice.cxx
new file mode 100644 (file)
index 0000000..c60f4ad
--- /dev/null
@@ -0,0 +1,202 @@
+// Copyright (c) 2020 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.
+
+#include <AIS_XRTrackedDevice.hxx>
+
+#include <Graphic3d_ArrayOfSegments.hxx>
+#include <Graphic3d_ArrayOfTriangles.hxx>
+#include <Graphic3d_Group.hxx>
+#include <Graphic3d_Texture2Dmanual.hxx>
+#include <Image_Texture.hxx>
+#include <Prs3d_LineAspect.hxx>
+#include <Prs3d_ShadingAspect.hxx>
+#include <Select3D_SensitivePrimitiveArray.hxx>
+#include <SelectMgr_EntityOwner.hxx>
+
+//! Texture holder.
+class AIS_XRTrackedDevice::XRTexture : public Graphic3d_Texture2Dmanual
+{
+public:
+
+  //! Constructor.
+  XRTexture (const Handle(Image_Texture)& theImageSource,
+             const Graphic3d_TextureUnit theUnit = Graphic3d_TextureUnit_BaseColor)
+  : Graphic3d_Texture2Dmanual (""), myImageSource (theImageSource)
+  {
+    if (!theImageSource->TextureId().IsEmpty())
+    {
+      myTexId = theImageSource->TextureId();
+    }
+    myParams->SetTextureUnit (theUnit);
+    myIsColorMap = theUnit == Graphic3d_TextureUnit_BaseColor
+                || theUnit == Graphic3d_TextureUnit_Emissive;
+  }
+
+  //! Image reader.
+  virtual Handle(Image_PixMap) GetImage() const Standard_OVERRIDE { return myImageSource->ReadImage(); }
+
+protected:
+  Handle(Image_Texture) myImageSource;
+};
+
+IMPLEMENT_STANDARD_RTTIEXT(AIS_XRTrackedDevice, AIS_InteractiveObject)
+
+//=======================================================================
+//function : AIS_XRTrackedDevice
+//purpose  :
+//=======================================================================
+AIS_XRTrackedDevice::AIS_XRTrackedDevice (const Handle(Graphic3d_ArrayOfTriangles)& theTris,
+                                          const Handle(Image_Texture)& theTexture)
+: myTris (theTris),
+  myLaserColor (Quantity_NOC_BLUE),
+  myLaserLength (0.0f),
+  myUnitFactor (1.0f),
+  myRole (Aspect_XRTrackedDeviceRole_Other),
+  myToShowAxes (false)
+{
+  myDrawer->SetShadingAspect (new Prs3d_ShadingAspect());
+  myDrawer->ShadingAspect()->SetMaterial (Graphic3d_NOM_DEFAULT);
+  myDrawer->ShadingAspect()->SetColor (Quantity_NOC_WHITE);
+  if (!theTexture.IsNull())
+  {
+    myDrawer->ShadingAspect()->Aspect()->SetTextureMap (new XRTexture (theTexture));
+    myDrawer->ShadingAspect()->Aspect()->SetTextureMapOn (true);
+  }
+}
+
+//=======================================================================
+//function : AIS_XRTrackedDevice
+//purpose  :
+//=======================================================================
+AIS_XRTrackedDevice::AIS_XRTrackedDevice()
+: myLaserColor (Quantity_NOC_BLUE),
+  myLaserLength (0.0f),
+  myUnitFactor (1.0f),
+  myRole (Aspect_XRTrackedDeviceRole_Other),
+  myToShowAxes (true)
+{
+  //
+}
+
+//=======================================================================
+//function : SetLaserColor
+//purpose  :
+//=======================================================================
+void AIS_XRTrackedDevice::SetLaserColor (const Quantity_Color& theColor)
+{
+  if (!myLaserColor.IsEqual (theColor))
+  {
+    myLaserColor = theColor;
+    computeLaserRay();
+  }
+}
+
+//=======================================================================
+//function : SetLaserLength
+//purpose  :
+//=======================================================================
+void AIS_XRTrackedDevice::SetLaserLength (Standard_ShortReal theLength)
+{
+  if (myLaserLength != theLength)
+  {
+    myLaserLength = theLength;
+    computeLaserRay();
+  }
+}
+
+//=======================================================================
+//function : computeLaserRay
+//purpose  :
+//=======================================================================
+void AIS_XRTrackedDevice::computeLaserRay()
+{
+  if (myRayGroup.IsNull())
+  {
+    return;
+  }
+
+  if (!myRayGroup->IsEmpty())
+  {
+    myRayGroup->Clear();
+  }
+  if (myLaserLength <= 0.0f)
+  {
+    return;
+  }
+
+  Handle(Graphic3d_ArrayOfPrimitives) aLines = new Graphic3d_ArrayOfSegments (2, 0, Graphic3d_ArrayFlags_VertexColor);
+  aLines->AddVertex (gp_Pnt (0.0, 0.0, 0.0), myLaserColor);
+  aLines->AddVertex (gp_Pnt (0.0, 0.0, -myLaserLength), myLaserColor);
+  myRayGroup->SetGroupPrimitivesAspect (myDrawer->LineAspect()->Aspect());
+  myRayGroup->AddPrimitiveArray (aLines, false); // do not extend camera frustum by ray
+}
+
+//=======================================================================
+//function : Compute
+//purpose  :
+//=======================================================================
+void AIS_XRTrackedDevice::Compute (const Handle(PrsMgr_PresentationManager3d)& ,
+                                   const Handle(Prs3d_Presentation)& thePrs,
+                                   const Standard_Integer theMode)
+{
+  if (theMode != 0)
+  {
+    return;
+  }
+
+  thePrs->SetInfiniteState (myInfiniteState);
+  Handle(Graphic3d_Group) aGroup = thePrs->NewGroup();
+  if (!myTris.IsNull())
+  {
+    aGroup->SetGroupPrimitivesAspect (myDrawer->ShadingAspect()->Aspect());
+    aGroup->AddPrimitiveArray (myTris);
+  }
+
+  if (myToShowAxes || myTris.IsNull())
+  {
+    const float aSize = 0.1f * myUnitFactor;
+    aGroup->SetGroupPrimitivesAspect (myDrawer->LineAspect()->Aspect());
+    Handle(Graphic3d_ArrayOfPrimitives) aLines = new Graphic3d_ArrayOfSegments (6, 0, Graphic3d_ArrayFlags_VertexColor);
+    aLines->AddVertex (gp_Pnt (0.0,   0.0, 0.0),   Quantity_Color (Quantity_NOC_RED));
+    aLines->AddVertex (gp_Pnt (aSize, 0.0, 0.0),   Quantity_Color (Quantity_NOC_RED));
+    aLines->AddVertex (gp_Pnt (0.0,   0.0, 0.0),   Quantity_Color (Quantity_NOC_GREEN));
+    aLines->AddVertex (gp_Pnt (0.0, aSize, 0.0),   Quantity_Color (Quantity_NOC_GREEN));
+    aLines->AddVertex (gp_Pnt (0.0,   0.0, 0.0),   Quantity_Color (Quantity_NOC_BLUE));
+    aLines->AddVertex (gp_Pnt (0.0,   0.0, aSize), Quantity_Color (Quantity_NOC_BLUE));
+    aGroup->AddPrimitiveArray (aLines);
+  }
+
+  myRayGroup = thePrs->NewGroup();
+  computeLaserRay();
+}
+
+//=======================================================================
+//function : ComputeSelection
+//purpose  :
+//=======================================================================
+void AIS_XRTrackedDevice::ComputeSelection (const Handle(SelectMgr_Selection)& theSel,
+                                            const Standard_Integer theMode)
+{
+  if (theMode != 0)
+  {
+    return;
+  }
+
+  if (!myTris.IsNull())
+  {
+    Handle(SelectMgr_EntityOwner) anOwner = new SelectMgr_EntityOwner (this);
+    Handle(Select3D_SensitivePrimitiveArray) aSensitive = new Select3D_SensitivePrimitiveArray (anOwner);
+    aSensitive->InitTriangulation (myTris->Attributes(), myTris->Indices(), TopLoc_Location(), true);
+    theSel->Add (aSensitive);
+  }
+}
diff --git a/src/AIS/AIS_XRTrackedDevice.hxx b/src/AIS/AIS_XRTrackedDevice.hxx
new file mode 100644 (file)
index 0000000..05f4efe
--- /dev/null
@@ -0,0 +1,92 @@
+// Copyright (c) 2020 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _AIS_XRTrackedDevice_HeaderFile
+#define _AIS_XRTrackedDevice_HeaderFile
+
+#include <AIS_InteractiveObject.hxx>
+#include <Aspect_XRTrackedDeviceRole.hxx>
+
+class Graphic3d_ArrayOfTriangles;
+class Image_Texture;
+
+//! Auxiliary textured mesh presentation of tracked XR device.
+class AIS_XRTrackedDevice : public AIS_InteractiveObject
+{
+  DEFINE_STANDARD_RTTIEXT(AIS_XRTrackedDevice, AIS_InteractiveObject)
+public:
+  //! Main constructor.
+  Standard_EXPORT AIS_XRTrackedDevice (const Handle(Graphic3d_ArrayOfTriangles)& theTris,
+                                       const Handle(Image_Texture)& theTexture);
+
+  //! Empty constructor.
+  Standard_EXPORT AIS_XRTrackedDevice();
+
+  //! Return device role.
+  Aspect_XRTrackedDeviceRole Role() const { return myRole; }
+
+  //! Set device role.
+  void SetRole (Aspect_XRTrackedDeviceRole theRole) { myRole = theRole; }
+
+  //! Return laser color.
+  const Quantity_Color& LaserColor() const { return myLaserColor; }
+
+  //! Set laser color.
+  Standard_EXPORT void SetLaserColor (const Quantity_Color& theColor);
+
+  //! Return laser length.
+  Standard_ShortReal LaserLength() const { return myLaserLength; }
+
+  //! Set laser length.
+  Standard_EXPORT void SetLaserLength (Standard_ShortReal theLength);
+
+  //! Return unit scale factor.
+  Standard_ShortReal UnitFactor() const { return myUnitFactor; }
+
+  //! Set unit scale factor.
+  void SetUnitFactor (Standard_ShortReal theFactor) { myUnitFactor = theFactor; }
+
+protected:
+
+  //! Returns true for 0 mode.
+  virtual Standard_Boolean AcceptDisplayMode (const Standard_Integer theMode) const Standard_OVERRIDE { return theMode == 0; }
+
+  //! Compute presentation.
+  Standard_EXPORT virtual void Compute (const Handle(PrsMgr_PresentationManager3d)& thePrsMgr,
+                                        const Handle(Prs3d_Presentation)& thePrs,
+                                        const Standard_Integer theMode) Standard_OVERRIDE;
+
+  //! Compute selection.
+  Standard_EXPORT virtual void ComputeSelection (const Handle(SelectMgr_Selection)& theSel,
+                                                 const Standard_Integer theMode) Standard_OVERRIDE;
+
+  //! Compute laser ray presentation.
+  Standard_EXPORT void computeLaserRay();
+
+private:
+  //! Texture holder.
+  class XRTexture;
+
+private:
+
+  Handle(Graphic3d_Group) myRayGroup;
+
+  Handle(Graphic3d_ArrayOfTriangles) myTris;
+  Quantity_Color             myLaserColor;
+  Standard_ShortReal         myLaserLength;
+  Standard_ShortReal         myUnitFactor;
+  Aspect_XRTrackedDeviceRole myRole;
+  Standard_Boolean           myToShowAxes;
+};
+
+#endif // _AIS_XRTrackedDevice_HeaderFile
index b774969..e5bebe3 100644 (file)
@@ -141,3 +141,5 @@ AIS_RadiusDimension.hxx
 AIS_Relation.hxx
 AIS_SymmetricRelation.hxx
 AIS_TangentRelation.hxx
+AIS_XRTrackedDevice.cxx
+AIS_XRTrackedDevice.hxx
diff --git a/src/Aspect/Aspect_ColorSpace.hxx b/src/Aspect/Aspect_ColorSpace.hxx
new file mode 100644 (file)
index 0000000..cbeb943
--- /dev/null
@@ -0,0 +1,26 @@
+// Copyright (c) 2020 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _Aspect_ColorSpace_HeaderFile
+#define _Aspect_ColorSpace_HeaderFile
+
+#include <Aspect_GraphicsLibrary.hxx>
+
+//! Texture color spaces accepted by XR composer.
+enum Aspect_ColorSpace
+{
+  Aspect_ColorSpace_sRGB   = 0, //!< non-linear sRGB color space
+  Aspect_ColorSpace_Linear = 1, //!< linear RGB color space
+};
+
+#endif // _Aspect_ColorSpace_HeaderFile
diff --git a/src/Aspect/Aspect_Eye.hxx b/src/Aspect/Aspect_Eye.hxx
new file mode 100644 (file)
index 0000000..24caad5
--- /dev/null
@@ -0,0 +1,24 @@
+// Copyright (c) 2020 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _Aspect_Eye_HeaderFile
+#define _Aspect_Eye_HeaderFile
+
+//! Camera eye index within stereoscopic pair.
+enum Aspect_Eye
+{
+  Aspect_Eye_Left,
+  Aspect_Eye_Right
+};
+
+#endif // _Aspect_Eye_HeaderFile
diff --git a/src/Aspect/Aspect_FrustumLRBT.hxx b/src/Aspect/Aspect_FrustumLRBT.hxx
new file mode 100644 (file)
index 0000000..7b05cda
--- /dev/null
@@ -0,0 +1,55 @@
+// Copyright (c) 2020 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _Aspect_FrustumLRBT_HeaderFile
+#define _Aspect_FrustumLRBT_HeaderFile
+
+//! Structure defining frustum boundaries.
+template<typename Elem_t>
+struct Aspect_FrustumLRBT
+{
+  Elem_t Left;
+  Elem_t Right;
+  Elem_t Bottom;
+  Elem_t Top;
+
+  //! Empty constructor.
+  Aspect_FrustumLRBT() : Left (0), Right (0), Bottom (0), Top (0) {}
+
+  //! Copy/cast constructor.
+  template<typename Other_t>
+  explicit Aspect_FrustumLRBT (const Aspect_FrustumLRBT<Other_t>& theOther)
+  : Left  (static_cast<Elem_t> (theOther.Left)),
+    Right (static_cast<Elem_t> (theOther.Right)),
+    Bottom(static_cast<Elem_t> (theOther.Bottom)),
+    Top   (static_cast<Elem_t> (theOther.Top)) {}
+
+  //! Apply multiply factor.
+  void Multiply (Elem_t theScale)
+  {
+    Left   *= theScale;
+    Right  *= theScale;
+    Bottom *= theScale;
+    Top    *= theScale;
+  }
+
+  //! Return multiplied frustum.
+  Aspect_FrustumLRBT<Elem_t> Multiplied (Elem_t theScale)
+  {
+    Aspect_FrustumLRBT<Elem_t> aCopy (*this);
+    aCopy.Multiply (theScale);
+    return aCopy;
+  }
+};
+
+#endif // _Aspect_FrustumLRBT_HeaderFile
diff --git a/src/Aspect/Aspect_GraphicsLibrary.hxx b/src/Aspect/Aspect_GraphicsLibrary.hxx
new file mode 100644 (file)
index 0000000..45c59e8
--- /dev/null
@@ -0,0 +1,24 @@
+// Copyright (c) 2020 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _Aspect_GraphicsLibrary_HeaderFile
+#define _Aspect_GraphicsLibrary_HeaderFile
+
+//! Graphics API enumeration.
+enum Aspect_GraphicsLibrary
+{
+  Aspect_GraphicsLibrary_OpenGL,
+  Aspect_GraphicsLibrary_OpenGLES,
+};
+
+#endif // _Aspect_GraphicsLibrary_HeaderFile
diff --git a/src/Aspect/Aspect_OpenVRSession.cxx b/src/Aspect/Aspect_OpenVRSession.cxx
new file mode 100644 (file)
index 0000000..2b85509
--- /dev/null
@@ -0,0 +1,1151 @@
+// Copyright (c) 2020 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.
+
+#include <Aspect_OpenVRSession.hxx>
+
+#include <Graphic3d_ArrayOfTriangles.hxx>
+#include <Image_PixMap.hxx>
+#include <Image_Texture.hxx>
+#include <Message.hxx>
+#include <Message_Messenger.hxx>
+#include <NCollection_LocalArray.hxx>
+#include <OSD.hxx>
+#include <OSD_Environment.hxx>
+#include <OSD_File.hxx>
+#include <OSD_Process.hxx>
+
+#ifdef HAVE_OPENVR
+  #include <openvr.h>
+
+namespace
+{
+  //! Print OpenVR compositor error.
+  static const char* getVRCompositorError (vr::EVRCompositorError theVRError)
+  {
+    switch (theVRError)
+    {
+      case vr::VRCompositorError_None:                         return "None";
+      case vr::VRCompositorError_RequestFailed:                return "Request Failed";
+      case vr::VRCompositorError_IncompatibleVersion:          return "Incompatible Version";
+      case vr::VRCompositorError_DoNotHaveFocus:               return "Do not have focus";
+      case vr::VRCompositorError_InvalidTexture:               return "Invalid Texture";
+      case vr::VRCompositorError_IsNotSceneApplication:        return "Is not scene application";
+      case vr::VRCompositorError_TextureIsOnWrongDevice:       return "Texture is on wrong device";
+      case vr::VRCompositorError_TextureUsesUnsupportedFormat: return "Texture uses unsupported format";
+      case vr::VRCompositorError_SharedTexturesNotSupported:   return "Shared textures not supported";
+      case vr::VRCompositorError_IndexOutOfRange:              return "Index out of range";
+      case vr::VRCompositorError_AlreadySubmitted:             return "Already submitted";
+      case vr::VRCompositorError_InvalidBounds:                return "Invalid Bounds";
+      case vr::VRCompositorError_AlreadySet:                   return "Already set";
+    }
+    return "UNKNOWN";
+  }
+
+  //! Print OpenVR input error.
+  static const char* getVRInputError (vr::EVRInputError theVRError)
+  {
+    switch (theVRError)
+    {
+      case vr::VRInputError_None:                     return "None";
+      case vr::VRInputError_NameNotFound:             return "NameNotFound";
+      case vr::VRInputError_WrongType:                return "WrongType";
+      case vr::VRInputError_InvalidHandle:            return "InvalidHandle";
+      case vr::VRInputError_InvalidParam:             return "InvalidParam";
+      case vr::VRInputError_NoSteam:                  return "NoSteam:";
+      case vr::VRInputError_MaxCapacityReached:       return "MaxCapacityReached";
+      case vr::VRInputError_IPCError:                 return "IPCError";
+      case vr::VRInputError_NoActiveActionSet:        return "NoActiveActionSet";
+      case vr::VRInputError_InvalidDevice:            return "InvalidDevice";
+      case vr::VRInputError_InvalidSkeleton:          return "InvalidSkeleton";
+      case vr::VRInputError_InvalidBoneCount:         return "InvalidBoneCount";
+      case vr::VRInputError_InvalidCompressedData:    return "InvalidCompressedData";
+      case vr::VRInputError_NoData:                   return "NoData";
+      case vr::VRInputError_BufferTooSmall:           return "BufferTooSmall";
+      case vr::VRInputError_MismatchedActionManifest: return "MismatchedActionManifest";
+      case vr::VRInputError_MissingSkeletonData:      return "MissingSkeletonData";
+      case vr::VRInputError_InvalidBoneIndex:         return "InvalidBoneIndex";
+      case vr::VRInputError_InvalidPriority:          return "InvalidPriority";
+      case vr::VRInputError_PermissionDenied:         return "PermissionDenied";
+      case vr::VRInputError_InvalidRenderModel:       return "InvalidRenderModel";
+    }
+    return "UNKNOWN";
+  }
+
+  //! Convert OpenVR mat4x4 into OCCT mat4x4.
+  static NCollection_Mat4<double> mat44vr2Occ (const vr::HmdMatrix44_t& theMat4)
+  {
+    NCollection_Mat4<double> aMat4;
+    for (int aRow = 0; aRow < 4; ++aRow)
+    {
+      aMat4.SetRow (aRow, NCollection_Vec4<double> (theMat4.m[aRow][0], theMat4.m[aRow][1], theMat4.m[aRow][2], theMat4.m[aRow][3]));
+    }
+    return aMat4;
+  }
+
+  //! Convert OpenVR mat3x4 into OCCT gp_Trsf.
+  static gp_Trsf mat34vr2OccTrsf (const vr::HmdMatrix34_t& theMat4)
+  {
+    gp_Trsf aTrsf;
+    aTrsf.SetValues (theMat4.m[0][0], theMat4.m[0][1], theMat4.m[0][2], theMat4.m[0][3],
+                     theMat4.m[1][0], theMat4.m[1][1], theMat4.m[1][2], theMat4.m[1][3],
+                     theMat4.m[2][0], theMat4.m[2][1], theMat4.m[2][2], theMat4.m[2][3]);
+    return aTrsf;
+  }
+
+  //! Convert OpenVR tracked pose.
+  static Aspect_TrackedDevicePose poseVr2Occ (const vr::TrackedDevicePose_t& theVrPose,
+                                              const Standard_Real theUnitFactor)
+  {
+    Aspect_TrackedDevicePose aPose;
+    aPose.Velocity.SetCoord (theVrPose.vVelocity.v[0], theVrPose.vVelocity.v[1], theVrPose.vVelocity.v[2]);
+    aPose.AngularVelocity.SetCoord (theVrPose.vAngularVelocity.v[0], theVrPose.vAngularVelocity.v[1], theVrPose.vAngularVelocity.v[2]);
+    aPose.IsValidPose = theVrPose.bPoseIsValid;
+    aPose.IsConnectedDevice = theVrPose.bDeviceIsConnected;
+    if (aPose.IsValidPose)
+    {
+      aPose.Orientation = mat34vr2OccTrsf (theVrPose.mDeviceToAbsoluteTracking);
+      if (theUnitFactor != 1.0)
+      {
+        aPose.Orientation.SetTranslationPart (aPose.Orientation.TranslationPart() * theUnitFactor);
+      }
+    }
+    return aPose;
+  }
+
+  //! Find location of default actions manifest file (based on CSF_OCCTResourcePath or CASROOT variables).
+  TCollection_AsciiString defaultActionsManifestInit()
+  {
+    const TCollection_AsciiString THE_ACTIONS_JSON = "occtvr_actions.json";
+    const TCollection_AsciiString aResRoot (OSD_Environment ("CSF_OCCTResourcePath").Value());
+    if (!aResRoot.IsEmpty())
+    {
+      if (OSD_File (aResRoot + "/" + THE_ACTIONS_JSON).Exists())
+      {
+        return aResRoot + "/" + THE_ACTIONS_JSON;
+      }
+      if (OSD_File (aResRoot + "/XRResources/" + THE_ACTIONS_JSON).Exists())
+      {
+        return aResRoot + "/XRResources/" + THE_ACTIONS_JSON;
+      }
+    }
+    const TCollection_AsciiString aCasRoot (OSD_Environment ("CASROOT").Value());
+    if (!aCasRoot.IsEmpty())
+    {
+      if (OSD_File (aCasRoot + "/" + THE_ACTIONS_JSON).Exists())
+      {
+        return aCasRoot + "/" + THE_ACTIONS_JSON;
+      }
+      if (OSD_File (aCasRoot + "/XRResources/" + THE_ACTIONS_JSON).Exists())
+      {
+        return aCasRoot + "/XRResources/" + THE_ACTIONS_JSON;
+      }
+      if (OSD_File (aCasRoot + "/XRResources/src/" + THE_ACTIONS_JSON).Exists())
+      {
+        return aCasRoot + "/XRResources/src/" + THE_ACTIONS_JSON;
+      }
+    }
+    return OSD_Process::ExecutablePath() + "/occtvr_actions.json";
+  }
+}
+#endif
+
+IMPLEMENT_STANDARD_RTTIEXT(Aspect_OpenVRSession, Aspect_XRSession)
+
+struct Aspect_OpenVRSession::VRContext
+{
+#ifdef HAVE_OPENVR
+  vr::TrackedDevicePose_t TrackedPoses[vr::k_unMaxTrackedDeviceCount]; //!< array of tracked devices poses
+  vr::IVRSystem*          System; //!< OpenVR session object
+
+  //! Empty constructor.
+  Aspect_OpenVRSession::VRContext() : System (NULL)
+  {
+    memset (TrackedPoses, 0, sizeof(TrackedPoses));
+  }
+
+  //! IVRSystem::PollNextEvent() wrapper.
+  bool PollNextEvent (vr::VREvent_t& theEvent)
+  {
+    return System->PollNextEvent (&theEvent, sizeof(vr::VREvent_t));
+  }
+
+  //! IVRSystem::GetControllerState() wrapper.
+  bool GetControllerState (vr::VRControllerState_t& theState,
+                           vr::TrackedDeviceIndex_t theDevice)
+  {
+    return System->GetControllerState (theDevice, &theState, sizeof(vr::VRControllerState_t&));
+  }
+
+  //! Retrieve string property from OpenVR.
+  TCollection_AsciiString getVrTrackedDeviceString (vr::TrackedDeviceIndex_t  theDevice,
+                                                    vr::TrackedDeviceProperty theProperty,
+                                                    vr::TrackedPropertyError* theError = NULL)
+  {
+    const uint32_t aBuffLen = System->GetStringTrackedDeviceProperty(theDevice, theProperty, NULL, 0, theError);
+    if (aBuffLen == 0)
+    {
+      return TCollection_AsciiString();
+    }
+
+    NCollection_LocalArray<char> aBuffer (aBuffLen + 1);
+    System->GetStringTrackedDeviceProperty (theDevice, theProperty, aBuffer, aBuffLen, theError);
+    aBuffer[aBuffLen] = '\0';
+    const TCollection_AsciiString aResult (aBuffer);
+    return aResult;
+  }
+#endif
+};
+
+#ifdef HAVE_OPENVR
+//! Image wrapping vr::RenderModel_TextureMap_t.
+class Aspect_OpenVRSession::VRImagePixmap : public Image_PixMap
+{
+public:
+  //! Empty constructor.
+  VRImagePixmap() : myVrTexture (NULL) {}
+
+  //! Load the texture.
+  bool Load (vr::TextureID_t theTexture, const TCollection_AsciiString& theVrModelName)
+  {
+    vr::RenderModel_TextureMap_t* aVrTexture = NULL;
+    vr::EVRRenderModelError aVrError = vr::VRRenderModelError_Loading;
+    for (; aVrError == vr::VRRenderModelError_Loading;)
+    {
+      aVrError = vr::VRRenderModels()->LoadTexture_Async (theTexture, &aVrTexture);
+      OSD::MilliSecSleep (1);
+    }
+    if (aVrError != vr::VRRenderModelError_None
+     || aVrTexture == NULL)
+    {
+      Message::DefaultMessenger()->Send (TCollection_AsciiString ("OpenVR, Unable to load render model texture: ") + theVrModelName + " - " + int(aVrError), Message_Fail);
+      return false;
+    }
+
+    InitWrapper (Image_Format_RGBA, (Standard_Byte* )aVrTexture->rubTextureMapData, aVrTexture->unWidth, aVrTexture->unHeight);
+    myVrTexture = aVrTexture;
+    return true;
+  }
+
+  virtual ~VRImagePixmap()
+  {
+    if (myVrTexture != NULL)
+    {
+      vr::VRRenderModels()->FreeTexture (myVrTexture);
+    }
+  }
+private:
+  vr::RenderModel_TextureMap_t* myVrTexture;
+};
+
+//! Image_Texture extension using vr::VRRenderModels().
+class Aspect_OpenVRSession::VRTextureSource : public Image_Texture
+{
+public:
+
+  //! Main constructor.
+  VRTextureSource (vr::TextureID_t theTextureId, const TCollection_AsciiString& theVrModelName)
+  : Image_Texture (""), myVrTextureId (theTextureId), myVrModelName (theVrModelName)
+  {
+    myTextureId = TCollection_AsciiString ("texturevr://map_#") + (int )theTextureId;
+  }
+
+protected:
+  //! Read image.
+  virtual Handle(Image_PixMap) ReadImage() const Standard_OVERRIDE
+  {
+    Handle(VRImagePixmap) aPixmap = new VRImagePixmap();
+    if (!aPixmap->Load (myVrTextureId, myVrModelName))
+    {
+      return Handle(VRImagePixmap)();
+    }
+    return aPixmap;
+  }
+private:
+  vr::TextureID_t         myVrTextureId;
+  TCollection_AsciiString myVrModelName;
+};
+#endif
+
+// =======================================================================
+// function : IsHmdPresent
+// purpose  :
+// =======================================================================
+bool Aspect_OpenVRSession::IsHmdPresent()
+{
+#ifdef HAVE_OPENVR
+  return vr::VR_IsHmdPresent();
+#else
+  return false;
+#endif
+}
+
+// =======================================================================
+// function : defaultActionsManifest
+// purpose  :
+// =======================================================================
+TCollection_AsciiString Aspect_OpenVRSession::defaultActionsManifest()
+{
+#ifdef HAVE_OPENVR
+  static const TCollection_AsciiString THE_ACTIONS_JSON_FULL = defaultActionsManifestInit();
+  return THE_ACTIONS_JSON_FULL;
+#else
+  return TCollection_AsciiString();
+#endif
+}
+
+// =======================================================================
+// function : Aspect_OpenVRSession
+// purpose  :
+// =======================================================================
+Aspect_OpenVRSession::Aspect_OpenVRSession()
+: myContext (new VRContext())
+{
+#ifdef HAVE_OPENVR
+  myActionsManifest = defaultActionsManifest();
+  myTrackedPoses.Resize (0, (Standard_Integer )vr::k_unMaxTrackedDeviceCount - 1, false);
+  {
+    Handle(Aspect_XRActionSet) aHeadActionSet = new Aspect_XRActionSet ("/actions/generic_head");
+    myActionSets.Add (aHeadActionSet->Id(), aHeadActionSet);
+
+    Handle(Aspect_XRAction) aHeadsetOn = new Aspect_XRAction (aHeadActionSet->Id() + "/in/headset_on_head", Aspect_XRActionType_InputDigital);
+    aHeadActionSet->AddAction (aHeadsetOn);
+    NCollection_Array1<Handle(Aspect_XRAction)>& aGenericSet = myRoleActions[Aspect_XRTrackedDeviceRole_Head];
+    aGenericSet[Aspect_XRGenericAction_IsHeadsetOn] = aHeadsetOn;
+  }
+  for (int aHand = 0; aHand < 2; ++aHand)
+  {
+    NCollection_Array1<Handle(Aspect_XRAction)>& aGenericSet = myRoleActions[aHand == 0 ? Aspect_XRTrackedDeviceRole_LeftHand : Aspect_XRTrackedDeviceRole_RightHand];
+    Handle(Aspect_XRActionSet) anActionSet = new Aspect_XRActionSet (aHand == 0 ? "/actions/generic_left" : "/actions/generic_right");
+    myActionSets.Add (anActionSet->Id(), anActionSet);
+
+    Handle(Aspect_XRAction) anAppMenuClick = new Aspect_XRAction (anActionSet->Id() + "/in/appmenu_click", Aspect_XRActionType_InputDigital);
+    anActionSet->AddAction (anAppMenuClick);
+    aGenericSet[Aspect_XRGenericAction_InputAppMenu] = anAppMenuClick;
+
+    Handle(Aspect_XRAction) aSysMenuClick = new Aspect_XRAction (anActionSet->Id() + "/in/sysmenu_click", Aspect_XRActionType_InputDigital);
+    anActionSet->AddAction (aSysMenuClick);
+    aGenericSet[Aspect_XRGenericAction_InputSysMenu] = aSysMenuClick;
+
+    Handle(Aspect_XRAction) aTriggerPull = new Aspect_XRAction (anActionSet->Id() + "/in/trigger_pull", Aspect_XRActionType_InputAnalog);
+    anActionSet->AddAction (aTriggerPull);
+    aGenericSet[Aspect_XRGenericAction_InputTriggerPull] = aTriggerPull;
+
+    Handle(Aspect_XRAction) aTriggerClick = new Aspect_XRAction (anActionSet->Id() + "/in/trigger_click", Aspect_XRActionType_InputDigital);
+    anActionSet->AddAction (aTriggerClick);
+    aGenericSet[Aspect_XRGenericAction_InputTriggerClick] = aTriggerClick;
+
+    Handle(Aspect_XRAction) aGripClick = new Aspect_XRAction (anActionSet->Id() + "/in/grip_click", Aspect_XRActionType_InputDigital);
+    anActionSet->AddAction (aGripClick);
+    aGenericSet[Aspect_XRGenericAction_InputGripClick] = aGripClick;
+
+    Handle(Aspect_XRAction) aPadPos = new Aspect_XRAction (anActionSet->Id() + "/in/trackpad_position", Aspect_XRActionType_InputAnalog);
+    anActionSet->AddAction (aPadPos);
+    aGenericSet[Aspect_XRGenericAction_InputTrackPadPosition] = aPadPos;
+
+    Handle(Aspect_XRAction) aPadTouch = new Aspect_XRAction (anActionSet->Id() + "/in/trackpad_touch", Aspect_XRActionType_InputDigital);
+    anActionSet->AddAction (aPadTouch);
+    aGenericSet[Aspect_XRGenericAction_InputTrackPadTouch] = aPadTouch;
+
+    Handle(Aspect_XRAction) aPadClick = new Aspect_XRAction (anActionSet->Id() + "/in/trackpad_click", Aspect_XRActionType_InputDigital);
+    anActionSet->AddAction (aPadClick);
+    aGenericSet[Aspect_XRGenericAction_InputTrackPadClick] = aPadClick;
+
+    Handle(Aspect_XRAction) aPoseBase = new Aspect_XRAction (anActionSet->Id() + "/in/pose_base", Aspect_XRActionType_InputPose);
+    anActionSet->AddAction (aPoseBase);
+    aGenericSet[Aspect_XRGenericAction_InputPoseBase] = aPoseBase;
+
+    Handle(Aspect_XRAction) aPoseFront = new Aspect_XRAction (anActionSet->Id() + "/in/pose_front", Aspect_XRActionType_InputPose);
+    anActionSet->AddAction (aPoseFront);
+    aGenericSet[Aspect_XRGenericAction_InputPoseFront] = aPoseFront;
+
+    Handle(Aspect_XRAction) aPoseGrip = new Aspect_XRAction (anActionSet->Id() + "/in/pose_handgrip", Aspect_XRActionType_InputPose);
+    anActionSet->AddAction (aPoseGrip);
+    aGenericSet[Aspect_XRGenericAction_InputPoseHandGrip] = aPoseGrip;
+
+    Handle(Aspect_XRAction) aPoseTip = new Aspect_XRAction (anActionSet->Id() + "/in/pose_tip", Aspect_XRActionType_InputPose);
+    anActionSet->AddAction (aPoseTip);
+    aGenericSet[Aspect_XRGenericAction_InputPoseFingerTip] = aPoseTip;
+
+    Handle(Aspect_XRAction) aHaptic = new Aspect_XRAction (anActionSet->Id() + "/out/haptic", Aspect_XRActionType_OutputHaptic);
+    anActionSet->AddAction (aHaptic);
+    aGenericSet[Aspect_XRGenericAction_OutputHaptic] = aHaptic;
+
+    Handle(Aspect_XRAction) aThumbsctickPos = new Aspect_XRAction (anActionSet->Id() + "/in/thumbstick_position", Aspect_XRActionType_InputAnalog);
+    anActionSet->AddAction (aThumbsctickPos);
+    aGenericSet[Aspect_XRGenericAction_InputThumbstickPosition] = aThumbsctickPos;
+
+    Handle(Aspect_XRAction) aThumbsctickTouch = new Aspect_XRAction (anActionSet->Id() + "/in/thumbstick_touch", Aspect_XRActionType_InputDigital);
+    anActionSet->AddAction (aThumbsctickTouch);
+    aGenericSet[Aspect_XRGenericAction_InputThumbstickTouch] = aThumbsctickTouch;
+
+    Handle(Aspect_XRAction) aThumbsctickClick = new Aspect_XRAction (anActionSet->Id() + "/in/thumbstick_click", Aspect_XRActionType_InputDigital);
+    anActionSet->AddAction (aThumbsctickClick);
+    aGenericSet[Aspect_XRGenericAction_InputThumbstickClick] = aThumbsctickClick;
+  }
+#endif
+}
+
+// =======================================================================
+// function : ~Aspect_OpenVRSession
+// purpose  :
+// =======================================================================
+Aspect_OpenVRSession::~Aspect_OpenVRSession()
+{
+  closeVR();
+  delete myContext;
+}
+
+// =======================================================================
+// function : closeVR
+// purpose  :
+// =======================================================================
+void Aspect_OpenVRSession::closeVR()
+{
+#ifdef HAVE_OPENVR
+  if (myContext->System != NULL)
+  {
+    vr::VR_Shutdown();
+    myContext->System = NULL;
+  }
+#endif
+}
+
+// =======================================================================
+// function : getVRSystem
+// purpose  :
+// =======================================================================
+void* Aspect_OpenVRSession::getVRSystem() const
+{
+#ifdef HAVE_OPENVR
+  return myContext->System;
+#else
+  return NULL;
+#endif
+}
+
+// =======================================================================
+// function : Close
+// purpose  :
+// =======================================================================
+void Aspect_OpenVRSession::Close()
+{
+  closeVR();
+}
+
+// =======================================================================
+// function : IsOpen
+// purpose  :
+// =======================================================================
+bool Aspect_OpenVRSession::IsOpen() const
+{
+#ifdef HAVE_OPENVR
+  return myContext->System != NULL;
+#else
+  return false;
+#endif
+}
+
+// =======================================================================
+// function : Open
+// purpose  :
+// =======================================================================
+bool Aspect_OpenVRSession::Open()
+{
+#ifdef HAVE_OPENVR
+  if (myContext->System != NULL)
+  {
+    return true;
+  }
+
+  vr::EVRInitError aVrError = vr::VRInitError_None;
+  myContext->System = vr::VR_Init (&aVrError, vr::VRApplication_Scene);
+  if (aVrError != vr::VRInitError_None)
+  {
+    myContext->System = NULL;
+    Message::DefaultMessenger()->Send (TCollection_AsciiString ("OpenVR, Unable to init VR runtime: ") + vr::VR_GetVRInitErrorAsEnglishDescription (aVrError),
+                                       Message_Fail);
+    Close();
+    return false;
+  }
+
+  /*vr::IVRRenderModels* aRenderModels = (vr::IVRRenderModels* )vr::VR_GetGenericInterface (vr::IVRRenderModels_Version, &aVrError);
+  if (aRenderModels == NULL)
+  {
+    Message::DefaultMessenger()->Send (TCollection_AsciiString ("Unable to get render model interface: ") + vr::VR_GetVRInitErrorAsEnglishDescription (aVrError),
+                                       Message_Fail);;
+  }*/
+
+  NCollection_Vec2<uint32_t> aRenderSize;
+  myContext->System->GetRecommendedRenderTargetSize (&aRenderSize.x(), &aRenderSize.y());
+  myRendSize = NCollection_Vec2<int> (aRenderSize);
+  myDispFreq = myContext->System->GetFloatTrackedDeviceProperty (vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_DisplayFrequency_Float);
+  if (myRendSize.x() <= 0
+   || myRendSize.y() <= 0)
+  {
+    Close();
+    return false;
+  }
+  updateProjectionFrustums();
+  initInput();
+  return true;
+#else
+  Message::DefaultMessenger()->Send ("Open CASCADE has been built without OpenVR support", Message_Fail);
+  return false;
+#endif
+}
+
+// =======================================================================
+// function : initInput
+// purpose  :
+// =======================================================================
+bool Aspect_OpenVRSession::initInput()
+{
+#ifdef HAVE_OPENVR
+  if (myContext->System == NULL)
+  {
+    return false;
+  }
+
+  if (!OSD_File (myActionsManifest).Exists())
+  {
+    Message::DefaultMessenger()->Send (TCollection_AsciiString ("OpenVR, Unable to open actions manifest '") + myActionsManifest  + "'", Message_Fail);
+    return false;
+  }
+
+  vr::EVRInputError aVrError = vr::VRInput()->SetActionManifestPath (myActionsManifest.ToCString());
+  if (aVrError != vr::VRInputError_None)
+  {
+    Message::DefaultMessenger()->Send (TCollection_AsciiString ("OpenVR, Unable to load actions manifest '") + myActionsManifest
+                                    + "': " + getVRInputError (aVrError), Message_Fail);
+    return false;
+  }
+
+  bool hasErrors = false;
+  for (Aspect_XRActionSetMap::Iterator aSetIter (myActionSets); aSetIter.More(); aSetIter.Next())
+  {
+    const Handle(Aspect_XRActionSet)& anActionSet = aSetIter.Value();
+    for (Aspect_XRActionMap::Iterator anActionIter (anActionSet->Actions()); anActionIter.More(); anActionIter.Next())
+    {
+      const Handle(Aspect_XRAction)& anAction = anActionIter.Value();
+      vr::VRActionHandle_t anActionHandle = 0;
+      aVrError = vr::VRInput()->GetActionHandle (anAction->Id().ToCString(), &anActionHandle);
+      if (aVrError == vr::VRInputError_None)
+      {
+        anAction->SetRawHandle (anActionHandle);
+      }
+      else
+      {
+        hasErrors = true;
+        Message::DefaultMessenger()->Send (TCollection_AsciiString ("OpenVR, Unable to load action '") + anAction->Id() + "' from '" + myActionsManifest
+                                         + "': " + getVRInputError (aVrError), Message_Fail);
+      }
+    }
+
+    vr::VRActionSetHandle_t anActionSetHandle = 0;
+    aVrError = vr::VRInput()->GetActionSetHandle (anActionSet->Id().ToCString(), &anActionSetHandle);
+    if (aVrError == vr::VRInputError_None)
+    {
+      anActionSet->SetRawHandle (anActionSetHandle);
+    }
+    else
+    {
+      hasErrors = true;
+      Message::DefaultMessenger()->Send (TCollection_AsciiString ("OpenVR, Unable to load action set '") + anActionSet->Id() + "' from '" + myActionsManifest
+                                        + "': " + getVRInputError (aVrError), Message_Fail);
+    }
+  }
+  return !hasErrors;
+#else
+  return false;
+#endif
+}
+
+// =======================================================================
+// function : GetString
+// purpose  :
+// =======================================================================
+TCollection_AsciiString Aspect_OpenVRSession::GetString (InfoString theInfo) const
+{
+#ifdef HAVE_OPENVR
+  if (myContext->System != NULL)
+  {
+    vr::ETrackedDeviceProperty aVrProp = vr::Prop_Invalid;
+    switch (theInfo)
+    {
+      case InfoString_Vendor:       aVrProp = vr::Prop_ManufacturerName_String; break;
+      case InfoString_Device:       aVrProp = vr::Prop_ModelNumber_String; break;
+      case InfoString_Tracker:      aVrProp = vr::Prop_TrackingSystemName_String; break;
+      case InfoString_SerialNumber: aVrProp = vr::Prop_SerialNumber_String; break;
+    }
+    if (aVrProp != vr::Prop_Invalid)
+    {
+      return myContext->getVrTrackedDeviceString (vr::k_unTrackedDeviceIndex_Hmd, aVrProp);
+    }
+  }
+#else
+  (void )theInfo;
+#endif
+  return TCollection_AsciiString();
+}
+
+// =======================================================================
+// function : NamedTrackedDevice
+// purpose  :
+// =======================================================================
+Standard_Integer Aspect_OpenVRSession::NamedTrackedDevice (Aspect_XRTrackedDeviceRole theDevice) const
+{
+#ifdef HAVE_OPENVR
+  if (myContext->System != NULL)
+  {
+    vr::TrackedDeviceIndex_t aDevIndex = vr::k_unTrackedDeviceIndexInvalid;
+    switch (theDevice)
+    {
+      case Aspect_XRTrackedDeviceRole_Head:      aDevIndex = vr::k_unTrackedDeviceIndex_Hmd; break;
+      case Aspect_XRTrackedDeviceRole_LeftHand:  aDevIndex = myContext->System->GetTrackedDeviceIndexForControllerRole (vr::TrackedControllerRole_LeftHand);  break;
+      case Aspect_XRTrackedDeviceRole_RightHand: aDevIndex = myContext->System->GetTrackedDeviceIndexForControllerRole (vr::TrackedControllerRole_RightHand); break;
+      case Aspect_XRTrackedDeviceRole_Other: break;
+    }
+    if (aDevIndex == vr::k_unTrackedDeviceIndexInvalid)
+    {
+      return -1;
+    }
+    return (Standard_Integer )aDevIndex;
+  }
+#else
+  (void )theDevice;
+#endif
+  return -1;
+}
+
+// =======================================================================
+// function : loadRenderModel
+// purpose  :
+// =======================================================================
+Handle(Graphic3d_ArrayOfTriangles) Aspect_OpenVRSession::loadRenderModel (Standard_Integer theDevice,
+                                                                          Standard_Boolean theToApplyUnitFactor,
+                                                                          Handle(Image_Texture)& theTexture)
+{
+  if (theDevice < 0)
+  {
+    return Handle(Graphic3d_ArrayOfTriangles)();
+  }
+#ifdef HAVE_OPENVR
+  if (myContext->System == NULL)
+  {
+    return Handle(Graphic3d_ArrayOfTriangles)();
+  }
+
+  const TCollection_AsciiString aRenderModelName = myContext->getVrTrackedDeviceString (theDevice, vr::Prop_RenderModelName_String);
+  if (aRenderModelName.IsEmpty())
+  {
+    return Handle(Graphic3d_ArrayOfTriangles)();
+  }
+
+  vr::RenderModel_t* aVrModel = NULL;
+  vr::EVRRenderModelError aVrError = vr::VRRenderModelError_Loading;
+  for (; aVrError == vr::VRRenderModelError_Loading;)
+  {
+    aVrError = vr::VRRenderModels()->LoadRenderModel_Async (aRenderModelName.ToCString(), &aVrModel);
+    OSD::MilliSecSleep (1);
+  }
+  if (aVrError != vr::VRRenderModelError_None)
+  {
+    Message::DefaultMessenger()->Send (TCollection_AsciiString ("OpenVR, Unable to load render model: ") + aRenderModelName + " - " + int(aVrError), Message_Fail);
+    return Handle(Graphic3d_ArrayOfTriangles)();
+  }
+
+  if (aVrModel->diffuseTextureId >= 0)
+  {
+    theTexture = new VRTextureSource (aVrModel->diffuseTextureId, aRenderModelName);
+  }
+
+  const float aScale = theToApplyUnitFactor ? float(myUnitFactor) : 1.0f;
+  Handle(Graphic3d_ArrayOfTriangles) aTris = new Graphic3d_ArrayOfTriangles ((int )aVrModel->unVertexCount, (int )aVrModel->unTriangleCount * 3,
+                                                                             Graphic3d_ArrayFlags_VertexNormal | Graphic3d_ArrayFlags_VertexTexel);
+  for (uint32_t aVertIter = 0; aVertIter < aVrModel->unVertexCount; ++aVertIter)
+  {
+    const vr::RenderModel_Vertex_t& aVert = aVrModel->rVertexData[aVertIter];
+    aTris->AddVertex (aVert.vPosition.v[0] * aScale, aVert.vPosition.v[1] * aScale, aVert.vPosition.v[2] * aScale,
+                      aVert.vNormal.v[0], aVert.vNormal.v[1], aVert.vNormal.v[2],
+                      aVert.rfTextureCoord[0], aVert.rfTextureCoord[1]);
+  }
+  for (uint32_t aTriIter = 0; aTriIter < aVrModel->unTriangleCount; ++aTriIter)
+  {
+    aTris->AddEdges (aVrModel->rIndexData[aTriIter * 3 + 0] + 1,
+                     aVrModel->rIndexData[aTriIter * 3 + 1] + 1,
+                     aVrModel->rIndexData[aTriIter * 3 + 2] + 1);
+  }
+
+  vr::VRRenderModels()->FreeRenderModel (aVrModel);
+  return aTris;
+#else
+  (void )theToApplyUnitFactor;
+  (void )theTexture;
+  return Handle(Graphic3d_ArrayOfTriangles)();
+#endif
+}
+
+// =======================================================================
+// function : EyeToHeadTransform
+// purpose  :
+// =======================================================================
+NCollection_Mat4<double> Aspect_OpenVRSession::EyeToHeadTransform (Aspect_Eye theEye) const
+{
+#ifdef HAVE_OPENVR
+  if (myContext->System != NULL)
+  {
+    const vr::HmdMatrix34_t aMatVr = myContext->System->GetEyeToHeadTransform (theEye == Aspect_Eye_Right ? vr::Eye_Right : vr::Eye_Left);
+    gp_Trsf aTrsf = mat34vr2OccTrsf (aMatVr);
+    if (myUnitFactor != 1.0)
+    {
+      aTrsf.SetTranslationPart (aTrsf.TranslationPart() * myUnitFactor);
+    }
+    NCollection_Mat4<double> aMat4;
+    aTrsf.GetMat4 (aMat4);
+    return aMat4;
+  }
+#else
+  (void )theEye;
+#endif
+  return NCollection_Mat4<double>();
+}
+
+// =======================================================================
+// function : ProjectionMatrix
+// purpose  :
+// =======================================================================
+NCollection_Mat4<double> Aspect_OpenVRSession::ProjectionMatrix (Aspect_Eye theEye,
+                                                                 double theZNear,
+                                                                 double theZFar) const
+{
+#ifdef HAVE_OPENVR
+  if (myContext->System != NULL)
+  {
+    const vr::HmdMatrix44_t aMat4 = myContext->System->GetProjectionMatrix (theEye == Aspect_Eye_Right ? vr::Eye_Right : vr::Eye_Left,
+                                                                            (float )theZNear, (float )theZFar);
+    return mat44vr2Occ (aMat4);
+  }
+#else
+  (void )theEye;
+  (void )theZNear;
+  (void )theZFar;
+#endif
+  return NCollection_Mat4<double>();
+}
+
+// =======================================================================
+// function : updateProjectionFrustums
+// purpose  :
+// =======================================================================
+void Aspect_OpenVRSession::updateProjectionFrustums()
+{
+#ifdef HAVE_OPENVR
+  Aspect_FrustumLRBT<float> aFrustL, aFrustR;
+  myContext->System->GetProjectionRaw (vr::Eye_Left,  &aFrustL.Left, &aFrustL.Right, &aFrustL.Top, &aFrustL.Bottom);
+  myContext->System->GetProjectionRaw (vr::Eye_Right, &aFrustR.Left, &aFrustR.Right, &aFrustR.Top, &aFrustR.Bottom);
+  myFrustumL = Aspect_FrustumLRBT<double> (aFrustL);
+  myFrustumR = Aspect_FrustumLRBT<double> (aFrustR);
+  std::swap (myFrustumL.Top, myFrustumL.Bottom);
+  std::swap (myFrustumR.Top, myFrustumR.Bottom);
+
+  const NCollection_Vec2<double> aTanHalfFov (NCollection_Vec4<float>(-aFrustL.Left, aFrustL.Right,  -aFrustR.Left, aFrustR.Right).maxComp(),
+                                              NCollection_Vec4<float>(-aFrustL.Top,  aFrustL.Bottom, -aFrustR.Top,  aFrustR.Bottom).maxComp());
+  myAspect = aTanHalfFov.x() / aTanHalfFov.y();
+  myFieldOfView = 2.0 * ATan(aTanHalfFov.y()) * 180.0 / M_PI;
+
+  // Intra-ocular Distance can be changed in runtime
+  //const vr::HmdMatrix34_t aLeftToHead  = myContext->System->GetEyeToHeadTransform (vr::Eye_Left);
+  //const vr::HmdMatrix34_t aRightToHead = myContext->System->GetEyeToHeadTransform (vr::Eye_Right);
+  //myIod = aRightToHead.m[0][3] - aLeftToHead.m[0][3];
+  myIod = myContext->System->GetFloatTrackedDeviceProperty (vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_UserIpdMeters_Float);
+  myIod *= myUnitFactor;
+#endif
+}
+
+// =======================================================================
+// function : SetTrackingOrigin
+// purpose  :
+// =======================================================================
+void Aspect_OpenVRSession::SetTrackingOrigin (TrackingUniverseOrigin theOrigin)
+{
+#ifdef HAVE_OPENVR
+  if (myContext->System != NULL)
+  {
+    vr::ETrackingUniverseOrigin anOrigin = vr::TrackingUniverseStanding;
+    switch (theOrigin)
+    {
+      case TrackingUniverseOrigin_Seated:   anOrigin = vr::TrackingUniverseSeated;   break;
+      case TrackingUniverseOrigin_Standing: anOrigin = vr::TrackingUniverseStanding; break;
+    }
+    vr::VRCompositor()->SetTrackingSpace (anOrigin);
+  }
+#endif
+  myTrackOrigin = theOrigin;
+}
+
+// =======================================================================
+// function : WaitPoses
+// purpose  :
+// =======================================================================
+bool Aspect_OpenVRSession::WaitPoses()
+{
+#ifdef HAVE_OPENVR
+  if (myContext->System == NULL)
+  {
+    return false;
+  }
+
+  switch (vr::VRCompositor()->GetTrackingSpace())
+  {
+    case vr::TrackingUniverseSeated:
+      myTrackOrigin = TrackingUniverseOrigin_Seated;
+      break;
+    case vr::TrackingUniverseRawAndUncalibrated:
+    case vr::TrackingUniverseStanding:
+      myTrackOrigin = TrackingUniverseOrigin_Standing;
+      break;
+  }
+
+  const vr::EVRCompositorError aVRError = vr::VRCompositor()->WaitGetPoses (myContext->TrackedPoses, vr::k_unMaxTrackedDeviceCount, NULL, 0);
+  if (aVRError != vr::VRCompositorError_None)
+  {
+    Message::DefaultMessenger()->Send (TCollection_AsciiString ("Compositor wait poses error: ") + getVRCompositorError (aVRError), Message_Trace);
+  }
+
+  for (Standard_Integer aPoseIter = myTrackedPoses.Lower(); aPoseIter <= myTrackedPoses.Upper(); ++aPoseIter)
+  {
+    const vr::TrackedDevicePose_t& aVrPose = myContext->TrackedPoses[aPoseIter];
+    myTrackedPoses[aPoseIter] = poseVr2Occ (aVrPose, myUnitFactor);
+  }
+
+  if (myContext->TrackedPoses[vr::k_unTrackedDeviceIndex_Hmd].bPoseIsValid)
+  {
+    myHeadPose = myTrackedPoses[vr::k_unTrackedDeviceIndex_Hmd].Orientation;
+    updateProjectionFrustums();
+  }
+  return aVRError != vr::VRCompositorError_None;
+#else
+  return false;
+#endif
+}
+
+// =======================================================================
+// function : GetDigitalActionData
+// purpose  :
+// =======================================================================
+Aspect_XRDigitalActionData Aspect_OpenVRSession::GetDigitalActionData (const Handle(Aspect_XRAction)& theAction) const
+{
+  if (theAction.IsNull()
+   || theAction->Type() != Aspect_XRActionType_InputDigital)
+  {
+    throw Standard_ProgramError("Aspect_OpenVRSession::GetDigitalActionData() called for wrong action");
+  }
+
+  Aspect_XRDigitalActionData anActionData;
+#ifdef HAVE_OPENVR
+  if (myContext->System != NULL
+   && theAction->RawHandle() != 0)
+  {
+    vr::InputDigitalActionData_t aNewData = {};
+    vr::EVRInputError anInErr = vr::VRInput()->GetDigitalActionData (theAction->RawHandle(), &aNewData, sizeof(aNewData), vr::k_ulInvalidInputValueHandle);
+    if (anInErr != vr::VRInputError_None)
+    {
+      Message::DefaultMessenger()->Send (TCollection_AsciiString ("Input Error on '") + theAction->Id() + "': " + getVRInputError (anInErr), Message_Fail);
+      return anActionData;
+    }
+
+    anActionData.IsActive     = aNewData.bActive;
+    anActionData.IsChanged    = aNewData.bChanged;
+    anActionData.IsPressed    = aNewData.bState;
+    anActionData.UpdateTime   = aNewData.fUpdateTime;
+    anActionData.ActiveOrigin = aNewData.activeOrigin;
+  }
+#endif
+  return anActionData;
+}
+
+// =======================================================================
+// function : GetAnalogActionData
+// purpose  :
+// =======================================================================
+Aspect_XRAnalogActionData Aspect_OpenVRSession::GetAnalogActionData (const Handle(Aspect_XRAction)& theAction) const
+{
+  if (theAction.IsNull()
+   || theAction->Type() != Aspect_XRActionType_InputAnalog)
+  {
+    throw Standard_ProgramError("Aspect_OpenVRSession::GetAnalogActionData() called for wrong action");
+  }
+
+  Aspect_XRAnalogActionData anActionData;
+#ifdef HAVE_OPENVR
+  if (myContext->System != NULL
+   && theAction->RawHandle() != 0)
+  {
+    vr::InputAnalogActionData_t aNewData = {};
+    vr::EVRInputError anInErr = vr::VRInput()->GetAnalogActionData (theAction->RawHandle(), &aNewData, sizeof(aNewData), vr::k_ulInvalidInputValueHandle);
+    if (anInErr != vr::VRInputError_None)
+    {
+      Message::DefaultMessenger()->Send (TCollection_AsciiString ("Input Error on '") + theAction->Id() + "': " + getVRInputError (anInErr), Message_Fail);
+      return anActionData;
+    }
+
+    anActionData.IsActive = aNewData.bActive;
+    anActionData.VecXYZ.SetValues (aNewData.x, aNewData.y, aNewData.z);
+    anActionData.DeltaXYZ.SetValues (aNewData.deltaX, aNewData.deltaY, aNewData.deltaZ);
+    anActionData.UpdateTime = aNewData.fUpdateTime;
+    anActionData.ActiveOrigin = aNewData.activeOrigin;
+  }
+#endif
+  return anActionData;
+}
+
+// =======================================================================
+// function : GetPoseActionDataForNextFrame
+// purpose  :
+// =======================================================================
+Aspect_XRPoseActionData Aspect_OpenVRSession::GetPoseActionDataForNextFrame (const Handle(Aspect_XRAction)& theAction) const
+{
+  if (theAction.IsNull()
+   || theAction->Type() != Aspect_XRActionType_InputPose)
+  {
+    throw Standard_ProgramError("Aspect_OpenVRSession::GetPoseActionDataForNextFrame() called for wrong action");
+  }
+
+  Aspect_XRPoseActionData anActionData;
+#ifdef HAVE_OPENVR
+  if (myContext->System != NULL
+   && theAction->RawHandle() != 0)
+  {
+    vr::ETrackingUniverseOrigin anOrigin = vr::TrackingUniverseSeated;
+    switch (myTrackOrigin)
+    {
+      case TrackingUniverseOrigin_Seated:   anOrigin = vr::TrackingUniverseSeated;   break;
+      case TrackingUniverseOrigin_Standing: anOrigin = vr::TrackingUniverseStanding; break;
+    }
+    vr::InputPoseActionData_t aNewData = {};
+    vr::EVRInputError anInErr = vr::VRInput()->GetPoseActionDataForNextFrame (theAction->RawHandle(), anOrigin, &aNewData, sizeof(aNewData), vr::k_ulInvalidInputValueHandle);
+    if (anInErr != vr::VRInputError_None)
+    {
+      Message::DefaultMessenger()->Send (TCollection_AsciiString ("Input Error on '") + theAction->Id() + "': " + getVRInputError (anInErr), Message_Fail);
+      return anActionData;
+    }
+
+    anActionData.Pose = poseVr2Occ (aNewData.pose, myUnitFactor);
+    anActionData.IsActive = aNewData.bActive;
+    anActionData.ActiveOrigin = aNewData.activeOrigin;
+  }
+#endif
+  return anActionData;
+}
+
+// =======================================================================
+// function : triggerHapticVibrationAction
+// purpose  :
+// =======================================================================
+void Aspect_OpenVRSession::triggerHapticVibrationAction (const Handle(Aspect_XRAction)& theAction,
+                                                         const Aspect_XRHapticActionData& theParams)
+{
+  if (theAction.IsNull()
+   || theAction->Type() != Aspect_XRActionType_OutputHaptic)
+  {
+    throw Standard_ProgramError("Aspect_OpenVRSession::triggerHapticVibrationAction() called for wrong action");
+  }
+
+#ifdef HAVE_OPENVR
+  if (myContext->System != NULL
+   && theAction->RawHandle() != 0)
+  {
+    Aspect_XRHapticActionData aParams = theParams;
+    if (!theParams.IsValid())
+    {
+      // preset for aborting
+      aParams.Duration  = 0.0f;
+      aParams.Frequency = 1.0f;
+      aParams.Amplitude = 0.1f;
+    }
+    vr::EVRInputError anInErr = vr::VRInput()->TriggerHapticVibrationAction (theAction->RawHandle(),
+                                                                             aParams.Delay, aParams.Duration, aParams.Frequency, aParams.Amplitude,
+                                                                             vr::k_ulInvalidInputValueHandle);
+    if (anInErr != vr::VRInputError_None)
+    {
+      Message::DefaultMessenger()->Send (TCollection_AsciiString ("Output Error on '") + theAction->Id() + "': " + getVRInputError (anInErr), Message_Fail);
+    }
+  }
+#else
+  (void )theParams;
+#endif
+}
+
+// =======================================================================
+// function : ProcessEvents
+// purpose  :
+// =======================================================================
+void Aspect_OpenVRSession::ProcessEvents()
+{
+#ifdef HAVE_OPENVR
+  if (myContext->System == NULL)
+  {
+    return;
+  }
+
+  // process OpenVR events
+  vr::VREvent_t aVREvent = {};
+  for (; myContext->PollNextEvent (aVREvent);)
+  {
+    switch (aVREvent.eventType)
+    {
+      case vr::VREvent_TrackedDeviceActivated:   onTrackedDeviceActivated  ((int )aVREvent.trackedDeviceIndex); break;
+      case vr::VREvent_TrackedDeviceDeactivated: onTrackedDeviceDeactivated((int )aVREvent.trackedDeviceIndex); break;
+      case vr::VREvent_TrackedDeviceUpdated:     onTrackedDeviceUpdated    ((int )aVREvent.trackedDeviceIndex); break;
+    }
+  }
+
+  // process OpenVR action state
+  if (myActionSets.Extent() > 0)
+  {
+    NCollection_LocalArray<vr::VRActiveActionSet_t, 8> anActionSets (myActionSets.Extent());
+    memset (anActionSets, 0, sizeof(vr::VRActiveActionSet_t) * myActionSets.Extent());
+    for (Standard_Integer aSetIter = 0; aSetIter < myActionSets.Extent(); ++aSetIter)
+    {
+      anActionSets[aSetIter].ulActionSet = myActionSets.FindFromIndex (aSetIter + 1)->RawHandle();
+    }
+    vr::VRInput()->UpdateActionState (anActionSets, sizeof(vr::VRActiveActionSet_t), myActionSets.Extent());
+  }
+
+  WaitPoses();
+
+  for (Aspect_XRActionSetMap::Iterator aSetIter (myActionSets); aSetIter.More(); aSetIter.Next())
+  {
+    const Handle(Aspect_XRActionSet)& anActionSet = aSetIter.Value();
+    for (Aspect_XRActionMap::Iterator anActionIter (anActionSet->Actions()); anActionIter.More(); anActionIter.Next())
+    {
+      const Handle(Aspect_XRAction)& anAction = anActionIter.Value();
+      if (anAction->RawHandle() == 0
+       || anAction->Id().IsEmpty())
+      {
+        continue;
+      }
+
+      switch (anAction->Type())
+      {
+        case Aspect_XRActionType_InputDigital:
+        {
+          Aspect_XRDigitalActionData aData = GetDigitalActionData (anAction);
+          //if (aData.IsChanged) { std::cout << "  " << anAction->Id() << " pressed: " << aData.IsPressed << "\n"; }
+          break;
+        }
+        case Aspect_XRActionType_InputAnalog:
+        {
+          Aspect_XRAnalogActionData aData = GetAnalogActionData (anAction);
+          //if (aData.IsChanged()) { std::cout << "  " << anAction->Id() << " changed: " << aData.VecXYZ[0] << " " << aData.VecXYZ[1] << " " << aData.VecXYZ[2] << "\n"; }
+          break;
+        }
+        case Aspect_XRActionType_InputPose:
+        {
+          GetPoseActionDataForNextFrame (anAction);
+          break;
+        }
+      }
+    }
+  }
+
+  // process OpenVR controller state using deprecated API
+  //for (vr::TrackedDeviceIndex_t aDevIter = 0; aDevIter < vr::k_unMaxTrackedDeviceCount; ++aDevIter) {
+  //  vr::VRControllerState_t aCtrlState = {}; if (myContext->GetControllerState (aCtrlState, aDevIter)) { aCtrlState.ulButtonPressed == 0; }
+  //}
+#endif
+}
+
+// =======================================================================
+// function : onTrackedDeviceActivated
+// purpose  :
+// =======================================================================
+void Aspect_OpenVRSession::onTrackedDeviceActivated (Standard_Integer theDeviceIndex)
+{
+  Message::DefaultMessenger()->Send (TCollection_AsciiString ("OpenVR, Device ") + theDeviceIndex + " attached", Message_Trace);
+}
+
+// =======================================================================
+// function : onTrackedDeviceDeactivated
+// purpose  :
+// =======================================================================
+void Aspect_OpenVRSession::onTrackedDeviceDeactivated (Standard_Integer theDeviceIndex)
+{
+  Message::DefaultMessenger()->Send (TCollection_AsciiString ("OpenVR, Device ") + theDeviceIndex + " detached", Message_Trace);
+}
+
+// =======================================================================
+// function : onTrackedDeviceUpdated
+// purpose  :
+// =======================================================================
+void Aspect_OpenVRSession::onTrackedDeviceUpdated (Standard_Integer theDeviceIndex)
+{
+  Message::DefaultMessenger()->Send (TCollection_AsciiString ("OpenVR, Device ") + theDeviceIndex + " updated", Message_Trace);
+}
+
+// =======================================================================
+// function : SubmitEye
+// purpose  :
+// =======================================================================
+bool Aspect_OpenVRSession::SubmitEye (void* theTexture,
+                                      Aspect_GraphicsLibrary theGraphicsLib,
+                                      Aspect_ColorSpace theColorSpace,
+                                      Aspect_Eye theEye)
+{
+  if (theTexture == NULL)
+  {
+    return false;
+  }
+#ifdef HAVE_OPENVR
+  if (myContext->System == NULL)
+  {
+    return false;
+  }
+
+  vr::Texture_t aVRTexture = { (void* )theTexture, vr::TextureType_OpenGL, vr::ColorSpace_Gamma };
+  switch (theGraphicsLib)
+  {
+    case Aspect_GraphicsLibrary_OpenGL:
+      aVRTexture.eType = vr::TextureType_OpenGL;
+      break;
+    default:
+      Message::DefaultMessenger()->Send ("Compositor error: unsupported Graphics API", Message_Fail);
+      return false;
+  }
+  switch (theColorSpace)
+  {
+    case Aspect_ColorSpace_sRGB:   aVRTexture.eColorSpace = vr::ColorSpace_Gamma;  break;
+    case Aspect_ColorSpace_Linear: aVRTexture.eColorSpace = vr::ColorSpace_Linear; break;
+  }
+
+  const vr::EVRCompositorError aVRError = vr::VRCompositor()->Submit (theEye == Aspect_Eye_Right ? vr::Eye_Right : vr::Eye_Left, &aVRTexture);
+  if (aVRError != vr::VRCompositorError_None)
+  {
+    if (aVRError != vr::VRCompositorError_AlreadySubmitted)
+    {
+      Message::DefaultMessenger()->Send (TCollection_AsciiString ("Compositor Error: ") + getVRCompositorError (aVRError), Message_Fail);
+    }
+    else
+    {
+      Message::DefaultMessenger()->Send (TCollection_AsciiString ("Compositor Error: ") + getVRCompositorError (aVRError), Message_Trace);
+    }
+    return false;
+  }
+  return true;
+#else
+  (void )theGraphicsLib;
+  (void )theColorSpace;
+  (void )theEye;
+  return false;
+#endif
+}
diff --git a/src/Aspect/Aspect_OpenVRSession.hxx b/src/Aspect/Aspect_OpenVRSession.hxx
new file mode 100644 (file)
index 0000000..d4280d0
--- /dev/null
@@ -0,0 +1,149 @@
+// Copyright (c) 2020 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _Aspect_OpenVRSession_HeaderFile
+#define _Aspect_OpenVRSession_HeaderFile
+
+#include <Aspect_XRSession.hxx>
+
+//! OpenVR wrapper implementing Aspect_XRSession interface.
+class Aspect_OpenVRSession : public Aspect_XRSession
+{
+  DEFINE_STANDARD_RTTIEXT(Aspect_OpenVRSession, Aspect_XRSession)
+public:
+
+  //! Return TRUE if an HMD may be presented on the system (e.g. to show VR checkbox in application GUI).
+  //! This is fast check, and even if it returns TRUE, opening session may fail.
+  Standard_EXPORT static bool IsHmdPresent();
+
+public:
+
+  //! Empty constructor.
+  Standard_EXPORT Aspect_OpenVRSession();
+
+  //! Destructor.
+  Standard_EXPORT virtual ~Aspect_OpenVRSession();
+
+  //! Return TRUE if session is opened.
+  Standard_EXPORT virtual bool IsOpen() const Standard_OVERRIDE;
+
+  //! Initialize session.
+  Standard_EXPORT virtual bool Open() Standard_OVERRIDE;
+
+  //! Release session.
+  Standard_EXPORT virtual void Close() Standard_OVERRIDE;
+
+  //! Fetch actual poses of tracked devices.
+  Standard_EXPORT virtual bool WaitPoses() Standard_OVERRIDE;
+
+  //! Return recommended viewport Width x Height for rendering into VR.
+  virtual NCollection_Vec2<int> RecommendedViewport() const Standard_OVERRIDE { return myRendSize; }
+
+  //! Return transformation from eye to head.
+  //! vr::GetEyeToHeadTransform() wrapper.
+  Standard_EXPORT virtual NCollection_Mat4<double> EyeToHeadTransform (Aspect_Eye theEye) const Standard_OVERRIDE;
+
+  //! Return projection matrix.
+  Standard_EXPORT virtual NCollection_Mat4<double> ProjectionMatrix (Aspect_Eye theEye,
+                                                                     double theZNear,
+                                                                     double theZFar) const Standard_OVERRIDE;
+
+  //! Return TRUE.
+  virtual bool HasProjectionFrustums() const Standard_OVERRIDE { return true; }
+
+  //! Receive XR events.
+  Standard_EXPORT virtual void ProcessEvents() Standard_OVERRIDE;
+
+  //! Submit texture eye to XR Composer.
+  //! @param theTexture     [in] texture handle
+  //! @param theGraphicsLib [in] graphics library in which texture handle is defined
+  //! @param theColorSpace  [in] texture color space;
+  //!                            sRGB means no color conversion by composer;
+  //!                            Linear means to sRGB color conversion by composer
+  //! @param theEye [in] eye to display
+  //! @return FALSE on error
+  Standard_EXPORT virtual bool SubmitEye (void* theTexture,
+                                          Aspect_GraphicsLibrary theGraphicsLib,
+                                          Aspect_ColorSpace theColorSpace,
+                                          Aspect_Eye theEye) Standard_OVERRIDE;
+
+  //! Query information.
+  Standard_EXPORT virtual TCollection_AsciiString GetString (InfoString theInfo) const Standard_OVERRIDE;
+
+  //! Return index of tracked device of known role.
+  Standard_EXPORT virtual Standard_Integer NamedTrackedDevice (Aspect_XRTrackedDeviceRole theDevice) const Standard_OVERRIDE;
+
+  //! Fetch data for digital input action (like button).
+  Standard_EXPORT virtual Aspect_XRDigitalActionData GetDigitalActionData (const Handle(Aspect_XRAction)& theAction) const Standard_OVERRIDE;
+
+  //! Fetch data for analog input action (like axis).
+  Standard_EXPORT virtual Aspect_XRAnalogActionData GetAnalogActionData (const Handle(Aspect_XRAction)& theAction) const Standard_OVERRIDE;
+
+  //! Fetch data for pose input action (like fingertip position).
+  Standard_EXPORT virtual Aspect_XRPoseActionData GetPoseActionDataForNextFrame (const Handle(Aspect_XRAction)& theAction) const Standard_OVERRIDE;
+
+  //! Set tracking origin.
+  Standard_EXPORT virtual void SetTrackingOrigin (TrackingUniverseOrigin theOrigin) Standard_OVERRIDE;
+
+protected:
+
+  //! Find location of default actions manifest file (based on CSF_OCCTResourcePath or CASROOT variables).
+  Standard_EXPORT TCollection_AsciiString defaultActionsManifest();
+
+  //! Release OpenVR device.
+  Standard_EXPORT void closeVR();
+
+  //! Update projection frustums.
+  Standard_EXPORT virtual void updateProjectionFrustums();
+
+  //! Init VR input.
+  Standard_EXPORT virtual bool initInput();
+
+  //! Handle tracked device activation.
+  Standard_EXPORT virtual void onTrackedDeviceActivated (Standard_Integer theDeviceIndex);
+
+  //! Handle tracked device deactivation.
+  Standard_EXPORT virtual void onTrackedDeviceDeactivated (Standard_Integer theDeviceIndex);
+
+  //! Handle tracked device update.
+  Standard_EXPORT virtual void onTrackedDeviceUpdated (Standard_Integer theDeviceIndex);
+
+  //! Trigger vibration.
+  Standard_EXPORT virtual void triggerHapticVibrationAction (const Handle(Aspect_XRAction)& theAction,
+                                                             const Aspect_XRHapticActionData& theParams) Standard_OVERRIDE;
+
+  //! Return model for displaying device.
+  Standard_EXPORT virtual Handle(Graphic3d_ArrayOfTriangles) loadRenderModel (Standard_Integer theDevice,
+                                                                              Standard_Boolean theToApplyUnitFactor,
+                                                                              Handle(Image_Texture)& theTexture) Standard_OVERRIDE;
+
+protected:
+
+  //! Access vr::IVRSystem* - OpenVR session object.
+  Standard_EXPORT void* getVRSystem() const;
+
+private:
+
+  //! Internal fields
+  struct VRContext;
+  class VRImagePixmap;
+  class VRTextureSource;
+
+protected:
+
+  VRContext* myContext;
+  TCollection_AsciiString myActionsManifest;
+
+};
+
+#endif // _Aspect_OpenVRSession_HeaderFile
diff --git a/src/Aspect/Aspect_TrackedDevicePose.hxx b/src/Aspect/Aspect_TrackedDevicePose.hxx
new file mode 100644 (file)
index 0000000..437f95c
--- /dev/null
@@ -0,0 +1,36 @@
+// Copyright (c) 2020 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _Aspect_TrackedDevicePose_HeaderFile
+#define _Aspect_TrackedDevicePose_HeaderFile
+
+#include <gp_Trsf.hxx>
+#include <NCollection_Array1.hxx>
+
+//! Describes a single pose for a tracked object (for XR).
+struct Aspect_TrackedDevicePose
+{
+  gp_Trsf Orientation;       //!< device to absolute transformation
+  gp_Vec  Velocity;          //!< velocity in tracker space in m/s
+  gp_Vec  AngularVelocity;   //!< angular velocity in radians/s
+  bool    IsValidPose;       //!< indicates valid pose
+  bool    IsConnectedDevice; //!< indicates connected state
+
+  //! Empty constructor.
+  Aspect_TrackedDevicePose() : IsValidPose (false), IsConnectedDevice (false) {}
+};
+
+//! Array of tracked poses.
+typedef NCollection_Array1<Aspect_TrackedDevicePose> Aspect_TrackedDevicePoseArray;
+
+#endif // _Aspect_TrackedDevicePose_HeaderFile
diff --git a/src/Aspect/Aspect_XRAction.hxx b/src/Aspect/Aspect_XRAction.hxx
new file mode 100644 (file)
index 0000000..eb9437f
--- /dev/null
@@ -0,0 +1,58 @@
+// Copyright (c) 2020 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _Aspect_XRAction_HeaderFile
+#define _Aspect_XRAction_HeaderFile
+
+#include <Aspect_XRActionType.hxx>
+#include <NCollection_IndexedDataMap.hxx>
+#include <Standard_Transient.hxx>
+#include <Standard_Type.hxx>
+#include <TCollection_AsciiString.hxx>
+
+//! XR action definition.
+class Aspect_XRAction : public Standard_Transient
+{
+  DEFINE_STANDARD_RTTIEXT(Aspect_XRAction, Standard_Transient)
+public:
+
+  //! Return action id.
+  const TCollection_AsciiString& Id() const { return myId; }
+
+  //! Return action type.
+  Aspect_XRActionType Type() const { return myType; }
+
+  //! Return TRUE if action is defined.
+  bool IsValid() const { return myRawHandle != 0; }
+
+  //! Return action handle.
+  uint64_t RawHandle() const { return myRawHandle; }
+
+  //! Set action handle.
+  void SetRawHandle (uint64_t theHande) { myRawHandle = theHande; }
+
+  //! Main constructor.
+  Aspect_XRAction (const TCollection_AsciiString& theId,
+                   const Aspect_XRActionType theType)
+  : myId (theId), myRawHandle (0), myType (theType) {}
+
+protected:
+  TCollection_AsciiString myId;        //!< action id
+  uint64_t                myRawHandle; //!< action handle
+  Aspect_XRActionType     myType;      //!< action type
+};
+
+//! Map of actions with action Id as a key.
+typedef NCollection_IndexedDataMap<TCollection_AsciiString, Handle(Aspect_XRAction), TCollection_AsciiString> Aspect_XRActionMap;
+
+#endif // _Aspect_XRAction_HeaderFile
diff --git a/src/Aspect/Aspect_XRActionSet.hxx b/src/Aspect/Aspect_XRActionSet.hxx
new file mode 100644 (file)
index 0000000..fa5c695
--- /dev/null
@@ -0,0 +1,55 @@
+// Copyright (c) 2020 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _Aspect_XRActionSet_HeaderFile
+#define _Aspect_XRActionSet_HeaderFile
+
+#include <Aspect_XRAction.hxx>
+
+//! XR action set.
+class Aspect_XRActionSet : public Standard_Transient
+{
+  DEFINE_STANDARD_RTTIEXT(Aspect_XRActionSet, Standard_Transient)
+public:
+
+  //! Return action id.
+  const TCollection_AsciiString& Id() const { return myId; }
+
+  //! Return action handle.
+  uint64_t RawHandle() const { return myRawHandle; }
+
+  //! Set action handle.
+  void SetRawHandle (uint64_t theHande) { myRawHandle = theHande; }
+
+  //! Add action.
+  void AddAction (const Handle(Aspect_XRAction)& theAction)
+  {
+    myActions.Add (theAction->Id(), theAction);
+  }
+
+  //! Return map of actions.
+  const Aspect_XRActionMap& Actions() const { return myActions; }
+
+  //! Main constructor.
+  Aspect_XRActionSet (const TCollection_AsciiString& theId)
+  : myId (theId), myRawHandle (0) {}
+
+protected:
+  TCollection_AsciiString myId;        //!< action set id
+  uint64_t                myRawHandle; //!< action set handle
+  Aspect_XRActionMap      myActions;   //!< map of actions
+};
+
+typedef NCollection_IndexedDataMap<TCollection_AsciiString, Handle(Aspect_XRActionSet), TCollection_AsciiString> Aspect_XRActionSetMap;
+
+#endif // _Aspect_XRActionSet_HeaderFile
diff --git a/src/Aspect/Aspect_XRActionType.hxx b/src/Aspect/Aspect_XRActionType.hxx
new file mode 100644 (file)
index 0000000..90f7f61
--- /dev/null
@@ -0,0 +1,27 @@
+// Copyright (c) 2020 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _Aspect_XRActionType_HeaderFile
+#define _Aspect_XRActionType_HeaderFile
+
+//! XR action type.
+enum Aspect_XRActionType
+{
+  Aspect_XRActionType_InputDigital,  //!< boolean input (like button)
+  Aspect_XRActionType_InputAnalog,   //!< analog input (1/2/3 axes)
+  Aspect_XRActionType_InputPose,     //!< positional input
+  Aspect_XRActionType_InputSkeletal, //!< skeletal input
+  Aspect_XRActionType_OutputHaptic   //!< haptic output (vibration)
+};
+
+#endif // _Aspect_XRActionType_HeaderFile
diff --git a/src/Aspect/Aspect_XRAnalogActionData.hxx b/src/Aspect/Aspect_XRAnalogActionData.hxx
new file mode 100644 (file)
index 0000000..bd6cf03
--- /dev/null
@@ -0,0 +1,35 @@
+// Copyright (c) 2020 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _Aspect_XRAnalogActionData_HeaderFile
+#define _Aspect_XRAnalogActionData_HeaderFile
+
+#include <NCollection_Vec3.hxx>
+
+//! Analog input XR action data.
+struct Aspect_XRAnalogActionData
+{
+  uint64_t                ActiveOrigin; //!< The origin that caused this action's current state
+  float                   UpdateTime;   //!< Time relative to now when this event happened. Will be negative to indicate a past time
+  NCollection_Vec3<float> VecXYZ;       //!< the current state of this action
+  NCollection_Vec3<float> DeltaXYZ;     //!< deltas since the previous update
+  bool                    IsActive;     //!< whether or not this action is currently available to be bound in the active action set
+
+  //! Return TRUE if delta is non-zero.
+  bool IsChanged() { return !DeltaXYZ.IsEqual (NCollection_Vec3<float> (0.0f, 0.0f, 0.0f)); }
+
+  //! Empty constructor.
+  Aspect_XRAnalogActionData() : ActiveOrigin (0), UpdateTime (0.0f), IsActive (false) {}
+};
+
+#endif // _Aspect_XRAnalogActionData_HeaderFile
diff --git a/src/Aspect/Aspect_XRDigitalActionData.hxx b/src/Aspect/Aspect_XRDigitalActionData.hxx
new file mode 100644 (file)
index 0000000..a72bff5
--- /dev/null
@@ -0,0 +1,32 @@
+// Copyright (c) 2020 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _Aspect_XRDigitalActionData_HeaderFile
+#define _Aspect_XRDigitalActionData_HeaderFile
+
+#include <Standard_TypeDef.hxx>
+
+//! Digital input XR action data.
+struct Aspect_XRDigitalActionData
+{
+  uint64_t ActiveOrigin; //!< The origin that caused this action's current state
+  float    UpdateTime;   //!< Time relative to now when this event happened. Will be negative to indicate a past time
+  bool     IsActive;     //!< whether or not this action is currently available to be bound in the active action set
+  bool     IsPressed;    //!< Aspect_InputActionType_Digital state - The current state of this action; will be true if currently pressed
+  bool     IsChanged;    //!< Aspect_InputActionType_Digital state - this is true if the state has changed since the last frame
+
+  //! Empty constructor.
+  Aspect_XRDigitalActionData() : ActiveOrigin (0), UpdateTime (0.0f), IsActive (false), IsPressed (false), IsChanged (false) {}
+};
+
+#endif // _Aspect_XRDigitalActionData_HeaderFile
diff --git a/src/Aspect/Aspect_XRGenericAction.hxx b/src/Aspect/Aspect_XRGenericAction.hxx
new file mode 100644 (file)
index 0000000..0d2d682
--- /dev/null
@@ -0,0 +1,40 @@
+// Copyright (c) 2020 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _Aspect_XRGenericAction_HeaderFile
+#define _Aspect_XRGenericAction_HeaderFile
+
+//! Generic XR action.
+enum Aspect_XRGenericAction
+{
+  Aspect_XRGenericAction_IsHeadsetOn,             //!< headset is on/off head
+  Aspect_XRGenericAction_InputAppMenu,            //!< application menu button pressed/released
+  Aspect_XRGenericAction_InputSysMenu,            //!< system menu button pressed/released
+  Aspect_XRGenericAction_InputTriggerPull,        //!< trigger squeezing [0..1], 1 to click
+  Aspect_XRGenericAction_InputTriggerClick,       //!< trigger clicked/released
+  Aspect_XRGenericAction_InputGripClick,          //!< grip state on/off
+  Aspect_XRGenericAction_InputTrackPadPosition,   //!< trackpad 2D position [-1,+1] with X and Y axes
+  Aspect_XRGenericAction_InputTrackPadTouch,      //!< trackpad touched/untouched
+  Aspect_XRGenericAction_InputTrackPadClick,      //!< trackpad clicked/released
+  Aspect_XRGenericAction_InputThumbstickPosition, //!< thumbstick 2D position [-1,+1] with X and Y axes
+  Aspect_XRGenericAction_InputThumbstickTouch,    //!< thumbstick touched/untouched
+  Aspect_XRGenericAction_InputThumbstickClick,    //!< thumbstick clicked/released
+  Aspect_XRGenericAction_InputPoseBase,           //!< base position of hand
+  Aspect_XRGenericAction_InputPoseFront,          //!< front position of hand
+  Aspect_XRGenericAction_InputPoseHandGrip,       //!< position of main handgrip
+  Aspect_XRGenericAction_InputPoseFingerTip,      //!< position of main fingertip
+  Aspect_XRGenericAction_OutputHaptic             //!< haptic output (vibration)
+};
+enum { Aspect_XRGenericAction_NB = Aspect_XRGenericAction_OutputHaptic + 1 };
+
+#endif // _Aspect_XRGenericAction_HeaderFile
diff --git a/src/Aspect/Aspect_XRHapticActionData.hxx b/src/Aspect/Aspect_XRHapticActionData.hxx
new file mode 100644 (file)
index 0000000..49da6ea
--- /dev/null
@@ -0,0 +1,38 @@
+// Copyright (c) 2020 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _Aspect_XRHapticActionData_HeaderFile
+#define _Aspect_XRHapticActionData_HeaderFile
+
+//! Haptic output XR action data.
+struct Aspect_XRHapticActionData
+{
+  float Delay;     //!< delay in seconds before start
+  float Duration;  //!< duration in seconds
+  float Frequency; //!< vibration frequency
+  float Amplitude; //!< vibration amplitude
+
+  //! Return TRUE if data is not empty.
+  bool IsValid() const
+  {
+    return Duration > 0.0f
+        && Amplitude > 0.0f
+        && Frequency > 0.0f
+        && Delay >= 0.0f;
+  }
+
+  //! Empty constructor.
+  Aspect_XRHapticActionData() : Delay (0.0f), Duration (0.0f), Frequency (0.0f), Amplitude (0.0f) {}
+};
+
+#endif // _Aspect_XRHapticActionData_HeaderFile
diff --git a/src/Aspect/Aspect_XRPoseActionData.hxx b/src/Aspect/Aspect_XRPoseActionData.hxx
new file mode 100644 (file)
index 0000000..5682784
--- /dev/null
@@ -0,0 +1,31 @@
+// Copyright (c) 2020 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _Aspect_XRPoseActionData_HeaderFile
+#define _Aspect_XRPoseActionData_HeaderFile
+
+#include <Aspect_TrackedDevicePose.hxx>
+#include <Standard_TypeDef.hxx>
+
+//! Pose input XR action data.
+struct Aspect_XRPoseActionData
+{
+  Aspect_TrackedDevicePose Pose;         //!< pose state
+  uint64_t                 ActiveOrigin; //!< The origin that caused this action's current state
+  bool                     IsActive;     //!< whether or not this action is currently available to be bound in the active action set
+
+  //! Empty constructor.
+  Aspect_XRPoseActionData() : ActiveOrigin (0), IsActive (false) {}
+};
+
+#endif // _Aspect_XRPoseActionData_HeaderFile
diff --git a/src/Aspect/Aspect_XRSession.cxx b/src/Aspect/Aspect_XRSession.cxx
new file mode 100644 (file)
index 0000000..830ff22
--- /dev/null
@@ -0,0 +1,60 @@
+// Copyright (c) 2020 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.
+
+#include <Aspect_XRSession.hxx>
+
+IMPLEMENT_STANDARD_RTTIEXT(Aspect_XRSession,   Standard_Transient)
+IMPLEMENT_STANDARD_RTTIEXT(Aspect_XRAction,    Standard_Transient)
+IMPLEMENT_STANDARD_RTTIEXT(Aspect_XRActionSet, Standard_Transient)
+
+// =======================================================================
+// function : Aspect_XRSession
+// purpose  :
+// =======================================================================
+Aspect_XRSession::Aspect_XRSession()
+: myTrackOrigin (TrackingUniverseOrigin_Standing),
+  myTrackedPoses (0, 0),
+  myUnitFactor (1.0),
+  myAspect (1.0),
+  myFieldOfView (90.0),
+  myIod (0.0),
+  myDispFreq (0.0f)
+{
+  for (Standard_Integer aRoleIter = 0; aRoleIter < Aspect_XRTrackedDeviceRole_NB; ++aRoleIter)
+  {
+    myRoleActions[aRoleIter].Resize (0, Aspect_XRGenericAction_NB - 1, false);
+  }
+}
+
+// =======================================================================
+// function : AbortHapticVibrationAction
+// purpose  :
+// =======================================================================
+void Aspect_XRSession::AbortHapticVibrationAction (const Handle(Aspect_XRAction)& theAction)
+{
+  triggerHapticVibrationAction (theAction, Aspect_XRHapticActionData());
+}
+
+// =======================================================================
+// function : TriggerHapticVibrationAction
+// purpose  :
+// =======================================================================
+void Aspect_XRSession::TriggerHapticVibrationAction (const Handle(Aspect_XRAction)& theAction,
+                                                     const Aspect_XRHapticActionData& theParams)
+{
+  if (!theParams.IsValid())
+  {
+    throw Standard_ProgramError("Aspect_OpenVRSession::TriggerHapticVibrationAction() called for wrong action");
+  }
+  triggerHapticVibrationAction (theAction, theParams);
+}
diff --git a/src/Aspect/Aspect_XRSession.hxx b/src/Aspect/Aspect_XRSession.hxx
new file mode 100644 (file)
index 0000000..0590937
--- /dev/null
@@ -0,0 +1,262 @@
+// Copyright (c) 2020 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _Aspect_XRSession_HeaderFile
+#define _Aspect_XRSession_HeaderFile
+
+#include <Aspect_ColorSpace.hxx>
+#include <Aspect_Eye.hxx>
+#include <Aspect_FrustumLRBT.hxx>
+#include <Aspect_GraphicsLibrary.hxx>
+#include <Aspect_XRActionSet.hxx>
+#include <Aspect_XRAnalogActionData.hxx>
+#include <Aspect_XRDigitalActionData.hxx>
+#include <Aspect_XRGenericAction.hxx>
+#include <Aspect_XRHapticActionData.hxx>
+#include <Aspect_XRPoseActionData.hxx>
+#include <Aspect_XRTrackedDeviceRole.hxx>
+#include <gp_Trsf.hxx>
+#include <NCollection_Array1.hxx>
+
+class Graphic3d_ArrayOfTriangles;
+class Image_Texture;
+
+//! Extended Reality (XR) Session interface.
+class Aspect_XRSession : public Standard_Transient
+{
+  DEFINE_STANDARD_RTTIEXT(Aspect_XRSession, Standard_Transient)
+public:
+
+  //! Identifies which style of tracking origin the application wants to use for the poses it is requesting.
+  enum TrackingUniverseOrigin
+  {
+    TrackingUniverseOrigin_Seated,   //! poses are provided relative to the seated zero pose
+    TrackingUniverseOrigin_Standing, //! poses are provided relative to the safe bounds configured by the user
+  };
+
+public:
+
+  //! Return TRUE if session is opened.
+  virtual bool IsOpen() const = 0;
+
+  //! Initialize session.
+  virtual bool Open() = 0;
+
+  //! Release session.
+  virtual void Close() = 0;
+
+  //! Fetch actual poses of tracked devices.
+  virtual bool WaitPoses() = 0;
+
+  //! Return recommended viewport Width x Height for rendering into VR.
+  virtual NCollection_Vec2<int> RecommendedViewport() const = 0;
+
+  //! Return transformation from eye to head.
+  virtual NCollection_Mat4<double> EyeToHeadTransform (Aspect_Eye theEye) const = 0;
+
+  //! Return transformation from head to eye.
+  NCollection_Mat4<double> HeadToEyeTransform (Aspect_Eye theEye) const
+  {
+    NCollection_Mat4<double> aMat;
+    EyeToHeadTransform (theEye).Inverted (aMat);
+    return aMat;
+  }
+
+  //! Return projection matrix.
+  virtual NCollection_Mat4<double> ProjectionMatrix (Aspect_Eye theEye,
+                                                     double theZNear,
+                                                     double theZFar) const = 0;
+
+  //! Return FALSE if projection frustums are unsupported and general 4x4 projection matrix should be fetched instead
+  virtual bool HasProjectionFrustums() const = 0;
+
+  //! Receive XR events.
+  virtual void ProcessEvents() = 0;
+
+  //! Submit texture eye to XR Composer.
+  //! @param theTexture     [in] texture handle
+  //! @param theGraphicsLib [in] graphics library in which texture handle is defined
+  //! @param theColorSpace  [in] texture color space;
+  //!                            sRGB means no color conversion by composer;
+  //!                            Linear means to sRGB color conversion by composer
+  //! @param theEye [in] eye to display
+  //! @return FALSE on error
+  virtual bool SubmitEye (void* theTexture,
+                          Aspect_GraphicsLibrary theGraphicsLib,
+                          Aspect_ColorSpace theColorSpace,
+                          Aspect_Eye theEye) = 0;
+
+  //! Return unit scale factor defined as scale factor for m (meters); 1.0 by default.
+  Standard_Real UnitFactor() const { return myUnitFactor; }
+
+  //! Set unit scale factor.
+  void SetUnitFactor (Standard_Real theFactor) { myUnitFactor = theFactor; }
+
+  //! Return aspect ratio.
+  Standard_Real Aspect() const { return myAspect; }
+
+  //! Return field of view.
+  Standard_Real FieldOfView() const { return myFieldOfView; }
+
+  //! Return Intra-ocular Distance (IOD); also known as Interpupillary Distance (IPD).
+  //! Defined in meters by default (@sa UnitFactor()).
+  Standard_Real IOD() const { return myIod; }
+
+  //! Return display frequency or 0 if unknown.
+  Standard_ShortReal DisplayFrequency() const { return myDispFreq; }
+
+  //! Return projection frustum.
+  //! @sa HasProjectionFrustums().
+  const Aspect_FrustumLRBT<double>& ProjectionFrustum (Aspect_Eye theEye) const
+  {
+    return theEye == Aspect_Eye_Right ? myFrustumR : myFrustumL;
+  }
+
+  //! Return head orientation in right-handed system:
+  //! +y is up
+  //! +x is to the right
+  //! -z is forward
+  //! Distance unit is meters by default (@sa UnitFactor()).
+  const gp_Trsf& HeadPose() const { return myHeadPose; }
+
+  //! Return left hand orientation.
+  gp_Trsf LeftHandPose()  const
+  {
+    const Standard_Integer aDevice = NamedTrackedDevice (Aspect_XRTrackedDeviceRole_LeftHand);
+    return aDevice != -1 ? myTrackedPoses[aDevice].Orientation : gp_Trsf();
+  }
+
+  //! Return right hand orientation.
+  gp_Trsf RightHandPose() const
+  {
+    const Standard_Integer aDevice = NamedTrackedDevice (Aspect_XRTrackedDeviceRole_RightHand);
+    return aDevice != -1 ? myTrackedPoses[aDevice].Orientation : gp_Trsf();
+  }
+
+  //! Return number of tracked poses array.
+  const Aspect_TrackedDevicePoseArray& TrackedPoses() const { return myTrackedPoses; }
+
+  //! Return TRUE if device orientation is defined.
+  bool HasTrackedPose (Standard_Integer theDevice) const { return myTrackedPoses[theDevice].IsValidPose; }
+
+  //! Return index of tracked device of known role, or -1 if undefined.
+  virtual Standard_Integer NamedTrackedDevice (Aspect_XRTrackedDeviceRole theDevice) const = 0;
+
+  //! Load model for displaying device.
+  //! @param theDevice  [in] device index
+  //! @param theTexture [out] texture source
+  //! @return model triangulation or NULL if not found
+  Handle(Graphic3d_ArrayOfTriangles) LoadRenderModel (Standard_Integer theDevice,
+                                                      Handle(Image_Texture)& theTexture)
+  {
+    return loadRenderModel (theDevice, true, theTexture);
+  }
+
+  //! Load model for displaying device.
+  //! @param theDevice  [in] device index
+  //! @param theToApplyUnitFactor [in] flag to apply unit scale factor
+  //! @param theTexture [out] texture source
+  //! @return model triangulation or NULL if not found
+  Handle(Graphic3d_ArrayOfTriangles) LoadRenderModel (Standard_Integer theDevice,
+                                                      Standard_Boolean theToApplyUnitFactor,
+                                                      Handle(Image_Texture)& theTexture)
+  {
+    return loadRenderModel (theDevice, theToApplyUnitFactor, theTexture);
+  }
+
+  //! Fetch data for digital input action (like button).
+  //! @param theAction [in] action of Aspect_XRActionType_InputDigital type
+  virtual Aspect_XRDigitalActionData GetDigitalActionData (const Handle(Aspect_XRAction)& theAction) const = 0;
+
+  //! Fetch data for digital input action (like axis).
+  //! @param theAction [in] action of Aspect_XRActionType_InputAnalog type
+  virtual Aspect_XRAnalogActionData GetAnalogActionData (const Handle(Aspect_XRAction)& theAction) const = 0;
+
+  //! Fetch data for pose input action (like fingertip position).
+  //! The returned values will match the values returned by the last call to WaitPoses().
+  //! @param theAction [in] action of Aspect_XRActionType_InputPose type
+  virtual Aspect_XRPoseActionData GetPoseActionDataForNextFrame (const Handle(Aspect_XRAction)& theAction) const = 0;
+
+  //! Trigger vibration.
+  Standard_EXPORT void TriggerHapticVibrationAction (const Handle(Aspect_XRAction)& theAction,
+                                                     const Aspect_XRHapticActionData& theParams);
+
+  //! Abort vibration.
+  Standard_EXPORT void AbortHapticVibrationAction (const Handle(Aspect_XRAction)& theAction);
+
+  //! Return tracking origin.
+  TrackingUniverseOrigin TrackingOrigin() const { return myTrackOrigin; }
+
+  //! Set tracking origin.
+  virtual void SetTrackingOrigin (TrackingUniverseOrigin theOrigin) { myTrackOrigin = theOrigin; }
+
+  //! Return generic action for specific hand or NULL if undefined.
+  const Handle(Aspect_XRAction)& GenericAction (Aspect_XRTrackedDeviceRole theDevice,
+                                                Aspect_XRGenericAction theAction) const
+  {
+    const NCollection_Array1<Handle(Aspect_XRAction)>& anActions = myRoleActions[theDevice];
+    return anActions[theAction];
+  }
+
+public:
+
+  //! Info string enumeration.
+  enum InfoString
+  {
+    InfoString_Vendor,
+    InfoString_Device,
+    InfoString_Tracker,
+    InfoString_SerialNumber,
+  };
+
+  //! Query information.
+  virtual TCollection_AsciiString GetString (InfoString theInfo) const = 0;
+
+protected:
+
+  //! Empty constructor.
+  Standard_EXPORT Aspect_XRSession();
+
+  //! Load model for displaying device.
+  //! @param theDevice  [in] device index
+  //! @param theToApplyUnitFactor [in] flag to apply unit scale factor
+  //! @param theTexture [out] texture source
+  //! @return model triangulation or NULL if not found
+  virtual Handle(Graphic3d_ArrayOfTriangles) loadRenderModel (Standard_Integer theDevice,
+                                                              Standard_Boolean theToApplyUnitFactor,
+                                                              Handle(Image_Texture)& theTexture) = 0;
+
+  //! Trigger vibration.
+  virtual void triggerHapticVibrationAction (const Handle(Aspect_XRAction)& theAction,
+                                             const Aspect_XRHapticActionData& theParams) = 0;
+
+protected:
+
+  NCollection_Array1<Handle(Aspect_XRAction)>
+                                  myRoleActions[Aspect_XRTrackedDeviceRole_NB]; //!< generic actions
+  Aspect_XRActionSetMap           myActionSets;   //!< actions sets
+  TrackingUniverseOrigin          myTrackOrigin;  //!< tracking origin
+  Aspect_TrackedDevicePoseArray   myTrackedPoses; //!< array of tracked poses
+  gp_Trsf                         myHeadPose;     //!< head orientation
+  NCollection_Vec2<int>           myRendSize;     //!< viewport Width x Height for rendering into VR
+  Aspect_FrustumLRBT<double>      myFrustumL;     //!< left  eye projection frustum
+  Aspect_FrustumLRBT<double>      myFrustumR;     //!< right eye projection frustum
+  Standard_Real                   myUnitFactor;   //!< unit scale factor defined as scale factor for m (meters)
+  Standard_Real                   myAspect;       //!< aspect ratio
+  Standard_Real                   myFieldOfView;  //!< field of view
+  Standard_Real                   myIod;          //!< intra-ocular distance in meters
+  Standard_ShortReal              myDispFreq;     //!< display frequency
+
+};
+
+#endif // _Aspect_XRSession_HeaderFile
diff --git a/src/Aspect/Aspect_XRTrackedDeviceRole.hxx b/src/Aspect/Aspect_XRTrackedDeviceRole.hxx
new file mode 100644 (file)
index 0000000..d0890ce
--- /dev/null
@@ -0,0 +1,27 @@
+// Copyright (c) 2020 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _Aspect_XRTrackedDeviceRole_HeaderFile
+#define _Aspect_XRTrackedDeviceRole_HeaderFile
+
+//! Predefined tracked devices.
+enum Aspect_XRTrackedDeviceRole
+{
+  Aspect_XRTrackedDeviceRole_Head,      //!< head
+  Aspect_XRTrackedDeviceRole_LeftHand,  //!< left hand
+  Aspect_XRTrackedDeviceRole_RightHand, //!< right hand
+  Aspect_XRTrackedDeviceRole_Other,     //!< other devices
+};
+enum { Aspect_XRTrackedDeviceRole_NB = Aspect_XRTrackedDeviceRole_Other + 1 };
+
+#endif // _Aspect_XRTrackedDeviceRole_HeaderFile
index 933f271..8f18be0 100755 (executable)
@@ -5,26 +5,32 @@ Aspect_Background.cxx
 Aspect_Background.hxx
 Aspect_CircularGrid.cxx
 Aspect_CircularGrid.hxx
+Aspect_ColorSpace.hxx
 Aspect_Convert.hxx
 Aspect_Display.hxx
 Aspect_DisplayConnection.cxx
 Aspect_DisplayConnection.hxx
 Aspect_DisplayConnectionDefinitionError.hxx
 Aspect_Drawable.hxx
+Aspect_Eye.hxx
 Aspect_FBConfig.hxx
 Aspect_FillMethod.hxx
+Aspect_FrustumLRBT.hxx
 Aspect_GenId.cxx
 Aspect_GenId.hxx
 Aspect_GradientBackground.cxx
 Aspect_GradientBackground.hxx
 Aspect_GradientFillMethod.hxx
 Aspect_GraphicDeviceDefinitionError.hxx
+Aspect_GraphicsLibrary.hxx
 Aspect_Grid.cxx
 Aspect_Grid.hxx
 Aspect_GridDrawMode.hxx
 Aspect_GridType.hxx
 Aspect_NeutralWindow.cxx
 Aspect_NeutralWindow.hxx
+Aspect_OpenVRSession.cxx
+Aspect_OpenVRSession.hxx
 Aspect_Handle.hxx
 Aspect_HatchStyle.hxx
 Aspect_IdentDefinitionError.hxx
@@ -37,6 +43,7 @@ Aspect_SequenceOfColor.hxx
 Aspect_ScrollDelta.hxx
 Aspect_Touch.hxx
 Aspect_TouchMap.hxx
+Aspect_TrackedDevicePose.hxx
 Aspect_TypeOfColorScaleData.hxx
 Aspect_TypeOfColorScaleOrientation.hxx
 Aspect_TypeOfColorScalePosition.hxx
@@ -60,4 +67,15 @@ Aspect_Window.hxx
 Aspect_WindowDefinitionError.hxx
 Aspect_WindowError.hxx
 Aspect_XAtom.hxx
+Aspect_XRAction.hxx
+Aspect_XRActionSet.hxx
+Aspect_XRActionType.hxx
+Aspect_XRAnalogActionData.hxx
+Aspect_XRDigitalActionData.hxx
+Aspect_XRGenericAction.hxx
+Aspect_XRHapticActionData.hxx
+Aspect_XRPoseActionData.hxx
+Aspect_XRSession.cxx
+Aspect_XRSession.hxx
+Aspect_XRTrackedDeviceRole.hxx
 Aspect_XWD.hxx
index 7943efc..ca79372 100644 (file)
@@ -362,6 +362,11 @@ static Standard_Integer dversion(Draw_Interpretor& di, Standard_Integer, const c
 #else
   di << "OpenGL: desktop\n";
 #endif
+#ifdef HAVE_OPENVR
+  di << "OpenVR enabled (HAVE_OPENVR)\n";
+#else
+  di << "OpenVR disabled\n";
+#endif
 #ifdef HAVE_RAPIDJSON
   di << "RapidJSON enabled (HAVE_RAPIDJSON)\n";
 #else
index eb01c68..18c117f 100644 (file)
@@ -13,6 +13,7 @@
 
 #include <Graphic3d_CView.hxx>
 
+#include <Aspect_OpenVRSession.hxx>
 #include <Graphic3d_Layer.hxx>
 #include <Graphic3d_MapIteratorOfMapOfStructure.hxx>
 #include <Graphic3d_StructureManager.hxx>
@@ -32,7 +33,8 @@ Graphic3d_CView::Graphic3d_CView (const Handle(Graphic3d_StructureManager)& theM
   myIsActive               (Standard_False),
   myIsRemoved              (Standard_False),
   myShadingModel           (Graphic3d_TOSM_FRAGMENT),
-  myVisualization          (Graphic3d_TOV_WIREFRAME)
+  myVisualization          (Graphic3d_TOV_WIREFRAME),
+  myUnitFactor             (1.0)
 {
   myId = myStructureManager->Identification (this);
 }
@@ -43,6 +45,7 @@ Graphic3d_CView::Graphic3d_CView (const Handle(Graphic3d_StructureManager)& theM
 //=======================================================================
 Graphic3d_CView::~Graphic3d_CView()
 {
+  myXRSession.Nullify();
   if (!IsRemoved())
   {
     myStructureManager->UnIdentification (this);
@@ -1083,3 +1086,311 @@ void Graphic3d_CView::SetShadingModel (Graphic3d_TypeOfShadingModel theModel)
 
   myShadingModel = theModel;
 }
+
+// =======================================================================
+// function : SetUnitFactor
+// purpose  :
+// =======================================================================
+void Graphic3d_CView::SetUnitFactor (Standard_Real theFactor)
+{
+  if (theFactor <= 0.0)
+  {
+    throw Standard_ProgramError ("Graphic3d_CView::SetUnitFactor() - invalid unit factor");
+  }
+  myUnitFactor = theFactor;
+  if (!myXRSession.IsNull())
+  {
+    myXRSession->SetUnitFactor (theFactor);
+  }
+}
+
+// =======================================================================
+// function : IsActiveXR
+// purpose  :
+// =======================================================================
+bool Graphic3d_CView::IsActiveXR() const
+{
+  return !myXRSession.IsNull()
+       && myXRSession->IsOpen();
+}
+
+// =======================================================================
+// function : InitXR
+// purpose  :
+// =======================================================================
+bool Graphic3d_CView::InitXR()
+{
+  if (myXRSession.IsNull())
+  {
+    myXRSession = new Aspect_OpenVRSession();
+    myXRSession->SetUnitFactor (myUnitFactor);
+  }
+  if (!myXRSession->IsOpen())
+  {
+    myXRSession->Open();
+    if (myBackXRCamera.IsNull())
+    {
+      // backup camera properties
+      myBackXRCamera = new Graphic3d_Camera (myCamera);
+    }
+  }
+  return myXRSession->IsOpen();
+}
+
+// =======================================================================
+// function : ReleaseXR
+// purpose  :
+// =======================================================================
+void Graphic3d_CView::ReleaseXR()
+{
+  if (!myXRSession.IsNull())
+  {
+    if (myXRSession->IsOpen()
+    && !myBackXRCamera.IsNull())
+    {
+      // restore projection properties overridden by HMD
+      myCamera->SetFOV2d (myBackXRCamera->FOV2d());
+      myCamera->SetFOVy  (myBackXRCamera->FOVy());
+      myCamera->SetAspect(myBackXRCamera->Aspect());
+      myCamera->SetIOD   (myBackXRCamera->GetIODType(), myBackXRCamera->IOD());
+      myCamera->SetZFocus(myBackXRCamera->ZFocusType(), myBackXRCamera->ZFocus());
+      myCamera->ResetCustomProjection();
+      myBackXRCamera.Nullify();
+    }
+    myXRSession->Close();
+  }
+}
+
+//=======================================================================
+//function : ProcessXRInput
+//purpose  :
+//=======================================================================
+void Graphic3d_CView::ProcessXRInput()
+{
+  if (myRenderParams.StereoMode == Graphic3d_StereoMode_OpenVR
+   && myCamera->ProjectionType() == Graphic3d_Camera::Projection_Stereo)
+  {
+    InitXR();
+  }
+  else
+  {
+    ReleaseXR();
+  }
+
+  if (!IsActiveXR())
+  {
+    myBaseXRCamera.Nullify();
+    myPosedXRCamera.Nullify();
+    return;
+  }
+
+  myXRSession->ProcessEvents();
+  Invalidate();
+
+  myCamera->SetFOV2d (myRenderParams.HmdFov2d);
+  myCamera->SetAspect(myXRSession->Aspect());
+  myCamera->SetFOVy  (myXRSession->FieldOfView());
+  myCamera->SetIOD   (Graphic3d_Camera::IODType_Absolute, myXRSession->IOD());
+  myCamera->SetZFocus(Graphic3d_Camera::FocusType_Absolute, 1.0 * myUnitFactor);
+
+  // VR APIs tend to decompose camera orientation-projection matrices into the following components:
+  // @begincode
+  //   Model * [View * Eye^-1] * [Projection]
+  // @endcode
+  // so that Eye position is encoded into Orientation matrix, and there should be 2 Orientation matrices and 2 Projection matrices to make the stereo.
+  // Graphic3d_Camera historically follows different decomposition, with Eye position encoded into Projection matrix,
+  // so that there is only 1 Orientation matrix (matching mono view) and 2 Projection matrices.
+  if (myXRSession->HasProjectionFrustums())
+  {
+    // note that this definition does not include a small forward/backward offset from head to eye
+    myCamera->SetCustomStereoFrustums (myXRSession->ProjectionFrustum (Aspect_Eye_Left),
+                                       myXRSession->ProjectionFrustum (Aspect_Eye_Right));
+  }
+  else
+  {
+    const Graphic3d_Mat4d aPoseL = myXRSession->HeadToEyeTransform (Aspect_Eye_Left);
+    const Graphic3d_Mat4d aPoseR = myXRSession->HeadToEyeTransform (Aspect_Eye_Right);
+    const Graphic3d_Mat4d aProjL = myXRSession->ProjectionMatrix (Aspect_Eye_Left,  myCamera->ZNear(), myCamera->ZFar());
+    const Graphic3d_Mat4d aProjR = myXRSession->ProjectionMatrix (Aspect_Eye_Right, myCamera->ZNear(), myCamera->ZFar());
+    myCamera->SetCustomStereoProjection (aProjL * aPoseL, aProjR * aPoseR);
+  }
+  myBaseXRCamera = myCamera;
+  if (myPosedXRCamera.IsNull())
+  {
+    myPosedXRCamera = new Graphic3d_Camera();
+  }
+  SynchronizeXRBaseToPosedCamera();
+}
+
+//=======================================================================
+//function : SynchronizeXRBaseToPosedCamera
+//purpose  :
+//=======================================================================
+void Graphic3d_CView::SynchronizeXRBaseToPosedCamera()
+{
+  if (!myPosedXRCamera.IsNull())
+  {
+    ComputeXRPosedCameraFromBase (*myPosedXRCamera, myXRSession->HeadPose());
+  }
+}
+
+//=======================================================================
+//function : ComputeXRPosedCameraFromBase
+//purpose  :
+//=======================================================================
+void Graphic3d_CView::ComputeXRPosedCameraFromBase (Graphic3d_Camera& theCam,
+                                                    const gp_Trsf& theXRTrsf) const
+{
+  theCam.Copy (myBaseXRCamera);
+
+  // convert head pose into camera transformation
+  const gp_Ax3 anAxVr    (gp::Origin(),  gp::DZ(), gp::DX());
+  const gp_Ax3 aCameraCS (gp::Origin(), -myBaseXRCamera->Direction(), -myBaseXRCamera->SideRight());
+  gp_Trsf aTrsfCS;
+  aTrsfCS.SetTransformation (aCameraCS, anAxVr);
+  const gp_Trsf aTrsfToCamera = aTrsfCS * theXRTrsf * aTrsfCS.Inverted();
+  gp_Trsf aTrsfToEye;
+  aTrsfToEye.SetTranslation (myBaseXRCamera->Eye().XYZ());
+
+  const gp_Trsf aTrsf = aTrsfToEye * aTrsfToCamera;
+  const gp_Dir anUpNew  = myBaseXRCamera->Up().Transformed (aTrsf);
+  const gp_Dir aDirNew  = myBaseXRCamera->Direction().Transformed (aTrsf);
+  const gp_Pnt anEyeNew = gp::Origin().Translated (aTrsf.TranslationPart());
+  theCam.SetUp (anUpNew);
+  theCam.SetDirectionFromEye (aDirNew);
+  theCam.MoveEyeTo (anEyeNew);
+}
+
+//=======================================================================
+//function : SynchronizeXRPosedToBaseCamera
+//purpose  :
+//=======================================================================
+void Graphic3d_CView::SynchronizeXRPosedToBaseCamera()
+{
+  if (myPosedXRCameraCopy.IsNull()
+   || myPosedXRCamera.IsNull()
+   || myBaseXRCamera.IsNull()
+   || myCamera != myPosedXRCamera)
+  {
+    return;
+  }
+
+  if (myPosedXRCameraCopy->Eye().IsEqual (myPosedXRCamera->Eye(), gp::Resolution())
+   && (myPosedXRCameraCopy->Distance() - myPosedXRCamera->Distance()) <= gp::Resolution()
+   && myPosedXRCameraCopy->Direction().IsEqual (myPosedXRCamera->Direction(), gp::Resolution())
+   && myPosedXRCameraCopy->Up().IsEqual (myPosedXRCamera->Up(), gp::Resolution()))
+  {
+    // avoid floating point math in case of no changes
+    return;
+  }
+
+  // re-compute myBaseXRCamera from myPosedXRCamera by applying reversed head pose transformation
+  ComputeXRBaseCameraFromPosed (myPosedXRCamera, myXRSession->HeadPose());
+  myPosedXRCameraCopy->Copy (myPosedXRCamera);
+}
+
+//=======================================================================
+//function : ComputeXRBaseCameraFromPosed
+//purpose  :
+//=======================================================================
+void Graphic3d_CView::ComputeXRBaseCameraFromPosed (const Graphic3d_Camera& theCamPosed,
+                                                    const gp_Trsf& thePoseTrsf)
+{
+  const gp_Ax3 anAxVr    (gp::Origin(),  gp::DZ(), gp::DX());
+  const gp_Ax3 aCameraCS (gp::Origin(), -myBaseXRCamera->Direction(), -myBaseXRCamera->SideRight());
+  gp_Trsf aTrsfCS;
+  aTrsfCS.SetTransformation (aCameraCS, anAxVr);
+  const gp_Trsf aTrsfToCamera  = aTrsfCS * thePoseTrsf * aTrsfCS.Inverted();
+  const gp_Trsf aTrsfCamToHead = aTrsfToCamera.Inverted();
+  const gp_Dir anUpNew  = theCamPosed.Up().Transformed (aTrsfCamToHead);
+  const gp_Dir aDirNew  = theCamPosed.Direction().Transformed (aTrsfCamToHead);
+  const gp_Pnt anEyeNew = theCamPosed.Eye().Translated (aTrsfToCamera.TranslationPart().Reversed());
+  myBaseXRCamera->SetUp (anUpNew);
+  myBaseXRCamera->SetDirectionFromEye (aDirNew);
+  myBaseXRCamera->MoveEyeTo (anEyeNew);
+}
+
+//=======================================================================
+//function : TurnViewXRCamera
+//purpose  :
+//=======================================================================
+void Graphic3d_CView::TurnViewXRCamera (const gp_Trsf& theTrsfTurn)
+{
+  // use current eye position as an anchor
+  const Handle(Graphic3d_Camera)& aCamBase = myBaseXRCamera;
+  gp_Trsf aHeadTrsfLocal;
+  aHeadTrsfLocal.SetTranslationPart (myXRSession->HeadPose().TranslationPart());
+  const gp_Pnt anEyeAnchor = PoseXRToWorld (aHeadTrsfLocal).TranslationPart();
+
+  // turn the view
+  aCamBase->SetDirectionFromEye (aCamBase->Direction().Transformed (theTrsfTurn));
+
+  // recompute new eye
+  const gp_Ax3 anAxVr    (gp::Origin(),  gp::DZ(), gp::DX());
+  const gp_Ax3 aCameraCS (gp::Origin(), -aCamBase->Direction(), -aCamBase->SideRight());
+  gp_Trsf aTrsfCS;
+  aTrsfCS.SetTransformation (aCameraCS, anAxVr);
+  const gp_Trsf aTrsfToCamera = aTrsfCS * aHeadTrsfLocal * aTrsfCS.Inverted();
+  const gp_Pnt anEyeNew = anEyeAnchor.Translated (aTrsfToCamera.TranslationPart().Reversed());
+  aCamBase->MoveEyeTo (anEyeNew);
+
+  SynchronizeXRBaseToPosedCamera();
+}
+
+//=======================================================================
+//function : SetupXRPosedCamera
+//purpose  :
+//=======================================================================
+void Graphic3d_CView::SetupXRPosedCamera()
+{
+  if (!myPosedXRCamera.IsNull())
+  {
+    myCamera = myPosedXRCamera;
+    if (myPosedXRCameraCopy.IsNull())
+    {
+      myPosedXRCameraCopy = new Graphic3d_Camera();
+    }
+    myPosedXRCameraCopy->Copy (myPosedXRCamera);
+  }
+}
+
+//=======================================================================
+//function : UnsetXRPosedCamera
+//purpose  :
+//=======================================================================
+void Graphic3d_CView::UnsetXRPosedCamera()
+{
+  if (myCamera == myPosedXRCamera
+  && !myBaseXRCamera.IsNull())
+  {
+    SynchronizeXRPosedToBaseCamera();
+    myCamera = myBaseXRCamera;
+  }
+}
+
+//=======================================================================
+//function : DiagnosticInformation
+//purpose  :
+//=======================================================================
+void Graphic3d_CView::DiagnosticInformation (TColStd_IndexedDataMapOfStringString& theDict,
+                                             Graphic3d_DiagnosticInfo theFlags) const
+{
+  if ((theFlags & Graphic3d_DiagnosticInfo_Device) != 0
+   && !myXRSession.IsNull())
+  {
+    TCollection_AsciiString aVendor  = myXRSession->GetString (Aspect_XRSession::InfoString_Vendor);
+    TCollection_AsciiString aDevice  = myXRSession->GetString (Aspect_XRSession::InfoString_Device);
+    TCollection_AsciiString aTracker = myXRSession->GetString (Aspect_XRSession::InfoString_Tracker);
+    TCollection_AsciiString aSerial  = myXRSession->GetString (Aspect_XRSession::InfoString_SerialNumber);
+    TCollection_AsciiString aDisplay = TCollection_AsciiString()
+                                     + myXRSession->RecommendedViewport().x() + "x" + myXRSession->RecommendedViewport().y()
+                                     + "@" + (int )Round (myXRSession->DisplayFrequency())
+                                     + " [FOVy: " + (int )Round (myXRSession->FieldOfView()) + "]";
+
+    theDict.ChangeFromIndex (theDict.Add ("VRvendor",  aVendor))  = aVendor;
+    theDict.ChangeFromIndex (theDict.Add ("VRdevice",  aDevice))  = aDevice;
+    theDict.ChangeFromIndex (theDict.Add ("VRtracker", aTracker)) = aTracker;
+    theDict.ChangeFromIndex (theDict.Add ("VRdisplay", aDisplay)) = aDisplay;
+    theDict.ChangeFromIndex (theDict.Add ("VRserial",  aSerial))  = aSerial;
+  }
+}
index 3642e60..d0c0887 100644 (file)
@@ -45,6 +45,7 @@
 #include <Standard_Transient.hxx>
 #include <TColStd_IndexedDataMapOfStringString.hxx>
 
+class Aspect_XRSession;
 class Graphic3d_CView;
 class Graphic3d_GraphicDriver;
 class Graphic3d_Layer;
@@ -427,8 +428,8 @@ public:
   //! The format of returned information (e.g. key-value layout)
   //! is NOT part of this API and can be changed at any time.
   //! Thus application should not parse returned information to weed out specific parameters.
-  virtual void DiagnosticInformation (TColStd_IndexedDataMapOfStringString& theDict,
-                                      Graphic3d_DiagnosticInfo theFlags) const = 0;
+  Standard_EXPORT virtual void DiagnosticInformation (TColStd_IndexedDataMapOfStringString& theDict,
+                                                      Graphic3d_DiagnosticInfo theFlags) const = 0;
 
   //! Returns string with statistic performance info.
   virtual TCollection_AsciiString StatisticInformation() const = 0;
@@ -436,6 +437,84 @@ public:
   //! Fills in the dictionary with statistic performance info.
   virtual void StatisticInformation (TColStd_IndexedDataMapOfStringString& theDict) const = 0;
 
+public:
+
+  //! Return unit scale factor defined as scale factor for m (meters); 1.0 by default.
+  //! Normally, view definition is unitless, however some operations like VR input requires proper units mapping.
+  Standard_Real UnitFactor() const { return myUnitFactor; }
+
+  //! Set unit scale factor.
+  Standard_EXPORT void SetUnitFactor (Standard_Real theFactor);
+
+  //! Return XR session.
+  const Handle(Aspect_XRSession)& XRSession() const { return myXRSession; }
+
+  //! Set XR session.
+  void SetXRSession (const Handle(Aspect_XRSession)& theSession) { myXRSession = theSession; }
+
+  //! Return TRUE if there is active XR session.
+  Standard_EXPORT bool IsActiveXR() const;
+
+  //! Initialize XR session.
+  Standard_EXPORT virtual bool InitXR();
+
+  //! Release XR session.
+  Standard_EXPORT virtual void ReleaseXR();
+
+  //! Process input.
+  Standard_EXPORT virtual void ProcessXRInput();
+
+  //! Compute PosedXRCamera() based on current XR head pose and make it active.
+  Standard_EXPORT void SetupXRPosedCamera();
+
+  //! Set current camera back to BaseXRCamera() and copy temporary modifications of PosedXRCamera().
+  //! Calls SynchronizeXRPosedToBaseCamera() beforehand.
+  Standard_EXPORT void UnsetXRPosedCamera();
+
+  //! Returns transient XR camera position with tracked head orientation applied.
+  const Handle(Graphic3d_Camera)& PosedXRCamera() const { return myPosedXRCamera; }
+
+  //! Sets transient XR camera position with tracked head orientation applied.
+  void SetPosedXRCamera (const Handle(Graphic3d_Camera)& theCamera) { myPosedXRCamera = theCamera; }
+
+  //! Returns anchor camera definition (without tracked head orientation).
+  const Handle(Graphic3d_Camera)& BaseXRCamera() const { return myBaseXRCamera; }
+
+  //! Sets anchor camera definition.
+  void SetBaseXRCamera (const Handle(Graphic3d_Camera)& theCamera) { myBaseXRCamera = theCamera; }
+
+  //! Convert XR pose to world space.
+  //! @param theTrsfXR [in] transformation defined in VR local coordinate system,
+  //!                       oriented as Y-up, X-right and -Z-forward
+  //! @return transformation defining orientation of XR pose in world space
+  gp_Trsf PoseXRToWorld (const gp_Trsf& thePoseXR) const
+  {
+    const Handle(Graphic3d_Camera)& anOrigin = myBaseXRCamera;
+    const gp_Ax3 anAxVr    (gp::Origin(),  gp::DZ(), gp::DX());
+    const gp_Ax3 aCameraCS (anOrigin->Eye().XYZ(), -anOrigin->Direction(), -anOrigin->SideRight());
+    gp_Trsf aTrsfCS;
+    aTrsfCS.SetTransformation (aCameraCS, anAxVr);
+    return aTrsfCS * thePoseXR;
+  }
+
+  //! Recomputes PosedXRCamera() based on BaseXRCamera() and head orientation.
+  Standard_EXPORT void SynchronizeXRBaseToPosedCamera();
+
+  //! Checks if PosedXRCamera() has been modified since SetupXRPosedCamera()
+  //! and copies these modifications to BaseXRCamera().
+  Standard_EXPORT void SynchronizeXRPosedToBaseCamera();
+
+  //! Compute camera position based on XR pose.
+  Standard_EXPORT void ComputeXRPosedCameraFromBase (Graphic3d_Camera& theCam,
+                                                     const gp_Trsf& theXRTrsf) const;
+
+  //! Update based camera from posed camera by applying reversed transformation.
+  Standard_EXPORT void ComputeXRBaseCameraFromPosed (const Graphic3d_Camera& theCamPosed,
+                                                     const gp_Trsf& thePoseTrsf);
+
+  //! Turn XR camera direction using current (head) eye position as anchor.
+  Standard_EXPORT void TurnViewXRCamera (const gp_Trsf& theTrsfTurn);
+
 public: //! @name obsolete Graduated Trihedron functionality
 
   //! Returns data of a graduated trihedron
@@ -490,6 +569,13 @@ protected:
   Graphic3d_TypeOfShadingModel  myShadingModel;
   Graphic3d_TypeOfVisualization myVisualization;
 
+  Handle(Aspect_XRSession) myXRSession;
+  Handle(Graphic3d_Camera) myBackXRCamera;       //!< camera projection parameters to restore after closing XR session (FOV, aspect and similar)
+  Handle(Graphic3d_Camera) myBaseXRCamera;       //!< neutral camera orientation defining coordinate system in which head tracking is defined
+  Handle(Graphic3d_Camera) myPosedXRCamera;      //!< transient XR camera orientation with tracked head orientation applied (based on myBaseXRCamera)
+  Handle(Graphic3d_Camera) myPosedXRCameraCopy;  //!< neutral camera orientation copy at the beginning of processing input
+  Standard_Real            myUnitFactor;         //!< unit scale factor defined as scale factor for m (meters)
+
 protected:
 
   Graphic3d_GraduatedTrihedron myGTrihedronData;
index 854ec60..a2abd6a 100644 (file)
@@ -81,6 +81,8 @@ Graphic3d_Camera::Graphic3d_Camera()
   myAxialScale (1.0, 1.0, 1.0),
   myProjType (Projection_Orthographic),
   myFOVy (45.0),
+  myFOVx (45.0),
+  myFOV2d (180.0),
   myFOVyTan (Tan (DTR_HALF * 45.0)),
   myZNear (DEFAULT_ZNEAR),
   myZFar (DEFAULT_ZFAR),
@@ -89,7 +91,10 @@ Graphic3d_Camera::Graphic3d_Camera()
   myZFocus (1.0),
   myZFocusType (FocusType_Relative),
   myIOD (0.05),
-  myIODType (IODType_Relative)
+  myIODType (IODType_Relative),
+  myIsCustomProjMatM (false),
+  myIsCustomProjMatLR(false),
+  myIsCustomFrustomLR(false)
 {
   myWorldViewProjState.Initialize ((Standard_Size)Standard_Atomic_Increment (&THE_STATE_COUNTER),
                                    (Standard_Size)Standard_Atomic_Increment (&THE_STATE_COUNTER),
@@ -108,6 +113,8 @@ Graphic3d_Camera::Graphic3d_Camera (const Handle(Graphic3d_Camera)& theOther)
   myAxialScale (1.0, 1.0, 1.0),
   myProjType (Projection_Orthographic),
   myFOVy (45.0),
+  myFOVx (45.0),
+  myFOV2d (180.0),
   myFOVyTan (Tan (DTR_HALF * 45.0)),
   myZNear (DEFAULT_ZNEAR),
   myZFar (DEFAULT_ZFAR),
@@ -116,7 +123,10 @@ Graphic3d_Camera::Graphic3d_Camera (const Handle(Graphic3d_Camera)& theOther)
   myZFocus (1.0),
   myZFocusType (FocusType_Relative),
   myIOD (0.05),
-  myIODType (IODType_Relative)
+  myIODType (IODType_Relative),
+  myIsCustomProjMatM (false),
+  myIsCustomProjMatLR(false),
+  myIsCustomFrustomLR(false)
 {
   myWorldViewProjState.Initialize (this);
 
@@ -130,6 +140,7 @@ Graphic3d_Camera::Graphic3d_Camera (const Handle(Graphic3d_Camera)& theOther)
 void Graphic3d_Camera::CopyMappingData (const Handle(Graphic3d_Camera)& theOtherCamera)
 {
   SetFOVy           (theOtherCamera->FOVy());
+  SetFOV2d          (theOtherCamera->FOV2d());
   SetZRange         (theOtherCamera->ZNear(), theOtherCamera->ZFar());
   SetAspect         (theOtherCamera->Aspect());
   SetScale          (theOtherCamera->Scale());
@@ -137,6 +148,20 @@ void Graphic3d_Camera::CopyMappingData (const Handle(Graphic3d_Camera)& theOther
   SetIOD            (theOtherCamera->GetIODType(), theOtherCamera->IOD());
   SetProjectionType (theOtherCamera->ProjectionType());
   SetTile           (theOtherCamera->myTile);
+
+  ResetCustomProjection();
+  if (theOtherCamera->IsCustomStereoProjection())
+  {
+    SetCustomStereoProjection (theOtherCamera->myCustomProjMatL, theOtherCamera->myCustomProjMatR);
+  }
+  else if (theOtherCamera->IsCustomStereoFrustum())
+  {
+    SetCustomStereoFrustums (theOtherCamera->myCustomFrustumL, theOtherCamera->myCustomFrustumR);
+  }
+  if (theOtherCamera->IsCustomMonoProjection())
+  {
+    SetCustomMonoProjection (theOtherCamera->myCustomProjMatM);
+  }
 }
 
 // =======================================================================
@@ -419,12 +444,28 @@ void Graphic3d_Camera::SetFOVy (const Standard_Real theFOVy)
   }
 
   myFOVy = theFOVy;
+  myFOVx = theFOVy * myAspect;
   myFOVyTan = Tan(DTR_HALF * myFOVy);
 
   InvalidateProjection();
 }
 
 // =======================================================================
+// function : SetFOV2d
+// purpose  :
+// =======================================================================
+void Graphic3d_Camera::SetFOV2d (const Standard_Real theFOV)
+{
+  if (FOV2d() == theFOV)
+  {
+    return;
+  }
+
+  myFOV2d = theFOV;
+  InvalidateProjection();
+}
+
+// =======================================================================
 // function : SetZRange
 // purpose  :
 // =======================================================================
@@ -462,6 +503,7 @@ void Graphic3d_Camera::SetAspect (const Standard_Real theAspect)
   }
 
   myAspect = theAspect;
+  myFOVx = myFOVy * theAspect;
 
   InvalidateProjection();
 }
@@ -872,6 +914,62 @@ const Graphic3d_Mat4& Graphic3d_Camera::ProjectionStereoRightF() const
 }
 
 // =======================================================================
+// function : ResetCustomProjection
+// purpose  :
+// =======================================================================
+void Graphic3d_Camera::ResetCustomProjection()
+{
+  if (myIsCustomFrustomLR
+   || myIsCustomProjMatLR
+   || myIsCustomProjMatM)
+  {
+    myIsCustomFrustomLR = false;
+    myIsCustomProjMatLR = false;
+    myIsCustomProjMatM  = false;
+    InvalidateProjection();
+  }
+}
+
+// =======================================================================
+// function : SetCustomStereoFrustums
+// purpose  :
+// =======================================================================
+void Graphic3d_Camera::SetCustomStereoFrustums (const Aspect_FrustumLRBT<Standard_Real>& theFrustumL,
+                                                const Aspect_FrustumLRBT<Standard_Real>& theFrustumR)
+{
+  myCustomFrustumL = theFrustumL;
+  myCustomFrustumR = theFrustumR;
+  myIsCustomFrustomLR = true;
+  myIsCustomProjMatLR = false;
+  InvalidateProjection();
+}
+
+// =======================================================================
+// function : SetCustomStereoProjection
+// purpose  :
+// =======================================================================
+void Graphic3d_Camera::SetCustomStereoProjection (const Graphic3d_Mat4d& theProjL,
+                                                  const Graphic3d_Mat4d& theProjR)
+{
+  myCustomProjMatL = theProjL;
+  myCustomProjMatR = theProjR;
+  myIsCustomProjMatLR = true;
+  myIsCustomFrustomLR = false;
+  InvalidateProjection();
+}
+
+// =======================================================================
+// function : SetCustomMonoProjection
+// purpose  :
+// =======================================================================
+void Graphic3d_Camera::SetCustomMonoProjection (const Graphic3d_Mat4d& theProj)
+{
+  myCustomProjMatM = theProj;
+  myIsCustomProjMatM = true;
+  InvalidateProjection();
+}
+
+// =======================================================================
 // function : UpdateProjection
 // purpose  :
 // =======================================================================
@@ -894,13 +992,11 @@ Graphic3d_Camera::TransformMatrices<Elem_t>&
   Elem_t aDXHalf = 0.0, aDYHalf = 0.0;
   if (IsOrthographic())
   {
-    aDXHalf = aScale * Elem_t (0.5);
-    aDYHalf = aScale * Elem_t (0.5);
+    aDXHalf = aDYHalf = aScale * Elem_t (0.5);
   }
   else
   {
-    aDXHalf = aZNear * Elem_t (myFOVyTan);
-    aDYHalf = aZNear * Elem_t (myFOVyTan);
+    aDXHalf = aDYHalf = aZNear * Elem_t (myFOVyTan);
   }
 
   if (anAspect > 1.0)
@@ -913,10 +1009,11 @@ Graphic3d_Camera::TransformMatrices<Elem_t>&
   }
 
   // sets right of frustum based on aspect ratio
-  Elem_t aLeft   = -aDXHalf;
-  Elem_t aRight  =  aDXHalf;
-  Elem_t aBot    = -aDYHalf;
-  Elem_t aTop    =  aDYHalf;
+  Aspect_FrustumLRBT<Elem_t> anLRBT;
+  anLRBT.Left   = -aDXHalf;
+  anLRBT.Right  =  aDXHalf;
+  anLRBT.Bottom = -aDYHalf;
+  anLRBT.Top    =  aDYHalf;
 
   Elem_t aIOD  = myIODType == IODType_Relative 
     ? static_cast<Elem_t> (myIOD * Distance())
@@ -931,56 +1028,83 @@ Graphic3d_Camera::TransformMatrices<Elem_t>&
     const Elem_t aDXFull = Elem_t(2) * aDXHalf;
     const Elem_t aDYFull = Elem_t(2) * aDYHalf;
     const Graphic3d_Vec2i anOffset = myTile.OffsetLowerLeft();
-    aLeft  = -aDXHalf + aDXFull * static_cast<Elem_t> (anOffset.x())                       / static_cast<Elem_t> (myTile.TotalSize.x());
-    aRight = -aDXHalf + aDXFull * static_cast<Elem_t> (anOffset.x() + myTile.TileSize.x()) / static_cast<Elem_t> (myTile.TotalSize.x());
-    aBot   = -aDYHalf + aDYFull * static_cast<Elem_t> (anOffset.y())                       / static_cast<Elem_t> (myTile.TotalSize.y());
-    aTop   = -aDYHalf + aDYFull * static_cast<Elem_t> (anOffset.y() + myTile.TileSize.y()) / static_cast<Elem_t> (myTile.TotalSize.y());
+    anLRBT.Left   = -aDXHalf + aDXFull * static_cast<Elem_t> (anOffset.x())                       / static_cast<Elem_t> (myTile.TotalSize.x());
+    anLRBT.Right  = -aDXHalf + aDXFull * static_cast<Elem_t> (anOffset.x() + myTile.TileSize.x()) / static_cast<Elem_t> (myTile.TotalSize.x());
+    anLRBT.Bottom = -aDYHalf + aDYFull * static_cast<Elem_t> (anOffset.y())                       / static_cast<Elem_t> (myTile.TotalSize.y());
+    anLRBT.Top    = -aDYHalf + aDYFull * static_cast<Elem_t> (anOffset.y() + myTile.TileSize.y()) / static_cast<Elem_t> (myTile.TotalSize.y());
   }
 
+  if (myIsCustomProjMatM)
+  {
+    theMatrices.MProjection.ConvertFrom (myCustomProjMatM);
+  }
   switch (myProjType)
   {
-    case Projection_Orthographic :
-      OrthoProj (aLeft, aRight, aBot, aTop, aZNear, aZFar, theMatrices.MProjection);
-      break;
-
-    case Projection_Perspective :
-      PerspectiveProj (aLeft, aRight, aBot, aTop, aZNear, aZFar, theMatrices.MProjection);
-      break;
-
-    case Projection_MonoLeftEye :
+    case Projection_Orthographic:
     {
-      StereoEyeProj (aLeft, aRight, aBot, aTop,
-                     aZNear, aZFar, aIOD, aFocus,
-                     Standard_True, theMatrices.MProjection);
-      theMatrices.LProjection = theMatrices.MProjection;
+      if (!myIsCustomProjMatM)
+      {
+        orthoProj (theMatrices.MProjection, anLRBT, aZNear, aZFar);
+      }
       break;
     }
-
-    case Projection_MonoRightEye :
+    case Projection_Perspective:
     {
-      StereoEyeProj (aLeft, aRight, aBot, aTop,
-                     aZNear, aZFar, aIOD, aFocus,
-                     Standard_False, theMatrices.MProjection);
-      theMatrices.RProjection = theMatrices.MProjection;
+      if (!myIsCustomProjMatM)
+      {
+        perspectiveProj (theMatrices.MProjection, anLRBT, aZNear, aZFar);
+      }
       break;
     }
-
-    case Projection_Stereo :
+    case Projection_MonoLeftEye:
+    case Projection_MonoRightEye:
+    case Projection_Stereo:
     {
-      PerspectiveProj (aLeft, aRight, aBot, aTop, aZNear, aZFar, theMatrices.MProjection);
-
-      StereoEyeProj (aLeft, aRight, aBot, aTop,
-                     aZNear, aZFar, aIOD, aFocus,
-                     Standard_True,
-                     theMatrices.LProjection);
-
-      StereoEyeProj (aLeft, aRight, aBot, aTop,
-                     aZNear, aZFar, aIOD, aFocus,
-                     Standard_False,
-                     theMatrices.RProjection);
+      if (!myIsCustomProjMatM)
+      {
+        perspectiveProj (theMatrices.MProjection, anLRBT, aZNear, aZFar);
+      }
+      if (myIsCustomProjMatLR)
+      {
+        theMatrices.LProjection.ConvertFrom (myCustomProjMatL);
+        theMatrices.RProjection.ConvertFrom (myCustomProjMatR);
+      }
+      else if (myIsCustomFrustomLR)
+      {
+        anLRBT = Aspect_FrustumLRBT<Elem_t> (myCustomFrustumL).Multiplied (aZNear);
+        perspectiveProj (theMatrices.LProjection, anLRBT, aZNear, aZFar);
+        if (aIOD != Elem_t (0.0))
+        {
+          theMatrices.LProjection.Translate (NCollection_Vec3<Elem_t> (Elem_t (0.5) * aIOD, Elem_t (0.0), Elem_t (0.0)));
+        }
+
+        anLRBT = Aspect_FrustumLRBT<Elem_t> (myCustomFrustumR).Multiplied (aZNear);
+        perspectiveProj (theMatrices.RProjection, anLRBT, aZNear, aZFar);
+        if (aIOD != Elem_t (0.0))
+        {
+          theMatrices.RProjection.Translate (NCollection_Vec3<Elem_t> (Elem_t (-0.5) * aIOD, Elem_t (0.0), Elem_t (0.0)));
+        }
+      }
+      else
+      {
+        stereoEyeProj (theMatrices.LProjection,
+                       anLRBT, aZNear, aZFar, aIOD, aFocus,
+                       Aspect_Eye_Left);
+        stereoEyeProj (theMatrices.RProjection,
+                       anLRBT, aZNear, aZFar, aIOD, aFocus,
+                       Aspect_Eye_Right);
+      }
       break;
     }
   }
+  if (myProjType == Projection_MonoLeftEye)
+  {
+    theMatrices.MProjection = theMatrices.LProjection;
+  }
+  else if (myProjType == Projection_MonoRightEye)
+  {
+    theMatrices.MProjection = theMatrices.RProjection;
+  }
 
   return theMatrices; // for inline accessors
 }
@@ -1044,29 +1168,26 @@ void Graphic3d_Camera::InvalidateOrientation()
 }
 
 // =======================================================================
-// function : OrthoProj
+// function : orthoProj
 // purpose  :
 // =======================================================================
 template <typename Elem_t>
-void Graphic3d_Camera::OrthoProj (const Elem_t theLeft,
-                                  const Elem_t theRight,
-                                  const Elem_t theBottom,
-                                  const Elem_t theTop,
+void Graphic3d_Camera::orthoProj (NCollection_Mat4<Elem_t>& theOutMx,
+                                  const Aspect_FrustumLRBT<Elem_t>& theLRBT,
                                   const Elem_t theNear,
-                                  const Elem_t theFar,
-                                  NCollection_Mat4<Elem_t>& theOutMx)
+                                  const Elem_t theFar)
 {
   // row 0
-  theOutMx.ChangeValue (0, 0) = Elem_t (2.0) / (theRight - theLeft);
+  theOutMx.ChangeValue (0, 0) = Elem_t (2.0) / (theLRBT.Right - theLRBT.Left);
   theOutMx.ChangeValue (0, 1) = Elem_t (0.0);
   theOutMx.ChangeValue (0, 2) = Elem_t (0.0);
-  theOutMx.ChangeValue (0, 3) = - (theRight + theLeft) / (theRight - theLeft);
+  theOutMx.ChangeValue (0, 3) = - (theLRBT.Right + theLRBT.Left) / (theLRBT.Right - theLRBT.Left);
 
   // row 1
   theOutMx.ChangeValue (1, 0) = Elem_t (0.0);
-  theOutMx.ChangeValue (1, 1) = Elem_t (2.0) / (theTop - theBottom);
+  theOutMx.ChangeValue (1, 1) = Elem_t (2.0) / (theLRBT.Top - theLRBT.Bottom);
   theOutMx.ChangeValue (1, 2) = Elem_t (0.0);
-  theOutMx.ChangeValue (1, 3) = - (theTop + theBottom) / (theTop - theBottom);
+  theOutMx.ChangeValue (1, 3) = - (theLRBT.Top + theLRBT.Bottom) / (theLRBT.Top - theLRBT.Bottom);
 
   // row 2
   theOutMx.ChangeValue (2, 0) = Elem_t (0.0);
@@ -1086,29 +1207,26 @@ void Graphic3d_Camera::OrthoProj (const Elem_t theLeft,
 // purpose  :
 // =======================================================================
 template <typename Elem_t>
-void Graphic3d_Camera::PerspectiveProj (const Elem_t theLeft,
-                                        const Elem_t theRight,
-                                        const Elem_t theBottom,
-                                        const Elem_t theTop,
+void Graphic3d_Camera::perspectiveProj (NCollection_Mat4<Elem_t>& theOutMx,
+                                        const Aspect_FrustumLRBT<Elem_t>& theLRBT,
                                         const Elem_t theNear,
-                                        const Elem_t theFar,
-                                        NCollection_Mat4<Elem_t>& theOutMx)
+                                        const Elem_t theFar)
 {
   // column 0
-  theOutMx.ChangeValue (0, 0) = (Elem_t (2.0) * theNear) / (theRight - theLeft);
+  theOutMx.ChangeValue (0, 0) = (Elem_t (2.0) * theNear) / (theLRBT.Right - theLRBT.Left);
   theOutMx.ChangeValue (1, 0) = Elem_t (0.0);
   theOutMx.ChangeValue (2, 0) = Elem_t (0.0);
   theOutMx.ChangeValue (3, 0) = Elem_t (0.0);
 
   // column 1
   theOutMx.ChangeValue (0, 1) = Elem_t (0.0);
-  theOutMx.ChangeValue (1, 1) = (Elem_t (2.0) * theNear) / (theTop - theBottom);
+  theOutMx.ChangeValue (1, 1) = (Elem_t (2.0) * theNear) / (theLRBT.Top - theLRBT.Bottom);
   theOutMx.ChangeValue (2, 1) = Elem_t (0.0);
   theOutMx.ChangeValue (3, 1) = Elem_t (0.0);
 
   // column 2
-  theOutMx.ChangeValue (0, 2) = (theRight + theLeft) / (theRight - theLeft);
-  theOutMx.ChangeValue (1, 2) = (theTop + theBottom) / (theTop - theBottom);
+  theOutMx.ChangeValue (0, 2) = (theLRBT.Right + theLRBT.Left) / (theLRBT.Right - theLRBT.Left);
+  theOutMx.ChangeValue (1, 2) = (theLRBT.Top + theLRBT.Bottom) / (theLRBT.Top - theLRBT.Bottom);
   theOutMx.ChangeValue (2, 2) = -(theFar + theNear) / (theFar - theNear);
   theOutMx.ChangeValue (3, 2) = Elem_t (-1.0);
 
@@ -1124,25 +1242,22 @@ void Graphic3d_Camera::PerspectiveProj (const Elem_t theLeft,
 // purpose  :
 // =======================================================================
 template <typename Elem_t>
-void Graphic3d_Camera::StereoEyeProj (const Elem_t theLeft,
-                                      const Elem_t theRight,
-                                      const Elem_t theBottom,
-                                      const Elem_t theTop,
+void Graphic3d_Camera::stereoEyeProj (NCollection_Mat4<Elem_t>& theOutMx,
+                                      const Aspect_FrustumLRBT<Elem_t>& theLRBT,
                                       const Elem_t theNear,
                                       const Elem_t theFar,
                                       const Elem_t theIOD,
                                       const Elem_t theZFocus,
-                                      const Standard_Boolean theIsLeft,
-                                      NCollection_Mat4<Elem_t>& theOutMx)
+                                      const Aspect_Eye theEyeIndex)
 {
-  Elem_t aDx = theIsLeft ? Elem_t (0.5) * theIOD : Elem_t (-0.5) * theIOD;
+  Elem_t aDx = theEyeIndex == Aspect_Eye_Left ? Elem_t (0.5) * theIOD : Elem_t (-0.5) * theIOD;
   Elem_t aDXStereoShift = aDx * theNear / theZFocus;
 
   // construct eye projection matrix
-  PerspectiveProj (theLeft  + aDXStereoShift,
-                   theRight + aDXStereoShift,
-                   theBottom, theTop, theNear, theFar,
-                   theOutMx);
+  Aspect_FrustumLRBT<Elem_t> aLRBT = theLRBT;
+  aLRBT.Left  = theLRBT.Left  + aDXStereoShift;
+  aLRBT.Right = theLRBT.Right + aDXStereoShift;
+  perspectiveProj (theOutMx, aLRBT, theNear, theFar);
 
   if (theIOD != Elem_t (0.0))
   {
index f4b5246..42cb0de 100644 (file)
@@ -16,6 +16,8 @@
 #ifndef _Graphic3d_Camera_HeaderFile
 #define _Graphic3d_Camera_HeaderFile
 
+#include <Aspect_Eye.hxx>
+#include <Aspect_FrustumLRBT.hxx>
 #include <Graphic3d_CameraTile.hxx>
 #include <Graphic3d_Mat4d.hxx>
 #include <Graphic3d_Mat4.hxx>
@@ -185,6 +187,12 @@ public:
   //! Return a copy of orthogonalized up direction vector.
   Standard_EXPORT gp_Dir OrthogonalizedUp() const;
 
+  //! Right side direction.
+  gp_Dir SideRight() const
+  {
+    return -(gp_Vec (Direction()) ^ gp_Vec (OrthogonalizedUp()));
+  }
+
   //! Get camera Eye position.
   //! @return camera eye location.
   const gp_Pnt& Eye() const { return myEye; }
@@ -284,15 +292,26 @@ public:
   }
 
   //! Set Field Of View (FOV) in y axis for perspective projection.
+  //! Field of View in x axis is automatically scaled from view aspect ratio.
   //! @param theFOVy [in] the FOV in degrees.
   Standard_EXPORT void SetFOVy (const Standard_Real theFOVy);
 
   //! Get Field Of View (FOV) in y axis.
   //! @return the FOV value in degrees.
-  Standard_Real FOVy() const
-  {
-    return myFOVy;
-  }
+  Standard_Real FOVy() const { return myFOVy; }
+
+  //! Get Field Of View (FOV) in x axis.
+  //! @return the FOV value in degrees.
+  Standard_Real FOVx() const { return myFOVx; }
+
+  //! Get Field Of View (FOV) restriction for 2D on-screen elements; 180 degrees by default.
+  //! When 2D FOV is smaller than FOVy or FOVx, 2D elements defined within offset from view corner
+  //! will be extended to fit into specified 2D FOV.
+  //! This can be useful to make 2D elements sharply visible, like in case of HMD normally having extra large FOVy.
+  Standard_Real FOV2d() const { return myFOV2d; }
+
+  //! Set Field Of View (FOV) restriction for 2D on-screen elements.
+  Standard_EXPORT void SetFOV2d (Standard_Real theFOV);
 
   //! Estimate Z-min and Z-max planes of projection volume to match the
   //! displayed objects. The methods ensures that view volume will
@@ -427,6 +446,24 @@ public:
   //! @return values in form of gp_Pnt (Width, Height, Depth).
   Standard_EXPORT gp_XYZ ViewDimensions (const Standard_Real theZValue) const;
 
+  //! Return offset to the view corner in NDC space within dimension X for 2d on-screen elements, which is normally 0.5.
+  //! Can be clamped when FOVx exceeds FOV2d.
+  Standard_Real NDC2dOffsetX() const
+  {
+    return myFOV2d >= myFOVx
+         ? 0.5
+         : 0.5 * myFOV2d / myFOVx;
+  }
+
+  //! Return offset to the view corner in NDC space within dimension X for 2d on-screen elements, which is normally 0.5.
+  //! Can be clamped when FOVy exceeds FOV2d.
+  Standard_Real NDC2dOffsetY() const
+  {
+    return myFOV2d >= myFOVy
+         ? 0.5
+         : 0.5 * myFOV2d / myFOVy;
+  }
+
   //! Calculate WCS frustum planes for the camera projection volume.
   //! Frustum is a convex volume determined by six planes directing
   //! inwards.
@@ -552,6 +589,32 @@ public:
   //! The matrix will be updated on request.
   Standard_EXPORT void InvalidateOrientation();
 
+public:
+
+  //! Unset all custom frustums and projection matrices.
+  Standard_EXPORT void ResetCustomProjection();
+
+  //! Return TRUE if custom stereo frustums are set.
+  bool IsCustomStereoFrustum() const { return myIsCustomFrustomLR; }
+
+  //! Set custom stereo frustums.
+  //! These can be retrieved from APIs like OpenVR.
+  Standard_EXPORT void SetCustomStereoFrustums (const Aspect_FrustumLRBT<Standard_Real>& theFrustumL,
+                                                const Aspect_FrustumLRBT<Standard_Real>& theFrustumR);
+
+  //! Return TRUE if custom stereo projection matrices are set.
+  bool IsCustomStereoProjection() const { return myIsCustomProjMatLR; }
+
+  //! Set custom stereo projection matrices.
+  Standard_EXPORT void SetCustomStereoProjection (const Graphic3d_Mat4d& theProjL,
+                                                  const Graphic3d_Mat4d& theProjR);
+
+  //! Return TRUE if custom projection matrix is set.
+  bool IsCustomMonoProjection() const { return myIsCustomProjMatM; }
+
+  //! Set custom projection matrix.
+  Standard_EXPORT void SetCustomMonoProjection (const Graphic3d_Mat4d& theProj);
+
   //! Dumps the content of me into the stream
   Standard_EXPORT void DumpJson (Standard_OStream& theOStream, Standard_Integer theDepth = -1) const;
 
@@ -572,68 +635,44 @@ private:
 
 private:
 
-  //! Compose orthographic projection matrix for
-  //! the passed camera volume mapping.
-  //! @param theLeft [in] the left mapping (clipping) coordinate.
-  //! @param theRight [in] the right mapping (clipping) coordinate.
-  //! @param theBottom [in] the bottom mapping (clipping) coordinate.
-  //! @param theTop [in] the top mapping (clipping) coordinate.
-  //! @param theNear [in] the near mapping (clipping) coordinate.
-  //! @param theFar [in] the far mapping (clipping) coordinate.
-  //! @param theOutMx [out] the projection matrix.
+  //! Compose orthographic projection matrix for the passed camera volume mapping.
+  //! @param theOutMx [out] the projection matrix
+  //! @param theLRBT [in] the left/right/bottom/top mapping (clipping) coordinates
+  //! @param theNear [in] the near mapping (clipping) coordinate
+  //! @param theFar [in] the far mapping (clipping) coordinate
   template <typename Elem_t>
-  static void 
-    OrthoProj (const Elem_t              theLeft,
-               const Elem_t              theRight,
-               const Elem_t              theBottom,
-               const Elem_t              theTop,
-               const Elem_t              theNear,
-               const Elem_t              theFar,
-               NCollection_Mat4<Elem_t>& theOutMx);
-
-  //! Compose perspective projection matrix for
-  //! the passed camera volume mapping.
-  //! @param theLeft [in] the left mapping (clipping) coordinate.
-  //! @param theRight [in] the right mapping (clipping) coordinate.
-  //! @param theBottom [in] the bottom mapping (clipping) coordinate.
-  //! @param theTop [in] the top mapping (clipping) coordinate.
-  //! @param theNear [in] the near mapping (clipping) coordinate.
-  //! @param theFar [in] the far mapping (clipping) coordinate.
-  //! @param theOutMx [out] the projection matrix.
+  static void orthoProj (NCollection_Mat4<Elem_t>& theOutMx,
+                         const Aspect_FrustumLRBT<Elem_t>& theLRBT,
+                         const Elem_t theNear,
+                         const Elem_t theFar);
+
+  //! Compose perspective projection matrix for the passed camera volume mapping.
+  //! @param theOutMx [out] the projection matrix
+  //! @param theLRBT [in] the left/right/bottom/top mapping (clipping) coordinates
+  //! @param theNear [in] the near mapping (clipping) coordinate
+  //! @param theFar [in] the far mapping (clipping) coordinate
   template <typename Elem_t>
-  static void
-    PerspectiveProj (const Elem_t              theLeft,
-                     const Elem_t              theRight,
-                     const Elem_t              theBottom,
-                     const Elem_t              theTop,
-                     const Elem_t              theNear,
-                     const Elem_t              theFar,
-                     NCollection_Mat4<Elem_t>& theOutMx);
+  static void perspectiveProj (NCollection_Mat4<Elem_t>& theOutMx,
+                               const Aspect_FrustumLRBT<Elem_t>& theLRBT,
+                               const Elem_t theNear,
+                               const Elem_t theFar);
 
   //! Compose projection matrix for L/R stereo eyes.
-  //! @param theLeft [in] the left mapping (clipping) coordinate.
-  //! @param theRight [in] the right mapping (clipping) coordinate.
-  //! @param theBottom [in] the bottom mapping (clipping) coordinate.
-  //! @param theTop [in] the top mapping (clipping) coordinate.
-  //! @param theNear [in] the near mapping (clipping) coordinate.
-  //! @param theFar [in] the far mapping (clipping) coordinate.
-  //! @param theIOD [in] the Intraocular distance.
-  //! @param theZFocus [in] the z coordinate of off-axis
-  //! projection plane with zero parallax.
-  //! @param theIsLeft [in] boolean flag to choose between L/R eyes.
-  //! @param theOutMx [out] the projection matrix.
+  //! @param theOutMx [out] the projection matrix
+  //! @param theLRBT [in] the left/right/bottom/top mapping (clipping) coordinates
+  //! @param theNear [in] the near mapping (clipping) coordinate
+  //! @param theFar [in] the far mapping (clipping) coordinate
+  //! @param theIOD [in] the Intraocular distance
+  //! @param theZFocus [in] the z coordinate of off-axis projection plane with zero parallax
+  //! @param theEyeIndex [in] choose between L/R eyes
   template <typename Elem_t>
-  static void
-    StereoEyeProj (const Elem_t              theLeft,
-                   const Elem_t              theRight,
-                   const Elem_t              theBottom,
-                   const Elem_t              theTop,
-                   const Elem_t              theNear,
-                   const Elem_t              theFar,
-                   const Elem_t              theIOD,
-                   const Elem_t              theZFocus,
-                   const Standard_Boolean    theIsLeft,
-                   NCollection_Mat4<Elem_t>& theOutMx);
+  static void stereoEyeProj (NCollection_Mat4<Elem_t>& theOutMx,
+                             const Aspect_FrustumLRBT<Elem_t>& theLRBT,
+                             const Elem_t theNear,
+                             const Elem_t theFar,
+                             const Elem_t theIOD,
+                             const Elem_t theZFocus,
+                             const Aspect_Eye theEyeIndex);
 
   //! Construct "look at" orientation transformation.
   //! Reference point differs for perspective and ortho modes 
@@ -684,6 +723,8 @@ private:
 
   Projection    myProjType; //!< Projection type used for rendering.
   Standard_Real myFOVy;     //!< Field Of View in y axis.
+  Standard_Real myFOVx;     //!< Field Of View in x axis.
+  Standard_Real myFOV2d;    //!< Field Of View limit for 2d on-screen elements
   Standard_Real myFOVyTan;  //!< Field Of View as Tan(DTR_HALF * myFOVy)
   Standard_Real myZNear;    //!< Distance to near clipping plane.
   Standard_Real myZFar;     //!< Distance to far clipping plane.
@@ -698,6 +739,15 @@ private:
 
   Graphic3d_CameraTile myTile;//!< Tile defining sub-area for drawing
 
+  Graphic3d_Mat4d  myCustomProjMatM;
+  Graphic3d_Mat4d  myCustomProjMatL;
+  Graphic3d_Mat4d  myCustomProjMatR;
+  Aspect_FrustumLRBT<Standard_Real> myCustomFrustumL; //!< left  custom frustum
+  Aspect_FrustumLRBT<Standard_Real> myCustomFrustumR; //!< right custom frustum
+  Standard_Boolean myIsCustomProjMatM;  //!< flag indicating usage of custom projection matrix
+  Standard_Boolean myIsCustomProjMatLR; //!< flag indicating usage of custom stereo projection matrices
+  Standard_Boolean myIsCustomFrustomLR; //!< flag indicating usage of custom stereo frustums
+
   mutable TransformMatrices<Standard_Real>      myMatricesD;
   mutable TransformMatrices<Standard_ShortReal> myMatricesF;
 
index f445930..5e51794 100644 (file)
@@ -134,8 +134,10 @@ public:
     WhitePoint                  (1.f),
     // stereoscopic parameters
     StereoMode (Graphic3d_StereoMode_QuadBuffer),
+    HmdFov2d (30.0f),
     AnaglyphFilter (Anaglyph_RedCyan_Optimized),
     ToReverseStereo (Standard_False),
+    ToMirrorComposer (Standard_True),
     //
     StatsPosition (new Graphic3d_TransformPers (Graphic3d_TMF_2d, Aspect_TOTP_LEFT_UPPER,  Graphic3d_Vec2i (20, 20))),
     ChartPosition (new Graphic3d_TransformPers (Graphic3d_TMF_2d, Aspect_TOTP_RIGHT_UPPER, Graphic3d_Vec2i (20, 20))),
@@ -225,10 +227,12 @@ public:
   Standard_ShortReal                WhitePoint;                  //!< white point value used in filmic tone mapping (path tracing), 1.0 by default
 
   Graphic3d_StereoMode              StereoMode;                  //!< stereoscopic output mode, Graphic3d_StereoMode_QuadBuffer by default
+  Standard_ShortReal                HmdFov2d;                    //!< sharp field of view range in degrees for displaying on-screen 2D elements, 30.0 by default;
   Anaglyph                          AnaglyphFilter;              //!< filter for anaglyph output, Anaglyph_RedCyan_Optimized by default
   Graphic3d_Mat4                    AnaglyphLeft;                //!< left  anaglyph filter (in normalized colorspace), Color = AnaglyphRight * theColorRight + AnaglyphLeft * theColorLeft;
   Graphic3d_Mat4                    AnaglyphRight;               //!< right anaglyph filter (in normalized colorspace), Color = AnaglyphRight * theColorRight + AnaglyphLeft * theColorLeft;
   Standard_Boolean                  ToReverseStereo;             //!< flag to reverse stereo pair, FALSE by default
+  Standard_Boolean                  ToMirrorComposer;            //!< if output device is an external composer - mirror rendering results in window in addition to sending frame to composer, TRUE by default
 
   Handle(Graphic3d_TransformPers)   StatsPosition;               //!< location of stats, upper-left position by default
   Handle(Graphic3d_TransformPers)   ChartPosition;               //!< location of stats chart, upper-right position by default
index 0c0a979..4aef0c1 100644 (file)
@@ -27,6 +27,7 @@ enum Graphic3d_StereoMode
   Graphic3d_StereoMode_SideBySide,       //!< horizontal pair
   Graphic3d_StereoMode_OverUnder,        //!< vertical   pair
   Graphic3d_StereoMode_SoftPageFlip,     //!< software PageFlip for shutter glasses, should NOT be used!
+  Graphic3d_StereoMode_OpenVR,           //!< OpenVR (HMD)
   Graphic3d_StereoMode_NB                //!< the number of modes
 };
 
index 5fd2ffc..cb5c501 100644 (file)
@@ -364,7 +364,7 @@ void Graphic3d_TransformPers::Apply (const Handle(Graphic3d_Camera)& theCamera,
     {
       const Standard_Real anOffsetX = (Standard_Real(myParams.Params2d.OffsetX) + aJitterComp) * aScale;
       const gp_Dir aSide   = aForward.Crossed (theCamera->Up());
-      const gp_XYZ aDeltaX = aSide.XYZ() * (Abs(aViewDim.X()) * 0.5 - anOffsetX);
+      const gp_XYZ aDeltaX = aSide.XYZ() * (Abs(aViewDim.X()) * theCamera->NDC2dOffsetX() - anOffsetX);
       if ((myParams.Params2d.Corner & Aspect_TOTP_RIGHT) != 0)
       {
         aCenter += aDeltaX;
@@ -377,7 +377,7 @@ void Graphic3d_TransformPers::Apply (const Handle(Graphic3d_Camera)& theCamera,
     if ((myParams.Params2d.Corner & (Aspect_TOTP_TOP | Aspect_TOTP_BOTTOM)) != 0)
     {
       const Standard_Real anOffsetY = (Standard_Real(myParams.Params2d.OffsetY) + aJitterComp) * aScale;
-      const gp_XYZ aDeltaY = theCamera->Up().XYZ() * (Abs(aViewDim.Y()) * 0.5 - anOffsetY);
+      const gp_XYZ aDeltaY = theCamera->Up().XYZ() * (Abs(aViewDim.Y()) * theCamera->NDC2dOffsetY() - anOffsetY);
       if ((myParams.Params2d.Corner & Aspect_TOTP_TOP) != 0)
       {
         aCenter += aDeltaY;
@@ -408,7 +408,7 @@ void Graphic3d_TransformPers::Apply (const Handle(Graphic3d_Camera)& theCamera,
     gp_XYZ aCenter (0.0, 0.0, -aFocus);
     if ((myParams.Params2d.Corner & (Aspect_TOTP_LEFT | Aspect_TOTP_RIGHT)) != 0)
     {
-      aCenter.SetX (-aViewDim.X() * 0.5 + (Standard_Real(myParams.Params2d.OffsetX) + aJitterComp) * aScale);
+      aCenter.SetX (-aViewDim.X() * theCamera->NDC2dOffsetX() + (Standard_Real(myParams.Params2d.OffsetX) + aJitterComp) * aScale);
       if ((myParams.Params2d.Corner & Aspect_TOTP_RIGHT) != 0)
       {
         aCenter.SetX (-aCenter.X());
@@ -416,7 +416,7 @@ void Graphic3d_TransformPers::Apply (const Handle(Graphic3d_Camera)& theCamera,
     }
     if ((myParams.Params2d.Corner & (Aspect_TOTP_TOP | Aspect_TOTP_BOTTOM)) != 0)
     {
-      aCenter.SetY (-aViewDim.Y() * 0.5 + (Standard_Real(myParams.Params2d.OffsetY) + aJitterComp) * aScale);
+      aCenter.SetY (-aViewDim.Y() * theCamera->NDC2dOffsetY() + (Standard_Real(myParams.Params2d.OffsetY) + aJitterComp) * aScale);
       if ((myParams.Params2d.Corner & Aspect_TOTP_TOP) != 0)
       {
         aCenter.SetY (-aCenter.Y());
index 3e563b3..9b60999 100644 (file)
@@ -40,10 +40,11 @@ proc Visualization:toolkits { } {
 ;# Autres UDs a prendre.
 ;#
 proc Visualization:ressources { } {
-    return [list \
-          [list both r Textures {}] \
-          [list both r Shaders {}] \
-           ]
+  return [list \
+         [list both r Textures {}] \
+         [list both r Shaders {}] \
+         [list both r XRResources {}] \
+  ]
 }
 ;#
 ;# Nom du module 
index 43d4670..bb85713 100644 (file)
@@ -4068,6 +4068,22 @@ void OpenGl_Context::SetPolygonOffset (const Graphic3d_PolygonOffset& theOffset)
 }
 
 // =======================================================================
+// function : SetCamera
+// purpose  :
+// =======================================================================
+void OpenGl_Context::SetCamera (const Handle(Graphic3d_Camera)& theCamera)
+{
+  myCamera = theCamera;
+  if (!theCamera.IsNull())
+  {
+    ProjectionState.SetCurrent (theCamera->ProjectionMatrixF());
+    WorldViewState .SetCurrent (theCamera->OrientationMatrixF());
+    ApplyProjectionMatrix();
+    ApplyWorldViewMatrix();
+  }
+}
+
+// =======================================================================
 // function : ApplyModelWorldMatrix
 // purpose  :
 // =======================================================================
index d81ed13..3806df4 100644 (file)
@@ -136,6 +136,7 @@ template<typename theBaseClass_t> struct OpenGl_TmplCore45;
 typedef OpenGl_TmplCore45<OpenGl_GlCore44Back> OpenGl_GlCore45Back;
 typedef OpenGl_TmplCore45<OpenGl_GlCore44>     OpenGl_GlCore45;
 
+class Graphic3d_Camera;
 class Graphic3d_PresentationAttributes;
 class OpenGl_Aspects;
 class OpenGl_FrameBuffer;
@@ -678,6 +679,12 @@ public:
   //! Returns currently applied polygon offset parameters.
   const Graphic3d_PolygonOffset& PolygonOffset() const { return myPolygonOffset; }
 
+  //! Returns camera object.
+  const Handle(Graphic3d_Camera)& Camera() const { return myCamera; }
+
+  //! Sets camera object to the context and update matrices.
+  Standard_EXPORT void SetCamera (const Handle(Graphic3d_Camera)& theCamera);
+
   //! Applies matrix into shader manager stored in ModelWorldState to OpenGl.
   //! In "model -> world -> view -> projection" it performs:
   //!     model -> world
@@ -1143,6 +1150,7 @@ private: // context info
 
 private: //! @name fields tracking current state
 
+  Handle(Graphic3d_Camera)      myCamera;          //!< active camera object
   Handle(OpenGl_FrameStats)     myFrameStats;      //!< structure accumulating frame statistics
   Handle(OpenGl_ShaderProgram)  myActiveProgram;   //!< currently active GLSL program
   Handle(OpenGl_TextureSet)     myActiveTextures;  //!< currently bound textures
index 066d0f1..95a5d3b 100644 (file)
@@ -389,7 +389,7 @@ void OpenGl_FrameStatsPrs::Render (const Handle(OpenGl_Workspace)& theWorkspace)
     aCtx->WorldViewState.Push();
     if (!myCountersTrsfPers.IsNull())
     {
-      myCountersTrsfPers->Apply (theWorkspace->View()->Camera(),
+      myCountersTrsfPers->Apply (aCtx->Camera(),
                                  aCtx->ProjectionState.Current(), aCtx->WorldViewState.ChangeCurrent(),
                                  aCtx->VirtualViewport()[2], aCtx->VirtualViewport()[3]);
     }
index b270c6a..34111b2 100755 (executable)
@@ -422,7 +422,7 @@ void OpenGl_GraduatedTrihedron::renderAxis (const Handle(OpenGl_Workspace)& theW
   const Standard_Integer aHeight = theWorkspace->Height();
 
   // Take into account Transform Persistence
-  aContext->ModelWorldState.SetCurrent (aTransMode.Compute (theWorkspace->View()->Camera(), aProjection, aWorldView, aWidth, aHeight));
+  aContext->ModelWorldState.SetCurrent (aTransMode.Compute (aContext->Camera(), aProjection, aWorldView, aWidth, aHeight));
   aContext->ApplyModelViewMatrix();
 
   anAxis.Arrow.Render (theWorkspace);
index 334e053..651ceed 100644 (file)
@@ -2979,6 +2979,7 @@ Standard_Boolean OpenGl_ShaderManager::prepareStdProgramStereo (Handle(OpenGl_Sh
     }
     case Graphic3d_StereoMode_QuadBuffer:
     case Graphic3d_StereoMode_SoftPageFlip:
+    case Graphic3d_StereoMode_OpenVR:
     default:
     {
       /*const Handle(OpenGl_ShaderProgram)& aProgram = myStereoPrograms[Graphic3d_StereoMode_QuadBuffer];
index 82f4ae4..b7533c1 100644 (file)
@@ -439,7 +439,7 @@ void OpenGl_Structure::Render (const Handle(OpenGl_Workspace) &theWorkspace) con
   {
     aCtx->WorldViewState.Push();
     OpenGl_Mat4& aWorldView = aCtx->WorldViewState.ChangeCurrent();
-    myTrsfPers->Apply (theWorkspace->View()->Camera(),
+    myTrsfPers->Apply (aCtx->Camera(),
                        aCtx->ProjectionState.Current(), aWorldView,
                        aCtx->VirtualViewport()[2], aCtx->VirtualViewport()[3]);
 
index d9551e0..6a9b844 100644 (file)
@@ -333,7 +333,7 @@ void OpenGl_Text::Render (const Handle(OpenGl_Workspace)& theWorkspace) const
 
   if (myText->HasPlane() && myText->HasOwnAnchorPoint())
   {
-    myOrientationMatrix = theWorkspace->View()->Camera()->OrientationMatrix();
+    myOrientationMatrix = aCtx->Camera()->OrientationMatrix();
     // reset translation part
     myOrientationMatrix.ChangeValue (0, 3) = 0.0;
     myOrientationMatrix.ChangeValue (1, 3) = 0.0;
index cd69af0..ac79137 100644 (file)
@@ -61,6 +61,7 @@ OpenGl_View::OpenGl_View (const Handle(Graphic3d_StructureManager)& theMgr,
   myFboColorFormat       (GL_SRGB8_ALPHA8), // note that GL_SRGB8 is not required to be renderable, unlike GL_RGB8, GL_RGBA8, GL_SRGB8_ALPHA8
   myFboDepthFormat       (GL_DEPTH24_STENCIL8),
   myToFlipOutput         (Standard_False),
+  //
   myFrameCounter         (0),
   myHasFboBlit           (Standard_True),
   myToDisableOIT         (Standard_False),
@@ -111,6 +112,7 @@ OpenGl_View::OpenGl_View (const Handle(Graphic3d_StructureManager)& theMgr,
   myImmediateSceneFbos[1]    = new OpenGl_FrameBuffer();
   myImmediateSceneFbosOit[0] = new OpenGl_FrameBuffer();
   myImmediateSceneFbosOit[1] = new OpenGl_FrameBuffer();
+  myXrSceneFbo               = new OpenGl_FrameBuffer();
   myOpenGlFBO                = new OpenGl_FrameBuffer();
   myOpenGlFBO2               = new OpenGl_FrameBuffer();
   myRaytraceFBO1[0]          = new OpenGl_FrameBuffer();
@@ -182,6 +184,7 @@ void OpenGl_View::releaseSrgbResources (const Handle(OpenGl_Context)& theCtx)
   myImmediateSceneFbos[1]   ->Release (theCtx.get());
   myImmediateSceneFbosOit[0]->Release (theCtx.get());
   myImmediateSceneFbosOit[1]->Release (theCtx.get());
+  myXrSceneFbo              ->Release (theCtx.get());
   myOpenGlFBO               ->Release (theCtx.get());
   myOpenGlFBO2              ->Release (theCtx.get());
   myFullScreenQuad           .Release (theCtx.get());
@@ -209,6 +212,7 @@ void OpenGl_View::ReleaseGlResources (const Handle(OpenGl_Context)& theCtx)
   {
     myPBREnvironment->Release (theCtx.get());
   }
+  ReleaseXR();
 }
 
 // =======================================================================
@@ -826,6 +830,7 @@ void OpenGl_View::changePriority (const Handle(Graphic3d_CStructure)& theStructu
 void OpenGl_View::DiagnosticInformation (TColStd_IndexedDataMapOfStringString& theDict,
                                          Graphic3d_DiagnosticInfo theFlags) const
 {
+  base_type::DiagnosticInformation (theDict, theFlags);
   Handle(OpenGl_Context) aCtx = myWorkspace->GetGlContext();
   if (!myWorkspace->Activate()
    || aCtx.IsNull())
index db1b635..99b1686 100644 (file)
@@ -523,6 +523,7 @@ protected: //! @name Rendering properties
   Handle(OpenGl_FrameBuffer) myMainSceneFbosOit[2];      //!< Additional buffers for transparent draw of main layer.
   Handle(OpenGl_FrameBuffer) myImmediateSceneFbos[2];    //!< Additional buffers for immediate layer in stereo mode.
   Handle(OpenGl_FrameBuffer) myImmediateSceneFbosOit[2]; //!< Additional buffers for transparency draw of immediate layer.
+  Handle(OpenGl_FrameBuffer) myXrSceneFbo;            //!< additional FBO (without MSAA) for submitting to XR
   OpenGl_VertexBuffer        myFullScreenQuad;        //!< Vertices for full-screen quad rendering.
   OpenGl_VertexBuffer        myFullScreenQuadFlip;
   Standard_Boolean           myToFlipOutput;          //!< Flag to draw result image upside-down
index 499351f..7fd8d7e 100644 (file)
@@ -18,6 +18,7 @@
 
 #include <OpenGl_GlCore11.hxx>
 
+#include <Aspect_XRSession.hxx>
 #include <Graphic3d_GraphicDriver.hxx>
 #include <Graphic3d_StructureManager.hxx>
 #include <Graphic3d_TextureParams.hxx>
@@ -158,7 +159,8 @@ void OpenGl_View::Redraw()
     return;
   }
 
-  myWindow->SetSwapInterval();
+  // implicitly disable VSync when using HMD composer (can be mirrored in window for debugging)
+  myWindow->SetSwapInterval (IsActiveXR());
 
   ++myFrameCounter;
   const Graphic3d_StereoMode   aStereoMode  = myRenderParams.StereoMode;
@@ -186,10 +188,22 @@ void OpenGl_View::Redraw()
   OpenGl_FrameBuffer* aFrameBuffer = myFBO.get();
   bool toSwap = aCtx->IsRender()
             && !aCtx->caps->buffersNoSwap
-            &&  aFrameBuffer == NULL;
+            &&  aFrameBuffer == NULL
+            &&  (!IsActiveXR() || myRenderParams.ToMirrorComposer);
+
+  Standard_Integer aSizeX = myWindow->Width();
+  Standard_Integer aSizeY = myWindow->Height();
+  if (aFrameBuffer != NULL)
+  {
+    aSizeX = aFrameBuffer->GetVPSizeX();
+    aSizeY = aFrameBuffer->GetVPSizeY();
+  }
+  else if (IsActiveXR())
+  {
+    aSizeX = myXRSession->RecommendedViewport().x();
+    aSizeY = myXRSession->RecommendedViewport().y();
+  }
 
-  const Standard_Integer aSizeX = aFrameBuffer != NULL ? aFrameBuffer->GetVPSizeX() : myWindow->Width();
-  const Standard_Integer aSizeY = aFrameBuffer != NULL ? aFrameBuffer->GetVPSizeY() : myWindow->Height();
   const Standard_Integer aRendSizeX = Standard_Integer(myRenderParams.RenderResolutionScale * aSizeX + 0.5f);
   const Standard_Integer aRendSizeY = Standard_Integer(myRenderParams.RenderResolutionScale * aSizeY + 0.5f);
   if (aSizeX < 1
@@ -275,15 +289,34 @@ void OpenGl_View::Redraw()
     myMainSceneFbos     [1]->Release (aCtx.operator->());
     myImmediateSceneFbos[0]->Release (aCtx.operator->());
     myImmediateSceneFbos[1]->Release (aCtx.operator->());
+    myXrSceneFbo           ->Release (aCtx.operator->());
     myMainSceneFbos     [0]->ChangeViewport (0, 0);
     myMainSceneFbos     [1]->ChangeViewport (0, 0);
     myImmediateSceneFbos[0]->ChangeViewport (0, 0);
     myImmediateSceneFbos[1]->ChangeViewport (0, 0);
+    myXrSceneFbo           ->ChangeViewport (0, 0);
   }
 
+  bool hasXRBlitFbo = false;
   if (aProjectType == Graphic3d_Camera::Projection_Stereo
+   && IsActiveXR()
    && myMainSceneFbos[0]->IsValid())
   {
+    if (aNbSamples != 0
+     || aSizeX != aRendSizeX)
+    {
+      hasXRBlitFbo = myXrSceneFbo->InitLazy (aCtx, aSizeX, aSizeY, myFboColorFormat, myFboDepthFormat, 0);
+      if (!hasXRBlitFbo)
+      {
+        TCollection_ExtendedString aMsg = TCollection_ExtendedString() + "Error! VR FBO "
+                                        + printFboFormat (myXrSceneFbo) + " initialization has failed";
+        aCtx->PushMessage (GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_ERROR, 0, GL_DEBUG_SEVERITY_HIGH, aMsg);
+      }
+    }
+  }
+  else if (aProjectType == Graphic3d_Camera::Projection_Stereo
+        && myMainSceneFbos[0]->IsValid())
+  {
     const bool wasFailedMain1 = checkWasFailedFbo (myMainSceneFbos[1], myMainSceneFbos[0]);
     if (!myMainSceneFbos[1]->InitLazy (aCtx, *myMainSceneFbos[0])
      && !wasFailedMain1)
@@ -326,6 +359,11 @@ void OpenGl_View::Redraw()
       }
     }
   }
+  if (!hasXRBlitFbo)
+  {
+    myXrSceneFbo->Release (aCtx.get());
+    myXrSceneFbo->ChangeViewport (0, 0);
+  }
 
   // process PBR environment
   if (myShadingModel == Graphic3d_TOSM_PBR
@@ -500,7 +538,18 @@ void OpenGl_View::Redraw()
         myImmediateSceneFbosOit[0]->IsValid() ? myImmediateSceneFbosOit[0].operator->() : NULL
     };
 
-    if (!myTransientDrawToFront)
+    if (IsActiveXR())
+    {
+      // use single frame for both views - caching main scene content makes no sense
+      // when head position is expected to be updated each frame redraw with high accuracy
+      aMainFbos[1]    = aMainFbos[0];
+      aMainFbosOit[1] = aMainFbosOit[0];
+      anImmFbos[0]    = aMainFbos[0];
+      anImmFbos[1]    = aMainFbos[1];
+      anImmFbosOit[0] = aMainFbosOit[0];
+      anImmFbosOit[1] = aMainFbosOit[1];
+    }
+    else if (!myTransientDrawToFront)
     {
       anImmFbos   [0] = aMainFbos   [0];
       anImmFbos   [1] = aMainFbos   [1];
@@ -539,6 +588,23 @@ void OpenGl_View::Redraw()
       aCtx->SwapBuffers();
     }
 
+    if (IsActiveXR())
+    {
+      // push Left frame to HMD display composer
+      OpenGl_FrameBuffer* anXRFbo = hasXRBlitFbo ? myXrSceneFbo.get() : aMainFbos[0];
+      if (anXRFbo != aMainFbos[0])
+      {
+        blitBuffers (aMainFbos[0], anXRFbo); // resize or resolve MSAA samples
+      }
+    #if !defined(GL_ES_VERSION_2_0)
+      const Aspect_GraphicsLibrary aGraphicsLib = Aspect_GraphicsLibrary_OpenGL;
+    #else
+      const Aspect_GraphicsLibrary aGraphicsLib = Aspect_GraphicsLibrary_OpenGLES;
+    #endif
+      myXRSession->SubmitEye ((void* )(size_t )anXRFbo->ColorTexture()->TextureId(),
+                              aGraphicsLib, Aspect_ColorSpace_sRGB, Aspect_Eye_Left);
+    }
+
   #if !defined(GL_ES_VERSION_2_0)
     aCtx->SetReadDrawBuffer (aStereoMode == Graphic3d_StereoMode_QuadBuffer ? GL_BACK_RIGHT : GL_BACK);
   #endif
@@ -555,7 +621,29 @@ void OpenGl_View::Redraw()
       toSwap = false;
     }
 
-    if (anImmFbos[0] != NULL)
+    if (IsActiveXR())
+    {
+      // push Right frame to HMD display composer
+      OpenGl_FrameBuffer* anXRFbo = hasXRBlitFbo ? myXrSceneFbo.get() : aMainFbos[1];
+      if (anXRFbo != aMainFbos[1])
+      {
+        blitBuffers (aMainFbos[1], anXRFbo); // resize or resolve MSAA samples
+      }
+    #if !defined(GL_ES_VERSION_2_0)
+      const Aspect_GraphicsLibrary aGraphicsLib = Aspect_GraphicsLibrary_OpenGL;
+    #else
+      const Aspect_GraphicsLibrary aGraphicsLib = Aspect_GraphicsLibrary_OpenGLES;
+    #endif
+      myXRSession->SubmitEye ((void* )(size_t )anXRFbo->ColorTexture()->TextureId(),
+                              aGraphicsLib, Aspect_ColorSpace_sRGB, Aspect_Eye_Right);
+      ::glFinish();
+
+      if (myRenderParams.ToMirrorComposer)
+      {
+        blitBuffers (anXRFbo, aFrameBuffer, myToFlipOutput);
+      }
+    }
+    else if (anImmFbos[0] != NULL)
     {
       aCtx->SetResolution (myRenderParams.Resolution, myRenderParams.ResolutionRatio(), 1.0f);
       drawStereoPair (aFrameBuffer);
@@ -657,6 +745,7 @@ void OpenGl_View::RedrawImmediate()
   if (!myWorkspace->Activate())
     return;
 
+  // no special handling of HMD display, since it will force full Redraw() due to no frame caching (myBackBufferRestored)
   Handle(OpenGl_Context) aCtx = myWorkspace->GetGlContext();
   if (!myTransientDrawToFront
    || !myBackBufferRestored
@@ -872,9 +961,11 @@ bool OpenGl_View::redrawImmediate (const Graphic3d_Camera::Projection theProject
   const Handle(OpenGl_Context)& aCtx = myWorkspace->GetGlContext();
   GLboolean toCopyBackToFront = GL_FALSE;
   if (theDrawFbo == theReadFbo
-   && theDrawFbo != NULL)
+   && theDrawFbo != NULL
+   && theDrawFbo->IsValid())
   {
     myBackBufferRestored = Standard_False;
+    theDrawFbo->BindBuffer (aCtx);
   }
   else if (theReadFbo != NULL
         && theReadFbo->IsValid()
@@ -993,10 +1084,7 @@ void OpenGl_View::render (Graphic3d_Camera::Projection theProjection,
   }
 
   myLocalOrigin.SetCoord (0.0, 0.0, 0.0);
-  aContext->ProjectionState.SetCurrent (myCamera->ProjectionMatrixF());
-  aContext->WorldViewState .SetCurrent (myCamera->OrientationMatrixF());
-  aContext->ApplyProjectionMatrix();
-  aContext->ApplyWorldViewMatrix();
+  aContext->SetCamera (myCamera);
   if (aManager->ModelWorldState().Index() == 0)
   {
     aContext->ShaderManager()->UpdateModelWorldStateTo (OpenGl_Mat4());
@@ -1042,7 +1130,7 @@ void OpenGl_View::render (Graphic3d_Camera::Projection theProjection,
 #if !defined(GL_ES_VERSION_2_0)
   // if the view is scaled normal vectors are scaled to unit
   // length for correct displaying of shaded objects
-  const gp_Pnt anAxialScale = myCamera->AxialScale();
+  const gp_Pnt anAxialScale = aContext->Camera()->AxialScale();
   if (anAxialScale.X() != 1.F ||
       anAxialScale.Y() != 1.F ||
       anAxialScale.Z() != 1.F)
@@ -1060,12 +1148,12 @@ void OpenGl_View::render (Graphic3d_Camera::Projection theProjection,
   // Redraw 3d scene
   if (theProjection == Graphic3d_Camera::Projection_MonoLeftEye)
   {
-    aContext->ProjectionState.SetCurrent (myCamera->ProjectionStereoLeftF());
+    aContext->ProjectionState.SetCurrent (aContext->Camera()->ProjectionStereoLeftF());
     aContext->ApplyProjectionMatrix();
   }
   else if (theProjection == Graphic3d_Camera::Projection_MonoRightEye)
   {
-    aContext->ProjectionState.SetCurrent (myCamera->ProjectionStereoRightF());
+    aContext->ProjectionState.SetCurrent (aContext->Camera()->ProjectionStereoRightF());
     aContext->ApplyProjectionMatrix();
   }
 
index d16e754..e94a0ce 100644 (file)
@@ -809,11 +809,12 @@ void OpenGl_Window::Init()
 // function : SetSwapInterval
 // purpose  :
 // =======================================================================
-void OpenGl_Window::SetSwapInterval()
+void OpenGl_Window::SetSwapInterval (Standard_Boolean theToForceNoSync)
 {
-  if (mySwapInterval != myGlContext->caps->swapInterval)
+  const Standard_Integer aSwapInterval = theToForceNoSync ? 0 : myGlContext->caps->swapInterval;
+  if (mySwapInterval != aSwapInterval)
   {
-    mySwapInterval = myGlContext->caps->swapInterval;
+    mySwapInterval = aSwapInterval;
     myGlContext->SetSwapInterval (mySwapInterval);
   }
 }
index 69a49a9..c165ffd 100644 (file)
@@ -73,7 +73,7 @@ public:
   Standard_EXPORT virtual Standard_Boolean Activate();
 
   //! Sets swap interval for this window according to the context's settings.
-  Standard_EXPORT void SetSwapInterval();
+  Standard_EXPORT void SetSwapInterval (Standard_Boolean theToForceNoSync);
 
 protected:
 
index f3a86ef..8b51fd0 100644 (file)
@@ -390,11 +390,12 @@ void OpenGl_Window::Init()
 // function : SetSwapInterval
 // purpose  :
 // =======================================================================
-void OpenGl_Window::SetSwapInterval()
+void OpenGl_Window::SetSwapInterval (Standard_Boolean theToForceNoSync)
 {
-  if (mySwapInterval != myGlContext->caps->swapInterval)
+  const Standard_Integer aSwapInterval = theToForceNoSync ? 0 : myGlContext->caps->swapInterval;
+  if (mySwapInterval != aSwapInterval)
   {
-    mySwapInterval = myGlContext->caps->swapInterval;
+    mySwapInterval = aSwapInterval;
     myGlContext->SetSwapInterval (mySwapInterval);
   }
 }
index 86df123..7652f2c 100644 (file)
@@ -85,15 +85,18 @@ public:
   //! Empties all the tables, removes all selections...
   Standard_EXPORT void Clear();
 
-  //! returns the Sensitivity of picking
-  Standard_Real Sensitivity() const { return myTolerances.Tolerance(); }
-
-  //! Returns the pixel tolerance.
-  Standard_Integer PixelTolerance() const { return myTolerances.Tolerance(); }
+  //! Returns custom pixel tolerance value.
+  Standard_Integer CustomPixelTolerance() const { return myTolerances.CustomTolerance(); }
 
   //! Sets the pixel tolerance <theTolerance>.
   Standard_EXPORT void SetPixelTolerance (const Standard_Integer theTolerance);
 
+  //! Returns the largest sensitivity of picking
+  Standard_Real Sensitivity() const { return myTolerances.Tolerance(); }
+
+  //! Returns the largest pixel tolerance.
+  Standard_Integer PixelTolerance() const { return myTolerances.Tolerance(); }
+
   //! Sorts the detected entites by priority and distance.
   //!          to be redefined if other criterion are used...
   Standard_EXPORT void SortResult();
index 80590f6..5bb0cee 100755 (executable)
@@ -2,6 +2,7 @@ TKernel
 TKMath
 CSF_user32
 CSF_advapi32
+CSF_OpenVR
 CSF_OpenGlLibs
 CSF_advapi32
 CSF_user32
index 4950ea4..cdb7b81 100644 (file)
@@ -233,6 +233,7 @@ void V3d_View::Update() const
   myIsInvalidatedImmediate = Standard_False;
   myView->Update();
   myView->Compute();
+  AutoZFit();
   myView->Redraw();
 }
 
@@ -618,8 +619,6 @@ void V3d_View::SetFront()
 
   aCamera->SetUp (gp_Dir (xu, yu, zu));
 
-  AutoZFit();
-
   SwitchSetFront = !SwitchSetFront;
 
   ImmediateUpdate();
@@ -675,8 +674,6 @@ void V3d_View::Rotate (const Standard_Real ax,
 
   aCamera->Transform (aTrsf);
 
-  AutoZFit();
-
   ImmediateUpdate();
 }
 
@@ -733,8 +730,6 @@ void V3d_View::Rotate(const Standard_Real ax, const Standard_Real ay, const Stan
 
   aCamera->Transform (aTrsf);
 
-  AutoZFit();
-
   ImmediateUpdate();
 }
 
@@ -803,8 +798,6 @@ void V3d_View::Rotate (const V3d_TypeOfAxe theAxe, const Standard_Real theAngle,
 
   aCamera->Transform (aRotation);
 
-  AutoZFit();
-
   ImmediateUpdate();
 }
 
@@ -840,8 +833,6 @@ void V3d_View::Rotate(const Standard_Real angle, const Standard_Boolean Start)
 
   aCamera->Transform (aRotation);
 
-  AutoZFit();
-
   ImmediateUpdate();
 }
 
@@ -892,8 +883,6 @@ void V3d_View::Turn(const Standard_Real ax, const Standard_Real ay, const Standa
 
   aCamera->Transform (aTrsf);
 
-  AutoZFit();
-
   ImmediateUpdate();
 }
 
@@ -948,8 +937,6 @@ void V3d_View::Turn(const Standard_Real angle, const Standard_Boolean Start)
 
   aCamera->Transform (aRotation);
 
-  AutoZFit();
-
   ImmediateUpdate();
 }
 
@@ -983,8 +970,6 @@ void V3d_View::SetTwist(const Standard_Real angle)
   aCamera->SetUp (gp_Dir (myYscreenAxis));
   aCamera->Transform (aTrsf);
 
-  AutoZFit();
-
   ImmediateUpdate();
 }
 
@@ -1004,8 +989,6 @@ void V3d_View::SetEye(const Standard_Real X,const Standard_Real Y,const Standard
 
   SetTwist (aTwistBefore);
 
-  AutoZFit();
-
   SetImmediateUpdate (wasUpdateEnabled);
 
   ImmediateUpdate();
@@ -1036,8 +1019,6 @@ void V3d_View::SetDepth(const Standard_Real Depth)
     aCamera->SetCenter (aCameraCenter);
   }
 
-  AutoZFit();
-
   ImmediateUpdate();
 }
 
@@ -1058,8 +1039,6 @@ void V3d_View::SetProj( const Standard_Real Vx,const Standard_Real Vy, const Sta
 
   SetTwist(aTwistBefore);
 
-  AutoZFit();
-
   SetImmediateUpdate (wasUpdateEnabled);
 
   ImmediateUpdate();
@@ -1108,8 +1087,6 @@ void V3d_View::SetProj (const V3d_TypeOfOrientation theOrientation,
 
   Panning (anOriginVCS.X(), anOriginVCS.Y());
 
-  AutoZFit();
-
   ImmediateUpdate();
 }
 
@@ -1127,8 +1104,6 @@ void V3d_View::SetAt(const Standard_Real X,const Standard_Real Y,const Standard_
 
   SetTwist (aTwistBefore);
 
-  AutoZFit();
-
   SetImmediateUpdate (wasUpdateEnabled);
 
   ImmediateUpdate();
@@ -1154,8 +1129,6 @@ void V3d_View::SetUp (const Standard_Real theVx, const Standard_Real theVy, cons
 
   aCamera->SetUp (gp_Dir (myYscreenAxis));
 
-  AutoZFit();
-
   ImmediateUpdate();
 }
 
@@ -1179,8 +1152,6 @@ void V3d_View::SetUp (const V3d_TypeOfOrientation theOrientation)
 
   aCamera->SetUp (gp_Dir (myYscreenAxis));
 
-  AutoZFit();
-
   ImmediateUpdate();
 }
 
@@ -1209,9 +1180,6 @@ void V3d_View::SetViewMappingDefault()
 void V3d_View::ResetViewOrientation()
 {
   Camera()->CopyOrientationData (myDefaultCamera);
-
-  AutoZFit();
-
   ImmediateUpdate();
 }
 
@@ -1222,9 +1190,6 @@ void V3d_View::ResetViewOrientation()
 void V3d_View::ResetViewMapping()
 {
   Camera()->CopyMappingData (myDefaultCamera);
-
-  AutoZFit();
-
   ImmediateUpdate();
 }
 
@@ -1236,8 +1201,6 @@ void V3d_View::Reset (const Standard_Boolean theToUpdate)
 {
   Camera()->Copy (myDefaultCamera);
 
-  AutoZFit();
-
   SwitchSetFront = Standard_False;
 
   if (myImmediateUpdate || theToUpdate)
@@ -1272,8 +1235,6 @@ void V3d_View::SetSize (const Standard_Real theSize)
 
   aCamera->SetScale (aCamera->Aspect() >= 1.0 ? theSize / aCamera->Aspect() : theSize);
 
-  AutoZFit();
-
   ImmediateUpdate();
 }
 
@@ -1375,8 +1336,6 @@ void V3d_View::SetZoom (const Standard_Real theCoef,const Standard_Boolean theTo
   aCamera->SetCenter (myCamStartOpCenter);
   aCamera->SetScale (aCamera->Scale() / aCoef);
 
-  AutoZFit();
-
   ImmediateUpdate();
 }
 
@@ -1394,8 +1353,6 @@ void V3d_View::SetScale( const Standard_Real Coef )
   aCamera->SetAspect (myDefaultCamera->Aspect());
   aCamera->SetScale (aDefaultScale / Coef);
 
-  AutoZFit();
-
   ImmediateUpdate();
 }
 
@@ -1408,8 +1365,6 @@ void V3d_View::SetAxialScale( const Standard_Real Sx, const Standard_Real Sy, co
   V3d_BadValue_Raise_if( Sx <= 0. || Sy <= 0. || Sz <= 0.,"V3d_View::SetAxialScale, bad coefficient");
 
   Camera()->SetAxialScale (gp_XYZ (Sx, Sy, Sz));
-
-  AutoZFit();
 }
 
 //=============================================================================
@@ -1463,8 +1418,6 @@ void V3d_View::FitAll (const Bnd_Box& theBox, const Standard_Real theMargin, con
     return;
   }
 
-  AutoZFit();
-
   if (myImmediateUpdate || theToUpdate)
   {
     Update();
@@ -1588,7 +1541,6 @@ void V3d_View::WindowFit (const Standard_Integer theMinXp,
 
     Translate (aCamera, aPanVec.X(), -aPanVec.Y());
     Scale (aCamera, aUSize, aVSize);
-    AutoZFit();
   }
   else
   {
@@ -2491,8 +2443,6 @@ void V3d_View::ZoomAtPoint (const Standard_Integer theMouseStartX,
   aCamera->SetScale (aCamera->Scale() / aCoef);
   Translate (aCamera, aZoomAtPointXv - aDxv, aZoomAtPointYv - aDyv);
 
-  AutoZFit();
-
   SetImmediateUpdate (wasUpdateEnabled);
 
   ImmediateUpdate();
@@ -2545,8 +2495,6 @@ void V3d_View::FitAll(const Standard_Real theXmin,
   Translate (aCamera, (theXmin + theXmax) * 0.5, (theYmin + theYmax) * 0.5);
   Scale (aCamera, aFitSizeU, aFitSizeV);
 
-  AutoZFit();
-
   ImmediateUpdate();
 }
 
@@ -2828,7 +2776,6 @@ Standard_Boolean V3d_View::ToPixMap (Image_PixMap&               theImage,
   {
     aCamera->SetAspect (Standard_Real(aTargetSize.x()) / Standard_Real(aTargetSize.y()));
   }
-  AutoZFit();
 
   // render immediate structures into back buffer rather than front
   const Standard_Boolean aPrevImmediateMode = myView->SetImmediateModeDrawToFront (Standard_False);
index 863dfc5..20d297f 100644 (file)
@@ -67,8 +67,6 @@ void V3d_View::Move (const Standard_Real Dx,
     + Dz * gp_Pnt (ZX, ZY, ZZ).XYZ()
     );
 
-  AutoZFit();
-
   ImmediateUpdate();
 }
 
@@ -86,8 +84,6 @@ void V3d_View::Move (const Standard_Real theLength, const Standard_Boolean theSt
   aCamera->SetEye (myCamStartOpEye);
   aCamera->SetEye (aCamera->Eye().XYZ() + theLength * myDefaultViewAxis.XYZ());
 
-  AutoZFit();
-
   ImmediateUpdate();
 }
 
@@ -149,8 +145,6 @@ void V3d_View::Translate (const Standard_Real Dx,
     - Dz * myZscreenAxis.XYZ()
     );
 
-  AutoZFit();
-
   ImmediateUpdate();
 }
 
@@ -205,7 +199,5 @@ void V3d_View::Translate (const Standard_Real theLength, const Standard_Boolean
   gp_Pnt aNewCenter (myCamStartOpCenter.XYZ() - myDefaultViewAxis.XYZ() * theLength);
   aCamera->SetCenter (aNewCenter);
 
-  AutoZFit();
-
   ImmediateUpdate();
 }
index b96b0c9..0da4745 100644 (file)
@@ -65,6 +65,7 @@
 #include <StdSelect_ViewerSelector3d.hxx>
 #include <TopTools_MapOfShape.hxx>
 #include <ViewerTest_AutoUpdater.hxx>
+#include <Aspect_XRSession.hxx>
 
 #include <stdio.h>
 
@@ -1155,16 +1156,23 @@ static Standard_Integer VDump (Draw_Interpretor& theDI,
                                Standard_Integer  theArgNb,
                                Standard_CString* theArgVec)
 {
+  Handle(V3d_View) aView = ViewerTest::CurrentView();
   if (theArgNb < 2)
   {
     Message::SendFail ("Error: wrong number of arguments! Image file name should be specified at least.");
     return 1;
   }
+  if (aView.IsNull())
+  {
+    Message::SendFail() << "Error: cannot find an active view!";
+    return 1;
+  }
 
   Standard_Integer      anArgIter   = 1;
   Standard_CString      aFilePath   = theArgVec[anArgIter++];
   ViewerTest_StereoPair aStereoPair = ViewerTest_SP_Single;
   V3d_ImageDumpOptions  aParams;
+  Handle(Graphic3d_Camera) aCustomCam;
   aParams.BufferType    = Graphic3d_BT_RGB;
   aParams.StereoOptions = V3d_SDO_MONO;
   for (; anArgIter < theArgNb; ++anArgIter)
@@ -1203,6 +1211,41 @@ static Standard_Integer VDump (Draw_Interpretor& theDI,
         return 1;
       }
     }
+    else if (anArgIter + 1 < theArgNb
+          && anArg == "-xrpose")
+    {
+      TCollection_AsciiString anXRArg (theArgVec[++anArgIter]);
+      anXRArg.LowerCase();
+      if (anXRArg == "base")
+      {
+        aCustomCam = aView->View()->BaseXRCamera();
+      }
+      else if (anXRArg == "head")
+      {
+        aCustomCam = aView->View()->PosedXRCamera();
+      }
+      else if (anXRArg == "handleft"
+            || anXRArg == "handright")
+      {
+        if (aView->View()->IsActiveXR())
+        {
+          aCustomCam = new Graphic3d_Camera();
+          aView->View()->ComputeXRPosedCameraFromBase (*aCustomCam, anXRArg == "handleft"
+                                                     ? aView->View()->XRSession()->LeftHandPose()
+                                                     : aView->View()->XRSession()->RightHandPose());
+        }
+      }
+      else
+      {
+        Message::SendFail() << "Syntax error: unknown XR pose '" << anXRArg << "'";
+        return 1;
+      }
+      if (aCustomCam.IsNull())
+      {
+        Message::SendFail() << "Error: undefined XR pose";
+        return 0;
+      }
+    }
     else if (anArg == "-stereo")
     {
       if (++anArgIter >= theArgNb)
@@ -1324,13 +1367,6 @@ static Standard_Integer VDump (Draw_Interpretor& theDI,
     return 1;
   }
 
-  Handle(V3d_View) aView = ViewerTest::CurrentView();
-  if (aView.IsNull())
-  {
-    Message::SendFail() << "Error: cannot find an active view!";
-    return 1;
-  }
-
   if (aParams.Width <= 0 || aParams.Height <= 0)
   {
     aView->Window()->Size (aParams.Width, aParams.Height);
@@ -1347,6 +1383,12 @@ static Standard_Integer VDump (Draw_Interpretor& theDI,
     case Graphic3d_BT_Red:                 aFormat = Image_Format_Gray;  break;
   }
 
+  const bool wasImmUpdate = aView->SetImmediateUpdate (false);
+  Handle(Graphic3d_Camera) aCamBack = aView->Camera();
+  if (!aCustomCam.IsNull())
+  {
+    aView->SetCamera (aCustomCam);
+  }
   switch (aStereoPair)
   {
     case ViewerTest_SP_Single:
@@ -1415,6 +1457,11 @@ static Standard_Integer VDump (Draw_Interpretor& theDI,
       break;
     }
   }
+  if (!aCustomCam.IsNull())
+  {
+    aView->SetCamera (aCamBack);
+  }
+  aView->SetImmediateUpdate (wasImmUpdate);
 
   if (!aPixMap.Save (aFilePath))
   {
@@ -6691,6 +6738,7 @@ void ViewerTest::Commands(Draw_Interpretor& theCommands)
               "vdump <filename>." DUMP_FORMATS " [-width Width -height Height]"
       "\n\t\t:       [-buffer rgb|rgba|depth=rgb]"
       "\n\t\t:       [-stereo mono|left|right|blend|sideBySide|overUnder=mono]"
+      "\n\t\t:       [-xrPose base|head|handLeft|handRight=base]"
       "\n\t\t:       [-tileSize Size=0]"
       "\n\t\t: Dumps content of the active view into image file",
                  __FILE__,VDump,group);
index a27a153..dcc828f 100644 (file)
@@ -9982,9 +9982,7 @@ static int VAutoZFit (Draw_Interpretor& theDi, Standard_Integer theArgsNb, const
   }
 
   aCurrentView->SetAutoZFitMode (isOn, aScale);
-  aCurrentView->AutoZFit();
   aCurrentView->Redraw();
-
   return 0;
 }
 
@@ -10022,6 +10020,8 @@ static int VCamera (Draw_Interpretor& theDI,
   {
     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";
@@ -10177,18 +10177,83 @@ static int VCamera (Draw_Interpretor& theDI,
         case Graphic3d_Camera::FocusType_Relative: theDI << "relative "; break;
       }
     }
+    else if (anArgCase == "-lockzup"
+          || anArgCase == "-turntable")
+    {
+      bool toLockUp = true;
+      if (++anArgIter < theArgsNb
+      && !ViewerTest::ParseOnOff (theArgVec[anArgIter], toLockUp))
+      {
+        --anArgIter;
+      }
+      ViewerTest::CurrentEventManager()->SetLockOrbitZUp (toLockUp);
+    }
     else if (anArgCase == "-fov"
-          || anArgCase == "-fovy")
+          || anArgCase == "-fovy"
+          || anArgCase == "-fovx"
+          || anArgCase == "-fov2d")
     {
       Standard_CString anArgValue = (anArgIter + 1 < theArgsNb) ? theArgVec[anArgIter + 1] : NULL;
       if (anArgValue != NULL
       && *anArgValue != '-')
       {
         ++anArgIter;
-        aCamera->SetFOVy (Draw::Atof (anArgValue));
+        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;
       }
-      theDI << aCamera->FOVy() << " ";
+      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 ("-"))
@@ -10205,7 +10270,6 @@ static int VCamera (Draw_Interpretor& theDI,
   if (aPrsName.IsEmpty()
    || theArgsNb > 2)
   {
-    aView->AutoZFit();
     aView->Redraw();
   }
 
@@ -10233,7 +10297,7 @@ static int VCamera (Draw_Interpretor& theDI,
       ViewerTest::GetAISContext()->Erase (aCameraFrustum, false);
       aView->ZFitAll();
     }
-    aCameraFrustum->SetCameraFrustum (aView->Camera());
+    aCameraFrustum->SetCameraFrustum (aCamera);
 
     ViewerTest::Display (aPrsName, aCameraFrustum);
   }
@@ -10286,6 +10350,11 @@ inline Standard_Boolean parseStereoMode (Standard_CString      theArg,
   {
     theMode = Graphic3d_StereoMode_SoftPageFlip;
   }
+  else if (aFlag == "openvr"
+        || aFlag == "vr")
+  {
+    theMode = Graphic3d_StereoMode_OpenVR;
+  }
   else
   {
     return Standard_False;
@@ -10361,6 +10430,7 @@ static int VStereo (Draw_Interpretor& theDI,
         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)
@@ -10430,7 +10500,10 @@ static int VStereo (Draw_Interpretor& theDI,
         aCamera->SetProjectionType (Graphic3d_Camera::Projection_Stereo);
       }
       ViewerTest_myDefaultCaps.contextStereo = Standard_True;
-      return 0;
+      if (aParams->StereoMode != Graphic3d_StereoMode_OpenVR)
+      {
+        return 0;
+      }
     }
     else if (aFlag == "-reverse"
           || aFlag == "-reversed"
@@ -10491,6 +10564,34 @@ static int VStereo (Draw_Interpretor& theDI,
         ViewerTest_myDefaultCaps.contextStereo = Standard_True;
       }
     }
+    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")
+    {
+      Standard_Boolean toEnable = Standard_True;
+      if (++anArgIter < theArgNb
+      && !ViewerTest::ParseOnOff (theArgVec[anArgIter], toEnable))
+      {
+        --anArgIter;
+      }
+      aParams->ToMirrorComposer = toEnable;
+    }
+    else if (anArgIter + 1 < theArgNb
+          && (aFlag == "-unitfactor"
+           || aFlag == "-unitscale"))
+    {
+      aView->View()->SetUnitFactor (Draw::Atof (theArgVec[++anArgIter]));
+    }
     else
     {
       Message::SendFail() << "Syntax error at '" << anArg << "'";
@@ -10502,6 +10603,11 @@ static int VStereo (Draw_Interpretor& theDI,
   {
     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;
 }
@@ -13372,6 +13478,7 @@ static int VDumpSelectionImage (Draw_Interpretor& /*theDi*/,
   }
 
   const Handle(AIS_InteractiveContext)& aContext = ViewerTest::GetAISContext();
+  const Handle(V3d_View)& aView = ViewerTest::CurrentView();
   if (aContext.IsNull())
   {
     Message::SendFail ("Error: no active viewer");
@@ -13380,6 +13487,7 @@ static int VDumpSelectionImage (Draw_Interpretor& /*theDi*/,
 
   TCollection_AsciiString aFile;
   StdSelect_TypeOfSelectionImage aType = StdSelect_TypeOfSelectionImage_NormalizedDepth;
+  Handle(Graphic3d_Camera) aCustomCam;
   Image_Format anImgFormat = Image_Format_BGR;
   Standard_Integer aPickedIndex = 1;
   for (Standard_Integer anArgIter = 1; anArgIter < theArgsNb; ++anArgIter)
@@ -13453,6 +13561,30 @@ static int VDumpSelectionImage (Draw_Interpretor& /*theDi*/,
 
       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];
@@ -13469,7 +13601,6 @@ static int VDumpSelectionImage (Draw_Interpretor& /*theDi*/,
     return 1;
   }
 
-  const Handle(V3d_View)& aView = ViewerTest::CurrentView();
   Standard_Integer aWidth = 0, aHeight = 0;
   aView->Window()->Size (aWidth, aHeight);
 
@@ -13479,11 +13610,24 @@ static int VDumpSelectionImage (Draw_Interpretor& /*theDi*/,
     Message::SendFail ("Error: can't allocate image");
     return 1;
   }
+
+  const bool wasImmUpdate = aView->SetImmediateUpdate (false);
+  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");
@@ -14208,8 +14352,12 @@ void ViewerTest::ViewerCommands(Draw_Interpretor& theCommands)
     __FILE__, VVbo, group);
   theCommands.Add ("vstereo",
             "vstereo [0|1] [-mode Mode] [-reverse {0|1}]"
+    "\n\t\t:         [-mirrorComposer] [-hmdfov2d AngleDegrees] [-unitFactor MetersFactor]"
     "\n\t\t:         [-anaglyph Filter]"
-    "\n\t\t: Control stereo output mode. Available modes for -mode:"
+    "\n\t\t: Control stereo output mode."
+    "\n\t\t: When -mirrorComposer is specified, VR rendered frame will be mirrored in window (debug)."
+    "\n\t\t: Parameter -unitFactor specifies meters scale factor for mapping VR input."
+    "\n\t\t: Available modes for -mode:"
     "\n\t\t:  quadBuffer        - OpenGL QuadBuffer stereo,"
     "\n\t\t:                     requires driver support."
     "\n\t\t:                     Should be called BEFORE vinit!"
@@ -14219,6 +14367,7 @@ void ViewerTest::ViewerCommands(Draw_Interpretor& theCommands)
     "\n\t\t:  chessBoard       - chess-board output"
     "\n\t\t:  sideBySide       - horizontal pair"
     "\n\t\t:  overUnder        - vertical   pair"
+    "\n\t\t:  openVR           - OpenVR (HMD)"
     "\n\t\t: Available Anaglyph filters for -anaglyph:"
     "\n\t\t:  redCyan, redCyanSimple, yellowBlue, yellowBlueSimple,"
     "\n\t\t:  greenMagentaSimple",
@@ -14387,6 +14536,8 @@ void ViewerTest::ViewerCommands(Draw_Interpretor& theCommands)
       "\n\t\t:         [-stereo] [-leftEye] [-rightEye]"
       "\n\t\t:         [-iod [Distance]] [-iodType    [absolute|relative]]"
       "\n\t\t:         [-zfocus [Value]] [-zfocusType [absolute|relative]]"
+      "\n\t\t:         [-fov2d  [Angle]] [-lockZup {0|1}]"
+      "\n\t\t:         [-xrPose base|head=base]"
       "\n\t\t: Manages camera parameters."
       "\n\t\t: Displays frustum when presntation name PrsName is specified."
       "\n\t\t: Prints current value when option called without argument."
@@ -14395,7 +14546,9 @@ void ViewerTest::ViewerCommands(Draw_Interpretor& theCommands)
       "\n\t\t: Perspective camera:"
       "\n\t\t:   -persp      activate perspective  projection (mono)"
       "\n\t\t:   -fovy       field of view in y axis, in degrees"
+      "\n\t\t:   -fov2d      field of view limit for 2d on-screen elements"
       "\n\t\t:   -distance   distance of eye from camera center"
+      "\n\t\t:   -lockZup    lock Z up (tunrtable mode)"
       "\n\t\t: Stereoscopic camera:"
       "\n\t\t:   -stereo     perspective  projection (stereo)"
       "\n\t\t:   -leftEye    perspective  projection (left  eye)"
@@ -14658,6 +14811,7 @@ void ViewerTest::ViewerCommands(Draw_Interpretor& theCommands)
 
   theCommands.Add ("vseldump",
                    "vseldump file -type {depth|unnormDepth|object|owner|selMode|entity}=depth -pickedIndex Index=1"
+                   "\n\t\t:       [-xrPose base|head=base]"
                    "\n\t\t: Generate an image based on detection results:"
                    "\n\t\t:   depth       normalized depth values"
                    "\n\t\t:   unnormDepth unnormalized depth values"
diff --git a/src/XRResources/FILES b/src/XRResources/FILES
new file mode 100644 (file)
index 0000000..43ea04e
--- /dev/null
@@ -0,0 +1,9 @@
+srcinc:::occtvr_actions.json
+srcinc:::occtvr_bindings_generic.json
+srcinc:::occtvr_bindings_holographic_hmd.json
+srcinc:::occtvr_bindings_index_hmd.json
+srcinc:::occtvr_bindings_rift.json
+srcinc:::occtvr_bindings_vive.json
+srcinc:::occtvr_bindings_vive_controller.json
+srcinc:::occtvr_bindings_vive_cosmos.json
+srcinc:::occtvr_bindings_vive_pro.json
diff --git a/src/XRResources/occtvr_actions.json b/src/XRResources/occtvr_actions.json
new file mode 100644 (file)
index 0000000..7bb5f3f
--- /dev/null
@@ -0,0 +1,225 @@
+{
+  "actions": [
+    {
+      "name": "/actions/generic_head/in/headset_on_head",
+      "type": "boolean",
+      "requirement": "optional"
+    },
+    {
+      "name": "/actions/generic_left/in/pose_base",
+      "type": "pose"
+    },
+    {
+      "name": "/actions/generic_right/in/pose_base",
+      "type": "pose"
+    },
+    {
+      "name": "/actions/generic_left/in/pose_front",
+      "type": "pose"
+    },
+    {
+      "name": "/actions/generic_right/in/pose_front",
+      "type": "pose"
+    },
+    {
+      "name": "/actions/generic_left/in/pose_handgrip",
+      "type": "pose"
+    },
+    {
+      "name": "/actions/generic_right/in/pose_handgrip",
+      "type": "pose"
+    },
+    {
+      "name": "/actions/generic_left/in/pose_tip",
+      "type": "pose"
+    },
+    {
+      "name": "/actions/generic_right/in/pose_tip",
+      "type": "pose"
+    },
+    {
+      "name": "/actions/generic_left/out/haptic",
+      "type": "vibration"
+    },
+    {
+      "name": "/actions/generic_right/out/haptic",
+      "type": "vibration"
+    },
+    {
+      "name": "/actions/generic_left/in/appmenu_click",
+      "type": "boolean"
+    },
+    {
+      "name": "/actions/generic_right/in/appmenu_click",
+      "type": "boolean"
+    },
+    {
+      "name": "/actions/generic_left/in/sysmenu_click",
+      "type": "boolean",
+      "requirement": "optional"
+    },
+    {
+      "name": "/actions/generic_right/in/sysmenu_click",
+      "type": "boolean",
+      "requirement": "optional"
+    },
+    {
+      "name": "/actions/generic_left/in/trigger_click",
+      "type": "boolean"
+    },
+    {
+      "name": "/actions/generic_right/in/trigger_click",
+      "type": "boolean"
+    },
+    {
+      "name": "/actions/generic_left/in/trigger_pull",
+      "type": "vector1"
+    },
+    {
+      "name": "/actions/generic_right/in/trigger_pull",
+      "type": "vector1"
+    },
+    {
+      "name": "/actions/generic_left/in/grip_click",
+      "type": "boolean"
+    },
+    {
+      "name": "/actions/generic_right/in/grip_click",
+      "type": "boolean"
+    },
+    {
+      "name": "/actions/generic_left/in/trackpad_position",
+      "type": "vector2"
+    },
+    {
+      "name": "/actions/generic_right/in/trackpad_position",
+      "type": "vector2"
+    },
+    {
+      "name": "/actions/generic_left/in/trackpad_touch",
+      "type": "boolean"
+    },
+    {
+      "name": "/actions/generic_right/in/trackpad_touch",
+      "type": "boolean"
+    },
+    {
+      "name": "/actions/generic_left/in/trackpad_click",
+      "type": "boolean"
+    },
+    {
+      "name": "/actions/generic_right/in/trackpad_click",
+      "type": "boolean"
+    },
+       {
+      "name": "/actions/generic_left/in/thumbstick_position",
+      "type": "vector2"
+    },
+    {
+      "name": "/actions/generic_right/in/thumbstick_position",
+      "type": "vector2"
+    },
+    {
+      "name": "/actions/generic_left/in/thumbstick_touch",
+      "type": "boolean"
+    },
+    {
+      "name": "/actions/generic_right/in/thumbstick_touch",
+      "type": "boolean"
+    },
+    {
+      "name": "/actions/generic_left/in/thumbstick_click",
+      "type": "boolean"
+    },
+    {
+      "name": "/actions/generic_right/in/thumbstick_click",
+      "type": "boolean"
+    }
+  ],
+  "action_sets": [
+    {
+      "name": "/actions/generic_head",
+      "usage": "single"
+    },
+    {
+      "name": "/actions/generic_left",
+      "usage": "leftright"
+    },
+    {
+      "name": "/actions/generic_right",
+      "usage": "leftright"
+    }
+  ],
+  "default_bindings": [
+    {
+      "controller_type": "vive_controller",
+      "binding_url": "occtvr_bindings_vive_controller.json"
+    },
+    {
+      "controller_type": "generic",
+      "binding_url": "occtvr_bindings_generic.json"
+    },
+    {
+      "controller_type": "holographic_controller",
+      "binding_url": "occtvr_bindings_holographic_hmd.json"
+    },
+    {
+      "controller_type": "indexhmd",
+      "binding_url": "occtvr_bindings_index_hmd.json"
+    },
+    {
+      "controller_type": "rift",
+      "binding_url": "occtvr_bindings_rift.json"
+    },
+    {
+      "controller_type": "vive",
+      "binding_url": "occtvr_bindings_vive.json"
+    },
+    {
+      "controller_type": "vive_cosmos",
+      "binding_url": "occtvr_bindings_vive_cosmos.json"
+    },
+    {
+      "controller_type": "vive_pro",
+      "binding_url": "occtvr_bindings_vive_pro.json"
+    }
+  ],
+  "localization": [
+    {
+      "language_tag": "en_US",
+      "/actions/generic_head/in/headset_on_head":      "Headset is on head",
+      "/actions/generic_left/in/appmenu_click":        "Left app menu",
+      "/actions/generic_left/in/sysmenu_click":        "Left system menu",
+      "/actions/generic_left/in/trigger_click":        "Left trigger click",
+      "/actions/generic_left/in/trigger_pull":         "Left trigger squeeze",
+      "/actions/generic_left/in/grip_click":           "Left hand full grip",
+      "/actions/generic_left/in/pose_base":            "Left hand base",
+      "/actions/generic_left/in/pose_front":           "Left hand front",
+      "/actions/generic_left/in/pose_handgrip":        "Left handgrip pose",
+      "/actions/generic_left/in/pose_tip":             "Left forefinger tip",
+      "/actions/generic_left/in/trackpad_position":    "Left trackpad position",
+      "/actions/generic_left/in/trackpad_touch":       "Left trackpad touch",
+      "/actions/generic_left/in/trackpad_click":       "Left trackpad click",
+      "/actions/generic_left/in/thumbstick_position":  "Left thumbstick position",
+      "/actions/generic_left/in/thumbstick_touch":     "Left thumbstick touch",
+      "/actions/generic_left/in/thumbstick_click":     "Left thumbstick click",
+      "/actions/generic_left/out/haptic":              "Left hand haptic",
+      "/actions/generic_right/in/appmenu_click":       "Right app menu",
+      "/actions/generic_right/in/sysmenu_click":       "Right system menu",
+      "/actions/generic_right/in/trigger_click":       "Right trigger click",
+      "/actions/generic_right/in/trigger_pull":        "Right trigger squeeze",
+      "/actions/generic_right/in/grip_click":          "Right hand full grip",
+      "/actions/generic_right/in/pose_base":           "Right hand base",
+      "/actions/generic_right/in/pose_front":          "Right hand front",
+      "/actions/generic_right/in/pose_handgrip":       "Right handgrip pose",
+      "/actions/generic_right/in/pose_tip":            "Right forefinger tip",
+      "/actions/generic_right/in/trackpad_position":   "Right trackpad position",
+      "/actions/generic_right/in/trackpad_touch":      "Right trackpad touch",
+      "/actions/generic_right/in/trackpad_click":      "Right trackpad click",
+      "/actions/generic_right/in/thumbstick_position": "Right thumbstick position",
+      "/actions/generic_right/in/thumbstick_touch":    "Right thumbstick touch",
+      "/actions/generic_right/in/thumbstick_click":    "Right thumbstick click",
+      "/actions/generic_right/out/haptic":             "Right hand haptic"
+    }
+  ]
+}
diff --git a/src/XRResources/occtvr_bindings_generic.json b/src/XRResources/occtvr_bindings_generic.json
new file mode 100644 (file)
index 0000000..dc9ff72
--- /dev/null
@@ -0,0 +1,87 @@
+{
+  "bindings": {
+    "/actions/generic_left": {
+      "haptics": [
+        {
+          "output": "/actions/generic_left/out/haptic",
+          "path": "/user/hand/left/output/haptic"
+        }
+      ],
+      "sources": [
+        {
+          "inputs": {
+            "click": { "output": "/actions/generic_left/in/appmenu_click" }
+          },
+          "mode": "button",
+          "path": "/user/hand/left/input/application_menu"
+        },
+        {
+          "inputs": {
+            "click": { "output": "/actions/generic_left/in/trigger_click" }
+          },
+          "mode": "button",
+          "path": "/user/hand/left/input/trigger"
+        },
+        {
+          "inputs": {
+            "click":    { "output": "/actions/generic_left/in/trackpad_click" },
+            "touch":    { "output": "/actions/generic_left/in/trackpad_touch" },
+            "position": { "output": "/actions/generic_left/in/trackpad_position" }
+          },
+          "mode": "trackpad",
+          "path": "/user/hand/left/input/trackpad"
+        },
+        {
+          "inputs": {
+            "click": { "output": "/actions/generic_left/in/grip_click" }
+          },
+          "mode": "button",
+          "path": "/user/hand/left/input/grip"
+        }
+      ]
+    },
+    "/actions/generic_right": {
+      "haptics": [
+        {
+          "output": "/actions/generic_right/out/haptic",
+          "path": "/user/hand/right/output/haptic"
+        }
+      ],
+      "sources": [
+        {
+          "inputs": {
+            "click": { "output": "/actions/generic_right/in/appmenu_click" }
+          },
+          "mode": "button",
+          "path": "/user/hand/right/input/application_menu"
+        },
+        {
+          "inputs": {
+            "click": { "output": "/actions/generic_right/in/trigger_click" }
+          },
+          "mode": "button",
+          "path": "/user/hand/right/input/trigger"
+        },
+        {
+          "inputs": {
+            "click":    { "output": "/actions/generic_right/in/trackpad_click" },
+            "touch":    { "output": "/actions/generic_right/in/trackpad_touch" },
+            "position": { "output": "/actions/generic_right/in/trackpad_position" }
+          },
+          "mode": "trackpad",
+          "path": "/user/hand/right/input/trackpad"
+        },
+        {
+          "inputs": {
+            "click": { "output": "/actions/generic_right/in/grip_click" }
+          },
+          "mode": "button",
+          "path": "/user/hand/right/input/grip"
+        }
+      ]
+    }
+  },
+  "controller_type": "generic",
+  "description": "Standard Open CASCADE Technology VR bindings for a generic controller",
+  "name": "OCCT VR bindings for a generic controller"
+}
diff --git a/src/XRResources/occtvr_bindings_holographic_hmd.json b/src/XRResources/occtvr_bindings_holographic_hmd.json
new file mode 100644 (file)
index 0000000..7000551
--- /dev/null
@@ -0,0 +1,18 @@
+{
+  "bindings": {
+    "/actions/generic_head": {
+      "sources": [
+        {
+          "inputs": {
+            "click": { "output": "/actions/generic_head/in/headset_on_head" }
+          },
+          "mode": "button",
+          "path": "/user/head/proximity"
+        }
+      ]
+    }
+  },
+  "controller_type": "holographic_hmd",
+  "description": "",
+  "name": "holographic_hmd defaults"
+}
diff --git a/src/XRResources/occtvr_bindings_index_hmd.json b/src/XRResources/occtvr_bindings_index_hmd.json
new file mode 100644 (file)
index 0000000..0fb413c
--- /dev/null
@@ -0,0 +1,18 @@
+{
+  "bindings": {
+    "/actions/generic_head": {
+      "sources": [
+        {
+          "inputs": {
+            "click": { "output": "/actions/generic_head/in/headset_on_head" }
+          },
+          "mode": "button",
+          "path": "/user/head/proximity"
+        }
+      ]
+    }
+  },
+  "controller_type": "indexhmd",
+  "description": "",
+  "name": "index hmd defaults"
+}
diff --git a/src/XRResources/occtvr_bindings_rift.json b/src/XRResources/occtvr_bindings_rift.json
new file mode 100644 (file)
index 0000000..3f65a84
--- /dev/null
@@ -0,0 +1,18 @@
+{
+  "bindings": {
+    "/actions/generic_head": {
+      "sources": [
+        {
+          "inputs": {
+            "click": { "output": "/actions/generic_head/in/headset_on_head" }
+          },
+          "mode": "button",
+          "path": "/user/head/proximity"
+        }
+      ]
+    }
+  },
+  "controller_type": "rift",
+  "description": "",
+  "name": "rift defaults"
+}
diff --git a/src/XRResources/occtvr_bindings_vive.json b/src/XRResources/occtvr_bindings_vive.json
new file mode 100644 (file)
index 0000000..5669044
--- /dev/null
@@ -0,0 +1,18 @@
+{
+  "bindings": {
+    "/actions/generic_head": {
+      "sources": [
+        {
+          "inputs": {
+            "click": { "output": "/actions/generic_head/in/headset_on_head" }
+          },
+          "mode": "button",
+          "path": "/user/head/proximity"
+        }
+      ]
+    }
+  },
+  "controller_type": "vive",
+  "description": "",
+  "name": "vive defaults"
+}
diff --git a/src/XRResources/occtvr_bindings_vive_controller.json b/src/XRResources/occtvr_bindings_vive_controller.json
new file mode 100644 (file)
index 0000000..a103998
--- /dev/null
@@ -0,0 +1,139 @@
+{
+  "bindings": {
+    "/actions/generic_left": {
+      "haptics": [
+        {
+          "output": "/actions/generic_left/out/haptic",
+          "path": "/user/hand/left/output/haptic"
+        }
+      ],
+      "poses": [
+        {
+          "output": "/actions/generic_left/in/pose_base",
+          "path": "/user/hand/left/pose/base"
+        },
+        {
+          "output": "/actions/generic_left/in/pose_front",
+          "path": "/user/hand/left/pose/front"
+        },
+        {
+          "output": "/actions/generic_left/in/pose_handgrip",
+          "path": "/user/hand/left/pose/handgrip"
+        },
+        {
+          "output": "/actions/generic_left/in/pose_tip",
+          "path": "/user/hand/left/pose/tip"
+        }
+      ],
+      "sources": [
+        {
+          "inputs": {
+            "click": { "output": "/actions/generic_left/in/appmenu_click" }
+          },
+          "mode": "button",
+          "path": "/user/hand/left/input/application_menu"
+        },
+        {
+          "inputs": {
+            "click": { "output": "/actions/generic_left/in/trigger_click" },
+            "pull":  { "output": "/actions/generic_left/in/trigger_pull" }
+          },
+          "mode": "trigger",
+          "path": "/user/hand/left/input/trigger"
+        },
+        {
+          "inputs": {
+            "click":    { "output": "/actions/generic_left/in/trackpad_click" },
+            "touch":    { "output": "/actions/generic_left/in/trackpad_touch" },
+            "position": { "output": "/actions/generic_left/in/trackpad_position" }
+          },
+          "mode": "trackpad",
+          "path": "/user/hand/left/input/trackpad"
+        },
+        {
+          "inputs": {
+            "click": { "output": "/actions/generic_left/in/grip_click" }
+          },
+          "mode": "button",
+          "path": "/user/hand/left/input/grip"
+        },
+        {
+          "inputs": {
+            "click": { "output": "/actions/generic_left/in/sysmenu_click" }
+          },
+          "mode": "button",
+          "path": "/user/hand/left/input/system"
+        }
+      ]
+    },
+    "/actions/generic_right": {
+      "haptics": [
+        {
+          "output": "/actions/generic_right/out/haptic",
+          "path": "/user/hand/right/output/haptic"
+        }
+      ],
+      "poses": [
+        {
+          "output": "/actions/generic_right/in/pose_base",
+          "path": "/user/hand/right/pose/base"
+        },
+        {
+          "output": "/actions/generic_right/in/pose_front",
+          "path": "/user/hand/right/pose/front"
+        },
+        {
+          "output": "/actions/generic_right/in/pose_handgrip",
+          "path": "/user/hand/right/pose/handgrip"
+        },
+        {
+          "output": "/actions/generic_right/in/pose_tip",
+          "path": "/user/hand/right/pose/tip"
+        }
+      ],
+      "sources": [
+        {
+          "inputs": {
+            "click": { "output": "/actions/generic_right/in/appmenu_click" }
+          },
+          "mode": "button",
+          "path": "/user/hand/right/input/application_menu"
+        },
+        {
+          "inputs": {
+            "click": { "output": "/actions/generic_right/in/trigger_click" },
+            "pull":  { "output": "/actions/generic_right/in/trigger_pull" }
+          },
+          "mode": "trigger",
+          "path": "/user/hand/right/input/trigger"
+        },
+        {
+          "inputs": {
+            "click":    { "output": "/actions/generic_right/in/trackpad_click" },
+            "touch":    { "output": "/actions/generic_right/in/trackpad_touch" },
+            "position": { "output": "/actions/generic_right/in/trackpad_position" }
+          },
+          "mode": "trackpad",
+          "path": "/user/hand/right/input/trackpad"
+        },
+        {
+          "inputs": {
+            "click": { "output": "/actions/generic_right/in/grip_click" }
+          },
+          "mode": "button",
+          "path": "/user/hand/right/input/grip"
+        },
+        {
+          "inputs": {
+            "click": { "output": "/actions/generic_right/in/sysmenu_click" }
+          },
+          "mode": "button",
+          "path": "/user/hand/right/input/system"
+        }
+      ]
+    }
+  },
+  "controller_type": "vive_controller",
+  "description": "Standard Open CASCADE Technology VR bindings for the Vive controller",
+  "name": "OCCT VR bindings for the Vive controller"
+}
diff --git a/src/XRResources/occtvr_bindings_vive_cosmos.json b/src/XRResources/occtvr_bindings_vive_cosmos.json
new file mode 100644 (file)
index 0000000..4bcd0db
--- /dev/null
@@ -0,0 +1,18 @@
+{
+  "bindings": {
+    "/actions/generic_head": {
+      "sources": [
+        {
+          "inputs": {
+            "click": { "output": "/actions/generic_head/in/headset_on_head" }
+          },
+          "mode": "button",
+          "path": "/user/head/proximity"
+        }
+      ]
+    }
+  },
+  "controller_type": "vive_cosmos",
+  "description": "",
+  "name": "vive cosmos hmd defaults",
+}
diff --git a/src/XRResources/occtvr_bindings_vive_pro.json b/src/XRResources/occtvr_bindings_vive_pro.json
new file mode 100644 (file)
index 0000000..d6d52f3
--- /dev/null
@@ -0,0 +1,18 @@
+{
+  "bindings": {
+    "/actions/generic_head": {
+      "sources": [
+        {
+          "inputs": {
+            "click": { "output": "/actions/generic_head/in/headset_on_head" }
+          },
+          "mode": "button",
+          "path": "/user/head/proximity"
+        }
+      ]
+    }
+  },
+  "controller_type": "vive_pro",
+  "description": "",
+  "name": "vive_pro defaults"
+}