mirror of
https://git.dev.opencascade.org/repos/occt.git
synced 2025-04-03 17:56:21 +03:00
Fixed emscripten_async_call() calls to use requestAnimationFrame() instead of setTimeout(). ViewerTest_EventManager::handleViewRedraw() - fixed queuing new onWasmRedrawView() before previous one has been processed (leading to multiplying of pending redraws executed at the same time). RWGltf_TriangulationReader::readDracoBuffer - suppressed CLang compilation warnings.
994 lines
35 KiB
C++
994 lines
35 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 <AIS_Shape.hxx>
|
|
#include <AIS_ViewCube.hxx>
|
|
#include <Aspect_Handle.hxx>
|
|
#include <Aspect_DisplayConnection.hxx>
|
|
#include <Image_AlienPixMap.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 <Wasm_Window.hxx>
|
|
|
|
#include <BRep_Builder.hxx>
|
|
#include <BRepBndLib.hxx>
|
|
#include <BRepTools.hxx>
|
|
#include <Standard_ArrayStreamBuffer.hxx>
|
|
|
|
#include <emscripten/bind.h>
|
|
|
|
#include <iostream>
|
|
|
|
#define THE_CANVAS_ID "canvas"
|
|
|
|
namespace
|
|
{
|
|
//! Auxiliary wrapper for loading model.
|
|
struct ModelAsyncLoader
|
|
{
|
|
std::string Name;
|
|
std::string Path;
|
|
|
|
ModelAsyncLoader (const char* theName, const char* thePath)
|
|
: Name (theName), Path (thePath) {}
|
|
|
|
//! File data read event.
|
|
static void onDataRead (void* theOpaque, void* theBuffer, int theDataLen)
|
|
{
|
|
const ModelAsyncLoader* aTask = (ModelAsyncLoader* )theOpaque;
|
|
WasmOcctView::openFromMemory (aTask->Name, reinterpret_cast<uintptr_t>(theBuffer), theDataLen, false);
|
|
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(Image_AlienPixMap) anImage = new Image_AlienPixMap();
|
|
if (anImage->Load (theFilePath))
|
|
{
|
|
aCubemap = new Graphic3d_CubeMapPacked (anImage);
|
|
}
|
|
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);
|
|
}
|
|
};
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : Instance
|
|
// Purpose :
|
|
// ================================================================
|
|
WasmOcctView& WasmOcctView::Instance()
|
|
{
|
|
static WasmOcctView aViewer;
|
|
return aViewer;
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : WasmOcctView
|
|
// Purpose :
|
|
// ================================================================
|
|
WasmOcctView::WasmOcctView()
|
|
: myDevicePixelRatio (1.0f),
|
|
myNbUpdateRequests (0)
|
|
{
|
|
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 = emscripten_get_device_pixel_ratio();
|
|
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);
|
|
// bind these events to window to track mouse movements outside of canvas
|
|
//emscripten_set_mouseup_callback (aTargetId, this, toUseCapture, onMouseCallback);
|
|
//emscripten_set_mousemove_callback (aTargetId, this, toUseCapture, onMouseCallback);
|
|
//emscripten_set_mouseleave_callback (aTargetId, this, toUseCapture, onMouseCallback);
|
|
emscripten_set_mouseup_callback (EMSCRIPTEN_EVENT_TARGET_WINDOW, this, toUseCapture, onMouseCallback);
|
|
emscripten_set_mousemove_callback (EMSCRIPTEN_EVENT_TARGET_WINDOW, 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_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 (aTargetId, this, toUseCapture, onKeyCallback);
|
|
emscripten_set_keydown_callback (aTargetId, this, toUseCapture, onKeyDownCallback);
|
|
emscripten_set_keyup_callback (aTargetId, this, toUseCapture, onKeyUpCallback);
|
|
//emscripten_set_focus_callback (aTargetId, this, toUseCapture, onFocusCallback);
|
|
//emscripten_set_focusin_callback (aTargetId, this, toUseCapture, onFocusCallback);
|
|
emscripten_set_focusout_callback (aTargetId, this, toUseCapture, onFocusCallback);
|
|
}
|
|
|
|
// ================================================================
|
|
// 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);
|
|
}*/
|
|
|
|
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
|
|
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_TypeOfShadingModel_Phong);
|
|
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_TypeOfLightSource_Directional)
|
|
{
|
|
aLight->SetCastShadows (true);
|
|
}
|
|
}
|
|
|
|
Handle(Wasm_Window) aWindow = new Wasm_Window (THE_CANVAS_ID);
|
|
aWindow->Size (myWinSizeOld.x(), myWinSizeOld.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->SetImmediateUpdate (false);
|
|
myView->ChangeRenderingParams().IsShadowEnabled = false;
|
|
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);
|
|
|
|
myContext = new AIS_InteractiveContext (aViewer);
|
|
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 : ProcessInput
|
|
// Purpose :
|
|
// ================================================================
|
|
void WasmOcctView::ProcessInput()
|
|
{
|
|
if (!myView.IsNull())
|
|
{
|
|
// Queue onRedrawView()/redrawView callback to redraw canvas after all user input is flushed by browser.
|
|
// Redrawing viewer on every single message would be a pointless waste of resources,
|
|
// as user will see only the last drawn frame due to WebGL implementation details.
|
|
// -1 in emscripten_async_call() redirects to requestAnimationFrame();
|
|
// requestPostAnimationFrame() is a better under development alternative.
|
|
if (++myNbUpdateRequests == 1)
|
|
{
|
|
emscripten_async_call (onRedrawView, this, -1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : UpdateView
|
|
// Purpose :
|
|
// ================================================================
|
|
void WasmOcctView::UpdateView()
|
|
{
|
|
if (!myView.IsNull())
|
|
{
|
|
myView->Invalidate();
|
|
// queue next onRedrawView()/redrawView()
|
|
ProcessInput();
|
|
}
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : redrawView
|
|
// Purpose :
|
|
// ================================================================
|
|
void WasmOcctView::redrawView()
|
|
{
|
|
if (!myView.IsNull())
|
|
{
|
|
myNbUpdateRequests = 0;
|
|
FlushViewEvents (myContext, myView, true);
|
|
}
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : handleViewRedraw
|
|
// Purpose :
|
|
// ================================================================
|
|
void WasmOcctView::handleViewRedraw (const Handle(AIS_InteractiveContext)& theCtx,
|
|
const Handle(V3d_View)& theView)
|
|
{
|
|
AIS_ViewController::handleViewRedraw (theCtx, theView);
|
|
if (myToAskNextFrame)
|
|
{
|
|
// ask more frames
|
|
if (++myNbUpdateRequests == 1)
|
|
{
|
|
emscripten_async_call (onRedrawView, this, -1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ================================================================
|
|
// 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(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (myView->Window());
|
|
Graphic3d_Vec2i aWinSizeNew;
|
|
aWindow->DoResize();
|
|
aWindow->Size (aWinSizeNew.x(), aWinSizeNew.y());
|
|
const float aPixelRatio = emscripten_get_device_pixel_ratio();
|
|
if (aWinSizeNew != myWinSizeOld
|
|
|| aPixelRatio != myDevicePixelRatio)
|
|
{
|
|
myWinSizeOld = aWinSizeNew;
|
|
if (myDevicePixelRatio != aPixelRatio)
|
|
{
|
|
myDevicePixelRatio = aPixelRatio;
|
|
initPixelScaleRatio();
|
|
}
|
|
|
|
myView->MustBeResized();
|
|
myView->Invalidate();
|
|
myView->Redraw();
|
|
dumpGlInfo (true);
|
|
}
|
|
return EM_TRUE;
|
|
}
|
|
|
|
//! Update canvas bounding rectangle.
|
|
EM_JS(void, jsUpdateBoundingClientRect, (), {
|
|
Module._myCanvasRect = Module.canvas.getBoundingClientRect();
|
|
});
|
|
|
|
//! Get canvas bounding top.
|
|
EM_JS(int, jsGetBoundingClientTop, (), {
|
|
return Math.round(Module._myCanvasRect.top);
|
|
});
|
|
|
|
//! Get canvas bounding left.
|
|
EM_JS(int, jsGetBoundingClientLeft, (), {
|
|
return Math.round(Module._myCanvasRect.left);
|
|
});
|
|
|
|
// ================================================================
|
|
// Function : onMouseEvent
|
|
// Purpose :
|
|
// ================================================================
|
|
EM_BOOL WasmOcctView::onMouseEvent (int theEventType, const EmscriptenMouseEvent* theEvent)
|
|
{
|
|
if (myView.IsNull())
|
|
{
|
|
return EM_FALSE;
|
|
}
|
|
|
|
Handle(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (myView->Window());
|
|
if (theEventType == EMSCRIPTEN_EVENT_MOUSEMOVE
|
|
|| theEventType == EMSCRIPTEN_EVENT_MOUSEUP)
|
|
{
|
|
// these events are bound to EMSCRIPTEN_EVENT_TARGET_WINDOW, and coordinates should be converted
|
|
jsUpdateBoundingClientRect();
|
|
EmscriptenMouseEvent anEvent = *theEvent;
|
|
anEvent.targetX -= jsGetBoundingClientLeft();
|
|
anEvent.targetY -= jsGetBoundingClientTop();
|
|
aWindow->ProcessMouseEvent (*this, theEventType, &anEvent);
|
|
return EM_FALSE;
|
|
}
|
|
|
|
return aWindow->ProcessMouseEvent (*this, theEventType, theEvent) ? EM_TRUE : EM_FALSE;
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : onWheelEvent
|
|
// Purpose :
|
|
// ================================================================
|
|
EM_BOOL WasmOcctView::onWheelEvent (int theEventType, const EmscriptenWheelEvent* theEvent)
|
|
{
|
|
if (myView.IsNull()
|
|
|| theEventType != EMSCRIPTEN_EVENT_WHEEL)
|
|
{
|
|
return EM_FALSE;
|
|
}
|
|
|
|
Handle(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (myView->Window());
|
|
return aWindow->ProcessWheelEvent (*this, theEventType, theEvent) ? EM_TRUE : EM_FALSE;
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : onTouchEvent
|
|
// Purpose :
|
|
// ================================================================
|
|
EM_BOOL WasmOcctView::onTouchEvent (int theEventType, const EmscriptenTouchEvent* theEvent)
|
|
{
|
|
if (myView.IsNull())
|
|
{
|
|
return EM_FALSE;
|
|
}
|
|
|
|
Handle(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (myView->Window());
|
|
return aWindow->ProcessTouchEvent (*this, theEventType, theEvent) ? 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 : onFocusEvent
|
|
// Purpose :
|
|
// ================================================================
|
|
EM_BOOL WasmOcctView::onFocusEvent (int theEventType, const EmscriptenFocusEvent* theEvent)
|
|
{
|
|
if (myView.IsNull()
|
|
|| (theEventType != EMSCRIPTEN_EVENT_FOCUS
|
|
&& theEventType != EMSCRIPTEN_EVENT_FOCUSIN // about to receive focus
|
|
&& theEventType != EMSCRIPTEN_EVENT_FOCUSOUT))
|
|
{
|
|
return EM_FALSE;
|
|
}
|
|
|
|
Handle(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (myView->Window());
|
|
return aWindow->ProcessFocusEvent (*this, theEventType, theEvent) ? EM_TRUE : EM_FALSE;
|
|
}
|
|
|
|
// ================================================================
|
|
// 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;
|
|
}
|
|
|
|
Handle(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (myView->Window());
|
|
return aWindow->ProcessKeyEvent (*this, theEventType, theEvent) ? EM_TRUE : EM_FALSE;
|
|
}
|
|
|
|
//=======================================================================
|
|
//function : KeyDown
|
|
//purpose :
|
|
//=======================================================================
|
|
void WasmOcctView::KeyDown (Aspect_VKey theKey,
|
|
double theTime,
|
|
double thePressure)
|
|
{
|
|
const unsigned int aModifOld = myKeys.Modifiers();
|
|
AIS_ViewController::KeyDown (theKey, theTime, thePressure);
|
|
|
|
const unsigned int aModifNew = myKeys.Modifiers();
|
|
if (aModifNew != aModifOld
|
|
&& navigationKeyModifierSwitch (aModifOld, aModifNew, theTime))
|
|
{
|
|
// modifier key just pressed
|
|
}
|
|
|
|
Aspect_VKey anAction = Aspect_VKey_UNKNOWN;
|
|
if (myNavKeyMap.Find (theKey | myKeys.Modifiers(), anAction)
|
|
&& anAction != Aspect_VKey_UNKNOWN)
|
|
{
|
|
AIS_ViewController::KeyDown (anAction, theTime, thePressure);
|
|
}
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : onKeyUpEvent
|
|
// Purpose :
|
|
// ================================================================
|
|
EM_BOOL WasmOcctView::onKeyUpEvent (int theEventType, const EmscriptenKeyboardEvent* theEvent)
|
|
{
|
|
if (myView.IsNull()
|
|
|| theEventType != EMSCRIPTEN_EVENT_KEYUP)
|
|
{
|
|
return EM_FALSE;
|
|
}
|
|
|
|
Handle(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (myView->Window());
|
|
return aWindow->ProcessKeyEvent (*this, theEventType, theEvent) ? EM_TRUE : EM_FALSE;
|
|
}
|
|
|
|
//=======================================================================
|
|
//function : KeyUp
|
|
//purpose :
|
|
//=======================================================================
|
|
void WasmOcctView::KeyUp (Aspect_VKey theKey,
|
|
double theTime)
|
|
{
|
|
const unsigned int aModifOld = myKeys.Modifiers();
|
|
AIS_ViewController::KeyUp (theKey, theTime);
|
|
|
|
Aspect_VKey anAction = Aspect_VKey_UNKNOWN;
|
|
if (myNavKeyMap.Find (theKey | myKeys.Modifiers(), anAction)
|
|
&& anAction != Aspect_VKey_UNKNOWN)
|
|
{
|
|
AIS_ViewController::KeyUp (anAction, theTime);
|
|
processKeyPress (anAction);
|
|
}
|
|
|
|
const unsigned int aModifNew = myKeys.Modifiers();
|
|
if (aModifNew != aModifOld
|
|
&& navigationKeyModifierSwitch (aModifOld, aModifNew, theTime))
|
|
{
|
|
// modifier key released
|
|
}
|
|
|
|
processKeyPress (theKey | aModifNew);
|
|
}
|
|
|
|
//==============================================================================
|
|
//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 : setCubemapBackground
|
|
// Purpose :
|
|
// ================================================================
|
|
void WasmOcctView::setCubemapBackground (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)
|
|
{
|
|
WasmOcctView& aViewer = Instance();
|
|
if (theAuto)
|
|
{
|
|
aViewer.FitAllAuto (aViewer.Context(), aViewer.View());
|
|
}
|
|
else
|
|
{
|
|
aViewer.View()->FitAll (0.01, false);
|
|
}
|
|
aViewer.UpdateView();
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : removeAllObjects
|
|
// Purpose :
|
|
// ================================================================
|
|
void WasmOcctView::removeAllObjects()
|
|
{
|
|
WasmOcctView& aViewer = Instance();
|
|
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();
|
|
aViewer.UpdateView();
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : removeObject
|
|
// Purpose :
|
|
// ================================================================
|
|
bool WasmOcctView::removeObject (const std::string& theName)
|
|
{
|
|
WasmOcctView& aViewer = Instance();
|
|
Handle(AIS_InteractiveObject) anObj;
|
|
if (!theName.empty()
|
|
&& !aViewer.myObjects.FindFromKey (theName.c_str(), anObj))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
aViewer.Context()->Remove (anObj, false);
|
|
aViewer.myObjects.RemoveKey (theName.c_str());
|
|
aViewer.UpdateView();
|
|
return true;
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : eraseObject
|
|
// Purpose :
|
|
// ================================================================
|
|
bool WasmOcctView::eraseObject (const std::string& theName)
|
|
{
|
|
WasmOcctView& aViewer = Instance();
|
|
Handle(AIS_InteractiveObject) anObj;
|
|
if (!theName.empty()
|
|
&& !aViewer.myObjects.FindFromKey (theName.c_str(), anObj))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
aViewer.Context()->Erase (anObj, false);
|
|
aViewer.UpdateView();
|
|
return true;
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : displayObject
|
|
// Purpose :
|
|
// ================================================================
|
|
bool WasmOcctView::displayObject (const std::string& theName)
|
|
{
|
|
WasmOcctView& aViewer = Instance();
|
|
Handle(AIS_InteractiveObject) anObj;
|
|
if (!theName.empty()
|
|
&& !aViewer.myObjects.FindFromKey (theName.c_str(), anObj))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
aViewer.Context()->Display (anObj, false);
|
|
aViewer.UpdateView();
|
|
return true;
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : openFromUrl
|
|
// Purpose :
|
|
// ================================================================
|
|
void WasmOcctView::openFromUrl (const std::string& theName,
|
|
const std::string& theModelPath)
|
|
{
|
|
ModelAsyncLoader* aTask = new ModelAsyncLoader (theName.c_str(), theModelPath.c_str());
|
|
emscripten_async_wget_data (theModelPath.c_str(), (void* )aTask, ModelAsyncLoader::onDataRead, ModelAsyncLoader::onReadFailed);
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : openFromMemory
|
|
// Purpose :
|
|
// ================================================================
|
|
bool WasmOcctView::openFromMemory (const std::string& theName,
|
|
uintptr_t theBuffer, int theDataLen,
|
|
bool theToFree)
|
|
{
|
|
removeObject (theName);
|
|
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, theBuffer, theDataLen, theToFree);
|
|
}
|
|
else if (dataStartsWithHeader(aBytes, "glTF"))
|
|
{
|
|
//return openGltfFromMemory (theName, 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,
|
|
uintptr_t theBuffer, int theDataLen,
|
|
bool theToFree)
|
|
{
|
|
removeObject (theName);
|
|
|
|
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;
|
|
}
|
|
|
|
// ================================================================
|
|
// Function : displayGround
|
|
// Purpose :
|
|
// ================================================================
|
|
void WasmOcctView::displayGround (bool theToShow)
|
|
{
|
|
static Handle(AIS_Shape) aGroundPrs = new AIS_Shape (TopoDS_Shape());
|
|
|
|
WasmOcctView& aViewer = Instance();
|
|
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);
|
|
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, AIS_Shaded, -1, false);
|
|
aGroundPrs->SetInfiniteState (true);
|
|
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());
|
|
}
|