///|
/// Samples a path at approximately uniform arc-length intervals.
pub fn sample_path(path : Path, samples : Int) -> Array[Vec2] {
let control = path.control_points()
if samples <= 0 || control.length() == 0 {
[]
} else if control.length() == 1 {
let result = []
for _ in 0.. Vec2 {
let mut walked = 0.0
for i in 0..<(points.length() - 1) {
let left = points[i]
let right = points[i + 1]
let length = left.distance(right)
if target <= walked + length {
let amount = if length == 0.0 { 0.0 } else { (target - walked) / length }
return left.lerp(right, amount)
}
walked += length
}
points[points.length() - 1]
}
///|
/// Returns points that satisfy a deterministic blue-noise-like minimum distance.
pub fn poisson_disc(
width : Double,
height : Double,
minimum_distance : Double,
candidates : Int,
seed : UInt,
) -> Array[Vec2] {
let result : Array[Vec2] = []
if minimum_distance > 0.0 && candidates > 0 {
let random = Random::new(seed)
let attempts = candidates * 12
for _ in 0..= candidates {
break
}
let point = Vec2::new(random.range(0.0, width), random.range(0.0, height))
let mut valid = true
for existing in result {
if existing.distance(point) < minimum_distance {
valid = false
}
}
if valid {
result.push(point)
}
}
}
result
}
///|
/// Creates a weighted random walk in a bounded canvas.
pub fn random_walk(
start : Vec2,
steps : Int,
step_length : Double,
sampler : ValueNoise,
bounds : Rect,
) -> Array[Vec2] {
let result = [start]
let mut current = start
for i in 0.. Double {
match mode {
Nearest => field.get(x.to_int(), y.to_int()).unwrap_or(0.0)
Bilinear => {
let x0 = x.to_int()
let y0 = y.to_int()
let fx = x - x0.to_double()
let fy = y - y0.to_double()
let a = field.get(x0, y0).unwrap_or(0.0)
let b = field.get(x0 + 1, y0).unwrap_or(a)
let c = field.get(x0, y0 + 1).unwrap_or(a)
let d = field.get(x0 + 1, y0 + 1).unwrap_or(a)
a + (b - a) * fx + (c - a) * fy + (a - b - c + d) * fx * fy
}
CatmullRom => sample_field(field, x, y, Bilinear)
}
}
///|
pub fn contour_points(
field : ScalarField,
threshold : Double,
) -> Array[Array[Vec2]] {
let contours = []
if field.width > 1 && field.height > 1 {
for y in 0..<(field.height - 1) {
for x in 0..<(field.width - 1) {
let top = field.get(x, y).unwrap_or(0.0)
let right = field.get(x + 1, y).unwrap_or(0.0)
let bottom = field.get(x, y + 1).unwrap_or(0.0)
let left = field.get(x, y + 1).unwrap_or(0.0)
let points : Array[Vec2] = []
if (top < threshold) != (right < threshold) {
points.push(Vec2::new(x.to_double() + 0.5, y.to_double()))
}
if (right < threshold) != (bottom < threshold) {
points.push(Vec2::new(x.to_double() + 1.0, y.to_double() + 0.5))
}
if (bottom < threshold) != (left < threshold) {
points.push(Vec2::new(x.to_double() + 0.5, y.to_double() + 1.0))
}
if points.length() > 0 {
contours.push(points)
}
}
}
}
contours
}