展开菜单
首页 精品内容 本月促销 装机必备 Windows macOS软件 IOS软件 Android AI PDF教程 专题
全部分类

当前位置:

首页 > 编程开发 > Python使用SymPy解决Manim曲线绘制速度不均的问题

Python使用SymPy解决Manim曲线绘制速度不均的问题

利用SymPy实现弧长参数化,解决Manim绘制参数曲线时因参数均匀但弧长不均导致的速度忽快忽慢问题。通过符号求导得弧长微元,数值积分计算弧长函数,再用二分法反解等弧长参数点,最终在Manim中实现匀速绘制动画。

如果你想绘制一个参数曲线,比如极坐标玫瑰线 r = cos(5θ),思路很直接:算出曲线上的点,然后用 Create 一笔画出来。但代码一渲染,问题就来了——花瓣尖端“唰”地一下就过去了,中间部分却慢吞吞的,整条曲线的绘制节奏忽快忽慢,看起来十分别扭。

问题的根源:参数 θ 均匀递增,但曲线上点移动的实际距离并不是均匀的。参数变化快的地方,点就“飞”过去;参数变化慢的地方,点就“爬”过去。要让画笔匀速移动,必须让参数按照弧长均匀分布——这就是弧长参数化。手工做这件事几乎不可能:求弧长得积分,反解参数得解方程,曲线稍微复杂一点就算不动了。好在,我们有 SymPy

1. 痛点场景还原:一个具体的例子

先看一段“有毛病”的代码,直观感受一下问题:

from manim import *
import numpy as np

class BadRoseCurve(Scene):
    def construct(self):
        axes = Axes(x_range=[-3, 3], y_range=[-3, 3])
        self.add(axes)

        # 用累积密度法生成非均匀 theta:尖端稀疏(快),中部密集(慢)
        n_points = 500
        t = np.linspace(0, 2 * PI, n_points)
        # 密度函数:在 cos(5t)=0 处(花瓣中部)密度高,在 |cos(5t)|=1 处(尖端)密度低
        density = 1 + 3 * np.sin(5 * t) ** 2
        theta = np.cumsum(density)
        theta = theta / theta[-1] * PI  # 归一化到 [0, π](k为奇数时只需π即可画完)
        r = np.cos(5 * theta)
        x = r * 2 * np.cos(theta)
        y = r * 2 * np.sin(theta)

        points = [axes.c2p(x[i], y[i]) for i in range(n_points)]
        curve = VMobject(color=PINK, stroke_width=3)
        curve.set_points_as_corners(points)

        # 用 Create 画曲线——速度明显不均匀!
        self.play(Create(curve), run_time=5, rate_func=linear)
        self.wait()

运行这个动画,你会看到花瓣尖端几帧就画完了,而花瓣中部却画得很慢。这里 rate_func=linear 控制的是动画进度的均匀,但曲线本身点的分布就是疏密不均的,所以视觉速度必然忽快忽慢。

2. SymPy 解决方案:弧长参数化三步走

要让绘制速度均匀,核心思路是:生成一组让相邻点之间实际距离相等的参数值。具体分三步:

  1. 用积分算出弧长函数 L(θ)——从起点到参数 θ 的弧长;
  2. 算总弧长,等分出一组目标弧长值 s₁, s₂, ...;
  3. 对每个 sᵢ,反解方程 L(θ) = sᵢ,得到对应的 θᵢ。

这三步,SymPy 都能帮上忙。

2.1 用 SymPy 推导弧长函数

import sympy as sp

theta = sp.Symbol('theta', real=True)
n = 5  # 五瓣玫瑰线

# 极坐标方程(注意这里放大了2倍,与痛点代码中的 r*2 保持一致)
r = 2 * sp.cos(n * theta)

# 极坐标 → 直角坐标(符号推导,零误差)
x = r * sp.cos(theta)  # x = r(θ)·cos(θ)
y = r * sp.sin(theta)  # y = r(θ)·sin(θ)

# 弧长微元:ds/dθ = sqrt((dx/dθ)² + (dy/dθ)²)
dx_dtheta = sp.diff(x, theta)
dy_dtheta = sp.diff(y, theta)

# 弧长微元表达式(注意:这里不算积分,只保留被积函数)
ds_dtheta = sp.sqrt(dx_dtheta**2 + dy_dtheta**2)
print("弧长微元 ds/dθ =", sp.simplify(ds_dtheta))

# 运行结果:
'''
弧长微元 ds/dθ = 2*sqrt((2*sin(4*theta) + 
                     3*sin(6*theta))**2 + 
                     (2*cos(4*theta) -
                     3*cos(6*theta))**2)
'''

SymPy 会输出一个椭圆积分形式的表达式——这是正常的,很多曲线的弧长都没有初等表达式。没关系,数值求解一样好用。

2.2 用 nsolve 反解等弧长参数点

import numpy as np
from scipy.integrate import quad
from scipy.optimize import bisect  # 数值求根,稳定且快

# 把弧长微元转成可数值计算的函数
ds_func = sp.lambdify(theta, ds_dtheta, 'numpy')

# 数值弧长函数:L(t) = ∫₀^t ds
def arc_length(t):
    """计算从 0 到 t 的弧长"""
    result, _ = quad(ds_func, 0, t, limit=100)
    return result

# 计算总弧长
total_length = arc_length(2 * np.pi)
print(f"总弧长: {total_length:.4f}")

# 等分弧长,用数值求根反解对应的 theta
N = 500
s_values = np.linspace(0, total_length, N)
theta_vals = []

# 辅助函数:f(t) = L(t) - s,我们要找 f(t)=0 的根
def f(t, s):
    return arc_length(t) - s

for i, s in enumerate(s_values):
    if i == 0:
        theta_vals.append(0.0)  # s=0 对应 theta=0
        continue

    # 初始搜索区间:用上一个 theta 作为左边界
    # 弧长是单调递增的,所以解一定在 [左边界, 右边界] 之间
    left = theta_vals[-1]  # 上一个已解出的 theta
    right = left + 0.5     # 向右扩展,足够覆盖下一个等分点

    # 扩展右边界直到 f(right, s) > 0(确保根在区间内)
    while f(right, s) < 0:
        right += 0.5

    # 二分法求根(稳定、快速)
    sol = bisect(f, left, right, args=(s,), xtol=1e-8)
    theta_vals.append(float(sol))

theta_vals = np.array(theta_vals)

代码要点sp.lambdify 把符号表达式编译成 NumPy 函数,求值快;bisect 二分法稳定可靠;猜测值用“弧长占比 × 总参数范围”做线性估计,足够接近真实解。

3. Manim 联动实战

把上面的计算和 Manim 动画串起来,就是一份完整可运行的代码:

from manim import *
import numpy as np
import sympy as sp
from scipy.integrate import quad
from scipy.optimize import bisect

class UniformRoseCurve(Scene):
    def construct(self):
        # ===== SymPy + 数值积分计算等弧长参数点 =====
        theta = sp.Symbol("theta", real=True)
        n = 5

        # 极坐标方程 r = 2*cos(5θ)
        r = 2 * sp.cos(n * theta)

        # 极坐标转直角坐标
        x_expr = r * sp.cos(theta)
        y_expr = r * sp.sin(theta)

        # 弧长微元
        dx = sp.diff(x_expr, theta)
        dy = sp.diff(y_expr, theta)
        ds_dtheta = sp.sqrt(dx**2 + dy**2)

        # 数值弧长函数
        ds_func = sp.lambdify(theta, ds_dtheta, "numpy")

        def arc_length(t):
            val, _ = quad(ds_func, 0, t, limit=100)
            return val

        # 总弧长(k为奇数时只需π即可画完)
        total_len = arc_length(np.pi)
        print(f"总弧长: {total_len:.4f}")

        # 用二分法反解等弧长 theta(速度快)
        N = 500
        s_vals = np.linspace(0, total_len, N)
        theta_vals = [0.0]

        def f(t, s):
            return arc_length(t) - s

        for i in range(1, N):
            s = s_vals[i]
            left = theta_vals[-1]
            right = left + 0.5
            # 扩展右边界直到 f(right) > 0
            while f(right, s) < 0:
                right += 0.5
            sol = bisect(f, left, right, args=(s,), xtol=1e-8)
            theta_vals.append(float(sol))

        theta_vals = np.array(theta_vals)

        # 计算直角坐标点
        x_func = sp.lambdify(theta, x_expr, "numpy")
        y_func = sp.lambdify(theta, y_expr, "numpy")

        # ===== Manim 动画 =====
        axes = Axes(x_range=[-3, 3], y_range=[-3, 3])
        self.add(axes)

        points = []
        for t in theta_vals:
            px = float(x_func(t))
            py = float(y_func(t))
            points.append(axes.c2p(px, py))

        curve = VMobject(color=PINK, stroke_width=3)
        curve.set_points_as_corners(points)

        self.play(Create(curve), run_time=5, rate_func=linear)
        self.wait()

4. 效果展示说明

运行 UniformRoseCurve,对比之前的 BadRoseCurve,差异一目了然:

  • 绘制过程均匀流畅:粉色线条以恒定速度从起点生长到终点,花瓣尖端不再“闪现”,花瓣中部也不再“拖沓”。整条曲线在 10 秒内被平稳地画完,节奏非常舒服。
  • 同一曲线,两种体验BadRoseCurve 由于刻意在花瓣中部堆了 3 倍密度,视觉上画笔会在那里明显减速;而弧长参数化版本抹平了所有速度波动,让 rate_func=linear 真正发挥作用。
  • 弧长参数化的本质:虽然 theta_vals 的数值分布是不均匀的(花瓣尖端附近 θ 变化慢,中部变化快),但映射到平面后,相邻点之间的实际距离完全相等。这就是“均匀”的真正含义——空间上的均匀,而非参数上的均匀

5. 本期小结

  • 问题本质:均匀参数 ≠ 均匀弧长,直接用均匀参数画曲线必然速度不均。
  • 解决思路:弧长参数化——先算弧长函数,再反解出等弧长分布的参数点。
  • SymPy 的角色
    • sp.diff → 求导得弧长微元
    • sp.integrate → 算弧长函数
    • sp.nsolve → 反解参数值
    • sp.lambdify → 表达式转数值函数,高效求值
  • Manim 中的用法:把等弧长点集喂给 VMobject.set_points_as_corners(),再用 Create + rate_func=linear,即可实现真正匀速的曲线绘制。
本文内容来源于互联网,如有侵权请联系删除。
作者最新文章
编程开发 Python
相关文章 更多
精品专题 更多
本月促销

正软商城本月促销专区,汇集办公、设计、安全、影音、系统工具及AI软件等正版软件优惠活动,提供限时折扣、特价授权和优惠购买信息,活动库存及价格以页面实时展示为准。

装机必备

正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

Windows

正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

macOS软件

正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

IOS软件

正软商城iOS软件专区,精选适用于iPhone和iPad的办公、学习、影音、设计、效率及AI应用,提供功能介绍、适用设备、系统要求和正版获取方式等信息。

AI

正软商城AI软件专区,汇集AI写作、AI绘画、AI视频、AI办公、AI编程、AI翻译、智能客服和数据分析等人工智能工具,提供功能介绍、适用平台、收费方式及正版购买信息。

PDF教程

正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

Mac软件 更多
灵活计算器
灵活计算器

灵活计算器是一款笔记式算数应用,支持实时计算、动态关联和云端同步功能。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

赤友清理大师
赤友清理大师

赤友清理大师是一款为 Mac 设计的智能清理优化工具,可精准扫描垃圾、大文件、重复文件等,释放磁盘空间。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

极度公式
极度公式

极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

图几
图几

图几是一款适用于 macOS 的截图、标注与美化工具,支持离线操作保障隐私。界面整理和高频系统操作被放到一起考虑,桌面或窗口内容一多时,管理起来会更省心。

密码键盘
密码键盘

密码键盘是一款兼具安全性与便捷性的高效密码管理器。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

思源笔记
思源笔记

思源笔记是一款本地笔记软件,提供所见即所得的编辑方式,为长文写作带来顺滑的体验。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

Office 365 简体中文
Office 365 简体中文

一款文字处理软件,一种订阅式的跨平台办公软件,基于云平台提供多种服务,通过将 Excel 和 Outlook 等应用与 OneDrive 和 Microsoft Teams 等强大的云服务相结合,Office 365 可让任何人使用任何设备随时随地创建和共享内容。

WALTR PRO
WALTR PRO

WALTR是一款电脑至iOS文件传输转换工具,操作简单,快速实现文件识别与传送。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

CodeExpander
CodeExpander

CodeExpander 是一款快捷短语输入增强工具,通过键入缩写自动展开为自定义文段,提升工作效率。任务管理和过程控制会更完整,持续下载、批量同步或需要稳定传输流程的场景会更适合它。

Mountain Duck
Mountain Duck

Mountain Duck 是一款能将多个网盘挂载到本地的工具,像本地磁盘一样使用网盘。清理链路的完整性会更好一些,做应用卸载、残留处理和空间整理时,通常能少走很多手动排查步骤。

Menuist
Menuist

Menuist 是一款面向 macOS 的 Finder 右键菜单增强工具,主要用来补充新建文件、快捷导航等常用操作,让日常文件管理和访问路径时更高效、更顺手。

Mole
Mole

Mole 是一款专为 Mac 设计的深度清理优化工具,涵盖缓存清理、应用管理及实时状态监控等功能。清理链路的完整性会更好一些,做应用卸载、残留处理和空间整理时,通常能少走很多手动排查步骤。

WINDOWS 更多
Windows 10
Windows 10

Windows 10 是一款微软推出的经典操作系统,拥有硬件兼容性与多任务处理能力。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。

极度公式
极度公式

极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

密码键盘
密码键盘

密码键盘是一款兼具安全性与便捷性的高效密码管理器。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

思源笔记
思源笔记

思源笔记是一款本地笔记软件,提供所见即所得的编辑方式,为长文写作带来顺滑的体验。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

傲梅轻松备份
傲梅轻松备份

傲梅轻松备份是一款专业易用的数据备份软件,为重要数据提供安全保障。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

Office 365 简体中文
Office 365 简体中文

一款文字处理软件,一种订阅式的跨平台办公软件,基于云平台提供多种服务,通过将 Excel 和 Outlook 等应用与 OneDrive 和 Microsoft Teams 等强大的云服务相结合,Office 365 可让任何人使用任何设备随时随地创建和共享内容。

Wise Folder Hider Pro
Wise Folder Hider Pro

Wise Folder Hider Pro 是一款专业级文件和文件夹隐藏加密软件,为私密数据添加多重保护。高频操作更强调就近处理,浏览、整理和跨目录移动文件时,来回切换和重复点击都会少很多。

WALTR PRO
WALTR PRO

WALTR是一款电脑至iOS文件传输转换工具,操作简单,快速实现文件识别与传送。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

CodeExpander
CodeExpander

CodeExpander 是一款快捷短语输入增强工具,通过键入缩写自动展开为自定义文段,提升工作效率。任务管理和过程控制会更完整,持续下载、批量同步或需要稳定传输流程的场景会更适合它。

PinStack
PinStack

PinStack是一款轻量级的Windows平台剪贴板管理工具,优化您的剪贴板使用体验。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。

Mountain Duck
Mountain Duck

Mountain Duck 是一款能将多个网盘挂载到本地的工具,像本地磁盘一样使用网盘。清理链路的完整性会更好一些,做应用卸载、残留处理和空间整理时,通常能少走很多手动排查步骤。

Seer
Seer

Seer是一款在Win平台下的空格键功能增强效率工具,只需轻敲空格键,就能预览几乎任何格式的文件。它更适合把零散的小功能集中起来使用,处理高频琐碎任务时会更省事。