server.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. const express = require('express');
  2. const dgram = require('dgram');
  3. const oscParser = require('./osc-parser');
  4. const bodyParser = require('body-parser');
  5. const { Bundle, Client } = require('node-osc');
  6. const path = require('path');
  7. const config = require('../frontend/assets/config.json');
  8. const app = express();
  9. app.use(bodyParser.json());
  10. app.use(bodyParser.urlencoded({extended:true}));
  11. app.use(express.static('./frontend/assets'));
  12. app.get('/', function (req, res) {
  13. res.sendFile('./frontend/index.html', {
  14. root: path.resolve(__dirname + '/..')
  15. });
  16. });
  17. const httpServer = require('http').Server(app);
  18. /* --------
  19. * RECEIVER
  20. * --------
  21. */
  22. const receiverIo = require('socket.io')(httpServer, {
  23. transports: ['websocket']
  24. });
  25. const onSocketListening = function() {
  26. const address = receiverUdpSocket.address();
  27. console.log('Serveur TUIO en écoute sur : ' + address.address + ':' + address.port);
  28. };
  29. const onSocketConnection = function(socket) {
  30. receiverUdpSocket.on('message', function(msg) {
  31. socket.emit('osc', oscParser.decode(msg));
  32. });
  33. };
  34. const receiverUdpSocket = dgram.createSocket('udp4');
  35. receiverUdpSocket.on('listening', onSocketListening);
  36. receiverUdpSocket.bind(config.app.oscUdpPort, '127.0.0.1');
  37. app.get('/receiver/json', function (req, res) {
  38. res.status(200).send();
  39. });
  40. receiverIo.sockets.on('connection', (socket) =>{
  41. console.log(`Connecté au client ${socket.id}`);
  42. const dgramCallback = function (buf) {
  43. if (config.app.debug && config.app.debugLog.backend.receiver.oscDatagram) {
  44. console.log(oscParser.decode(buf));
  45. }
  46. socket.emit('osc', oscParser.decode(buf));
  47. };
  48. // forward UDP packets via socket.io
  49. receiverUdpSocket.on('message', dgramCallback);
  50. // prevent memory leak on disconnect
  51. socket.on('disconnect', function (socket) {
  52. receiverUdpSocket.removeListener('message', dgramCallback);
  53. });
  54. });
  55. /* -------
  56. * EMITTER
  57. * -------
  58. */
  59. const emitterOscClient = new Client('127.0.0.1', config.app.oscUdpPort);
  60. let alive = [];
  61. let fseq;
  62. let objectsAlive = [] ;
  63. let lastDots = [];
  64. function getHypotenuse(touch1, touch2) {
  65. const x = Math.abs(touch1.x - touch2.x);
  66. const y = Math.abs(touch1.y - touch2.y);
  67. return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
  68. }
  69. function getTop(dotTrio) {
  70. const dist01 = getHypotenuse(dotTrio[0], dotTrio[1]);
  71. const dist02 = getHypotenuse(dotTrio[0], dotTrio[2]);
  72. const dist12 = getHypotenuse(dotTrio[1], dotTrio[2]);
  73. const diff01m02 = Math.abs(dist01 - dist02);
  74. const diff01m12 = Math.abs(dist01 - dist12);
  75. const diff02m12 = Math.abs(dist02 - dist12);
  76. if (diff01m02 < diff02m12 && diff01m02 < diff01m12) {
  77. return 0;
  78. }
  79. else if (diff01m12 < diff01m02 && diff01m12 < diff02m12) {
  80. return 1;
  81. }
  82. else if (diff02m12 < diff01m02 && diff02m12 < diff01m12) {
  83. return 2;
  84. }
  85. }
  86. function getAngleApex(dotTrio, topIndex) {
  87. let dotA;
  88. let dotB;
  89. let dotC;
  90. dotB = dotTrio[topIndex];
  91. if (topIndex == 0) {
  92. dotA = dotTrio[1];
  93. dotC = dotTrio[2];
  94. }
  95. else if (topIndex == 1) {
  96. dotA = dotTrio[0];
  97. dotC = dotTrio[2];
  98. }
  99. else if (topIndex == 2) {
  100. dotA = dotTrio[0];
  101. dotC = dotTrio[1];
  102. }
  103. const AB = [dotB.x - dotA.x, dotB.y - dotA.y];
  104. const CB = [dotB.x - dotC.x, dotB.y - dotC.y];
  105. const dotProd = (AB[0] * CB[0] + AB[1] * CB[1]);
  106. const crossProd = (AB[0] * CB[1] - AB[1] * CB[0]);
  107. const alpha = Math.atan2(crossProd, dotProd);
  108. //return alpha ;
  109. return Math.abs(Math.floor(alpha * 180. / Math.PI + 0.5)) ;
  110. }
  111. function getOrientation(dotTrio, topIndex) {
  112. let dotA;
  113. let dotB;
  114. let dotC;
  115. dotB = dotTrio[topIndex];
  116. if (topIndex == 0) {
  117. dotA = dotTrio[1];
  118. dotC = dotTrio[2];
  119. }
  120. else if (topIndex == 1) {
  121. dotA = dotTrio[0];
  122. dotC = dotTrio[2];
  123. }
  124. else if (topIndex == 2) {
  125. dotA = dotTrio[0];
  126. dotC = dotTrio[1];
  127. }
  128. const middlePt = [(dotA.x + dotC.x) / 2, (dotA.y + dotC.y) / 2 ] ;
  129. let diff = [dotB.x - middlePt[0], dotB.y - middlePt[1]] ;
  130. //const length = Math.sqrt(Math.pow(diff[0], 2) + Math.pow(diff[1], 2) ) ;
  131. //normalize diff
  132. //diff = [diff[0] / length, diff[1] / length];
  133. const rad = Math.atan2(diff[1], diff[0]) ;
  134. return Math.floor(rad * 180 / Math.PI) ;
  135. //return length ;
  136. }
  137. function objectGarbageCollector(){
  138. for(const triangle of objectsAlive){
  139. triangle.remainingDuration -= 1;
  140. };
  141. objectsAlive = objectsAlive.filter(triangle => triangle.remainingDuration > 0);
  142. createBundle();
  143. console.dir(objectsAlive);
  144. }
  145. setInterval(objectGarbageCollector, 1000);
  146. let currentOscBundle = null;
  147. let hasPending = false;
  148. function createBundle(){
  149. currentOscBundle = new Bundle ;
  150. currentOscBundle.append([ '/tuio/2Dobj', 'alive'].concat(objectsAlive.map(t => t.matchingObject.apexAngle) ));
  151. for(const triangle of objectsAlive){
  152. currentOscBundle.append([
  153. '/tuio/2Dobj',
  154. 'set',
  155. triangle.matchingObject.apexAngle,
  156. triangle.matchingObject.apexAngle,
  157. triangle.center[0],
  158. triangle.center[1],
  159. triangle.orientation,
  160. 0.0,
  161. 0.0,
  162. 0.0,
  163. 0.0,
  164. 0.0
  165. ]);
  166. }
  167. currentOscBundle.append(['/tuio/2Dobj', 'fseq', fseq]);
  168. hasPending = true;
  169. }
  170. function listDots(touches){
  171. const dots = [];
  172. for(const touch of touches){
  173. dots.push({
  174. id: touch.identifier,
  175. x: touch.clientX,
  176. y: touch.clientY
  177. });
  178. };
  179. if (config.app.debug && config.app.debugLog.backend.emitter.dots) {
  180. console.log('-- dots --', dots);
  181. }
  182. return dots
  183. }
  184. function listSegments(dots){
  185. const segments = [];
  186. if (dots.length > 2) {
  187. for (var i = 0; i < dots.length; i++) {
  188. for (var j = 0; j < dots.length; j++) {
  189. if (j !== i) {
  190. /* on vérifie que le segment n'est pas déjà listé */
  191. const alreadyExists = segments.find(segment => {
  192. return segment.identifiers.includes(i) && segment.identifiers.includes(j);
  193. });
  194. /* on calcule la taille du segment (l'hypoténuse) */
  195. var hyp = getHypotenuse(dots[i], dots[j]);
  196. /* on garde uniquement les segments inférieurs à 750px (valeur par défaut)
  197. * cette valeur est la variable de configuration "maxDistanceBetweenPoints" */
  198. if (!alreadyExists && hyp <= config.app.maxDistanceBetweenPoints) {
  199. segments.push({
  200. identifiers: [i, j],
  201. x1: dots[i].x,
  202. x2: dots[j].x,
  203. y1: dots[i].y,
  204. y2: dots[j].y,
  205. hyp
  206. });
  207. }
  208. }
  209. }
  210. }
  211. }
  212. if (config.app.debug && config.app.debugLog.backend.emitter.segments) {
  213. console.log('-- segments --', segments);
  214. }
  215. return segments;
  216. }
  217. function listTriangles(segments){
  218. const triangles = [];
  219. /* on boucle sur les segments */
  220. for(const segment of segments) {
  221. const dot1 = segment.identifiers[0];
  222. const dot2 = segment.identifiers[1];
  223. /* on vérifie que le triangle n'est pas déjà listé */
  224. const alreadyExists = triangles.find(triangle => {
  225. return triangle.includes(dot1) && triangle.includes(dot2);
  226. });
  227. if (!alreadyExists) {
  228. /* on cherche les segments qui contiennent un des 2 points du segment actuel
  229. * ex: si le segment actuel est AB, on cherche un segment contenant A (pour AC) et un autre contenant B (pour BC) */
  230. const found1 = segments.findIndex(seg => {
  231. return (seg.identifiers.includes(dot1) && !seg.identifiers.includes(dot2));
  232. });
  233. const found2 = segments.findIndex(seg => {
  234. return (seg.identifiers.includes(dot2) && !seg.identifiers.includes(dot1));
  235. });
  236. /* si on trouve bien les 2 segments (AC et BC), on peut créer un triangle */
  237. if (found1 !== -1 && found2 !== -1) {
  238. /* on devine quel est le 3ème point du triangle par rapport au segment actuel (le point C par rapport au segment AB) */
  239. const dot3 = segments[found1].identifiers.find(identifier => {
  240. return identifier !== dot1 && identifier !== dot2;
  241. });
  242. triangles.push([dot1, dot2, dot3]);
  243. }
  244. }
  245. };
  246. if (config.app.debug && config.app.debugLog.backend.emitter.triangles) {
  247. console.log('-- triangles --', triangles);
  248. }
  249. return triangles
  250. }
  251. function filterTriangles(dots, triangles){
  252. const filteredTriangles = [];
  253. /* Définition de l'apex, de la position du centre et de l'orientation */
  254. for(const triangle of triangles){
  255. const objTriangle = {} ;
  256. objTriangle.dots = [];
  257. objTriangle.dots[0] = dots[triangle[0]];
  258. objTriangle.dots[1] = dots[triangle[1]];
  259. objTriangle.dots[2] = dots[triangle[2]];
  260. objTriangle.apex = getTop(objTriangle.dots);
  261. objTriangle.center = [
  262. (objTriangle.dots[0].x + objTriangle.dots[1].x + objTriangle.dots[2].x) / 3,
  263. (objTriangle.dots[0].y + objTriangle.dots[1].y + objTriangle.dots[2].y) / 3
  264. ];
  265. objTriangle.angleApex = getAngleApex(objTriangle.dots, objTriangle.apex);
  266. objTriangle.orientation = getOrientation(objTriangle.dots, objTriangle.apex);
  267. if (config.app.debug && config.app.debugLog.backend.emitter.apex) {
  268. console.log('-- apex --', objTriangle.apex);
  269. console.log('angle: ', objTriangle.angleApex);
  270. console.log('centerPos: ' + objTriangle.center);
  271. console.log('orientation: ' + objTriangle.orientation);
  272. }
  273. //verify if triangle has a corresponding triangle in config
  274. const matchingObject = config.objects.find(item => {
  275. return objTriangle.angleApex > item.apexAngle - config.app.matchingTolerance && objTriangle.angleApex < item.apexAngle + config.app.matchingTolerance;
  276. });
  277. if (matchingObject) {
  278. objTriangle.matchingObject = matchingObject;
  279. filteredTriangles.push(objTriangle);
  280. if (config.app.debug && config.app.debugLog.backend.emitter.matchingObject) {
  281. console.log('-- matchingObject --', objTriangle);
  282. }
  283. }
  284. };
  285. return filteredTriangles;
  286. }
  287. function updateAliveTriangles(filteredTriangles) {
  288. for (const triangle of filteredTriangles) {
  289. let idx = objectsAlive.findIndex(item => {
  290. return triangle.matchingObject.name == item.matchingObject.name;
  291. });
  292. if (idx == -1) {
  293. triangle.remainingDuration = config.app.remainingDuration;
  294. objectsAlive.push(triangle);
  295. } else {
  296. triangle.remainingDuration = config.app.remainingDuration;
  297. objectsAlive[idx] = triangle;
  298. }
  299. }
  300. if (config.app.debug && config.app.debugLog.backend.emitter.aliveTriangles) {
  301. console.log('-- aliveTriangles --', objectsAlive);
  302. }
  303. }
  304. const sendBundle = () => {
  305. if (hasPending) {
  306. emitterOscClient.send(currentOscBundle, () => {
  307. hasPending = false;
  308. });
  309. }
  310. setTimeout(() => {
  311. sendBundle();
  312. }, config.app.timerRefresh);
  313. };
  314. sendBundle();
  315. app.post('/emitter/json', function (req, res) {
  316. if (config.app.debug && config.app.debugLog.backend.emitter.httpRequest) {
  317. console.log('### Emitter POST request ###');
  318. }
  319. let oscBundle;
  320. // if (req.body.type === 'touchend') {
  321. // fseq = fseq ? fseq + 1 : 1;
  322. // const aliveMessage = [ '/tuio/2Dcur', 'alive' ].concat(alive);
  323. // currentOscBundle = new Bundle(
  324. // [ '/tuio/2Dcur', 'source', `tangibles${req.body.section.toString()}@127.0.0.1` ],
  325. // aliveMessage,
  326. // [ '/tuio/2Dcur', 'fseq', fseq ],
  327. // [
  328. // '/tuio/2Dcur',
  329. // 'del',
  330. // req.body.touches[0].identifier
  331. // ]
  332. // );
  333. // emitterOscClient.send(currentOscBundle, () => {
  334. // const index = alive.indexOf(req.body.touches[0].identifier);
  335. // alive.splice(index, 1);
  336. // if (alive.length === 0) {
  337. // currentOscBundle = new Bundle(
  338. // [ '/tuio/2Dcur', 'source', `tangibles${req.body.section.toString()}@127.0.0.1` ],
  339. // [ '/tuio/2Dcur', 'alive' ],
  340. // [ '/tuio/2Dcur', 'fseq', fseq ]
  341. // );
  342. // emitterOscClient.send(currentOscBundle, () => {
  343. // res.status(200).send();
  344. // fseq = 0;
  345. // hasPending = false;
  346. // });
  347. // } else {
  348. // res.status(200).send();
  349. // }
  350. // });
  351. // } else {
  352. if (req.body.touches && req.body.touches.length && req.body.touches.length > 0) {
  353. fseq = fseq ? fseq + 1 : 1;
  354. const touches = Object.keys(req.body.touches);
  355. const aliveMessage = [ '/tuio/2Dcur', 'alive' ].concat(alive);
  356. touches.forEach(touch => {
  357. const id = req.body.touches[touch].identifier;
  358. if (!alive.includes(id)) {
  359. alive.push(id);
  360. aliveMessage.push(id);
  361. }
  362. });
  363. /* Listage de tous les points */
  364. const dots = listDots(req.body.touches);
  365. lastDots = dots;
  366. const segments = listSegments(dots);
  367. const triangles = listTriangles(segments);
  368. const filteredTriangles = filterTriangles(dots, triangles);
  369. updateAliveTriangles(filteredTriangles);
  370. createBundle();
  371. res.status(200).send();
  372. } else {
  373. res.status(200).send();
  374. }
  375. });
  376. httpServer.listen(config.app.httpPort, function () {
  377. console.log(`Votre app est disponible sur http://localhost:${config.app.httpPort} !`)
  378. });