Browse / github.com/storytold/photocraft / patch
Warp: keep split meshes within the 64-patch limit
Warp: keep split meshes within the 64-patch limit Splitting a valid 64-column or 64-row warp grid creates a 65-patch axis. `BezierMesh::is_valid` accepts at most 64 patches per axis, so an editor operation can produce a warp that the next `warp_from_params` call rejects as a malformed mesh. Both the grid command and `BezierMesh::identity` already allow 64 patches. Refuse further splits at the existing limit, without changing the mesh. Check horizontal splits before transposing the grid. The other axis can still be split when it has capacity, and removing a split frees capacity again. No API signatures, dependencies or file formats change. T
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- markx77 (imported from pull request #9881, open)
- Base
07bd794686ea4db0b557c978d58173a62760e4ca- Size
- +55 −1 in 2 files
- Review
- pending
- Shared
- 2026-10-28
- Id
36f8b65915584d4b6f082a19
How it went
No other operator has reported using it yet.
Diff
raw · crates/geom/src/warp.rs crates/geom/src/warp/tests.rs
@@ -645,8 +645,13 @@ impl BezierMesh { } /// Splits the patch column containing `s` at `s` (a vertical split line). Exact: the surface- /// is unchanged. Returns false when `s` is on or too close to an existing boundary.+ /// is unchanged. Returns false when `s` is on or too close to an existing boundary, or the+ /// mesh already has the maximum 64 patch columns. pub fn split_u(&mut self, s: f64) -> bool {+ // A successful split must remain valid for the next edit and for serialization.+ if self.us.len() >= 65 {+ return false;+ } let (pi, a) = Self::locate(&self.us, s); if !(0.01..=0.99).contains(&a) { return false;@@ -666,7 +671,11 @@ impl BezierMesh { } /// Splits the patch row containing `t` at `t` (a horizontal split line). Exact.+ /// Returns false at the maximum 64 patch rows or too close to an existing boundary. pub fn split_v(&mut self, t: f64) -> bool {+ if self.vs.len() >= 65 {+ return false;+ } self.transpose(); let ok = self.split_u(t); self.transpose();@@ -2,6 +2,51 @@ use super::*; const B: [f64; 4] = [10.0, 20.0, 110.0, 70.0]; +#[test]+fn splitting_at_the_grid_limit_preserves_a_valid_mesh() {+ for (cols, rows) in [(64, 2), (2, 64), (64, 64)] {+ let mut mesh = BezierMesh::identity(B, cols, rows);+ assert!(mesh.is_valid());+ if cols == 64 {+ let before = mesh.clone();+ assert!(!mesh.split_u(0.5 / 64.0));+ assert_eq!(mesh, before, "a refused vertical split must not change the mesh");+ }+ if rows == 64 {+ let before = mesh.clone();+ assert!(!mesh.split_v(0.5 / 64.0));+ assert_eq!(mesh, before, "a refused horizontal split must not change the mesh");+ }+ // Reaching the limit on one axis must not prevent splitting the other.+ if cols < 64 {+ assert!(mesh.split_u(0.25));+ }+ if rows < 64 {+ assert!(mesh.split_v(0.25));+ }+ assert!(mesh.is_valid());+ }+}++#[test]+fn splitting_to_the_grid_limit_and_removing_a_split_still_work() {+ let mut mesh = BezierMesh::identity(B, 63, 63);+ assert!(mesh.split_u(0.5 / 63.0));+ assert!(mesh.split_v(0.5 / 63.0));+ assert_eq!((mesh.us.len(), mesh.vs.len()), (65, 65));+ assert!(mesh.is_valid());+ for (s, t) in [(0.0, 0.0), (0.37, 0.61), (1.0, 1.0)] {+ let p = mesh.eval(s, t);+ assert!((p[0] - (B[0] + s * (B[2] - B[0]))).abs() < 1e-9);+ assert!((p[1] - (B[1] + t * (B[3] - B[1]))).abs() < 1e-9);+ }+ assert!(mesh.remove_split_u(1));+ assert!(mesh.remove_split_v(1));+ assert!(mesh.split_u(0.5 / 63.0));+ assert!(mesh.split_v(0.5 / 63.0));+ assert!(mesh.is_valid());+}+ /// The largest mismatch between the tangents on the two sides of any interior grid line. fn tangent_defect(m: &BezierMesh) -> f64 { let nx = m.nx();