我的博客和笔记我的博客和笔记
首页
文章
  • TurboLink
  • TinyEncrypt
  • UnrealStyleGuide
  • AxTrace
  • Cyclone
  • 数学相关
  • 图形学
  • 密码学
  • 编程语言
关于
GitHub
首页
文章
  • TurboLink
  • TinyEncrypt
  • UnrealStyleGuide
  • AxTrace
  • Cyclone
  • 数学相关
  • 图形学
  • 密码学
  • 编程语言
关于
GitHub
  • 我的文章

    • 从抛币协议到智能合约

      • Part1
      • Part2
    • JPEG算法解密

      • Part1
      • Part2
      • Part3
      • Part4
      • Part5
      • Github
    • SPH算法简介

      • Part1
      • Part2
      • Part3
      • Part4
      • Github
    • 赌博中的数学:Martingle策略
    • 如何生成一个随机的圆形
    • 一个简单的DH密钥协商算法的实现
    • 如何计算线段和圆的交点
    • 一道数学趣题
    • 斐波那契数列和1/89
    • 匀速贝塞尔曲线运动的实现

      • Part1
      • Part2
  • 开源项目

    • TurboLink
    • TinyEncrypt
    • UnrealStyleGuide
    • AxTrace
    • Cyclone
  • 学习笔记

    • 数学相关

      • 常用数学符号
      • 群
      • 数论(一)
      • 数论(二)
      • 数论(三)
      • 概率
    • 密码学

      • RSA
      • 抛币协议
      • 智能扑克协议
    • 图形学

      • 数学基础

        • 矢量
        • 矩阵
        • 立体角
        • 几何变换(一)
        • 几何变换(二)
        • 法线变换
        • 摄像机变换
      • 光照模型

        • 传统光照模型
        • 光度学
        • 双向反射分布函数(BRDF)
        • 微平面理论(一)
        • 微平面理论(二)
        • 微平面理论(三)
        • 光照方程
      • 环境光渲染

        • 环境光渲染(一)
        • 环境光渲染(二)
    • 编程语言

      • JavaScript

        • 环境搭建
        • 基本语法
        • 函数
        • 对象和类

匀速贝塞尔曲线运动的实现(一)

贝塞尔曲线(Bézier curve)是一种常用的曲线,以最简单的二次贝塞尔曲线为例,通常以如下方式构建,给定二维平面上的固定点P0, P1, P2,用B(t)表示该条曲线

B(t)=(1−t)2P0+2t(1−t)P1+t2P2,t∈[0,1]

用一个动画来演示,可以更加清楚的表明这条曲线的构建过程

如果t变量本身是线性变化的话,这条贝塞尔曲线的生成过程是并不是匀速的,通常都是两头快中间慢。

可以看出中间的点较为密集,而两边则较为稀疏。 如何得到匀速的贝塞尔曲线运动呢?比如我们在某款游戏中设计了一条贝塞尔曲线的路径,如何实现NPC匀速在这条路径上运动?​  首先需要求得B(t)相对于t的速度公式s(t)

s(t)=Bx′(t)2+By′(t)2

为了简化公式,定义如下变量

a=P0−2P1+P2b=2P1−2P0A=4(ax2+ay2)B=4(axbx+ayby)C=bx2+by2

计算出s(t)可以表达为

s(t)=At2+Bt+C

根据这个公式,求得贝塞尔曲线的长度公式L(t)为

L(t)=∫0tAx2+Bx+Cdx=18A3/2(2A[(2At+B)At2+Bt+C−BC]+(B2−4AC)[ln(B+2AC)−ln(B+2At+2AAt2+Bt+C)])

特别当t=1.0时,L(1.0)就是这条曲线的总长度。
设u是能够使L(u)实现匀速运动的自变量,那么此时曲线长度应该满足随着时间t线性增长,也就是

(1)L(u)=L(1.0)t

也就是u=L−1(L(1.0)t),由于L(t)函数非常复杂,直接求其逆函数的解析解几乎不可能,还好我们知道它的导数为s(t),在实际使用中,可以使用牛顿切线法获得u的数值解。
视u为未知数,根据公式1有以下方程

F(u)=L(u)−L(1.0)t=0

根据牛顿切线法,求解u的迭代公式为:

un+1=un−F(un)F′(un)=un−L(un)−L(1.0)ts(un)

由于t和u相差不大,可以设u0=t来开始求解,以下是修正后的匀速贝塞尔曲线

上面是使用javascript实现的互动曲线,核心代码如下

class QuadBezierLine {
	constructor(_p0, _p1, _p2, _step, _uniformSpeedMode) {
		this.step=_step;
		this.points=[];
		this.uniformSpeedMode=_uniformSpeedMode;
		this.updatePoints(_p0, _p1, _p2);
	}
	
	//Speed(t_) = Sqrt[A*t*t+B*t+C]
	speed(t) {
		return Math.sqrt(this.A * t * t + this.B * t + this.C);
	}
	
	//Length(t) = Integrate[Speed[t], t]
	//Length(t_) = ((2*Sqrt[A]*((2*A*t + B)*Sqrt[A*t^2 + B*t + C] - B*Sqrt[C]) +
	//	(B^2 - 4*A*C)*(Log[B + 2*Sqrt[A]*Sqrt[C]] - 
	//	Log[B + 2*A*t + 2*Sqrt[A]*Sqrt[A*t^2 + B*t + C]]))/(8*A^(3/2)))
	length(t) {
		let temp1 = Math.sqrt(this.C + t * (this.B + this.A * t));
		let temp2 = (2 * this.A * t + this.B) * temp1 - this.B * Math.sqrt(this.C);
		let temp3 = Math.log(this.B + 2 * Math.sqrt(this.A * this.C));
		let temp4 = Math.log(this.B + 2 * this.A * t + 2 * Math.sqrt(this.A) * temp1);
		let temp5 = 2 * Math.sqrt(this.A) * temp2;
		let temp6 = (this.B * this.B - 4 * this.A * this.C) * (temp3 - temp4);
		return (temp5 + temp6) / (8 * Math.pow(this.A, 1.5));
	}
	
	//u'=u-(Length(u)-len)/Speed(u)
	invertLength(u0, len) {
		let u1 = u0, u2;
		do {
			u2 = u1 - (this.length(u1) - len) / this.speed(u1);
			if (Math.abs(u1 - u2) < 0.000001)
				break;
			u1 = u2;
		} while (true);
		return u2;
	}
	
	updatePoints(_p0, _p1, _p2) {
		this.p0=_p0;
		this.p1=_p1;
		this.p2=_p2;
		
		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);
		
		for (let index = 1; index < this.step; index++) {  
			let t = index / this.step;
			
			if(this.uniformSpeedMode) {
				t = this.invertLength(t, totalLength*t);
			}
			
			let x = (1-t)*(1-t)*this.p0.x+2*(1-t)*t*this.p1.x+t*t*this.p2.x;
			let y = (1-t)*(1-t)*this.p0.y+2*(1-t)*t*this.p1.y+t*t*this.p2.y;
			
			this.points.push({x:x, y:y});
		}
	}
	
	draw() {
		this.points.forEach(pt => {
			fill('green');
			ellipse(pt.x, pt.y, 5, 5);
		});
	}
}
Next
Part2