1 // Copyright (c) 2019 OPEN CASCADE SAS
3 // This file is part of the examples of the Open CASCADE Technology software library.
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:
12 // The above copyright notice and this permission notice shall be included in all
13 // copies or substantial portions of the Software.
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
22 #include "WasmOcctView.h"
24 #include "WasmVKeys.h"
26 #include <AIS_Shape.hxx>
27 #include <AIS_ViewCube.hxx>
28 #include <Aspect_Handle.hxx>
29 #include <Aspect_DisplayConnection.hxx>
30 #include <Aspect_NeutralWindow.hxx>
31 #include <Message.hxx>
32 #include <Message_Messenger.hxx>
33 #include <OpenGl_GraphicDriver.hxx>
34 #include <Prs3d_DatumAspect.hxx>
38 #define THE_CANVAS_ID "canvas"
42 EM_JS(int, jsCanvasGetWidth, (), {
46 EM_JS(int, jsCanvasGetHeight, (), {
50 EM_JS(float, jsDevicePixelRatio, (), {
51 var aDevicePixelRatio = window.devicePixelRatio || 1;
52 return aDevicePixelRatio;
55 //! Return cavas size in pixels.
56 static Graphic3d_Vec2i jsCanvasSize()
58 return Graphic3d_Vec2i (jsCanvasGetWidth(), jsCanvasGetHeight());
62 // ================================================================
63 // Function : WasmOcctView
65 // ================================================================
66 WasmOcctView::WasmOcctView()
67 : myDevicePixelRatio (1.0f),
72 // ================================================================
73 // Function : ~WasmOcctView
75 // ================================================================
76 WasmOcctView::~WasmOcctView()
80 // ================================================================
83 // ================================================================
84 void WasmOcctView::run()
94 myView->MustBeResized();
97 // There is no inifinite message loop, main() will return from here immediately.
98 // Tell that our Module should be left loaded and handle events through callbacks.
99 //emscripten_set_main_loop (redrawView, 60, 1);
100 //emscripten_set_main_loop (redrawView, -1, 1);
101 EM_ASM(Module['noExitRuntime'] = true);
104 // ================================================================
105 // Function : initWindow
107 // ================================================================
108 void WasmOcctView::initWindow()
110 myDevicePixelRatio = jsDevicePixelRatio();
111 myCanvasId = THE_CANVAS_ID;
112 const char* aTargetId = !myCanvasId.IsEmpty() ? myCanvasId.ToCString() : NULL;
113 const EM_BOOL toUseCapture = EM_TRUE;
114 emscripten_set_resize_callback (NULL, this, toUseCapture, onResizeCallback);
116 emscripten_set_mousedown_callback (NULL, this, toUseCapture, onMouseCallback);
117 emscripten_set_mouseup_callback (NULL, this, toUseCapture, onMouseCallback);
118 emscripten_set_mousemove_callback (NULL, this, toUseCapture, onMouseCallback);
119 emscripten_set_dblclick_callback (aTargetId, this, toUseCapture, onMouseCallback);
120 emscripten_set_click_callback (aTargetId, this, toUseCapture, onMouseCallback);
121 emscripten_set_mouseenter_callback (aTargetId, this, toUseCapture, onMouseCallback);
122 emscripten_set_mouseleave_callback (aTargetId, this, toUseCapture, onMouseCallback);
123 emscripten_set_wheel_callback (aTargetId, this, toUseCapture, onWheelCallback);
125 emscripten_set_touchstart_callback (aTargetId, this, toUseCapture, onTouchCallback);
126 emscripten_set_touchend_callback (aTargetId, this, toUseCapture, onTouchCallback);
127 emscripten_set_touchmove_callback (aTargetId, this, toUseCapture, onTouchCallback);
128 emscripten_set_touchcancel_callback(aTargetId, this, toUseCapture, onTouchCallback);
130 //emscripten_set_keypress_callback (NULL, this, toUseCapture, onKeyCallback);
131 emscripten_set_keydown_callback (NULL, this, toUseCapture, onKeyDownCallback);
132 emscripten_set_keyup_callback (NULL, this, toUseCapture, onKeyUpCallback);
135 // ================================================================
136 // Function : dumpGlInfo
138 // ================================================================
139 void WasmOcctView::dumpGlInfo (bool theIsBasic)
141 TColStd_IndexedDataMapOfStringString aGlCapsDict;
142 myView->DiagnosticInformation (aGlCapsDict, theIsBasic ? Graphic3d_DiagnosticInfo_Basic : Graphic3d_DiagnosticInfo_Complete);
145 TCollection_AsciiString aViewport;
146 aGlCapsDict.FindFromKey ("Viewport", aViewport);
148 aGlCapsDict.Add ("Viewport", aViewport);
150 aGlCapsDict.Add ("Display scale", TCollection_AsciiString(myDevicePixelRatio));
154 TCollection_AsciiString* aGlVer = aGlCapsDict.ChangeSeek ("GLversion");
155 TCollection_AsciiString* aGlslVer = aGlCapsDict.ChangeSeek ("GLSLversion");
159 *aGlVer = *aGlVer + " [GLSL: " + *aGlslVer + "]";
164 TCollection_AsciiString anInfo;
165 for (TColStd_IndexedDataMapOfStringString::Iterator aValueIter (aGlCapsDict); aValueIter.More(); aValueIter.Next())
167 if (!aValueIter.Value().IsEmpty())
169 if (!anInfo.IsEmpty())
173 anInfo += aValueIter.Key() + ": " + aValueIter.Value();
177 ::Message::DefaultMessenger()->Send (anInfo, Message_Warning);
180 // ================================================================
181 // Function : initPixelScaleRatio
183 // ================================================================
184 void WasmOcctView::initPixelScaleRatio()
186 SetTouchToleranceScale (myDevicePixelRatio);
187 if (!myView.IsNull())
189 myView->ChangeRenderingParams().Resolution = (unsigned int )(96.0 * myDevicePixelRatio + 0.5);
191 if (!myContext.IsNull())
193 myContext->SetPixelTolerance (int(myDevicePixelRatio * 6.0));
194 if (!myViewCube.IsNull())
196 static const double THE_CUBE_SIZE = 60.0;
197 myViewCube->SetSize (myDevicePixelRatio * THE_CUBE_SIZE, false);
198 myViewCube->SetBoxFacetExtension (myViewCube->Size() * 0.15);
199 myViewCube->SetAxesPadding (myViewCube->Size() * 0.10);
200 myViewCube->SetFontHeight (THE_CUBE_SIZE * 0.16);
201 if (myViewCube->HasInteractiveContext())
203 myContext->Redisplay (myViewCube, false);
209 // ================================================================
210 // Function : initViewer
212 // ================================================================
213 bool WasmOcctView::initViewer()
215 // Build with "--preload-file MyFontFile.ttf" option
216 // and register font in Font Manager to use custom font(s).
217 /*const char* aFontPath = "MyFontFile.ttf";
218 if (Handle(Font_SystemFont) aFont = Font_FontMgr::GetInstance()->CheckFont (aFontPath))
220 Font_FontMgr::GetInstance()->RegisterFont (aFont, true);
224 Message::DefaultMessenger()->Send (TCollection_AsciiString ("Error: font '") + aFontPath + "' is not found", Message_Fail);
227 Handle(Aspect_DisplayConnection) aDisp;
228 Handle(OpenGl_GraphicDriver) aDriver = new OpenGl_GraphicDriver (aDisp, false);
229 aDriver->ChangeOptions().buffersNoSwap = true; // swap has no effect in WebGL
230 if (!aDriver->InitContext())
232 Message::DefaultMessenger()->Send (TCollection_AsciiString ("Error: EGL initialization failed"), Message_Fail);
236 Handle(V3d_Viewer) aViewer = new V3d_Viewer (aDriver);
237 aViewer->SetComputedMode (false);
238 aViewer->SetDefaultShadingModel (Graphic3d_TOSM_FRAGMENT);
239 aViewer->SetDefaultLights();
240 aViewer->SetLightOn();
242 Handle(Aspect_NeutralWindow) aWindow = new Aspect_NeutralWindow();
243 Graphic3d_Vec2i aWinSize = jsCanvasSize();
244 if (aWinSize.x() < 10 || aWinSize.y() < 10)
246 Message::DefaultMessenger()->Send (TCollection_AsciiString ("Warning: invalid canvas size"), Message_Warning);
248 aWindow->SetSize (aWinSize.x(), aWinSize.y());
250 myTextStyle = new Prs3d_TextAspect();
251 myTextStyle->SetFont (Font_NOF_ASCII_MONO);
252 myTextStyle->SetHeight (12);
253 myTextStyle->Aspect()->SetColor (Quantity_NOC_GRAY95);
254 myTextStyle->Aspect()->SetColorSubTitle (Quantity_NOC_BLACK);
255 myTextStyle->Aspect()->SetDisplayType (Aspect_TODT_SHADOW);
256 myTextStyle->Aspect()->SetTextFontAspect (Font_FA_Bold);
257 myTextStyle->Aspect()->SetTextZoomable (false);
258 myTextStyle->SetHorizontalJustification (Graphic3d_HTA_LEFT);
259 myTextStyle->SetVerticalJustification (Graphic3d_VTA_BOTTOM);
261 myView = new V3d_View (aViewer);
262 myView->SetImmediateUpdate (false);
263 myView->ChangeRenderingParams().Resolution = (unsigned int )(96.0 * myDevicePixelRatio + 0.5);
264 myView->ChangeRenderingParams().ToShowStats = true;
265 myView->ChangeRenderingParams().StatsTextAspect = myTextStyle->Aspect();
266 myView->ChangeRenderingParams().StatsTextHeight = (int )myTextStyle->Height();
267 myView->SetWindow (aWindow);
270 myContext = new AIS_InteractiveContext (aViewer);
271 initPixelScaleRatio();
275 // ================================================================
276 // Function : initDemoScene
278 // ================================================================
279 void WasmOcctView::initDemoScene()
281 if (myContext.IsNull())
286 //myView->TriedronDisplay (Aspect_TOTP_LEFT_LOWER, Quantity_NOC_GOLD, 0.08, V3d_WIREFRAME);
288 myViewCube = new AIS_ViewCube();
289 // presentation parameters
290 initPixelScaleRatio();
291 myViewCube->SetTransformPersistence (new Graphic3d_TransformPers (Graphic3d_TMF_TriedronPers, Aspect_TOTP_RIGHT_LOWER, Graphic3d_Vec2i (100, 100)));
292 myViewCube->Attributes()->SetDatumAspect (new Prs3d_DatumAspect());
293 myViewCube->Attributes()->DatumAspect()->SetTextAspect (myTextStyle);
294 // animation parameters
295 myViewCube->SetViewAnimation (myViewAnimation);
296 myViewCube->SetFixedAnimationLoop (false);
297 myViewCube->SetAutoStartAnimation (true);
298 myContext->Display (myViewCube, false);
300 // Build with "--preload-file MySampleFile.brep" option to load some shapes here.
304 // ================================================================
305 // Function : updateView
307 // ================================================================
308 void WasmOcctView::updateView()
310 if (!myView.IsNull())
312 if (++myUpdateRequests == 1)
314 emscripten_async_call (onRedrawView, this, 0);
319 // ================================================================
320 // Function : redrawView
322 // ================================================================
323 void WasmOcctView::redrawView()
325 if (!myView.IsNull())
327 FlushViewEvents (myContext, myView, true);
331 // ================================================================
332 // Function : handleViewRedraw
334 // ================================================================
335 void WasmOcctView::handleViewRedraw (const Handle(AIS_InteractiveContext)& theCtx,
336 const Handle(V3d_View)& theView)
338 myUpdateRequests = 0;
339 AIS_ViewController::handleViewRedraw (theCtx, theView);
340 if (myToAskNextFrame)
344 emscripten_async_call (onRedrawView, this, 0);
348 // ================================================================
349 // Function : onResizeEvent
351 // ================================================================
352 EM_BOOL WasmOcctView::onResizeEvent (int theEventType, const EmscriptenUiEvent* theEvent)
354 (void )theEventType; // EMSCRIPTEN_EVENT_RESIZE or EMSCRIPTEN_EVENT_CANVASRESIZED
361 Handle(Aspect_NeutralWindow) aWindow = Handle(Aspect_NeutralWindow)::DownCast (myView->Window());
362 Graphic3d_Vec2i aWinSizeOld, aWinSizeNew (jsCanvasSize());
363 if (aWinSizeNew.x() < 10 || aWinSizeNew.y() < 10)
365 Message::DefaultMessenger()->Send (TCollection_AsciiString ("Warning: invalid canvas size"), Message_Warning);
367 aWindow->Size (aWinSizeOld.x(), aWinSizeOld.y());
368 const float aPixelRatio = jsDevicePixelRatio();
369 if (aWinSizeNew != aWinSizeOld
370 || aPixelRatio != myDevicePixelRatio)
372 if (myDevicePixelRatio != aPixelRatio)
374 myDevicePixelRatio = aPixelRatio;
375 initPixelScaleRatio();
377 aWindow->SetSize (aWinSizeNew.x(), aWinSizeNew.y());
378 myView->MustBeResized();
379 myView->Invalidate();
386 // ================================================================
387 // Function : onMouseEvent
389 // ================================================================
390 EM_BOOL WasmOcctView::onMouseEvent (int theEventType, const EmscriptenMouseEvent* theEvent)
397 Graphic3d_Vec2i aWinSize;
398 myView->Window()->Size (aWinSize.x(), aWinSize.y());
399 const Graphic3d_Vec2i aNewPos = convertPointToBacking (Graphic3d_Vec2i (theEvent->canvasX, theEvent->canvasY));
400 Aspect_VKeyFlags aFlags = 0;
401 if (theEvent->ctrlKey == EM_TRUE) { aFlags |= Aspect_VKeyFlags_CTRL; }
402 if (theEvent->shiftKey == EM_TRUE) { aFlags |= Aspect_VKeyFlags_SHIFT; }
403 if (theEvent->altKey == EM_TRUE) { aFlags |= Aspect_VKeyFlags_ALT; }
404 if (theEvent->metaKey == EM_TRUE) { aFlags |= Aspect_VKeyFlags_META; }
406 const bool isEmulated = false;
407 const Aspect_VKeyMouse aButtons = WasmVKeys_MouseButtonsFromNative (theEvent->buttons);
408 switch (theEventType)
410 case EMSCRIPTEN_EVENT_MOUSEMOVE:
412 if ((aNewPos.x() < 0 || aNewPos.x() > aWinSize.x()
413 || aNewPos.y() < 0 || aNewPos.y() > aWinSize.y())
414 && PressedMouseButtons() == Aspect_VKeyMouse_NONE)
418 if (UpdateMousePosition (aNewPos, aButtons, aFlags, isEmulated))
424 case EMSCRIPTEN_EVENT_MOUSEDOWN:
425 case EMSCRIPTEN_EVENT_MOUSEUP:
427 if (aNewPos.x() < 0 || aNewPos.x() > aWinSize.x()
428 || aNewPos.y() < 0 || aNewPos.y() > aWinSize.y())
432 if (UpdateMouseButtons (aNewPos, aButtons, aFlags, isEmulated))
438 case EMSCRIPTEN_EVENT_CLICK:
439 case EMSCRIPTEN_EVENT_DBLCLICK:
441 if (aNewPos.x() < 0 || aNewPos.x() > aWinSize.x()
442 || aNewPos.y() < 0 || aNewPos.y() > aWinSize.y())
448 case EMSCRIPTEN_EVENT_MOUSEENTER:
452 case EMSCRIPTEN_EVENT_MOUSELEAVE:
454 // there is no SetCapture() support, so that mouse unclick events outside canvas will not arrive,
455 // so we have to forget current state...
456 if (UpdateMouseButtons (aNewPos, Aspect_VKeyMouse_NONE, aFlags, isEmulated))
466 // ================================================================
467 // Function : onWheelEvent
469 // ================================================================
470 EM_BOOL WasmOcctView::onWheelEvent (int theEventType, const EmscriptenWheelEvent* theEvent)
473 || theEventType != EMSCRIPTEN_EVENT_WHEEL)
478 Graphic3d_Vec2i aWinSize;
479 myView->Window()->Size (aWinSize.x(), aWinSize.y());
480 const Graphic3d_Vec2i aNewPos = convertPointToBacking (Graphic3d_Vec2i (theEvent->mouse.canvasX, theEvent->mouse.canvasY));
481 if (aNewPos.x() < 0 || aNewPos.x() > aWinSize.x()
482 || aNewPos.y() < 0 || aNewPos.y() > aWinSize.y())
488 switch (theEvent->deltaMode)
490 case DOM_DELTA_PIXEL:
492 aDelta = theEvent->deltaY / (5.0 * myDevicePixelRatio);
497 aDelta = theEvent->deltaY * 8.0;
502 aDelta = theEvent->deltaY >= 0.0 ? 24.0 : -24.0;
507 if (UpdateZoom (Aspect_ScrollDelta (aNewPos, -aDelta)))
514 // ================================================================
515 // Function : onTouchEvent
517 // ================================================================
518 EM_BOOL WasmOcctView::onTouchEvent (int theEventType, const EmscriptenTouchEvent* theEvent)
520 const double aClickTolerance = 5.0;
526 Graphic3d_Vec2i aWinSize;
527 myView->Window()->Size (aWinSize.x(), aWinSize.y());
528 bool hasUpdates = false;
529 for (int aTouchIter = 0; aTouchIter < theEvent->numTouches; ++aTouchIter)
531 const EmscriptenTouchPoint& aTouch = theEvent->touches[aTouchIter];
532 if (!aTouch.isChanged)
537 const Standard_Size aTouchId = (Standard_Size )aTouch.identifier;
538 const Graphic3d_Vec2i aNewPos = convertPointToBacking (Graphic3d_Vec2i (aTouch.canvasX, aTouch.canvasY));
539 switch (theEventType)
541 case EMSCRIPTEN_EVENT_TOUCHSTART:
543 if (aNewPos.x() >= 0 && aNewPos.x() < aWinSize.x()
544 && aNewPos.y() >= 0 && aNewPos.y() < aWinSize.y())
547 AddTouchPoint (aTouchId, Graphic3d_Vec2d (aNewPos));
548 myClickTouch.From.SetValues (-1.0, -1.0);
549 if (myTouchPoints.Extent() == 1)
551 myClickTouch.From = Graphic3d_Vec2d (aNewPos);
556 case EMSCRIPTEN_EVENT_TOUCHMOVE:
558 const int anOldIndex = myTouchPoints.FindIndex (aTouchId);
562 UpdateTouchPoint (aTouchId, Graphic3d_Vec2d (aNewPos));
563 if (myTouchPoints.Extent() == 1
564 && (myClickTouch.From - Graphic3d_Vec2d (aNewPos)).cwiseAbs().maxComp() > aClickTolerance)
566 myClickTouch.From.SetValues (-1.0, -1.0);
571 case EMSCRIPTEN_EVENT_TOUCHEND:
572 case EMSCRIPTEN_EVENT_TOUCHCANCEL:
574 if (RemoveTouchPoint (aTouchId))
576 if (myTouchPoints.IsEmpty()
577 && myClickTouch.From.minComp() >= 0.0)
579 if (myDoubleTapTimer.IsStarted()
580 && myDoubleTapTimer.ElapsedTime() <= myMouseDoubleClickInt)
582 myView->FitAll (0.01, false);
583 myView->Invalidate();
587 myDoubleTapTimer.Stop();
588 myDoubleTapTimer.Reset();
589 myDoubleTapTimer.Start();
590 SelectInViewer (Graphic3d_Vec2i (myClickTouch.From), false);
603 return hasUpdates || !myTouchPoints.IsEmpty() ? EM_TRUE : EM_FALSE;
606 // ================================================================
607 // Function : onKeyDownEvent
609 // ================================================================
610 EM_BOOL WasmOcctView::onKeyDownEvent (int theEventType, const EmscriptenKeyboardEvent* theEvent)
613 || theEventType != EMSCRIPTEN_EVENT_KEYDOWN) // EMSCRIPTEN_EVENT_KEYPRESS
618 const double aTimeStamp = EventTime();
619 const Aspect_VKey aVKey = WasmVKeys_VirtualKeyFromNative (theEvent->keyCode);
620 if (aVKey == Aspect_VKey_UNKNOWN)
625 if (theEvent->repeat == EM_FALSE)
627 myKeys.KeyDown (aVKey, aTimeStamp);
630 if (Aspect_VKey2Modifier (aVKey) == 0)
637 // ================================================================
638 // Function : onKeyUpEvent
640 // ================================================================
641 EM_BOOL WasmOcctView::onKeyUpEvent (int theEventType, const EmscriptenKeyboardEvent* theEvent)
644 || theEventType != EMSCRIPTEN_EVENT_KEYUP)
649 const double aTimeStamp = EventTime();
650 const Aspect_VKey aVKey = WasmVKeys_VirtualKeyFromNative (theEvent->keyCode);
651 if (aVKey == Aspect_VKey_UNKNOWN)
656 if (theEvent->repeat == EM_TRUE)
661 const unsigned int aModif = myKeys.Modifiers();
662 myKeys.KeyUp (aVKey, aTimeStamp);
663 if (Aspect_VKey2Modifier (aVKey) == 0)
665 // normal key released
666 switch (aVKey | aModif)
670 myView->FitAll (0.01, false);
671 myView->Invalidate();