use crate::limit::{validate_refined_quads, vertex_ring};
use crate::{Adjacency, KernelError, Mesh, RefinementResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum QuadClass {
Regular,
Feature,
}
#[derive(Debug, Clone, PartialEq)]
pub struct PatchTable {
pub control_points: Vec<[u32; 16]>,
pub faces: Vec<u32>,
face_patches: Vec<u32>,
}
impl RefinementResult {
pub fn patch_table(&self) -> Result<PatchTable, KernelError> {
build_patch_table(self)
}
}
impl PatchTable {
pub fn len(&self) -> usize {
self.control_points.len()
}
pub fn is_empty(&self) -> bool {
self.control_points.is_empty()
}
pub fn quad_class(&self, face: u32) -> QuadClass {
if self.face_patches[face as usize] == u32::MAX {
QuadClass::Feature
} else {
QuadClass::Regular
}
}
pub fn face_patch(&self, face: u32) -> Option<u32> {
let patch = self.face_patches[face as usize];
(patch != u32::MAX).then_some(patch)
}
pub fn eval(&self, patch: usize, uv: [f32; 2], control_positions: &[[f32; 3]]) -> [f32; 3] {
let wu = basis(uv[0] as f64);
let wv = basis(uv[1] as f64);
tensor(&self.control_points[patch], &wu, &wv, control_positions)
}
pub fn eval_with_derivatives(
&self,
patch: usize,
uv: [f32; 2],
control_positions: &[[f32; 3]],
) -> ([f32; 3], [f32; 3], [f32; 3]) {
let points = &self.control_points[patch];
let (u, v) = (uv[0] as f64, uv[1] as f64);
let (wu, wv) = (basis(u), basis(v));
let (du, dv) = (derivative(u), derivative(v));
(
tensor(points, &wu, &wv, control_positions),
tensor(points, &du, &wv, control_positions),
tensor(points, &wu, &dv, control_positions),
)
}
pub(crate) fn position_weights(&self, patch: usize, uv: [f32; 2]) -> [(u32, f64); 16] {
let points = &self.control_points[patch];
let wu = basis(uv[0] as f64);
let wv = basis(uv[1] as f64);
core::array::from_fn(|slot| (points[slot], wu[slot % 4] * wv[slot / 4]))
}
}
fn basis(t: f64) -> [f64; 4] {
let s = 1.0 - t;
[
s * s * s / 6.0,
(3.0 * t * t * t - 6.0 * t * t + 4.0) / 6.0,
(-3.0 * t * t * t + 3.0 * t * t + 3.0 * t + 1.0) / 6.0,
t * t * t / 6.0,
]
}
fn derivative(t: f64) -> [f64; 4] {
let s = 1.0 - t;
[
-0.5 * s * s,
1.5 * t * t - 2.0 * t,
-1.5 * t * t + t + 0.5,
0.5 * t * t,
]
}
fn tensor(
points: &[u32; 16],
wu: &[f64; 4],
wv: &[f64; 4],
control_positions: &[[f32; 3]],
) -> [f32; 3] {
let mut acc = [0.0f64; 3];
for (j, &row_weight) in wv.iter().enumerate() {
for (i, &column_weight) in wu.iter().enumerate() {
let p = control_positions[points[4 * j + i] as usize];
let w = column_weight * row_weight;
acc[0] += w * p[0] as f64;
acc[1] += w * p[1] as f64;
acc[2] += w * p[2] as f64;
}
}
[acc[0] as f32, acc[1] as f32, acc[2] as f32]
}
const fn grid(i: usize, j: usize) -> usize {
4 * j + i
}
const RING_GRID: [[usize; 5]; 4] = [
[grid(0, 1), grid(1, 0), grid(0, 2), grid(0, 0), grid(2, 0)],
[grid(2, 0), grid(3, 1), grid(1, 0), grid(3, 0), grid(3, 2)],
[grid(3, 2), grid(2, 3), grid(3, 1), grid(3, 3), grid(1, 3)],
[grid(1, 3), grid(0, 2), grid(2, 3), grid(0, 3), grid(0, 1)],
];
fn build_patch_table(result: &RefinementResult) -> Result<PatchTable, KernelError> {
validate_refined_quads(result)?;
let mesh = &result.topology;
let adjacency = &result.adjacency;
let regular_corner: Vec<bool> = (0..mesh.vertex_count as usize)
.map(|vi| {
let start = adjacency.vertex_edge_offsets[vi] as usize;
let end = adjacency.vertex_edge_offsets[vi + 1] as usize;
!adjacency.vertex_is_boundary[vi]
&& end - start == 4
&& mesh.vertex_corners[vi] <= 0.0
&& adjacency.vertex_edges[start..end].iter().all(|&ei| {
mesh.edge_creases[ei as usize] <= 0.0
&& !adjacency.edge_is_boundary[ei as usize]
})
})
.collect();
let face_count = mesh.face_vertex_counts.len();
let mut control_points = Vec::new();
let mut faces = Vec::new();
let mut face_patches = vec![u32::MAX; face_count];
for (face, corners) in mesh.face_vertex_indices.chunks_exact(4).enumerate() {
if corners.iter().all(|&c| regular_corner[c as usize]) {
face_patches[face] = control_points.len() as u32;
control_points.push(extract_control_points(
face as u32,
corners,
mesh,
adjacency,
)?);
faces.push(face as u32);
}
}
Ok(PatchTable {
control_points,
faces,
face_patches,
})
}
fn extract_control_points(
face: u32,
corners: &[u32],
mesh: &Mesh,
adjacency: &Adjacency,
) -> Result<[u32; 16], KernelError> {
let mut points = [u32::MAX; 16];
let mut place = |slot: usize, vertex: u32| {
debug_assert!(
points[slot] == u32::MAX || points[slot] == vertex,
"control-point slot {slot} of face {face} disagrees between corner rings",
);
points[slot] = vertex;
};
place(grid(1, 1), corners[0]);
place(grid(2, 1), corners[1]);
place(grid(2, 2), corners[2]);
place(grid(1, 2), corners[3]);
for (k, &corner) in corners.iter().enumerate() {
let ring = vertex_ring(corner as usize, mesh, adjacency)?;
let slot =
ring.faces
.iter()
.position(|&f| f == face)
.ok_or(KernelError::InvalidTopology(
"quad corner ring does not contain the quad",
))?;
let ring = ring.rotated(slot);
debug_assert_eq!(
ring.neighbors[0],
corners[(k + 1) % 4],
"rotated ring of corner {k} does not lead with the quad's out-edge",
);
debug_assert_eq!(
ring.diagonals[0],
corners[(k + 2) % 4],
"rotated ring of corner {k} does not see the quad's diagonal",
);
debug_assert_eq!(
ring.neighbors[1],
corners[(k + 3) % 4],
"rotated ring of corner {k} does not trail into the quad's in-edge",
);
let [n2, n3, d1, d2, d3] = RING_GRID[k];
place(n2, ring.neighbors[2]);
place(n3, ring.neighbors[3]);
place(d1, ring.diagonals[1]);
place(d2, ring.diagonals[2]);
place(d3, ring.diagonals[3]);
}
debug_assert!(
points.iter().all(|&p| p != u32::MAX),
"regular quad {face} did not fill its 4x4 neighborhood",
);
Ok(points)
}
#[cfg(test)]
mod tests {
use core::num::NonZeroU8;
use crate::{KernelError, Mesh, Refiner, Scheme, SchemeOptions, UniformRefine};
fn grid() -> Mesh {
Mesh {
vertex_count: 9,
face_vertex_counts: vec![4; 4],
face_vertex_indices: vec![0, 3, 4, 1, 1, 4, 5, 2, 3, 6, 7, 4, 4, 7, 8, 5],
edge_vertices: Vec::new(),
edge_creases: Vec::new(),
vertex_corners: vec![0.0; 9],
}
}
#[test]
fn non_catmull_clark_scheme_is_rejected() {
let refiner =
Refiner::new(grid(), Scheme::DooSabin, SchemeOptions::default()).expect("refiner");
let result = refiner
.refine_uniform(&UniformRefine::default())
.expect("refinement");
assert!(matches!(
result.patch_table(),
Err(KernelError::NotImplemented(_)),
));
}
#[test]
fn partial_face_selection_is_rejected() {
let req = UniformRefine {
levels: NonZeroU8::new(1).unwrap(),
selected_faces: Some(vec![true, true, true, false]),
..Default::default()
};
let refiner =
Refiner::new(grid(), Scheme::CatmullClark, SchemeOptions::default()).expect("refiner");
let result = refiner.refine_uniform(&req).expect("refinement");
assert!(matches!(
result.patch_table(),
Err(KernelError::NotImplemented(_)),
));
}
}