贝塞尔曲线及实践案例

article/2025/10/18 19:34:39

文章目录

  • 1. 前言
  • 2. 介绍
    • 2.1 一阶贝济埃曲线
    • 2.2 二阶贝塞尔曲线
    • 2.3 三阶贝塞尔曲线
  • 3. 一、二、三阶贝塞尔曲线实现
  • 4. 案例
  • 5. 后记

1. 前言

贝塞尔曲线(Bézier curve),又称贝兹曲线或贝济埃曲线,是应用于二维图形应用程序的数学曲线。一般的矢量图形软件通过它来精确画出曲线,贝兹曲线由线段与节点组成,节点是可拖动的支点,线段像可伸缩的皮筋,我们在绘图工具上看到的钢笔工具就是来做这种矢量曲线的。

2. 介绍

2.1 一阶贝济埃曲线

一阶贝济埃曲线的公式如下:

 B(t)=(1-t)P_0+tP_1,t属于0-1

P0为起始点,P1为终点,t 表示当前时间,B(t)表示公式的结果值。其实也就是一条从P0到P1的直线上,匀速运动的点值。

2.2 二阶贝塞尔曲线

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x1sAavqD-1647325585493)(images/screenshot_1647310332528.png)]

在这里插入图片描述

这条曲线的构成也就是每个t时刻,Q0和Q1的所属的直线的的t时刻的距离的点,这里也就是B。不妨将上面这个图简单标注下:
在这里插入图片描述

也就是在从P0到P1,进行匀速运动,在t=0.25的时刻走到Q0,类似的,从P1到P2经过匀速运动,在t=0.25的时刻走到Q1,对于Q0到Q1,经过匀速运动,在t=0.25的时刻走到B。而B也就是二阶贝塞尔曲线上的点。

2.3 三阶贝塞尔曲线

在这里插入图片描述

也就是说此时有两个控制点,对应着也就是三根连着的线段,类似的我们可以得到最终的t点:
在这里插入图片描述

那么,根据上面的规则,我们可以自己来实现一下贝赛尔曲线的计算方式,并将曲线绘制出来。

3. 一、二、三阶贝塞尔曲线实现

定义为:

class Point(var x: Float, var y: Float){
}/*** 得到贝赛尔曲线上的点集* @param points 起始、控制和终止点坐标* @param number 需要计算的贝赛尔曲线上的点的个数* @return 返回路径*/
private fun getBezierPointsPath(points: Array<Point>, number: Int): Path{val path = Path()for (time in 0 until number){val t = time * 1f / numberval point = calcPoint(points, t)if(time == 0){path.moveTo(point.x, point.y)} else {path.lineTo(point.x, point.y)}Log.e("TAG", "getBezierPointsPath: ${point.x} , ${point.y}", )}return path
}/*** 计算在t时刻上,位于贝赛尔曲线上的点的坐标* @param points 点的集合* @param t 时刻,属于0-1* @return 点坐标 Point*/
private fun calcPoint(points: Array<Point>, t: Float): Point{// 分别求任意两个点之间的在t时刻运动的距离// 任意两点,按照顺序分别为始和终var index = 0var len = points.size - 1while (index < len){points[index].x = getValueByTime(points[index].x, points[index + 1].x, t)points[index].y = getValueByTime(points[index].y, points[index + 1].y, t)index++if(index == len){index = 0len--}}return points[0]
}/*** 定义匀速运动的计算坐标* @param start 开始的位置* @param end 结束的位置* @param time 运动的时间,范围0-1* @return time时刻的运动位置*/
private fun getValueByTime(start: Float, end: Float, time: Float): Float{return start + (end - start) * time
}

然后使用:

// 绘图方法
override fun onDraw(canvas: Canvas?) {super.onDraw(canvas)canvas?.apply {val points = arrayOf(Point(200f, 400f), Point(100f, 20f), Point(500f, 20f), Point(800f, 400f))val numberOfPoint = 100mPath = getBezierPointsPath(points, numberOfPoint)drawPath(mPath, mPaint)}
}

在这里插入图片描述

很明显,这里细粒度不够。可以把numberOfPoint 设置的更大些。当设置为1000的时候:
在这里插入图片描述

当然这里可以使用arrayOf的时候添加更多的点,以做到更加高阶的贝塞尔曲线,比如简单修改一下:

val points = arrayOf(Point(200f, 400f),Point(100f, 20f),Point(500f, 20f),Point(800f, 400f),Point(1000f, 20f)
)

也就是对应三个控制点,对应四阶本塞尔曲线,对应效果:
在这里插入图片描述
当然,在系统中其实也提供了一、二、三阶的贝赛尔曲线的API,所以通常直接调用即可。对应的如下:

  • mPath.lineTo:进行直线绘制 ;
  • mPath.quadTo(x1, y1, x2, y2) :生成二次贝塞尔曲线,(x1,y1) 为控制点,(x2,y2)为结束点 ;
  • mPath.cubicTo(x1, y1, x2, y2, x3, y3):生成三次贝塞尔曲线, (x1,y1) 为控制点,(x2,y2)为控制点,(x3,y3) 为结束点;

4. 案例

/*** 学习波浪效果,其实也就是移动类似于正弦的连续图像,带来的视觉效果* @author 梦否* 2022年3月15日*/
class WaterRippleView : View {constructor(context: Context?) : super(context) {init()}constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {init()}constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context,attrs,defStyleAttr) {init()}private lateinit var mPath: Pathprivate lateinit var mPaint: Paintprivate lateinit var points1: Array<MyPoint>private lateinit var points2: Array<MyPoint>class MyPoint(var x: Float, var y: Float)/*** 初始化方法*/private fun init() {mPath = Path()mPaint = Paint()mPaint.isDither = truemPaint.isAntiAlias = truemPaint.strokeWidth = 5fmPaint.color = Color.GRAYmPaint.style = Paint.Style.FILLval viewWidth = resources.displayMetrics.widthPixelspoints1 = arrayOf(MyPoint(0f * viewWidth, 200f),MyPoint(.33f * viewWidth, 20f),MyPoint(.66f * viewWidth, 360f),MyPoint(1f * viewWidth, 200f))points2 = arrayOf(MyPoint(-1f * viewWidth, 200f),MyPoint(-.66f * viewWidth, 20f),MyPoint(-.33f * viewWidth, 360f),MyPoint(0f * viewWidth, 200f),)// 三阶贝塞尔曲线,传入0,也就是初始时刻updatePathByDistance(0f)}override fun onDraw(canvas: Canvas?) {super.onDraw(canvas)canvas?.apply {drawPath(mPath, mPaint)}}/*** 根据距离来进行更新在贝赛尔曲线中的点的坐标值* @param distance 传入的距离*/private fun updatePathByDistance(distance: Float) {// 重置mPath.reset()// 设置mPath.moveTo(points2[0].x, points2[0].y)mPath.cubicTo(points2[1].x + distance,points2[1].y,points2[2].x + distance,points2[2].y,points2[3].x + distance,points2[3].y)mPath.cubicTo(points1[1].x + distance,points1[1].y,points1[2].x + distance,points1[2].y,points1[3].x + distance,points1[3].y)val y = resources.displayMetrics.heightPixelsmPath.lineTo(points1[3].x, y.toFloat())mPath.lineTo(points2[0].x + distance,  y.toFloat())mPath.lineTo(points2[0].x + distance, points2[0].y)}/*** 一直移动绘制的两个类似于正弦函数的路径*/var startedMove = falseprivate fun startMove() {startedMove = trueval animator = ValueAnimator.ofFloat(0f, resources.displayMetrics.widthPixels.toFloat())animator.duration = 800// 线性插值器,使之匀速运动animator.interpolator = LinearInterpolator()// 循环animator.repeatCount = ValueAnimator.INFINITEanimator.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener {override fun onAnimationUpdate(animation: ValueAnimator?) {val value = animator.getAnimatedValue()updatePathByDistance(value as Float)// 重绘invalidate()}})animator.start()}override fun onTouchEvent(event: MotionEvent?): Boolean {super.onTouchEvent(event)var flag = falsewhen (event?.action) {MotionEvent.ACTION_DOWN -> {flag = trueif(!startedMove) startMove()}MotionEvent.ACTION_MOVE,MotionEvent.ACTION_UP -> {flag = false}}return flag}override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {super.onMeasure(widthMeasureSpec, heightMeasureSpec)val minHeight = dp2px(300)val minWidth = dp2px(500)val widthSize = getMeasureSize(widthMeasureSpec, minWidth.toInt())val heightSize = getMeasureSize(heightMeasureSpec, minHeight.toInt())setMeasuredDimension(widthSize, heightSize)}/*** 计算高度和宽度*/private fun getMeasureSize(Spec: Int, minValue: Int): Int {var result = 0// 获取模式val mode = MeasureSpec.getMode(Spec)val size = MeasureSpec.getSize(Spec)// 判断一下when (mode) {MeasureSpec.AT_MOST -> {result = Math.min(size, minValue)}MeasureSpec.UNSPECIFIED -> {result = minValue}MeasureSpec.EXACTLY -> {result = size}}return result}/*** dp转换为px*/private fun dp2px(size: Int): Float {return resources.displayMetrics.density * size}
}

5. 后记

当然关于贝赛尔曲线的应用远不止如此。比如:Android开发之贝塞尔曲线进阶篇(仿直播送礼物,饿了么购物车动画),感兴趣的可以查阅原文。


http://chatgpt.dhexx.cn/article/xOGZij09.shtml

相关文章

贝塞尔曲线工具类

贝塞尔曲线工具类 先上一张效果图看效果 贝塞尔曲线 用于计算N阶贝塞尔曲线上的点&#xff0c;根据传入控制点的个数判定阶数N 贝塞尔曲线计算公式: 工具类源码 public class BezierUtils {/*** 获取二项式系数** param l 行(杨辉三角)* param c 列(杨辉三角)* return 系…

贝塞尔曲线打断生成两个贝塞尔曲线

问题&#xff1a;如何将一个三阶贝塞尔曲线打断生成两个三阶贝塞尔曲线&#xff0c;生成的两条贝塞尔曲线与原来的贝塞尔曲线重合&#xff1f; 输入&#xff1a;一条贝塞尔曲线的四个控制点P1,C1,C2,P2,和一个打断点E(E在曲线上) 输出&#xff1a;两条贝塞尔曲线: P1,F,I,E E,J…

html5贝塞尔曲线,Canvas学习:贝塞尔曲线

在绘制圆和圆弧一节中,了解到在Canvas中可以使用arc()和arcTo()绘制制圆或弧线,但很多时候,仅这两个方法还不能满足我们实际的需求,特别是绘制复杂的曲线。不过值得庆幸的是,在Canvas中还提供了其他的方法可以帮助我们绘制复杂的曲线。那就是我们今天要说的贝塞尔曲线,在…

贝塞尔曲线(Bezier Curve)

有兴趣的建议看这篇&#xff0c;比较新&#xff1a;https://zhuanlan.zhihu.com/p/366678047 曲线和曲面 在生活中存在着各种各样光滑的曲线或曲面&#xff0c;例如汽车的表面&#xff0c;钢珠球等。 在建模的时候&#xff0c;我们通常使用很多的小三角形来逼近这样的曲面&am…

java贝塞尔曲线_贝塞尔曲线学习

贝塞尔曲线学习 1.贝塞尔曲线 以下公式中: B(t)为t时间下 点的坐标; P0为起点,Pn为终点,Pi为控制点 一阶贝塞尔曲线(线段): 一阶贝塞尔曲线公式 一阶贝塞尔曲线演示 意义:由 P0 至 P1 的连续点, 描述的一条线段 二阶贝塞尔曲线(抛物线): 二阶贝塞尔曲线公式 二阶贝塞尔曲…

贝塞尔曲线

一&#xff1a;简介 1962年&#xff0c;法国工程师贝塞尔发表&#xff0c;他运用贝塞尔曲线来为汽车的主体进行设计。 贝塞尔曲线是最基本的曲线&#xff0c;一般用在计算机 图形学和 图像处理。贝塞尔曲线可以用来创建平滑的曲线的道路、 弯曲的路径就像 祖玛游戏、 弯曲型的…

【Unity3d游戏开发】游戏中的贝塞尔曲线以及其在Unity中的实现

RT&#xff0c;马三最近在参与一款足球游戏的开发&#xff0c;其中涉及到足球的各种运动轨迹和路径&#xff0c;比如射门的轨迹&#xff0c;高吊球&#xff0c;香蕉球的轨迹。最早的版本中马三是使用物理引擎加力的方式实现的足球各种运动&#xff0c;后来的版本中使用了根据物…

Bezier Curve贝塞尔曲线概念

一、贝塞尔曲线的概念 对于两点之间的连线&#xff0c;我们可以用直线进行连接效果如下&#xff1a; 其中的每一个F点都在AB连接的线段上。 这就是一阶贝塞尔曲线。 如果我们加入一个控制点C&#xff0c;那么做图如下&#xff1a; 1、连接AC,BC&#xff1b; 2、在AC,B…

贝塞尔(贝兹尔)曲线介绍

2019独角兽企业重金招聘Python工程师标准>>> 贝塞尔(贝兹尔)曲线介绍 什么是贝塞尔曲线&#xff1f; “贝赛尔曲线”是由法国数学家Pierre Bzier所发明&#xff0c;由此为计算机矢量图形学奠定了基础。它的主要意义在于无论是直线或曲线都能在数学上予以描述。贝塞尔…

【ARM】-栈帧

ARM 栈帧 本系列均以 corter-A7(armv7-a) 为例 在 ARM 中&#xff0c;通常为满减栈&#xff08;Full Descending FD&#xff09;, 也就是说&#xff0c;堆栈指针指向堆栈内存中最后一个填充的位置&#xff0c;并且随着每个新数据项被压入堆栈而递减。 栈的本质 要理解栈的本…

JVM 栈和栈帧

tag: jvm,stack,stack frame,栈,栈帧 原文&#xff1a;JVM Stacks and Stack Frames 翻译&#xff1a;陈同学 欢迎访问陈同学博客原文&#xff0c;文章可读性更佳 前情提要 对于没有深度递归的函数来说&#xff0c;无需担心上篇文章中的算法。当知道正在处理数据集有限时&am…

C语言的函数栈帧

⭐️前面的话⭐️ &#x1f4d2;博客主页&#xff1a;未见花闻的博客主页 &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f4cc;本文由未见花闻原创&#xff0c;CSDN首发&#xff01; ✉️坚持和努力一定能换来诗与远方&#xff01; &…

【C语言】函数栈帧的创建和销毁(1)

前言&#xff1a; ❓ 在我们前期学习C语言时&#xff0c;你是否曾产生过很多困惑&#xff1f; &#x1f4ad; 比如&#xff1a;局部变量是怎么创建出来的&#xff1f;因为局部变量的值是随机值&#xff0c;我们建议将它初始化&#xff0c;那么为什么局部变量的值是随机值&am…

函数栈帧(详解版)

文章目录 前言1.浅谈C语言内存1.1 内存分配1.2 栈1.3 寄存器 2. 为main()函数开辟栈帧3.变量的初始化及函数调用4.ADD函数4.1 ADD函数的创建4.2 ADD函数使用与销毁 5.总结 前言 前期学习的时候&#xff0c;我们可能有很多困感? 比如: ●局部变量是怎么创建的?I为什么局部变量…

也谈栈和栈帧

&#xfeff;&#xfeff; 一个码农要是没遇见过coredump&#xff0c;那他就是神仙了。core file(coredump的转储文件)中保存的最重要内容之一&#xff0c;就是函数的call trace。还原这部分内容 (栈回溯) &#xff0c;并与原代码对应上&#xff0c;尽快找出程序崩溃的位置和…

(栈帧和函数调用一)栈帧,函数调用与栈的关系

&#xff08;栈帧和函数调用一&#xff09;栈帧&#xff0c;函数调用与栈的关系 一&#xff0c;栈帧的介绍二&#xff0c;函数调用与栈的关系三&#xff0c;汇编演示四&#xff0c;总结 在计算机科学中&#xff0c;栈是一个特殊的容器&#xff0c;用户可以将数据压入栈中&#…

理解栈帧和栈的运行原理

栈中的数据都是以栈帧&#xff08;Stack Frame&#xff09;的格式存在&#xff0c;栈帧是一个内存区块&#xff0c;是一个数据集&#xff0c;是一个有关方法 (Method) 和运行期数据的数据集&#xff0c;当一个方法A被调用时就产生了一个栈帧 F1&#xff0c;并被压入到栈中&…

函数栈帧的形成与释放

✅作者简介&#xff1a;嵌入式入坑者&#xff0c;与大家一起加油&#xff0c;希望文章能够帮助各位&#xff01;&#xff01;&#xff01;&#xff01; &#x1f4c3;个人主页&#xff1a;rivencode的个人主页 &#x1f525;系列专栏&#xff1a;玩转C语言 &#x1f4ac;推荐一…

【函数栈帧的创建和销毁】(超详细图解)

想必大家在学完C语言函数章节之后&#xff0c;是否有这样的困惑&#xff1a; 局部变量是怎么创建的 &#xff1f; 为什么局部变量的值是随机值 &#xff1f; 函数是怎么传参的&#xff1f;传参的顺序又是什么样的 &#xff1f; 形参和实参是什么关系 &#xff1f; 函数调用…

C语言函数栈帧详解

系列文章目录 前言 最近正在学习栈帧方面的知识&#xff0c;由于本人对汇编不太熟悉&#xff0c;对其中频繁出现的ESP寄存器和EBP寄存器一直没搞清楚&#xff0c;在查找资料后&#xff0c;在此进行整理&#xff0c;方便以后温故知新。 一、预备知识 要清楚理解栈帧的概念&…