Neverball 的关卡生成工具 curve.c 用了很多年,只会画一种曲线:圆弧。参数怎么调都逃不出这个形状。
有开发者做关卡时被这条限制卡住,先写了一堆脚本救急,后来把脚本整理成一个正经工具——Curveball。网页版、桌面版、GitHub 源码全公开,核心实现用 Rust。
Curveball 解决了什么,谁会用
老工具只能生成圆弧类曲线,想要别的形状只能手写脚本、手动试错。Curveball 把这些一次性脚本整理成通用生成逻辑,带可视化预览。
| curve.c(老工具) | Curveball | |
|---|---|---|
| 能生成的曲线 | 只有圆弧 | 任意 profile + path 组合 |
| 复杂曲线做法 | 手写脚本、手动试错 | 定义截面和路径,自动挤出 |
| 拆分 brush | 人工挑点,顺序不能错 | chull 库自动求凸包 |
| 纹理分配 | 手动处理 | 仍交给 TrenchBroom |
| 使用门槛 | 需要读源码改参数 | 网页直接用,或下载/编译 |
受众很窄:Neverball 关卡制作者是核心用户,直接影响是拆 brush 的时间从小时级降到分钟级,不用再逐点核对切割顺序。对几何生成、Rust 小工具感兴趣的技术读者,能看到的是一个具体案例:怎么用凸包算法和 CAD 抽象解决一个小众但真实的工程痛点,值得参考的是思路,不是工具本身。
关卡是怎么“长”出来的
Neverball 关卡文件用 Quake map 格式,每块几何叫 brush。brush 靠平面切割半空间的交集定义,这决定了一件事:brush 必须是凸的,没法凹。
大多数曲线天生不凸。想做一条弯曲轨道,就得拆成一堆凸的小 brush 拼起来。curve.c 和作者早期脚本都靠人工挑点完成拆分,点的顺序还不能错——错了就可能切反半空间。
Curveball 换了思路,用 Rust 的 chull 库跑凸包算法,把点丢给它,自动算出怎么拆分。省掉了手工核对的步骤,代价是纹理分配变麻烦,作者索性放弃自己解决,交给专门的关卡编辑器 TrenchBroom 处理。
这里没有什么工程奇迹,只是把一个枯燥重复的手工步骤,换成了一行库调用。价值恰恰在这种“不炫技”。
从脚本到抽象:extrusion
真正让工具从“一堆脚本”变成“一套系统”的,是作者从机械 CAD 软件(如 Solidworks)借来的思路。
定义一个 2D 截面(profile),沿一条路径(path)复制、挤出,就能生成 3D 形状。Profile 和 path 是两个独立变量,随便组合就是一种新曲线,不用逐个手写生成器。
这套抽象也带来新麻烦:截面沿路径移动时该怎么转向。作者一开始漏算了一个自由度——挠率(torsion)。
后来用上 Frenet frame,给路径每一点定义切向、法向、副法向三个向量,靠矩阵旋转控制截面朝向。
工具没有把所有旧功能都塞进这套新抽象。它保留了三种例外实现(Curve Classic、Curve Slope、Rayto),比如 Curve Slope 的 brush 结构天生套不进 extrude 逻辑,只能单独写。这说明作者清楚抽象的边界,没有为了统一而硬凑。
这类工具真正的价值,不在名气
Curveball 不是通用建模软件,离开 Neverball 的 Quake map 格式基本没用。
它打动人的地方,是把一次性劳动升级成可复用系统。早期脚本能用,但每加一种新曲线就要重写一遍。有了 profile+path 的抽象,新曲线变成组合问题,不再是重复劳动。
“工欲善其事,必先利其器”——这句话说滥了,但放在这里恰好对症。curve.c 只能画圆弧这么多年没人动,不是需求不存在,是没人愿意花时间把工具本身重新设计一遍。
对 Neverball 关卡制作者来说,接下来最实际的动作是:想做复杂曲线轨道的,可以先去网页版试试效果,不用装环境;已经用惯 curve.c 的老用户,遇到圆弧就没必要迁移,遇到非圆弧形状再切过去。对关注 Rust 小工具和创作者工具链的技术读者来说,这个案例值得记的不是 chull 或 Frenet frame 本身,是那种“先攒脚本、再抽象成系统”的路径——这是大多数个人工具从能用走向好用的common path。
这也是开源生态一个老问题的翻版:真正被长期使用的工具,往往不是团队立项做出来的,是某个用户自己被卡住了才顺手补上的洞。
Curveball 能走多远,不取决于它写没写好,取决于作者接下来还愿不愿意为自己的关卡继续填坑。纹理适配、后续维护这些边角问题,一旦作者失去兴趣,工具很容易停在半成品状态——这是所有个人开源工具共享的风险,Curveball 也不例外。
