// Simulation trajectoire d'un robot tondeuse // sur une surface rectangulaire // // (c) Keops 26/10 à 02/11/2014 - Public Domain // (c) Keops couloir 18/07/2015 - Public Domain // (c) Keops Faire 23/06/2017 - Public Domain // // Réalisé par emmanuel.ostenne@ac-lille.fr // // avec ProgLab : http://proglab.fr // et JavascriptEnLigne : http://emmanuel.ostenne.free.fr/mepirem/algo/ // testé aussi sous JSFiddle : http://jsfiddle.net/ // // Le nombre de changements de directions saisie est un minimum // pour le nombre réellement accompli par le bouton Lancer : // l'algorithme se termine toujours par un déplacement solution // donc peut dépasser le minimum visé. // // // Interface // //Sélection automatique de la sortie pour JSGeoDynA, Algo ou ProgLab, JSFiddle if (document.getElementById("jsxbox") !== null) var sortie = document.getElementById("sortiesDiv"); //jsgeodyna else if (document.getElementById("aide") !== null) var sortie = document.getElementById("b"); //algo else var sortie = document.body; //autre : HTML,ProgLab,jsFiddle ... // //Zone de dessin var canvas = sortie.appendChild(document.createElement("canvas")); var context = canvas.getContext("2d"); // // Largeur et hauteur en pixels // org : 300 x 200 canvas.width = 300; canvas.height = 200; // context.lineWidth = 1; context.strokeStyle = "blue"; context.fillStyle = "red"; // // affichage infos utilisateur sortie.appendChild(document.createElement("p")); var label1 = sortie.appendChild(document.createElement("label")); label1.innerHTML = "Total des changements de direction."; sortie.appendChild(document.createElement("br")); var label2 = sortie.appendChild(document.createElement("label")); label2.innerHTML = "Surface parcourue : 0 %."; sortie.appendChild(document.createElement("br")); var label4 = sortie.appendChild(document.createElement("label")); label4.innerHTML = "Distance parcourue : 0 px."; // sortie.appendChild(document.createElement("br")); var label3 = sortie.appendChild(document.createElement("label")); label3.innerHTML = "Nombre changements à faire :"; // // saisie du nombre de changements à faire ... var input1 = sortie.appendChild(document.createElement("input")); input1.value = 1; // quand on appuie sur le bouton var btn = document.createElement("input"); btn.setAttribute("name", "bouton1"); btn.setAttribute("value", "Lancer"); btn.setAttribute("type", "button"); var btn1 = sortie.appendChild(btn); var abtn = document.createElement("input"); abtn.setAttribute("name", "bouton2"); abtn.setAttribute("value", "Couleurs"); abtn.setAttribute("type", "button"); var btn2 = sortie.appendChild(abtn); sortie.appendChild(document.createElement("p")); sortie.appendChild(document.createElement("p")); // pour télécharger le fichier des passages sur chaque point var link = document.createElement("a"); var textLink = document.createTextNode("Télécharger fichier csv"); link.appendChild(textLink); var download = sortie.appendChild(link); // // //variables // //position courante var x = 0; var y = 175; //précalcul pour sortie de zone var x1 = x; var y1 = y; //déplacement h et v : *100 (cf cos/sin et arrondis) var deltax = 100; var deltay = 0; //angle de rotation en ° : 90 puis aléatoire var angle = 90; //correction aléatoire de la rotation var corr_angle = 0; //coord. de la rotation var p; //drapeau pour arrêter la boucle des calculs/déplacements var stopflag = 0; //compteurs pour les boucles var maxcompteur = 1; var compteur1 = 0; //pour compter les trajest corrects var trajetok = false; //compteur de trajets corrects var compteur2 = 0; // var anc_x =x; var anc_y =y; var distance = 0 //dessin des limites de la zone var codageZone = []; var maxAChanger = 0; initialiseCodagezone(); // // Dessin (activer la bonne forme) // ****** // //dessineEtCodeZoneSimple(); //dessineEtCodeZoneComplexe(); //dessineEtCodeZoneSimpleIlot(); //dessineEtCodeZoneComplexe2(); dessineEtCodeZoneCouloir(); // //point de départ context.fillRect(x - 2, y - 2, 4, 4); // //rotation des coordonnées suivant l'angle (centre (0,0)) //( cercle trigo et angle en ° ) function rotation(pointX, pointY, angle) { angle = angle * Math.PI / 180; return { x: Math.cos(angle) * pointX - Math.sin(angle) * pointY, y: Math.sin(angle) * pointX + Math.cos(angle) * pointY }; } // //algo de déplacement / changement de direction function relance() { //on boucle tant que maxcompteur n'est pas atteint/dépassé //après un calcul de trajectoire correct while (stopflag == 0) { //nouvelles coordonnées provisoires si pas d'obstacle x1 = x + deltax / 100; y1 = y + deltay / 100; //si on sort de la zone, if (estDansZoneReel(x1,y1) == false) { //on recalcule l'angle et la trajectoire // //on recule de 2 positions (2 :choix arbitraire) x1 = x - deltax / 100; y1 = y - deltay / 100; //angle aléatoire en mutliple de 30° //entre 30° et 330° (0° et 360° exclus) angle = Math.floor((Math.random() * 11 + 1) * 30); //correction aléatoire de l'angle (+-9°) corr_angle = Math.floor(Math.random(19)) - 9; angle = angle + corr_angle; //calcul nouvelle trajectoire p = rotation(deltax, deltay, angle); deltax = p.x; deltay = p.y; //compteur changements de direction compteur1++; //le chemin ne sera peut-être pas bon à la suite trajetok = false; if (compteur1 >= maxcompteur) { //info utilisateur label1.innerHTML = "Il y a eu " + compteur1 + " changements de direction et " + compteur2 + " trajets."; label2.innerHTML = "Surface parcourue : " + tauxRemplissage(); exportCodageZone(); //on arrête la boucle stopflag = 1; } } else { //coordonnées provisoires correctes : on roule // //on compte le trajet if (trajetok == false) { trajetok = true; compteur2++; } //on garde les coordonnées x = x1; y = y1; //arrondis pour dessin et zone x1 = Math.round(x1); y1 = Math.round(y1); //nouvelle position dessinées context.fillRect(x1, y1, 1, 1); //on marque le point comme tondu/parcouru setPointZone(x1, y1, getPointZone(x1, y1) + 1); //Borne pour donner la possibilité d'arrêt à l'utilisateur //sinon boucle infinie (et/ou JS détecte une boucle "trop" longue) if (compteur1 >= maxcompteur) { //info utilisateur label1.innerHTML = "Il y a eu " + compteur1 + " changements de direction et " + compteur2 + " trajets."; label2.innerHTML = "Surface parcourue : " + tauxRemplissage(); exportCodageZone(); //on arrête la boucle stopflag = 1; } } } } // //Taux de remplissage/tonte de la zone function tauxRemplissage() { //on compte les points var compte = 0; //on parcourt la liste à la recherche des 2 for (var i = 0; i < codageZone.length; i++) { //c'est parcouru : valeur=1 if (codageZone[i] >= 1) { compte++; } } // //résultat //var total=(compte>maxAChanger?100:((compte/maxAChanger)*100)); var total = compte / maxAChanger * 100; return "" + compte + "/" + maxAChanger + " ou " + total + " % de la zone."; } // //remplissage de codeZone avec des 0 (pas à parcourir/tondre) function initialiseCodagezone() { //on remplit avec des 0 for (var i = 0; i < canvas.width * canvas.height; i++) { codageZone[i] = -1; } } //changement de la valeur d'un point du dessin //à la bonne position dans codeZone function setPointZone(a, b, valeur) { //if (estDansZone(a, b) == true) { codageZone[a + b * canvas.width] = valeur; //} } //récupération de la valeur d'une position de codeZone //avec les coordonnées du dessin function getPointZone(a, b) { //on récupère la valeur de la bonne case return codageZone[a + b * canvas.width]; } // //test si le point est dans la zone function estDansZoneEntier(a, b) { return (getPointZone(a,b)>=0); } function estDansZoneReel(a, b) { // test si dans rectangle principal // car a et b ne sont pas entiers ! (pb arrondis) var large=((a>=0) && (a=0) && (b=0); } return large; } //dessin graphique //remplisage du tableau codeZone des parties à parcourir/tondre // // forme simple : rectangle // **** // **** function dessineEtCodeZoneSimple() { //dessin context.beginPath(); context.rect(0, 0, canvas.width, canvas.height); context.stroke(); //remplissage codeZone : 0 pour à parcourir for (var j = 0; j < canvas.height; j++) { for (var i = 0; i < canvas.width; i++) { setPointZone(i, j, 0); } } // //calcul du nombre de points marqués à tondre/parcourir //somme des (valeurs + 1 ) car vide = -1, à tondre = 0 maxAChanger = 0; for (var i = 0; i < canvas.width * canvas.height; i++) { maxAChanger += codageZone[i]+1; } } // forme complexe // ** // * // **** // **** function dessineEtCodeZoneComplexe() { context.beginPath(); context.moveTo(0, 0); //pour éviter des calculs dans les boucles var cfixe = 0; // cfixe = canvas.width / 2; // //dessin graphique context.lineTo(cfixe, 0); context.lineTo(cfixe, 50); //tableau codageZone : 0 pour à parcourir for (var j = 0; j <= 50; j++) { for (var i = 0; i < cfixe; i++) { setPointZone(i, j, 0); } } // cfixe = canvas.width / 4; context.lineTo(cfixe, 50); context.lineTo(cfixe, 100); // for (var j = 50; j < 100; j++) { for (var i = 0; i < cfixe; i++) { setPointZone(i, j, 0); } } // context.lineTo(canvas.width, 100); context.lineTo(canvas.width, canvas.height); // for (var j = 100; j < canvas.height; j++) { for (var i = 0; i < canvas.width; i++) { setPointZone(i, j, 0); } } // context.lineTo(0, canvas.height); context.closePath(); // context.stroke(); // //calcul du nombre de points marqués à tondre/parcourir //somme des (valeurs + 1 ) car vide = -1, à tondre = 0 maxAChanger = 0; for (var i = 0; i < canvas.width * canvas.height; i++) { maxAChanger += codageZone[i]+1; } } // forme rectangle, avec ilôt // **** // *[]* // **** function dessineEtCodeZoneSimpleIlot() { //dessin context.beginPath(); context.rect(0, 0, canvas.width, canvas.height); context.rect(100,100,20,10); context.stroke(); //remplissage codeZone : 0 pour à parcourir for (var j = 0; j < canvas.height; j++) { for (var i = 0; i < canvas.width; i++) { setPointZone(i, j, 0); } } // on interdit certaines zones : -1 for (var j = 100; j < 110; j++) { for (var i = 100; i < 120; i++) { setPointZone(i, j, -1); } } // //calcul du nombre de points marqués à tondre/parcourir //somme des (valeurs + 1 ) car vide = -1, à tondre = 0 maxAChanger = 0; for (var i = 0; i < canvas.width * canvas.height; i++) { maxAChanger += codageZone[i]+1; } } // forme complexe // ***** // *[ ]* // * function dessineEtCodeZoneComplexe2() { //dessin context.beginPath(); context.rect(0, 0, canvas.width, canvas.height); context.stroke(); //remplissage codeZone : 0 pour à parcourir for (var j = 0; j < canvas.height; j++) { for (var i = 0; i < canvas.width; i++) { setPointZone(i, j, 0); } } // on interdit certaines zones : -1 // // /!\ zone max de larg 200 x haut 300 // //muret haut for (var j = 50; j < 52; j++) { for (var i = 30; i < 170; i++) { setPointZone(i, j, -1); } } //maison bas for (var j = 230; j < 300; j++) { for (var i = 30; i < 130; i++) { setPointZone(i, j, -1); } } //jardin gauche for (var j = 150; j < 200; j++) { for (var i = 30; i < 50; i++) { setPointZone(i, j, -1); } } //muret bas for (var j = 198; j < 200; j++) { for (var i = 30; i < 130; i++) { setPointZone(i, j, -1); } } //terrasse for (var j = 200; j < 230; j++) { for (var i = 80; i < 130; i++) { setPointZone(i, j, -1); } } // //calcul du nombre de points marqués à tondre/parcourir //somme des (valeurs + 1 ) car vide = -1, à tondre = 0 maxAChanger = 0; for (var i = 0; i < canvas.width * canvas.height; i++) { maxAChanger += codageZone[i]+1; } } // forme couloir // ******* // * *** * // * * // * *** * // ******* function dessineEtCodeZoneCouloir() { //dessin context.beginPath(); context.rect(0, 0, canvas.width, canvas.height); context.rect(50,0,200,70); context.rect(50,80,200,200); context.stroke(); //remplissage codeZone : 0 pour à parcourir for (var j = 0; j < canvas.height; j++) { for (var i = 0; i < canvas.width; i++) { setPointZone(i, j, 0); } } // on interdit certaines zones : -1 for (var j = 0; j < 70; j++) { for (var i = 50; i < 250; i++) { setPointZone(i, j, -1); } } for (var j = 80; j < 200; j++) { for (var i = 50; i < 250; i++) { setPointZone(i, j, -1); } } // //calcul du nombre de points marqués à tondre/parcourir //somme des (valeurs + 1 ) car vide = -1, à tondre = 0 maxAChanger = 0; for (var i = 0; i < canvas.width * canvas.height; i++) { maxAChanger += codageZone[i]+1; } } // //Pour visualiser le nombre de passage par un point. // function dessineNbPassages() { //dessin context.beginPath(); context.strokeStyle="blue"; context.rect(0, 0, canvas.width, canvas.height); context.stroke(); var coul =""; var gpz = 0; var teinte = 0; var v; //remplissage codeZone : 0 pour à parcourir context.beginPath(); for (var j = 0; j < canvas.height; j++) { for (var i = 0; i < canvas.width; i++) { gpz=getPointZone(i,j); if(gpz>0) { teinte=256-gpz*30; if(teinte<0) teinte=0; v=0x1000000 + 0x10000 *teinte + 0x100 * teinte + teinte; coul="#"+v.toString(16).substr(1); } else { coul="#FFFFFF"; } context.fillStyle=coul; context.fillRect(i, j, 1, 1); } } context.fillStyle="red"; context.stroke(); } //pour exporter le contenu du tableau en csv : texte function exportCodageZone() { var s = ""; var t = ""; //séparteur , et non \t tabulation for (var j = 0; j < canvas.height; j++) { s = ""; for (var i = 0; i < canvas.width; i++) { s = s + "," + getPointZone(i, j); } t = t + s.slice(1) + "\n"; } //fichier associé au lien (activé la 1ère fois) download.setAttribute("href", "data:text/csv;charset=utf-8," + encodeURI(t)); download.setAttribute("download", "simRobotTondeuse.csv"); } // // function bouton_go() { //nouvelle borne à dépasser maxcompteur = Math.max(maxcompteur, compteur1) + parseFloat(input1.value); //on autorise la boucle des calculs stopflag = 0; //c'est parti relance(); } // //L'appui sur le bouton lance ou relance les calculs btn1.onclick = bouton_go; btn2.onclick = dessineNbPassages;