use super::math::{Vector2, degrees_to_radian};
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum DeformerTransform<'a> {
Rotation {
angle_degrees: f32,
scale: f32,
translation: Vector2,
flip_x: bool,
flip_y: bool,
},
Warp {
grid: &'a [Vector2],
cols: usize,
rows: usize,
interpolation: WarpInterpolation,
},
}
pub fn rotation_deformer_transform_point(
point: Vector2,
angle_degrees: f32,
scale: f32,
translation: Vector2,
flip_x: bool,
flip_y: bool,
) -> Vector2 {
let theta = degrees_to_radian(angle_degrees);
let cos = theta.cos();
let sin = theta.sin();
let sign_x = if flip_x { -1.0 } else { 1.0 };
let sign_y = if flip_y { -1.0 } else { 1.0 };
let m00 = cos * scale * sign_x;
let m01 = -sin * scale * sign_y;
let m10 = sin * scale * sign_x;
let m11 = cos * scale * sign_y;
Vector2::new(
m00 * point.x() + m01 * point.y() + translation.x(),
m10 * point.x() + m11 * point.y() + translation.y(),
)
}
pub fn transform_art_mesh_vertices_by_deformers(
vertices: &[Vector2],
transforms: &[DeformerTransform<'_>],
) -> Option<Vec<Vector2>> {
let mut out = vertices.to_vec();
for transform in transforms {
for vertex in &mut out {
*vertex = match *transform {
DeformerTransform::Rotation {
angle_degrees,
scale,
translation,
flip_x,
flip_y,
} => rotation_deformer_transform_point(
*vertex,
angle_degrees,
scale,
translation,
flip_x,
flip_y,
),
DeformerTransform::Warp {
grid,
cols,
rows,
interpolation,
} => warp_deformer_transform_target(*vertex, grid, cols, rows, interpolation)?,
};
}
}
Some(out)
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum WarpInterpolation {
Quad,
Triangle,
}
pub fn warp_deformer_transform_inside(
local_point: Vector2,
grid: &[Vector2],
cols: usize,
rows: usize,
interpolation: WarpInterpolation,
) -> Option<Vector2> {
if !(0.0..1.0).contains(&local_point.x()) || !(0.0..1.0).contains(&local_point.y()) {
return None;
}
let stride = cols.checked_add(1)?;
let required = stride.checked_mul(rows.checked_add(1)?)?;
if grid.len() < required {
return None;
}
let u = local_point.x() * cols as f32;
let v = local_point.y() * rows as f32;
let i = u.trunc() as usize;
let j = v.trunc() as usize;
let s = u - i as f32;
let t = v - j as f32;
if i >= cols || j >= rows {
return None;
}
let c00 = grid[j * stride + i];
let c10 = grid[j * stride + i + 1];
let c01 = grid[(j + 1) * stride + i];
let c11 = grid[(j + 1) * stride + i + 1];
Some(match interpolation {
WarpInterpolation::Quad => bilinear_cell(s, t, c00, c10, c01, c11),
WarpInterpolation::Triangle => triangle_cell(s, t, c00, c10, c01, c11),
})
}
pub fn warp_deformer_transform_target(
local_point: Vector2,
grid: &[Vector2],
cols: usize,
rows: usize,
interpolation: WarpInterpolation,
) -> Option<Vector2> {
if (0.0..1.0).contains(&local_point.x()) && (0.0..1.0).contains(&local_point.y()) {
return warp_deformer_transform_inside(local_point, grid, cols, rows, interpolation);
}
let stride = cols.checked_add(1)?;
let required = stride.checked_mul(rows.checked_add(1)?)?;
if cols == 0 || rows == 0 || grid.len() < required {
return None;
}
let u = local_point.x() * cols as f32;
let v = local_point.y() * rows as f32;
let i = outside_cell_index(u, cols)?;
let j = outside_cell_index(v, rows)?;
let s = u - i as f32;
let t = v - j as f32;
let c00 = grid[j * stride + i];
let c10 = grid[j * stride + i + 1];
let c01 = grid[(j + 1) * stride + i];
let c11 = grid[(j + 1) * stride + i + 1];
Some(match interpolation {
WarpInterpolation::Quad => bilinear_cell(s, t, c00, c10, c01, c11),
WarpInterpolation::Triangle => triangle_cell(s, t, c00, c10, c01, c11),
})
}
fn outside_cell_index(value: f32, cell_count: usize) -> Option<usize> {
if !value.is_finite() {
return None;
}
let max_index = cell_count.checked_sub(1)?;
let index = value.floor().clamp(0.0, max_index as f32);
Some(index as usize)
}
fn bilinear_cell(
s: f32,
t: f32,
c00: Vector2,
c10: Vector2,
c01: Vector2,
c11: Vector2,
) -> Vector2 {
let w00 = (1.0 - s) * (1.0 - t);
let w10 = s * (1.0 - t);
let w01 = (1.0 - s) * t;
let w11 = s * t;
Vector2::new(
w00 * c00.x() + w10 * c10.x() + w01 * c01.x() + w11 * c11.x(),
w00 * c00.y() + w10 * c10.y() + w01 * c01.y() + w11 * c11.y(),
)
}
fn triangle_cell(
s: f32,
t: f32,
c00: Vector2,
c10: Vector2,
c01: Vector2,
c11: Vector2,
) -> Vector2 {
if s + t <= 1.0 {
return Vector2::new(
c00.x() + (c10.x() - c00.x()) * s + (c01.x() - c00.x()) * t,
c00.y() + (c10.y() - c00.y()) * s + (c01.y() - c00.y()) * t,
);
}
let a = 1.0 - s;
let b = 1.0 - t;
Vector2::new(
c11.x() + (c01.x() - c11.x()) * a + (c10.x() - c11.x()) * b,
c11.y() + (c01.y() - c11.y()) * a + (c10.y() - c11.y()) * b,
)
}