#![allow(dead_code)]
use std::collections::HashMap;
#[allow(dead_code)]
pub struct ButterflyResult {
pub positions: Vec<[f32; 3]>,
pub indices: Vec<u32>,
pub level: u32,
}
#[inline]
fn add3(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
[a[0] + b[0], a[1] + b[1], a[2] + b[2]]
}
#[inline]
fn scale3(v: [f32; 3], s: f32) -> [f32; 3] {
[v[0] * s, v[1] * s, v[2] * s]
}
#[allow(dead_code)]
pub fn butterfly_edge_point_regular(
p0: [f32; 3],
p1: [f32; 3],
opp_l: [f32; 3],
opp_r: [f32; 3],
) -> [f32; 3] {
let half = scale3(add3(p0, p1), 0.5);
let w = 1.0 / 16.0;
let correction = scale3(add3(opp_l, opp_r), -w);
add3(half, correction)
}
#[allow(dead_code)]
pub fn butterfly_edge_midpoint(p0: [f32; 3], p1: [f32; 3]) -> [f32; 3] {
scale3(add3(p0, p1), 0.5)
}
#[allow(dead_code)]
pub fn butterfly_subdivide(positions: &[[f32; 3]], indices: &[u32]) -> ButterflyResult {
let mut new_pos = positions.to_vec();
let mut edge_map: HashMap<(u32, u32), u32> = HashMap::new();
let mut new_idx: Vec<u32> = Vec::with_capacity(indices.len() * 4);
let tri_count = indices.len() / 3;
for t in 0..tri_count {
let a = indices[3 * t];
let b = indices[3 * t + 1];
let c = indices[3 * t + 2];
let get_mid = |p: u32, q: u32, pos: &mut Vec<[f32; 3]>, map: &mut HashMap<(u32, u32), u32>| {
let key = if p < q { (p, q) } else { (q, p) };
*map.entry(key).or_insert_with(|| {
let mp = butterfly_edge_midpoint(pos[p as usize], pos[q as usize]);
let idx = pos.len() as u32;
pos.push(mp);
idx
})
};
let ab = get_mid(a, b, &mut new_pos, &mut edge_map);
let bc = get_mid(b, c, &mut new_pos, &mut edge_map);
let ca = get_mid(c, a, &mut new_pos, &mut edge_map);
new_idx.extend_from_slice(&[a, ab, ca]);
new_idx.extend_from_slice(&[ab, b, bc]);
new_idx.extend_from_slice(&[ca, bc, c]);
new_idx.extend_from_slice(&[ab, bc, ca]);
}
ButterflyResult { positions: new_pos, indices: new_idx, level: 1 }
}
#[allow(dead_code)]
pub fn butterfly_subdivide_n(positions: &[[f32; 3]], indices: &[u32], n: u32) -> ButterflyResult {
let mut pos = positions.to_vec();
let mut idx = indices.to_vec();
for _ in 0..n {
let r = butterfly_subdivide(&pos, &idx);
pos = r.positions;
idx = r.indices;
}
ButterflyResult { positions: pos, indices: idx, level: n }
}
#[allow(dead_code)]
pub fn butterfly_irregular_weight(n: usize, j: usize) -> f32 {
use std::f32::consts::PI;
if n == 3 {
let weights = [5.0 / 12.0, -1.0 / 12.0, -1.0 / 12.0];
weights[j % 3]
} else if n == 4 {
let weights = [3.0 / 8.0, 0.0, -1.0 / 8.0, 0.0];
weights[j % 4]
} else {
let nf = n as f32;
(1.0 / nf) * (0.25 + (2.0 * PI * j as f32 / nf).cos() + 0.5 * (4.0 * PI * j as f32 / nf).cos())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn triangle() -> (Vec<[f32; 3]>, Vec<u32>) {
(vec![[0.0,0.0,0.0],[1.0,0.0,0.0],[0.0,1.0,0.0]], vec![0,1,2])
}
#[test]
fn one_step_quad_faces() {
let (p, i) = triangle();
let r = butterfly_subdivide(&p, &i);
assert_eq!(r.indices.len(), 12);
}
#[test]
fn two_levels() {
let (p, i) = triangle();
let r = butterfly_subdivide_n(&p, &i, 2);
assert_eq!(r.level, 2);
assert_eq!(r.indices.len(), 48);
}
#[test]
fn edge_point_regular_close_to_midpoint() {
let ep = butterfly_edge_point_regular(
[0.0,0.0,0.0],[2.0,0.0,0.0],[1.0,1.0,0.0],[1.0,-1.0,0.0],
);
assert!((ep[0] - 1.0).abs() < 0.5);
}
#[test]
fn midpoint_exact() {
let m = butterfly_edge_midpoint([0.0,0.0,0.0],[2.0,0.0,0.0]);
assert!((m[0] - 1.0).abs() < 1e-6);
}
#[test]
fn irregular_weight_n3_sums_near_one() {
let s: f32 = (0..3).map(|j| butterfly_irregular_weight(3, j)).sum();
assert!((s - 0.25).abs() < 1e-5);
}
#[test]
fn irregular_weight_n6_positive_first() {
let w = butterfly_irregular_weight(6, 0);
assert!(w > 0.0);
}
#[test]
fn vertex_count_increases() {
let (p, i) = triangle();
let r = butterfly_subdivide(&p, &i);
assert!(r.positions.len() > p.len());
}
#[test]
fn level_field() {
let (p, i) = triangle();
let r = butterfly_subdivide(&p, &i);
assert_eq!(r.level, 1);
}
#[test]
fn n_levels_level_field() {
let (p, i) = triangle();
let r = butterfly_subdivide_n(&p, &i, 3);
assert_eq!(r.level, 3);
}
}