From 14f6cac854aff1cf5edd882fe4535b8a27a5ff98 Mon Sep 17 00:00:00 2001 From: kgv Date: Wed, 20 Jan 2021 14:30:04 +0300 Subject: [PATCH] 0031373: Samples - provide sample combining OCCT and 3 viewer gltf reader --- samples/webgl/CMakeLists.txt | 15 +- samples/webgl/OccViewerModule.idl | 98 ++++ samples/webgl/WasmOcctObject.cpp | 251 ++++++++++ samples/webgl/WasmOcctObject.h | 80 +++ samples/webgl/WasmOcctView.cpp | 718 ++++++++++++++++++++++++--- samples/webgl/WasmOcctView.h | 112 ++++- samples/webgl/main.cpp | 2 +- samples/webgl/occt-webgl-sample.html | 112 ++++- samples/webgl/occt-webgl-viewer.js | 28 -- samples/webgl/threejs-sample.html | 110 ++++ samples/webgl/threejs-sample.js | 354 +++++++++++++ 11 files changed, 1745 insertions(+), 135 deletions(-) create mode 100644 samples/webgl/OccViewerModule.idl create mode 100644 samples/webgl/WasmOcctObject.cpp create mode 100644 samples/webgl/WasmOcctObject.h delete mode 100644 samples/webgl/occt-webgl-viewer.js create mode 100644 samples/webgl/threejs-sample.html create mode 100644 samples/webgl/threejs-sample.js diff --git a/samples/webgl/CMakeLists.txt b/samples/webgl/CMakeLists.txt index a3d7ef3052..157f482bc1 100644 --- a/samples/webgl/CMakeLists.txt +++ b/samples/webgl/CMakeLists.txt @@ -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 index 0000000000..923c8a25fe --- /dev/null +++ b/samples/webgl/OccViewerModule.idl @@ -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 index 0000000000..9e1691180d --- /dev/null +++ b/samples/webgl/WasmOcctObject.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//! 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 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::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 index 0000000000..19a61ec035 --- /dev/null +++ b/samples/webgl/WasmOcctObject.h @@ -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 +#include +#include + +//! 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 diff --git a/samples/webgl/WasmOcctView.cpp b/samples/webgl/WasmOcctView.cpp index ec8e5f7254..e09bca11de 100644 --- a/samples/webgl/WasmOcctView.cpp +++ b/samples/webgl/WasmOcctView.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include @@ -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(theBuffer), theDataLen, false); + ModelAsyncLoader* aTask = (ModelAsyncLoader* )theOpaque; + WasmOcctView::openFromMemory (aTask->Name, aTask->ToExpand, reinterpret_cast(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::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::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::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::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(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 +#include +#include +#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::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::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(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::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()); } diff --git a/samples/webgl/WasmOcctView.h b/samples/webgl/WasmOcctView.h index 3acf35e86e..eee78aa4f5 100644 --- a/samples/webgl/WasmOcctView.h +++ b/samples/webgl/WasmOcctView.h @@ -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 myObjects; //!< map of named objects + bool myToShowGround = true; + NCollection_DataMap myNavKeyMap; //!< map of Hot-Key (key+modifiers) to Action diff --git a/samples/webgl/main.cpp b/samples/webgl/main.cpp index d39f55e261..76f7dff719 100644 --- a/samples/webgl/main.cpp +++ b/samples/webgl/main.cpp @@ -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); diff --git a/samples/webgl/occt-webgl-sample.html b/samples/webgl/occt-webgl-sample.html index c04add8b47..2970206466 100644 --- a/samples/webgl/occt-webgl-sample.html +++ b/samples/webgl/occt-webgl-sample.html @@ -9,17 +9,29 @@

OCCT WebGL Viewer Sample

- - +
- - - + + + + + +
+ + + + + + +
+

Console output:

+ + - diff --git a/samples/webgl/occt-webgl-viewer.js b/samples/webgl/occt-webgl-viewer.js deleted file mode 100644 index 484b519231..0000000000 --- a/samples/webgl/occt-webgl-viewer.js +++ /dev/null @@ -1,28 +0,0 @@ -var OccViewerModule = -{ - print: (function() { - var anElement = document.getElementById('output'); - return function(theText) { anElement.innerHTML += theText + "
"; }; - })(), - printErr: function(theText) { - //var anElement = document.getElementById('output'); - //anElement.innerHTML += theText + "
"; - console.warn(theText); - }, - canvas: (function() { - var aCanvas = document.getElementById('occViewerCanvas'); - var aGlCtx = aCanvas.getContext ('webgl2', { alpha: false, depth: true, antialias: false, preserveDrawingBuffer: true } ); - if (aGlCtx == null) { aGlCtx = aCanvas.getContext ('webgl', { alpha: false, depth: true, antialias: false, preserveDrawingBuffer: true } ); } - return aCanvas; - })(), - - onRuntimeInitialized: function() { - //console.log(" @@ onRuntimeInitialized()" + Object.getOwnPropertyNames(OccViewerModule)); - } -}; - -const OccViewerModuleInitialized = createOccViewerModule(OccViewerModule); -OccViewerModuleInitialized.then(function(Module) { - //OccViewerModule.setCubemapBackground ("cubemap.jpg"); - OccViewerModule.openFromUrl ("ball", "samples/Ball.brep"); -}); diff --git a/samples/webgl/threejs-sample.html b/samples/webgl/threejs-sample.html new file mode 100644 index 0000000000..1bcf01d1cd --- /dev/null +++ b/samples/webgl/threejs-sample.html @@ -0,0 +1,110 @@ + + + + + +Three.js Viewer Sample + + + +

Three.js Viewer Sample

+
+ +
+ +
+ + + + + +
+ + + + + + +
+ +
+

Console output:

+

+ + + + + + + + + + diff --git a/samples/webgl/threejs-sample.js b/samples/webgl/threejs-sample.js new file mode 100644 index 0000000000..51da2c6233 --- /dev/null +++ b/samples/webgl/threejs-sample.js @@ -0,0 +1,354 @@ +class OccThreejsViewer +{ + myScene = new THREE.Scene(); + myRaycaster = new THREE.Raycaster(); + myObjects = new Map(); + myLastPicked = null; + myLastPickedName = ""; + myToDynamicHighlight = true; + myDynHighlightColor = 0x00FFFF; + mySelectionColor = 0xFFFF00; + myGround = null; + myDirLight = new THREE.DirectionalLight (0xffffff, 1); + + myFpsMeter = new Stats(); + + constructor (theCanvas, theCubemap, theAntiAlias) + { + var aViewer = this; + + var aGlCtx = theCanvas.getContext ('webgl2', { alpha: false, depth: true, antialias: theAntiAlias, preserveDrawingBuffer: true } ); + if (aGlCtx == null) { aGlCtx = theCanvas.getContext ('webgl', { alpha: false, depth: true, antialias: theAntiAlias, preserveDrawingBuffer: true } ); } + this.myCamera = new THREE.PerspectiveCamera (45, theCanvas.width / theCanvas.height, 0.1, 1000); + this.myCamera.position.set (1, 1, 1); + + this.myRenderer = new THREE.WebGLRenderer ({antialias: false, canvas: theCanvas, context: aGlCtx}); + this.myRenderer.autoClear = true; + this.myRenderer.autoClearColor = true; + this.myRenderer.autoClearDepth = true; + this.myRenderer.autoClearStencil = true; + this.myRenderer.setSize (theCanvas.width, theCanvas.height); + this.myRenderer.outputEncoding = THREE.sRGBEncoding; + this.myRenderer.shadowMap.enabled = true; + //this.myRenderer.shadowMap.type = THREE.PCFSoftShadowMap; // default THREE.PCFShadowMap + + //this.myScene.background = new THREE.Color('black'); + this.setBackgroundCubemap (theCubemap, false); + + this.myControls = new THREE.OrbitControls (this.myCamera, theCanvas); + this.myControls.target.set (0, 0, -0.2); + this.myControls.update(); + this.myControls.addEventListener ('change', function() { aViewer.updateView(); }); + + this.myDirLight.position.set (-0.098, 0.98, -0.20); + this.myDirLight.position.normalize() + this.myDirLight.castShadow = true; + this.myScene.add (new THREE.HemisphereLight (0xffffff, 0x000000, 0.4)); + this.myScene.add (this.myDirLight); + //const aShadowHelper = new THREE.CameraHelper (this.myDirLight.shadow.camera); this.myScene.add (aShadowHelper) + + const aPlaneGeom = new THREE.PlaneBufferGeometry (15, 15, 32, 32); + const aPlaneMat = new THREE.MeshStandardMaterial ({ color: 0xAAAAAA }); + this.myGround = new THREE.Mesh (aPlaneGeom, aPlaneMat); + this.myGround.receiveShadow = true; + this.myGround.position.y = -1.0; + this.myGround.rotation.x = -1.57; + this.myScene.add (this.myGround); + this.updateView(); + + theCanvas.addEventListener ('click', this.onClick, false); + theCanvas.addEventListener ('mousemove', this.onMouseMove, false); + + // FPS meter + this.myFpsMeter.showPanel (0); + document.body.appendChild (this.myFpsMeter.dom); + } + + toDynamicHighlight() { return this.myToDynamicHighlight; } + setDynamicHighlight(theToEnable) { this.myToDynamicHighlight = theToEnable; } + + onMouseMove() + { + if (event.buttons !== 0) { return; } + + var aViewer = OccViewerModule; + if (!aViewer.myToDynamicHighlight) { return; } + + var aTime1 = performance.now(); + event.preventDefault(); + var aMouse = new THREE.Vector2(); + aMouse.x = (event.clientX / aViewer.myRenderer.domElement.width) * 2 - 1; + aMouse.y = -(event.clientY / aViewer.myRenderer.domElement.height) * 2 + 1; + aViewer.myRaycaster.setFromCamera (aMouse, aViewer.myCamera); + var anRes = aViewer.myRaycaster.intersectObject (aViewer.myScene, true); + var aNewPicked = anRes.length > 0 ? anRes[0].object : null; + if (aNewPicked !== aViewer.myLastPicked) + { + aViewer.myLastPickedName = ""; + if (aViewer.myLastPicked !== null) + { + aViewer.myLastPicked.material.color.set (aViewer.myLastPicked.material.userData.oldColor); + aViewer.myLastPicked = null; + } + if (aNewPicked !== null) + { + for (var aParentIter = aNewPicked; aParentIter != null; aParentIter = aParentIter.parent) + { + if (aParentIter.name !== "") + { + if (aViewer.myLastPickedName !== "") { aViewer.myLastPickedName = "/" + aViewer.myLastPickedName; } + aViewer.myLastPickedName = aParentIter.name + aViewer.myLastPickedName; + } + } +var aTime2 = performance.now(); +console.log ( "New picked '" + aViewer.myLastPickedName + "' in " + ((aTime2 - aTime1) * 0.001) + " s") /// + + aNewPicked.material.userData.oldColor = aNewPicked.material.color.getHex(); + aNewPicked.material.color.set (aViewer.myDynHighlightColor); + aViewer.myLastPicked = aNewPicked; + } + aViewer.updateView(); + } + } + + onClick() + { + var aViewer = OccViewerModule; + event.preventDefault(); + if (aViewer.myLastPicked !== null) + { + aViewer.myLastPicked.material.color.set (aViewer.mySelectionColor); + aViewer.myLastPicked = null + } + aViewer.updateView(); + } + + setBackgroundColor (theR, theG, theB, theToUpdate) + { + this.myScene.background = new THREE.Color (theR, theG, theB); + if (theToUpdate) { this.updateView(); } + } + + setBackgroundCubemap (theFolderPath, theToUpdate) + { + this.myScene.background = new THREE.CubeTextureLoader() + .setPath (theFolderPath) + .load (['px.jpg','nx.jpg','py.jpg','ny.jpg','pz.jpg','nz.jpg']); + + /// TODO disable sRGB for consistency with OCCT sample workaround + //this.myScene.background.encoding = THREE.sRGBEncoding; + + if (theToUpdate) { this.updateView(); } + } + + /** + * Fit all/selected objects into view. + * @param {boolean} theAuto [in] fit selected objects (TRUE) or all objects (FALSE) + */ + fitAllObjects(theAuto, theToUpdate) + { + const aFitOffset = 1.2 + const aBox = new THREE.Box3(); + //this.myScene.traverse (function (theChild) { aBox.expandByObject (theChild) }); + for (let [aKey, anObjIter] of this.myObjects) + { + aBox.expandByObject (anObjIter); + } + + const aSize = aBox.getSize (new THREE.Vector3()); + const aCenter = aBox.getCenter(new THREE.Vector3()); +console.log (" @@ aSize= " + aSize.x + "x" + aSize.y + "x" + aSize.z) /// + + const aMaxSize = Math.max (aSize.x, aSize.y, aSize.z); + const aFitHeightDist = aMaxSize / (2 * Math.atan (Math.PI * this.myCamera.fov / 360)); + const aFitWidthDist = aFitHeightDist / this.myCamera.aspect; + const aDist = aFitOffset * Math.max (aFitHeightDist, aFitWidthDist); + + const aDir = this.myControls.target.clone() + .sub (this.myCamera.position).normalize().multiplyScalar (aDist); + this.myControls.maxDistance = aDist * 10; + this.myControls.target.copy (aCenter); + + this.myCamera.near = aDist / 100; + this.myCamera.far = aDist * 100; + this.myCamera.updateProjectionMatrix(); + + this.myCamera.position.copy (this.myControls.target).sub (aDir); + this.myControls.update(); + if (theToUpdate) { this.updateView(); } + } + + /** + * Redraw the view. + */ + updateView() + { + this.myFpsMeter.begin(); + + this.myRenderer.render (this.myScene, this.myCamera); + + this.myFpsMeter.end(); + } + + /** + * 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 + */ + removeObject (theName, theToUpdate) + { + var anOldObj = this.myObjects.get (theName); + if (anOldObj !== undefined) + { + this.myScene.remove (anOldObj); + this.myObjects.delete (theName) + if (theToUpdate) { this.updateView(); } + return true; + } + return false; + } + + /** + * Clear all named objects from viewer. + */ + removeAllObjects (theToUpdate) + { + for (let [aKey, anObjIter] of this.myObjects) + { + this.myScene.remove (anObjIter); + } + this.myObjects.clear(); + if (theToUpdate) { this.updateView(); } + } + + /** Return TRUE if ground is displayed */ + toShowGround() { return this.myGround.parent === this.myScene; } + + /** + * Show/hide ground. + * @param theToShow [in] show or hide flag + */ + setShowGround (theToShow, theToUpdate) + { + if (theToShow) + { + if (this.myGround.parent !== this.myScene) + { + this.myScene.add (this.myGround); + } + } + else + { + this.myScene.remove (this.myGround); + } + if (theToUpdate) { this.updateView(); } + } + + /** Returns TRUE if shadows are turned ON */ + toCastShadows() { return this.myDirLight.castShadow; } + + /** Turn shadows on/off */ + setCastShadows (theUseShadows, theToUpdate) + { + this.myDirLight.castShadow = theUseShadows; + if (theToUpdate) { this.updateView(); } + } + + /** + * Open object from the given URL. + * File will be loaded asynchronously. + * @param {string} theName [in] object name + * @param {string} theModelPath [in] model path + * @param {boolean} theToExpand [in] expand (explore) model or represent it as single object + */ + openFromUrl (theName, theModelPath, theToExpand) + { + this.removeObject (theName, false); + var aViewer = this; + + var aTime1 = performance.now(); + const aLoader = new THREE.GLTFLoader(); + //aLoader.setPath(); + aLoader.load (theModelPath, function (theGltf) { + if (theToExpand) + { + theGltf.scene.traverse (function (theChild) { + if (theChild.isMesh) + { + theChild.material = theChild.material.clone(); // duplicate materials to dynamically highlight nodes + theChild.material.envMap = aViewer.myScene.background; + theChild.castShadow = true; + theChild.receiveShadow = true + } + }); + + var aModelRoot = theGltf.scene; + aModelRoot.name = theName; + aViewer.myScene.add (aModelRoot); + aViewer.myObjects.set (theName, aModelRoot); + aViewer.fitAllObjects(); + return; + } + + // merge meshes with common material + var isFirst = true + const aMatMap = new Map(); + theGltf.scene.traverse (function (theChild) { + if (theChild.isMesh) + { + var aMatObjects = aMatMap.get (theChild.material.name) + if (aMatObjects === undefined) + { + var aMatObjects = []; + aMatMap.set (theChild.material.name, aMatObjects) + } + aMatObjects.push (theChild) + } + }); + + var aModelRoot = new THREE.Group(); + aModelRoot.name = theName; + theGltf.scene.updateMatrixWorld(); + for (let [aMatName, aMeshes] of aMatMap) + { + const aGeomList = []; + for (let i = 0; i < aMeshes.length; ++i) + { + const aMesh = aMeshes[i]; + if (aMesh.geometry.applyMatrix4 != undefined) + { + aMesh.geometry.applyMatrix4 (aMesh.matrixWorld); // pre-apply transformation + } + else + { + aMesh.geometry.applyMatrix (aMesh.matrixWorld); // pre-apply transformation + } + aGeomList.push (aMesh.geometry); + } + + const aMaterial = aMeshes[0].material; + aMaterial.envMap = aViewer.myScene.background; + const aGeom = THREE.BufferGeometryUtils.mergeBufferGeometries (aGeomList); + const aNode = new THREE.Mesh (aGeom, aMaterial); + aNode.name = ""; + aNode.castShadow = true; + aNode.receiveShadow = true + aModelRoot.add (aNode); + } + + aViewer.myScene.add (aModelRoot); + aViewer.myObjects.set (theName, aModelRoot); + aViewer.fitAllObjects(); + + var aTime2 = performance.now(); + console.log ("glTF '" + theModelPath + "' loading time: " + ((aTime2 - aTime1) * 0.001) + " s"); + }); + } +} + +function createOccThreejsViewer (theCanvas, theCubemap, theAntiAlias) +{ + let aViewer = new OccThreejsViewer (theCanvas, theCubemap, theAntiAlias); + return aViewer; +} -- 2.39.5