///|
/// A named batch of motifs ready to add to a composition layer.
pub(all) struct PatternBatch {
name : String
motifs : Array[Motif]
bounds : Rect
seed : UInt
} derive(Debug)
///|
pub fn PatternBatch::new(
name : String,
bounds : Rect,
seed : UInt,
) -> PatternBatch {
{ name, motifs: [], bounds, seed }
}
///|
pub fn PatternBatch::add(self : PatternBatch, motif : Motif) -> PatternBatch {
self.motifs.push(motif)
self
}
///|
pub fn PatternBatch::append_all(
self : PatternBatch,
motifs : Array[Motif],
) -> PatternBatch {
for motif in motifs {
self.motifs.push(motif)
}
self
}
///|
pub fn PatternBatch::count(self : PatternBatch) -> Int {
self.motifs.length()
}
///|
pub fn PatternBatch::translate(
self : PatternBatch,
offset : Vec2,
) -> PatternBatch {
let result = PatternBatch::new(self.name, self.bounds, self.seed)
for motif in self.motifs {
let _ = result.add(
motif.placed(
motif.anchor.x + offset.x,
motif.anchor.y + offset.y,
motif.scale,
motif.rotation,
),
)
}
result
}
///|
pub fn PatternBatch::to_layer(
self : PatternBatch,
opacity : Double,
) -> PatternLayer {
let layer = PatternLayer::new(self.name, opacity~)
for motif in self.motifs {
layer.add(motif)
}
layer
}
///|
fn batch(
name : String,
bounds : Rect,
seed : UInt,
motifs : Array[Motif],
) -> PatternBatch {
PatternBatch::new(name, bounds, seed).append_all(motifs)
}
///|
fn make_motif(
name : String,
path : Path,
color : Color,
width : Double,
opacity : Double,
) -> Motif {
Motif::new(
name,
path,
SvgStyle::new(
fill=color.to_hex(),
stroke=color.to_hex(),
stroke_width=width,
opacity~,
),
)
}
///|
/// Generates an evenly spaced dot grid with a scalar size field.
pub fn catalog_dot_field(
bounds : Rect,
columns : Int,
rows : Int,
minimum : Double,
maximum : Double,
palette : Palette,
seed : UInt,
) -> PatternBatch {
let motifs = []
let random = Random::new(seed)
if columns > 0 && rows > 0 {
for row in 0.. PatternBatch {
let bounds = Rect::new(
center.x - outer_radius,
center.y - outer_radius,
outer_radius * 2.0,
outer_radius * 2.0,
)
let motifs = []
if rings > 0 && petals > 0 {
for ring in 0.. PatternBatch {
let motifs = []
if count > 0 {
for index in 0.. PatternBatch {
let bounds = Rect::new(
center.x - radius,
center.y - radius,
radius * 2.0,
radius * 2.0,
)
let motifs = []
let safe_spacing = spacing.max(0.1)
if lines > 0 {
for index in 0.. PatternBatch {
let bounds = Rect::new(
center.x - radius,
center.y - radius,
radius * 2.0,
radius * 2.0,
)
let motifs = []
if rays > 0 {
for index in 0.. PatternBatch {
let motifs = []
if count > 0 {
for index in 0.. 0.0 && frame.height > 0.0 {
let color = palette
.at((index + seed.reinterpret_as_int()) % palette.colors.length())
.unwrap_or(Color::rgb8(0, 0, 0))
motifs.push(
make_motif(
"catalog-frame",
Path::rounded_rect(
frame.width,
frame.height,
inset.max(0.0),
x=frame.x,
y=frame.y,
),
color,
1.0 + index.to_double() * 0.1,
0.8,
),
)
}
}
}
batch("nested-frames", bounds, seed, motifs)
}
///|
/// Generates diagonal hatch bands clipped conceptually to a rectangular canvas.
pub fn catalog_hatch_bundle(
bounds : Rect,
spacing : Double,
angle : Double,
count : Int,
palette : Palette,
seed : UInt,
) -> PatternBatch {
let motifs = []
let safe_spacing = spacing.max(0.1)
if count > 0 {
let diagonal = bounds.width + bounds.height
for index in 0.. PatternBatch {
let points = poisson_disc(
bounds.width,
bounds.height,
point_radius.max(0.1) * 3.0,
count,
seed,
)
let motifs = []
for index, local_point in points {
let point = local_point.add(Vec2::new(bounds.x, bounds.y))
let color = palette
.at(index % palette.colors.length())
.unwrap_or(Color::rgb8(255, 255, 255))
motifs.push(
make_motif(
"catalog-star",
Path::star(point.x, point.y, 5, point_radius, point_radius * 2.0),
color,
0.6,
1.0,
),
)
for previous in 0.. PatternBatch {
let motifs = []
if columns > 0 && rows > 0 {
for row in 0.. PatternBatch {
let random = Random::new(seed)
let motifs = []
if count > 0 {
for index in 0.. PatternBatch {
let radius = minimum_radius + rings.max(0).to_double() * radius_step.max(0.0)
let bounds = Rect::new(
center.x - radius,
center.y - radius,
radius * 2.0,
radius * 2.0,
)
let motifs = []
if rings > 0 && satellites > 0 {
for ring in 1..<=rings {
let orbit = minimum_radius + (ring - 1).to_double() * radius_step
motifs.push(
make_motif(
"catalog-orbit",
Path::circle(orbit, cx=center.x, cy=center.y),
palette
.at(ring % palette.colors.length())
.unwrap_or(Color::rgb8(0, 0, 0)),
0.7,
0.6,
),
)
for satellite in 0.. PatternBatch {
let radius = end_radius.max(start_radius)
let bounds = Rect::new(
center.x - radius,
center.y - radius,
radius * 2.0,
radius * 2.0,
)
let motifs = []
if count > 0 {
for index in 0.. PatternBatch {
let random = Random::new(seed)
let motifs = []
if count > 0 {
let step = bounds.width / count.to_double()
for index in 0.. PatternBatch {
let motifs = []
let points = [
Vec2::new(bounds.x + gap, bounds.y + gap),
Vec2::new(bounds.right() - gap, bounds.y + gap),
Vec2::new(bounds.right() - gap, bounds.bottom() - gap),
Vec2::new(bounds.x + gap, bounds.bottom() - gap),
]
for point in points {
let horizontal = Path::new()
.move_to(point.x - size, point.y)
.line_to(point.x + size, point.y)
let vertical = Path::new()
.move_to(point.x, point.y - size)
.line_to(point.x, point.y + size)
motifs.push(make_motif("catalog-mark", horizontal, color, 0.7, 0.8))
motifs.push(make_motif("catalog-mark", vertical, color, 0.7, 0.8))
}
batch("registration-marks", bounds, seed, motifs)
}
///|
/// Generates a repeated chevron border around a rectangular canvas.
pub fn catalog_chevron_border(
bounds : Rect,
spacing : Double,
depth : Double,
color : Color,
seed : UInt,
) -> PatternBatch {
let motifs = []
let step = spacing.max(1.0)
let count = ((bounds.width + bounds.height) * 2.0 / step).to_int()
for index in 0.. PatternBatch {
let motifs = []
if columns > 0 && rows > 0 {
for row in 0.. PatternBatch {
let motifs = []
if columns > 0 && rows > 0 {
let width = bounds.width / columns.to_double()
let height = bounds.height / rows.to_double()
for row in 0.. PatternBatch {
let motifs = []
if columns > 0 && rows > 0 {
for row in 0.. String {
"name=\{batch.name},motifs=\{batch.motifs.length()},bounds=\{batch.bounds.width}x\{batch.bounds.height},seed=\{batch.seed}"
}
///|
/// Adds a batch to a composition with one stable layer name.
pub fn add_pattern_batch(
composition : Composition,
batch : PatternBatch,
opacity : Double,
) -> Unit {
composition.add_layer(batch.to_layer(opacity))
}