123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451 |
- const express = require('express');
- const dgram = require('dgram');
- const oscParser = require('./osc-parser');
- const bodyParser = require('body-parser');
- const { Bundle, Client } = require('node-osc');
- const path = require('path');
- const config = require('../frontend/assets/config.json');
- const app = express();
- app.use(bodyParser.json());
- app.use(bodyParser.urlencoded({extended:true}));
- app.use(express.static('./frontend/assets'));
- app.get('/', function (req, res) {
- res.sendFile('./frontend/index.html', {
- root: path.resolve(__dirname + '/..')
- });
- });
- const httpServer = require('http').Server(app);
- /* --------
- * RECEIVER
- * --------
- */
- const receiverIo = require('socket.io')(httpServer, {
- transports: ['websocket']
- });
- const onSocketListening = function() {
- const address = receiverUdpSocket.address();
- console.log('Serveur TUIO en écoute sur : ' + address.address + ':' + address.port);
- };
- const onSocketConnection = function(socket) {
- receiverUdpSocket.on('message', function(msg) {
- socket.emit('osc', oscParser.decode(msg));
- });
- };
- const receiverUdpSocket = dgram.createSocket('udp4');
- receiverUdpSocket.on('listening', onSocketListening);
- receiverUdpSocket.bind(config.app.oscUdpPort, '127.0.0.1');
- app.get('/receiver/json', function (req, res) {
- res.status(200).send();
- });
- receiverIo.sockets.on('connection', (socket) =>{
- console.log(`Connecté au client ${socket.id}`);
- const dgramCallback = function (buf) {
- if (config.app.debug && config.app.debugLog.backend.receiver.oscDatagram) {
- console.log(oscParser.decode(buf));
- }
- socket.emit('osc', oscParser.decode(buf));
- };
- // forward UDP packets via socket.io
- receiverUdpSocket.on('message', dgramCallback);
- // prevent memory leak on disconnect
- socket.on('disconnect', function (socket) {
- receiverUdpSocket.removeListener('message', dgramCallback);
- });
- });
- /* -------
- * EMITTER
- * -------
- */
- const emitterOscClient = new Client('127.0.0.1', config.app.oscUdpPort);
- let alive = [];
- let fseq;
- let objectsAlive = [] ;
- let lastDots = [];
- function getHypotenuse(touch1, touch2) {
- const x = Math.abs(touch1.x - touch2.x);
- const y = Math.abs(touch1.y - touch2.y);
- return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
- }
- function getTop(dotTrio) {
- const dist01 = getHypotenuse(dotTrio[0], dotTrio[1]);
- const dist02 = getHypotenuse(dotTrio[0], dotTrio[2]);
- const dist12 = getHypotenuse(dotTrio[1], dotTrio[2]);
- const diff01m02 = Math.abs(dist01 - dist02);
- const diff01m12 = Math.abs(dist01 - dist12);
- const diff02m12 = Math.abs(dist02 - dist12);
- if (diff01m02 < diff02m12 && diff01m02 < diff01m12) {
- return 0;
- }
- else if (diff01m12 < diff01m02 && diff01m12 < diff02m12) {
- return 1;
- }
- else if (diff02m12 < diff01m02 && diff02m12 < diff01m12) {
- return 2;
- }
- }
- function getAngleApex(dotTrio, topIndex) {
- let dotA;
- let dotB;
- let dotC;
- dotB = dotTrio[topIndex];
- if (topIndex == 0) {
- dotA = dotTrio[1];
- dotC = dotTrio[2];
- }
- else if (topIndex == 1) {
- dotA = dotTrio[0];
- dotC = dotTrio[2];
- }
- else if (topIndex == 2) {
- dotA = dotTrio[0];
- dotC = dotTrio[1];
- }
- const AB = [dotB.x - dotA.x, dotB.y - dotA.y];
- const CB = [dotB.x - dotC.x, dotB.y - dotC.y];
- const dotProd = (AB[0] * CB[0] + AB[1] * CB[1]);
- const crossProd = (AB[0] * CB[1] - AB[1] * CB[0]);
- const alpha = Math.atan2(crossProd, dotProd);
- //return alpha ;
- return Math.abs(Math.floor(alpha * 180. / Math.PI + 0.5)) ;
- }
- function getOrientation(dotTrio, topIndex) {
- let dotA;
- let dotB;
- let dotC;
- dotB = dotTrio[topIndex];
- if (topIndex == 0) {
- dotA = dotTrio[1];
- dotC = dotTrio[2];
- }
- else if (topIndex == 1) {
- dotA = dotTrio[0];
- dotC = dotTrio[2];
- }
- else if (topIndex == 2) {
- dotA = dotTrio[0];
- dotC = dotTrio[1];
- }
- const middlePt = [(dotA.x + dotC.x) / 2, (dotA.y + dotC.y) / 2 ] ;
- let diff = [dotB.x - middlePt[0], dotB.y - middlePt[1]] ;
- //const length = Math.sqrt(Math.pow(diff[0], 2) + Math.pow(diff[1], 2) ) ;
- //normalize diff
- //diff = [diff[0] / length, diff[1] / length];
- const rad = Math.atan2(diff[1], diff[0]) ;
- return Math.floor(rad * 180 / Math.PI) ;
- //return length ;
- }
- function objectGarbageCollector(){
- //si un point dans last dots est detecté dans un des triangle alors on ne réduit pas sa duration
- for(const triangle of objectsAlive){
- if(triangle.dots.some(dot => lastDots.some(lastDot => lastDot.target == dot.target))){
- } else {
- triangle.remainingDuration -= 1;
- }
- };
- objectsAlive = objectsAlive.filter(triangle => triangle.remainingDuration > 0);
- if (config.app.debug && config.app.debugLog.backend.emitter.dots) {
- console.log("--garbageCollection--", lastDots);
- objectsAlive.forEach(tr => console.log(tr));
- }
- createBundle();
- }
- setInterval(objectGarbageCollector, config.app.garbageCollectorInterval);
- let currentOscBundle = null;
- let hasPending = false;
- function createBundle(){
- currentOscBundle = new Bundle ;
- currentOscBundle.append([ '/tuio/2Dobj', 'alive'].concat(objectsAlive.map(t => t.matchingObject.apexAngle) ));
- for(const triangle of objectsAlive){
- currentOscBundle.append([
- '/tuio/2Dobj',
- 'set',
- triangle.matchingObject.apexAngle,
- triangle.matchingObject.apexAngle,
- triangle.target, //we use x as target identifier
- triangle.center[1],
- triangle.orientation,
- 0.0,
- 0.0,
- 0.0,
- 0.0,
- 0.0
- ]);
- }
- currentOscBundle.append(['/tuio/2Dobj', 'fseq', fseq]);
- hasPending = true;
- }
- function listDots(touches){
- const dots = [];
- for(const touch of touches){
- dots.push({
- id: touch.identifier,
- x: touch.clientX,
- y: touch.clientY,
- target: touch.target
- });
- };
- if (config.app.debug && config.app.debugLog.backend.emitter.dots) {
- console.log('-- dots --', dots);
- }
- return dots
- }
- function listSegments(dots){
- const segments = [];
- if (dots.length > 2) {
- for (var i = 0; i < dots.length; i++) {
- for (var j = 0; j < dots.length; j++) {
- if (j !== i) {
- /* on vérifie que le segment n'est pas déjà listé */
- const alreadyExists = segments.find(segment => {
- return segment.identifiers.includes(i) && segment.identifiers.includes(j);
- });
- /* on calcule la taille du segment (l'hypoténuse) */
- var hyp = getHypotenuse(dots[i], dots[j]);
- /* on garde uniquement les segments inférieurs à 750px (valeur par défaut)
- * cette valeur est la variable de configuration "maxDistanceBetweenPoints" */
- if (!alreadyExists && hyp <= config.app.maxDistanceBetweenPoints) {
- segments.push({
- identifiers: [i, j],
- x1: dots[i].x,
- x2: dots[j].x,
- y1: dots[i].y,
- y2: dots[j].y,
- target: dots[i].target,
- hyp
- });
- }
- }
- }
- }
- }
- if (config.app.debug && config.app.debugLog.backend.emitter.segments) {
- console.log('-- segments --', segments);
- }
- return segments;
- }
- function listTriangles(segments){
- const triangles = [];
- /* on boucle sur les segments */
- for(const segment of segments) {
- const dot1 = segment.identifiers[0];
- const dot2 = segment.identifiers[1];
- /* on vérifie que le triangle n'est pas déjà listé */
- const alreadyExists = triangles.find(triangle => {
- return triangle.includes(dot1) && triangle.includes(dot2);
- });
- if (!alreadyExists) {
- /* on cherche les segments qui contiennent un des 2 points du segment actuel
- * ex: si le segment actuel est AB, on cherche un segment contenant A (pour AC) et un autre contenant B (pour BC) */
- const found1 = segments.findIndex(seg => {
- return (seg.identifiers.includes(dot1) && !seg.identifiers.includes(dot2));
- });
- const found2 = segments.findIndex(seg => {
- return (seg.identifiers.includes(dot2) && !seg.identifiers.includes(dot1));
- });
- /* si on trouve bien les 2 segments (AC et BC), on peut créer un triangle */
- if (found1 !== -1 && found2 !== -1) {
- /* on devine quel est le 3ème point du triangle par rapport au segment actuel (le point C par rapport au segment AB) */
- const dot3 = segments[found1].identifiers.find(identifier => {
- return identifier !== dot1 && identifier !== dot2;
- });
- triangles.push([dot1, dot2, dot3]);
- }
- }
- };
- if (config.app.debug && config.app.debugLog.backend.emitter.triangles) {
- console.log('-- triangles --', triangles);
- }
- return triangles
- }
- function filterTriangles(dots, triangles){
- const filteredTriangles = [];
- /* Définition de l'apex, de la position du centre et de l'orientation */
- for(const triangle of triangles){
- const objTriangle = {} ;
- objTriangle.dots = [];
- objTriangle.dots[0] = dots[triangle[0]];
- objTriangle.dots[1] = dots[triangle[1]];
- objTriangle.dots[2] = dots[triangle[2]];
- objTriangle.target = objTriangle.dots[0].target
- objTriangle.apex = getTop(objTriangle.dots);
- objTriangle.center = [
- (objTriangle.dots[0].x + objTriangle.dots[1].x + objTriangle.dots[2].x) / 3,
- (objTriangle.dots[0].y + objTriangle.dots[1].y + objTriangle.dots[2].y) / 3
- ];
- objTriangle.angleApex = getAngleApex(objTriangle.dots, objTriangle.apex);
- objTriangle.orientation = getOrientation(objTriangle.dots, objTriangle.apex);
- if (config.app.debug && config.app.debugLog.backend.emitter.apex) {
- console.log('-- apex --', objTriangle.apex);
- console.log('angle: ', objTriangle.angleApex);
- console.log('centerPos: ' + objTriangle.center);
- console.log('orientation: ' + objTriangle.orientation);
- }
- //verify if triangle has a corresponding triangle in config
- const matchingObject = config.objects.find(item => {
- return objTriangle.angleApex > item.apexAngle - config.app.matchingTolerance && objTriangle.angleApex < item.apexAngle + config.app.matchingTolerance;
- });
- if (matchingObject) {
- objTriangle.matchingObject = matchingObject;
- filteredTriangles.push(objTriangle);
- if (config.app.debug && config.app.debugLog.backend.emitter.matchingObject) {
- console.log('-- matchingObject --', objTriangle);
- }
- }
- };
- return filteredTriangles;
- }
- function updateAliveTriangles(filteredTriangles) {
- for (const triangle of filteredTriangles) {
- let idx = objectsAlive.findIndex(item => {
- return triangle.matchingObject.name == item.matchingObject.name;
- });
- if (idx == -1) {
- triangle.remainingDuration = config.app.remainingDuration;
- if(objectsAlive.some(obj => obj.target == triangle.target)){
- console.log("already a triangle in this box");
- } else {
- objectsAlive.push(triangle);
- }
- } else {
- triangle.remainingDuration = config.app.remainingDuration;
- objectsAlive[idx] = triangle;
- }
- }
- if (config.app.debug && config.app.debugLog.backend.emitter.aliveTriangles) {
- console.log('-- aliveTriangles --', objectsAlive);
- }
-
- }
- const sendBundle = () => {
- if (hasPending) {
- emitterOscClient.send(currentOscBundle, () => {
- hasPending = false;
- });
- }
- setTimeout(() => {
- sendBundle();
- }, config.app.timerRefresh);
- };
- sendBundle();
- app.post('/emitter/json', function (req, res) {
- if (config.app.debug && config.app.debugLog.backend.emitter.httpRequest) {
- console.log('### Emitter POST request ###');
- }
- let oscBundle;
- // if (req.body.type === 'touchend') {
- // fseq = fseq ? fseq + 1 : 1;
- // const aliveMessage = [ '/tuio/2Dcur', 'alive' ].concat(alive);
- // currentOscBundle = new Bundle(
- // [ '/tuio/2Dcur', 'source', `tangibles${req.body.section.toString()}@127.0.0.1` ],
- // aliveMessage,
- // [ '/tuio/2Dcur', 'fseq', fseq ],
- // [
- // '/tuio/2Dcur',
- // 'del',
- // req.body.touches[0].identifier
- // ]
- // );
- // emitterOscClient.send(currentOscBundle, () => {
- // const index = alive.indexOf(req.body.touches[0].identifier);
- // alive.splice(index, 1);
- // if (alive.length === 0) {
- // currentOscBundle = new Bundle(
- // [ '/tuio/2Dcur', 'source', `tangibles${req.body.section.toString()}@127.0.0.1` ],
- // [ '/tuio/2Dcur', 'alive' ],
- // [ '/tuio/2Dcur', 'fseq', fseq ]
- // );
- // emitterOscClient.send(currentOscBundle, () => {
- // res.status(200).send();
- // fseq = 0;
- // hasPending = false;
- // });
- // } else {
- // res.status(200).send();
- // }
- // });
- // } else {
- if (req.body.touches && req.body.touches.length && req.body.touches.length > 0) {
- fseq = fseq ? fseq + 1 : 1;
- const touches = Object.keys(req.body.touches);
- // const aliveMessage = [ '/tuio/2Dcur', 'alive' ].concat(alive);
- // touches.forEach(touch => {
- // const id = req.body.touches[touch].identifier;
- // if (!alive.includes(id)) {
- // alive.push(id);
- // aliveMessage.push(id);
- // }
- // });
- /* Listage de tous les points */
- const dots = listDots(req.body.touches);
-
- lastDots = dots;
- const segments = listSegments(dots);
- const triangles = listTriangles(segments);
- const filteredTriangles = filterTriangles(dots, triangles);
- updateAliveTriangles(filteredTriangles);
- createBundle();
- res.status(200).send();
- } else {
- lastDots = [];
- res.status(200).send();
- }
- });
- httpServer.listen(config.app.httpPort, function () {
- console.log(`Votre app est disponible sur http://localhost:${config.app.httpPort} !`)
- });
|