mirror of
https://git.dev.opencascade.org/repos/occt.git
synced 2025-08-09 13:22:24 +03:00
1735 lines
58 KiB
C++
1735 lines
58 KiB
C++
// Copyright (c) 2019 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 "WasmOcctView.h"
|
|
|
|
#include "WasmVKeys.h"
|
|
#include "WasmOcctPixMap.h"
|
|
|
|
#include <AIS_Shape.hxx>
|
|
#include <AIS_ViewCube.hxx>
|
|
#include <Aspect_Handle.hxx>
|
|
#include <Aspect_DisplayConnection.hxx>
|
|
#include <Aspect_NeutralWindow.hxx>
|
|
#include <Message.hxx>
|
|
#include <Message_Messenger.hxx>
|
|
#include <Graphic3d_CubeMapPacked.hxx>
|
|
#include <OpenGl_GraphicDriver.hxx>
|
|
#include <Prs3d_DatumAspect.hxx>
|
|
#include <Prs3d_ToolCylinder.hxx>
|
|
#include <Prs3d_ToolDisk.hxx>
|
|
|
|
#include <BRep_Builder.hxx>
|
|
#include <BRepBndLib.hxx>
|
|
#include <BRepTools.hxx>
|
|
#include <Standard_ArrayStreamBuffer.hxx>
|
|
#include <TopExp_Explorer.hxx>
|
|
|
|
#include <emscripten/bind.h>
|
|
|
|
#include <iostream>
|
|
|
|
#define THE_CANVAS_ID "canvas"
|
|
|
|
namespace
|
|
{
|
|
EM_JS(int, jsCanvasGetWidth, (), {
|
|
return Module.canvas.width;
|
|
});
|
|
|
|
EM_JS(int, jsCanvasGetHeight, (), {
|
|
return Module.canvas.height;
|
|
});
|
|
|
|
EM_JS(float, jsDevicePixelRatio, (), {
|
|
var aDevicePixelRatio = window.devicePixelRatio || 1;
|
|
return aDevicePixelRatio;
|
|
});
|
|
|
|
//! Return cavas size in pixels.
|
|
static Graphic3d_Vec2i jsCanvasSize()
|
|
{
|
|
return Graphic3d_Vec2i (jsCanvasGetWidth(), jsCanvasGetHeight());
|
|
}
|
|
|
|
//! Auxiliary wrapper for loading model.
|
|
struct ModelAsyncLoader
|
|
{
|
|
std::string Name;
|
|
std::string Path;
|
|
bool ToExpand;
|
|
OSD_Timer Timer;
|
|
|
|
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)
|
|
{
|
|
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;
|
|
}
|
|
|
|
//! File read error event.
|
|
static void onReadFailed (void* theOpaque)
|
|
{
|
|
const ModelAsyncLoader* aTask = (ModelAsyncLoader* )theOpaque;
|
|
Message::DefaultMessenger()->Send (TCollection_AsciiString("Error: unable to load file ") + aTask->Path.c_str(), Message_Fail);
|
|
delete aTask;
|
|
}
|
|
};
|
|
|
|
//! Auxiliary wrapper for loading cubemap.
|
|
struct CubemapAsyncLoader
|
|
{
|
|
//! Image file read event.
|
|
static void onImageRead (const char* theFilePath)
|
|
{
|
|
Handle(Graphic3d_CubeMapPacked) aCubemap;
|
|
Handle(WasmOcctPixMap) anImage = new WasmOcctPixMap();
|
|
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();
|
|
}
|
|
|
|
//! Image file failed read event.
|
|
static void onImageFailed (const char* theFilePath)
|
|
{
|
|
Message::DefaultMessenger()->Send (TCollection_AsciiString("Error: unable to load image ") + theFilePath, Message_Fail);
|
|
}
|
|
};
|
|
|
|
//! Object list separator.
|
|
static const char THE_LIST_SEPARATOR = '\n';
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : Instance
|
|
// Purpose :
|
|
// ================================================================
|
|
WasmOcctView& WasmOcctView::Instance()
|
|
{
|
|
static WasmOcctView aViewer;
|
|
return aViewer;
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : WasmOcctView
|
|
// Purpose :
|
|
// ================================================================
|
|
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);
|
|
addActionHotKeys (Aspect_VKey_NavSlideRight, Aspect_VKey_D, Aspect_VKey_D | Aspect_VKeyFlags_SHIFT);
|
|
addActionHotKeys (Aspect_VKey_NavRollCCW, Aspect_VKey_Q, Aspect_VKey_Q | Aspect_VKeyFlags_SHIFT);
|
|
addActionHotKeys (Aspect_VKey_NavRollCW, Aspect_VKey_E, Aspect_VKey_E | Aspect_VKeyFlags_SHIFT);
|
|
|
|
addActionHotKeys (Aspect_VKey_NavSpeedIncrease, Aspect_VKey_Plus, Aspect_VKey_Plus | Aspect_VKeyFlags_SHIFT,
|
|
Aspect_VKey_Equal,
|
|
Aspect_VKey_NumpadAdd, Aspect_VKey_NumpadAdd | Aspect_VKeyFlags_SHIFT);
|
|
addActionHotKeys (Aspect_VKey_NavSpeedDecrease, Aspect_VKey_Minus, Aspect_VKey_Minus | Aspect_VKeyFlags_SHIFT,
|
|
Aspect_VKey_NumpadSubtract, Aspect_VKey_NumpadSubtract | Aspect_VKeyFlags_SHIFT);
|
|
|
|
// arrow keys conflict with browser page scrolling, so better be avoided in non-fullscreen mode
|
|
addActionHotKeys (Aspect_VKey_NavLookUp, Aspect_VKey_Numpad8); // Aspect_VKey_Up
|
|
addActionHotKeys (Aspect_VKey_NavLookDown, Aspect_VKey_Numpad2); // Aspect_VKey_Down
|
|
addActionHotKeys (Aspect_VKey_NavLookLeft, Aspect_VKey_Numpad4); // Aspect_VKey_Left
|
|
addActionHotKeys (Aspect_VKey_NavLookRight, Aspect_VKey_Numpad6); // Aspect_VKey_Right
|
|
addActionHotKeys (Aspect_VKey_NavSlideLeft, Aspect_VKey_Numpad1); // Aspect_VKey_Left |Aspect_VKeyFlags_SHIFT
|
|
addActionHotKeys (Aspect_VKey_NavSlideRight, Aspect_VKey_Numpad3); // Aspect_VKey_Right|Aspect_VKeyFlags_SHIFT
|
|
addActionHotKeys (Aspect_VKey_NavSlideUp, Aspect_VKey_Numpad9); // Aspect_VKey_Up |Aspect_VKeyFlags_SHIFT
|
|
addActionHotKeys (Aspect_VKey_NavSlideDown, Aspect_VKey_Numpad7); // Aspect_VKey_Down |Aspect_VKeyFlags_SHIFT
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : ~WasmOcctView
|
|
// Purpose :
|
|
// ================================================================
|
|
WasmOcctView::~WasmOcctView()
|
|
{
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : run
|
|
// Purpose :
|
|
// ================================================================
|
|
void WasmOcctView::run()
|
|
{
|
|
initWindow();
|
|
initViewer();
|
|
initDemoScene();
|
|
if (myView.IsNull())
|
|
{
|
|
return;
|
|
}
|
|
|
|
myView->MustBeResized();
|
|
myView->Redraw();
|
|
|
|
// There is no infinite message loop, main() will return from here immediately.
|
|
// Tell that our Module should be left loaded and handle events through callbacks.
|
|
//emscripten_set_main_loop (redrawView, 60, 1);
|
|
//emscripten_set_main_loop (redrawView, -1, 1);
|
|
EM_ASM(Module['noExitRuntime'] = true);
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : initWindow
|
|
// Purpose :
|
|
// ================================================================
|
|
void WasmOcctView::initWindow()
|
|
{
|
|
myDevicePixelRatio = jsDevicePixelRatio();
|
|
myCanvasId = THE_CANVAS_ID;
|
|
const char* aTargetId = !myCanvasId.IsEmpty() ? myCanvasId.ToCString() : EMSCRIPTEN_EVENT_TARGET_WINDOW;
|
|
const EM_BOOL toUseCapture = EM_TRUE;
|
|
emscripten_set_resize_callback (EMSCRIPTEN_EVENT_TARGET_WINDOW, this, toUseCapture, onResizeCallback);
|
|
|
|
emscripten_set_mousedown_callback (aTargetId, this, toUseCapture, onMouseCallback);
|
|
emscripten_set_mouseup_callback (aTargetId, this, toUseCapture, onMouseCallback);
|
|
emscripten_set_mousemove_callback (aTargetId, this, toUseCapture, onMouseCallback);
|
|
emscripten_set_dblclick_callback (aTargetId, this, toUseCapture, onMouseCallback);
|
|
emscripten_set_click_callback (aTargetId, this, toUseCapture, onMouseCallback);
|
|
emscripten_set_mouseenter_callback (aTargetId, this, toUseCapture, onMouseCallback);
|
|
emscripten_set_mouseleave_callback (aTargetId, this, toUseCapture, onMouseCallback);
|
|
emscripten_set_wheel_callback (aTargetId, this, toUseCapture, onWheelCallback);
|
|
|
|
emscripten_set_touchstart_callback (aTargetId, this, toUseCapture, onTouchCallback);
|
|
emscripten_set_touchend_callback (aTargetId, this, toUseCapture, onTouchCallback);
|
|
emscripten_set_touchmove_callback (aTargetId, this, toUseCapture, onTouchCallback);
|
|
emscripten_set_touchcancel_callback(aTargetId, this, toUseCapture, onTouchCallback);
|
|
|
|
//emscripten_set_keypress_callback (EMSCRIPTEN_EVENT_TARGET_WINDOW, this, toUseCapture, onKeyCallback);
|
|
emscripten_set_keydown_callback (EMSCRIPTEN_EVENT_TARGET_WINDOW, this, toUseCapture, onKeyDownCallback);
|
|
emscripten_set_keyup_callback (EMSCRIPTEN_EVENT_TARGET_WINDOW, this, toUseCapture, onKeyUpCallback);
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : dumpGlInfo
|
|
// Purpose :
|
|
// ================================================================
|
|
void WasmOcctView::dumpGlInfo (bool theIsBasic)
|
|
{
|
|
TColStd_IndexedDataMapOfStringString aGlCapsDict;
|
|
myView->DiagnosticInformation (aGlCapsDict, theIsBasic ? Graphic3d_DiagnosticInfo_Basic : Graphic3d_DiagnosticInfo_Complete);
|
|
if (theIsBasic)
|
|
{
|
|
TCollection_AsciiString aViewport;
|
|
aGlCapsDict.FindFromKey ("Viewport", aViewport);
|
|
aGlCapsDict.Clear();
|
|
aGlCapsDict.Add ("Viewport", aViewport);
|
|
}
|
|
aGlCapsDict.Add ("Display scale", TCollection_AsciiString(myDevicePixelRatio));
|
|
|
|
// beautify output
|
|
{
|
|
TCollection_AsciiString* aGlVer = aGlCapsDict.ChangeSeek ("GLversion");
|
|
TCollection_AsciiString* aGlslVer = aGlCapsDict.ChangeSeek ("GLSLversion");
|
|
if (aGlVer != NULL
|
|
&& aGlslVer != NULL)
|
|
{
|
|
*aGlVer = *aGlVer + " [GLSL: " + *aGlslVer + "]";
|
|
aGlslVer->Clear();
|
|
}
|
|
}
|
|
|
|
TCollection_AsciiString anInfo;
|
|
for (TColStd_IndexedDataMapOfStringString::Iterator aValueIter (aGlCapsDict); aValueIter.More(); aValueIter.Next())
|
|
{
|
|
if (!aValueIter.Value().IsEmpty())
|
|
{
|
|
if (!anInfo.IsEmpty())
|
|
{
|
|
anInfo += "\n";
|
|
}
|
|
anInfo += aValueIter.Key() + ": " + aValueIter.Value();
|
|
}
|
|
}
|
|
|
|
::Message::DefaultMessenger()->Send (anInfo, Message_Warning);
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : initPixelScaleRatio
|
|
// Purpose :
|
|
// ================================================================
|
|
void WasmOcctView::initPixelScaleRatio()
|
|
{
|
|
SetTouchToleranceScale (myDevicePixelRatio);
|
|
if (!myView.IsNull())
|
|
{
|
|
myView->ChangeRenderingParams().Resolution = (unsigned int )(96.0 * myDevicePixelRatio + 0.5);
|
|
}
|
|
if (!myContext.IsNull())
|
|
{
|
|
myContext->SetPixelTolerance (int(myDevicePixelRatio * 6.0));
|
|
if (!myViewCube.IsNull())
|
|
{
|
|
static const double THE_CUBE_SIZE = 60.0;
|
|
myViewCube->SetSize (myDevicePixelRatio * THE_CUBE_SIZE, false);
|
|
myViewCube->SetBoxFacetExtension (myViewCube->Size() * 0.15);
|
|
myViewCube->SetAxesPadding (myViewCube->Size() * 0.10);
|
|
myViewCube->SetFontHeight (THE_CUBE_SIZE * 0.16);
|
|
if (myViewCube->HasInteractiveContext())
|
|
{
|
|
myContext->Redisplay (myViewCube, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : initViewer
|
|
// Purpose :
|
|
// ================================================================
|
|
bool WasmOcctView::initViewer()
|
|
{
|
|
// Build with "--preload-file MyFontFile.ttf" option
|
|
// and register font in Font Manager to use custom font(s).
|
|
/*const char* aFontPath = "MyFontFile.ttf";
|
|
if (Handle(Font_SystemFont) aFont = Font_FontMgr::GetInstance()->CheckFont (aFontPath))
|
|
{
|
|
Font_FontMgr::GetInstance()->RegisterFont (aFont, true);
|
|
}
|
|
else
|
|
{
|
|
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);
|
|
return false;
|
|
}
|
|
|
|
Handle(V3d_Viewer) aViewer = new V3d_Viewer (aDriver);
|
|
aViewer->SetComputedMode (false);
|
|
aViewer->SetDefaultShadingModel (Graphic3d_TOSM_FRAGMENT);
|
|
aViewer->SetDefaultLights();
|
|
aViewer->SetLightOn();
|
|
for (V3d_ListOfLight::Iterator aLightIter (aViewer->ActiveLights()); aLightIter.More(); aLightIter.Next())
|
|
{
|
|
const Handle(V3d_Light)& aLight = aLightIter.Value();
|
|
if (aLight->Type() == Graphic3d_TOLS_DIRECTIONAL)
|
|
{
|
|
aLight->SetCastShadows (true);
|
|
aLight->SetHeadlight (false);
|
|
aLight->SetDirection (gp_Dir (0.098f, -0.20f, -0.98f));
|
|
}
|
|
}
|
|
|
|
Handle(Aspect_NeutralWindow) aWindow = new Aspect_NeutralWindow();
|
|
Graphic3d_Vec2i aWinSize = jsCanvasSize();
|
|
if (aWinSize.x() < 10 || aWinSize.y() < 10)
|
|
{
|
|
Message::DefaultMessenger()->Send (TCollection_AsciiString ("Warning: invalid canvas size"), Message_Warning);
|
|
}
|
|
aWindow->SetSize (aWinSize.x(), aWinSize.y());
|
|
|
|
myTextStyle = new Prs3d_TextAspect();
|
|
myTextStyle->SetFont (Font_NOF_ASCII_MONO);
|
|
myTextStyle->SetHeight (12);
|
|
myTextStyle->Aspect()->SetColor (Quantity_NOC_GRAY95);
|
|
myTextStyle->Aspect()->SetColorSubTitle (Quantity_NOC_BLACK);
|
|
myTextStyle->Aspect()->SetDisplayType (Aspect_TODT_SHADOW);
|
|
myTextStyle->Aspect()->SetTextFontAspect (Font_FA_Bold);
|
|
myTextStyle->Aspect()->SetTextZoomable (false);
|
|
myTextStyle->SetHorizontalJustification (Graphic3d_HTA_LEFT);
|
|
myTextStyle->SetVerticalJustification (Graphic3d_VTA_BOTTOM);
|
|
|
|
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().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();
|
|
myView->ChangeRenderingParams().StatsTextHeight = (int )myTextStyle->Height();
|
|
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;
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : initDemoScene
|
|
// Purpose :
|
|
// ================================================================
|
|
void WasmOcctView::initDemoScene()
|
|
{
|
|
if (myContext.IsNull())
|
|
{
|
|
return;
|
|
}
|
|
|
|
//myView->TriedronDisplay (Aspect_TOTP_LEFT_LOWER, Quantity_NOC_GOLD, 0.08, V3d_WIREFRAME);
|
|
|
|
myViewCube = new AIS_ViewCube();
|
|
// presentation parameters
|
|
initPixelScaleRatio();
|
|
myViewCube->SetTransformPersistence (new Graphic3d_TransformPers (Graphic3d_TMF_TriedronPers, Aspect_TOTP_RIGHT_LOWER, Graphic3d_Vec2i (100, 100)));
|
|
myViewCube->Attributes()->SetDatumAspect (new Prs3d_DatumAspect());
|
|
myViewCube->Attributes()->DatumAspect()->SetTextAspect (myTextStyle);
|
|
// animation parameters
|
|
myViewCube->SetViewAnimation (myViewAnimation);
|
|
myViewCube->SetFixedAnimationLoop (false);
|
|
myViewCube->SetAutoStartAnimation (true);
|
|
myContext->Display (myViewCube, false);
|
|
|
|
// Build with "--preload-file MySampleFile.brep" option to load some shapes here.
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : UpdateView
|
|
// Purpose :
|
|
// ================================================================
|
|
void WasmOcctView::UpdateView()
|
|
{
|
|
if (!myView.IsNull())
|
|
{
|
|
myView->Invalidate();
|
|
updateView();
|
|
}
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : updateView
|
|
// Purpose :
|
|
// ================================================================
|
|
void WasmOcctView::updateView()
|
|
{
|
|
if (!myView.IsNull())
|
|
{
|
|
if (++myUpdateRequests == 1)
|
|
{
|
|
emscripten_async_call (onRedrawView, this, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : redrawView
|
|
// Purpose :
|
|
// ================================================================
|
|
void WasmOcctView::redrawView()
|
|
{
|
|
if (!myView.IsNull())
|
|
{
|
|
FlushViewEvents (myContext, myView, true);
|
|
}
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : handleViewRedraw
|
|
// Purpose :
|
|
// ================================================================
|
|
void WasmOcctView::handleViewRedraw (const Handle(AIS_InteractiveContext)& theCtx,
|
|
const Handle(V3d_View)& theView)
|
|
{
|
|
myUpdateRequests = 0;
|
|
AIS_ViewController::handleViewRedraw (theCtx, theView);
|
|
|
|
for (NCollection_DataMap<unsigned int, Aspect_VKey>::Iterator aNavKeyIter (myNavKeyMap);
|
|
!myToAskNextFrame && aNavKeyIter.More(); aNavKeyIter.Next())
|
|
{
|
|
const Aspect_VKey aVKey = aNavKeyIter.Key() & ~Aspect_VKeyFlags_ALL;
|
|
myToAskNextFrame = myKeys.IsKeyDown (aVKey);
|
|
}
|
|
|
|
if (myToAskNextFrame)
|
|
{
|
|
// ask more frames
|
|
++myUpdateRequests;
|
|
emscripten_async_call (onRedrawView, this, 0);
|
|
}
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : onResizeEvent
|
|
// Purpose :
|
|
// ================================================================
|
|
EM_BOOL WasmOcctView::onResizeEvent (int theEventType, const EmscriptenUiEvent* theEvent)
|
|
{
|
|
(void )theEventType; // EMSCRIPTEN_EVENT_RESIZE or EMSCRIPTEN_EVENT_CANVASRESIZED
|
|
(void )theEvent;
|
|
if (myView.IsNull())
|
|
{
|
|
return EM_FALSE;
|
|
}
|
|
|
|
Handle(Aspect_NeutralWindow) aWindow = Handle(Aspect_NeutralWindow)::DownCast (myView->Window());
|
|
Graphic3d_Vec2i aWinSizeOld, aWinSizeNew (jsCanvasSize());
|
|
if (aWinSizeNew.x() < 10 || aWinSizeNew.y() < 10)
|
|
{
|
|
Message::DefaultMessenger()->Send (TCollection_AsciiString ("Warning: invalid canvas size"), Message_Warning);
|
|
}
|
|
aWindow->Size (aWinSizeOld.x(), aWinSizeOld.y());
|
|
const float aPixelRatio = jsDevicePixelRatio();
|
|
if (aWinSizeNew != aWinSizeOld
|
|
|| aPixelRatio != myDevicePixelRatio)
|
|
{
|
|
if (myDevicePixelRatio != aPixelRatio)
|
|
{
|
|
myDevicePixelRatio = aPixelRatio;
|
|
initPixelScaleRatio();
|
|
}
|
|
aWindow->SetSize (aWinSizeNew.x(), aWinSizeNew.y());
|
|
myView->MustBeResized();
|
|
myView->Invalidate();
|
|
myView->Redraw();
|
|
dumpGlInfo (true);
|
|
}
|
|
return EM_TRUE;
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : onMouseEvent
|
|
// Purpose :
|
|
// ================================================================
|
|
EM_BOOL WasmOcctView::onMouseEvent (int theEventType, const EmscriptenMouseEvent* theEvent)
|
|
{
|
|
if (myView.IsNull())
|
|
{
|
|
return EM_FALSE;
|
|
}
|
|
|
|
Graphic3d_Vec2i aWinSize;
|
|
myView->Window()->Size (aWinSize.x(), aWinSize.y());
|
|
const Graphic3d_Vec2i aNewPos = convertPointToBacking (Graphic3d_Vec2i (theEvent->targetX, theEvent->targetY));
|
|
Aspect_VKeyFlags aFlags = 0;
|
|
if (theEvent->ctrlKey == EM_TRUE) { aFlags |= Aspect_VKeyFlags_CTRL; }
|
|
if (theEvent->shiftKey == EM_TRUE) { aFlags |= Aspect_VKeyFlags_SHIFT; }
|
|
if (theEvent->altKey == EM_TRUE) { aFlags |= Aspect_VKeyFlags_ALT; }
|
|
if (theEvent->metaKey == EM_TRUE) { aFlags |= Aspect_VKeyFlags_META; }
|
|
|
|
const bool isEmulated = false;
|
|
const Aspect_VKeyMouse aButtons = WasmVKeys_MouseButtonsFromNative (theEvent->buttons);
|
|
switch (theEventType)
|
|
{
|
|
case EMSCRIPTEN_EVENT_MOUSEMOVE:
|
|
{
|
|
if ((aNewPos.x() < 0 || aNewPos.x() > aWinSize.x()
|
|
|| aNewPos.y() < 0 || aNewPos.y() > aWinSize.y())
|
|
&& PressedMouseButtons() == Aspect_VKeyMouse_NONE)
|
|
{
|
|
return EM_FALSE;
|
|
}
|
|
if (UpdateMousePosition (aNewPos, aButtons, aFlags, isEmulated))
|
|
{
|
|
updateView();
|
|
}
|
|
break;
|
|
}
|
|
case EMSCRIPTEN_EVENT_MOUSEDOWN:
|
|
case EMSCRIPTEN_EVENT_MOUSEUP:
|
|
{
|
|
if (aNewPos.x() < 0 || aNewPos.x() > aWinSize.x()
|
|
|| aNewPos.y() < 0 || aNewPos.y() > aWinSize.y())
|
|
{
|
|
return EM_FALSE;
|
|
}
|
|
if (UpdateMouseButtons (aNewPos, aButtons, aFlags, isEmulated))
|
|
{
|
|
updateView();
|
|
}
|
|
break;
|
|
}
|
|
case EMSCRIPTEN_EVENT_CLICK:
|
|
case EMSCRIPTEN_EVENT_DBLCLICK:
|
|
{
|
|
if (aNewPos.x() < 0 || aNewPos.x() > aWinSize.x()
|
|
|| aNewPos.y() < 0 || aNewPos.y() > aWinSize.y())
|
|
{
|
|
return EM_FALSE;
|
|
}
|
|
break;
|
|
}
|
|
case EMSCRIPTEN_EVENT_MOUSEENTER:
|
|
{
|
|
break;
|
|
}
|
|
case EMSCRIPTEN_EVENT_MOUSELEAVE:
|
|
{
|
|
// there is no SetCapture() support, so that mouse unclick events outside canvas will not arrive,
|
|
// so we have to forget current state...
|
|
if (UpdateMouseButtons (aNewPos, Aspect_VKeyMouse_NONE, aFlags, isEmulated))
|
|
{
|
|
updateView();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return EM_TRUE;
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : onWheelEvent
|
|
// Purpose :
|
|
// ================================================================
|
|
EM_BOOL WasmOcctView::onWheelEvent (int theEventType, const EmscriptenWheelEvent* theEvent)
|
|
{
|
|
if (myView.IsNull()
|
|
|| theEventType != EMSCRIPTEN_EVENT_WHEEL)
|
|
{
|
|
return EM_FALSE;
|
|
}
|
|
|
|
Graphic3d_Vec2i aWinSize;
|
|
myView->Window()->Size (aWinSize.x(), aWinSize.y());
|
|
const Graphic3d_Vec2i aNewPos = convertPointToBacking (Graphic3d_Vec2i (theEvent->mouse.targetX, theEvent->mouse.targetY));
|
|
if (aNewPos.x() < 0 || aNewPos.x() > aWinSize.x()
|
|
|| aNewPos.y() < 0 || aNewPos.y() > aWinSize.y())
|
|
{
|
|
return EM_FALSE;
|
|
}
|
|
|
|
double aDelta = 0.0;
|
|
switch (theEvent->deltaMode)
|
|
{
|
|
case DOM_DELTA_PIXEL:
|
|
{
|
|
aDelta = theEvent->deltaY / (5.0 * myDevicePixelRatio);
|
|
break;
|
|
}
|
|
case DOM_DELTA_LINE:
|
|
{
|
|
aDelta = theEvent->deltaY * 8.0;
|
|
break;
|
|
}
|
|
case DOM_DELTA_PAGE:
|
|
{
|
|
aDelta = theEvent->deltaY >= 0.0 ? 24.0 : -24.0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (UpdateZoom (Aspect_ScrollDelta (aNewPos, -aDelta)))
|
|
{
|
|
updateView();
|
|
}
|
|
return EM_TRUE;
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : onTouchEvent
|
|
// Purpose :
|
|
// ================================================================
|
|
EM_BOOL WasmOcctView::onTouchEvent (int theEventType, const EmscriptenTouchEvent* theEvent)
|
|
{
|
|
const double aClickTolerance = 5.0;
|
|
if (myView.IsNull())
|
|
{
|
|
return EM_FALSE;
|
|
}
|
|
|
|
Graphic3d_Vec2i aWinSize;
|
|
myView->Window()->Size (aWinSize.x(), aWinSize.y());
|
|
bool hasUpdates = false;
|
|
for (int aTouchIter = 0; aTouchIter < theEvent->numTouches; ++aTouchIter)
|
|
{
|
|
const EmscriptenTouchPoint& aTouch = theEvent->touches[aTouchIter];
|
|
if (!aTouch.isChanged)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const Standard_Size aTouchId = (Standard_Size )aTouch.identifier;
|
|
const Graphic3d_Vec2i aNewPos = convertPointToBacking (Graphic3d_Vec2i (aTouch.targetX, aTouch.targetY));
|
|
switch (theEventType)
|
|
{
|
|
case EMSCRIPTEN_EVENT_TOUCHSTART:
|
|
{
|
|
if (aNewPos.x() >= 0 && aNewPos.x() < aWinSize.x()
|
|
&& aNewPos.y() >= 0 && aNewPos.y() < aWinSize.y())
|
|
{
|
|
hasUpdates = true;
|
|
AddTouchPoint (aTouchId, Graphic3d_Vec2d (aNewPos));
|
|
myClickTouch.From.SetValues (-1.0, -1.0);
|
|
if (myTouchPoints.Extent() == 1)
|
|
{
|
|
myClickTouch.From = Graphic3d_Vec2d (aNewPos);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case EMSCRIPTEN_EVENT_TOUCHMOVE:
|
|
{
|
|
const int anOldIndex = myTouchPoints.FindIndex (aTouchId);
|
|
if (anOldIndex != 0)
|
|
{
|
|
hasUpdates = true;
|
|
UpdateTouchPoint (aTouchId, Graphic3d_Vec2d (aNewPos));
|
|
if (myTouchPoints.Extent() == 1
|
|
&& (myClickTouch.From - Graphic3d_Vec2d (aNewPos)).cwiseAbs().maxComp() > aClickTolerance)
|
|
{
|
|
myClickTouch.From.SetValues (-1.0, -1.0);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case EMSCRIPTEN_EVENT_TOUCHEND:
|
|
case EMSCRIPTEN_EVENT_TOUCHCANCEL:
|
|
{
|
|
if (RemoveTouchPoint (aTouchId))
|
|
{
|
|
if (myTouchPoints.IsEmpty()
|
|
&& myClickTouch.From.minComp() >= 0.0)
|
|
{
|
|
if (myDoubleTapTimer.IsStarted()
|
|
&& myDoubleTapTimer.ElapsedTime() <= myMouseDoubleClickInt)
|
|
{
|
|
myView->FitAll (0.01, false);
|
|
myView->Invalidate();
|
|
}
|
|
else
|
|
{
|
|
myDoubleTapTimer.Stop();
|
|
myDoubleTapTimer.Reset();
|
|
myDoubleTapTimer.Start();
|
|
SelectInViewer (Graphic3d_Vec2i (myClickTouch.From), false);
|
|
}
|
|
}
|
|
hasUpdates = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (hasUpdates)
|
|
{
|
|
updateView();
|
|
}
|
|
return hasUpdates || !myTouchPoints.IsEmpty() ? EM_TRUE : EM_FALSE;
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : navigationKeyModifierSwitch
|
|
// Purpose :
|
|
// ================================================================
|
|
bool WasmOcctView::navigationKeyModifierSwitch (unsigned int theModifOld,
|
|
unsigned int theModifNew,
|
|
double theTimeStamp)
|
|
{
|
|
bool hasActions = false;
|
|
for (unsigned int aKeyIter = 0; aKeyIter < Aspect_VKey_ModifiersLower; ++aKeyIter)
|
|
{
|
|
if (!myKeys.IsKeyDown (aKeyIter))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Aspect_VKey anActionOld = Aspect_VKey_UNKNOWN, anActionNew = Aspect_VKey_UNKNOWN;
|
|
myNavKeyMap.Find (aKeyIter | theModifOld, anActionOld);
|
|
myNavKeyMap.Find (aKeyIter | theModifNew, anActionNew);
|
|
if (anActionOld == anActionNew)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (anActionOld != Aspect_VKey_UNKNOWN)
|
|
{
|
|
myKeys.KeyUp (anActionOld, theTimeStamp);
|
|
}
|
|
if (anActionNew != Aspect_VKey_UNKNOWN)
|
|
{
|
|
hasActions = true;
|
|
myKeys.KeyDown (anActionNew, theTimeStamp);
|
|
}
|
|
}
|
|
return hasActions;
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : onKeyDownEvent
|
|
// Purpose :
|
|
// ================================================================
|
|
EM_BOOL WasmOcctView::onKeyDownEvent (int theEventType, const EmscriptenKeyboardEvent* theEvent)
|
|
{
|
|
if (myView.IsNull()
|
|
|| theEventType != EMSCRIPTEN_EVENT_KEYDOWN) // EMSCRIPTEN_EVENT_KEYPRESS
|
|
{
|
|
return EM_FALSE;
|
|
}
|
|
|
|
const double aTimeStamp = EventTime();
|
|
const Aspect_VKey aVKey = WasmVKeys_VirtualKeyFromNative (theEvent->keyCode);
|
|
if (aVKey == Aspect_VKey_UNKNOWN)
|
|
{
|
|
return EM_FALSE;
|
|
}
|
|
if (theEvent->repeat == EM_TRUE)
|
|
{
|
|
return EM_FALSE;
|
|
}
|
|
|
|
const unsigned int aModifOld = myKeys.Modifiers();
|
|
AIS_ViewController::KeyDown (aVKey, aTimeStamp);
|
|
|
|
const unsigned int aModifNew = myKeys.Modifiers();
|
|
if (aModifNew != aModifOld
|
|
&& navigationKeyModifierSwitch (aModifOld, aModifNew, aTimeStamp))
|
|
{
|
|
// modifier key just pressed
|
|
}
|
|
|
|
Aspect_VKey anAction = Aspect_VKey_UNKNOWN;
|
|
if (myNavKeyMap.Find (aVKey | myKeys.Modifiers(), anAction)
|
|
&& anAction != Aspect_VKey_UNKNOWN)
|
|
{
|
|
AIS_ViewController::KeyDown (anAction, aTimeStamp);
|
|
UpdateView();
|
|
}
|
|
return EM_FALSE;
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : onKeyUpEvent
|
|
// Purpose :
|
|
// ================================================================
|
|
EM_BOOL WasmOcctView::onKeyUpEvent (int theEventType, const EmscriptenKeyboardEvent* theEvent)
|
|
{
|
|
if (myView.IsNull()
|
|
|| theEventType != EMSCRIPTEN_EVENT_KEYUP)
|
|
{
|
|
return EM_FALSE;
|
|
}
|
|
|
|
const double aTimeStamp = EventTime();
|
|
const Aspect_VKey aVKey = WasmVKeys_VirtualKeyFromNative (theEvent->keyCode);
|
|
if (aVKey == Aspect_VKey_UNKNOWN)
|
|
{
|
|
return EM_FALSE;
|
|
}
|
|
|
|
const unsigned int aModifOld = myKeys.Modifiers();
|
|
AIS_ViewController::KeyUp (aVKey, aTimeStamp);
|
|
|
|
Aspect_VKey anAction = Aspect_VKey_UNKNOWN;
|
|
if (myNavKeyMap.Find (aVKey | myKeys.Modifiers(), anAction)
|
|
&& anAction != Aspect_VKey_UNKNOWN)
|
|
{
|
|
AIS_ViewController::KeyUp (anAction, aTimeStamp);
|
|
UpdateView();
|
|
}
|
|
|
|
const unsigned int aModifNew = myKeys.Modifiers();
|
|
if (aModifNew != aModifOld
|
|
&& navigationKeyModifierSwitch (aModifOld, aModifNew, aTimeStamp))
|
|
{
|
|
// modifier key released
|
|
}
|
|
|
|
return processKeyPress (aVKey | aModifNew) ? EM_TRUE : EM_FALSE;
|
|
}
|
|
|
|
//==============================================================================
|
|
//function : processKeyPress
|
|
//purpose :
|
|
//==============================================================================
|
|
bool WasmOcctView::processKeyPress (Aspect_VKey theKey)
|
|
{
|
|
switch (theKey)
|
|
{
|
|
case Aspect_VKey_F:
|
|
{
|
|
myView->FitAll (0.01, false);
|
|
UpdateView();
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// ================================================================
|
|
// 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::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())
|
|
{
|
|
emscripten_async_wget (theImagePath.c_str(), "/emulated/cubemap.jpg", CubemapAsyncLoader::onImageRead, CubemapAsyncLoader::onImageFailed);
|
|
}
|
|
else
|
|
{
|
|
WasmOcctView::Instance().View()->SetBackgroundCubeMap (Handle(Graphic3d_CubeMapPacked)(), true, false);
|
|
WasmOcctView::Instance().UpdateView();
|
|
}
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : fitAllObjects
|
|
// Purpose :
|
|
// ================================================================
|
|
void WasmOcctView::fitAllObjects (bool theAuto,
|
|
bool theToUpdate)
|
|
{
|
|
WasmOcctView& aViewer = Instance();
|
|
if (theAuto)
|
|
{
|
|
aViewer.FitAllAuto (aViewer.Context(), aViewer.View());
|
|
}
|
|
else
|
|
{
|
|
aViewer.View()->FitAll (0.01, false);
|
|
}
|
|
if (theToUpdate)
|
|
{
|
|
aViewer.UpdateView();
|
|
}
|
|
}
|
|
|
|
EM_JS(void, jsOnDisplayedObjectsChanged, (), {
|
|
Module.onDisplayedObjectsChanged();
|
|
});
|
|
EM_JS(void, jsOnSelectedObjectsChanged, (), {
|
|
Module.onSelectedObjectsChanged();
|
|
});
|
|
|
|
// ================================================================
|
|
// 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 :
|
|
// ================================================================
|
|
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())
|
|
{
|
|
if (!aList.empty()) { aList += THE_LIST_SEPARATOR; }
|
|
aList += anObjIter.Key().ToCString();
|
|
}
|
|
return aList;
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : selectedObjects
|
|
// Purpose :
|
|
// ================================================================
|
|
std::string WasmOcctView::selectedObjects()
|
|
{
|
|
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()->IsSelected (anObjIter.Value()))
|
|
{
|
|
if (!aList.empty()) { aList += THE_LIST_SEPARATOR; }
|
|
aList += anObjIter.Key().ToCString();
|
|
}
|
|
}
|
|
return aList;
|
|
}
|
|
|
|
// ================================================================
|
|
// 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 : removeObjectList
|
|
// Purpose :
|
|
// ================================================================
|
|
bool WasmOcctView::removeObjectList (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))
|
|
{
|
|
/// 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());
|
|
}
|
|
|
|
if (hasChanged)
|
|
{
|
|
if (theToUpdate) { aViewer.UpdateView(); }
|
|
aViewer.onDisplayedObjectsChanged();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : eraseObjectList
|
|
// Purpose :
|
|
// ================================================================
|
|
bool WasmOcctView::eraseObjectList (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))
|
|
{
|
|
/// 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);
|
|
}
|
|
|
|
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 bool theToExpand)
|
|
{
|
|
ModelAsyncLoader* aTask = new ModelAsyncLoader (theName.c_str(), theModelPath.c_str(), theToExpand);
|
|
emscripten_async_wget_data (theModelPath.c_str(), (void* )aTask, ModelAsyncLoader::onDataRead, ModelAsyncLoader::onReadFailed);
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : openFromMemory
|
|
// Purpose :
|
|
// ================================================================
|
|
bool WasmOcctView::openFromMemory (const std::string& theName,
|
|
const bool theToExpand,
|
|
uintptr_t theBuffer, int theDataLen,
|
|
bool theToFree)
|
|
{
|
|
removeObjectList (theName, false);
|
|
char* aBytes = reinterpret_cast<char*>(theBuffer);
|
|
if (aBytes == nullptr
|
|
|| theDataLen <= 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Function to check if specified data stream starts with specified header.
|
|
#define dataStartsWithHeader(theData, theHeader) (::strncmp(theData, theHeader, sizeof(theHeader) - 1) == 0)
|
|
|
|
if (dataStartsWithHeader(aBytes, "DBRep_DrawableShape"))
|
|
{
|
|
return openBRepFromMemory (theName, theToExpand, theBuffer, theDataLen, theToFree);
|
|
}
|
|
else if (dataStartsWithHeader(aBytes, "glTF"))
|
|
{
|
|
return openGltfFromMemory (theName, theToExpand, theBuffer, theDataLen, theToFree);
|
|
}
|
|
if (theToFree)
|
|
{
|
|
free (aBytes);
|
|
}
|
|
|
|
Message::SendFail() << "Error: file '" << theName.c_str() << "' has unsupported format";
|
|
return false;
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : openBRepFromMemory
|
|
// Purpose :
|
|
// ================================================================
|
|
bool WasmOcctView::openBRepFromMemory (const std::string& theName,
|
|
const bool theToExpand,
|
|
uintptr_t theBuffer, int theDataLen,
|
|
bool theToFree)
|
|
{
|
|
removeObjectList (theName, false);
|
|
|
|
/*WasmOcctView& aViewer = Instance();
|
|
TopoDS_Shape aShape;
|
|
BRep_Builder aBuilder;
|
|
bool isLoaded = false;
|
|
{
|
|
char* aRawData = reinterpret_cast<char*>(theBuffer);
|
|
Standard_ArrayStreamBuffer aStreamBuffer (aRawData, theDataLen);
|
|
std::istream aStream (&aStreamBuffer);
|
|
BRepTools::Read (aShape, aStream, aBuilder);
|
|
if (theToFree)
|
|
{
|
|
free (aRawData);
|
|
}
|
|
isLoaded = true;
|
|
}
|
|
if (!isLoaded)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Handle(AIS_Shape) aShapePrs = new AIS_Shape (aShape);
|
|
if (!theName.empty())
|
|
{
|
|
aViewer.myObjects.Add (theName.c_str(), aShapePrs);
|
|
}
|
|
aShapePrs->SetMaterial (Graphic3d_NameOfMaterial_Silver);
|
|
aViewer.Context()->Display (aShapePrs, AIS_Shaded, 0, false);
|
|
aViewer.View()->FitAll (0.01, false);
|
|
aViewer.UpdateView();
|
|
|
|
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 : toShowGround
|
|
// Purpose :
|
|
// ================================================================
|
|
bool WasmOcctView::toShowGround()
|
|
{
|
|
return Instance().myToShowGround;
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : setShowGround
|
|
// Purpose :
|
|
// ================================================================
|
|
void WasmOcctView::setShowGround (bool theToShow,
|
|
bool theToUpdate)
|
|
{
|
|
static Handle(WasmOcctObject) aGroundPrs = new WasmOcctObject();
|
|
|
|
WasmOcctView& aViewer = Instance();
|
|
aViewer.myToShowGround = theToShow;
|
|
Bnd_Box aBox;
|
|
if (theToShow)
|
|
{
|
|
aViewer.Context()->Remove (aGroundPrs, false);
|
|
aBox = aViewer.View()->View()->MinMaxValues();
|
|
}
|
|
if (aBox.IsVoid()
|
|
|| aBox.IsZThin (Precision::Confusion()))
|
|
{
|
|
if (!aGroundPrs.IsNull()
|
|
&& aGroundPrs->HasInteractiveContext())
|
|
{
|
|
aViewer.Context()->Remove (aGroundPrs, false);
|
|
if (theToUpdate)
|
|
{
|
|
aViewer.UpdateView();
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
const gp_XYZ aSize = aBox.CornerMax().XYZ() - aBox.CornerMin().XYZ();
|
|
const double aRadius = Max (aSize.X(), aSize.Y());
|
|
const double aZValue = aBox.CornerMin().Z() - Min (10.0, aSize.Z() * 0.01);
|
|
const double aZSize = aRadius * 0.01;
|
|
gp_XYZ aGroundCenter ((aBox.CornerMin().X() + aBox.CornerMax().X()) * 0.5,
|
|
(aBox.CornerMin().Y() + aBox.CornerMax().Y()) * 0.5,
|
|
aZValue);
|
|
|
|
TopoDS_Compound aGround;
|
|
gp_Trsf aTrsf1, aTrsf2;
|
|
aTrsf1.SetTranslation (gp_Vec (aGroundCenter - gp_XYZ(0.0, 0.0, aZSize)));
|
|
aTrsf2.SetTranslation (gp_Vec (aGroundCenter));
|
|
Prs3d_ToolCylinder aCylTool (aRadius, aRadius, aZSize, 50, 1);
|
|
Prs3d_ToolDisk aDiskTool (0.0, aRadius, 50, 1);
|
|
TopoDS_Face aCylFace, aDiskFace1, aDiskFace2;
|
|
BRep_Builder().MakeFace (aCylFace, aCylTool .CreatePolyTriangulation (aTrsf1));
|
|
BRep_Builder().MakeFace (aDiskFace1, aDiskTool.CreatePolyTriangulation (aTrsf1));
|
|
BRep_Builder().MakeFace (aDiskFace2, aDiskTool.CreatePolyTriangulation (aTrsf2));
|
|
|
|
BRep_Builder().MakeCompound (aGround);
|
|
BRep_Builder().Add (aGround, aCylFace);
|
|
BRep_Builder().Add (aGround, aDiskFace1);
|
|
BRep_Builder().Add (aGround, aDiskFace2);
|
|
|
|
aGroundPrs->SetShape (aGround);
|
|
aGroundPrs->SetToUpdate();
|
|
aGroundPrs->SetMaterial (Graphic3d_NameOfMaterial_Stone);
|
|
aGroundPrs->SetInfiniteState (false);
|
|
aViewer.Context()->Display (aGroundPrs, 0, -1, false);
|
|
aGroundPrs->SetInfiniteState (true);
|
|
if (theToUpdate)
|
|
{
|
|
aViewer.UpdateView();
|
|
}
|
|
}
|
|
|
|
// Module exports
|
|
EMSCRIPTEN_BINDINGS(OccViewerModule) {
|
|
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());
|
|
}
|