当前位置:

首页 > 编程开发 > 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,即可实现真正匀速的曲线绘制。
本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
作者最新文章
编程开发 Python
相关文章 更多
langgraph python guide中文入门教程详解
langgraph python guide中文入门教程详解

本篇LangGraph Python中文入门教程详解,系统梳理StateGraph状态机定义、节点编写、边与条件路由、检查点持久化(MemorySaver)等核心机制,提供从环境安装到完整循环智能体构建的代码示例与实操指导。

Windows 安装 Python:从官方入口到虚拟环境的可复现实践
Windows 安装 Python:从官方入口到虚拟环境的可复现实践

面向后端开发者,基于 Python 官方文档说明如何在 Windows 上安装 Python、创建 venv 虚拟环境及使用 pip 管理依赖。包含可复制命令、验证方法及工具选择边界,避免常见配置误区。

Windows系统Python安装教程:下载、勾选PATH及环境变量配置
Windows系统Python安装教程:下载、勾选PATH及环境变量配置

针对Windows初学者的Python安装实战指南。详细讲解如何从Python官网下载匹配架构的安装包,重点演示安装首屏勾选“Add python.exe to PATH”的关键操作,并提供使用python --version和py命令验证环境变量的具体步骤,帮助新手快速搭建开发环境并排查路径问题。

Python安装后怎么打开:使用IDLE或命令行启动解释器
Python安装后怎么打开:使用IDLE或命令行启动解释器

刚在Windows安装好Python却不知道如何启动?本文详细演示如何通过开始菜单找到并打开IDLE集成开发环境,以及如何在PowerShell或命令提示符中使用python和py命令启动交互式解释器、运行.py脚本文件。包含退出解释器的方法及常见启动问题排查,帮助初学者快速验证安装成功并开始编写代码。

了解Python编程领域中最好的就业行业
了解Python编程领域中最好的就业行业

了解Python编程领域中最好的就业行业导语:近年来,Python作为一种易学易用的编程语言,越来越受到企业和开发者的青睐。在Python编程领域中,有许多就业机会,涵盖了各个行业。本文将介绍一些Python编程领域中最好的就业行业,并提供相应的代码示例。数据科学和机器学习数据科学和机器学习是Python编程领域中最热门的行业之一。Python在数据科学和机

python处理csv文件的方法完整实用教程
python处理csv文件的方法完整实用教程

针对Python处理CSV文件时常见的编码报错、内存溢出和数据类型错误,本文对比标准库csv与Pandas库的适用场景,提供可复制的代码示例和因果排查思路,帮助开发者选择最优处理方案。

python字典怎么按值排序用sorted函数实现升降序
python字典怎么按值排序用sorted函数实现升降序

本文介绍如何使用Python的sorted函数对字典按值进行升序和降序排序,结合具体代码示例和应用场景,帮助读者理解排序逻辑及其在数据处理中的实际价值。

python正则匹配修改方法与代码实现教程
python正则匹配修改方法与代码实现教程

深入讲解Python中使用re.sub进行正则匹配与替换的技巧,涵盖基础语法、分组引用及回调函数应用,帮助开发者解决复杂字符串修改需求,提升代码效率与可维护性。

selenium中文安装包下载与安装教程
selenium中文安装包下载与安装教程

详解Selenium在Python环境下的安装步骤,包括使用pip安装库文件、下载对应版本的浏览器驱动以及验证安装是否成功,适合自动化测试初学者参考。

skyvern官网入口及中文版使用教程
skyvern官网入口及中文版使用教程

Skyvern是一款基于大语言模型的开源浏览器自动化框架,目前仅提供英文界面且无官方中文版。本文指导用户通过GitHub获取源码,使用Docker快速部署本地环境,并解释其如何通过DOM树解析与自然语言指令实现网页自动操作,同时提供针对中文用户的配置建议与替代方案。

查看更多
精品专题 更多
装机必备
装机必备

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

Windows
Windows

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

PDF教程
PDF教程

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

Mac软件 更多
Postman macOS版
Postman macOS版
Mac

Postman是一套面向API全生命周期的开发与测试平台,可在Mac上发送和调试多种协议请求,管理集合、环境及认证信息,并完成脚本测试、模拟服务、监控、文档发布和团队协作。它适合开发、测试及运维人员使用,提供个人免费方案。

Docker Desktop macOS版
Docker Desktop macOS版
Mac

Docker Desktop 是 Docker 官方为 Mac 提供的容器开发环境,集成 Docker Engine、命令行工具、Compose、Build、Kubernetes 与图形化管理界面。

Android Studio macOS版
Android Studio macOS版
Mac

Android Studio 是 Google 面向安卓应用开发提供的官方集成开发环境,基于 IntelliJ IDEA 构建。它整合智能代码编辑、Gradle 构建、界面预览、设备模拟、性能分析、调试测试及 APK 分析等工具。

WINDOWS 更多
PixPix 网页版
PixPix 网页版
Windows

PixPix 是面向品牌、卖家和内容团队的在线 AI 图片视频创作平台,提供图视频 Agent、无限画布、图片生成、视频生成、自然语言编辑及商品套图、产品精修和带货视频等电商工具。

Postman Windows版
Postman Windows版
Windows

Postman 是面向 API 全生命周期的开发与协作平台。Windows 桌面版可用于构建和发送 HTTP、GraphQL、gRPC、WebSocket、MQTT 等请求,并提供环境变量、集合、脚本测试、模拟服务、监控、文档及团队工作区等

Docker Desktop Windows版
Docker Desktop Windows版
Windows

Docker Desktop 为 Windows 开发者提供集成式容器环境,内置 Docker Engine、命令行工具、Compose、Build 与 Kubernetes 等组件,可在本机完成镜像构建、容器运行、日志查看和资源管理。