带绳索图案的二次曲线
Quadratic Curve with Rope pattern
我正在尝试在 Fabric 中制作绳索。当它是一条直绳时一切都很好,但是如果我把它稍微弯曲一点,它就会变成这样
http://i.stack.imgur.com/EZeSb.png
我的绳子图案在弯曲时丢失了一部分。
所以我试着把它分割成多个小矩形,然后把图案加到所有的小矩形上,但是看起来不自然,所有的矩形都在一个凌乱的坐标系中。
那么有没有办法制作一条普通的曲线绳索呢?谢谢!
可以,但是比较复杂,而且弯道一定比较"gentle"(没有急转弯)。
您在问题中使用的曲线可以正常工作。
您必须在原生 html5 canvas 上绘制弯曲的绳索,然后在 FabricJS 上显示原生 canvas。
请参阅此 link,了解如何在曲线上绘制渐变:
Gradient Stroke Along Curve in Canvas。
然后沿着曲线而不是梯度逐渐绘制绳索图像:
- 从水平绳索图像开始,
- 您要将其垂直切成 1px 的长度,并且
- 沿曲线逐渐绘制绳索切片,每个切片与曲线上每个新点处的曲线角度相切。
您可以使用转换完成#3:
- 将canvas原点重置为曲线上的a点:
context.translate(curveX,curveY)
- 将 canvas 旋转与曲线相切的角度:
context.rotate(tangentAngle).
使用 drawImage
的裁剪版本绘制图像的下一个切片:
drawImage(ropeImg,sliceX,0,1,ropeImg.height,0,0,1,ropeImg.height);
通过撤消转换进行清理:context.setTransform(1,0,0,1,0,0);
- 对曲线上的下一个点和下一段绳子重复#1。
- 对曲线上的所有点继续。
此时你在原生 html5 canvas 上有一根弯曲的绳子。
使用 nativeCanvas.toDataURL()
作为图像源创建一个新的 FabricJS 图像。
示例代码和演示
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
// variables defining a cubic bezier curve
var PI2=Math.PI*2;
var s={x:20,y:50};
var c1={x:200,y:70};
var c2={x:40,y:230};
var e={x:270,y:250};
// an array of points plotted along the bezier curve
var points=[];
// we use PI often so put it in a variable
var PI=Math.PI;
// plot 400 points along the curve
// and also calculate the angle of the curve at that point
// NOTE: You may need to adjust the point count (==100 here)
// if the curve is much shorter or longer than this demo's curve
for(var t=0;t<=100;t+=0.25){
var T=t/100;
// plot a point on the curve
var pos=getCubicBezierXYatT(s,c1,c2,e,T);
// calculate the tangent angle of the curve at that point
var tx = bezierTangent(s.x,c1.x,c2.x,e.x,T);
var ty = bezierTangent(s.y,c1.y,c2.y,e.y,T);
var a = Math.atan2(ty, tx)-PI/2;
// save the x/y position of the point and the tangent angle
// in the points array
points.push({
x:pos.x,
y:pos.y,
angle:a
});
}
var img=new Image();
img.onload=function(){
slicer();
};
img.src='https://dl.dropboxusercontent.com/u/139992952/multple/rope.png';
function slicer(){
// Note: increase the lineWidth if
// the gradient has noticable gaps
ctx.lineWidth=2;
ctx.strokeStyle='skyblue';
var sliceCount=0;
// draw a gradient-stroked line tangent to each point on the curve
for(var i=0;i<points.length;i++){
var p=points[i];
ctx.translate(p.x,p.y);
ctx.rotate(p.angle-PI/2);
// draw multiple times to fill gaps on outside of rope slices
ctx.drawImage(img,sliceCount,0,1,img.height,0,0,1,img.height);
ctx.drawImage(img,sliceCount,0,1,img.height,0,0,1,img.height);
ctx.drawImage(img,sliceCount,0,1,img.height,0,0,1,img.height);
ctx.setTransform(1,0,0,1,0,0);
++sliceCount;
if(sliceCount>(img.width-1)){sliceCount=0;}
}
}
//////////////////////////////////////////
// helper functions
//////////////////////////////////////////
// calculate one XY point along Cubic Bezier at interval T
// (where T==0.00 at the start of the curve and T==1.00 at the end)
function getCubicBezierXYatT(startPt,controlPt1,controlPt2,endPt,T){
var x=CubicN(T,startPt.x,controlPt1.x,controlPt2.x,endPt.x);
var y=CubicN(T,startPt.y,controlPt1.y,controlPt2.y,endPt.y);
return({x:x,y:y});
}
// cubic helper formula at T distance
function CubicN(T, a,b,c,d) {
var t2 = T * T;
var t3 = t2 * T;
return a + (-a * 3 + T * (3 * a - a * T)) * T
+ (3 * b + T * (-6 * b + b * 3 * T)) * T
+ (c * 3 - c * 3 * T) * t2
+ d * t3;
}
// calculate the tangent angle at interval T on the curve
function bezierTangent(a, b, c, d, t) {
return (3 * t * t * (-a + 3 * b - 3 * c + d) + 6 * t * (a - 2 * b + c) + 3 * (-a + b));
};
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<h4>Source horizontal rope image</h4>
<img src='https://dl.dropboxusercontent.com/u/139992952/multple/rope.png'>
<h4>After slicing rope and drawing it along curve</h4>
<canvas id="canvas" width=300 height=300></canvas>
我正在尝试在 Fabric 中制作绳索。当它是一条直绳时一切都很好,但是如果我把它稍微弯曲一点,它就会变成这样
http://i.stack.imgur.com/EZeSb.png
我的绳子图案在弯曲时丢失了一部分。
所以我试着把它分割成多个小矩形,然后把图案加到所有的小矩形上,但是看起来不自然,所有的矩形都在一个凌乱的坐标系中。
那么有没有办法制作一条普通的曲线绳索呢?谢谢!
可以,但是比较复杂,而且弯道一定比较"gentle"(没有急转弯)。
您在问题中使用的曲线可以正常工作。
您必须在原生 html5 canvas 上绘制弯曲的绳索,然后在 FabricJS 上显示原生 canvas。
请参阅此 link,了解如何在曲线上绘制渐变:
Gradient Stroke Along Curve in Canvas。
然后沿着曲线而不是梯度逐渐绘制绳索图像:
- 从水平绳索图像开始,
- 您要将其垂直切成 1px 的长度,并且
- 沿曲线逐渐绘制绳索切片,每个切片与曲线上每个新点处的曲线角度相切。
您可以使用转换完成#3:
- 将canvas原点重置为曲线上的a点:
context.translate(curveX,curveY)
- 将 canvas 旋转与曲线相切的角度:
context.rotate(tangentAngle).
使用
drawImage
的裁剪版本绘制图像的下一个切片:drawImage(ropeImg,sliceX,0,1,ropeImg.height,0,0,1,ropeImg.height);
通过撤消转换进行清理:
context.setTransform(1,0,0,1,0,0);
- 对曲线上的下一个点和下一段绳子重复#1。
- 对曲线上的所有点继续。
此时你在原生 html5 canvas 上有一根弯曲的绳子。
使用 nativeCanvas.toDataURL()
作为图像源创建一个新的 FabricJS 图像。
示例代码和演示
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
// variables defining a cubic bezier curve
var PI2=Math.PI*2;
var s={x:20,y:50};
var c1={x:200,y:70};
var c2={x:40,y:230};
var e={x:270,y:250};
// an array of points plotted along the bezier curve
var points=[];
// we use PI often so put it in a variable
var PI=Math.PI;
// plot 400 points along the curve
// and also calculate the angle of the curve at that point
// NOTE: You may need to adjust the point count (==100 here)
// if the curve is much shorter or longer than this demo's curve
for(var t=0;t<=100;t+=0.25){
var T=t/100;
// plot a point on the curve
var pos=getCubicBezierXYatT(s,c1,c2,e,T);
// calculate the tangent angle of the curve at that point
var tx = bezierTangent(s.x,c1.x,c2.x,e.x,T);
var ty = bezierTangent(s.y,c1.y,c2.y,e.y,T);
var a = Math.atan2(ty, tx)-PI/2;
// save the x/y position of the point and the tangent angle
// in the points array
points.push({
x:pos.x,
y:pos.y,
angle:a
});
}
var img=new Image();
img.onload=function(){
slicer();
};
img.src='https://dl.dropboxusercontent.com/u/139992952/multple/rope.png';
function slicer(){
// Note: increase the lineWidth if
// the gradient has noticable gaps
ctx.lineWidth=2;
ctx.strokeStyle='skyblue';
var sliceCount=0;
// draw a gradient-stroked line tangent to each point on the curve
for(var i=0;i<points.length;i++){
var p=points[i];
ctx.translate(p.x,p.y);
ctx.rotate(p.angle-PI/2);
// draw multiple times to fill gaps on outside of rope slices
ctx.drawImage(img,sliceCount,0,1,img.height,0,0,1,img.height);
ctx.drawImage(img,sliceCount,0,1,img.height,0,0,1,img.height);
ctx.drawImage(img,sliceCount,0,1,img.height,0,0,1,img.height);
ctx.setTransform(1,0,0,1,0,0);
++sliceCount;
if(sliceCount>(img.width-1)){sliceCount=0;}
}
}
//////////////////////////////////////////
// helper functions
//////////////////////////////////////////
// calculate one XY point along Cubic Bezier at interval T
// (where T==0.00 at the start of the curve and T==1.00 at the end)
function getCubicBezierXYatT(startPt,controlPt1,controlPt2,endPt,T){
var x=CubicN(T,startPt.x,controlPt1.x,controlPt2.x,endPt.x);
var y=CubicN(T,startPt.y,controlPt1.y,controlPt2.y,endPt.y);
return({x:x,y:y});
}
// cubic helper formula at T distance
function CubicN(T, a,b,c,d) {
var t2 = T * T;
var t3 = t2 * T;
return a + (-a * 3 + T * (3 * a - a * T)) * T
+ (3 * b + T * (-6 * b + b * 3 * T)) * T
+ (c * 3 - c * 3 * T) * t2
+ d * t3;
}
// calculate the tangent angle at interval T on the curve
function bezierTangent(a, b, c, d, t) {
return (3 * t * t * (-a + 3 * b - 3 * c + d) + 6 * t * (a - 2 * b + c) + 3 * (-a + b));
};
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<h4>Source horizontal rope image</h4>
<img src='https://dl.dropboxusercontent.com/u/139992952/multple/rope.png'>
<h4>After slicing rope and drawing it along curve</h4>
<canvas id="canvas" width=300 height=300></canvas>