0032065: Samples - use MODULARIZE within WebGL sample
[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
24#include "WasmVKeys.h"
5de4b704 25#include "WasmOcctPixMap.h"
565baee6 26
27#include <AIS_Shape.hxx>
28#include <AIS_ViewCube.hxx>
29#include <Aspect_Handle.hxx>
30#include <Aspect_DisplayConnection.hxx>
31#include <Aspect_NeutralWindow.hxx>
32#include <Message.hxx>
33#include <Message_Messenger.hxx>
5de4b704 34#include <Graphic3d_CubeMapPacked.hxx>
565baee6 35#include <OpenGl_GraphicDriver.hxx>
36#include <Prs3d_DatumAspect.hxx>
5de4b704 37#include <Prs3d_ToolCylinder.hxx>
38#include <Prs3d_ToolDisk.hxx>
39
40#include <BRep_Builder.hxx>
41#include <BRepBndLib.hxx>
42#include <BRepTools.hxx>
43#include <Standard_ArrayStreamBuffer.hxx>
44
45#include <emscripten/bind.h>
565baee6 46
47#include <iostream>
48
49#define THE_CANVAS_ID "canvas"
50
51namespace
52{
53 EM_JS(int, jsCanvasGetWidth, (), {
5de4b704 54 return Module.canvas.width;
565baee6 55 });
56
57 EM_JS(int, jsCanvasGetHeight, (), {
5de4b704 58 return Module.canvas.height;
565baee6 59 });
60
61 EM_JS(float, jsDevicePixelRatio, (), {
62 var aDevicePixelRatio = window.devicePixelRatio || 1;
63 return aDevicePixelRatio;
64 });
65
66 //! Return cavas size in pixels.
67 static Graphic3d_Vec2i jsCanvasSize()
68 {
69 return Graphic3d_Vec2i (jsCanvasGetWidth(), jsCanvasGetHeight());
70 }
5de4b704 71
72 //! Auxiliary wrapper for loading model.
73 struct ModelAsyncLoader
74 {
75 std::string Name;
76 std::string Path;
77
78 ModelAsyncLoader (const char* theName, const char* thePath)
79 : Name (theName), Path (thePath) {}
80
81 //! File data read event.
82 static void onDataRead (void* theOpaque, void* theBuffer, int theDataLen)
83 {
84 const ModelAsyncLoader* aTask = (ModelAsyncLoader* )theOpaque;
85 WasmOcctView::openFromMemory (aTask->Name, reinterpret_cast<uintptr_t>(theBuffer), theDataLen, false);
86 delete aTask;
87 }
88
89 //! File read error event.
90 static void onReadFailed (void* theOpaque)
91 {
92 const ModelAsyncLoader* aTask = (ModelAsyncLoader* )theOpaque;
93 Message::DefaultMessenger()->Send (TCollection_AsciiString("Error: unable to load file ") + aTask->Path.c_str(), Message_Fail);
94 delete aTask;
95 }
96 };
97
98 //! Auxiliary wrapper for loading cubemap.
99 struct CubemapAsyncLoader
100 {
101 //! Image file read event.
102 static void onImageRead (const char* theFilePath)
103 {
104 Handle(Graphic3d_CubeMapPacked) aCubemap;
105 Handle(WasmOcctPixMap) anImage = new WasmOcctPixMap();
106 if (anImage->Init (theFilePath))
107 {
108 aCubemap = new Graphic3d_CubeMapPacked (anImage);
109 }
110 WasmOcctView::Instance().View()->SetBackgroundCubeMap (aCubemap, true, false);
111 WasmOcctView::Instance().UpdateView();
112 }
113
114 //! Image file failed read event.
115 static void onImageFailed (const char* theFilePath)
116 {
117 Message::DefaultMessenger()->Send (TCollection_AsciiString("Error: unable to load image ") + theFilePath, Message_Fail);
118 }
119 };
120}
121
122// ================================================================
123// Function : Instance
124// Purpose :
125// ================================================================
126WasmOcctView& WasmOcctView::Instance()
127{
128 static WasmOcctView aViewer;
129 return aViewer;
565baee6 130}
131
132// ================================================================
133// Function : WasmOcctView
134// Purpose :
135// ================================================================
136WasmOcctView::WasmOcctView()
137: myDevicePixelRatio (1.0f),
138 myUpdateRequests (0)
139{
140}
141
142// ================================================================
143// Function : ~WasmOcctView
144// Purpose :
145// ================================================================
146WasmOcctView::~WasmOcctView()
147{
148}
149
150// ================================================================
151// Function : run
152// Purpose :
153// ================================================================
154void WasmOcctView::run()
155{
156 initWindow();
157 initViewer();
158 initDemoScene();
159 if (myView.IsNull())
160 {
161 return;
162 }
163
164 myView->MustBeResized();
165 myView->Redraw();
166
a110c4a3 167 // There is no infinite message loop, main() will return from here immediately.
565baee6 168 // Tell that our Module should be left loaded and handle events through callbacks.
169 //emscripten_set_main_loop (redrawView, 60, 1);
170 //emscripten_set_main_loop (redrawView, -1, 1);
171 EM_ASM(Module['noExitRuntime'] = true);
172}
173
174// ================================================================
175// Function : initWindow
176// Purpose :
177// ================================================================
178void WasmOcctView::initWindow()
179{
180 myDevicePixelRatio = jsDevicePixelRatio();
181 myCanvasId = THE_CANVAS_ID;
7465bfa6 182 const char* aTargetId = !myCanvasId.IsEmpty() ? myCanvasId.ToCString() : EMSCRIPTEN_EVENT_TARGET_WINDOW;
565baee6 183 const EM_BOOL toUseCapture = EM_TRUE;
7465bfa6 184 emscripten_set_resize_callback (EMSCRIPTEN_EVENT_TARGET_WINDOW, this, toUseCapture, onResizeCallback);
565baee6 185
7465bfa6 186 emscripten_set_mousedown_callback (aTargetId, this, toUseCapture, onMouseCallback);
187 emscripten_set_mouseup_callback (aTargetId, this, toUseCapture, onMouseCallback);
188 emscripten_set_mousemove_callback (aTargetId, this, toUseCapture, onMouseCallback);
565baee6 189 emscripten_set_dblclick_callback (aTargetId, this, toUseCapture, onMouseCallback);
190 emscripten_set_click_callback (aTargetId, this, toUseCapture, onMouseCallback);
191 emscripten_set_mouseenter_callback (aTargetId, this, toUseCapture, onMouseCallback);
192 emscripten_set_mouseleave_callback (aTargetId, this, toUseCapture, onMouseCallback);
193 emscripten_set_wheel_callback (aTargetId, this, toUseCapture, onWheelCallback);
194
195 emscripten_set_touchstart_callback (aTargetId, this, toUseCapture, onTouchCallback);
196 emscripten_set_touchend_callback (aTargetId, this, toUseCapture, onTouchCallback);
197 emscripten_set_touchmove_callback (aTargetId, this, toUseCapture, onTouchCallback);
198 emscripten_set_touchcancel_callback(aTargetId, this, toUseCapture, onTouchCallback);
199
7465bfa6 200 //emscripten_set_keypress_callback (EMSCRIPTEN_EVENT_TARGET_WINDOW, this, toUseCapture, onKeyCallback);
201 emscripten_set_keydown_callback (EMSCRIPTEN_EVENT_TARGET_WINDOW, this, toUseCapture, onKeyDownCallback);
202 emscripten_set_keyup_callback (EMSCRIPTEN_EVENT_TARGET_WINDOW, this, toUseCapture, onKeyUpCallback);
565baee6 203}
204
205// ================================================================
206// Function : dumpGlInfo
207// Purpose :
208// ================================================================
209void WasmOcctView::dumpGlInfo (bool theIsBasic)
210{
211 TColStd_IndexedDataMapOfStringString aGlCapsDict;
212 myView->DiagnosticInformation (aGlCapsDict, theIsBasic ? Graphic3d_DiagnosticInfo_Basic : Graphic3d_DiagnosticInfo_Complete);
213 if (theIsBasic)
214 {
215 TCollection_AsciiString aViewport;
216 aGlCapsDict.FindFromKey ("Viewport", aViewport);
217 aGlCapsDict.Clear();
218 aGlCapsDict.Add ("Viewport", aViewport);
219 }
220 aGlCapsDict.Add ("Display scale", TCollection_AsciiString(myDevicePixelRatio));
221
222 // beautify output
223 {
224 TCollection_AsciiString* aGlVer = aGlCapsDict.ChangeSeek ("GLversion");
225 TCollection_AsciiString* aGlslVer = aGlCapsDict.ChangeSeek ("GLSLversion");
226 if (aGlVer != NULL
227 && aGlslVer != NULL)
228 {
229 *aGlVer = *aGlVer + " [GLSL: " + *aGlslVer + "]";
230 aGlslVer->Clear();
231 }
232 }
233
234 TCollection_AsciiString anInfo;
235 for (TColStd_IndexedDataMapOfStringString::Iterator aValueIter (aGlCapsDict); aValueIter.More(); aValueIter.Next())
236 {
237 if (!aValueIter.Value().IsEmpty())
238 {
239 if (!anInfo.IsEmpty())
240 {
241 anInfo += "\n";
242 }
243 anInfo += aValueIter.Key() + ": " + aValueIter.Value();
244 }
245 }
246
247 ::Message::DefaultMessenger()->Send (anInfo, Message_Warning);
248}
249
250// ================================================================
251// Function : initPixelScaleRatio
252// Purpose :
253// ================================================================
254void WasmOcctView::initPixelScaleRatio()
255{
256 SetTouchToleranceScale (myDevicePixelRatio);
257 if (!myView.IsNull())
258 {
259 myView->ChangeRenderingParams().Resolution = (unsigned int )(96.0 * myDevicePixelRatio + 0.5);
260 }
261 if (!myContext.IsNull())
262 {
263 myContext->SetPixelTolerance (int(myDevicePixelRatio * 6.0));
264 if (!myViewCube.IsNull())
265 {
266 static const double THE_CUBE_SIZE = 60.0;
267 myViewCube->SetSize (myDevicePixelRatio * THE_CUBE_SIZE, false);
268 myViewCube->SetBoxFacetExtension (myViewCube->Size() * 0.15);
269 myViewCube->SetAxesPadding (myViewCube->Size() * 0.10);
270 myViewCube->SetFontHeight (THE_CUBE_SIZE * 0.16);
271 if (myViewCube->HasInteractiveContext())
272 {
273 myContext->Redisplay (myViewCube, false);
274 }
275 }
276 }
277}
278
279// ================================================================
280// Function : initViewer
281// Purpose :
282// ================================================================
283bool WasmOcctView::initViewer()
284{
285 // Build with "--preload-file MyFontFile.ttf" option
286 // and register font in Font Manager to use custom font(s).
287 /*const char* aFontPath = "MyFontFile.ttf";
288 if (Handle(Font_SystemFont) aFont = Font_FontMgr::GetInstance()->CheckFont (aFontPath))
289 {
290 Font_FontMgr::GetInstance()->RegisterFont (aFont, true);
291 }
292 else
293 {
294 Message::DefaultMessenger()->Send (TCollection_AsciiString ("Error: font '") + aFontPath + "' is not found", Message_Fail);
295 }*/
296
297 Handle(Aspect_DisplayConnection) aDisp;
298 Handle(OpenGl_GraphicDriver) aDriver = new OpenGl_GraphicDriver (aDisp, false);
299 aDriver->ChangeOptions().buffersNoSwap = true; // swap has no effect in WebGL
31174e1a 300 aDriver->ChangeOptions().buffersOpaqueAlpha = true; // avoid unexpected blending of canvas with page background
565baee6 301 if (!aDriver->InitContext())
302 {
303 Message::DefaultMessenger()->Send (TCollection_AsciiString ("Error: EGL initialization failed"), Message_Fail);
304 return false;
305 }
306
307 Handle(V3d_Viewer) aViewer = new V3d_Viewer (aDriver);
308 aViewer->SetComputedMode (false);
309 aViewer->SetDefaultShadingModel (Graphic3d_TOSM_FRAGMENT);
310 aViewer->SetDefaultLights();
311 aViewer->SetLightOn();
5de4b704 312 for (V3d_ListOfLight::Iterator aLightIter (aViewer->ActiveLights()); aLightIter.More(); aLightIter.Next())
313 {
314 const Handle(V3d_Light)& aLight = aLightIter.Value();
315 if (aLight->Type() == Graphic3d_TOLS_DIRECTIONAL)
316 {
317 aLight->SetCastShadows (true);
318 }
319 }
565baee6 320
321 Handle(Aspect_NeutralWindow) aWindow = new Aspect_NeutralWindow();
322 Graphic3d_Vec2i aWinSize = jsCanvasSize();
323 if (aWinSize.x() < 10 || aWinSize.y() < 10)
324 {
325 Message::DefaultMessenger()->Send (TCollection_AsciiString ("Warning: invalid canvas size"), Message_Warning);
326 }
327 aWindow->SetSize (aWinSize.x(), aWinSize.y());
328
329 myTextStyle = new Prs3d_TextAspect();
330 myTextStyle->SetFont (Font_NOF_ASCII_MONO);
331 myTextStyle->SetHeight (12);
332 myTextStyle->Aspect()->SetColor (Quantity_NOC_GRAY95);
333 myTextStyle->Aspect()->SetColorSubTitle (Quantity_NOC_BLACK);
334 myTextStyle->Aspect()->SetDisplayType (Aspect_TODT_SHADOW);
335 myTextStyle->Aspect()->SetTextFontAspect (Font_FA_Bold);
336 myTextStyle->Aspect()->SetTextZoomable (false);
337 myTextStyle->SetHorizontalJustification (Graphic3d_HTA_LEFT);
338 myTextStyle->SetVerticalJustification (Graphic3d_VTA_BOTTOM);
339
340 myView = new V3d_View (aViewer);
5de4b704 341 myView->Camera()->SetProjectionType (Graphic3d_Camera::Projection_Perspective);
565baee6 342 myView->SetImmediateUpdate (false);
5de4b704 343 myView->ChangeRenderingParams().IsShadowEnabled = false;
565baee6 344 myView->ChangeRenderingParams().Resolution = (unsigned int )(96.0 * myDevicePixelRatio + 0.5);
345 myView->ChangeRenderingParams().ToShowStats = true;
346 myView->ChangeRenderingParams().StatsTextAspect = myTextStyle->Aspect();
347 myView->ChangeRenderingParams().StatsTextHeight = (int )myTextStyle->Height();
348 myView->SetWindow (aWindow);
349 dumpGlInfo (false);
350
351 myContext = new AIS_InteractiveContext (aViewer);
352 initPixelScaleRatio();
353 return true;
354}
355
356// ================================================================
357// Function : initDemoScene
358// Purpose :
359// ================================================================
360void WasmOcctView::initDemoScene()
361{
362 if (myContext.IsNull())
363 {
364 return;
365 }
366
367 //myView->TriedronDisplay (Aspect_TOTP_LEFT_LOWER, Quantity_NOC_GOLD, 0.08, V3d_WIREFRAME);
368
369 myViewCube = new AIS_ViewCube();
370 // presentation parameters
371 initPixelScaleRatio();
372 myViewCube->SetTransformPersistence (new Graphic3d_TransformPers (Graphic3d_TMF_TriedronPers, Aspect_TOTP_RIGHT_LOWER, Graphic3d_Vec2i (100, 100)));
373 myViewCube->Attributes()->SetDatumAspect (new Prs3d_DatumAspect());
374 myViewCube->Attributes()->DatumAspect()->SetTextAspect (myTextStyle);
375 // animation parameters
376 myViewCube->SetViewAnimation (myViewAnimation);
377 myViewCube->SetFixedAnimationLoop (false);
378 myViewCube->SetAutoStartAnimation (true);
379 myContext->Display (myViewCube, false);
380
381 // Build with "--preload-file MySampleFile.brep" option to load some shapes here.
382}
383
5de4b704 384// ================================================================
385// Function : UpdateView
386// Purpose :
387// ================================================================
388void WasmOcctView::UpdateView()
389{
390 if (!myView.IsNull())
391 {
392 myView->Invalidate();
393 updateView();
394 }
395}
565baee6 396
397// ================================================================
398// Function : updateView
399// Purpose :
400// ================================================================
401void WasmOcctView::updateView()
402{
403 if (!myView.IsNull())
404 {
405 if (++myUpdateRequests == 1)
406 {
407 emscripten_async_call (onRedrawView, this, 0);
408 }
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
454 Handle(Aspect_NeutralWindow) aWindow = Handle(Aspect_NeutralWindow)::DownCast (myView->Window());
455 Graphic3d_Vec2i aWinSizeOld, aWinSizeNew (jsCanvasSize());
456 if (aWinSizeNew.x() < 10 || aWinSizeNew.y() < 10)
457 {
458 Message::DefaultMessenger()->Send (TCollection_AsciiString ("Warning: invalid canvas size"), Message_Warning);
459 }
460 aWindow->Size (aWinSizeOld.x(), aWinSizeOld.y());
461 const float aPixelRatio = jsDevicePixelRatio();
462 if (aWinSizeNew != aWinSizeOld
463 || aPixelRatio != myDevicePixelRatio)
464 {
465 if (myDevicePixelRatio != aPixelRatio)
466 {
467 myDevicePixelRatio = aPixelRatio;
468 initPixelScaleRatio();
469 }
470 aWindow->SetSize (aWinSizeNew.x(), aWinSizeNew.y());
471 myView->MustBeResized();
472 myView->Invalidate();
473 myView->Redraw();
474 dumpGlInfo (true);
475 }
476 return EM_TRUE;
477}
478
479// ================================================================
480// Function : onMouseEvent
481// Purpose :
482// ================================================================
483EM_BOOL WasmOcctView::onMouseEvent (int theEventType, const EmscriptenMouseEvent* theEvent)
484{
485 if (myView.IsNull())
486 {
487 return EM_FALSE;
488 }
489
490 Graphic3d_Vec2i aWinSize;
491 myView->Window()->Size (aWinSize.x(), aWinSize.y());
7465bfa6 492 const Graphic3d_Vec2i aNewPos = convertPointToBacking (Graphic3d_Vec2i (theEvent->targetX, theEvent->targetY));
565baee6 493 Aspect_VKeyFlags aFlags = 0;
494 if (theEvent->ctrlKey == EM_TRUE) { aFlags |= Aspect_VKeyFlags_CTRL; }
495 if (theEvent->shiftKey == EM_TRUE) { aFlags |= Aspect_VKeyFlags_SHIFT; }
496 if (theEvent->altKey == EM_TRUE) { aFlags |= Aspect_VKeyFlags_ALT; }
497 if (theEvent->metaKey == EM_TRUE) { aFlags |= Aspect_VKeyFlags_META; }
498
499 const bool isEmulated = false;
500 const Aspect_VKeyMouse aButtons = WasmVKeys_MouseButtonsFromNative (theEvent->buttons);
501 switch (theEventType)
502 {
503 case EMSCRIPTEN_EVENT_MOUSEMOVE:
504 {
505 if ((aNewPos.x() < 0 || aNewPos.x() > aWinSize.x()
506 || aNewPos.y() < 0 || aNewPos.y() > aWinSize.y())
507 && PressedMouseButtons() == Aspect_VKeyMouse_NONE)
508 {
509 return EM_FALSE;
510 }
511 if (UpdateMousePosition (aNewPos, aButtons, aFlags, isEmulated))
512 {
513 updateView();
514 }
515 break;
516 }
517 case EMSCRIPTEN_EVENT_MOUSEDOWN:
518 case EMSCRIPTEN_EVENT_MOUSEUP:
519 {
520 if (aNewPos.x() < 0 || aNewPos.x() > aWinSize.x()
521 || aNewPos.y() < 0 || aNewPos.y() > aWinSize.y())
522 {
523 return EM_FALSE;
524 }
525 if (UpdateMouseButtons (aNewPos, aButtons, aFlags, isEmulated))
526 {
527 updateView();
528 }
529 break;
530 }
531 case EMSCRIPTEN_EVENT_CLICK:
532 case EMSCRIPTEN_EVENT_DBLCLICK:
533 {
534 if (aNewPos.x() < 0 || aNewPos.x() > aWinSize.x()
535 || aNewPos.y() < 0 || aNewPos.y() > aWinSize.y())
536 {
537 return EM_FALSE;
538 }
539 break;
540 }
541 case EMSCRIPTEN_EVENT_MOUSEENTER:
542 {
543 break;
544 }
545 case EMSCRIPTEN_EVENT_MOUSELEAVE:
546 {
547 // there is no SetCapture() support, so that mouse unclick events outside canvas will not arrive,
548 // so we have to forget current state...
549 if (UpdateMouseButtons (aNewPos, Aspect_VKeyMouse_NONE, aFlags, isEmulated))
550 {
551 updateView();
552 }
553 break;
554 }
555 }
556 return EM_TRUE;
557}
558
559// ================================================================
560// Function : onWheelEvent
561// Purpose :
562// ================================================================
563EM_BOOL WasmOcctView::onWheelEvent (int theEventType, const EmscriptenWheelEvent* theEvent)
564{
565 if (myView.IsNull()
566 || theEventType != EMSCRIPTEN_EVENT_WHEEL)
567 {
568 return EM_FALSE;
569 }
570
571 Graphic3d_Vec2i aWinSize;
572 myView->Window()->Size (aWinSize.x(), aWinSize.y());
7465bfa6 573 const Graphic3d_Vec2i aNewPos = convertPointToBacking (Graphic3d_Vec2i (theEvent->mouse.targetX, theEvent->mouse.targetY));
565baee6 574 if (aNewPos.x() < 0 || aNewPos.x() > aWinSize.x()
575 || aNewPos.y() < 0 || aNewPos.y() > aWinSize.y())
576 {
577 return EM_FALSE;
578 }
579
580 double aDelta = 0.0;
581 switch (theEvent->deltaMode)
582 {
583 case DOM_DELTA_PIXEL:
584 {
585 aDelta = theEvent->deltaY / (5.0 * myDevicePixelRatio);
586 break;
587 }
588 case DOM_DELTA_LINE:
589 {
590 aDelta = theEvent->deltaY * 8.0;
591 break;
592 }
593 case DOM_DELTA_PAGE:
594 {
595 aDelta = theEvent->deltaY >= 0.0 ? 24.0 : -24.0;
596 break;
597 }
598 }
599
600 if (UpdateZoom (Aspect_ScrollDelta (aNewPos, -aDelta)))
601 {
602 updateView();
603 }
604 return EM_TRUE;
605}
606
607// ================================================================
608// Function : onTouchEvent
609// Purpose :
610// ================================================================
611EM_BOOL WasmOcctView::onTouchEvent (int theEventType, const EmscriptenTouchEvent* theEvent)
612{
613 const double aClickTolerance = 5.0;
614 if (myView.IsNull())
615 {
616 return EM_FALSE;
617 }
618
619 Graphic3d_Vec2i aWinSize;
620 myView->Window()->Size (aWinSize.x(), aWinSize.y());
621 bool hasUpdates = false;
622 for (int aTouchIter = 0; aTouchIter < theEvent->numTouches; ++aTouchIter)
623 {
624 const EmscriptenTouchPoint& aTouch = theEvent->touches[aTouchIter];
625 if (!aTouch.isChanged)
626 {
627 continue;
628 }
629
630 const Standard_Size aTouchId = (Standard_Size )aTouch.identifier;
5de4b704 631 const Graphic3d_Vec2i aNewPos = convertPointToBacking (Graphic3d_Vec2i (aTouch.targetX, aTouch.targetY));
565baee6 632 switch (theEventType)
633 {
634 case EMSCRIPTEN_EVENT_TOUCHSTART:
635 {
636 if (aNewPos.x() >= 0 && aNewPos.x() < aWinSize.x()
637 && aNewPos.y() >= 0 && aNewPos.y() < aWinSize.y())
638 {
639 hasUpdates = true;
640 AddTouchPoint (aTouchId, Graphic3d_Vec2d (aNewPos));
641 myClickTouch.From.SetValues (-1.0, -1.0);
642 if (myTouchPoints.Extent() == 1)
643 {
644 myClickTouch.From = Graphic3d_Vec2d (aNewPos);
645 }
646 }
647 break;
648 }
649 case EMSCRIPTEN_EVENT_TOUCHMOVE:
650 {
651 const int anOldIndex = myTouchPoints.FindIndex (aTouchId);
652 if (anOldIndex != 0)
653 {
654 hasUpdates = true;
655 UpdateTouchPoint (aTouchId, Graphic3d_Vec2d (aNewPos));
656 if (myTouchPoints.Extent() == 1
657 && (myClickTouch.From - Graphic3d_Vec2d (aNewPos)).cwiseAbs().maxComp() > aClickTolerance)
658 {
659 myClickTouch.From.SetValues (-1.0, -1.0);
660 }
661 }
662 break;
663 }
664 case EMSCRIPTEN_EVENT_TOUCHEND:
665 case EMSCRIPTEN_EVENT_TOUCHCANCEL:
666 {
667 if (RemoveTouchPoint (aTouchId))
668 {
669 if (myTouchPoints.IsEmpty()
670 && myClickTouch.From.minComp() >= 0.0)
671 {
672 if (myDoubleTapTimer.IsStarted()
673 && myDoubleTapTimer.ElapsedTime() <= myMouseDoubleClickInt)
674 {
675 myView->FitAll (0.01, false);
676 myView->Invalidate();
677 }
678 else
679 {
680 myDoubleTapTimer.Stop();
681 myDoubleTapTimer.Reset();
682 myDoubleTapTimer.Start();
683 SelectInViewer (Graphic3d_Vec2i (myClickTouch.From), false);
684 }
685 }
686 hasUpdates = true;
687 }
688 break;
689 }
690 }
691 }
692 if (hasUpdates)
693 {
694 updateView();
695 }
696 return hasUpdates || !myTouchPoints.IsEmpty() ? EM_TRUE : EM_FALSE;
697}
698
699// ================================================================
700// Function : onKeyDownEvent
701// Purpose :
702// ================================================================
703EM_BOOL WasmOcctView::onKeyDownEvent (int theEventType, const EmscriptenKeyboardEvent* theEvent)
704{
705 if (myView.IsNull()
706 || theEventType != EMSCRIPTEN_EVENT_KEYDOWN) // EMSCRIPTEN_EVENT_KEYPRESS
707 {
708 return EM_FALSE;
709 }
710
711 const double aTimeStamp = EventTime();
712 const Aspect_VKey aVKey = WasmVKeys_VirtualKeyFromNative (theEvent->keyCode);
713 if (aVKey == Aspect_VKey_UNKNOWN)
714 {
715 return EM_FALSE;
716 }
717
718 if (theEvent->repeat == EM_FALSE)
719 {
720 myKeys.KeyDown (aVKey, aTimeStamp);
721 }
722
723 if (Aspect_VKey2Modifier (aVKey) == 0)
724 {
725 // normal key
726 }
727 return EM_FALSE;
728}
729
730// ================================================================
731// Function : onKeyUpEvent
732// Purpose :
733// ================================================================
734EM_BOOL WasmOcctView::onKeyUpEvent (int theEventType, const EmscriptenKeyboardEvent* theEvent)
735{
736 if (myView.IsNull()
737 || theEventType != EMSCRIPTEN_EVENT_KEYUP)
738 {
739 return EM_FALSE;
740 }
741
742 const double aTimeStamp = EventTime();
743 const Aspect_VKey aVKey = WasmVKeys_VirtualKeyFromNative (theEvent->keyCode);
744 if (aVKey == Aspect_VKey_UNKNOWN)
745 {
746 return EM_FALSE;
747 }
748
749 if (theEvent->repeat == EM_TRUE)
750 {
751 return EM_FALSE;
752 }
753
754 const unsigned int aModif = myKeys.Modifiers();
755 myKeys.KeyUp (aVKey, aTimeStamp);
756 if (Aspect_VKey2Modifier (aVKey) == 0)
757 {
758 // normal key released
759 switch (aVKey | aModif)
760 {
761 case Aspect_VKey_F:
762 {
763 myView->FitAll (0.01, false);
764 myView->Invalidate();
765 updateView();
766 return EM_TRUE;
767 }
768 }
769 }
770 return EM_FALSE;
771}
5de4b704 772
773// ================================================================
774// Function : setCubemapBackground
775// Purpose :
776// ================================================================
777void WasmOcctView::setCubemapBackground (const std::string& theImagePath)
778{
779 if (!theImagePath.empty())
780 {
781 emscripten_async_wget (theImagePath.c_str(), "/emulated/cubemap.jpg", CubemapAsyncLoader::onImageRead, CubemapAsyncLoader::onImageFailed);
782 }
783 else
784 {
785 WasmOcctView::Instance().View()->SetBackgroundCubeMap (Handle(Graphic3d_CubeMapPacked)(), true, false);
786 WasmOcctView::Instance().UpdateView();
787 }
788}
789
790// ================================================================
791// Function : fitAllObjects
792// Purpose :
793// ================================================================
794void WasmOcctView::fitAllObjects (bool theAuto)
795{
796 WasmOcctView& aViewer = Instance();
797 if (theAuto)
798 {
799 aViewer.FitAllAuto (aViewer.Context(), aViewer.View());
800 }
801 else
802 {
803 aViewer.View()->FitAll (0.01, false);
804 }
805 aViewer.UpdateView();
806}
807
808// ================================================================
809// Function : removeAllObjects
810// Purpose :
811// ================================================================
812void WasmOcctView::removeAllObjects()
813{
814 WasmOcctView& aViewer = Instance();
815 for (NCollection_IndexedDataMap<TCollection_AsciiString, Handle(AIS_InteractiveObject)>::Iterator anObjIter (aViewer.myObjects);
816 anObjIter.More(); anObjIter.Next())
817 {
818 aViewer.Context()->Remove (anObjIter.Value(), false);
819 }
820 aViewer.myObjects.Clear();
821 aViewer.UpdateView();
822}
823
824// ================================================================
825// Function : removeObject
826// Purpose :
827// ================================================================
828bool WasmOcctView::removeObject (const std::string& theName)
829{
830 WasmOcctView& aViewer = Instance();
831 Handle(AIS_InteractiveObject) anObj;
832 if (!theName.empty()
833 && !aViewer.myObjects.FindFromKey (theName.c_str(), anObj))
834 {
835 return false;
836 }
837
838 aViewer.Context()->Remove (anObj, false);
839 aViewer.myObjects.RemoveKey (theName.c_str());
840 aViewer.UpdateView();
841 return true;
842}
843
844// ================================================================
845// Function : eraseObject
846// Purpose :
847// ================================================================
848bool WasmOcctView::eraseObject (const std::string& theName)
849{
850 WasmOcctView& aViewer = Instance();
851 Handle(AIS_InteractiveObject) anObj;
852 if (!theName.empty()
853 && !aViewer.myObjects.FindFromKey (theName.c_str(), anObj))
854 {
855 return false;
856 }
857
858 aViewer.Context()->Erase (anObj, false);
859 aViewer.UpdateView();
860 return true;
861}
862
863// ================================================================
864// Function : displayObject
865// Purpose :
866// ================================================================
867bool WasmOcctView::displayObject (const std::string& theName)
868{
869 WasmOcctView& aViewer = Instance();
870 Handle(AIS_InteractiveObject) anObj;
871 if (!theName.empty()
872 && !aViewer.myObjects.FindFromKey (theName.c_str(), anObj))
873 {
874 return false;
875 }
876
877 aViewer.Context()->Display (anObj, false);
878 aViewer.UpdateView();
879 return true;
880}
881
882// ================================================================
883// Function : openFromUrl
884// Purpose :
885// ================================================================
886void WasmOcctView::openFromUrl (const std::string& theName,
887 const std::string& theModelPath)
888{
889 ModelAsyncLoader* aTask = new ModelAsyncLoader (theName.c_str(), theModelPath.c_str());
890 emscripten_async_wget_data (theModelPath.c_str(), (void* )aTask, ModelAsyncLoader::onDataRead, ModelAsyncLoader::onReadFailed);
891}
892
893// ================================================================
894// Function : openFromMemory
895// Purpose :
896// ================================================================
897bool WasmOcctView::openFromMemory (const std::string& theName,
898 uintptr_t theBuffer, int theDataLen,
899 bool theToFree)
900{
901 removeObject (theName);
902 char* aBytes = reinterpret_cast<char*>(theBuffer);
903 if (aBytes == nullptr
904 || theDataLen <= 0)
905 {
906 return false;
907 }
908
909 // Function to check if specified data stream starts with specified header.
910 #define dataStartsWithHeader(theData, theHeader) (::strncmp(theData, theHeader, sizeof(theHeader) - 1) == 0)
911
912 if (dataStartsWithHeader(aBytes, "DBRep_DrawableShape"))
913 {
914 return openBRepFromMemory (theName, theBuffer, theDataLen, theToFree);
915 }
916 else if (dataStartsWithHeader(aBytes, "glTF"))
917 {
918 //return openGltfFromMemory (theName, theBuffer, theDataLen, theToFree);
919 }
920 if (theToFree)
921 {
922 free (aBytes);
923 }
924
925 Message::SendFail() << "Error: file '" << theName.c_str() << "' has unsupported format";
926 return false;
927}
928
929// ================================================================
930// Function : openBRepFromMemory
931// Purpose :
932// ================================================================
933bool WasmOcctView::openBRepFromMemory (const std::string& theName,
934 uintptr_t theBuffer, int theDataLen,
935 bool theToFree)
936{
937 removeObject (theName);
938
939 WasmOcctView& aViewer = Instance();
940 TopoDS_Shape aShape;
941 BRep_Builder aBuilder;
942 bool isLoaded = false;
943 {
944 char* aRawData = reinterpret_cast<char*>(theBuffer);
945 Standard_ArrayStreamBuffer aStreamBuffer (aRawData, theDataLen);
946 std::istream aStream (&aStreamBuffer);
947 BRepTools::Read (aShape, aStream, aBuilder);
948 if (theToFree)
949 {
950 free (aRawData);
951 }
952 isLoaded = true;
953 }
954 if (!isLoaded)
955 {
956 return false;
957 }
958
959 Handle(AIS_Shape) aShapePrs = new AIS_Shape (aShape);
960 if (!theName.empty())
961 {
962 aViewer.myObjects.Add (theName.c_str(), aShapePrs);
963 }
964 aShapePrs->SetMaterial (Graphic3d_NameOfMaterial_Silver);
965 aViewer.Context()->Display (aShapePrs, AIS_Shaded, 0, false);
966 aViewer.View()->FitAll (0.01, false);
967 aViewer.UpdateView();
968
969 Message::DefaultMessenger()->Send (TCollection_AsciiString("Loaded file ") + theName.c_str(), Message_Info);
970 Message::DefaultMessenger()->Send (OSD_MemInfo::PrintInfo(), Message_Trace);
971 return true;
972}
973
974// ================================================================
975// Function : displayGround
976// Purpose :
977// ================================================================
978void WasmOcctView::displayGround (bool theToShow)
979{
980 static Handle(AIS_Shape) aGroundPrs = new AIS_Shape (TopoDS_Shape());
981
982 WasmOcctView& aViewer = Instance();
983 Bnd_Box aBox;
984 if (theToShow)
985 {
986 aViewer.Context()->Remove (aGroundPrs, false);
987 aBox = aViewer.View()->View()->MinMaxValues();
988 }
989 if (aBox.IsVoid()
990 || aBox.IsZThin (Precision::Confusion()))
991 {
992 if (!aGroundPrs.IsNull()
993 && aGroundPrs->HasInteractiveContext())
994 {
995 aViewer.Context()->Remove (aGroundPrs, false);
996 aViewer.UpdateView();
997 }
998 return;
999 }
1000
1001 const gp_XYZ aSize = aBox.CornerMax().XYZ() - aBox.CornerMin().XYZ();
1002 const double aRadius = Max (aSize.X(), aSize.Y());
1003 const double aZValue = aBox.CornerMin().Z() - Min (10.0, aSize.Z() * 0.01);
1004 const double aZSize = aRadius * 0.01;
1005 gp_XYZ aGroundCenter ((aBox.CornerMin().X() + aBox.CornerMax().X()) * 0.5,
1006 (aBox.CornerMin().Y() + aBox.CornerMax().Y()) * 0.5,
1007 aZValue);
1008
1009 TopoDS_Compound aGround;
1010 gp_Trsf aTrsf1, aTrsf2;
1011 aTrsf1.SetTranslation (gp_Vec (aGroundCenter - gp_XYZ(0.0, 0.0, aZSize)));
1012 aTrsf2.SetTranslation (gp_Vec (aGroundCenter));
1013 Prs3d_ToolCylinder aCylTool (aRadius, aRadius, aZSize, 50, 1);
1014 Prs3d_ToolDisk aDiskTool (0.0, aRadius, 50, 1);
1015 TopoDS_Face aCylFace, aDiskFace1, aDiskFace2;
1016 BRep_Builder().MakeFace (aCylFace, aCylTool .CreatePolyTriangulation (aTrsf1));
1017 BRep_Builder().MakeFace (aDiskFace1, aDiskTool.CreatePolyTriangulation (aTrsf1));
1018 BRep_Builder().MakeFace (aDiskFace2, aDiskTool.CreatePolyTriangulation (aTrsf2));
1019
1020 BRep_Builder().MakeCompound (aGround);
1021 BRep_Builder().Add (aGround, aCylFace);
1022 BRep_Builder().Add (aGround, aDiskFace1);
1023 BRep_Builder().Add (aGround, aDiskFace2);
1024
1025 aGroundPrs->SetShape (aGround);
1026 aGroundPrs->SetToUpdate();
1027 aGroundPrs->SetMaterial (Graphic3d_NameOfMaterial_Stone);
1028 aGroundPrs->SetInfiniteState (false);
1029 aViewer.Context()->Display (aGroundPrs, AIS_Shaded, -1, false);
1030 aGroundPrs->SetInfiniteState (true);
1031 aViewer.UpdateView();
1032}
1033
1034// Module exports
1035EMSCRIPTEN_BINDINGS(OccViewerModule) {
1036 emscripten::function("setCubemapBackground", &WasmOcctView::setCubemapBackground);
1037 emscripten::function("fitAllObjects", &WasmOcctView::fitAllObjects);
1038 emscripten::function("removeAllObjects", &WasmOcctView::removeAllObjects);
1039 emscripten::function("removeObject", &WasmOcctView::removeObject);
1040 emscripten::function("eraseObject", &WasmOcctView::eraseObject);
1041 emscripten::function("displayObject", &WasmOcctView::displayObject);
1042 emscripten::function("displayGround", &WasmOcctView::displayGround);
1043 emscripten::function("openFromUrl", &WasmOcctView::openFromUrl);
1044 emscripten::function("openFromMemory", &WasmOcctView::openFromMemory, emscripten::allow_raw_pointers());
1045 emscripten::function("openBRepFromMemory", &WasmOcctView::openBRepFromMemory, emscripten::allow_raw_pointers());
1046}