4 Commits dd2958f5aa ... 7b4d0de849

Author SHA1 Message Date
  asr@asr.fr 7b4d0de849 multiple object v2 + doc 2 years ago
  asr@asr.fr 0a45ca684c more filters 2 years ago
  unknown d964d11343 multiple objects v1 + cleaning 2 years ago
  unknown 4cca31efa2 change logic of triangle searching 2 years ago
4 changed files with 325 additions and 344 deletions
  1. 20 0
      README.md
  2. 245 242
      backend/server.js
  3. 17 17
      frontend/assets/config.json
  4. 43 85
      frontend/index.html

+ 20 - 0
README.md

@@ -0,0 +1,20 @@
+## install
+
+node JS lts (v16.15.0)
+
+puis `npm install`
+
+## lancer
+
+node backend/server.js
+Une page de test est accessible sur localhost:5000
+Penser à modifier maxDistanceBetweenPoints en fonction de l'écran
+
+## idées d'amélioration
+
+attendre plusieur signaux pour valider un triangle
+filter sur la position (pas deux triangles dans la même zone)
+points plus éloignés sur les tangibles -> meilleure précision
+connaitre les distances précises entre les points d'un triangle pour éviter les faux positifs
+traitement uniquement coté navigateur
+check performances

+ 245 - 242
backend/server.js

@@ -76,7 +76,8 @@ const emitterOscClient = new Client('127.0.0.1', config.app.oscUdpPort);
 let alive = [];
 let fseq;
 
-let objTriangle = [] ;
+let objectsAlive = [] ;
+let lastDots = [];
 
 function getHypotenuse(touch1, touch2) {
 	const x = Math.abs(touch1.x - touch2.x);
@@ -130,7 +131,7 @@ function getAngleApex(dotTrio, topIndex) {
 
 	const alpha = Math.atan2(crossProd, dotProd);
 	//return alpha ;
-	return Math.floor(alpha * 180. / Math.PI + 0.5) ;
+	return Math.abs(Math.floor(alpha * 180. / Math.PI + 0.5)) ;
 }
 
 function getOrientation(dotTrio, topIndex) {
@@ -153,17 +154,200 @@ function getOrientation(dotTrio, topIndex) {
 
 	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) ) ;
+	//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[0], diff[1]) ;
- 	return Math.floor(-1 * rad * 180 / Math.PI)  ;
+	//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.x == dot.x))){	
+		} else {
+			triangle.remainingDuration -= 1;
+		}
+	};
+	objectsAlive = objectsAlive.filter(triangle => triangle.remainingDuration > 0);
+	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.center[0],
+					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
+		});
+	};
+	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,
+							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.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;
+			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, () => {
@@ -181,256 +365,75 @@ app.post('/emitter/json', function (req, res) {
 		console.log('### Emitter POST request ###');
 	}
 	let oscBundle;
-	if (req.body.type === 'touchend') {
+	// 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);
-		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();
+		touches.forEach(touch => {
+			const id = req.body.touches[touch].identifier;
+			if (!alive.includes(id)) {
+				alive.push(id);
+				aliveMessage.push(id);
 			}
 		});
-	} 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 = [];
-			touches.forEach(function(touch) {
-				dots.push({
-					id: req.body.touches[touch].identifier,
-					x: req.body.touches[touch].clientX,
-					y: req.body.touches[touch].clientY
-				});
-			});
-			if (config.app.debug && config.app.debugLog.backend.emitter.dots) {
-				console.log('-- dots --', dots);
-			}
-
-			/* Listage des segments */
-			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,
-									hyp
-								});
-							}
-						}
-					}
-				}
-			}
-			if (config.app.debug && config.app.debugLog.backend.emitter.segments) {
-				console.log('-- segments --', segments);
-			}
-
-			/* Listage des triangles */
-			const triangles = [];
-			/* on boucle sur les segments */
-			segments.forEach((segment) => {
-				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);
-			}
 
-			/* objet pour stocker les informations des triangles identifiés (points, centre, apexAngle, orientation, indice apex, width, height) */
+		/* Listage de tous les points */
+		const dots = listDots(req.body.touches);
+		
+		lastDots = dots;
 
-			objTriangle = {} ;
+		const segments = listSegments(dots);
 
-			/* Définition de l'apex, de la position du centre et de l'orientation */
-			triangles.forEach(triangle => {
-				objTriangle.dots = [];
-				objTriangle.dots[0] = dots[triangle[0]];
-				objTriangle.dots[1] = dots[triangle[1]];
-				objTriangle.dots[2] = dots[triangle[2]];
+		const triangles = listTriangles(segments);
 
-				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
-				];
+		const filteredTriangles = filterTriangles(dots, triangles);
 
-				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);
-				}
-			});
+		updateAliveTriangles(filteredTriangles);
 
+		createBundle();
 
-			//plante vite
-			// if (objTriangle.dots != undefined){
-			// 	let oscBundleObj ;
-			// 	oscBundleObj = new Bundle(
-			// 		[ '/tuio/2Dobj', 'source', `tangibles${req.body.section.toString()}@127.0.0.1` ],
-			// 		[ '/tuio/2Dobj', 'alive', 1 ],
-			// 		[
-			// 			'/tuio/2Dobj',
-			// 			'set',
-			// 			1,
-			// 			1,
-			// 			objTriangle.center[0],
-			// 			objTriangle.center[1],
-			// 			objTriangle.orientation,
-			// 			0.0,
-			// 			0.0,
-			// 			0.0,
-			// 			0.0,
-			// 			0.0
-			// 		],
-			// 		[ '/tuio/2Dobj', 'fseq', fseq ]
-			// 	);
-			// 	// 	// objTriangle.forEach(triangleIndex => {
-			// 	// 	// 	oscBundle.append(
-			// 	// 	// 		[
-			// 	// 	// 			'/tuio/2Dobj',
-			// 	// 	// 			'set',
-			// 	// 	// 			triangleIndex,
-			// 	// 	// 			triangleIndex,
-			// 	// 	// 			objTriangle[triangleIndex].center[0],
-			// 	// 	// 			objTriangle[triangleIndex].center[1],
-			// 	// 	// 			objTriangle[triangleIndex].orientation,
-			// 	// 	// 			0.0,
-			// 	// 	// 			0.0,
-			// 	// 	// 			0.0,
-			// 	// 	// 			0.0,
-			// 	// 	// 			0.0
-			// 	// 	// 		]
-			// 	// 	// 	);
-			// 	// 	// });
-			// 	console.log(oscBundleObj);
-			// 	emitterOscClient.send(oscBundleObj, () => {
-			// 		res.status(200).send();
-			// 	});
-			// }
-
-
-			// oscBundle = new Bundle(
-			// 	[ '/tuio/2Dcur', 'source', `tangibles${req.body.section.toString()}@127.0.0.1` ],
-			// 	aliveMessage,
-			// 	[ '/tuio/2Dcur', 'fseq', fseq ]
-			// );
-			// touches.forEach(touch => {
-			// 	oscBundle.append(
-			// 		[
-			// 			'/tuio/2Dcur',
-			// 			'set',
-			// 			req.body.touches[touch].identifier,
-			// 			req.body.touches[touch].clientX / req.body.screenW,
-			// 			req.body.touches[touch].clientY / req.body.screenH,
-			// 			0.0,
-			// 			0.0
-			// 		]
-			// 	);
-			// });
-			currentOscBundle = new Bundle ;
-			currentOscBundle.append([ '/tuio/2Dobj', 'alive', 1 ]);
-			if (objTriangle.dots) {
-				const matchingObject = config.objects.find(item => {
-					return objTriangle.angleApex > item.apexAngle - config.app.matchingTolerance && objTriangle.angleApex < item.apexAngle + config.app.matchingTolerance;
-				});
-				console.log(matchingObject);
-				currentOscBundle.append([
-					'/tuio/2Dobj',
-					'set',
-					1,
-					matchingObject ? matchingObject.apexAngle : 1,
-					objTriangle.center[0],
-					objTriangle.center[1],
-					// objTriangle.center[0] / req.body.screenW,
-					// objTriangle.center[1] / req.body.screenH,
-					objTriangle.orientation,
-					0.0,
-					0.0,
-					0.0,
-					0.0,
-					0.0
-				]);
-			}
-			currentOscBundle.append(['/tuio/2Dobj', 'fseq', fseq]);
-			hasPending = true;
-			res.status(200).send();
-		} else {
-			res.status(400).send();
-		}
+		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} !`)
 });
+
+

+ 17 - 17
frontend/assets/config.json

@@ -2,9 +2,11 @@
 	"app": {
 		"oscUdpPort": 3333,
 		"httpPort": 5000,
-		"timerRefresh": 100,
-		"maxDistanceBetweenPoints": 750,
-		"matchingTolerance": 2.5,
+		"timerRefresh": 50,
+		"maxDistanceBetweenPoints": 60,
+		"matchingTolerance": 1,
+		"remainingDuration": 3,
+		"garbageCollectorInterval": 1000,
 		"debug": true,
 		"debugLog": {
 			"backend": {
@@ -13,7 +15,9 @@
 					"dots": false,
 					"segments": false,
 					"triangles": false,
-					"apex": false
+					"apex": false,
+					"matchingObject": false,
+					"aliveTriangles": false
 				},
 				"receiver": {
 					"oscDatagram": false
@@ -28,19 +32,19 @@
 					"onAddTuioCursor": false,
 					"onUpdateTuioCursor": false,
 					"onRemoveTuioCursor": false,
-					"onAddTuioObject": false,
-					"onUpdateTuioObject": false,
-					"onRemoveTuioObject": false,
+					"onAddTuioObject": true,
+					"onUpdateTuioObject": true,
+					"onRemoveTuioObject": true,
 					"onRefresh": false
 				}
 			}
 		},
-		"debugDisplay": false
+		"debugDisplay": true
 	},
 	"objects": [
 		{
 			"name": "object1",
-			"apexAngle": 35
+			"apexAngle": 30
 		},
 		{
 			"name": "object2",
@@ -48,23 +52,19 @@
 		},
 		{
 			"name": "object2",
-			"apexAngle": 45
+			"apexAngle": 50
 		},
 		{
 			"name": "object4",
-			"apexAngle": 50
+			"apexAngle": 70
 		},
 		{
 			"name": "object5",
-			"apexAngle": 55
+			"apexAngle": 80
 		},
 		{
 			"name": "object6",
-			"apexAngle": 60
-		},
-		{
-			"name": "object7",
-			"apexAngle": 65
+			"apexAngle": 90
 		}
 	]
 }

+ 43 - 85
frontend/index.html

@@ -80,19 +80,19 @@
 
 				const eventHandler = (evt, type) => {
 					const touches = [];
-					for (var i = 0; i < evt.changedTouches.length; i++) {
+					for (var i = 0; i < evt.touches.length; i++) {
 						touches[i] = {
-							clientX: evt.changedTouches[i].clientX,
-							clientY: evt.changedTouches[i].clientY,
-							force: evt.changedTouches[i].force,
-							identifier: ((evt.changedTouches[i].identifier + 1) % 100) + section,
-							pageX: evt.changedTouches[i].pageX,
-							pageY: evt.changedTouches[i].pageY,
-							radiusX: evt.changedTouches[i].radiusX,
-							radiusY: evt.changedTouches[i].radiusY,
-							rotationAngle: evt.changedTouches[i].rotationAngle,
-							screenX: evt.changedTouches[i].screenX,
-							screenY: evt.changedTouches[i].screenY
+							clientX: evt.touches[i].clientX,
+							clientY: evt.touches[i].clientY,
+							force: evt.touches[i].force,
+							identifier: ((evt.touches[i].identifier + 1) % 100) + section,
+							pageX: evt.touches[i].pageX,
+							pageY: evt.touches[i].pageY,
+							radiusX: evt.touches[i].radiusX,
+							radiusY: evt.touches[i].radiusY,
+							rotationAngle: evt.touches[i].rotationAngle,
+							screenX: evt.touches[i].screenX,
+							screenY: evt.touches[i].screenY
 						};
 					};
 
@@ -146,26 +146,41 @@
 				canvas.width = screenW;
 				canvas.height = screenH;
 				context = canvas.getContext('2d');
+				context.font = '10px serif';
 
-				const drawTriangle = (a, b, c, center) => {
+				setInterval(draw, 10);
+
+				const cursors = {};
+				const tuioObjects = {};
+
+				function draw(){
 					context.clearRect(0, 0, canvas.width, canvas.height);
+					for(obj of Object.values(tuioObjects)){
+						if (config.app.debugDisplay) {
+							const apex = {
+								x: Math.cos(obj.angle * Math.PI /180) * 150 + obj.xPos,
+								y: Math.sin(obj.angle * Math.PI /180) * 150 + obj.yPos
+							};
+							drawIndicationLine(apex, {
+								x: obj.xPos,
+								y: obj.yPos
+							}, obj.symbolId);
+						}
+					}
+				}
+
+				const drawIndicationLine = (apex, center, id) => {
 					const path = new Path2D();
-					/*
-					path.moveTo(Math.round(a.x), Math.round(a.y));
-					path.lineTo(Math.round(b.x), Math.round(b.y));
-					path.lineTo(Math.round(c.x), Math.round(c.y));
-					*/
 					path.moveTo(Math.round(center.x), Math.round(center.y));
-					path.lineTo(Math.round(a.x), Math.round(a.y));
+					path.lineTo(Math.round(apex.x), Math.round(apex.y));
 					context.stroke(path);
+					context.fillText(id, Math.round(apex.x), Math.round(apex.y));
 				};
+				
 				const client = new Tuio.Client({
 					host: `http://localhost:${config.app.httpPort}`
 				});
 
-				const cursors = {};
-				const objects = {};
-
 				onConnect = function() {
 					if (config.app.debug) {
 						console.log('connected to web-socket');
@@ -173,11 +188,6 @@
 				},
 
 				onAddTuioCursor = function(addCursor) {
-					/*
-					var $addCursor = $('<div class="tuioCursor"></div>');
-					$('body').append($addCursor);
-					cursors[addCursor.getCursorId()] = $addCursor;
-					*/
 					onUpdateTuioCursor(addCursor);
 					if (config.app.debug && config.app.debugLog.frontend.receiver.onAddTuioCursor) {
 						console.log('addTuioCursor', addCursor);
@@ -185,76 +195,26 @@
 				},
 
 				onUpdateTuioCursor = function(updateCursor) {
-					/*
-					var $updateCursor = cursors[updateCursor.getCursorId()];
-					$updateCursor.css({
-						left: updateCursor.getScreenX(screenW),
-						top: updateCursor.getScreenY(screenH)
-					});
-					*/
 					if (config.app.debug && config.app.debugLog.frontend.receiver.onUpdateTuioCursor) {
 						console.log('updateTuioCursor', updateCursor);
 					}
 				},
 
 				onRemoveTuioCursor = function(removeCursor) {
-					/*
-					var $removeCursor = cursors[removeCursor.getCursorId()];
-					$removeCursor.remove();
-					delete[removeCursor.getCursorId()];
-					*/
 					if (config.app.debug && config.app.debugLog.frontend.receiver.onRemoveTuioCursor) {
 						console.log('removeTuioCursor', removeCursor);
 					}
 				},
 
 				onAddTuioObject = function(addObject) {
-					/*
-					var $addObject = $('<div class="tuioObj"></div>');
-					$('body').append($addObject);
-					objects[addObject.symbolID] = $addObject;
-					*/
-
-					onUpdateTuioObject(addObject);
+					tuioObjects[addObject.symbolId] = addObject;
 					if (config.app.debug && config.app.debugLog.frontend.receiver.onAddTuioObject) {
 						console.log('addTuioObject', addObject);
 					}
 				},
 
 				onUpdateTuioObject = function(updateObject) {
-					/*
-					var $updateObject = objects[updateObject.symbolID];
-					$updateObject.css({
-						left: updateObject.getScreenX(screenW),
-						top: updateObject.getScreenY(screenH),
-						rotate : updateObject.getAngleDegrees()
-					});
-					*/
-
-					const apexAngle = 35;
-					const mediatrice = 300;
-
-					const convertedAngle = updateObject.angle < 0 ? 180 + updateObject.angle : updateObject.angle + 180;
-
-					console.log(updateObject.angle, convertedAngle);
-					if (config.app.debugDisplay) {
-						const apex = {
-							x: Math.cos(updateObject.angle * Math.PI / 180) * (mediatrice / 2) + updateObject.xPos,
-							y: Math.sin(updateObject.angle * Math.PI / 180) * (mediatrice / 2) + updateObject.yPos
-						};
-						const b = {
-							x: Math.cos((apexAngle / 2) * Math.PI / 180) * (mediatrice / 2) + apex.x,
-							y: Math.sin((apexAngle / 2) * Math.PI / 180) * (mediatrice / 2) + apex.y
-						};
-						const c = {
-							x: Math.cos((-1 * apexAngle / 2) * Math.PI / 180) * (mediatrice / 2) + apex.x,
-							y: Math.sin((-1 * apexAngle / 2) * Math.PI / 180) * (mediatrice / 2) + apex.y
-						};
-						drawTriangle(apex, b, c, {
-							x: updateObject.xPos,
-							y: updateObject.yPos
-						});
-					}
+					tuioObjects[updateObject.symbolId] = updateObject;
 
 					if (config.app.debug && config.app.debugLog.frontend.receiver.onUpdateTuioObject) {
 						console.log('updateTuioObject', updateObject);
@@ -262,11 +222,9 @@
 				},
 
 				onRemoveTuioObject = function(removeObject) {
-					/*
-					var $removeObject = objects[removeObject.symbolID];
-					$removeObject.remove();
-					delete[removeObject.symbolID];
-					*/
+
+					delete tuioObjects[removeObject.symbolId];
+
 					if (config.app.debug && config.app.debugLog.frontend.receiver.onRemoveTuioObject) {
 						console.log('removeTuioObject', removeObject);
 					}
@@ -274,7 +232,7 @@
 
 				onRefresh = function(time) {
 					if (config.app.debug && config.app.debugLog.frontend.receiver.onRefresh) {
-						console.log('refresh', time);
+						console.log('refresh', this.objectList);
 					}
 				};