1use std::f64::consts::PI;
93
94use crate::catmull_clark::stencils::{Sparse, merge, pack};
95use crate::{
96 Adjacency, BoundaryInterpolation, KernelError, Mesh, RefinementResult, Scheme, SchemeOptions,
97 StencilTable,
98};
99
100#[derive(Debug, Clone, PartialEq)]
109pub struct LimitStencils {
110 pub position: StencilTable,
112 pub tangent1: StencilTable,
114 pub tangent2: StencilTable,
116}
117
118#[derive(Debug, Clone, PartialEq)]
126pub struct SectoredLimitStencils {
127 pub position: StencilTable,
130 pub tangent1: StencilTable,
132 pub tangent2: StencilTable,
134 pub corner_sector: Vec<u32>,
140}
141
142impl RefinementResult {
143 pub fn limit_stencils(&self) -> Result<LimitStencils, KernelError> {
150 build_limit_stencils(self)
151 }
152
153 pub fn compose_limit_stencils(
162 &self,
163 input_vertex_count: usize,
164 ) -> Result<LimitStencils, KernelError> {
165 let limit = self.limit_stencils()?;
166 let cage_to_refined = self.compose_stencils(input_vertex_count);
167 Ok(LimitStencils {
168 position: cage_to_refined.compose(&limit.position),
169 tangent1: cage_to_refined.compose(&limit.tangent1),
170 tangent2: cage_to_refined.compose(&limit.tangent2),
171 })
172 }
173
174 pub fn sectored_limit_stencils(&self) -> Result<SectoredLimitStencils, KernelError> {
182 build_sectored_limit_stencils(self)
183 }
184
185 pub fn compose_sectored_limit_stencils(
195 &self,
196 input_vertex_count: usize,
197 ) -> Result<SectoredLimitStencils, KernelError> {
198 let sectored = self.sectored_limit_stencils()?;
199 let cage_to_refined = self.compose_stencils(input_vertex_count);
200 Ok(SectoredLimitStencils {
201 position: cage_to_refined.compose(§ored.position),
202 tangent1: cage_to_refined.compose(§ored.tangent1),
203 tangent2: cage_to_refined.compose(§ored.tangent2),
204 corner_sector: sectored.corner_sector,
205 })
206 }
207}
208
209pub(crate) struct Ring {
219 pub(crate) neighbors: Vec<u32>,
221 pub(crate) edges: Vec<u32>,
223 pub(crate) diagonals: Vec<u32>,
225 pub(crate) faces: Vec<u32>,
228 pub(crate) boundary: bool,
230}
231
232impl Ring {
233 pub(crate) fn rotated(&self, by: usize) -> Self {
236 debug_assert!(!self.boundary, "boundary fans cannot be rotated");
237 let shift = |ring: &[u32]| -> Vec<u32> {
238 (0..ring.len())
239 .map(|i| ring[(i + by) % ring.len()])
240 .collect()
241 };
242 Self {
243 neighbors: shift(&self.neighbors),
244 edges: shift(&self.edges),
245 diagonals: shift(&self.diagonals),
246 faces: shift(&self.faces),
247 boundary: false,
248 }
249 }
250}
251
252enum LimitRule {
254 Smooth,
255 Crease([usize; 2]),
257 Corner(Vec<usize>),
260}
261
262struct FanStep {
268 out_edge: u32,
269 in_edge: u32,
270 diagonal: u32,
271 face: u32,
272 used: bool,
273}
274
275pub(crate) fn validate_refined_quads(result: &RefinementResult) -> Result<(), KernelError> {
279 if result.scheme != Scheme::CatmullClark {
280 return Err(KernelError::NotImplemented(
281 "limit-surface machinery is only implemented for Catmull-Clark",
282 ));
283 }
284 if result
287 .selected_faces
288 .as_ref()
289 .is_some_and(|mask| mask.iter().any(|&selected| !selected))
290 {
291 return Err(KernelError::NotImplemented(
292 "limit-surface machinery is not defined for partially selected refinements",
293 ));
294 }
295 if result.topology.face_vertex_counts.iter().any(|&c| c != 4) {
296 return Err(KernelError::NotImplemented(
297 "limit-surface machinery needs an all-quad refined level (refine the cage at least \
298 once)",
299 ));
300 }
301 Ok(())
302}
303
304fn validate_limit_topology(result: &RefinementResult) -> Result<(), KernelError> {
308 validate_refined_quads(result)?;
309 if result.options.boundary_interpolation == BoundaryInterpolation::Natural
310 && result.adjacency.edge_is_boundary.iter().any(|&b| b)
311 {
312 return Err(KernelError::NotImplemented(
313 "limit stencils for BoundaryInterpolation::Natural on open meshes",
314 ));
315 }
316 Ok(())
317}
318
319fn build_limit_stencils(result: &RefinementResult) -> Result<LimitStencils, KernelError> {
320 validate_limit_topology(result)?;
321 let mesh = &result.topology;
322 let adjacency = &result.adjacency;
323
324 let vertex_count = mesh.vertex_count as usize;
325 let mut position_rows: Vec<Sparse> = Vec::with_capacity(vertex_count);
326 let mut tangent1_rows: Vec<Sparse> = Vec::with_capacity(vertex_count);
327 let mut tangent2_rows: Vec<Sparse> = Vec::with_capacity(vertex_count);
328
329 for vi in 0..vertex_count {
330 let ring = vertex_ring(vi, mesh, adjacency)?;
331 let masks = match classify(vi, &ring, mesh, adjacency, &result.options) {
332 LimitRule::Smooth => smooth_masks(vi as u32, &ring),
333 LimitRule::Crease(ends) => crease_masks(vi as u32, &ring, ends),
334 LimitRule::Corner(_) => corner_masks(vi as u32, &ring),
335 };
336 debug_assert!(
337 (masks.position.iter().map(|&(_, w)| w).sum::<f32>() - 1.0).abs() < 1e-4,
338 "limit position mask of vertex {vi} is not affine",
339 );
340 debug_assert!(
341 masks.tangent1.iter().map(|&(_, w)| w).sum::<f32>().abs() < 1e-4
342 && masks.tangent2.iter().map(|&(_, w)| w).sum::<f32>().abs() < 1e-4,
343 "limit tangent masks of vertex {vi} are not derivations",
344 );
345 position_rows.push(masks.position);
346 tangent1_rows.push(masks.tangent1);
347 tangent2_rows.push(masks.tangent2);
348 }
349
350 Ok(LimitStencils {
351 position: pack(&position_rows),
352 tangent1: pack(&tangent1_rows),
353 tangent2: pack(&tangent2_rows),
354 })
355}
356
357fn build_sectored_limit_stencils(
358 result: &RefinementResult,
359) -> Result<SectoredLimitStencils, KernelError> {
360 validate_limit_topology(result)?;
361 let mesh = &result.topology;
362 let adjacency = &result.adjacency;
363
364 let vertex_count = mesh.vertex_count as usize;
365 let mut position_rows: Vec<Sparse> = Vec::with_capacity(vertex_count);
366 let mut tangent1_rows: Vec<Sparse> = Vec::with_capacity(vertex_count);
367 let mut tangent2_rows: Vec<Sparse> = Vec::with_capacity(vertex_count);
368 let mut corner_sector = vec![u32::MAX; mesh.face_vertex_indices.len()];
369
370 for vi in 0..vertex_count {
371 let ring = vertex_ring(vi, mesh, adjacency)?;
372 let rule = classify(vi, &ring, mesh, adjacency, &result.options);
373 let sectors = vertex_sectors(vi as u32, &ring, rule);
374 debug_assert!(
375 (sectors.position.iter().map(|&(_, w)| w).sum::<f32>() - 1.0).abs() < 1e-4,
376 "limit position mask of vertex {vi} is not affine",
377 );
378 debug_assert!(
379 sectors.tangents.iter().all(|(tangent1, tangent2)| {
380 tangent1.iter().map(|&(_, w)| w).sum::<f32>().abs() < 1e-4
381 && tangent2.iter().map(|&(_, w)| w).sum::<f32>().abs() < 1e-4
382 }),
383 "a sector tangent mask of vertex {vi} is not a derivation",
384 );
385
386 let first_row = tangent1_rows.len() as u32;
389 for (fan_slot, &fi) in ring.faces.iter().enumerate() {
390 let off = (fi * 4) as usize;
391 let corner = mesh.face_vertex_indices[off..off + 4]
392 .iter()
393 .position(|&c| c == vi as u32)
394 .ok_or(KernelError::InvalidTopology(
395 "vertex-face adjacency references a face without that vertex",
396 ))?;
397 corner_sector[off + corner] = first_row + sectors.fan_sector[fan_slot];
398 }
399
400 position_rows.push(sectors.position);
401 for (tangent1, tangent2) in sectors.tangents {
402 tangent1_rows.push(tangent1);
403 tangent2_rows.push(tangent2);
404 }
405 }
406 debug_assert!(
407 corner_sector.iter().all(|&row| row != u32::MAX),
408 "a refined face-corner was not covered by any vertex ring",
409 );
410
411 Ok(SectoredLimitStencils {
412 position: pack(&position_rows),
413 tangent1: pack(&tangent1_rows),
414 tangent2: pack(&tangent2_rows),
415 corner_sector,
416 })
417}
418
419struct VertexSectors {
424 position: Sparse,
425 tangents: Vec<(Sparse, Sparse)>,
426 fan_sector: Vec<u32>,
427}
428
429impl VertexSectors {
430 fn single(masks: LimitMasks, fan_count: usize) -> Self {
432 Self {
433 position: masks.position,
434 tangents: vec![(masks.tangent1, masks.tangent2)],
435 fan_sector: vec![0; fan_count],
436 }
437 }
438}
439
440fn vertex_sectors(vi: u32, ring: &Ring, rule: LimitRule) -> VertexSectors {
443 let fan_count = ring.faces.len();
444 match rule {
445 LimitRule::Smooth => VertexSectors::single(smooth_masks(vi, ring), fan_count),
446 LimitRule::Crease(ends) if ring.boundary => {
448 VertexSectors::single(crease_masks(vi, ring, ends), fan_count)
449 }
450 LimitRule::Crease([lead, trail]) => {
455 let near = crease_masks(vi, ring, [lead, trail]);
456 let far = crease_masks(vi, &ring.rotated(trail), [0, fan_count - (trail - lead)]);
457 VertexSectors {
458 position: near.position,
459 tangents: vec![(near.tangent1, near.tangent2), (far.tangent1, far.tangent2)],
460 fan_sector: (0..fan_count)
461 .map(|slot| u32::from(!(lead..trail).contains(&slot)))
462 .collect(),
463 }
464 }
465 LimitRule::Corner(sharp_slots) if sharp_slots.len() < 2 => {
469 VertexSectors::single(corner_masks(vi, ring), fan_count)
470 }
471 LimitRule::Corner(sharp_slots) => {
477 let wrap = (!ring.boundary).then(|| {
478 let first = sharp_slots[0];
479 let last = sharp_slots[sharp_slots.len() - 1];
481 (last, first + ring.edges.len())
482 });
483 let spans: Vec<(usize, usize)> = sharp_slots
484 .windows(2)
485 .map(|pair| (pair[0], pair[1]))
486 .chain(wrap)
487 .collect();
488 let tangents = spans
489 .iter()
490 .map(|&(lead, trail)| {
491 let masks = if ring.boundary {
492 crease_masks(vi, ring, [lead, trail])
493 } else {
494 crease_masks(vi, &ring.rotated(lead), [0, trail - lead])
495 };
496 (masks.tangent1, masks.tangent2)
497 })
498 .collect();
499 let fan_sector = (0..fan_count)
500 .map(|slot| {
501 let led_by = sharp_slots.partition_point(|&sharp| sharp <= slot);
505 (if led_by == 0 {
506 spans.len() - 1
507 } else {
508 led_by - 1
509 }) as u32
510 })
511 .collect();
512 VertexSectors {
513 position: vec![(vi, 1.0)],
514 tangents,
515 fan_sector,
516 }
517 }
518 }
519}
520
521pub(crate) enum SectorDerivatives {
523 Parametric { d_out: Sparse, d_in: Sparse },
534 Plane { tangent1: Sparse, tangent2: Sparse },
538}
539
540pub(crate) fn corner_limit_sector(
550 face: u32,
551 corner: usize,
552 mesh: &Mesh,
553 adjacency: &Adjacency,
554 options: &SchemeOptions,
555) -> Result<(Sparse, SectorDerivatives), KernelError> {
556 let off = (face * 4) as usize;
557 let vi = mesh.face_vertex_indices[off + corner];
558 let ring = vertex_ring(vi as usize, mesh, adjacency)?;
559 if ring.boundary && options.boundary_interpolation == BoundaryInterpolation::Natural {
560 return Err(KernelError::NotImplemented(
561 "limit evaluation at boundary feature vertices under BoundaryInterpolation::Natural",
562 ));
563 }
564 let rule = classify(vi as usize, &ring, mesh, adjacency, options);
565 let crease_ends = match &rule {
566 LimitRule::Crease(ends) => Some(*ends),
567 _ => None,
568 };
569 let fan_slot =
570 ring.faces
571 .iter()
572 .position(|&f| f == face)
573 .ok_or(KernelError::InvalidTopology(
574 "corner vertex ring does not contain the corner's face",
575 ))?;
576 let mut sectors = vertex_sectors(vi, &ring, rule);
577 let row = sectors.fan_sector[fan_slot] as usize;
578 let (tangent1, tangent2) = sectors.tangents.swap_remove(row);
579
580 let span = crease_ends.map(|[lead, trail]| {
584 if ring.boundary || row == 0 {
585 (lead, trail - lead)
586 } else {
587 (trail, ring.edges.len() - (trail - lead))
588 }
589 });
590 let edge_derivative = |slot: usize| -> Option<Sparse> {
595 let (lead, len) = span?;
596 let rel = if ring.boundary {
597 slot.checked_sub(lead)?
598 } else {
599 (slot + ring.edges.len() - lead) % ring.edges.len()
600 };
601 if rel == 0 {
602 Some(tangent1.clone())
603 } else if rel == len {
604 Some(tangent1.iter().map(|&(i, w)| (i, -w)).collect())
605 } else if len == 2 && rel == 1 {
606 Some(tangent2.clone())
607 } else {
608 None
609 }
610 };
611 let slot_of = |edge: u32| ring.edges.iter().position(|&e| e == edge);
612 let out_slot = slot_of(adjacency.face_edges[off + corner]);
613 let in_slot = slot_of(adjacency.face_edges[off + (corner + 3) % 4]);
614 let derivatives = match (
615 out_slot.and_then(&edge_derivative),
616 in_slot.and_then(&edge_derivative),
617 ) {
618 (Some(d_out), Some(d_in)) => SectorDerivatives::Parametric { d_out, d_in },
619 _ => SectorDerivatives::Plane { tangent1, tangent2 },
620 };
621 Ok((sectors.position, derivatives))
622}
623
624pub(crate) fn vertex_ring(
626 vi: usize,
627 mesh: &Mesh,
628 adjacency: &Adjacency,
629) -> Result<Ring, KernelError> {
630 let face_start = adjacency.vertex_face_offsets[vi] as usize;
631 let face_end = adjacency.vertex_face_offsets[vi + 1] as usize;
632 let incident_faces = &adjacency.vertex_faces[face_start..face_end];
633 if incident_faces.is_empty() {
634 return Err(KernelError::InvalidTopology(
635 "vertex without incident faces has no limit ring",
636 ));
637 }
638
639 let mut steps = incident_faces
640 .iter()
641 .map(|&fi| {
642 let off = (fi * 4) as usize;
645 let corners = &mesh.face_vertex_indices[off..off + 4];
646 corners
647 .iter()
648 .position(|&c| c == vi as u32)
649 .map(|j| FanStep {
650 out_edge: adjacency.face_edges[off + j],
651 in_edge: adjacency.face_edges[off + (j + 3) % 4],
652 diagonal: corners[(j + 2) % 4],
653 face: fi,
654 used: false,
655 })
656 .ok_or(KernelError::InvalidTopology(
657 "vertex-face adjacency references a face without that vertex",
658 ))
659 })
660 .collect::<Result<Vec<_>, _>>()?;
661
662 let boundary = adjacency.vertex_is_boundary[vi];
663 let start = if boundary {
664 steps
667 .iter()
668 .find(|s| adjacency.edge_is_boundary[s.out_edge as usize])
669 .map(|s| s.out_edge)
670 .ok_or(KernelError::InvalidTopology(
671 "boundary vertex has no leading boundary edge (inconsistent face winding)",
672 ))?
673 } else {
674 steps[0].out_edge
675 };
676
677 let face_count = steps.len();
678 let mut edges = Vec::with_capacity(face_count + 1);
679 let mut diagonals = Vec::with_capacity(face_count);
680 let mut faces = Vec::with_capacity(face_count);
681 let mut current = start;
682 edges.push(current);
683 for k in 0..face_count {
684 let step = steps
685 .iter_mut()
686 .find(|s| !s.used && s.out_edge == current)
687 .ok_or(KernelError::InvalidTopology(
688 "vertex ring is not a single oriented fan",
689 ))?;
690 step.used = true;
691 diagonals.push(step.diagonal);
692 faces.push(step.face);
693 current = step.in_edge;
694 if boundary || k + 1 < face_count {
695 edges.push(current);
696 }
697 }
698 if !boundary && current != start {
699 return Err(KernelError::InvalidTopology(
700 "interior vertex ring does not close",
701 ));
702 }
703 if boundary && !adjacency.edge_is_boundary[current as usize] {
704 return Err(KernelError::InvalidTopology(
705 "boundary vertex ring does not end on a boundary edge",
706 ));
707 }
708 let incident_edge_count =
709 (adjacency.vertex_edge_offsets[vi + 1] - adjacency.vertex_edge_offsets[vi]) as usize;
710 if edges.len() != incident_edge_count {
711 return Err(KernelError::InvalidTopology(
712 "vertex ring does not cover all incident edges (non-manifold fan)",
713 ));
714 }
715
716 let neighbors = edges
717 .iter()
718 .map(|&ei| {
719 let [a, b] = mesh.edge_vertices[ei as usize];
720 if a as usize == vi { b } else { a }
721 })
722 .collect();
723
724 Ok(Ring {
725 neighbors,
726 edges,
727 diagonals,
728 faces,
729 boundary,
730 })
731}
732
733fn classify(
735 vi: usize,
736 ring: &Ring,
737 mesh: &Mesh,
738 adjacency: &Adjacency,
739 options: &SchemeOptions,
740) -> LimitRule {
741 let sharp_slots: Vec<usize> = ring
742 .edges
743 .iter()
744 .enumerate()
745 .filter(|&(_, &ei)| {
746 mesh.edge_creases[ei as usize] > 0.0 || adjacency.edge_is_boundary[ei as usize]
747 })
748 .map(|(slot, _)| slot)
749 .collect();
750
751 if mesh.vertex_corners[vi] > 0.0 {
753 return LimitRule::Corner(sharp_slots);
754 }
755 if ring.boundary
758 && options.boundary_interpolation == BoundaryInterpolation::EdgesAndCorners
759 && ring.diagonals.len() <= 1
760 {
761 return LimitRule::Corner(sharp_slots);
762 }
763
764 match sharp_slots.as_slice() {
765 [] | [_] => LimitRule::Smooth,
767 [lead, trail] => LimitRule::Crease([*lead, *trail]),
768 _ => LimitRule::Corner(sharp_slots),
769 }
770}
771
772struct LimitMasks {
774 position: Sparse,
775 tangent1: Sparse,
776 tangent2: Sparse,
777}
778
779fn push(row: &mut Sparse, index: u32, weight: f64) {
782 if weight != 0.0 {
783 merge(row, &[(index, weight as f32)], 1.0);
784 }
785}
786
787fn corner_masks(vi: u32, ring: &Ring) -> LimitMasks {
790 let mut tangent1 = Sparse::new();
791 push(&mut tangent1, vi, -1.0);
792 push(&mut tangent1, ring.neighbors[0], 1.0);
793
794 let mut tangent2 = Sparse::new();
795 push(&mut tangent2, vi, -1.0);
796 push(&mut tangent2, ring.neighbors[1], 1.0);
797
798 LimitMasks {
799 position: vec![(vi, 1.0)],
800 tangent1,
801 tangent2,
802 }
803}
804
805fn crease_masks(vi: u32, ring: &Ring, ends: [usize; 2]) -> LimitMasks {
811 let [lead, trail] = ends;
812
813 let mut position = Sparse::new();
814 push(&mut position, vi, 2.0 / 3.0);
815 push(&mut position, ring.neighbors[lead], 1.0 / 6.0);
816 push(&mut position, ring.neighbors[trail], 1.0 / 6.0);
817
818 let mut tangent1 = Sparse::new();
820 push(&mut tangent1, ring.neighbors[lead], 0.5);
821 push(&mut tangent1, ring.neighbors[trail], -0.5);
822
823 let mut tangent2 = Sparse::new();
826 let interior_edge_count = trail - lead - 1;
827 if interior_edge_count == 1 {
828 push(&mut tangent2, vi, -4.0 / 6.0);
830 push(&mut tangent2, ring.neighbors[lead], -1.0 / 6.0);
831 push(&mut tangent2, ring.neighbors[lead + 1], 4.0 / 6.0);
832 push(&mut tangent2, ring.neighbors[trail], -1.0 / 6.0);
833 push(&mut tangent2, ring.diagonals[lead], 1.0 / 6.0);
834 push(&mut tangent2, ring.diagonals[lead + 1], 1.0 / 6.0);
835 } else if interior_edge_count > 1 {
836 let k = (interior_edge_count + 1) as f64;
838 let theta = PI / k;
839 let cos_theta = theta.cos();
840 let sin_theta = theta.sin();
841 let common_denom = 1.0 / (k * (3.0 + cos_theta));
842 let r = (cos_theta + 1.0) / sin_theta;
843
844 push(
845 &mut tangent2,
846 vi,
847 4.0 * r * (cos_theta - 1.0) * common_denom,
848 );
849 let crease_weight = -r * (1.0 + 2.0 * cos_theta) * common_denom;
850 push(&mut tangent2, ring.neighbors[lead], crease_weight);
851 push(&mut tangent2, ring.neighbors[trail], crease_weight);
852 push(
853 &mut tangent2,
854 ring.diagonals[lead],
855 sin_theta * common_denom,
856 );
857 for i in 1..interior_edge_count + 1 {
858 let sin_theta_i = (i as f64 * theta).sin();
859 let sin_theta_i_plus_1 = ((i + 1) as f64 * theta).sin();
860 push(
861 &mut tangent2,
862 ring.neighbors[lead + i],
863 4.0 * sin_theta_i * common_denom,
864 );
865 push(
866 &mut tangent2,
867 ring.diagonals[lead + i],
868 (sin_theta_i + sin_theta_i_plus_1) * common_denom,
869 );
870 }
871 } else {
872 push(&mut tangent2, vi, -6.0);
875 push(&mut tangent2, ring.neighbors[lead], 3.0);
876 push(&mut tangent2, ring.neighbors[trail], 3.0);
877 }
878
879 LimitMasks {
880 position,
881 tangent1,
882 tangent2,
883 }
884}
885
886fn smooth_masks(vi: u32, ring: &Ring) -> LimitMasks {
890 let valence = ring.diagonals.len();
891 if valence == 2 {
892 return corner_masks(vi, ring);
893 }
894
895 let mut position = Sparse::new();
896 let mut tangent1 = Sparse::new();
897 let mut tangent2 = Sparse::new();
898
899 if valence == 4 {
900 push(&mut position, vi, 4.0 / 9.0);
901 let tan1_edge = [4.0, 0.0, -4.0, 0.0];
902 let tan1_face = [1.0, -1.0, -1.0, 1.0];
903 let tan2_edge = [0.0, 4.0, 0.0, -4.0];
904 let tan2_face = [1.0, 1.0, -1.0, -1.0];
905 for i in 0..4 {
906 push(&mut position, ring.neighbors[i], 1.0 / 9.0);
907 push(&mut position, ring.diagonals[i], 1.0 / 36.0);
908 push(&mut tangent1, ring.neighbors[i], tan1_edge[i]);
909 push(&mut tangent1, ring.diagonals[i], tan1_face[i]);
910 push(&mut tangent2, ring.neighbors[i], tan2_edge[i]);
911 push(&mut tangent2, ring.diagonals[i], tan2_face[i]);
912 }
913 } else {
914 let n = valence as f32;
916 let face_weight = 1.0 / (n * (n + 5.0));
917 let edge_weight = 4.0 * face_weight;
918 push(
919 &mut position,
920 vi,
921 (1.0 - n * (edge_weight + face_weight)) as f64,
922 );
923
924 let theta = 2.0 * PI / valence as f64;
926 let cos_theta = theta.cos();
927 let cos_half_theta = (theta * 0.5).cos();
928 let lambda = (5.0 / 16.0)
929 + (1.0 / 16.0) * (cos_theta + cos_half_theta * (2.0 * (9.0 + cos_theta)).sqrt());
930 let face_weight_scale = 1.0 / (4.0 * lambda - 1.0);
931
932 let rotated = |i: usize| (i + valence - 1) % valence;
934 for i in 0..valence {
935 push(&mut position, ring.neighbors[i], edge_weight as f64);
936 push(&mut position, ring.diagonals[i], face_weight as f64);
937
938 let cos_theta_i = (i as f64 * theta).cos();
939 let cos_theta_i_plus_1 = ((i + 1) as f64 * theta).cos();
940 push(&mut tangent1, ring.neighbors[i], 4.0 * cos_theta_i);
941 push(
942 &mut tangent1,
943 ring.diagonals[i],
944 face_weight_scale * (cos_theta_i + cos_theta_i_plus_1),
945 );
946
947 let j = rotated(i);
948 let cos_theta_j = (j as f64 * theta).cos();
949 let cos_theta_j_plus_1 = ((j + 1) as f64 * theta).cos();
950 push(&mut tangent2, ring.neighbors[i], 4.0 * cos_theta_j);
951 push(
952 &mut tangent2,
953 ring.diagonals[i],
954 face_weight_scale * (cos_theta_j + cos_theta_j_plus_1),
955 );
956 }
957 }
958
959 LimitMasks {
960 position,
961 tangent1,
962 tangent2,
963 }
964}
965
966#[cfg(test)]
967mod tests {
968 use core::num::NonZeroU8;
969
970 use crate::{KernelError, Mesh, Refiner, Scheme, SchemeOptions, UniformRefine};
971
972 fn grid() -> Mesh {
976 Mesh {
977 vertex_count: 9,
978 face_vertex_counts: vec![4; 4],
979 face_vertex_indices: vec![0, 3, 4, 1, 1, 4, 5, 2, 3, 6, 7, 4, 4, 7, 8, 5],
980 edge_vertices: Vec::new(),
981 edge_creases: Vec::new(),
982 vertex_corners: vec![0.0; 9],
983 }
984 }
985
986 fn refine(
987 scheme: Scheme,
988 options: SchemeOptions,
989 req: &UniformRefine,
990 ) -> crate::RefinementResult {
991 let refiner = Refiner::new(grid(), scheme, options).expect("refiner");
992 refiner.refine_uniform(req).expect("refinement")
993 }
994
995 #[test]
996 fn non_catmull_clark_scheme_is_rejected() {
997 let result = refine(
998 Scheme::DooSabin,
999 SchemeOptions::default(),
1000 &UniformRefine::default(),
1001 );
1002 assert!(matches!(
1003 result.limit_stencils(),
1004 Err(KernelError::NotImplemented(_)),
1005 ));
1006 assert!(matches!(
1007 result.sectored_limit_stencils(),
1008 Err(KernelError::NotImplemented(_)),
1009 ));
1010 }
1011
1012 #[test]
1013 fn partial_face_selection_is_rejected() {
1014 let req = UniformRefine {
1015 levels: NonZeroU8::new(1).unwrap(),
1017 selected_faces: Some(vec![true, true, true, false]),
1018 ..Default::default()
1019 };
1020 let result = refine(Scheme::CatmullClark, SchemeOptions::default(), &req);
1021 assert!(matches!(
1022 result.limit_stencils(),
1023 Err(KernelError::NotImplemented(_)),
1024 ));
1025 assert!(matches!(
1026 result.sectored_limit_stencils(),
1027 Err(KernelError::NotImplemented(_)),
1028 ));
1029 }
1030
1031 #[test]
1032 fn natural_boundary_on_open_mesh_is_rejected() {
1033 let options = SchemeOptions {
1034 boundary_interpolation: crate::BoundaryInterpolation::Natural,
1035 ..Default::default()
1036 };
1037 let result = refine(Scheme::CatmullClark, options, &UniformRefine::default());
1038 assert!(matches!(
1039 result.limit_stencils(),
1040 Err(KernelError::NotImplemented(_)),
1041 ));
1042 assert!(matches!(
1043 result.sectored_limit_stencils(),
1044 Err(KernelError::NotImplemented(_)),
1045 ));
1046 }
1047}