研究贝塞尔曲线, 定距等分 ...

https://en.wikipedia.org/wiki/Bézier_curve
贝塞尔曲线公式推导原理
https://www.cnblogs.com/equal/p/6414263.html
贝塞尔曲线原理(简单阐述)
https://www.cnblogs.com/hnfxs/p/3148483.html
n 阶贝塞尔曲线计算公式实现
https://blog.csdn.net/aimeimeits/article/details/72809382
[转]匀速贝塞尔曲线运动的实现
http://as3.iteye.com/blog/865587
贝塞尔曲线运动n阶追踪方程的数学原理及其匀速化方法和应用
https://blog.csdn.net/iSunwish/article/details/78935257

https://www.zhihu.com/question/27715729 如何得到贝塞尔曲线的曲线长度和 t 的近似关系?
https://www.geometrictools.com/Documentation/MovingAlongCurveSpecifiedSpeed.pdf

Approximation of a cubic bezier curve by circular arcs and vice versa
https://www.researchgate.net/publication/265893293_Approximation_of_a_cubic_bezier_curve_by_circular_arcs_and_vice_versa
Approximation of a planar cubic Bezier spiral by circular arcs
https://www.sciencedirect.com/science/article/pii/S0377042796000568

<html>
<head>
  <meta charset="UTF-8">
  <title>二阶贝塞尔曲线</title>
   <style type="text/css">
   .box {
   	background-color: black;
   }
   </style>
</head>

<body>
<canvas id="canvas" class="box">
    canvas not supported, please use html5 browser.
</canvas>
</body>
<script>
window.onload = function() {
	var canvas = document.querySelector(".box");
	canvas.width = 800;
	canvas.height = 600;
	var origin = {x: 400, y: 550};
	
	var ctx = canvas.getContext('2d');
	ctx.beginPath();
	ctx.strokeStyle = "green";
		
	var p0 = {x: 140, y: 370};
	var p1 = {x: 340, y: 120};
	var p2 = {x: 690, y: 520};
	var p3 = {x: 230, y: 440};

	
	var t = 0, step = 0.005;
	//二阶贝塞尔曲线
	// function enterFrame() {
	// 	t += step;
	// 	var p = {};
 //        p.x = (1-t)**2 * p0.x + 2 * (1-t) * t * p1.x + t**2 * p2.x;
 //        p.y = (1-t)**2 * p0.y + 2 * (1-t) * t * p1.y + t**2 * p2.y;
 //        ctx.lineTo(p.x, p.y);
 //        ctx.stroke();
 //        if (t >= 1) {
 //        	return;
 //        }
 //        requestAnimationFrame(enterFrame);
 //    }
 //    enterFrame();
 	var radius = 7; 
 	ctx.fillStyle = "#EE9611";	//#2BD56F
	ctx.arc(p0.x, p0.y, radius, Math.PI*2, false);
	ctx.fill();
	ctx.closePath();
	ctx.beginPath();
	ctx.arc(p1.x, p1.y, radius, Math.PI*2, false);
	ctx.fill();
	ctx.closePath();
	ctx.beginPath();
	ctx.arc(p2.x, p2.y, radius, Math.PI*2, false);
	ctx.fill();
	ctx.closePath();
	ctx.beginPath();
	ctx.arc(p3.x, p3.y, radius, Math.PI*2, false);
	ctx.fill();
	ctx.closePath();
	ctx.beginPath();
	ctx.strokeStyle = "#8855AA";
	ctx.moveTo(p0.x, p0.y);
	ctx.lineTo(p1.x, p1.y);
	ctx.lineTo(p2.x, p2.y);
	ctx.lineTo(p3.x, p3.y);
	ctx.stroke();

	ctx.beginPath();
	ctx.strokeStyle = "green";
	ctx.moveTo(p0.x, p0.y);
 	//三阶贝塞尔曲线
 	function enterFrame() {
		t += step;
		var p = {};
        p.x = (1-t)**3 * p0.x + 3 * (1-t)**2 * t * p1.x + 3*(1-t)*t**2 * p2.x + t**3 * p3.x;
        p.y = (1-t)**3 * p0.y + 3 * (1-t)**2 * t * p1.y + 3*(1-t)*t**2 * p2.y + t**3 * p3.y;
        ctx.lineTo(p.x, p.y);
        ctx.stroke();
        if (t >= 1) {
        	return;
        }
        requestAnimationFrame(enterFrame);
    }
    enterFrame();
}

function getX(origin, x) {
	return origin.x + x;
}
function getY(origin, y) {
	return origin.y - y;
}
</script>
</html>
<html>
<head>
  <meta charset="UTF-8">
  <title>贝塞尔曲线</title>
   <style type="text/css">
   .box {
   	/*float: left;*/
   	background-color: black;
   }
   .ctrlBox {
   	float: left;
   	/*border: 1px solid #A0F;*/
   	width: 400;
   	/*height: 700;*/
   }
   </style>
</head>

<body>
<canvas id="canvas" class="box">
    canvas not supported, please use html5 browser.
</canvas>
<div class="ctrlBox">
	<p>p0:<span id="p0"></span><p>
	<p>p1:<span id="p1"></span><p>
	<p>p2:<span id="p2"></span><p>
	<p>p3:<span id="p3"></span><p>
	<p><span><input type="checkbox" name="cb" onclick="hideCbClick(event)" id="hideCb" />隐藏控制点</span></p>
</div>
</body>
<script>
window.onload = function() {
	var canvas = document.querySelector(".box");
	canvas.width = 800;
	canvas.height = 600;
	
	var ctx = canvas.getContext('2d');
	var t = 0, step = 0.01;
	//二阶贝塞尔曲线
	// function enterFrame() {
	// 	t += step;
	// 	var p = {};
 //        p.x = (1-t)**2 * p0.x + 2 * (1-t) * t * p1.x + t**2 * p2.x;
 //        p.y = (1-t)**2 * p0.y + 2 * (1-t) * t * p1.y + t**2 * p2.y;
 //        ctx.lineTo(p.x, p.y);
 //        ctx.stroke();
 //        if (t >= 1) {
 //        	return;
 //        }
 //        requestAnimationFrame(enterFrame);
 //    }
 //    enterFrame();
 	
    var p0 = DraggableCircle({x: 140, y: 370, dom: document.querySelector("#p0")});
	var p1 = DraggableCircle({x: 340, y: 120, dom: document.querySelector("#p1")});
	var p2 = DraggableCircle({x: 690, y: 520, dom: document.querySelector("#p2")});
	var p3 = DraggableCircle({x: 230, y: 440, dom: document.querySelector("#p3")});
    var draggables = [p0, p1, p2, p3];
    var selectedP;
    canvas.onmousedown = function(e) {
    	var evt = e || event;//获取事件对象
        var x = evt.offsetX;
        var y = evt.offsetY;
        // console.log("clicked: " + x + ", " + y);
        for (var i = 0; i < draggables.length; i++) {
        	var p = draggables[i];
        	if (p.hitTest(x, y)) {
        		selectedP = p;
        		p.selected = true;
        		break;
        	}
        }
    };
    canvas.onmousemove = function(e) {
    	if (selectedP) {
    		var evt = e || event;//获取事件对象
	        var x = evt.offsetX;
	        var y = evt.offsetY;
	        selectedP.move(x, y);
    	}
    };
    canvas.onmouseup = function(e) {
    	if (selectedP) {
    		selectedP.selected = false;
    		showCoordinates();
    		selectedP = null;
    	}
    };
    function showCoordinates() {
    	draggables.forEach(p => p.dom.innerText = "x: " + p.x + ", y: " + p.y);
    }
    showCoordinates();
    var hideCb = document.querySelector("#hideCb");
    function enterFrame() {
    	ctx.clearRect(0, 0, canvas.width, canvas.height);
    	if (!hideCb.checked) {
    		draggables.forEach(p => p.draw(ctx));
	    	for (var i = 0; i < draggables.length; i++) {
	    		var p = draggables[i];
	    		if (i === 0) {
	    			ctx.beginPath();
					ctx.strokeStyle = "#8855AA";
					ctx.moveTo(p.x, p.y);
	    		} else {
	    			ctx.lineTo(p.x, p.y);
	    		}
	    	}
	    	ctx.stroke();
    	}
    	
    	var stepLen = 0.01;
    	var tt = 0;
    	ctx.beginPath();
		ctx.strokeStyle = "green";
		ctx.moveTo(draggables[0].x, draggables[0].y);
    	while (tt <= 1) {
    		var p = {};
    		p.x = (1-tt)**3 * p0.x + 3 * (1-tt)**2 * tt * p1.x + 3*(1-tt)*tt**2 * p2.x + tt**3 * p3.x;
    		p.y = (1-tt)**3 * p0.y + 3 * (1-tt)**2 * tt * p1.y + 3*(1-tt)*tt**2 * p2.y + tt**3 * p3.y;
    		ctx.lineTo(p.x, p.y);
    		tt += stepLen;
    	}
    	ctx.stroke();
    	requestAnimationFrame(enterFrame);
    }
    enterFrame();
}

var DraggableCircle = function(point) {
	var that = {
		dom: point.dom,
		x: point.x,
		y: point.y,
		radius: 7,
		selected: false,
		hitTest: function(x2, y2) {
			var distance = (that.x-x2)**2 + (that.y-y2)**2;
			return distance <= that.radius**2;
		},
		move: function(x3, y3) {
			that.x = x3;
			that.y = y3;
		},
		draw: function(ctx) {
			ctx.beginPath();
			ctx.fillStyle = "#EE9611"; 
			if (that.selected) {
				ctx.fillStyle = "#2BD56F";
			}
			ctx.arc(that.x, that.y, that.radius, Math.PI*2, false);
			ctx.fill();
			ctx.closePath();
		}
	};
	return that;
};

function hideCbClick(event) {
	console.log("hide checkbox checked: " + event.target.checked);
}

</script>
</html>

用微积分对二阶贝塞尔曲线,进行定距等分
在这里插入图片描述
相应的 javascript 代码如下:

<html>
<head>
  <meta charset="UTF-8">
  <title>贝塞尔曲线</title>
   <style type="text/css">
   .box {
   	/*float: left;*/
   	background-color: black;
   }
   .ctrlBox {
   	float: left;
   	/*border: 1px solid #A0F;*/
   	width: 400;
   	/*height: 700;*/
   }
   </style>
</head>

<body>
<canvas id="canvas" class="box">
    canvas not supported, please use html5 browser.
</canvas>
<div class="ctrlBox">
	<p>p0 = {<span id="p0"></span>}<p>
	<p>p1 = {<span id="p1"></span>}<p>
	<p>p2 = {<span id="p2"></span>}<p>
	<p>p3 = {<span id="p3"></span>}<p>
	<p><span><input type="checkbox" name="cb" onclick="hideCbClick(event)" id="hideCb" />隐藏控制点</span></p>
</div>

</body>
<script>
var moreAccurate = true;    //false表示不考虑二阶导数的保号性,即忽略掉拐点
window.onload = function() {
	var canvas = document.querySelector(".box");
	canvas.width = 800;
	canvas.height = 600;
	
	var ctx = canvas.getContext('2d');
	var t = 0, step = 0.01;
 	
    var p0 = DraggableCircle({x: 140, y: 370, dom: document.querySelector("#p0")});
	var p1 = DraggableCircle({x: 340, y: 120, dom: document.querySelector("#p1")});
	var p2 = DraggableCircle({x: 690, y: 520, dom: document.querySelector("#p2")});
	var p3 = DraggableCircle({x: 230, y: 440, dom: document.querySelector("#p3")});
    var draggables = [p0, p1, p2];
    var selectedP, curveObj;
    var dividePoints = [];
    var dividePoints2 = [];
    var dividePoints3 = [];

    function divideCurve(curveObj, divideObj) {
    	var subLen = divideObj.len;
    	var total = curveObj.len;
    	if (divideObj.partNum) {
    		subLen = total / divideObj.partNum;
    	}
    	var curLen = subLen;
    	var arr = [];
    	while (curLen < total) {
    		arr.push(divideCurveBy(curveObj, curLen));
    		curLen += subLen;
    	}
    	return arr;
    }
    //从起始点算起的长度 subLen
    function divideCurveBy(curveObj, subLen) {
    	var f_x0, x0, f_x_1;	//f_x_1 表示 f(x)的一阶导数
    	var considerSpinodal = moreAccurate && curveObj.part1 > 0;
        var subLen2 = subLen;
    	if (curveObj.a > 0 ) {
    		x0 = curveObj.b;
    		// f_x0 = subLen;
    		f_x0 = curveObj.len - subLen;
    		considerSpinodal = considerSpinodal && curveObj.part1 > subLen;
            if (considerSpinodal) {
                subLen2 -= curveObj.len - subLen - curveObj.part2;//curveObj.part1 - subLen;
                f_x0 = subLen2;
                x0 = curveObj.t0_2;
            }
    	} else {
    		x0 = curveObj.a;
            f_x0 = -subLen;
    		considerSpinodal = considerSpinodal && curveObj.part1 < subLen;
            if (considerSpinodal) {
                subLen2 = subLen - curveObj.part1;
                f_x0 = -subLen2;
                x0 = curveObj.t0_2;
            }
    	}   
    	
    	f_x_1 = Math.sqrt(x0 * x0 + curveObj.C) * 2/curveObj.A;
    	var accuracy = 0.0001;
    	var x = -1;
    	while (true) {
    		x = x0 - f_x0 / f_x_1;
    		// console.log("divideCurveBy, x=" + x);
    		if (Math.abs(x - x0) <= accuracy) {
    			break;
    		}
    		x0 = x;
    		if (curveObj.a > 0 ) {
    			// f_x0 = curveObj.foo(x) - curveObj.yb + subLen2;
                if (considerSpinodal) {
                    f_x0 = curveObj.foo(x) - curveObj.y0 + subLen2;
                } else {
                    f_x0 = curveObj.foo(x) - curveObj.yb + curveObj.len - subLen;
                }
    		} else {
                if (considerSpinodal) {
                    f_x0 = curveObj.foo(x) - curveObj.y0 - subLen2;
                } else {
                    f_x0 = curveObj.foo(x) - curveObj.ya - subLen;
                }
    		}
    		f_x_1 = Math.sqrt(x * x + curveObj.C) * 2/curveObj.A;
    	}
    	var t = (x - curveObj.B/(2*curveObj.A)) / curveObj.A;
    	// console.log("divideCurveBy, t=" + t);
    	var p = {};
    	p.x = (1-t)**2 * p0.x + 2 * (1-t) * t * p1.x + t**2 * p2.x;
    	p.y = (1-t)**2 * p0.y + 2 * (1-t) * t * p1.y + t**2 * p2.y;
    	return p;
    }
    function getCurveLen() {
    	var a_x = p0.x - 2*p1.x + p2.x;
    	var b_x = p1.x - p0.x;
    	var a_y = p0.y - 2*p1.y + p2.y;
    	var b_y = p1.y - p0.y;
    	var A = Math.sqrt(a_x**2 + a_y**2);
    	var B = 2*(a_x*b_x + a_y*b_y);
    	var C = b_x**2 + b_y**2 - B*B/(4*A*A);
    	var xb = (2*A*A + B)/(2*A), xa = B/(2*A),len=0;
    	var T0 = -B/(2*A*A);	//原函数的二阶导数为零,原函数在x = T0处的点,是拐点
    	var part1 = -1, yb, ya;	//二阶导数小于零的长度
    	if (C >= 0) {
    		yb = curve2_1(C, xb) / A;
    		ya = curve2_1(C, xa) / A;
    		len = yb - ya;
    		if (isBetween(xa, xb, T0)) {
    			part1 = curve2_1(C, T0) / A - ya;
    		}
    	} else {
    		yb = curve2_2(C, xb) / A;
    		ya = curve2_2(C, xa) / A;
    		len = yb - ya;
    		if (isBetween(xa, xb, T0)) {
    			part1 = curve2_2(C, T0) / A - ya;
    		}
    	}
    	
    	console.log("len = " + len + ", xb="+xb+", xa="+xa+ ", yb="+yb+", ya="+ya+", C="+C+", B="+B+", A="+A+", T0="+T0+", part1="+part1);
    	//T == 0
    	var obj = {len:len, t0_2:T0, part1: part1, a: xa, b: xb, ya:ya, yb:yb, C:C, A:A, B:B,
    		foo:function(x) {
    			if (obj.C >= 0) {
    				return curve2_1(obj.C, x) / obj.A;
    			} else {
    				return curve2_2(obj.C, x) / obj.A;
    			}
    		}};
        obj.y0 = obj.foo(T0);
    	obj.part2 = obj.len - obj.part1;
    	return obj;
    }
    function isBetween(min, max, val) {
    	return min < val && val < max;
    }
    //以直带曲,第二种朴素的,适用范围更广,但效率较低的定距等分方法
    function divideCurve2(subLen) {
        var step = 0.001, t = 0;
        var p = {};
        var sum = 0, dd = 0;
        var start = {x: p0.x, y: p0.y};
        var divides = [];
        while (t <= 1) {
            p.x = (1-t)**2 * p0.x + 2 * (1-t) * t * p1.x + t**2 * p2.x;
            p.y = (1-t)**2 * p0.y + 2 * (1-t) * t * p1.y + t**2 * p2.y;
            var d = Math.sqrt((p.x - start.x)**2 + (p.y - start.y)**2);
            sum += d;
            dd += d;
            if (dd >= subLen) {
                dd = 0;
                divides.push({x: p.x, y: p.y});
            }
            t += step;
            start.x = p.x;
            start.y = p.y;
        }
        console.log("divideCurveBy2, total len: " + sum);
        return divides;
    }

    curveObj = getCurveLen();
    dividePoints = divideCurve(curveObj, {partNum: 10});
    dividePoints2 = divideCurve2(curveObj.len/10);
    // dividePoints = [divideCurveBy(curveObj, curveObj.len*0.3)];

    function curve2_1(C, x) {
    	return x * Math.sqrt(x*x + C) + C * Math.log(x + Math.sqrt(x*x + C));
    }
    function curve2_2(C, x) {
    	var c = -C;
    	return x * Math.sqrt(x*x - c) - c * Math.log(Math.abs(x + Math.sqrt(x*x - c) ) );
    }

    canvas.onmousedown = function(e) {
    	var evt = e || event;//获取事件对象
        var x = evt.offsetX;
        var y = evt.offsetY;
        // console.log("clicked: " + x + ", " + y);
        for (var i = 0; i < draggables.length; i++) {
        	var p = draggables[i];
        	if (p.hitTest(x, y)) {
        		selectedP = p;
        		p.selected = true;
        		break;
        	}
        }
    };
    canvas.onmousemove = function(e) {
    	if (selectedP) {
    		var evt = e || event;//获取事件对象
	        var x = evt.offsetX;
	        var y = evt.offsetY;
	        selectedP.move(x, y);
    	}
    };
    canvas.onmouseup = function(e) {
    	if (selectedP) {
    		selectedP.selected = false;
    		showCoordinates();
    		curveObj = getCurveLen();
    		dividePoints = divideCurve(curveObj, {
    			partNum: 10
    			// len: 30
    		});
            dividePoints2 = divideCurve2(curveObj.len/10);
    		selectedP = null;
    	}
    };
    function showCoordinates() {
    	draggables.forEach(p => p.dom.innerText = "x: " + p.x + ", y: " + p.y);
    }
    showCoordinates();
    var hideCb = document.querySelector("#hideCb");
    function enterFrame() {
    	ctx.clearRect(0, 0, canvas.width, canvas.height);
    	if (!hideCb.checked) {
    		draggables.forEach(p => p.draw(ctx));
	    	for (var i = 0; i < draggables.length; i++) {
	    		var p = draggables[i];
	    		if (i === 0) {
	    			ctx.beginPath();
					ctx.strokeStyle = "#8855AA";
					ctx.moveTo(p.x, p.y);
	    		} else {
	    			ctx.lineTo(p.x, p.y);
	    		}
	    	}
	    	ctx.stroke();
    	}
    	
    	var stepLen = 0.01;
    	var tt = 0;
    	ctx.beginPath();
		ctx.strokeStyle = "green";
		ctx.moveTo(draggables[0].x, draggables[0].y);
    	while (tt <= 1) {
    		var p = {};
    		// p.x = (1-tt)**3 * p0.x + 3 * (1-tt)**2 * tt * p1.x + 3*(1-tt)*tt**2 * p2.x + tt**3 * p3.x;
    		// p.y = (1-tt)**3 * p0.y + 3 * (1-tt)**2 * tt * p1.y + 3*(1-tt)*tt**2 * p2.y + tt**3 * p3.y;
    		p.x = (1-tt)**2 * p0.x + 2 * (1-tt) * tt * p1.x + tt**2 * p2.x;
    		p.y = (1-tt)**2 * p0.y + 2 * (1-tt) * tt * p1.y + tt**2 * p2.y;
    		ctx.lineTo(p.x, p.y);
    		tt += stepLen;
    	}
    	ctx.stroke();
    	var pp = {};
    	tt = curveObj.t0_2;
    	if ( tt >= 0 && tt <= 1) {
    		pp.x = (1-tt)**2 * p0.x + 2 * (1-tt) * tt * p1.x + tt**2 * p2.x;
	    	pp.y = (1-tt)**2 * p0.y + 2 * (1-tt) * tt * p1.y + tt**2 * p2.y;
	    	ctx.beginPath();
			ctx.fillStyle = "#4DB39E";
			ctx.arc(pp.x, pp.y, 4, Math.PI*2, false);
			ctx.fill();
			ctx.closePath();
    	}
    	dividePoints.forEach(dp => {
    		ctx.beginPath();
			ctx.fillStyle = "#EE11C2";
			ctx.arc(dp.x, dp.y, 4, Math.PI*2, false);
			ctx.fill();
			ctx.closePath();
    	});
        dividePoints2.forEach(dp => {
            ctx.beginPath();
            ctx.fillStyle = "#694ED3";
            ctx.arc(dp.x, dp.y, 4, Math.PI*2, false);
            ctx.fill();
            ctx.closePath();
        });
    	requestAnimationFrame(enterFrame);
    }
    enterFrame();
}

var DraggableCircle = function(point) {
	var that = {
		dom: point.dom,
		x: point.x,
		y: point.y,
		radius: 7,
		selected: false,
		hitTest: function(x2, y2) {
			var distance = (that.x-x2)**2 + (that.y-y2)**2;
			return distance <= that.radius**2;
		},
		move: function(x3, y3) {
			that.x = x3;
			that.y = y3;
		},
		draw: function(ctx) {
			ctx.beginPath();
			ctx.fillStyle = "#EE9611"; 
			if (that.selected) {
				ctx.fillStyle = "#2BD56F";
			}
			ctx.arc(that.x, that.y, that.radius, Math.PI*2, false);
			ctx.fill();
			ctx.closePath();
		}
	};
	return that;
};

function hideCbClick(event) {
	console.log("hide checkbox checked: " + event.target.checked);
}

</script>
</html>

moreAccurate = false;
divideCurveBy, x=10.07993902100614
curve.html:94 divideCurveBy, x=48.34371660200954
curve.html:94 divideCurveBy, x=48.084959281910635
curve.html:94 divideCurveBy, x=48.08493456734062

moreAccurate = true
divideCurveBy, x=48.37720923128524
curve.html:95 divideCurveBy, x=48.08496610650087
curve.html:95 divideCurveBy, x=48.084934567340724

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值