#![allow(dead_code)]
use std::f32::consts::FRAC_1_SQRT_2;
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct FaceWidthV2Config {
pub diagonal_factor: f32,
pub link_regions: bool,
}
impl Default for FaceWidthV2Config {
fn default() -> Self {
FaceWidthV2Config {
diagonal_factor: FRAC_1_SQRT_2,
link_regions: false,
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct FaceWidthV2State {
bizygomatic: f32,
bigonial: f32,
temporal: f32,
config: FaceWidthV2Config,
}
pub fn default_face_width_v2_config() -> FaceWidthV2Config {
FaceWidthV2Config::default()
}
pub fn new_face_width_v2_state(config: FaceWidthV2Config) -> FaceWidthV2State {
FaceWidthV2State {
bizygomatic: 0.5,
bigonial: 0.5,
temporal: 0.5,
config,
}
}
pub fn fw2_set_bizygomatic(state: &mut FaceWidthV2State, v: f32) {
state.bizygomatic = v.clamp(0.0, 1.0);
if state.config.link_regions {
state.bigonial = state.bizygomatic;
}
}
pub fn fw2_set_bigonial(state: &mut FaceWidthV2State, v: f32) {
state.bigonial = v.clamp(0.0, 1.0);
}
pub fn fw2_set_temporal(state: &mut FaceWidthV2State, v: f32) {
state.temporal = v.clamp(0.0, 1.0);
}
pub fn fw2_reset(state: &mut FaceWidthV2State) {
state.bizygomatic = 0.5;
state.bigonial = 0.5;
state.temporal = 0.5;
}
pub fn fw2_is_neutral(state: &FaceWidthV2State) -> bool {
(state.bizygomatic - 0.5).abs() < 1e-5
&& (state.bigonial - 0.5).abs() < 1e-5
&& (state.temporal - 0.5).abs() < 1e-5
}
pub fn fw2_average_width(state: &FaceWidthV2State) -> f32 {
(state.bizygomatic * 0.5 + state.bigonial * 0.3 + state.temporal * 0.2).clamp(0.0, 1.0)
}
pub fn fw2_to_weights(state: &FaceWidthV2State) -> [f32; 3] {
[state.bizygomatic, state.bigonial, state.temporal]
}
pub fn fw2_blend(a: &FaceWidthV2State, b: &FaceWidthV2State, t: f32) -> FaceWidthV2State {
let t = t.clamp(0.0, 1.0);
FaceWidthV2State {
bizygomatic: a.bizygomatic + (b.bizygomatic - a.bizygomatic) * t,
bigonial: a.bigonial + (b.bigonial - a.bigonial) * t,
temporal: a.temporal + (b.temporal - a.temporal) * t,
config: a.config.clone(),
}
}
pub fn fw2_to_json(state: &FaceWidthV2State) -> String {
format!(
r#"{{"bizygomatic":{:.4},"bigonial":{:.4},"temporal":{:.4}}}"#,
state.bizygomatic, state.bigonial, state.temporal
)
}
#[cfg(test)]
mod tests {
use super::*;
fn make() -> FaceWidthV2State {
new_face_width_v2_state(default_face_width_v2_config())
}
#[test]
fn neutral_on_creation() {
assert!(fw2_is_neutral(&make()));
}
#[test]
fn set_bizygomatic_clamps() {
let mut s = make();
fw2_set_bizygomatic(&mut s, 5.0);
assert!((s.bizygomatic - 1.0).abs() < 1e-5);
}
#[test]
fn reset_restores_neutral() {
let mut s = make();
fw2_set_bizygomatic(&mut s, 0.1);
fw2_reset(&mut s);
assert!(fw2_is_neutral(&s));
}
#[test]
fn average_in_range() {
let s = make();
assert!((0.0..=1.0).contains(&fw2_average_width(&s)));
}
#[test]
fn weights_in_range() {
let s = make();
for v in fw2_to_weights(&s) {
assert!((0.0..=1.0).contains(&v));
}
}
#[test]
fn blend_at_one_is_b() {
let mut b = make();
fw2_set_bizygomatic(&mut b, 0.9);
let r = fw2_blend(&make(), &b, 1.0);
assert!((r.bizygomatic - 0.9).abs() < 1e-5);
}
#[test]
fn blend_midpoint() {
let mut a = make();
let mut b = make();
fw2_set_bizygomatic(&mut a, 0.0);
fw2_set_bizygomatic(&mut b, 1.0);
let m = fw2_blend(&a, &b, 0.5);
assert!((m.bizygomatic - 0.5).abs() < 1e-5);
}
#[test]
fn json_has_bizygomatic() {
assert!(fw2_to_json(&make()).contains("bizygomatic"));
}
#[test]
fn link_regions_propagates() {
let mut cfg = default_face_width_v2_config();
cfg.link_regions = true;
let mut s = new_face_width_v2_state(cfg);
fw2_set_bizygomatic(&mut s, 0.3);
assert!((s.bigonial - 0.3).abs() < 1e-5);
}
}