zoukankan      html  css  js  c++  java
  • 图形学_Bezier曲线

    Bezier曲线由n个控制点生成,举个例子:当n=2时,点$P_0$、$P_1$之间遵从计算:

    $P_0=(1-t)P_0+tP_1$

    而推广为n维时,有:

    $P^n_0=(1-t)P^{n-1}_0+tP^{n-1}_1$

    递推公式有:

    $P^k_i=(1-t)P^{k-1}_i+tP^{k-1}_{i+1}$

     要递推生成Bezier的理论知识如上。使用py动态(…课程要求)生成曲线需要掌握matplotlib库的键鼠响应事件。

    #fig.canvas.mpl_connect() 事件绑定规范用法
    import matplotlib.pyplot as plt
    
    def on_key_press(event):
        print(event.key)
    
    fig, ax = plt.subplots()
    fig.canvas.mpl_connect('key_press_event', on_key_press)
    plt.show()

    下面是实现过程:

    定义一个Bezier类,初始化函数中保存事件状态与响应,这里就用到了事件绑定:

        def __init__(self, line):
            self.line = line
            self.index_02 = None  # 保存拖动索引
            self.press = None  # 按下
            self.pick = None  # 选中
            self.motion = None  #拖动
            self.xs = list()  # x坐标
            self.ys = list()  # y坐标
            self.cidpress = line.figure.canvas.mpl_connect('button_press_event', self.on_press)  # 鼠标按下事件
            self.cidrelease = line.figure.canvas.mpl_connect('button_release_event', self.on_release)  # 鼠标放开事件
            self.cidmotion = line.figure.canvas.mpl_connect('motion_notify_event', self.on_motion)  # 鼠标拖动事件
            self.cidpick = line.figure.canvas.mpl_connect('pick_event', self.on_picker)  # 鼠标选中事件

    这里xs、ys为控制点的横、纵坐标列表。

    将self.press设定为1,即表示鼠标按下被调用;

        def on_press(self, event):  # 鼠标按下调用
            if event.inaxes != self.line.axes: return
            self.press = 1

    选中调用同理;

        def on_picker(self, event):  # 选中调用
            self.pick = 1

    鼠标拖动事件的行为就稍微有,复杂了:

        def on_motion(self, event):  # 鼠标拖动调用
            if event.inaxes != self.line.axes: return
            if self.press is None: return
            if self.pick is None: return
            if self.motion is None:  # 按下并选中
                self.motion = 1
                x = self.xs
                xdata = event.xdata
                ydata = event.ydata
                index_01 = 0
                for i in x:
                    if abs(i - xdata) < 0.02:  # 0.02 为点的半径
                        if abs(self.ys[index_01] - ydata) < 0.02: break
                    index_01 = index_01 + 1
                self.index_02 = index_01
            if self.index_02 is None: return
            self.xs[self.index_02] = event.xdata  # 鼠标的坐标覆盖选中的点的坐标
            self.ys[self.index_02] = event.ydata
            self.draw_01()

    一个点的按下与选中值都为1时,才可能被拖动;将self.motion设定为1后,在控制点列表中寻找被拖动的点的位置,然后更新该点的坐标。最后调用draw_01,重新构图。

    draw_01如下:

        def draw_01(self):  # 绘图函数
            self.line.clear()  # 不清除的话会保留原有的图
            self.line.axis([0, 1, 0, 1])  # x和y范围0到1
            self.bezier(self.xs, self.ys)  # Bezier曲线
            self.line.scatter(self.xs, self.ys, color='b', s=200, marker="o", picker=5)  # 画点
            self.line.plot(self.xs, self.ys, color='r')  # 画线
            self.line.figure.canvas.draw()  # 重构子图

    draw先清除了原本的图(不然生成的图会在原图上,生成多条曲线),使用更新过的xs、ys调用Bezier函数生成新的曲线;使用scatter作点;最后重构子图。

    Bezier函数如下:

        def bezier(self, *args):  # Bezier曲线公式转换,获取x和y
            n = len(args[0])  # 点的个数
            xarray, yarray = [], []
            x, y = [], []
            index = 0
            for t in np.linspace(0, 1):
                for i in range(1, n):
                    for j in range(0, n - i):
                        if i == 1:
                            xarray.insert(j, args[0][j] * (1 - t) + args[0][j + 1] * t)
                            yarray.insert(j, args[1][j] * (1 - t) + args[1][j + 1] * t)
                            continue
                        # i != 1时,通过上一次迭代的结果计算
                        xarray[j] = xarray[j] * (1 - t) + xarray[j + 1] * t
                        yarray[j] = yarray[j] * (1 - t) + yarray[j + 1] * t
                if n == 1:
                    x.insert(index, args[0][0])
                    y.insert(index, args[1][0])
                else:
                    x.insert(index, xarray[0])
                    y.insert(index, yarray[0])
                    xarray = []
                    yarray = []
                index = index + 1
            self.line.plot(x, y)

    这里算法的j的范围让我疑惑了一下,一开始写的是和i一样,…然后报错了;个人理解是一个点不会被自己影响过的点影响;将生成的点加入x、y数组后,放入plot中。

    最后是响应鼠标松开事件的函数:

        def on_release(self, event):  # 鼠标放开调用
            if event.inaxes != self.line.axes: return
            if self.pick == None:  # 如果不是选中点,那就添加点
                self.xs.append(event.xdata)
                self.ys.append(event.ydata)
            if self.pick == 1 and self.motion != 1:  # 如果是选中点,但不是拖动点,那就删去点
                x = self.xs
                xdata = event.xdata
                ydata = event.ydata
                index_01 = 0
                for i in x:
                    if abs(i - xdata) < 0.02:
                        if abs(self.ys[index_01] - ydata) < 0.02: break
                    index_01 = index_01 + 1
                self.xs.pop(index_01)
                self.ys.pop(index_01)
            self.draw_01()
            self.pick = None  # 所有状态恢复,鼠标按下到释放为一个周期
            self.motion = None
            self.press = None
            self.index_02 = None

     这里再次点击点被设定为将点从控制点列表中删去。函数的最后,各状态被重置。

     主函数:

    fig = plt.figure(2, figsize=(12, 6))  # 创建第2个绘图对象,1200*600像素
    ax = fig.add_subplot(111)  # 一行一列第一个子图
    ax.set_title("Missouter's bezier")
    myBezier = MyBezier(ax)
    plt.xlabel('X')
    plt.ylabel('Y')
    plt.show()

    这个程序在pycharm里不好运行的样子,cmd运行指令python demo.py。

    参考博客:

    https://blog.csdn.net/guofei9987/article/details/78106492

    https://www.jianshu.com/p/116be2cfa708

    https://blog.csdn.net/Hachi_Lin/article/details/89052318

  • 相关阅读:
    解决spring配置文件没有提示的问题
    SpringMVC 中HttpMessageConverter简介和Http请求415 Unsupported Media Type的问题
    在编辑Spring的配置文件时的自动提示
    Java注释@interface的用法【转】
    spring-autowire机制
    一些汇编指令
    Windows底层开发前期学习准备工作
    log4j.properties中的这句话“log4j.logger.org.hibernate.SQL=DEBUG ”该怎么写在log4j.xml里面呢?
    log4j 配置文件 (XML/.properties)
    [VC]C++ operator 两种用法
  • 原文地址:https://www.cnblogs.com/missouter/p/12667810.html
Copyright © 2011-2022 走看看