贝塞尔曲线

贝塞尔曲线有着很多特殊的性质, 在图形设计和路径规划中应用都非常广泛, 我就是想在路径规划中贝塞尔曲线完全由其控制点决定其形状, n个控制点对应着n-1阶的贝塞尔曲线,并且可以通过递归的方式来绘制.

画重点了啊: 递归 :2点确定一个点(随着t变化),3点确定2个点,4点确定3个点,5点确定4个点,无限延伸,就像二进制可以延伸出我们美丽计算机世界一样。

 

一阶曲线:

对于一阶贝塞尔曲线为我们可以看到是一条直线,通过几何知识,很容易根据t的值得出线段上那个点的坐标:

一阶曲线就是很好理解, 就是根据t来的线性插值. P0表示的是一个向量 [x ,y], 其中x和y是分别按照这个公式来计算的.

二阶贝塞尔:


既然重点是递归, 那么二阶贝塞尔必然和一阶有关系.

在平面内任选 3 个不共线的点,依次用线段连接。在第一条线段上任选一个点 D。计算该点到线段起点的距离 AD,与该线段总长 AB 的比例。
根据上一步得到的比例,从第二条线段上找出对应的点 E,使得 AD:AB = BE:BC。

这时候DE又是一条直线了, 就可以按照一阶的贝塞尔方程来进行线性插值了, t= AD:AE
这时候就可以推出公式了.

推公式的主图
对应着上图绿色线段的左端点
对应着上图绿色线段的右端点
对应着绿色线段的一阶贝塞尔曲线(线性插值)整理一下公式, 得到二阶贝塞尔公式

 

三阶贝塞尔曲线:

二阶的贝塞尔通过在控制点之间再采点的方式实现降阶, 每一次选点都是一次的降阶.
四个点对应是三次的贝塞尔曲线. 分别在 AB BC CD 之间采EFG点, EFG三个点对应着二阶贝塞尔, 在EF FG之间采集HI点来降阶为一阶贝塞尔曲线.


高阶贝塞尔曲线:
高阶的贝塞尔可以通过不停的递归直到一阶

贝塞尔曲线 公式
可以通过递归的方式来理解贝塞尔曲线, 但是还是给出公式才方便计算的.


仔细看可以发现, 贝塞尔的参数B是二项式(t+(1-t))^n = (1)^n的展开公式. 划重点了: 系数是二项式的展开. 后面的很多的贝塞尔曲线的性质都可以用这个来解释

贝塞尔曲线的导数

变化一下贝塞尔公式:

 
 
和上文中的公式相同, 但是有一些字母的替换, 表达习惯不同

控制点是独立的, 因此求导是直接对u就行求导, 就是仅仅对参数项B进行求导.

 

定义: Q0=n*(P1-P0), Q0=n*(P2-P1), Q0=n*(P3-P2),...Qn-1=n*(Pn-Pn-1), . 如果我们把Q当做一组新的控制点, 那么原贝塞尔的导数可以写成如下:

导数还是贝塞尔曲线, 只不过是控制点是原来控制点的组合而已.


 

以下是一个三阶贝塞尔曲线golang例子:

 

package main

import(
  "fmt"
  "image"
  "image/color"
  "image/png"
  "log"
  "os"
)

// Putpixel describes a function expected to draw a point on a bitmap at (x, y) coordinates.
type Putpixel func(x, y int)

func drawline(x0, y0, x3, y3 int, brush Putpixel) {
  x1 := 500
  y1 := 500

  x2 := 0
  y2 := 250

  for i := 0; i < 1000; i++ {
    t := float32(i) / 1000.0
    x := int((1.0 - t) * (1.0 - t) * (1.0 - t) * float32(x0) + 3.0 * t * (1 - t) * (1 - t) * float32(x1) + 3.0 * t * t * (1 - t) * float32(x2) + t * t * t * float32(x3))
    y := int((1.0 - t) * (1.0 - t) * (1.0 - t) * float32(y0) + 3.0 * t * (1 - t) * (1 - t) * float32(y1) + 3.0 * t * t * (1 - t) * float32(y2) + t * t * t * float32(y3))
    brush(x,y)
  }
}

 

func main() {
  dx := 500
  dy := 500

 

  img := image.NewNRGBA(image.Rect(0, 0, dx, dy))
  drawline(350, 250, 500, 250, func(x, y int) {
    img.Set(x, y, color.Black)
  })

  // 左右都画一条竖线
  for i := 0; i < dy; i++ {
    img.Set(0, i, color.Black)
    img.Set(dx - 1, i, color.Black)
  }

  imgcounter := 250
  imgfile, _ := os.Create(fmt.Sprintf("%03d.png", imgcounter))
  defer imgfile.Close()

  // 以PNG格式保存文件
  err := png.Encode(imgfile, img)
  if err != nil{
    log.Fatal(err)
  }
}

posted @ 2020-06-09 20:13  沙漠中的雨滴  阅读(995)  评论(0编辑  收藏  举报