0032463: Visualization - implement Image_AlienPixMap::Load() via emscripten_get_prelo...
[occt.git] / samples / webgl / WasmOcctView.cpp
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
24 #include <AIS_Shape.hxx>
25 #include <AIS_ViewCube.hxx>
26 #include <Aspect_Handle.hxx>
27 #include <Aspect_DisplayConnection.hxx>
28 #include <Image_AlienPixMap.hxx>
29 #include <Message.hxx>
30 #include <Message_Messenger.hxx>
31 #include <Graphic3d_CubeMapPacked.hxx>
32 #include <OpenGl_GraphicDriver.hxx>
33 #include <Prs3d_DatumAspect.hxx>
34 #include <Prs3d_ToolCylinder.hxx>
35 #include <Prs3d_ToolDisk.hxx>
36 #include <Wasm_Window.hxx>
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>
44
45 #include <iostream>
46
47 #define THE_CANVAS_ID "canvas"
48
49 namespace
50 {
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;
84       Handle(Image_AlienPixMap) anImage = new Image_AlienPixMap();
85       if (anImage->Load (theFilePath))
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 // ================================================================
105 WasmOcctView& WasmOcctView::Instance()
106 {
107   static WasmOcctView aViewer;
108   return aViewer;
109 }
110
111 // ================================================================
112 // Function : WasmOcctView
113 // Purpose  :
114 // ================================================================
115 WasmOcctView::WasmOcctView()
116 : myDevicePixelRatio (1.0f),
117   myUpdateRequests (0)
118 {
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
141 }
142
143 // ================================================================
144 // Function : ~WasmOcctView
145 // Purpose  :
146 // ================================================================
147 WasmOcctView::~WasmOcctView()
148 {
149 }
150
151 // ================================================================
152 // Function : run
153 // Purpose  :
154 // ================================================================
155 void 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
168   // There is no infinite message loop, main() will return from here immediately.
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 // ================================================================
179 void WasmOcctView::initWindow()
180 {
181   myDevicePixelRatio = emscripten_get_device_pixel_ratio();
182   myCanvasId = THE_CANVAS_ID;
183   const char* aTargetId = !myCanvasId.IsEmpty() ? myCanvasId.ToCString() : EMSCRIPTEN_EVENT_TARGET_WINDOW;
184   const EM_BOOL toUseCapture = EM_TRUE;
185   emscripten_set_resize_callback     (EMSCRIPTEN_EVENT_TARGET_WINDOW, this, toUseCapture, onResizeCallback);
186
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);
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
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);
204 }
205
206 // ================================================================
207 // Function : dumpGlInfo
208 // Purpose  :
209 // ================================================================
210 void 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 // ================================================================
255 void 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 // ================================================================
284 bool 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
301   aDriver->ChangeOptions().buffersOpaqueAlpha = true; // avoid unexpected blending of canvas with page background
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();
313   for (V3d_ListOfLight::Iterator aLightIter (aViewer->ActiveLights()); aLightIter.More(); aLightIter.Next())
314   {
315     const Handle(V3d_Light)& aLight = aLightIter.Value();
316     if (aLight->Type() == Graphic3d_TOLS_DIRECTIONAL)
317     {
318       aLight->SetCastShadows (true);
319     }
320   }
321
322   Handle(Wasm_Window) aWindow = new Wasm_Window (THE_CANVAS_ID);
323   aWindow->Size (myWinSizeOld.x(), myWinSizeOld.y());
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);
337   myView->Camera()->SetProjectionType (Graphic3d_Camera::Projection_Perspective);
338   myView->SetImmediateUpdate (false);
339   myView->ChangeRenderingParams().IsShadowEnabled = false;
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 // ================================================================
356 void 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
380 // ================================================================
381 // Function : ProcessInput
382 // Purpose  :
383 // ================================================================
384 void WasmOcctView::ProcessInput()
385 {
386   if (!myView.IsNull())
387   {
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     }
395   }
396 }
397
398 // ================================================================
399 // Function : UpdateView
400 // Purpose  :
401 // ================================================================
402 void WasmOcctView::UpdateView()
403 {
404   if (!myView.IsNull())
405   {
406     myView->Invalidate();
407     // queue next onRedrawView()/redrawView()
408     ProcessInput();
409   }
410 }
411
412 // ================================================================
413 // Function : redrawView
414 // Purpose  :
415 // ================================================================
416 void WasmOcctView::redrawView()
417 {
418   if (!myView.IsNull())
419   {
420     FlushViewEvents (myContext, myView, true);
421   }
422 }
423
424 // ================================================================
425 // Function : handleViewRedraw
426 // Purpose  :
427 // ================================================================
428 void 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 // ================================================================
445 EM_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
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
460    || aPixelRatio != myDevicePixelRatio)
461   {
462     myWinSizeOld = aWinSizeNew;
463     if (myDevicePixelRatio != aPixelRatio)
464     {
465       myDevicePixelRatio = aPixelRatio;
466       initPixelScaleRatio();
467     }
468
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 // ================================================================
481 EM_BOOL WasmOcctView::onMouseEvent (int theEventType, const EmscriptenMouseEvent* theEvent)
482 {
483   if (myView.IsNull())
484   {
485     return EM_FALSE;
486   }
487
488   Handle(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (myView->Window());
489   return aWindow->ProcessMouseEvent (*this, theEventType, theEvent) ? EM_TRUE : EM_FALSE;
490 }
491
492 // ================================================================
493 // Function : onWheelEvent
494 // Purpose  :
495 // ================================================================
496 EM_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
504   Handle(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (myView->Window());
505   return aWindow->ProcessWheelEvent (*this, theEventType, theEvent) ? EM_TRUE : EM_FALSE;
506 }
507
508 // ================================================================
509 // Function : onTouchEvent
510 // Purpose  :
511 // ================================================================
512 EM_BOOL WasmOcctView::onTouchEvent (int theEventType, const EmscriptenTouchEvent* theEvent)
513 {
514   if (myView.IsNull())
515   {
516     return EM_FALSE;
517   }
518
519   Handle(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (myView->Window());
520   return aWindow->ProcessTouchEvent (*this, theEventType, theEvent) ? EM_TRUE : EM_FALSE;
521 }
522
523 // ================================================================
524 // Function : navigationKeyModifierSwitch
525 // Purpose  :
526 // ================================================================
527 bool 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
560 // ================================================================
561 // Function : onKeyDownEvent
562 // Purpose  :
563 // ================================================================
564 EM_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
572   Handle(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (myView->Window());
573   return aWindow->ProcessKeyEvent (*this, theEventType, theEvent) ? EM_TRUE : EM_FALSE;
574 }
575
576 //=======================================================================
577 //function : KeyDown
578 //purpose  :
579 //=======================================================================
580 void WasmOcctView::KeyDown (Aspect_VKey theKey,
581                             double theTime,
582                             double thePressure)
583 {
584   const unsigned int aModifOld = myKeys.Modifiers();
585   AIS_ViewController::KeyDown (theKey, theTime, thePressure);
586
587   const unsigned int aModifNew = myKeys.Modifiers();
588   if (aModifNew != aModifOld
589    && navigationKeyModifierSwitch (aModifOld, aModifNew, theTime))
590   {
591     // modifier key just pressed
592   }
593
594   Aspect_VKey anAction = Aspect_VKey_UNKNOWN;
595   if (myNavKeyMap.Find (theKey | myKeys.Modifiers(), anAction)
596   &&  anAction != Aspect_VKey_UNKNOWN)
597   {
598     AIS_ViewController::KeyDown (anAction, theTime, thePressure);
599   }
600 }
601
602 // ================================================================
603 // Function : onKeyUpEvent
604 // Purpose  :
605 // ================================================================
606 EM_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
614   Handle(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (myView->Window());
615   return aWindow->ProcessKeyEvent (*this, theEventType, theEvent) ? EM_TRUE : EM_FALSE;
616 }
617
618 //=======================================================================
619 //function : KeyUp
620 //purpose  :
621 //=======================================================================
622 void WasmOcctView::KeyUp (Aspect_VKey theKey,
623                           double theTime)
624 {
625   const unsigned int aModifOld = myKeys.Modifiers();
626   AIS_ViewController::KeyUp (theKey, theTime);
627
628   Aspect_VKey anAction = Aspect_VKey_UNKNOWN;
629   if (myNavKeyMap.Find (theKey | myKeys.Modifiers(), anAction)
630   &&  anAction != Aspect_VKey_UNKNOWN)
631   {
632     AIS_ViewController::KeyUp (anAction, theTime);
633     processKeyPress (anAction);
634   }
635
636   const unsigned int aModifNew = myKeys.Modifiers();
637   if (aModifNew != aModifOld
638    && navigationKeyModifierSwitch (aModifOld, aModifNew, theTime))
639   {
640     // modifier key released
641   }
642
643   processKeyPress (theKey | aModifNew);
644 }
645
646 //==============================================================================
647 //function : processKeyPress
648 //purpose  :
649 //==============================================================================
650 bool WasmOcctView::processKeyPress (Aspect_VKey theKey)
651 {
652   switch (theKey)
653   {
654     case Aspect_VKey_F:
655     {
656       myView->FitAll (0.01, false);
657       UpdateView();
658       return true;
659     }
660   }
661   return false;
662 }
663
664 // ================================================================
665 // Function : setCubemapBackground
666 // Purpose  :
667 // ================================================================
668 void 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 // ================================================================
685 void 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 // ================================================================
703 void 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 // ================================================================
719 bool 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 // ================================================================
739 bool 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 // ================================================================
758 bool 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 // ================================================================
777 void 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 // ================================================================
788 bool 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 // ================================================================
824 bool 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 // ================================================================
869 void 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
926 EMSCRIPTEN_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 }