1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) 2025 lacklustr@protonmail.com https://github.com/eadf
use crate::common::macros::{integrity_assert_eq, integrity_println};
use crate::corner_table::CornerIndex;
use crate::isotropic_remesh::IsotropicRemeshAlgo;
use std::fmt::Debug;
use vector_traits::num_traits::AsPrimitive;
use vector_traits::prelude::GenericVector3;
impl<S, V, const ENABLE_UNSAFE: bool> IsotropicRemeshAlgo<S, V, ENABLE_UNSAFE>
where
S: crate::common::sealed::ScalarType,
f64: AsPrimitive<S>,
V: Debug + Copy + From<[S; 3]> + Into<[S; 3]> + Sync + 'static,
{
/// Split an edge in a manifold mesh (everything is CCW)
///
/// ```text
/// Notation: cₓ = corner, Vₓ = vertex, tₓ = triangle
/// cₓn = cₓ.next, cₓp = cₓ.prev, cₓo = cₓ.opposite
///
/// Input: corner 'c₀' with vertex 'V₀', where edge to split is V₀->V₂
/// c₀o
/// V₃ ─────────────── V₂ V₃ ─────────────── V₂
/// │c₀p c₀n / │ │ \ c₃p c₃n / │
/// │ / │ │c₀p\ t₃ /c₂p│
/// │ t₀ / c₁ │ │ \ / │
/// │ / │ => │ t₀ \c₃/ t₂ │
/// c₀no│ / │c₁no │ c₀n M c₂ │
/// │ / │ │ / \ │
/// │ / t₁ │ │ / c₁ \ │
/// │c₀/ │ │c₀ / t₁ \c₂n│
/// │/ c₁n c₁p │ │ / c₁n c₁p \ │
/// V₀ ────────────── V₁ V₀ ────────────── V₁
/// c₁o
/// ```
pub(crate) fn split_edges_dihedral(&mut self, adjusted_target_length_sq: S) -> bool {
let original_num_triangles = self.corner_table.active_triangles();
let relaxed_max_valence = self.params.max_valence + 1;
// Split with relaxed valence for the longest edges
const RELAXED_EDGE_COUNT: usize = 3;
//#[cfg(feature = "integrity_check")]
//self.dump_status("pre-split_long_edges()");
// Collect only long edges that need splitting
let mut edges_to_split = Vec::new();
integrity_println!(
"####### split_edges_dihedral threshold:{}",
adjusted_target_length_sq.sqrt()
);
// Iterate over all corners, filtering for valid vertices
for (corner, vertex) in self
.corner_table
.vertex_of_corners()
.iter()
.cloned()
.enumerate()
.filter_map(|(c, v)| v.is_valid().then_some((CornerIndex(c as u32), v)))
{
// Only process each edge once by comparing with twin corner
let twin_corner = self.corner_table.twin(corner);
if corner.0 < twin_corner.0 {
let v1 = self.vertex(vertex);
let v2 = self.vertex_of_corner(twin_corner);
// Check if edge is too long - only add to list if it needs splitting
let edge_length_sq = (v2 - v1).magnitude_sq();
if edge_length_sq > adjusted_target_length_sq {
edges_to_split.push((
corner,
edge_length_sq,
self.corner_table.canonical_edge_hash(corner),
));
}
}
}
// Sort by edge length (longest first) - now we only sort the edges that need splitting
edges_to_split.sort_unstable_by(|a, &b| {
(b.1, b.2)
.partial_cmp(&(a.1, a.2))
.unwrap_or(std::cmp::Ordering::Equal)
});
#[cfg(feature = "integrity_check")]
if edges_to_split.len() > 2 {
let last = edges_to_split.len().saturating_sub(1);
debug_assert!(
edges_to_split[0].1 >= edges_to_split[last].1,
"first {:.3} < last {:.3}",
edges_to_split[0].1,
edges_to_split[last].1
);
}
// Split edges: longest first
for (count, (c0, _, _)) in edges_to_split.into_iter().enumerate() {
// Re-verify edge still exists and is still long (topology might have changed)
let (c0n, c0p) = self.corner_table.next_prev(c0);
let v1 = self.vertex_of_corner(c0);
let v2 = self.vertex_of_corner(c0n);
let edge_length_sq = (v2 - v1).magnitude_sq();
if edge_length_sq > adjusted_target_length_sq {
// Check valence of the four vertices that will be affected by the split
// Get opposite triangle (c1)
let c1p = self.corner_table.opposite(c0p);
integrity_assert_eq!(c1p, self.corner_table.prev(self.corner_table.twin(c0)));
// Check valence of all four surrounding vertices
let valence_v1 = self.corner_table.valence(c1p);
let valence_v3 = self.corner_table.valence(c0p);
// After split:
// - V0 and V2 will have their valence unchanged (edge removed, edge added)
// - V1 and V3 will have their valence increased by 1 (new edge to M)
let will_exceed_valence = if count < RELAXED_EDGE_COUNT {
valence_v1 >= relaxed_max_valence || valence_v3 >= relaxed_max_valence
} else {
valence_v1 >= self.params.max_valence || valence_v3 >= self.params.max_valence
};
if will_exceed_valence {
#[cfg(feature = "integrity_check")]
{
let v1 = self.corner_table.vertex(c1p);
let v2 = self.corner_table.vertex(c0n);
let valence_v0 = self.corner_table.valence(c0);
let valence_v2 = self.corner_table.valence(c0n);
println!(
"Skipping edge split {:?}-{:?}: would exceed max valence (v0:{}, v1:{}, v2:{}, v3:{})",
v1, v2, valence_v0, valence_v1, valence_v2, valence_v3
);
}
continue;
}
#[cfg(feature = "integrity_check")]
{
let valence_v0 = self.corner_table.valence(c0);
let valence_v2 = self.corner_table.valence(c0n);
println!(
"Edge length²:{edge_length_sq} split_threshold_sq:{adjusted_target_length_sq} (valences: v0:{}, v1:{}, v2:{}, v3:{})",
valence_v0, valence_v1, valence_v2, valence_v3
);
}
self.split_manifold_edge(c0);
#[cfg(feature = "integrity_check")]
self.check_mesh_integrity(
format!(
"after manifold edge split {:?}-{:?}",
self.corner_table.vertex(c0),
self.corner_table.vertex(c0n)
)
.as_str(),
)
.unwrap();
}
}
//println!("split_long_edges() finished. Changes made:{}", original_num_triangles != self.corner_table.active_triangles());
original_num_triangles != self.corner_table.active_triangles()
}
}