加入新的文章
This commit is contained in:
@@ -41,7 +41,14 @@ module.exports = [
|
||||
"2025/02/SegmentCircle.md",
|
||||
"2025/02/Ellipse.md",
|
||||
"2025/02/Fibonacci.md",
|
||||
"2025/03/BezierLine01.md"
|
||||
{
|
||||
text: '匀速贝塞尔曲线运动的实现',
|
||||
collapsible: true,
|
||||
children: [
|
||||
{ text: "Part1", link: "2025/03/BezierLine01.md" },
|
||||
{ text: "Part2", link: "2025/03/BezierLine02.md" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<html>
|
||||
<head>
|
||||
<script src="https://cdn.jsdelivr.net/npm/p5@1.4.2/lib/p5.js"></script>
|
||||
<script src="/js/bezier_cubic.js"></script>
|
||||
<script src="/js/bezier_base.js"></script>
|
||||
</head>
|
||||
<body style="margin:0px; padding:0px; overflow: hidden">
|
||||
<script>
|
||||
|
||||
var lineSegments;
|
||||
var cubicBezierLine;
|
||||
var P0={x:10.0, y:10.0}, P1={x:100.0, y:160.0}, P2={x:350.0, y:200.0}, P3={x:300.0, y:80.0};
|
||||
|
||||
function setup() {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
let uniformSpeed = searchParams.get('uniformSpeed')!=0;
|
||||
|
||||
canvas = createCanvas(windowWidth, windowHeight);
|
||||
|
||||
lineSegments = new LineSegments();
|
||||
lineSegments.addPoint(P0.x, P0.y);
|
||||
lineSegments.addPoint(P1.x, P1.y);
|
||||
lineSegments.addPoint(P2.x, P2.y);
|
||||
lineSegments.addPoint(P3.x, P3.y);
|
||||
|
||||
cubicBezierLine = new CubicBezierLine(P0, P1, P2, P3, 20, uniformSpeed)
|
||||
}
|
||||
|
||||
function draw() {
|
||||
clear();
|
||||
background('white');
|
||||
noStroke();
|
||||
|
||||
lineSegments.draw();
|
||||
cubicBezierLine.draw()
|
||||
}
|
||||
|
||||
function mousePressed(){
|
||||
lineSegments.handleMousePressed();
|
||||
}
|
||||
|
||||
function mouseDragged(){
|
||||
lineSegments.handleMouseDragged();
|
||||
}
|
||||
|
||||
function mouseReleased(){
|
||||
lineSegments.handleMouseReleased();
|
||||
cubicBezierLine.updatePoints(lineSegments.points[0], lineSegments.points[1], lineSegments.points[2], lineSegments.points[3]);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 211 KiB |
@@ -0,0 +1,116 @@
|
||||
class CubicBezierLine {
|
||||
constructor(_p0, _p1, _p2, _p3, _step, _uniformSpeedMode) {
|
||||
this.step=_step;
|
||||
this.points=[];
|
||||
this.uniformSpeedMode=_uniformSpeedMode;
|
||||
this.updatePoints(_p0, _p1, _p2, _p3);
|
||||
}
|
||||
|
||||
position(t) {
|
||||
let it = 1-t;
|
||||
let x = it*it*it*this.p0.x + 3*it*it*t*this.p1.x +
|
||||
3*it*t*t*this.p2.x + t*t*t*this.p3.x;
|
||||
let y =it*it*it*this.p0.y + 3*it*it*t*this.p1.y +
|
||||
3*it*t*t*this.p2.y + t*t*t*this.p3.y;
|
||||
return {x:x, y:y}
|
||||
}
|
||||
|
||||
speed(t) {
|
||||
let it = 1-t;
|
||||
let sx = -3 * this.p0.x*it*it + 3*this.p1.x*it*it -
|
||||
6*this.p1.x*it*t + 6*this.p2.x*it*t - 3*this.p2.x*t*t + 3*this.p3.x*t*t;
|
||||
let sy = -3 * this.p0.y*it*it + 3*this.p1.y*it*it -
|
||||
6*this.p1.y*it*t + 6*this.p2.y*it*t - 3*this.p2.y*t*t + 3*this.p3.y*t*t;
|
||||
return Math.sqrt(sx*sx+sy*sy);
|
||||
}
|
||||
|
||||
length(t){
|
||||
//使用simpson算法的分割数
|
||||
const TOTAL_SIMPSON_STEP = 1000;
|
||||
//分割份数
|
||||
let stepCounts = Math.floor(TOTAL_SIMPSON_STEP*t);
|
||||
if(stepCounts & 1) stepCounts++; //偶数
|
||||
if(stepCounts==0) return 0.0;
|
||||
|
||||
let halfCounts = stepCounts/2;
|
||||
let sum1=0.0, sum2=0.0;
|
||||
let dStep = t/stepCounts;
|
||||
|
||||
for(var i=0; i<halfCounts; i++) {
|
||||
sum1 += this.speed((2*i+1)*dStep);
|
||||
}
|
||||
|
||||
for(var i=1; i<halfCounts; i++) {
|
||||
sum2 += this.speed((2*i)*dStep);
|
||||
}
|
||||
|
||||
return (this.speed(0.0) + this.speed(1.0)+2*sum2+4*sum1)*dStep/3.0;
|
||||
}
|
||||
|
||||
length_from_table(t) {
|
||||
//通过查表获取t对应的曲线长度
|
||||
if(t<=0) return 0;
|
||||
if(t>=1.0) return this.length_table[this.length_table.length-1];
|
||||
|
||||
let step=t*this.step;
|
||||
let low_bounder = Math.floor(step);
|
||||
let lerp = step-low_bounder;
|
||||
|
||||
return this.length_table[low_bounder]*(1-lerp)+this.length_table[low_bounder+1]*lerp;
|
||||
}
|
||||
|
||||
//根据t推导出匀速运动自变量t'的方程(使用牛顿切线法)
|
||||
invertLength(t, target_length) {
|
||||
let t1 = t, t2;
|
||||
do {
|
||||
t2 = t1 - (this.length_from_table(t1)-target_length)/this.speed(t1);
|
||||
if(abs(t1-t2)<0.00001) break;
|
||||
t1=t2;
|
||||
}while(true);
|
||||
return t2;
|
||||
}
|
||||
|
||||
updatePoints(_p0, _p1, _p2, _p3) {
|
||||
this.p0=_p0;
|
||||
this.p1=_p1;
|
||||
this.p2=_p2;
|
||||
this.p3=_p3;
|
||||
|
||||
let ax = this.p0.x - 2 * this.p1.x + this.p2.x;
|
||||
let ay = this.p0.y - 2 * this.p1.y + this.p2.y;
|
||||
let bx = 2 * this.p1.x - 2 * this.p0.x;
|
||||
let by = 2 * this.p1.y - 2 * this.p0.y;
|
||||
|
||||
this.A = 4 * (ax * ax + ay *ay);
|
||||
this.B = 4 * (ax * bx + ay *by);
|
||||
this.C = bx * bx + by * by;
|
||||
|
||||
this.points.length = 0;
|
||||
let totalLength = this.length(1);
|
||||
|
||||
//构建一个length table
|
||||
this.length_table=[];
|
||||
for (let index = 0; index <= this.step; index++) {
|
||||
let t = index / this.step;
|
||||
this.length_table.push(this.length(t));
|
||||
}
|
||||
|
||||
for (let index = 1; index < this.step; index++) {
|
||||
let t = index / this.step;
|
||||
let target_length=totalLength*t;
|
||||
|
||||
if(this.uniformSpeedMode) {
|
||||
t = this.invertLength(t, target_length);
|
||||
}
|
||||
|
||||
this.points.push(this.position(t));
|
||||
}
|
||||
}
|
||||
|
||||
draw() {
|
||||
this.points.forEach(pt => {
|
||||
fill('green');
|
||||
ellipse(pt.x, pt.y, 5, 5);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
title: "匀速贝塞尔曲线运动的实现(二)"
|
||||
tags: 程序 算法
|
||||
---
|
||||
# 匀速贝塞尔曲线运动的实现(二)
|
||||
|
||||
实际工程应用中最为常见的是三次贝塞尔曲线,也就是下面这种用4个控制点生成的曲线
|
||||
|
||||

|
||||
|
||||
三次贝塞尔曲线的一个方便之处在于可以将相邻的两个控制点之间的连线视作控制点的“切线”,进而便于使用者编辑,因此在很多软件中的贝塞尔曲线编辑器都是使用的三次贝塞尔曲线。比如下面这段曲线,其实就是由几段三次贝塞尔曲线组成的。
|
||||
|
||||

|
||||
|
||||
三次贝塞尔曲线的公式为:
|
||||
$$
|
||||
\boldsymbol{B}(t)=(1-t)^3 \boldsymbol{P_0}+3t(1-t)^2 \boldsymbol{P_1}+3(1-t)t^2 \boldsymbol{P_2}+t^3\boldsymbol{P_3}, t\in[0, 1]
|
||||
$$
|
||||
虽然仍然可以按照相同的思路去实现匀速运动,但是由于三次贝塞尔曲线的长度计算已经非常复杂,根本无法通过对速度进行积分得到解析解,更别说通过反函数去求解匀速需要的自变量了。 因此在实际计算中,一般也只能通过提前建立一个曲线的长度查询表辅助运算。 这个长度查询表可以利用一般的数值积分的方式建立,比如[辛普森积分法](https://en.wikipedia.org/wiki/Simpson%27s_rule)
|
||||
下面是一个JavaScript实现的互动范例,代码可以直接查看这个<a href="/html/bezier02.html" target="_blank">页面</a>的<a href="/js/bezier_cubic.js" target="_blank">JS源码</a>
|
||||
|
||||
<iframe width="100%" height="270" frameborder=0 src="/html/bezier02.html?uniformSpeed=1"></iframe>
|
||||
|
||||
+2
-1
@@ -14,4 +14,5 @@
|
||||
* [如何计算线段和圆的交点](/blog/2025/02/SegmentCircle.md)
|
||||
* [一道数学趣题](/blog/2025/02/Ellipse.md)
|
||||
* [斐波那契数列和1/89](/blog/2025/02/Fibonacci.md)
|
||||
* [匀速贝塞尔曲线运动实现](/blog/2025/03/BezierLine01.md)
|
||||
* 匀速贝塞尔曲线运动实现
|
||||
* [Part1](/blog/2025/03/BezierLine01.md), [Part2](/blog/2025/03/BezierLine02.md)
|
||||
|
||||
+2
-1
@@ -13,7 +13,8 @@
|
||||
* [如何计算线段和圆的交点](/blog/2025/02/SegmentCircle.md)
|
||||
* [一道数学趣题](/blog/2025/02/Ellipse.md)
|
||||
* [斐波那契数列和1/89](/blog/2025/02/Fibonacci.md)
|
||||
* [匀速贝塞尔曲线运动实现](/blog/2025/03/BezierLine01.md)
|
||||
* 匀速贝塞尔曲线运动实现
|
||||
* [Part1](/blog/2025/03/BezierLine01.md), [Part2](/blog/2025/03/BezierLine02.md)
|
||||
|
||||
## 开源项目
|
||||
* [Turbolink](https://github.com/thejinchao/turbolink)
|
||||
|
||||
Reference in New Issue
Block a user