///|
/// Deterministic procedural scenes used by examples, tests, and demos.
pub(all) enum PatternKind {
Checker
Stripes
Rings
Waves
Noise
Gradient
} derive(Eq, Debug)
///|
pub(all) struct PatternOptions {
kind : PatternKind
scale : Int
seed : Int
first : Rgba
second : Rgba
} derive(Debug)
///|
pub fn PatternOptions::default() -> PatternOptions {
{
kind: Checker,
scale: 8,
seed: 1,
first: rgb(24, 28, 42),
second: rgb(230, 180, 60),
}
}
///|
pub fn PatternOptions::kind(
self : PatternOptions,
value : PatternKind,
) -> PatternOptions {
{ ..self, kind: value }
}
///|
pub fn PatternOptions::scale(
self : PatternOptions,
value : Int,
) -> PatternOptions {
{ ..self, scale: if value < 1 { 1 } else { value } }
}
///|
pub fn PatternOptions::seed(
self : PatternOptions,
value : Int,
) -> PatternOptions {
{ ..self, seed: value }
}
///|
pub fn PatternOptions::colors(
self : PatternOptions,
first : Rgba,
second : Rgba,
) -> PatternOptions {
{ ..self, first, second }
}
///|
fn pattern_noise(x : Int, y : Int, seed : Int) -> Int {
let mut value = x * 374761393 + y * 668265263 + seed * 1442695041
value = (value ^ (value >> 13)) * 1274126177
let result = value ^ (value >> 16)
let positive = if result < 0 { -result } else { result }
positive % 256
}
///|
fn mix_pattern_color(
first : Rgba,
second : Rgba,
numerator : Int,
denominator : Int,
) -> Rgba {
let safe = if numerator < 0 {
0
} else if numerator > denominator {
denominator
} else {
numerator
}
let inverse = denominator - safe
rgba(
((first.red().to_int() * inverse + second.red().to_int() * safe) /
denominator).to_byte(),
((first.green().to_int() * inverse + second.green().to_int() * safe) /
denominator).to_byte(),
((first.blue().to_int() * inverse + second.blue().to_int() * safe) /
denominator).to_byte(),
((first.alpha().to_int() * inverse + second.alpha().to_int() * safe) /
denominator).to_byte(),
)
}
///|
/// Render a deterministic procedural pattern at a requested size.
pub fn procedural_pattern(
width : Int,
height : Int,
options? : PatternOptions = PatternOptions::default(),
) -> Canvas {
let safe_width = if width < 0 { 0 } else { width }
let safe_height = if height < 0 { 0 } else { height }
let safe_scale = if options.scale < 1 { 1 } else { options.scale }
let result = Canvas::new(
width=safe_width,
height=safe_height,
fill=options.first,
)
for y in 0..
if (x / safe_scale + y / safe_scale) % 2 == 0 {
0
} else {
1000
}
Stripes => if x / safe_scale % 2 == 0 { 0 } else { 1000 }
Rings => {
let dx = x - safe_width / 2
let dy = y - safe_height / 2
if (dx * dx + dy * dy) / safe_scale % 2 == 0 {
0
} else {
1000
}
}
Waves => (x * 31 + y * 17) / safe_scale % 20 * 1000 / 19
Noise => pattern_noise(x, y, options.seed) * 1000 / 255
Gradient =>
if safe_width <= 1 {
0
} else {
x * 1000 / (safe_width - 1)
}
}
result.pixels[x + y * safe_width] = mix_pattern_color(
options.first,
options.second,
value,
1000,
)
}
}
result
}
///|
/// Build a palette-friendly animation by shifting a pattern phase.
pub fn procedural_animation(
width : Int,
height : Int,
frames : Int,
options : PatternOptions,
delay_cs? : Int = 6,
) -> Animation {
let result = Animation::new(width~, height~)
let count = if frames < 1 { 1 } else { frames }
for index in 0.. Canvas {
let safe_width = if width < 4 { 4 } else { width }
let safe_height = if height < 4 { 4 } else { height }
let result = Canvas::new(
width=safe_width,
height=safe_height,
fill=rgb(16, 16, 16),
)
let cell_width = safe_width / 2
let cell_height = safe_height / 3
let kinds = [Checker, Stripes, Rings, Waves, Noise, Gradient]
for index, kind in kinds {
let row = index / 2
let column = index % 2
let cell = procedural_pattern(
cell_width,
cell_height,
options=PatternOptions::default().kind(kind).scale(4 + index),
)
ignore(
result.blit(
cell,
destination_x=column * cell_width,
destination_y=row * cell_height,
),
)
}
result
}
///|
/// Apply a phase-shifted pattern behind an existing animation.
pub fn Animation::pattern_background(
self : Animation,
options : PatternOptions,
) -> Animation {
let result = Animation::new(width=self.width, height=self.height)
.with_loop_count(loop_count=self.loop_count)
.with_background(background=self.background)
for index, frame in self.frames {
let background = procedural_pattern(
self.width,
self.height,
options=options.seed(options.seed + index),
)
let composed = background.overlay(frame.canvas(), left=0, top=0)
ignore(result.add_frame(frame.with_canvas(composed)))
}
result
}