use std::collections::BinaryHeap;
use bevy::{prelude::{Vec2, Vec3}, utils::hashbrown::HashSet};
use itertools::Itertools;
use slotmap::SecondaryMap;
use crate::mesh::{attributes::{AttributeKind, AttributeValues, TraversalQueries}, FaceId, HalfEdgeId, HalfEdgeMesh};
pub(crate) mod primitive_mapping;
pub(crate) mod least_squares_conformal_maps;
pub struct Basis{
pub tangent: Vec3,
pub bitangent: Vec3,
pub normal: Vec3
}
impl Basis {
pub fn project_vertex(&self, pos:Vec3) -> Vec2 {
Vec2{x:self.tangent.dot(pos), y:self.bitangent.dot(pos)}
}
pub fn from_normal(normal:Vec3) -> Self {
let tangent = Self::compute_tangent(normal);
let bitangent = Self::compute_bitangent(normal, tangent);
Self { tangent, bitangent, normal}
}
pub fn from_eigen_vectors(eigen_vectors:[Vec3;3]) -> Self {
Basis{tangent:eigen_vectors[0].normalize(), bitangent:eigen_vectors[1].normalize(), normal:eigen_vectors[2].normalize()}
}
fn compute_tangent(normal:Vec3) -> Vec3 {
let mut tangent = Vec3::ZERO;
if normal.x.abs() < normal.y.abs() && normal.x.abs() < normal.z.abs() {
tangent.x = 1.0;
} else if normal.y.abs() < normal.z.abs() {
tangent.y = 1.0;
} else {
tangent.z = 1.0;
}
tangent -= normal * normal.dot(tangent);
tangent.normalize()
}
#[inline]
fn compute_bitangent(normal:Vec3, tangent:Vec3) -> Vec3 {
normal.cross(tangent)
}
}
pub struct Chart {
pub faces: HashSet<FaceId>,
max_distance:i32
}
impl Default for Chart {
fn default() -> Self {
Self::new()
}
}
impl Chart {
pub fn new() -> Self {
Self { faces: HashSet::new(), max_distance:0}
}
}
pub enum ProjectionMethod{
LSCM,
Sphere{center:Vec3, radius:Vec3},
Cube{center:Vec3, scale:Vec3}
}
pub fn create_charts(mesh:&mut HalfEdgeMesh) -> Vec<Chart>{
const MIN_FEATURE_LENGTH:usize = 15;
let count = mesh.face_keys().len();
let edges = mesh.face_keys().map(|f| mesh[f].halfedge).sorted_by(|&l, &r| {
mesh.goto(r).sharpness().partial_cmp(&mesh.goto(l).sharpness()).unwrap_or(std::cmp::Ordering::Less)
}).take(5 * count / 100).collect::<Vec<_>>();
let mut neighborhoods = HashSet::new();
let mut feature_faces:Vec<FaceId> = Vec::new();
let mut uv_seams:SecondaryMap<HalfEdgeId, bool> = SecondaryMap::new();
for edge in edges {
let feature = expand_feature_curve(mesh,&mut neighborhoods, edge);
if feature.len() >= MIN_FEATURE_LENGTH {
for &edge in &feature {
uv_seams.insert(edge, true);
let edge = mesh.goto(edge);
uv_seams.insert(*edge.twin(), true);
if let Some(face) = edge.face() {
feature_faces.push(face);
}
if let Some(twin_face) = edge.twin().face() {
feature_faces.push(twin_face);
}
for edge in edge.iter_outgoing() {
neighborhoods.insert(*edge);
neighborhoods.insert(*edge.twin());
}
}
}
}
let (maximas, distances) = find_distance_to_features_with_dikstra(mesh, &feature_faces);
let (charts, boundaries) = expand_charts(mesh, &maximas, &distances);
let mut uv_seams:SecondaryMap<HalfEdgeId, bool> = SecondaryMap::new();
for &edge in &boundaries {
let twin = * mesh.goto(edge).twin();
uv_seams.insert(edge, true);
uv_seams.insert(twin, true);
}
mesh.add_attribute(AttributeKind::UVSeams, AttributeValues::EdgeBool(uv_seams));
let charted_faces = charts.iter().fold(0, |acc, i| acc + i.faces.len());
assert_eq!(mesh.face_count(), charted_faces);
charts
}
pub fn expand_feature_curve(mesh:&mut HalfEdgeMesh, neighborhood_edges:&mut HashSet<HalfEdgeId>, start:HalfEdgeId) -> Vec<HalfEdgeId> {
const MAX_STRING_LENGTH:usize = 5;
const THRESHOLD:f32 = 0.1;
fn dfs(mesh:&HalfEdgeMesh, start_pos:Vec3, pos:HalfEdgeId, string:Vec<HalfEdgeId>, neighborhood_edges: &HashSet<HalfEdgeId>) -> (f32, Vec<HalfEdgeId>) {
let v_pos = mesh.goto(pos).position();
let (mut best_path_sharpness, mut best_path) = (0.0, Vec::new());
for next in mesh.goto(pos).next().iter_outgoing().sorted_by(|l, r| r.sharpness().partial_cmp(&l.sharpness()).unwrap_or(std::cmp::Ordering::Less)) {
let next_pos = next.position();
let next = *next;
if (start_pos - v_pos).length() < (start_pos - next_pos).length() && string.len() <= MAX_STRING_LENGTH && !neighborhood_edges.contains(&next) {
let mut string = string.clone();
string.push(next);
let (path_sharpness, path) = dfs(mesh, start_pos, next, string, neighborhood_edges);
if path_sharpness > best_path_sharpness {
best_path_sharpness = path_sharpness;
best_path = path;
}
}
}
if best_path.len() > string.len() {
(best_path_sharpness, best_path)
} else {
let path_sharpness = string.iter().map(|e| mesh.goto(*e).sharpness()).sum();
(path_sharpness, string)
}
}
let mut detected_feature:Vec<HalfEdgeId> = Vec::new();
let twin = *mesh.goto(start).twin();
for edge in [start, twin] {
let mut current = edge;
let pos = mesh.goto(edge).position();
loop {
let (sharpness, string) = dfs(mesh, pos, current, Vec::new(), neighborhood_edges);
if sharpness > MAX_STRING_LENGTH as f32 * THRESHOLD {
current = string[0];
detected_feature.push(current);
} else {
break;
}
}
}
detected_feature
}
fn find_distance_to_features_with_dikstra(mesh:&HalfEdgeMesh, features:&[FaceId]) -> (Vec<FaceId>, SecondaryMap<FaceId, usize>) {
#[derive(PartialOrd, Eq)]
struct FaceWithDistance{
face:FaceId,
distance:usize
}
impl PartialEq for FaceWithDistance {
fn eq(&self, other: &Self) -> bool {
self.face == other.face
}
}
impl Ord for FaceWithDistance {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
other.distance.cmp(&self.distance)
}
}
let mut min_heap:BinaryHeap<FaceWithDistance> = BinaryHeap::new();
let mut distances:SecondaryMap<FaceId, usize> = SecondaryMap::new();
let mut local_maxima: HashSet<FaceId> = HashSet::new();
for &face in features {
distances.insert(face, 0);
min_heap.push(FaceWithDistance{face, distance:0});
}
while let Some(FaceWithDistance{face, distance}) = min_heap.pop() {
let mut local_found = true;
for neighbor_face in mesh.goto(face).iter_loop().flat_map(|e| e.adjacent_faces()) {
let alternative_distance = distance + 1;
if let Some(face) = neighbor_face.face() {
let mut known_distance = *distances.get(face).unwrap_or(&usize::MAX);
if alternative_distance < known_distance {
distances.insert(face, alternative_distance);
min_heap.push(FaceWithDistance{face, distance:alternative_distance});
known_distance = alternative_distance;
}
if known_distance >= distance {
local_found = false;
}
}
}
if local_found {
local_maxima.insert(face);
}
}
let mut other_local_maxima: Vec<FaceId> = Vec::new();
for face in mesh.face_keys() {
let face_distance = distances[face];
if face_distance != 0 && !mesh.goto(face).iter_loop().any(|e| face_distance <= e.twin().face().map(|f| distances[f]).unwrap_or(0)) {
other_local_maxima.push(face);
}
}
(other_local_maxima, distances)
}
fn expand_charts(mesh:&HalfEdgeMesh, seeds:&[FaceId], distances:&SecondaryMap<FaceId, usize>) -> (Vec<Chart>, HashSet<HalfEdgeId>) {
#[derive(Eq)]
struct EdgeWithCost{
edge:HalfEdgeId,
cost:usize
}
impl Ord for EdgeWithCost {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.cost.cmp(&other.cost)
}
}
impl PartialOrd for EdgeWithCost {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for EdgeWithCost {
fn eq(&self, other: &Self) -> bool {
self.edge == other.edge
}
}
let mut chart_boundaries:HashSet<HalfEdgeId> = HashSet::from_iter(mesh.edge_keys());
let mut heap = BinaryHeap::new();
let mut charts = Vec::new();
let mut global_max_distance = 0;
for &face in seeds {
let mut chart = Chart::new();
chart.faces.insert(face);
chart.max_distance = distances[face] as i32;
global_max_distance = global_max_distance.max(chart.max_distance);
charts.push(chart);
for edge in mesh.goto(face).iter_loop() {
heap.push(EdgeWithCost{edge:*edge, cost:0});
}
}
let threshold:i32 = global_max_distance / 4;
while let Some(e) = heap.pop() {
let edge = mesh.goto(e.edge);
let face = edge.face().unwrap();
let twin = edge.twin();
let face_opposite = twin.face().unwrap();
let (mut face_chart_idx, _) = charts.iter().enumerate().find(|c| c.1.faces.contains(&face)).unwrap();
let face_opposite_chart_idx = charts.iter().enumerate().find(|c| c.1.faces.contains(&face_opposite));
if let Some((face_opposite_chart_idx, _)) = face_opposite_chart_idx {
if face_chart_idx != face_opposite_chart_idx &&
charts[face_chart_idx].max_distance - (distances[face] as i32) < threshold &&
charts[face_opposite_chart_idx].max_distance - (distances[face] as i32) < threshold
{
chart_boundaries.remove(&(*edge));
chart_boundaries.remove(&(*twin));
let mut opposite_chart = charts.remove(face_opposite_chart_idx);
if face_opposite_chart_idx < face_chart_idx {
face_chart_idx -= 1;
}
charts[face_chart_idx].faces.extend(opposite_chart.faces.drain());
}
if face_chart_idx == face_opposite_chart_idx {
chart_boundaries.remove(&(*edge));
chart_boundaries.remove(&(*twin));
}
} else {
chart_boundaries.remove(&(*edge));
charts[face_chart_idx].faces.insert(face_opposite);
for edge in twin.iter_loop() {
if !edge.twin().face().map(|f| charts[face_chart_idx].faces.contains(&f)).unwrap_or(true) {
heap.push(EdgeWithCost { edge: *edge, cost: distances[face_opposite] });
} else {
chart_boundaries.remove(&(*edge));
chart_boundaries.remove(&(*edge.twin()));
}
}
}
}
(charts, chart_boundaries)
}