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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
//! Dual Contouring — the sharp-feature method.
//!
//! Same topology as [`SurfaceNets`](crate::surface_nets::SurfaceNets), different vertex.
//! That sentence is the whole algorithm, and it is not a simplification: V-19
//! records that the original paper's method *is* one vertex per sign-changing
//! cube and one quad per sign-changing edge, with only placement differing. Both
//! methods therefore run the same engine in `crate::dual` and supply a
//! vertex rule each.
//!
//! Surface Nets averages the cell's edge crossings, which cannot land on a
//! corner. Dual Contouring solves for the point where the crossings' **tangent
//! planes** agree, which can — that is what A-006's Hermite data is for, and why
//! this method is the one a CAD tool needs.
//!
//! ```
//! use isomesh::{MeshBuffer, RuntimeShape3};
//! use isomesh::dual_contouring::DualContouring;
//! use isomesh::fields::BoxExact;
//!
//! let mut dc = DualContouring::<f32>::new();
//! let mut out = MeshBuffer::<f32>::new();
//! let shape = RuntimeShape3::new([33; 3])?;
//! dc.extract(&BoxExact::<f32>::canonical(), &shape, [-2.0; 3], 0.125, &mut out)?;
//!
//! assert!(out.triangle_count() > 0);
//! # Ok::<(), isomesh::Error>(())
//! ```
//!
//! # What it costs
//!
//! Two things, both structural rather than incidental.
//!
//! - **The vertex can leave its cell.** Surface Nets' centroid is inside by
//! construction; a solved vertex is not, and a vertex outside its own cell is
//! how a dual method self-intersects. A-009 adds the `(1−ε)` clamp and measures
//! what it costs in sharpness against what it buys in self-intersections.
//! Until then this is the unclamped solve, so that A-009 has a baseline.
//! - **One vertex per cell.** Where two sheets of surface share a cell they share
//! its vertex, and the mesh is non-manifold there. Inherited from the shared
//! topology, measured on Surface Nets as M-4 and M-15, and fixed
//! architecturally by A-010's vertex splitting rather than by patching.
//!
//! Neither is a bug to be fixed here. Both are the reason A-009 and A-010 exist.
use crate;
use crateHermiteCell;
use crate::;
/// Dual Contouring over a sampled grid.
///
/// Owns its scratch buffers; reuse it across chunks.
///
/// Deliberately exposes no smoothing control, unlike
/// [`SurfaceNets`](crate::surface_nets::SurfaceNets): a Laplacian pass averages each vertex
/// with its neighbours, which is exactly the operation that destroys the sharp
/// feature the solve just recovered.
/// Whether a solved vertex is confined to the cell that produced it.
///
/// Surface Nets' centroid is inside its cell by construction. A solved vertex is
/// not, and **a vertex outside its own cell is how a dual method
/// self-intersects** — the cells no longer partition space, so two patches can
/// overlap.
///
/// Manson & Schaefer's guarantee is exactly this constraint:
///
/// > As long as the dual vertices lie within their corresponding cells, this
/// > process cannot produce any inverted tetrahedra and creates a partition of
/// > space… Since our decomposition is one-to-one, our surface cannot intersect
/// > itself.
///
/// A-009 measured what it costs here; the numbers are on that ticket's archive
/// entry and in M-28.
/// How far inside the cell the clamp keeps a vertex, as a fraction of the cell.
///
/// The literature's `(1 − ε)` with `ε = 1e-4`: the cell is scaled about its own
/// centre, so the vertex stays *strictly* interior rather than landing on a face
/// it shares with a neighbour. Strictness is what rules out the degenerate
/// coincidences the partition argument would otherwise have to handle.
pub const CLAMP_EPSILON: f64 = 1e-4;
/// The Dual Contouring vertex rule: the regularized plane-intersection solve.
///
/// Builds the cell's [`HermiteCell`] — a position and a surface normal per edge
/// crossing — and hands it to [`solve::solve`]. All of the mathematics, and all
/// of the justification for this particular form of it, is in that module.
/// Confine a solved vertex to its own cell, if the rule says to.
///
/// Shared by [`Qef`] and
/// [`CycleQef`](crate::manifold_dual_contouring::CycleQef) so the two cannot
/// disagree about what "inside the cell" means — a disagreement that would show
/// up only as a self-intersection count nobody could account for.
///
/// The cell is scaled about its own centre by `(1 − ε)`. `min`/`max` is a
/// *continuous* operation, which is the reason this does not reintroduce the
/// failure ✗12 is about: a vertex pushed against a wall slides along it as the
/// field moves, where an SVD rank branch jumps. Clamping costs sharpness, not
/// stability.
pub