From 50e5cec6d48d6441075496fc9cdae82e41873965 Mon Sep 17 00:00:00 2001 From: Massimo Bernava <> Date: Tue, 15 Jun 2021 07:53:52 +0200 Subject: [PATCH] v0.2 --- README.md | 57 +- client/CMakeLists.txt | 102 ++- client/Logo.ico | Bin 0 -> 93062 bytes client/Proteo.icns | Bin 0 -> 149578 bytes client/config.json | 1 + client/em.sh | 14 +- client/icon.rc | 2 + client/proteo.c | 853 +++++------------- client/proteo.h | 83 +- client/proteo_config.c | 109 ++- client/proteo_enet.c | 2 +- client/proteo_gl.c | 202 +++++ client/proteo_graphics.c | 393 ++++++++- client/proteo_gui.c | 262 +++++- client/proteo_network.c | 114 ++- client/proteo_opencv.cpp | 180 +++- client/proteo_sdl.c | 36 +- client/proteo_shell_minimal.html | 1 - client/proteo_system.c | 7 + client/proteo_tensorflow.c | 378 ++++++++ client/proteo_utility.c | 574 ++++++++++++ client/update.sh | 6 - client/upload.sh | 3 - server/CMakeLists.txt | 67 +- server/install.sh | 26 +- server/lib/FormatMini.lua | 365 -------- server/lib/ParseLua.lua | 1411 ------------------------------ server/lib/Scope.lua | 195 ----- server/lib/WebForm.lua | 181 ---- server/lib/class.lua | 183 ---- server/lib/classy.lua | 527 ----------- server/lib/demo_lib.lua | 2 +- server/lib/middleclass.lua | 170 ---- server/lib/room.lua | 75 -- server/lib/skl_utility.lua | 10 +- server/lib/strict.lua | 39 - server/lib/tetris_lib.lua | 56 +- server/lib/tfl_blazeface.lua | 10 +- server/lib/tfl_blazepose.lua | 37 +- server/lib/tfl_emotion.lua | 2 +- server/lib/tfl_hand.lua | 322 +++++++ server/lib/tfl_utility.lua | 142 ++- server/plugin/admin.lua | 7 +- server/plugin/deepcrimson.lua | 115 ++- server/plugin/proteo.lua | 27 +- server/proteo.service | 2 +- server/proteo_auth.c | 11 +- server/proteo_config.c | 24 +- server/proteo_info.c | 93 +- server/proteo_lua.c | 4 +- server/proteo_network.c | 4 +- server/proteo_opencv.cpp | 58 +- server/proteo_server.c | 89 +- server/proteo_server.h | 11 +- server/proteo_sqlite.c | 10 +- server/proteo_system.c | 8 +- server/script/admin.lua | 39 +- server/script/demo/movelab.json | 3 +- server/script/demo/movelab.lua | 173 +++- server/script/prova.json | 3 +- server/script/prova.lua | 95 +- server/ticket.lua | 41 + server/update.sh | 6 - server/upload.sh | 3 - server/web/blockly/code.js | 2 +- server/web/blockly/demo.js | 92 +- server/web/blockly/index.html | 2 + 67 files changed, 4037 insertions(+), 4084 deletions(-) create mode 100644 client/Logo.ico create mode 100644 client/Proteo.icns create mode 100644 client/config.json create mode 100644 client/icon.rc create mode 100644 client/proteo_gl.c create mode 100644 client/proteo_tensorflow.c create mode 100644 client/proteo_utility.c delete mode 100755 client/update.sh delete mode 100755 client/upload.sh delete mode 100644 server/lib/FormatMini.lua delete mode 100644 server/lib/ParseLua.lua delete mode 100644 server/lib/Scope.lua delete mode 100644 server/lib/WebForm.lua delete mode 100644 server/lib/class.lua delete mode 100644 server/lib/classy.lua delete mode 100644 server/lib/middleclass.lua delete mode 100644 server/lib/room.lua delete mode 100644 server/lib/strict.lua create mode 100644 server/lib/tfl_hand.lua create mode 100644 server/ticket.lua delete mode 100755 server/update.sh delete mode 100755 server/upload.sh diff --git a/README.md b/README.md index 55501c9..1d6b059 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,63 @@ ## Installation -## Server - -sudo apt install git cmake lua5.1 liblua5.1-0-dev libssl-dev libcurl4-openssl-dev libsqlite3-dev libmicrohttpd-dev libjson-c-dev libenet-dev +## Server Linux + +Install the required libraries: +```bash +sudo apt install git cmake libluajit-5.1-dev libssl-dev libcurl4-openssl-dev libjson-c-dev libmicrohttpd-dev libsqlite3-dev libzmq3-dev libavcodec-dev libavformat-dev libavutil-dev libswresample-dev libswscale-dev libopencv-dev +``` + +Optional install nosql ejdb2 database: +```bash +sudo apt-get install software-properties-common +sudo add-apt-repository ppa:adamansky/ejdb2 +sudo apt-get install ejdb2 +``` + +Install Tensorflow Lite: +```bash +git clone https://github.com/tensorflow/tensorflow.git tensorflow_src +mkdir tflite_build +cd tflite_build +cmake ../tensorflow_src/tensorflow/lite/c -DTFLITE_ENABLE_GPU=ON +cmake --build . -j +``` + +Clone repository and use cmake: +```bash +git clone https://github.com/massimobernava/proteo.git +cd proteo/server +mkdir build +cd build +cmake -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl .. +make +``` + +Update config.json: +```json +{ "version":"0.1", + "basedir":"/usr/local/etc/Proteo/", + "baseurl":"YOUR_LOCAL_IP", + "port":8888,"master":1,"ssl":0, + "server_key":"01234567890123456789012345678901", + "client_key":"01234567890123456789012345678901", + "ssl_key":"", + "ssl_cert":"", + "admin_enabled":1,"plugins":["proteo","admin","edit","deepcrimson","deepindigo"], + "servers":[] +} +``` +Install it as a service: +```bash +chmod +x ./install.sh +sudo ./install.sh +``` ## Client +Choose, red pill and continue to compile, [blue pill](https://github.com/massimobernava/proteo/releases) and download one of the releases. + ### Linux Test: Ubuntu ... Raspberry ... diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 2660d1e..7ef5fdf 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -17,6 +17,8 @@ find_package(JSONC REQUIRED) #find_package(ENet REQUIRED) find_package(OpenCV REQUIRED) find_package(ZeroMQ REQUIRED) +find_package(OpenGL) +find_package(GLEW 2.0) find_path( AVCODEC_INCLUDE_DIR libavcodec/avcodec.h ) find_library( AVCODEC_LIBRARY avcodec ) @@ -25,27 +27,103 @@ find_library( SWSCALE_LIBRARY swscale ) find_path(AVUTIL_INCLUDE_DIR libavutil/avutil.h) find_library(AVUTIL_LIBRARY avutil) -include_directories(${SWSCALE_INCLUDE_DIR} ${AVCODEC_INCLUDE_DIR} ${AVUTIL_INCLUDE_DIR} ${SDL2_INCLUDE_DIRS} ${SDL2_TTF_INCLUDE_DIRS} ${SDL2_IMAGE_INCLUDE_DIRS} ${SDL2_GFX_INCLUDE_DIRS} ${LUAJIT_INCLUDE_DIR} ${OPENSSL_INCLUDE_DIR} ${CURL_INCLUDE_DIRS} ${JSONC_INCLUDE_DIRS} ${OpenCV_INCLUDE_DIRS} ${ZMQ_INCLUDE_DIRS}) #${ENET_INCLUDE_DIRS} +find_path(TensorFlowLite_INCLUDE_DIR +NAMES +tensorflow/lite +tensorflow +HINTS +${CMAKE_SOURCE_DIR}/tflite/include +/usr/local/include) + +find_library(TensorFlowLite_LIBRARY +NAMES +tensorflowlite_c +tensorflowlite +HINTS +${CMAKE_SOURCE_DIR}/tflite/lib +/usr/lib +/usr/local/lib) + +FIND_PACKAGE_HANDLE_STANDARD_ARGS(TFLITE DEFAULT_MSG TensorFlowLite_INCLUDE_DIR) + +if(TFLITE_FOUND) + include_directories(${TensorFlowLite_INCLUDE_DIR}) + add_definitions(-DTFLITE) +endif() + +if (GLEW_FOUND) + include_directories(${GLEW_INCLUDE_DIRS} ${OPENGL_INCLUDE_DIR}) + add_definitions(-DGLEW) +endif() + +include_directories( ${SWSCALE_INCLUDE_DIR} ${AVCODEC_INCLUDE_DIR} ${AVUTIL_INCLUDE_DIR} ${SDL2_INCLUDE_DIRS} ${SDL2_TTF_INCLUDE_DIRS} ${SDL2_IMAGE_INCLUDE_DIRS} ${SDL2_GFX_INCLUDE_DIRS} ${LUAJIT_INCLUDE_DIR} ${OPENSSL_INCLUDE_DIR} ${CURL_INCLUDE_DIRS} ${JSONC_INCLUDE_DIRS} ${OpenCV_INCLUDE_DIRS} ${ZMQ_INCLUDE_DIRS}) #${ENET_INCLUDE_DIRS} if(ANDROID) add_executable(proteo SHARED proteo.c) +elseif(APPLE) + set(ICON_NAME "Proteo.icns") + set(ICON_PATH ${CMAKE_SOURCE_DIR}/${ICON_NAME}) + set(proteo_ICON ${ICON_PATH}) + set_source_files_properties(${proteo_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + + set(FONT1_NAME "ColabReg.otf") + set(FONT1_PATH ${CMAKE_SOURCE_DIR}/${FONT1_NAME}) + set(proteo_FONT1 ${FONT1_PATH}) + set_source_files_properties(${proteo_FONT1} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + + set(FONT2_NAME "OpenSans-Regular.ttf") + set(FONT2_PATH ${CMAKE_SOURCE_DIR}/${FONT2_NAME}) + set(proteo_FONT2 ${FONT2_PATH}) + set_source_files_properties(${proteo_FONT2} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + + set(CONFIG_NAME "config.json") + set(CONFIG_PATH ${CMAKE_SOURCE_DIR}/${CONFIG_NAME}) + set(proteo_CONFIG ${CONFIG_PATH}) + set_source_files_properties(${proteo_CONFIG} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + + file(COPY ${ICON_PATH} DESTINATION "proteo.app/Contents/Resources") + add_executable(proteo MACOSX_BUNDLE ${ICON_PATH} ${FONT1_PATH} ${FONT2_PATH} ${CONFIG_PATH} proteo.c proteo_opencv.cpp) + set_target_properties(proteo PROPERTIES MACOSX_BUNDLE_ICONFILE ${ICON_NAME}) + set_target_properties(proteo PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/MacOSXBundleInfo.plist.in) + + SET(APPS "\${CMAKE_INSTALL_PREFIX}/proteo.app") + + #Copia il file nella directory principale + #INSTALL(TARGETS proteo BUNDLE DESTINATION ${CMAKE_SOURCE_DIR} COMPONENT Runtime) + + #set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}) + install(CODE " + + MESSAGE(\"Fixup\") + include(BundleUtilities) + fixup_bundle(${CMAKE_SOURCE_DIR}/install/proteo.app \"\" \"\") + + " COMPONENT Runtime) else() add_executable(proteo proteo.c proteo_opencv.cpp) endif() -target_link_libraries(proteo ${SWSCALE_LIBRARY} ${AVCODEC_LIBRARY} ${AVUTIL_LIBRARY} ${SDL2_LIBRARIES} ${SDL2_TTF_LIBRARIES} ${SDL2_IMAGE_LIBRARIES} ${SDL2_GFX_LIBRARIES} ${OPENSSL_LIBRARIES} ${LUAJIT_LIBRARIES} ${CURL_LIBRARIES} ${JSONC_LIBRARIES} ${OpenCV_LIBS} ${ZMQ_LIBRARIES}) #${ENET_LIBRARIES} +if(TFLITE_FOUND) + target_link_libraries(proteo ${TensorFlowLite_LIBRARY}) +endif() + +if (GLEW_FOUND) + target_link_libraries(proteo ${GLEW_LIBRARIES} ${OPENGL_gl_LIBRARY}) +endif() + +target_link_libraries(proteo ${SWSCALE_LIBRARY} ${AVCODEC_LIBRARY} ${AVUTIL_LIBRARY} ${SDL2_LIBRARIES} ${SDL2_TTF_LIBRARIES} ${SDL2_IMAGE_LIBRARIES} ${SDL2_GFX_LIBRARIES} ${OPENSSL_LIBRARIES} ${LUAJIT_LIBRARIES} ${CURL_LIBRARIES} ${JSONC_LIBRARIES} ${OpenCV_LIBS} ${ZMQ_LIBRARIES}) #${ENET_LIBRARIES} + +#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -latomic") + +#set(CMAKE_XCODE_GENERATE_SCHEME TRUE) +#set_property (TARGET proteo PROPERTY XCODE_SCHEME_ARGUMENTS " -g -d") +#set_property (TARGET proteo PROPERTY XCODE_SCHEME_ADDRESS_SANITIZER TRUE) -#include(InstallRequiredSystemLibraries) -#set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/License.txt") -#set(CPACK_GENERATOR "DragNDrop") -#set(CPACK_PACKAGE_NAME "Proteo") -#set(CPACK_PACKAGE_VERSION,1.0) +#set(CPACK_BUNDLE_NAME "Proteo") +#set(CPACK_BUNDLE_ICON "Proteo.icns") +#set(CPACK_BUNDLE_PLIST "Info.plist") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -latomic") +#include(CPack) -set(CMAKE_XCODE_GENERATE_SCHEME TRUE) -set_property (TARGET proteo PROPERTY XCODE_SCHEME_ARGUMENTS " -g -d") -set_property (TARGET proteo PROPERTY XCODE_SCHEME_ADDRESS_SANITIZER TRUE) -include(CPack) diff --git a/client/Logo.ico b/client/Logo.ico new file mode 100644 index 0000000000000000000000000000000000000000..9bbd2eb773523f93ba7f17216c0d1d7790159dd7 GIT binary patch literal 93062 zcmeI52YeM(_Qzi+c0p8hby0WmuOGXvWp`cCwXCic`>qW|Kt-h2rMQ9u3Sz;6lt3VK zNFY5SosbG7wDjIX2r5`s`uU&VeeXWzP3GnGl4KsA!%XJQ+;+Zm?z!jQnP)Hr@V^BM z4E+Be45Qx*FkE6V7_MXCLx!6IjK_J`@Z}o;hQ)miLh!dYKXrBWhT7V?fcpA|UJVV6 zcd!g-Y;2lSRaO#SkdvObD>6k! z=4gB9pd);^J_fE!SFYQYEH7|fzhcQ`+0Sy6h0M~lpPGa;^Hvwf+h9L&vz~5GW80#Q z(bj15ub>0y0y=?i+S8||rqIB_IF<xV5ctT?F^O!EEepbe%yD{fdef<@VELLTYoFB-@m_E^LlkY z&CM;s|F@Pl(uDB%h)PS#sIag|Z1Fepz;}+871zmn;(BX&<2cfm=i0lql_tK2Fb`B# zRxavPQc_CnH#t)$=yHZhw+86i8z1<1hYlSAQ@3&cu`DOiUP?jR6&IJ#7hilyGiT1C zS+izSYU)-|N9B3e_Hlh>Wfd)0@Eu=2Q%H1lOe=lkJhbE2Uw>`NXBN$!`zh_-y{FZ= z&^`MP6d4&slO|1~#~*)!?zrPly6L8y==R%hr$--sl*W!7OQE5gsG_1$=*e37l*dtq z@bGYRITtTpEb>vFBll5;?b~HYWLr~du>(_MGn#dW%w{`99m(ZdfvOv8tdptWn)iF?NupR%01g-&CkWuoPr)SXbe)l_K)`iTQ_nEj)tzDx$4qffrmrvJUf4%v7 z=v>_{`1{*$zoj$JJkxyM{rBHbMMcFHTp_*q_yl_Hx##JE3obC9E6b+bo_F4P^v{3( zQ}Bf{;aX+c)cdIG(4j-kX+oc{6% zzyP}V;)~l%@9eYBq5JN;k5;Z+CHT}Tn=)<1ehG99o!8ZQCN5!Qs=hnt^DgImP?2}5 zcJ~Zd<1DyB+kidXpHd&adiA0W8#c5$Z`raSy6UPwn9q~vckS9$FxjnJw^rZv?%kU{ z{`eDN58Bd$cer}Yf-B@15)xu@-^n_4>C#2iseAYCt?JaXXHS|uc`~tIuF1U>ogQSJ z4`a5vvu)&T-1p&hG2T0aw-Y$;pRD9>=F1ygArJJiV1sKaWXO;qf&*DToCD4-yX-Ra zIXJgZpDSp{khf^z!iBVM-FgZN3Ze-UCekgp+(OWcOb@mTcGgo|f#-Ygy_cSO<{5hW z>8Ca2$tRzr4?dV~?gvq)xVSjF`s%CAFl%5HmCdh-~XQVxW$~Vy8TfW zXZyi(&pnsI!oo#axSfTrwYJ>$;+{pF(8t1WlI&x9&prJJ{wK~;*9qg2DO09Uj~+eD z*YxUjA+29;Y1h^D()jyVu*Lqh%XX{2zLEW@diJN9(D&K2Q?e|c<_hgQZrpftJHh6| z7e>CP%Wy#W!%F+};DZke*3^F5>1&!re=};-C<{CI$}6u}*g<(dwyts&9v)%QZ!6PQ z@1xuc7cR2!Co%TO&d#ywyGmO-efkUwzYgORj8EkE>b$J{4ZAh0s;Y+lE$8|~=85NH znlbKsmh)(_l8;%B%I~$f`s_1T{dS{&g|t&rQt65-t}t`;zylA6@uzGfY%xyk*RP-X zoQp2Hh$12)ZR!x|=jRt#*tyFuzns$2wpnm1>+4Of5DUSWPueQjJM?$3QL;Q*TeL@c zd4+|Y!}tYbV{7-ARyw9*@VB08X=!nI{s-eqPD92LirdITvp!l}&6_t*)Pc(+u0dT< zCy3-h=7Yc0b?wRMLxN?dGyCW_BGe3U%z|B{iAL_t-2vi z_>)S1e#VRsE!s`%IxELYxxc=hzcaAS<0di~#@%2rOkkPFGU-NfsMUY0doXnoUl(m9 z-8S{3#T8;i&p-bH%k%WoOE1w|Z@tAam;ci0)oX-(z%?>2Ew120Oqnv3h7B7=7hZUw zxow|#;t3H4#Pza$(S~c*tg+}Xz=;+YGEe0eT*bu162?#5I6}savmB4g^O3K!{?(ah zo<-xwPoPDM7Sng%Eo>zV7Az3)4)|OsQ*3OUMVUsA9<6CZW!aQlloS5vWtUxUuG8zU zzhQ23<+=9kGas?s7hcR|6Z}kMmd1lE-a^zLoQHS?r~Ls-tC*)fd7=*L_!Z}4u8(pb zZ42fC0|SLELi$=&tXaSz%FZH>4$^1}D9{mYlHuv-Tx!;T$0=*lavGV5T_pg|tt zN|xW+7I9w86QE8ZA)yvrIT~xm*bwszGOzyqUE&HlKftU!0A}J>erL2-CY!~1m_Hc* zv0l#S^CFyO3fF0I_2!#{C^$HnmM&e!^D_%XY~{~?{)+`;_uO-j7)RrJwSR>;&$Map zi@tE;#EGKsx%b}tEZX^!OD++81^h6?rNA0jPKYD-Hu@aI=av2Km@#8CeG!z^*?th* z-+JqZ7Uqbrm%Cnv|k1|wE)&$Vrx!(2skf7p}7 zK;IettnrF|5Xae0z*fD+>A18m0d-LKgP1G);fEi^eTRDwg7^}~{!;hQV`5^W_)g80 z^xM&n5afYzGUCUugE9^HV7!({%yB5^kzRc9UshuU>UKl=pMLtOnR(dP`SZULP%XiC$P+ev*RI`!IGwC3+78#?J9W9#f1|yZEn8;cbHcy1mrHee ziocICGjDJ^7@`-?GmPQ|@bKZ~$7HLpJ|SIkjm`3EovX!_md%F#KmPb*b34JlY}l|- zaG~aE?AX?RGuj04QDw~G)mL8=?F{A+e~>zWErTy^s~u5x^cQ#EeYeH6NlD3qWp#aB z#WoB z0T{F3-m;d4EHlQ#%K3TBLt~wg{9c`xgMS}q|7kE087}Q=2#{Q{9TV$4Ijv*|)6=wP zT3mhp`4=Mogzr!n#F8Q-Bk6`4ZZI=}c|6R~qm9&DA-)dYa6JU Ev&W5!xAFlo|% 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
  • 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%0DtEYJ~NN5Ep(M`!x5kFQLuCn0Dp)$ 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

    -x)f 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

    ^8bKwg#9+&O|mlk^GL@6*u69CdvuTU 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 literal 0 HcmV?d00001 diff --git a/client/config.json b/client/config.json new file mode 100644 index 0000000..7a97414 --- /dev/null +++ b/client/config.json @@ -0,0 +1 @@ +{"version":"2.0.0","basedir":"./","server":"http://localhost:8888","username":"demo","password":"demo","script":"","appkey":"01234567890123456789012345678901","ssl":0} diff --git a/client/em.sh b/client/em.sh index bc66dbe..a3b8a94 100755 --- a/client/em.sh +++ b/client/em.sh @@ -1,12 +1,20 @@ +#./emsdk install latest +#./emsdk activate latest +#source ./emsdk_env.sh + #emcc -Iem-include proteo_opencv.c em-lib/liblua.a -o proteo_opencv.html --shell-file ./proteo_shell_minimal.html if [[ $1 == DEBUG ]]; then echo "DEBUG MODE" - emcc -Iem-include proteo.c em-lib/liblua.a -lidbfs.js -O2 -g1 -s ASSERTIONS=2 -s SAFE_HEAP=1 -s STACK_OVERFLOW_CHECK=2 -s WASM=1 -s USE_SDL=2 -s USE_SDL_IMAGE=2 -s SDL2_IMAGE_FORMATS='["bmp","png"]' -s USE_SDL_TTF=2 -s USE_SDL_GFX=2 -s FETCH=1 -s EXPORTED_FUNCTIONS='["_main","_em_login_function","_em_header_callback","_proteo_get_callback","_proteo_post_callback"]' -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' -o index.html --shell-file ./proteo_shell_minimal.html + #emcc -Iem-include proteo_opencv.cpp em-lib/liblua.a em-lib/libopencv_core.a -lidbfs.js -O2 -g1 -s LLD_REPORT_UNDEFINED -s ASSERTIONS=2 -s SAFE_HEAP=1 -s STACK_OVERFLOW_CHECK=2 -s WASM=1 -s FETCH=1 + emcc -Iem-include proteo_opencv.cpp proteo.c em-lib/liblua.a em-lib/libopencv_*.a -lidbfs.js -O2 -g1 -s LLD_REPORT_UNDEFINED -s ASSERTIONS=2 -s STACK_OVERFLOW_CHECK=2 -s WASM=1 -s USE_SDL=2 -s USE_SDL_IMAGE=2 -s SDL2_IMAGE_FORMATS='["bmp","png"]' -s USE_SDL_TTF=2 -s USE_SDL_GFX=2 -s FETCH=1 -s EXPORTED_FUNCTIONS='["_main","_em_login_function","_em_header_callback","_proteo_get_callback","_proteo_post_callback"]' -s EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' -o index.html --shell-file ./proteo_shell_minimal.html elif [[ $1 == SAN ]]; then echo "SANITIZER MODE" - emcc -Iem-include proteo.c em-lib/liblua.a -lidbfs.js -O2 -g1 -fsanitize=address -s ALLOW_MEMORY_GROWTH -s WASM=1 -s USE_SDL=2 -s USE_SDL_IMAGE=2 -s SDL2_IMAGE_FORMATS='["bmp","png"]' -s USE_SDL_TTF=2 -s USE_SDL_GFX=2 -s FETCH=1 -s EXPORTED_FUNCTIONS='["_main","_em_login_function","_em_header_callback","_proteo_get_callback","_proteo_post_callback"]' -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' -o index.html --shell-file ./proteo_shell_minimal.html + emcc -Iem-include proteo_opencv.cpp proteo.c em-lib/liblua.a em-lib/libopencv_*.a -lidbfs.js -O2 -g1 -fsanitize=address -s ALLOW_MEMORY_GROWTH -s WASM=1 -s USE_SDL=2 -s USE_SDL_IMAGE=2 -s SDL2_IMAGE_FORMATS='["bmp","png"]' -s USE_SDL_TTF=2 -s USE_SDL_GFX=2 -s FETCH=1 -s EXPORTED_FUNCTIONS='["_main","_em_login_function","_em_header_callback","_proteo_get_callback","_proteo_post_callback"]' -s EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' -o index.html --shell-file ./proteo_shell_minimal.html +elif [[ $1 == NOOPENCV ]]; then + echo "NO OPENCV MODE" + emcc -Iem-include proteo.c em-lib/liblua.a -lidbfs.js -Oz -fsanitize=undefined -s ALLOW_MEMORY_GROWTH=1 -s ASSERTIONS=1 -g1 -s WASM=1 -s USE_SDL=2 -s USE_SDL_IMAGE=2 -s SDL2_IMAGE_FORMATS='["bmp","png"]' -s USE_SDL_TTF=2 -s USE_SDL_GFX=2 -s FETCH=1 -s EXPORTED_FUNCTIONS='["_main","_em_login_function","_em_header_callback","_proteo_get_callback","_proteo_post_callback","_image_to_mat"]' -s EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' -s NO_DISABLE_EXCEPTION_CATCHING -o index.html --shell-file ./proteo_shell_minimal.html else - emcc -Iem-include proteo.c em-lib/liblua.a -lidbfs.js -Oz -s ASSERTIONS=1 -g1 -s WASM=1 -s USE_SDL=2 -s USE_SDL_IMAGE=2 -s SDL2_IMAGE_FORMATS='["bmp","png"]' -s USE_SDL_TTF=2 -s USE_SDL_GFX=2 -s FETCH=1 -s EXPORTED_FUNCTIONS='["_main","_em_login_function","_em_header_callback","_proteo_get_callback","_proteo_post_callback"]' -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' -o index.html --shell-file ./proteo_shell_minimal.html + emcc -Iem-include proteo_opencv.cpp proteo.c em-lib/liblua.a em-lib/libopencv_*.a -lidbfs.js -Oz -fsanitize=undefined -s ALLOW_MEMORY_GROWTH=1 -s ASSERTIONS=1 -g1 -s WASM=1 -s USE_SDL=2 -s USE_SDL_IMAGE=2 -s SDL2_IMAGE_FORMATS='["bmp","png"]' -s USE_SDL_TTF=2 -s USE_SDL_GFX=2 -s FETCH=1 -s EXPORTED_FUNCTIONS='["_main","_em_login_function","_em_header_callback","_proteo_get_callback","_proteo_post_callback","_image_to_mat"]' -s EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' -s NO_DISABLE_EXCEPTION_CATCHING -o index.html --shell-file ./proteo_shell_minimal.html fi diff --git a/client/icon.rc b/client/icon.rc new file mode 100644 index 0000000..c356d64 --- /dev/null +++ b/client/icon.rc @@ -0,0 +1,2 @@ + +MAINICON ICON "Logo.ico" diff --git a/client/proteo.c b/client/proteo.c index 6f6d948..980fa6e 100644 --- a/client/proteo.c +++ b/client/proteo.c @@ -1,20 +1,27 @@ //============================================================================== -//=> Proteo v2.0 +//=> Proteo v0.2 //=> //=> //=> CC BY-NC-SA 3.0 //=> //=> Massimo Bernava //=> massimo.bernava@gmail.com -//=> 2021-05-13 +//=> 2021-06-15 //============================================================================== +//#define TFLITE //CMAKE +//#define GLEW //CMAKE + //#if defined(__MINGW32__) || defined(__MINGW64__) // #define _WIN32 //#endif +#define TARGET_OS_WINDOWS 0 #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) - //define something for Windows (32-bit and 64-bit, this part is common) + + #define TARGET_OS_WINDOWS 1 + #define TFLITE //For code::blocks + #ifdef _WIN64 //define something for Windows (64-bit only) #else @@ -47,21 +54,26 @@ #define PROTEO_USE_TINYJSON #define PROTEO_USE_INTERNALMD5 -#elif defined(_WIN32) - #define PROTEO_USE_TINYJSON - +#else //if defined(_WIN32) + // #define PROTEO_USE_TINYJSON + #define PROTEO_ZMQ + #define PROTEO_FFMPEG + #define LUA_JIT + #endif -//#define PROTEO_USE_TINYJSON +#ifdef GLEW +//#define USEOPENGL +#endif -#define PROTEO_ZMQ #define PROTEO_OPENCV -#define PROTEO_FFMPEG +//#define BACKBUFFER + +//#define PROTEO_USE_TINYJSON //TODO //#define DLINK_LIST_COMPONENT - #define PROTEO_ENET #define BEZIER @@ -72,6 +84,9 @@ #include "proteo_b64.c" #include "proteo_config.c" #include "proteo_sdl.c" +#ifdef USEOPENGL +#include "proteo_gl.c" +#endif #include "proteo_gui.c" #include "proteo_lua.c" #include "proteo_network.c" @@ -87,548 +102,24 @@ #ifdef PROTEO_ENET #include "proteo_enet.c" #endif -#ifdef __EMSCRIPTEN__ - #ifdef PROTEO_OPENCV - #include "proteo_opencv.c" - #endif -#endif +//#ifdef __EMSCRIPTEN__ + //#ifdef PROTEO_OPENCV + //#include "proteo_opencv.c" + //#endif +//#endif #ifdef PROTEO_FFMPEG #include "proteo_ffmpeg.c" #endif -//============================================================================== -// TODO -//============================================================================== - -// -//============================================================================== -// UTILITY -//============================================================================== - -ProteoColor brightness(ProteoColor color,float bright) -{ - ProteoColor col={255, - MIN(255,color.r*bright), - MIN(255,color.g*bright), - MIN(255,color.b*bright)}; - - return col; -} - -ProteoColor saturation(ProteoColor color, float sat) -{ - const float Pr=0.299; - const float Pg=0.587; - const float Pb=0.114; - - float P=sqrt(color.r*color.r*Pr+color.g*color.g*Pg+color.b*color.b*Pb); - - ProteoColor col={255, - P+(color.r-P)*sat, - P+(color.g-P)*sat, - P+(color.b-P)*sat}; - - return col; -} - -char font_path[256]; -char* font2path(const char* font) -{ - //#ifdef __EMSCRIPTEN__ - //strcpy(font_path,""); - //#else - strcpy(font_path,config.basedir); - //#endif - - size_t n = sizeof(nfonts)/sizeof(nfonts[0]); - for(int i=0;i> 24) & 0xff; - col.r = (value >> 16) & 0xff; - col.g = (value >> 8) & 0xff; - col.b = (value >> 0) & 0xff; - //return color; - - return col; -} - -void mkpath(const char* dir) { - char tmp[256]; - char *p = NULL; - size_t len; - - snprintf(tmp, sizeof(tmp),"%s",dir); - dirname(tmp); - len = strlen(tmp); - if(tmp[len - 1] == '/') - tmp[len - 1] = 0; - for(p = tmp + 1; *p; p++) - if(*p == '/') { - *p = 0; - #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || defined(__MINGW64__) - _mkdir(tmp); - #else - mkdir(tmp, S_IRWXU); - #endif - *p = '/'; - } - #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || defined(__MINGW64__) - _mkdir(tmp); - #else - mkdir(tmp, S_IRWXU); - #endif -} - -int writefile(char* filename,char* data) -{ - FILE *handler=fopen(filename,"w"); - if (handler) - { - fprintf(handler,"%s",data); - fclose(handler); - return 0; - } - - return 1; -} - -int writedatafile(char* filename,char* data,int data_size) -{ - int write_size; - FILE *handler=fopen(filename,"wb"); - if (handler) - { - write_size = fwrite(data, sizeof(char), data_size, handler); - fclose(handler); - if(debug) printf("Write data file: %s,%d,%d\n",filename,data_size,write_size); - return write_size; - } - else printf("Write data %s error: %s\n",filename,strerror(errno)); - - return 0; -} - -char* loadfile(char *filename) -{ - char *buffer = NULL; - int string_size, read_size; - FILE *handler = fopen(filename, "r"); - - if (handler) - { - fseek(handler, 0, SEEK_END); - string_size = ftell(handler); - rewind(handler); - - buffer = (char*) malloc(sizeof(char) * (string_size + 1) ); - if(buffer!=NULL) - { - read_size = fread(buffer, sizeof(char), string_size, handler); - buffer[string_size] = '\0'; - - if (string_size != read_size) - { - free(buffer); - buffer = NULL; - } - } - - fclose(handler); - } - else printf("Load file %s error: %s\n",filename,strerror(errno)); - - return buffer; -} - -int loaddatafile(char *filename,char **buffer) -{ - int data_size, read_size; - FILE *handler = fopen(filename, "r"); - - if (handler) - { - fseek(handler, 0, SEEK_END); - data_size = ftell(handler); - rewind(handler); - - *buffer = (char*) malloc(sizeof(char) * (data_size) ); - if(buffer!=NULL) - { - read_size = fread(*buffer, sizeof(char), data_size, handler); - - if (data_size != read_size) - { - free(*buffer); - *buffer = NULL; - return 0; - } - //if(debug) printf("Load data file: %s,%d,%d\n",filename,data_size,read_size); - if(debug) printf("Load data file: %s\n",filename); - } - fclose(handler); - } - else printf("Load data %s error: %s\n",filename,strerror(errno)); - - return read_size; -} - -void hexDump (const char * desc, const void * addr, const int len) { - int i; - unsigned char buff[17]; - const unsigned char * pc = (const unsigned char *)addr; - - // Output description if given. - - if (desc != NULL) - printf ("%s:\n", desc); - - // Length checks. - - if (len == 0) { - printf(" ZERO LENGTH\n"); - return; - } - else if (len < 0) { - printf(" NEGATIVE LENGTH: %d\n", len); - return; - } - - // Process every byte in the data. - - for (i = 0; i < len; i++) { - // Multiple of 16 means new line (with line offset). - - if ((i % 16) == 0) { - // Don't print ASCII buffer for the "zeroth" line. - - if (i != 0) - printf (" %s\n", buff); - - // Output the offset. - - printf (" %04x ", i); - } - - // Now the hex code for the specific character. - printf (" %02x", pc[i]); - - // And buffer a printable ASCII character for later. - - if ((pc[i] < 0x20) || (pc[i] > 0x7e)) // isprint() may be better. - buff[i % 16] = '.'; - else - buff[i % 16] = pc[i]; - buff[(i % 16) + 1] = '\0'; - } - - // Pad out last line if not exactly 16 characters. - - while ((i % 16) != 0) { - printf (" "); - i++; - } - - // And print the final ASCII buffer. - - printf (" %s\n", buff); -} - -char* concat(const char *s1, const char *s2) -{ - const size_t len1 = strlen(s1); - const size_t len2 = strlen(s2); - char *result = malloc(len1 + len2 + 1); - - if(result!=NULL) - { - memcpy(result, s1, len1); - memcpy(result + len1, s2, len2 + 1); - } - result[len1 + len2]=0; - - return result; -} - -char* concat3(const char *s1, const char *s2, const char *s3) -{ - const size_t len1 = strlen(s1); - const size_t len2 = strlen(s2); - const size_t len3 = strlen(s3); - - char *result = malloc(len1 + len2 + len3 + 1); - - if(result!=NULL) - { - memcpy(result, s1, len1); - memcpy(result + len1, s2, len2); - memcpy(result + len1 + len2, s3, len3 + 1); // +1 to copy the null-terminator - } - result[len1 + len2 + len3]=0; - - return result; -} - -int c_quote(const char* src, char* dest, int maxlen) { - int count = 0; - if(maxlen < 0) { - maxlen = strlen(src)+1; /* add 1 for NULL-terminator */ - } - - while(src && maxlen > 0) { - switch(*src) { - - /* these normal, printable chars just need a slash appended */ - case '\\': - case '\"': - case '\'': - if(dest) { - *dest++ = '\\'; - *dest++ = *src; - } - count += 2; - break; - - /* newlines/tabs and unprintable characters need a special code. - * Use the macro CASE_CHAR defined below. - * The first arg for the macro is the char to compare to, - * the 2nd arg is the char to put in the result string, after the '\' */ -#define CASE_CHAR(c, d) case c:\ - if(dest) {\ - *dest++ = '\\'; *dest++ = (d);\ - }\ -count += 2;\ -break; - /* -------------- */ - CASE_CHAR('\n', 'n'); - CASE_CHAR('\t', 't'); - CASE_CHAR('\b', 'b'); - /* ------------- */ - -#undef CASE_CHAR - - - /* by default, just copy the char over */ - default: - if(dest) { - *dest++ = *src; - } - count++; - } - - ++src; - --maxlen; - } - return count; -} - -//simple encrypt-decrypt function -void encryptDecrypt(char inpString[]) -{ - char xorKey[] = "Key"; - int len = strlen(inpString); - - for (int i = 0; i < len; i++) - { - inpString[i] = inpString[i] ^ xorKey[i%strlen(xorKey)]; - printf("%c",inpString[i]); - } -} - -#ifdef AFFINE -SDL_Point affineTrasformation(SDL_Point point,float affine[3][3]) -{ - SDL_Point ret; - - ret.x=point.x*affine[0][0]+point.y*affine[1][0]+affine[2][0]; - ret.y=point.x*affine[0][1]+point.y*affine[1][1]+affine[2][1]; - - return ret; -} +#ifdef TFLITE +#include "proteo_tensorflow.c" #endif -void mult_matrices(float a[3][3], float b[3][3], float result[3][3]) -{ - int i, j, k; - for(i = 0; i < 3; i++) - { - for(j = 0; j < 3; j++) - { - for(k = 0; k < 3; k++) - { - result[i][j] += a[i][k] * b[k][j]; - } - } - } -} - - -void laderman_mul(const float a[3][3],const float b[3][3],float c[3][3]) { - - float m[24]; // not off by one, just wanted to match the index from the paper - - m[1 ]= (a[0][0]+a[0][1]+a[0][2]-a[1][0]-a[1][1]-a[2][1]-a[2][2])*b[1][1]; - m[2 ]= (a[0][0]-a[1][0])*(-b[0][1]+b[1][1]); - m[3 ]= a[1][1]*(-b[0][0]+b[0][1]+b[1][0]-b[1][1]-b[1][2]-b[2][0]+b[2][2]); - m[4 ]= (-a[0][0]+a[1][0]+a[1][1])*(b[0][0]-b[0][1]+b[1][1]); - m[5 ]= (a[1][0]+a[1][1])*(-b[0][0]+b[0][1]); - m[6 ]= a[0][0]*b[0][0]; - m[7 ]= (-a[0][0]+a[2][0]+a[2][1])*(b[0][0]-b[0][2]+b[1][2]); - m[8 ]= (-a[0][0]+a[2][0])*(b[0][2]-b[1][2]); - m[9 ]= (a[2][0]+a[2][1])*(-b[0][0]+b[0][2]); - m[10]= (a[0][0]+a[0][1]+a[0][2]-a[1][1]-a[1][2]-a[2][0]-a[2][1])*b[1][2]; - m[11]= a[2][1]*(-b[0][0]+b[0][2]+b[1][0]-b[1][1]-b[1][2]-b[2][0]+b[2][1]); - m[12]= (-a[0][2]+a[2][1]+a[2][2])*(b[1][1]+b[2][0]-b[2][1]); - m[13]= (a[0][2]-a[2][2])*(b[1][1]-b[2][1]); - m[14]= a[0][2]*b[2][0]; - m[15]= (a[2][1]+a[2][2])*(-b[2][0]+b[2][1]); - m[16]= (-a[0][2]+a[1][1]+a[1][2])*(b[1][2]+b[2][0]-b[2][2]); - m[17]= (a[0][2]-a[1][2])*(b[1][2]-b[2][2]); - m[18]= (a[1][1]+a[1][2])*(-b[2][0]+b[2][2]); - m[19]= a[0][1]*b[1][0]; - m[20]= a[1][2]*b[2][1]; - m[21]= a[1][0]*b[0][2]; - m[22]= a[2][0]*b[0][1]; - m[23]= a[2][2]*b[2][2]; - - c[0][0] = m[6]+m[14]+m[19]; - c[0][1] = m[1]+m[4]+m[5]+m[6]+m[12]+m[14]+m[15]; - c[0][2] = m[6]+m[7]+m[9]+m[10]+m[14]+m[16]+m[18]; - c[1][0] = m[2]+m[3]+m[4]+m[6]+m[14]+m[16]+m[17]; - c[1][1] = m[2]+m[4]+m[5]+m[6]+m[20]; - c[1][2] = m[14]+m[16]+m[17]+m[18]+m[21]; - c[2][0] = m[6]+m[7]+m[8]+m[11]+m[12]+m[13]+m[14]; - c[2][1] = m[12]+m[13]+m[14]+m[15]+m[22]; - c[2][2] = m[6]+m[7]+m[8]+m[9]+m[23]; -} - -SDL_FPoint I2FPoint(SDL_Point p) -{ - SDL_FPoint ret={p.x,p.y}; - return ret; -} -SDL_Point F2IPoint(SDL_FPoint p) -{ - SDL_Point ret={p.x,p.y}; - return ret; -} -SDL_FPoint subtract(SDL_FPoint p0, SDL_FPoint p1) -{ - SDL_FPoint ret={p0.x-p1.x,p0.y-p1.y}; - - return ret; -} - -SDL_FPoint sum(SDL_FPoint p0, SDL_FPoint p1) -{ - SDL_FPoint ret={p0.x+p1.x,p0.y+p1.y}; - - return ret; -} - -SDL_FPoint multiply(SDL_FPoint p, float f) -{ - SDL_FPoint ret={p.x*f,p.y*f}; - - return ret; -} - -float dot(SDL_FPoint p0, SDL_FPoint p1) -{ - float ret=(p0.x*p1.x)+(p0.y*p1.y); - - return ret; -} - -float distance(SDL_FPoint p0,SDL_FPoint p1) -{ - return hypotf(p0.x-p1.x,p0.y-p1.y); -} - -SDL_FPoint normalize(SDL_FPoint p) -{ - float mag=hypotf(p.x,p.y); - SDL_FPoint ret={p.x/mag,p.y/mag}; - - //problema di normalizzazione? vettore spesso 0 - return ret; -} - -float magnitude(SDL_FPoint p) -{ - float len=hypotf(p.x,p.y); +#include "proteo_utility.c" - return len; -} - -SDL_FPoint getClosestPointOnSegment(SDL_FPoint p0, SDL_FPoint p1, SDL_FPoint p) -{ - SDL_FPoint d=subtract(p1,p0); - float c=dot(subtract(p,p0),d)/dot(d,d); - if(c>=1) return p1; - else if(c<=0) return p0; - else return sum(p0,multiply(d,c)); -} +//============================================================================== +// TODO +//============================================================================== -/*static getClosestPointOnSegment(p0, p1, p) { - let d = p1.subtract(p0); - let c = p.subtract(p0).dot(d) / (d.dot(d)); - if (c >= 1) { - return p1.clone(); - } else if (c <= 0) { - return p0.clone(); - } else { - return p0.add(d.multiply(c)); - } -} -*/ //============================================================================== // PROTEO //============================================================================== @@ -755,7 +246,10 @@ LUALIB_API int luaopen_proteo (lua_State *state) { lua_newtable(state); lua_setfield(state, -2, "ffmpeg"); - + + lua_newtable(state); + lua_setfield(state, -2, "tflite"); + lua_setglobal(state,"proteo"); lua_pushinteger(state, PROTEO_MAJOR_VERSION); @@ -790,7 +284,11 @@ LUALIB_API int luaopen_proteo (lua_State *state) { #ifdef PROTEO_FFMPEG add_ffmpeg_proteo(state); #endif - + + #ifdef TFLITE + add_tensorflow_proteo(state); + #endif + return 1; } @@ -895,7 +393,7 @@ int initLUA() addFunction_proteo(L,"opencv","getAffineTransform",opencv_getaffinetransform); addFunction_proteo(L,"opencv","warpAffine",opencv_warpaffine); addFunction_proteo(L,"opencv","toTable",opencv_totable); - + addTable_proteo(L,"opencv","matType"); addEnum_proteo(L,"opencv","matType","CV_8U",0); addEnum_proteo(L,"opencv","matType","CV_8S",1); @@ -1048,7 +546,7 @@ void eventloop(ProteoComponent* list) else if(e.type == SDL_FINGERDOWN) { if(verbose) printf("SDL_FINGERDOWN\n"); - + lua_getglobal(L,"touch"); if(lua_isfunction(L, -1) ) { @@ -1065,7 +563,7 @@ void eventloop(ProteoComponent* list) else if(e.type == SDL_FINGERUP) { if(verbose) printf("SDL_FINGERUP\n"); - + lua_getglobal(L,"release"); if(lua_isfunction(L, -1) ) { @@ -1121,66 +619,143 @@ void eventloop(ProteoComponent* list) } } } -void mainloop() -{ - if(components==NULL) sleep(1); - - //SDL_RenderClear(gRenderer); - //SDL_RenderCopy(renderer, texture, NULL, NULL); - //SDL_RenderCopy(renderer, spriteTexture, &rcSprite, &gdSprite); - SDL_SetRenderDrawColor( gRenderer, backgroundColor.r, backgroundColor.g, backgroundColor.b, 255 ); - //frameStart = SDL_GetTicks(); - // Clear window - SDL_RenderClear( gRenderer ); +void drawloop(ProteoComponent* current) +{ + //SDL_RenderClear(gRenderer); + //SDL_RenderCopy(renderer, texture, NULL, NULL); + //SDL_RenderCopy(renderer, spriteTexture, &rcSprite, &gdSprite); + SDL_SetRenderDrawColor( gRenderer, backgroundColor.r, backgroundColor.g, backgroundColor.b, 255 ); + + //frameStart = SDL_GetTicks(); + // Clear window +#ifdef BACKBUFFER + SDL_SetRenderTarget(gRenderer, gTarget); //BACKBUFFER +#endif + SDL_RenderClear( gRenderer ); - ProteoComponent* current=components; + //ProteoComponent* current=components; #ifdef DLINK_LIST_COMPONENT - ProteoComponent* last=NULL; + ProteoComponent* last=NULL; #endif - while(current!=NULL) - { - if(current->type!=Deleted && current->hidden==FALSE && current->parent==NULL) - { - SDL_SetRenderDrawBlendMode(gRenderer,SDL_BLENDMODE_BLEND); - - if(current->type==Rect) graphics_drawRect (gRenderer,current); - else if(current->type==Ellips) graphics_drawEllipse (gRenderer,current); - else if(current->type==Label) gui_drawLabel (gRenderer,current); - else if(current->type==Image) graphics_drawImage (gRenderer,current); - else if(current->type==Icon) graphics_drawIcon (gRenderer,current); - else if(current->type==Button) gui_drawButton (gRenderer,current); - else if(current->type==TextField) gui_drawTextField (gRenderer,current); - else if(current->type==List) gui_drawList (gRenderer,current); - else if(current->type==Checkbox) gui_drawCheckbox (gRenderer,current); - else if(current->type==Sprite) graphics_drawSprite (gRenderer,current); - else if(current->type==Polyg) graphics_drawPolygon (gRenderer,current); - //else if(current->type==Bezier) graphics_drawBezier (gRenderer,current); - else if(current->type==Shape) graphics_drawShape (gRenderer,current); - else if(current->type==DropDown) gui_drawDropDown (gRenderer,current); - else if(current->type==Form) gui_drawForm (gRenderer,current); - else if(current->type==Container) gui_drawContainer (gRenderer,current); - } + while(current!=NULL) + { + if(current->type!=Deleted && current->hidden==FALSE && current->parent==NULL) + { + SDL_SetRenderDrawBlendMode(gRenderer,SDL_BLENDMODE_BLEND); + + if(current->type==Rect) graphics_drawRect (gRenderer,current); + else if(current->type==Ellips) graphics_drawEllipse (gRenderer,current); + else if(current->type==Label) gui_drawLabel (gRenderer,current); + else if(current->type==Image) graphics_drawImage (gRenderer,current); + else if(current->type==Icon) graphics_drawIcon (gRenderer,current); + else if(current->type==Button) gui_drawButton (gRenderer,current); + else if(current->type==TextField) gui_drawTextField (gRenderer,current); + else if(current->type==List) gui_drawList (gRenderer,current); + else if(current->type==Checkbox) gui_drawCheckbox (gRenderer,current); + else if(current->type==Sprite) graphics_drawSprite (gRenderer,current); + else if(current->type==Particle) graphics_drawParticle (gRenderer,current); + else if(current->type==Polyg) graphics_drawPolygon (gRenderer,current); + //else if(current->type==Bezier) graphics_drawBezier (gRenderer,current); + else if(current->type==Shape) graphics_drawShape (gRenderer,current); + else if(current->type==DropDown) gui_drawDropDown (gRenderer,current); + else if(current->type==Form) gui_drawForm (gRenderer,current); + else if(current->type==Container) gui_drawContainer (gRenderer,current); + } #ifdef DLINK_LIST_COMPONENT - last=current; + last=current; #endif - if(current->next!=NULL && current->next->type==Deleted) //TODO non cancella il primo elemento - freecomponent(current,current->next); - current=current->next; - } + if(current->next!=NULL && current->next->type==Deleted) //TODO non cancella il primo elemento + freecomponent(current,current->next); + current=current->next; + } - ProteoSkeleton* current_sk=skeletons; - while(current_sk!=NULL) + ProteoSkeleton* current_sk=skeletons; + while(current_sk!=NULL) + { + if(current_sk->hidden==FALSE) { - if(current_sk->hidden==FALSE) - { - graphics_drawSkeleton (gRenderer,current_sk); - } + graphics_drawSkeleton (gRenderer,current_sk); + } + + current_sk=current_sk->next; + } +#ifdef BACKBUFFER +SDL_SetRenderTarget(gRenderer, NULL); //BACKBUFFER +SDL_RenderCopy(gRenderer, gTarget, NULL, NULL ); //BACKBUFFER +#endif + +SDL_RenderPresent(gRenderer); + +} + +#ifdef USEOPENGL +void drawloop_GL(ProteoComponent* current) +{ + glViewport(0, 0, gWidth, gHeight); + glClearColor(backgroundColor.r/255.0f, backgroundColor.g/255.0f, backgroundColor.b/255.0f, backgroundColor.a/255.0f); + glClear(GL_COLOR_BUFFER_BIT); - current_sk=current_sk->next; + //glColor3f(0.8f, 0.2f, 0.4f); + //glRectf(-0.5f, -0.5f, 0.5f, 0.5f); + + //glEnable(GL_TEXTURE_2D); + //glActiveTexture(GL_TEXTURE0); + + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + +#ifdef DLINK_LIST_COMPONENT + ProteoComponent* last=NULL; +#endif + while(current!=NULL) + { + if(current->type!=Deleted && current->hidden==FALSE && current->parent==NULL) + { + //SDL_SetRenderDrawBlendMode(gRenderer,SDL_BLENDMODE_BLEND); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + if(current->type==Rect) graphics_drawRect_GL (current); + //else if(current->type==Ellips) graphics_drawEllipse (gRenderer,current); + else if(current->type==Label) gui_drawLabel_GL (current); + //else if(current->type==Image) graphics_drawImage (gRenderer,current); + //else if(current->type==Icon) graphics_drawIcon (gRenderer,current); + else if(current->type==Button) gui_drawButton_GL (current); + else if(current->type==TextField) gui_drawTextField_GL (current); + //else if(current->type==List) gui_drawList (gRenderer,current); + //else if(current->type==Checkbox) gui_drawCheckbox (gRenderer,current); + //else if(current->type==Sprite) graphics_drawSprite (gRenderer,current); + //else if(current->type==Sprite) graphics_drawParticle (gRenderer,current); + //else if(current->type==Polyg) graphics_drawPolygon (gRenderer,current); + //else if(current->type==Bezier) graphics_drawBezier (gRenderer,current); + //else if(current->type==Shape) graphics_drawShape (gRenderer,current); + //else if(current->type==DropDown) gui_drawDropDown (gRenderer,current); + else if(current->type==Form) gui_drawForm_GL (current); + //else if(current->type==Container) gui_drawContainer (gRenderer,current); } +#ifdef DLINK_LIST_COMPONENT + last=current; +#endif + if(current->next!=NULL && current->next->type==Deleted) //TODO non cancella il primo elemento + freecomponent(current,current->next); + current=current->next; + } + + SDL_GL_SwapWindow(gWindow); +} +#endif + +void mainloop() +{ + if(components==NULL) sleep(1); + +#ifdef USEOPENGL + drawloop_GL(components); +#else + drawloop(components); +#endif - SDL_RenderPresent(gRenderer); #ifdef DLINK_LIST_COMPONENT eventloop(last); #else @@ -1203,11 +778,11 @@ void mainloop() struct timespec now; clock_gettime(CLOCK_REALTIME, &now); - double n=now.tv_sec*1000; + double n=now.tv_sec*1000.0; n=n+(now.tv_nsec/1000000.0); ProteoTimer* timer=timers; - double min=25; + double min=25; while(timer!=NULL) { if(timer->state==1) @@ -1259,7 +834,19 @@ int main(int argc,char **argv) pthread_t t_update; +#ifdef __EMSCRIPTEN__ + printf("Proteo Emscripten\n"); +#else printf("Proteo %s\n",argv[0]); +#endif + +#if TARGET_OS_MAC + wordexp_t exp_result; + wordexp("~/Library/Preferences/Proteo", &exp_result, 0); + mkpath(exp_result.we_wordv[0]); + wordfree(&exp_result); + +#endif //char luafile[50]; char username[50]; @@ -1269,9 +856,13 @@ int main(int argc,char **argv) int opt_password=FALSE; int opt_script=FALSE; int opt_gmode=FALSE; - + +#ifdef __EMSCRIPTEN__ + strcpy(app_path,""); +#else strcpy(app_path,dirname(argv[0])); - +#endif + //int opt_appkey=FALSE; config=load_config(); if(config.load_error!=0) @@ -1333,16 +924,22 @@ int main(int argc,char **argv) char basedir_tmp[PATH_MAX]; strcpy(basedir_tmp,config.basedir); strcpy(config.basedir,app_path); - strcat(config.basedir,"/"); + strcat(config.basedir,"/../Resources/"); + //strcpy(config.basedir,"~/Library/Preferences/Proteo/"); strcat(config.basedir,basedir_tmp); - + + debug=TRUE; verbose=FALSE; +#elif TARGET_OS_WINDOWS + opt_gmode=TRUE; + opt_remoteconsole=TRUE; + #elif __EMSCRIPTEN__ opt_gmode=TRUE; - debug=FALSE;//TRUE; - verbose=FALSE;//TRUE; + debug=TRUE; + verbose=FALSE; EM_ASM( // Make a directory other than '/' @@ -1369,7 +966,7 @@ int main(int argc,char **argv) case 'f': //Fullscreen opt_fullscreen=TRUE; break; - case 'e': //REmote Console + case 'e': //Remote Console opt_remoteconsole=TRUE; break; case 'c': //loadconfig @@ -1447,6 +1044,13 @@ int main(int argc,char **argv) } #endif // TARGET_OS_IPHONE +#ifdef LUAJIT_VERSION + printf("%s\n",LUAJIT_VERSION); +#else + printf("%s\n",LUA_VERSION); +#endif + + //if(opt_appkey==FALSE) return 0; #ifdef PROTEO_ENET enet_initialize(); @@ -1456,6 +1060,25 @@ int main(int argc,char **argv) printf( "Failed to initialize LUA!\n" ); return 1; } +#ifdef LUA_JIT + lua_pushinteger(L, 1); +#else + lua_pushinteger(L, 0); +#endif + lua_setglobal(L, "LUAJIT"); + +#ifdef __EMSCRIPTEN__ + lua_pushinteger(L, 1); +#else + lua_pushinteger(L, 0); +#endif + lua_setglobal(L, "EMSCRIPTEN"); + + lua_pushboolean(L, debug); + lua_setglobal(L, "DEBUG"); + + lua_pushboolean(L, verbose); + lua_setglobal(L, "VERBOSE"); if( initSDL() == FALSE) { @@ -1463,6 +1086,15 @@ int main(int argc,char **argv) return 2; } +#ifdef USEOPENGL + printf("GLEW Version: %s \n",glewGetString(GLEW_VERSION)); + printf("Shader Version: %s \n",glGetString(GL_SHADING_LANGUAGE_VERSION)); + lua_pushinteger(L, 1); +#else + lua_pushinteger(L, 0); +#endif + lua_setglobal(L,"SHADER"); + setLuaScreenSize(L); if(opt_gmode) @@ -1549,7 +1181,7 @@ int main(int argc,char **argv) #ifdef PROTEO2 proteo_login(username,password,script); #else - proteo_login(username,password,"8756c123-4567-9876-9876-1234567"); + proteo_login(username,password,"8756c442-3e57-4754-b3b2-0a82b7b5d295"); #endif } } @@ -1569,6 +1201,7 @@ int main(int argc,char **argv) #ifdef __EMSCRIPTEN__ emscripten_set_main_loop(mainloop, -1, 1); + //emscripten_set_main_loop_timing(EM_TIMING_RAF,2); //emscripten_request_animation_frame_loop(mainloop,0); #else while(run) @@ -1585,7 +1218,7 @@ int main(int argc,char **argv) //pthread_cancel(t_update); //free(Appkey); - + //CLOSE lua_getglobal(L,"close"); if(lua_isfunction(L, -1) ) diff --git a/client/proteo.h b/client/proteo.h index 23c5fac..a0eb49b 100644 --- a/client/proteo.h +++ b/client/proteo.h @@ -19,16 +19,25 @@ #include #include +#if !defined(_WIN32) +#include +#endif // defined + //#include "lua.h" //#include "lauxlib.h" //#include "lualib.h" #ifdef __EMSCRIPTEN__ #include -#elif defined(_WIN32) -#include +//#elif defined(_WIN32) +//#include +//#include #else +#ifdef LUA_JIT #include +#else +#include +#endif #endif //emscripten #include @@ -39,6 +48,17 @@ #include #include +#ifdef GLEW +#include +#include +//#include +//#include +#endif + +#ifdef TFLITE +#include +#endif + #ifdef __EMSCRIPTEN__ #include #include @@ -219,17 +239,18 @@ typedef struct font_name { char * name; char* path; + int base; } font_name; font_name nfonts[]= { - {"ColaborateThin","ColabThi.otf"}, - {"ColaborateBold","ColabBol.otf"}, - {"ColaborateLight","ColabLig.otf"}, - {"ColaborateMedium","ColabMed.otf"}, - {"Colaborate","ColabReg.otf"}, - {"Helvetica","Helvetica 400.ttf"}, - {"OpenSans","OpenSans-Regular.ttf"} + {"ColaborateThin","ColabThi.otf",FALSE}, + {"ColaborateBold","ColabBol.otf",FALSE}, + {"ColaborateLight","ColabLig.otf",FALSE}, + {"ColaborateMedium","ColabMed.otf",FALSE}, + {"Colaborate","ColabReg.otf",TRUE}, + {"Helvetica","Helvetica 400.ttf",FALSE}, + {"OpenSans","OpenSans-Regular.ttf",TRUE} }; @@ -364,7 +385,7 @@ const char server_form[] = "local label_server=nil \n" "label_server=proteo.gui.newLabel('label_server','Server:','OpenSans',18,'black','Clear',proteo.gui.LabelAlignment.Left,MIN_X +80,MIN_Y + 250,150,30) \n" "text_server=proteo.gui.newTextField('text_server',config.server,'OpenSans',18,'Black','White',MIN_X +200,MIN_Y + 250,380,30,'') \n" - + "label_appkey=proteo.gui.newLabel('label_appkey','AppKey:','OpenSans',18,'black','Clear',proteo.gui.LabelAlignment.Left,MIN_X +80,MIN_Y + 300,150,30) \n" "text_appkey=proteo.gui.newTextField('text_appkey',config.appkey,'OpenSans',18,'Black','White',MIN_X +200,MIN_Y + 300,380,30,'') \n" @@ -462,7 +483,8 @@ enum ProteoType Icon, Container, Shape, - Polyg + Polyg, + Particle //Bezier }; @@ -583,6 +605,36 @@ typedef struct SDL_RendererFlip flip; } _sprite; +typedef struct +{ + Uint32 frameTick; + + SDL_FPoint position; + + float speed; + float direction; + float rotation; + int life; + int lifeTime; + + float linearAccelerationX; + float linearAccelerationY; + float angularSpeed; + + float spin; + float size; + float startSize; + float endSize; + + ProteoColor startColor; + ProteoColor endColor; + SDL_BlendMode blendMode; + + SDL_Rect frameRect; + ProteoTexture* image; + +} _particle; + typedef struct { ProteoTexture* image; @@ -596,6 +648,9 @@ typedef struct typedef struct { ProteoColor color; + unsigned int shaderId; + int ref_render; + } _rect; /*typedef struct @@ -688,6 +743,7 @@ typedef union _container container; _sprite sprite; + _particle particle; _image image; _ellipse ellipse; _rect rect; @@ -764,6 +820,8 @@ void addFunction_proteo(lua_State *state,const char *lib,const char *fname,lua_C void addTable_proteo(lua_State *state,const char *lib,const char *tname); void addEnum_proteo(lua_State *state,const char *lib,const char *enumname,const char *vname,int value); +ProteoColor ColorInterpolation(ProteoColor startColor, float t, ProteoColor endColor); +float interpolation(float start, float t, float end); ProteoColor brightness(ProteoColor color,float bright); ProteoColor saturation(ProteoColor color,float sat); void mkpath(const char* dir); @@ -939,6 +997,7 @@ static int graphics_drawEllipse (SDL_Renderer* renderer,ProteoComponent* ellipse static int graphics_newEllipse (lua_State *state); //static int graphics_eventRect (lua_State *state,ProteoComponent* rect,SDL_Event e,SDL_Renderer* renderer); static int graphics_drawRect (SDL_Renderer* renderer,ProteoComponent* rect); +static int graphics_drawRect_GL(ProteoComponent* rect); static int graphics_newRect (lua_State *state); //static int graphics_eventImage (lua_State *state,ProteoComponent* image,SDL_Event e,SDL_Renderer* renderer); static int graphics_drawImage (SDL_Renderer* renderer,ProteoComponent* image); @@ -1006,7 +1065,7 @@ typedef struct OCVImage int height; int type; - + unsigned char *data; unsigned long step; diff --git a/client/proteo_config.c b/client/proteo_config.c index e71bb9d..0fe95f7 100644 --- a/client/proteo_config.c +++ b/client/proteo_config.c @@ -22,19 +22,35 @@ typedef struct conf } conf; conf config; +conf load_config(); //TODO conf default_config() { conf _config; +#if TARGET_OS_MAC + char path_config[PATH_MAX]; + strcpy(path_config,app_path); + strcat(path_config,"/../Resources/config.json"); + + char* json_config=loadfile(path_config); + wordexp_t exp_result; + wordexp("~/Library/Preferences/Proteo/config.json", &exp_result, 0); + writefile(exp_result.we_wordv[0],json_config); + wordfree(&exp_result); + + return load_config(); +#else + strcpy(_config.basedir,"./"); strcpy(_config.version,PROTEO_VERSION); strcpy(_config.username,""); strcpy(_config.password,""); strcpy(_config.server,"http://localhost:8888"); - strcpy(_config.appkey,"1234567890123456789012"); + strcpy(_config.appkey,"12345678901234567890123456789012"); _config.ssl=0; +#endif #ifdef __EMSCRIPTEN__ //Nella versione web si può scrivere un config parziale dentro il js (default_config), se non presente chiede il server @@ -53,6 +69,22 @@ conf default_config() ); strcpy(_config.server,server); free(server); + + char* appkey=(char*)EM_ASM_INT( + + var jsString = default_config.appkey; + + if(jsString==null) + jsString=window.prompt('App Key: '); + + + var lengthBytes = lengthBytesUTF8(jsString)+1; + var stringOnWasmHeap = _malloc(lengthBytes); + stringToUTF8(jsString, stringOnWasmHeap, lengthBytes); + return stringOnWasmHeap; + ); + strcpy(_config.appkey,appkey); + free(appkey); #endif if(verbose) printf("Default config server: %s\n",_config.server); @@ -82,6 +114,15 @@ conf load_config() char *home = getenv("HOME"); strcpy(path_config,home); strcat(path_config,"/Documents/config.json"); +#elif TARGET_OS_MAC + char path_config[PATH_MAX]; + //strcpy(path_config,app_path); + //strcat(path_config,"/../Resources/config.json"); + + wordexp_t exp_result; + wordexp("~/Library/Preferences/Proteo/config.json", &exp_result, 0); + strcpy(path_config,exp_result.we_wordv[0]); + wordfree(&exp_result); #else char path_config[PATH_MAX]; strcpy(path_config,app_path); @@ -92,13 +133,15 @@ conf load_config() char* json_config=loadfile(path_config); #endif - if(verbose) printf("CONFIG: %s\n",json_config); if(json_config==NULL) { + if(verbose) printf("CONFIG NOT FOUND\n"); _config.load_error=2; return _config; } + + if(verbose) printf("CONFIG: %s\n",json_config); #ifdef PROTEO_USE_TINYJSON json_t mem[32]; @@ -106,6 +149,7 @@ conf load_config() if(json==NULL) { + if(verbose) printf("CONFIG JSON ERROR\n"); _config.load_error=1; return _config; } @@ -139,12 +183,12 @@ conf load_config() if ( !server || JSON_TEXT != json_getType( server ) ) printf("Error, the server property is not found."); else strcpy(_config.server,json_getValue(server)); - + json_t const* appkey = json_getProperty( json, "appkey" ); if ( !appkey || JSON_TEXT != json_getType( appkey ) ) printf("Error, the appkey property is not found."); else strcpy(_config.appkey,json_getValue(appkey)); - + json_t const* ssl = json_getProperty( json, "ssl" ); if ( !ssl || JSON_INTEGER != json_getType( ssl ) ) printf("Error, the ssl property is not found."); else @@ -154,6 +198,7 @@ conf load_config() json_object * jobj = json_tokener_parse(json_config); if(jobj==NULL) { + if(verbose) printf("CONFIG JSON ERROR\n"); _config.load_error=1; return _config; } @@ -187,7 +232,7 @@ conf load_config() json_object_object_get_ex(jobj, "appkey", &obj); const char* appkey=json_object_get_string(obj); if(appkey!=NULL) strcpy(_config.appkey,appkey); - + json_object_object_get_ex(jobj, "ssl", &obj); _config.ssl=json_object_get_int(obj); @@ -206,6 +251,11 @@ int save_config(conf _config) { #ifdef __EMSCRIPTEN__ char json_config[512]; + + int dest_len=c_quote(_config.appkey,NULL,-1); + char* q_appkey=malloc(dest_len+1); + c_quote(_config.appkey,q_appkey,-1); + sprintf(json_config,"{" "\"version\":\"%s\"," "\"basedir\":\"%s\"," @@ -216,9 +266,12 @@ int save_config(conf _config) "\"appkey\":\"%s\"," "\"ssl\":%d" "}",_config.version,_config.basedir,_config.server, - _config.username,_config.password,_config.script,_config.appkey,_config.ssl); + _config.username,_config.password,_config.script,q_appkey,_config.ssl); + + free(q_appkey); + EM_ASM({ - var d = new Date(2021, 01, 01); + var d = new Date(2077, 04, 24); var p = '/'; document.cookie = escape(UTF8ToString($0)) + ';path=' + p @@ -226,10 +279,18 @@ int save_config(conf _config) },json_config); #else #if TARGET_OS_IPHONE - char path_config[256]; + char path_config[PATH_MAX]; char *home = getenv("HOME"); strcpy(path_config,home); strcat(path_config,"/Documents/config.json"); + #elif TARGET_OS_MAC + char path_config[PATH_MAX]; + //strcpy(path_config,app_path); + //strcat(path_config,"/../Resources/config.json"); + wordexp_t exp_result; + wordexp("~/Library/Preferences/Proteo/config.json", &exp_result, 0); + strcpy(path_config,exp_result.we_wordv[0]); + wordfree(&exp_result); #else char path_config[PATH_MAX]; strcpy(path_config,app_path); @@ -241,13 +302,13 @@ int save_config(conf _config) { return 1; } - + int dest_len=c_quote(_config.appkey,NULL,-1); - char* q_appkey=malloc(dest_len); + char* q_appkey=malloc(dest_len+1); c_quote(_config.appkey,q_appkey,-1); //printf("q appkey: %s",q_appkey); - - + + fprintf(f, "{" "\"version\":\"%s\"," "\"basedir\":\"%s\"," @@ -261,7 +322,7 @@ int save_config(conf _config) _config.username,_config.password,_config.script,q_appkey,_config.ssl); fclose(f); free(q_appkey); - + #endif return 0; } @@ -307,7 +368,7 @@ static int system_loadConfig (lua_State *state) { lua_pushstring(state, _config.appkey); lua_setfield(state, -2, "appkey"); - + lua_pushinteger(state, _config.ssl); lua_setfield(state, -2, "ssl"); @@ -329,20 +390,24 @@ static int system_saveConfig (lua_State *state) { { double value = lua_tonumber(state, -2); if(strcmp(key,"ssl")==0) _config.ssl=(int)value; + + //TODO nel caso in cui password o appkey sono formate solo da numeri senza punto funziona, + if(strcmp(key,"appkey")==0) snprintf(_config.appkey,32,"%d",(int)value); + if(strcmp(key,"password")==0) snprintf(_config.password,50,"%d",(int)value); //printf("%s => %f\n", key, value); } else { const char *value = lua_tostring(state, -2); - if(strcmp(key,"server")==0) strcpy(_config.server,value); - else if(strcmp(key,"basedir")==0) strcpy(_config.basedir,value); - else if(strcmp(key,"version")==0) strcpy(_config.version,value); - else if(strcmp(key,"username")==0) strcpy(_config.username,value); - else if(strcmp(key,"password")==0) strcpy(_config.password,value); - else if(strcmp(key,"script")==0) strcpy(_config.script,value); - else if(strcmp(key,"appkey")==0) strcpy(_config.appkey,value); - + if(strcmp(key,"server")==0) strlcpy(_config.server,value,sizeof(_config.server)); + else if(strcmp(key,"basedir")==0) strlcpy(_config.basedir,value,sizeof(_config.basedir)); + else if(strcmp(key,"version")==0) strlcpy(_config.version,value,sizeof(_config.version)); + else if(strcmp(key,"username")==0) strlcpy(_config.username,value,sizeof(_config.username)); + else if(strcmp(key,"password")==0) strlcpy(_config.password,value,sizeof(_config.password)); + else if(strcmp(key,"script")==0) strlcpy(_config.script,value,sizeof(_config.script)); + else if(strcmp(key,"appkey")==0) strlcpy(_config.appkey,value,sizeof(_config.appkey)); + //printf("%s => %s\n", key, value); } diff --git a/client/proteo_enet.c b/client/proteo_enet.c index 6ec2b00..d99d4f9 100644 --- a/client/proteo_enet.c +++ b/client/proteo_enet.c @@ -241,7 +241,7 @@ static int host_create(lua_State *l) { if (!lua_isnil(l, 2)) peer_count = luaL_checkint(l, 2); } - if(debug) printf("host create, peers=%d, channels=%d, in=%d, out=%d\n", + if(debug) printf("host create, peers=%zu, channels=%zu, in=%d, out=%d\n", peer_count, channel_count, in_bandwidth, out_bandwidth); host = enet_host_create(have_address ? &address : NULL, peer_count, channel_count, in_bandwidth, out_bandwidth); diff --git a/client/proteo_gl.c b/client/proteo_gl.c new file mode 100644 index 0000000..99179b4 --- /dev/null +++ b/client/proteo_gl.c @@ -0,0 +1,202 @@ + +unsigned int power_two_floor(unsigned int val) { + unsigned int power = 2, nextVal = power*2; + + while((nextVal *= 2) <= val) + power*=2; + + return power*2; + } + +void GL_SetRenderDrawColor(Uint8 r,Uint8 g,Uint8 b,Uint8 a) +{ + glColor4ub(r,g,b,a ); +} + +void GL_RenderFillRect(const SDL_Rect * rect) +{ + float x1=(2.0f*(float)rect->x/(float)gWidth)-1.0f; + float y1=(-2.0f*(float)rect->y/(float)gHeight)+1.0f; + float x2=(2.0f*(float)(rect->x+rect->w)/(float)gWidth)-1.0f; + float y2=(-2.0f*(float)(rect->y+rect->h)/(float)gHeight)+1.0f; + + glRectf(x1,y1,x2,y2); +} + +void GL_RenderCopy(SDL_Surface * surface,const SDL_Rect * srcrect,const SDL_Rect * texture_rect) +{ + float x1=(2.0f*(float)texture_rect->x/(float)gWidth)-1.0f; + float y1=(-2.0f*(float)texture_rect->y/(float)gHeight)+1.0f; + float x2=(2.0f*(float)(texture_rect->x+texture_rect->w)/(float)gWidth)-1.0f; + float y2=(-2.0f*(float)(texture_rect->y+texture_rect->h)/(float)gHeight)+1.0f; + + //Draw the SDL_Texture * as a Quad + glEnable(GL_TEXTURE_2D); + //glActiveTexture(GL_TEXTURE0); + //glEnable(GL_BLEND); + //glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + //Bind the SDL_Texture in OpenGL + /*if(SDL_GL_BindTexture(form->texture, NULL, NULL)!=0) + { + printf("Error binding texture form %s\n",SDL_GetError()); + }*/ + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture_rect->w, texture_rect->h, 0, GL_BGRA, GL_UNSIGNED_BYTE, surface->pixels); + + glBegin(GL_QUADS); { + glColor4f(1.0f, 1.0f, 1.0f, 1.0f); + glTexCoord2f(0, 0); glVertex3f(x1, y1, 0); + glTexCoord2f(1, 0); glVertex3f(x2, y1, 0); + glTexCoord2f(1, 1); glVertex3f(x2, y2, 0); + glTexCoord2f(0, 1); glVertex3f(x1, y2, 0); + } glEnd(); + glDisable(GL_TEXTURE_2D); + + glDisable(GL_TEXTURE_2D); +} + + +#define ROUNDING_POINT_COUNT 8 // Larger values makes circle smoother. +void GL_RenderFillRoundRect(const SDL_Rect * rect, float r) +{ + float x=(2.0f*(float)rect->x/(float)gWidth)-1.0f; + float y=(-2.0f*(float)rect->y/(float)gHeight)+1.0f; + //float x2=(2.0f*(float)(rect->x+rect->w)/(float)gWidth)-1.0f; + //float y2=(-2.0f*(float)(rect->y+rect->h)/(float)gHeight)+1.0f; + float w=2.0f*(float)(rect->w)/(float)gWidth; + float h=2.0f*(float)(rect->h)/(float)gHeight; + + SDL_FPoint top_left[ROUNDING_POINT_COUNT]; + SDL_FPoint bottom_left[ROUNDING_POINT_COUNT]; + SDL_FPoint top_right[ROUNDING_POINT_COUNT]; + SDL_FPoint bottom_right[ROUNDING_POINT_COUNT]; + + + float radius = fmin(w, h); + radius *= r; + + + int i = 0; + float x_offset, y_offset; + float step = ( 2.0f * M_PI ) / (ROUNDING_POINT_COUNT * 4), + angle = 0.0f; + + unsigned int index = 0, segment_count = ROUNDING_POINT_COUNT; + SDL_FPoint bottom_left_corner = { x + radius, y - h + radius }; + + + while( i != segment_count ) + { + x_offset = cosf( angle ); + y_offset = sinf( angle ); + + + top_left[ index ].x = bottom_left_corner.x - + ( x_offset * radius ); + top_left[ index ].y = ( h - ( radius * 2.0f ) ) + + bottom_left_corner.y - + ( y_offset * radius ); + + + top_right[ index ].x = ( w - ( radius * 2.0f ) ) + + bottom_left_corner.x + + ( x_offset * radius ); + top_right[ index ].y = ( h - ( radius * 2.0f ) ) + + bottom_left_corner.y - + ( y_offset * radius ); + + + bottom_right[ index ].x = ( w - ( radius * 2.0f ) ) + + bottom_left_corner.x + + ( x_offset * radius ); + bottom_right[ index ].y = bottom_left_corner.y + + ( y_offset * radius ); + + + bottom_left[ index ].x = bottom_left_corner.x - + ( x_offset * radius ); + bottom_left[ index ].y = bottom_left_corner.y + + ( y_offset * radius ); + + + /*top_left[ index ].x = top_left[ index ].x; + top_left[ index ].y = top_left[ index ].y; + + + top_right[ index ].x = top_right[ index ].x; + top_right[ index ].y = top_right[ index ].y; + + + bottom_right[ index ].x = bottom_right[ index ].x ; + bottom_right[ index ].y = bottom_right[ index ].y; + + + bottom_left[ index ].x = bottom_left[ index ].x ; + bottom_left[ index ].y = bottom_left[ index ].y ;*/ + + angle -= step; + + ++index; + + ++i; + } + + glBegin( GL_TRIANGLE_STRIP ); + { + // Top + for( i = segment_count - 1 ; i >= 0 ; i--) + { + glVertex2f( top_left[ i ].x, top_left[ i ].y ); + glVertex2f( top_right[ i ].x, top_right[ i ].y ); + } + + // In order to stop and restart the strip. + glVertex2f( top_right[ 0 ].x, top_right[ 0 ].y ); + glVertex2f( top_right[ 0 ].x, top_right[ 0 ].y ); + + // Center + glVertex2f( top_right[ 0 ].x, top_right[ 0 ].y ); + glVertex2f( top_left[ 0 ].x, top_left[ 0 ].y ); + glVertex2f( bottom_right[ 0 ].x, bottom_right[ 0 ].y ); + glVertex2f( bottom_left[ 0 ].x, bottom_left[ 0 ].y ); + + // Bottom + for( i = 0; i != segment_count ; i++ ) + { + glVertex2f( bottom_right[ i ].x, bottom_right[ i ].y ); + glVertex2f( bottom_left[ i ].x, bottom_left[ i ].y ); + } + } + glEnd(); +} //DrawRoundRect + +void GL_RenderSetClipRect(const SDL_Rect * rect) +{ + if(rect==NULL) + { + glDisable(GL_SCISSOR_TEST); + return; + } + + float x=(2.0f*(float)rect->x/(float)gWidth)-1.0f; + float y=(-2.0f*(float)rect->y/(float)gHeight)+1.0f; + //float x2=(2.0f*(float)(rect->x+rect->w)/(float)gWidth)-1.0f; + //float y2=(-2.0f*(float)(rect->y+rect->h)/(float)gHeight)+1.0f; + float w=2.0f*(float)(rect->w)/(float)gWidth; + float h=2.0f*(float)(rect->h)/(float)gHeight; + + glEnable(GL_SCISSOR_TEST); + //glScissor(x, y, w, h); + glScissor(rect->x,rect->y,rect->w,rect->h); +} + +void GL_ResizeSurface(ProteoComponent* pc) +{ + pc->texture_rect.w = power_two_floor(pc->texture_rect.w)*2; + pc->texture_rect.h = power_two_floor(pc->texture_rect.h)*2; + + SDL_Surface *tmp_surface = SDL_CreateRGBSurface(0, pc->texture_rect.w, pc->texture_rect.h, 32, 0x00ff0000,0x0000ff00,0x000000ff,0xff000000); + SDL_BlitSurface(pc->surface, NULL, tmp_surface, NULL); + SDL_FreeSurface(pc->surface); + pc->surface=tmp_surface; +} diff --git a/client/proteo_graphics.c b/client/proteo_graphics.c index 24fb706..d29f990 100644 --- a/client/proteo_graphics.c +++ b/client/proteo_graphics.c @@ -878,7 +878,7 @@ static int graphics_changeImage (lua_State *state) void* mat = luaL_checkudata(L, 2, "OpenCVMat"); OCVImage opencvimg=getImage(mat); - //if(debug) printf("graphics.changeImage OpenCV(%d,%d) Depth:%d Channels:%d Step:%lu\n",opencvimg.width, opencvimg.height,opencvimg.depth,opencvimg.channels,opencvimg.step); + if(verbose) printf("graphics.changeImage OpenCV(%d,%d) Depth:%d Channels:%d Step:%lu\n",opencvimg.width, opencvimg.height,opencvimg.depth,opencvimg.channels,opencvimg.step); if(pc->surface!=NULL) SDL_FreeSurface(pc->surface); pc->surface = SDL_CreateRGBSurfaceFrom((void*)opencvimg.data, @@ -1035,7 +1035,7 @@ static int graphics_addFrameSource(lua_State *state) { const int pos_y=luaL_checkint(state,3); const int width=luaL_checkint(state,4); const int height=luaL_checkint(state,5); - const int tw=luaL_checkint(state,6); + const int tw=luaL_checkint(state,6); //Destination size const int th=luaL_checkint(state,7); sprite->framesSource[sprite->nFrame].x=pos_x; @@ -2443,7 +2443,7 @@ static int graphics_bindSkeleton(lua_State *state) { ProteoBone* nearBone=getBone(ps,current); - int* bones=getBonesGroup(ps,nearBone,current); + int* bones=getBonesGroup(ps,nearBone,current); //array of bones that impact that shape (1=yes, 0=no) for(int k=0;knbones;k++) { if(bones[k]==1) @@ -2617,7 +2617,7 @@ static int graphics_addItem(lua_State *state) ProteoComponent* shape=toProteoComponent(state,1); ProteoComponent* item=toProteoComponent(state,2); - if(debug) printf("graphics.addItem %s -> %s\n",item->id,shape->id); + if(verbose) printf("graphics.addItem %s -> %s\n",item->id,shape->id); item->parent=shape; //child->child_next=NULL; @@ -2717,6 +2717,35 @@ static int graphics_newEllipse (lua_State *state) { //============================================================================== // RECT //============================================================================== +#ifdef USEOPENGL +static int graphics_drawRect_GL (ProteoComponent* rect) +{ + SDL_Rect rect_rect=rect->rect; + + if(rect->parent!=NULL) + { + rect_rect.x+=rect->parent->rect.x; + rect_rect.y+=rect->parent->rect.y; + } + + if(rect->component.rect.shaderId>0) + glUseProgram(rect->component.rect.shaderId); + + if(rect->component.rect.ref_render!=-1) + { + lua_getref(L,rect->component.rect.ref_render); + lua_pushinteger(L,rect->component.rect.shaderId); + int error = lua_pcall(L, 1, 0, 0); + } + + GL_SetRenderDrawColor(rect->color.r, rect->color.g, rect->color.b, rect->color.a); + GL_RenderFillRect(&rect_rect); + + if(rect->component.rect.shaderId>0) glUseProgram(NULL); + + return 1; +} +#endif static int graphics_drawRect (SDL_Renderer* renderer,ProteoComponent* rect) { @@ -2727,16 +2756,16 @@ static int graphics_drawRect (SDL_Renderer* renderer,ProteoComponent* rect) rect_rect.x+=rect->parent->rect.x; rect_rect.y+=rect->parent->rect.y; } - + rectangleRGBA(renderer, rect_rect.x, rect_rect.y, rect_rect.x+rect_rect.w, rect_rect.y+rect_rect.h, rect->color.r, rect->color.g, rect->color.b, rect->color.a); boxRGBA(renderer, rect_rect.x, rect_rect.y, rect_rect.x+rect_rect.w, rect_rect.y+rect_rect.h,rect->component.rect.color.r, rect->component.rect.color.g, rect->component.rect.color.b, rect->component.rect.color.a); - + /*SDL_SetRenderDrawColor( renderer, rect->component.rect.color.r, rect->component.rect.color.g, rect->component.rect.color.b, rect->component.rect.color.a ); SDL_RenderFillRect( renderer, & rect_rect );*/ - + return 1; } @@ -2776,6 +2805,8 @@ static int graphics_newRect (lua_State *state) { pc->rect.w=width; pc->rect.h=height; pc->component.rect.color=hex2color(color); + pc->component.rect.shaderId=0; + pc->component.rect.ref_render=-1; pc->color=hex2color(border_color); //pc->next=NULL; pc->surface=NULL; @@ -2946,6 +2977,234 @@ static int graphics_newSprite (lua_State *state) { return 1; } +//============================================================================== +// PARTICLE +//============================================================================== + +static int graphics_updateParticle (ProteoComponent* particle) +{ + if(particle->hidden==TRUE) return 3; + + Uint32 frameTime = SDL_GetTicks() - particle->component.particle.frameTick; + particle->component.particle.life-=frameTime; + + if(particle->component.particle.life < 0) + { + particle->hidden=TRUE; + bool callback_valid=false; + if(particle->callback!=NULL) + { + callback_valid=true; + lua_getglobal(L,particle->callback); + } + else if(particle->ref_callback!=-1) + { + callback_valid=true; + //lua_getref(L,particle->ref_callback); + lua_rawgeti(L, LUA_REGISTRYINDEX,particle->ref_callback); + } + + if(callback_valid) + { + lua_pushlightuserdata(L, particle); + int error = lua_pcall(L, 1, 0, 0); + + if (error) { + fprintf(stderr, "ERROR particle callback: %s\n", lua_tostring(L, -1)); + lua_pop(L, 1); + } + } + return 2; + } + + particle->component.particle.frameTick = SDL_GetTicks(); + + //printf("Particle %s life %d\n",particle->id,particle->component.particle.life); + particle->component.particle.direction+=particle->component.particle.angularSpeed*(float)frameTime/1000.0f; + + float speed_x=particle->component.particle.speed * cosf(particle->component.particle.direction); + float speed_y=particle->component.particle.speed * sinf(particle->component.particle.direction); + + speed_x+=(1.0f-(float)particle->component.particle.life/(float)particle->component.particle.lifeTime)*particle->component.particle.linearAccelerationX; + speed_y+=(1.0f-(float)particle->component.particle.life/(float)particle->component.particle.lifeTime)*particle->component.particle.linearAccelerationY; + + + /*speed_x+=(1.0f-(float)particle->component.particle.life/(float)particle->component.particle.lifeTime) * particle->component.particle.tangentialAcceleration * cosf(particle->component.particle.direction+1.5708f);//90° + speed_y+=(1.0f-(float)particle->component.particle.life/(float)particle->component.particle.lifeTime) * particle->component.particle.tangentialAcceleration * sinf(particle->component.particle.direction+1.5708f);//90°*/ + //printf("Particle %s speed_x: %f speed_y: %f\n",particle->id,speed_x,speed_y); + + particle->component.particle.position.x+=speed_x*(float)frameTime/1000.0f; + particle->component.particle.position.y+=speed_y*(float)frameTime/1000.0f; + + particle->component.particle.rotation+=particle->component.particle.spin; + + float t=1.0f-(float)particle->component.particle.life/(float)particle->component.particle.lifeTime; + particle->component.particle.size=interpolation(particle->component.particle.startSize,t,particle->component.particle.endSize); + + ProteoColor color=ColorInterpolation(particle->component.particle.startColor,t,particle->component.particle.endColor); + + SDL_SetTextureAlphaMod( particle->component.particle.image->texture,color.a); + SDL_SetTextureColorMod( particle->component.particle.image->texture, color.r, color.g, color.b ); + SDL_SetTextureBlendMode(particle->component.particle.image->texture,SDL_BLENDMODE_ADD); + + return 1; +} +static int graphics_drawParticle (SDL_Renderer* renderer,ProteoComponent* particle) +{ + graphics_updateParticle (particle); + + if(particle->component.particle.image!=NULL) + { + SDL_Rect location={particle->rect.x+(int)particle->component.particle.position.x, + particle->rect.y+(int)particle->component.particle.position.y, + particle->rect.w*particle->component.particle.size,particle->rect.h*particle->component.particle.size}; + + + SDL_RenderCopyEx(renderer, particle->component.particle.image->texture, &particle->component.particle.frameRect, &location,particle->component.particle.rotation,NULL,SDL_FLIP_NONE); + } + + return 1; +} + +static int set_particle(lua_State *state,int index,_particle* prt) +{ + prt->frameRect.x=0; + prt->frameRect.y=0; + prt->frameRect.w=0; + prt->frameRect.h=0; + + prt->speed=0; + prt->direction=0; + prt->life=prt->lifeTime=0; + + prt->spin=0.0f; + prt->rotation=0.0f; + + prt->linearAccelerationX=0; + prt->linearAccelerationY=0; + + prt->angularSpeed=0.0f; + + float radialAcceleration; //TODO away from the position (rect.x,rect.y) + + prt->startSize=prt->endSize=prt->size=1.0; + + char startColor[10]; + char endColor[10]; + //TODO third color + + SDL_BlendMode blendMode; + + prt->position.x=0; + prt->position.y=0; + + lua_pushvalue(state, index); + lua_pushnil(state); + while (lua_next(state, -2)) + { + lua_pushvalue(state, -2); + const char *key = lua_tostring(state, -1); + if (lua_isnumber(state, -2)==1) + { + double value = lua_tonumber(state, -2); + if(strcmp(key,"frame_x")==0) prt->frameRect.x=(int)value; + if(strcmp(key,"frame_y")==0) prt->frameRect.y=(int)value; + if(strcmp(key,"frame_width")==0) prt->frameRect.w=(int)value; + if(strcmp(key,"frame_height")==0) prt->frameRect.h=(int)value; + if(strcmp(key,"position_x")==0) prt->position.x=(int)value; + if(strcmp(key,"position_y")==0) prt->position.y=(int)value; + + if(strcmp(key,"startSpeed")==0) prt->speed=(float)value; + if(strcmp(key,"startDirection")==0) prt->direction=(float)value; + if(strcmp(key,"lifeTime")==0) prt->life=prt->lifeTime=(int)value; + + if(strcmp(key,"spin")==0) prt->spin=(float)value; + if(strcmp(key,"startRotation")==0) prt->rotation=(float)value; + if(strcmp(key,"angularSpeed")==0) prt->angularSpeed=(float)value; + + if(strcmp(key,"startSize")==0) prt->startSize=prt->size=(float)value; + if(strcmp(key,"endSize")==0) prt->endSize=(float)value; + + if(strcmp(key,"linearAccelerationX")==0) prt->linearAccelerationX=(float)value; + if(strcmp(key,"linearAccelerationY")==0) prt->linearAccelerationY=(float)value; + } + else if(lua_isstring(state, -2)==1) + { + const char *value = lua_tostring(state, -2); + + if(strcmp(key,"startColor")==0) strlcpy(startColor,value,10); + else if(strcmp(key,"endColor")==0) strlcpy(endColor,value,10); + + } + else printf("Key %s is type: %s\n",key, + lua_typename(L, lua_type(L, -2))); + + lua_pop(state, 2); + } + lua_pop(state, 1); + + prt->startColor=hex2color(startColor); + prt->endColor=hex2color(endColor); + + prt->frameTick = SDL_GetTicks(); + + return 1; +} + +static int graphics_setParticle (lua_State *state) { + + ProteoComponent* particle=toProteoComponent(state,1); + set_particle(state,2,&particle->component.particle); + particle->hidden=FALSE; +} + +static int graphics_newParticle (lua_State *state) { + const char* id=luaL_checkstring(state,1); + const char* file=luaL_checkstring(state,2); + const int pos_x=luaL_checkint(state,3); + const int pos_y=luaL_checkint(state,4); + const int width=luaL_checkint(state,5); + const int height=luaL_checkint(state,6); + + _particle prt; + set_particle(state,7,&prt); + + char* callback=NULL; + int ref_callback=-1; + if (lua_isstring(state,8)==1) + callback=(char*)luaL_checkstring(state,8); + else + ref_callback=lua_ref( L, TRUE ); + + ProteoComponent* pc=pushProteoComponent(state); + + //strcpy(pc->id,id); + strlcpy(pc->id,id,PROTEO_MAX_ID); + pc->rect.x=pos_x; + pc->rect.y=pos_y; + pc->rect.w=width; + pc->rect.h=height; + + pc->component.particle=prt; + + pc->component.particle.image=newTexture(gRenderer,file); + if(pc->component.particle.image==NULL) printf("Error newParticle: %s\n",file); + + pc->surface=NULL; + pc->texture=NULL; + pc->parent=NULL; + pc->hidden=FALSE; + pc->type=Particle; + pc->layer=100; + pc->font=NULL; + pc->txt=NULL; + pc->callback=callback; + pc->ref_callback=ref_callback; + + addComponent(pc,&components); + return 1; +} + //============================================================================== // ICON //============================================================================== @@ -3301,6 +3560,119 @@ static int graphics_newShape(lua_State *state) return 1; } +#ifdef USEOPENGL +GLuint compileShader(const char* source, GLuint shaderType) { + GLuint result = glCreateShader(shaderType); + // Define shader text + glShaderSource(result, 1, &source, NULL); + // Compile shader + glCompileShader(result); + + //Check vertex shader for errors + GLint shaderCompiled = GL_FALSE; + glGetShaderiv( result, GL_COMPILE_STATUS, &shaderCompiled ); + if( shaderCompiled != GL_TRUE ) { + printf("Shader compilation error!\n"); + GLint logLength; + glGetShaderiv(result, GL_INFO_LOG_LENGTH, &logLength); + if (logLength > 0) + { + GLchar *log = (GLchar*)malloc(logLength); + glGetShaderInfoLog(result, logLength, &logLength, log); + printf("Shader compile log: %s\n",log); + free(log); + } + glDeleteShader(result); + result = 0; + }else { + + + } + + return result; +} + +static int graphics_setShader (lua_State *state) { + ProteoComponent* pc=toProteoComponent(state,1); + GLuint programId=luaL_checkint(state,2); + int render=lua_ref(state, TRUE); + + if(pc!=NULL) + { + pc->component.rect.shaderId=programId; + pc->component.rect.ref_render=render; + } + + return 0; +} + +static int graphics_setUniform (lua_State *state) { + GLuint programId=luaL_checkint(state,1); + char* uniformName=luaL_checkstring(state,2); + + int uniformLocation=glGetUniformLocation(programId,uniformName); + + //TODO if number .... + float uniformValue=luaL_checknumber(state,3); + glUniform1f(uniformLocation,uniformValue); + + return 0; +} + +//----------------------------------------------------------------------------- +//-- newShader +//-- +//-- @param id component id (string, max size PROTEO_MAX_ID) +//-- @param pos_x position x on the screen +//-- @param pos_y position y on the screen +//-- +//----------------------------------------------------------------------------- + +static int graphics_newShader(lua_State *state) +{ + const char* vertex_shader_source=luaL_checkstring(state,1); + const char* fragment_shader_source=luaL_checkstring(state,2); + + GLuint programId = 0; + GLuint vtxShaderId, fragShaderId; + + programId = glCreateProgram(); + vtxShaderId = compileShader(vertex_shader_source, GL_VERTEX_SHADER); + fragShaderId = compileShader(fragment_shader_source, GL_FRAGMENT_SHADER); + + if(vtxShaderId && fragShaderId) { + // Associate shader with program + glAttachShader(programId, vtxShaderId); + glAttachShader(programId, fragShaderId); + glLinkProgram(programId); + glValidateProgram(programId); + + // Check the status of the compile/link + GLint logLen; + glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &logLen); + if(logLen > 0) { + char* log = (char*) malloc(logLen * sizeof(char)); + // Show any errors as appropriate + glGetProgramInfoLog(programId, logLen, &logLen, log); + printf("Prog Info Log: %s\n" ,log); + free(log); + } + } + if(vtxShaderId) { + glDeleteShader(vtxShaderId); + } + if(fragShaderId) { + glDeleteShader(fragShaderId); + } + + lua_pushinteger(state, programId); + + return 1; +} + + +#endif + //============================================================================== // LUA //============================================================================== @@ -3311,6 +3683,8 @@ void add_graphics_proteo(lua_State* state) addFunction_proteo(state,"graphics","newRect",graphics_newRect); addFunction_proteo(state,"graphics","newEllipse",graphics_newEllipse); addFunction_proteo(state,"graphics","newSprite",graphics_newSprite); + addFunction_proteo(state,"graphics","newParticle",graphics_newParticle); + addFunction_proteo(state,"graphics","setParticle",graphics_setParticle); addFunction_proteo(state,"graphics","newIcon",graphics_newIcon); addFunction_proteo(state,"graphics","newPolygon",graphics_newPolygon); addFunction_proteo(state,"graphics","newShape",graphics_newShape); @@ -3355,4 +3729,9 @@ void add_graphics_proteo(lua_State* state) addFunction_proteo(state,"graphics","saveSkeleton",graphics_saveSkeleton); addFunction_proteo(state,"graphics","updateSkeleton",graphics_updateSkeleton); addFunction_proteo(state,"graphics","hideSkeleton",graphics_hideSkeleton); +#ifdef USEOPENGL + addFunction_proteo(state,"graphics","newShader",graphics_newShader); + addFunction_proteo(state,"graphics","setShader",graphics_setShader); + addFunction_proteo(state,"graphics","setUniform",graphics_setUniform); +#endif } diff --git a/client/proteo_gui.c b/client/proteo_gui.c index bc45749..b392703 100644 --- a/client/proteo_gui.c +++ b/client/proteo_gui.c @@ -176,6 +176,9 @@ void updatetxt(ProteoComponent* pc,SDL_Renderer* renderer) }*/ pc->surface = getTxtSurface(pc); +#ifdef USEOPENGL + GL_ResizeSurface(pc); +#endif pc->texture = SDL_CreateTextureFromSurface(renderer, pc->surface); } @@ -184,6 +187,12 @@ static int gui_getText (lua_State *state) { ProteoComponent* pc=toProteoComponent(state,1); if(verbose) printf("gui.getText\n"); + if(pc->txt==NULL) + { + lua_pushnil(state); + return 1; + } + if(pc->type==DropDown) { //TODO per il dropdown deve tornare il testo del controller o quello del child selezionato? @@ -328,7 +337,7 @@ static int gui_setHidden (lua_State *state) { ProteoComponent* pc=toProteoComponent(state,1); - if(pc!=NULL && pc->type==Deleted) return 0; + if(pc==NULL || pc->type==Deleted) return 0; const int hidden=lua_toboolean(state,2); pc->hidden=hidden; @@ -723,6 +732,7 @@ static int gui_newListItem (lua_State *state) { pc->layer=100; pc->font=newFont(font2path(font), font_size); //strcpy(pc->txt,txt); + pc->txt=NULL; if(txt!=NULL && strlen(txt)>0) { pc->txt=malloc(strlen(txt)+1); @@ -763,6 +773,31 @@ static int gui_eventLabel (lua_State *state,ProteoComponent* label,SDL_Event e,S return FALSE; } +#ifdef USEOPENGL +static int gui_drawLabel_GL (ProteoComponent* label) +{ + SDL_Rect label_rect=label->rect; + SDL_Rect texture_rect=label->texture_rect; + + if(label->parent!=NULL) + { + label_rect.x+=label->parent->rect.x; + label_rect.y+=label->parent->rect.y; + + texture_rect.x+=label->parent->rect.x; + texture_rect.y+=label->parent->rect.y; + } + + GL_SetRenderDrawColor(label->color.r, label->color.g, label->color.b, label->color.a ); + GL_RenderFillRect(& label_rect ); + + if(label->texture) + GL_RenderCopy(label->surface, NULL, & texture_rect ); + + return 1; +} +#endif + static int gui_drawLabel (SDL_Renderer* renderer,ProteoComponent* label) { SDL_Rect label_rect=label->rect; @@ -857,16 +892,28 @@ static int gui_newLabel (lua_State *state) { else { - /*int w,h; - TTF_SizeText(pc->font->font,txt,&w,&h); - pc->texture_rect.x=pos_x; - pc->texture_rect.y=pos_y; - pc->texture_rect.w=w; - pc->texture_rect.h=h;*/ +/*#ifdef USEOPENGL + //Find the first power of two for OpenGL image + int w,h; + TTF_SizeText(pc->font->font,txt,&w,&h); + pc->texture_rect.x=pos_x; + pc->texture_rect.y=pos_y; + SDL_Color Col = {pc->component.label.font_color.r, pc->component.label.font_color.g, pc->component.label.font_color.b}; + SDL_Surface *tmp_surface=TTF_RenderText_Blended(pc->font->font, txt, Col); + pc->texture_rect.w = power_two_floor(w)*2; + pc->texture_rect.h = power_two_floor(h)*2; + + pc->surface = SDL_CreateRGBSurface(0, pc->texture_rect.w, pc->texture_rect.h, 32, 0x00ff0000,0x0000ff00,0x000000ff,0xff000000); + SDL_BlitSurface(tmp_surface, NULL, pc->surface, NULL); + SDL_FreeSurface(tmp_surface); +#else*/ + pc->texture_rect=getTextureRect(pc); - /*SDL_Color Col = {pc->component.label.font_color.r, pc->component.label.font_color.g, pc->component.label.font_color.b}; - pc->surface = TTF_RenderText_Blended(pc->font->font, txt, Col);*/ pc->surface = getTxtSurface(pc); +//#endif +#ifdef USEOPENGL + GL_ResizeSurface(pc); +#endif pc->texture = SDL_CreateTextureFromSurface(gRenderer, pc->surface); } @@ -1047,6 +1094,49 @@ int proteo_makecursor(SDL_Rect *rect,ProteoComponent* textfield) return 0; } +#ifdef USEOPENGL +static int gui_drawTextField_GL (ProteoComponent* textfield) +{ + SDL_Rect textfield_rect=textfield->rect; + SDL_Rect texture_rect=textfield->texture_rect; + + if(textfield->parent!=NULL) + { + textfield_rect.x+=textfield->parent->rect.x; + textfield_rect.y+=textfield->parent->rect.y; + + texture_rect.x+=textfield->parent->rect.x; + texture_rect.y+=textfield->parent->rect.y; + } + + GL_SetRenderDrawColor(textfield->color.r, textfield->color.g, textfield->color.b, textfield->color.a ); + GL_RenderFillRect( & textfield_rect ); + + // GL_RenderSetClipRect(&textfield_rect); + + if(textfield->texture!=NULL) GL_RenderCopy(textfield->surface, NULL, & texture_rect ); + + if(textfield==selected) + { + blinkCursor++; + if(blinkCursor<30) + { + GL_SetRenderDrawColor( textfield->component.textfield.font_color.r, + textfield->component.textfield.font_color.g, + textfield->component.textfield.font_color.b, + 255 ); + SDL_Rect cursor; + proteo_makecursor(&cursor,textfield); + GL_RenderFillRect( & cursor ); + } + else if (blinkCursor>60) blinkCursor=0; + } + // GL_RenderSetClipRect(NULL); + + return TRUE; +} +#endif + static int gui_drawTextField (SDL_Renderer* renderer,ProteoComponent* textfield) { SDL_Rect textfield_rect=textfield->rect; @@ -1217,6 +1307,56 @@ static int gui_eventButton (lua_State *state,ProteoComponent* button,SDL_Event e return FALSE; } +#ifdef USEOPENGL +static int gui_drawButton_GL (ProteoComponent* button) +{ + SDL_Rect button_rect=button->rect; + SDL_Rect texture_rect=button->texture_rect; + + if(button->parent!=NULL) + { + button_rect.x+=button->parent->rect.x; + button_rect.y+=button->parent->rect.y; + + texture_rect.x+=button->parent->rect.x; + texture_rect.y+=button->parent->rect.y; + } + ProteoColor bcol={button->color.a,button->color.r,button->color.g,button->color.b}; + if(button->enabled) + { + int x,y; + + SDL_GetMouseState(&x,&y); + SDL_Point point={x/gScale,y/gScale}; + + if(SDL_PointInRect(&point,&button_rect)) + { + bcol=brightness(button->color,1.1f); + + } + + } + else + { + bcol=saturation(button->color,0.3f); + + } + + GL_SetRenderDrawColor(button->colorB.r, button->colorB.g, button->colorB.b, button->colorB.a); + SDL_Rect border={button_rect.x-button->component.button.border_size,button_rect.y-button->component.button.border_size, + button_rect.w+2*button->component.button.border_size,button_rect.h+2*button->component.button.border_size}; + GL_RenderFillRoundRect(& border,button->component.button.rounded?0.2:0 ); + + GL_SetRenderDrawColor(bcol.r, bcol.g, bcol.b, bcol.a); + + GL_RenderFillRoundRect(& button_rect,button->component.button.rounded?0.2:0 ); + + if(button->texture!=NULL) GL_RenderCopy(button->surface, NULL, & texture_rect ); + + return 1; +} +#endif + static int gui_drawButton (SDL_Renderer* renderer,ProteoComponent* button) { SDL_Rect button_rect=button->rect; @@ -1348,11 +1488,9 @@ static int gui_newButton (lua_State *state) { if(!pc->font->font) { if(verbose) printf("Font newButton(%d) error: %s\n",currentline(state), TTF_GetError()); - } else { - /*int w,h; TTF_SizeText(pc->font->font,txt,&w,&h); pc->texture_rect.x=pos_x+(width-w)/2; @@ -1363,6 +1501,9 @@ static int gui_newButton (lua_State *state) { /*SDL_Color Col = {pc->component.button.font_color.r, pc->component.button.font_color.g, pc->component.button.font_color.b}; pc->surface = TTF_RenderText_Blended(pc->font->font, txt, Col);*/ pc->surface = getTxtSurface(pc); +#ifdef USEOPENGL + GL_ResizeSurface(pc); +#endif pc->texture = SDL_CreateTextureFromSurface(gRenderer, pc->surface); } @@ -1960,6 +2101,90 @@ static int gui_newDropDownItem (lua_State *state) //============================================================================== // Form //============================================================================== +#ifdef USEOPENGL +static int gui_drawForm_GL (ProteoComponent* form) +{ + ProteoColor back_color={form->component.form.back_color.a,form->component.form.back_color.r,form->component.form.back_color.g,form->component.form.back_color.b}; + ProteoColor bar_color={form->component.form.bar_color.a,form->component.form.bar_color.r,form->component.form.bar_color.g,form->component.form.bar_color.b}; + + if(form->enabled) + { + int x,y; + SDL_GetMouseState(&x,&y); + SDL_Point point={x/gScale,y/gScale}; + + + if(SDL_PointInRect(&point,&form->rect)) + { + //back_color=brightness(form->component.form.back_color,1.1f); + bar_color=brightness(form->component.form.bar_color,1.1f); + } + } + else + { + back_color=saturation(form->component.form.back_color,0.3f); + bar_color=saturation(form->component.form.bar_color,0.3f); + } + + if(form->component.form.state==2) + { + GL_SetRenderDrawColor(back_color.r,back_color.g, back_color.b, back_color.a ); + GL_RenderFillRect(& form->rect ); + } + + if((form->component.form.mode & 2) == 0) + { + + GL_SetRenderDrawColor(bar_color.r, + bar_color.g, bar_color.b, bar_color.a ); + SDL_Rect bar; + bar.x=form->rect.x; + bar.y=form->rect.y; + bar.w=form->rect.w; + bar.h=form->component.form.bar_size; + GL_RenderFillRect( & bar); + + SDL_Rect texture_rect=form->texture_rect; + texture_rect.x+=form->rect.x; + texture_rect.y+=form->rect.y;//+(form->component.form.bar_size-form->texture_rect.h)/2; + //if(form->texture!=NULL) SDL_RenderCopy(renderer, form->texture, NULL, & form->texture_rect ); + if(form->texture!=NULL) GL_RenderCopy(form->surface,NULL,&texture_rect); + + ProteoColor button_color=brightness(form->component.form.bar_color,0.9f); + } + + if(form->component.form.state==2) + { + //ProteoComponent* current=form->component.form.child_next; + // while(current!=NULL) + for(int i=0;icomponent.form.nchilds;i++) + { + ProteoComponent* current=form->component.form.childs[i]; + if(current->type!=Deleted && current->hidden==FALSE) + { + //SDL_SetRenderDrawBlendMode(renderer,SDL_BLENDMODE_BLEND); + if(current->type==Rect) graphics_drawRect_GL (current); + //else if(current->type==Ellips) graphics_drawEllipse (renderer,current); + else if(current->type==Label) gui_drawLabel_GL (current); + //else if(current->type==Image) graphics_drawImage (renderer,current); + //else if(current->type==Icon) graphics_drawIcon (renderer,current); + //else if(current->type==Polyg) graphics_drawPolygon (renderer,current); + else if(current->type==Button) gui_drawButton_GL (current); + else if(current->type==TextField) gui_drawTextField_GL (current); + //else if(current->type==List) gui_drawList (renderer,current); + //else if(current->type==Checkbox) gui_drawCheckbox (renderer,current); + //else if(current->type==Sprite) graphics_drawSprite (renderer,current); + //else if(current->type==DropDown) gui_drawDropDown (renderer,current); + else if(current->type==Form) gui_drawForm_GL (current); + } + //current=current->component.form.child_next; + } + } + + return 1; +} +#endif + static int gui_drawForm (SDL_Renderer* renderer,ProteoComponent* form) { ProteoColor back_color={form->component.form.back_color.a,form->component.form.back_color.r,form->component.form.back_color.g,form->component.form.back_color.b}; @@ -2003,9 +2228,10 @@ static int gui_drawForm (SDL_Renderer* renderer,ProteoComponent* form) bar.h=form->component.form.bar_size; SDL_RenderFillRect( renderer, & bar); - form->texture_rect.x=10+form->rect.x; - form->texture_rect.y=form->rect.y+(form->component.form.bar_size-form->texture_rect.h)/2; - if(form->texture!=NULL) SDL_RenderCopy(renderer, form->texture, NULL, & form->texture_rect ); + SDL_Rect texture_rect=form->texture_rect; + texture_rect.x+=form->rect.x; + texture_rect.y+=form->rect.y;//+(form->component.form.bar_size-form->texture_rect.h)/2; + if(form->texture!=NULL) SDL_RenderCopy(renderer, form->texture, NULL, & texture_rect ); ProteoColor button_color=brightness(form->component.form.bar_color,0.9f); @@ -2264,10 +2490,16 @@ static int gui_newForm (lua_State *state) { int w,h; TTF_SizeText(pc->font->font,title,&w,&h); pc->texture_rect.x=10;//(width-w)/2; - pc->texture_rect.y=(bar_size-h)/2; + pc->texture_rect.y=(bar_size-h)/2; //ATTENZIONE vengono sovrascritti durante il draw + pc->texture_rect.w=w; pc->texture_rect.h=h; pc->surface = TTF_RenderText_Blended(pc->font->font, title, Col); + +#ifdef USEOPENGL + GL_ResizeSurface(pc); +#endif + pc->texture = SDL_CreateTextureFromSurface(gRenderer, pc->surface); } diff --git a/client/proteo_network.c b/client/proteo_network.c index 8b2c89d..e372208 100644 --- a/client/proteo_network.c +++ b/client/proteo_network.c @@ -282,10 +282,29 @@ static size_t write_data(void *ptr, size_t size, size_t nmemb, void *stream) } #ifdef __EMSCRIPTEN__ + +typedef struct ProteoDownload +{ + char name[256]; + char callback[256]; + +} ProteoDownload; + void downloadSucceeded(emscripten_fetch_t *fetch) { printf("Finished downloading %llu bytes from URL %s.\n", fetch->numBytes, fetch->url); - // The data is now available at fetch->data[0] through fetch->data[fetch->numBytes-1]; + if(fetch->userData!=NULL) + { + writedatafile(((ProteoDownload*)fetch->userData)->name,fetch->data,fetch->numBytes); + + EM_ASM( + FS.syncfs(function (err) { + //console.log("Download font error: "+err); + }); + ); + + free(fetch->userData); + } emscripten_fetch_close(fetch); // Free data associated with the fetch. } @@ -307,15 +326,31 @@ static int network_download(lua_State *state) //TODO completamente da scrivere, vedi newFont - emscripten_fetch_attr_t attr; + /*emscripten_fetch_attr_t attr; emscripten_fetch_attr_init(&attr); strcpy(attr.requestMethod, "GET"); //attr.userData callback + name - attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY | EMSCRIPTEN_FETCH_PERSIST_FILE; + attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY | EMSCRIPTEN_FETCH_PERSIST_FILE; attr.onsuccess = downloadSucceeded; attr.onerror = downloadFailed; - emscripten_fetch(&attr, "myfile.dat"); - + emscripten_fetch(&attr, "myfile.dat");*/ + + emscripten_fetch_attr_t attr; + emscripten_fetch_attr_init(&attr); + strcpy(attr.requestMethod, "GET"); + attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY;// | EMSCRIPTEN_FETCH_PERSIST_FILE; + ProteoDownload* prtDownload=malloc(sizeof(ProteoDownload)); + strcpy(prtDownload->name,name); + strcpy(prtDownload->callback,callback); + attr.userData = prtDownload; + attr.onsuccess = downloadSucceeded; + attr.onerror = downloadFailed; + //attr.destinationPath=path; + const char* full_path=concat3(config.server,"/",url); + if(verbose) printf("Download font: %s\n",full_path); + emscripten_fetch_t *fetch = emscripten_fetch(&attr, full_path); + free(full_path); + } #else static int network_download(lua_State *state) { @@ -554,8 +589,12 @@ void get_lib(json_object * jobj) addLib(name,scr); //printf("Download LUA: %s\n",scr); - luaL_dostring(L,scr); + int error = luaL_dostring(L,scr); + if (error) { + //printf("ERROR pcall(timer->callback,%d): %s\n",currentline(L),lua_tostring(L, -1)); + printf("ERROR pcall(get_lib): %s\n",lua_tostring(L, -1)); + } } } } @@ -592,12 +631,12 @@ void get_script(json_object * jobj) int error = luaL_dostring(L,scr); //lua_pushcfunction(L, traceback); - + if (error) { fprintf(stderr, "ERROR start script: %s\n", lua_tostring(L, -1)); lua_pop(L, 1); } - + lua_getglobal(L,"init"); //int error = lua_pcall(L, 0, 0, -2); error = lua_trace_pcall(L,0,0); @@ -636,8 +675,8 @@ void proteo_post(const char* res,const char* appkey, const char* token,const cha //proteo_post_callback(const char* res,const char* data,const char* callback) var result = Module.ccall('proteo_post_callback', 'null', - ['string','string','string'], - [UTF8ToString($1),this.responseText,UTF8ToString($5)]); + ['string','string','string','number'], + [UTF8ToString($1),this.responseText,UTF8ToString($5),$6]); } } @@ -653,7 +692,7 @@ void proteo_post(const char* res,const char* appkey, const char* token,const cha http.send(UTF8ToString($4)); - },config.server,res,token,appkey,post_data,callback); + },config.server,res,token,appkey,post_data,callback,ref_callback); /*//console.log(data.strValues); var http = new XMLHttpRequest(); @@ -682,11 +721,11 @@ void proteo_post(const char* res,const char* appkey, const char* token,const cha void proteo_post(const char* res,const char* appkey, const char* token,const char* post_data, const char* callback,const int ref_callback) { char* url=NULL; - + char tmp[256]; strcpy(tmp,res); char* app=strtok(tmp+1, "/"); - + /*if(strstr(res,"deepblue")!=NULL) { url=concat(getaccesspoint("deepblue"),res); @@ -701,7 +740,7 @@ void proteo_post(const char* res,const char* appkey, const char* token,const cha } else url=concat(config.server,res); - + char* data=concat(res,post_data); // if(debug) printf("HMAC \n json) %s\n",data); @@ -768,12 +807,14 @@ void proteo_post_callback(const char* res,const char* data,const char* callback, { //printf("%lu bytes retrieved\n", (unsigned long)chunk.size); #ifdef PROTEO_USE_TINYJSON - //int count=0; - //for(int i=0; data[i]; i++) if(data[i]==':') count++; //Very simple estimation - + int count=0; + for(int i=0; data[i]; i++) if(data[i]==':') count++; //Very simple estimation + json_t *mem=malloc(count*2*sizeof(json_t)); + json_t const* jobj = json_create( data, mem, count*2 ); + //TODO - json_t mem[32]; - json_t const* jobj = json_create( data, mem, sizeof mem / sizeof *mem ); + //json_t mem[32]; + //json_t const* jobj = json_create( data, mem, sizeof mem / sizeof *mem ); #else json_object * jobj = json_tokener_parse(data); #endif @@ -811,7 +852,7 @@ void proteo_post_callback(const char* res,const char* data,const char* callback, } } } - + #ifdef PROTEO_USE_TINYJSON free(mem); #endif @@ -877,7 +918,7 @@ void proteo_get(const char* res,const char* appkey, const char* token,const char char tmp[256]; strcpy(tmp,res); char* app=strtok(tmp+1, "/"); - + /*if(strstr(res,"deepblue")!=NULL) { url=concat(getaccesspoint("deepblue"),res); @@ -892,7 +933,7 @@ void proteo_get(const char* res,const char* appkey, const char* token,const char } else url=concat(config.server,res); - + char* data=concat(res,token); /*char data[2000]; @@ -973,8 +1014,14 @@ void proteo_get_callback(const char* res,const char* data,const char* callback) //printf("%lu bytes retrieved\n", (unsigned long)chunk.size); #ifdef PROTEO_USE_TINYJSON - json_t mem[32]; - json_t const* jobj = json_create( data, mem, sizeof mem / sizeof *mem ); + //json_t mem[32]; + //json_t const* jobj = json_create( data, mem, sizeof mem / sizeof *mem ); + + int count=0; + for(int i=0; data[i]; i++) if(data[i]==':') count++; //Very simple estimation + json_t *mem=malloc(count*2*sizeof(json_t)); + json_t const* jobj = json_create( data, mem, count*2 ); + #else json_object * jobj = json_tokener_parse(data); #endif @@ -1006,6 +1053,9 @@ void proteo_get_callback(const char* res,const char* data,const char* callback) } } } +#ifdef PROTEO_USE_TINYJSON + free(mem); +#endif } static int network_proteo_get(lua_State *state) { @@ -1178,9 +1228,9 @@ static int proteo_login_callback(char* ptr) #ifdef PROTEO2 struct json_object *tick; json_object_object_get_ex(jobj, "tickets", &tick); - struct array_list *tickets=json_object_get_array(tick); - for (int i = 0; i < array_list_length(tickets); i++) { - struct json_object *t = (struct json_object *) array_list_get_idx(tickets, i); + //struct array_list *tickets=json_object_get_array(tick); + for (int i = 0; i < /*array_list_length(tickets)*/ json_object_array_length(tick); i++) { + struct json_object *t = (struct json_object *) json_object_array_get_idx(tick, i);//array_list_get_idx(tickets, i); json_object_object_foreach(t, key, val) { //if(strcmp(app,key)==0 && val!=NULL) @@ -1321,7 +1371,7 @@ int em_login_function(void *ptr) strcpy(get_script,"/proteo/scriptandlibs/"); strcat(get_script,script); printf("Download LUA: %s\n",get_script); - proteo_get(get_script,PROTEO_APP_KEY,Token,""); + proteo_get(get_script,config.appkey,Token,""); } else if(res==2) @@ -1458,14 +1508,14 @@ void proteo_login_md5(const char* username,const char* md5_hex, const char* id) strftime (timestamp, 100, "%Y-%m-%d %H:%M:%S", localtime (&now)); //printf ("%s\n", timestamp); -#ifdef PROTEO_USE_TINYJSON +//#ifdef PROTEO_USE_TINYJSON char json[512]; sprintf(json,"{" "\"username\":\"%s\"," "\"scriptId\":\"%s\"," "\"timestamp\":\"%s\"" "}",username,id,timestamp); -#else +/*#else json_object * jobj = json_object_new_object(); #ifndef PROTEO2 json_object *jstring1 = json_object_new_string(id); @@ -1490,7 +1540,7 @@ void proteo_login_md5(const char* username,const char* md5_hex, const char* id) const char *json = json_object_to_json_string(jobj); //json_object_put(jobj); -#endif +#endif*/ if(debug) printf("login json: %s\n",json); @@ -1584,7 +1634,7 @@ void proteo_login_md5(const char* username,const char* md5_hex, const char* id) curl_easy_cleanup (hnd); #ifndef PROTEO_USE_TINYJSON - free( (void*)json ); + //free( (void*)json ); #endif free(chunk.memory); diff --git a/client/proteo_opencv.cpp b/client/proteo_opencv.cpp index 1caa95f..5101058 100644 --- a/client/proteo_opencv.cpp +++ b/client/proteo_opencv.cpp @@ -97,6 +97,9 @@ static UMat *checkMat (lua_State *L, int index) return mat; } +#ifdef __EMSCRIPTEN__ + +#else static VideoCapture *checkCap (lua_State *L, int index) { VideoCapture *cap; @@ -105,6 +108,7 @@ static VideoCapture *checkCap (lua_State *L, int index) if (cap == NULL) luaL_typerror(L, index, OPENCVVC); return cap; } +#endif #ifdef OPENCVNET static Net *checkNet (lua_State *L, int index) @@ -117,6 +121,9 @@ static Net *checkNet (lua_State *L, int index) } #endif +#ifdef __EMSCRIPTEN__ + +#else static VideoWriter *checkWriter (lua_State *L, int index) { VideoWriter *vw; @@ -125,6 +132,7 @@ static VideoWriter *checkWriter (lua_State *L, int index) if (vw == NULL) luaL_typerror(L, index, OPENCVVW); return vw; } +#endif //============================================================================== // GC @@ -143,6 +151,9 @@ static int mat_gc(lua_State *l) { return 0; } +#ifdef __EMSCRIPTEN__ + +#else static int vc_gc(lua_State *l) { VideoCapture* cap = checkCap(l, 1); @@ -155,6 +166,7 @@ static int vc_gc(lua_State *l) { return 0; } +#endif #ifdef OPENCVNET static int net_gc(lua_State *l) { @@ -171,6 +183,9 @@ static int net_gc(lua_State *l) { } #endif +#ifdef __EMSCRIPTEN__ + +#else static int vw_gc(lua_State *l) { VideoWriter* vw = checkWriter(l, 1); @@ -183,6 +198,7 @@ static int vw_gc(lua_State *l) { return 0; } +#endif //============================================================================== // PUSH @@ -246,6 +262,9 @@ static Net *pushNet (lua_State *state) } #endif +#ifdef __EMSCRIPTEN__ + +#else static VideoWriter *pushWriter (lua_State *state) { VideoWriter *vw = new (lua_newuserdata(state, sizeof(VideoWriter))) VideoWriter(); @@ -258,7 +277,11 @@ static VideoWriter *pushWriter (lua_State *state) return vw; } +#endif + +#ifdef __EMSCRIPTEN__ +#else static VideoCapture * pushCap (lua_State *state) { #if TARGET_OS_IPHONE @@ -277,6 +300,7 @@ static VideoCapture * pushCap (lua_State *state) #endif return ret; } +#endif //============================================================================== // OPENCV @@ -289,26 +313,39 @@ int opencv_img(lua_State *state) return 1; } + + int opencv_imread(lua_State *state) { const char* file=luaL_checkstring(state,1); +#ifdef __EMSCRIPTEN__ + +#else Mat tmp = imread( file, 1 ); UMat *ret=pushMat(state); tmp.copyTo(*ret); +#endif return 1; } + + int opencv_imwrite(lua_State* state) { const char* file=luaL_checkstring(state,1); +#ifdef __EMSCRIPTEN__ + +#else UMat* frame=checkMat(state, 2); if (!frame->empty()) imwrite(file, *frame); - +#endif return 0; } + + int opencv_imencode(lua_State* state) { UMat *img=checkMat(state,1); @@ -320,7 +357,9 @@ int opencv_imencode(lua_State* state) lua_pushnil(state); return 1; } +#ifdef __EMSCRIPTEN__ +#else vector buf; imencode(".jpg",*img,buf); @@ -332,15 +371,20 @@ int opencv_imencode(lua_State* state) lua_pushstring(state, b64); free(b64); - +#endif return 1; } + + int opencv_imdecode(lua_State* state) { const char* img=luaL_checkstring(state, 1); UMat *out=checkMat(state,2); +#ifdef __EMSCRIPTEN__ + +#else char *tmp=(char *)malloc(Base64decode_len(img)); int len = Base64decode(tmp, img); @@ -351,12 +395,17 @@ int opencv_imdecode(lua_State* state) imdecode(buf, 1).copyTo(*out); free(tmp); - +#endif + return 0; } + int opencv_videowriter(lua_State* state) { +#ifdef __EMSCRIPTEN__ + +#else VideoWriter *vw=pushWriter(state); const char *filename = lua_tostring(state, 1); @@ -368,12 +417,48 @@ int opencv_videowriter(lua_State* state) if(fourcc==0) fourcc=VideoWriter::fourcc('M','J','P','G'); vw->open(filename, fourcc, fps, Size(w,h)); - +#endif return 1; } + + int opencv_videocapture(lua_State* state) { +#ifdef __EMSCRIPTEN__ + const char *value = lua_tostring(state, 1); + + //Se il valore di input è 0 potremmo non settare l'attributo src e invece richiamare il codice di avvio della camera + char *cap=(char*)EM_ASM_INT({ + let video_element_id='VideoCapture'; + let video = document.createElement('video'); + video.id=video_element_id; + video.style.display = "none"; + //video.autoplay=true; + video.width=576; + video.height=720; + video.setAttribute('src',UTF8ToString($0)); + + document.body.insertBefore(video, null); + + var canvas=document.createElement('canvas'); + canvas.width=video.width; + canvas.height=video.height; + var ctx=canvas.getContext('2d'); + + //document.body.insertBefore(canvas, null); + + video.ctx=ctx; + + var lengthBytes = lengthBytesUTF8(video_element_id)+1; + var stringOnWasmHeap = _malloc(lengthBytes); + stringToUTF8(video_element_id, stringOnWasmHeap, lengthBytes); + return stringOnWasmHeap; + },value); + lua_pushstring(state, cap); + free(cap); + return 1; +#else VideoCapture *cap=pushCap(state); if (lua_isnumber(state, 1)==1) @@ -404,42 +489,55 @@ cap->set(CAP_PROP_BUFFERSIZE, 3); */ printf("VideoCapture open error\n"); } } - +#endif return 1; } int opencv_getFrameSize(lua_State* state) { - VideoCapture *cap=checkCap(state,1); +#ifdef __EMSCRIPTEN__ +#else + VideoCapture *cap=checkCap(state,1); +#endif return 1; } int opencv_setFrameSize(lua_State* state) { +#ifdef __EMSCRIPTEN__ + //x.setAttribute("width", "320"); + //x.setAttribute("height", "240"); +#else VideoCapture *cap=checkCap(state,1); int w=luaL_checkinteger(state, 2); int h=luaL_checkinteger(state, 3); cap->set(CAP_PROP_FRAME_WIDTH, w); cap->set(CAP_PROP_FRAME_HEIGHT, h); - +#endif return 0; } int opencv_setBufferSize(lua_State* state) { +#ifdef __EMSCRIPTEN__ + +#else VideoCapture *cap=checkCap(state,1); int s=luaL_checkinteger(state, 2); cap->set(CAP_PROP_BUFFERSIZE, s); - +#endif return 0; } int opencv_write(lua_State* state) { +#ifdef __EMSCRIPTEN__ + +#else //UMat *ret=pushMat(state); VideoWriter *vw=checkWriter(state,1); UMat *ret=checkMat(state,2); @@ -457,11 +555,63 @@ int opencv_write(lua_State* state) success=0; } lua_pushinteger(state, success); +#endif return 1; } +lua_State* tmp_state; +int image_to_mat(int width, int height,uint8_t* data) +{ + UMat *ret=checkMat(tmp_state,2); + cv::Mat mat =cv::Mat(height,width, CV_8UC4, data); + + + mat.copyTo(*ret); + + int success=1; + if (ret->empty()) { + success=0; + } + + return success; +} + int opencv_frame(lua_State* state) { +#ifdef __EMSCRIPTEN__ + tmp_state=state; + const char *cap=luaL_checkstring(state, 1); + + //printf("VideoCapture id:%s\n",cap); + + int success=EM_ASM_INT({ + var video=document.getElementById(UTF8ToString($0)); + video.ctx.drawImage(video,0,0,video.width,video.height); + + //video.ctx.fillStyle = "#FF0000"; + //video.ctx.fillRect(0, 0, 150, 75); + + var data = video.ctx.getImageData(0,0,video.width,video.height).data; + //const uint8ArrData=new Uint8Array(data); + //console.log(data.length); + + var result = Module.ccall('image_to_mat', + 'number', + ['number','number','array'], + [video.width,video.height,data]); + + return result; + //uint8ArrData=new Uint8Array(imgData.data); + },cap); + + //ret->create() + //printf("Data : %d %d %d\n",data[0],data[1],data[2]); + + lua_pushinteger(state, success); +/*ctx.drawImage(video,0,0,video.width,video.height); + frame.data.set(ctx.getImageData(0,0,video.width,video.height).data)*/ + +#else //UMat *ret=pushMat(state); VideoCapture *cap=checkCap(state,1); UMat *ret=checkMat(state,2); @@ -472,6 +622,7 @@ int opencv_frame(lua_State* state) success=0; } lua_pushinteger(state, success); +#endif return 1; } @@ -512,7 +663,11 @@ int opencv_resize(lua_State* state) int rows=luaL_optinteger(state, 4, -1); // - if(in->empty()) return 0; + if(in->empty()) + { + printf("OpenCV Resize: Input image is empty\n"); + return 0; + } /*if(rows==-1 || cols==-1) resize(*in, *out,out->size()); @@ -533,14 +688,17 @@ int opencv_resize(lua_State* state) } else { cv::resize( *in, *out, cv::Size(w2, rows)); } - +#ifdef __EMSCRIPTEN__ + +#else int top = (rows - out->rows) / 2; int down = (rows - out->rows+1) / 2; int left = (cols - out->cols) / 2; int right = (cols - out->cols+1) / 2; + cv::copyMakeBorder(*out, *out, top, down, left, right, cv::BORDER_CONSTANT, 0 ); - +#endif return 0; } diff --git a/client/proteo_sdl.c b/client/proteo_sdl.c index 91b74c8..7f9bb0b 100644 --- a/client/proteo_sdl.c +++ b/client/proteo_sdl.c @@ -6,6 +6,9 @@ SDL_Window* gWindow = NULL; //SDL_Surface* gScreenSurface = NULL; SDL_Renderer* gRenderer = NULL; +SDL_GLContext gContext =NULL; +SDL_Texture *gTarget=NULL; + float gScale=1.0f; ProteoColor backgroundColor={255,0,0,0}; @@ -163,11 +166,15 @@ int initSDL() #if TARGET_OS_IPHONE gWindow = SDL_CreateWindow( NULL, 0, 0, 0, 0, SDL_WINDOW_SHOWN| SDL_WINDOW_BORDERLESS ); #else - gWindow = SDL_CreateWindow( "Proteo", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN/*|SDL_WINDOW_RESIZABLE*/ ); + gWindow = SDL_CreateWindow( "Proteo", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN|SDL_WINDOW_OPENGL/*|SDL_WINDOW_RESIZABLE*/ ); SDL_SetWindowResizable(gWindow,TRUE); #endif - - + +#ifdef USEOPENGL + SDL_SetHint(SDL_HINT_RENDER_DRIVER,"opengles2"); +#endif + printf("Hint render driver: %s\n",SDL_GetHint(SDL_HINT_RENDER_DRIVER)); + if( gWindow == NULL ) { printf( "Window could not be created! SDL_Error: %s\n", SDL_GetError() ); @@ -178,11 +185,25 @@ int initSDL() //Get window surface //gScreenSurface = SDL_GetWindowSurface( gWindow ); - gRenderer = SDL_CreateRenderer( gWindow, -1, SDL_RENDERER_ACCELERATED|SDL_RENDERER_PRESENTVSYNC); + gRenderer = SDL_CreateRenderer( gWindow, -1, SDL_RENDERER_ACCELERATED|SDL_RENDERER_PRESENTVSYNC|SDL_RENDERER_TARGETTEXTURE); SDL_SetRenderDrawBlendMode(gRenderer,SDL_BLENDMODE_BLEND); } +#ifdef USEOPENGL + gContext = SDL_GL_CreateContext( gWindow ); + //SDL_GL_MakeCurrent(gWindow,gContext); + + gTarget = SDL_CreateTexture(gRenderer, SDL_PIXELFORMAT_RGBA8888, + SDL_TEXTUREACCESS_TARGET, SCREEN_WIDTH, SCREEN_HEIGHT); + //Initialize GLEW + glewExperimental = GL_TRUE; + GLenum glewError = glewInit(); + if( glewError != GLEW_OK ) + { + printf( "Error initializing GLEW! %s\n", glewGetErrorString( glewError ) ); + } +#endif if(opt_fullscreen) { SDL_SetWindowFullscreen(gWindow,SDL_WINDOW_FULLSCREEN_DESKTOP); @@ -231,7 +252,12 @@ void closeSDL() freesounds(); freeskeletons(); - SDL_DestroyRenderer(gRenderer); + SDL_GL_DeleteContext(gContext); + + SDL_DestroyTexture(gTarget); + gTarget=NULL; + + SDL_DestroyRenderer(gRenderer); gRenderer = NULL; SDL_DestroyWindow( gWindow ); diff --git a/client/proteo_shell_minimal.html b/client/proteo_shell_minimal.html index b643e8e..fa58479 100644 --- a/client/proteo_shell_minimal.html +++ b/client/proteo_shell_minimal.html @@ -145,7 +145,6 @@ - {{{ SCRIPT }}} diff --git a/client/proteo_system.c b/client/proteo_system.c index 958e4ee..a11aae9 100644 --- a/client/proteo_system.c +++ b/client/proteo_system.c @@ -74,7 +74,14 @@ static int system_fileexist(lua_State *state) { static int system_document(lua_State *state) { if(debug) printf("system.document\n"); //lua_pushliteral(state,"./"); +#if TARGET_OS_MAC + char path_doc[PATH_MAX]; + strcpy(path_doc,app_path); + strcat(path_doc,"/../../../"); + lua_pushlstring(state,path_doc,strlen(path_doc)); +#else lua_pushlstring(state,config.basedir,strlen(config.basedir)); +#endif return 1; } diff --git a/client/proteo_tensorflow.c b/client/proteo_tensorflow.c new file mode 100644 index 0000000..051e4bc --- /dev/null +++ b/client/proteo_tensorflow.c @@ -0,0 +1,378 @@ +//============================================================================== +// TENSORFLOW +//============================================================================== + +#define TFLITE_MODEL "TfLiteModel" +#define TFLITE_INTERPRETEROPTIONS "TfLiteInterpreterOptions" +#define TFLITE_INTERPRETER "TfLiteInterpreter" +#define TFLITE_TENSOR "TfLiteTensor" + +//============================================================================== +// CHECK +//============================================================================== + +static void *checkTFLModel (lua_State *state, int index) +{ + void *model; + luaL_checktype(state, index, LUA_TUSERDATA); + model = *(void**)luaL_checkudata(state, index, TFLITE_MODEL); + if (model == NULL) luaL_typerror(state, index, TFLITE_MODEL); + return model; +} + +static void *checkTFLInterpreterOptions (lua_State *state, int index) +{ + void *options; + luaL_checktype(state, index, LUA_TUSERDATA); + options = *(void**)luaL_checkudata(state, index, TFLITE_INTERPRETEROPTIONS); + if (options == NULL) luaL_typerror(state, index, TFLITE_INTERPRETEROPTIONS); + return options; +} + +static void *checkTFLInterpreter (lua_State *state, int index) +{ + void *interpreter; + luaL_checktype(state, index, LUA_TUSERDATA); + interpreter = *(void**)luaL_checkudata(state, index, TFLITE_INTERPRETER); + if (interpreter == NULL) luaL_typerror(state, index, TFLITE_INTERPRETER); + return interpreter; +} + +static TfLiteTensor *toTFLTensor (lua_State *state, int index) +{ + TfLiteTensor *tensor = (TfLiteTensor *)lua_touserdata(state, index); + if (tensor == NULL) luaL_typerror(state, index, TFLITE_TENSOR); + return tensor; +} + +static void *checkTFLTensor (lua_State *state, int index) +{ + TfLiteTensor *tensor=NULL; + + if(lua_islightuserdata(state, index)) + tensor=toTFLTensor(state,index); + else luaL_typerror(state, index, TFLITE_TENSOR); + + return tensor; +} + +//============================================================================== +// GC +//============================================================================== + +static int tflmodel_gc(lua_State *state) { + + void* model = checkTFLModel(state, 1); + + if(model!=NULL) + { + printf("TFLite Model release\n"); + TfLiteModelDelete(model); + } + + return 0; +} + +static int tflinterpreteroptions_gc(lua_State *state) { + + void* options = checkTFLInterpreterOptions(state, 1); + + if(options!=NULL) + { + printf("TFLite InterpreterOptions release\n"); + TfLiteInterpreterOptionsDelete(options); + } + + return 0; +} + +static int tflinterpreter_gc(lua_State *state) { + + void* interpreter = checkTFLInterpreter(state, 1); + + if(interpreter!=NULL) + { + printf("TFLite Interpreter release\n"); + TfLiteInterpreterDelete(interpreter); + } + + return 0; +} + +//============================================================================== +// PUSH +//============================================================================== + +static void * pushTFLModelFromFile (lua_State *state,const char *modelpath) +{ + TfLiteModel* model = TfLiteModelCreateFromFile(modelpath); + if(model==NULL) printf("TfLiteModelCreateFromFile error\n"); + + *(void**)lua_newuserdata(state, sizeof(void*)) = model; + + if(luaL_newmetatable(state, TFLITE_MODEL)){ + lua_pushcfunction(state, tflmodel_gc); + lua_setfield(state, -2, "__gc"); + } + lua_setmetatable(state, -2); + + return model; +} + +static void * pushTFLInterpreterOptions (lua_State *state) +{ + TfLiteInterpreterOptions* options = TfLiteInterpreterOptionsCreate(); + if(options==NULL) printf("TfLiteInterpreterOptionsCreate error\n"); + + *(void**)lua_newuserdata(state, sizeof(void*)) = options; + + if(luaL_newmetatable(state, TFLITE_INTERPRETEROPTIONS)){ + lua_pushcfunction(state, tflinterpreteroptions_gc); + lua_setfield(state, -2, "__gc"); + } + lua_setmetatable(state, -2); + + return options; +} + +static void * pushTFLInterpreter (lua_State *state,const TfLiteModel* model, const TfLiteInterpreterOptions* optional_options) +{ + TfLiteInterpreter* interpreter = TfLiteInterpreterCreate(model,optional_options); + if(interpreter==NULL) printf("TfLiteInterpreterCreate error\n"); + + *(void**)lua_newuserdata(state, sizeof(void*)) = interpreter; + + if(luaL_newmetatable(state, TFLITE_INTERPRETER)){ + lua_pushcfunction(state, tflinterpreter_gc); + lua_setfield(state, -2, "__gc"); + } + lua_setmetatable(state, -2); + + return interpreter; +} + +static void * pushTFLInterpreterInputTensor (lua_State *state,TfLiteInterpreter* interpreter,int index) +{ + TfLiteTensor* input_tensor = + TfLiteInterpreterGetInputTensor(interpreter, index); + + if(input_tensor==NULL) printf("TfLiteInterpreterGetInputTensor error \n"); + + lua_pushlightuserdata(state, input_tensor); + + return input_tensor; +} + +static void * pushTFLInterpreterOutputTensor (lua_State *state,TfLiteInterpreter* interpreter,int index) +{ + TfLiteTensor* output_tensor = + TfLiteInterpreterGetOutputTensor(interpreter, index); + + if(output_tensor==NULL) printf("TfLiteInterpreterGetOutputTensor error \n"); + + lua_pushlightuserdata(state, output_tensor); + + return output_tensor; +} + +//============================================================================== +// TFLITE +//============================================================================== + +static int tflite_modelfromfile(lua_State *state) { + + const char* modelfile=luaL_checkstring(state,1); + + TfLiteModel* model = pushTFLModelFromFile(state,modelfile); + return 1; +} + +static int tflite_createinterpreteroptions(lua_State *state) { + + TfLiteInterpreterOptions* options = pushTFLInterpreterOptions(state); + return 1; +} + +static int tflite_createinterpreter(lua_State *state) { + + TfLiteModel* model=checkTFLModel(state, 1); + TfLiteInterpreterOptions* options =checkTFLInterpreterOptions(state, 2); + + TfLiteInterpreter* interpreter = pushTFLInterpreter(state,model,options); + + return 1; +} + +static int tflite_interpreterallocatetensors(lua_State *state) { + + TfLiteInterpreter* interpreter = checkTFLInterpreter(state,1); + TfLiteStatus ret=TfLiteInterpreterAllocateTensors(interpreter); + if(ret!=kTfLiteOk) + { + if(debug) printf("TFLite AllocateTensor error \n"); + } + return 0; +} + +static int tflite_interpretergetinputtensor(lua_State *state) { + + TfLiteInterpreter* interpreter = checkTFLInterpreter(state,1); + int index=luaL_checkinteger(state, 2); + TfLiteTensor* input_tensor = pushTFLInterpreterInputTensor(state,interpreter,index); + + return 1; +} + +static int tflite_interpretergetoutputtensor(lua_State *state) { + + TfLiteInterpreter* interpreter = checkTFLInterpreter(state,1); + int index=luaL_checkinteger(state, 2); + TfLiteTensor* output_tensor = pushTFLInterpreterOutputTensor(state,interpreter,index); + + return 1; +} + +static int tflite_gettensorsize(lua_State *state) +{ + /*int ndim=TfLiteTensorNumDims(tensor); + printf("Tensor size(%d",TfLiteTensorDim(tensor,0)); + for(int i=1;i> 24) & 0xff; + col.r = (value >> 16) & 0xff; + col.g = (value >> 8) & 0xff; + col.b = (value >> 0) & 0xff; + //return color; + + return col; +} + +void mkpath(const char* dir) { + char tmp[256]; + char *p = NULL; + size_t len; + + snprintf(tmp, sizeof(tmp),"%s",dir); + dirname(tmp); + len = strlen(tmp); + if(tmp[len - 1] == '/') + tmp[len - 1] = 0; + for(p = tmp + 1; *p; p++) + if(*p == '/') { + *p = 0; + #if TARGET_OS_WINDOWS //defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || defined(__MINGW64__) + _mkdir(tmp); + #else + int ret=mkdir(tmp, S_IRWXU); + #endif + *p = '/'; + } + #if TARGET_OS_WINDOWS //defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || defined(__MINGW64__) + _mkdir(tmp); + #else + int ret=mkdir(tmp, S_IRWXU); + + #endif +} + +int writefile(char* filename,char* data) +{ + FILE *handler=fopen(filename,"w"); + if (handler) + { + fprintf(handler,"%s",data); + fclose(handler); + return 0; + } + else printf("Write file %s error: %s\n",filename,strerror(errno)); + + return 1; +} + +int writedatafile(char* filename,char* data,int data_size) +{ + int write_size; + FILE *handler=fopen(filename,"wb"); + if (handler) + { + write_size = fwrite(data, sizeof(char), data_size, handler); + fclose(handler); + if(debug) printf("Write data file: %s,%d,%d\n",filename,data_size,write_size); + return write_size; + } + else printf("Write data %s error: %s\n",filename,strerror(errno)); + + return 0; +} + +char* loadfile(char *filename) +{ + char *buffer = NULL; + int string_size, read_size; + FILE *handler = fopen(filename, "r"); + + if (handler) + { + fseek(handler, 0, SEEK_END); + string_size = ftell(handler); + rewind(handler); + + buffer = (char*) malloc(sizeof(char) * (string_size + 1) ); + if(buffer!=NULL) + { + read_size = fread(buffer, sizeof(char), string_size, handler); + buffer[string_size] = '\0'; + + if (string_size != read_size) + { + free(buffer); + buffer = NULL; + } + } + + fclose(handler); + } + else printf("Load file %s error: %s\n",filename,strerror(errno)); + + return buffer; +} + +int loaddatafile(char *filename,char **buffer) +{ + int data_size, read_size; + FILE *handler = fopen(filename, "r"); + + if (handler) + { + fseek(handler, 0, SEEK_END); + data_size = ftell(handler); + rewind(handler); + + *buffer = (char*) malloc(sizeof(char) * (data_size) ); + if(buffer!=NULL) + { + read_size = fread(*buffer, sizeof(char), data_size, handler); + + if (data_size != read_size) + { + free(*buffer); + *buffer = NULL; + return 0; + } + //if(debug) printf("Load data file: %s,%d,%d\n",filename,data_size,read_size); + if(debug) printf("Load data file: %s\n",filename); + } + fclose(handler); + } + else printf("Load data %s error: %s\n",filename,strerror(errno)); + + return read_size; +} + +void hexDump (const char * desc, const void * addr, const int len) { + int i; + unsigned char buff[17]; + const unsigned char * pc = (const unsigned char *)addr; + + // Output description if given. + + if (desc != NULL) + printf ("%s:\n", desc); + + // Length checks. + + if (len == 0) { + printf(" ZERO LENGTH\n"); + return; + } + else if (len < 0) { + printf(" NEGATIVE LENGTH: %d\n", len); + return; + } + + // Process every byte in the data. + + for (i = 0; i < len; i++) { + // Multiple of 16 means new line (with line offset). + + if ((i % 16) == 0) { + // Don't print ASCII buffer for the "zeroth" line. + + if (i != 0) + printf (" %s\n", buff); + + // Output the offset. + + printf (" %04x ", i); + } + + // Now the hex code for the specific character. + printf (" %02x", pc[i]); + + // And buffer a printable ASCII character for later. + + if ((pc[i] < 0x20) || (pc[i] > 0x7e)) // isprint() may be better. + buff[i % 16] = '.'; + else + buff[i % 16] = pc[i]; + buff[(i % 16) + 1] = '\0'; + } + + // Pad out last line if not exactly 16 characters. + + while ((i % 16) != 0) { + printf (" "); + i++; + } + + // And print the final ASCII buffer. + + printf (" %s\n", buff); +} + +char* concat(const char *s1, const char *s2) +{ + const size_t len1 = strlen(s1); + const size_t len2 = strlen(s2); + char *result = malloc(len1 + len2 + 1); + + if(result!=NULL) + { + memcpy(result, s1, len1); + memcpy(result + len1, s2, len2 + 1); + } + result[len1 + len2]=0; + + return result; +} + +char* concat3(const char *s1, const char *s2, const char *s3) +{ + const size_t len1 = strlen(s1); + const size_t len2 = strlen(s2); + const size_t len3 = strlen(s3); + + char *result = malloc(len1 + len2 + len3 + 1); + + if(result!=NULL) + { + memcpy(result, s1, len1); + memcpy(result + len1, s2, len2); + memcpy(result + len1 + len2, s3, len3 + 1); // +1 to copy the null-terminator + } + result[len1 + len2 + len3]=0; + + return result; +} + +int c_quote(const char* src, char* dest, int maxlen) { + int count = 0; + if(maxlen < 0) { + maxlen = strlen(src)+1; /* add 1 for NULL-terminator */ + } + + while(src && maxlen > 0) { + switch(*src) { + + /* these normal, printable chars just need a slash appended */ + case '\\': + case '\"': + case '\'': + if(dest) { + *dest++ = '\\'; + *dest++ = *src; + } + count += 2; + break; + + /* newlines/tabs and unprintable characters need a special code. + * Use the macro CASE_CHAR defined below. + * The first arg for the macro is the char to compare to, + * the 2nd arg is the char to put in the result string, after the '\' */ +#define CASE_CHAR(c, d) case c:\ + if(dest) {\ + *dest++ = '\\'; *dest++ = (d);\ + }\ +count += 2;\ +break; + /* -------------- */ + CASE_CHAR('\n', 'n'); + CASE_CHAR('\t', 't'); + CASE_CHAR('\b', 'b'); + /* ------------- */ + +#undef CASE_CHAR + + + /* by default, just copy the char over */ + default: + if(dest) { + *dest++ = *src; + } + count++; + } + + ++src; + --maxlen; + } + return count; +} + +//simple encrypt-decrypt function +void encryptDecrypt(char inpString[]) +{ + char xorKey[] = "Key"; + int len = strlen(inpString); + + for (int i = 0; i < len; i++) + { + inpString[i] = inpString[i] ^ xorKey[i%strlen(xorKey)]; + printf("%c",inpString[i]); + } +} + +#ifdef AFFINE +SDL_Point affineTrasformation(SDL_Point point,float affine[3][3]) +{ + SDL_Point ret; + + ret.x=point.x*affine[0][0]+point.y*affine[1][0]+affine[2][0]; + ret.y=point.x*affine[0][1]+point.y*affine[1][1]+affine[2][1]; + + return ret; +} +#endif + +void mult_matrices(float a[3][3], float b[3][3], float result[3][3]) +{ + int i, j, k; + for(i = 0; i < 3; i++) + { + for(j = 0; j < 3; j++) + { + for(k = 0; k < 3; k++) + { + result[i][j] += a[i][k] * b[k][j]; + } + } + } +} + + +void laderman_mul(const float a[3][3],const float b[3][3],float c[3][3]) { + + float m[24]; // not off by one, just wanted to match the index from the paper + + m[1 ]= (a[0][0]+a[0][1]+a[0][2]-a[1][0]-a[1][1]-a[2][1]-a[2][2])*b[1][1]; + m[2 ]= (a[0][0]-a[1][0])*(-b[0][1]+b[1][1]); + m[3 ]= a[1][1]*(-b[0][0]+b[0][1]+b[1][0]-b[1][1]-b[1][2]-b[2][0]+b[2][2]); + m[4 ]= (-a[0][0]+a[1][0]+a[1][1])*(b[0][0]-b[0][1]+b[1][1]); + m[5 ]= (a[1][0]+a[1][1])*(-b[0][0]+b[0][1]); + m[6 ]= a[0][0]*b[0][0]; + m[7 ]= (-a[0][0]+a[2][0]+a[2][1])*(b[0][0]-b[0][2]+b[1][2]); + m[8 ]= (-a[0][0]+a[2][0])*(b[0][2]-b[1][2]); + m[9 ]= (a[2][0]+a[2][1])*(-b[0][0]+b[0][2]); + m[10]= (a[0][0]+a[0][1]+a[0][2]-a[1][1]-a[1][2]-a[2][0]-a[2][1])*b[1][2]; + m[11]= a[2][1]*(-b[0][0]+b[0][2]+b[1][0]-b[1][1]-b[1][2]-b[2][0]+b[2][1]); + m[12]= (-a[0][2]+a[2][1]+a[2][2])*(b[1][1]+b[2][0]-b[2][1]); + m[13]= (a[0][2]-a[2][2])*(b[1][1]-b[2][1]); + m[14]= a[0][2]*b[2][0]; + m[15]= (a[2][1]+a[2][2])*(-b[2][0]+b[2][1]); + m[16]= (-a[0][2]+a[1][1]+a[1][2])*(b[1][2]+b[2][0]-b[2][2]); + m[17]= (a[0][2]-a[1][2])*(b[1][2]-b[2][2]); + m[18]= (a[1][1]+a[1][2])*(-b[2][0]+b[2][2]); + m[19]= a[0][1]*b[1][0]; + m[20]= a[1][2]*b[2][1]; + m[21]= a[1][0]*b[0][2]; + m[22]= a[2][0]*b[0][1]; + m[23]= a[2][2]*b[2][2]; + + c[0][0] = m[6]+m[14]+m[19]; + c[0][1] = m[1]+m[4]+m[5]+m[6]+m[12]+m[14]+m[15]; + c[0][2] = m[6]+m[7]+m[9]+m[10]+m[14]+m[16]+m[18]; + c[1][0] = m[2]+m[3]+m[4]+m[6]+m[14]+m[16]+m[17]; + c[1][1] = m[2]+m[4]+m[5]+m[6]+m[20]; + c[1][2] = m[14]+m[16]+m[17]+m[18]+m[21]; + c[2][0] = m[6]+m[7]+m[8]+m[11]+m[12]+m[13]+m[14]; + c[2][1] = m[12]+m[13]+m[14]+m[15]+m[22]; + c[2][2] = m[6]+m[7]+m[8]+m[9]+m[23]; +} + +SDL_FPoint I2FPoint(SDL_Point p) +{ + SDL_FPoint ret={p.x,p.y}; + return ret; +} +SDL_Point F2IPoint(SDL_FPoint p) +{ + SDL_Point ret={p.x,p.y}; + return ret; +} +SDL_FPoint subtract(SDL_FPoint p0, SDL_FPoint p1) +{ + SDL_FPoint ret={p0.x-p1.x,p0.y-p1.y}; + + return ret; +} + +SDL_FPoint sum(SDL_FPoint p0, SDL_FPoint p1) +{ + SDL_FPoint ret={p0.x+p1.x,p0.y+p1.y}; + + return ret; +} + +SDL_FPoint multiply(SDL_FPoint p, float f) +{ + SDL_FPoint ret={p.x*f,p.y*f}; + + return ret; +} + +float dot(SDL_FPoint p0, SDL_FPoint p1) +{ + float ret=(p0.x*p1.x)+(p0.y*p1.y); + + return ret; +} + +float distance(SDL_FPoint p0,SDL_FPoint p1) +{ + return hypotf(p0.x-p1.x,p0.y-p1.y); +} + +SDL_FPoint normalize(SDL_FPoint p) +{ + float mag=hypotf(p.x,p.y); + SDL_FPoint ret={p.x/mag,p.y/mag}; + + //problema di normalizzazione? vettore spesso 0 + return ret; +} + +float magnitude(SDL_FPoint p) +{ + float len=hypotf(p.x,p.y); + + return len; +} + +SDL_FPoint getClosestPointOnSegment(SDL_FPoint p0, SDL_FPoint p1, SDL_FPoint p) +{ + SDL_FPoint d=subtract(p1,p0); + float c=dot(subtract(p,p0),d)/dot(d,d); + if(c>=1) return p1; + else if(c<=0) return p0; + else return sum(p0,multiply(d,c)); +} + +/*static getClosestPointOnSegment(p0, p1, p) { + let d = p1.subtract(p0); + let c = p.subtract(p0).dot(d) / (d.dot(d)); + if (c >= 1) { + return p1.clone(); + } else if (c <= 0) { + return p0.clone(); + } else { + return p0.add(d.multiply(c)); + } +} +*/ diff --git a/client/update.sh b/client/update.sh deleted file mode 100755 index 3f561cd..0000000 --- a/client/update.sh +++ /dev/null @@ -1,6 +0,0 @@ -git pull -cd build -cmake -DCMAKE_BUILD_TYPE=Debug -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl .. -make -cp ./proteo ../ -cd .. diff --git a/client/upload.sh b/client/upload.sh deleted file mode 100755 index ba9e24d..0000000 --- a/client/upload.sh +++ /dev/null @@ -1,3 +0,0 @@ -git stage * -git commit -git push diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 451d291..43e9e76 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -13,8 +13,7 @@ find_package(CURL REQUIRED) find_package(JSONC REQUIRED) find_package(libmicrohttpd REQUIRED) find_package(SQLite3 REQUIRED) -#find_package(ENet REQUIRED) -find_package(EJDB REQUIRED) +find_package(EJDB) find_package (Threads) find_package(OpenCV REQUIRED) find_package(ZeroMQ REQUIRED) @@ -42,19 +41,71 @@ tensorflowlite HINTS ${CMAKE_SOURCE_DIR}/tflite/lib /usr/lib -/usr/local/lib REQUIRED) +/usr/local/lib) +find_path(DeepSpeech_INCLUDE_DIR +NAMES +deepspeech +HINTS +${CMAKE_SOURCE_DIR}/deepspeech/include +/usr/local/include) + +find_library(DeepSpeech_LIBRARY +NAMES +deepspeech +HINTS +${CMAKE_SOURCE_DIR}/deepspeech/lib +/usr/lib +/usr/local/lib) + +FIND_PACKAGE_HANDLE_STANDARD_ARGS(DEEPSPEECH DEFAULT_MSG DeepSpeech_INCLUDE_DIR) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(TFLITE DEFAULT_MSG TensorFlowLite_INCLUDE_DIR) + +if (OpenCV_VERSION_MAJOR GREATER 4) + add_definitions(-DOPENCV_DNN) +endif() -include_directories(${TensorFlowLite_INCLUDE_DIR} ${AVUTIL_INCLUDE_DIR} ${SWSCALE_INCLUDE_DIR} ${AVCODEC_INCLUDE_DIR} ${LUAJIT_INCLUDE_DIR} ${OPENSSL_INCLUDE_DIR} ${CURL_INCLUDE_DIRS} ${JSONC_INCLUDE_DIRS} ${LIBMICROHTTPD_INCLUDE_DIRS} ${SQLite3_INCLUDE_DIRS} ${EJDB_INCLUDE_DIRS} ${OpenCV_INCLUDE_DIRS} ${ZMQ_INCLUDE_DIRS}) #${ENET_INCLUDE_DIRS} +if(ejdb2_FOUND) + include_directories(${EJDB_INCLUDE_DIRS}) + add_definitions(-DEJDB2) +endif() + +if(DEEPSPEECH_FOUND) + include_directories(${DeepSpeech_INCLUDE_DIR}) + add_definitions(-DDEEPSPEECH) +endif() + +if(TFLITE_FOUND) + include_directories(${TensorFlowLite_INCLUDE_DIR}) + add_definitions(-DTFLITE) +endif() + +include_directories(${AVUTIL_INCLUDE_DIR} ${SWSCALE_INCLUDE_DIR} ${AVCODEC_INCLUDE_DIR} ${LUAJIT_INCLUDE_DIR} ${OPENSSL_INCLUDE_DIR} ${CURL_INCLUDE_DIRS} ${JSONC_INCLUDE_DIRS} ${LIBMICROHTTPD_INCLUDE_DIRS} ${SQLite3_INCLUDE_DIRS} ${OpenCV_INCLUDE_DIRS} ${ZMQ_INCLUDE_DIRS}) #include_directories(${CMAKE_SOURCE_DIR}/darknet) #link_directories(${CMAKE_SOURCE_DIR}/darknet) -#include_directories(${CMAKE_SOURCE_DIR}/tflite/include) -#link_directories(${CMAKE_SOURCE_DIR}/tflite/lib) - #link_directories(${CMAKE_SOURCE_DIR}/usr/local/opt/curl/lib) add_executable(proteo_server proteo_server.c proteo_opencv.cpp) -target_link_libraries(proteo_server ${TensorFlowLite_LIBRARY} ${AVUTIL_LIBRARY} ${SWSCALE_LIBRARY} ${AVCODEC_LIBRARY} ${OPENSSL_LIBRARIES} ${LUAJIT_LIBRARIES} ${CURL_LIBRARIES} ${JSONC_LIBRARIES} ${LIBMICROHTTPD_LIBRARIES} ${SQLite3_LIBRARIES} ${EJDB_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${OpenCV_LIBS} ${ZMQ_LIBRARIES})# dark ${ENET_LIBRARIES} +if(ejdb2_FOUND) + target_link_libraries(proteo_server ${EJDB_LIBRARIES}) +endif() + +if(DEEPSPEECH_FOUND) + target_link_libraries(proteo_server ${DeepSpeech_LIBRARY}) +endif() + +if(TFLITE_FOUND) + target_link_libraries(proteo_server ${TensorFlowLite_LIBRARY}) +endif() + +target_link_libraries(proteo_server ${AVUTIL_LIBRARY} ${SWSCALE_LIBRARY} ${AVCODEC_LIBRARY} ${OPENSSL_LIBRARIES} ${LUAJIT_LIBRARIES} ${CURL_LIBRARIES} ${JSONC_LIBRARIES} ${LIBMICROHTTPD_LIBRARIES} ${SQLite3_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${OpenCV_LIBS} ${ZMQ_LIBRARIES}) + +#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -latomic") + +SET(CPACK_DEBIAN_PACKAGE_DEPENDS libluajit-5.1-dev ,libssl-dev,libcurl4-openssl-dev,libjson-c-dev,libmicrohttpd-dev,libsqlite3-dev,ejdb2,libopencv-dev,libzmq3-dev,libavcodec-dev,libavformat-dev,libavutil-dev,libswresample-dev,libswscale-dev) +SET(CPACK_GENERATOR "DEB") +SET(CPACK_DEBIAN_PACKAGE_MAINTAINER "M") +INCLUDE(CPack) diff --git a/server/install.sh b/server/install.sh index 3ec5aa2..ada26df 100755 --- a/server/install.sh +++ b/server/install.sh @@ -1,13 +1,21 @@ systemctl stop proteo cp ./build/proteo_server /usr/local/bin/ -rm -r /usr/local/etc/proteo -mkdir /usr/local/etc/proteo -cp -r ./lib /usr/local/etc/proteo/ -cp -r ./plugin /usr/local/etc/proteo/ -cp -r ./script /usr/local/etc/proteo/ -cp -r ./web /usr/local/etc/proteo/ -cp favicon.ico /usr/local/etc/proteo/ -cp login.lua /usr/local/etc/proteo/ -cp route.lua /usr/local/etc/proteo/ + +cp /usr/local/etc/Proteo/config.json ./config.bkp || : + +rm -rf /usr/local/etc/Proteo +mkdir /usr/local/etc/Proteo +cp -r ./lib /usr/local/etc/Proteo/ +cp -r ./plugin /usr/local/etc/Proteo/ +cp -r ./script /usr/local/etc/Proteo/ +cp -r ./web /usr/local/etc/Proteo/ +cp -r ./dl /usr/local/etc/Proteo/ +cp favicon.ico /usr/local/etc/Proteo/ +cp login.lua /usr/local/etc/Proteo/ +cp route.lua /usr/local/etc/Proteo/ +cp ticket.lua /usr/local/etc/Proteo/ +cp debugger.lua /usr/local/etc/Proteo/ +cp config.json /usr/local/etc/Proteo/ + cp ./proteo.service /etc/systemd/system/ systemctl start proteo diff --git a/server/lib/FormatMini.lua b/server/lib/FormatMini.lua deleted file mode 100644 index 4d04602..0000000 --- a/server/lib/FormatMini.lua +++ /dev/null @@ -1,365 +0,0 @@ - -local parser = require'ParseLua' -local ParseLua = parser.ParseLua -local util = require'Util' -local lookupify = util.lookupify - --- --- FormatMini.lua --- --- Returns the minified version of an AST. Operations which are performed: --- - All comments and whitespace are ignored --- - All local variables are renamed --- - -local LowerChars = lookupify{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', - 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', - 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'} -local UpperChars = lookupify{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', - 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', - 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'} -local Digits = lookupify{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'} -local Symbols = lookupify{'+', '-', '*', '/', '^', '%', ',', '{', '}', '[', ']', '(', ')', ';', '#'} - -local function Format_Mini(ast) - local formatStatlist, formatExpr; - local count = 0 - -- - local function joinStatementsSafe(a, b, sep) - --print(a, b) - if count > 150 then - count = 0 - return a.."\n"..b - end - sep = sep or ' ' - local aa, bb = a:sub(-1,-1), b:sub(1,1) - if UpperChars[aa] or LowerChars[aa] or aa == '_' then - if not (UpperChars[bb] or LowerChars[bb] or bb == '_' or Digits[bb]) then - --bb is a symbol, can join without sep - return a..b - elseif bb == '(' then - print("==============>>>",aa,bb) - --prevent ambiguous syntax - return a..sep..b - else - return a..sep..b - end - elseif Digits[aa] then - if bb == '(' then - --can join statements directly - return a..b - elseif Symbols[bb] then - return a .. b - else - return a..sep..b - end - elseif aa == '' then - return a..b - else - if bb == '(' then - --don't want to accidentally call last statement, can't join directly - return a..sep..b - else - --print("asdf", '"'..a..'"', '"'..b..'"') - return a..b - end - end - end - - formatExpr = function(expr, precedence) - local precedence = precedence or 0 - local currentPrecedence = 0 - local skipParens = false - local out = "" - if expr.AstType == 'VarExpr' then - if expr.Variable then - out = out..expr.Variable.Name - else - out = out..expr.Name - end - - elseif expr.AstType == 'NumberExpr' then - out = out..expr.Value.Data - - elseif expr.AstType == 'StringExpr' then - out = out..expr.Value.Data - - elseif expr.AstType == 'BooleanExpr' then - out = out..tostring(expr.Value) - - elseif expr.AstType == 'NilExpr' then - out = joinStatementsSafe(out, "nil") - - elseif expr.AstType == 'BinopExpr' then - currentPrecedence = expr.OperatorPrecedence - out = joinStatementsSafe(out, formatExpr(expr.Lhs, currentPrecedence)) - out = joinStatementsSafe(out, expr.Op) - out = joinStatementsSafe(out, formatExpr(expr.Rhs)) - if expr.Op == '^' or expr.Op == '..' then - currentPrecedence = currentPrecedence - 1 - end - - if currentPrecedence < precedence then - skipParens = false - else - skipParens = true - end - --print(skipParens, precedence, currentPrecedence) - elseif expr.AstType == 'UnopExpr' then - out = joinStatementsSafe(out, expr.Op) - out = joinStatementsSafe(out, formatExpr(expr.Rhs)) - - elseif expr.AstType == 'DotsExpr' then - out = out.."..." - - elseif expr.AstType == 'CallExpr' then - out = out..formatExpr(expr.Base) - out = out.."(" - for i = 1, #expr.Arguments do - out = out..formatExpr(expr.Arguments[i]) - if i ~= #expr.Arguments then - out = out.."," - end - end - out = out..")" - - elseif expr.AstType == 'TableCallExpr' then - out = out..formatExpr(expr.Base) - out = out..formatExpr(expr.Arguments[1]) - - elseif expr.AstType == 'StringCallExpr' then - out = out..formatExpr(expr.Base) - out = out..expr.Arguments[1].Data - - elseif expr.AstType == 'IndexExpr' then - out = out..formatExpr(expr.Base).."["..formatExpr(expr.Index).."]" - - elseif expr.AstType == 'MemberExpr' then - out = out..formatExpr(expr.Base)..expr.Indexer..expr.Ident.Data - - elseif expr.AstType == 'Function' then - expr.Scope:ObfuscateVariables() - out = out.."function(" - if #expr.Arguments > 0 then - for i = 1, #expr.Arguments do - out = out..expr.Arguments[i].Name - if i ~= #expr.Arguments then - out = out.."," - elseif expr.VarArg then - out = out..",..." - end - end - elseif expr.VarArg then - out = out.."..." - end - out = out..")" - out = joinStatementsSafe(out, formatStatlist(expr.Body)) - out = joinStatementsSafe(out, "end") - - elseif expr.AstType == 'ConstructorExpr' then - out = out.."{" - for i = 1, #expr.EntryList do - local entry = expr.EntryList[i] - if entry.Type == 'Key' then - out = out.."["..formatExpr(entry.Key).."]="..formatExpr(entry.Value) - elseif entry.Type == 'Value' then - out = out..formatExpr(entry.Value) - elseif entry.Type == 'KeyString' then - out = out..entry.Key.."="..formatExpr(entry.Value) - end - if i ~= #expr.EntryList then - out = out.."," - end - end - out = out.."}" - - elseif expr.AstType == 'Parentheses' then - out = out.."("..formatExpr(expr.Inner)..")" - - end - --print(">>", skipParens, expr.ParenCount, out) - if not skipParens then - --print("hehe") - out = string.rep('(', expr.ParenCount or 0) .. out - out = out .. string.rep(')', expr.ParenCount or 0) - --print("", out) - end - count = count + #out - return --[[print(out) or]] out - end - - local formatStatement = function(statement) - local out = '' - if statement.AstType == 'AssignmentStatement' then - for i = 1, #statement.Lhs do - out = out..formatExpr(statement.Lhs[i]) - if i ~= #statement.Lhs then - out = out.."," - end - end - if #statement.Rhs > 0 then - out = out.."=" - for i = 1, #statement.Rhs do - out = out..formatExpr(statement.Rhs[i]) - if i ~= #statement.Rhs then - out = out.."," - end - end - end - - elseif statement.AstType == 'CallStatement' then - out = formatExpr(statement.Expression) - - elseif statement.AstType == 'LocalStatement' then - out = out.."local " - for i = 1, #statement.LocalList do - out = out..statement.LocalList[i].Name - if i ~= #statement.LocalList then - out = out.."," - end - end - if #statement.InitList > 0 then - out = out.."=" - for i = 1, #statement.InitList do - out = out..formatExpr(statement.InitList[i]) - if i ~= #statement.InitList then - out = out.."," - end - end - end - - elseif statement.AstType == 'IfStatement' then - out = joinStatementsSafe("if", formatExpr(statement.Clauses[1].Condition)) - out = joinStatementsSafe(out, "then") - out = joinStatementsSafe(out, formatStatlist(statement.Clauses[1].Body)) - for i = 2, #statement.Clauses do - local st = statement.Clauses[i] - if st.Condition then - out = joinStatementsSafe(out, "elseif") - out = joinStatementsSafe(out, formatExpr(st.Condition)) - out = joinStatementsSafe(out, "then") - else - out = joinStatementsSafe(out, "else") - end - out = joinStatementsSafe(out, formatStatlist(st.Body)) - end - out = joinStatementsSafe(out, "end") - - elseif statement.AstType == 'WhileStatement' then - out = joinStatementsSafe("while", formatExpr(statement.Condition)) - out = joinStatementsSafe(out, "do") - out = joinStatementsSafe(out, formatStatlist(statement.Body)) - out = joinStatementsSafe(out, "end") - - elseif statement.AstType == 'DoStatement' then - out = joinStatementsSafe(out, "do") - out = joinStatementsSafe(out, formatStatlist(statement.Body)) - out = joinStatementsSafe(out, "end") - - elseif statement.AstType == 'ReturnStatement' then - out = "return" - for i = 1, #statement.Arguments do - out = joinStatementsSafe(out, formatExpr(statement.Arguments[i])) - if i ~= #statement.Arguments then - out = out.."," - end - end - - elseif statement.AstType == 'BreakStatement' then - out = "break" - - elseif statement.AstType == 'RepeatStatement' then - out = "repeat" - out = joinStatementsSafe(out, formatStatlist(statement.Body)) - out = joinStatementsSafe(out, "until") - out = joinStatementsSafe(out, formatExpr(statement.Condition)) - - elseif statement.AstType == 'Function' then - statement.Scope:ObfuscateVariables() - if statement.IsLocal then - out = "local" - end - out = joinStatementsSafe(out, "function ") - if statement.IsLocal then - out = out..statement.Name.Name - else - out = out..formatExpr(statement.Name) - end - out = out.."(" - if #statement.Arguments > 0 then - for i = 1, #statement.Arguments do - out = out..statement.Arguments[i].Name - if i ~= #statement.Arguments then - out = out.."," - elseif statement.VarArg then - --print("Apply vararg") - out = out..",..." - end - end - elseif statement.VarArg then - out = out.."..." - end - out = out..")" - out = joinStatementsSafe(out, formatStatlist(statement.Body)) - out = joinStatementsSafe(out, "end") - - elseif statement.AstType == 'GenericForStatement' then - statement.Scope:ObfuscateVariables() - out = "for " - for i = 1, #statement.VariableList do - out = out..statement.VariableList[i].Name - if i ~= #statement.VariableList then - out = out.."," - end - end - out = out.." in" - for i = 1, #statement.Generators do - out = joinStatementsSafe(out, formatExpr(statement.Generators[i])) - if i ~= #statement.Generators then - out = joinStatementsSafe(out, ',') - end - end - out = joinStatementsSafe(out, "do") - out = joinStatementsSafe(out, formatStatlist(statement.Body)) - out = joinStatementsSafe(out, "end") - - elseif statement.AstType == 'NumericForStatement' then - statement.Scope:ObfuscateVariables() - out = "for " - out = out..statement.Variable.Name.."=" - out = out..formatExpr(statement.Start)..","..formatExpr(statement.End) - if statement.Step then - out = out..","..formatExpr(statement.Step) - end - out = joinStatementsSafe(out, "do") - out = joinStatementsSafe(out, formatStatlist(statement.Body)) - out = joinStatementsSafe(out, "end") - elseif statement.AstType == 'LabelStatement' then - out = getIndentation() .. "::" .. statement.Label .. "::" - elseif statement.AstType == 'GotoStatement' then - out = getIndentation() .. "goto " .. statement.Label - elseif statement.AstType == 'Comment' then - -- ignore - elseif statement.AstType == 'Eof' then - -- ignore - else - print("Unknown AST Type: " .. statement.AstType) - end - count = count + #out - return out - end - - formatStatlist = function(statList) - local out = '' - statList.Scope:ObfuscateVariables() - for _, stat in pairs(statList.Body) do - out = joinStatementsSafe(out, formatStatement(stat), ';') - end - return out - end - - ast.Scope:ObfuscateVariables() - return formatStatlist(ast) -end - -return Format_Mini diff --git a/server/lib/ParseLua.lua b/server/lib/ParseLua.lua deleted file mode 100644 index 6af7ee4..0000000 --- a/server/lib/ParseLua.lua +++ /dev/null @@ -1,1411 +0,0 @@ - --- --- ParseLua.lua --- --- The main lua parser and lexer. --- LexLua returns a Lua token stream, with tokens that preserve --- all whitespace formatting information. --- ParseLua returns an AST, internally relying on LexLua. --- - -require'strict' - -local util = require'Util' -local lookupify = util.lookupify - -local WhiteChars = lookupify{' ', '\n', '\t', '\r'} -local EscapeLookup = {['\r'] = '\\r', ['\n'] = '\\n', ['\t'] = '\\t', ['"'] = '\\"', ["'"] = "\\'"} -local LowerChars = lookupify{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', - 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', - 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'} -local UpperChars = lookupify{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', - 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', - 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'} -local Digits = lookupify{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'} -local HexDigits = lookupify{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - 'A', 'a', 'B', 'b', 'C', 'c', 'D', 'd', 'E', 'e', 'F', 'f'} - -local Symbols = lookupify{'+', '-', '*', '/', '^', '%', ',', '{', '}', '[', ']', '(', ')', ';', '#'} -local Scope = require'Scope' - -local Keywords = lookupify{ - 'and', 'break', 'do', 'else', 'elseif', - 'end', 'false', 'for', 'function', 'goto', 'if', - 'in', 'local', 'nil', 'not', 'or', 'repeat', - 'return', 'then', 'true', 'until', 'while', -}; - -local function LexLua(src) - --token dump - local tokens = {} - - local st, err = pcall(function() - --line / char / pointer tracking - local p = 1 - local line = 1 - local char = 1 - - --get / peek functions - local function get() - local c = src:sub(p,p) - if c == '\n' then - char = 1 - line = line + 1 - else - char = char + 1 - end - p = p + 1 - return c - end - local function peek(n) - n = n or 0 - return src:sub(p+n,p+n) - end - local function consume(chars) - local c = peek() - for i = 1, #chars do - if c == chars:sub(i,i) then return get() end - end - end - - --shared stuff - local function generateError(err) - return error(">> :"..line..":"..char..": "..err, 0) - end - - local function tryGetLongString() - local start = p - if peek() == '[' then - local equalsCount = 0 - local depth = 1 - while peek(equalsCount+1) == '=' do - equalsCount = equalsCount + 1 - end - if peek(equalsCount+1) == '[' then - --start parsing the string. Strip the starting bit - for _ = 0, equalsCount+1 do get() end - - --get the contents - local contentStart = p - while true do - --check for eof - if peek() == '' then - generateError("Expected `]"..string.rep('=', equalsCount).."]` near .", 3) - end - - --check for the end - local foundEnd = true - if peek() == ']' then - for i = 1, equalsCount do - if peek(i) ~= '=' then foundEnd = false end - end - if peek(equalsCount+1) ~= ']' then - foundEnd = false - end - else - if peek() == '[' then - -- is there an embedded long string? - local embedded = true - for i = 1, equalsCount do - if peek(i) ~= '=' then - embedded = false - break - end - end - if peek(equalsCount + 1) == '[' and embedded then - -- oh look, there was - depth = depth + 1 - for i = 1, (equalsCount + 2) do - get() - end - end - end - foundEnd = false - end - -- - if foundEnd then - depth = depth - 1 - if depth == 0 then - break - else - for i = 1, equalsCount + 2 do - get() - end - end - else - get() - end - end - - --get the interior string - local contentString = src:sub(contentStart, p-1) - - --found the end. Get rid of the trailing bit - for i = 0, equalsCount+1 do get() end - - --get the exterior string - local longString = src:sub(start, p-1) - - --return the stuff - return contentString, longString - else - return nil - end - else - return nil - end - end - - --main token emitting loop - while true do - --get leading whitespace. The leading whitespace will include any comments - --preceding the token. This prevents the parser needing to deal with comments - --separately. - local leading = { } - local leadingWhite = '' - local longStr = false - while true do - local c = peek() - if c == '#' and peek(1) == '!' and line == 1 then - -- #! shebang for linux scripts - get() - get() - leadingWhite = "#!" - while peek() ~= '\n' and peek() ~= '' do - leadingWhite = leadingWhite .. get() - end - local token = { - Type = 'Comment', - CommentType = 'Shebang', - Data = leadingWhite, - Line = line, - Char = char - } - token.Print = function() - return "<"..(token.Type .. string.rep(' ', 7-#token.Type)).." "..(token.Data or '').." >" - end - leadingWhite = "" - table.insert(leading, token) - end - if c == ' ' or c == '\t' then - --whitespace - --leadingWhite = leadingWhite..get() - local c2 = get() -- ignore whitespace - table.insert(leading, { Type = 'Whitespace', Line = line, Char = char, Data = c2 }) - elseif c == '\n' or c == '\r' then - local nl = get() - if leadingWhite ~= "" then - local token = { - Type = 'Comment', - CommentType = longStr and 'LongComment' or 'Comment', - Data = leadingWhite, - Line = line, - Char = char, - } - token.Print = function() - return "<"..(token.Type .. string.rep(' ', 7-#token.Type)).." "..(token.Data or '').." >" - end - table.insert(leading, token) - leadingWhite = "" - end - table.insert(leading, { Type = 'Whitespace', Line = line, Char = char, Data = nl }) - elseif c == '-' and peek(1) == '-' then - --comment - get() - get() - leadingWhite = leadingWhite .. '--' - local _, wholeText = tryGetLongString() - if wholeText then - leadingWhite = leadingWhite..wholeText - longStr = true - else - while peek() ~= '\n' and peek() ~= '' do - leadingWhite = leadingWhite..get() - end - end - else - break - end - end - if leadingWhite ~= "" then - local token = { - Type = 'Comment', - CommentType = longStr and 'LongComment' or 'Comment', - Data = leadingWhite, - Line = line, - Char = char, - } - token.Print = function() - return "<"..(token.Type .. string.rep(' ', 7-#token.Type)).." "..(token.Data or '').." >" - end - table.insert(leading, token) - end - - --get the initial char - local thisLine = line - local thisChar = char - local errorAt = ":"..line..":"..char..":> " - local c = peek() - - --symbol to emit - local toEmit = nil - - --branch on type - if c == '' then - --eof - toEmit = { Type = 'Eof' } - - elseif UpperChars[c] or LowerChars[c] or c == '_' then - --ident or keyword - local start = p - repeat - get() - c = peek() - until not (UpperChars[c] or LowerChars[c] or Digits[c] or c == '_') - local dat = src:sub(start, p-1) - if Keywords[dat] then - toEmit = {Type = 'Keyword', Data = dat} - else - toEmit = {Type = 'Ident', Data = dat} - end - - elseif Digits[c] or (peek() == '.' and Digits[peek(1)]) then - --number const - local start = p - if c == '0' and peek(1) == 'x' then - get();get() - while HexDigits[peek()] do get() end - if consume('Pp') then - consume('+-') - while Digits[peek()] do get() end - end - else - while Digits[peek()] do get() end - if consume('.') then - while Digits[peek()] do get() end - end - if consume('Ee') then - consume('+-') - while Digits[peek()] do get() end - end - end - toEmit = {Type = 'Number', Data = src:sub(start, p-1)} - - elseif c == '\'' or c == '\"' then - local start = p - --string const - local delim = get() - local contentStart = p - while true do - local c = get() - if c == '\\' then - get() --get the escape char - elseif c == delim then - break - elseif c == '' then - generateError("Unfinished string near ") - end - end - local content = src:sub(contentStart, p-2) - local constant = src:sub(start, p-1) - toEmit = {Type = 'String', Data = constant, Constant = content} - - elseif c == '[' then - local content, wholetext = tryGetLongString() - if wholetext then - toEmit = {Type = 'String', Data = wholetext, Constant = content} - else - get() - toEmit = {Type = 'Symbol', Data = '['} - end - - elseif consume('>=<') then - if consume('=') then - toEmit = {Type = 'Symbol', Data = c..'='} - else - toEmit = {Type = 'Symbol', Data = c} - end - - elseif consume('~') then - if consume('=') then - toEmit = {Type = 'Symbol', Data = '~='} - else - generateError("Unexpected symbol `~` in source.", 2) - end - - elseif consume('.') then - if consume('.') then - if consume('.') then - toEmit = {Type = 'Symbol', Data = '...'} - else - toEmit = {Type = 'Symbol', Data = '..'} - end - else - toEmit = {Type = 'Symbol', Data = '.'} - end - - elseif consume(':') then - if consume(':') then - toEmit = {Type = 'Symbol', Data = '::'} - else - toEmit = {Type = 'Symbol', Data = ':'} - end - - elseif Symbols[c] then - get() - toEmit = {Type = 'Symbol', Data = c} - - else - local contents, all = tryGetLongString() - if contents then - toEmit = {Type = 'String', Data = all, Constant = contents} - else - generateError("Unexpected Symbol `"..c.."` in source.", 2) - end - end - - --add the emitted symbol, after adding some common data - toEmit.LeadingWhite = leading -- table of leading whitespace/comments - --for k, tok in pairs(leading) do - -- tokens[#tokens + 1] = tok - --end - - toEmit.Line = thisLine - toEmit.Char = thisChar - toEmit.Print = function() - return "<"..(toEmit.Type..string.rep(' ', 7-#toEmit.Type)).." "..(toEmit.Data or '').." >" - end - tokens[#tokens+1] = toEmit - - --halt after eof has been emitted - if toEmit.Type == 'Eof' then break end - end - end) - if not st then - return false, err - end - - --public interface: - local tok = {} - local savedP = {} - local p = 1 - - function tok:getp() - return p - end - - function tok:setp(n) - p = n - end - - function tok:getTokenList() - return tokens - end - - --getters - function tok:Peek(n) - n = n or 0 - return tokens[math.min(#tokens, p+n)] - end - function tok:Get(tokenList) - local t = tokens[p] - p = math.min(p + 1, #tokens) - if tokenList then - table.insert(tokenList, t) - end - return t - end - function tok:Is(t) - return tok:Peek().Type == t - end - - --save / restore points in the stream - function tok:Save() - savedP[#savedP+1] = p - end - function tok:Commit() - savedP[#savedP] = nil - end - function tok:Restore() - p = savedP[#savedP] - savedP[#savedP] = nil - end - - --either return a symbol if there is one, or return true if the requested - --symbol was gotten. - function tok:ConsumeSymbol(symb, tokenList) - local t = self:Peek() - if t.Type == 'Symbol' then - if symb then - if t.Data == symb then - self:Get(tokenList) - return true - else - return nil - end - else - self:Get(tokenList) - return t - end - else - return nil - end - end - - function tok:ConsumeKeyword(kw, tokenList) - local t = self:Peek() - if t.Type == 'Keyword' and t.Data == kw then - self:Get(tokenList) - return true - else - return nil - end - end - - function tok:IsKeyword(kw) - local t = tok:Peek() - return t.Type == 'Keyword' and t.Data == kw - end - - function tok:IsSymbol(s) - local t = tok:Peek() - return t.Type == 'Symbol' and t.Data == s - end - - function tok:IsEof() - return tok:Peek().Type == 'Eof' - end - - return true, tok -end - - -local function ParseLua(src) - local st, tok - if type(src) ~= 'table' then - st, tok = LexLua(src) - else - st, tok = true, src - end - if not st then - return false, tok - end - -- - local function GenerateError(msg) - local err = ">> :"..tok:Peek().Line..":"..tok:Peek().Char..": "..msg.."\n" - --find the line - local lineNum = 0 - if type(src) == 'string' then - for line in src:gmatch("[^\n]*\n?") do - if line:sub(-1,-1) == '\n' then line = line:sub(1,-2) end - lineNum = lineNum+1 - if lineNum == tok:Peek().Line then - err = err..">> `"..line:gsub('\t',' ').."`\n" - for i = 1, tok:Peek().Char do - local c = line:sub(i,i) - if c == '\t' then - err = err..' ' - else - err = err..' ' - end - end - err = err.." ^^^^" - break - end - end - end - return err - end - -- - local VarUid = 0 - -- No longer needed: handled in Scopes now local GlobalVarGetMap = {} - local VarDigits = {'_', 'a', 'b', 'c', 'd'} - local function CreateScope(parent) - --[[ - local scope = {} - scope.Parent = parent - scope.LocalList = {} - scope.LocalMap = {} - - function scope:ObfuscateVariables() - for _, var in pairs(scope.LocalList) do - local id = "" - repeat - local chars = "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuioplkjhgfdsazxcvbnm_" - local chars2 = "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuioplkjhgfdsazxcvbnm_1234567890" - local n = math.random(1, #chars) - id = id .. chars:sub(n, n) - for i = 1, math.random(0,20) do - local n = math.random(1, #chars2) - id = id .. chars2:sub(n, n) - end - until not GlobalVarGetMap[id] and not parent:GetLocal(id) and not scope.LocalMap[id] - var.Name = id - scope.LocalMap[id] = var - end - end - - scope.RenameVars = scope.ObfuscateVariables - - -- Renames a variable from this scope and down. - -- Does not rename global variables. - function scope:RenameVariable(old, newName) - if type(old) == "table" then -- its (theoretically) an AstNode variable - old = old.Name - end - for _, var in pairs(scope.LocalList) do - if var.Name == old then - var.Name = newName - scope.LocalMap[newName] = var - end - end - end - - function scope:GetLocal(name) - --first, try to get my variable - local my = scope.LocalMap[name] - if my then return my end - - --next, try parent - if scope.Parent then - local par = scope.Parent:GetLocal(name) - if par then return par end - end - - return nil - end - - function scope:CreateLocal(name) - --create my own var - local my = {} - my.Scope = scope - my.Name = name - my.CanRename = true - -- - scope.LocalList[#scope.LocalList+1] = my - scope.LocalMap[name] = my - -- - return my - end]] - local scope = Scope:new(parent) - scope.RenameVars = scope.ObfuscateLocals - scope.ObfuscateVariables = scope.ObfuscateLocals - scope.Print = function() return "" end - return scope - end - - local ParseExpr - local ParseStatementList - local ParseSimpleExpr, - ParseSubExpr, - ParsePrimaryExpr, - ParseSuffixedExpr - - local function ParseFunctionArgsAndBody(scope, tokenList) - local funcScope = CreateScope(scope) - if not tok:ConsumeSymbol('(', tokenList) then - return false, GenerateError("`(` expected.") - end - - --arg list - local argList = {} - local isVarArg = false - while not tok:ConsumeSymbol(')', tokenList) do - if tok:Is('Ident') then - local arg = funcScope:CreateLocal(tok:Get(tokenList).Data) - argList[#argList+1] = arg - if not tok:ConsumeSymbol(',', tokenList) then - if tok:ConsumeSymbol(')', tokenList) then - break - else - return false, GenerateError("`)` expected.") - end - end - elseif tok:ConsumeSymbol('...', tokenList) then - isVarArg = true - if not tok:ConsumeSymbol(')', tokenList) then - return false, GenerateError("`...` must be the last argument of a function.") - end - break - else - return false, GenerateError("Argument name or `...` expected") - end - end - - --body - local st, body = ParseStatementList(funcScope) - if not st then return false, body end - - --end - if not tok:ConsumeKeyword('end', tokenList) then - return false, GenerateError("`end` expected after function body") - end - local nodeFunc = {} - nodeFunc.AstType = 'Function' - nodeFunc.Scope = funcScope - nodeFunc.Arguments = argList - nodeFunc.Body = body - nodeFunc.VarArg = isVarArg - nodeFunc.Tokens = tokenList - -- - return true, nodeFunc - end - - - function ParsePrimaryExpr(scope) - local tokenList = {} - - if tok:ConsumeSymbol('(', tokenList) then - local st, ex = ParseExpr(scope) - if not st then return false, ex end - if not tok:ConsumeSymbol(')', tokenList) then - return false, GenerateError("`)` Expected.") - end - if false then - --save the information about parenthesized expressions somewhere - ex.ParenCount = (ex.ParenCount or 0) + 1 - return true, ex - else - local parensExp = {} - parensExp.AstType = 'Parentheses' - parensExp.Inner = ex - parensExp.Tokens = tokenList - return true, parensExp - end - - elseif tok:Is('Ident') then - local id = tok:Get(tokenList) - local var = scope:GetLocal(id.Data) - if not var then - var = scope:GetGlobal(id.Data) - if not var then - var = scope:CreateGlobal(id.Data) - else - var.References = var.References + 1 - end - else - var.References = var.References + 1 - end - -- - local nodePrimExp = {} - nodePrimExp.AstType = 'VarExpr' - nodePrimExp.Name = id.Data - nodePrimExp.Variable = var - nodePrimExp.Tokens = tokenList - -- - return true, nodePrimExp - else - return false, GenerateError("primary expression expected") - end - end - - function ParseSuffixedExpr(scope, onlyDotColon) - --base primary expression - local st, prim = ParsePrimaryExpr(scope) - if not st then return false, prim end - -- - while true do - local tokenList = {} - - if tok:IsSymbol('.') or tok:IsSymbol(':') then - local symb = tok:Get(tokenList).Data - if not tok:Is('Ident') then - return false, GenerateError(" expected.") - end - local id = tok:Get(tokenList) - local nodeIndex = {} - nodeIndex.AstType = 'MemberExpr' - nodeIndex.Base = prim - nodeIndex.Indexer = symb - nodeIndex.Ident = id - nodeIndex.Tokens = tokenList - -- - prim = nodeIndex - - elseif not onlyDotColon and tok:ConsumeSymbol('[', tokenList) then - local st, ex = ParseExpr(scope) - if not st then return false, ex end - if not tok:ConsumeSymbol(']', tokenList) then - return false, GenerateError("`]` expected.") - end - local nodeIndex = {} - nodeIndex.AstType = 'IndexExpr' - nodeIndex.Base = prim - nodeIndex.Index = ex - nodeIndex.Tokens = tokenList - -- - prim = nodeIndex - - elseif not onlyDotColon and tok:ConsumeSymbol('(', tokenList) then - local args = {} - while not tok:ConsumeSymbol(')', tokenList) do - local st, ex = ParseExpr(scope) - if not st then return false, ex end - args[#args+1] = ex - if not tok:ConsumeSymbol(',', tokenList) then - if tok:ConsumeSymbol(')', tokenList) then - break - else - return false, GenerateError("`)` Expected.") - end - end - end - local nodeCall = {} - nodeCall.AstType = 'CallExpr' - nodeCall.Base = prim - nodeCall.Arguments = args - nodeCall.Tokens = tokenList - -- - prim = nodeCall - - elseif not onlyDotColon and tok:Is('String') then - --string call - local nodeCall = {} - nodeCall.AstType = 'StringCallExpr' - nodeCall.Base = prim - nodeCall.Arguments = { tok:Get(tokenList) } - nodeCall.Tokens = tokenList - -- - prim = nodeCall - - elseif not onlyDotColon and tok:IsSymbol('{') then - --table call - local st, ex = ParseSimpleExpr(scope) - -- FIX: ParseExpr(scope) parses the table AND and any following binary expressions. - -- We just want the table - if not st then return false, ex end - local nodeCall = {} - nodeCall.AstType = 'TableCallExpr' - nodeCall.Base = prim - nodeCall.Arguments = { ex } - nodeCall.Tokens = tokenList - -- - prim = nodeCall - - else - break - end - end - return true, prim - end - - - function ParseSimpleExpr(scope) - local tokenList = {} - - if tok:Is('Number') then - local nodeNum = {} - nodeNum.AstType = 'NumberExpr' - nodeNum.Value = tok:Get(tokenList) - nodeNum.Tokens = tokenList - return true, nodeNum - - elseif tok:Is('String') then - local nodeStr = {} - nodeStr.AstType = 'StringExpr' - nodeStr.Value = tok:Get(tokenList) - nodeStr.Tokens = tokenList - return true, nodeStr - - elseif tok:ConsumeKeyword('nil', tokenList) then - local nodeNil = {} - nodeNil.AstType = 'NilExpr' - nodeNil.Tokens = tokenList - return true, nodeNil - - elseif tok:IsKeyword('false') or tok:IsKeyword('true') then - local nodeBoolean = {} - nodeBoolean.AstType = 'BooleanExpr' - nodeBoolean.Value = (tok:Get(tokenList).Data == 'true') - nodeBoolean.Tokens = tokenList - return true, nodeBoolean - - elseif tok:ConsumeSymbol('...', tokenList) then - local nodeDots = {} - nodeDots.AstType = 'DotsExpr' - nodeDots.Tokens = tokenList - return true, nodeDots - - elseif tok:ConsumeSymbol('{', tokenList) then - local v = {} - v.AstType = 'ConstructorExpr' - v.EntryList = {} - -- - while true do - if tok:IsSymbol('[', tokenList) then - --key - tok:Get(tokenList) - local st, key = ParseExpr(scope) - if not st then - return false, GenerateError("Key Expression Expected") - end - if not tok:ConsumeSymbol(']', tokenList) then - return false, GenerateError("`]` Expected") - end - if not tok:ConsumeSymbol('=', tokenList) then - return false, GenerateError("`=` Expected") - end - local st, value = ParseExpr(scope) - if not st then - return false, GenerateError("Value Expression Expected") - end - v.EntryList[#v.EntryList+1] = { - Type = 'Key'; - Key = key; - Value = value; - } - - elseif tok:Is('Ident') then - --value or key - local lookahead = tok:Peek(1) - if lookahead.Type == 'Symbol' and lookahead.Data == '=' then - --we are a key - local key = tok:Get(tokenList) - if not tok:ConsumeSymbol('=', tokenList) then - return false, GenerateError("`=` Expected") - end - local st, value = ParseExpr(scope) - if not st then - return false, GenerateError("Value Expression Expected") - end - v.EntryList[#v.EntryList+1] = { - Type = 'KeyString'; - Key = key.Data; - Value = value; - } - - else - --we are a value - local st, value = ParseExpr(scope) - if not st then - return false, GenerateError("Value Exected") - end - v.EntryList[#v.EntryList+1] = { - Type = 'Value'; - Value = value; - } - - end - elseif tok:ConsumeSymbol('}', tokenList) then - break - - else - --value - local st, value = ParseExpr(scope) - v.EntryList[#v.EntryList+1] = { - Type = 'Value'; - Value = value; - } - if not st then - return false, GenerateError("Value Expected") - end - end - - if tok:ConsumeSymbol(';', tokenList) or tok:ConsumeSymbol(',', tokenList) then - --all is good - elseif tok:ConsumeSymbol('}', tokenList) then - break - else - return false, GenerateError("`}` or table entry Expected") - end - end - v.Tokens = tokenList - return true, v - - elseif tok:ConsumeKeyword('function', tokenList) then - local st, func = ParseFunctionArgsAndBody(scope, tokenList) - if not st then return false, func end - -- - func.IsLocal = true - return true, func - - else - return ParseSuffixedExpr(scope) - end - end - - - local unops = lookupify{'-', 'not', '#'} - local unopprio = 8 - local priority = { - ['+'] = {6,6}; - ['-'] = {6,6}; - ['%'] = {7,7}; - ['/'] = {7,7}; - ['*'] = {7,7}; - ['^'] = {10,9}; - ['..'] = {5,4}; - ['=='] = {3,3}; - ['<'] = {3,3}; - ['<='] = {3,3}; - ['~='] = {3,3}; - ['>'] = {3,3}; - ['>='] = {3,3}; - ['and'] = {2,2}; - ['or'] = {1,1}; - } - function ParseSubExpr(scope, level) - --base item, possibly with unop prefix - local st, exp - if unops[tok:Peek().Data] then - local tokenList = {} - local op = tok:Get(tokenList).Data - st, exp = ParseSubExpr(scope, unopprio) - if not st then return false, exp end - local nodeEx = {} - nodeEx.AstType = 'UnopExpr' - nodeEx.Rhs = exp - nodeEx.Op = op - nodeEx.OperatorPrecedence = unopprio - nodeEx.Tokens = tokenList - exp = nodeEx - else - st, exp = ParseSimpleExpr(scope) - if not st then return false, exp end - end - - --next items in chain - while true do - local prio = priority[tok:Peek().Data] - if prio and prio[1] > level then - local tokenList = {} - local op = tok:Get(tokenList).Data - local st, rhs = ParseSubExpr(scope, prio[2]) - if not st then return false, rhs end - local nodeEx = {} - nodeEx.AstType = 'BinopExpr' - nodeEx.Lhs = exp - nodeEx.Op = op - nodeEx.OperatorPrecedence = prio[1] - nodeEx.Rhs = rhs - nodeEx.Tokens = tokenList - -- - exp = nodeEx - else - break - end - end - - return true, exp - end - - - ParseExpr = function(scope) - return ParseSubExpr(scope, 0) - end - - - local function ParseStatement(scope) - local stat = nil - local tokenList = {} - if tok:ConsumeKeyword('if', tokenList) then - --setup - local nodeIfStat = {} - nodeIfStat.AstType = 'IfStatement' - nodeIfStat.Clauses = {} - - --clauses - repeat - local st, nodeCond = ParseExpr(scope) - if not st then return false, nodeCond end - if not tok:ConsumeKeyword('then', tokenList) then - return false, GenerateError("`then` expected.") - end - local st, nodeBody = ParseStatementList(scope) - if not st then return false, nodeBody end - nodeIfStat.Clauses[#nodeIfStat.Clauses+1] = { - Condition = nodeCond; - Body = nodeBody; - } - until not tok:ConsumeKeyword('elseif', tokenList) - - --else clause - if tok:ConsumeKeyword('else', tokenList) then - local st, nodeBody = ParseStatementList(scope) - if not st then return false, nodeBody end - nodeIfStat.Clauses[#nodeIfStat.Clauses+1] = { - Body = nodeBody; - } - end - - --end - if not tok:ConsumeKeyword('end', tokenList) then - return false, GenerateError("`end` expected.") - end - - nodeIfStat.Tokens = tokenList - stat = nodeIfStat - - elseif tok:ConsumeKeyword('while', tokenList) then - --setup - local nodeWhileStat = {} - nodeWhileStat.AstType = 'WhileStatement' - - --condition - local st, nodeCond = ParseExpr(scope) - if not st then return false, nodeCond end - - --do - if not tok:ConsumeKeyword('do', tokenList) then - return false, GenerateError("`do` expected.") - end - - --body - local st, nodeBody = ParseStatementList(scope) - if not st then return false, nodeBody end - - --end - if not tok:ConsumeKeyword('end', tokenList) then - return false, GenerateError("`end` expected.") - end - - --return - nodeWhileStat.Condition = nodeCond - nodeWhileStat.Body = nodeBody - nodeWhileStat.Tokens = tokenList - stat = nodeWhileStat - - elseif tok:ConsumeKeyword('do', tokenList) then - --do block - local st, nodeBlock = ParseStatementList(scope) - if not st then return false, nodeBlock end - if not tok:ConsumeKeyword('end', tokenList) then - return false, GenerateError("`end` expected.") - end - - local nodeDoStat = {} - nodeDoStat.AstType = 'DoStatement' - nodeDoStat.Body = nodeBlock - nodeDoStat.Tokens = tokenList - stat = nodeDoStat - - elseif tok:ConsumeKeyword('for', tokenList) then - --for block - if not tok:Is('Ident') then - return false, GenerateError(" expected.") - end - local baseVarName = tok:Get(tokenList) - if tok:ConsumeSymbol('=', tokenList) then - --numeric for - local forScope = CreateScope(scope) - local forVar = forScope:CreateLocal(baseVarName.Data) - -- - local st, startEx = ParseExpr(scope) - if not st then return false, startEx end - if not tok:ConsumeSymbol(',', tokenList) then - return false, GenerateError("`,` Expected") - end - local st, endEx = ParseExpr(scope) - if not st then return false, endEx end - local st, stepEx; - if tok:ConsumeSymbol(',', tokenList) then - st, stepEx = ParseExpr(scope) - if not st then return false, stepEx end - end - if not tok:ConsumeKeyword('do', tokenList) then - return false, GenerateError("`do` expected") - end - -- - local st, body = ParseStatementList(forScope) - if not st then return false, body end - if not tok:ConsumeKeyword('end', tokenList) then - return false, GenerateError("`end` expected") - end - -- - local nodeFor = {} - nodeFor.AstType = 'NumericForStatement' - nodeFor.Scope = forScope - nodeFor.Variable = forVar - nodeFor.Start = startEx - nodeFor.End = endEx - nodeFor.Step = stepEx - nodeFor.Body = body - nodeFor.Tokens = tokenList - stat = nodeFor - else - --generic for - local forScope = CreateScope(scope) - -- - local varList = { forScope:CreateLocal(baseVarName.Data) } - while tok:ConsumeSymbol(',', tokenList) do - if not tok:Is('Ident') then - return false, GenerateError("for variable expected.") - end - varList[#varList+1] = forScope:CreateLocal(tok:Get(tokenList).Data) - end - if not tok:ConsumeKeyword('in', tokenList) then - return false, GenerateError("`in` expected.") - end - local generators = {} - local st, firstGenerator = ParseExpr(scope) - if not st then return false, firstGenerator end - generators[#generators+1] = firstGenerator - while tok:ConsumeSymbol(',', tokenList) do - local st, gen = ParseExpr(scope) - if not st then return false, gen end - generators[#generators+1] = gen - end - if not tok:ConsumeKeyword('do', tokenList) then - return false, GenerateError("`do` expected.") - end - local st, body = ParseStatementList(forScope) - if not st then return false, body end - if not tok:ConsumeKeyword('end', tokenList) then - return false, GenerateError("`end` expected.") - end - -- - local nodeFor = {} - nodeFor.AstType = 'GenericForStatement' - nodeFor.Scope = forScope - nodeFor.VariableList = varList - nodeFor.Generators = generators - nodeFor.Body = body - nodeFor.Tokens = tokenList - stat = nodeFor - end - - elseif tok:ConsumeKeyword('repeat', tokenList) then - local st, body = ParseStatementList(scope) - if not st then return false, body end - -- - if not tok:ConsumeKeyword('until', tokenList) then - return false, GenerateError("`until` expected.") - end - -- FIX: Used to parse in parent scope - -- Now parses in repeat scope - local st, cond = ParseExpr(body.Scope) - if not st then return false, cond end - -- - local nodeRepeat = {} - nodeRepeat.AstType = 'RepeatStatement' - nodeRepeat.Condition = cond - nodeRepeat.Body = body - nodeRepeat.Tokens = tokenList - stat = nodeRepeat - - elseif tok:ConsumeKeyword('function', tokenList) then - if not tok:Is('Ident') then - return false, GenerateError("Function name expected") - end - local st, name = ParseSuffixedExpr(scope, true) --true => only dots and colons - if not st then return false, name end - -- - local st, func = ParseFunctionArgsAndBody(scope, tokenList) - if not st then return false, func end - -- - func.IsLocal = false - func.Name = name - stat = func - - elseif tok:ConsumeKeyword('local', tokenList) then - if tok:Is('Ident') then - local varList = { tok:Get(tokenList).Data } - while tok:ConsumeSymbol(',', tokenList) do - if not tok:Is('Ident') then - return false, GenerateError("local var name expected") - end - varList[#varList+1] = tok:Get(tokenList).Data - end - - local initList = {} - if tok:ConsumeSymbol('=', tokenList) then - repeat - local st, ex = ParseExpr(scope) - if not st then return false, ex end - initList[#initList+1] = ex - until not tok:ConsumeSymbol(',', tokenList) - end - - --now patch var list - --we can't do this before getting the init list, because the init list does not - --have the locals themselves in scope. - for i, v in pairs(varList) do - varList[i] = scope:CreateLocal(v) - end - - local nodeLocal = {} - nodeLocal.AstType = 'LocalStatement' - nodeLocal.LocalList = varList - nodeLocal.InitList = initList - nodeLocal.Tokens = tokenList - -- - stat = nodeLocal - - elseif tok:ConsumeKeyword('function', tokenList) then - if not tok:Is('Ident') then - return false, GenerateError("Function name expected") - end - local name = tok:Get(tokenList).Data - local localVar = scope:CreateLocal(name) - -- - local st, func = ParseFunctionArgsAndBody(scope, tokenList) - if not st then return false, func end - -- - func.Name = localVar - func.IsLocal = true - stat = func - - else - return false, GenerateError("local var or function def expected") - end - - elseif tok:ConsumeSymbol('::', tokenList) then - if not tok:Is('Ident') then - return false, GenerateError('Label name expected') - end - local label = tok:Get(tokenList).Data - if not tok:ConsumeSymbol('::', tokenList) then - return false, GenerateError("`::` expected") - end - local nodeLabel = {} - nodeLabel.AstType = 'LabelStatement' - nodeLabel.Label = label - nodeLabel.Tokens = tokenList - stat = nodeLabel - - elseif tok:ConsumeKeyword('return', tokenList) then - local exList = {} - if not tok:IsKeyword('end') then - local st, firstEx = ParseExpr(scope) - if st then - exList[1] = firstEx - while tok:ConsumeSymbol(',', tokenList) do - local st, ex = ParseExpr(scope) - if not st then return false, ex end - exList[#exList+1] = ex - end - end - end - - local nodeReturn = {} - nodeReturn.AstType = 'ReturnStatement' - nodeReturn.Arguments = exList - nodeReturn.Tokens = tokenList - stat = nodeReturn - - elseif tok:ConsumeKeyword('break', tokenList) then - local nodeBreak = {} - nodeBreak.AstType = 'BreakStatement' - nodeBreak.Tokens = tokenList - stat = nodeBreak - - elseif tok:ConsumeKeyword('goto', tokenList) then - if not tok:Is('Ident') then - return false, GenerateError("Label expected") - end - local label = tok:Get(tokenList).Data - local nodeGoto = {} - nodeGoto.AstType = 'GotoStatement' - nodeGoto.Label = label - nodeGoto.Tokens = tokenList - stat = nodeGoto - - else - --statementParseExpr - local st, suffixed = ParseSuffixedExpr(scope) - if not st then return false, suffixed end - - --assignment or call? - if tok:IsSymbol(',') or tok:IsSymbol('=') then - --check that it was not parenthesized, making it not an lvalue - if (suffixed.ParenCount or 0) > 0 then - return false, GenerateError("Can not assign to parenthesized expression, is not an lvalue") - end - - --more processing needed - local lhs = { suffixed } - while tok:ConsumeSymbol(',', tokenList) do - local st, lhsPart = ParseSuffixedExpr(scope) - if not st then return false, lhsPart end - lhs[#lhs+1] = lhsPart - end - - --equals - if not tok:ConsumeSymbol('=', tokenList) then - return false, GenerateError("`=` Expected.") - end - - --rhs - local rhs = {} - local st, firstRhs = ParseExpr(scope) - if not st then return false, firstRhs end - rhs[1] = firstRhs - while tok:ConsumeSymbol(',', tokenList) do - local st, rhsPart = ParseExpr(scope) - if not st then return false, rhsPart end - rhs[#rhs+1] = rhsPart - end - - --done - local nodeAssign = {} - nodeAssign.AstType = 'AssignmentStatement' - nodeAssign.Lhs = lhs - nodeAssign.Rhs = rhs - nodeAssign.Tokens = tokenList - stat = nodeAssign - - elseif suffixed.AstType == 'CallExpr' or - suffixed.AstType == 'TableCallExpr' or - suffixed.AstType == 'StringCallExpr' - then - --it's a call statement - local nodeCall = {} - nodeCall.AstType = 'CallStatement' - nodeCall.Expression = suffixed - nodeCall.Tokens = tokenList - stat = nodeCall - else - return false, GenerateError("Assignment Statement Expected") - end - end - - if tok:IsSymbol(';') then - stat.Semicolon = tok:Get( stat.Tokens ) - end - return true, stat - end - - - local statListCloseKeywords = lookupify{'end', 'else', 'elseif', 'until'} - - ParseStatementList = function(scope) - local nodeStatlist = {} - nodeStatlist.Scope = CreateScope(scope) - nodeStatlist.AstType = 'Statlist' - nodeStatlist.Body = { } - nodeStatlist.Tokens = { } - -- - --local stats = {} - -- - while not statListCloseKeywords[tok:Peek().Data] and not tok:IsEof() do - local st, nodeStatement = ParseStatement(nodeStatlist.Scope) - if not st then return false, nodeStatement end - --stats[#stats+1] = nodeStatement - nodeStatlist.Body[#nodeStatlist.Body + 1] = nodeStatement - end - - if tok:IsEof() then - local nodeEof = {} - nodeEof.AstType = 'Eof' - nodeEof.Tokens = { tok:Get() } - nodeStatlist.Body[#nodeStatlist.Body + 1] = nodeEof - end - - -- - --nodeStatlist.Body = stats - return true, nodeStatlist - end - - - local function mainfunc() - local topScope = CreateScope() - return ParseStatementList(topScope) - end - - local st, main = mainfunc() - --print("Last Token: "..PrintTable(tok:Peek())) - return st, main -end - -return { LexLua = LexLua, ParseLua = ParseLua } - diff --git a/server/lib/Scope.lua b/server/lib/Scope.lua deleted file mode 100644 index c4b8444..0000000 --- a/server/lib/Scope.lua +++ /dev/null @@ -1,195 +0,0 @@ -local Scope = { - new = function(self, parent) - local s = { - Parent = parent, - Locals = { }, - Globals = { }, - oldLocalNamesMap = { }, - oldGlobalNamesMap = { }, - Children = { }, - } - - if parent then - table.insert(parent.Children, s) - end - - return setmetatable(s, { __index = self }) - end, - - AddLocal = function(self, v) - table.insert(self.Locals, v) - end, - - AddGlobal = function(self, v) - table.insert(self.Globals, v) - end, - - CreateLocal = function(self, name) - local v - v = self:GetLocal(name) - if v then return v end - v = { } - v.Scope = self - v.Name = name - v.IsGlobal = false - v.CanRename = true - v.References = 1 - self:AddLocal(v) - return v - end, - - GetLocal = function(self, name) - for k, var in pairs(self.Locals) do - if var.Name == name then return var end - end - - if self.Parent then - return self.Parent:GetLocal(name) - end - end, - - GetOldLocal = function(self, name) - if self.oldLocalNamesMap[name] then - return self.oldLocalNamesMap[name] - end - return self:GetLocal(name) - end, - - mapLocal = function(self, name, var) - self.oldLocalNamesMap[name] = var - end, - - GetOldGlobal = function(self, name) - if self.oldGlobalNamesMap[name] then - return self.oldGlobalNamesMap[name] - end - return self:GetGlobal(name) - end, - - mapGlobal = function(self, name, var) - self.oldGlobalNamesMap[name] = var - end, - - GetOldVariable = function(self, name) - return self:GetOldLocal(name) or self:GetOldGlobal(name) - end, - - RenameLocal = function(self, oldName, newName) - oldName = type(oldName) == 'string' and oldName or oldName.Name - local found = false - local var = self:GetLocal(oldName) - if var then - var.Name = newName - self:mapLocal(oldName, var) - found = true - end - if not found and self.Parent then - self.Parent:RenameLocal(oldName, newName) - end - end, - - RenameGlobal = function(self, oldName, newName) - oldName = type(oldName) == 'string' and oldName or oldName.Name - local found = false - local var = self:GetGlobal(oldName) - if var then - var.Name = newName - self:mapGlobal(oldName, var) - found = true - end - if not found and self.Parent then - self.Parent:RenameGlobal(oldName, newName) - end - end, - - RenameVariable = function(self, oldName, newName) - oldName = type(oldName) == 'string' and oldName or oldName.Name - if self:GetLocal(oldName) then - self:RenameLocal(oldName, newName) - else - self:RenameGlobal(oldName, newName) - end - end, - - GetAllVariables = function(self) - local ret = self:getVars(true) -- down - for k, v in pairs(self:getVars(false)) do -- up - table.insert(ret, v) - end - return ret - end, - - getVars = function(self, top) - local ret = { } - if top then - for k, v in pairs(self.Children) do - for k2, v2 in pairs(v:getVars(true)) do - table.insert(ret, v2) - end - end - else - for k, v in pairs(self.Locals) do - table.insert(ret, v) - end - for k, v in pairs(self.Globals) do - table.insert(ret, v) - end - if self.Parent then - for k, v in pairs(self.Parent:getVars(false)) do - table.insert(ret, v) - end - end - end - return ret - end, - - CreateGlobal = function(self, name) - local v - v = self:GetGlobal(name) - if v then return v end - v = { } - v.Scope = self - v.Name = name - v.IsGlobal = true - v.CanRename = true - v.References = 1 - self:AddGlobal(v) - return v - end, - - GetGlobal = function(self, name) - for k, v in pairs(self.Globals) do - if v.Name == name then return v end - end - - if self.Parent then - return self.Parent:GetGlobal(name) - end - end, - - GetVariable = function(self, name) - return self:GetLocal(name) or self:GetGlobal(name) - end, - - ObfuscateLocals = function(self, recommendedMaxLength, validNameChars) - recommendedMaxLength = recommendedMaxLength or 7 - local chars = validNameChars or "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuioplkjhgfdsazxcvbnm_" - local chars2 = validNameChars or "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuioplkjhgfdsazxcvbnm_1234567890" - for _, var in pairs(self.Locals) do - local id = "" - local tries = 0 - repeat - local n = math.random(1, #chars) - id = id .. chars:sub(n, n) - for i = 1, math.random(0, tries > 5 and 30 or recommendedMaxLength) do - local n = math.random(1, #chars2) - id = id .. chars2:sub(n, n) - end - tries = tries + 1 - until not self:GetVariable(id) - self:RenameLocal(var.Name, id) - end - end, -} - -return Scope diff --git a/server/lib/WebForm.lua b/server/lib/WebForm.lua deleted file mode 100644 index e8fef17..0000000 --- a/server/lib/WebForm.lua +++ /dev/null @@ -1,181 +0,0 @@ -gForms={} -if Form==nil then - function Form(name) - local self = {}; - self.name = name; - self.controls = {} - self.backgroundColor='Black' - gForms[#gForms+1]=self - function self:addControl(control) - self.controls[#self.controls+1]=control - end - function self:show() - print("SHOW: " .. self.name) - for i=1,#gForms do - gForms[i]:hide(true) - end - self:hide(false) - end - function self:hide(hide) - for i=1,#self.controls do - proteo.gui.setHidden(self.controls[i],hide) - end - if not hide then - proteo.graphics.setBackground(self.backgroundColor) - end - end - function self:is_A(t) - return false - end - return self; - end -end - -if GridForm==nil then - function GridForm(name,width,height) - local self = {}; - self.height=height - self.width=width - self.name=name - self.backgroundColor='Black' - self.margin=5 - self.grid = {} - self.anchor = {} - self.grid_lines = {} - self.grid_rectangles= {} - self.grid_rectangles_y= {} - self.hiddenGrid=true - self.y_scroll=0 - gForms[#gForms+1]=self - - function self:clear() - self.grid = {} - self.anchor = {} - self:removeGrid() - self.grid_rectangles= {} - end - function self:copy() - local grid = GridForm() - for x,y,v in self:iterate() do - grid:set(x,y,v) - end - return grid - end - function self:removeGrid() - for i=1,#self.grid_lines do - proteo.system.remove(self.grid_lines[i]) - end - self.grid_lines={} - end - function self:createGrid() - self:removeGrid() - - local h=(MAX_Y / self.height) - local v=(MAX_X / self.width) - --H - for i=1,self.height-1 do - lineh=proteo.graphics.newLine(self.name .."_hline"..i,"White",1.0,MIN_X,i*h,MAX_X,i*h) - self.grid_lines[#self.grid_lines +1]=lineh - end - --V - for i=1,self.width-1 do - linev=proteo.graphics.newLine(self.name .."_vline"..i,"White",1.0,i*v,MIN_Y,i*v,MAX_Y) - self.grid_lines[#self.grid_lines +1]=linev - end - end - function self:addControl(x, y,anchor, value) - if not self.grid[x] then self.grid[x] = {} end - self.grid[x][y] = value - - if not self.anchor[x] then self.anchor[x] = {} end - self.anchor[x][y] = anchor - - if x>self.width then self.width=x end - if y>self.height then self.height=y end - end - function self:iterate() - local x, row = next(self.grid) - if x == nil then return function() end end - local y, val - return function() - repeat - y,val = next(row,y) - if y == nil then x,row = next(self.grid, x) end - until (val and x and y) or (not val and not x and not y) - return x,y,val - end - end - function self:updatePosition() - for x, y, v in self:iterate() do - local x_anchor=0 - local y_anchor=0 - local x_margin=0 - local y_margin=0 - - if self.anchor[x][y] == proteo.gui.ControlAnchor.Center then x_anchor=0.5 y_anchor=0.5 - elseif self.anchor[x][y] == proteo.gui.ControlAnchor.CenterLeft then x_anchor=1 y_anchor=0.5 x_margin=self.margin end - - local pos_x= x_margin + (MAX_X / self.width) * (x - x_anchor) - local pos_y= y_margin + (MAX_Y / self.height) * (y - y_anchor) - proteo.gui.setPosition(v,pos_x,pos_y+self.y_scroll,self.anchor[x][y]) - end - - for i=1,#self.grid_rectangles do - pos=proteo.graphics.getPosition(self.grid_rectangles[i]) - --print('1:'..pos[0]..' 2: '..pos[1]) - proteo.graphics.setPosition(self.grid_rectangles[i],pos[1],self.grid_rectangles_y[i]+self.y_scroll) - end - end - function self:setColor(color,from_x,from_y,to_x,to_y) - local h=(MAX_Y / self.height) - local v=(MAX_X / self.width) - - crect=proteo.graphics.newRect(self.name .."_crect",color,color,(from_x-1)*v,(from_y-1)*h,(to_x-from_x+1)*v,(to_y-from_y+1)*h) - self.grid_rectangles[#self.grid_rectangles +1]=crect - self.grid_rectangles_y[#self.grid_rectangles]=(from_y-1)*h - end - function self:is_A(t) - return true - end - function self:scroll(y_scroll) - self.y_scroll=y_scroll - - self:updatePosition() - end - function self:show() - print("SHOW: " .. self.name) - for i=1,#gForms do - gForms[i]:hide(true) - end - self:hide(false) - end - function self:hideGrid(hide) - self.hiddenGrid=hide - end - function self:hide(hide) - for x, y, v in self:iterate() do - proteo.gui.setHidden(v,hide) - end - - for i=1,#self.grid_rectangles do - proteo.graphics.setHidden(self.grid_rectangles[i],hide) - end - - local tmp_hide=true - if hide then - tmp_hide=true - else - tmp_hide=self.hiddenGrid - end - - for i=1,#self.grid_lines do - proteo.graphics.setHidden(self.grid_lines[i],tmp_hide) - end - - if not hide then - proteo.graphics.setBackground(self.backgroundColor) - end - end - return self; - end -end diff --git a/server/lib/class.lua b/server/lib/class.lua deleted file mode 100644 index e384af7..0000000 --- a/server/lib/class.lua +++ /dev/null @@ -1,183 +0,0 @@ -------------------------------------------------------------------------------- --- Lua with Classes --- lclass --- Author: Andrew McWatters -------------------------------------------------------------------------------- -local setmetatable = setmetatable -local package = package -local type = type -local error = error -local pcall = pcall -local unpack = unpack -local newproxy = newproxy -local string = string -local rawget = rawget -local ipairs = ipairs -local module = module -local _G = _G - -------------------------------------------------------------------------------- --- new() --- Purpose: Creates an object --- Input: metatable --- Output: object -------------------------------------------------------------------------------- -local function new( metatable ) - local object = {} - setmetatable( object, metatable ) - return object -end - -------------------------------------------------------------------------------- --- getbaseclass() --- Purpose: Get a base class --- Input: class - Class metatable --- Output: class -------------------------------------------------------------------------------- -local function getbaseclass( class ) - local name = class.__base - return package.loaded[ name ] -end - -_G.getbaseclass = getbaseclass - -------------------------------------------------------------------------------- --- eventnames --- Purpose: Provide a list of all inheritable internal event names -------------------------------------------------------------------------------- -local eventnames = { - "__add", "__sub", "__mul", "__div", "__mod", - "__pow", "__unm", "__len", "__lt", "__le", - "__concat", "__call", - "__tostring" -} - -------------------------------------------------------------------------------- --- metamethod() --- Purpose: Creates a filler metamethod for metamethod inheritance --- Input: class - Class metatable --- eventname - Event name --- Output: function -------------------------------------------------------------------------------- -local function metamethod( class, eventname ) - return function( ... ) - local event = nil - local base = getbaseclass( class ) - while ( base ~= nil ) do - if ( base[ eventname ] ) then - event = base[ eventname ] - break - end - base = getbaseclass( base ) - end - local type = type( event ) - if ( type ~= "function" ) then - error( "attempt to call metamethod '" .. eventname .. "' " .. - "(a " .. type .. " value)", 2 ) - end - local returns = { pcall( event, ... ) } - if ( returns[ 1 ] ~= true ) then - error( returns[ 2 ], 2 ) - else - return unpack( returns, 2 ) - end - end -end - -------------------------------------------------------------------------------- --- setproxy() --- Purpose: Set a proxy for __gc --- Input: object -------------------------------------------------------------------------------- -local function setproxy( object ) - local __newproxy = newproxy( true ) - local metatable = getmetatable( __newproxy ) - metatable.__gc = function() - local metatable = getmetatable( object ) - metatable.__gc( object ) - end - object.__newproxy = __newproxy -end - -_G.setproxy = setproxy - -------------------------------------------------------------------------------- --- package.class --- Purpose: Turns a module into a class --- Input: module - Module table -------------------------------------------------------------------------------- -function package.class( module ) - module.__index = module - module.__type = string.gsub( module._NAME, module._PACKAGE, "" ) - -- Create a shortcut to name() - setmetatable( module, { - __call = function( self, ... ) - -- Create an instance of this object - local object = new( self ) - -- Call its constructor (function name:name( ... ) ... end) if it - -- exists - local constructor = rawget( self, self.__type ) - if ( constructor ~= nil ) then - local type = type( constructor ) - if ( type ~= "function" ) then - error( "attempt to call constructor '" .. name .. "' " .. - "(a " .. type .. " value)", 2 ) - end - constructor( object, ... ) - end - -- Return the instance - return object - end - } ) -end - -------------------------------------------------------------------------------- --- package.inherit --- Purpose: Sets a base class --- Input: base - Class name -------------------------------------------------------------------------------- -function package.inherit( base ) - return function( module ) - -- Set our base class - module.__base = base - -- Overwrite our existing __index value with a metamethod which checks - -- our members, metatable, and base class, in that order, a la behavior - -- via the Lua 5.1 manual's illustrative code for indexing access - module.__index = function( table, key ) - local v = rawget( module, key ) - if ( v ~= nil ) then return v end - local baseclass = getbaseclass( module ) - if ( baseclass == nil ) then - error( "attempt to index base class '" .. base .. "' " .. - "(a nil value)", 2 ) - end - local h = rawget( baseclass, "__index" ) - if ( h == nil ) then return nil end - if ( type( h ) == "function" ) then - return h( table, key ) - else - return h[ key ] - end - end - -- Create inheritable metamethods - for _, event in ipairs( eventnames ) do - module[ event ] = metamethod( module, event ) - end - end -end - -------------------------------------------------------------------------------- --- class() --- Purpose: Creates a class --- Input: name - Name of class -------------------------------------------------------------------------------- -function class( name ) - local function setmodule( name ) - module( name, package.class ) - end setmodule( name ) - -- For syntactic sugar, return a function to set inheritance - return function( base ) - local _M = package.loaded[ name ] - package.inherit( base )( _M ) - end -end diff --git a/server/lib/classy.lua b/server/lib/classy.lua deleted file mode 100644 index 98b906f..0000000 --- a/server/lib/classy.lua +++ /dev/null @@ -1,527 +0,0 @@ --- class-based OO module for Lua - --- cache globals -local assert = assert -local V = assert( _VERSION ) -local setmetatable = assert( setmetatable ) -local select = assert( select ) -local pairs = assert( pairs ) -local ipairs = assert( ipairs ) -local type = assert( type ) -local error = assert( error ) -local load = assert( load ) -local s_rep = assert( string.rep ) -local t_unpack = assert( V == "Lua 5.1" and unpack or table.unpack ) - - --- list of all metamethods that a user of this library is allowed to --- add to a class -local allowed_metamethods = { - __add = true, __sub = true, __mul = true, __div = true, - __mod = true, __pow = true, __unm = true, __concat = true, - __len = true, __eq = true, __lt = true, __le = true, __call = true, - __tostring = true, __pairs = true, __ipairs = true, __gc = true, - __newindex = true, __metatable = true, __idiv = true, __band = true, - __bor = true, __bxor = true, __bnot = true, __shl = true, - __shr = true, -} - --- this metatable is (re-)used often: -local mode_k_meta = { __mode = "k" } - --- store information for every registered class (still in use) --- [ cls ] = { --- -- the name of the class --- name = "clsname", --- -- an array of superclasses in an order suitable for method --- -- lookup, the first n are direct superclasses (parents) --- super = { n = 2, super1, super2, super1_1, super1_2 }, --- -- a set of subclasses (value is "inheritance difference") --- sub = { [ subcls1 ] = 1, [ subcls2 ] = 2 }, -- mode="k" --- -- direct member functions/variables for this class --- members = {}, --- -- the metatable for objects of this class --- o_meta = { __index = {} }, --- -- the metatable for the class itself --- c_meta = { __index = ..., __call = ..., __newindex = ... }, --- } -local classinfo = setmetatable( {}, mode_k_meta ) - - --- object constructor for the class if no custom __init function is --- defined -local function default_constructor( meta ) - return function() - return setmetatable( {}, meta ) - end -end - --- object constructor for the class if a custom __init function is --- available -local function init_constructor( meta, init ) - return function( _, ... ) - local o = setmetatable( {}, meta ) - init( o, ... ) - return o - end -end - - --- propagate a changed method to a sub class -local function propagate_update( cls, key ) - local info = classinfo[ cls ] - if info.members[ key ] ~= nil then - info.o_meta.__index[ key ] = info.members[ key ] - else - for i = 1, #info.super do - local val = classinfo[ info.super[ i ] ].members[ key ] - if val ~= nil then - info.o_meta.__index[ key ] = val - return - end - end - info.o_meta.__index[ key ] = nil - end -end - - --- __newindex handler for class proxy tables, allowing to set certain --- metamethods, initializers, and normal members. updates sub classes! -local function class_newindex( cls, key, val ) - local info = classinfo[ cls ] - if allowed_metamethods[ key ] then - assert( info.o_meta[ key ] == nil, - "overwriting metamethods not allowed" ) - info.o_meta[ key ] = val - elseif key == "__init" then - info.members.__init = val - info.o_meta.__index.__init = val - if type( val ) == "function" then - info.c_meta.__call = init_constructor( info.o_meta, val ) - else - info.c_meta.__call = default_constructor( info.o_meta ) - end - else - assert( key ~= "__class", "key '__class' is reserved" ) - info.members[ key ] = val - propagate_update( cls, key ) - for sub in pairs( info.sub ) do - propagate_update( sub, key ) - end - end -end - - --- __pairs/__ipairs metamethods for iterating members of classes -local function class_pairs( cls ) - return pairs( classinfo[ cls ].o_meta.__index ) -end - -local function class_ipairs( cls ) - return ipairs( classinfo[ cls ].o_meta.__index ) -end - - --- put the inheritance tree into a flat array using a width-first --- iteration (similar to a binary heap); also set the "inheritance --- difference" in superclasses -local function linearize_ancestors( cls, super, ... ) - local n = select( '#', ... ) - for i = 1, n do - local pcls = select( i, ... ) - assert( classinfo[ pcls ], "invalid class" ) - super[ i ] = pcls - end - super.n = n - local diff, newn = 1, n - for i,p in ipairs( super ) do - local pinfo = classinfo[ p ] - local psuper, psub = pinfo.super, pinfo.sub - if not psub[ cls ] then psub[ cls ] = diff end - for i = 1, psuper.n do - super[ #super+1 ] = psuper[ i ] - end - newn = newn + psuper.n - if i == n then - n, diff = newn, diff+1 - end - end -end - - --- create the necessary metadata for the class, setup the inheritance --- hierarchy, set a suitable metatable, and return the class -local function create_class( _, name, ... ) - assert( type( name ) == "string", "class name must be a string" ) - local cls, index = {}, {} - local o_meta = { - __index = index, - __name = name, - } - local info = { - name = name, - super = { n = 0 }, - sub = setmetatable( {}, mode_k_meta ), - members = {}, - o_meta = o_meta, - c_meta = { - __index = index, - __newindex = class_newindex, - __call = default_constructor( o_meta ), - __pairs = class_pairs, - __ipairs = class_ipairs, - __name = "class", - __metatable = false, - }, - } - linearize_ancestors( cls, info.super, ... ) - for i = #info.super, 1, -1 do - for k,v in pairs( classinfo[ info.super[ i ] ].members ) do - if k ~= "__init" then index[ k ] = v end - end - end - index.__class = cls - classinfo[ cls ] = info - return setmetatable( cls, info.c_meta ) -end - - --- the exported class module -local M = {} -setmetatable( M, { __call = create_class } ) - - --- returns the class of an object -function M.of( o ) - return type( o ) == "table" and o.__class or nil -end - - --- returns the class name of an object or class -function M.name( oc ) - if oc == nil then return nil end - oc = type( oc ) == "table" and oc.__class or oc - local info = classinfo[ oc ] - return info and info.name -end - - --- checks if an object or class is in an inheritance --- relationship with a given class -function M.is_a( oc, cls ) - if oc == nil then return nil end - local info = assert( classinfo[ cls ], "invalid class" ) - oc = type( oc ) == "table" and oc.__class or oc - if oc == cls then return 0 end - return info.sub[ oc ] -end - - --- change the type of an object to the new class -function M.cast( o, newcls ) - local info = classinfo[ newcls ] - if not info then - error( "invalid class" ) - end - setmetatable( o, info.o_meta ) - return o -end - - -local function make_delegate( cls, field, method ) - cls[ method ] = function( self, ... ) - local obj = self[ field ] - return obj[ method ]( obj, ... ) - end -end - --- create delegation methods -function M.delegate( cls, fieldname, ... ) - if type( (...) ) == "table" then - for k,v in pairs( (...) ) do - if cls[ k ] == nil and k ~= "__init" and - type( v ) == "function" then - make_delegate( cls, fieldname, k ) - end - end - else - for i = 1, select( '#', ... ) do - local k = select( i, ... ) - if cls[ k ] == nil and k ~= "__init" then - make_delegate( cls, fieldname, k ) - end - end - end - return cls -end - - --- multimethod stuff -do - -- store multimethods and map them to the meta-data - local mminfo = setmetatable( {}, mode_k_meta ) - - local erroffset = 0 - if V == "Lua 5.1" then erroffset = 1 end - - local function no_match2() - error( "no matching multimethod overload", 2+erroffset ) - end - - local function no_match3() - error( "no matching multimethod overload", 3+erroffset ) - end - - local function amb_call() - error( "ambiguous multimethod call", 3+erroffset ) - end - - local empty = {} -- just an empty table used as dummy - local FIRST_OL = 4 -- index of first overload specification - - - -- create a multimethod using the parameter indices given - -- as arguments for dynamic dispatch - function M.multimethod( ... ) - local t, n = { ... }, select( '#', ... ) - assert( n >= 1, "no polymorphic parameter for multimethod" ) - local max = 0 - for i = 1, n do - local x = t[ i ] - max = assert( x > max and x % 1 == 0 and x, - "invalid parameter overload specification" ) - end - local mm_impl = { no_match2, t, max } - local function mm( ... ) - return mm_impl[ 1 ]( mm_impl, ... ) - end - mminfo[ mm ] = mm_impl - return mm - end - - - local function make_weak() - return setmetatable( {}, mode_k_meta ) - end - - - local function calculate_cost( ol, ... ) - local c = 0 - for i = 1, select( '#', ... ) do - local a, pt = ol[ i ], select( i, ... ) - if type( a ) == "table" then -- class table - local info = classinfo[ a ] - local diff = (pt == a) and 0 or info and info.sub[ pt ] - if not diff then return nil end - c = c + diff - else -- type name - if pt ~= a then return nil end - end - end - return c - end - - - local function select_impl( cost, f, amb, ol, ... ) - local c = calculate_cost( ol, ... ) - if c then - if cost then - if c < cost then - cost, f, amb = c, ol.func, false - elseif c == cost then - amb = true - end - else - cost, f, amb = c, ol.func, false - end - end - return cost, f, amb - end - - - local function collect_type_checkers( mm, a ) - local funcs = {}, {} - for i = FIRST_OL, #mm do - local ol = mm[ i ] - for k,v in pairs( ol ) do - if type( k ) == "function" and - (a == nil or v[ a ]) and - not funcs[ k ] then - local j = #funcs+1 - funcs[ j ] = k - funcs[ k ] = j - end - end - end - return funcs - end - - - local function c_varlist( t, m, prefix ) - local n = #t - if m >= 1 then - t[ n+1 ] = prefix - t[ n+2 ] = 1 - end - for i = 2, m do - local j = i*3+n - t[ j-3 ] = "," - t[ j-2 ] = prefix - t[ j-1 ] = i - end - end - - local function c_typecheck( t, mm, funcs, j ) - local n, ai = #t, mm[ 2 ][ j ] - t[ n+1 ] = " t=type(_" - t[ n+2 ] = ai - t[ n+3 ] = ")\n local t" - t[ n+4 ] = j - t[ n+5 ] = "=(t=='table' and _" - t[ n+6 ] = ai - t[ n+7 ] = ".__class) or " - local ltcs = collect_type_checkers( mm, j ) - local m = #ltcs - for i = 1, m do - local k = i*5+n+3 - t[ k ] = "tc" - t[ k+1 ] = funcs[ ltcs[ i ] ] - t[ k+2 ] = "(_" - t[ k+3 ] = ai - t[ k+4 ] = ") or " - end - t[ m*5+n+8 ] = "t\n" - end - - local function c_cache( t, mm ) - local c = #mm[ 2 ] - local n = #t - t[ n+1 ] = s_rep( "(", c-1 ) - t[ n+2 ] = "cache" - for i = 1, c-1 do - local j = i*3+n - t[ j ] = "[t" - t[ j+1 ] = i - t[ j+2 ] = "] or empty)" - end - local j = c*3+n - t[ j ] = "[t" - t[ j+1 ] = c - t[ j+2 ] = "]\n" - end - - local function c_costcheck( t, i, j ) - local n = #t - t[ n+1 ] = " cost,f,is_amb=sel_impl(cost,f,is_amb,mm[" - t[ n+2 ] = j+FIRST_OL-1 - t[ n+3 ] = "]," - c_varlist( t, i, "t" ) - t[ #t+1 ] = ")\n" - end - - local function c_updatecache( t, i ) - local n = #t - t[ n+1 ] = " if not t[t" - t[ n+2 ] = i - t[ n+3 ] = "] then t[t" - t[ n+4 ] = i - t[ n+5 ] = "]=mk_weak() end\n t=t[t" - t[ n+6 ] = i - t[ n+7 ] = "]\n" - end - - - local function recompile_and_call( mm, ... ) - local n = #mm[ 2 ] -- number of polymorphic parameters - local tcs = collect_type_checkers( mm ) - local code = { - "local type,cache,empty,mk_weak,sel_impl,no_match,amb_call" - } - if #tcs >= 1 then - code[ #code+1 ] = "," - end - c_varlist( code, #tcs, "tc" ) - code[ #code+1 ] = "=...\nreturn function(mm," - c_varlist( code, mm[ 3 ], "_" ) - code[ #code+1 ] = ",...)\n local t\n" - for i = 1, n do - c_typecheck( code, mm, tcs, i ) - end - code[ #code+1 ] = " local f=" - c_cache( code, mm ) - code[ #code+1 ] = [=[ - if f==nil then - local is_amb,cost -]=] - for i = 1, #mm-FIRST_OL+1 do - c_costcheck( code, n, i ) - end - code[ #code+1 ] = [=[ - if f==nil then - no_match() - elseif is_amb then - amb_call() - end - t=cache -]=] - for i = 1, n-1 do - c_updatecache( code, i ) - end - code[ #code+1 ] = " t[t" - code[ #code+1 ] = n - code[ #code+1 ] = "]=f\n end\n return f(" - c_varlist( code, mm[ 3 ], "_" ) - code[ #code+1 ] = ",...)\nend\n" - local i = 0 - local function ld() - i = i + 1 - return code[ i ] - end - --print( table.concat( code ) ) -- XXX - local f = assert( load( ld, "=[multimethod]" ) )( - type, make_weak(), empty, make_weak, select_impl, no_match3, - amb_call, t_unpack( tcs ) - ) - mm[ 1 ] = f - return f( mm, ... ) - end - - - -- register a new overload for this multimethod - function M.overload( f, ... ) - local mm = assert( type( f ) == "function" and mminfo[ f ], - "argument is not a multimethod" ) - local i, n = 1, select( '#', ... ) - local ol = {} - local func = assert( n >= 1 and select( n, ... ), - "missing function in overload specification" ) - while i < n do - local a = select( i, ... ) - local t = type( a ) - if t == "string" then - ol[ #ol+1 ] = a - elseif t == "table" then - assert( classinfo[ a ], "invalid class" ) - ol[ #ol+1 ] = a - else - assert( t == "function", "invalid overload specification" ) - i = i + 1 - assert( i < n, "missing function in overload specification" ) - ol[ a ] = ol[ a ] or {} - ol[ #ol+1 ] = select( i, ... ) - ol[ a ][ #ol ] = true - end - i = i + 1 - end - assert( #mm[ 2 ] == #ol, "wrong number of overloaded parameters" ) - ol.func = func - mm[ #mm+1 ] = ol - mm[ 1 ] = recompile_and_call - end - -end - - --- return module table -return M - diff --git a/server/lib/demo_lib.lua b/server/lib/demo_lib.lua index 33c33a1..49447b3 100644 --- a/server/lib/demo_lib.lua +++ b/server/lib/demo_lib.lua @@ -368,7 +368,7 @@ demo.start_callback = function(res,data) demo.zmq_context = proteo.zmq.ctx_new() demo.zmq_socket = proteo.zmq.socket_new(demo.zmq_context,proteo.zmq.sockType.ZMQ_REQ) - proteo.zmq.connect(demo.zmq_socket,'tcp://localhost:5555') + proteo.zmq.connect(demo.zmq_socket,'tcp://poseidone.irib.cloud:5555') demo.frame = proteo.opencv.img() demo.cap = proteo.opencv.videocapture(0) diff --git a/server/lib/middleclass.lua b/server/lib/middleclass.lua deleted file mode 100644 index 0d9a834..0000000 --- a/server/lib/middleclass.lua +++ /dev/null @@ -1,170 +0,0 @@ -local middleclass = { - _VERSION = 'middleclass v4.1.0', - _DESCRIPTION = 'Object Orientation for Lua', - _URL = 'https://github.com/kikito/middleclass', - _LICENSE = [[ - MIT LICENSE - - Copyright (c) 2011 Enrique García Cota - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ]] -} - -local function _createIndexWrapper(aClass, f) - if f == nil then - return aClass.__instanceDict - else - return function(self, name) - local value = aClass.__instanceDict[name] - - if value ~= nil then - return value - elseif type(f) == "function" then - return (f(self, name)) - else - return f[name] - end - end - end -end - -local function _propagateInstanceMethod(aClass, name, f) - f = name == "__index" and _createIndexWrapper(aClass, f) or f - aClass.__instanceDict[name] = f - - for subclass in pairs(aClass.subclasses) do - if rawget(subclass.__declaredMethods, name) == nil then - _propagateInstanceMethod(subclass, name, f) - end - end -end - -local function _declareInstanceMethod(aClass, name, f) - aClass.__declaredMethods[name] = f - - if f == nil and aClass.super then - f = aClass.super.__instanceDict[name] - end - - _propagateInstanceMethod(aClass, name, f) -end - -local function _tostring(self) return "class " .. self.name end -local function _call(self, ...) return self:new(...) end - -local function _createClass(name, super) - local dict = {} - dict.__index = dict - - local aClass = { name = name, super = super, static = {}, - __instanceDict = dict, __declaredMethods = {}, - subclasses = setmetatable({}, {__mode='k'}) } - - if super then - setmetatable(aClass.static, { __index = function(_,k) return rawget(dict,k) or super.static[k] end }) - else - setmetatable(aClass.static, { __index = function(_,k) return rawget(dict,k) end }) - end - - setmetatable(aClass, { __index = aClass.static, __tostring = _tostring, - __call = _call, __newindex = _declareInstanceMethod }) - - return aClass -end - -local function _includeMixin(aClass, mixin) - assert(type(mixin) == 'table', "mixin must be a table") - - for name,method in pairs(mixin) do - if name ~= "included" and name ~= "static" then aClass[name] = method end - end - - for name,method in pairs(mixin.static or {}) do - aClass.static[name] = method - end - - if type(mixin.included)=="function" then mixin:included(aClass) end - return aClass -end - -local DefaultMixin = { - __tostring = function(self) return "instance of " .. tostring(self.class) end, - - initialize = function(self, ...) end, - - isInstanceOf = function(self, aClass) - return type(aClass) == 'table' and (aClass == self.class or self.class:isSubclassOf(aClass)) - end, - - static = { - allocate = function(self) - assert(type(self) == 'table', "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'") - return setmetatable({ class = self }, self.__instanceDict) - end, - - new = function(self, ...) - assert(type(self) == 'table', "Make sure that you are using 'Class:new' instead of 'Class.new'") - local instance = self:allocate() - instance:initialize(...) - return instance - end, - - subclass = function(self, name) - assert(type(self) == 'table', "Make sure that you are using 'Class:subclass' instead of 'Class.subclass'") - assert(type(name) == "string", "You must provide a name(string) for your class") - - local subclass = _createClass(name, self) - - for methodName, f in pairs(self.__instanceDict) do - _propagateInstanceMethod(subclass, methodName, f) - end - subclass.initialize = function(instance, ...) return self.initialize(instance, ...) end - - self.subclasses[subclass] = true - self:subclassed(subclass) - - return subclass - end, - - subclassed = function(self, other) end, - - isSubclassOf = function(self, other) - return type(other) == 'table' and - type(self.super) == 'table' and - ( self.super == other or self.super:isSubclassOf(other) ) - end, - - include = function(self, ...) - assert(type(self) == 'table', "Make sure you that you are using 'Class:include' instead of 'Class.include'") - for _,mixin in ipairs({...}) do _includeMixin(self, mixin) end - return self - end - } -} - -function middleclass.class(name, super) - assert(type(name) == 'string', "A name (string) is needed for the new class") - return super and super:subclass(name) or _includeMixin(_createClass(name), DefaultMixin) -end - -setmetatable(middleclass, { __call = function(_, ...) return middleclass.class(...) end }) - -return middleclass diff --git a/server/lib/room.lua b/server/lib/room.lua deleted file mode 100644 index ca88577..0000000 --- a/server/lib/room.lua +++ /dev/null @@ -1,75 +0,0 @@ ---[[ - - ---]] - -local host = proteo.enet.host_create() -local server = nil - -local rooms={} -local peer_server=nil -local init_callback=nil - -local function split(s, sep) - local fields = {} - - local sep = sep or " " - local pattern = string.format("([^%s]+)", sep) - string.gsub(s, pattern, function(c) fields[#fields + 1] = c end) - - return fields -end - -proteo.network.roomInit = function(server,callback) - print("roomInit: "..server) - server = host:connect(server) - init_callback=callback -end - -proteo.network.roomConnect = function(room,callback) - print("roomConnect: "..room.."->"..callback) - rooms[room]={} - rooms[room].callback=callback - rooms[room].state="wait" - peer_server:send(room.."#connect") -end - -proteo.network.roomDisconnect = function(room) - -end - -proteo.network.roomSend = function(room,data) - peer_server:send(room.."#"..data) -end - ---Deve essere invocata dentro l'update dello script che lo usa -proteo.network.roomCheck = function() - --print("roomCheck") - local event = host:service(0) - while event do - if event.type == "receive" then - --print("Got message: ", event.data, event.peer) - --event.peer:send( "ping" ) - local split_room=split(event.data, "#") - if #split_room==2 then - if rooms[split_room[1]] ~= nil then - if split_room[2]=="connected" then rooms[split_room[1]].state="connected" end - _G[rooms[split_room[1]].callback](split_room[1],split_room[2]) - end - end - elseif event.type == "connect" then - print(event.peer, "connected.") - --event.peer:send( "ping" ) - peer_server=event.peer - - --local f=loadstring('return '..init_callback) - --if f==nil then print("Callback error: " .. init_callback) - --else f() print("Call : " .. init_callback) end - _G[init_callback]() - - elseif event.type == "disconnect" then - print(event.peer, "disconnected.") - end - event = host:service() - end -end diff --git a/server/lib/skl_utility.lua b/server/lib/skl_utility.lua index a4bd98c..98ffaa9 100644 --- a/server/lib/skl_utility.lua +++ b/server/lib/skl_utility.lua @@ -4,13 +4,13 @@ local inspect = require "inspect" function svg2shape (svgfile, shape) for i=1,#svgfile do - print("Shape "..i..") "..svgfile[i].id.." color: "..svgfile[i].fill_color) + if VERBOSE then print("Shape "..i..") "..svgfile[i].id.." color: "..svgfile[i].fill_color) end for j=1,#svgfile[i] do - print(" Path "..j) + if VERBOSE then print(" Path "..j) end local p=proteo.graphics.newPolygon(svgfile[i].id.."_"..j,"clear",svgfile[i].fill_color,0,0) proteo.graphics.addItem(shape,p) for k=1,#svgfile[i][j] do - print(" Bezier "..k..") x0:"..svgfile[i][j][k].x0.." y0:"..svgfile[i][j][k].y0.." x3:"..svgfile[i][j][k].x3.." y3:"..svgfile[i][j][k].y3) + if VERBOSE then rint(" Bezier "..k..") x0:"..svgfile[i][j][k].x0.." y0:"..svgfile[i][j][k].y0.." x3:"..svgfile[i][j][k].x3.." y3:"..svgfile[i][j][k].y3) end proteo.graphics.addBezier(p,svgfile[i][j][k].x0,svgfile[i][j][k].y0, svgfile[i][j][k].x1,svgfile[i][j][k].y1, svgfile[i][j][k].x2,svgfile[i][j][k].y2, @@ -23,11 +23,11 @@ end function json2skl(jsonfile,skeleton) for k, j in pairs(jsonfile.joints) do - print("Joint ("..k..") x: "..j.x.." y: "..j.y) + if VERBOSE then print("Joint ("..k..") x: "..j.x.." y: "..j.y) end proteo.graphics.addJoint(k,skeleton,j.x,j.y) end for k, b in pairs(jsonfile.bones) do - print("Bone ("..k..") a: "..b.a.." yb: "..b.b.." group: "..b.group) + if VERBOSE then print("Bone ("..k..") a: "..b.a.." yb: "..b.b.." group: "..b.group) end proteo.graphics.addBone(k,skeleton,b.a,b.b,b.group) end end diff --git a/server/lib/strict.lua b/server/lib/strict.lua deleted file mode 100644 index c67243f..0000000 --- a/server/lib/strict.lua +++ /dev/null @@ -1,39 +0,0 @@ --- From http://metalua.luaforge.net/src/lib/strict.lua.html --- --- strict.lua --- checks uses of undeclared global variables --- All global variables must be 'declared' through a regular assignment --- (even assigning nil will do) in a main chunk before being used --- anywhere or assigned to inside a function. --- - -local mt = getmetatable(_G) -if mt == nil then - mt = {} - setmetatable(_G, mt) -end - -__STRICT = true -mt.__declared = {} - -mt.__newindex = function (t, n, v) - if __STRICT and not mt.__declared[n] then - local w = debug.getinfo(2, "S").what - if w ~= "main" and w ~= "C" then - error("assign to undeclared variable '"..n.."'", 2) - end - mt.__declared[n] = true - end - rawset(t, n, v) -end - -mt.__index = function (t, n) - if not mt.__declared[n] and debug.getinfo(2, "S").what ~= "C" then - error("variable '"..n.."' is not declared", 2) - end - return rawget(t, n) -end - -function global(...) - for _, v in ipairs{...} do mt.__declared[v] = true end -end diff --git a/server/lib/tetris_lib.lua b/server/lib/tetris_lib.lua index a59d003..6ab5a6d 100644 --- a/server/lib/tetris_lib.lua +++ b/server/lib/tetris_lib.lua @@ -1,5 +1,6 @@ local inspect = require("inspect") local json=require "json" +require "tfl_blazepose" require "tfl_utility" @@ -26,6 +27,8 @@ tetris.left_move_rotate = function() end end +--REMOTE TFL +--[[ tetris.pose = function(data) for _, b in ipairs(data['data']) do landmarks = show_landmark(b,tetris.template.left_frame,b['landmarks']) @@ -37,6 +40,7 @@ tetris.pose = function(data) end end + tetris.pipe = function(json_data) data = json.decode(json_data) proteo.opencv.frame(tetris.left_cap,tetris.template.left_frame) @@ -53,14 +57,41 @@ tetris.pipe = function(json_data) return json.encode(data) end + tetris.update_event = function(dt) + if tetris.zmq_socket_tetris ~= nil then buffer = proteo.zmq.recv(tetris.zmq_socket_tetris,proteo.zmq.flag.ZMQ_DONTWAIT) if buffer ~= nil then proteo.zmq.send (tetris.zmq_socket_tetris,(tetris.pipe(buffer)),proteo.zmq.flag.ZMQ_DONTWAIT) end end + + end + ]]-- +--============= + +--LOCAL TFL +tetris.update_local_event = function(dt) + + proteo.opencv.frame(tetris.left_cap,tetris.template.left_frame) + + bbox = invoke_pose_detect(tetris.template.left_frame) + size = proteo.opencv.size(tetris.template.left_frame) + + for i=1,#bbox do + feed_image = generate_pose_landmark_input_image(tetris.template.left_frame,bbox[i]) + bbox[i].landmarks=invoke_pose_landmark(feed_image) + landmarks = get_landmark(bbox[i],tetris.template.left_frame,bbox[i].landmarks) + show_pose(landmarks,tetris.template.left_frame) + tetris.pose_control(landmarks) + end + + proteo.opencv.flip(tetris.template.left_frame,tetris.template.left_frame,1) + proteo.graphics.changeImage(tetris.template.left_image,tetris.template.left_frame) +end +--============= tetris.update_t_event = function(dt) if tetris.check_t_left(tetris.DOWN) ~= 1 then @@ -310,16 +341,22 @@ tetris.check_t_left = function(d) return 0 end +--REMOTE TFL +--[[ tetris.start_callback = function(res,data) tetris.current_session = data['session'] proteo.system.stopTimer(tetris.webcam_timer) proteo.system.startTimer(tetris.t_timer) + + tetris.zmq_context_tetris = proteo.zmq.ctx_new() tetris.zmq_socket_tetris = proteo.zmq.socket_new(tetris.zmq_context_tetris,proteo.zmq.sockType.ZMQ_REQ) - proteo.zmq.connect(tetris.zmq_socket_tetris,'tcp://localhost:5555') + proteo.zmq.connect(tetris.zmq_socket_tetris,'tcp://poseidone.irib.cloud:5555') + proteo.opencv.frame(tetris.left_cap,tetris.template.left_frame) proteo.opencv.flip(tetris.template.left_frame,tetris.template.left_frame,1) proteo.graphics.changeImage(tetris.template.left_image,tetris.template.left_frame) + tmp = proteo.opencv.imencode(tetris.template.left_frame) data = {} data['type']='FRAME' @@ -328,12 +365,23 @@ tetris.start_callback = function(res,data) data['encoding']='JPEG' data['request']='TFLPOSE' proteo.zmq.send (tetris.zmq_socket_tetris,json.encode(data),proteo.zmq.flag.ZMQ_DONTWAIT) + tetris.event_timer = proteo.system.createTimer(50,tetris.update_event) proteo.system.startTimer(tetris.event_timer) end + ]]-- + --=========== tetris.start = function(sender) - proteo.network.proteo_post("/deepcrimson/start",'{}',tetris.start_callback) + +--REMOTE TFL +--proteo.network.proteo_post("/deepcrimson/start",'{}',tetris.start_callback) +--LOCAL TFL + proteo.system.stopTimer(tetris.webcam_timer) + proteo.system.startTimer(tetris.t_timer) + tetris.event_timer = proteo.system.createTimer(50,tetris.update_local_event) + proteo.system.startTimer(tetris.event_timer) +--========== end tetris.update_left_square = function() @@ -407,7 +455,7 @@ tetris.create_template = function(pose_control_function) proteo.opencv.setBufferSize(tetris.left_cap,3) proteo.opencv.frame(tetris.left_cap,tetris.template.left_frame) tetris.template.sagoma = proteo.opencv.imread(proteo.system.document()..'sagoma.jpg') - proteo.opencv.resize(tetris.template.sagoma,tetris.template.sagoma,480,640) + proteo.opencv.resize(tetris.template.sagoma,tetris.template.sagoma,480,640) tetris.webcam_timer = proteo.system.createTimer(300,tetris.webcam_event) proteo.system.startTimer(tetris.webcam_timer) for y = 1, 20, 1 do @@ -421,4 +469,4 @@ tetris.create_template = function(pose_control_function) tetris.update_t_left() end -return tetris \ No newline at end of file +return tetris diff --git a/server/lib/tfl_blazeface.lua b/server/lib/tfl_blazeface.lua index b98212f..6d1ab0a 100644 --- a/server/lib/tfl_blazeface.lua +++ b/server/lib/tfl_blazeface.lua @@ -8,10 +8,10 @@ local function decode_face_bounds (scores, bbox, score_thresh, input_img_w, inpu local region_list={} - for i=1,#anchors do + for i=1,#face_anchors do region = {} - anchor = anchors[i] + anchor = face_anchors[i] score0 = scores[i] score = 1.0 / (1.0 + math.exp(-score0)) @@ -207,8 +207,8 @@ kRightEyeTragion=6 FACE_LM_NUM=468 -local tfl_detect_model=proteo.tflite.modelFromFile("pose/face_detection_front.tflite") -local tfl_landmark_model=proteo.tflite.modelFromFile("pose/face_landmark.tflite") +local tfl_detect_model=proteo.tflite.modelFromFile(proteo.system.document().."dl/face_detection_front.tflite") +local tfl_landmark_model=proteo.tflite.modelFromFile(proteo.system.document().."dl/face_landmark.tflite") local tfl_interpreter_options=proteo.tflite.createInterpreterOptions() @@ -235,7 +235,7 @@ local tfl_landmark_score_outputTensorSize=proteo.tflite.getTensorSize(tfl_landma local tfl_frame=proteo.opencv.img() proteo.opencv.setSize(tfl_frame,tfl_detect_inputTensorSize[2],tfl_detect_inputTensorSize[3]) -local anchors=create_face_ssd_anchors(tfl_detect_inputTensorSize[3],tfl_detect_inputTensorSize[2]) +face_anchors=create_face_ssd_anchors(tfl_detect_inputTensorSize[3],tfl_detect_inputTensorSize[2]) --ocv_detect_model=proteo.opencv.readnet("pose/pose_detection_model_float32.pb","") --proteo.opencv.infoNet(ocv_detect_model) diff --git a/server/lib/tfl_blazepose.lua b/server/lib/tfl_blazepose.lua index 87aeddf..700d1ec 100644 --- a/server/lib/tfl_blazepose.lua +++ b/server/lib/tfl_blazepose.lua @@ -13,10 +13,10 @@ function decode_pose_bounds (scores, bbox, score_thresh, input_img_w, input_img_ --print("#anchors: "..#anchors) --print("#bbox: "..#bbox) - for i=1,#anchors do + for i=1,#pose_anchors do region = {} - anchor = anchors[i] + anchor = pose_anchors[i] score0 = scores[i] score = 1.0 / (1.0 + math.exp(-score0)) @@ -169,7 +169,7 @@ function pack_detect_pose_result (region_list) end -function create_pose_ssd_anchors(input_w,intput_h) +function create_pose_ssd_anchors_old(input_w,intput_h) local anchor_options = {} anchor_options.num_layers = 4 @@ -191,6 +191,27 @@ function create_pose_ssd_anchors(input_w,intput_h) return GenerateAnchors(anchor_options) end +function create_pose_ssd_anchors(input_w,intput_h) + + local anchor_options = {} + anchor_options.num_layers = 5 + anchor_options.strides = {8,16,32,32,32} + anchor_options.aspect_ratios = {1.0} + anchor_options.feature_map_height ={} + + anchor_options.min_scale = 0.1484375 + anchor_options.max_scale = 0.75 + anchor_options.input_size_height = 224 + anchor_options.input_size_width = 224 + anchor_options.anchor_offset_x = 0.5 + anchor_options.anchor_offset_y = 0.5 + + anchor_options.reduce_boxes_in_lowest_layer = false + anchor_options.interpolated_scale_aspect_ratio = 1.0 + anchor_options.fixed_anchor_size = true + + return GenerateAnchors(anchor_options) +end --========= INIT kPoseDetectKeyNum=4 -- FullBody:4, UpperBody:2 @@ -200,8 +221,9 @@ kMidShoulderCenter=3 POSE_JOINT_NUM=33 -tfl_detect_model=proteo.tflite.modelFromFile("pose/pose_detection.tflite") -tfl_landmark_model=proteo.tflite.modelFromFile("pose/pose_landmark_full_body.tflite") +--tfl_detect_model=proteo.tflite.modelFromFile(proteo.system.document().."dl/pose_detection_old.tflite") +tfl_detect_model=proteo.tflite.modelFromFile(proteo.system.document().."dl/pose_detection.tflite") +tfl_landmark_model=proteo.tflite.modelFromFile(proteo.system.document().."dl/pose_landmark_heavy.tflite")--"dl/pose_landmark_full_body.tflite")-- tfl_interpreter_options=proteo.tflite.createInterpreterOptions() @@ -229,7 +251,7 @@ tfl_landmark_score_outputTensorSize=proteo.tflite.getTensorSize(tfl_landmark_sco tfl_frame=proteo.opencv.img() proteo.opencv.setSize(tfl_frame,tfl_detect_inputTensorSize[2],tfl_detect_inputTensorSize[3]) -anchors=create_pose_ssd_anchors(tfl_detect_inputTensorSize[3],tfl_detect_inputTensorSize[2]) +pose_anchors=create_pose_ssd_anchors(tfl_detect_inputTensorSize[3],tfl_detect_inputTensorSize[2]) --ocv_detect_model=proteo.opencv.readnet("pose/pose_detection_model_float32.pb","") --proteo.opencv.infoNet(ocv_detect_model) @@ -257,7 +279,8 @@ function invoke_pose_detect (img) scores = proteo.tflite.tensorToTable(tfl_detect_scores_tensor) bbox = proteo.tflite.tensorToTable(tfl_detect_bbox_tensor) - score_thresh = 0.4 + --score_thresh = 0.4 + score_thresh = 0.5 region_list = decode_pose_bounds (scores, bbox, score_thresh, tfl_detect_inputTensorSize[3], tfl_detect_inputTensorSize[2]) diff --git a/server/lib/tfl_emotion.lua b/server/lib/tfl_emotion.lua index 1c20b7f..93ba95a 100644 --- a/server/lib/tfl_emotion.lua +++ b/server/lib/tfl_emotion.lua @@ -5,7 +5,7 @@ local inspect = require "inspect" --========= INIT -local tfl_emotion_model=proteo.tflite.modelFromFile("pose/emotion_model_optimized.tflite") +local tfl_emotion_model=proteo.tflite.modelFromFile(proteo.system.document().."dl/emotion_model_optimized.tflite") local tfl_interpreter_options=proteo.tflite.createInterpreterOptions() diff --git a/server/lib/tfl_hand.lua b/server/lib/tfl_hand.lua new file mode 100644 index 0000000..22595d4 --- /dev/null +++ b/server/lib/tfl_hand.lua @@ -0,0 +1,322 @@ + +local inspect = require "inspect" + +require "tfl_utility" + +local function decode_hand_bounds (scores, bbox, score_thresh, input_img_w, input_img_h) + + local region_list={} + + + for i=1,#hand_anchors do + + region = {} + anchor = hand_anchors[i] + score0 = scores[i] + score = 1.0 / (1.0 + math.exp(-score0)) + + if score > score_thresh then + + local numkey = kHandDetectKeyNum + local bbx_idx = (4 + 2 * numkey) * (i-1) + + -- boundary box + local sx = bbox[bbx_idx + 1] + local sy = bbox[bbx_idx + 2] + local w = bbox[bbx_idx + 3] + local h = bbox[bbx_idx + 4] + + local cx = sx + anchor.x_center * input_img_w + local cy = sy + anchor.y_center * input_img_h + + cx = cx / input_img_w + cy = cy / input_img_h + w = w / input_img_w + h = h / input_img_h + + topleft = {} + btmright = {} + topleft.x = cx - w * 0.5 + topleft.y = cy - h * 0.5 + btmright.x = cx + w * 0.5 + btmright.y = cy + h * 0.5 + + region.score = score + region.topleft = topleft + region.btmright = btmright + + -- landmark positions (6 keys) + local keys={} + for j=1,kHandDetectKeyNum do + + local lx = bbox[bbx_idx + 4 + (2 * (j-1)) + 1] + local ly = bbox[bbx_idx + 4 + (2 * (j-1)) + 2] + lx = lx + anchor.x_center * input_img_w + ly = ly + anchor.y_center * input_img_h + lx = lx / input_img_w + ly = ly / input_img_h + + keys[j] = {} + keys[j].x=lx + keys[j].y=ly + + end + + region.keys = keys + table.insert(region_list,region) + end + end + + return region_list +end + + +function compute_detect_hand_to_roi (region) + + local input_img_w = tfl_detect_inputTensorSize[2] + local input_img_h = tfl_detect_inputTensorSize[3] + --local x_center = (region.topleft.x + region.btmright.x) * input_img_w / 2 + --local y_center = (region.topleft.y + region.btmright.y) * input_img_h / 2 + + local x_center = region.keys[kMiddle].x * input_img_w + local y_center = region.keys[kMiddle].y * input_img_h + + + --local box_size = math.sqrt((x_left - x_right) * (x_left - x_right) + + -- (y_left - y_right) * (y_left - y_right)) * 2.5 + --local box_size = math.sqrt((region.topleft.x - region.btmright.x) * (region.topleft.x - region.btmright.x) + + -- (region.topleft.y - region.btmright.y) * (region.topleft.y - region.btmright.y))*2.0 + local box_size = math.max(math.abs(region.topleft.x - region.btmright.x) * input_img_w,math.abs(region.topleft.y - region.btmright.y) * input_img_h) + + ---se il volto è troppo lontano questo valore diventa troppo piccolo (ad occhio sotto il 40) + + -- RectTransformationCalculator::TransformNormalizedRect() + local width = math.max(math.abs(region.topleft.x - region.btmright.x) * input_img_w, math.abs(region.keys[kThumb_MCP].x - region.keys[kPinky].x) * input_img_w) --box_size + local height = math.max(math.abs(region.topleft.y - region.btmright.y) * input_img_h, math.abs(region.keys[kWrist].y - region.keys[kMiddle].y) * input_img_h) --box_size + local rotation = region.rotation + local shift_x = 0.0 + local shift_y = 0.0 + local roi_cx + local roi_cy + + if rotation == 0.0 then + + roi_cx = x_center + (width * shift_x) + roi_cy = y_center + (height * shift_y) + + else + + local dx = (width * shift_x) * math.cos(rotation) - + (height * shift_y) * math.sin(rotation) + local dy = (width * shift_x) * math.sin(rotation) + + (height * shift_y) * math.cos(rotation) + roi_cx = x_center + dx + roi_cy = y_center + dy + end + + local scale_x = 2.6 --1.5 + local scale_y = 2.6 --1.5 + local long_side = math.max (width, height) + local roi_w = long_side * scale_x + local roi_h = long_side * scale_y + + region.roi_center = {x = roi_cx / input_img_w, y= roi_cy / input_img_h} + region.roi_size = {x= roi_w / input_img_w, y= roi_h / input_img_h} + + -- calculate ROI coordinates + local dx = roi_w * 0.5 + local dy = roi_h * 0.5 + region.roi_coord = {} + region.roi_coord[1] = {x= - dx, y= - dy} + region.roi_coord[2] = {x= dx, y= - dy} + region.roi_coord[3] = {x= dx, y= dy} + region.roi_coord[4] = {x= - dx, y= dy} + + for i = 1,4 do + rot_vec (region.roi_coord[i], rotation) + region.roi_coord[i].x = region.roi_coord[i].x + roi_cx + region.roi_coord[i].y = region.roi_coord[i].y + roi_cy + + region.roi_coord[i].x = region.roi_coord[i].x / input_img_h + region.roi_coord[i].y = region.roi_coord[i].y / input_img_h + end +end + +function pack_detect_hand_result (region_list) + + local detect_result={} + + for i = 1,#region_list do + + region = region_list[i] + + compute_rotation (region,kWrist,kMiddle) + compute_detect_hand_to_roi (region) + + table.insert(detect_result,region) + end + + return detect_result + +end + +function create_hand_ssd_anchors(input_w,intput_h) + + local anchor_options = {} + anchor_options.num_layers = 4 + anchor_options.strides = {8,16,16,16} + anchor_options.aspect_ratios = {1.0} + anchor_options.feature_map_height ={} + + anchor_options.min_scale = 0.1484375 + anchor_options.max_scale = 0.75 + anchor_options.input_size_height = 128 + anchor_options.input_size_width = 128 + anchor_options.anchor_offset_x = 0.5 + anchor_options.anchor_offset_y = 0.5 + + anchor_options.reduce_boxes_in_lowest_layer = false + anchor_options.interpolated_scale_aspect_ratio = 1.0 + anchor_options.fixed_anchor_size = true + +--[[ +# Options to generate anchors for SSD object detection models. + ssd_anchors_calculator_options = SsdAnchorsCalculatorOptions(input_size_width=256, input_size_height=256, min_scale=0.15625, max_scale=0.75 + , anchor_offset_x=0.5, anchor_offset_y=0.5, num_layers=4 + , feature_map_width=[], feature_map_height=[] + , strides=[16, 32, 32, 32], aspect_ratios=[1.0] + , reduce_boxes_in_lowest_layer=False, interpolated_scale_aspect_ratio=1.0 + , fixed_anchor_size=True) + ]] + return GenerateAnchors(anchor_options) +end + + +--========= INIT + +kHandDetectKeyNum=7 + +kWrist=1 +kIndex=2 +kMiddle=3 +kRing=4 +kPinky=5 +kThumb_CMC=6 +kThumb_MCP=7 + +HAND_LM_NUM=21 + +local tfl_detect_model=proteo.tflite.modelFromFile(proteo.system.document().."dl/palm_detection.tflite") +local tfl_landmark_model=proteo.tflite.modelFromFile(proteo.system.document().."dl/hand_landmark.tflite") + +local tfl_interpreter_options=proteo.tflite.createInterpreterOptions() + +local tfl_detect_interpreter=proteo.tflite.createInterpreter(tfl_detect_model,tfl_interpreter_options) +proteo.tflite.InterpreterAllocateTensors(tfl_detect_interpreter) + +local tfl_landmark_interpreter=proteo.tflite.createInterpreter(tfl_landmark_model,tfl_interpreter_options) +proteo.tflite.InterpreterAllocateTensors(tfl_landmark_interpreter) + +local tfl_detect_input_tensor =proteo.tflite.InterpreterGetInputTensor(tfl_detect_interpreter, 0) +local tfl_detect_inputTensorSize=proteo.tflite.getTensorSize(tfl_detect_input_tensor) + +local tfl_landmark_input_tensor =proteo.tflite.InterpreterGetInputTensor(tfl_landmark_interpreter, 0) +local tfl_landmark_inputTensorSize=proteo.tflite.getTensorSize(tfl_landmark_input_tensor) + +local tfl_detect_scores_tensor =proteo.tflite.InterpreterGetOutputTensor(tfl_detect_interpreter, 1) +local tfl_detect_scores_outputTensorSize=proteo.tflite.getTensorSize(tfl_detect_scores_tensor) +local tfl_detect_bbox_tensor =proteo.tflite.InterpreterGetOutputTensor(tfl_detect_interpreter, 0) +local tfl_detect_bbox_outputTensorSize=proteo.tflite.getTensorSize(tfl_detect_bbox_tensor) + +local tfl_landmark_score_tensor =proteo.tflite.InterpreterGetOutputTensor(tfl_landmark_interpreter, 0) +local tfl_landmark_score_outputTensorSize=proteo.tflite.getTensorSize(tfl_landmark_score_tensor) + +local tfl_frame=proteo.opencv.img() +proteo.opencv.setSize(tfl_frame,tfl_detect_inputTensorSize[2],tfl_detect_inputTensorSize[3]) + +hand_anchors=create_hand_ssd_anchors(tfl_detect_inputTensorSize[3],tfl_detect_inputTensorSize[2]) + +--ocv_detect_model=proteo.opencv.readnet("pose/pose_detection_model_float32.pb","") +--proteo.opencv.infoNet(ocv_detect_model) + +function invoke_hand_detect (img) + + proteo.opencv.resize(img,tfl_frame) + proteo.opencv.convert(tfl_frame,tfl_frame,proteo.opencv.matType.CV_32F,tfl_detect_inputTensorSize[4]) + + + proteo.opencv.mul(tfl_frame,1.0/255.0,tfl_frame) + proteo.opencv.add(tfl_frame,-0.5,tfl_frame) + proteo.opencv.mul(tfl_frame,2.0,tfl_frame) + + proteo.tflite.copyImage(tfl_detect_input_tensor,tfl_frame) + + --[[proteo.tflite.copyTensor(tfl_frame,tfl_detect_input_tensor) + proteo.opencv.mul(tfl_frame,0.5,tfl_frame) + proteo.opencv.add(tfl_frame,0.5,tfl_frame) + proteo.opencv.mul(tfl_frame,255.0,tfl_frame) + proteo.opencv.imwrite(proteo.system.document().."prova.jpg",tfl_frame)]] + + proteo.tflite.interpreterInvoke(tfl_detect_interpreter) + + scores = proteo.tflite.tensorToTable(tfl_detect_scores_tensor) + bbox = proteo.tflite.tensorToTable(tfl_detect_bbox_tensor) + score_thresh = 0.5 + + region_list = decode_hand_bounds (scores, bbox, score_thresh, tfl_detect_inputTensorSize[3], tfl_detect_inputTensorSize[2]) + + iou_thresh = 0.3 + + region_nms_list = non_max_suppression (region_list, iou_thresh) + + detect_result=pack_detect_hand_result (region_nms_list) + + --print(inspect(detect_result)) + + return detect_result +end + +function invoke_hand_landmark(img) + landmark_result={} + + proteo.tflite.copyImage(tfl_landmark_input_tensor,img) + proteo.tflite.interpreterInvoke(tfl_landmark_interpreter) + + landmarks = proteo.tflite.tensorToTable(tfl_landmark_score_tensor) + + landmark_result.score = 0 --TODO + landmark_result.joint={} + for i = 1,HAND_LM_NUM do + landmark_result.joint[i]={} + landmark_result.joint[i].x = landmarks[3 * (i-1) + 1] / tfl_landmark_inputTensorSize[3] + landmark_result.joint[i].y = landmarks[3 * (i-1) + 2] / tfl_landmark_inputTensorSize[2] + landmark_result.joint[i].z = landmarks[3 * (i-1) + 3] + end + + + return landmark_result +end + +function generate_hand_landmark_input_image (image, pose) + + local size=proteo.opencv.size(image) + local H=size[1] + local W=size[2] + + local mat=proteo.opencv.getAffineTransform(pose.roi_coord[1].x * W, pose.roi_coord[1].y * H + ,pose.roi_coord[2].x * W, pose.roi_coord[2].y * H + ,pose.roi_coord[3].x * W, pose.roi_coord[3].y * H + ,0,0 + ,tfl_landmark_inputTensorSize[3],0 + ,tfl_landmark_inputTensorSize[3],tfl_landmark_inputTensorSize[2] ) + img_affine=proteo.opencv.img() + proteo.opencv.setImg(img_affine,tfl_landmark_inputTensorSize[3],tfl_landmark_inputTensorSize[2],"#000000") + proteo.opencv.warpAffine(image,img_affine,mat) + + --proteo.opencv.imwrite(proteo.system.document().."prova.jpg",img_affine) + + proteo.opencv.convert(img_affine,img_affine,proteo.opencv.matType.CV_32F,tfl_landmark_inputTensorSize[4]) + proteo.opencv.mul(img_affine,1.0/255.0,img_affine) + + return img_affine +end diff --git a/server/lib/tfl_utility.lua b/server/lib/tfl_utility.lua index 1eac2a1..e9032ba 100644 --- a/server/lib/tfl_utility.lua +++ b/server/lib/tfl_utility.lua @@ -194,24 +194,97 @@ function GenerateAnchors(options) end function show_pose(landmarks,image) - proteo.opencv.line(image,(landmarks[12].x),(landmarks[12].y),(landmarks[13].x),(landmarks[13].y),16,'#cc33cc') - proteo.opencv.line(image,(landmarks[12].x),(landmarks[12].y),(landmarks[14].x),(landmarks[14].y),16,'#993399') - proteo.opencv.line(image,(landmarks[13].x),(landmarks[13].y),(landmarks[15].x),(landmarks[15].y),16,'#993399') - proteo.opencv.line(image,(landmarks[14].x),(landmarks[14].y),(landmarks[16].x),(landmarks[16].y),16,'#cc33cc') - proteo.opencv.line(image,(landmarks[15].x),(landmarks[15].y),(landmarks[17].x),(landmarks[17].y),16,'#cc33cc') - proteo.opencv.line(image,(landmarks[13].x),(landmarks[13].y),(landmarks[25].x),(landmarks[25].y),16,'#cc66cc') - proteo.opencv.line(image,(landmarks[12].x),(landmarks[12].y),(landmarks[24].x),(landmarks[24].y),16,'#cc66cc') - proteo.opencv.line(image,(landmarks[25].x),(landmarks[25].y),(landmarks[24].x),(landmarks[24].y),16,'#ff99ff') + + proteo.opencv.line(image,(landmarks[4].x),(landmarks[4].y),(landmarks[8].x),(landmarks[8].y),3,'#cc66cc') + proteo.opencv.line(image,(landmarks[3].x),(landmarks[3].y),(landmarks[4].x),(landmarks[4].y),3,'#cc66cc') + proteo.opencv.line(image,(landmarks[2].x),(landmarks[2].y),(landmarks[3].x),(landmarks[3].y),3,'#993399') + proteo.opencv.line(image,(landmarks[1].x),(landmarks[1].y),(landmarks[2].x),(landmarks[2].y),3,'#cc33cc') + proteo.opencv.line(image,(landmarks[1].x),(landmarks[1].y),(landmarks[5].x),(landmarks[5].y),3,'#cc33cc') + proteo.opencv.line(image,(landmarks[5].x),(landmarks[5].y),(landmarks[6].x),(landmarks[6].y),3,'#993399') + proteo.opencv.line(image,(landmarks[6].x),(landmarks[6].y),(landmarks[7].x),(landmarks[7].y),3,'#cc66cc') + proteo.opencv.line(image,(landmarks[7].x),(landmarks[7].y),(landmarks[9].x),(landmarks[9].y),3,'#cc66cc') + + + proteo.opencv.line(image,(landmarks[10].x),(landmarks[10].y),(landmarks[11].x),(landmarks[11].y),3,'#cc33cc') + + proteo.opencv.line(image,(landmarks[12].x),(landmarks[12].y),(landmarks[13].x),(landmarks[13].y),3,'#cc33cc') + proteo.opencv.line(image,(landmarks[12].x),(landmarks[12].y),(landmarks[14].x),(landmarks[14].y),3,'#993399') + proteo.opencv.line(image,(landmarks[13].x),(landmarks[13].y),(landmarks[15].x),(landmarks[15].y),3,'#993399') + proteo.opencv.line(image,(landmarks[14].x),(landmarks[14].y),(landmarks[16].x),(landmarks[16].y),3,'#cc33cc') + proteo.opencv.line(image,(landmarks[15].x),(landmarks[15].y),(landmarks[17].x),(landmarks[17].y),3,'#cc33cc') + proteo.opencv.line(image,(landmarks[16].x),(landmarks[16].y),(landmarks[18].x),(landmarks[18].y),3,'#cc66cc') + proteo.opencv.line(image,(landmarks[17].x),(landmarks[17].y),(landmarks[19].x),(landmarks[19].y),3,'#cc66cc') + proteo.opencv.line(image,(landmarks[20].x),(landmarks[20].y),(landmarks[18].x),(landmarks[18].y),3,'#cc66cc') + proteo.opencv.line(image,(landmarks[21].x),(landmarks[21].y),(landmarks[19].x),(landmarks[19].y),3,'#cc66cc') + proteo.opencv.line(image,(landmarks[20].x),(landmarks[20].y),(landmarks[16].x),(landmarks[16].y),3,'#cc66cc') + proteo.opencv.line(image,(landmarks[21].x),(landmarks[21].y),(landmarks[17].x),(landmarks[17].y),3,'#cc66cc') + proteo.opencv.line(image,(landmarks[22].x),(landmarks[22].y),(landmarks[16].x),(landmarks[16].y),3,'#cc66cc') + proteo.opencv.line(image,(landmarks[23].x),(landmarks[23].y),(landmarks[17].x),(landmarks[17].y),3,'#cc66cc') + + proteo.opencv.line(image,(landmarks[13].x),(landmarks[13].y),(landmarks[25].x),(landmarks[25].y),3,'#cc66cc') + proteo.opencv.line(image,(landmarks[12].x),(landmarks[12].y),(landmarks[24].x),(landmarks[24].y),3,'#cc66cc') + proteo.opencv.line(image,(landmarks[25].x),(landmarks[25].y),(landmarks[24].x),(landmarks[24].y),3,'#ff99ff') + + proteo.opencv.line(image,(landmarks[25].x),(landmarks[25].y),(landmarks[27].x),(landmarks[27].y),3,'#ff99ff') + proteo.opencv.line(image,(landmarks[26].x),(landmarks[26].y),(landmarks[24].x),(landmarks[24].y),3,'#ff99ff') + proteo.opencv.line(image,(landmarks[29].x),(landmarks[29].y),(landmarks[27].x),(landmarks[27].y),3,'#ff99ff') + proteo.opencv.line(image,(landmarks[26].x),(landmarks[26].y),(landmarks[28].x),(landmarks[28].y),3,'#ff99ff') + proteo.opencv.line(image,(landmarks[29].x),(landmarks[29].y),(landmarks[31].x),(landmarks[31].y),3,'#ff99ff') + proteo.opencv.line(image,(landmarks[32].x),(landmarks[32].y),(landmarks[28].x),(landmarks[28].y),3,'#ff99ff') + proteo.opencv.line(image,(landmarks[33].x),(landmarks[33].y),(landmarks[31].x),(landmarks[31].y),3,'#ff99ff') + proteo.opencv.line(image,(landmarks[32].x),(landmarks[32].y),(landmarks[30].x),(landmarks[30].y),3,'#ff99ff') + proteo.opencv.line(image,(landmarks[33].x),(landmarks[33].y),(landmarks[29].x),(landmarks[29].y),3,'#ff99ff') + proteo.opencv.line(image,(landmarks[28].x),(landmarks[28].y),(landmarks[30].x),(landmarks[30].y),3,'#ff99ff') end function show_facemesh(landmarks,image) + for i=1,#landmarks do + proteo.opencv.circle(image, landmarks[i].x, landmarks[i].y,1,"#00FF00") + end +end + +function show_hand(landmark,image) + + proteo.opencv.line(image,(landmarks[1].x),(landmarks[1].y),(landmarks[6].x),(landmarks[6].y),3,'#00AA00') + proteo.opencv.line(image,(landmarks[10].x),(landmarks[10].y),(landmarks[6].x),(landmarks[6].y),3,'#00AA00') + proteo.opencv.line(image,(landmarks[10].x),(landmarks[10].y),(landmarks[14].x),(landmarks[14].y),3,'#00AA00') + proteo.opencv.line(image,(landmarks[14].x),(landmarks[14].y),(landmarks[18].x),(landmarks[18].y),3,'#00AA00') + proteo.opencv.line(image,(landmarks[1].x),(landmarks[1].y),(landmarks[18].x),(landmarks[18].y),3,'#00AA00') + + proteo.opencv.line(image,(landmarks[6].x),(landmarks[6].y),(landmarks[7].x),(landmarks[7].y),3,'#00BB00') + proteo.opencv.line(image,(landmarks[7].x),(landmarks[7].y),(landmarks[8].x),(landmarks[8].y),3,'#00DD00') + proteo.opencv.line(image,(landmarks[8].x),(landmarks[8].y),(landmarks[9].x),(landmarks[9].y),3,'#00FF00') + + proteo.opencv.line(image,(landmarks[10].x),(landmarks[10].y),(landmarks[11].x),(landmarks[11].y),3,'#00BB00') + proteo.opencv.line(image,(landmarks[11].x),(landmarks[11].y),(landmarks[12].x),(landmarks[12].y),3,'#00DD00') + proteo.opencv.line(image,(landmarks[12].x),(landmarks[12].y),(landmarks[13].x),(landmarks[13].y),3,'#00FF00') + + proteo.opencv.line(image,(landmarks[14].x),(landmarks[14].y),(landmarks[15].x),(landmarks[15].y),3,'#00BB00') + proteo.opencv.line(image,(landmarks[15].x),(landmarks[15].y),(landmarks[16].x),(landmarks[16].y),3,'#00DD00') + proteo.opencv.line(image,(landmarks[16].x),(landmarks[16].y),(landmarks[17].x),(landmarks[17].y),3,'#00FF00') + + proteo.opencv.line(image,(landmarks[18].x),(landmarks[18].y),(landmarks[19].x),(landmarks[19].y),3,'#00BB00') + proteo.opencv.line(image,(landmarks[19].x),(landmarks[19].y),(landmarks[20].x),(landmarks[20].y),3,'#00DD00') + proteo.opencv.line(image,(landmarks[20].x),(landmarks[20].y),(landmarks[21].x),(landmarks[21].y),3,'#00FF00') + + proteo.opencv.line(image,(landmarks[1].x),(landmarks[1].y),(landmarks[2].x),(landmarks[2].y),3,'#00BB00') + proteo.opencv.line(image,(landmarks[2].x),(landmarks[2].y),(landmarks[3].x),(landmarks[3].y),3,'#00CC00') + proteo.opencv.line(image,(landmarks[3].x),(landmarks[3].y),(landmarks[4].x),(landmarks[4].y),3,'#00DD00') + proteo.opencv.line(image,(landmarks[4].x),(landmarks[4].y),(landmarks[5].x),(landmarks[5].y),3,'#00FF00') end function show_landmark(bbox,image,landmarks) local size=proteo.opencv.size(image) - + --print("Size W:"..size[2].." H:"..size[1]) + + --print("Roi1 X:"..bbox.roi_coord[1].x.." Y:"..bbox.roi_coord[1].y) + --print("Roi2 X:"..bbox.roi_coord[2].x.." Y:"..bbox.roi_coord[2].y) + --print("Roi3 X:"..bbox.roi_coord[3].x.." Y:"..bbox.roi_coord[3].y) + + --print("Landmark1 X:"..landmarks.joint[1].x.." Y:"..landmarks.joint[1].y) + proteo.opencv.rectangle(image,bbox.topleft.x*size[2],bbox.topleft.y*size[1], bbox.btmright.x*size[2],bbox.btmright.y*size[1], 3,"#FF0000") @@ -225,7 +298,9 @@ function show_landmark(bbox,image,landmarks) proteo.opencv.line(image,bbox.roi_coord[4].x*size[2],bbox.roi_coord[4].y*size[1],bbox.roi_coord[1].x*size[2],bbox.roi_coord[1].y*size[1],1,"#FFFF00") proteo.opencv.rectangle(image,bbox.topleft.x*size[2],bbox.topleft.y*size[1],bbox.btmright.x*size[2],bbox.btmright.y*size[1],1,"#FF0000") - + + + for j=1,#bbox.keys do proteo.opencv.circle(image, bbox.keys[j].x*size[2], bbox.keys[j].y*size[1], 2,"#0000FF") end @@ -240,26 +315,23 @@ function show_landmark(bbox,image,landmarks) ) if landmarks~=nil then --proteo.opencv.print(mat) - mat_table=proteo.opencv.toTable(mat) + local mat_table=proteo.opencv.toTable(mat) --tprint(mat_table) - ret={} + local ret={} for j=1,#landmarks.joint do ret[j]={} ret[j].x=landmarks.joint[j].x*mat_table[1][1]+landmarks.joint[j].y*mat_table[1][2]+mat_table[1][3] ret[j].y=landmarks.joint[j].x*mat_table[2][1]+landmarks.joint[j].y*mat_table[2][2]+mat_table[2][3] ret[j].z=landmarks.joint[j].z - if j==8 or j==9 or j==250 then + --if j==8 or j==9 or j==250 then --proteo.opencv.circle(image, ret[j].x, ret[j].y,8,"#0000FF") - id=string.format("%d",j) - proteo.opencv.putText(image,id,ret[j].x, ret[j].y,0.5,"#FF0000") - --elseif j>=249 and j<=255 then - -- id=string.format("%d",j) - -- proteo.opencv.putText(image,id,ret[j].x, ret[j].y,0.5,"#0000FF") - else - proteo.opencv.circle(image, ret[j].x, ret[j].y,2,"#00FF00") - end + -- id=string.format("%d",j) + -- proteo.opencv.putText(image,id,ret[j].x, ret[j].y,0.5,"#FF0000") + --else + proteo.opencv.circle(image, ret[j].x, ret[j].y,1,"#00FF00") + --end end return ret @@ -271,6 +343,13 @@ end function get_landmark(bbox,image,landmarks) local size=proteo.opencv.size(image) + --print("Size W:"..size[2].." H:"..size[1]) + + --print("Roi1 X:"..bbox.roi_coord[1].x.." Y:"..bbox.roi_coord[1].y) + --print("Roi2 X:"..bbox.roi_coord[2].x.." Y:"..bbox.roi_coord[2].y) + --print("Roi3 X:"..bbox.roi_coord[3].x.." Y:"..bbox.roi_coord[3].y) + + --print("Landmark1 X:"..landmarks.joint[1].x.." Y:"..landmarks.joint[1].y) --LANDMARK local mat=proteo.opencv.getAffineTransform(0,0 @@ -282,10 +361,10 @@ function get_landmark(bbox,image,landmarks) ) if landmarks~=nil then --proteo.opencv.print(mat) - mat_table=proteo.opencv.toTable(mat) + local mat_table=proteo.opencv.toTable(mat) --tprint(mat_table) - ret={} + local ret={} for j=1,#landmarks.joint do ret[j]={} ret[j].x=landmarks.joint[j].x*mat_table[1][1]+landmarks.joint[j].y*mat_table[1][2]+mat_table[1][3] @@ -297,4 +376,19 @@ function get_landmark(bbox,image,landmarks) end return nil -end \ No newline at end of file +end + +--[[function tprint (tbl, indent) + if not indent then indent = 0 end + for k, v in pairs(tbl) do + formatting = string.rep(" ", indent) .. k .. ": " + if type(v) == "table" then + print(formatting) + tprint(v, indent+1) + elseif type(v) == 'boolean' then + print(formatting .. tostring(v)) + else + print(formatting .. v) + end + end +end]]-- diff --git a/server/plugin/admin.lua b/server/plugin/admin.lua index d82a136..91669de 100644 --- a/server/plugin/admin.lua +++ b/server/plugin/admin.lua @@ -67,8 +67,11 @@ proteo.route.get("admin/config", --local conf=proteo.system.readFile(BASEDIR.."config.json") --TODO attenzione, se dentro il config "originale" il basedir punta altrove questo readfile non lo trova - --TODO forse si popssono aggiungere anche i file nella cartella plugin in modo da poter vedere quali sono attivi e quali no - local conf=proteo.system.readFile("./config.json") + --TODO forse si possono aggiungere anche i file nella cartella plugin in modo da poter vedere quali sono attivi e quali no + + local conf=proteo.system.readFile(CONFIG_PATH) + + --TODO non è possibile inviare il file di configurazione integrale, si devono togliere almeno le key return conf end diff --git a/server/plugin/deepcrimson.lua b/server/plugin/deepcrimson.lua index 23fe813..7caa53b 100644 --- a/server/plugin/deepcrimson.lua +++ b/server/plugin/deepcrimson.lua @@ -3,6 +3,7 @@ local json=require "json" require "tfl_blazepose" require "tfl_blazeface" +require "tfl_hand" require "tfl_emotion" local deepcrimson_data={} @@ -12,6 +13,9 @@ deepcrimson_data.zmq_context=nil deepcrimson_data.sessions={} +--t_profile=0 +--last_profile=0 + deepcrimson_data.pipe = function (json_data) data = json.decode(json_data) @@ -56,6 +60,7 @@ deepcrimson_data.pipe = function (json_data) ret["size"]=size elseif data["request"]=="TFLPOSE" then + bbox = invoke_pose_detect(deepcrimson_data.sessions[session].frame) size = proteo.opencv.size(deepcrimson_data.sessions[session].frame) for i=1,#bbox do @@ -69,17 +74,120 @@ deepcrimson_data.pipe = function (json_data) ret['request']='TFLPOSE' ret['data']=bbox ret['size']=size - + elseif data["request"]=="TFLHAND" then + + bbox = invoke_hand_detect(deepcrimson_data.sessions[session].frame) + size = proteo.opencv.size(deepcrimson_data.sessions[session].frame) + for i=1,#bbox do + feed_image = generate_hand_landmark_input_image(deepcrimson_data.sessions[session].frame,bbox[i]) + bbox[i].landmarks=invoke_hand_landmark(feed_image) + end + + deepcrimson_data.sessions[session].bbox=bbox + + ret['type']='BBOX' + ret['request']='TFLHAND' + ret['data']=bbox + ret['size']=size + elseif data["request"]=="TFLHOLI" then + bbox = invoke_pose_detect(deepcrimson_data.sessions[session].frame) + face_bbox={} + for i=1,#bbox do + feed_image = generate_pose_landmark_input_image(deepcrimson_data.sessions[session].frame,bbox[i]) + bbox[i].landmarks=invoke_pose_landmark(feed_image) + + --Due possibilità: + + --Estraiamo un Roi per la faccia e lo facciamo analizzare al face_detect per ottimizzarlo prima di passarlo al face_landmark + --face_frame=face_roi(deepcrimson_data.sessions[session].frame,bbox[i].landmarks) + --bbox = invoke_face_detect (face_frame) + + --Generiamo direttamente un roi ottimizzato da passare al face_landmark + pose_landmarks = get_landmark(bbox[i],deepcrimson_data.sessions[session].frame,bbox[i].landmarks) + --print("Center X:"..pose_landmarks[1].x.." Y:"..pose_landmarks[1].y) + face_bbox[i]=generate_face_bbox_from_pose(pose_landmarks,proteo.opencv.size(deepcrimson_data.sessions[session].frame)) + feed_image=generate_face_landmark_input_image (deepcrimson_data.sessions[session].frame, face_bbox[i]) + face_bbox[i].landmarks=invoke_face_landmark (feed_image) + end + + deepcrimson_data.sessions[session].bbox=bbox + + ret['type']='BBOX' + ret['request']='TFLHOLI' + ret['data']=bbox + ret['face']=face_bbox + ret['size']=size end + + --ret['nframe']=data['nframe'] return json.encode(ret) end +function face_roi(frame,pose_landmarks) + +end + +function generate_face_bbox_from_pose(pose_landmarks,size) + + local x_center = pose_landmarks[1].x/size[2] + local y_center = pose_landmarks[1].y/size[1] + + local x_lefteye = pose_landmarks[3].x/size[2] + local y_lefteye = pose_landmarks[3].y/size[1] + local x_righteye = pose_landmarks[6].x/size[2] + local y_righteye = pose_landmarks[6].y/size[1] + + local roi_size = math.sqrt((x_lefteye - x_righteye) * (x_lefteye - x_righteye) + (y_lefteye - y_righteye) * (y_lefteye - y_righteye)) + local ret={} + + ret.score=0 + + ret.topleft={x=x_center-roi_size/2,y=y_center-roi_size/2} + ret.btmright={x=x_center+roi_size/2,y=y_center+roi_size/2} + + ret.keys={} + ret.keys[1]={x=x_center,y=y_center} + ret.keys[2]={x=x_lefteye,y=y_lefteye} + ret.keys[3]={x=x_righteye,y=y_righteye} + + compute_rotation(ret,3,2) + ret.rotation=ret.rotation - math.pi * 0.5 + + local roi_scale = 4.0 + + local dx = roi_size * roi_scale * 0.5 + local dy = roi_size * roi_scale * 0.5 + ret.roi_coord = {} + ret.roi_coord[1] = {x= - dx, y= - dy} + ret.roi_coord[2] = {x= dx, y= - dy} + ret.roi_coord[3] = {x= dx, y= dy} + ret.roi_coord[4] = {x= - dx, y= dy} + + for i = 1,4 do + rot_vec (ret.roi_coord[i], ret.rotation) + ret.roi_coord[i].x = ret.roi_coord[i].x + x_center + ret.roi_coord[i].y = ret.roi_coord[i].y + y_center + + ret.roi_coord[i].x = ret.roi_coord[i].x + ret.roi_coord[i].y = ret.roi_coord[i].y + end + + return ret +end + deepcrimson_data.update= function(dt) if deepcrimson_data.zmq_socket ~= nil then buffer = proteo.zmq.recv(deepcrimson_data.zmq_socket,proteo.zmq.flag.ZMQ_DONTWAIT) if buffer ~= nil then - proteo.zmq.send (deepcrimson_data.zmq_socket,deepcrimson_data.pipe(buffer),proteo.zmq.flag.ZMQ_DONTWAIT) + + -- last_profile=proteo.system.clock() + + proteo.zmq.send (deepcrimson_data.zmq_socket,deepcrimson_data.pipe(buffer),proteo.zmq.flag.ZMQ_DONTWAIT) + + -- t_profile=proteo.system.clock()-last_profile + -- print(t_profile) + end end end @@ -128,6 +236,9 @@ proteo.route.get("deepcrimson/permissions", proteo.route.post("deepcrimson/stop", function(username,permission,data,param) + + --TODO chiude la sessione, se non ce ne sono più attive interrompe il timer + proteo.system.stopTimer(deepcrimson_data.timer) if deepcrimson_data.zmq_socket~=nil then proteo.zmq.socket_close(deepcrimson_data.zmq_socket) diff --git a/server/plugin/proteo.lua b/server/plugin/proteo.lua index 4ff8aeb..7308d65 100644 --- a/server/plugin/proteo.lua +++ b/server/plugin/proteo.lua @@ -16,11 +16,15 @@ libs["json"]="lib/json.lua" libs["room"]="lib/room.lua" libs["md5"]="lib/md5.lua" libs["bit"]="lib/numberlua.lua" +--libs["bit32"]="lib/numberlua.lua" libs["matrix"]="lib/matrix.lua" libs["base64"]="lib/base64.lua" libs["aeslua"]="lib/aeslua.lua" libs["inspect"]="lib/inspect.lua" libs["tfl_utility"]="lib/tfl_utility.lua" +libs["tfl_blazepose"]="lib/tfl_blazepose.lua" +libs["tfl_blazeface"]="lib/tfl_blazeface.lua" +libs["tfl_hand"]="lib/tfl_hand.lua" libs["skl_utility"]="lib/skl_utility.lua" libs["demo_lib"]="lib/demo_lib.lua" @@ -90,6 +94,27 @@ proteo.route.get("proteo/scriptandlibs/:script", --print("GET SCRIPT PERMISSION: "..permission) + if username=="demo" then --TODO può diventare l'opzione di default cercare lo script ANCHE nella cartella del proprio utente + --potrebbe essere un modo per testare gli script prima di rilasciarli nella cartella comune + script={} + script["type"]="SCRIPT" + script["result"]="OK" + data=proteo.system.readFile(BASEDIR.."script/"..username.."/"..param["script"]..".lua") + script["script"]=data + script["libs"]={} + + local script_info=json.decode(proteo.system.readFile(BASEDIR.."script/"..username.."/"..param["script"]..".json")) + + if script_info["libs"]~=nil then + for i=1,#script_info["libs"] do + local lib_name=script_info["libs"][i] + script["libs"][lib_name]=proteo.system.readFile(BASEDIR..libs[lib_name]) + end + end + + return json.encode(script) + end + local permitted=false for i=1,#permission do print("PERMISSION: "..permission[i]) @@ -147,7 +172,7 @@ proteo.route.get("proteo/script/:script", script={} script["type"]="SCRIPT" script["result"]="OK" - data=proteo.system.readFile(BASEDIR.."script/demo/"..param["script"]..".lua") + data=proteo.system.readFile(BASEDIR.."script/"..username.."/"..param["script"]..".lua") script["script"]=data return json.encode(script) diff --git a/server/proteo.service b/server/proteo.service index e7048fd..4d7153b 100644 --- a/server/proteo.service +++ b/server/proteo.service @@ -7,7 +7,7 @@ StartLimitIntervalSec=0 Type=simple Restart=always RestartSec=1 -ExecStart=/usr/local/bin/proteo_server -d -c /usr/local/etc/proteo/config.json +ExecStart=/usr/local/bin/proteo_server -d -c /usr/local/etc/Proteo/config.json [Install] WantedBy=multi-user.target diff --git a/server/proteo_auth.c b/server/proteo_auth.c index 1fa95f5..ceb45f6 100644 --- a/server/proteo_auth.c +++ b/server/proteo_auth.c @@ -1,7 +1,4 @@ -unsigned char *serverkey = (unsigned char *)"01234567890123456789012345678901"; //TODO va nel config -unsigned char *clientkey = (unsigned char *)"1234567890123456789012"; //TODO va nel config - struct connection_info_struct { int type; @@ -36,7 +33,7 @@ int aes_encrypt(const unsigned char *plaintext, int plaintext_len, unsigned char if(!(ctx = EVP_CIPHER_CTX_new())) handleErrors(); - if(1 != EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, serverkey, iv)) + if(1 != EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, config.server_key, iv)) handleErrors(); @@ -73,7 +70,7 @@ int aes_decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *ke if(!(ctx = EVP_CIPHER_CTX_new())) handleErrors(); - if(1 != EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, serverkey, iv)) + if(1 != EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, config.server_key, iv)) handleErrors(); if(1 != EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len)) @@ -212,7 +209,7 @@ int createTokenAndTicket(const char* username,const char* scriptId, char* token, if(debug) printf("Create Token: %s\n",json_token); unsigned char ciphertext[512]; - int ciphertext_len = aes_encrypt ((const unsigned char *)json_token, strlen (json_token), serverkey, iv, ciphertext); + int ciphertext_len = aes_encrypt ((const unsigned char *)json_token, strlen (json_token), config.server_key, iv, ciphertext); strcpy(ciphertext+ciphertext_len, "::"); strcpy(ciphertext+ciphertext_len+2,iv); ciphertext_len+=18; @@ -265,7 +262,7 @@ int verifyToken(char* username,char* permissions,const char* app,const char* tok //printf("OUT %d-%d: %s\n",len,strlen(json),json); //printf("IV: %s\n",iv); - int decryptedtext_len = aes_decrypt(tmp,len-18, serverkey, iv,(unsigned char*)decryptedtext); + int decryptedtext_len = aes_decrypt(tmp,len-18, config.server_key, iv,(unsigned char*)decryptedtext); free(tmp); //unsigned char *iv=tmp+len-16; diff --git a/server/proteo_config.c b/server/proteo_config.c index 3ad5bb9..eb61fe0 100644 --- a/server/proteo_config.c +++ b/server/proteo_config.c @@ -16,7 +16,7 @@ struct server struct conf { char version[10]; - char local[64]; + //char local[64]; char basedir[256]; char baseurl[64]; int port; @@ -29,6 +29,8 @@ struct conf int ssl; char ssl_key[64]; char ssl_cert[64]; + char server_key[33]; + char client_key[33]; } conf; @@ -47,15 +49,17 @@ int load_config(char *path_config) } fprintf(f, "{" "\"version\":\"0.1\"," - "\"local\":\"http://localhost:8888\"," + //"\"local\":\"http://localhost:8888\"," "\"master\":1," "\"basedir\":\"%s/\"," "\"baseurl\":\"localhost\"," "\"port\":8888," "\"ssl\":0," + "\"server_key\":\"01234567890123456789012345678901\"," + "\"client_key\":\"01234567890123456789012345678901\"," "\"admin_enabled\":1," "\"plugins\":[\"proteo\",\"admin\"]," - "\"servers\":[\"http://remote.server.com:8888\"]" + "\"servers\":[\"http://remote.server:8888\"]" "}",dirname(path_config)); fclose(f); } @@ -75,9 +79,9 @@ int load_config(char *path_config) const char* ver=json_object_get_string(obj); strcpy(config.version,ver); - json_object_object_get_ex(jobj, "local", &obj); - const char* lcl=json_object_get_string(obj); - strcpy(config.local,lcl); + //json_object_object_get_ex(jobj, "local", &obj); + //const char* lcl=json_object_get_string(obj); + //strcpy(config.local,lcl); json_object_object_get_ex(jobj, "basedir", &obj); const char* basedir=json_object_get_string(obj); @@ -87,6 +91,14 @@ int load_config(char *path_config) const char* baseurl=json_object_get_string(obj); if(baseurl!=NULL) strcpy(config.baseurl,baseurl); + json_object_object_get_ex(jobj, "server_key", &obj); + const char* server_key=json_object_get_string(obj); + if(server_key!=NULL) strcpy(config.server_key,server_key); + + json_object_object_get_ex(jobj, "client_key", &obj); + const char* client_key=json_object_get_string(obj); + if(client_key!=NULL) strcpy(config.client_key,client_key); + json_object_object_get_ex(jobj, "port", &obj); config.port=json_object_get_int(obj); diff --git a/server/proteo_info.c b/server/proteo_info.c index addc546..59a28ad 100644 --- a/server/proteo_info.c +++ b/server/proteo_info.c @@ -5,20 +5,66 @@ struct MemoryStruct { int addTicket(const char* app,const char* url,int value) { - //TODO - //Lua script + lua_getglobal(L,"ticket_add"); + lua_pushlstring(L,app,strlen(app)); + lua_pushlstring(L,url,strlen(url)); + lua_pushinteger(L, value); +#ifdef DEBUGGER + int error = dbg_pcall(L,3,1,0); +#else + int error = lua_trace_pcall(L,3,1);//lua_pcall(L, 1, 0, 0); +#endif + + if (error) { + printf("ERROR pcall(ticket_add): %s\n", lua_tostring(L, -1)); + //lua_pop(L, 1); + if(verbose) printf("Add ticket GetTop %d\n",lua_gettop(L)); + return 1; + + } + + return 0; //IF SQLITE - return sqlite_addTicket(app,url,value); + //return sqlite_addTicket(app,url,value); } int getTicket(const char* app,char** url) { - //TODO - //Lua script + lua_getglobal(L,"ticket_get"); + lua_pushlstring(L,app,strlen(app)); + //int error = lua_trace_pcall(L, 6, 1); +#ifdef DEBUGGER + int error = dbg_pcall(L,1,1,0); +#else + int error = lua_trace_pcall(L,1,1);//lua_pcall(L, 1, 0, 0); +#endif + + if (error) { + printf("ERROR pcall(ticket_get): %s\n", lua_tostring(L, -1)); + //lua_pop(L, 1); + if(verbose) printf("Get ticket GetTop %d\n",lua_gettop(L)); + return 1; + + } + + const char* ret=NULL; + int iType = lua_type(L, -1); + switch (iType) + { + case LUA_TSTRING: + ret = lua_tostring(L, -1);//Oppure 1? + lua_pop(L, 1); + *url=malloc(strlen(ret)+1); + strcpy(*url,ret); + break; + default: + printf("WHAT?!?!?!\n"); + } + return 0; //IF SQLITE - return sqlite_getTicket(app,url); + //return sqlite_getTicket(app,url); } struct MHD_Response * proteo_info() @@ -135,8 +181,20 @@ void get_info(char* url) void get_server_info() { char path[100]; - strcpy(path,config.local); - strcat(path,"/info"); + /*if(config.ssl) + strcpy(path,"https://"); + else + strcpy(path,"http://"); + strcat(path,config.baseurl); + strcat(path,":"); + strcat(path,config.port); + strcat(path,"/info");*/ + + //snprintf(path,sizeof(path)-1,"%s%s:%d/info",config.ssl?"https://":"http://",config.baseurl,config.port); + + //With some urls it may not work, so it's better to set always localhost + snprintf(path,sizeof(path)-1,"%slocalhost:%d/info",config.ssl?"https://":"http://",config.port); + if(verbose) printf("get_info: %s\n",path); get_info(path); @@ -149,3 +207,22 @@ void get_server_info() get_info(path); } } + +static int info_updateTickets(lua_State *state) { + + const char* url = luaL_checkstring(state,1); + if(strcmp(url, config.baseurl)!=0) + { + //TODO + //Non può invocare se stesso perchè si blocca, bisogna procedere in altro modo + + return 0; + } + char path[100]; + strcpy(path,url); + strcat(path,"/info"); + + get_info(path); + + return 0; +} diff --git a/server/proteo_lua.c b/server/proteo_lua.c index 04ce020..34462e8 100644 --- a/server/proteo_lua.c +++ b/server/proteo_lua.c @@ -35,6 +35,7 @@ static int traceback(lua_State *state) { } sem_t* lua_sem; + int lua_trace_pcall( lua_State* state, int nargs, int nret ) { //sem_wait(lua_sem); @@ -47,6 +48,7 @@ int lua_trace_pcall( lua_State* state, int nargs, int nret ) { ret = lua_pcall( state, nargs, nret, hpos ); + lua_remove( state, hpos ); //sem_post(lua_sem); @@ -81,5 +83,5 @@ void init(lua_State *state,const char* app) //chiamato da initLUA void closeLUA() { lua_close(L); - sem_close(lua_sem); + if(lua_sem!=SEM_FAILED) sem_close(lua_sem); } diff --git a/server/proteo_network.c b/server/proteo_network.c index ba96bd4..6c0235e 100644 --- a/server/proteo_network.c +++ b/server/proteo_network.c @@ -317,7 +317,7 @@ static int network_get(lua_State *state) { const char* token = luaL_checkstring(state,2); const char* callback = luaL_checkstring(state,3); - server_get(url,(const char*)clientkey,token,callback); + server_get(url,(const char*)config.client_key,token,callback); return 0; } @@ -333,7 +333,7 @@ static int network_post(lua_State *state) { const char* token = luaL_checkstring(state,3); const char* callback = luaL_checkstring(state,4); - server_post(url,(const char*)clientkey,token,json,callback); + server_post(url,(const char*)config.client_key,token,json,callback); free((void*)json); diff --git a/server/proteo_opencv.cpp b/server/proteo_opencv.cpp index 66f7946..2bb8027 100644 --- a/server/proteo_opencv.cpp +++ b/server/proteo_opencv.cpp @@ -2,21 +2,36 @@ //#ifdef PROTEO_OPENCV //#ifdef __cplusplus + +#include #include -#include +/*#include #include +#include #include +#include +#include */ #include #define OPENCVMAT "OpenCVMat" #define OPENCVVC "OpenCVVideoCapture" -#define OPENCVNET "OpenCVNet" #define OPENCVVW "OpenCVVideoWriter" +#if OPENCV_DNN +//CV_VERSION_MAJOR >= 4 +#define OPENCVNET "OpenCVNet" +#endif + +#ifndef CV_16F +#define CV_16F 7 +#endif + using namespace std; using namespace cv; +#ifdef OPENCVNET using namespace cv::dnn; -//using namespace cv::cuda; +using namespace cv::cuda; +#endif extern "C" { //#endif @@ -87,6 +102,7 @@ static VideoCapture *checkCap (lua_State *L, int index) return cap; } +#ifdef OPENCVNET static Net *checkNet (lua_State *L, int index) { Net *net; @@ -95,6 +111,7 @@ static Net *checkNet (lua_State *L, int index) if (net == NULL) luaL_typerror(L, index, OPENCVNET); return net; } +#endif static VideoWriter *checkWriter (lua_State *L, int index) { @@ -135,6 +152,7 @@ static int vc_gc(lua_State *l) { return 0; } +#ifdef OPENCVNET static int net_gc(lua_State *l) { Net* net = checkNet(l, 1); @@ -147,6 +165,7 @@ static int net_gc(lua_State *l) { return 0; } +#endif static int vw_gc(lua_State *l) { @@ -208,7 +227,7 @@ static UMat * pushMat (lua_State *state) }*/ - +#ifdef OPENCVNET static Net *pushNet (lua_State *state) { Net *net = new (lua_newuserdata(state, sizeof(Net))) Net(); @@ -229,6 +248,7 @@ static Net *pushNet (lua_State *state) return net; } +#endif static VideoWriter *pushWriter (lua_State *state) { @@ -425,7 +445,12 @@ int opencv_write(lua_State* state) //UMat *ret=pushMat(state); VideoWriter *vw=checkWriter(state,1); UMat *ret=checkMat(state,2); - if(vw!=NULL) vw->write(*ret); + if(vw!=NULL) + #ifdef OPENCVNET + vw->write(*ret); + #else + vw->write(ret->getMat(ACCESS_READ)); + #endif // check if we succeeded int success=1; if (ret->empty()) { @@ -704,6 +729,7 @@ int opencv_print(lua_State* state) } +#ifdef OPENCVNET int opencv_readnet(lua_State* state) { const char* model=luaL_checkstring(state,1); @@ -715,7 +741,9 @@ int opencv_readnet(lua_State* state) return 1; } +#endif +#ifdef OPENCVNET int opencv_forward(lua_State* state) { Net* net=checkNet(state,1); @@ -755,7 +783,9 @@ int opencv_forward(lua_State* state) return 0; } +#endif +#ifdef OPENCVNET int opencv_forwardTable(lua_State* state) { Net* net=checkNet(state,1); @@ -832,6 +862,7 @@ int opencv_forwardTable(lua_State* state) return 0; } +#endif int opencv_sliceImg(lua_State* state) { @@ -1077,6 +1108,8 @@ int opencv_infoImg(lua_State* state) return 0; } + +#ifdef OPENCVNET int opencv_infoNet(lua_State* state) { Net * net = (Net *)luaL_checkudata(state, 1, OPENCVNET); @@ -1098,6 +1131,7 @@ int opencv_infoNet(lua_State* state) return 0; } +#endif int opencv_size(lua_State* state) { @@ -1169,7 +1203,19 @@ int opencv_totable(lua_State* state) lua_newtable(state); for (int c = 0; c < mat.cols; c++) { lua_pushinteger(state, c+1); - lua_pushnumber(state,mat.at(r, c)); + float value; + + switch (mat.depth()) + { + case CV_8U: value = mat.at(r, c); break; + case CV_8S: value = mat.at(r, c); break; + case CV_16U: value = mat.at(r, c); break; + case CV_16S: value = mat.at(r, c); break; + case CV_32S: value = mat.at(r, c); break; + case CV_32F: value = mat.at(r, c); break; + case CV_64F: value = mat.at(r, c); break; + } + lua_pushnumber(state,value); lua_settable(state,-3); } lua_settable(state,-3); diff --git a/server/proteo_server.c b/server/proteo_server.c index 31636e5..2f247d9 100644 --- a/server/proteo_server.c +++ b/server/proteo_server.c @@ -1,15 +1,19 @@ //============================================================================== -//=> Proteo Server v0.0.1 +//=> Proteo Server v0.2 //=> //=> CC BY-NC-SA 3.0 //=> //=> Massimo Bernava //=> massimo.bernava@gmail.com -//=> 2021-05-13 +//=> 2021-06-15 //============================================================================== +//#define EJDB2 //CMAKE +//#define DEEPSPEECH //CMAKE +//#define TFLITE //CMAKE + #define PROTEO_OPENCV -#define DEBUGGER +//#define DEBUGGER #include "proteo_server.h" #include "proteo_b64.c" @@ -22,7 +26,9 @@ #include "proteo_auth.c" #include "proteo_sqlite.c" +#ifdef EJDB2 #include "proteo_ejdb.c" +#endif #include "proteo_system.c" #include "proteo_info.c" #include "proteo_network.c" @@ -30,7 +36,9 @@ #include "proteo_admin.c" #include "proteo_zmq.c" #include "proteo_ffmpeg.c" +#ifdef TFLITE #include "proteo_tensorflow.c" +#endif //============================================================================== @@ -382,10 +390,10 @@ LUALIB_API int luaopen_proteo (lua_State *state) { lua_newtable(state); lua_setfield(state, -2, "opencv"); - +#ifdef EJDB2 lua_newtable(state); lua_setfield(state, -2, "ejdb"); - +#endif lua_newtable(state); lua_setfield(state, -2, "ffmpeg"); @@ -397,7 +405,9 @@ LUALIB_API int luaopen_proteo (lua_State *state) { add_system_proteo(state); add_zmq_proteo(state); add_ffmpeg_proteo(state); +#ifdef TFLITE add_tensorflow_proteo(state); +#endif return 1; } @@ -436,8 +446,14 @@ void addEnum_proteo(lua_State *state,const char *lib,const char *enumname,const int initLUA() { //sem_init(&lua_mutex,0,1); - sem_unlink("/lua_call_sem"); - lua_sem = sem_open ("/lua_call_sem", O_CREAT , 0644, 1);//| O_EXCL + char sem_name[10]; + + rand_string(sem_name, 10); + sem_name[0]="/"; + //printf("sem name: %s",sem_name); + //if the proteo crash it could remain a suspended semaphore and deny permission to open it. In this way a different semaphore is created each time. + sem_unlink(sem_name); + lua_sem = sem_open (sem_name, O_CREAT , 0666,1);//0666, 1);//| O_EXCL if(lua_sem==SEM_FAILED) { printf("Sem_open: Failed: %s!\n",strerror(errno)); @@ -467,9 +483,12 @@ int initLUA() addFunction_proteo(L,"opencv","videowriter",opencv_videowriter); addFunction_proteo(L,"opencv","write",opencv_write); addFunction_proteo(L,"opencv","frame",opencv_frame); +#ifdef OPENCV_DNN addFunction_proteo(L,"opencv","readnet",opencv_readnet); addFunction_proteo(L,"opencv","forward",opencv_forward); addFunction_proteo(L,"opencv","forwardTable",opencv_forwardTable); + addFunction_proteo(L,"opencv","infoNet",opencv_infoNet); +#endif //addFunction_proteo(L,"opencv","getpoints",opencv_getpoints); addFunction_proteo(L,"opencv","circle",opencv_circle); addFunction_proteo(L,"opencv","rectangle",opencv_rectangle); @@ -481,7 +500,7 @@ int initLUA() addFunction_proteo(L,"opencv","size",opencv_size); addFunction_proteo(L,"opencv","cropImg",opencv_cropImg); addFunction_proteo(L,"opencv","infoImg",opencv_infoImg); - addFunction_proteo(L,"opencv","infoNet",opencv_infoNet); + addFunction_proteo(L,"opencv","setImg",opencv_setImg); addFunction_proteo(L,"opencv","setSize",opencv_setSize); addFunction_proteo(L,"opencv","fill",opencv_fill); @@ -526,7 +545,7 @@ int initLUA() #endif addFunction_proteo(L,"sqlite","exe",sqlite_exe); - +#ifdef EJDB2 addFunction_proteo(L,"ejdb","exe",ejdb_lua_exe); //addFunction_proteo(L,"ejdb","new",ejdb_lua_new); addFunction_proteo(L,"ejdb","del",ejdb_lua_del); @@ -534,7 +553,7 @@ int initLUA() addFunction_proteo(L,"ejdb","put",ejdb_lua_put); addFunction_proteo(L,"ejdb","merge",ejdb_lua_merge); addFunction_proteo(L,"ejdb","patch",ejdb_lua_patch); - +#endif //addFunction_proteo(L,"network","download",network_download); addFunction_proteo(L,"network","get",network_get); addFunction_proteo(L,"network","post",network_post); @@ -596,6 +615,12 @@ int initLUA() luaL_dofile(L,route_path); free(route_path); + char* ticket_path=concat(config.basedir,"ticket.lua"); + luaL_dofile(L,ticket_path); + free(ticket_path); + + addFunction_proteo(L,"system","updateTickets",info_updateTickets); + for (int i = 0; i < config.plugin_count; i++) { printf("Init Plug: %s\n",config.plugins[i].name); //init(plug_name); //proteo_lua.c @@ -607,7 +632,7 @@ int initLUA() const char* app_call(int type,const char* url,char* data,char* permissions,char* username,const char* token) { - sem_wait(lua_sem); + if(lua_sem!=SEM_FAILED) sem_wait(lua_sem); lua_getglobal(L,"route_call"); /*int type=0; if(0 == strcmp (method, "GET") ) type=GET; @@ -638,7 +663,7 @@ const char* app_call(int type,const char* url,char* data,char* permissions,char* printf("ERROR pcall(route.call): %s\n", lua_tostring(L, -1)); //lua_pop(L, 1); if(verbose) printf("App_call GetTop %d\n",lua_gettop(L)); - sem_post(lua_sem); + if(lua_sem!=SEM_FAILED) sem_post(lua_sem); return NULL; } @@ -666,7 +691,7 @@ const char* app_call(int type,const char* url,char* data,char* permissions,char* if(verbose) printf("App_call GetTop %d\n",top); //lua_pop(L, 1); - sem_post(lua_sem); + if(lua_sem!=SEM_FAILED) sem_post(lua_sem); if(paused && toreboot>=0) { @@ -794,6 +819,7 @@ struct MHD_Response* parse_info(struct connection_info_struct *con_info,const ch if (login_correct==1) { char* path=concat(config.basedir,"web/blockly/index.html"); + //char* path=concat(config.basedir,"web/ace/editor.html"); char *page=loadfile(path); free(path); @@ -893,7 +919,7 @@ struct MHD_Response* parse_info(struct connection_info_struct *con_info,const ch char permissions[512]; char username[100]; - if(verifyToken(username,permissions,app,token)==0 && verifyHMAC(con_info->url,con_info->data,token,clientkey,hmac)==0) + if(verifyToken(username,permissions,app,token)==0 && verifyHMAC(con_info->url,con_info->data,token,config.client_key,hmac)==0) { if(verbose) printf("Token and HMAC ok\n"); //luaL_dostring(L, "package.path = './lib/?.lua;' .. package.path"); @@ -1136,8 +1162,16 @@ int main(int argc,char **argv) { int c; char config_path[256]; + + //TODO andrebbe messo in una cartella standard sul server, indipendente da basedir +#ifdef __linux__ + //mkpath("/usr/local/etc/Proteo"); + //strcpy(config_path,"/usr/local/etc/Proteo/config.json"); + strcpy(config_path,"./config.json"); +#else strcpy(config_path,"./config.json"); - +#endif + while ((c = getopt (argc, argv, "vhdc:")) != -1) switch (c) { @@ -1195,13 +1229,16 @@ int main(int argc,char **argv) return 1; } + lua_pushlstring(L,config_path,strlen(config_path)); + lua_setglobal(L, "CONFIG_PATH"); +#ifdef EJDB2 iwrc rc = ejdb_init(); if (rc) { iwlog_ecode_error3(rc); printf( "Failed to initialize EJDB!\n" ); return 1; } - +#endif //pthread_t poll_event; if(config.master==1) @@ -1223,7 +1260,9 @@ int main(int argc,char **argv) printf("%s\n",LUA_VERSION); #endif printf("JSON v.%s\n",JSON_C_VERSION); +#ifdef TFLITE printf("TensorFlowLite v.%s\n", TfLiteVersion()); +#endif MHD_set_panic_func(&panicCallback, NULL); @@ -1233,7 +1272,7 @@ int main(int argc,char **argv) if(config.ssl==0) { - daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD, 8888, + daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD, config.port, NULL, NULL, &url_handler,NULL, MHD_OPTION_NOTIFY_COMPLETED, &request_completed, @@ -1253,7 +1292,7 @@ int main(int argc,char **argv) return 1; } - daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_DEBUG | MHD_USE_SSL, 8888, + daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_DEBUG | MHD_USE_SSL, config.port, NULL, NULL, &url_handler,NULL, MHD_OPTION_NOTIFY_COMPLETED, &request_completed,NULL, @@ -1298,7 +1337,7 @@ int main(int argc,char **argv) } ProteoTimer* timer=timers; - double min=1000; + long min=1000; while(timer!=NULL) { if(timer->state==1) @@ -1308,7 +1347,7 @@ int main(int argc,char **argv) { if(paused) continue; - sem_wait(lua_sem); + if(lua_sem!=SEM_FAILED) sem_wait(lua_sem); if(timer->callback!=NULL) lua_getglobal(L,timer->callback); else if(timer->ref_callback!=-1) @@ -1333,11 +1372,11 @@ int main(int argc,char **argv) } } timer->last=n; - sem_post(lua_sem); + if(lua_sem!=SEM_FAILED) sem_post(lua_sem); } else { - double next=timer->time -(n-timer->last); + long next=timer->time -(n-timer->last); if(next #include //#include +#ifdef TFLITE #include +#endif + +#ifdef DEEPSPEECH +#include +#endif #include #include @@ -77,7 +83,10 @@ #define ENET_IMPLEMENTATION #define ENET_DEBUG #include "enet.h" + +#ifdef EJDB2 #include +#endif #include @@ -165,7 +174,7 @@ int paused; int toreboot=-1; int verbose=FALSE; int debug=FALSE; -char basedir[50]; +//char basedir[50]; #define PROTEOCOMPONENT "ProteoComponent" #define PROTEOTIMER "ProteoTimer" diff --git a/server/proteo_sqlite.c b/server/proteo_sqlite.c index 473e62b..27ffd2b 100644 --- a/server/proteo_sqlite.c +++ b/server/proteo_sqlite.c @@ -58,15 +58,15 @@ int sqlite_create_auth_user_db(const char* path) ");" "INSERT INTO `permissions` VALUES (1,1,'proteo','admin',1,CURRENT_TIMESTAMP);" - "INSERT INTO `permissions` VALUES (2,1,'admin','admin',1,CURRENT_TIMESTAMP);" + "INSERT INTO `permissions` VALUES (2,1,'admin','admin',1,CURRENT_TIMESTAMP);"; - "DROP TABLE IF EXISTS `tickets`;" + /*"DROP TABLE IF EXISTS `tickets`;" "CREATE TABLE `tickets` (" "`id_tick` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT," "`app` TEXT NOT NULL," "`url` TEXT NOT NULL," "`ticket` INTEGER NOT NULL," - "UNIQUE(app,url));"; + "UNIQUE(app,url));";*/ return sqlite_exec(path,sql); } @@ -303,7 +303,7 @@ static int sqlite_exe (lua_State *state) { //============================================= // TICKETS //============================================= -int sqlite_addTicket(const char* app,const char* url,int value) +/*int sqlite_addTicket(const char* app,const char* url,int value) { char sql[256]; @@ -417,7 +417,7 @@ int sqlite_getTicket(const char* app,char** url) return 1; } - +*/ //============================================= // AUTH //============================================= diff --git a/server/proteo_system.c b/server/proteo_system.c index ea952bd..907b4b9 100644 --- a/server/proteo_system.c +++ b/server/proteo_system.c @@ -44,7 +44,7 @@ static int system_fileexist(lua_State *state) { static int system_document(lua_State *state) { if(verbose) printf("system.document\n"); //lua_pushliteral(state,"./"); - lua_pushlstring(state,basedir,strlen(basedir)); + lua_pushlstring(state,config.basedir,strlen(config.basedir)); return 1; } @@ -186,6 +186,8 @@ static int system_createTimer(lua_State *state) { ProteoTimer* pt=pushProteoTimer(state); + printf("Create timer ms: %f\n",time); + pt->time=time; //pt->callback=callback; @@ -320,8 +322,8 @@ void sendMail(const char* to_mail, const char* from_mail, const char* cc_mail, c curl = curl_easy_init(); if(curl) { /* Set username and password */ - curl_easy_setopt(curl, CURLOPT_USERNAME, ""); - curl_easy_setopt(curl, CURLOPT_PASSWORD, ""); + curl_easy_setopt(curl, CURLOPT_USERNAME, "massimo.bernava@gmail.com"); + curl_easy_setopt(curl, CURLOPT_PASSWORD, "juzbjdkglpzhspnf"); /* This is the URL for your mailserver. Note the use of smtps:// rather * than smtp:// to request a SSL based connection. */ diff --git a/server/script/admin.lua b/server/script/admin.lua index 84f43ab..b739b0d 100644 --- a/server/script/admin.lua +++ b/server/script/admin.lua @@ -5,15 +5,20 @@ local json=require "json" -- delete checkbox -- password mode per i textbox -- avvisi per passord assende, sbagliata o user che non è una mail --- search +-- search textbox -- server REBOOT +-- REPOSITORY +-- A che serve PERMISSIONS? + + +admin={} --TODO una sola variabile globale form_users=nil -form_options=nil +--form_config=nil form_permissions=nil -option_button=nil +--option_button=nil users_button=nil permissions_button=nil @@ -52,10 +57,10 @@ function init() proteo.gui.setState(username_admin,proteo.gui.FormState.WindowBar) form_users=proteo.gui.newForm("form_users","Users",'Helvetica',20,"jet","crimson","blanchedalmond",30,proteo.gui.FormType.Fixed+proteo.gui.FormType.HideResize+proteo.gui.FormType.HideClose,"","",150,150,MAX_X-300,MAX_Y-300,"") - form_options=proteo.gui.newForm("form_options","Options",'Helvetica',20,"jet","crimson","blanchedalmond",30,proteo.gui.FormType.Fixed+proteo.gui.FormType.HideResize+proteo.gui.FormType.HideClose,"","",150,150,MAX_X-300,MAX_Y-300,"") + admin.form_config=proteo.gui.newForm("form_config","Config",'Helvetica',20,"jet","crimson","blanchedalmond",30,proteo.gui.FormType.Fixed+proteo.gui.FormType.HideResize+proteo.gui.FormType.HideClose,"","",150,150,MAX_X-300,MAX_Y-300,"") form_permissions=proteo.gui.newForm("form_permissions","Permissions",'Helvetica',20,"jet","crimson","blanchedalmond",30,proteo.gui.FormType.Fixed+proteo.gui.FormType.HideResize+proteo.gui.FormType.HideClose,"","",150,150,MAX_X-300,MAX_Y-300,"") - option_button=proteo.gui.newButton('option_button','Options','Helvetica',20,'jet','blanchedalmond',1,"crimson",false,MIN_X,180,150,50,open_options) + admin.config_button=proteo.gui.newButton('config_button','Config','Helvetica',20,'jet','blanchedalmond',1,"crimson",false,MIN_X,180,150,50,open_options) users_button=proteo.gui.newButton('users_button','Users','Helvetica',20,'jet','blanchedalmond',1,"crimson",false,MIN_X,230,150,50,open_users) permissions_button=proteo.gui.newButton('permissions_button','Permissions','Helvetica',20,'jet','blanchedalmond',1,"crimson",false,MIN_X,280,150,50,open_permissions) @@ -106,15 +111,15 @@ function init() removeplugin_button=proteo.gui.newButton('removeplugin_button',"Remove",'Helvetica',20,"jet","blanchedalmond",1,"crimson",false,175 ,425,100,25,"remove_plugin") save_button=proteo.gui.newButton('save_button',"Save",'Helvetica',20,"jet","blanchedalmond",1,"crimson",false,MAX_X-425 ,MAX_Y-350,100,25,"save_option") - proteo.gui.addItem(form_options,label_plugins) - proteo.gui.addItem(form_options,list_plugins) - proteo.gui.addItem(form_options,addplugin_button) - proteo.gui.addItem(form_options,removeplugin_button) - proteo.gui.addItem(form_options,save_button) + proteo.gui.addItem(admin.form_config,label_plugins) + proteo.gui.addItem(admin.form_config,list_plugins) + proteo.gui.addItem(admin.form_config,addplugin_button) + proteo.gui.addItem(admin.form_config,removeplugin_button) + proteo.gui.addItem(admin.form_config,save_button) proteo.network.proteo_get("/admin/config","config_callback") - open_options(option_button) + open_options(admin.config_button) end -------------------- @@ -146,20 +151,20 @@ end function open_options(sender) proteo.gui.setHidden(form_users,true) - proteo.gui.setHidden(form_options,false) + proteo.gui.setHidden(admin.form_config,false) proteo.gui.setHidden(form_permissions,true) - proteo.gui.setColor(option_button,'blanchedalmond') + proteo.gui.setColor(admin.config_button,'blanchedalmond') proteo.gui.setColor(users_button,'cadetgrey') proteo.gui.setColor(permissions_button,'cadetgrey') end function open_users(sender) proteo.gui.setHidden(form_users,false) - proteo.gui.setHidden(form_options,true) + proteo.gui.setHidden(admin.form_config,true) proteo.gui.setHidden(form_permissions,true) - proteo.gui.setColor(option_button,'cadetgrey') + proteo.gui.setColor(admin.config_button,'cadetgrey') proteo.gui.setColor(users_button,'blanchedalmond') proteo.gui.setColor(permissions_button,'cadetgrey') @@ -176,10 +181,10 @@ end function open_permissions(sender) proteo.gui.setHidden(form_users,true) - proteo.gui.setHidden(form_options,true) + proteo.gui.setHidden(admin.form_config,true) proteo.gui.setHidden(form_permissions,false) - proteo.gui.setColor(option_button,'cadetgrey') + proteo.gui.setColor(admin.config_button,'cadetgrey') proteo.gui.setColor(users_button,'cadetgrey') proteo.gui.setColor(permissions_button,'blanchedalmond') end diff --git a/server/script/demo/movelab.json b/server/script/demo/movelab.json index 05606e8..f6c6195 100644 --- a/server/script/demo/movelab.json +++ b/server/script/demo/movelab.json @@ -1,4 +1,5 @@ { "version": "0.1", - "plugins": ["proteo","deepcrimson"] + "plugins": ["proteo","deepcrimson"], + "libs":["json","md5","inspect","tfl_utility"] } \ No newline at end of file diff --git a/server/script/demo/movelab.lua b/server/script/demo/movelab.lua index cba995c..0167084 100644 --- a/server/script/demo/movelab.lua +++ b/server/script/demo/movelab.lua @@ -27,39 +27,74 @@ colors.button_font="black" colors.button_back="aliceblue" colors.button_border="black" + + function selectvideo(sel) selected_video=videos[proteo.system.getName(sel)] + print("Selected video: "..selected_video) + --video_cap=proteo.opencv.videocapture("http://localhost:8888/sample_video.avi") end +function frame_update(dt) + + --analyzeframe(current_frame) + --frame_wl=proteo.opencv.img() + --proteo.opencv.copy(current_video[current_frame].frame,frame_wl) + + --proteo.graphics.changeImage(video_img,frame_wl) + + --current_frame=current_frame+1 + + analyzeframe(current_frame) + + current_frame=current_frame+1 + + if current_frame>#current_video then + proteo.system.stopTimer(frame_timer) + end +end + +frame_timer=proteo.system.createTimer(50,frame_update) + function landmarks_callback(res,data) frame_wl=proteo.opencv.img() --proteo.opencv.setSize(current_video[current_frame].frame_wl,video_size.x,video_size.y) proteo.opencv.copy(current_video[current_frame].frame,frame_wl) + current_video[current_frame].request=data['request'] current_video[current_frame].data=data['data'] - - for _, b in ipairs(data['data']) do - landmarks = show_landmark(b,frame_wl,b['landmarks']) + current_video[current_frame].landmarks={} + + for i, b in ipairs(data['data']) do + --print("Center X:"..landmarks[1].x.." Y:"..landmarks[1].y) + landmarks = get_landmark(b,frame_wl,b['landmarks']) + current_video[current_frame].landmarks[i]=landmarks + + if data['request']=="TFLPOSE" or data['request']=="TFLHOLI" then + show_pose(landmarks,frame_wl) + elseif data['request'] == "TFLFACE" then + show_facemesh(landmarks,frame_wl) + else + show_hand(landmarks,frame_wl) + end end + if current_video[current_frame].request=="TFLHOLI" then + for _, b in ipairs(data['face']) do + landmarks = show_landmark(b,frame_wl,b['landmarks']) + end + end + proteo.graphics.changeImage(video_img,frame_wl) + end -function analysevideo(sender) - - sel=proteo.gui.getValue(drop_video) - if sel==nil then - return - end - --proteo.opencv.resize(video_frame,video_resized) - --proteo.opencv.cropImg(video_frame,video_resized,(videosize[2]-456)/2,(videosize[1]-256)/2,456,256) - - --proteo.opencv.convert(current_video[videoframe].frame,current_video[videoframe].frame,proteo.opencv.matType.CV_32F) - local tmp=proteo.opencv.imencode(current_video[current_frame].frame) +function analyzeframe(nframe) + local tmp=proteo.opencv.imencode(current_video[nframe].frame) local data={} data['type']='FRAME' @@ -69,6 +104,10 @@ function analysevideo(sender) data["request"]="TFLFACE" elseif proteo.gui.getId(sel)=="drop_gui_pose" then data["request"]="TFLPOSE" + elseif proteo.gui.getId(sel)=="drop_gui_holi" then + data["request"]="TFLHOLI" + elseif proteo.gui.getId(sel)=="drop_gui_hand" then + data["request"]="TFLHAND" end data["frame"]=tmp @@ -76,22 +115,64 @@ function analysevideo(sender) local js=json.encode(data) proteo.network.proteo_post("/deepcrimson/landmarks",js,landmarks_callback) +end +function analyzecurrentframe(sender) + + sel=proteo.gui.getValue(drop_video) + if sel==nil then + return + end + + analyzeframe(current_frame) + +end + +function analyzevideo(sender) + + sel=proteo.gui.getValue(drop_video) + if sel==nil then + return + end + + current_frame=1 + proteo.system.startTimer(frame_timer) + +end + +function savedata(sender) + + for i=1,#current_video do + if current_video[i].landmarks~=nil then + for j=1,#current_video[i].landmarks do + row="Frame"..i..",Box"..j + for k=1,#current_video[i].landmarks[j] do + row=row..","..current_video[i].landmarks[j][k].x + row=row..","..current_video[i].landmarks[j][k].y + row=row..","..current_video[i].landmarks[j][k].z + end + print(row) + end + end + end end function showvideo(sender) if selected_video~=nil then current_video={} --data=proteo.system.readFile(selected_file,readfile) + --selected_video="http://localhost:8888/sample_video.avi" + + --In modalità web la fase di videocapture richiede un'attesa prima di richiedere il frame, per adesso ho spostato in selectvideo video_cap=proteo.opencv.videocapture(selected_video) local i=1 video_frame=proteo.opencv.img() while proteo.opencv.frame(video_cap,video_frame)==1 do + --if proteo.opencv.frame(video_cap,video_frame)==1 then current_video[i]={} current_video[i].frame=proteo.opencv.img() - proteo.opencv.setImg(current_video[i].frame,video_size.x,video_size.y,"#000000") proteo.opencv.resize(video_frame,current_video[i].frame) --proteo.opencv.infoImg(current_video[i].frame) @@ -99,7 +180,6 @@ function showvideo(sender) end current_frame=1 - proteo.graphics.changeImage(video_img,current_video[current_frame].frame) end @@ -114,10 +194,15 @@ function nextvideo(sender) frame_wl=proteo.opencv.img() proteo.opencv.copy(current_video[current_frame].frame,frame_wl) - for _, b in ipairs(current_video[current_frame].data) do - landmarks = show_landmark(b,frame_wl,b['landmarks']) - end - + for i=1,#current_video[current_frame].landmarks do + if current_video[current_frame].request == "TFLPOSE" or current_video[current_frame].requestv=="TFLHOLI" then + show_pose(current_video[current_frame].landmarks[i],frame_wl) + elseif current_video[current_frame].request == "TFLFACE" then + show_facemesh(current_video[current_frame].landmarks[i],frame_wl) + else + show_hand(current_video[current_frame].landmarks[i],frame_wl) + end + end proteo.graphics.changeImage(video_img,frame_wl) else @@ -136,10 +221,15 @@ function prevvideo(sender) frame_wl=proteo.opencv.img() proteo.opencv.copy(current_video[current_frame].frame,frame_wl) - for _, b in ipairs(current_video[current_frame].data) do - landmarks = show_landmark(b,frame_wl,b['landmarks']) - end - + for i=1,#current_video[current_frame].landmarks do + if current_video[current_frame].request == "TFLPOSE" or current_video[current_frame].requestv=="TFLHOLI" then + show_pose(current_video[current_frame].landmarks[i],frame_wl) + elseif current_video[current_frame].request == "TFLFACE" then + show_facemesh(current_video[current_frame].landmarks[i],frame_wl) + else + show_hand(current_video[current_frame].landmarks[i],frame_wl) + end + end proteo.graphics.changeImage(video_img,frame_wl) else @@ -155,16 +245,22 @@ function create_page() video_load=proteo.gui.newList('video_load','','Helvetica',30,colors.list_font,colors.list_back,MIN_X+20,150,500,400,selectvideo) show_video=proteo.gui.newButton('show_video',"Show>>",'Helvetica',25,colors.button_font,colors.button_back,1,colors.button_border,true,MIN_X + 420 ,MAX_Y - 220,100,50,showvideo) video_img=proteo.graphics.newImage("video","@video",800,350,video_size.x,video_size.y) - next_video=proteo.gui.newButton('next_video',">>",'Helvetica',25,colors.button_font,colors.button_back,1,colors.button_border,true,MIN_X + 650 ,MAX_Y - 120,100,50,nextvideo) - prev_video=proteo.gui.newButton('prev_video',"<<",'Helvetica',25,colors.button_font,colors.button_back,1,colors.button_border,true,MIN_X + 500 ,MAX_Y - 120,100,50,prevvideo) + next_video=proteo.gui.newButton('next_video',">>",'Helvetica',25,colors.button_font,colors.button_back,1,colors.button_border,true,MIN_X + 500 ,MAX_Y - 120,100,50,nextvideo) + prev_video=proteo.gui.newButton('prev_video',"<<",'Helvetica',25,colors.button_font,colors.button_back,1,colors.button_border,true,MIN_X + 350 ,MAX_Y - 120,100,50,prevvideo) - analyse_video=proteo.gui.newButton('analyse_video',"Analyse",'Helvetica',25,colors.button_font,colors.button_back,1,colors.button_border,true,MIN_X + 800 ,MAX_Y - 120,300,50,analysevideo) + analyze_frame=proteo.gui.newButton('analyse_frame',"Analyze Frame",'Helvetica',20,colors.button_font,colors.button_back,1,colors.button_border,true,MIN_X + 650 ,MAX_Y - 120,150,50,analyzecurrentframe) + analyze_video=proteo.gui.newButton('analyse_video',"Analyze All",'Helvetica',20,colors.button_font,colors.button_back,1,colors.button_border,true,MIN_X + 820 ,MAX_Y - 120,150,50,analyzevideo) + save_data=proteo.gui.newButton('analyse_video',"Save",'Helvetica',25,colors.button_font,colors.button_back,1,colors.button_border,true,MIN_X + 990 ,MAX_Y - 120,150,50,savedata) - drop_video=proteo.gui.newDropDown('drop_video',"Select Mode",'Helvetica',20,colors.button_font,colors.button_back,"",proteo.gui.DropDownType.Normal,colors.button_back,MIN_X+200 ,MAX_Y - 110,200,30,"") + drop_video=proteo.gui.newDropDown('drop_video',"Select Mode",'Helvetica',20,colors.button_font,colors.button_back,"",proteo.gui.DropDownType.Normal,colors.button_back,MIN_X+50 ,MAX_Y - 150,200,30,"") drop_gui_pose=proteo.gui.newDropDownItem('drop_gui_pose',"BlazePose",'Helvetica',20,colors.button_font,colors.button_back,"","",200,30) drop_gui_face=proteo.gui.newDropDownItem('drop_gui_face',"BlazeFace",'Helvetica',20,colors.button_font,colors.button_back,"","",200,30) + drop_gui_hand=proteo.gui.newDropDownItem('drop_gui_hand',"Hand",'Helvetica',20,colors.button_font,colors.button_back,"","",200,30) + drop_gui_holi=proteo.gui.newDropDownItem('drop_gui_holi',"Holistic",'Helvetica',20,colors.button_font,colors.button_back,"","",200,30) proteo.gui.addItem(drop_video,drop_gui_pose) proteo.gui.addItem(drop_video,drop_gui_face) + proteo.gui.addItem(drop_video,drop_gui_hand) + proteo.gui.addItem(drop_video,drop_gui_holi) proteo.gui.addItem(form,video_load) proteo.gui.addItem(form,video_img) @@ -172,7 +268,9 @@ function create_page() proteo.gui.addItem(form,next_video) proteo.gui.addItem(form,prev_video) proteo.gui.addItem(form,drop_video) - proteo.gui.addItem(form,analyse_video) + proteo.gui.addItem(form,analyze_video) + proteo.gui.addItem(form,analyze_frame) + proteo.gui.addItem(form,save_data) page={} page.form=form @@ -189,11 +287,17 @@ function load_list(video_list) proteo.system.document(), "%a*avi$", function(filename) + print("Found:"..filename) + --if EMSCRIPTEN==0 then videos[md5.sumhexa(filename)]=filename - list_item_file=proteo.gui.newListItem(md5.sumhexa(filename),proteo.system.basename(filename),'Helvetica',15,colors.list_font,colors.list_back,"","",500,50) - proteo.gui.addItem(video_list,list_item_file) + --else + -- videos[filename]=filename + -- list_item_file=proteo.gui.newListItem(filename,proteo.system.basename(filename),'Helvetica',15,colors.list_font,colors.list_back,"","",500,50) + --end + + proteo.gui.addItem(video_list,list_item_file) end ) end @@ -203,12 +307,19 @@ function start_callback(res,data) current_session = data['session'] end +function download_callback() + +end function init() home=create_page() load_list(home.video_load) + --if EMSCRIPTEN==1 then + -- proteo.network.download("sample_video.avi","/proteo/sample_video.avi","download_callback") + --end + proteo.network.proteo_post("/deepcrimson/start",'{}',start_callback) proteo.gui.setHidden(home.form,false) diff --git a/server/script/prova.json b/server/script/prova.json index 68069cc..23821f9 100644 --- a/server/script/prova.json +++ b/server/script/prova.json @@ -1,4 +1,5 @@ { "version": "0.1", - "plugins": ["proteo"] + "plugins": ["proteo"], + "libs":["json","skl_utility","tfl_utility"] } \ No newline at end of file diff --git a/server/script/prova.lua b/server/script/prova.lua index 1f96868..4ae55ce 100644 --- a/server/script/prova.lua +++ b/server/script/prova.lua @@ -109,6 +109,7 @@ farfalla_animation = {{0,0,33,0,1,1, 2,1, 3,1, 4,1, 5,1, 6,1, 7,1, 8,1, 9,1, 10, } farfalla_animation = farfalla_animation[1] graphics_event = function(dt) + farfalla_animation[CURRENT_TICK] = farfalla_animation[CURRENT_TICK] + 1 if math.random(1, 50) == 1 then d_x = math.random(-2, 2) @@ -145,6 +146,47 @@ skl_read_callback = function(data,filename) proteo.graphics.updateSkeleton(skeleton) end +--shader=nil +shader_u_time=2.0 + +function render(my_shader) + if my_shader~=nil then + proteo.graphics.setUniform(my_shader,"u_time",shader_u_time) + shader_u_time=shader_u_time+0.1 + end +end + +particle_option={} + +function endLife(sender) + + particle_option.frame_x=0 + particle_option.frame_y=0 + particle_option.frame_width=256 + particle_option.frame_height=256 + + particle_option.startSpeed=80.0+20*math.random() + particle_option.startDirection=(-3.14/2)-(math.random()-0.5) + particle_option.lifeTime=3000*math.random() + particle_option.linearAccelerationX=150.0 + 50*math.random() + particle_option.linearAccelerationY=50.0 + 50*math.random() + particle_option.angularSpeed=0 -- 1.5708 + + particle_option.position_x=20*math.random()-10 + particle_option.position_y=20*math.random()-10 + + particle_option.spin=5.0*math.random() + + particle_option.startSize=1.0 + particle_option.endSize=2.0 + + particle_option.startColor="#aae25822" + particle_option.endColor="#00bbaaff" + + proteo.graphics.setParticle(sender,particle_option) + proteo.graphics.setPosition(sender,farfalla_x,farfalla_y) +end + graphics_init = function(sender) t5 = proteo.system.createTimer(20,graphics_event) proteo.system.startTimer(t5) @@ -165,13 +207,64 @@ graphics_init = function(sender) shape = proteo.graphics.newShape("shape",150,150) svg2shape(svgfile,shape) skeleton = proteo.graphics.newSkeleton("skeleton") - proteo.system.readFile(proteo.system.document()..'skeleton.json',skl_read_callback) + --proteo.system.readFile(proteo.system.document()..'skeleton.json',skl_read_callback) + rect_graphics=proteo.graphics.newRect("rect","white","",100,100,200,200) + +math.randomseed( os.time() ) + + particle_option.frame_x=0 + particle_option.frame_y=0 + particle_option.frame_width=128 + particle_option.frame_height=128 + particle_option.startColor="#aae25822" + particle_option.endColor="#00bbaaff" + +particles={} + for p=1,500 do + particle_option.startSpeed=80.0+20*math.random() + particle_option.startDirection=(-3.14/2)-(math.random()-0.5) + particle_option.lifeTime=3000*math.random() + particle_option.linearAccelerationX=150.0 + 50*math.random() + particle_option.linearAccelerationY=50.0 + 50*math.random() + particle_option.angularSpeed=0 -- 1.5708 + particle_option.spin=5.0*math.random() + particle_option.startSize=1.0 + particle_option.endSize=2.0 + + particles[p]=proteo.graphics.newParticle("P"..p,proteo.system.document().."sparkle.png",500,200,32,32,particle_option,endLife) + end + +if SHADER==1 then + shader=proteo.graphics.newShader([[ + void main() + { + gl_Position = ftransform(); + } +]],[[ + uniform float u_time; + + void main() + { + //gl_FragColor = vec4( 1.0, 0.0, 1.0, 1.0 ); + gl_FragColor = vec4(abs(sin(u_time)),0.0,0.0,1.0); + } +]]) + +proteo.graphics.setShader(rect_graphics,shader,render) +end + graphicsForm=Form('graphicsForm') graphicsForm.backgroundColor='#000000' graphicsForm:addControl(t_graphics) graphicsForm:addControl(back_graphics) graphicsForm:addControl(farfalla) graphicsForm:addControl(shape) + graphicsForm:addControl(rect_graphics) + --graphicsForm:addControl(part) + for p=1,500 do + graphicsForm:addControl(particles[p]) + end + forms['graphicsForm']=graphicsForm forms['graphicsForm']:show() end diff --git a/server/ticket.lua b/server/ticket.lua new file mode 100644 index 0000000..af85cda --- /dev/null +++ b/server/ticket.lua @@ -0,0 +1,41 @@ + + +current_ticket_db={} + +function ticket_get(app) + + maxT=-1 + bestUrl="localhost" + for url, value in pairs(current_ticket_db[app]) do + + if value>maxT then + maxT=value + bestUrl=url + end + end + + print("Get Ticket: " ..app.." "..bestUrl.." "..maxT) + + if maxT>0 then + current_ticket_db[app][bestUrl]=maxT-1 + else + proteo.system.updateTickets(bestUrl) + end + + return bestUrl +end + +function ticket_add(app,url,value) + + --If the url is localhost you have to modify it before sending it to the client + url=url:gsub("localhost",BASEURL) + + print("Add Ticket: " ..app.." "..url.." "..value) + + if current_ticket_db[app]==nil then + current_ticket_db[app]={} + end + + current_ticket_db[app][url]=value + +end \ No newline at end of file diff --git a/server/update.sh b/server/update.sh deleted file mode 100755 index 271b761..0000000 --- a/server/update.sh +++ /dev/null @@ -1,6 +0,0 @@ -git pull -cd build -cmake -DCMAKE_BUILD_TYPE=Debug -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl .. -make -cp ./proteo_server ../ -cd .. diff --git a/server/upload.sh b/server/upload.sh deleted file mode 100755 index ba9e24d..0000000 --- a/server/upload.sh +++ /dev/null @@ -1,3 +0,0 @@ -git stage * -git commit -git push diff --git a/server/web/blockly/code.js b/server/web/blockly/code.js index 7363799..0ac7a77 100644 --- a/server/web/blockly/code.js +++ b/server/web/blockly/code.js @@ -14,7 +14,7 @@ * Create a namespace for the application. */ var Code = {}; -var appKey="1234567890123456789012"; +var appKey="}~?d1BE+\"d5?TZ(j`{+n`pfK&*2U(WPy"; /** * Lookup for names of supported languages. Keys should be in ISO 639 format. diff --git a/server/web/blockly/demo.js b/server/web/blockly/demo.js index 814420e..774a938 100644 --- a/server/web/blockly/demo.js +++ b/server/web/blockly/demo.js @@ -537,7 +537,7 @@ Blockly.Lua['tetris_getjoint'] = function(block) { Blockly.Blocks['tetris_move'] = { init: function() { this.appendDummyInput() - .appendField("move") + .appendField("move block") .appendField(new Blockly.FieldDropdown([["left","left"], ["right","right"], ["rotate","rotate"], ["down","down"]]), "joint"); this.setPreviousStatement(true, null); this.setNextStatement(true, null); @@ -559,4 +559,94 @@ Blockly.Lua['tetris_move'] = function(block) { return code; }; +Blockly.Blocks['tetris_move_action'] = { + init: function() { + this.appendStatementInput("effect") + .setCheck(null) + .appendField("if user") + .appendField(new Blockly.FieldDropdown([["raise left arm","LEFT_ARM"], ["raise right arm","RIGHT_ARM"], ["crouch","CROUCH"], ["jump","JUMP"], ["tilt head left","TILT_LEFT"], ["tilt head right","TILT_RIGHT"]]), "NAME"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(225); + this.setTooltip(""); + this.setHelpUrl(""); + } +}; + +Blockly.Lua['tetris_move_action'] = function(block) { + var dropdown_name = block.getFieldValue('NAME'); + var statements_effect = Blockly.Lua.statementToCode(block, 'effect'); + // TODO: Assemble Lua into code variable. + var code = '...\n'; + return code; +}; + +Blockly.Blocks['tetris_save_block'] = { + init: function() { + this.appendDummyInput() + .appendField("save current information:"); + this.appendDummyInput() + .setAlign(Blockly.ALIGN_RIGHT) + .appendField("number of times raise left arm") + .appendField(new Blockly.FieldCheckbox("TRUE"), "NAME"); + this.appendDummyInput() + .setAlign(Blockly.ALIGN_RIGHT) + .appendField("number of times raise right arm") + .appendField(new Blockly.FieldCheckbox("TRUE"), "NAME"); + this.appendDummyInput() + .setAlign(Blockly.ALIGN_RIGHT) + .appendField("greater angle of rotation of the right shoulder") + .appendField(new Blockly.FieldCheckbox("TRUE"), "NAME"); + this.appendDummyInput() + .setAlign(Blockly.ALIGN_RIGHT) + .appendField("greater angle of rotation of the left shoulder") + .appendField(new Blockly.FieldCheckbox("TRUE"), "NAME"); + this.appendDummyInput() + .setAlign(Blockly.ALIGN_RIGHT) + .appendField("number of jumps") + .appendField(new Blockly.FieldCheckbox("TRUE"), "NAME"); + this.appendDummyInput() + .setAlign(Blockly.ALIGN_RIGHT) + .appendField("number of squats") + .appendField(new Blockly.FieldCheckbox("TRUE"), "NAME"); + this.appendDummyInput() + .setAlign(Blockly.ALIGN_RIGHT) + .appendField("number of left head tilts") + .appendField(new Blockly.FieldCheckbox("TRUE"), "NAME"); + this.appendDummyInput() + .setAlign(Blockly.ALIGN_RIGHT) + .appendField("number of right head tilts") + .appendField(new Blockly.FieldCheckbox("TRUE"), "NAME"); + this.appendDummyInput() + .setAlign(Blockly.ALIGN_RIGHT) + .appendField("greater angle of left head tilts") + .appendField(new Blockly.FieldCheckbox("TRUE"), "NAME"); + this.appendDummyInput() + .setAlign(Blockly.ALIGN_RIGHT) + .appendField("greater angle of right head tilts") + .appendField(new Blockly.FieldCheckbox("TRUE"), "NAME"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(225); + this.setTooltip(""); + this.setHelpUrl(""); + } +}; + +Blockly.Lua['tetris_save_block'] = function(block) { + var checkbox_name = block.getFieldValue('NAME') == 'TRUE'; + var checkbox_name = block.getFieldValue('NAME') == 'TRUE'; + var checkbox_name = block.getFieldValue('NAME') == 'TRUE'; + var checkbox_name = block.getFieldValue('NAME') == 'TRUE'; + var checkbox_name = block.getFieldValue('NAME') == 'TRUE'; + var checkbox_name = block.getFieldValue('NAME') == 'TRUE'; + var checkbox_name = block.getFieldValue('NAME') == 'TRUE'; + var checkbox_name = block.getFieldValue('NAME') == 'TRUE'; + var checkbox_name = block.getFieldValue('NAME') == 'TRUE'; + var checkbox_name = block.getFieldValue('NAME') == 'TRUE'; + // TODO: Assemble Lua into code variable. + var code = '...\n'; + return code; +}; + diff --git a/server/web/blockly/index.html b/server/web/blockly/index.html index 06cfff6..bdb02fd 100644 --- a/server/web/blockly/index.html +++ b/server/web/blockly/index.html @@ -711,6 +711,8 @@

    + +