`

[转]匀速贝塞尔曲线运动的实现

阅读更多
http://www.thecodeway.com/blog/?p=293
二次贝塞尔曲线通常以如下方式构建,给定二维平面上的固定点P0,P1,P2,用B(t)表示该条曲线





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

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

如何想要得到匀速的贝塞尔曲线运动呢?比如我们在某款游戏中设计了一条贝塞尔曲线的路径,如何实现玩家匀速在这条路径上运动呢?
思考这个算法颇费了一番脑筋,其间还得到数学牛人Charlesgao的帮助,非常感谢他(比较糗的是,我问问题的时候就把其中的一个公式搞错了,见笑了-_-!)。

首先需要求得B(t)相对于t的速度公式s(t)





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





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





其中A,B,C是根据P0,P1,P2计算出的常数





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





设t`就是能够使L实现匀速运动的自变量,那么显然L(t`)=L(1.0)*t,即





由于L(t)函数非常复杂,直接求逆函数的表达式几乎不可能,还好我们可以知道它的导数为s(t),在实际使用中,可以使用牛顿切线法求出近似解。其迭代算法可以表达为





我写了一个测试程序用于验证该算法,运算结果如下,可以看到,这条曲线已经是以匀速方式生成的了 ^_^



完整的示例源代码附载下面:
下载: Bezeier.cpp (4.2KB)
#include <stdio.h>
#include <math.h>
#include <windows.h>

//三个控制点
POINT P0={50,50},P1={500,600},P2={800,200};

int ax = P0.x-2*P1.x+P2.x;
int ay = P0.y-2*P1.y+P2.y;
int bx = 2*P1.x-2*P0.x;
int by = 2*P1.y-2*P0.y;

double A = 4*(ax*ax+ay*ay);
double B = 4*(ax*bx+ay*by);
double C = bx*bx+by*by;

//曲线总长度
double total_length = 0.0;

//曲线分割的份数
const int STEP = 70;

//用于保存绘制点数据的数组
POINT pixels[STEP];

//-------------------------------------------------------------------------------------
//速度函数
/*
s(t_) = Sqrt[A*t*t+B*t+C]
*/
double s(double t)
{
	return sqrt(A*t*t+B*t+C);
}

//-------------------------------------------------------------------------------------
//长度函数
/*

L(t) = Integrate[s[t], t]

L(t_) = ((2*Sqrt[A]*(2*A*t*Sqrt[C + t*(B + A*t)] + B*(-Sqrt[C] + Sqrt[C + t*(B + A*t)])) + 
			(B^2 - 4*A*C) (Log[B + 2*Sqrt[A]*Sqrt[C]] - Log[B + 2*A*t + 2 Sqrt[A]*Sqrt[C + t*(B + A*t)]]))
				/(8* A^(3/2)));
*/
double L(double t)
{
	double temp1 = sqrt(C+t*(B+A*t));
	double temp2 = (2*A*t*temp1+B*(temp1-sqrt(C)));
	double temp3 = log(B+2*sqrt(A)*sqrt(C));
	double temp4 = log(B+2*A*t+2*sqrt(A)*temp1);
	double temp5 = 2*sqrt(A)*temp2;
	double temp6 = (B*B-4*A*C)*(temp3-temp4);
	
	return (temp5+temp6)/(8*pow(A,1.5));
}

//-------------------------------------------------------------------------------------
//长度函数反函数,使用牛顿切线法求解
/*
	X(n+1) = Xn - F(Xn)/F'(Xn)
*/
double InvertL(double t, double l)
{
	double t1=t, t2;
	
	do
	{
		t2 = t1 - (L(t1)-l)/s(t1);
		if(abs(t1-t2)<0.000001) break;
		t1=t2;
	}while(true);
	return t2;
}

//-------------------------------------------------------------------------------------
LRESULT CALLBACK _WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message) 
	{
	case WM_TIMER:
		{
			static nIndex = 0;
			if(nIndex>=0 && nIndex<=STEP)
			{
				double t = (double)nIndex/STEP;
				//如果按照线形增长,此时对应的曲线长度
				double l = t*total_length;
				//根据L函数的反函数,求得l对应的t值
				t = InvertL(t, l);

				//根据贝塞尔曲线函数,求得取得此时的x,y坐标
				double x = (1-t)*(1-t)*P0.x +2*(1-t)*t*P1.x + t*t*P2.x;
				double y = (1-t)*(1-t)*P0.y +2*(1-t)*t*P1.y + t*t*P2.y;

				//取整
				pixels[nIndex].x = (int)(x+0.5);
				pixels[nIndex].y = (int)(y+0.5);

				nIndex++;
				InvalidateRect(hWnd, 0, 0);
			}
			else
			{
				KillTimer(hWnd, 101);
			}
		}
		break;
	case WM_PAINT:
		{
			PAINTSTRUCT ps;
			HDC hdc = BeginPaint(hWnd, &ps);
			::MoveToEx(hdc, P0.x, P0.y, 0);
			LineTo(hdc, P1.x, P1.y);
			LineTo(hdc, P2.x, P2.y);

			for(int i=0; i<STEP; i++)
			{
				const POINT &pt = pixels[i];
				if(pt.x==0 && pt.y==0) break;

				::MoveToEx(hdc, pt.x-2, pt.y, 0);
				::LineTo(hdc, pt.x+2, pt.y);
				::MoveToEx(hdc, pt.x, pt.y-2, 0);
				::LineTo(hdc, pt.x, pt.y+2);
			}
			EndPaint(hWnd, &ps);
		}
		break;
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

//-------------------------------------------------------------------------------------
int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
{
	//注册窗口类
	WNDCLASSEX wcex;
	ZeroMemory(&wcex, sizeof(WNDCLASSEX));

	wcex.cbSize = sizeof(WNDCLASSEX); 
	wcex.style			= CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc	= (WNDPROC)_WndProc;
	wcex.hInstance		= hInstance;
	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground	= (HBRUSH)(COLOR_WINDOW+1);
	wcex.lpszClassName	= "BezierClass";
	RegisterClassEx(&wcex);

	//创建窗口
	HWND hWnd = CreateWindow("BezierClass", "BezierDemo", WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);

	//计算总长度
	total_length = L(1);

	//清空绘制点数据
	ZeroMemory(&pixels, sizeof(pixels));

	//设定定时刷新计时器
	SetTimer(hWnd, 101, 10, 0);

	//消息循环
	MSG msg;
	while(GetMessage(&msg, NULL, 0, 0)) 
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int) msg.wParam;
}


  • 大小: 1.1 KB
  • 大小: 815 Bytes
  • 大小: 1.5 KB
  • 大小: 4.5 KB
  • 大小: 4.3 KB
  • 大小: 935 Bytes
  • 大小: 703 Bytes
  • 大小: 657 Bytes
  • 大小: 1 KB
分享到:
评论

相关推荐

    贝塞尔曲线高阶匀速运动算法 HTML5/JS 实现

    HTML5/JS实现贝塞尔曲线运动的关键技术: 1. **canvas API**:HTML5中的元素提供了绘图功能,我们可以通过JavaScript来操作canvas的绘图上下文(`CanvasRenderingContext2D`)绘制贝塞尔曲线。 2. **路径方法**:...

    Android 属性动画使控件沿贝塞尔曲线移动

    本项目"Android 属性动画使控件沿贝塞尔曲线移动"就利用了这一特性,实现了让控件沿着自定义的贝塞尔曲线轨迹平滑移动,并且允许开发者自定义动画的时长和运动幅度,同时还支持对移动的控件添加点击事件。...

    BezierCurvePathCreater:用于创建贝塞尔曲线路径,可匀速运动 - 基于CocosCreator_2.2.2 - Used to create a Bezier curve path with uniform motion Based on CocosCreator_2.2.2

    匀速运动实现思路很简单,其实就是利用了微积分思想,把曲线分割成许多份,每一份此时就可以看作直线运动了! 需要注意的是,该项目分辨率是1920*1080的,在不同的项目中使用可能需要转换下坐标! 导出的json数据...

    基于CocosCreator-2.2.2,创建贝塞尔曲线路径,可匀速运动

    匀速运动实现思路很简单,其实就是利用了微积分思想,把曲线分割成许多份,每一份此时就可以看作直线运动了! 需要注意的是,该项目分辨率是1920*1080的,在不同的项目中使用可能需要转换下坐标!

    简易的运动曲线代码实现

    运动曲线的匀速、开始和结束慢速,中间加速、开始慢速,结尾突然停止、突然开始,结束时慢速、开始和结束时慢速、贝塞尔(贝兹)曲线、超出再缩回的弹性效果。仅供代码实现参考!

    Android模仿贝尔塞曲线路径动画demo

    贝塞尔曲线是一种在图形设计和计算机图形学中广泛使用的数学工具,它允许开发者精确地控制曲线的形状,从而实现流畅的动画效果。 贝塞尔曲线分为一阶、二阶、三阶乃至更高阶,其中二阶和三阶最为常见。在Android中...

    jQuery沿贝兹曲线和弧线运动特效特效代码

    本主题聚焦于“jQuery沿贝兹曲线和弧线运动特效”,这是一个利用jQuery实现的特殊动画插件,旨在让元素在网页上沿着复杂的路径,如贝塞尔曲线或圆弧进行平滑移动。 贝塞尔曲线(Bezier Curve)是一种在图形设计和...

    BezierEditor.zip

    匀速运动确保游戏对象沿着贝塞尔曲线移动时速度保持一致,即使曲线的曲率变化很大。这可以提供流畅的游戏体验,避免因为速度突然变化导致的不自然感。 此外,该编辑器还具有保存数据的功能,这意味着开发者可以将...

    (完整版)课次六Premiere关键帧与运动特效.ppt

    直线插值提供匀速过渡,而贝塞尔曲线插值则允许手动调整速度,产生更复杂的曲线运动。自动贝塞尔曲线和连续贝塞尔曲线则提供平滑过渡,但前者不可手动调节,后者则可手动调节两边关键帧的平滑度。固定插值则保持关键...

    PathInterpolator-路径插值器

    贝塞尔曲线由控制点决定,通常有四类:线性贝塞尔曲线(两个控制点相同),二次贝塞尔曲线(三个控制点),三次贝塞尔曲线(四个控制点),以及n次贝塞尔曲线。PathInterpolator主要使用的是三次贝塞尔曲线,因为它...

    JointTrajectorySCurve.rar_Scurve_S曲线 位置_s 曲线_s曲线 matlab_s速度曲线

    总的来说,这个项目提供了关于如何在MATLAB中实现S曲线运动规划的实例,这对于理解和应用机器人控制策略,特别是需要平滑运动的场合,如工业自动化、无人驾驶等领域,具有重要的学习价值。同时,了解并掌握S曲线的...

    css transition animation的使用(内含贝赛尔曲线详解)

    贝塞尔曲线在`transition-timing-function`中用于自定义非线性的时间函数,它可以更精确地控制动画的速度变化。贝塞尔曲线由控制点决定,这些点决定了曲线的形状,进而影响动画的速度分布。在CSS中,三阶贝塞尔曲线...

    模拟鼠标真人移动直线轨迹曲线轨迹

    例如,我们可以使用三次贝塞尔曲线来模拟平滑的曲线路径,它需要四个控制点:起点、两个控制点和终点。通过调整控制点的位置,可以改变曲线的形状和弯曲度。计算每个时间步长内鼠标的当前位置,然后应用到系统中。 ...

    步进电机S曲线.zip

    2. 生成速度时间曲线:基于S曲线的数学特性(如三次贝塞尔曲线)生成平滑的速度变化曲线。 3. 转换为脉冲频率:将速度值转换为对应的脉冲频率,然后发送给步进电机驱动器。 4. 实时控制:实时跟踪和调整脉冲频率以...

    JavaScript Canvas 非线性移动示例

    本示例将聚焦于“非线性移动”,这是一种在Canvas上实现物体以非匀速或非直线轨迹移动的技术。 非线性移动的概念主要涉及物理学中的运动学,它通常指的是物体沿着曲线路径移动或者速度随时间变化的情况。在Canvas中...

    windows编程GDI+绘图实现

    1. **线条和曲线绘制**:GDI+提供了多种绘制线条的方法,如直线(`DrawLine`)、曲线(`DrawCurve`)、贝塞尔曲线(`DrawBezier`)等。这些函数接受Pen对象作为参数,Pen定义了线条的颜色、宽度和样式。 2. **形状填充**...

    原生js弹性运动.zip

    可以使用贝塞尔曲线(Bezier curve)来控制速度变化,使得物体在接近目标时产生平滑的减速效果。 5. **属性变化过程的动画**:JavaScript可以用来改变HTML元素的各种属性,如位置、大小、颜色等,通过在每一帧更新...

    SJNWaterRippleAndRotate.zip

    水波纹效果模拟了水面上波纹扩散的视觉感受,可以使用CAShapeLayer配合CGPath或贝塞尔曲线来绘制动态变化的形状。关键在于计算波纹的形状、大小以及移动速度,这涉及到数学和图形学知识。开发者可以通过调整参数来...

    爱心的不规则上升动画效果

    通常,动画的运动轨迹可以是直线,但在本例中,爱心并不是简单地从底部匀速上升,而是根据用户的触屏位置开始,呈现出一种随性、自由的上升轨迹,这可能涉及到贝塞尔曲线(Bezier Curve)的应用。贝塞尔曲线是一种在...

    easing动画运动效果.zip

    这些函数可以基于数学公式,比如二次、三次或四次贝塞尔曲线,来计算每个时间点上元素的位置。例如,线性(easeInOut)缓动就是元素以恒定速度运动,而easeIn(加速)会让元素在开始时缓慢,然后逐渐加速,easeOut...

Global site tag (gtag.js) - Google Analytics