如果一个几何图形,在另一个几何图形的边界上“滚动”,它的某个固定点,会留下些什么?
不是公式,不是结论,而是那种真正走过之后留下的轨迹。
但很快我发现,这个问题在脑子里是想不清楚的。尤其当滚动的不再是圆,而是一个有棱有角的多边形时,想象力几乎立刻失效。
现在有了python这个强大的工具应该能实现这个梦想中的轨迹吧?
但一开始的时后,我总是失败。这种失败并不是需要什么复杂的计算公式,
而是你不知道下一步该让图形“怎么动”。支点在哪里?转多少?什么时候换边?
所有这些问题,在脑中都是模糊的。
后来我意识到,与其在一个完全想不清楚的情形里反复尝试,不如先把问题压缩到最简单的情况。
于是我做了一个几乎没有任何“理论依据”的选择:
先让正方形的边长,等于正三角形的边长。
这个 1:1 的比例,并不是为了研究比例,只是为了让滚动这件事,在代码和视觉上先“站得住”。在这个设定下,
我终于可以把“滚动”拆解成一件可执行的事情:
这是正方形ABED按顺序在三角形的BC,CA,AB边上滚动的示意说明图
我们把上面图示的三个滚动阶段称为1轮。
推理表显示:相位的“漂移”规律
我们可以发现一个非常漂亮的数学循环。每一轮(三个阶段)结束后,与三角形起始支点 B 重合的正方形顶点会按以下顺序演变:
| 轮次 | 阶段结束点 | 与三角形 B 点重合的正方形顶点 | 说明 |
| 第0轮 | 初始 | B | 初始对齐 |
| 第1轮 | 第3阶段后 | A | 逆时针位移 1 个顶点 |
| 第2轮 | 第6阶段后 | D | 逆时针位移 2 个顶点 |
| 第3轮 | 第9阶段后 | E | 逆时针位移 3 个顶点 |
| 第4轮 | 第12阶段后 | B | 完全复原 |
结论:由于正方形有 4 个顶点,三角形有 3 个顶点,这个运动系统需要完整地绕三角形转 4 圈(共 12 个旋转阶段),正方形的顶点标注才会和最开始第一图之前的初始状态完全重合。
在代码里,这不再是连续的“滑动”,而是一段一段明确的刚体旋转。
在 1:1 的比例下,整个滚动过程可以被压缩成一个极其简单的循环:# 三个三角形顶点,依次作为旋转支点pivots = [B_tri, C_tri, A_tri]# 每一轮包含 3 次旋转,共 4 轮 = 12 次for step inrange(12): pivot = pivots[step % 3]# 每一次滚动,都是一次刚体旋转 self.play( Rotate( square, angle=210 * DEGREES, about_point=pivot, rate_func=linear ) )
当我第一次看到正方形真的沿着三角形翻滚起来时,我意识到一件事:
几何直觉,有时候不是靠想出来的,而是靠“跑出来的”。
在整个实现过程中,我并没有单独去“画轨迹”。
我只是做了一件很简单的事:在正方形滚动的过程中,顺手记录下某个顶点的位置。
path = TracedPath(lambda i=i: square.get_vertices()[i], stroke_width=3, stroke_opacity=0.6)
当动画播放结束,那些被记录下来的点,自然连成了一条曲线。
这条轨迹并不是我设计的形状,而是运动本身留下的痕迹。
当正方形和正三角形边长相等时,滚动这件事,第一次在屏幕上成功了。
我终于可以不再靠想象,而是靠几条明确的规则,让图形动起来:
支点在哪里
围绕哪个点旋转
旋转多少角度
什么时候“翻到下一条边”
在这个设定下,正方形 ABED 依次沿着三角形的BC → CA → AB,外侧滚动。
这并不意味着问题“解决了”。恰恰相反——正是在这个看似简单的 1:1 情形里,我第一次意识到:
滚动不是连续的而是被“相位”切成了一段一段
每一次贴边,每一次换支点,每一次转角,本质上都是一次状态跃迁。而代码之所以能跑通,
不是因为我理解了全部几何,而是因为在这个比例下:
支点切换刚好“对齐”
旋转角度不会出现歧义
下一步几乎是“显而易见的”
这让我产生了一种错觉:是不是问题本来就这么简单?
在代码终于稳定跑完 1:1 之后,我几乎是本能地,做了下一步。
既然简单的已经能跑,那我想看看——复杂的,会发生什么。
我把比例改成了:
正方形边长为 L,正三角形边长为 3L。
也正是在这一刻,之前被 1:1 “遮住”的东西,一下子全暴露出来了。
滚动不再是“一边贴完,顺势翻下去”。而是又乱套了
但是有了比例为1:1的经历
我们很快精准对齐物理接触点和相位变化。当正方形边长为 L,三角形边长为 3L 时:
阶段 1:跨越顶点
阶段 2:第一次沿边翻滚
支点:BC 边上距离 B 点的 L 处 E 点
动作:绕 E 点旋转 90°
结果:ED 边贴在 BC 边中段
位置:D 点距离 三角形 B 点 为 2L
阶段 3:第二次沿边翻滚
支点:BC 边上距离 B 2L 的 D 点
动作:绕 D 点旋转 90°
结果:下一条边平贴 BC 边
位置:A 点与三角形顶点 C 对齐
阶段 4:进入下一条边
顶点 A 与 C 重合
动作:绕 C 顶点旋转 210°
结果:正方形下一条AB边贴在 CA 边上
循环重复
通过这四个阶段,我第一次清楚地看到:轨迹的复杂性,是由多次分段相位累积产生的。
每一次旋转、每一个支点,都像是在控制一个“离散几何系统”,
phase = [ ("vertex", 210), ("edge", 90), ("edge", 90),]
没有一步可以随意,否则,轨迹不会“稍微错一点”,而是直接崩掉。而这一切的精确知道得益于正方形与三角形边长有着确定的比例
我最初那些失败的尝试,问题在于——比例关系根本没有被约束清楚。
比例,是滚动能否发生的隐形规则。
正方形与三角形边长之间,一旦存在确定的比例,
接触点的位置、阶段的切换、以及“下一步该怎么转”,才第一次变成了可以被精确描述的事情。
也正是从这一刻开始,轨迹不再是“试出来的”,而是被运动本身一步步逼出来的
也正是从这一刻开始,我才真正意识到:
我一开始问的那个问题,
其实从来就不是“轨迹长什么样”。
而是——在怎样的约束下,一个几何系统,才被允许留下轨迹。
当比例不清楚时,图形当然也可以动,代码当然也可以跑,但那只是一种“看起来在滚”的假象。支点是随意的,角度是拍脑袋的,下一步只能靠猜。
这种运动,不可能留下真正的痕迹。
只有当比例被固定下来,当每一次接触、每一次旋转、
每一次“不得不这样转”的选择,都被约束住时,轨迹才第一次成为一个必然的结果。
直到这时,我才回头重新理解,读中学时那个最原始的念头:
如果一个几何图形,在另一个几何图形的边界上滚动,它的某个固定点,会留下些什么?
现在我终于可以回答它了。
它不会留下一个“我想象中的形状”,也不会留下一个“可以事先写下的公式”。
它留下的,只是它在所有约束之下,所能走过的全部路径。
这也是为什么,当我看着最后生成的视频我并不关心它们像不像花,像不像心形,
或者能不能被一句话概括。
我看到的,是另一件事:
一个被严格约束的几何系统,在没有任何“审美目标”的前提下,仍然不可避免地,生成了某种秩序。
这次尝试让我越来越确信一件事:
理解,并不总是从复杂开始的,很多时候,它是从一个“能跑起来的简化”开始的。
而 Python 在这里,让我把几何的运动,看得更慢一点,也更清楚一点。
当结构退场,,
运动留下的东西,
才第一次显现为对象
当我的视线再一次停留在视频结束时的最后一帧画面的对象上。我发现它很美,真的很美!今天,我终于实现了年轻时隐藏在梦中角落里的那种数学之美“