前言
碎形(fractal)是數學中最迷人的概念之一。一個簡單的規則不斷重複,就能產生無限複雜的結構。海岸線的曲折、花椰菜的分支、雪花的對稱——自然界處處是碎形。
當我第一次用程式畫出 Koch 雪花的時候,立刻就想:「這個能不能用雷切做出來?」答案是可以,但有一些有趣的挑戰。螢幕上的碎形可以無限放大,但實體化有尺寸限制;數學上的碎形有無限細節,但雷切有精度極限。怎麼在這些限制中找到最佳平衡點,就是這篇文章要探討的主題。
我會介紹幾種經典碎形曲線的 SVG 生成方法,討論迭代深度與切割精度的取捨,最後分享如何把碎形做成項鍊、耳環等飾品。
經典碎形曲線
Koch 曲線與 Koch 雪花
Koch 曲線是最經典的碎形之一。它的生成規則非常簡單:
- 從一條直線段開始
- 將線段三等分
- 以中間那段為底邊,向外畫一個等邊三角形
- 移除底邊
- 對所有線段重複步驟 2-4
把三條 Koch 曲線組成等邊三角形,就是 Koch 雪花。
import math
def koch_curve(start, end, depth):
"""遞迴生成 Koch 曲線的點序列"""
if depth == 0:
return [start, end]
# 三等分點
dx = end[0] - start[0]
dy = end[1] - start[1]
p1 = (start[0] + dx/3, start[1] + dy/3)
p3 = (start[0] + 2dx/3, start[1] + 2dy/3)
# 等邊三角形頂點
angle = math.atan2(dy, dx) - math.pi / 3
length = math.sqrt(dx2 + dy2) / 3
p2 = (p1[0] + length * math.cos(angle),
p1[1] + length * math.sin(angle))
# 遞迴
points = []
points.extend(koch_curve(start, p1, depth - 1)[:-1])
points.extend(koch_curve(p1, p2, depth - 1)[:-1])
points.extend(koch_curve(p2, p3, depth - 1)[:-1])
points.extend(koch_curve(p3, end, depth - 1))
return points
def koch_snowflake(center, size, depth):
"""生成 Koch 雪花的所有點"""
# 等邊三角形的三個頂點
angles = [-math.pi/2, -math.pi/2 + 2math.pi/3, -math.pi/2 + 4math.pi/3]
vertices = [(center[0] + size * math.cos(a),
center[1] + size * math.sin(a)) for a in angles]
all_points = []
for i in range(3):
start = vertices[i]
end = vertices[(i + 1) % 3]
segment = koch_curve(start, end, depth)
all_points.extend(segment[:-1]) # 避免重複點
all_points.append(all_points[0]) # 閉合
return all_points
Hilbert 曲線
Hilbert 曲線是一種空間填充曲線(space-filling curve)——它在極限情況下會經過正方形內的每一個點。它的美在於只用一條不交叉的連續線就填滿整個空間。
def hilbert_curve(order, size=100):
"""使用 L-system 生成 Hilbert 曲線"""
# L-system 規則
# A → -BF+AFA+FB-
# B → +AF-BFB-FA+
def generate_string(order):
s = 'A'
for _ in range(order):
new_s = ''
for c in s:
if c == 'A':
new_s += '-BF+AFA+FB-'
elif c == 'B':
new_s += '+AF-BFB-FA+'
else:
new_s += c
s = new_s
return s
instructions = generate_string(order)
# 執行繪圖指令
x, y = 0, 0
angle = 0
step = size / (2**order - 1) if order > 0 else size
points = [(x, y)]
for c in instructions:
if c == 'F':
x += step * math.cos(math.radians(angle))
y += step * math.sin(math.radians(angle))
points.append((x, y))
elif c == '+':
angle += 90
elif c == '-':
angle -= 90
return points
Sierpinski 三角形
Sierpinski 三角形是另一個經典碎形,適合做成鏤空飾品:
def sierpinski_triangle(vertices, depth):
"""生成 Sierpinski 三角形的所有三角形"""
if depth == 0:
return [vertices]
# 三邊中點
mid01 = ((vertices[0][0] + vertices[1][0]) / 2,
(vertices[0][1] + vertices[1][1]) / 2)
mid12 = ((vertices[1][0] + vertices[2][0]) / 2,
(vertices[1][1] + vertices[2][1]) / 2)
mid02 = ((vertices[0][0] + vertices[2][0]) / 2,
(vertices[0][1] + vertices[2][1]) / 2)
# 遞迴三個子三角形(中間的三角形被移除)
triangles = []
triangles.extend(sierpinski_triangle(
[vertices[0], mid01, mid02], depth - 1))
triangles.extend(sierpinski_triangle(
[mid01, vertices[1], mid12], depth - 1))
triangles.extend(sierpinski_triangle(
[mid02, mid12, vertices[2]], depth - 1))
return triangles
Dragon 曲線
Dragon 曲線(龍曲線)摺疊後的形狀像一條龍,非常適合做成掛飾:
def dragon_curve(order):
"""生成龍曲線的轉向序列"""
turns = []
for _ in range(order):
turns = turns + [1] + [-t for t in reversed(turns)]
# 轉換為座標
x, y = 0, 0
dx, dy = 1, 0
points = [(x, y)]
for turn in turns:
# 前進
x += dx
y += dy
points.append((x, y))
# 轉向
if turn == 1: # 左轉
dx, dy = -dy, dx
else: # 右轉
dx, dy = dy, -dx
x += dx
y += dy
points.append((x, y))
return points
迭代深度 vs 切割精度
問題的本質
碎形的數學定義是無限迭代,但雷切機有最小特徵尺寸的限制。迭代太深,最小的線段會小於雷射光束寬度,導致線條糊在一起。
以 Koch 雪花為例,每次迭代會讓最短線段變為原來的 1/3:
迭代 0:線段長度 = L
迭代 1:線段長度 = L/3
迭代 2:線段長度 = L/9
迭代 3:線段長度 = L/27
迭代 4:線段長度 = L/81
迭代 5:線段長度 = L/243
如果初始邊長 L = 60mm:
| 迭代深度 | 最短線段 | 線段數量 | 適合雷切? |
|———|———|———|———–|
| 0 | 60mm | 3 | 太簡單 |
| 1 | 20mm | 12 | 太簡單 |
| 2 | 6.7mm | 48 | 可以 |
| 3 | 2.2mm | 192 | 理想 |
| 4 | 0.74mm | 768 | 極限 |
| 5 | 0.25mm | 3072 | 太細 |
結論:對於 60mm 的 Koch 雪花,迭代深度 3-4 是最佳範圍。
不同碎形的建議深度
| 碎形類型 | 作品尺寸 | 建議迭代深度 |
|———|———|————|
| Koch 雪花 | 50-80mm | 3-4 |
| Hilbert 曲線 | 50-80mm | 3-4 |
| Sierpinski 三角形 | 60-100mm | 4-5 |
| Dragon 曲線 | 50-80mm | 8-10 |
Dragon 曲線可以迭代更深,因為它的線段不會急速縮短——它是在空間中蜿蜒而非分岔。
SVG 匯出
完整的 Koch 雪花 SVG 生成器
import svgwrite
import math
def koch_snowflake_svg(filename, size=60, depth=3, stroke_w=0.2):
"""生成可直接雷切的 Koch 雪花 SVG"""
center = (size 1.2, size 1.2)
points = koch_snowflake(center, size, depth)
# 計算邊界
xs = [p[0] for p in points]
ys = [p[1] for p in points]
min_x, max_x = min(xs) - 2, max(xs) + 2
min_y, max_y = min(ys) - 2, max(ys) + 2
w = max_x - min_x
h = max_y - min_y
dwg = svgwrite.Drawing(filename,
size=(f'{w}mm', f'{h}mm'),
viewBox=f'{min_x} {min_y} {w} {h}')
# 繪製雪花輪廓
path_data = f'M {points[0][0]:.3f},{points[0][1]:.3f} '
for p in points[1:]:
path_data += f'L {p[0]:.3f},{p[1]:.3f} '
dwg.add(dwg.path(d=path_data, fill='none',
stroke='black', stroke_width=stroke_w))
# 吊環孔(如果做成掛飾)
hole_y = min(ys) + 2
dwg.add(dwg.circle((center[0], hole_y), 1.5,
fill='none', stroke='black',
stroke_width=stroke_w))
dwg.save()
print(f"Koch snowflake (depth={depth}) saved to {filename}")
print(f" Size: {w:.1f} x {h:.1f} mm")
print(f" Segments: {len(points) - 1}")
koch_snowflake_svg('koch_pendant.svg', size=30, depth=3)
Hilbert 曲線 SVG
def hilbert_svg(filename, order=4, size=50, line_width=0.8):
"""生成 Hilbert 曲線 SVG"""
points = hilbert_curve(order, size)
# 偏移到正數座標
min_x = min(p[0] for p in points)
min_y = min(p[1] for p in points)
points = [(p[0] - min_x + 5, p[1] - min_y + 5) for p in points]
max_x = max(p[0] for p in points) + 5
max_y = max(p[1] for p in points) + 5
dwg = svgwrite.Drawing(filename,
size=(f'{max_x}mm', f'{max_y}mm'),
viewBox=f'0 0 {max_x} {max_y}')
# 繪製曲線
path_data = f'M {points[0][0]:.3f},{points[0][1]:.3f} '
for p in points[1:]:
path_data += f'L {p[0]:.3f},{p[1]:.3f} '
dwg.add(dwg.path(d=path_data, fill='none',
stroke='black', stroke_width=line_width))
# 外框(切割線)
margin = 3
dwg.add(dwg.rect((margin, margin),
(max_x - 2margin, max_y - 2margin),
rx=2, ry=2,
fill='none', stroke='red',
stroke_width=0.2))
dwg.save()
hilbert_svg('hilbert_coaster.svg', order=4, size=60)
飾品設計
項鍊墜飾
Koch 雪花是最受歡迎的碎形項鍊設計。設計要點:
- 尺寸:30-40mm 直徑最合適
- 材料:3mm 壓克力或 1.5mm 不鏽鋼(需光纖雷切)
- 吊環:在頂部留一個直徑 3mm 的小孔,穿過項鍊繩或跳環
- 迭代深度:3 次迭代。太少沒有碎形感,太多會太脆弱
- 表面處理:壓克力可以用火焰拋光邊緣,金屬可以做拋光或霧面
耳環
耳環要更輕更小:
- 尺寸:15-25mm
- 材料:1.5-2mm 壓克力最輕,木材也可以
- 迭代深度:2-3 次(因為尺寸小,不需要太多層次)
- 配件:黏上耳環鉤(可以在手工材料行買到)
成對耳環可以做鏡像設計(一個用碎形、一個用碎形的「負空間」),形成有趣的對比。
Hilbert 曲線胸針
Hilbert 曲線做成胸針效果很好,因為它填滿正方形的特性讓作品看起來很「完整」:
- 尺寸:40-50mm 正方形
- 材料:3mm 深色壓克力上雕刻白色曲線(反向雕刻)
- 配件:背面黏上胸針底座
Sierpinski 三角形吊飾
Sierpinski 三角形天生適合鏤空設計——被移除的部分本來就是三角形的洞:
- 切割外輪廓和所有被移除的三角形
- 迭代 4 次的 Sierpinski 三角形有 27 個三角形孔
- 看起來像蕾絲一樣精緻
進階:碎形飾品的結構強化
碎形形狀往往有很細的突出部分,容易折斷。幾個強化技巧:
- 加粗輪廓:在 SVG 中把線段寬度從理論上的零寬度加到 1-2mm,然後做 offset 處理
- 填充細節:最深層的碎形細節不做鏤空,改成表面雕刻
- 背板支撐:用一片完整的圓形或方形背板黏在碎形後面
- 樹脂灌注:在鏤空的碎形上灌注 UV 樹脂,既強化結構又增加光澤
小結
碎形飾品的魅力在於它們的「自相似性」——不管你從哪個距離看,都能發現重複的結構。這種數學之美以實體形式呈現,總是能引起旁人的好奇和讚嘆。
「這個圖案是什麼?」幾乎是每個看到碎形飾品的人的第一反應。而你可以藉此機會分享碎形的迷人世界——從 Koch 雪花的無限周長到 Hilbert 曲線的空間填充,每一個碎形都有一個精彩的故事。
延伸閱讀:
- Benoit Mandelbrot, The Fractal Geometry of Nature — 碎形幾何的奠基之作
- Daniel Shiffman, The Nature of Code — 用程式碼探索碎形和自然現象
- Nervous System 的碎形珠寶作品 — 商業級碎形飾品的設計參考
- L-system 線上模擬器 — 快速實驗不同的碎形規則