diff --git a/crates/difftest/src/units/surface_surface_p9.rs b/crates/difftest/src/units/surface_surface_p9.rs new file mode 100644 index 0000000000..f032f966ee --- /dev/null +++ b/crates/difftest/src/units/surface_surface_p9.rs @@ -0,0 +1,318 @@ +//! Differential runner for surface.surface_p9. Reference: SmSurface.cpp:11644, +//! 13887, 14877. A quadratic Bezier patch is used on both sides. CSI fixtures use +//! one planar patch and one line span, so both subdivision trees have one leaf. +use crate::{Case, CaseContext, HarnessError, Output, Rng, UnitDiff}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use solidcore::curve::curve::Curve; +use solidcore::curve::curve_cache::{CurveCache, CurveCacheOptions}; +use solidcore::geom::extent1d::Extent1d; +use solidcore::geom::extent2d::Extent2d; +use solidcore::geom::extent3d::Extent3d; +use solidcore::geom::global_solver::{GlobalSolver, SolverOperation}; +use solidcore::geom::pseudo_box::PseudoBox; +use solidcore::geom::tree::{ + AuxDataType, NodeClass, NodeData, NodeId, SplitDir, SurfaceAux, Tree, TreeNode, TreeOwner, +}; +use solidcore::geom::vector3d::Vec3; +use solidcore::kernel::Kernel; +use solidcore::nurbs::b_spline_curve::BSplineCurve; +use solidcore::nurbs::b_spline_surface::{BSplineSurface, SurfParamType}; +use solidcore::nurbs::crv_basic::Curve as NlCurve; +use solidcore::nurbs::math::{CPoint, NOW}; +use solidcore::nurbs::srf_basic::Surface as NlSurface; +use solidcore::surface::surface::p9::CsiGlobalSolver; +use solidcore::surface::surface::Surface; +use solidcore::surface::surface_cache::{ + BezierAux2d, BezierPatch, SurfaceCache, SurfaceCacheOptions, +}; +use solidcore_ffi::units::surface_surface_p9 as ffi; + +pub struct Runner; +#[derive(Debug, Clone, Serialize, Deserialize)] +struct Inputs { + op: i32, + net: [[f64; 3]; 9], + #[serde(default = "domain")] + domain: [f64; 4], + direction: i32, + controls: [f64; 3], + #[serde(default)] + diagonals: bool, + #[serde(default = "line")] + line: [[f64; 3]; 2], +} +fn domain() -> [f64; 4] { + [0.0, 0.0, 1.0, 1.0] +} +fn line() -> [[f64; 3]; 2] { + [[0.5, 0.5, -5.0], [0.5, 0.5, 5.0]] +} +fn harness(e: impl std::fmt::Display) -> HarnessError { + HarnessError::new(e.to_string()) +} +fn surface(i: &Inputs) -> Result { + let net = i + .net + .chunks(3) + .map(|row| { + row.iter() + .map(|p| CPoint::new(p[0], p[1], p[2], NOW)) + .collect() + }) + .collect(); + let knots = vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0]; + let nurb = NlSurface::new(net, 2, 2, knots.clone(), knots).map_err(harness)?; + Ok(Surface::from(BSplineSurface::from_nurb(nurb, false))) +} +fn output(flat: Vec) -> Output { + let (status, rest) = flat.split_first().unwrap_or((&-1.0, &[])); + Output::new() + .scalar("status", *status) + .values("out", rest.to_vec()) +} +fn run_rust(i: &Inputs) -> Result { + let k = Kernel::default(); + let surface = surface(i)?; + let direction = + SurfParamType::from_code(i.direction as u64).ok_or_else(|| harness("direction"))?; + let domain = Extent2d::from_bounds(i.domain[0], i.domain[1], i.domain[2], i.domain[3]); + let result = match i.op { + 0 => surface + .find_tessellation_splits( + &k, + direction, + &domain, + i.controls[0], + i.controls[1], + i.controls[2], + i.diagonals, + ) + .map(|v| { + let mut out = vec![0.0]; + out.extend(v); + out + }), + 1 => surface + .create_iso_boundaries(&k, direction, i.controls[0]) + .and_then(|curves| { + let mut out = vec![0.0, if curves.is_some() { 2.0 } else { 0.0 }]; + if let Some((a, b)) = curves { + for curve in [a, b] { + for n in 0..=4 { + let p = curve.evaluate_point( + curve.natural_interval().evaluate(f64::from(n) / 4.0), + )?; + out.extend([p.x, p.y, p.z]); + } + } + } + Ok(out) + }), + 2 => { + let points = i + .line + .iter() + .map(|p| CPoint::new(p[0], p[1], p[2], NOW)) + .collect(); + let curve = Curve::from(BSplineCurve::from_nurb( + NlCurve::new(points, 1, vec![0.0, 0.0, 1.0, 1.0]).map_err(harness)?, + 3, + false, + )); + let interval = Extent1d::from_min_max(0.0, 1.0); + let mut cc = CurveCache::::new( + curve.clone(), + interval, + CurveCacheOptions { + make_full_tree: true, + ..CurveCacheOptions::default() + }, + ); + cc.set_curve_handle(Some(0)); + match cc.tessellate(&k) { + Err(e) => Err(e), + Ok(()) => { + let mut cache = SurfaceCache::::new( + surface, + Extent2d::from_bounds(0.0, 0.0, 1.0, 1.0), + SurfaceCacheOptions::default(), + ); + cache.set_surface_handle(Some(1)); + let uv = cache.uv_domain(); + let mut bbox = Extent3d::new(); + for p in i.net { + bbox = bbox.union(&Extent3d::from_points( + Vec3::new(p[0], p[1], p[2]), + Vec3::new(p[0], p[1], p[2]), + )); + } + cache.patches_mut().push(BezierPatch { + aux: BezierAux2d { + uv_domain: uv, + owning_tree_node: Some(NodeId(0)), + ..BezierAux2d::default() + }, + pseudo_box: PseudoBox::from_extent3d(&bbox), + ..BezierPatch::default() + }); + let mut tree = Tree::new(Some(TreeOwner::SurfaceCache)); + tree.add_node(TreeNode { + bbox, + aux_type: AuxDataType::BezierSurface, + data: NodeData::Surface(SurfaceAux { + split_dir: SplitDir::Unknown, + is_marked: false, + node_class: NodeClass::Unknown, + face: None, + surface: Some(1), + slot: Some(0), + }), + ..TreeNode::new() + }); + tree.set_top_node(Some(NodeId(0))); + cache.set_tree(Some(tree)); + let curve_tree = cc.tree().ok_or_else(|| harness("curve tree"))?; + let tree = cache.tree().ok_or_else(|| harness("surface tree"))?; + let mut base = GlobalSolver::with_trees(curve_tree, tree, 3); + base.operation = SolverOperation::Intersect; + base.tolerance_3d = 1e-6; + let mut solver = CsiGlobalSolver { + base, + curve: &curve, + surface_cache: &cache, + kernel: &k, + found_coincidence: false, + interval, + uv_domain: domain, + }; + solver + .local_solve_branch(&[Some(NodeId(0)), Some(NodeId(0)), None, None]) + .map(|more| { + let mut out = vec![ + 0.0, + f64::from(u8::from(more)), + solver.base.solutions.len() as f64, + ]; + for solution in solver.base.solutions.as_slice() { + out.extend([ + f64::from(solution.solution_type.code()), + solution.num_variables as f64, + solution.num_objects as f64, + solution.start.solution_value, + solution.end.solution_value, + ]); + out.extend(solution.start.parameters.iter().take(3)); + out.extend(solution.end.parameters.iter().take(3)); + out.extend( + solution + .nodes + .iter() + .take(2) + .map(|v| v.map_or(-1.0, |n| n.0 as f64)), + ); + out.extend( + solution + .objects + .iter() + .take(2) + .map(|v| v.map_or(-1.0, |n| n as f64)), + ); + } + out + }) + } + } + } + _ => return Err(harness("invalid op")), + }; + Ok(match result { + Ok(v) => output(v), + Err(e) => Output::unported(&e).unwrap_or_else(|| { + let status = match e { + solidcore::SolidError::Upstream { code, .. } => f64::from(code), + _ => 1007.0, + }; + output(vec![status]) + }), + }) +} +impl UnitDiff for Runner { + fn id(&self) -> &'static str { + "surface.surface_p9" + } + fn cases(&self, ctx: &CaseContext) -> Result, HarnessError> { + let ctx = CaseContext { + seeds: ctx.seeds.max(600), + ..ctx.clone() + }; + crate::runner::default_cases(self, &ctx) + } + fn run_cpp(&self, case: &Case) -> Result { + let i: Inputs = case.parse()?; + ffi::eval( + i.op, + &i.net, + i.domain, + i.direction, + i.controls, + i.diagonals, + i.line, + ) + .map(output) + .map_err(harness) + } + fn run_rust(&self, case: &Case) -> Result { + run_rust(&case.parse()?) + } + fn generate(&self, rng: &mut Rng) -> Option { + let mut net = [[0.0; 3]; 9]; + let scale = rng.uniform(0.5, 3.0); + let height = rng.uniform(0.0, 1.5); + for (index, p) in net.iter_mut().enumerate() { + *p = [ + (index / 3) as f64 * scale, + (index % 3) as f64 * scale, + rng.uniform(-height, height), + ]; + } + let direction = rng.below(2) as i32; + let (op, controls, diagonals) = if rng.chance(0.25) { + (1, [0.01, 0.0, 0.0], false) + } else { + let mode = rng.below(4); + ( + 0, + [ + if mode == 0 { + 0.0 + } else { + rng.uniform(0.02, 0.2) + }, + if mode < 2 { + 0.0 + } else { + rng.uniform(5.0, 30.0) + }, + if mode == 3 { + rng.uniform(0.5, 2.0) + } else { + 0.0 + }, + ], + rng.chance(0.5), + ) + }; + serde_json::to_value(Inputs { + op, + net, + direction, + domain: domain(), + controls, + diagonals, + line: line(), + }) + .ok() + } +} + diff --git a/crates/solidcore-ffi/src/units/surface_surface_p9.rs b/crates/solidcore-ffi/src/units/surface_surface_p9.rs new file mode 100644 index 0000000000..83acc042e4 --- /dev/null +++ b/crates/solidcore-ffi/src/units/surface_surface_p9.rs @@ -0,0 +1,110 @@ +//! Safe wrappers for `surface.surface_p9`; shim: `surface_surface_p9.cpp`. +//! Upstream: `source/SMLib/src/SmSurface.cpp` (tessellation, iso boundaries and CSI). +//! Inputs are one non-rational quadratic 3x3 Bezier patch, a domain within [0,1]^2, +//! bounded controls and, for CSI, a non-degenerate line. No counts cross the FFI. +use crate::{call_with_buffer, FfiError}; + +mod sys { + extern "C" { + pub fn sc_surface_surface_p9_eval( + op: i32, + xyz: *const f64, + bounds: *const f64, + direction: i32, + chord: f64, + angle: f64, + max_side: f64, + diagonals: i32, + line: *const f64, + output: *mut f64, + cap: i64, + out_len: *mut i64, + ) -> i32; + } +} + +/// Evaluates a bounded surface case: op 0 splits, 1 iso boundaries, 2 CSI leaf solve. +/// +/// # Errors +/// Invalid data is rejected before C++; shim failures return `FfiError::Status`. +pub fn eval( + op: i32, + net: &[[f64; 3]; 9], + domain: [f64; 4], + direction: i32, + controls: [f64; 3], + diagonals: bool, + line: [[f64; 3]; 2], +) -> Result, FfiError> { + let invalid = || FfiError::InvalidArgument("unsafe surface p9 input".into()); + if !(0..=2).contains(&op) + || !(0..=9).contains(&direction) + || net + .iter() + .flatten() + .any(|v| !v.is_finite() || v.abs() > 10.0) + || domain + .iter() + .any(|v| !v.is_finite() || !(0.0..=1.0).contains(v)) + || domain[2] - domain[0] < 1e-4 + || domain[3] - domain[1] < 1e-4 + || controls.iter().any(|v| !v.is_finite() || *v < 0.0) + || (controls[0] != 0.0 && !(1e-3..=10.0).contains(&controls[0])) + || (controls[1] != 0.0 && !(1.0..=90.0).contains(&controls[1])) + || (controls[2] != 0.0 && !(0.05..=100.0).contains(&controls[2])) + || line + .iter() + .flatten() + .any(|v| !v.is_finite() || v.abs() > 20.0) + { + return Err(invalid()); + } + if op == 0 && direction > 1 { + return Err(invalid()); + } + if controls[2] > 0.0 { + let distances = if direction == 0 { + [(0, 2), (3, 5), (6, 8)] + } else { + [(0, 6), (1, 7), (2, 8)] + }; + for (a, b) in distances { + let pa = net.get(a).ok_or_else(invalid)?; + let pb = net.get(b).ok_or_else(invalid)?; + if pa + .iter() + .zip(pb) + .map(|(a, b)| (a - b) * (a - b)) + .sum::() + < 1e-4 + { + return Err(invalid()); + } + } + } + if op == 2 && line[0] == line[1] { + return Err(invalid()); + } + let xyz: Vec = net.iter().flatten().copied().collect(); + let ray: Vec = line.iter().flatten().copied().collect(); + call_with_buffer(128, |buffer, len| { + // SAFETY: fixed input lengths, finite bounded values and valid enumerators + // were checked above; the shim copies output only within buffer capacity. + unsafe { + sys::sc_surface_surface_p9_eval( + op, + xyz.as_ptr(), + domain.as_ptr(), + direction, + controls[0], + controls[1], + controls[2], + i32::from(diagonals), + ray.as_ptr(), + buffer.as_mut_ptr(), + buffer.len() as i64, + &raw mut *len, + ) + } + }) +} diff --git a/crates/solidcore/src/surface/surface.rs b/crates/solidcore/src/surface/surface.rs index 28476ecc3d..2d5f6ddecd 100644 --- a/crates/solidcore/src/surface/surface.rs +++ b/crates/solidcore/src/surface/surface.rs @@ -8,5 +8,6 @@ //! functions). pub mod p2; +pub mod p9; pub use p2::{Surface, SurfaceKind}; diff --git a/crates/solidcore/src/surface/surface/p9.rs b/crates/solidcore/src/surface/surface/p9.rs new file mode 100644 index 0000000000..12e36c6f4d --- /dev/null +++ b/crates/solidcore/src/surface/surface/p9.rs @@ -0,0 +1,1024 @@ +//! Surface tessellation splits, iso boundaries and curve/surface leaf intersection. +//! +//! Upstream: `source/SMLib/src/SmSurface.cpp`, `source/SMLib/inc/SmSurface.h`. +//! +//! | Rust | Upstream | +//! |------|----------| +//! | [`Surface::find_tessellation_splits`] | `SmSurface::FindTessellationSplits` | +//! | [`Surface::create_iso_boundaries`] | `SmSurface::CreateIsoBoundaries` | +//! | [`CsiGlobalSolver::local_solve_branch`] | `SmCSIGlobalSolver::LocalSolve` | +//! +//! Deviations: return values replace output arrays. The maximum-length branch +//! recurses once with maximum length zero; an explicit depth limit of two guards +//! that invariant. Split arrays are limited to 1,000,000 entries before allocation +//! or concatenation. Unsupported surface virtuals return `Unported`, never an +//! approximate replacement. Failed tessellations retain the empty parameter array; +//! unported errors are propagated even where upstream ignores a status. + +use super::p2::{merge_double_arrays, Surface, SurfaceKind}; +use crate::curve::crv_on_surf::CrvOnSurf; +use crate::curve::curve::Curve; +use crate::curve::iso_curve::IsoCurve; +use crate::error::{ignored, Result, SolidError}; +use crate::geom::extent1d::Extent1d; +use crate::geom::extent2d::Extent2d; +use crate::kernel::Kernel; +use crate::math::EFF_ZERO_SQRT; +use crate::nurbs::b_spline_curve::BSplineCurve; +use crate::nurbs::b_spline_surface::{BSplineSurface, IsoCurveOptions, SurfParamType}; +use crate::nurbs::crv_basic::Curve as NurbCurve; +use crate::nurbs::math::{CPoint, NOW, NOZ}; + +const OP: &str = "surface::surface::p9::find_tessellation_splits"; +const MAX_SPLITS: usize = 1_000_000; + +fn bss(surface: &Surface) -> Result<&BSplineSurface> { + match surface.kind() { + SurfaceKind::BSpline(s) => Ok(s), + SurfaceKind::CurveBounded(_) => { + // UNPORTED(surface.surface_p5): SmSurface::CreateIsoParametricCurve + Err(SolidError::unported( + "SmSurface::CreateIsoParametricCurve", + "surface.surface_p5", + )) + } + } +} + +fn iso_curve( + k: &Kernel, + surface: &Surface, + direction: SurfParamType, + value: f64, +) -> Result { + Ok(bss(surface)? + .create_iso_parametric_curve(k, direction, value, IsoCurveOptions::default())? + .curve) +} + +fn implicit_iso( + surface: &Surface, + direction: SurfParamType, + value: f64, + left: bool, +) -> Result { + Ok(Curve::from(IsoCurve::new( + bss(surface)?.copy(), + direction, + value, + left, + true, + ))) +} + +fn tessellate(curve: &Curve, interval: &Extent1d, chord: f64, angle: f64) -> Result> { + Ok( + ignored(curve.tessellate_by_bisection(interval, chord, angle, 0.0))? + .map(|t| t.parameters) + .unwrap_or_default(), + ) +} + +fn check_count(n: usize) -> Result<()> { + if n > MAX_SPLITS { + return Err(SolidError::invalid_input( + OP, + "tessellation split limit exceeded", + )); + } + Ok(()) +} + +impl Surface { + /// Creates the curves at the minimum and maximum natural-domain boundary in + /// `direction`. Other enumerators leave both outputs unset, as upstream. + /// + /// # Errors + /// Those of iso-curve construction; `Unported` for unsupported surface kinds. + pub fn create_iso_boundaries( + &self, + k: &Kernel, + direction: SurfParamType, + _tolerance: f64, + ) -> Result> { + let domain = self.natural_uv_domain(); + let (min, max) = match direction { + SurfParamType::U => (domain.min().x, domain.max().x), + SurfParamType::V => (domain.min().y, domain.max().y), + _ => return Ok(None), + }; + let first = iso_curve(k, self, direction, min)?; + let second = iso_curve(k, self, direction, max)?; + Ok(Some((first, second))) + } + + /// Selects increasing parameters along the direction perpendicular to the + /// constant iso parameter, including the interval endpoints. + /// + /// # Errors + /// Evaluation/construction errors, unsupported surface virtuals and bounded + /// split-count or recursion limits. + pub fn find_tessellation_splits( + &self, + k: &Kernel, + direction: SurfParamType, + domain: &Extent2d, + chord_height: f64, + angle_deg: f64, + max_side: f64, + check_diagonals: bool, + ) -> Result> { + self.find_splits_at_depth( + k, + direction, + domain, + chord_height, + angle_deg, + max_side, + check_diagonals, + 0, + ) + } + + #[allow(clippy::too_many_arguments, clippy::too_many_lines)] + fn find_splits_at_depth( + &self, + k: &Kernel, + direction: SurfParamType, + domain: &Extent2d, + chord: f64, + angle: f64, + max_side: f64, + diagonals: bool, + depth: usize, + ) -> Result> { + if depth >= 2 { + return Err(SolidError::invalid_input( + OP, + "tessellation recursion limit exceeded", + )); + } + let interval = if direction == SurfParamType::V { + domain.u_interval() + } else { + domain.v_interval() + }; + if max_side > 0.0 { + let mut length = 0.0; + let mut max_length = -1.0; + let mut longest = None; + for i in 0..=5 { + let uv = domain.evaluate(f64::from(i) / 5.0, f64::from(i) / 5.0); + let iso = if direction == SurfParamType::V { + uv.y + } else { + uv.x + }; + let curve = iso_curve(k, self, direction, iso)?; + let generic = Curve::from(curve.clone()); + if generic.is_degenerate(crate::geom::tol::ZONE_TOL_3D, None)? { + continue; + } + length = generic.approximate_length(&interval, 30, false)?.0; + if length > max_length { + max_length = length; + longest = Some(curve); + } + } + let count = max_length / max_side + 1.0; + if !count.is_finite() || count < 0.0 || count > MAX_SPLITS as f64 { + return Err(SolidError::invalid_input( + OP, + "tessellation split limit exceeded", + )); + } + let count = count.max(0.0) as usize; + let splits = if count < 2 { + vec![interval.min(), interval.max()] + } else { + longest + .ok_or(SolidError::sm_err(OP))? + .equally_spaced_points(interval.min(), interval.max(), count, length / 1000.0)? + .1 + }; + let mut all = Vec::new(); + for (index, span) in splits.windows(2).enumerate() { + let (Some(&start), Some(&end)) = (span.first(), span.last()) else { + continue; + }; + let mut sub_domain = *domain; + let span_interval = Extent1d::from_min_max(start, end); + if direction == SurfParamType::V { + sub_domain.set_u_interval(&span_interval); + } else { + sub_domain.set_v_interval(&span_interval); + } + let mut part = self.find_splits_at_depth( + k, + direction, + &sub_domain, + chord, + angle, + 0.0, + diagonals, + depth + 1, + )?; + if index + 2 < splits.len() { + part.pop(); + } + check_count(all.len().saturating_add(part.len()))?; + all.extend(part); + } + return Ok(if all.len() > splits.len() { + all + } else { + splits + }); + } + if chord == 0.0 && angle == 0.0 { + return Ok(vec![interval.min(), interval.max()]); + } + let linear_tol = if chord == 0.0 { + EFF_ZERO_SQRT + } else { + chord / 10.0 + }; + let mut params = [Vec::new(), Vec::new()]; + let mut linear = [false; 2]; + let mut failures = 0; + for (index, (boundary, fallback)) in [(domain.min(), 0.4), (domain.max(), 0.6)] + .into_iter() + .enumerate() + { + let value = if direction == SurfParamType::V { + boundary.y + } else { + boundary.x + }; + let curve = implicit_iso(self, direction, value, true)?; + let degenerate = curve.is_degenerate(crate::geom::tol::ZONE_TOL_3D, None)?; + let is_linear = curve.is_linear(linear_tol, true)?.linear; + let mut splits = Vec::new(); + let mut fallback_needed = degenerate || is_linear; + if !fallback_needed { + match curve.tessellate_by_bisection(&interval, chord, angle, 0.0) { + Ok(result) => splits = result.parameters, + Err(e) if e.is_unported() => return Err(e), + Err(_) => fallback_needed = true, + } + } + let mut final_linear = is_linear; + if fallback_needed { + let uv = domain.evaluate(fallback, fallback); + let value = if direction == SurfParamType::V { + uv.y + } else { + uv.x + }; + let curve = implicit_iso(self, direction, value, true)?; + final_linear = curve.is_linear(linear_tol, true)?.linear; + if !final_linear { + match curve.tessellate_by_bisection(&interval, chord, angle, 0.0) { + Ok(result) => splits = result.parameters, + Err(e) if e.is_unported() => return Err(e), + Err(_) => failures += 1, + } + } + } + if let Some(p) = params.get_mut(index) { + *p = splits; + } + if let Some(l) = linear.get_mut(index) { + *l = final_linear; + } + } + if diagonals && (linear.iter().all(|&l| l) || failures == 2) { + for (index, (start, end)) in [ + (domain.min(), domain.max()), + ( + Point2::new(domain.min().x, domain.max().y), + Point2::new(domain.max().x, domain.min().y), + ), + ] + .into_iter() + .enumerate() + { + let uv_curve = Curve::from(BSplineCurve::from_nurb( + NurbCurve::new( + vec![ + CPoint::new(start.x, start.y, NOZ, NOW), + CPoint::new(end.x, end.y, NOZ, NOW), + ], + 1, + vec![0.0, 0.0, 1.0, 1.0], + ) + .map_err(SolidError::nl_ser(OP))?, + 2, + false, + )); + let curve = Curve::from(CrvOnSurf::new( + k, + uv_curve.clone(), + bss(self)?.copy(), + Some(*domain), + 0, + )?); + let mut p = tessellate(&curve, &Extent1d::from_min_max(0.0, 1.0), chord, angle)?; + if index == 1 && direction == SurfParamType::U { + p.reverse(); + } + for t in &mut p { + let uv = uv_curve.evaluate_point(*t)?; + *t = if direction == SurfParamType::V { + uv.x + } else { + uv.y + }; + } + if let Some(slot) = params.get_mut(index) { + *slot = p; + } + } + } + let [first, second] = params; + check_count(first.len().saturating_add(second.len()))?; + let merged = merge_double_arrays(&first, &second); + Ok(if merged.is_empty() { + vec![interval.min(), interval.max()] + } else { + merged + }) + } +} + +// ---- curve/surface leaf solver ----------------------------------------------------- + +use crate::curve::curve::p3::{local_curve_intersect, PointSolveOptions}; +use crate::geom::geom_utility::p2::ContinuityType; +use crate::geom::global_solver::{ + Branch, GlobalSolve, GlobalSolver, PreferredSolution, SolutionPairType, SolverOperation, +}; +use crate::geom::solution_array::{Solution, SolutionObject, SolutionType, SortKey}; +use crate::geom::tree::NodeId; +use crate::geom::vector2d::Point2; +use crate::geom::vector3d::Point3; +use crate::math::{max2, EFF_ZERO}; +use crate::surface::surface_cache::{BezierPatch, SurfaceCache}; + +/// Curve/surface intersection solver state (`SmCSIGlobalSolver`). Its constructor +/// belongs to `surface.surface_p1`; fields are explicit so that unit can assemble +/// the solver without global state or pointer casts. +#[derive(Debug, Clone)] +pub struct CsiGlobalSolver<'a, O> { + /// The global traversal and sorted solutions. + pub base: GlobalSolver<'a, O>, + /// Original curve decomposed by tree zero. + pub curve: &'a Curve, + /// Surface cache owning tree one and its patch arena. + pub surface_cache: &'a SurfaceCache, + /// Kernel reached through the geometry's context upstream. + pub kernel: &'a Kernel, + /// Whether coincidence was already found (`m_bFoundCoincidence`). + pub found_coincidence: bool, + /// Curve intersection interval (`m_crInterval`). + pub interval: Extent1d, + /// Surface intersection domain (`m_crUVDomain`). + pub uv_domain: Extent2d, +} + +struct CsiIntersection { + uv: Point2, + parameter: f64, + deviation: f64, +} + +fn surface_to_evaluate(_cache: &SurfaceCache, _node: NodeId) -> Result<&Surface> { + // UNPORTED(surface.surface_p1): sm_GetSurfaceToEvaluate + Err(SolidError::unported( + "sm_GetSurfaceToEvaluate", + "surface.surface_p1", + )) +} +fn node_valid(_cache: &SurfaceCache, _node: NodeId) -> Result { + // UNPORTED(surface.surface_p1): sm_IsNodeValid + Err(SolidError::unported("sm_IsNodeValid", "surface.surface_p1")) +} +fn surface_closed( + _surface: &Surface, + _domain: &Extent2d, + _direction: SurfParamType, +) -> Result<(bool, ContinuityType)> { + // UNPORTED(surface.surface_p7): SmSurface::IsClosed + Err(SolidError::unported( + "SmSurface::IsClosed", + "surface.surface_p7", + )) +} +fn tangent_drop(_surface: &Surface, _uv: Point2, _point: Point3) -> Result { + // UNPORTED(surface.surface_p7): SmSurface::DropPointsToTangentPlane + Err(SolidError::unported( + "SmSurface::DropPointsToTangentPlane", + "surface.surface_p7", + )) +} +fn intersect_surface_curve( + _surface: &Surface, + _domain: &Extent2d, + _curve: &Curve, + _interval: &Extent1d, + _tol: f64, + _uv: Point2, + _t: f64, +) -> Result> { + // UNPORTED(surface.surface_p6): SmSurface::LocalCurveIntersect + Err(SolidError::unported( + "SmSurface::LocalCurveIntersect", + "surface.surface_p6", + )) +} +fn filter_solution( + _surface: &Surface, + _domain: &Extent2d, + _curve: &Curve, + _interval: &Extent1d, + _conts: [[ContinuityType; 2]; 3], + _solution: &Solution, +) -> Result { + // UNPORTED(surface.surface_p5): sm_FilterSolution + Err(SolidError::unported( + "sm_FilterSolution", + "surface.surface_p5", + )) +} +fn point_valid(_cache: &SurfaceCache, _node: NodeId, _uv: Point2) -> Result { + // UNPORTED(surface.surface_p1): sm_IsPointValid + Err(SolidError::unported( + "sm_IsPointValid", + "surface.surface_p1", + )) +} +fn nearby_intersection( + _surface: &Surface, + _domain: &Extent2d, + _curve: &Curve, + _interval: &Extent1d, + _uv: Point2, + _t: f64, +) -> Result> { + // UNPORTED(surface.surface_p4): sm_ClassifyCurveSurfaceXSectPoint + Err(SolidError::unported( + "sm_ClassifyCurveSurfaceXSectPoint", + "surface.surface_p4", + )) +} +fn surface_point_solve( + _surface: &Surface, + _domain: &Extent2d, + _point: Point3, + _guess: Point2, +) -> Result<(bool, Solution)> { + // UNPORTED(surface.surface_p5): SmSurface::LocalPointSolve + Err(SolidError::unported( + "SmSurface::LocalPointSolve", + "surface.surface_p5", + )) +} + +impl<'a, O: SolutionObject + 'a> GlobalSolve<'a, O> for CsiGlobalSolver<'a, O> { + fn solver(&self) -> &GlobalSolver<'a, O> { + &self.base + } + fn solver_mut(&mut self) -> &mut GlobalSolver<'a, O> { + &mut self.base + } + fn local_solve(&mut self, branch: &Branch) -> Result { + self.local_solve_branch(branch) + } + fn identical_solutions( + &self, + _first: &Solution, + _second: &Solution, + ) -> Result<(SolutionPairType, Option)> { + // UNPORTED(surface.surface_p5): SmCSIGlobalSolver::IdenticalSolutions + Err(SolidError::unported( + "SmCSIGlobalSolver::IdenticalSolutions", + "surface.surface_p5", + )) + } +} + +impl<'a, O: SolutionObject + 'a> CsiGlobalSolver<'a, O> { + fn solution(&self, branch: &Branch, t: f64, uv: Point2, deviation: f64) -> Result> { + let mut solution = Solution::new(); + solution.num_variables = 3; + for (slot, value) in solution.start.parameters.iter_mut().zip([t, uv.x, uv.y]) { + *slot = value; + } + solution.start.solution_value = deviation; + solution.solution_type = SolutionType::SingleValue; + solution.num_objects = 2; + solution.objects[0] = self.base.tree(0)?.owner_object()?; + solution.objects[1] = + super::p2::original_surface(self.base.tree(1)?, self.base.branch_node(branch, 1)?)? + .into(); + for (slot, id) in solution + .nodes + .iter_mut() + .take(2) + .zip(branch.iter().copied().take(2)) + { + *slot = id; + } + Ok(solution) + } + + fn keep( + &mut self, + branch: &Branch, + surface: &Surface, + domain: &Extent2d, + interval: &Extent1d, + conts: [[ContinuityType; 2]; 3], + solution: &Solution, + ) -> Result<()> { + if filter_solution(surface, domain, self.curve, interval, conts, solution)? { + let uv = Point2::new(solution.start.param(1)?, solution.start.param(2)?); + let node = branch + .get(1) + .copied() + .flatten() + .ok_or(SolidError::null_pointer(OP))?; + if point_valid(self.surface_cache, node, uv)? { + self.add_sorted_solution(solution, SortKey::ByAllParameters, true)?; + } + } + Ok(()) + } + + /// `SmCSIGlobalSolver::LocalSolve`. Returns the request for more subdivision + /// (always false in the upstream implementation), appending kept solutions to + /// `base.solutions` in upstream order. + /// + /// # Errors + /// Invalid branch/patch data and explicit unported surface operations. No + /// missing solver operation is replaced with a synthetic intersection. + #[allow(clippy::too_many_lines)] + pub fn local_solve_branch(&mut self, branch: &Branch) -> Result { + let curve_node = *self.base.branch_node(branch, 0)?; + let surface_node = *self.base.branch_node(branch, 1)?; + let curve_tree = self.base.tree(0)?; + let span = *curve_tree.curve_span(&curve_node)?; + let surface_id = branch + .get(1) + .copied() + .flatten() + .ok_or(SolidError::null_pointer(OP))?; + let patch: BezierPatch = self.surface_cache.patch(surface_id)?.clone(); + let parent_surface = self.surface_cache.surface(); + let eval_surface = surface_to_evaluate(self.surface_cache, surface_id)?; + let mut domain = patch.aux.uv_domain; + let u_interval = domain.u_interval(); + let v_interval = domain.v_interval(); + let mut interval = *span.interval(); + if !node_valid(self.surface_cache, surface_id)? { + return Ok(false); + } + if domain.are_disjoint(&self.uv_domain, 0.0) || self.interval.are_disjoint(&interval, 0.0) { + return Ok(false); + } + let mut pseudo_box = span.pseudo_box; + pseudo_box.expand_absolute(self.base.tolerance_3d); + if pseudo_box.are_disjoint(&patch.pseudo_box, None) { + return Ok(false); + } + let scaled_zero = EFF_ZERO * (1.0 + curve_node.bbox.max().max_dimension()); + let bigger = curve_node.bbox.size().length() > surface_node.bbox.size().length() * 2.0; + let mut t_conts = span.aux.conts; + let mut u_conts = patch.u_curve_conts; + let mut v_conts = patch.v_curve_conts; + if self.uv_domain.min().x >= domain.min().x { + u_conts[0] = ContinuityType::Discontinuous; + } + if self.uv_domain.max().x <= domain.max().x { + u_conts[1] = ContinuityType::Discontinuous; + } + if self.uv_domain.min().y >= domain.min().y { + v_conts[0] = ContinuityType::Discontinuous; + } + if self.uv_domain.max().y <= domain.max().y { + v_conts[1] = ContinuityType::Discontinuous; + } + if !domain.is_contained_by(&self.uv_domain, EFF_ZERO) { + domain = domain.intersect(&self.uv_domain)?; + if crate::geom::tol::are_same(domain.max().x, domain.min().x) + || crate::geom::tol::are_same(domain.max().y, domain.min().y) + { + return Ok(false); + } + } + if self.interval.min() >= interval.min() { + t_conts[0] = ContinuityType::Discontinuous; + } + if self.interval.max() <= interval.max() { + t_conts[1] = ContinuityType::Discontinuous; + } + if !interval.is_contained_by(&self.interval, EFF_ZERO) { + interval = interval.intersect(&self.interval)?; + if crate::geom::tol::are_same(interval.max(), interval.min()) { + return Ok(false); + } + } + let natural = self.curve.natural_interval(); + if (crate::geom::tol::is_zero(interval.min() - natural.min()) + || crate::geom::tol::is_zero(interval.max() - natural.max())) + && crate::geom::tol::is_zero(self.interval.min() - natural.min()) + && crate::geom::tol::is_zero(self.interval.max() - natural.max()) + && self.curve.is_closed(&natural, 0.0)? + { + let continuity = ignored(crate::curve::curve::p2::evaluate_continuity( + self.curve, + natural.min(), + false, + self.curve, + natural.max(), + true, + 0.5, + ))? + .map_or(ContinuityType::Discontinuous, |c| c.continuity); + if crate::geom::tol::is_zero(interval.min() - natural.min()) { + t_conts[0] = continuity; + } + if crate::geom::tol::is_zero(interval.max() - natural.max()) { + t_conts[1] = continuity; + } + } + let natural_domain = parent_surface.natural_uv_domain(); + for (dir, leaf, parent, work, conts) in [ + ( + SurfParamType::U, + u_interval, + natural_domain.u_interval(), + self.uv_domain.u_interval(), + &mut u_conts, + ), + ( + SurfParamType::V, + v_interval, + natural_domain.v_interval(), + self.uv_domain.v_interval(), + &mut v_conts, + ), + ] { + if (crate::geom::tol::is_zero(leaf.min() - parent.min()) + || crate::geom::tol::is_zero(leaf.max() - parent.max())) + && crate::geom::tol::is_zero(work.min() - parent.min()) + && crate::geom::tol::is_zero(work.max() - parent.max()) + { + let (closed, continuity) = surface_closed(parent_surface, &natural_domain, dir)?; + if closed { + if crate::geom::tol::is_zero(leaf.min() - parent.min()) { + conts[0] = continuity; + } + if crate::geom::tol::is_zero(leaf.max() - parent.max()) { + conts[1] = continuity; + } + } + } + } + let conts = [u_conts, v_conts, t_conts]; + let mut guesses = vec![(domain.evaluate(0.5, 0.5), interval.evaluate(0.5))]; + let mut uv = domain.evaluate(0.5, 0.5); + let mut t = interval.evaluate(0.5); + if bigger { + let point = eval_surface.evaluate_point(uv)?; + let (found, solution) = self.curve.local_point_solve::( + &interval, + SolverOperation::Minimize, + point, + &PointSolveOptions::default(), + t, + )?; + if found { + t = solution.start.param(0)?; + } + } + uv = tangent_drop(eval_surface, uv, self.curve.evaluate_point(t)?)?; + if !domain.contains_point(uv, 0.0) { + if bigger { + uv = domain.clamp_point(uv); + let (found, solution) = self.curve.local_point_solve::( + &interval, + SolverOperation::Minimize, + eval_surface.evaluate_point(uv)?, + &PointSolveOptions::default(), + interval.evaluate(0.5), + )?; + if found { + let t2 = solution.start.param(0)?; + let uv2 = domain.clamp_point(tangent_drop( + eval_surface, + uv, + self.curve.evaluate_point(t2)?, + )?); + guesses.push((uv2, t2)); + } + } else { + uv = domain.evaluate(0.5, 0.5); + t = interval.min(); + uv = tangent_drop(eval_surface, uv, self.curve.evaluate_point(t)?)?; + if !domain.contains_point(uv, 0.0) { + uv = domain.evaluate(0.5, 0.5); + t = interval.max(); + uv = tangent_drop(eval_surface, uv, self.curve.evaluate_point(t)?)?; + if !domain.contains_point(uv, 0.0) { + t = interval.evaluate(0.5); + uv = domain.evaluate(0.5, 0.5); + uv = domain.clamp_point(tangent_drop( + eval_surface, + uv, + self.curve.evaluate_point(t)?, + )?); + for fraction in [0.5, 0.0, 1.0] { + guesses.push((domain.evaluate(0.5, 0.5), interval.evaluate(fraction))); + } + } + } + } + } + if let Some(first) = guesses.first_mut() { + *first = (uv, t); + } + if self.found_coincidence { + guesses.clear(); + } + let mut found_intersection = false; + let mut deviation = 0.0; + let mut index = 0; + while let Some(&(guess_uv, guess_t)) = guesses.get(index) { + let result = intersect_surface_curve( + eval_surface, + &domain, + self.curve, + &interval, + self.base.tolerance_3d, + guess_uv, + guess_t, + )?; + found_intersection = result.is_some(); + if let Some(hit) = result { + deviation = hit.deviation; + let solution = self.solution(branch, hit.parameter, hit.uv, hit.deviation)?; + if !filter_solution( + eval_surface, + &domain, + self.curve, + &interval, + conts, + &solution, + )? { + index += 1; + continue; + } + if point_valid(self.surface_cache, surface_id, hit.uv)? { + self.add_sorted_solution(&solution, SortKey::ByAllParameters, true)?; + } + if super::p2::cs_are_perpen_disjoint(curve_tree, &curve_node, &surface_node)? { + return Ok(false); + } + if index < 1 { + if let Some(new_guess) = ignored(nearby_intersection( + eval_surface, + &domain, + self.curve, + &interval, + hit.uv, + hit.parameter, + ))? + .flatten() + { + if guesses.len() >= 6 { + return Err(SolidError::invalid_input( + OP, + "intersection guess limit exceeded", + )); + } + guesses.push(new_guess); + } + } + } + index += 1; + } + for j in 0..2 { + let t_cont = *t_conts.get(j).ok_or(SolidError::null_pointer(OP))?; + if (t_cont as i32) <= ContinuityType::C0 as i32 && !self.found_coincidence { + let t = if j == 0 { + interval.min() + } else { + interval.max() + }; + let point = self.curve.evaluate_point(t)?; + if pseudo_box.distance_to_point(point) < EFF_ZERO { + let (found, mut solution) = surface_point_solve::( + parent_surface, + &natural_domain, + point, + domain.evaluate(0.5, 0.5), + )?; + if found && solution.start.solution_value < self.base.tolerance_3d { + let uv = Point2::new(solution.start.param(0)?, solution.start.param(1)?); + let value = solution.start.solution_value; + solution = self.solution(branch, t, uv, value)?; + self.keep( + branch, + parent_surface, + &natural_domain, + &interval, + conts, + &solution, + )?; + } + } + } + if !self.surface_cache.process_boundary_curves { + continue; + } + for (dir, cont, constant, iso_interval) in [ + ( + SurfParamType::U, + u_conts.get(j), + if j == 0 { + domain.min().x + } else { + domain.max().x + }, + domain.v_interval(), + ), + ( + SurfParamType::V, + v_conts.get(j), + if j == 0 { + domain.min().y + } else { + domain.max().y + }, + domain.u_interval(), + ), + ] { + if cont.is_none_or(|c| (*c as i32) > ContinuityType::C0 as i32) { + continue; + } + let iso = implicit_iso(eval_surface, dir, constant, j == 0)?; + if iso.is_degenerate(crate::geom::tol::ZONE_TOL_3D, None)? { + continue; + } + let coincident = self.curve.simple_coincidence_checker::( + self.kernel, + &interval, + &iso, + &iso_interval, + self.base.tolerance_3d, + Some(&span.pseudo_box), + Some(&patch.pseudo_box), + [None, None], + )?; + let count = coincident.len(); + let mut max_error = 0.0; + let mut found_coincidence = false; + for mut solution in coincident { + solution.num_variables = 3; + let other_t = solution.start.param(1)?; + let uv = if dir == SurfParamType::U { + Point2::new(constant, other_t) + } else { + Point2::new(other_t, constant) + }; + let t = solution.start.param(0)?; + let value = solution.start.solution_value; + let mut adapted = self.solution(branch, t, uv, value)?; + adapted.solution_type = solution.solution_type; + if found_intersection + && (if dir == SurfParamType::U { + value + EFF_ZERO > deviation + } else { + value > deviation + EFF_ZERO + }) + { + continue; + } + max_error = max2(max_error, value); + if solution.solution_type == SolutionType::RangeOfValues { + found_coincidence = true; + let end_other = solution.end.param(1)?; + let end_uv = if dir == SurfParamType::U { + Point2::new(constant, end_other) + } else { + Point2::new(end_other, constant) + }; + let end_t = solution.end.param(0)?; + for (slot, value) in solution + .end + .parameters + .iter_mut() + .zip([end_t, end_uv.x, end_uv.y]) + { + *slot = value; + } + adapted.end = solution.end; + self.add_sorted_solution(&adapted, SortKey::ByAllParameters, true)?; + } else { + self.keep(branch, eval_surface, &domain, &interval, conts, &adapted)?; + } + } + if found_coincidence { + return Ok(false); + } + let zero = if dir == SurfParamType::U { + EFF_ZERO + } else { + scaled_zero + }; + if max_error > zero || count == 0 { + if let Some(hit) = local_curve_intersect( + &iso, + &iso_interval, + self.curve, + &interval, + self.base.tolerance_3d, + iso_interval.evaluate(0.5), + interval.evaluate(0.5), + )? { + let uv = if dir == SurfParamType::U { + Point2::new(constant, hit.my_param) + } else { + Point2::new(hit.my_param, constant) + }; + let solution = self.solution(branch, hit.other_param, uv, hit.deviation)?; + self.keep(branch, eval_surface, &domain, &interval, conts, &solution)?; + } + } + } + } + Ok(false) + } +} + +#[cfg(test)] +mod tests { + use super::*; + fn plane() -> Surface { + let net = (0..3) + .map(|i| { + (0..3) + .map(|j| CPoint::new(f64::from(i) / 2.0, f64::from(j) / 2.0, 0.0, NOW)) + .collect() + }) + .collect(); + let knots = vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0]; + let n = crate::nurbs::srf_basic::Surface::new(net, 2, 2, knots.clone(), knots).unwrap(); + Surface::from(BSplineSurface::from_nurb(n, false)) + } + #[test] + fn plane_splits_and_natural_boundaries() { + let s = plane(); + let k = Kernel::default(); + let domain = s.natural_uv_domain(); + for direction in [SurfParamType::U, SurfParamType::V] { + assert_eq!( + s.find_tessellation_splits(&k, direction, &domain, 0.01, 10.0, 0.0, true) + .unwrap(), + vec![0.0, 1.0] + ); + let (a, b) = s + .create_iso_boundaries(&k, direction, 0.01) + .unwrap() + .unwrap(); + let a = a.evaluate_point(0.5).unwrap(); + let b = b.evaluate_point(0.5).unwrap(); + if direction == SurfParamType::U { + assert_eq!((a.x, b.x), (0.0, 1.0)); + } else { + assert_eq!((a.y, b.y), (0.0, 1.0)); + } + } + assert!(s + .create_iso_boundaries(&k, SurfParamType::Unknown, 0.01) + .unwrap() + .is_none()); + } + #[test] + fn excessive_split_count_returns_error_before_allocating() { + let s = plane(); + let k = Kernel::default(); + assert!(s + .find_tessellation_splits( + &k, + SurfParamType::U, + &s.natural_uv_domain(), + 0.01, + 0.0, + 1e-100, + false + ) + .is_err()); + } +} diff --git a/fixtures/surface.surface_p9/cases.json b/fixtures/surface.surface_p9/cases.json new file mode 100644 index 0000000000..e0b8dfcc02 --- /dev/null +++ b/fixtures/surface.surface_p9/cases.json @@ -0,0 +1,2038 @@ +{ + "cases": [ + { + "name": "plane_splits_0_0", + "upstream_ref": "source/SMLib/src/SmSurface.cpp:11644 (analytic boundary regression)", + "inputs": { + "op": 0, + "net": [ + [ + 0.0, + 0.0, + 0 + ], + [ + 0.0, + 0.5, + 0 + ], + [ + 0.0, + 1.0, + 0 + ], + [ + 0.5, + 0.0, + 0 + ], + [ + 0.5, + 0.5, + 0 + ], + [ + 0.5, + 1.0, + 0 + ], + [ + 1.0, + 0.0, + 0 + ], + [ + 1.0, + 0.5, + 0 + ], + [ + 1.0, + 1.0, + 0 + ] + ], + "direction": 0, + "controls": [ + 0, + 0, + 0 + ], + "diagonals": true, + "domain": [ + 0, + 0, + 1, + 1 + ] + } + }, + { + "name": "plane_splits_0_1", + "upstream_ref": "source/SMLib/src/SmSurface.cpp:11644 (analytic boundary regression)", + "inputs": { + "op": 0, + "net": [ + [ + 0.0, + 0.0, + 0 + ], + [ + 0.0, + 0.5, + 0 + ], + [ + 0.0, + 1.0, + 0 + ], + [ + 0.5, + 0.0, + 0 + ], + [ + 0.5, + 0.5, + 0 + ], + [ + 0.5, + 1.0, + 0 + ], + [ + 1.0, + 0.0, + 0 + ], + [ + 1.0, + 0.5, + 0 + ], + [ + 1.0, + 1.0, + 0 + ] + ], + "direction": 0, + "controls": [ + 0.01, + 0, + 0 + ], + "diagonals": true, + "domain": [ + 0, + 0, + 1, + 1 + ] + } + }, + { + "name": "plane_splits_0_2", + "upstream_ref": "source/SMLib/src/SmSurface.cpp:11644 (analytic boundary regression)", + "inputs": { + "op": 0, + "net": [ + [ + 0.0, + 0.0, + 0 + ], + [ + 0.0, + 0.5, + 0 + ], + [ + 0.0, + 1.0, + 0 + ], + [ + 0.5, + 0.0, + 0 + ], + [ + 0.5, + 0.5, + 0 + ], + [ + 0.5, + 1.0, + 0 + ], + [ + 1.0, + 0.0, + 0 + ], + [ + 1.0, + 0.5, + 0 + ], + [ + 1.0, + 1.0, + 0 + ] + ], + "direction": 0, + "controls": [ + 0, + 10, + 0 + ], + "diagonals": true, + "domain": [ + 0, + 0, + 1, + 1 + ] + } + }, + { + "name": "plane_splits_0_3", + "upstream_ref": "source/SMLib/src/SmSurface.cpp:11644 (analytic boundary regression)", + "inputs": { + "op": 0, + "net": [ + [ + 0.0, + 0.0, + 0 + ], + [ + 0.0, + 0.5, + 0 + ], + [ + 0.0, + 1.0, + 0 + ], + [ + 0.5, + 0.0, + 0 + ], + [ + 0.5, + 0.5, + 0 + ], + [ + 0.5, + 1.0, + 0 + ], + [ + 1.0, + 0.0, + 0 + ], + [ + 1.0, + 0.5, + 0 + ], + [ + 1.0, + 1.0, + 0 + ] + ], + "direction": 0, + "controls": [ + 0.01, + 10, + 0.3 + ], + "diagonals": true, + "domain": [ + 0, + 0, + 1, + 1 + ] + } + }, + { + "name": "trimmed_domain_0", + "upstream_ref": "source/SMLib/src/SmSurface.cpp:11644 (analytic boundary regression)", + "inputs": { + "op": 0, + "net": [ + [ + 0.0, + 0.0, + 0 + ], + [ + 0.0, + 0.5, + 0 + ], + [ + 0.0, + 1.0, + 0 + ], + [ + 0.5, + 0.0, + 0 + ], + [ + 0.5, + 0.5, + 0 + ], + [ + 0.5, + 1.0, + 0 + ], + [ + 1.0, + 0.0, + 0 + ], + [ + 1.0, + 0.5, + 0 + ], + [ + 1.0, + 1.0, + 0 + ] + ], + "direction": 0, + "controls": [ + 0.01, + 10, + 0 + ], + "diagonals": true, + "domain": [ + 0.2, + 0.3, + 0.8, + 0.9 + ] + } + }, + { + "name": "plane_iso_boundaries_0", + "upstream_ref": "source/SMLib/src/SmSurface.cpp:14877", + "inputs": { + "op": 1, + "net": [ + [ + 0.0, + 0.0, + 0 + ], + [ + 0.0, + 0.5, + 0 + ], + [ + 0.0, + 1.0, + 0 + ], + [ + 0.5, + 0.0, + 0 + ], + [ + 0.5, + 0.5, + 0 + ], + [ + 0.5, + 1.0, + 0 + ], + [ + 1.0, + 0.0, + 0 + ], + [ + 1.0, + 0.5, + 0 + ], + [ + 1.0, + 1.0, + 0 + ] + ], + "direction": 0, + "controls": [ + 0.01, + 0, + 0 + ], + "diagonals": false, + "domain": [ + 0, + 0, + 1, + 1 + ] + } + }, + { + "name": "surf1_subnet_0_False", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:29 (first 3x3 control points of my_load_surf1_pts)", + "inputs": { + "op": 0, + "net": [ + [ + 0, + 0, + -0.6 + ], + [ + 1, + 0.1, + 0.4 + ], + [ + 2, + 0.2, + 0.3 + ], + [ + 0, + 1, + 0.2 + ], + [ + 1, + 1, + 1.5 + ], + [ + 2, + 1, + 1.6 + ], + [ + 0, + 2, + 0.3 + ], + [ + 1, + 2, + 1.6 + ], + [ + 2, + 2, + 1.5 + ] + ], + "direction": 0, + "controls": [ + 0.05, + 15, + 0 + ], + "diagonals": false, + "domain": [ + 0, + 0, + 1, + 1 + ] + } + }, + { + "name": "surf1_subnet_0_True", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:29 (first 3x3 control points of my_load_surf1_pts)", + "inputs": { + "op": 0, + "net": [ + [ + 0, + 0, + -0.6 + ], + [ + 1, + 0.1, + 0.4 + ], + [ + 2, + 0.2, + 0.3 + ], + [ + 0, + 1, + 0.2 + ], + [ + 1, + 1, + 1.5 + ], + [ + 2, + 1, + 1.6 + ], + [ + 0, + 2, + 0.3 + ], + [ + 1, + 2, + 1.6 + ], + [ + 2, + 2, + 1.5 + ] + ], + "direction": 0, + "controls": [ + 0.05, + 15, + 0 + ], + "diagonals": true, + "domain": [ + 0, + 0, + 1, + 1 + ] + } + }, + { + "name": "surf1_max_length_0", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:29", + "inputs": { + "op": 0, + "net": [ + [ + 0, + 0, + -0.6 + ], + [ + 1, + 0.1, + 0.4 + ], + [ + 2, + 0.2, + 0.3 + ], + [ + 0, + 1, + 0.2 + ], + [ + 1, + 1, + 1.5 + ], + [ + 2, + 1, + 1.6 + ], + [ + 0, + 2, + 0.3 + ], + [ + 1, + 2, + 1.6 + ], + [ + 2, + 2, + 1.5 + ] + ], + "direction": 0, + "controls": [ + 0.05, + 15, + 0.8 + ], + "diagonals": true, + "domain": [ + 0, + 0, + 1, + 1 + ] + } + }, + { + "name": "surf1_boundaries_0", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:29", + "inputs": { + "op": 1, + "net": [ + [ + 0, + 0, + -0.6 + ], + [ + 1, + 0.1, + 0.4 + ], + [ + 2, + 0.2, + 0.3 + ], + [ + 0, + 1, + 0.2 + ], + [ + 1, + 1, + 1.5 + ], + [ + 2, + 1, + 1.6 + ], + [ + 0, + 2, + 0.3 + ], + [ + 1, + 2, + 1.6 + ], + [ + 2, + 2, + 1.5 + ] + ], + "direction": 0, + "controls": [ + 0.01, + 0, + 0 + ], + "diagonals": false, + "domain": [ + 0, + 0, + 1, + 1 + ] + } + }, + { + "name": "plane_splits_1_10", + "upstream_ref": "source/SMLib/src/SmSurface.cpp:11644 (analytic boundary regression)", + "inputs": { + "op": 0, + "net": [ + [ + 0.0, + 0.0, + 0 + ], + [ + 0.0, + 0.5, + 0 + ], + [ + 0.0, + 1.0, + 0 + ], + [ + 0.5, + 0.0, + 0 + ], + [ + 0.5, + 0.5, + 0 + ], + [ + 0.5, + 1.0, + 0 + ], + [ + 1.0, + 0.0, + 0 + ], + [ + 1.0, + 0.5, + 0 + ], + [ + 1.0, + 1.0, + 0 + ] + ], + "direction": 1, + "controls": [ + 0, + 0, + 0 + ], + "diagonals": true, + "domain": [ + 0, + 0, + 1, + 1 + ] + } + }, + { + "name": "plane_splits_1_11", + "upstream_ref": "source/SMLib/src/SmSurface.cpp:11644 (analytic boundary regression)", + "inputs": { + "op": 0, + "net": [ + [ + 0.0, + 0.0, + 0 + ], + [ + 0.0, + 0.5, + 0 + ], + [ + 0.0, + 1.0, + 0 + ], + [ + 0.5, + 0.0, + 0 + ], + [ + 0.5, + 0.5, + 0 + ], + [ + 0.5, + 1.0, + 0 + ], + [ + 1.0, + 0.0, + 0 + ], + [ + 1.0, + 0.5, + 0 + ], + [ + 1.0, + 1.0, + 0 + ] + ], + "direction": 1, + "controls": [ + 0.01, + 0, + 0 + ], + "diagonals": true, + "domain": [ + 0, + 0, + 1, + 1 + ] + } + }, + { + "name": "plane_splits_1_12", + "upstream_ref": "source/SMLib/src/SmSurface.cpp:11644 (analytic boundary regression)", + "inputs": { + "op": 0, + "net": [ + [ + 0.0, + 0.0, + 0 + ], + [ + 0.0, + 0.5, + 0 + ], + [ + 0.0, + 1.0, + 0 + ], + [ + 0.5, + 0.0, + 0 + ], + [ + 0.5, + 0.5, + 0 + ], + [ + 0.5, + 1.0, + 0 + ], + [ + 1.0, + 0.0, + 0 + ], + [ + 1.0, + 0.5, + 0 + ], + [ + 1.0, + 1.0, + 0 + ] + ], + "direction": 1, + "controls": [ + 0, + 10, + 0 + ], + "diagonals": true, + "domain": [ + 0, + 0, + 1, + 1 + ] + } + }, + { + "name": "plane_splits_1_13", + "upstream_ref": "source/SMLib/src/SmSurface.cpp:11644 (analytic boundary regression)", + "inputs": { + "op": 0, + "net": [ + [ + 0.0, + 0.0, + 0 + ], + [ + 0.0, + 0.5, + 0 + ], + [ + 0.0, + 1.0, + 0 + ], + [ + 0.5, + 0.0, + 0 + ], + [ + 0.5, + 0.5, + 0 + ], + [ + 0.5, + 1.0, + 0 + ], + [ + 1.0, + 0.0, + 0 + ], + [ + 1.0, + 0.5, + 0 + ], + [ + 1.0, + 1.0, + 0 + ] + ], + "direction": 1, + "controls": [ + 0.01, + 10, + 0.3 + ], + "diagonals": true, + "domain": [ + 0, + 0, + 1, + 1 + ] + } + }, + { + "name": "trimmed_domain_1", + "upstream_ref": "source/SMLib/src/SmSurface.cpp:11644 (analytic boundary regression)", + "inputs": { + "op": 0, + "net": [ + [ + 0.0, + 0.0, + 0 + ], + [ + 0.0, + 0.5, + 0 + ], + [ + 0.0, + 1.0, + 0 + ], + [ + 0.5, + 0.0, + 0 + ], + [ + 0.5, + 0.5, + 0 + ], + [ + 0.5, + 1.0, + 0 + ], + [ + 1.0, + 0.0, + 0 + ], + [ + 1.0, + 0.5, + 0 + ], + [ + 1.0, + 1.0, + 0 + ] + ], + "direction": 1, + "controls": [ + 0.01, + 10, + 0 + ], + "diagonals": true, + "domain": [ + 0.2, + 0.3, + 0.8, + 0.9 + ] + } + }, + { + "name": "plane_iso_boundaries_1", + "upstream_ref": "source/SMLib/src/SmSurface.cpp:14877", + "inputs": { + "op": 1, + "net": [ + [ + 0.0, + 0.0, + 0 + ], + [ + 0.0, + 0.5, + 0 + ], + [ + 0.0, + 1.0, + 0 + ], + [ + 0.5, + 0.0, + 0 + ], + [ + 0.5, + 0.5, + 0 + ], + [ + 0.5, + 1.0, + 0 + ], + [ + 1.0, + 0.0, + 0 + ], + [ + 1.0, + 0.5, + 0 + ], + [ + 1.0, + 1.0, + 0 + ] + ], + "direction": 1, + "controls": [ + 0.01, + 0, + 0 + ], + "diagonals": false, + "domain": [ + 0, + 0, + 1, + 1 + ] + } + }, + { + "name": "surf1_subnet_1_False", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:29 (first 3x3 control points of my_load_surf1_pts)", + "inputs": { + "op": 0, + "net": [ + [ + 0, + 0, + -0.6 + ], + [ + 1, + 0.1, + 0.4 + ], + [ + 2, + 0.2, + 0.3 + ], + [ + 0, + 1, + 0.2 + ], + [ + 1, + 1, + 1.5 + ], + [ + 2, + 1, + 1.6 + ], + [ + 0, + 2, + 0.3 + ], + [ + 1, + 2, + 1.6 + ], + [ + 2, + 2, + 1.5 + ] + ], + "direction": 1, + "controls": [ + 0.05, + 15, + 0 + ], + "diagonals": false, + "domain": [ + 0, + 0, + 1, + 1 + ] + } + }, + { + "name": "surf1_subnet_1_True", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:29 (first 3x3 control points of my_load_surf1_pts)", + "inputs": { + "op": 0, + "net": [ + [ + 0, + 0, + -0.6 + ], + [ + 1, + 0.1, + 0.4 + ], + [ + 2, + 0.2, + 0.3 + ], + [ + 0, + 1, + 0.2 + ], + [ + 1, + 1, + 1.5 + ], + [ + 2, + 1, + 1.6 + ], + [ + 0, + 2, + 0.3 + ], + [ + 1, + 2, + 1.6 + ], + [ + 2, + 2, + 1.5 + ] + ], + "direction": 1, + "controls": [ + 0.05, + 15, + 0 + ], + "diagonals": true, + "domain": [ + 0, + 0, + 1, + 1 + ] + } + }, + { + "name": "surf1_max_length_1", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:29", + "inputs": { + "op": 0, + "net": [ + [ + 0, + 0, + -0.6 + ], + [ + 1, + 0.1, + 0.4 + ], + [ + 2, + 0.2, + 0.3 + ], + [ + 0, + 1, + 0.2 + ], + [ + 1, + 1, + 1.5 + ], + [ + 2, + 1, + 1.6 + ], + [ + 0, + 2, + 0.3 + ], + [ + 1, + 2, + 1.6 + ], + [ + 2, + 2, + 1.5 + ] + ], + "direction": 1, + "controls": [ + 0.05, + 15, + 0.8 + ], + "diagonals": true, + "domain": [ + 0, + 0, + 1, + 1 + ] + } + }, + { + "name": "surf1_boundaries_1", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:29", + "inputs": { + "op": 1, + "net": [ + [ + 0, + 0, + -0.6 + ], + [ + 1, + 0.1, + 0.4 + ], + [ + 2, + 0.2, + 0.3 + ], + [ + 0, + 1, + 0.2 + ], + [ + 1, + 1, + 1.5 + ], + [ + 2, + 1, + 1.6 + ], + [ + 0, + 2, + 0.3 + ], + [ + 1, + 2, + 1.6 + ], + [ + 2, + 2, + 1.5 + ] + ], + "direction": 1, + "controls": [ + 0.01, + 0, + 0 + ], + "diagonals": false, + "domain": [ + 0, + 0, + 1, + 1 + ] + } + }, + { + "name": "iso_non_direction_2", + "upstream_ref": "source/SMLib/src/SmSurface.cpp:14877", + "inputs": { + "op": 1, + "net": [ + [ + 0.0, + 0.0, + 0 + ], + [ + 0.0, + 0.5, + 0 + ], + [ + 0.0, + 1.0, + 0 + ], + [ + 0.5, + 0.0, + 0 + ], + [ + 0.5, + 0.5, + 0 + ], + [ + 0.5, + 1.0, + 0 + ], + [ + 1.0, + 0.0, + 0 + ], + [ + 1.0, + 0.5, + 0 + ], + [ + 1.0, + 1.0, + 0 + ] + ], + "direction": 2, + "controls": [ + 0, + 0, + 0 + ], + "diagonals": false, + "domain": [ + 0, + 0, + 1, + 1 + ] + } + }, + { + "name": "iso_non_direction_3", + "upstream_ref": "source/SMLib/src/SmSurface.cpp:14877", + "inputs": { + "op": 1, + "net": [ + [ + 0.0, + 0.0, + 0 + ], + [ + 0.0, + 0.5, + 0 + ], + [ + 0.0, + 1.0, + 0 + ], + [ + 0.5, + 0.0, + 0 + ], + [ + 0.5, + 0.5, + 0 + ], + [ + 0.5, + 1.0, + 0 + ], + [ + 1.0, + 0.0, + 0 + ], + [ + 1.0, + 0.5, + 0 + ], + [ + 1.0, + 1.0, + 0 + ] + ], + "direction": 3, + "controls": [ + 0, + 0, + 0 + ], + "diagonals": false, + "domain": [ + 0, + 0, + 1, + 1 + ] + } + }, + { + "name": "iso_non_direction_4", + "upstream_ref": "source/SMLib/src/SmSurface.cpp:14877", + "inputs": { + "op": 1, + "net": [ + [ + 0.0, + 0.0, + 0 + ], + [ + 0.0, + 0.5, + 0 + ], + [ + 0.0, + 1.0, + 0 + ], + [ + 0.5, + 0.0, + 0 + ], + [ + 0.5, + 0.5, + 0 + ], + [ + 0.5, + 1.0, + 0 + ], + [ + 1.0, + 0.0, + 0 + ], + [ + 1.0, + 0.5, + 0 + ], + [ + 1.0, + 1.0, + 0 + ] + ], + "direction": 4, + "controls": [ + 0, + 0, + 0 + ], + "diagonals": false, + "domain": [ + 0, + 0, + 1, + 1 + ] + } + }, + { + "name": "iso_non_direction_5", + "upstream_ref": "source/SMLib/src/SmSurface.cpp:14877", + "inputs": { + "op": 1, + "net": [ + [ + 0.0, + 0.0, + 0 + ], + [ + 0.0, + 0.5, + 0 + ], + [ + 0.0, + 1.0, + 0 + ], + [ + 0.5, + 0.0, + 0 + ], + [ + 0.5, + 0.5, + 0 + ], + [ + 0.5, + 1.0, + 0 + ], + [ + 1.0, + 0.0, + 0 + ], + [ + 1.0, + 0.5, + 0 + ], + [ + 1.0, + 1.0, + 0 + ] + ], + "direction": 5, + "controls": [ + 0, + 0, + 0 + ], + "diagonals": false, + "domain": [ + 0, + 0, + 1, + 1 + ] + } + }, + { + "name": "iso_non_direction_6", + "upstream_ref": "source/SMLib/src/SmSurface.cpp:14877", + "inputs": { + "op": 1, + "net": [ + [ + 0.0, + 0.0, + 0 + ], + [ + 0.0, + 0.5, + 0 + ], + [ + 0.0, + 1.0, + 0 + ], + [ + 0.5, + 0.0, + 0 + ], + [ + 0.5, + 0.5, + 0 + ], + [ + 0.5, + 1.0, + 0 + ], + [ + 1.0, + 0.0, + 0 + ], + [ + 1.0, + 0.5, + 0 + ], + [ + 1.0, + 1.0, + 0 + ] + ], + "direction": 6, + "controls": [ + 0, + 0, + 0 + ], + "diagonals": false, + "domain": [ + 0, + 0, + 1, + 1 + ] + } + }, + { + "name": "iso_non_direction_7", + "upstream_ref": "source/SMLib/src/SmSurface.cpp:14877", + "inputs": { + "op": 1, + "net": [ + [ + 0.0, + 0.0, + 0 + ], + [ + 0.0, + 0.5, + 0 + ], + [ + 0.0, + 1.0, + 0 + ], + [ + 0.5, + 0.0, + 0 + ], + [ + 0.5, + 0.5, + 0 + ], + [ + 0.5, + 1.0, + 0 + ], + [ + 1.0, + 0.0, + 0 + ], + [ + 1.0, + 0.5, + 0 + ], + [ + 1.0, + 1.0, + 0 + ] + ], + "direction": 7, + "controls": [ + 0, + 0, + 0 + ], + "diagonals": false, + "domain": [ + 0, + 0, + 1, + 1 + ] + } + }, + { + "name": "iso_non_direction_8", + "upstream_ref": "source/SMLib/src/SmSurface.cpp:14877", + "inputs": { + "op": 1, + "net": [ + [ + 0.0, + 0.0, + 0 + ], + [ + 0.0, + 0.5, + 0 + ], + [ + 0.0, + 1.0, + 0 + ], + [ + 0.5, + 0.0, + 0 + ], + [ + 0.5, + 0.5, + 0 + ], + [ + 0.5, + 1.0, + 0 + ], + [ + 1.0, + 0.0, + 0 + ], + [ + 1.0, + 0.5, + 0 + ], + [ + 1.0, + 1.0, + 0 + ] + ], + "direction": 8, + "controls": [ + 0, + 0, + 0 + ], + "diagonals": false, + "domain": [ + 0, + 0, + 1, + 1 + ] + } + }, + { + "name": "iso_non_direction_9", + "upstream_ref": "source/SMLib/src/SmSurface.cpp:14877", + "inputs": { + "op": 1, + "net": [ + [ + 0.0, + 0.0, + 0 + ], + [ + 0.0, + 0.5, + 0 + ], + [ + 0.0, + 1.0, + 0 + ], + [ + 0.5, + 0.0, + 0 + ], + [ + 0.5, + 0.5, + 0 + ], + [ + 0.5, + 1.0, + 0 + ], + [ + 1.0, + 0.0, + 0 + ], + [ + 1.0, + 0.5, + 0 + ], + [ + 1.0, + 1.0, + 0 + ] + ], + "direction": 9, + "controls": [ + 0, + 0, + 0 + ], + "diagonals": false, + "domain": [ + 0, + 0, + 1, + 1 + ] + } + }, + { + "name": "csi_crossing_plane", + "upstream_ref": "source/SMLib/src/SmSurface.cpp:13887 (one patch/one span intersection)", + "inputs": { + "op": 2, + "net": [ + [ + 0.0, + 0.0, + 0 + ], + [ + 0.0, + 0.5, + 0 + ], + [ + 0.0, + 1.0, + 0 + ], + [ + 0.5, + 0.0, + 0 + ], + [ + 0.5, + 0.5, + 0 + ], + [ + 0.5, + 1.0, + 0 + ], + [ + 1.0, + 0.0, + 0 + ], + [ + 1.0, + 0.5, + 0 + ], + [ + 1.0, + 1.0, + 0 + ] + ], + "direction": 0, + "controls": [ + 0, + 0, + 0 + ], + "diagonals": false, + "domain": [ + 0, + 0, + 1, + 1 + ], + "line": [ + [ + 0.5, + 0.5, + -5 + ], + [ + 0.5, + 0.5, + 5 + ] + ] + } + }, + { + "name": "csi_disjoint_plane", + "upstream_ref": "source/SMLib/src/SmSurface.cpp:13887 (disjoint boxes)", + "inputs": { + "op": 2, + "net": [ + [ + 0.0, + 0.0, + 0 + ], + [ + 0.0, + 0.5, + 0 + ], + [ + 0.0, + 1.0, + 0 + ], + [ + 0.5, + 0.0, + 0 + ], + [ + 0.5, + 0.5, + 0 + ], + [ + 0.5, + 1.0, + 0 + ], + [ + 1.0, + 0.0, + 0 + ], + [ + 1.0, + 0.5, + 0 + ], + [ + 1.0, + 1.0, + 0 + ] + ], + "direction": 0, + "controls": [ + 0, + 0, + 0 + ], + "diagonals": false, + "domain": [ + 0, + 0, + 1, + 1 + ], + "line": [ + [ + 0.5, + 0.5, + 5 + ], + [ + 0.5, + 0.5, + 10 + ] + ] + } + } + ] +} diff --git a/harness/cpp/shim/surface_surface_p9.cpp b/harness/cpp/shim/surface_surface_p9.cpp new file mode 100644 index 0000000000..c361f47444 --- /dev/null +++ b/harness/cpp/shim/surface_surface_p9.cpp @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: Apache-2.0 +// Unit surface.surface_p9. Calls the pinned SmSurface methods and the file-local +// SmCSIGlobalSolver class (declaration reproduced from SmSurface.cpp:13781). +#include "sc_shim.h" +#include "StdAfx.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class SmCSIGlobalSolver : public SmGlobalSolver { +public: + const SmSurface* m_pSurf; + SmBoolean m_bFoundCoincidence; + const SmExtent1d& m_crInterval; + const SmExtent2d& m_crUVDomain; + SmCSIGlobalSolver(SmTree*, SmTree*, SmBoolean, const SmExtent1d&, const SmExtent2d&); + virtual SmStatus LocalSolve(SmTreeNode* [SM_GS_MAX_TREES], SmBoolean&); + virtual SmSolutionPairType IdenticalSolutions(const SmSolution&, const SmSolution&, const SmSolution*&) const; +}; + +SC_EXPORT int32_t sc_surface_surface_p9_eval(int32_t op, const double* xyz, const double* bounds, + int32_t direction, double chord, double angle, double max_side, int32_t diagonals, + const double* line, double* output, int64_t cap, int64_t* out_len) { + SC_TRY + SmContext context; + gw_CPOINT points[3][3]; gw_CPOINT* rows[3] = {points[0],points[1],points[2]}; + for (int i=0;i<3;i++) for(int j=0;j<3;j++) { + auto& p=points[i][j]; const int at=3*(3*i+j); + p.x=xyz[at];p.y=xyz[at+1];p.z=xyz[at+2];p.w=NL_NOW; + } + double knots[6]={0,0,0,1,1,1}; + SmBSplineSurface* surface=new(context) SmBSplineSurface(2,2,2,2,5,5,rows,knots,knots); + SmExtent2d domain(bounds[0],bounds[1],bounds[2],bounds[3]); + std::vector out; + auto status=[](SmStatus r){return r==SM_SUCCESS ? 0.0 : (double)r;}; + if(op==0) { + SmTArray splits; + SmStatus r=surface->FindTessellationSplits((SmSurfParamType)direction,domain,chord,angle,max_side,splits,diagonals?TRUE:FALSE); + out.push_back(status(r)); + if(r==SM_SUCCESS) for(ULONG i=0;iCreateIsoBoundaries(context,(SmSurfParamType)direction,SmApproxTol3d(chord),a,b); + out.push_back(status(r)); + if(r==SM_SUCCESS) { + out.push_back((a?1.0:0.0)+(b?1.0:0.0)); + for(auto curve:{a,b}) if(curve) for(int i=0;i<=4;i++) { + SmPoint3d p; + SmStatus er=curve->EvaluatePoint(curve->GetNaturalInterval().Evaluate((double)i/4.0),p); + if(er!=SM_SUCCESS) { delete a;delete b;delete surface;return SC_ERR_ARG; } + out.push_back(p.x);out.push_back(p.y);out.push_back(p.z); + } + } + delete a;delete b; + } else if(op==2) { + gw_CPOINT cp[2]={{line[0],line[1],line[2],NL_NOW},{line[3],line[4],line[5],NL_NOW}}; + double ck[4]={0,0,1,1}; + SmBSplineCurve* curve=new(context) SmBSplineCurve(3,1,3,1,cp,ck); + { + SmExtent1d interval(0.0,1.0); + SmCurveCache cc(*curve,interval,TRUE); + SmSurfaceCache sc(*surface,surface->GetNaturalUVDomain()); + SmStatus r=cc.Tessellate(); + if(r==SM_SUCCESS) r=sc.BuildTree(NULL); + if(r==SM_SUCCESS) { + SmCSIGlobalSolver solver(cc.GetTree(),sc.GetTree(),FALSE,interval,domain); + SmSolutionArray solutions;solver.SetSolutions(&solutions); + solver.SetSolverOperation(SM_SO_INTERSECT);solver.Set3DTolerance(1e-6);solver.SetSolutionRequested(SM_SR_ALL); + SmTreeNode* branch[SM_GS_MAX_TREES]={cc.GetTree()->GetFirstLeafNode(),sc.GetTree()->GetFirstLeafNode(),NULL,NULL}; + SmBoolean more=FALSE;r=solver.LocalSolve(branch,more); + out.push_back(status(r)); + if(r==SM_SUCCESS) { + out.push_back(more==TRUE?1.0:0.0);out.push_back((double)solutions.GetSize()); + for(ULONG i=0;i