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 | |
51 | namespace |
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 | // ================================================================ |
126 | WasmOcctView& WasmOcctView::Instance() |
127 | { |
128 | static WasmOcctView aViewer; |
129 | return aViewer; |
565baee6 |
130 | } |
131 | |
132 | // ================================================================ |
133 | // Function : WasmOcctView |
134 | // Purpose : |
135 | // ================================================================ |
136 | WasmOcctView::WasmOcctView() |
137 | : myDevicePixelRatio (1.0f), |
138 | myUpdateRequests (0) |
139 | { |
140 | } |
141 | |
142 | // ================================================================ |
143 | // Function : ~WasmOcctView |
144 | // Purpose : |
145 | // ================================================================ |
146 | WasmOcctView::~WasmOcctView() |
147 | { |
148 | } |
149 | |
150 | // ================================================================ |
151 | // Function : run |
152 | // Purpose : |
153 | // ================================================================ |
154 | void 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 | // ================================================================ |
178 | void 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 | // ================================================================ |
209 | void 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 | // ================================================================ |
254 | void 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 | // ================================================================ |
283 | bool 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 | // ================================================================ |
360 | void 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 | // ================================================================ |
388 | void WasmOcctView::UpdateView() |
389 | { |
390 | if (!myView.IsNull()) |
391 | { |
392 | myView->Invalidate(); |
393 | updateView(); |
394 | } |
395 | } |
565baee6 |
396 | |
397 | // ================================================================ |
398 | // Function : updateView |
399 | // Purpose : |
400 | // ================================================================ |
401 | void 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 | // ================================================================ |
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(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 | // ================================================================ |
483 | EM_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 | // ================================================================ |
563 | EM_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 | // ================================================================ |
611 | EM_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 | // ================================================================ |
703 | EM_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 | // ================================================================ |
734 | EM_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 | // ================================================================ |
777 | void 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 | // ================================================================ |
794 | void 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 | // ================================================================ |
812 | void 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 | // ================================================================ |
828 | bool 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 | // ================================================================ |
848 | bool 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 | // ================================================================ |
867 | bool 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 | // ================================================================ |
886 | void 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 | // ================================================================ |
897 | bool 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 | // ================================================================ |
933 | bool 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 | // ================================================================ |
978 | void 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 |
1035 | EMSCRIPTEN_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 | } |