#![allow(dead_code)]
use std::f32::consts::PI;
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub struct EyebrowArchParams {
pub arch_height: f32,
pub peak_position: f32,
pub thickness: f32,
pub tilt: f32,
pub inner_height: f32,
pub outer_height: f32,
}
impl Default for EyebrowArchParams {
fn default() -> Self {
Self {
arch_height: 0.5,
peak_position: 0.65,
thickness: 0.5,
tilt: 0.0,
inner_height: 0.0,
outer_height: 0.0,
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct EyebrowArchResult {
pub displacements: Vec<(usize, f32)>,
pub curvature: f32,
}
#[allow(dead_code)]
pub fn arch_curve(t: f32, peak_pos: f32, arch_height: f32) -> f32 {
let t = t.clamp(0.0, 1.0);
let peak_pos = peak_pos.clamp(0.1, 0.9);
let a = if t <= peak_pos {
t / peak_pos
} else {
(1.0 - t) / (1.0 - peak_pos)
};
arch_height * a * 0.01
}
#[allow(dead_code)]
pub fn tilt_offset(t: f32, tilt: f32) -> f32 {
let t = t.clamp(0.0, 1.0);
(t - 0.5) * tilt.sin() * 0.005
}
#[allow(dead_code)]
pub fn endpoint_offset(t: f32, inner: f32, outer: f32) -> f32 {
let t = t.clamp(0.0, 1.0);
let inner_w = 1.0 - t;
let outer_w = t;
let inner_fade = if t < 0.3 { 1.0 - t / 0.3 } else { 0.0 };
let outer_fade = if t > 0.7 { (t - 0.7) / 0.3 } else { 0.0 };
let _ = inner_w;
let _ = outer_w;
inner * inner_fade * 0.005 + outer * outer_fade * 0.005
}
#[allow(dead_code)]
pub fn thickness_displacement(vertical_offset: f32, thickness: f32) -> f32 {
let v = vertical_offset.abs();
let half_thick = thickness * 0.005;
if v < half_thick {
1.0 - v / half_thick
} else {
0.0
}
}
#[allow(dead_code)]
pub fn evaluate_eyebrow_arch(
brow_coords: &[(f32, f32)],
params: &EyebrowArchParams,
) -> EyebrowArchResult {
let mut displacements = Vec::with_capacity(brow_coords.len());
for (i, &(t, v_off)) in brow_coords.iter().enumerate() {
let arch = arch_curve(t, params.peak_position, params.arch_height);
let tilt = tilt_offset(t, params.tilt);
let endpoint = endpoint_offset(t, params.inner_height, params.outer_height);
let thick_w = thickness_displacement(v_off, params.thickness);
let dy = (arch + tilt + endpoint) * thick_w;
if dy.abs() > 1e-7 {
displacements.push((i, dy));
}
}
let inner_h = arch_curve(0.0, params.peak_position, params.arch_height);
let outer_h = arch_curve(1.0, params.peak_position, params.arch_height);
let peak_h = arch_curve(params.peak_position, params.peak_position, params.arch_height);
let curvature = peak_h - (inner_h + outer_h) * 0.5;
EyebrowArchResult {
displacements,
curvature,
}
}
#[allow(dead_code)]
pub fn blend_eyebrow_arch(a: &EyebrowArchParams, b: &EyebrowArchParams, t: f32) -> EyebrowArchParams {
let t = t.clamp(0.0, 1.0);
let inv = 1.0 - t;
EyebrowArchParams {
arch_height: a.arch_height * inv + b.arch_height * t,
peak_position: a.peak_position * inv + b.peak_position * t,
thickness: a.thickness * inv + b.thickness * t,
tilt: a.tilt * inv + b.tilt * t,
inner_height: a.inner_height * inv + b.inner_height * t,
outer_height: a.outer_height * inv + b.outer_height * t,
}
}
#[allow(dead_code)]
pub fn mirror_eyebrow_arch(params: &EyebrowArchParams) -> EyebrowArchParams {
EyebrowArchParams {
peak_position: 1.0 - params.peak_position,
tilt: -params.tilt,
inner_height: params.outer_height,
outer_height: params.inner_height,
..params.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_params() {
let p = EyebrowArchParams::default();
assert!((0.0..=1.0).contains(&p.arch_height));
assert!((0.0..=1.0).contains(&p.peak_position));
}
#[test]
fn test_arch_curve_peak() {
let v = arch_curve(0.65, 0.65, 1.0);
assert!(v > 0.0);
}
#[test]
fn test_arch_curve_endpoints() {
let inner = arch_curve(0.0, 0.65, 1.0);
let outer = arch_curve(1.0, 0.65, 1.0);
assert!(inner.abs() < 1e-6);
assert!(outer.abs() < 1e-6);
}
#[test]
fn test_tilt_offset_zero() {
let o = tilt_offset(0.5, 0.0);
assert!(o.abs() < 1e-6);
}
#[test]
fn test_tilt_offset_symmetry() {
let left = tilt_offset(0.0, 0.5);
let right = tilt_offset(1.0, 0.5);
assert!((left + right).abs() < 1e-6);
}
#[test]
fn test_thickness_inside() {
let w = thickness_displacement(0.0, 0.5);
assert!((w - 1.0).abs() < 1e-5);
}
#[test]
fn test_thickness_outside() {
let w = thickness_displacement(1.0, 0.5);
assert!(w.abs() < 1e-5);
}
#[test]
fn test_evaluate_empty() {
let r = evaluate_eyebrow_arch(&[], &EyebrowArchParams::default());
assert!(r.displacements.is_empty());
}
#[test]
fn test_mirror_peak_position() {
let p = EyebrowArchParams { peak_position: 0.7, ..Default::default() };
let m = mirror_eyebrow_arch(&p);
assert!((m.peak_position - 0.3).abs() < 1e-5);
}
#[test]
fn test_blend_eyebrow_arch() {
let a = EyebrowArchParams { arch_height: 0.0, ..Default::default() };
let b = EyebrowArchParams { arch_height: 1.0, ..Default::default() };
let r = blend_eyebrow_arch(&a, &b, 0.5);
assert!((r.arch_height - 0.5).abs() < 1e-6);
}
}