use std::collections::BTreeMap;
use glam::{Mat3, Vec2, Vec3};
use slotmap::SecondaryMap;
use super::{selection::Selection, traversal::Traversal, HalfEdgeId, StackVec, VertexId};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum AttributeKind {
Positions,
Normals,
UVs,
Indices,
Creases,
UVSeams,
}
pub type AttributeStore<K, T> = SecondaryMap<K, T>;
pub enum AttributeValues {
VertexU32(AttributeStore<VertexId, u32>),
VertexVec3(AttributeStore<VertexId, Vec3>),
VertexVec2(AttributeStore<VertexId, Vec2>),
VertexBool(AttributeStore<VertexId, bool>),
EdgeVec2(AttributeStore<HalfEdgeId, Vec2>),
EdgeVec3(AttributeStore<HalfEdgeId, Vec3>),
EdgeBool(AttributeStore<HalfEdgeId, bool>),
}
impl AttributeValues {
pub fn as_vertices_u32(&self) -> &AttributeStore<VertexId, u32> {
match self {
Self::VertexU32(v) => v,
_ => panic!("Unexpected attribute kind"),
}
}
pub fn as_vertices_u32_mut(&mut self) -> &mut AttributeStore<VertexId, u32> {
match self {
Self::VertexU32(v) => v,
_ => panic!("Unexpected attribute kind"),
}
}
pub fn as_vertices_vec2(&self) -> &AttributeStore<VertexId, Vec2> {
match self {
Self::VertexVec2(v) => v,
_ => panic!("Unexpected attribute kind"),
}
}
pub fn as_vertices_vec2_mut(&mut self) -> &mut AttributeStore<VertexId, Vec2> {
match self {
Self::VertexVec2(v) => v,
_ => panic!("Unexpected attribute kind"),
}
}
pub fn as_vertices_vec3(&self) -> &AttributeStore<VertexId, Vec3> {
match self {
Self::VertexVec3(v) => v,
_ => panic!("Unexpected attribute kind"),
}
}
pub fn as_vertices_vec3_mut(&mut self) -> &mut AttributeStore<VertexId, Vec3> {
match self {
Self::VertexVec3(v) => v,
_ => panic!("Unexpected attribute kind"),
}
}
pub fn as_vertices_bool(&self) -> &AttributeStore<VertexId, bool> {
match self {
Self::VertexBool(v) => v,
_ => panic!("Unexpected attribute kind"),
}
}
pub fn as_vertices_bool_mut(&mut self) -> &mut AttributeStore<VertexId, bool> {
match self {
Self::VertexBool(v) => v,
_ => panic!("Unexpected attribute kind"),
}
}
pub fn as_edge_vec2(&self) -> &AttributeStore<HalfEdgeId, Vec2> {
match self {
Self::EdgeVec2(v) => v,
_ => panic!("Unexpected attribute kind"),
}
}
pub fn as_edge_vec2_mut(&mut self) -> &mut AttributeStore<HalfEdgeId, Vec2> {
match self {
Self::EdgeVec2(v) => v,
_ => panic!("Unexpected attribute kind"),
}
}
pub fn as_edge_vec3(&self) -> &AttributeStore<HalfEdgeId, Vec3> {
match self {
Self::EdgeVec3(v) => v,
_ => panic!("Unexpected attribute kind"),
}
}
pub fn as_edge_vec3_mut(&mut self) -> &mut AttributeStore<HalfEdgeId, Vec3> {
match self {
Self::EdgeVec3(v) => v,
_ => panic!("Unexpected attribute kind"),
}
}
pub fn as_edge_bool(&self) -> &AttributeStore<HalfEdgeId, bool> {
match self {
Self::EdgeBool(v) => v,
_ => panic!("Unexpected attribute kind"),
}
}
pub fn as_edge_bool_mut(&mut self) -> &mut AttributeStore<HalfEdgeId, bool> {
match self {
Self::EdgeBool(v) => v,
_ => panic!("Unexpected attribute kind"),
}
}
}
pub type Attributes = BTreeMap<AttributeKind, AttributeValues>;
impl From<AttributeStore<VertexId, u32>> for AttributeValues {
fn from(value: AttributeStore<VertexId, u32>) -> Self {
Self::VertexU32(value)
}
}
impl From<AttributeStore<VertexId, Vec3>> for AttributeValues {
fn from(value: AttributeStore<VertexId, Vec3>) -> Self {
Self::VertexVec3(value)
}
}
impl From<AttributeStore<HalfEdgeId, Vec2>> for AttributeValues {
fn from(value: AttributeStore<HalfEdgeId, Vec2>) -> Self {
Self::EdgeVec2(value)
}
}
impl From<AttributeStore<HalfEdgeId, Vec3>> for AttributeValues {
fn from(value: AttributeStore<HalfEdgeId, Vec3>) -> Self {
Self::EdgeVec3(value)
}
}
pub trait TraversalQueries {
fn position(&self) -> Vec3;
fn is_smooth_normals(&self) -> bool;
fn is_uv_seam(&self) -> bool;
fn sharpness(&self) -> f32;
fn uv(&self) -> Vec2;
fn triangulate(&self) -> Vec<VertexId>;
}
pub trait SelectionQueries {
fn calculate_centroid(&self) -> Vec3;
fn calculate_normal(&self) -> Option<Vec3>;
fn calculate_covariance(&self) -> Mat3;
}
impl<'m> TraversalQueries for Traversal<'m> {
fn position(&self) -> Vec3 {
let vertex = self.vertex();
let values = self
.mesh
.attribute(&super::attributes::AttributeKind::Positions)
.expect("Vertices don't have position attribute.");
values.as_vertices_vec3().get(vertex).copied().unwrap()
}
fn uv(&self) -> Vec2 {
let values = self
.mesh
.attribute(&super::attributes::AttributeKind::UVs)
.expect("Vertices don't have UV attribute.");
values.as_edge_vec2().get(self.halfedge()).copied().unwrap()
}
fn is_smooth_normals(&self) -> bool {
if let Some(store) = self
.mesh
.attribute(&super::attributes::AttributeKind::Creases)
{
store
.as_vertices_bool()
.get(self.vertex())
.copied()
.unwrap_or(self.mesh.is_smooth)
} else {
self.mesh.is_smooth
}
}
fn is_uv_seam(&self) -> bool {
if let Some(store) = self
.mesh
.attribute(&super::attributes::AttributeKind::UVSeams)
{
store.as_edge_bool().get(self.halfedge()).copied().unwrap_or(false)
} else {
false
}
}
fn sharpness(&self) -> f32 {
let n = self.calculate_normal();
let n_other = self.twin().calculate_normal();
1.0 - match (n, n_other) {
(Some(n), Some(n_other)) => {
n.dot(n_other).abs() / n.length() / n_other.length() }
_ => 1.0, }
}
fn triangulate(&self) -> Vec<VertexId> {
let normal = self.calculate_normal().unwrap();
let u = (self.position() - self.calculate_centroid()).normalize();
let v = u.cross(normal);
let mut vertex_ids = StackVec::new();
let vertices = self
.iter_loop()
.flat_map(|p| {
vertex_ids.push(p.vertex());
let p = p.position();
[p.dot(u), p.dot(v)]
})
.collect::<Vec<_>>();
let result = earcutr::earcut(&vertices, &[], 2).unwrap();
result
.into_iter()
.map(|idx| vertex_ids[idx])
.rev()
.collect()
}
}
impl<'m> SelectionQueries for Traversal<'m> {
fn calculate_centroid(&self) -> Vec3 {
let (sum, count) = self.iter_loop().fold((Vec3::ZERO, 0.0), |acc, i| {
(acc.0 + i.position(), acc.1 + 1.0)
});
sum / count
}
fn calculate_normal(&self) -> Option<Vec3> {
let points = self
.iter_loop()
.map(|f| f.position())
.collect::<StackVec<_>>();
let n = points.len() as f32;
let mut sum = Vec3 {
x: 0.0,
y: 0.0,
z: 0.0,
};
for &p in &points {
sum += p;
}
let centroid = sum * (1.0 / n);
let mut xx = 0.0;
let mut xy = 0.0;
let mut xz = 0.0;
let mut yy = 0.0;
let mut yz = 0.0;
let mut zz = 0.0;
let a = centroid - points[0];
let b = centroid - points[1];
let simple_normal = a.cross(b);
for p in points {
let r = p - centroid;
xx += r.x * r.x;
xy += r.x * r.y;
xz += r.x * r.z;
yy += r.y * r.y;
yz += r.y * r.z;
zz += r.z * r.z;
}
xx /= n;
xy /= n;
xz /= n;
yy /= n;
yz /= n;
zz /= n;
let mut weighted_dir = Vec3 {
x: 0.0,
y: 0.0,
z: 0.0,
};
{
let det_x = yy * zz - yz * yz;
let axis_dir = Vec3 {
x: det_x,
y: xz * yz - xy * zz,
z: xy * yz - xz * yy,
};
let mut weight = det_x * det_x;
if weighted_dir.dot(axis_dir) < 0.0 {
weight = -weight;
}
weighted_dir += axis_dir * weight;
}
{
let det_y = xx * zz - xz * xz;
let axis_dir = Vec3 {
x: xz * yz - xy * zz,
y: det_y,
z: xy * xz - yz * xx,
};
let mut weight = det_y * det_y;
if weighted_dir.dot(axis_dir) < 0.0 {
weight = -weight;
}
weighted_dir += axis_dir * weight;
}
{
let det_z = xx * yy - xy * xy;
let axis_dir = Vec3 {
x: xy * yz - xz * yy,
y: xy * xz - yz * xx,
z: det_z,
};
let mut weight = det_z * det_z;
if weighted_dir.dot(axis_dir) < 0.0 {
weight = -weight;
}
weighted_dir += axis_dir * weight;
}
let normal = weighted_dir.normalize();
if normal.is_finite() {
let sign = simple_normal.dot(normal).signum();
Some(sign * normal)
} else {
None
}
}
fn calculate_covariance(&self) -> Mat3 {
let centroid = self.calculate_centroid();
self.iter_loop().fold(Mat3::ZERO, |acc, i| {
let r = Mat3::from_cols(i.position() - centroid, Vec3::ZERO, Vec3::ZERO);
acc + r * r.transpose()
})
}
}
impl<'m> SelectionQueries for Selection<'m> {
fn calculate_centroid(&self) -> Vec3 {
let (sum, count) = self
.iter()
.map(|t| t.calculate_centroid())
.fold((Vec3::ZERO, 0.0), |acc, i| (acc.0 + i, acc.1 + 1.0));
sum / count
}
fn calculate_covariance(&self) -> Mat3 {
self.iter().map(|t| t.calculate_covariance()).sum()
}
fn calculate_normal(&self) -> Option<Vec3> {
let (sum, count) = self
.iter()
.filter_map(|t| t.calculate_normal())
.fold((Vec3::ZERO, 0.0), |acc, i| (acc.0 + i, acc.1 + 1.0));
if count == 0.0 {
None
} else {
Some(sum / count)
}
}
}
#[cfg(test)]
mod tests {
use glam::{Mat3, Vec3};
#[test]
fn test_quad() {
let v1 = Vec3::new(0.0, 0.875, -0.25); let v2 = Vec3::new(-0.25, 0.875, 0.0); let v3 = Vec3::new(0.0, 0.875, 0.25); let v4 = Vec3::new(0.25, 0.875, 0.0); let n = (v1 - v2).cross(v3 - v2).normalize();
let n2 = (v3 - v4).cross(v1 - v4).normalize();
assert_eq!(n, n2);
}
#[test]
fn test_matrix() {
let v1 = Vec3::new(0.0, 0.875, -0.25); let v2 = Vec3::new(-0.25, 0.875, 0.0); let v3 = Vec3::new(0.0, 0.875, 0.25); let v4 = Vec3::new(0.25, 0.875, 0.0); let centroid = 0.25 * (v1 + v2 + v3 + v4);
println!("Centroid: {centroid:?}");
let r1 = v1 - centroid;
let r2 = v2 - centroid;
let r3 = v3 - centroid;
let r4 = v4 - centroid;
let m1 = Mat3::from_cols(r1, Vec3::ZERO, Vec3::ZERO);
let m2 = Mat3::from_cols(r2, Vec3::ZERO, Vec3::ZERO);
let m3 = Mat3::from_cols(r3, Vec3::ZERO, Vec3::ZERO);
let m4 = Mat3::from_cols(r4, Vec3::ZERO, Vec3::ZERO);
let covariance =
m1 * m1.transpose() + m2 * m2.transpose() + m3 * m3.transpose() + m4 * m4.transpose();
let mut other_way = Mat3::ZERO;
other_way.x_axis.x = r1.x * r1.x + r2.x * r2.x + r3.x * r3.x + r4.x * r4.x;
other_way.x_axis.y = r1.x * r1.y + r2.x * r2.y + r3.x * r3.y + r4.x * r4.y;
other_way.x_axis.z = r1.x * r1.z + r2.x * r2.z + r3.x * r3.z + r4.x * r4.z;
other_way.y_axis.y = r1.y * r1.y + r2.y * r2.y + r3.y * r3.y + r4.y * r4.y;
other_way.y_axis.z = r1.y * r1.z + r2.y * r2.z + r3.y * r3.z + r4.y * r4.z;
other_way.z_axis.z = r1.z * r1.z + r2.z * r2.z + r3.z * r3.z + r4.z * r4.z;
println!("Covariance: {covariance:?} other: {other_way:?}");
assert_eq!(covariance, other_way);
let det_x =
other_way.y_axis.y * other_way.z_axis.z - other_way.y_axis.z * other_way.y_axis.z;
let det_y =
other_way.x_axis.x * other_way.z_axis.z - other_way.x_axis.z * other_way.x_axis.z;
let det_z =
other_way.x_axis.x * other_way.y_axis.y - other_way.x_axis.y * other_way.x_axis.y;
let cdx = covariance.y_axis.cross(covariance.z_axis);
let cdy = covariance.z_axis.cross(covariance.x_axis);
let cdz = covariance.y_axis.cross(covariance.y_axis);
println!("cdy: {cdy:?}");
assert_eq!(det_x, cdx.x);
assert_eq!(det_y, cdy.y);
assert_eq!(det_z, cdz.x);
let dir = Vec3::new(
det_x,
other_way.x_axis.z * other_way.y_axis.z - other_way.x_axis.y * other_way.z_axis.z,
other_way.x_axis.y * other_way.y_axis.z - other_way.x_axis.z * other_way.y_axis.y,
);
assert_eq!(dir, cdx);
let dir = Vec3::new(
other_way.x_axis.z * other_way.y_axis.z - other_way.x_axis.y * other_way.z_axis.z,
det_y,
other_way.x_axis.y * other_way.x_axis.z - other_way.y_axis.z * other_way.x_axis.x,
);
assert_eq!(dir, cdy);
let dir = Vec3::new(
other_way.x_axis.y * other_way.y_axis.z - other_way.x_axis.z * other_way.y_axis.y,
other_way.x_axis.y * other_way.x_axis.z - other_way.y_axis.z * other_way.x_axis.x,
det_z,
);
assert_eq!(dir, cdz);
}
}