use std::collections::HashSet;
use avian3d::parry::math::Point as ParryPoint;
use avian3d::parry::transformation::convex_hull;
use bevy::prelude::*;
use jackdaw_geometry::{EPSILON, sort_face_vertices_by_winding};
use jackdaw_jsn::{Brush, BrushFaceData, BrushPlane};
fn vec3_to_point(v: Vec3) -> ParryPoint<f32> {
ParryPoint::new(v.x, v.y, v.z)
}
fn point_to_vec3(p: &ParryPoint<f32>) -> Vec3 {
Vec3::new(p.x, p.y, p.z)
}
pub struct HullFace {
pub normal: Vec3,
pub distance: f32,
pub vertex_indices: Vec<usize>,
}
pub(crate) fn merge_hull_triangles(vertices: &[Vec3], triangles: &[[u32; 3]]) -> Vec<HullFace> {
let mut face_groups: Vec<(Vec3, f32, HashSet<usize>)> = Vec::new();
for tri in triangles {
let a = vertices[tri[0] as usize];
let b = vertices[tri[1] as usize];
let c = vertices[tri[2] as usize];
let normal = (b - a).cross(c - a).normalize_or_zero();
if normal.length_squared() < 0.5 {
continue; }
let distance = normal.dot(a);
let mut found = false;
for (gn, gd, gverts) in &mut face_groups {
if gn.dot(normal) > 1.0 - EPSILON && (distance - *gd).abs() < EPSILON {
gverts.insert(tri[0] as usize);
gverts.insert(tri[1] as usize);
gverts.insert(tri[2] as usize);
found = true;
break;
}
}
if !found {
let mut verts = HashSet::new();
verts.insert(tri[0] as usize);
verts.insert(tri[1] as usize);
verts.insert(tri[2] as usize);
face_groups.push((normal, distance, verts));
}
}
face_groups
.into_iter()
.map(|(normal, distance, vert_set)| {
let mut vertex_indices: Vec<usize> = vert_set.into_iter().collect();
sort_face_vertices_by_winding(vertices, &mut vertex_indices, normal);
HullFace {
normal,
distance,
vertex_indices,
}
})
.collect()
}
fn compute_face_uv_metrics(
vertices: &[Vec3],
face_vert_indices: &[usize],
u_axis: Vec3,
v_axis: Vec3,
) -> (Vec2, Vec2) {
let (mut min_u, mut max_u) = (f32::MAX, f32::MIN);
let (mut min_v, mut max_v) = (f32::MAX, f32::MIN);
let mut sum_u = 0.0_f32;
let mut sum_v = 0.0_f32;
for &vi in face_vert_indices {
let pos = vertices[vi];
let u = pos.dot(u_axis);
let v = pos.dot(v_axis);
min_u = min_u.min(u);
max_u = max_u.max(u);
min_v = min_v.min(v);
max_v = max_v.max(v);
sum_u += u;
sum_v += v;
}
let n = face_vert_indices.len() as f32;
(
Vec2::new((max_u - min_u).max(0.001), (max_v - min_v).max(0.001)),
Vec2::new(sum_u / n, sum_v / n),
)
}
pub(super) fn rebuild_brush_from_vertices(
old_brush: &Brush,
old_vertices: &[Vec3],
old_face_polygons: &[Vec<usize>],
new_vertices: &[Vec3],
) -> Option<(Brush, Vec<usize>)> {
if new_vertices.len() < 4 {
return None;
}
let points: Vec<ParryPoint<f32>> = new_vertices.iter().map(|v| vec3_to_point(*v)).collect();
let (hull_verts, hull_tris) = convex_hull(&points);
if hull_verts.len() < 4 || hull_tris.is_empty() {
return None;
}
let hull_positions: Vec<Vec3> = hull_verts.iter().map(point_to_vec3).collect();
let hull_faces = merge_hull_triangles(&hull_positions, &hull_tris);
if hull_faces.len() < 4 {
return None;
}
let hull_to_input: Vec<usize> = hull_positions
.iter()
.map(|hp| {
new_vertices
.iter()
.enumerate()
.min_by(|(_, a), (_, b)| {
(**a - *hp)
.length_squared()
.partial_cmp(&(**b - *hp).length_squared())
.unwrap_or(std::cmp::Ordering::Equal)
})
.map(|(i, _)| i)
.unwrap_or(0)
})
.collect();
let mut faces = Vec::with_capacity(hull_faces.len());
let mut best_old_per_new = Vec::with_capacity(hull_faces.len());
for hull_face in &hull_faces {
let input_verts: HashSet<usize> = hull_face
.vertex_indices
.iter()
.map(|&hi| hull_to_input[hi])
.collect();
let mut best_old = 0usize;
let mut best_score = -1.0_f32;
for (old_idx, old_polygon) in old_face_polygons.iter().enumerate() {
let old_set: HashSet<usize> = old_polygon.iter().copied().collect();
let overlap = input_verts.intersection(&old_set).count() as f32;
let normal_sim = hull_face.normal.dot(old_brush.faces[old_idx].plane.normal);
let score = overlap + normal_sim * 0.1;
if score > best_score {
best_score = score;
best_old = old_idx;
}
}
best_old_per_new.push(best_old);
let old_face = &old_brush.faces[best_old];
let (u_axis, v_axis) =
if old_face.uv_u_axis != Vec3::ZERO && old_face.uv_v_axis != Vec3::ZERO {
(old_face.uv_u_axis, old_face.uv_v_axis)
} else {
jackdaw_geometry::compute_face_tangent_axes(old_face.plane.normal)
};
let remapped_indices: Vec<usize> = hull_face
.vertex_indices
.iter()
.map(|&hi| hull_to_input[hi])
.collect();
let old_polygon = &old_face_polygons[best_old];
let (_, old_centroid) = compute_face_uv_metrics(old_vertices, old_polygon, u_axis, v_axis);
let (_, new_centroid) =
compute_face_uv_metrics(new_vertices, &remapped_indices, u_axis, v_axis);
let new_scale = old_face.uv_scale;
let safe_scale = new_scale.max(Vec2::splat(0.001));
let old_uv_center = old_centroid / safe_scale;
let new_uv_center = new_centroid / safe_scale;
let new_offset = old_face.uv_offset + (old_uv_center - new_uv_center);
faces.push(BrushFaceData {
plane: BrushPlane {
normal: hull_face.normal,
distance: hull_face.distance,
},
material: old_face.material.clone(),
uv_offset: new_offset,
uv_scale: new_scale,
uv_rotation: old_face.uv_rotation,
uv_u_axis: u_axis,
uv_v_axis: v_axis,
..default()
});
}
let mut old_to_new = vec![0usize; old_brush.faces.len()];
for (new_idx, &old_idx) in best_old_per_new.iter().enumerate() {
old_to_new[old_idx] = new_idx;
}
Some((Brush { faces }, old_to_new))
}