最後にキャンバス忍者へのリンク。
具体的には、Path要素と、それをキャンバスに実装する方法に焦点を当てます。
思い出すと、svgのパスは、ベジェ曲線、これらの曲線からのスプライン、および円を描くことができます。 この点に関しては、Canvasの機会ははるかに少ないため、Canvasで作業します。 まず、曲線を描く方法を学びます。 svgでは、キャンバスと同様に、曲線は3度に制限されています。これは最適化のために行われます。標準方程式を使用して計算し、任意の順序の曲線を作成します。
曲線の計算に複雑なことはありません。 以下は[x、y]の形式の配列を返す関数のコードです-これは曲線上の点です。 関数の入力パラメーター:制御点の配列と曲線の始点に対するオフセット係数。 オフセットは、0%〜100%のパーセンテージ値です(0は曲線の始まり、100は終わりです)。この点が明確であることを願っています。
// function factorial (number) { var result = 1; while(number){ result *= number--; } return result; } function getPointOnCurve (shift, points) { var result = [0,0]; var powerOfCurve = points.length - 1; shift = shift/100; for(var i = 0;points[i];i++){ var polynom = (factorial(powerOfCurve)/(factorial(i)*factorial(powerOfCurve - i))) * Math.pow(shift,i) * Math.pow(1-shift,powerOfCurve - i); result[0] += points[i][0] * polynom; result[1] += points[i][1] * polynom; } return result; } getPointOnCurve(60, [[0,0],[100,0],[100,100]]); // -> [84, 36]
ここでの秘Theは、ポイントを計算するための係数の役割を果たす多項式です。 曲線については一度書いたことがありますが、再帰によって計算するだけです( こちら )。
次の目的地はスプラインです。
一般に、前の関数を使用すると、曲線の任意のセクションを描画できますが、スプラインを使用すると、スプラインの一部のみを描画することは難しくなります。その長さ、したがって曲線の長さを知る必要があります。 スプライン全体を出力する場合、これは必要ありませんが、誰が簡単な方法を探していますか?
これで、コントロールポイントの配列は次のようになります。
[[0,0],[100,0],[100,100,true],[200,100],[200,0]]
真のフラグのあるポイントはピボットポイントです。
機能コード
function getCenterToPointDistance (coordinates){ return Math.sqrt(Math.pow(coordinates[0],2) + Math.pow(coordinates[1],2)); } function getLengthOfCurve (points, step) { step = step || 1; var result = 0; var lastPoint = points[0]; for(var sift = 0;sift <= 100;sift += step){ var coord = getPointOnCurve(sift,points); result += getCenterToPointDistance([ coord[0] - lastPoint[0], coord[1] - lastPoint[1] ]); lastPoint = coord; } return result; }; function getMapOfSpline (points, step) { var map = [[]]; var index = 0; for(var i = 0;points[i];i++){ var curvePointsCount = map[index].length; map[index][+curvePointsCount] = points[i]; if(points[i][2] && i != points.length - 1){ map[index] = getLengthOfCurve(map[index],step); index++; map[index] = [points[i]]; } } map[index] = getLengthOfCurve(map[index],step); return map; }; function getPointOnSpline (shift, points, services) { var shiftLength = services.length / 100 * shift; if(shift >= 100){ shiftLength = services.length; } var counter = 0; var lastControlPoint = 0; var controlPointsCounter = 0; var checkedCurve = []; for(; services.map[lastControlPoint] && counter + services.map[lastControlPoint] < shiftLength; lastControlPoint++ ){ counter += services.map[lastControlPoint]; } for( var pointIndex = 0; points[pointIndex] && controlPointsCounter <= lastControlPoint; pointIndex++ ){ if(points[pointIndex][2] === true){ controlPointsCounter++; } if(controlPointsCounter >= lastControlPoint){ checkedCurve.push(points[pointIndex]); } } return getPointOnCurve( (shiftLength - counter) / (services.map[lastControlPoint] / 100), checkedCurve ); }; var points = [[0,0],[100,0],[100,100,true],[200,100],[200,0]]; var services = {}; services.map = getMapOfSpline(points); services.length= 0; for(var key in services.map){ services.length += services.map[key]; } getPointOnSpline(60, points, services); // -> [136, 95.(9)]
まあ、それはすべて残っています。スプラインに楕円弧を含める方法を学びます。 ここでは、ネイティブSVGアルゴリズムと、それとの違いについて説明する価値があります。
パス内のアークの場合、SVGは7つのパラメーターを提供しますが、これらのパラメーターは不要であると考えられます。これらのパラメーターは次のとおりです。
A rx ry x-axis-rotation large-arc-flag sweep-flag xy
デコード:
- 最初の2つのパラメーターは非常に明白です-これらは縦座標と横座標の軸です
- 3番目は、度で指定された楕円の勾配です
- 4番目と5番目は、アークの方向を指定します
- 6番目と7番目-終点の座標
より少ない数のパラメータでアークを完全に制御できます。さらに、実際には、終点の座標は常にわかっているとは限りません。 さらに、誤って示された最終座標は楕円を変形させ、その値が大きすぎる場合、半軸のパラメーターは指定されたものに対応しなくなります。
アークの場合、次のことを知る必要があります。
- 楕円の半軸
- 弧の始まりと終わりの角度
- 楕円斜面
5つのパラメーターのみ。
ロジックはこれになります、手に従ってください。 楕円弧の中心(その中心と混同しないでください)は、配列内の前の点、または前の点がない場合は原点になります。 円弧の終点は、パスをさらに構築するための基準点です。 パラメータの助けを借りて、アークはあらゆる形を取り、どこにでも行きます、すべてが装飾的です。 繰り返しますが、パスの一部のみを構築するには、すべてのセグメントの長さを計算する必要があります。
楕円が含まれるポイントのセットは次のようになります。
[ [x1,y1], [x2,y2], [x3,y3], [radiusX,radiusY,startRadian,endRadian,tilt], // tilt - optional [x5,y5], ... [xN,yN] ]
コード
function getPointOnEllipse (radiusX,radiusY,shift,tilt,centerX,centerY){ tilt = tilt || 0; tilt *= -1; centerX = centerX || 0; centerY = centerY || 0; var x1 = radiusX*Math.cos(+shift), y1 = radiusY*Math.sin(+shift), x2 = x1 * Math.cos(tilt) + y1 * Math.sin(tilt), y2 = -x1 * Math.sin(tilt) + y1 * Math.cos(tilt); return [x2 + centerX,y2 + centerY]; } function getLengthOfEllipticArc (radiusX, radiusY, startRadian, endRadian, step) { step = step || 1; var length = 0; var lastPoint = getPointOnEllipse(radiusX,radiusY,startRadian); var radianPercent = (endRadian - startRadian) / 100; for(var i = 0;i<=100;i+=step){ var radian = startRadian + radianPercent * i; var point = getPointOnEllipse(radiusX,radiusY,radian); length += getCenterToPointDistance([point[0]-lastPoint[0],point[1]-lastPoint[1]]); lastPoint = point; } return length; }; function getMapOfPath (points, step) { var map = [[]]; var index = 0; var lastPoint = []; for(var i = 0;points[i];i++){ var point = points[i]; if(point.length > 3){ map[index] = getLengthOfEllipticArc(point[0], point[1], point[2], point[3], step); if(!points[i+1]){continue} var centerOfArc = getPointOnEllipse(point[0], point[1], point[2] + Math.PI, point[4], lastPoint[0], lastPoint[1]); var endOfArc = getPointOnEllipse(point[0], point[1], point[3], point[4], centerOfArc[0], centerOfArc[1]); index++; map[index] = [endOfArc]; lastPoint = endOfArc; continue; } map[index].push(point); if(point[2] === true || (points[i+1] && points[i+1].length > 3)){ map[index] = getLengthOfCurve(map[index],step); index++; map[index] = [point]; } lastPoint = point; } if(typeof map[index] !== 'number'){map[index] = getLengthOfCurve(map[index],step);} return map; }; function getPointOnPath (shift, points, services) { var shiftLength = services.length / 100 * shift; if(shift >= 100){ shiftLength = services.length; } var counter = 0; var lastControlPoint = 0; var controlPointsCounter = 0; var checkedCurve = []; for(; services.map[lastControlPoint] && counter + services.map[lastControlPoint] < shiftLength; lastControlPoint++){ counter += services.map[lastControlPoint]; } var lastPoint = []; for(var pointIndex = 0; points[pointIndex] && controlPointsCounter <= lastControlPoint; pointIndex++){ var point = points[pointIndex]; if(point.length > 3){ var centerOfArc = getPointOnEllipse(point[0], point[1], point[2] + Math.PI, point[4], lastPoint[0], lastPoint[1]); if(controlPointsCounter === lastControlPoint){ var percent = (shiftLength - counter) / (services.map[lastControlPoint] / 100); var resultRadian = point[2] + ((point[3] - point[2])/100*percent); return getPointOnEllipse(point[0], point[1], resultRadian, point[4], centerOfArc[0], centerOfArc[1]); } lastPoint = getPointOnEllipse(point[0], point[1], point[3], point[4], centerOfArc[0], centerOfArc[1]); controlPointsCounter++; if(controlPointsCounter === lastControlPoint){ checkedCurve.push(lastPoint); } continue } if(point[2] === true || (points[pointIndex+1] && points[pointIndex+1].length > 3)){ controlPointsCounter++; } if(controlPointsCounter >= lastControlPoint){ checkedCurve.push(point); } lastPoint = point; } return getPointOnCurve( (shiftLength - counter) / (services.map[lastControlPoint] / 100), checkedCurve ); }; var points = [[0,0],[100,0],[100,100],[20,20,0,Math.PI],[200,100],[200,0]]; var services = {}; services.map = getMapOfPath(points); services.length= 0; for(var key in services.map){ services.length += services.map[key]; } getPointOnPath(60, points, services); // -> [96.495, 98.036]
今、私たちはクールな男です、私たちは多くのことをすることができます。
ここの例へのリンク。
UPD。 例の余分な行を恐れないでください-これは楕円が含まれるパスです 忍者はスプラインによってのみ描かれます。