1. 这个 demo 到底画什么:把三轮车的轮廓拆分清楚
1.1 三轮车轮廓可以拆成哪些几何元素
前阵子整理旧代码,翻到一个用 Python 画三轮车轮廓的小 demo,项目名就叫“三轮轮廓演示”。当时做这个的起因很简单:一位朋友想给某个教学课件配一张三轮车示意图,要求不是网上那种写实的照片风格,而是干净、可编辑、线条清晰的工程线稿图。找来找去没有合适的素材,我就干脆用 Python 画了一个。
很多人一听到“用 Python 画三轮车”,第一反应是“画一辆三轮车很难吧”。其实把三轮车拆开看,它的轮廓就是几类基础几何元素的组合:圆(车轮)、直线(车架梁)、弧线(车把弯管)、不规则多边形(车座、货斗)。关键不是把每个笔画画得多么精细,而是先想清楚这辆车由哪些部件组成,部件之间用什么顺序连接,比例关系怎么定。
以最常见的拉货三轮车为例,侧视图看过去主要包含几个部分:
- 前轮:直径较小,承担转向。
- 后轮:直径较大,承担主要载荷,侧视图里能看到一个,另一个被车身遮挡。
- 前叉:连接前轮与车把的斜向结构。
- 车架主梁:从前叉向后延伸,贯穿车身底部。
- 车座:通常位于车架中部偏后上方。
- 车把:连接前叉顶部,形成操控结构。
- 货斗:车身中后部的载货区域,轮廓一般呈梯形或矩形加斜边。
这些部件的轮廓一旦梳理清楚,绘制代码的骨架也就出来了。每个部件对应一个绘图函数,函数只负责“接收坐标参数,画出一块封闭或开放路径”,最后由一个主函数把它们组织在同一个坐标系里。这样做的好处非常明显:改动某个部件时不需要去翻其他部分的代码,调试的时候也能单独画一个车轮出来看效果。
1.2 侧视图为主、前视图为辅的绘制思路
我最终采用“侧视图为主”的绘制方案。原因很简单:三轮车侧视图的信息量最大,车架结构、轮径差、人机尺寸都能在一张图上体现。前视图或俯视图作为辅助,可以用来补充说明左右轮距、货斗宽度这些侧视图看不出来的参数。
不过这个 demo 的目标不是绘制标准的工程三视图,而是“轮廓演示”。所以我只保留了两件事:一张干净的侧视轮廓图,加上一组可调节的几何参数。这样既保持了视觉上的直观性,又不需要引入过多的制图规范,对初学者也友好。
在侧视图中,“三个轮子”实际上只能看到两个。另一个后轮被货斗和车架遮挡住了,但这不影响我们称它为“三轮轮廓”——因为从侧视图角度,三条轴线的空间位置是明确的。如果你希望真正画出三个轮子同时可见的效果,那就得用斜等轴测视角,复杂度会高不少,需要引入投影变换。这个我放在后面的扩展方案里讲,demo 本身先不做。
1.3 为什么选 matplotlib 而不是 turtle 或纯 PIL
工具选型上我试过几种方案。turtle 虽然名字听着适合画图,但它的工作方式是一步一步移动画笔,画完一个圆再画另一根线,很难做到“画错了重新调整某一段”。如果让我画一个矩形,turtle 没问题,但画一辆三轮车,要求对多个部件反复调整比例和位置,turtle 的交互方式就很吃力了。
另外我也考虑过 PIL/Pillow。它的图像处理能力很强,但本质上是像素级的绘制,画直线和圆弧还行,涉及矢量轮廓、坐标变换的时候就不太顺手。尤其后期要把线条加粗、做透明填充,Pillow 需要自己维护各种绘制上下文,代码量会膨胀很多。
最后选了 matplotlib。它虽然是一个以数据可视化出名的库,但它底层实际上是带状态机的绘图引擎,支持matplotlib.patches里的各种形状对象,也能用Path构建自定义多边形。更关键的是,matplotlib 的坐标系就是数学坐标系,y 轴向上为正,跟我设计三轮车几何模型时用的坐标习惯完全一致,不用做坐标系转换。
这里就引出一个下一步必须先解决的问题:用什么坐标系来定位这辆车的各个关键点。
2. 坐标系和比例参数:轮廓图好看的关键
2.1 画布大小、坐标轴与单位长度
画之前先确定坐标范围。我把画布设成figsize=(12, 6),对应的横轴范围是0到10,纵轴范围是0到6,这样三轮车在画面中大概占八成的宽度,四周留一点边距,视觉上不会太挤。
在数学坐标系里,横轴方向为车身前进方向,纵轴方向为垂直向上。我习惯把后轮中心安放在(7.0, 1.2),前轮中心安放在(2.5, 1.0)。这里的前后顺序对应三轮车的真实结构:前轮在左侧,后轮在右侧,车头朝左。
这样设置之后,坐标轴的单位长度其实是“抽象单位”,不刻意对应厘米或米。只要保证车体内部各个部件之间的比例正确,出图的视觉效果就是协调的。比例对了,图就耐看;比例不对,哪怕线条画得再精细,也会觉得哪里不对劲。
绘制之前,我在代码里用一个字典保存所有比例参数,方便后续统一调整:
# 三轮车侧视图关键参数 PARAMS = { "canvas_x": 10.0, "canvas_y": 6.0, "front_wheel_x": 2.5, "front_wheel_r": 1.0, "rear_wheel_x": 7.0, "rear_wheel_r": 1.2, "wheel_base": 4.5, # 轴距,即前后轮水平距离 "frame_height": 2.2, # 主梁距离地面高度 "seat_h": 2.8, # 车座高度 "handlebar_h": 3.5, # 车把高度 "bucket_front": 3.0, # 货斗前边缘 x 坐标 "bucket_rear": 5.8, # 货斗后边缘 x 坐标 "bucket_top": 2.0, # 货斗上沿高度 "bucket_bottom": 1.6, # 货斗下沿高度 }这些参数中,最核心的是轴距和轮径比。货斗参数、车座高度等都是从轴距派生出来的。我后面画图时不会逐个硬编码坐标点,而是先算这几个关键参数,再由它们推算其他点位。
2.2 轴距、轮径、车架高度这些参数怎么定
轴距wheel_base我定为4.5,含义是前后轮中心在水平方向上的距离。实际三轮车轴距通常在 1.6 到 1.8 米,我把画面比例设定为 1 个单位约等于 0.36 米,这样轴距 4.5 个单位就是约 1.6 米,和真实的货运三轮车相近。
前轮半径取1.0,后轮半径取1.2。真实三轮车后轮确实比前轮更大更厚,这样画既有结构上的依据,视觉上也形成了前低后高的姿态,看起来更像一辆载重车。如果你要画的是儿童三轮车,就把两个轮的半径改成相等,同时把轴距缩小到 3 左右。
车架高度frame_height = 2.2表示主梁离地高度。这个参数决定整车重心给人的感觉:调高 0.5,车身会显得轻盈甚至有点“飘”;调低 0.5,车身会显得敦实。实际应用中,货运三轮车的车架离地间隙并不高,取 2.2 这个数值是为了在轮廓图中清楚地展现车架与车轮的上下关系,太低了会和车轮重叠。
车座高度seat_h = 2.8,车把高度handlebar_h = 3.5。这两个高度差为 0.7 个单位,模拟驾驶者骑车时手比臀高的姿态。你如果想让三轮车看起来更像“送货老人”的休闲车型,可以把车座往后移、车把降低,让整体姿态更俯身向前;如果画的是共享三轮车,就把车座和车把的高度差缩小,让驾驶姿势更直立。
2.3 从坐标点到线条:绘制顺序没有想象中那么随意
很多入门教程画图时想到哪画到哪,其实绘制顺序是有讲究的。我总结出的经验是:先画辅助定位线,再画车轮,再画车架,最后画车身附件。
辅助定位线就是前后轮中心的水平连线和垂直参考线。这些线在出图前会被清理掉,但绘图过程中它们非常有用。我在写代码时会先画出轮心的水平和垂直参考线,确保车架各个接点的高度相对于轮心位置是准确的。
绘制顺序的核心逻辑是“遮挡关系”。后轮在车身后面,所以要最先画;车架主梁横穿车身,其次画;货斗和车座在车架上方,最后画。如果反过来了——比如先画货斗再画车架主梁——那么主梁线条会压在货斗边界上,看起来就像车架穿透了货斗,非常影响观感。matplotlib 本身没有真正的图层概念,它只有“后画的覆盖先画的”这一规则,所以顺序错了调整起来会很麻烦。
3. 核心绘图代码:车轮、车架和细节一次到位
3.1 车轮绘制函数:圆+轮毂+辐条
车轮是整个轮廓中最好画也最见功底的部分。想要一个轮子不像“圆饼”,至少要画出三样东西:外轮胎轮廓、轮毂、辐条。
我用matplotlib.patches.Circle绘制外轮廓,但是注意,Circle默认是实心填充的,所以必须先设置fill=False。为了让轮胎看起来有厚度,我画两个嵌套的圆:外层大圆描边代表轮胎外沿,内层小一点的圆描边代表轮毂外沿。两个圆的半径差,就是视觉上轮胎的“厚度”。
辐条我选择画六根。六根辐条间隔 60 度,在视觉上既不稀疏也不拥挤,而且三轮车的轮子通常就是六辐或八辐。画辐条的代码很简单,用角度遍历生成从轮心到轮缘的线段即可:
import numpy as np import matplotlib.pyplot as plt import matplotlib.patches as patches def draw_wheel(ax, cx, cy, r, spoke_count=6, tire_color="#333333"): # 外轮胎轮廓 tire = patches.Circle((cx, cy), r, fill=False, edgecolor=tire_color, linewidth=4) ax.add_patch(tire) # 轮毂 hub = patches.Circle((cx, cy), r * 0.12, facecolor=tire_color, edgecolor=tire_color, linewidth=2) ax.add_patch(hub) # 辐条 for i in range(spoke_count): angle = (i / spoke_count) * 2 * np.pi x2 = cx + r * 0.98 * np.cos(angle) y2 = cy + r * 0.98 * np.sin(angle) ax.plot([cx, x2], [cy, y2], color=tire_color, linewidth=1.2) # 轮毂中心小圆 inner_hub = patches.Circle((cx, cy), r * 0.05, facecolor="#777777", edgecolor="none") ax.add_patch(inner_hub)这里我特别把辐条的终点放在0.98 * r,而不是1.0 * r。原因是外轮胎轮廓线本身有宽度,如果辐条刚好画到理论半径处,会被轮胎描边覆盖掉一部分,看上去像是辐条没有到达轮缘。缩短 2% 之后,辐条末端刚好在外轮廓线的内侧,视觉上就自然了。
3.2 车架轮廓:Path 多段线与闭合策略
车架比车轮复杂,因为它不是规则几何形状,而是多个点之间的连线。最初我用ax.plot()直接画折线,但这样产生的问题是折线默认不会闭合,车架最后一笔到第一笔之间缺一条边。
后来我改用matplotlib.path.Path配合PathPatch。Path可以自定义每个顶点的操作码:第一个点是MOVETO,后面的点用LINETO,最后如果需要闭合就再加一个CLOSEPOLY。这种方式特别适合画封闭的车架轮廓:
from matplotlib.path import Path import matplotlib.patches as mpatches def draw_frame(ax, points, closed=True, color="#222222", lw=5): verts = list(points) if closed: codes = [Path.MOVETO] + [Path.LINETO] * (len(verts) - 1) + [Path.CLOSEPOLY] verts.append((0, 0)) # CLOSEPOLY 需要占位点 else: codes = [Path.MOVETO] + [Path.LINETO] * (len(verts) - 1) path = Path(verts, codes) patch = mpatches.PathPatch(path, fill=False, edgecolor=color, linewidth=lw) ax.add_patch(patch)这里有个很多人会忽略的细节:CLOSEPOLY的占位点坐标是无所谓的,因为 matplotlib 会自动把它当成回到路径起点来处理,但你必须让verts和codes长度一致,否则会抛异常。因此闭合路径时,我在列表末尾追加了一个(0, 0)占位点。
对于三轮车车架,我实际上画了两条路径:一条是主梁大框架,从后轴位置出发,沿水平方向到前轴上方,再斜向上连接到车把底部;另一条是后轮上方的副梁,连接后轴和车座下方。两条路径在视觉上互相搭接,正好模拟真实三轮车车架由主梁和副梁组合的结构。
3.3 车座、车把、货斗这些细节怎么补
车座我画成一个“水滴”形状的封闭多边形。用四个控制点:前端圆弧起点、后端圆弧终点、上弧中点、下弧中点。如果只用直线连起来,车座会显得很生硬。我在这里用了一个简单的二次贝塞尔拟合,把上下两条弧线分别近似为两段折线。其实对于轮廓演示来说,使用五六个点的折线已经足够,因为在低线宽情况下,人眼很难分辨出折线和曲线的差别。
车把结构分两段:前叉和把手。前叉是从前轮中心斜向延伸到车架主梁前端的直线段;把手是在主梁前端向上延伸的短竖线,再向左水平折出一个小手柄。真实三轮车的车把会比这复杂得多,但轮廓图只需要表达出“这个位置是操控结构”即可。
货斗是最能体现“三轮车身份”的部件。我画成一个梯形轮廓:下底边靠后、上底边略短、前边斜切。梯形的具体坐标直接用参数计算,不写死。比如货斗下沿从x=3.0到x=5.8,上沿从x=3.2到x=5.6,这样形成前低后高的装载姿态。货斗内部再加一条平行于底边的线,表示车厢深度。
3.4 完整代码骨架
把所有部件函数写好后,主流程就非常清爽了:
def draw_tricycle_outline(show_helpers=False, save_path=None): fig, ax = plt.subplots(figsize=(12, 6)) ax.set_xlim(0, PARAMS["canvas_x"]) ax.set_ylim(0, PARAMS["canvas_y"]) ax.set_aspect("equal") # 绘制车轮 draw_wheel(ax, PARAMS["front_wheel_x"], 1.0, PARAMS["front_wheel_r"]) draw_wheel(ax, PARAMS["rear_wheel_x"], 1.2, PARAMS["rear_wheel_r"]) # 绘制车架 frame_points = build_frame_points() draw_frame(ax, frame_points, closed=False) # 绘制货斗、车座、车把 draw_bucket(ax) draw_seat(ax) draw_handlebar(ax) # 辅助线 if show_helpers: ax.axhline(y=1.0, color="blue", linestyle="--", linewidth=0.8) ax.axhline(y=1.2, color="blue", linestyle="--", linewidth=0.8) if save_path: fig.savefig(save_path, dpi=200, bbox_inches="tight") return fig, ax整个 demo 的核心逻辑就这么多。真正花时间的地方不是车轮和车架的绘制,而是反复调整各个部件之间的相对位置,也就是“比例感”。这其实是一种审美调参,我后面在第四节里会讲具体怎么调的。
4. 画完之后的样子:输出设置、配色和图形修饰
4.1 让轮子不变形:set_aspect 的坑
第一次跑通代码时,我盯着屏幕看了半天,总觉得车轮是扁的。当时我以为是自己画圆的代码出了问题,检查了半天,发现Circle的半径计算没有毛病。最后才意识到:matplotlib 默认的坐标轴纵横比不是 1:1,也就是说 x 轴方向的 1 个单位和 y 轴方向的 1 个单位在屏幕上占的像素不一样,于是圆就被拉伸成了椭圆。
解决方案只有一行:
ax.set_aspect("equal")这一行让 x 轴和 y 轴的单位长度保持一致,圆才能显示成圆。这个坑非常基础,但画图项目里最容易漏。特别是当你用subplots创建多个子图时,每个子图都必须单独设置set_aspect("equal"),否则不同子图之间也会出现比例不统一的问题。
顺带说一句,ax.axis("equal")也有类似效果,但它还会顺带重置坐标轴范围,可能导致你之前设置好的xlim和ylim失效。所以我在代码里统一用set_aspect("equal"),这样xlim、ylim和aspect三者互不干扰。
4.2 线宽、配色与填充
轮廓图好不好看,线宽和配色占了六成因素。我最终的配色方案是三色体系:深灰色#333333用于车轮和车架结构线,更浅的灰色#777777用于辐条和细节线,主体线宽 4 到 5,细节线宽 1.2 到 2。
这个配色的灵感来自传统工程图纸,用不同线宽区分主次结构:粗线代表轮廓边界,细线代表内部纹理或辅助结构。你如果在社区上看过很多 Python 绘图的教程,会发现很多画得“乱”的图,问题不是画错了,而是所有线条的粗细都一样,没有层次感。
填充色我用了低饱和度的浅蓝色#dbe9f6给货斗区域做了半透明填充。这样做的好处是让货斗在视觉上“浮”起来,不会和车架线条混在一起。matplotlib里设置填充色只需在PathPatch里增加facecolor和alpha参数:
patch = mpatches.PathPatch(path, facecolor="#dbe9f6", alpha=0.5, edgecolor="#222222", linewidth=4)注意,一旦设置了facecolor,如果没有alpha控制透明度,实心填充会把底下绘制的车架线条全部遮住。这个细节我在这里踩过一次坑,后面第 5 节会展开说。
4.3 去掉坐标轴、加网格与保存高清图
最终展示时,坐标轴刻度当然是不要的。清除刻度只需一行:
ax.axis("off")但这里有个微妙的问题:axis("off")会把整个坐标轴连同边框一起关掉,如果你的背景是白色,关掉后图片四周会显得很空。所以我在保存时会带上bbox_inches="tight",让画布自动裁掉多余的空白区域:
fig.savefig("tricycle_outline.png", dpi=200, bbox_inches="tight")200 DPI 是我测试下来比较合适的值:放大看线条边缘依然平滑,文件体积又不会过大。如果只是网页展示,150 DPI 足够;如果要打印到 A4 纸,300 DPI 更稳妥。
调试过程中加网格非常有用。我建议开发阶段保留ax.grid(True, linestyle="--", alpha=0.4),这样能快速看出车架关键点的坐标有没有对齐。等调好参数准备出正式图,再注释掉网格。
5. 调试和踩坑记录:我在这个 demo 里遇到的问题
5.1 轮廓上下颠倒,原来是我把 y 轴理解反了
开发这个 demo 最早期版本时,我是在写轮子代码前先用ax.plot()画了一个简易的三角框架,结果发现车架在屏幕上看起来是倒过来的——车把在下方,车轮在上方。
排查过程很有意思。我打印出所有坐标点,数值明明是正确的:后轮y=1.2,车把y=3.5。坐标没错,为什么画出来是反的呢?后来我意识到,我在调试时使用了.png图片的坐标系习惯,误以为屏幕上的“上方”对应 y 值小的一侧。但 matplotlib 的笛卡尔坐标系是 y 轴向上增长,车把 y=3.5 应该显示在画面上方。
问题是我把两组车轮坐标错误地放在了循环里:前轮每次循环都会重新设置cx, cy,最后一轮循环结束后,前轮中心变成了后轮中心,画出来自然就乱了。
这种“函数内部状态污染”的问题在绘制类项目里非常常见。解决方式很直接,不在绘图函数里直接修改全局参数,而是把坐标值作为参数传入。比如draw_wheel(ax, cx, cy, r),绝不在函数内部用global去改cx。
5.2 车架边缘多出一根“斜线”
整个车架轮廓第一次画完整时,我发现在主梁和货斗底部之间多出一条斜线,从车架中部斜插到后轮上方。这条线既不属于车架,也不属于货斗,看起来非常突兀。
我用二分法排查,逐个注释掉部件函数,最终定位到问题是draw_frame的闭合逻辑造成的。当时我用closed=True画主梁框架,但主梁框架实际上不应该闭合——它的末端应该和货斗轮廓自然交接,而不是自己首尾相连形成一个多边形。
这个案例给我的经验是:不是所有轮廓路径都需要闭合。车架、车把这种“结构线”通常保持开放,让它们和相邻部件互相穿插,形成整体感;只有车座、货斗这种独立封闭的区域才需要闭合。开放和闭合的选择,要结合部件关系来判断,不能一刀切。
5.3 货斗填充色把车轮盖住了
给货斗加填充色后,我发现后轮上半部分突然看不见了。货斗的填充区域正好覆盖在后轮上方,因为后画的图元会覆盖先画的图元,而货斗的facecolor是实心的,不透明。
解决办法是在PathPatch里加上alpha=0.5,让货斗呈现半透明效果。如果你希望货斗完全不遮挡后轮,还可以调整绘制顺序:先画货斗,再画后轮,让后轮压在货斗上面。但这样会带来另一个问题——后轮又会压住车架横梁。所以要权衡清楚,什么部件在同层,什么部件必须分层。
我用“半透明填充”方案的另一个原因是,半透明效果在视觉上更有层次感,能够同时看到货斗底边、车架横梁和后轮轮廓的叠加关系,传递的信息量反而更大。
5.4 辐条循环绘制慢,怎么优化
画六根辐条根本不至于慢,但如果把辐条数量加多,或者后续扩展成动画逐帧绘制,性能问题就会显现。我在 demo 中实际测试过:辐条数量从 6 根增加到 36 根时,单帧渲染时间从 0.03 秒暴增到 0.4 秒左右,原因不是ax.plot()本身慢,而是每次调用ax.plot()都会创建一个新的Line2D对象,对绘图层造成额外负担。
优化方式是改用批量绘制。把辐条的所有线段坐标组装成一个LineCollection,一次性添加到绘图区域:
from matplotlib.collections import LineCollection def draw_spokes(ax, cx, cy, r, count=6): segments = [] for i in range(count): angle = (i / count) * 2 * np.pi x2 = cx + r * np.cos(angle) y2 = cy + r * np.sin(angle) segments.append([(cx, cy), (x2, y2)]) lc = LineCollection(segments, colors="#555555", linewidths=1.2) ax.add_collection(lc)改完之后,36 根辐条的渲染时间降低到了 0.08 秒,几乎可以忽略不计。如果你后面打算做动画,这个优化几乎是必须的。
6. 从 demo 到可复用工具:参数化与下一步玩法
6.1 把车架关键点参数化
demo 跑通以后,我开始想一个问题:这个代码只能画这一种三轮车吗?如果把关键参数全部公开,能不能通过改参数快速生成不同形态的三轮车?
我把参数继续细分成三组:
| 参数类别 | 包含项 | 影响 |
|---|---|---|
| 整车尺寸 | 轴距、轮距、整车高度 | 决定整体轮廓的大小与比例 |
| 部件几何 | 轮径、货斗宽高、车座尺寸 | 决定每个部件的具体形状 |
| 姿态参数 | 车把高度、车座前后位置 | 决定整车给观者的姿态感受 |
例如把bucket_front从 3.0 调到 4.2,货斗就明显变短,整辆车更接近“三轮摩托车”而不是“货运三轮车”;把seat_h从 2.8 调到 2.2,车座降低,整辆车会呈现一种趴地姿态,更像运动车型。
参数化之后,还可以用循环批量生成图片,用于方案对比:
variants = [ {"bucket_front": 3.0, "seat_h": 2.8}, {"bucket_front": 3.8, "seat_h": 2.5}, {"bucket_front": 4.5, "seat_h": 2.2}, ] for i, params in enumerate(variants): PARAMS.update(params) fig, ax = draw_tricycle_outline() fig.savefig(f"variant_{i}.png", dpi=150, bbox_inches="tight")这样导出的图可以直接拿到文档里对比不同车型方案的外形差异。比重新画一遍方便太多。
6.2 从静态轮廓到动画演示:让车轮转起来
静态图只能表达形态,动态演示则能表达运动逻辑。我接下来做了一个简单扩展:把辐条角度作为时间参数,让车轮随着时间旋转。
核心思路是redraw时不再创建新的图元,而是复用已有的Line2D对象,只更新它们的坐标。我之前踩过性能坑,所以深知不能用销毁重建的方式来写动画。用 matplotlib 的FuncAnimation:
from matplotlib.animation import FuncAnimation def update(frame): angle_offset = frame * 0.1 # 更新所有辐条的坐标 update_spokes(angle_offset) return spoke_lines anim = FuncAnimation(fig, update, frames=120, interval=50) anim.save("tricycle_rotation.gif", writer="pillow")动画一旦跑起来,整个 demo 的感觉就完全不同了。虽然实质只是把辐条角度增加,但配合车体轮廓,很直观地表现出“三轮车在前进”的视觉意象。这个动画后来也被朋友拿去做课件素材了,他说学生看静态图半天理解不了轮子怎么转,看动画一下就懂了。
6.3 这支 demo 还能用在哪些地方
除了教学课件,三轮车轮廓还可以作为图标素材、物料印刷线稿、甚至进一步变成车辆设计的原型验证工具。
如果你愿意继续投入,有几个明确的方向可以走:
- 绘制前视图和俯视图,组成完整三视图。前视图可以看到左右两个后轮和中间的车架;俯视图可以看到货斗平面轮廓和整车的宽度比例。
- 增加贝塞尔曲线,把车架从折线结构升级为曲线结构,让轮廓更接近真实车辆的流线造型。
- 接入参数界面,比如用简单的滑块控件,让用户拖动轴距、轮径等参数,实时看到轮廓变化。
我后来把第三点的代码封装得更通用了一点,把三轮车轮廓绘制函数做成一个独立模块,里面只保留draw_wheel、draw_frame、draw_bucket这些基础绘图原语,外部通过参数组合出不同类型的车辆轮廓。这样不只是三轮车,两轮自行车、四轮平板车的轮廓也能复用同一套逻辑画出来。
回头来看,这个 demo 的代码量不算大,但它在“如何用代码描述一个具体物体的轮廓”这件事上给了我不少启发。很多人学绘图的时候过度关注 API 本身,却忽略了真正难的部分其实是“如何把一个视觉物体拆解成可计算的几何参数”。等你把一个三轮车轮廓拆到能用十几个参数描述时,再看任何复杂的图形,都会有种“不过如此”的感觉。
最后再分享一个小技巧:调试这类图形项目时,不要把坐标数据藏在代码里,建议在绘图函数里临时打印一份“关键坐标表”,列清楚每个构造点的x、y值和它们之间的逻辑关系。看起来麻烦,但排查比例失调、部件错位这类问题时,它会比肉眼盯着屏幕高效得多。