怎么玩
- 10 关,由浅入深:红纸先折好(对折、四折或八折),纸上用铅笔画好了要剪的线。按住拖动就是剪刀,可以剪任意曲线、任意多刀。
- 剪下来的纸屑会掉下去:手里捏着的是折起来的那一边,所以留下贴着折痕最长的那块,其余失去支撑,从纸的后面掉出画面。绕一圈剪,中间那块掉下去,纸上就多了一个洞。
- 在折痕上剪的缺口,展开会变成洞:折痕的边用虚线标了出来。
- 剪好了点「展开」:窗花一片片翻开,和目标叠在一起比较,按相似度给一到三星,每关记录最好成绩。可以回去接着修,也可以撤销上一刀。
- 挑战模式:关掉「提示线」,纸上就没有铅笔线了,只能看着左上角的目标凭眼力剪。挑战的成绩单独记录。
- 最后一关之后是「自由剪纸」:从一整张纸开始,自己挑对称轴折(竖、横、两条对角线;只能沿这一叠自己的对称轴折,折得齐,所以最多折三次、八层),随时剪、随时展开一层再接着剪。折之前剪的地方不会被镜像,所以能剪出不完全对称的图案。左上角实时显示摊开后的样子。
怎么做的
所有几何都是从头手写的,没有用几何库:布尔运算在 static/games/engines/polygon-boolean.mjs,剪纸规则在 papercut.mjs,纸屑在 paper-fragments.mjs。三个模块都不碰 DOM,用 Node 单元测试覆盖。
纸是一份带洞的多边形。 纸由若干区域组成,每块一条外轮廓,可以带几个洞。画面直接用 Canvas 的 evenodd 规则填充,洞和洞里的岛都交给奇偶规则,不需要三角化;评分也读同一份数据。
一刀 = 纸 − 带子。 剪刀轨迹是一条没有面积的折线,先把每一段向两侧膨胀成一个两端是半圆的"胶囊",再把胶囊并成一条带子,转角和两端自然都是圆角,然后用纸减去带子。圆上的点取在固定的角度上,所以相邻两段在同一个点上的圆角完全重合,不会出现两条近似圆互相交错的碎边。
布尔运算:平面划分 + 面分类。 差集、交集、并集是同一套做法:
- 把两个形状的所有边放在一起,在交点处断开;距离在 ε 以内的点并成同一个结点。这样顶点恰好落在边上、两条边重合这类退化情况,都不需要单独写分支。
- 沿半边走出平面图的每一个面,在面里取一个离所有边都有距离的样本点,按奇偶规则判断它在不在纸里、在不在带子里。
- 留下"在纸里、不在带子里"的面;只有一侧保留、另一侧不保留的边,才是新的边界。
- 把边界串成环:逆时针的是外轮廓,顺时针的是洞,洞挂到包含它的最小外轮廓下面。洞里的岛自然就成了另一块。
所以一刀剪成好几块、在中间剪出窟窿、窟窿里再剪出岛,都不用额外处理。每次运算之后都会检查结果:环不自交、环之间最多在顶点处接触、洞在外轮廓里面。不合格就丢掉这一批,纸保持原样。包围盒碰不到剪刀的碎块和洞不参与运算,所以每一批的开销主要取决于被剪到的那块纸的轮廓有多少条边,远处已经剪好的洞再多也不会拖慢。自由模式里折了几层就要减几次,把纸剪得极碎时会比关卡里慢。
边拖边剪,不留细缝。 输入按距离采样,排进队列,每攒够 3 个新点处理一批。每批先化简折线,再膨胀成带子。下一批从上一批的倒数第二个点开始,两批重叠一段,所以交界处不会留下一条没剪断的细缝;同一处减两次,结果也不变。
哪块留下、哪块掉落。 剪完后只有一块还捏在手里:贴着折痕最长的那块。剪窗花时手捏的是折起来的那一边,贴着折痕的边越长,展开后连成的那片就越大。如果都不贴折痕,就留面积最大的那块。其余的块失去支撑,带着物理掉下去。
纸屑的物理和上限。 每一刀(按下到抬起)掉下来的块里,最大的 3 块做成真正的刚体,带着初速度、自转和阻尼往下掉。纸屑画在纸的后面,一路掉出画面下沿就回收,不会挡住正在剪的纸。自转和面积的平方根成反比:大块慢慢歪倒,小片转得快,重力对所有纸屑一样。其余的块合并成一块,只做视觉效果。纸屑总数由一个 32 个的池子兜底,剪得再碎,手机上也不会越剪越卡。物理用 120 Hz 固定步长推进,结果和帧率无关。
自由折叠:存展开的纸,而不是折好的那一片。 自由模式里可以先剪再折,所以权威几何是整张展开的纸,另外记着现在折成的这一叠的形状,以及每一层对应整张纸的哪个镜像。对称轴都过纸的中心,镜像矩阵只由 0 和 ±1 组成,复合是精确的;折一次层数翻倍,层的映射按"旧映射 ∘ 这次的镜像"复合。在这一叠上剪一刀,就是先把带子裁到这一叠的范围里(伸到外面的那截下面并没有纸),再经过每一层的映射搬到整张纸上减掉。画这一叠时不做布尔运算:每一层把整张纸按逆映射搬回来,裁到这一叠的轮廓里画,只有每一层都剪掉的地方才透光。折和展开的动画,是把翻过去的那一半按 p ↦ p + s·(R(p) − p) 压扁再翻到另一侧。
展开和评分。 每种折法对应一组镜像,都是坐标取反或交换,所以折痕上的点映射后完全不变,相邻两片沿折痕严格共边,并集会把折痕抹掉。也因为这样,折痕上的缺口展开后会变成洞。相似度是剪出的纸和目标的交并比;各个镜像完全相同,所以只需要在折好的那一片上算。测试会模拟"沿着铅笔线剪",确认每一关都能拿到三星。