///|
/// A report about the solid mask.
pub(all) struct MaskReport {
total_cells : Int
solid_cells : Int
fluid_cells : Int
solid_fraction : Double
} derive(Debug)
///|
/// A row-major solid-cell mask used by advanced simulations.
pub(all) struct DomainMask {
size : Size
solid : Array[Bool]
} derive(Debug)
///|
/// Allocate an empty mask.
pub fn DomainMask::new(size~ : Size) -> DomainMask {
{ size, solid: Array::make(size.width.max(0) * size.height.max(0), false) }
}
///|
/// Set a solid flag when coordinates are inside the mask.
pub fn DomainMask::set(
self : DomainMask,
x~ : Int,
y~ : Int,
solid~ : Bool,
) -> Unit {
if x >= 0 && y >= 0 && x < self.size.width && y < self.size.height {
self.solid[y * self.size.width + x] = solid
}
}
///|
/// Read a solid flag; outside cells are treated as solid.
pub fn DomainMask::is_solid(self : DomainMask, x : Int, y : Int) -> Bool {
if x >= 0 && y >= 0 && x < self.size.width && y < self.size.height {
self.solid[y * self.size.width + x]
} else {
true
}
}
///|
/// Count solid cells.
pub fn DomainMask::solid_count(self : DomainMask) -> Int {
let mut count = 0
for solid in self.solid {
if solid {
count += 1
}
}
count
}
///|
/// Count fluid cells inside the mask.
pub fn DomainMask::fluid_count(self : DomainMask) -> Int {
self.size.width.max(0) * self.size.height.max(0) - self.solid_count()
}
///|
/// Return a copy of the mask.
pub fn DomainMask::copy(self : DomainMask) -> DomainMask {
{ size: self.size, solid: self.solid.copy() }
}
///|
/// Read a mask as empty outside its finite raster for morphology operations.
fn mask_solid_or_false(mask : DomainMask, x : Int, y : Int) -> Bool {
x >= 0 &&
y >= 0 &&
x < mask.size.width &&
y < mask.size.height &&
mask.solid[y * mask.size.width + x]
}
///|
/// Grow solid cells by a four-neighbor stencil.
pub fn DomainMask::dilate(
self : DomainMask,
iterations? : Int = 1,
) -> DomainMask {
let result = self.copy()
for _ in 0.. DomainMask {
let result = self.copy()
for _ in 0.. Double {
let mut total = 0
for y in 0.. Point {
let mut x = 0.0
let mut y = 0.0
let count = self.solid_count()
if count == 0 {
Point::new(x=0.0, y=0.0)
} else {
for row in 0.. DomainMask {
let result = DomainMask::new(size=left.size)
for y in 0.. DomainMask {
let result = DomainMask::new(size=left.size)
for y in 0.. String {
let builder = StringBuilder(
size_hint=(self.size.width + 1) * self.size.height,
)
for y in 0.. Unit {
if radius >= 0.0 {
let r2 = radius * radius
for y in 0.. Unit {
for y in 0.. MaskReport {
let total = self.size.width.max(0) * self.size.height.max(0)
let solid = self.solid_count()
{
total_cells: total,
solid_cells: solid,
fluid_cells: total - solid,
solid_fraction: safe_divide(
solid.to_double(),
total.to_double(),
fallback=0.0,
),
}
}
///|
/// Rasterize a shape into a new mask.
pub fn rasterize_shape(size~ : Size, shape~ : Shape) -> DomainMask {
let mask = DomainMask::new(size~)
for y in 0.. Array[ConnectedComponent] {
let visited = Array::make(self.solid.length(), false)
let components = Array::new()
for y in 0..= 0 &&
neighbor.y >= 0 &&
neighbor.x < self.size.width &&
neighbor.y < self.size.height {
let index = neighbor.y * self.size.width + neighbor.x
if self.solid[index] && !visited[index] {
visited[index] = true
queue.push(neighbor)
}
}
}
}
components.push({ size: cells.length(), cells })
}
}
}
components
}