]> OCCT Git - occt-copy.git/commitdiff
0031373: Samples - provide sample combining OCCT and 3 viewer CR31373_1
authorkgv <kgv@opencascade.com>
Wed, 20 Jan 2021 11:30:04 +0000 (14:30 +0300)
committerkgv <kgv@opencascade.com>
Sun, 7 Feb 2021 08:40:14 +0000 (11:40 +0300)
gltf reader

samples/webgl/CMakeLists.txt
samples/webgl/OccViewerModule.idl [new file with mode: 0644]
samples/webgl/WasmOcctObject.cpp [new file with mode: 0644]
samples/webgl/WasmOcctObject.h [new file with mode: 0644]
samples/webgl/WasmOcctView.cpp
samples/webgl/WasmOcctView.h
samples/webgl/main.cpp
samples/webgl/occt-webgl-sample.html
samples/webgl/occt-webgl-viewer.js [deleted file]
samples/webgl/threejs-sample.html [new file with mode: 0644]
samples/webgl/threejs-sample.js [new file with mode: 0644]

index a3d7ef3052b5c20a1b566233cbeadd23bfbec9d0..157f482bc16035b12fb8cc463856a86669bc2a92 100644 (file)
@@ -19,7 +19,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s ALLOW_MEMORY_GROWTH=1")
 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --bind")
 #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s SAFE_HEAP=1")
 #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s NO_EXIT_RUNTIME=1")
-#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s TOTAL_MEMORY=16MB")
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s TOTAL_MEMORY=256MB")
 #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s ABORTING_MALLOC=0")
 #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s FORCE_FILESYSTEM=1")
 #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --preload-file myFile")
@@ -29,7 +29,6 @@ endif()
 set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s MODULARIZE=1")
 set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXPORT_NAME='createOccViewerModule'")
 set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS=['ccall','cwrap']")
-set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --extern-post-js ${CMAKE_CURRENT_SOURCE_DIR}/occt-webgl-viewer.js")
 
 INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR})
 file(GLOB SOURCES
@@ -38,10 +37,12 @@ file(GLOB SOURCES
 )
 source_group ("Headers" FILES
   WasmOcctView.h
-  WasmOcctPixMap.h)
+  WasmOcctPixMap.h
+  WasmOcctObject.h)
 source_group ("Sources" FILES
   WasmOcctView.cpp
   WasmOcctPixMap.cpp
+  WasmOcctObject.cpp
   main.cpp)
 
 # FreeType
@@ -66,6 +67,9 @@ endif()
 
 set(OpenCASCADE_LIBS TKRWMesh TKBinXCAF TKBin TKBinL TKOpenGl TKXCAF TKVCAF TKCAF TKV3d TKHLR TKMesh TKService TKShHealing TKPrim TKTopAlgo TKGeomAlgo TKBRep TKGeomBase TKG3d TKG2d TKMath TKLCAF TKCDF TKernel)
 
+INCLUDE_DIRECTORIES("c:/workssd/Develop/3rdparty/android/rapidjson-1.1.0/include")
+add_definitions (-DHAVE_RAPIDJSON)
+
 add_executable(${APP_TARGET} ${SOURCES})
 target_link_libraries(
   ${APP_TARGET}
@@ -81,5 +85,10 @@ if (NOT "${SOURCE_MAP_BASE}" STREQUAL "")
   endif()
 endif()
 install(FILES occt-webgl-sample.html DESTINATION ${CMAKE_INSTALL_PREFIX})
+install(FILES threejs-sample.html    DESTINATION ${CMAKE_INSTALL_PREFIX})
+install(FILES threejs-sample.js      DESTINATION ${CMAKE_INSTALL_PREFIX})
+install(DIRECTORY threejs            DESTINATION ${CMAKE_INSTALL_PREFIX})
+install(DIRECTORY textures           DESTINATION ${CMAKE_INSTALL_PREFIX})
+install(DIRECTORY models             DESTINATION ${CMAKE_INSTALL_PREFIX})
 install(FILES ${OpenCASCADE_RESOURCE_DIR}/DrawResources/OCC_logo.png DESTINATION ${CMAKE_INSTALL_PREFIX})
 install(FILES ${OpenCASCADE_RESOURCE_DIR}/DrawResources/lamp.ico DESTINATION ${CMAKE_INSTALL_PREFIX})
diff --git a/samples/webgl/OccViewerModule.idl b/samples/webgl/OccViewerModule.idl
new file mode 100644 (file)
index 0000000..923c8a2
--- /dev/null
@@ -0,0 +1,98 @@
+/**
+ * OCC Viewer global object.
+ */
+interface OccViewerModule {
+
+  /**
+   * Set cubemap background.
+   * File will be loaded asynchronously.
+   * @param {string} theImagePath [in] image path to load
+   */
+  void setCubemapBackground (DOMString theImagePath);
+
+  /**
+   * Clear all named objects from viewer.
+   */
+  void removeAllObjects();
+
+  /**
+   * Fit all/selected objects into view.
+   * @param {boolean} theAuto [in] fit selected objects (TRUE) or all objects (FALSE)
+   */
+  void fitAllObjects (boolean theAuto);
+
+  /**
+   * Remove named object from viewer.
+   * @param {string} theName [in] object name
+   * @param {boolean} theToUpdate [in] immediatly request viewer update
+   * @return {boolean} FALSE if object was not found
+   */
+  boolean removeObject (DOMString theName,
+                        boolean theToUpdate);
+
+  /**
+   * Temporarily hide named object.
+   * @param {string} theName [in] object name
+   * @return {boolean} FALSE if object was not found
+   */
+  boolean eraseObject (DOMString theName);
+
+  /**
+   * Display temporarily hidden object.
+   * @param {string} theName [in] object name
+   * @return {boolean} FALSE if object was not found
+   */
+  boolean displayObject (DOMString theName);
+
+  /**
+   * Show/hide ground.
+   * @param {boolean} theToShow [in] show or hide flag
+   */
+  void displayGround (boolean theToShow);
+
+  /**
+   * Open object from the given URL.
+   * File will be loaded asynchronously.
+   * @param {string} theName      [in] object name
+   * @param {string} theModelPath [in] model path
+   */
+  void openFromUrl (DOMString theName,
+                    DOMString theModelPath);
+
+  /**
+   * Open object from memory.
+   * @param theName    [in] object name
+   * @param theBuffer  [in] pointer to data
+   * @param theDataLen [in] data length
+   * @param theToFree  [in] free theBuffer if set to TRUE
+   * @return {boolean} FALSE on reading error
+   */
+  //boolean openFromMemory (DOMString theName,
+  //                        long theBuffer, int theDataLen,
+  //                        boolean theToFree);
+
+  /**
+   * Open BRep object from memory.
+   * @param theName    [in] object name
+   * @param theBuffer  [in] pointer to data
+   * @param theDataLen [in] data length
+   * @param theToFree  [in] free theBuffer if set to TRUE
+   * @return FALSE on reading error
+   */
+  //static boolean openBRepFromMemory (DOMString theName,
+  //                                   long theBuffer, int theDataLen,
+  //                                   boolean theToFree);
+
+  /**
+   * Open glTF object from memory.
+   * @param theName    [in] object name
+   * @param theBuffer  [in] pointer to data
+   * @param theDataLen [in] data length
+   * @param theToFree  [in] free theBuffer if set to TRUE
+   * @return FALSE on reading error
+   */
+  //static boolean openGltfFromMemory (DOMString theName,
+  //                                   long theBuffer, int theDataLen,
+  //                                   boolean theToFree);
+
+};
diff --git a/samples/webgl/WasmOcctObject.cpp b/samples/webgl/WasmOcctObject.cpp
new file mode 100644 (file)
index 0000000..9e16911
--- /dev/null
@@ -0,0 +1,251 @@
+// Copyright (c) 2021 OPEN CASCADE SAS
+//
+// This file is part of the examples of the Open CASCADE Technology software library.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
+
+#include "WasmOcctObject.h"
+
+#include <BRep_Builder.hxx>
+#include <BRep_Tool.hxx>
+#include <Graphic3d_ArrayOfTriangles.hxx>
+#include <Prs3d_ShadingAspect.hxx>
+#include <Select3D_SensitiveTriangulation.hxx>
+#include <SelectMgr_EntityOwner.hxx>
+#include <TopExp_Explorer.hxx>
+#include <TopoDS.hxx>
+#include <TopoDS_Face.hxx>
+#include <XCAFDoc_VisMaterial.hxx>
+
+//! Gets triangulation of every face of shape and fills output array of triangles
+static Handle(Graphic3d_ArrayOfTriangles) fillTriangles (const TopoDS_Shape& theShape,
+                                                         const bool theHasTexels,
+                                                         const gp_Pnt2d& theUVOrigin,
+                                                         const gp_Pnt2d& theUVRepeat,
+                                                         const gp_Pnt2d& theUVScale)
+{
+  TopLoc_Location aLoc;
+  Standard_Integer aNbTriangles = 0, aNbVertices = 0;
+  bool hasNormals = true;
+  for (TopExp_Explorer aFaceIt (theShape, TopAbs_FACE); aFaceIt.More(); aFaceIt.Next())
+  {
+    const TopoDS_Face& aFace = TopoDS::Face (aFaceIt.Current());
+    if (const Handle(Poly_Triangulation)& aT = BRep_Tool::Triangulation (aFace, aLoc))
+    {
+      aNbTriangles += aT->NbTriangles();
+      aNbVertices  += aT->NbNodes();
+      hasNormals = hasNormals && aT->HasNormals();
+    }
+  }
+  if (aNbVertices < 3 || aNbTriangles < 1)
+  {
+    return Handle(Graphic3d_ArrayOfTriangles)();
+  }
+
+  const Graphic3d_ArrayFlags aFlags = (hasNormals   ? Graphic3d_ArrayFlags_VertexNormal : Graphic3d_ArrayFlags_None)
+                                    | (theHasTexels ? Graphic3d_ArrayFlags_VertexTexel  : Graphic3d_ArrayFlags_None);
+  Handle(Graphic3d_ArrayOfTriangles) anArray = new Graphic3d_ArrayOfTriangles (aNbVertices, 3 * aNbTriangles, aFlags);
+  Standard_Real aUmin (0.0), aUmax (1.0), aVmin (0.0), aVmax (1.0), dUmax (1.0), dVmax (1.0);
+  for (TopExp_Explorer aFaceIt(theShape, TopAbs_FACE); aFaceIt.More(); aFaceIt.Next())
+  {
+    const TopoDS_Face& aFace = TopoDS::Face (aFaceIt.Current());
+    const Handle(Poly_Triangulation)& aT = BRep_Tool::Triangulation (aFace, aLoc);
+    if (aT.IsNull())
+    {
+      continue;
+    }
+
+    // Determinant of transform matrix less then 0 means that mirror transform applied.
+    const gp_Trsf& aTrsf = aLoc.Transformation();
+    const bool isMirrored = aTrsf.VectorialPart().Determinant() < 0;
+
+    // Extracts vertices & normals from nodes
+    const TColgp_Array1OfPnt&   aNodes   = aT->Nodes();
+    const TColgp_Array1OfPnt2d* aUVNodes = theHasTexels && aT->HasUVNodes() && aT->UVNodes().Upper() == aNodes.Upper()
+                                         ? &aT->UVNodes()
+                                         : NULL;
+
+    const TShort_Array1OfShortReal* aNormals = aT->HasNormals() ? &aT->Normals() : NULL;
+    const Standard_ShortReal*       aNormArr = aNormals != NULL ? &aNormals->First() : NULL;
+
+    const Standard_Integer aVertFrom = anArray->VertexNumber();
+    for (Standard_Integer aNodeIter = aNodes.Lower(); aNodeIter <= aNodes.Upper(); ++aNodeIter)
+    {
+      gp_Pnt aPoint = aNodes (aNodeIter);
+      const Standard_Integer anId = 3 * (aNodeIter - aNodes.Lower());
+      gp_Dir aNorm = aNormArr != NULL ? gp_Dir (aNormArr[anId + 0], aNormArr[anId + 1], aNormArr[anId + 2]) : gp::DZ();
+      if ((aFace.Orientation() == TopAbs_REVERSED) ^ isMirrored)
+      {
+        aNorm.Reverse();
+      }
+      if (!aLoc.IsIdentity())
+      {
+        aPoint.Transform (aTrsf);
+        aNorm .Transform (aTrsf);
+      }
+
+      if (aUVNodes != NULL)
+      {
+        const gp_Pnt2d aTexel = (dUmax == 0.0 || dVmax == 0.0)
+                              ? aUVNodes->Value (aNodeIter)
+                              : gp_Pnt2d ((-theUVOrigin.X() + (theUVRepeat.X() * (aUVNodes->Value (aNodeIter).X() - aUmin)) / dUmax) / theUVScale.X(),
+                                          (-theUVOrigin.Y() + (theUVRepeat.Y() * (aUVNodes->Value (aNodeIter).Y() - aVmin)) / dVmax) / theUVScale.Y());
+        anArray->AddVertex (aPoint, aNorm, aTexel);
+      }
+      else
+      {
+        anArray->AddVertex (aPoint, aNorm);
+      }
+    }
+
+    // Fill array with vertex and edge visibility info
+    const Poly_Array1OfTriangle& aTriangles = aT->Triangles();
+    Standard_Integer anIndex[3] = {};
+    for (Standard_Integer aTriIter = 1; aTriIter <= aT->NbTriangles(); ++aTriIter)
+    {
+      aTriangles (aTriIter).Get (anIndex[0], anIndex[1], anIndex[2]);
+      if (aFace.Orientation() == TopAbs_REVERSED) { std::swap (anIndex[1], anIndex[2]); }
+      anArray->AddEdges (anIndex[0] + aVertFrom, anIndex[1] + aVertFrom, anIndex[2] + aVertFrom);
+    }
+  }
+  return anArray;
+}
+
+// ================================================================
+// Function : WasmOcctObject
+// Purpose  :
+// ================================================================
+WasmOcctObject::WasmOcctObject()
+{
+}
+
+// ================================================================
+// Function : ~WasmOcctObject
+// Purpose  :
+// ================================================================
+WasmOcctObject::~WasmOcctObject()
+{
+}
+
+// ================================================================
+// Function : Compute
+// Purpose  :
+// ================================================================
+void WasmOcctObject::Compute (const Handle(PrsMgr_PresentationManager3d)& thePrsMgr,
+                              const Handle(Prs3d_Presentation)& thePrs,
+                              const Standard_Integer theMode)
+{
+  if (theMode != 0)
+  {
+    return;
+  }
+
+  gp_Pnt2d anUVOrigin, anUVRepeat, anUVScale;
+  bool hasTexCoords = false;
+
+  NCollection_DataMap<Handle(XCAFDoc_VisMaterial), TopoDS_Compound> aMatMap;
+
+  RWMesh_NodeAttributes aDefAttribs;
+  {
+    Handle(XCAFDoc_VisMaterial) aDefMat = new XCAFDoc_VisMaterial();
+    XCAFDoc_VisMaterialPBR aPbrMar;
+    aPbrMar.IsDefined = true;
+    aPbrMar.BaseColor.SetValues (0.243137f, 0.243137f, 0.243137f, 1.0f);
+    aDefMat->SetPbrMaterial (aPbrMar);
+    aDefAttribs.Style.SetMaterial (aDefMat);
+  }
+  for (TopTools_SequenceOfShape::Iterator aRootIter (myRootShapes); aRootIter.More(); aRootIter.Next())
+  {
+    const TopoDS_Shape& aShape = aRootIter.Value();
+    /*if (Handle(Graphic3d_ArrayOfTriangles) aPArray = fillTriangles (aShape, hasTexCoords, anUVOrigin, anUVRepeat, anUVScale))
+    {
+      Handle(Graphic3d_Group) aGroup = thePrs->NewGroup();
+      aGroup->SetGroupPrimitivesAspect (myDrawer->ShadingAspect()->Aspect());
+      aGroup->AddPrimitiveArray (aPArray);
+    }*/
+
+    for (TopExp_Explorer aFaceIter (aShape, TopAbs_FACE); aFaceIter.More(); aFaceIter.Next())
+    {
+      const TopoDS_Face& aFace = TopoDS::Face (aFaceIter.Value());
+      const RWMesh_NodeAttributes* anAttribs = myAttribMap.Seek (aFace);
+      if (anAttribs == NULL)
+      {
+        anAttribs = myAttribMap.Seek (aFace.Located (TopLoc_Location()));
+        if (anAttribs == NULL)
+        {
+          anAttribs = &aDefAttribs;
+        }
+      }
+      if (Handle(XCAFDoc_VisMaterial) aVisMat = anAttribs->Style.Material())
+      {
+        TopoDS_Compound* aComp = aMatMap.ChangeSeek (aVisMat);
+        if (aComp == NULL)
+        {
+          aComp = aMatMap.Bound (aVisMat, TopoDS_Compound());
+          BRep_Builder().MakeCompound (*aComp);
+        }
+        BRep_Builder().Add (*aComp, aFace);
+      }
+    }
+  }
+
+  for (NCollection_DataMap<Handle(XCAFDoc_VisMaterial), TopoDS_Compound>::Iterator aMatIter (aMatMap); aMatIter.More(); aMatIter.Next())
+  {
+    const Handle(XCAFDoc_VisMaterial)& aVisMat = aMatIter.Key();
+    const TopoDS_Compound& aShape = aMatIter.Value();
+    if (Handle(Graphic3d_ArrayOfTriangles) aPArray = fillTriangles (aShape, hasTexCoords, anUVOrigin, anUVRepeat, anUVScale))
+    {
+      Handle(Graphic3d_AspectFillArea3d) anAspects = new Graphic3d_AspectFillArea3d();
+      *anAspects = *myDrawer->ShadingAspect()->Aspect();
+      aVisMat->FillAspect (anAspects);
+
+      Handle(Graphic3d_Group) aGroup = thePrs->NewGroup();
+      aGroup->SetGroupPrimitivesAspect (anAspects);
+      aGroup->AddPrimitiveArray (aPArray);
+    }
+  }
+}
+
+// ================================================================
+// Function : ComputeSelection
+// Purpose  :
+// ================================================================
+void WasmOcctObject::ComputeSelection (const Handle(SelectMgr_Selection)& theSel,
+                                       const Standard_Integer theMode)
+{
+  if (theMode != 0)
+  {
+    return;
+  }
+
+  Handle(SelectMgr_EntityOwner) anOwner = new SelectMgr_EntityOwner (this, 5);
+  for (TopTools_SequenceOfShape::Iterator aRootIter (myRootShapes); aRootIter.More(); aRootIter.Next())
+  {
+    const TopoDS_Shape& aShape = aRootIter.Value();
+    for (TopExp_Explorer aFaceIter (aShape, TopAbs_FACE); aFaceIter.More(); aFaceIter.Next())
+    {
+      const TopoDS_Face& aFace = TopoDS::Face (aFaceIter.Value());
+      TopLoc_Location aLoc;
+      if (Handle(Poly_Triangulation) aPolyTri = BRep_Tool::Triangulation (aFace, aLoc))
+      {
+        Handle(Select3D_SensitiveTriangulation) aSensTris = new Select3D_SensitiveTriangulation (anOwner, aPolyTri, aLoc);
+        theSel->Add (aSensTris);
+      }
+    }
+  }
+}
diff --git a/samples/webgl/WasmOcctObject.h b/samples/webgl/WasmOcctObject.h
new file mode 100644 (file)
index 0000000..19a61ec
--- /dev/null
@@ -0,0 +1,80 @@
+// Copyright (c) 2021 OPEN CASCADE SAS
+//
+// This file is part of the examples of the Open CASCADE Technology software library.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
+
+#ifndef _WasmOcctObject_HeaderFile
+#define _WasmOcctObject_HeaderFile
+
+#include <AIS_InteractiveObject.hxx>
+#include <RWMesh_NodeAttributes.hxx>
+#include <TopTools_SequenceOfShape.hxx>
+
+//! Sample presentation.
+class WasmOcctObject : public AIS_InteractiveObject
+{
+public:
+  //! Default constructor.
+  Standard_EXPORT WasmOcctObject();
+
+  //! Destructor.
+  Standard_EXPORT virtual ~WasmOcctObject();
+
+  //! Return sequence of root shapes.
+  TopTools_SequenceOfShape& ChangeShapes() { return myRootShapes; }
+
+  //! Return shape attributes.
+  RWMesh_NodeAttributeMap& ChangeAttributes() { return myAttribMap; }
+
+  //! Set a single shape.
+  void SetShape (const TopoDS_Shape& theShape)
+  {
+    myRootShapes.Clear();
+    myRootShapes.Append (theShape);
+  }
+
+protected:
+
+  //! Return TRUE for supported display mode.
+  virtual Standard_Boolean AcceptDisplayMode (const Standard_Integer theMode) const Standard_OVERRIDE { return theMode == 0; }
+
+  //! Compute 3D part of View Cube.
+  //! @param thePrsMgr [in] presentation manager.
+  //! @param thePrs [in] input presentation that is to be filled with flat presentation primitives.
+  //! @param theMode [in] display mode.
+  //! @warning this object accept only 0 display mode.
+  Standard_EXPORT virtual void Compute (const Handle(PrsMgr_PresentationManager3d)& thePrsMgr,
+                                        const Handle(Prs3d_Presentation)& thePrs,
+                                        const Standard_Integer theMode) Standard_OVERRIDE;
+
+  //! Redefine computing of sensitive entities for View Cube.
+  //! @param theSelection [in] input selection object that is to be filled with sensitive entities.
+  //! @param theMode [in] selection mode.
+  //! @warning object accepts only 0 selection mode.
+  Standard_EXPORT virtual void ComputeSelection (const Handle(SelectMgr_Selection)& theSel,
+                                                 const Standard_Integer theMode) Standard_OVERRIDE;
+
+private:
+
+  TopTools_SequenceOfShape myRootShapes; //!< sequence of result root shapes
+  RWMesh_NodeAttributeMap  myAttribMap;  //!< shape attributes
+
+};
+
+#endif // _WasmOcctObject_HeaderFile
index ec8e5f72541e8973eb60a29f2e47e2b0c0b32aab..e09bca11de666c4bb7a5250ffdfa7e951cc44ab5 100644 (file)
@@ -41,6 +41,7 @@
 #include <BRepBndLib.hxx>
 #include <BRepTools.hxx>
 #include <Standard_ArrayStreamBuffer.hxx>
+#include <TopExp_Explorer.hxx>
 
 #include <emscripten/bind.h>
 
@@ -74,15 +75,22 @@ namespace
   {
     std::string Name;
     std::string Path;
+    bool ToExpand;
+    OSD_Timer Timer;
 
-    ModelAsyncLoader (const char* theName, const char* thePath)
-    : Name (theName), Path (thePath) {}
+    ModelAsyncLoader (const char* theName, const char* thePath, const bool theToExpand)
+    : Name (theName), Path (thePath), ToExpand (theToExpand)
+    {
+      Timer.Start();
+    }
 
     //! File data read event.
     static void onDataRead (void* theOpaque, void* theBuffer, int theDataLen)
     {
-      const ModelAsyncLoader* aTask = (ModelAsyncLoader* )theOpaque;
-      WasmOcctView::openFromMemory (aTask->Name, reinterpret_cast<uintptr_t>(theBuffer), theDataLen, false);
+      ModelAsyncLoader* aTask = (ModelAsyncLoader* )theOpaque;
+      WasmOcctView::openFromMemory (aTask->Name, aTask->ToExpand, reinterpret_cast<uintptr_t>(theBuffer), theDataLen, false);
+      aTask->Timer.Stop();
+      Message::SendInfo() << aTask->Path << " loaded in " << aTask->Timer.ElapsedTime() << " s";
       delete aTask;
     }
 
@@ -106,6 +114,9 @@ namespace
       if (anImage->Init (theFilePath))
       {
         aCubemap = new Graphic3d_CubeMapPacked (anImage);
+        /// TODO WebGL 2.0 ensures sRGB conformance within glGenerateMipmap()
+        /// which leads to extremely SLOW generation (5 seconds instead of 0.035)
+        aCubemap->SetColorMap (false);
       }
       WasmOcctView::Instance().View()->SetBackgroundCubeMap (aCubemap, true, false);
       WasmOcctView::Instance().UpdateView();
@@ -117,6 +128,9 @@ namespace
       Message::DefaultMessenger()->Send (TCollection_AsciiString("Error: unable to load image ") + theFilePath, Message_Fail);
     }
   };
+
+  //! Object list separator.
+  static const char THE_LIST_SEPARATOR = '\n';
 }
 
 // ================================================================
@@ -137,6 +151,8 @@ WasmOcctView::WasmOcctView()
 : myDevicePixelRatio (1.0f),
   myUpdateRequests (0)
 {
+  SetLockOrbitZUp (true);
+
   addActionHotKeys (Aspect_VKey_NavForward,        Aspect_VKey_W, Aspect_VKey_W | Aspect_VKeyFlags_SHIFT);
   addActionHotKeys (Aspect_VKey_NavBackward ,      Aspect_VKey_S, Aspect_VKey_S | Aspect_VKeyFlags_SHIFT);
   addActionHotKeys (Aspect_VKey_NavSlideLeft,      Aspect_VKey_A, Aspect_VKey_A | Aspect_VKeyFlags_SHIFT);
@@ -316,10 +332,14 @@ bool WasmOcctView::initViewer()
     Message::DefaultMessenger()->Send (TCollection_AsciiString ("Error: font '") + aFontPath + "' is not found", Message_Fail);
   }*/
 
+  const bool toAntialias = myDevicePixelRatio <= 1.25f;
+
   Handle(Aspect_DisplayConnection) aDisp;
   Handle(OpenGl_GraphicDriver) aDriver = new OpenGl_GraphicDriver (aDisp, false);
   aDriver->ChangeOptions().buffersNoSwap = true; // swap has no effect in WebGL
   aDriver->ChangeOptions().buffersOpaqueAlpha = true; // avoid unexpected blending of canvas with page background
+  aDriver->ChangeOptions().useSystemBuffer = false; ///
+  //aDriver->ChangeOptions().useSystemBuffer = true; ///
   if (!aDriver->InitContext())
   {
     Message::DefaultMessenger()->Send (TCollection_AsciiString ("Error: EGL initialization failed"), Message_Fail);
@@ -337,6 +357,8 @@ bool WasmOcctView::initViewer()
     if (aLight->Type() == Graphic3d_TOLS_DIRECTIONAL)
     {
       aLight->SetCastShadows (true);
+      aLight->SetHeadlight (false);
+      aLight->SetDirection (gp_Dir (0.098f, -0.20f, -0.98f));
     }
   }
 
@@ -361,8 +383,11 @@ bool WasmOcctView::initViewer()
 
   myView = new V3d_View (aViewer);
   myView->Camera()->SetProjectionType (Graphic3d_Camera::Projection_Perspective);
+  myView->Camera()->SetFOV2d (360.0f);
+  myView->Camera()->SetFOVy (45.0f);
   myView->SetImmediateUpdate (false);
-  myView->ChangeRenderingParams().IsShadowEnabled = false;
+  //myView->ChangeRenderingParams().NbMsaaSamples = toAntialias ? 4 : 0;
+  myView->ChangeRenderingParams().RenderResolutionScale = toAntialias ? 2.0f : 1.0f;
   myView->ChangeRenderingParams().Resolution = (unsigned int )(96.0 * myDevicePixelRatio + 0.5);
   myView->ChangeRenderingParams().ToShowStats = true;
   myView->ChangeRenderingParams().StatsTextAspect = myTextStyle->Aspect();
@@ -370,7 +395,26 @@ bool WasmOcctView::initViewer()
   myView->SetWindow (aWindow);
   dumpGlInfo (false);
 
+  myView->SetShadingModel (aDriver->InquireLimit (Graphic3d_TypeOfLimit_HasPBR) == 1 ? Graphic3d_TOSM_PBR : Graphic3d_TOSM_FRAGMENT);
+  ///myView->ChangeRenderingParams().IsShadowEnabled = aDriver->InquireLimit (Graphic3d_TypeOfLimit_HasPBR) == 1; /// TODO
+  myView->ChangeRenderingParams().IsShadowEnabled = true; ///
+
   myContext = new AIS_InteractiveContext (aViewer);
+  {
+    {
+      const Handle(Prs3d_Drawer)& aHiStyle = myContext->HighlightStyle();
+      aHiStyle->SetTransparency (0.8f);
+
+      Handle(Graphic3d_AspectFillArea3d) anAspect = new Graphic3d_AspectFillArea3d();
+      *anAspect = *myContext->DefaultDrawer()->ShadingAspect()->Aspect();
+      Graphic3d_MaterialAspect aMat (Graphic3d_NOM_STONE);
+      aMat.SetColor (aHiStyle->Color());
+      aMat.SetTransparency (aHiStyle->Transparency());
+      anAspect->SetFrontMaterial (aMat);
+      anAspect->SetInteriorColor (aHiStyle->Color());
+      aHiStyle->SetBasicFillAreaAspect (anAspect);
+    }
+  }
   initPixelScaleRatio();
   return true;
 }
@@ -865,10 +909,95 @@ bool WasmOcctView::processKeyPress (Aspect_VKey theKey)
 }
 
 // ================================================================
-// Function : setCubemapBackground
+// Function : toDynamicHighlight
+// Purpose  :
+// ================================================================
+bool WasmOcctView::toDynamicHighlight()
+{
+  return WasmOcctView::Instance().ToAllowHighlight();
+}
+
+// ================================================================
+// Function : setDynamicHighlight
+// Purpose  :
+// ================================================================
+void WasmOcctView::setDynamicHighlight (bool theToEnable)
+{
+  WasmOcctView::Instance().SetAllowHighlight (theToEnable);
+}
+
+// ================================================================
+// Function : toCastShadows
+// Purpose  :
+// ================================================================
+bool WasmOcctView::toCastShadows()
+{
+  return WasmOcctView::Instance().View()->RenderingParams().IsShadowEnabled;
+}
+
+// ================================================================
+// Function : setCastShadows
 // Purpose  :
 // ================================================================
-void WasmOcctView::setCubemapBackground (const std::string& theImagePath)
+void WasmOcctView::setCastShadows (bool theUseShadows,
+                                   bool theToUpdate)
+{
+  if (WasmOcctView::Instance().View()->RenderingParams().IsShadowEnabled != theUseShadows)
+  {
+    WasmOcctView::Instance().View()->ChangeRenderingParams().IsShadowEnabled = theUseShadows;
+    if (theToUpdate)
+    {
+      WasmOcctView::Instance().UpdateView();
+    }
+  }
+}
+
+// ================================================================
+// Function : isAntiAliasingOn
+// Purpose  :
+// ================================================================
+bool WasmOcctView::isAntiAliasingOn()
+{
+  return WasmOcctView::Instance().View()->RenderingParams().RenderResolutionScale > 1.1f;
+}
+
+// ================================================================
+// Function : setAntiAliasingOn
+// Purpose  :
+// ================================================================
+void WasmOcctView::setAntiAliasingOn (bool theToEnable, bool theToUpdate)
+{
+  if (isAntiAliasingOn() != theToEnable)
+  {
+    WasmOcctView::Instance().View()->ChangeRenderingParams().RenderResolutionScale = theToEnable ? 2.0f : 1.0f;
+    if (theToUpdate)
+    {
+      WasmOcctView::Instance().UpdateView();
+    }
+  }
+}
+
+// ================================================================
+// Function : setBackgroundColor
+// Purpose  :
+// ================================================================
+void WasmOcctView::setBackgroundColor (float theR, float theG, float theB,
+                                       bool theToUpdate)
+{
+  Quantity_Color aColor (theR, theG, theB, Quantity_TOC_RGB);
+  WasmOcctView::Instance().View()->SetBackgroundColor (aColor);
+  WasmOcctView::Instance().View()->SetBackgroundCubeMap (Handle(Graphic3d_CubeMap)(), true);
+  if (theToUpdate)
+  {
+    WasmOcctView::Instance().UpdateView();
+  }
+}
+
+// ================================================================
+// Function : setBackgroundCubemap
+// Purpose  :
+// ================================================================
+void WasmOcctView::setBackgroundCubemap (const std::string& theImagePath)
 {
   if (!theImagePath.empty())
   {
@@ -885,7 +1014,8 @@ void WasmOcctView::setCubemapBackground (const std::string& theImagePath)
 // Function : fitAllObjects
 // Purpose  :
 // ================================================================
-void WasmOcctView::fitAllObjects (bool theAuto)
+void WasmOcctView::fitAllObjects (bool theAuto,
+                                  bool theToUpdate)
 {
   WasmOcctView& aViewer = Instance();
   if (theAuto)
@@ -896,91 +1026,230 @@ void WasmOcctView::fitAllObjects (bool theAuto)
   {
     aViewer.View()->FitAll (0.01, false);
   }
-  aViewer.UpdateView();
+  if (theToUpdate)
+  {
+    aViewer.UpdateView();
+  }
 }
 
+EM_JS(void, jsOnDisplayedObjectsChanged, (), {
+  Module.onDisplayedObjectsChanged();
+});
+EM_JS(void, jsOnSelectedObjectsChanged, (), {
+  Module.onSelectedObjectsChanged();
+});
+
 // ================================================================
-// Function : removeAllObjects
+// Function : onDisplayedObjectsChanged
+// Purpose  :
+// ================================================================
+void WasmOcctView::onDisplayedObjectsChanged()
+{
+  jsOnDisplayedObjectsChanged();
+}
+
+// ================================================================
+// Function : OnSelectionChanged
+// Purpose  :
+// ================================================================
+void WasmOcctView::OnSelectionChanged (const Handle(AIS_InteractiveContext)& ,
+                                       const Handle(V3d_View)& )
+{
+  jsOnSelectedObjectsChanged();
+}
+
+// ================================================================
+// Function : displayedObjects
 // Purpose  :
 // ================================================================
-void WasmOcctView::removeAllObjects()
+std::string WasmOcctView::displayedObjects()
 {
   WasmOcctView& aViewer = Instance();
+  std::string aList;
   for (NCollection_IndexedDataMap<TCollection_AsciiString, Handle(AIS_InteractiveObject)>::Iterator anObjIter (aViewer.myObjects);
        anObjIter.More(); anObjIter.Next())
   {
-    aViewer.Context()->Remove (anObjIter.Value(), false);
+    if (!aList.empty()) { aList += THE_LIST_SEPARATOR; }
+    aList += anObjIter.Key().ToCString();
   }
-  aViewer.myObjects.Clear();
-  aViewer.UpdateView();
+  return aList;
 }
 
 // ================================================================
-// Function : removeObject
+// Function : selectedObjects
 // Purpose  :
 // ================================================================
-bool WasmOcctView::removeObject (const std::string& theName)
+std::string WasmOcctView::selectedObjects()
 {
   WasmOcctView& aViewer = Instance();
-  Handle(AIS_InteractiveObject) anObj;
-  if (!theName.empty()
-   && !aViewer.myObjects.FindFromKey (theName.c_str(), anObj))
+  std::string aList;
+  for (NCollection_IndexedDataMap<TCollection_AsciiString, Handle(AIS_InteractiveObject)>::Iterator anObjIter (aViewer.myObjects);
+       anObjIter.More(); anObjIter.Next())
   {
-    return false;
+    if (aViewer.Context()->IsSelected (anObjIter.Value()))
+    {
+      if (!aList.empty()) { aList += THE_LIST_SEPARATOR; }
+      aList += anObjIter.Key().ToCString();
+    }
   }
+  return aList;
+}
 
-  aViewer.Context()->Remove (anObj, false);
-  aViewer.myObjects.RemoveKey (theName.c_str());
-  aViewer.UpdateView();
+// ================================================================
+// Function : erasedObjects
+// Purpose  :
+// ================================================================
+std::string WasmOcctView::erasedObjects()
+{
+  WasmOcctView& aViewer = Instance();
+  std::string aList;
+  for (NCollection_IndexedDataMap<TCollection_AsciiString, Handle(AIS_InteractiveObject)>::Iterator anObjIter (aViewer.myObjects);
+       anObjIter.More(); anObjIter.Next())
+  {
+    if (!aViewer.Context()->IsDisplayed (anObjIter.Value()))
+    {
+      if (!aList.empty()) { aList += THE_LIST_SEPARATOR; }
+      aList += anObjIter.Key().ToCString();
+    }
+  }
+  return aList;
+}
+
+// ================================================================
+// Function : displayObjectList
+// Purpose  :
+// ================================================================
+bool WasmOcctView::displayObjectList (const std::string& theNames,
+                                      bool theToUpdate)
+{
+  WasmOcctView& aViewer = Instance();
+
+  std::stringstream aListStream (theNames);
+  std::string aName;
+  bool hasChanged = false;
+  while (std::getline (aListStream, aName, THE_LIST_SEPARATOR))
+  {
+    Handle(AIS_InteractiveObject) anObj;
+    if (!aViewer.myObjects.FindFromKey (aName.c_str(), anObj))
+    {
+      Message::SendFail() << "Error: unknown object '" << aName << "'";
+      return false;
+    }
+
+    hasChanged = true;
+    aViewer.Context()->Display (anObj, false);
+  }
+  if (theToUpdate
+   && hasChanged)
+  {
+    aViewer.UpdateView();
+  }
   return true;
 }
 
 // ================================================================
-// Function : eraseObject
+// Function : removeObjectList
 // Purpose  :
 // ================================================================
-bool WasmOcctView::eraseObject (const std::string& theName)
+bool WasmOcctView::removeObjectList (const std::string& theNames,
+                                     bool theToUpdate)
 {
   WasmOcctView& aViewer = Instance();
-  Handle(AIS_InteractiveObject) anObj;
-  if (!theName.empty()
-   && !aViewer.myObjects.FindFromKey (theName.c_str(), anObj))
+
+  std::stringstream aListStream (theNames);
+  std::string aName;
+  bool hasChanged = false;
+  while (std::getline (aListStream, aName, THE_LIST_SEPARATOR))
   {
-    return false;
+    /// TODO filtering should be done for all keys starting with aName
+    Handle(AIS_InteractiveObject) anObj;
+    if (!aViewer.myObjects.FindFromKey (aName.c_str(), anObj))
+    {
+      //Message::SendFail() << "Error: unknown object '" << aName << "'";
+      return false;
+    }
+
+    hasChanged = true;
+    aViewer.Context()->Remove (anObj, false);
+    aViewer.myObjects.RemoveKey (aName.c_str());
   }
 
-  aViewer.Context()->Erase (anObj, false);
-  aViewer.UpdateView();
+  if (hasChanged)
+  {
+    if (theToUpdate) { aViewer.UpdateView(); }
+    aViewer.onDisplayedObjectsChanged();
+  }
   return true;
 }
 
 // ================================================================
-// Function : displayObject
+// Function : eraseObjectList
 // Purpose  :
 // ================================================================
-bool WasmOcctView::displayObject (const std::string& theName)
+bool WasmOcctView::eraseObjectList (const std::string& theNames,
+                                    bool theToUpdate)
 {
   WasmOcctView& aViewer = Instance();
-  Handle(AIS_InteractiveObject) anObj;
-  if (!theName.empty()
-   && !aViewer.myObjects.FindFromKey (theName.c_str(), anObj))
+
+  std::stringstream aListStream (theNames);
+  std::string aName;
+  bool hasChanged = false;
+  while (std::getline (aListStream, aName, THE_LIST_SEPARATOR))
   {
-    return false;
+    /// TODO filtering should be done for all keys starting with aName
+    Handle(AIS_InteractiveObject) anObj;
+    if (!aViewer.myObjects.FindFromKey (aName.c_str(), anObj))
+    {
+      Message::SendFail() << "Error: unknown object '" << aName << "'";
+      return false;
+    }
+
+    hasChanged = true;
+    aViewer.Context()->Erase (anObj, false);
   }
 
-  aViewer.Context()->Display (anObj, false);
-  aViewer.UpdateView();
+  if (hasChanged
+   && theToUpdate)
+  {
+    aViewer.UpdateView();
+  }
   return true;
 }
 
+// ================================================================
+// Function : removeAllObjects
+// Purpose  :
+// ================================================================
+void WasmOcctView::removeAllObjects (bool theToUpdate)
+{
+  WasmOcctView& aViewer = Instance();
+  if (aViewer.myObjects.IsEmpty())
+  {
+    return;
+  }
+
+  for (NCollection_IndexedDataMap<TCollection_AsciiString, Handle(AIS_InteractiveObject)>::Iterator anObjIter (aViewer.myObjects);
+       anObjIter.More(); anObjIter.Next())
+  {
+    aViewer.Context()->Remove (anObjIter.Value(), false);
+  }
+  aViewer.myObjects.Clear();
+  if (theToUpdate)
+  {
+    aViewer.UpdateView();
+  }
+  aViewer.onDisplayedObjectsChanged();
+}
+
 // ================================================================
 // Function : openFromUrl
 // Purpose  :
 // ================================================================
 void WasmOcctView::openFromUrl (const std::string& theName,
-                                const std::string& theModelPath)
+                                const std::string& theModelPath,
+                                const bool theToExpand)
 {
-  ModelAsyncLoader* aTask = new ModelAsyncLoader (theName.c_str(), theModelPath.c_str());
+  ModelAsyncLoader* aTask = new ModelAsyncLoader (theName.c_str(), theModelPath.c_str(), theToExpand);
   emscripten_async_wget_data (theModelPath.c_str(), (void* )aTask, ModelAsyncLoader::onDataRead, ModelAsyncLoader::onReadFailed);
 }
 
@@ -989,10 +1258,11 @@ void WasmOcctView::openFromUrl (const std::string& theName,
 // Purpose  :
 // ================================================================
 bool WasmOcctView::openFromMemory (const std::string& theName,
+                                   const bool theToExpand,
                                    uintptr_t theBuffer, int theDataLen,
                                    bool theToFree)
 {
-  removeObject (theName);
+  removeObjectList (theName, false);
   char* aBytes = reinterpret_cast<char*>(theBuffer);
   if (aBytes == nullptr
    || theDataLen <= 0)
@@ -1005,11 +1275,11 @@ bool WasmOcctView::openFromMemory (const std::string& theName,
 
   if (dataStartsWithHeader(aBytes, "DBRep_DrawableShape"))
   {
-    return openBRepFromMemory (theName, theBuffer, theDataLen, theToFree);
+    return openBRepFromMemory (theName, theToExpand, theBuffer, theDataLen, theToFree);
   }
   else if (dataStartsWithHeader(aBytes, "glTF"))
   {
-    //return openGltfFromMemory (theName, theBuffer, theDataLen, theToFree);
+    return openGltfFromMemory (theName, theToExpand, theBuffer, theDataLen, theToFree);
   }
   if (theToFree)
   {
@@ -1025,12 +1295,13 @@ bool WasmOcctView::openFromMemory (const std::string& theName,
 // Purpose  :
 // ================================================================
 bool WasmOcctView::openBRepFromMemory (const std::string& theName,
+                                       const bool theToExpand,
                                        uintptr_t theBuffer, int theDataLen,
                                        bool theToFree)
 {
-  removeObject (theName);
+  removeObjectList (theName, false);
 
-  WasmOcctView& aViewer = Instance();
+  /*WasmOcctView& aViewer = Instance();
   TopoDS_Shape aShape;
   BRep_Builder aBuilder;
   bool isLoaded = false;
@@ -1062,18 +1333,322 @@ bool WasmOcctView::openBRepFromMemory (const std::string& theName,
 
   Message::DefaultMessenger()->Send (TCollection_AsciiString("Loaded file ") + theName.c_str(), Message_Info);
   Message::DefaultMessenger()->Send (OSD_MemInfo::PrintInfo(), Message_Trace);
+  return true;*/
+  return false;
+}
+
+#include <OSD_OpenFile.hxx>
+#include <RWGltf_GltfJsonParser.hxx>
+#include <RWGltf_TriangulationReader.hxx>
+#include "WasmOcctObject.h"
+
+class WasmTriangulationReader : public RWGltf_TriangulationReader
+{
+public:
+
+  WasmTriangulationReader (std::istream& theStream,
+                           const TCollection_AsciiString& theFile)
+  : myRootStream (&theStream), myRootPath (theFile)
+  {
+    //
+  }
+
+  virtual bool load (const Handle(RWGltf_GltfLatePrimitiveArray)& theMesh) override
+  {
+    reset();
+    if (theMesh.IsNull()
+     || theMesh->PrimitiveMode() == RWGltf_GltfPrimitiveMode_UNKNOWN)
+    {
+      return false;
+    }
+
+    for (NCollection_Sequence<RWGltf_GltfPrimArrayData>::Iterator aDataIter (theMesh->Data()); aDataIter.More(); aDataIter.Next())
+    {
+      const RWGltf_GltfPrimArrayData& aData = aDataIter.Value();
+      if (!aData.StreamData.IsNull())
+      {
+        Standard_ArrayStreamBuffer aStreamBuffer ((const char* )aData.StreamData->Data(), aData.StreamData->Size());
+        std::istream aStream (&aStreamBuffer);
+        aStream.seekg ((std::streamoff )aData.StreamOffset, std::ios_base::beg);
+        if (!readBuffer (aStream, theMesh->Id(), aData.Accessor, aData.Type, theMesh->PrimitiveMode()))
+        {
+          return false;
+        }
+        continue;
+      }
+      else if (aData.StreamUri.IsEmpty())
+      {
+        reportError (TCollection_AsciiString ("Buffer '") + theMesh->Id() + "' does not define uri.");
+        return false;
+      }
+
+      std::istream* aStream = &mySharedStream.Stream;
+      if (aData.StreamUri == myRootPath
+       && myRootStream != NULL)
+      {
+        aStream = myRootStream;
+      }
+      else if (mySharedStream.Path != aData.StreamUri)
+      {
+        mySharedStream.Stream.close();
+        mySharedStream.Path = aData.StreamUri;
+      }
+      if (aStream == &mySharedStream.Stream
+      && !mySharedStream.Stream.is_open())
+      {
+        OSD_OpenStream (mySharedStream.Stream, aData.StreamUri.ToCString(), std::ios::in | std::ios::binary);
+        if (!mySharedStream.Stream.is_open())
+        {
+          mySharedStream.Stream.close();
+          reportError (TCollection_AsciiString ("Buffer '") + theMesh->Id() + "refers to non-existing file '" + aData.StreamUri + "'.");
+          return false;
+        }
+      }
+
+      aStream->seekg ((std::streamoff )aData.StreamOffset, std::ios_base::beg);
+      if (!aStream->good())
+      {
+        mySharedStream.Stream.close();
+        reportError (TCollection_AsciiString ("Buffer '") + theMesh->Id() + "refers to invalid location.");
+        return false;
+      }
+
+      if (!readBuffer (*aStream, theMesh->Id(), aData.Accessor, aData.Type, theMesh->PrimitiveMode()))
+      {
+        return false;
+      }
+    }
+    return true;
+  }
+
+private:
+
+  std::istream* myRootStream;
+  TCollection_AsciiString myRootPath;
+
+};
+
+//! Parse glTF data.
+static bool parseGltfFromMemory (RWGltf_GltfJsonParser& theParser,
+                                 std::istream& theStream,
+                                 const TCollection_AsciiString& theFile)
+{
+  bool isBinaryFile = false;
+  char aGlbHeader[12] = {};
+  theStream.read (aGlbHeader, sizeof(aGlbHeader));
+  int64_t aBinBodyOffset = 0, aBinBodyLen = 0, aJsonBodyOffset = 0, aJsonBodyLen = 0;
+  if (::strncmp (aGlbHeader, "glTF", 4) == 0)
+  {
+    isBinaryFile = true;
+    const uint32_t* aVer = (const uint32_t* )(aGlbHeader + 4);
+    const uint32_t* aLen = (const uint32_t* )(aGlbHeader + 8);
+    if (*aVer != 2)
+    {
+      Message::SendFail (TCollection_AsciiString ("File '") + theFile + "' is written using unknown version " + int(*aVer));
+      return false;
+    }
+
+    for (int aChunkIter = 0; !theStream.eof() && aChunkIter < 2; ++aChunkIter)
+    {
+      char aChunkHeader2[8] = {};
+      if (int64_t(theStream.tellg()) + int64_t(sizeof(aChunkHeader2)) > int64_t(*aLen))
+      {
+        break;
+      }
+
+      theStream.read (aChunkHeader2, sizeof(aChunkHeader2));
+      if (!theStream.good())
+      {
+        Message::SendFail (TCollection_AsciiString ("File '") + theFile + "' is written using unsupported format");
+        return false;
+      }
+
+      const uint32_t* aChunkLen  = (const uint32_t* )(aChunkHeader2 + 0);
+      const uint32_t* aChunkType = (const uint32_t* )(aChunkHeader2 + 4);
+      if (*aChunkType == 0x4E4F534A)
+      {
+        aJsonBodyOffset = int64_t(theStream.tellg());
+        aJsonBodyLen    = int64_t(*aChunkLen);
+      }
+      else if (*aChunkType == 0x004E4942)
+      {
+        aBinBodyOffset = int64_t(theStream.tellg());
+        aBinBodyLen    = int64_t(*aChunkLen);
+      }
+      if (*aChunkLen != 0)
+      {
+        theStream.seekg (*aChunkLen, std::ios_base::cur);
+      }
+    }
+
+    theStream.seekg ((std::streamoff )aJsonBodyOffset, std::ios_base::beg);
+  }
+  else
+  {
+    theStream.seekg (0, std::ios_base::beg);
+  }
+  if (isBinaryFile)
+  {
+    theParser.SetBinaryFormat (aBinBodyOffset, aBinBodyLen);
+  }
+
+  rapidjson::ParseResult aRes;
+  rapidjson::IStreamWrapper aFileStream (theStream);
+  if (isBinaryFile)
+  {
+    aRes = theParser.ParseStream<rapidjson::kParseStopWhenDoneFlag, rapidjson::UTF8<>, rapidjson::IStreamWrapper> (aFileStream);
+  }
+  else
+  {
+    aRes = theParser.ParseStream (aFileStream);
+  }
+  if (aRes.IsError())
+  {
+    if (aRes.Code() == rapidjson::kParseErrorDocumentEmpty)
+    {
+      Message::SendFail (TCollection_AsciiString ("File '") + theFile + "' is empty");
+      return false;
+    }
+    TCollection_AsciiString anErrDesc (RWGltf_GltfJsonParser::FormatParseError (aRes.Code()));
+    Message::SendFail (TCollection_AsciiString ("File '") + theFile + "' defines invalid JSON document!\n"
+                     + anErrDesc + " [at offset " + (int )aRes.Offset() + "].");
+    return false;
+  }
+
+  if (!theParser.Parse (Message_ProgressRange()))
+  {
+    return false;
+  }
+
+  return true;
+}
+
+// ================================================================
+// Function : openGltfFromMemory
+// Purpose  :
+// ================================================================
+bool WasmOcctView::openGltfFromMemory (const std::string& theName,
+                                       const bool theToExpand,
+                                       uintptr_t theBuffer, int theDataLen,
+                                       bool theToFree)
+{
+  removeObjectList (theName, false);
+
+  WasmOcctView& aViewer = Instance();
+
+  char* aRawData = reinterpret_cast<char*>(theBuffer);
+  Standard_ArrayStreamBuffer aStreamBuffer (aRawData, theDataLen);
+  std::istream aStream (&aStreamBuffer);
+
+  Handle(WasmOcctObject) aShapePrs = new WasmOcctObject();
+
+  RWMesh_CoordinateSystemConverter aTrsf;
+  aTrsf.SetInputLengthUnit (1.0); // meters
+  aTrsf.SetInputCoordinateSystem (RWMesh_CoordinateSystem_glTF);
+  aTrsf.SetOutputLengthUnit (1.0); // meters
+  aTrsf.SetOutputCoordinateSystem(RWMesh_CoordinateSystem_Zup);
+
+  RWGltf_GltfJsonParser aParser (aShapePrs->ChangeShapes());
+  aParser.SetFilePath (theName.c_str());
+  aParser.SetErrorPrefix (TCollection_AsciiString ("File '") + theName.c_str() + "' defines invalid glTF!\n");
+  aParser.SetAttributeMap (aShapePrs->ChangeAttributes());
+  aParser.SetCoordinateSystemConverter (aTrsf);
+  //aParser.SetSkipEmptyNodes (myToSkipEmptyNodes);
+  //aParser.SetMeshNameAsFallback (myUseMeshNameAsFallback);
+  bool isParsed = parseGltfFromMemory (aParser, aStream, theName.c_str());
+  if (isParsed)
+  {
+    Handle(RWGltf_PrimitiveArrayReader) aReader = new WasmTriangulationReader (aStream, theName.c_str());
+    aReader->SetCoordinateSystemConverter (aTrsf);
+    for (NCollection_Vector<TopoDS_Face>::Iterator aFaceIter (aParser.FaceList()); aFaceIter.More(); aFaceIter.Next())
+    {
+      TopoDS_Face& aFace = aFaceIter.ChangeValue();
+      TopLoc_Location aDummyLoc;
+      Handle(RWGltf_GltfLatePrimitiveArray) aLateData = Handle(RWGltf_GltfLatePrimitiveArray)::DownCast (BRep_Tool::Triangulation (aFace, aDummyLoc));
+      Handle(Poly_Triangulation) aPolyData = aReader->Load (aLateData);
+      BRep_Builder aBuilder;
+      aBuilder.UpdateFace (aFace, aPolyData);
+    }
+  }
+  if (theToFree)
+  {
+    free (aRawData);
+  }
+
+  if (!isParsed)
+  {
+    return false;
+  }
+
+  if (theToExpand)
+  {
+    /// TODO this is just a dummy logic for testing - expanding should be done by assembly tree, not faces!
+    Standard_Integer aSubIndex = 0;
+    for (TopTools_SequenceOfShape::Iterator aShapeIter (aShapePrs->ChangeShapes()); aShapeIter.More(); aShapeIter.Next())
+    {
+      for (TopExp_Explorer aFaceIter (aShapeIter.Value(), TopAbs_FACE); aFaceIter.More(); aFaceIter.Next())
+      {
+        const TopoDS_Shape& aSubShape = aFaceIter.Current();
+        Handle(WasmOcctObject) aSubShapePrs = new WasmOcctObject();
+        aSubShapePrs->SetShape (aSubShape);
+        if (const RWMesh_NodeAttributes* anAttribs = aShapePrs->ChangeAttributes().Seek (aSubShape.Located (TopLoc_Location())))
+        {
+          aSubShapePrs->ChangeAttributes().Bind (aSubShape.Located (TopLoc_Location()), *anAttribs);
+        }
+        if (!theName.empty())
+        {
+          ++aSubIndex;
+          TCollection_AsciiString aName = TCollection_AsciiString (theName.c_str()) + "/" + aSubIndex;
+          aViewer.myObjects.Add (aName, aSubShapePrs);
+        }
+        aViewer.Context()->Display (aSubShapePrs, 0, 0, false);
+      }
+    }
+  }
+  else
+  {
+    if (!theName.empty())
+    {
+      aViewer.myObjects.Add (theName.c_str(), aShapePrs);
+    }
+    aViewer.Context()->Display (aShapePrs, 0, 0, false);
+  }
+
+  aViewer.View()->FitAll (0.01, false);
+  aViewer.View()->Invalidate();
+  aViewer.updateView();
+
+  setShowGround (aViewer.myToShowGround, false);
+
+  Message::DefaultMessenger()->Send (TCollection_AsciiString("Loaded file ") + theName.c_str(), Message_Info);
+  Message::DefaultMessenger()->Send (OSD_MemInfo::PrintInfo(), Message_Trace);
+  if (!theName.empty())
+  {
+    aViewer.onDisplayedObjectsChanged();
+  }
   return true;
 }
 
 // ================================================================
-// Function : displayGround
+// Function : toShowGround
+// Purpose  :
+// ================================================================
+bool WasmOcctView::toShowGround()
+{
+  return Instance().myToShowGround;
+}
+
+// ================================================================
+// Function : setShowGround
 // Purpose  :
 // ================================================================
-void WasmOcctView::displayGround (bool theToShow)
+void WasmOcctView::setShowGround (bool theToShow,
+                                  bool theToUpdate)
 {
-  static Handle(AIS_Shape) aGroundPrs = new AIS_Shape (TopoDS_Shape());
+  static Handle(WasmOcctObject) aGroundPrs = new WasmOcctObject();
 
   WasmOcctView& aViewer = Instance();
+  aViewer.myToShowGround = theToShow;
   Bnd_Box aBox;
   if (theToShow)
   {
@@ -1087,7 +1662,10 @@ void WasmOcctView::displayGround (bool theToShow)
       && aGroundPrs->HasInteractiveContext())
     {
       aViewer.Context()->Remove (aGroundPrs, false);
-      aViewer.UpdateView();
+      if (theToUpdate)
+      {
+        aViewer.UpdateView();
+      }
     }
     return;
   }
@@ -1120,21 +1698,37 @@ void WasmOcctView::displayGround (bool theToShow)
   aGroundPrs->SetToUpdate();
   aGroundPrs->SetMaterial (Graphic3d_NameOfMaterial_Stone);
   aGroundPrs->SetInfiniteState (false);
-  aViewer.Context()->Display (aGroundPrs, AIS_Shaded, -1, false);
+  aViewer.Context()->Display (aGroundPrs, 0, -1, false);
   aGroundPrs->SetInfiniteState (true);
-  aViewer.UpdateView();
+  if (theToUpdate)
+  {
+    aViewer.UpdateView();
+  }
 }
 
 // Module exports
 EMSCRIPTEN_BINDINGS(OccViewerModule) {
-  emscripten::function("setCubemapBackground", &WasmOcctView::setCubemapBackground);
-  emscripten::function("fitAllObjects",    &WasmOcctView::fitAllObjects);
-  emscripten::function("removeAllObjects", &WasmOcctView::removeAllObjects);
-  emscripten::function("removeObject",     &WasmOcctView::removeObject);
-  emscripten::function("eraseObject",      &WasmOcctView::eraseObject);
-  emscripten::function("displayObject",    &WasmOcctView::displayObject);
-  emscripten::function("displayGround",    &WasmOcctView::displayGround);
-  emscripten::function("openFromUrl",      &WasmOcctView::openFromUrl);
-  emscripten::function("openFromMemory",   &WasmOcctView::openFromMemory, emscripten::allow_raw_pointers());
-  emscripten::function("openBRepFromMemory", &WasmOcctView::openBRepFromMemory, emscripten::allow_raw_pointers());
+  emscripten::function("toDynamicHighlight",   &WasmOcctView::toDynamicHighlight);
+  emscripten::function("setDynamicHighlight",  &WasmOcctView::setDynamicHighlight);
+  emscripten::function("toCastShadows",        &WasmOcctView::toCastShadows);
+  emscripten::function("setCastShadows",       &WasmOcctView::setCastShadows);
+  //emscripten::property("toCastShadows", &WasmOcctView::toCastShadows, &WasmOcctView::setCastShadows); // this could be only in class
+  emscripten::function("isAntiAliasingOn",     &WasmOcctView::isAntiAliasingOn);
+  emscripten::function("setAntiAliasingOn",    &WasmOcctView::setAntiAliasingOn);
+  emscripten::function("setBackgroundColor",   &WasmOcctView::setBackgroundColor);
+  emscripten::function("setBackgroundCubemap", &WasmOcctView::setBackgroundCubemap);
+  emscripten::function("fitAllObjects",        &WasmOcctView::fitAllObjects);
+  emscripten::function("removeAllObjects",     &WasmOcctView::removeAllObjects);
+  emscripten::function("removeObjectList",     &WasmOcctView::removeObjectList);
+  emscripten::function("eraseObjectList",      &WasmOcctView::eraseObjectList);
+  emscripten::function("displayedObjects",     &WasmOcctView::displayedObjects);
+  emscripten::function("selectedObjects",      &WasmOcctView::selectedObjects);
+  emscripten::function("erasedObjects",        &WasmOcctView::erasedObjects);
+  emscripten::function("displayObjectList",    &WasmOcctView::displayObjectList);
+  emscripten::function("toShowGround",         &WasmOcctView::toShowGround);
+  emscripten::function("setShowGround",        &WasmOcctView::setShowGround);
+  emscripten::function("openFromUrl",          &WasmOcctView::openFromUrl);
+  emscripten::function("openFromMemory",       &WasmOcctView::openFromMemory, emscripten::allow_raw_pointers());
+  emscripten::function("openBRepFromMemory",   &WasmOcctView::openBRepFromMemory, emscripten::allow_raw_pointers());
+  emscripten::function("openGltfFromMemory",   &WasmOcctView::openGltfFromMemory, emscripten::allow_raw_pointers());
 }
index 3acf35e86e405e0841206403166843e77d66a8e8..eee78aa4f562e5de29ae270147be5b521d1eca44 100644 (file)
@@ -41,64 +41,125 @@ public:
 
 public: //! @name methods exported by Module
 
+  //! Returns TRUE if dynamic highlighting is turned ON.
+  static bool toDynamicHighlight();
+
+  //! Set if dynamic highlighting should be enabled or not.
+  static void setDynamicHighlight (bool theToEnable);
+
+  //! Returns TRUE if shadows are turned ON.
+  static bool toCastShadows();
+
+  //! Turn shadows on/off.
+  static void setCastShadows (bool theUseShadows,
+                              bool theToUpdate);
+
+  //! Returns TRUE if anti-aliasing is turned ON.
+  static bool isAntiAliasingOn();
+
+  //! Turn antialiasing on/off.
+  static void setAntiAliasingOn (bool theToEnable, bool theToUpdate);
+
+  //! Set solid color background.
+  static void setBackgroundColor (float theR, float theG, float theB,
+                                  bool theToUpdate);
+
   //! Set cubemap background.
   //! File will be loaded asynchronously.
   //! @param theImagePath [in] image path to load
-  static void setCubemapBackground (const std::string& theImagePath);
+  static void setBackgroundCubemap (const std::string& theImagePath);
 
   //! Clear all named objects from viewer.
-  static void removeAllObjects();
+  static void removeAllObjects (bool theToUpdate);
 
   //! Fit all/selected objects into view.
   //! @param theAuto [in] fit selected objects (TRUE) or all objects (FALSE)
-  static void fitAllObjects (bool theAuto);
+  static void fitAllObjects (bool theAuto,
+                             bool theToUpdate);
+
+  //! Return the list of displayed objects.
+  static std::string displayedObjects();
+
+  //! Return the list of selected objects.
+  static std::string selectedObjects();
+
+  //! Return the list of erased objects.
+  static std::string erasedObjects();
 
-  //! Remove named object from viewer.
-  //! @param theName [in] object name
+  //! Display temporarily hidden object.
+  //! @param theNames [in] object name list
   //! @return FALSE if object was not found
-  static bool removeObject (const std::string& theName);
+  static bool displayObjectList (const std::string& theNames,
+                                 bool theToUpdate);
 
-  //! Temporarily hide named object.
-  //! @param theName [in] object name
+  //! Remove named objects from viewer.
+  //! @param theNames [in] object name found
   //! @return FALSE if object was not found
-  static bool eraseObject (const std::string& theName);
+  static bool removeObjectList (const std::string& theNames,
+                                bool theToUpdate);
 
-  //! Display temporarily hidden object.
-  //! @param theName [in] object name
+  //! Temporarily hide named objects.
+  //! @param theNames [in] object name list
   //! @return FALSE if object was not found
-  static bool displayObject (const std::string& theName);
+  static bool eraseObjectList (const std::string& theNames,
+                               bool theToUpdate);
+
+  //! Return TRUE if ground is displayed.
+  static bool toShowGround();
 
   //! Show/hide ground.
   //! @param theToShow [in] show or hide flag
-  static void displayGround (bool theToShow);
+  static void setShowGround (bool theToShow,
+                             bool theToUpdate);
 
   //! Open object from the given URL.
   //! File will be loaded asynchronously.
   //! @param theName      [in] object name
   //! @param theModelPath [in] model path
+  //! @param theToExpand  [in] expand model or display as a single part
   static void openFromUrl (const std::string& theName,
-                           const std::string& theModelPath);
+                           const std::string& theModelPath,
+                           const bool theToExpand);
 
   //! Open object from memory.
-  //! @param theName    [in] object name
-  //! @param theBuffer  [in] pointer to data
-  //! @param theDataLen [in] data length
-  //! @param theToFree  [in] free theBuffer if set to TRUE
+  //! @param theName     [in] object name
+  //! @param theToExpand [in] expand model or display as a single part
+  //! @param theBuffer   [in] pointer to data
+  //! @param theDataLen  [in] data length
+  //! @param theToFree   [in] free theBuffer if set to TRUE
   //! @return FALSE on reading error
   static bool openFromMemory (const std::string& theName,
+                              const bool theToExpand,
                               uintptr_t theBuffer, int theDataLen,
                               bool theToFree);
 
   //! Open BRep object from memory.
-  //! @param theName    [in] object name
-  //! @param theBuffer  [in] pointer to data
-  //! @param theDataLen [in] data length
-  //! @param theToFree  [in] free theBuffer if set to TRUE
+  //! @param theName     [in] object name
+  //! @param theToExpand [in] expand model or display as a single part
+  //! @param theBuffer   [in] pointer to data
+  //! @param theDataLen  [in] data length
+  //! @param theToFree   [in] free theBuffer if set to TRUE
   //! @return FALSE on reading error
   static bool openBRepFromMemory (const std::string& theName,
+                                  const bool theToExpand,
+                                  uintptr_t theBuffer, int theDataLen,
+                                  bool theToFree);
+
+  //! Open glTF object from memory.
+  //! @param theName     [in] object name
+  //! @param theToExpand [in] expand model or display as a single part
+  //! @param theBuffer   [in] pointer to data
+  //! @param theDataLen  [in] data length
+  //! @param theToFree   [in] free theBuffer if set to TRUE
+  //! @return FALSE on reading error
+  static bool openGltfFromMemory (const std::string& theName,
+                                  const bool theToExpand,
                                   uintptr_t theBuffer, int theDataLen,
                                   bool theToFree);
 
+  //! Displayed map changed notification.
+  void onDisplayedObjectsChanged();
+
 public:
 
   //! Default constructor.
@@ -209,6 +270,11 @@ private:
   static EM_BOOL onKeyUpCallback (int theEventType, const EmscriptenKeyboardEvent* theEvent, void* theView)
   { return ((WasmOcctView* )theView)->onKeyUpEvent (theEventType, theEvent); }
 
+  //! Callback called by handleMoveTo() on Selection in 3D Viewer.
+  //! This method is expected to be called from rendering thread.
+  virtual void OnSelectionChanged (const Handle(AIS_InteractiveContext)& theCtx,
+                                   const Handle(V3d_View)& theView) override;
+
 private:
 
   //! Register hot-keys for specified Action.
@@ -237,6 +303,8 @@ private:
 private:
 
   NCollection_IndexedDataMap<TCollection_AsciiString, Handle(AIS_InteractiveObject)> myObjects; //!< map of named objects
+  bool myToShowGround = true;
+
 
   NCollection_DataMap<unsigned int, Aspect_VKey> myNavKeyMap; //!< map of Hot-Key (key+modifiers) to Action
 
index d39f55e2616e080435e7d8670c3645517abf4e44..76f7dff7196718bca89d8b996c7a0897a2230a00 100644 (file)
@@ -20,7 +20,7 @@ extern "C" void onMainLoop()
 
 EMSCRIPTEN_KEEPALIVE int main()
 {
-  Message::DefaultMessenger()->Printers().First()->SetTraceLevel (Message_Trace);
+  Message::DefaultMessenger()->Printers().First()->SetTraceLevel (Message_Info);
   Handle(Message_PrinterSystemLog) aJSConsolePrinter = new Message_PrinterSystemLog ("webgl-sample", Message_Trace);
   Message::DefaultMessenger()->AddPrinter (aJSConsolePrinter); // open JavaScript console within the Browser to see this output
   Message::DefaultMessenger()->Send (TCollection_AsciiString("NbLogicalProcessors: ") + OSD_Parallel::NbLogicalProcessors(), Message_Trace);
index c04add8b47a38b855f5b7dd2d06ace29364eac5a..2970206466dcb83419f6e3030a09b206b507df85 100644 (file)
@@ -9,17 +9,29 @@
 \r
 <h2>OCCT WebGL Viewer Sample</h2>\r
 <div>\r
-  <canvas id=occViewerCanvas oncontextmenu=event.preventDefault() tabindex=-1 style="border:0 none;background-color:#000" width="409" height="409"></canvas>\r
-  <img id=occlogo src="OCC_logo.png" style="position: absolute; left: 20px; top: 0px; z-index: 2;" />\r
+  <canvas id=occViewerCanvas  oncontextmenu=event.preventDefault() tabindex=-1 style="border:0 none;background-color:#000" width="409" height="409"></canvas>\r
 </div>\r
 \r
 <div>\r
-  <label for="fileInput">Choose BREP file to upload: </label><input type="file" id="fileInput" accept=".brep">\r
-  <input type="button" value="Clear All" onclick="OccViewerModule.removeAllObjects()">\r
-  <input type="button" value="Fit All"   onclick="OccViewerModule.fitAllObjects(true)">\r
+  <input type="checkbox" id="inputCheckShadows"><label for="inputCheckShadows">Shadows</label>\r
+  <input type="checkbox" id="inputAntiAliasing"><label for="inputAntiAliasing">Antialiasing</label>\r
+  <input type="checkbox" id="inputCheckGround" ><label for="inputCheckGround">Ground</label>\r
+  <input type="checkbox" id="inputCheckSkyBox" ><label for="inputCheckSkyBox">SkyBox</label>\r
+  <input type="checkbox" id="inputCheckHighlight"><label for="inputCheckHighlight">Dynamic highlight</label>\r
+  <br>\r
+  <input type="button" value="Clear All"   onclick="OccViewerModule.removeAllObjects(true)">\r
+  <input type="file" id="inputUploadModel" accept=".gltf, .glb">\r
+  <input type="button" value="Upload File" onclick="doInputUploadModel()">\r
+  <input type="button" value="Fit All"     onclick="OccViewerModule.fitAllObjects(true, true)">\r
+  <input type="button" value="Hide selected" onclick="doEraseSelected()">\r
+  <input type="button" value="Show hidden" onclick="doShowErased()">\r
+  <br>\r
+  <input type="checkbox" id="inputCheckExpand" ><label for="inputCheckExpand">Expand model</label>\r
 </div>\r
 <h4>Console output:</h4>\r
 <p id="output"></p>\r
+\r
+<script type="text/javascript" src="occt-webgl-sample.js" charset="utf-8"></script>\r
 <script>\r
 //! Resize canvas to fit into window.\r
 function updateCanvasSize()\r
@@ -36,8 +48,6 @@ function updateCanvasSize()
   var aDevicePixelRatio = window.devicePixelRatio || 1;\r
   occViewerCanvas.width  = aSizeX * aDevicePixelRatio;\r
   occViewerCanvas.height = aSizeY * aDevicePixelRatio;\r
-\r
-  occlogo.style.top = (aSizeY - 30) + "px";\r
 }\r
 window.onresize = updateCanvasSize;\r
 updateCanvasSize();\r
@@ -63,25 +73,89 @@ if (!isWasmSupported())
   anElement.innerHTML += "Browser is too old - WebAssembly support is missing!<br>Please check updates or install a modern browser.<br>";\r
 }\r
 \r
+var OccViewerModule =\r
+{\r
+  print: (function() {\r
+    var anElement = document.getElementById('output');\r
+    return function(theText) { anElement.innerHTML += theText + "<br>"; };\r
+  })(),\r
+  printErr: function(theText) { console.warn(theText); },\r
+  canvas: (function() {\r
+    var aCanvas = document.getElementById('occViewerCanvas');\r
+    //var aGlCtx =                   aCanvas.getContext ('webgl2', { alpha: false, depth: true, antialias: false, preserveDrawingBuffer: true } );\r
+    //if (aGlCtx == null) { aGlCtx = aCanvas.getContext ('webgl',  { alpha: false, depth: true, antialias: false, preserveDrawingBuffer: true } ); }\r
+    return aCanvas;\r
+  })(),\r
+\r
+  onRuntimeInitialized: function() {\r
+    //console.log(" @@ onRuntimeInitialized()" + Object.getOwnPropertyNames(OccViewerModule));\r
+  },\r
+\r
+  onDisplayedObjectsChanged: function() {\r
+    console.log(" @@ onDisplayedObjectsChanged() " + OccViewerModule.displayedObjects());\r
+  },\r
+  onSelectedObjectsChanged: function() {\r
+    console.log(" @@ onSelectedObjectsChanged() " + OccViewerModule.selectedObjects());\r
+  }\r
+};\r
+\r
+const OccViewerModuleInitialized = createOccViewerModule(OccViewerModule);\r
+OccViewerModuleInitialized.then(function(Module) {\r
+  //var aSkyBox = "textures/cubemap512.jpg"; // "textures/cubemap2048.jpg";\r
+  var aSkyBox = "textures/cubemap2048.jpg";\r
+  var aDefModel = "models/yellow_up.glb";\r
+  OccViewerModule.setBackgroundCubemap (aSkyBox);\r
+  OccViewerModule.openFromUrl (aDefModel, aDefModel, false);\r
+\r
+  inputCheckShadows.checked  = OccViewerModule.toCastShadows();\r
+  inputCheckShadows.onchange = function() { OccViewerModule.setCastShadows (inputCheckShadows.checked, true); }\r
+  inputAntiAliasing.checked  = OccViewerModule.isAntiAliasingOn();\r
+  inputAntiAliasing.onchange = function() { OccViewerModule.setAntiAliasingOn (inputAntiAliasing.checked, true); }\r
+  inputCheckGround .checked  = OccViewerModule.toShowGround();\r
+  inputCheckGround .onchange = function() { OccViewerModule.setShowGround (inputCheckGround.checked, true); }\r
+  inputCheckHighlight.checked  = OccViewerModule.toDynamicHighlight();\r
+  inputCheckHighlight.onchange = function() { OccViewerModule.setDynamicHighlight (inputCheckHighlight.checked); }\r
+  inputCheckSkyBox .checked  = true;\r
+  inputCheckSkyBox .onchange = function() {\r
+    if (inputCheckSkyBox.checked) { OccViewerModule.setBackgroundCubemap (aSkyBox); }\r
+    else { OccViewerModule.setBackgroundColor (0.0, 0.0, 0.0, true); }\r
+  }\r
+  inputCheckExpand.checked  = false;\r
+  inputCheckExpand.onchange = function() {\r
+    OccViewerModule.removeAllObjects (true);\r
+    OccViewerModule.openFromUrl (aDefModel, aDefModel, inputCheckExpand.checked);\r
+  }\r
+});\r
+\r
+//! Handle erasing.\r
+function doEraseSelected()\r
+{\r
+  var aSelected = OccViewerModule.selectedObjects();\r
+  OccViewerModule.eraseObjectList (aSelected, true);\r
+}\r
+\r
+//! Handle show all.\r
+function doShowErased()\r
+{\r
+  var anErased = OccViewerModule.erasedObjects();\r
+  OccViewerModule.displayObjectList (anErased, true);\r
+}\r
+\r
 //! Handle file uploading.\r
-fileInput.onchange = function()\r
+function doInputUploadModel()\r
 {\r
-  if (fileInput.files.length == 0) { return; }\r
-  // Warning! Entire file is pre-loaded into memory.\r
-  var aFile = fileInput.files[0];\r
-  var aReader = new FileReader();\r
-  aReader.onload = function()\r
-  {\r
+  if (inputUploadModel.files.length == 0) { return; }\r
+  var aFile = inputUploadModel.files[0];\r
+  var aReader = new FileReader(); // Warning! Entire file is pre-loaded into memory\r
+  aReader.onload = function() {\r
     var aDataArray = new Uint8Array (aReader.result);\r
     const aDataBuffer = OccViewerModule._malloc (aDataArray.length);\r
     OccViewerModule.HEAPU8.set (aDataArray, aDataBuffer);\r
-    OccViewerModule.openFromMemory (aFile.name, aDataBuffer, aDataArray.length, true);\r
-    //OccViewerModule._free (aDataBuffer); will be freed by called method\r
-    OccViewerModule.displayGround (true);\r
+    OccViewerModule.openFromMemory (aFile.name, inputCheckExpand.checked, aDataBuffer, aDataArray.length, true);\r
   };\r
-  aReader.readAsArrayBuffer(aFile);\r
+  aReader.readAsArrayBuffer (aFile);\r
 };\r
+\r
 </script>\r
-<script type="text/javascript" src="occt-webgl-sample.js" charset="utf-8"></script>\r
 </body>\r
 </html>\r
diff --git a/samples/webgl/occt-webgl-viewer.js b/samples/webgl/occt-webgl-viewer.js
deleted file mode 100644 (file)
index 484b519..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-var OccViewerModule =\r
-{\r
-  print: (function() {\r
-    var anElement = document.getElementById('output');\r
-    return function(theText) { anElement.innerHTML += theText + "<br>"; };\r
-  })(),\r
-  printErr: function(theText) {\r
-    //var anElement = document.getElementById('output');\r
-    //anElement.innerHTML += theText + "<br>";\r
-    console.warn(theText);\r
-  },\r
-  canvas: (function() {\r
-    var aCanvas = document.getElementById('occViewerCanvas');\r
-    var aGlCtx =                   aCanvas.getContext ('webgl2', { alpha: false, depth: true, antialias: false, preserveDrawingBuffer: true } );\r
-    if (aGlCtx == null) { aGlCtx = aCanvas.getContext ('webgl',  { alpha: false, depth: true, antialias: false, preserveDrawingBuffer: true } ); }\r
-    return aCanvas;\r
-  })(),\r
-\r
-  onRuntimeInitialized: function() {\r
-    //console.log(" @@ onRuntimeInitialized()" + Object.getOwnPropertyNames(OccViewerModule));\r
-  }\r
-};\r
-\r
-const OccViewerModuleInitialized = createOccViewerModule(OccViewerModule);\r
-OccViewerModuleInitialized.then(function(Module) {\r
-  //OccViewerModule.setCubemapBackground ("cubemap.jpg");\r
-  OccViewerModule.openFromUrl ("ball", "samples/Ball.brep");\r
-});\r
diff --git a/samples/webgl/threejs-sample.html b/samples/webgl/threejs-sample.html
new file mode 100644 (file)
index 0000000..1bcf01d
--- /dev/null
@@ -0,0 +1,110 @@
+<!DOCTYPE html>\r
+<html lang=en-us>\r
+<head>\r
+<meta charset=utf-8><meta content="text/html; charset=utf-8" http-equiv=Content-Type>\r
+<link rel="shortcut icon" href="lamp.ico" type="image/x-icon" />\r
+<title>Three.js Viewer Sample</title>\r
+</head>\r
+<body>\r
+\r
+<h2>Three.js Viewer Sample</h2>\r
+<div>\r
+  <canvas id=occViewerCanvas oncontextmenu=event.preventDefault() tabindex=-1 style="border:0 none;background-color:#000" width="409" height="409"></canvas>\r
+</div>\r
+\r
+<div>\r
+  <input type="checkbox" id="inputCheckShadows"><label for="inputCheckShadows">Shadows</label>\r
+  <input type="checkbox" id="inputAntiAliasing"><label for="inputAntiAliasing">Antialiasing</label>\r
+  <input type="checkbox" id="inputCheckGround" ><label for="inputCheckGround">Ground</label>\r
+  <input type="checkbox" id="inputCheckSkyBox" ><label for="inputCheckSkyBox">SkyBox</label>\r
+  <input type="checkbox" id="inputCheckHighlight"><label for="inputCheckHighlight">Dynamic highlight</label>\r
+  <br>\r
+  <input type="button" value="Clear All"   onclick="OccViewerModule.removeAllObjects(true)">\r
+  <input type="file" id="inputUploadModel" accept=".gltf, .glb">\r
+  <input type="button" value="Upload File" onclick="doInputUploadModel()">\r
+  <input type="button" value="Fit All"     onclick="OccViewerModule.fitAllObjects(true, true)">\r
+  <input type="button" value="Hide selected" onclick="doEraseSelected()">\r
+  <input type="button" value="Show hidden" onclick="doShowErased()">\r
+  <br>\r
+  <input type="checkbox" id="inputCheckExpand" ><label for="inputCheckExpand">Expand model</label>\r
+</div>\r
+<h4>Console output:</h4>\r
+<p id="output"></p>\r
+\r
+<script src="threejs/three.min.js"></script>\r
+<script src="threejs/OrbitControls.js"></script>\r
+<script src="threejs/GLTFLoader.js"></script>\r
+<script src="threejs/BufferGeometryUtils.js"></script>\r
+<script src="threejs/stats.min.js"></script>\r
+<script src="threejs-sample.js"></script>\r
+<script>\r
+//! Resize canvas to fit into window.\r
+function updateCanvasSize()\r
+{\r
+  // size of canvas in logical (density-independent) units\r
+  var aSizeX = Math.min (window.innerWidth,  window.screen.availWidth);\r
+  var aSizeY = Math.min (window.innerHeight, window.screen.availHeight);\r
+  aSizeX = Math.max (300, aSizeX - 30);\r
+  aSizeY = Math.max (300, aSizeY / 2);\r
+  occViewerCanvas.style.width  = aSizeX + "px";\r
+  occViewerCanvas.style.height = aSizeY + "px";\r
+\r
+  // drawing buffer size (aka backing store)\r
+  var aDevicePixelRatio = window.devicePixelRatio || 1;\r
+  occViewerCanvas.width  = aSizeX * aDevicePixelRatio;\r
+  occViewerCanvas.height = aSizeY * aDevicePixelRatio;\r
+}\r
+window.onresize = updateCanvasSize;\r
+updateCanvasSize();\r
+\r
+var aSkyBox = "textures/landing_pad/";\r
+var aDefModel = "models/yellow_up.glb";\r
+var aDefName  = "yellow";\r
+var aDevicePixelRatio = window.devicePixelRatio || 1;\r
+inputAntiAliasing.checked = aDevicePixelRatio <= 1.25;\r
+var OccViewerModule = createOccThreejsViewer (document.getElementById ('occViewerCanvas'), aSkyBox, inputAntiAliasing.checked);\r
+OccViewerModule.openFromUrl (aDefName, aDefModel, false);\r
+//OccViewerModule.openFromUrl ("stork", "models/Stork.glb");\r
+//OccViewerModule.openFromUrl ("spheres", "models/spheres.glb");\r
+\r
+inputCheckShadows.checked  = OccViewerModule.toCastShadows();\r
+inputCheckShadows.onchange = function() { OccViewerModule.setCastShadows (inputCheckShadows.checked, true); }\r
+inputAntiAliasing.onchange = function() {}\r
+inputCheckGround .checked  = OccViewerModule.toShowGround();\r
+inputCheckGround .onchange = function() { OccViewerModule.setShowGround (inputCheckGround.checked, true); }\r
+inputCheckHighlight.checked  = OccViewerModule.toDynamicHighlight();\r
+inputCheckHighlight.onchange = function() { OccViewerModule.setDynamicHighlight (inputCheckHighlight.checked); }\r
+inputCheckSkyBox .checked  = true;\r
+inputCheckSkyBox .onchange = function() {\r
+  if (inputCheckSkyBox.checked) { OccViewerModule.setBackgroundCubemap (aSkyBox, true); }\r
+  else { OccViewerModule.setBackgroundColor (0.0, 0.0, 0.0, true); }\r
+}\r
+inputCheckExpand.checked  = false;\r
+inputCheckExpand.onchange = function() {\r
+  OccViewerModule.removeAllObjects (true);\r
+  OccViewerModule.openFromUrl (aDefName, aDefModel, inputCheckExpand.checked);\r
+}\r
+\r
+//! Handle file uploading.\r
+inputUploadModel.onchange = function()\r
+{\r
+  if (inputUploadModel.files.length == 0) { return; }\r
+  // Warning! Entire file is pre-loaded into memory.\r
+  var aFile = inputUploadModel.files[0];\r
+  /*var aReader = new FileReader();\r
+  aReader.onload = function()\r
+  {\r
+    var aDataArray = new Uint8Array (aReader.result);\r
+    const aDataBuffer = OccViewerModule._malloc (aDataArray.length);\r
+    OccViewerModule.HEAPU8.set (aDataArray, aDataBuffer);\r
+    //OccViewerModule.openBRepFromMemory (aFile.name, aDataBuffer, aDataArray.length, true);\r
+    OccViewerModule.openGltfFromMemory (aFile.name, aDataBuffer, aDataArray.length, true);\r
+    //OccViewerModule._free (aDataBuffer); will be freed by called method\r
+    OccViewerModule.displayGround (true);\r
+  };\r
+  aReader.readAsArrayBuffer(aFile);*/\r
+  //OccViewerModule.openFromUrl ("object", aFile.name);\r
+};\r
+</script>\r
+</body>\r
+</html>\r
diff --git a/samples/webgl/threejs-sample.js b/samples/webgl/threejs-sample.js
new file mode 100644 (file)
index 0000000..51da2c6
--- /dev/null
@@ -0,0 +1,354 @@
+class OccThreejsViewer\r
+{\r
+  myScene = new THREE.Scene();\r
+  myRaycaster = new THREE.Raycaster();\r
+  myObjects = new Map();\r
+  myLastPicked = null;\r
+  myLastPickedName = "";\r
+  myToDynamicHighlight = true;\r
+  myDynHighlightColor = 0x00FFFF;\r
+  mySelectionColor = 0xFFFF00;\r
+  myGround = null;\r
+  myDirLight = new THREE.DirectionalLight (0xffffff, 1);\r
+\r
+  myFpsMeter = new Stats();\r
+\r
+  constructor (theCanvas, theCubemap, theAntiAlias)\r
+  {\r
+       var aViewer = this;\r
+\r
+    var aGlCtx                   = theCanvas.getContext ('webgl2', { alpha: false, depth: true, antialias: theAntiAlias, preserveDrawingBuffer: true } );\r
+    if (aGlCtx == null) { aGlCtx = theCanvas.getContext ('webgl',  { alpha: false, depth: true, antialias: theAntiAlias, preserveDrawingBuffer: true } ); }\r
+    this.myCamera = new THREE.PerspectiveCamera (45, theCanvas.width / theCanvas.height, 0.1, 1000);\r
+    this.myCamera.position.set (1, 1, 1);\r
+\r
+    this.myRenderer = new THREE.WebGLRenderer ({antialias: false, canvas: theCanvas, context: aGlCtx});\r
+    this.myRenderer.autoClear = true;\r
+    this.myRenderer.autoClearColor = true;\r
+    this.myRenderer.autoClearDepth = true;\r
+    this.myRenderer.autoClearStencil = true;\r
+    this.myRenderer.setSize (theCanvas.width, theCanvas.height);\r
+    this.myRenderer.outputEncoding = THREE.sRGBEncoding;\r
+    this.myRenderer.shadowMap.enabled = true;\r
+    //this.myRenderer.shadowMap.type = THREE.PCFSoftShadowMap; // default THREE.PCFShadowMap\r
+\r
+    //this.myScene.background = new THREE.Color('black');\r
+    this.setBackgroundCubemap (theCubemap, false);\r
+\r
+    this.myControls = new THREE.OrbitControls (this.myCamera, theCanvas);\r
+    this.myControls.target.set (0, 0, -0.2);\r
+    this.myControls.update();\r
+    this.myControls.addEventListener ('change', function() { aViewer.updateView(); });\r
+\r
+    this.myDirLight.position.set (-0.098, 0.98, -0.20);\r
+    this.myDirLight.position.normalize()\r
+    this.myDirLight.castShadow = true;\r
+    this.myScene.add (new THREE.HemisphereLight (0xffffff, 0x000000, 0.4));\r
+    this.myScene.add (this.myDirLight);\r
+    //const aShadowHelper = new THREE.CameraHelper (this.myDirLight.shadow.camera); this.myScene.add (aShadowHelper)\r
+\r
+    const aPlaneGeom = new THREE.PlaneBufferGeometry (15, 15, 32, 32);\r
+    const aPlaneMat  = new THREE.MeshStandardMaterial ({ color: 0xAAAAAA });\r
+    this.myGround = new THREE.Mesh (aPlaneGeom, aPlaneMat);\r
+    this.myGround.receiveShadow = true;\r
+    this.myGround.position.y = -1.0;\r
+    this.myGround.rotation.x = -1.57;\r
+    this.myScene.add (this.myGround);\r
+    this.updateView();\r
+\r
+    theCanvas.addEventListener ('click',     this.onClick, false);\r
+       theCanvas.addEventListener ('mousemove', this.onMouseMove, false);\r
+\r
+    // FPS meter\r
+    this.myFpsMeter.showPanel (0);\r
+    document.body.appendChild (this.myFpsMeter.dom);\r
+  }\r
+\r
+  toDynamicHighlight()             { return this.myToDynamicHighlight; }\r
+  setDynamicHighlight(theToEnable) { this.myToDynamicHighlight = theToEnable; }\r
+\r
+  onMouseMove()\r
+  {\r
+    if (event.buttons !== 0) { return; }\r
+\r
+    var aViewer = OccViewerModule;\r
+    if (!aViewer.myToDynamicHighlight) { return; }\r
+\r
+    var aTime1 = performance.now();\r
+    event.preventDefault();\r
+    var aMouse = new THREE.Vector2();\r
+    aMouse.x =  (event.clientX / aViewer.myRenderer.domElement.width)  * 2 - 1;\r
+    aMouse.y = -(event.clientY / aViewer.myRenderer.domElement.height) * 2 + 1;\r
+    aViewer.myRaycaster.setFromCamera (aMouse, aViewer.myCamera);\r
+    var anRes = aViewer.myRaycaster.intersectObject (aViewer.myScene, true);\r
+    var aNewPicked = anRes.length > 0 ? anRes[0].object : null;\r
+    if (aNewPicked !== aViewer.myLastPicked)\r
+    {\r
+      aViewer.myLastPickedName = "";\r
+      if (aViewer.myLastPicked !== null)\r
+      {\r
+        aViewer.myLastPicked.material.color.set (aViewer.myLastPicked.material.userData.oldColor);\r
+        aViewer.myLastPicked = null;\r
+      }\r
+      if (aNewPicked !== null)\r
+      {\r
+        for (var aParentIter = aNewPicked; aParentIter != null; aParentIter = aParentIter.parent)\r
+        {\r
+          if (aParentIter.name !== "")\r
+          {\r
+            if (aViewer.myLastPickedName !== "") { aViewer.myLastPickedName = "/" + aViewer.myLastPickedName; }\r
+            aViewer.myLastPickedName = aParentIter.name + aViewer.myLastPickedName;\r
+          }\r
+        }\r
+var aTime2 = performance.now();\r
+console.log ( "New picked '" + aViewer.myLastPickedName + "' in " + ((aTime2 - aTime1) * 0.001) + " s") ///\r
+\r
+        aNewPicked.material.userData.oldColor = aNewPicked.material.color.getHex();\r
+        aNewPicked.material.color.set (aViewer.myDynHighlightColor);\r
+        aViewer.myLastPicked = aNewPicked;\r
+      }\r
+      aViewer.updateView();\r
+    }\r
+  }\r
+\r
+  onClick()\r
+  {\r
+    var aViewer = OccViewerModule;\r
+    event.preventDefault();\r
+    if (aViewer.myLastPicked !== null)\r
+    {\r
+      aViewer.myLastPicked.material.color.set (aViewer.mySelectionColor);\r
+      aViewer.myLastPicked = null\r
+    }\r
+    aViewer.updateView();\r
+  }\r
+\r
+  setBackgroundColor (theR, theG, theB, theToUpdate)\r
+  {\r
+    this.myScene.background = new THREE.Color (theR, theG, theB);\r
+    if (theToUpdate) { this.updateView(); }\r
+  }\r
+\r
+  setBackgroundCubemap (theFolderPath, theToUpdate)\r
+  {\r
+    this.myScene.background = new THREE.CubeTextureLoader()\r
+      .setPath (theFolderPath)\r
+      .load (['px.jpg','nx.jpg','py.jpg','ny.jpg','pz.jpg','nz.jpg']);\r
+\r
+    /// TODO disable sRGB for consistency with OCCT sample workaround\r
+    //this.myScene.background.encoding = THREE.sRGBEncoding;\r
+\r
+    if (theToUpdate) { this.updateView(); }\r
+  }\r
+\r
+  /**\r
+   * Fit all/selected objects into view.\r
+   * @param {boolean} theAuto [in] fit selected objects (TRUE) or all objects (FALSE)\r
+   */\r
+  fitAllObjects(theAuto, theToUpdate)\r
+  {\r
+    const aFitOffset = 1.2\r
+    const aBox = new THREE.Box3();\r
+    //this.myScene.traverse (function (theChild) { aBox.expandByObject (theChild) });\r
+    for (let [aKey, anObjIter] of this.myObjects)\r
+    {\r
+      aBox.expandByObject (anObjIter);\r
+    }\r
+\r
+    const aSize   = aBox.getSize  (new THREE.Vector3());\r
+    const aCenter = aBox.getCenter(new THREE.Vector3());\r
+console.log (" @@ aSize= " + aSize.x + "x" + aSize.y + "x" + aSize.z) ///\r
+\r
+    const aMaxSize = Math.max (aSize.x, aSize.y, aSize.z);\r
+    const aFitHeightDist = aMaxSize / (2 * Math.atan (Math.PI * this.myCamera.fov / 360));\r
+    const aFitWidthDist = aFitHeightDist / this.myCamera.aspect;\r
+    const aDist = aFitOffset * Math.max (aFitHeightDist, aFitWidthDist);\r
+\r
+    const aDir = this.myControls.target.clone()\r
+      .sub (this.myCamera.position).normalize().multiplyScalar (aDist);\r
+    this.myControls.maxDistance = aDist * 10;\r
+    this.myControls.target.copy (aCenter);\r
+\r
+    this.myCamera.near = aDist / 100;\r
+    this.myCamera.far  = aDist * 100;\r
+    this.myCamera.updateProjectionMatrix();\r
+\r
+    this.myCamera.position.copy (this.myControls.target).sub (aDir);\r
+    this.myControls.update();\r
+    if (theToUpdate) { this.updateView(); }\r
+  }\r
+\r
+  /**\r
+   * Redraw the view.\r
+   */\r
+  updateView()\r
+  {\r
+    this.myFpsMeter.begin();\r
+\r
+    this.myRenderer.render (this.myScene, this.myCamera);\r
+\r
+    this.myFpsMeter.end();\r
+  }\r
+\r
+  /**\r
+   * Remove named object from viewer.\r
+   * @param {string}  theName [in] object name\r
+   * @param {boolean} theToUpdate [in] immediatly request viewer update\r
+   * @return {boolean} FALSE if object was not found\r
+   */\r
+  removeObject (theName, theToUpdate)\r
+  {\r
+    var anOldObj = this.myObjects.get (theName);\r
+    if (anOldObj !== undefined)\r
+    {\r
+      this.myScene.remove (anOldObj);\r
+      this.myObjects.delete (theName)\r
+      if (theToUpdate) { this.updateView(); }\r
+      return true;\r
+    }\r
+    return false;\r
+  }\r
+\r
+  /**\r
+   * Clear all named objects from viewer.\r
+   */\r
+  removeAllObjects (theToUpdate)\r
+  {\r
+    for (let [aKey, anObjIter] of this.myObjects)\r
+    {\r
+      this.myScene.remove (anObjIter);\r
+    }\r
+    this.myObjects.clear();\r
+    if (theToUpdate) { this.updateView(); }\r
+  }\r
+\r
+  /** Return TRUE if ground is displayed */\r
+  toShowGround() { return this.myGround.parent === this.myScene; }\r
+\r
+  /**\r
+   * Show/hide ground.\r
+   * @param theToShow [in] show or hide flag\r
+   */\r
+  setShowGround (theToShow, theToUpdate)\r
+  {\r
+    if (theToShow)\r
+    {\r
+      if (this.myGround.parent !== this.myScene)\r
+      {\r
+        this.myScene.add (this.myGround);\r
+      }\r
+    }\r
+    else\r
+    {\r
+      this.myScene.remove (this.myGround);\r
+    }\r
+    if (theToUpdate) { this.updateView(); }\r
+  }\r
+\r
+  /** Returns TRUE if shadows are turned ON */\r
+  toCastShadows() { return this.myDirLight.castShadow; }\r
+\r
+  /** Turn shadows on/off */\r
+  setCastShadows (theUseShadows, theToUpdate)\r
+  {\r
+    this.myDirLight.castShadow = theUseShadows;\r
+    if (theToUpdate) { this.updateView(); }\r
+  }\r
+\r
+  /**\r
+   * Open object from the given URL.\r
+   * File will be loaded asynchronously.\r
+   * @param {string} theName      [in] object name\r
+   * @param {string} theModelPath [in] model path\r
+   * @param {boolean} theToExpand [in] expand (explore) model or represent it as single object\r
+   */\r
+  openFromUrl (theName, theModelPath, theToExpand)\r
+  {\r
+    this.removeObject (theName, false);\r
+    var aViewer = this;\r
+\r
+    var aTime1 = performance.now();\r
+    const aLoader = new THREE.GLTFLoader();\r
+    //aLoader.setPath();\r
+    aLoader.load (theModelPath, function (theGltf) {\r
+      if (theToExpand)\r
+      {\r
+        theGltf.scene.traverse (function (theChild) {\r
+          if (theChild.isMesh)\r
+          {\r
+            theChild.material = theChild.material.clone(); // duplicate materials to dynamically highlight nodes\r
+            theChild.material.envMap = aViewer.myScene.background;\r
+            theChild.castShadow = true;\r
+            theChild.receiveShadow = true\r
+          }\r
+        });\r
+\r
+        var aModelRoot = theGltf.scene;\r
+        aModelRoot.name = theName;\r
+        aViewer.myScene.add (aModelRoot);\r
+        aViewer.myObjects.set (theName, aModelRoot);\r
+        aViewer.fitAllObjects();\r
+        return;\r
+      }\r
+\r
+      // merge meshes with common material\r
+      var isFirst = true\r
+      const aMatMap = new Map();\r
+      theGltf.scene.traverse (function (theChild) {\r
+        if (theChild.isMesh)\r
+        {\r
+          var aMatObjects = aMatMap.get (theChild.material.name)\r
+          if (aMatObjects === undefined)\r
+          {\r
+            var aMatObjects = [];\r
+            aMatMap.set (theChild.material.name, aMatObjects)\r
+          }\r
+          aMatObjects.push (theChild)\r
+        }\r
+      });\r
+\r
+      var aModelRoot = new THREE.Group();\r
+      aModelRoot.name = theName;\r
+      theGltf.scene.updateMatrixWorld();\r
+      for (let [aMatName, aMeshes] of aMatMap)\r
+      {\r
+        const aGeomList = [];\r
+        for (let i = 0; i < aMeshes.length; ++i)\r
+        {\r
+          const aMesh = aMeshes[i];\r
+          if (aMesh.geometry.applyMatrix4 != undefined)\r
+          {\r
+            aMesh.geometry.applyMatrix4 (aMesh.matrixWorld); // pre-apply transformation\r
+          }\r
+          else\r
+          {\r
+            aMesh.geometry.applyMatrix (aMesh.matrixWorld); // pre-apply transformation\r
+          }\r
+          aGeomList.push (aMesh.geometry);\r
+        }\r
+\r
+        const aMaterial = aMeshes[0].material;\r
+        aMaterial.envMap = aViewer.myScene.background;\r
+        const aGeom = THREE.BufferGeometryUtils.mergeBufferGeometries (aGeomList);\r
+        const aNode = new THREE.Mesh (aGeom, aMaterial);\r
+        aNode.name = "";\r
+        aNode.castShadow = true;\r
+        aNode.receiveShadow = true\r
+        aModelRoot.add (aNode);\r
+      }\r
+\r
+      aViewer.myScene.add (aModelRoot);\r
+      aViewer.myObjects.set (theName, aModelRoot);\r
+      aViewer.fitAllObjects();\r
+\r
+      var aTime2 = performance.now();\r
+      console.log ("glTF '" + theModelPath + "' loading time: " + ((aTime2 - aTime1) * 0.001) + " s");\r
+    });\r
+  }\r
+}\r
+\r
+function createOccThreejsViewer (theCanvas, theCubemap, theAntiAlias)\r
+{\r
+  let aViewer = new OccThreejsViewer (theCanvas, theCubemap, theAntiAlias);\r
+  return aViewer;\r
+}\r