Bonjour,
La fonction computePt calcule les coordonnées d'un point sur la courbe de Bézier, la formule de la courbe étant une équation de la forme d'un polynôme un peu particulier. On trouve cette formule très facilement sur internet, par exemple sur Wikipédia. Ici, on considère une courbe de Bézier dite "cubique" (le polynôme est de degré 3 d'où ce nom de "cubique" qui signifie élévation au cube pour ceux qui ont quelques souvenirs en Maths).
Le paramètre t est une variable auquel on donne des valeurs allant de 0 à 1 pour parcourir l'ensemble de la courbe (t=0 correspond au point initial de la courbe et t=1 à sa fin). Les t1, t1_3, t1_3a, t1_3b,t1_3c sont des résultats de calculs intermédiaires de termes du polynôme. Le paramètre c est un tableau contenant quatre points (le point initial, deux points situés à l'extérieur de courbe "attirant" en quelques sortes la courbe vers eux et qu'on appelle les poignées, et le point final). L'un des grands intérêts des courbes de Bézier, c'est que connaissant les coordonnées de ces quatre points, on est capable de calculer facilement les coordonnées de tous les points de la courbe en faisant varier t de 0 à 1, et c'est ce que fait la fonction computePt. Des mathématiciens ont mis au point la formule, nous, on a juste à l'utiliser tel quel. Ça a l'air compliqué comme ça, mais en ce qui concerne le calcul, ce sont en fait des maths assez simple. Ce calcul des coordonnées d'un point sur la courbe connaissant la formule peut être fait sans trop de problème par un élève de collège : il n'y a que des additions et des multiplications.
On notera que bien que t=0.5 soit au milieu de l'intervalle [0..1], le point correspondant sur la courbe n'est pas forcément au "milieu" de celle-ci, le parcours de la courbe en fonction de la valeur de t étant plus ou moins "rapide" selon la forme de cette courbe. C'est pour ça qu'on calcule les coordonnées d'un grand nombre de points de la courbe pas à pas, et les distances entre chacun de ses points, pour savoir lequel est au milieu (il y a peut-être une formule pour calculer directement les coordonnées du milieu, mais la méthode pas à pas à l'avantage de la simplicité). C'est ce que fait la fonction cubicBezierCurvesMiddle : elle calcule les coordonnées de 1000 points, calcule les distances entre chacun de ces points (des petits morceaux de la courbe donc), et détermine lequel de ces points est le plus proche du milieu en ajoutant ces petits morceaux les uns aux autres jusqu'à atteindre la moitié de la distance totale.
Voici un code un peu mieux optimisé (avec quelques commentaires supplémentaires en anglais car je commente toujours mon code en anglais), et qui permet de "jouer" avec la formule pour produire une courbe cubique de Bézier quelconque (ce type de courbe de Bézier a 2 "poignées"). Le point M est le point initial de la courbe (en coordonnées absolues), les points c1 et c2 sont les deux poignées (en coordonnées relatives par rapport à M), le point c3 est le point final (en coordonnées relatives par rapport à M lui aussi). Dans les calculs, on utilise également un point c0 qui représente lui aussi le point initial, mais en coordonnées relatives par rapport à M. Ses coordonnées sont donc toujours (0,0). J'ai choisi les dénominations M (en lettre majuscule), c1, c2, et c3 (en lettre minuscule) dans le formulaire parce qu'ensuite, quand on écrit le code svg correspondant, ce sont les coordonnées de ces points qu'on met comme valeur dans l'attribut d de la balise path tout simplement.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
</head>
<style>
body {text-align:center;}
svg {border:10px solid blue;}
#control span {display:inline-block;width:2em;text-align:right;margin-right:0.5em;}
#trace {display:table;margin:1em auto;text-align:left;}
#trace span:nth-of-type(2),
#trace span:nth-of-type(3) {padding-left:2em;}
</style>
<body>
<h1>Cubic Bézier curve and its middle point</h1>
<svg id="svgRef" width="250" height="250">
<path id="pRef" d="M 75,75 c 20,180 180,20 -5,-5" fill="transparent" stroke="blue" stroke-width="10" />
<circle id="cRef" cx="0" cy="0" r="10" fill="transparent" stroke="blue" stroke-width="10" />
</svg>
<br>
<div class="control">
<span>M:</span>
<input id="mx" type="text" onkeyup="updateSvg()">
<input id="my" type="text" onkeyup="updateSvg()"><br>
<span>c1:</span>
<input id="c1x" type="text" onkeyup="updateSvg()">
<input id="c1y" type="text" onkeyup="updateSvg()"><br>
<span>c2:</span>
<input id="c2x" type="text" onkeyup="updateSvg()">
<input id="c2y" type="text" onkeyup="updateSvg()"><br>
<span>c3:</span>
<input id="c3x" type="text" onkeyup="updateSvg()">
<input id="c3y" type="text" onkeyup="updateSvg()"><br>
</div>
<div id="trace"></div>
<script>
// compute coordinates of the middle point of a cubic Bezier curve
// need two functions: computePt and cubicBezierCurvesMiddle
function computePt(t,c)
{
// compute relative coordinates of a point on the curve using t and c
// t is a number between 0 and 1
// c is an array of 4 points:
// the initial point of the curve (always (0,0))
// the 2 "handle" points of the curve
// the final point of the curve
var t1,t1_3,t1_3a,t1_3b,t1_3c;
t1=1-t;
t1_3=t1*t1*t1;
t1_3a=(3*t)*(t1*t1);
t1_3b=(3*(t*t))*t1;
t1_3c=(t*t*t);
return {x: (c[0].x*t1_3)+(t1_3a*c[1].x)+(t1_3b*c[2].x)+(t1_3c*c[3].x),
y: (c[0].y*t1_3)+(t1_3a*c[1].y)+(t1_3b*c[2].y)+(t1_3c*c[3].y)};
}
function cubicBezierCurvesMiddle(m,c)
{
var k,km=1000,km2=(km>>1),len=0,len2,x,y,a=new Array(km+1);
// compute curve lengths from start point to any point
// store relative point coordinates and corresponding length in array a
for (k=0;k<=km;k++)
{
a[k]={pt:computePt(k/km,c),len:0};
if (k>0)
{
x=a[k].pt.x-a[k-1].pt.x;
y=a[k].pt.y-a[k-1].pt.y;
a[k].len=a[k-1].len+Math.sqrt(x*x+y*y);
}
}
// retrieve the point which is at a distance of half the whole curve length from start point
// most of the time, this point is not the one at indice km2 in array a, but it is near it
len2=a[ km ].len/2;
if (a[ km2 ].len>len2) for (k=km2;k>=0;k--) {if (len2>=a[k].len) break;}
else for (k=km2;k<=km;k++) {if (len2<=a[k].len) break;}
// return absolute coordinates of the point
return {x: Math.round((a[k].pt.x+m.x)*100)/100,
y: Math.round((a[k].pt.y+m.y)*100)/100};
}
// manage the data form and update the svg graphic
// need two functions: getVal and updateSvg
function getVal(id)
{
var v=document.getElementById(id).value;
if (!v||isNaN(v)) return 0;
return parseInt(v);
}
function updateSvg()
{
var m,c=[],pt,s,t;
m={x:getVal("mx"),y:getVal("my")};
c[0]={x:0,y:0};
c[1]={x:getVal("c1x"),y:getVal("c1y")};
c[2]={x:getVal("c2x"),y:getVal("c2y")};
c[3]={x:getVal("c3x"),y:getVal("c3y")};
s="M "+m.x+","+m.y+" c "+c[1].x+","+c[1].y+" "+c[2].x+","+c[2].y+" "+c[3].x+","+c[3].y;
document.getElementById("pRef").setAttribute("d",s);
pt=cubicBezierCurvesMiddle(m,c);
document.getElementById("cRef").setAttribute("cx",pt.x);
document.getElementById("cRef").setAttribute("cy",pt.y);
t="<span><svg width=\"250\" height=\"250\"></span>";
t+="<br>";
t+="<span><path d=\""+s+"\" fill=\"transparent\" stroke=\"blue\" stroke-width=\"10\" /></span>";
t+="<br>";
t+="<span><circle cx=\""+pt.x+"\" cy=\""+pt.y+"\" r=\"10\" fill=\"transparent\" stroke=\"blue\" stroke-width=\"10\" /></span>";
t+="<br>";
t+="<span></svg></span>";
document.getElementById("trace").innerHTML=t;
}
var s=document.getElementById("pRef").getAttribute("d");
var mc=s.match(/M ([0-9-]*),([0-9-]*) c ([0-9-]*),([0-9-]*) ([0-9-]*),([0-9-]*) ([0-9-]*),([0-9-]*)/);
document.getElementById("mx").value=mc[1];
document.getElementById("my").value=mc[2];
document.getElementById("c1x").value=mc[3];
document.getElementById("c1y").value=mc[4];
document.getElementById("c2x").value=mc[5];
document.getElementById("c2y").value=mc[6];
document.getElementById("c3x").value=mc[7];
document.getElementById("c3y").value=mc[8];
updateSvg();
</script>
</body>
</html>
Voici le jsfiddle mis à jour
http://jsfiddle.net/LeFx9/ (il faut penser à sélectionner "no wrap - in body" dans le panneau "Frameworks & Extensions" pour que ça marche).
EDIT (15/06/2014) : correction d'une erreur dans la fonction cubicBezierCurvesMiddle() (remplacement de for (k=km2;k<=0;k--) par for (k=km2;k>=0;k--)
Amicalement,
Modifié par parsimonhi (15 Jun 2014 - 23:13)