Struck pane
inspired by Anders Hoff, Fracture1
Glass struck high on the right. Cracks run to every edge, ringed by shorter ones, and the shards at the impact are filled in.
Made with Claude Opus 5.5
- Technique
- line art
- Inspired by
- creative coding
- Shape
- Any screen
- Added
- 27 September 2026
Colours
- #1C1B1Abackground
- #DAD8CEforeground
- #CF6A4Caccent
Export
Notes
Each long crack is a random walk that turns a little at every step, thinning and fading as it runs out to the edge of the screen. Shorter cracks join neighbouring long ones in rings that spread wider each time and sag toward the impact, and a few branches grow off them until they meet another crack. The shards are an irregular star cut apart along the cracks, around a crushed core where some pieces are missing.
Sources
Source code
wallpapers/fracture/design.py, 247 lines
"""A pane struck once: random-walk radial cracks tied by sagging rings, shards cut at the impact."""
import itertools
import math
from collections.abc import Callable
import numpy as np
from numpy.typing import NDArray
from shapely.geometry import LineString, MultiLineString, Point, Polygon
from shapely.geometry.base import BaseGeometry
from shapely.ops import unary_union
from shapely.prepared import prep
from walldye import (
ACCENT,
ACCENT_1,
ACCENT_4,
BG_ALT,
UI,
Canvas,
NpRng,
P,
Path,
Rect,
Vec,
clamp,
design,
ladder,
polar,
)
from walldye.geom import parts
type Line = list[Vec]
N_RADIAL = 14
TONES = ladder((UI, BG_ALT), 4) # crack tone, stepping down with distance from the impact
SHARDS = (ACCENT, ACCENT_1, ACCENT_4) # bright, deep, and the dim snapped-off tips
def walk(
rng: NpRng,
p: Vec,
a: float,
sigma: float,
bounds: Rect,
step: float = 12,
stop: Callable[[Vec, Vec], bool] | None = None,
max_len: float = 4000,
) -> Line:
"""A crack from `p` heading `a` radians, turning by N(0, sigma) each `step`, until it leaves
`bounds`, `stop(last, new)` holds, or it has run `max_len`; the last point is included."""
pts = [p]
for _ in range(int(max_len / step)):
a += rng.normal(0, sigma)
p = polar(p, step, rad=a)
pts.append(p)
if not bounds.contains(p) or (stop and stop(pts[-2], p)):
break
return pts
def at_radius(line: Line, c: Vec, r: float) -> Vec | None:
"""First point along `line` at distance `r` from `c`, or None."""
for p0, p1 in itertools.pairwise(line):
d0, d1 = abs(p0 - c), abs(p1 - c)
if d0 <= r < d1:
return p0 + (p1 - p0) * ((r - d0) / (d1 - d0))
return None
def jagged(rng: NpRng, a: Vec, b: Vec, bow: float, n: int = 8) -> Line:
"""Slightly bowed, slightly wobbly crack from `a` to `b`; `bow` is a fraction of its length."""
normal = (b - a).perp()
return [
a
+ (b - a) * t
+ normal * (bow * 4 * t * (1 - t) + (rng.normal(0, 0.005) if 0 < t < 1 else 0))
for t in np.linspace(0, 1, n)
]
def tone(d: float) -> int:
"""TONES index for a crack `d` from the impact: UI out to 350, BG_ALT from 1050."""
return round(clamp((d - 350) / 700) * 3)
def taper(s: Canvas, c: Vec, lines: list[Line], width: Callable[[float], float]) -> None:
"""Stroke polylines with width and tone falling off with distance from the impact `c`."""
paths: dict[tuple[int, int], Path] = {} # (width in fifths of a unit, tone index)
for line in lines:
prev = None
for a, b in itertools.pairwise(line):
d = abs((a + b) / 2 - c)
k = round(width(d) * 5), tone(d)
if k != prev:
paths.setdefault(k, P()).M(a)
paths[k].L(b)
prev = k
# thinnest first; within a width the faintest first, so brighter cracks cross on top
for (w, t), d in sorted(paths.items(), key=lambda kv: (kv[0][0], -kv[0][1])):
s.stroke(d, TONES[t], w / 5, cap="round", join="round")
@design(aspects="any")
def draw(s: Canvas) -> None:
# upper right on a landscape screen, upper third on a portrait one
c = s.pick(landscape=(1400 / 1920, 380 / 1080), portrait=(0.62, 0.3))
bounds = s.inset(-20)
rng = s.np_rng(11)
base = rng.uniform(0, 2 * math.pi)
angles = [base + (k + rng.uniform(-0.3, 0.3)) * 2 * math.pi / N_RADIAL for k in range(N_RADIAL)]
# one fewer ray into the top edge, where the bar and window titles sit
angles = [a for a in angles if not 245 < math.degrees(a) % 360 < 262]
n = len(angles)
reach = 58 * rng.uniform(0.6, 1.4, n)
splinters = set(rng.choice(n, 3, replace=False).tolist())
reach[list(splinters)] *= 1.6
radials = [
walk(rng, polar(c, r, rad=a), a, math.radians(1.2), bounds) for a, r in zip(angles, reach)
]
rings: list[Line] = []
r = 110
while r < 900:
for i in range(n):
a = at_radius(radials[i], c, r * rng.uniform(0.94, 1.06))
b = at_radius(radials[(i + 1) % n], c, r * rng.uniform(0.94, 1.06))
if a is None or b is None or rng.random() > 0.75 - r / 1100:
continue
if r > 600 and abs(b - a) > 350:
continue
# concentric glass cracks sag slightly toward the impact
sag = rng.uniform(0.005, 0.015)
rings.append(jagged(rng, a, b, sag if (b - a).perp().dot(c - a) > 0 else -sag))
r *= rng.uniform(1.35, 1.55)
net = prep(MultiLineString(radials + rings))
branches: list[Line] = []
while len(branches) < 8:
line = radials[rng.integers(n)]
j = int(rng.integers(len(line) // 10, len(line) // 2))
p0, p1 = line[j], line[j + 1]
a = math.atan2(p1.y - p0.y, p1.x - p0.x) + rng.choice([-1, 1]) * rng.uniform(0.4, 0.8)
pts = walk(
rng,
polar(p0, 4, rad=a),
a,
math.radians(3),
bounds,
8,
lambda p, q: net.intersects(LineString([p, q])),
500,
)
if len(pts) > 8:
branches.append([p0, *pts])
taper(s, c, radials, lambda d: max(0.8, 1.6 - d / 1000))
rings = [q for q in rings if q[0].y + q[-1].y > 300] # keep the strip under the top bar clear
taper(s, c, rings + branches, lambda d: max(0.6, 1.3 - d / 700))
shards(s, rng, c, angles, reach, splinters)
def shards(
s: Canvas,
rng: NpRng,
c: Vec,
angles: list[float],
reach: NDArray[np.float64],
splinters: set[int],
) -> None:
"""Burst of glass at `c`: a crushed core, a ring of fragments, wedges out to an irregular rim."""
tau = 2 * math.pi
rays = sorted(
[(a % tau, r, i in splinters) for i, (a, r) in enumerate(zip(angles, reach))]
+ [(rng.uniform(0, tau), 58 * rng.uniform(0.6, 1.4), False) for _ in range(8)]
)
eff = [r / 1.6 if sp else r for _, r, sp in rays] # body radius, splinters excluded
nxt = [*range(1, len(rays)), 0]
def pt(r: float, a: float) -> Vec:
return polar(c, r, rad=a)
def gap(line: BaseGeometry) -> BaseGeometry: # every crack gets its own width
return line.buffer(rng.uniform(0.8, 1.6) / 2, cap_style="flat")
def cut(p: Vec, q: Vec) -> BaseGeometry:
return gap(LineString([p, q]))
rim: list[Vec] = []
cuts: list[BaseGeometry] = []
for i, j in enumerate(nxt):
(a0, r0, sp), a1 = rays[i], rays[j][0] + (tau if j == 0 else 0)
if sp: # a long thin splinter running out along the crack, snapped off at its base
b = eff[i]
rim += [pt(b * 0.95, a0 - 0.07), pt(r0, a0), pt(b * 0.95, a0 + 0.07)]
cuts += [
cut(pt(12, a0), pt(b * 0.9, a0)),
cut(pt(b * 0.9, a0 - 0.09), pt(b * 0.9, a0 + 0.09)),
]
else:
rim.append(pt(r0, a0))
cuts.append(cut(pt(12, a0), pt(r0 + 8, a0)))
lo = min(eff[i], eff[j])
rim.append(pt(lo * rng.uniform(0.55, 1.05), (a0 + a1) / 2))
sector = Polygon([pt(0, a0), pt(300, a0), pt(300, a1)])
# zero to two cross cracks through this wedge
for rc in rng.uniform(24, max(26, 0.8 * lo), rng.choice([0, 1, 1, 2])):
chord = LineString(
[pt(rc * rng.uniform(0.9, 1.1), a0 - 0.3), pt(rc * rng.uniform(0.9, 1.1), a1 + 0.3)]
)
cuts.append(gap(chord.intersection(sector)))
burst = Polygon(rim).buffer(0)
ca = np.sort(rng.uniform(0, tau, 13))
core = Polygon([pt(8 * rng.uniform(0.7, 1.3), a) for a in ca])
ring = Polygon([pt(15 * rng.uniform(0.7, 1.3), a) for a in ca])
cuts += [core, gap(ring.exterior)]
m = rng.integers(6, 11)
cuts += [
cut(pt(0, a), pt(25, a)).intersection(ring)
for a in (np.arange(m) + rng.uniform(-0.3, 0.3, m)) * tau / m
]
pieces = burst.difference(unary_union(cuts))
centre = Point(c)
cells = sorted((g for g in parts(pieces) if g.area > 3), key=lambda g: -g.distance(centre))
dim = 0
with s.buckets(SHARDS, "fill") as fill:
for g in cells:
if g.within(ring):
if rng.random() < 0.2: # crushed: some fragments are gone
continue
k = rng.choice(2, p=[0.8, 0.2])
elif rng.random() < 0.06:
continue
elif (
g.distance(centre) > 22
and g.area < 200
and dim < 0.2 * len(cells)
and rng.random() < 0.5
):
k, dim = 2, dim + 1 # only snapped-off outer tips go dim
else:
k = rng.choice(2, p=[0.55, 0.45] if g.distance(centre) < 22 else [0.25, 0.75])
fill[k].shape(g)"""A pane struck once: random-walk radial cracks tied by sagging rings, shards cut at the impact."""
import itertools
import math
from collections.abc import Callable
import numpy as np
from numpy.typing import NDArray
from shapely.geometry import LineString, MultiLineString, Point, Polygon
from shapely.geometry.base import BaseGeometry
from shapely.ops import unary_union
from shapely.prepared import prep
from walldye import (
ACCENT,
ACCENT_1,
ACCENT_4,
BG_ALT,
UI,
Canvas,
NpRng,
P,
Path,
Rect,
Vec,
clamp,
design,
ladder,
polar,
)
from walldye.geom import parts
type Line = list[Vec]
N_RADIAL = 14
TONES = ladder((UI, BG_ALT), 4) # crack tone, stepping down with distance from the impact
SHARDS = (ACCENT, ACCENT_1, ACCENT_4) # bright, deep, and the dim snapped-off tips
def walk(
rng: NpRng,
p: Vec,
a: float,
sigma: float,
bounds: Rect,
step: float = 12,
stop: Callable[[Vec, Vec], bool] | None = None,
max_len: float = 4000,
) -> Line:
"""A crack from `p` heading `a` radians, turning by N(0, sigma) each `step`, until it leaves
`bounds`, `stop(last, new)` holds, or it has run `max_len`; the last point is included."""
pts = [p]
for _ in range(int(max_len / step)):
a += rng.normal(0, sigma)
p = polar(p, step, rad=a)
pts.append(p)
if not bounds.contains(p) or (stop and stop(pts[-2], p)):
break
return pts
def at_radius(line: Line, c: Vec, r: float) -> Vec | None:
"""First point along `line` at distance `r` from `c`, or None."""
for p0, p1 in itertools.pairwise(line):
d0, d1 = abs(p0 - c), abs(p1 - c)
if d0 <= r < d1:
return p0 + (p1 - p0) * ((r - d0) / (d1 - d0))
return None
def jagged(rng: NpRng, a: Vec, b: Vec, bow: float, n: int = 8) -> Line:
"""Slightly bowed, slightly wobbly crack from `a` to `b`; `bow` is a fraction of its length."""
normal = (b - a).perp()
return [
a
+ (b - a) * t
+ normal * (bow * 4 * t * (1 - t) + (rng.normal(0, 0.005) if 0 < t < 1 else 0))
for t in np.linspace(0, 1, n)
]
def tone(d: float) -> int:
"""TONES index for a crack `d` from the impact: UI out to 350, BG_ALT from 1050."""
return round(clamp((d - 350) / 700) * 3)
def taper(s: Canvas, c: Vec, lines: list[Line], width: Callable[[float], float]) -> None:
"""Stroke polylines with width and tone falling off with distance from the impact `c`."""
paths: dict[tuple[int, int], Path] = {} # (width in fifths of a unit, tone index)
for line in lines:
prev = None
for a, b in itertools.pairwise(line):
d = abs((a + b) / 2 - c)
k = round(width(d) * 5), tone(d)
if k != prev:
paths.setdefault(k, P()).M(a)
paths[k].L(b)
prev = k
# thinnest first; within a width the faintest first, so brighter cracks cross on top
for (w, t), d in sorted(paths.items(), key=lambda kv: (kv[0][0], -kv[0][1])):
s.stroke(d, TONES[t], w / 5, cap="round", join="round")
@design(aspects="any")
def draw(s: Canvas) -> None:
# upper right on a landscape screen, upper third on a portrait one
c = s.pick(landscape=(1400 / 1920, 380 / 1080), portrait=(0.62, 0.3))
bounds = s.inset(-20)
rng = s.np_rng(11)
base = rng.uniform(0, 2 * math.pi)
angles = [base + (k + rng.uniform(-0.3, 0.3)) * 2 * math.pi / N_RADIAL for k in range(N_RADIAL)]
# one fewer ray into the top edge, where the bar and window titles sit
angles = [a for a in angles if not 245 < math.degrees(a) % 360 < 262]
n = len(angles)
reach = 58 * rng.uniform(0.6, 1.4, n)
splinters = set(rng.choice(n, 3, replace=False).tolist())
reach[list(splinters)] *= 1.6
radials = [
walk(rng, polar(c, r, rad=a), a, math.radians(1.2), bounds) for a, r in zip(angles, reach)
]
rings: list[Line] = []
r = 110
while r < 900:
for i in range(n):
a = at_radius(radials[i], c, r * rng.uniform(0.94, 1.06))
b = at_radius(radials[(i + 1) % n], c, r * rng.uniform(0.94, 1.06))
if a is None or b is None or rng.random() > 0.75 - r / 1100:
continue
if r > 600 and abs(b - a) > 350:
continue
# concentric glass cracks sag slightly toward the impact
sag = rng.uniform(0.005, 0.015)
rings.append(jagged(rng, a, b, sag if (b - a).perp().dot(c - a) > 0 else -sag))
r *= rng.uniform(1.35, 1.55)
net = prep(MultiLineString(radials + rings))
branches: list[Line] = []
while len(branches) < 8:
line = radials[rng.integers(n)]
j = int(rng.integers(len(line) // 10, len(line) // 2))
p0, p1 = line[j], line[j + 1]
a = math.atan2(p1.y - p0.y, p1.x - p0.x) + rng.choice([-1, 1]) * rng.uniform(0.4, 0.8)
pts = walk(
rng,
polar(p0, 4, rad=a),
a,
math.radians(3),
bounds,
8,
lambda p, q: net.intersects(LineString([p, q])),
500,
)
if len(pts) > 8:
branches.append([p0, *pts])
taper(s, c, radials, lambda d: max(0.8, 1.6 - d / 1000))
rings = [q for q in rings if q[0].y + q[-1].y > 300] # keep the strip under the top bar clear
taper(s, c, rings + branches, lambda d: max(0.6, 1.3 - d / 700))
shards(s, rng, c, angles, reach, splinters)
def shards(
s: Canvas,
rng: NpRng,
c: Vec,
angles: list[float],
reach: NDArray[np.float64],
splinters: set[int],
) -> None:
"""Burst of glass at `c`: a crushed core, a ring of fragments, wedges out to an irregular rim."""
tau = 2 * math.pi
rays = sorted(
[(a % tau, r, i in splinters) for i, (a, r) in enumerate(zip(angles, reach))]
+ [(rng.uniform(0, tau), 58 * rng.uniform(0.6, 1.4), False) for _ in range(8)]
)
eff = [r / 1.6 if sp else r for _, r, sp in rays] # body radius, splinters excluded
nxt = [*range(1, len(rays)), 0]
def pt(r: float, a: float) -> Vec:
return polar(c, r, rad=a)
def gap(line: BaseGeometry) -> BaseGeometry: # every crack gets its own width
return line.buffer(rng.uniform(0.8, 1.6) / 2, cap_style="flat")
def cut(p: Vec, q: Vec) -> BaseGeometry:
return gap(LineString([p, q]))
rim: list[Vec] = []
cuts: list[BaseGeometry] = []
for i, j in enumerate(nxt):
(a0, r0, sp), a1 = rays[i], rays[j][0] + (tau if j == 0 else 0)
if sp: # a long thin splinter running out along the crack, snapped off at its base
b = eff[i]
rim += [pt(b * 0.95, a0 - 0.07), pt(r0, a0), pt(b * 0.95, a0 + 0.07)]
cuts += [
cut(pt(12, a0), pt(b * 0.9, a0)),
cut(pt(b * 0.9, a0 - 0.09), pt(b * 0.9, a0 + 0.09)),
]
else:
rim.append(pt(r0, a0))
cuts.append(cut(pt(12, a0), pt(r0 + 8, a0)))
lo = min(eff[i], eff[j])
rim.append(pt(lo * rng.uniform(0.55, 1.05), (a0 + a1) / 2))
sector = Polygon([pt(0, a0), pt(300, a0), pt(300, a1)])
# zero to two cross cracks through this wedge
for rc in rng.uniform(24, max(26, 0.8 * lo), rng.choice([0, 1, 1, 2])):
chord = LineString(
[pt(rc * rng.uniform(0.9, 1.1), a0 - 0.3), pt(rc * rng.uniform(0.9, 1.1), a1 + 0.3)]
)
cuts.append(gap(chord.intersection(sector)))
burst = Polygon(rim).buffer(0)
ca = np.sort(rng.uniform(0, tau, 13))
core = Polygon([pt(8 * rng.uniform(0.7, 1.3), a) for a in ca])
ring = Polygon([pt(15 * rng.uniform(0.7, 1.3), a) for a in ca])
cuts += [core, gap(ring.exterior)]
m = rng.integers(6, 11)
cuts += [
cut(pt(0, a), pt(25, a)).intersection(ring)
for a in (np.arange(m) + rng.uniform(-0.3, 0.3, m)) * tau / m
]
pieces = burst.difference(unary_union(cuts))
centre = Point(c)
cells = sorted((g for g in parts(pieces) if g.area > 3), key=lambda g: -g.distance(centre))
dim = 0
with s.buckets(SHARDS, "fill") as fill:
for g in cells:
if g.within(ring):
if rng.random() < 0.2: # crushed: some fragments are gone
continue
k = rng.choice(2, p=[0.8, 0.2])
elif rng.random() < 0.06:
continue
elif (
g.distance(centre) > 22
and g.area < 200
and dim < 0.2 * len(cells)
and rng.random() < 0.5
):
k, dim = 2, dim + 1 # only snapped-off outer tips go dim
else:
k = rng.choice(2, p=[0.55, 0.45] if g.distance(centre) < 22 else [0.25, 0.75])
fill[k].shape(g)
Run it yourself
$ git clone https://github.com/nickolaj-jepsen/walldye && cd walldye$ uv run walldye render fracture --theme fireproof -o fracture-fireproof-16x9.svg