0032121: Draw Harness, ViewerTest - implement -reset option for vlight command
[occt.git] / samples / webgl / WasmOcctView.cpp
CommitLineData
565baee6 1// Copyright (c) 2019 OPEN CASCADE SAS
2//
3// This file is part of the examples of the Open CASCADE Technology software library.
4//
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to deal
7// in the Software without restriction, including without limitation the rights
8// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9// copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11//
12// The above copyright notice and this permission notice shall be included in all
13// copies or substantial portions of the Software.
14//
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
21
22#include "WasmOcctView.h"
23
565baee6 24#include <AIS_Shape.hxx>
25#include <AIS_ViewCube.hxx>
26#include <Aspect_Handle.hxx>
27#include <Aspect_DisplayConnection.hxx>
16222b8c 28#include <Image_AlienPixMap.hxx>
565baee6 29#include <Message.hxx>
30#include <Message_Messenger.hxx>
5de4b704 31#include <Graphic3d_CubeMapPacked.hxx>
565baee6 32#include <OpenGl_GraphicDriver.hxx>
33#include <Prs3d_DatumAspect.hxx>
5de4b704 34#include <Prs3d_ToolCylinder.hxx>
35#include <Prs3d_ToolDisk.hxx>
f9ab9f7f 36#include <Wasm_Window.hxx>
5de4b704 37
38#include <BRep_Builder.hxx>
39#include <BRepBndLib.hxx>
40#include <BRepTools.hxx>
41#include <Standard_ArrayStreamBuffer.hxx>
42
43#include <emscripten/bind.h>
565baee6 44
45#include <iostream>
46
47#define THE_CANVAS_ID "canvas"
48
49namespace
50{
5de4b704 51 //! Auxiliary wrapper for loading model.
52 struct ModelAsyncLoader
53 {
54 std::string Name;
55 std::string Path;
56
57 ModelAsyncLoader (const char* theName, const char* thePath)
58 : Name (theName), Path (thePath) {}
59
60 //! File data read event.
61 static void onDataRead (void* theOpaque, void* theBuffer, int theDataLen)
62 {
63 const ModelAsyncLoader* aTask = (ModelAsyncLoader* )theOpaque;
64 WasmOcctView::openFromMemory (aTask->Name, reinterpret_cast<uintptr_t>(theBuffer), theDataLen, false);
65 delete aTask;
66 }
67
68 //! File read error event.
69 static void onReadFailed (void* theOpaque)
70 {
71 const ModelAsyncLoader* aTask = (ModelAsyncLoader* )theOpaque;
72 Message::DefaultMessenger()->Send (TCollection_AsciiString("Error: unable to load file ") + aTask->Path.c_str(), Message_Fail);
73 delete aTask;
74 }
75 };
76
77 //! Auxiliary wrapper for loading cubemap.
78 struct CubemapAsyncLoader
79 {
80 //! Image file read event.
81 static void onImageRead (const char* theFilePath)
82 {
83 Handle(Graphic3d_CubeMapPacked) aCubemap;
16222b8c 84 Handle(Image_AlienPixMap) anImage = new Image_AlienPixMap();
85 if (anImage->Load (theFilePath))
5de4b704 86 {
87 aCubemap = new Graphic3d_CubeMapPacked (anImage);
88 }
89 WasmOcctView::Instance().View()->SetBackgroundCubeMap (aCubemap, true, false);
90 WasmOcctView::Instance().UpdateView();
91 }
92
93 //! Image file failed read event.
94 static void onImageFailed (const char* theFilePath)
95 {
96 Message::DefaultMessenger()->Send (TCollection_AsciiString("Error: unable to load image ") + theFilePath, Message_Fail);
97 }
98 };
99}
100
101// ================================================================
102// Function : Instance
103// Purpose :
104// ================================================================
105WasmOcctView& WasmOcctView::Instance()
106{
107 static WasmOcctView aViewer;
108 return aViewer;
565baee6 109}
110
111// ================================================================
112// Function : WasmOcctView
113// Purpose :
114// ================================================================
115WasmOcctView::WasmOcctView()
116: myDevicePixelRatio (1.0f),
117 myUpdateRequests (0)
118{
e03a03fd 119 addActionHotKeys (Aspect_VKey_NavForward, Aspect_VKey_W, Aspect_VKey_W | Aspect_VKeyFlags_SHIFT);
120 addActionHotKeys (Aspect_VKey_NavBackward , Aspect_VKey_S, Aspect_VKey_S | Aspect_VKeyFlags_SHIFT);
121 addActionHotKeys (Aspect_VKey_NavSlideLeft, Aspect_VKey_A, Aspect_VKey_A | Aspect_VKeyFlags_SHIFT);
122 addActionHotKeys (Aspect_VKey_NavSlideRight, Aspect_VKey_D, Aspect_VKey_D | Aspect_VKeyFlags_SHIFT);
123 addActionHotKeys (Aspect_VKey_NavRollCCW, Aspect_VKey_Q, Aspect_VKey_Q | Aspect_VKeyFlags_SHIFT);
124 addActionHotKeys (Aspect_VKey_NavRollCW, Aspect_VKey_E, Aspect_VKey_E | Aspect_VKeyFlags_SHIFT);
125
126 addActionHotKeys (Aspect_VKey_NavSpeedIncrease, Aspect_VKey_Plus, Aspect_VKey_Plus | Aspect_VKeyFlags_SHIFT,
127 Aspect_VKey_Equal,
128 Aspect_VKey_NumpadAdd, Aspect_VKey_NumpadAdd | Aspect_VKeyFlags_SHIFT);
129 addActionHotKeys (Aspect_VKey_NavSpeedDecrease, Aspect_VKey_Minus, Aspect_VKey_Minus | Aspect_VKeyFlags_SHIFT,
130 Aspect_VKey_NumpadSubtract, Aspect_VKey_NumpadSubtract | Aspect_VKeyFlags_SHIFT);
131
132 // arrow keys conflict with browser page scrolling, so better be avoided in non-fullscreen mode
133 addActionHotKeys (Aspect_VKey_NavLookUp, Aspect_VKey_Numpad8); // Aspect_VKey_Up
134 addActionHotKeys (Aspect_VKey_NavLookDown, Aspect_VKey_Numpad2); // Aspect_VKey_Down
135 addActionHotKeys (Aspect_VKey_NavLookLeft, Aspect_VKey_Numpad4); // Aspect_VKey_Left
136 addActionHotKeys (Aspect_VKey_NavLookRight, Aspect_VKey_Numpad6); // Aspect_VKey_Right
137 addActionHotKeys (Aspect_VKey_NavSlideLeft, Aspect_VKey_Numpad1); // Aspect_VKey_Left |Aspect_VKeyFlags_SHIFT
138 addActionHotKeys (Aspect_VKey_NavSlideRight, Aspect_VKey_Numpad3); // Aspect_VKey_Right|Aspect_VKeyFlags_SHIFT
139 addActionHotKeys (Aspect_VKey_NavSlideUp, Aspect_VKey_Numpad9); // Aspect_VKey_Up |Aspect_VKeyFlags_SHIFT
140 addActionHotKeys (Aspect_VKey_NavSlideDown, Aspect_VKey_Numpad7); // Aspect_VKey_Down |Aspect_VKeyFlags_SHIFT
565baee6 141}
142
143// ================================================================
144// Function : ~WasmOcctView
145// Purpose :
146// ================================================================
147WasmOcctView::~WasmOcctView()
148{
149}
150
151// ================================================================
152// Function : run
153// Purpose :
154// ================================================================
155void WasmOcctView::run()
156{
157 initWindow();
158 initViewer();
159 initDemoScene();
160 if (myView.IsNull())
161 {
162 return;
163 }
164
165 myView->MustBeResized();
166 myView->Redraw();
167
a110c4a3 168 // There is no infinite message loop, main() will return from here immediately.
565baee6 169 // Tell that our Module should be left loaded and handle events through callbacks.
170 //emscripten_set_main_loop (redrawView, 60, 1);
171 //emscripten_set_main_loop (redrawView, -1, 1);
172 EM_ASM(Module['noExitRuntime'] = true);
173}
174
175// ================================================================
176// Function : initWindow
177// Purpose :
178// ================================================================
179void WasmOcctView::initWindow()
180{
f9ab9f7f 181 myDevicePixelRatio = emscripten_get_device_pixel_ratio();
565baee6 182 myCanvasId = THE_CANVAS_ID;
7465bfa6 183 const char* aTargetId = !myCanvasId.IsEmpty() ? myCanvasId.ToCString() : EMSCRIPTEN_EVENT_TARGET_WINDOW;
565baee6 184 const EM_BOOL toUseCapture = EM_TRUE;
7465bfa6 185 emscripten_set_resize_callback (EMSCRIPTEN_EVENT_TARGET_WINDOW, this, toUseCapture, onResizeCallback);
565baee6 186
7465bfa6 187 emscripten_set_mousedown_callback (aTargetId, this, toUseCapture, onMouseCallback);
188 emscripten_set_mouseup_callback (aTargetId, this, toUseCapture, onMouseCallback);
189 emscripten_set_mousemove_callback (aTargetId, this, toUseCapture, onMouseCallback);
565baee6 190 emscripten_set_dblclick_callback (aTargetId, this, toUseCapture, onMouseCallback);
191 emscripten_set_click_callback (aTargetId, this, toUseCapture, onMouseCallback);
192 emscripten_set_mouseenter_callback (aTargetId, this, toUseCapture, onMouseCallback);
193 emscripten_set_mouseleave_callback (aTargetId, this, toUseCapture, onMouseCallback);
194 emscripten_set_wheel_callback (aTargetId, this, toUseCapture, onWheelCallback);
195
196 emscripten_set_touchstart_callback (aTargetId, this, toUseCapture, onTouchCallback);
197 emscripten_set_touchend_callback (aTargetId, this, toUseCapture, onTouchCallback);
198 emscripten_set_touchmove_callback (aTargetId, this, toUseCapture, onTouchCallback);
199 emscripten_set_touchcancel_callback(aTargetId, this, toUseCapture, onTouchCallback);
200
7465bfa6 201 //emscripten_set_keypress_callback (EMSCRIPTEN_EVENT_TARGET_WINDOW, this, toUseCapture, onKeyCallback);
202 emscripten_set_keydown_callback (EMSCRIPTEN_EVENT_TARGET_WINDOW, this, toUseCapture, onKeyDownCallback);
203 emscripten_set_keyup_callback (EMSCRIPTEN_EVENT_TARGET_WINDOW, this, toUseCapture, onKeyUpCallback);
565baee6 204}
205
206// ================================================================
207// Function : dumpGlInfo
208// Purpose :
209// ================================================================
210void WasmOcctView::dumpGlInfo (bool theIsBasic)
211{
212 TColStd_IndexedDataMapOfStringString aGlCapsDict;
213 myView->DiagnosticInformation (aGlCapsDict, theIsBasic ? Graphic3d_DiagnosticInfo_Basic : Graphic3d_DiagnosticInfo_Complete);
214 if (theIsBasic)
215 {
216 TCollection_AsciiString aViewport;
217 aGlCapsDict.FindFromKey ("Viewport", aViewport);
218 aGlCapsDict.Clear();
219 aGlCapsDict.Add ("Viewport", aViewport);
220 }
221 aGlCapsDict.Add ("Display scale", TCollection_AsciiString(myDevicePixelRatio));
222
223 // beautify output
224 {
225 TCollection_AsciiString* aGlVer = aGlCapsDict.ChangeSeek ("GLversion");
226 TCollection_AsciiString* aGlslVer = aGlCapsDict.ChangeSeek ("GLSLversion");
227 if (aGlVer != NULL
228 && aGlslVer != NULL)
229 {
230 *aGlVer = *aGlVer + " [GLSL: " + *aGlslVer + "]";
231 aGlslVer->Clear();
232 }
233 }
234
235 TCollection_AsciiString anInfo;
236 for (TColStd_IndexedDataMapOfStringString::Iterator aValueIter (aGlCapsDict); aValueIter.More(); aValueIter.Next())
237 {
238 if (!aValueIter.Value().IsEmpty())
239 {
240 if (!anInfo.IsEmpty())
241 {
242 anInfo += "\n";
243 }
244 anInfo += aValueIter.Key() + ": " + aValueIter.Value();
245 }
246 }
247
248 ::Message::DefaultMessenger()->Send (anInfo, Message_Warning);
249}
250
251// ================================================================
252// Function : initPixelScaleRatio
253// Purpose :
254// ================================================================
255void WasmOcctView::initPixelScaleRatio()
256{
257 SetTouchToleranceScale (myDevicePixelRatio);
258 if (!myView.IsNull())
259 {
260 myView->ChangeRenderingParams().Resolution = (unsigned int )(96.0 * myDevicePixelRatio + 0.5);
261 }
262 if (!myContext.IsNull())
263 {
264 myContext->SetPixelTolerance (int(myDevicePixelRatio * 6.0));
265 if (!myViewCube.IsNull())
266 {
267 static const double THE_CUBE_SIZE = 60.0;
268 myViewCube->SetSize (myDevicePixelRatio * THE_CUBE_SIZE, false);
269 myViewCube->SetBoxFacetExtension (myViewCube->Size() * 0.15);
270 myViewCube->SetAxesPadding (myViewCube->Size() * 0.10);
271 myViewCube->SetFontHeight (THE_CUBE_SIZE * 0.16);
272 if (myViewCube->HasInteractiveContext())
273 {
274 myContext->Redisplay (myViewCube, false);
275 }
276 }
277 }
278}
279
280// ================================================================
281// Function : initViewer
282// Purpose :
283// ================================================================
284bool WasmOcctView::initViewer()
285{
286 // Build with "--preload-file MyFontFile.ttf" option
287 // and register font in Font Manager to use custom font(s).
288 /*const char* aFontPath = "MyFontFile.ttf";
289 if (Handle(Font_SystemFont) aFont = Font_FontMgr::GetInstance()->CheckFont (aFontPath))
290 {
291 Font_FontMgr::GetInstance()->RegisterFont (aFont, true);
292 }
293 else
294 {
295 Message::DefaultMessenger()->Send (TCollection_AsciiString ("Error: font '") + aFontPath + "' is not found", Message_Fail);
296 }*/
297
298 Handle(Aspect_DisplayConnection) aDisp;
299 Handle(OpenGl_GraphicDriver) aDriver = new OpenGl_GraphicDriver (aDisp, false);
300 aDriver->ChangeOptions().buffersNoSwap = true; // swap has no effect in WebGL
31174e1a 301 aDriver->ChangeOptions().buffersOpaqueAlpha = true; // avoid unexpected blending of canvas with page background
565baee6 302 if (!aDriver->InitContext())
303 {
304 Message::DefaultMessenger()->Send (TCollection_AsciiString ("Error: EGL initialization failed"), Message_Fail);
305 return false;
306 }
307
308 Handle(V3d_Viewer) aViewer = new V3d_Viewer (aDriver);
309 aViewer->SetComputedMode (false);
310 aViewer->SetDefaultShadingModel (Graphic3d_TOSM_FRAGMENT);
311 aViewer->SetDefaultLights();
312 aViewer->SetLightOn();
5de4b704 313 for (V3d_ListOfLight::Iterator aLightIter (aViewer->ActiveLights()); aLightIter.More(); aLightIter.Next())
314 {
315 const Handle(V3d_Light)& aLight = aLightIter.Value();
06d40093 316 if (aLight->Type() == Graphic3d_TypeOfLightSource_Directional)
5de4b704 317 {
318 aLight->SetCastShadows (true);
319 }
320 }
565baee6 321
f9ab9f7f 322 Handle(Wasm_Window) aWindow = new Wasm_Window (THE_CANVAS_ID);
323 aWindow->Size (myWinSizeOld.x(), myWinSizeOld.y());
565baee6 324
325 myTextStyle = new Prs3d_TextAspect();
326 myTextStyle->SetFont (Font_NOF_ASCII_MONO);
327 myTextStyle->SetHeight (12);
328 myTextStyle->Aspect()->SetColor (Quantity_NOC_GRAY95);
329 myTextStyle->Aspect()->SetColorSubTitle (Quantity_NOC_BLACK);
330 myTextStyle->Aspect()->SetDisplayType (Aspect_TODT_SHADOW);
331 myTextStyle->Aspect()->SetTextFontAspect (Font_FA_Bold);
332 myTextStyle->Aspect()->SetTextZoomable (false);
333 myTextStyle->SetHorizontalJustification (Graphic3d_HTA_LEFT);
334 myTextStyle->SetVerticalJustification (Graphic3d_VTA_BOTTOM);
335
336 myView = new V3d_View (aViewer);
5de4b704 337 myView->Camera()->SetProjectionType (Graphic3d_Camera::Projection_Perspective);
565baee6 338 myView->SetImmediateUpdate (false);
5de4b704 339 myView->ChangeRenderingParams().IsShadowEnabled = false;
565baee6 340 myView->ChangeRenderingParams().Resolution = (unsigned int )(96.0 * myDevicePixelRatio + 0.5);
341 myView->ChangeRenderingParams().ToShowStats = true;
342 myView->ChangeRenderingParams().StatsTextAspect = myTextStyle->Aspect();
343 myView->ChangeRenderingParams().StatsTextHeight = (int )myTextStyle->Height();
344 myView->SetWindow (aWindow);
345 dumpGlInfo (false);
346
347 myContext = new AIS_InteractiveContext (aViewer);
348 initPixelScaleRatio();
349 return true;
350}
351
352// ================================================================
353// Function : initDemoScene
354// Purpose :
355// ================================================================
356void WasmOcctView::initDemoScene()
357{
358 if (myContext.IsNull())
359 {
360 return;
361 }
362
363 //myView->TriedronDisplay (Aspect_TOTP_LEFT_LOWER, Quantity_NOC_GOLD, 0.08, V3d_WIREFRAME);
364
365 myViewCube = new AIS_ViewCube();
366 // presentation parameters
367 initPixelScaleRatio();
368 myViewCube->SetTransformPersistence (new Graphic3d_TransformPers (Graphic3d_TMF_TriedronPers, Aspect_TOTP_RIGHT_LOWER, Graphic3d_Vec2i (100, 100)));
369 myViewCube->Attributes()->SetDatumAspect (new Prs3d_DatumAspect());
370 myViewCube->Attributes()->DatumAspect()->SetTextAspect (myTextStyle);
371 // animation parameters
372 myViewCube->SetViewAnimation (myViewAnimation);
373 myViewCube->SetFixedAnimationLoop (false);
374 myViewCube->SetAutoStartAnimation (true);
375 myContext->Display (myViewCube, false);
376
377 // Build with "--preload-file MySampleFile.brep" option to load some shapes here.
378}
379
5de4b704 380// ================================================================
f9ab9f7f 381// Function : ProcessInput
5de4b704 382// Purpose :
383// ================================================================
f9ab9f7f 384void WasmOcctView::ProcessInput()
5de4b704 385{
386 if (!myView.IsNull())
387 {
f9ab9f7f 388 // Queue onRedrawView()/redrawView callback to redraw canvas after all user input is flushed by browser.
389 // Redrawing viewer on every single message would be a pointless waste of resources,
390 // as user will see only the last drawn frame due to WebGL implementation details.
391 if (++myUpdateRequests == 1)
392 {
393 emscripten_async_call (onRedrawView, this, 0);
394 }
5de4b704 395 }
396}
565baee6 397
398// ================================================================
f9ab9f7f 399// Function : UpdateView
565baee6 400// Purpose :
401// ================================================================
f9ab9f7f 402void WasmOcctView::UpdateView()
565baee6 403{
404 if (!myView.IsNull())
405 {
f9ab9f7f 406 myView->Invalidate();
407 // queue next onRedrawView()/redrawView()
408 ProcessInput();
565baee6 409 }
410}
411
412// ================================================================
413// Function : redrawView
414// Purpose :
415// ================================================================
416void WasmOcctView::redrawView()
417{
418 if (!myView.IsNull())
419 {
420 FlushViewEvents (myContext, myView, true);
421 }
422}
423
424// ================================================================
425// Function : handleViewRedraw
426// Purpose :
427// ================================================================
428void WasmOcctView::handleViewRedraw (const Handle(AIS_InteractiveContext)& theCtx,
429 const Handle(V3d_View)& theView)
430{
431 myUpdateRequests = 0;
432 AIS_ViewController::handleViewRedraw (theCtx, theView);
433 if (myToAskNextFrame)
434 {
435 // ask more frames
436 ++myUpdateRequests;
437 emscripten_async_call (onRedrawView, this, 0);
438 }
439}
440
441// ================================================================
442// Function : onResizeEvent
443// Purpose :
444// ================================================================
445EM_BOOL WasmOcctView::onResizeEvent (int theEventType, const EmscriptenUiEvent* theEvent)
446{
447 (void )theEventType; // EMSCRIPTEN_EVENT_RESIZE or EMSCRIPTEN_EVENT_CANVASRESIZED
448 (void )theEvent;
449 if (myView.IsNull())
450 {
451 return EM_FALSE;
452 }
453
f9ab9f7f 454 Handle(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (myView->Window());
455 Graphic3d_Vec2i aWinSizeNew;
456 aWindow->DoResize();
457 aWindow->Size (aWinSizeNew.x(), aWinSizeNew.y());
458 const float aPixelRatio = emscripten_get_device_pixel_ratio();
459 if (aWinSizeNew != myWinSizeOld
565baee6 460 || aPixelRatio != myDevicePixelRatio)
461 {
f9ab9f7f 462 myWinSizeOld = aWinSizeNew;
565baee6 463 if (myDevicePixelRatio != aPixelRatio)
464 {
465 myDevicePixelRatio = aPixelRatio;
466 initPixelScaleRatio();
467 }
f9ab9f7f 468
565baee6 469 myView->MustBeResized();
470 myView->Invalidate();
471 myView->Redraw();
472 dumpGlInfo (true);
473 }
474 return EM_TRUE;
475}
476
477// ================================================================
478// Function : onMouseEvent
479// Purpose :
480// ================================================================
481EM_BOOL WasmOcctView::onMouseEvent (int theEventType, const EmscriptenMouseEvent* theEvent)
482{
483 if (myView.IsNull())
484 {
485 return EM_FALSE;
486 }
487
f9ab9f7f 488 Handle(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (myView->Window());
489 return aWindow->ProcessMouseEvent (*this, theEventType, theEvent) ? EM_TRUE : EM_FALSE;
565baee6 490}
491
492// ================================================================
493// Function : onWheelEvent
494// Purpose :
495// ================================================================
496EM_BOOL WasmOcctView::onWheelEvent (int theEventType, const EmscriptenWheelEvent* theEvent)
497{
498 if (myView.IsNull()
499 || theEventType != EMSCRIPTEN_EVENT_WHEEL)
500 {
501 return EM_FALSE;
502 }
503
f9ab9f7f 504 Handle(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (myView->Window());
505 return aWindow->ProcessWheelEvent (*this, theEventType, theEvent) ? EM_TRUE : EM_FALSE;
565baee6 506}
507
508// ================================================================
509// Function : onTouchEvent
510// Purpose :
511// ================================================================
512EM_BOOL WasmOcctView::onTouchEvent (int theEventType, const EmscriptenTouchEvent* theEvent)
513{
565baee6 514 if (myView.IsNull())
515 {
516 return EM_FALSE;
517 }
518
f9ab9f7f 519 Handle(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (myView->Window());
520 return aWindow->ProcessTouchEvent (*this, theEventType, theEvent) ? EM_TRUE : EM_FALSE;
565baee6 521}
522
e03a03fd 523// ================================================================
524// Function : navigationKeyModifierSwitch
525// Purpose :
526// ================================================================
527bool WasmOcctView::navigationKeyModifierSwitch (unsigned int theModifOld,
528 unsigned int theModifNew,
529 double theTimeStamp)
530{
531 bool hasActions = false;
532 for (unsigned int aKeyIter = 0; aKeyIter < Aspect_VKey_ModifiersLower; ++aKeyIter)
533 {
534 if (!myKeys.IsKeyDown (aKeyIter))
535 {
536 continue;
537 }
538
539 Aspect_VKey anActionOld = Aspect_VKey_UNKNOWN, anActionNew = Aspect_VKey_UNKNOWN;
540 myNavKeyMap.Find (aKeyIter | theModifOld, anActionOld);
541 myNavKeyMap.Find (aKeyIter | theModifNew, anActionNew);
542 if (anActionOld == anActionNew)
543 {
544 continue;
545 }
546
547 if (anActionOld != Aspect_VKey_UNKNOWN)
548 {
549 myKeys.KeyUp (anActionOld, theTimeStamp);
550 }
551 if (anActionNew != Aspect_VKey_UNKNOWN)
552 {
553 hasActions = true;
554 myKeys.KeyDown (anActionNew, theTimeStamp);
555 }
556 }
557 return hasActions;
558}
559
565baee6 560// ================================================================
561// Function : onKeyDownEvent
562// Purpose :
563// ================================================================
564EM_BOOL WasmOcctView::onKeyDownEvent (int theEventType, const EmscriptenKeyboardEvent* theEvent)
565{
566 if (myView.IsNull()
567 || theEventType != EMSCRIPTEN_EVENT_KEYDOWN) // EMSCRIPTEN_EVENT_KEYPRESS
568 {
569 return EM_FALSE;
570 }
571
f9ab9f7f 572 Handle(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (myView->Window());
573 return aWindow->ProcessKeyEvent (*this, theEventType, theEvent) ? EM_TRUE : EM_FALSE;
574}
e03a03fd 575
f9ab9f7f 576//=======================================================================
577//function : KeyDown
578//purpose :
579//=======================================================================
580void WasmOcctView::KeyDown (Aspect_VKey theKey,
581 double theTime,
582 double thePressure)
583{
e03a03fd 584 const unsigned int aModifOld = myKeys.Modifiers();
f9ab9f7f 585 AIS_ViewController::KeyDown (theKey, theTime, thePressure);
565baee6 586
e03a03fd 587 const unsigned int aModifNew = myKeys.Modifiers();
588 if (aModifNew != aModifOld
f9ab9f7f 589 && navigationKeyModifierSwitch (aModifOld, aModifNew, theTime))
565baee6 590 {
e03a03fd 591 // modifier key just pressed
565baee6 592 }
593
e03a03fd 594 Aspect_VKey anAction = Aspect_VKey_UNKNOWN;
f9ab9f7f 595 if (myNavKeyMap.Find (theKey | myKeys.Modifiers(), anAction)
e03a03fd 596 && anAction != Aspect_VKey_UNKNOWN)
565baee6 597 {
f9ab9f7f 598 AIS_ViewController::KeyDown (anAction, theTime, thePressure);
565baee6 599 }
565baee6 600}
601
602// ================================================================
603// Function : onKeyUpEvent
604// Purpose :
605// ================================================================
606EM_BOOL WasmOcctView::onKeyUpEvent (int theEventType, const EmscriptenKeyboardEvent* theEvent)
607{
608 if (myView.IsNull()
609 || theEventType != EMSCRIPTEN_EVENT_KEYUP)
610 {
611 return EM_FALSE;
612 }
613
f9ab9f7f 614 Handle(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (myView->Window());
615 return aWindow->ProcessKeyEvent (*this, theEventType, theEvent) ? EM_TRUE : EM_FALSE;
616}
565baee6 617
f9ab9f7f 618//=======================================================================
619//function : KeyUp
620//purpose :
621//=======================================================================
622void WasmOcctView::KeyUp (Aspect_VKey theKey,
623 double theTime)
624{
e03a03fd 625 const unsigned int aModifOld = myKeys.Modifiers();
f9ab9f7f 626 AIS_ViewController::KeyUp (theKey, theTime);
e03a03fd 627
628 Aspect_VKey anAction = Aspect_VKey_UNKNOWN;
f9ab9f7f 629 if (myNavKeyMap.Find (theKey | myKeys.Modifiers(), anAction)
e03a03fd 630 && anAction != Aspect_VKey_UNKNOWN)
565baee6 631 {
f9ab9f7f 632 AIS_ViewController::KeyUp (anAction, theTime);
633 processKeyPress (anAction);
565baee6 634 }
635
e03a03fd 636 const unsigned int aModifNew = myKeys.Modifiers();
637 if (aModifNew != aModifOld
f9ab9f7f 638 && navigationKeyModifierSwitch (aModifOld, aModifNew, theTime))
565baee6 639 {
e03a03fd 640 // modifier key released
641 }
642
f9ab9f7f 643 processKeyPress (theKey | aModifNew);
e03a03fd 644}
645
646//==============================================================================
647//function : processKeyPress
648//purpose :
649//==============================================================================
650bool WasmOcctView::processKeyPress (Aspect_VKey theKey)
651{
652 switch (theKey)
653 {
654 case Aspect_VKey_F:
565baee6 655 {
e03a03fd 656 myView->FitAll (0.01, false);
657 UpdateView();
658 return true;
565baee6 659 }
660 }
e03a03fd 661 return false;
565baee6 662}
5de4b704 663
664// ================================================================
665// Function : setCubemapBackground
666// Purpose :
667// ================================================================
668void WasmOcctView::setCubemapBackground (const std::string& theImagePath)
669{
670 if (!theImagePath.empty())
671 {
672 emscripten_async_wget (theImagePath.c_str(), "/emulated/cubemap.jpg", CubemapAsyncLoader::onImageRead, CubemapAsyncLoader::onImageFailed);
673 }
674 else
675 {
676 WasmOcctView::Instance().View()->SetBackgroundCubeMap (Handle(Graphic3d_CubeMapPacked)(), true, false);
677 WasmOcctView::Instance().UpdateView();
678 }
679}
680
681// ================================================================
682// Function : fitAllObjects
683// Purpose :
684// ================================================================
685void WasmOcctView::fitAllObjects (bool theAuto)
686{
687 WasmOcctView& aViewer = Instance();
688 if (theAuto)
689 {
690 aViewer.FitAllAuto (aViewer.Context(), aViewer.View());
691 }
692 else
693 {
694 aViewer.View()->FitAll (0.01, false);
695 }
696 aViewer.UpdateView();
697}
698
699// ================================================================
700// Function : removeAllObjects
701// Purpose :
702// ================================================================
703void WasmOcctView::removeAllObjects()
704{
705 WasmOcctView& aViewer = Instance();
706 for (NCollection_IndexedDataMap<TCollection_AsciiString, Handle(AIS_InteractiveObject)>::Iterator anObjIter (aViewer.myObjects);
707 anObjIter.More(); anObjIter.Next())
708 {
709 aViewer.Context()->Remove (anObjIter.Value(), false);
710 }
711 aViewer.myObjects.Clear();
712 aViewer.UpdateView();
713}
714
715// ================================================================
716// Function : removeObject
717// Purpose :
718// ================================================================
719bool WasmOcctView::removeObject (const std::string& theName)
720{
721 WasmOcctView& aViewer = Instance();
722 Handle(AIS_InteractiveObject) anObj;
723 if (!theName.empty()
724 && !aViewer.myObjects.FindFromKey (theName.c_str(), anObj))
725 {
726 return false;
727 }
728
729 aViewer.Context()->Remove (anObj, false);
730 aViewer.myObjects.RemoveKey (theName.c_str());
731 aViewer.UpdateView();
732 return true;
733}
734
735// ================================================================
736// Function : eraseObject
737// Purpose :
738// ================================================================
739bool WasmOcctView::eraseObject (const std::string& theName)
740{
741 WasmOcctView& aViewer = Instance();
742 Handle(AIS_InteractiveObject) anObj;
743 if (!theName.empty()
744 && !aViewer.myObjects.FindFromKey (theName.c_str(), anObj))
745 {
746 return false;
747 }
748
749 aViewer.Context()->Erase (anObj, false);
750 aViewer.UpdateView();
751 return true;
752}
753
754// ================================================================
755// Function : displayObject
756// Purpose :
757// ================================================================
758bool WasmOcctView::displayObject (const std::string& theName)
759{
760 WasmOcctView& aViewer = Instance();
761 Handle(AIS_InteractiveObject) anObj;
762 if (!theName.empty()
763 && !aViewer.myObjects.FindFromKey (theName.c_str(), anObj))
764 {
765 return false;
766 }
767
768 aViewer.Context()->Display (anObj, false);
769 aViewer.UpdateView();
770 return true;
771}
772
773// ================================================================
774// Function : openFromUrl
775// Purpose :
776// ================================================================
777void WasmOcctView::openFromUrl (const std::string& theName,
778 const std::string& theModelPath)
779{
780 ModelAsyncLoader* aTask = new ModelAsyncLoader (theName.c_str(), theModelPath.c_str());
781 emscripten_async_wget_data (theModelPath.c_str(), (void* )aTask, ModelAsyncLoader::onDataRead, ModelAsyncLoader::onReadFailed);
782}
783
784// ================================================================
785// Function : openFromMemory
786// Purpose :
787// ================================================================
788bool WasmOcctView::openFromMemory (const std::string& theName,
789 uintptr_t theBuffer, int theDataLen,
790 bool theToFree)
791{
792 removeObject (theName);
793 char* aBytes = reinterpret_cast<char*>(theBuffer);
794 if (aBytes == nullptr
795 || theDataLen <= 0)
796 {
797 return false;
798 }
799
800 // Function to check if specified data stream starts with specified header.
801 #define dataStartsWithHeader(theData, theHeader) (::strncmp(theData, theHeader, sizeof(theHeader) - 1) == 0)
802
803 if (dataStartsWithHeader(aBytes, "DBRep_DrawableShape"))
804 {
805 return openBRepFromMemory (theName, theBuffer, theDataLen, theToFree);
806 }
807 else if (dataStartsWithHeader(aBytes, "glTF"))
808 {
809 //return openGltfFromMemory (theName, theBuffer, theDataLen, theToFree);
810 }
811 if (theToFree)
812 {
813 free (aBytes);
814 }
815
816 Message::SendFail() << "Error: file '" << theName.c_str() << "' has unsupported format";
817 return false;
818}
819
820// ================================================================
821// Function : openBRepFromMemory
822// Purpose :
823// ================================================================
824bool WasmOcctView::openBRepFromMemory (const std::string& theName,
825 uintptr_t theBuffer, int theDataLen,
826 bool theToFree)
827{
828 removeObject (theName);
829
830 WasmOcctView& aViewer = Instance();
831 TopoDS_Shape aShape;
832 BRep_Builder aBuilder;
833 bool isLoaded = false;
834 {
835 char* aRawData = reinterpret_cast<char*>(theBuffer);
836 Standard_ArrayStreamBuffer aStreamBuffer (aRawData, theDataLen);
837 std::istream aStream (&aStreamBuffer);
838 BRepTools::Read (aShape, aStream, aBuilder);
839 if (theToFree)
840 {
841 free (aRawData);
842 }
843 isLoaded = true;
844 }
845 if (!isLoaded)
846 {
847 return false;
848 }
849
850 Handle(AIS_Shape) aShapePrs = new AIS_Shape (aShape);
851 if (!theName.empty())
852 {
853 aViewer.myObjects.Add (theName.c_str(), aShapePrs);
854 }
855 aShapePrs->SetMaterial (Graphic3d_NameOfMaterial_Silver);
856 aViewer.Context()->Display (aShapePrs, AIS_Shaded, 0, false);
857 aViewer.View()->FitAll (0.01, false);
858 aViewer.UpdateView();
859
860 Message::DefaultMessenger()->Send (TCollection_AsciiString("Loaded file ") + theName.c_str(), Message_Info);
861 Message::DefaultMessenger()->Send (OSD_MemInfo::PrintInfo(), Message_Trace);
862 return true;
863}
864
865// ================================================================
866// Function : displayGround
867// Purpose :
868// ================================================================
869void WasmOcctView::displayGround (bool theToShow)
870{
871 static Handle(AIS_Shape) aGroundPrs = new AIS_Shape (TopoDS_Shape());
872
873 WasmOcctView& aViewer = Instance();
874 Bnd_Box aBox;
875 if (theToShow)
876 {
877 aViewer.Context()->Remove (aGroundPrs, false);
878 aBox = aViewer.View()->View()->MinMaxValues();
879 }
880 if (aBox.IsVoid()
881 || aBox.IsZThin (Precision::Confusion()))
882 {
883 if (!aGroundPrs.IsNull()
884 && aGroundPrs->HasInteractiveContext())
885 {
886 aViewer.Context()->Remove (aGroundPrs, false);
887 aViewer.UpdateView();
888 }
889 return;
890 }
891
892 const gp_XYZ aSize = aBox.CornerMax().XYZ() - aBox.CornerMin().XYZ();
893 const double aRadius = Max (aSize.X(), aSize.Y());
894 const double aZValue = aBox.CornerMin().Z() - Min (10.0, aSize.Z() * 0.01);
895 const double aZSize = aRadius * 0.01;
896 gp_XYZ aGroundCenter ((aBox.CornerMin().X() + aBox.CornerMax().X()) * 0.5,
897 (aBox.CornerMin().Y() + aBox.CornerMax().Y()) * 0.5,
898 aZValue);
899
900 TopoDS_Compound aGround;
901 gp_Trsf aTrsf1, aTrsf2;
902 aTrsf1.SetTranslation (gp_Vec (aGroundCenter - gp_XYZ(0.0, 0.0, aZSize)));
903 aTrsf2.SetTranslation (gp_Vec (aGroundCenter));
904 Prs3d_ToolCylinder aCylTool (aRadius, aRadius, aZSize, 50, 1);
905 Prs3d_ToolDisk aDiskTool (0.0, aRadius, 50, 1);
906 TopoDS_Face aCylFace, aDiskFace1, aDiskFace2;
907 BRep_Builder().MakeFace (aCylFace, aCylTool .CreatePolyTriangulation (aTrsf1));
908 BRep_Builder().MakeFace (aDiskFace1, aDiskTool.CreatePolyTriangulation (aTrsf1));
909 BRep_Builder().MakeFace (aDiskFace2, aDiskTool.CreatePolyTriangulation (aTrsf2));
910
911 BRep_Builder().MakeCompound (aGround);
912 BRep_Builder().Add (aGround, aCylFace);
913 BRep_Builder().Add (aGround, aDiskFace1);
914 BRep_Builder().Add (aGround, aDiskFace2);
915
916 aGroundPrs->SetShape (aGround);
917 aGroundPrs->SetToUpdate();
918 aGroundPrs->SetMaterial (Graphic3d_NameOfMaterial_Stone);
919 aGroundPrs->SetInfiniteState (false);
920 aViewer.Context()->Display (aGroundPrs, AIS_Shaded, -1, false);
921 aGroundPrs->SetInfiniteState (true);
922 aViewer.UpdateView();
923}
924
925// Module exports
926EMSCRIPTEN_BINDINGS(OccViewerModule) {
927 emscripten::function("setCubemapBackground", &WasmOcctView::setCubemapBackground);
928 emscripten::function("fitAllObjects", &WasmOcctView::fitAllObjects);
929 emscripten::function("removeAllObjects", &WasmOcctView::removeAllObjects);
930 emscripten::function("removeObject", &WasmOcctView::removeObject);
931 emscripten::function("eraseObject", &WasmOcctView::eraseObject);
932 emscripten::function("displayObject", &WasmOcctView::displayObject);
933 emscripten::function("displayGround", &WasmOcctView::displayGround);
934 emscripten::function("openFromUrl", &WasmOcctView::openFromUrl);
935 emscripten::function("openFromMemory", &WasmOcctView::openFromMemory, emscripten::allow_raw_pointers());
936 emscripten::function("openBRepFromMemory", &WasmOcctView::openBRepFromMemory, emscripten::allow_raw_pointers());
937}