Make canvas response to key press event
We often create our custom iteractor style object to define our interactive behaviors.
Write some debug code in OnKeyPress
event.
For web page, the element canvas is like Qt’s widget. Set property tabindex
for responsing to user key press event.
<canvas id="canvas" style="position: absolute; left: 0; top: 0; width:100%; height:100%; " tabindex=-1></canvas>
Asynchronous call
It’s synchronous for JS code call wasm exported interface as introduced in post Synchronous call between JS and C function.
Asynchronous call can be implemented by js code directly.
var resolveAfter2Seconds = function() {
console.log("> starting slow promise");
return new Promise(resolve => {
setTimeout(function() {
resolve("> slow");
console.log("> slow promise is done");
}, 2000);
});
};
async function asyncCall() {
console.log('> calling');
const result = await resolveAfter2Seconds();
console.log(result);
}
asyncCall();
console.log( "> finish!" );
Output order:
Will it response to key press event immediately if we call a time-consuming interface before?
Conclusion:
The page will wait until the time-consuming interface finish, then responsing works.
Let’s create a 3D scene and a time-consuming interface to test our guess.
#include <iostream>
#include <stdio.h>
#include <vtkSmartPointer.h>
#include <vtkSphereSource.h>
#include <vtkActor.h>
#include <vtkConeSource.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkPolyDataMapper.h>
#include <vtkRenderWindowInteractor.h>
#include "CustomIteractorStyle.h"
#ifdef __EMSCRIPTEN__
#include "vtkSDL2OpenGLRenderWindow.h"
#include "vtkSDL2RenderWindowInteractor.h"
#include <unistd.h>
#endif // __EMSCRIPTEN__
#define vtkSPtr vtkSmartPointer
#define vtkSPtrNew(Var, Type) vtkSPtr<Type> Var = vtkSPtr<Type>::New();
using namespace std;
// export the function.
extern "C"
{
void setup()
{
printf( "let's start!\n" );
}
int compute( int a, int b )
{
int ans = 1;
for( int i = 0; i < b; ++i )
{
sleep(1);
ans = ans * a;
}
return ans;
}
}
int main()
{
vtkSPtrNew( cone, vtkConeSource );
vtkSPtrNew( mapper, vtkPolyDataMapper );
mapper->SetInputConnection( cone->GetOutputPort() );
vtkSPtrNew( actor, vtkActor );
actor->SetMapper( mapper );
vtkSPtrNew( renderer, vtkRenderer );
renderer->AddActor(actor);
renderer->SetBackground( 0, 0, 0 );
#ifdef __EMSCRIPTEN__
vtkSPtrNew(renderWindow, vtkSDL2OpenGLRenderWindow);
#else
vtkSPtrNew(renderWindow, vtkRenderWindow);
#endif
renderWindow->AddRenderer( renderer );
#ifdef __EMSCRIPTEN__
vtkSPtrNew(renderWindowInteractor, vtkSDL2RenderWindowInteractor);
#else
vtkSPtrNew(renderWindowInteractor, vtkRenderWindowInteractor);
#endif
renderWindowInteractor->SetRenderWindow( renderWindow );
renderer->ResetCamera();
vtkSPtrNew( iStyle, CustomIteractorStyle );
renderWindowInteractor->SetInteractorStyle( iStyle );
renderWindow->Render();
renderWindowInteractor->Start();
return 0;
}
Configure CMakeLists.txt to export our test and main functions.
project(wasmTester)
if (VTK_VERSION VERSION_LESS "8.9") #"9.0.3"
find_package( VTK REQUIRED )
else ()
find_package( VTK COMPONENTS vtkCommonCore vtkRenderingCore
vtkInteractionStyle vtkRenderingOpenGL2 vtkFiltersSources
vtkRenderingFreeType IOGeometry )
endif()
include_directories(
${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_CURRENT_SOURCE_DIR}
)
if(EMSCRIPTEN)
set(emscripten_options)
list(APPEND emscripten_options
"--bind"
"-O3"
"-g"
"-DLOG_OFF"
"SHELL:-s USE_SDL=2"
"SHELL:-s EXPORTED_FUNCTIONS=['_compute, _setup, _main']"
"SHELL:-s EXPORTED_RUNTIME_METHODS=['allocate,stringToUTF8,UTF8ToString,FS,intArrayFromString']"
"SHELL:-s EXTRA_EXPORTED_RUNTIME_METHODS=['ALLOC_NORMAL']"
"SHELL:-s -fdebug-compilation-dir=."
"SHELL:-s EXPORT_NAME=tester"
"SHELL:-s ALLOW_MEMORY_GROWTH=1"
"SHELL:-s DEMANGLE_SUPPORT=1"
"SHELL:-s EMULATE_FUNCTION_POINTER_CASTS=0"
"SHELL:-s ERROR_ON_UNDEFINED_SYMBOLS=0"
"SHELL:-s MODULARIZE=1"
"SHELL:-s WASM=1"
"SHELL:-s --no-heap-copy"
"SHELL:-s INITIAL_MEMORY=200MB"
"SHELL:-s MAXIMUM_MEMORY=512MB"
"SHELL:-s ASSERTIONS=2"
"SHELL:-s TOTAL_MEMORY=512MB"
"SHELL:-s DISABLE_EXCEPTION_CATCHING=0"
)
message("Configuring data directory for wasm..........")
endif()
set( cpps
main.cpp
CustomIteractorStyle.cpp
CustomIteractorStyle.h
)
add_executable(${PROJECT_NAME} ${cpps})
if (VTK_VERSION VERSION_LESS "8.9")
# old system
include(${VTK_USE_FILE})
else ()
# vtk_module_autoinit is needed
message("autoinit module for higher version vtk")
vtk_module_autoinit(
TARGETS ${PROJECT_NAME}
MODULES ${VTK_LIBRARIES}
)
endif ()
target_compile_options(${PROJECT_NAME}
PUBLIC
${emscripten_options}
)
target_link_options(${PROJECT_NAME}
PUBLIC
${emscripten_options}
)
target_link_libraries( ${PROJECT_NAME} ${VTK_LIBRARIES} ${LINK_FLAGS} )
Core code for test key press event.
void CustomIteractorStyle::OnKeyPress()
{
vtkRenderWindowInteractor* rwi = this->Interactor;
int isCtrl = rwi->GetControlKey();
std::string sym = rwi->GetKeySym();
std::cout << "sym: " << sym << std::endl;
}
emscripten loops are not real loops
We will not get the following message output in browser. It seems like that program goes to an infinite loop. But we can still click on html button and use shortcut key to debug, so Emscripten’s event loop is not an infinite loop !
var Module = {
canvas: (function() {
var canvas = document.getElementById('canvas');
//...
})(),
onRuntimeInitialized: function() {
console.log('initialized');
Module._main();
console.log( "the message will not be showed!" );
},
};
Most graphical apps use infinite loops to sequentially render like the following code.
int main(int argc, char* argv[]) {
while(1) {
renderFrame();
SDL_Delay(time_to_next_frame());
}
}
But Emscripten simulates an infinite loop, but in actuality just calls the loop function – in our case renderFrame() – at a specified number of frames per second.
int main(int argc, char* argv[]) {
emscripten_set_main_loop(renderFrame, 0, 1);
}
Reference link: https://www.jamesfmackenzie.com/2019/12/03/webassembly-emscripten-loops/
All source files are uploaded to Github.
https://github.com/theArcticOcean/tutorials/tree/main/learnWebAssembly/jsCallCFunctionWith3D