diff --git a/crates/difftest/src/units/intersect_ray_tracer.rs b/crates/difftest/src/units/intersect_ray_tracer.rs index 053a89085d..bb22562ea8 100644 --- a/crates/difftest/src/units/intersect_ray_tracer.rs +++ b/crates/difftest/src/units/intersect_ray_tracer.rs @@ -11,9 +11,13 @@ //! (`SmLSI3LocalSolver::LocalSolve` on one leaf) and `add_surface` //! (`SmRayTracer::AddSurfaceToGrid`: builds the surface cache and expands the surface into //! its subdivision leaves; the record holds each leaf's domain, box, mid-point data, pseudo box -//! and polar box). Both sides produce the flat record documented in the ffi module, reported -//! as the values `rec`. The grid elements of a ray walk are reported as positions among the -//! `add_leaf` leaves (`-1` for a leaf `add_surface` made). +//! and polar box) and `add_poly_brep` (an `SmPolyBrep` of one region and shell with closed +//! loops, then `SmRayTracer::AddPolyBrepToGrid`: builds its `SmBrepCache` and adds a polygon +//! element per surface-tree leaf, whose box and sphere the record holds; rays reaching them run +//! `sm_FindPolyHit`, and a poly face hit is reported by poly B-rep and face position). Both +//! sides produce the flat record documented in the ffi module, reported as the values `rec`. +//! The grid elements of a ray walk are reported as positions among the `add_leaf` leaves (`-1` +//! for an element `add_surface` or `add_poly_brep` made). //! //! Deferred cases: every ray or solve that gets past the leaf's pseudo box test reaches //! `SmSurface::LocalLineIntersect` (`surface.surface_p8`). The generator makes mostly cases whose rays are rejected before @@ -42,7 +46,13 @@ use solidcore::nurbs::b_spline_surface::BSplineSurface; use solidcore::nurbs::knots::Side; use solidcore::nurbs::math::{CPoint, NOW}; use solidcore::nurbs::srf_basic::Surface as NurbSurface; +use solidcore::geom::a_object::CacheStore; +use solidcore::geom::attribute::{AttributeStore, ObjectKey}; +use solidcore::geom::object::ObjectRef; +use solidcore::query::poly::{PolyBrep, PolyFaceId, PolyNotifyEnv, PolyObjectKind}; use solidcore::surface::plane::Plane; +use solidcore::topology::topology::Topology; +use std::collections::BTreeMap; use solidcore::surface::surface::Surface; use solidcore::SolidError; use solidcore_ffi::units::intersect_ray_tracer as ffi; @@ -91,6 +101,16 @@ struct GridIn { ray_acc: f64, } +/// A poly face: tolerance (also its edges'), the constructor normal and closed loops of +/// vertex indices, outer first. +#[derive(Debug, Clone, Serialize, Deserialize)] +struct PolyFaceSpec { + tol: f64, + #[serde(default)] + normal: Option, + loops: Vec>, +} + #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "op", rename_all = "snake_case")] enum OpIn { @@ -157,6 +177,13 @@ enum OpIn { p: P3, v: P3, }, + AddPolyBrep { + tol: f64, + /// `x y z tol` per vertex. + vertices: Vec<[f64; 4]>, + faces: Vec, + expansion: f64, + }, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -293,6 +320,24 @@ fn ffi_program(i: &Inputs) -> ffi::Program { }, OpIn::RenderPixel { x, y } => ffi::Op::RenderPixel(*x, *y), OpIn::RenderRay { p, v } => ffi::Op::RenderRay(*p, *v), + OpIn::AddPolyBrep { + tol, + vertices, + faces, + expansion, + } => ffi::Op::AddPolyBrep(ffi::PolyBrepIn { + tolerance: *tol, + vertices: vertices.clone(), + faces: faces + .iter() + .map(|f| ffi::PolyFaceIn { + tolerance: f.tol, + normal: f.normal, + loops: f.loops.clone(), + }) + .collect(), + expansion: *expansion, + }), }) .collect(), } @@ -355,6 +400,60 @@ fn rust_surface(s: &SurfIn) -> Result { } } +/// The poly B-rep the shim builds: `SmPolyBrep(tol)`, one region and shell, the vertices +/// (`SmPolyVertex` put on the vertex list), then per face `SmPolyFace(tol, .., normal)` and +/// per loop `StartPolyEdgeLoop`, one `AddPolyEdge` per vertex and `FinishPolyEdgeLoop`. +/// Returns it with its faces in creation order. +fn build_poly( + k: &Kernel, + tol: f64, + vertices: &[[f64; 4]], + faces: &[PolyFaceSpec], +) -> solidcore::Result<(PolyBrep, Vec)> { + let bad = |reason| SolidError::InvalidInput { + operation: "difftest::intersect_ray_tracer::build_poly", + reason, + }; + // the FFI's bounds, so a fuzzed case cannot make the Rust side allocate without end + if vertices.is_empty() + || vertices.len() > ffi::MAX_POLY_VERTICES + || faces.is_empty() + || faces.len() > ffi::MAX_POLY_FACES + { + return Err(bad("poly B-rep size")); + } + let mut b = PolyBrep::new(k, tol); + let region = b.add_region()?; + let shell = b.add_shell(region)?; + let mut vs = Vec::new(); + for v in vertices { + vs.push(b.add_vertex(k, Vec3::new(v[0], v[1], v[2]), v[3])?); + } + let mut out = Vec::new(); + for f in faces { + if f.loops.is_empty() || f.loops.len() > ffi::MAX_POLY_LOOPS { + return Err(bad("poly loop count")); + } + let face = b.add_face(k, shell, f.tol, f.normal.map(v3))?; + out.push(face); + for l in &f.loops { + if l.is_empty() || l.len() > ffi::MAX_LOOP_VERTICES { + return Err(bad("poly loop size")); + } + let lp = b.start_poly_edge_loop(face)?; + for (j, &i0) in l.iter().enumerate() { + let i1 = l.get((j + 1) % l.len()).copied().ok_or(bad("loop"))?; + let v0 = *vs.get(i0).ok_or(bad("poly vertex index"))?; + let v1 = *vs.get(i1).ok_or(bad("poly vertex index"))?; + let (p0, p1) = (b.vertex_point(v0)?, b.vertex_point(v1)?); + b.add_poly_edge(k, lp, f.tol, p0, p1, v0)?; + } + b.finish_poly_edge_loop(lp)?; + } + } + Ok((b, out)) +} + /// The status of a failed Rust operation, as upstream numbers it. fn status_of(e: &SolidError) -> f64 { match e { @@ -364,9 +463,18 @@ fn status_of(e: &SolidError) -> f64 { } } -/// `surface_of` maps the tracer's surface list to the runner's surfaces: `add_surface` -/// lists a surface a second time. -fn put_solution(o: &mut Vec, s: &Solution, surface_of: &[usize]) { +/// The runner's view of the tracer's lists: `surface_of` maps the tracer's surface list to +/// the runner's surfaces (`add_surface` lists a surface a second time), `poly_of` the +/// tracer's poly B-rep list to the program's `add_poly_brep` ops, whose faces in creation +/// order are `poly_faces`. +struct Lists<'a> { + surface_of: &'a [usize], + poly_of: &'a [usize], + poly_faces: &'a [Vec], +} + +fn put_solution(o: &mut Vec, s: &Solution, lists: &Lists<'_>) { + let surface_of = lists.surface_of; o.push(f64::from(s.solution_type.code())); o.push(s.num_variables as f64); o.push(s.num_objects as f64); @@ -386,6 +494,16 @@ fn put_solution(o: &mut Vec, s: &Solution, surface_of: &[usize]) } else { 0.0 }); + for j in 0..2 { + let ids = match s.objects.get(j).copied().flatten() { + Some(RayObject::PolyFace(b, f)) => lists.poly_of.get(b).and_then(|&b| { + let faces = lists.poly_faces.get(b)?; + Some([b as f64, faces.iter().position(|&x| x == f)? as f64]) + }), + _ => None, + }; + o.extend(ids.unwrap_or([-1.0, -1.0])); + } } fn put_color( @@ -417,6 +535,8 @@ struct Stats { elements_seen: usize, rejected_leaves: usize, errors: usize, + /// Rays whose nearest (or any) hit is a poly face. + poly_hits: usize, } fn run_rust(k: &Kernel, i: &Inputs, st: &mut Stats) -> solidcore::Result> { @@ -430,6 +550,32 @@ fn run_rust(k: &Kernel, i: &Inputs, st: &mut Stats) -> solidcore::Result>()?; + // the poly B-reps of the `add_poly_brep` ops, built up front so the tracer can hold them + let mut polys: Vec = Vec::new(); + let mut poly_faces: Vec> = Vec::new(); + for op in &i.ops { + if let OpIn::AddPolyBrep { + tol, + vertices, + faces, + .. + } = op + { + if polys.len() >= ffi::MAX_POLY_BREPS { + return Err(SolidError::InvalidInput { + operation: "difftest::intersect_ray_tracer", + reason: "too many poly B-reps", + }); + } + let (b, f) = build_poly(k, *tol, vertices, faces)?; + polys.push(b); + poly_faces.push(f); + } + } + let mut poly_slots = polys.iter_mut(); + // the program's poly B-rep of each entry of the tracer's poly B-rep list + let mut poly_of: Vec = Vec::new(); + let mut poly_ops = 0_usize; let grid = Grid::new(&ext3(i.grid.bbox), i.grid.size)?; let mut tracer_slot = Some(RayTracer::new(grid, i.grid.ray_tol, i.grid.ray_acc)); let mut renderer: Option = None; @@ -526,7 +672,15 @@ fn run_rust(k: &Kernel, i: &Inputs, st: &mut Stats) -> solidcore::Result { rec.push(0.0); rec.push(f64::from(u8::from(hit.hits_something))); - put_solution(&mut rec, &hit.solution, &surface_of); + put_solution( + &mut rec, + &hit.solution, + &Lists { + surface_of: &surface_of, + poly_of: &poly_of, + poly_faces: &poly_faces, + }, + ); rec.push(hit.grid_elements.len() as f64); rec.extend(hit.grid_elements.iter().map(|e| { leaf_ids.iter().position(|l| l == e).map_or(-1.0, |p| p as f64) @@ -539,13 +693,25 @@ fn run_rust(k: &Kernel, i: &Inputs, st: &mut Stats) -> solidcore::Result return Err(e), Err(e) => { st.errors += 1; rec.push(status_of(&e)); rec.push(0.0); - put_solution(&mut rec, &Solution::new(), &surface_of); + put_solution( + &mut rec, + &Solution::new(), + &Lists { + surface_of: &surface_of, + poly_of: &poly_of, + poly_faces: &poly_faces, + }, + ); rec.push(0.0); } } @@ -589,7 +755,15 @@ fn run_rust(k: &Kernel, i: &Inputs, st: &mut Stats) -> solidcore::Result { let surf = surfaces.get(*surface).ok_or_else(|| bad_index("surface"))?; @@ -702,6 +876,53 @@ fn run_rust(k: &Kernel, i: &Inputs, st: &mut Stats) -> solidcore::Result { + let brep = poly_slots.next().ok_or_else(|| bad_index("poly B-rep"))?; + let program_index = poly_ops; + poly_ops += 1; + let tracer = tracer!(); + let before = tracer.grid_mut().elements().len(); + let mut records: BTreeMap = BTreeMap::new(); + let mut attributes = AttributeStore::new(); + let mut caches: CacheStore<()> = CacheStore::new(); + let mut env = PolyNotifyEnv { + records: &mut records, + attributes: &mut attributes, + caches: &mut caches, + context: None, + this: ObjectRef { + type_code: PolyObjectKind::Brep.type_code(), + key: ObjectKey(1), + }, + }; + let status = + match tracer.add_poly_brep_to_grid(k, brep, &mut env, *expansion) { + Ok(()) => { + poly_of.push(program_index); + 0.0 + } + Err(e @ SolidError::Unported { .. }) => return Err(e), + Err(e) => status_of(&e), + }; + // the polygon elements it made: count, then per element its box, sphere + // centre and radius + let made: Vec<_> = tracer + .grid_mut() + .elements() + .iter() + .skip(before) + .filter(|e| matches!(e.item(), GridItem::Polygon(_))) + .collect(); + rec.push(status); + rec.push(made.len() as f64); + for e in made { + let b = e.bounding_box(); + let (b0, b1) = (b.min(), b.max()); + rec.extend([b0.x, b0.y, b0.z, b1.x, b1.y, b1.z]); + let c = e.sphere_center(); + rec.extend([c.x, c.y, c.z, e.sphere_radius()]); + } + } } } Ok(rec) @@ -1172,6 +1393,11 @@ fn gen_case(rng: &mut Rng, safe: bool) -> Inputs { if !safe && rng.chance(0.1) && !surfaces.is_empty() { ops.push(OpIn::AddSurface { surface: 0 }); } + // poly B-reps and rays at their faces (drawn after everything above, so the ops before + // them do not depend on whether they are made) + if rng.chance(0.35) { + gen_poly_ops(rng, &mut ops, gbox, scale); + } Inputs { surfaces, grid, @@ -1179,6 +1405,114 @@ fn gen_case(rng: &mut Rng, safe: bool) -> Inputs { } } +/// One planar polygon face: its loops as points, outer first (a triangle, a quad, a regular +/// polygon, a non-convex star or a quad with a square hole), on a plane through `c` spanned +/// by `bx`, `by`, half size `r`; wound the other way with `reversed`. +fn gen_polygon(rng: &mut Rng, c: P3, bx: P3, by: P3, r: f64, reversed: bool) -> Vec> { + let at = |a: f64, b: f64| add(c, add(scale3(bx, a * r), scale3(by, b * r))); + let ring = |n: usize, radius: &dyn Fn(usize) -> f64| -> Vec { + (0..n) + .map(|i| { + let t = 2.0 * std::f64::consts::PI * i as f64 / n as f64; + at(radius(i) * t.cos(), radius(i) * t.sin()) + }) + .collect() + }; + let mut loops = match rng.below(5) { + 0 => vec![vec![at(-1.0, -1.0), at(1.0, -0.5), at(0.0, 1.0)]], + 1 => vec![vec![at(-1.0, -1.0), at(1.0, -1.0), at(1.0, 1.0), at(-1.0, 1.0)]], + 2 => vec![ring(rng.range_i64(5, 8) as usize, &|_| 1.0)], + 3 => vec![ring(2 * rng.range_i64(3, 6) as usize, &|i| if i % 2 == 0 { 1.0 } else { 0.4 })], + _ => { + let h = rng.uniform(0.2, 0.6); + vec![ + vec![at(-1.0, -1.0), at(1.0, -1.0), at(1.0, 1.0), at(-1.0, 1.0)], + vec![at(-h, -h), at(-h, h), at(h, h), at(h, -h)], + ] + } + }; + if reversed { + for l in &mut loops { + l.reverse(); + } + } + loops +} + +/// Appends up to two poly B-reps of random planar faces inside `gbox` and rays at them. +fn gen_poly_ops(rng: &mut Rng, ops: &mut Vec, gbox: E6, scale: f64) { + let lo = [gbox[0], gbox[1], gbox[2]]; + let hi = [gbox[3], gbox[4], gbox[5]]; + let diag = len3(sub(hi, lo)); + let mut targets: Vec<(P3, P3)> = Vec::new(); + for _ in 0..rng.range_i64(1, 2) { + if ops.len() + 2 > ffi::MAX_OPS { + return; + } + let tol = *rng.choose(&[1e-5, 1e-6, 1e-3]).unwrap_or(&1e-5) * scale; + let mut vertices: Vec<[f64; 4]> = Vec::new(); + let mut faces = Vec::new(); + for _ in 0..rng.range_i64(1, 12) { + let c = [ + rng.uniform(lo[0], hi[0]), + rng.uniform(lo[1], hi[1]), + rng.uniform(lo[2], hi[2]), + ]; + let [bx, by, bz] = if rng.chance(0.3) { + [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]] + } else { + rotation_basis(rng) + }; + let r = diag * rng.uniform(0.02, 0.3); + let reversed = rng.chance(0.3); + let loops = gen_polygon(rng, c, bx, by, r, reversed); + let mut idx = Vec::new(); + for l in &loops { + idx.push( + l.iter() + .map(|p| { + vertices.push([p[0], p[1], p[2], tol]); + vertices.len() - 1 + }) + .collect(), + ); + } + let normal = if rng.chance(0.15) { + Some(if reversed { scale3(bz, -1.0) } else { bz }) + } else { + None + }; + faces.push(PolyFaceSpec { tol, normal, loops: idx }); + targets.push((add(c, scale3(add(bx, by), r * rng.uniform(-0.3, 0.3))), bz)); + } + ops.push(OpIn::AddPolyBrep { + tol, + vertices, + faces, + expansion: *rng.choose(&[0.0, 1e-3 * scale, 0.05 * diag]).unwrap_or(&0.0), + }); + } + while ops.len() < ffi::MAX_OPS && rng.chance(0.85) { + let Some(&(target, normal)) = rng.choose(&targets) else { + return; + }; + // through the target: along the face normal (either way) or any direction + let dir = match rng.below(5) { + 0 => normal, + 1 => scale3(normal, -1.0), + _ => unit_dir(rng), + }; + let start = sub(target, scale3(dir, diag * rng.uniform(0.2, 1.5))); + ops.push(OpIn::Fire { + p: start, + v: dir, + max_t: if rng.chance(0.85) { BIG_DOUBLE } else { rng.uniform(0.0, 2.0) * diag }, + hit_any: rng.chance(0.4), + skip: None, + }); + } +} + /// Mostly cases the Rust side can finish (rays rejected before `LocalLineIntersect`), /// and some that defer. fn gen_checked(k: &Kernel, rng: &mut Rng) -> Inputs { @@ -1256,5 +1590,95 @@ mod tests { assert!(ok + deferred == MIN_GENERATED, "{ok} {deferred} {st:?}"); assert!(st.walks > 200, "{st:?}"); assert!(st.elements_seen > 100, "{st:?}"); + assert!(st.poly_hits > 50, "{st:?}"); + } + + fn poly_fixture_cases() -> Vec<(String, Inputs)> { + let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../../fixtures/intersect.ray_tracer/poly_breps.json"); + let text = std::fs::read_to_string(path).unwrap(); + let v: Value = serde_json::from_str(&text).unwrap(); + v["cases"] + .as_array() + .unwrap() + .iter() + .map(|c| { + let name = c["name"].as_str().unwrap().to_string(); + (name, serde_json::from_value(c["inputs"].clone()).unwrap()) + }) + .collect() + } + + /// Every poly fixture gets a ray onto a poly face (passing cases that only miss would + /// leave `sm_FindPolyHit`'s hit path unchecked). + #[test] + fn poly_fixtures_hit_poly_faces() { + let k = Kernel::default(); + for (name, i) in poly_fixture_cases() { + let mut st = Stats::default(); + run_rust(&k, &i, &mut st).unwrap(); + if name != "poly_render_shadows" { + assert!(st.poly_hits > 0, "{name}: {st:?}"); + } + } + } + + /// In `poly_big_face_over_small_faces` the big face lies on an inner node of the cache's + /// surface tree, so the rays that hit it reach it only by the walk up the parents. + #[test] + fn the_big_poly_face_sits_on_an_inner_tree_node() { + use solidcore::topology::brep_cache::{ + arena_identity, make_brep_cache, CacheObject, CacheSource, + }; + let k = Kernel::default(); + let (_, i) = poly_fixture_cases() + .into_iter() + .find(|(n, _)| n == "poly_big_face_over_small_faces") + .unwrap(); + let Some(OpIn::AddPolyBrep { + tol, + vertices, + faces, + .. + }) = i.ops.first() + else { + panic!("first op is the poly B-rep"); + }; + let (b, ids) = build_poly(&k, *tol, vertices, faces).unwrap(); + let cache = make_brep_cache(&k, CacheSource::PolyBrep(&b), &arena_identity).unwrap(); + let tree = cache.surface_tree().unwrap(); + let holder = tree + .all_tree_nodes() + .into_iter() + .find(|&n| { + tree.node_object_list(n).is_ok_and(|l| { + l.iter().any(|&o| { + tree.list(o).unwrap().object == Some(CacheObject::PolyFace(ids[0])) + }) + }) + }) + .unwrap(); + assert!(tree.node(holder).unwrap().child1.is_some()); + // and a ray from above hits it first, found through the walk up from its leaf + let (mut b, ids) = build_poly(&k, *tol, vertices, faces).unwrap(); + let mut tracer = RayTracer::new(Grid::new(&ext3(i.grid.bbox), i.grid.size).unwrap(), 1e-5, 1e-8); + let mut records: BTreeMap = BTreeMap::new(); + let mut attributes = AttributeStore::new(); + let mut caches: CacheStore<()> = CacheStore::new(); + let mut env = PolyNotifyEnv { + records: &mut records, + attributes: &mut attributes, + caches: &mut caches, + context: None, + this: ObjectRef { + type_code: PolyObjectKind::Brep.type_code(), + key: ObjectKey(1), + }, + }; + tracer.add_poly_brep_to_grid(&k, &mut b, &mut env, 1e-3).unwrap(); + let hit = tracer + .fire_ray(&k, Vec3::new(4.5, 6.5, 9.8), Vec3::new(0.0, 0.0, -1.0), BIG_DOUBLE) + .unwrap(); + assert_eq!(hit.solution.objects[0], Some(RayObject::PolyFace(0, ids[0]))); } } diff --git a/crates/solidcore-ffi/src/units/intersect_ray_tracer.rs b/crates/solidcore-ffi/src/units/intersect_ray_tracer.rs index 751868984f..fbe4cedf74 100644 --- a/crates/solidcore-ffi/src/units/intersect_ray_tracer.rs +++ b/crates/solidcore-ffi/src/units/intersect_ray_tracer.rs @@ -6,7 +6,7 @@ //! //! [`run`] builds real `SmPlane` / `SmBSplineSurface` surfaces, an `SmGrid` and an //! `SmRayTracer` owning it, applies the [`Op`]s in order and returns one flat record: per -//! `Fire` and `Local` op +//! `Fire`, `Local`, `AddSurface` and `AddPolyBrep` op //! //! * `Fire`: `status hits n_elements ids(n) mins(n) maxs(n)`; //! * `Local`: `status found needs_subdivision `; @@ -14,11 +14,15 @@ //! `u_min v_min u_max v_max`, the box `min(3) max(3)`, the mid-point data `point(3) du(3) //! dv(3) normal(3)`, the pseudo box `basis(9) interval_min_max(6)` and the polar box `state //! domain(4) basis(9)` (domain and basis only for a bounded state, else zeros); +//! * `AddPolyBrep`: `status n_elements element(n)`, 10 values per polygon element it made: +//! the box `min(3) max(3)`, the sphere centre (3) and radius; //! //! where `` is `type num_variables num_objects solution_value p[0..10] surface -//! object1 node0` (`surface` the surface index or `-1`, `object1` `-1` for null, `node0` `1` -//! when `m_apNodes[0]` is set). The ids of a `Fire` are positions among the `AddLeaf` leaves, `-1` -//! for an element `AddSurface` made. `SolverTol` and `AddLeaf` add nothing. +//! object1 node0 poly0(2) poly1(2)` (`surface` the surface index or `-1`, `object1` `-1` for +//! null, `node0` `1` when `m_apNodes[0]` is set, `poly0` / `poly1` the poly B-rep and face +//! indices of objects 0 and 1, `-1 -1` when not a poly face). The ids of a `Fire` are positions +//! among the `AddLeaf` leaves, `-1` for an element `AddSurface` or `AddPolyBrep` made. +//! `SolverTol` and `AddLeaf` add nothing. //! //! | [`Op`] | upstream | //! |--------------|-----------------------------------------------------------------------------| @@ -27,6 +31,7 @@ //! | `Fire` | `SmRayTracer::FireRay` | //! | `Local` | `SmLSI3LocalSolver::LocalSolve` on a leaf | //! | `AddSurface` | `SmRayTracer::AddSurfaceToGrid` | +//! | `AddPolyBrep` | `SmPolyBrep` (one region and shell, closed loops) and `SmRayTracer::AddPolyBrepToGrid` | //! //! [`run`] accepts only finite inputs of magnitude at most [`MAX_COORD`] (ray parameters and //! tolerances: any finite value); planes with independent axes and positive scales; Bezier @@ -35,7 +40,10 @@ //! natural domain with non-inverted bounding boxes (a pseudo box may be inverted) and non-zero polar vectors; ray vectors of `Local` //! ops that are zero or at least [`MIN_LOCAL_VECTOR`] (the Newton iteration divides by it); at most //! [`MAX_OPS`] ops, [`MAX_LEAVES`] leaves, [`MAX_SURFACES`] surfaces and [`MAX_POLAR`] polar -//! vectors per leaf. +//! vectors per leaf; at most [`MAX_POLY_BREPS`] poly B-reps of 1..=[`MAX_POLY_VERTICES`] +//! vertices and 1..=[`MAX_POLY_FACES`] faces, each face with 1..=[`MAX_POLY_LOOPS`] loops of +//! 1..=[`MAX_LOOP_VERTICES`] vertices (a face needs an outer loop with a first edge, which +//! `sm_FindPolyHit` dereferences), and vertex indices in range. use crate::validate::bad; use crate::{call_with_buffer, FfiError}; @@ -66,6 +74,16 @@ pub const MAX_SURFACES: usize = 4; pub const MAX_POLAR: usize = 8; /// Shortest non-zero ray vector of a `Local` op (a zero vector is allowed: `LocalSolve` handles it). pub const MIN_LOCAL_VECTOR: f64 = 1e-6; +/// Most poly B-reps per program. +pub const MAX_POLY_BREPS: usize = 4; +/// Most vertices per poly B-rep. +pub const MAX_POLY_VERTICES: usize = 1024; +/// Most faces per poly B-rep. +pub const MAX_POLY_FACES: usize = 512; +/// Most loops per poly face. +pub const MAX_POLY_LOOPS: usize = 8; +/// Most vertices per poly loop. +pub const MAX_LOOP_VERTICES: usize = 32; /// `NL_NOW`: the weight of a non-rational control point. pub const NOW: f64 = -f64::MAX; @@ -133,6 +151,31 @@ pub struct LeafIn { pub elem_box: [f64; 6], } +/// A poly face: its tolerance (also its edges'), the normal given to its constructor and +/// its closed loops, outer first, as vertex indices. +#[derive(Debug, Clone, PartialEq)] +pub struct PolyFaceIn { + /// `SmPolyFace(dTol, ..)` and the `AddPolyEdge` tolerance of its edges. + pub tolerance: f64, + /// `SmPolyFace(.., pOptNormal)` (`None` = null: the normal is computed). + pub normal: Option<[f64; 3]>, + /// The loops (each closed: one edge per vertex). + pub loops: Vec>, +} + +/// A poly B-rep of one region and one shell, added to the grid. +#[derive(Debug, Clone, PartialEq)] +pub struct PolyBrepIn { + /// `SmPolyBrep(dTol)`. + pub tolerance: f64, + /// `x y z tol` per vertex (`SmPolyVertex(point, tol)`). + pub vertices: Vec<[f64; 4]>, + /// The faces, in creation order. + pub faces: Vec, + /// `dExpansionTolerance` of `AddPolyBrepToGrid`. + pub expansion: f64, +} + /// A `FireRay` call. #[derive(Debug, Clone, Copy, PartialEq)] pub struct FireIn { @@ -211,6 +254,8 @@ pub enum Op { RenderPixel(usize, usize), /// `RenderRay(point, vector)` (after `DoRender`): record `status r g b pixels_shaded`. RenderRay([f64; 3], [f64; 3]), + /// Builds a poly B-rep and runs `AddPolyBrepToGrid` on it. + AddPolyBrep(PolyBrepIn), } /// The renderer constructor arguments. @@ -409,6 +454,7 @@ fn encode(prog: &Program) -> Result, FfiError> { } e.int(prog.ops.len()); let mut leaves = 0_usize; + let mut poly_breps = 0_usize; let mut rendered = Render::None; for op in &prog.ops { match op { @@ -589,6 +635,49 @@ fn encode(prog: &Program) -> Result, FfiError> { e.nums(p)?; e.nums(v)?; } + Op::AddPolyBrep(b) => { + if poly_breps >= MAX_POLY_BREPS { + return Err(bad("too many poly B-reps")); + } + poly_breps += 1; + let nv = b.vertices.len(); + if !(1..=MAX_POLY_VERTICES).contains(&nv) { + return Err(bad("poly vertex count")); + } + if !(1..=MAX_POLY_FACES).contains(&b.faces.len()) { + return Err(bad("poly face count")); + } + e.int(11); + e.any(b.tolerance)?; + e.int(nv); + for v in &b.vertices { + e.nums(&v[..3])?; + e.any(v[3])?; + } + e.int(b.faces.len()); + for f in &b.faces { + if !(1..=MAX_POLY_LOOPS).contains(&f.loops.len()) { + return Err(bad("poly loop count")); + } + e.any(f.tolerance)?; + e.flag(f.normal.is_some()); + e.nums(&f.normal.unwrap_or([0.0; 3]))?; + e.int(f.loops.len()); + for l in &f.loops { + if !(1..=MAX_LOOP_VERTICES).contains(&l.len()) { + return Err(bad("poly loop vertex count")); + } + e.int(l.len()); + for &v in l { + if v >= nv { + return Err(bad("poly vertex index")); + } + e.int(v); + } + } + } + e.any(b.expansion)?; + } } } Ok(e.0) diff --git a/crates/solidcore/src/intersect/ray_tracer.rs b/crates/solidcore/src/intersect/ray_tracer.rs index 204c32e730..76e4dae5d8 100644 --- a/crates/solidcore/src/intersect/ray_tracer.rs +++ b/crates/solidcore/src/intersect/ray_tracer.rs @@ -15,6 +15,7 @@ //! | [`RayTracer::fire_ray`], [`RayHit`] | `SmRayTracer::FireRay` | //! | [`RayTracer::add_surface_to_grid`], [`RayTracer::add_surface_with_bbox`] | `SmRayTracer::AddSurfaceToGrid` | //! | [`RayTracer::add_poly_brep_to_grid`] | `SmRayTracer::AddPolyBrepToGrid` | +//! | [`RayTracer::poly_brep`], [`RayTracer::poly_brep_count`] | `m_sPolyBreps` | //! | [`RayRenderer`], [`RayRenderer::new`] | `SmRayRenderer`, `SmRayRenderer::SmRayRenderer` | //! | `Drop` (implicit) | `~SmRayRenderer` | //! | [`RayRenderer::do_render`], [`RayRenderer::render_pixel`], [`RayRenderer::render_ray`] | `SmRayRenderer::DoRender`, `RenderPixel`, `RenderRay` | @@ -59,25 +60,41 @@ //! on; see the status deviation of `geom::grid` (a failed `Evaluate` is retried //! without `non_zero_tangents`, a failed `EvaluateNormal` keeps the cross product, //! [`SolidError::Unported`] is always propagated). -//! - **Polygon elements.** `sm_FindPolyHit` walks the `SmBrepCache` tree of a -//! polygon grid element and tests `SmPolyFace`s: neither exists yet, so a polygon -//! element in the ray's path is [`SolidError::Unported`], as is -//! [`RayTracer::add_poly_brep_to_grid`], so the `SmPolyBrep` list and the -//! per-context mark locks of `FireRay` (nothing can put a poly B-rep in the list) -//! are not ported; `GetMemoryUsed`, `Dump` and the drawing blocks are not reached -//! by the upstream tests and not ported either. +//! - **Poly B-reps.** The tracer holds each poly B-rep it was given mutably +//! (`SmPolyFace::GetNormal` and `PointInPolygon` cache through `const_cast` +//! upstream) together with the `SmBrepCache` that `AddPolyBrepToGrid` builds; the +//! cache is owned by the tracer rather than the cache manager (the `SM_NO_PRE_EDIT` +//! notification just dropped any cache the poly B-rep had, so `GetOrCreateObjectCache` +//! always makes a new one). A polygon grid element holds its tree node, and the +//! tracer maps the element to its poly B-rep; a [`RayObject::PolyFace`] names the +//! poly B-rep by its index in the tracer's list. Upstream returns from +//! `AddPolyBrepToGrid` with elements already in the grid when `AddElement` fails; +//! the poly B-rep is then not listed, here as upstream, and a later ray reaching such +//! an element is [`SolidError::InvalidInput`]. +//! - **Marks.** `FireRay` takes a new `SM_MT_MARK` on the context of every poly B-rep +//! (`SmNewMarkAndLock`, unlocked on return) and `sm_FindPolyHit` marks each poly face +//! it tests with it; here each ray keeps the set of poly faces it has tested. (The +//! loop meant to skip poly B-reps sharing a context `continue`s only its inner loop; +//! a second new mark on the same context changes no face test.) +//! - **`sm_FindPolyHit` recursion** on the parent node, after the node's own faces, +//! is a loop up the parent links in the same order, bounded by the tree's node count +//! (a longer chain is a cycle: [`SolidError::InvalidInput`]). +//! - `GetMemoryUsed`, `Dump` and the drawing blocks are not reached by the upstream +//! tests and not ported. //! - **Renderer.** [`RayRenderer`] owns its tracer and light sources by value. //! `DoRender` returns the pixel colours (upstream keeps one row in a local and prints //! a progress line per row with `smos_WriteBuffer`, which is dropped); the colours //! upstream leaves unset before `DoRender` are black; the hit node `RenderRay` //! passes as the skip element is `m_apNodes[0]`, which `FireRay` always clears, so //! no element is skipped; unsigned products wrap as upstream's `ULONG`. -//! - **Unported callees.** `SmFace::PointClassify` (`query.face_p2`) and the poly -//! types return [`SolidError::Unported`] (see the `UNPORTED` markers). The surface -//! cache and its tree (`smsurf_GetSurfaceCache`, `GetTree`) are ported. +//! - **Unported callees.** `SmFace::PointClassify` goes through the caller's +//! [`RayFaceClassifier`]; a face-owned surface traced without one is +//! [`SolidError::Unported`] (`query.face_p2`, a dispatch gap). The surface cache and +//! its tree (`smsurf_GetSurfaceCache`, `GetTree`), the poly types and the poly B-rep +//! cache are ported. //! - `SM_ERR` results the upstream ignores or `SER`s keep their behaviour; the //! `SM_DEBUG_CODE` blocks are not ported. No recursion depends on input here -//! (`sm_FindPolyHit`, which recurses up a tree, is unported). +//! (`sm_FindPolyHit`'s is the bounded loop above). use crate::curve::curve::p2::evaluate_geometric; use crate::curve::curve::Curve; @@ -88,25 +105,31 @@ use crate::geom::extent1d::Extent1d; use crate::geom::extent2d::Extent2d; use crate::geom::extent3d::Extent3d; use crate::geom::geom_utility::p1::{ - circle_coplanar_circle_intersect, line_closest_point, line_coplanar_circle_intersect, - line_plane_intersect_uv, CircleCircle, + self as gu, circle_coplanar_circle_intersect, line_closest_point, + line_coplanar_circle_intersect, line_plane_intersect_uv, CircleCircle, }; use crate::geom::grid::{ - ElementId, Grid, GridItem, HierarchyElement, HierarchyId, SurfaceGridElement, + ElementId, ElementSource, Grid, GridItem, HierarchyElement, HierarchyId, SurfaceGridElement, }; -use crate::geom::object::ObjectRef; +use crate::geom::object::{NotifyOperation, ObjectRef}; use crate::geom::solution_array::{ObjectKind, Solution, SolutionObject, SolutionType}; -use crate::geom::tree::NodeId; +use crate::geom::tree::{NodeId, Tree}; use crate::geom::vector2d::{Point2, Vec2}; use crate::geom::vector3d::{Point3, Vec3}; use crate::kernel::Kernel; use crate::math::{BIG_DOUBLE, EFF_ZERO, EFF_ZERO_SQ, PI}; use crate::nurbs::knots::Side; +use crate::query::poly::{PolyBrep, PolyFaceId, PolyNotifyEnv}; use crate::surface::surface::p1::evaluate_1st_derivatives; use crate::surface::surface::p2::surface_cache; use crate::surface::surface::Surface; use crate::surface::surface_cache::p1::SurfaceCache; +use crate::topology::brep_cache::{ + arena_identity, make_brep_cache, BrepCache, CacheObject, CacheSource, +}; +use crate::topology::topology::TopologyRecords; use crate::topology::{EdgeId, FaceId, VertexId}; +use std::collections::{BTreeMap, BTreeSet}; /// A light source (`SmLightSource`): white light from a point or in a direction. #[derive(Debug, Clone, Copy, PartialEq)] @@ -139,6 +162,9 @@ pub enum RayObject { Edge(EdgeId), /// The vertex of the hit point (`SmVertex`). Vertex(VertexId), + /// A poly face that was hit (`SmPolyFace`): the poly B-rep's index in the + /// tracer's poly B-rep list and the face in it. + PolyFace(usize, PolyFaceId), } impl SolutionObject for RayObject { @@ -148,10 +174,20 @@ impl SolutionObject for RayObject { Self::Face(_) => ObjectKind::Face, Self::Edge(_) => ObjectKind::Edge, Self::Vertex(_) => ObjectKind::Vertex, + Self::PolyFace(..) => ObjectKind::PolyFace, } } } +/// A poly B-rep in the tracer (`m_sPolyBreps`) with the `SmBrepCache` that +/// `AddPolyBrepToGrid` built for it: its polygon grid elements are leaves of the +/// cache's surface tree. +#[derive(Debug)] +struct TracedPolyBrep<'s> { + brep: &'s mut PolyBrep, + cache: BrepCache, +} + /// The outputs of `SmSurface::EvaluateNormalSection`. #[derive(Debug, Clone, Copy, PartialEq)] pub struct NormalSection { @@ -831,6 +867,11 @@ pub struct RayTracer<'s> { solver: Lsi3LocalSolver, /// `m_sSurfaces`: the surfaces added to the grid. surfaces: Vec<&'s Surface>, + /// `m_sPolyBreps`: the poly B-reps added to the grid, with their caches. + poly_breps: Vec>, + /// The poly B-rep (index in `poly_breps`) whose cache tree holds the node of + /// each polygon grid element (upstream reaches the tree through the node). + poly_elements: BTreeMap, /// `m_pSkipElement`: an element `FireRay` does not test. pub skip_element: Option, /// How a face-owned surface's points are classified against its face. @@ -863,6 +904,8 @@ impl<'s> RayTracer<'s> { grid, solver, surfaces: Vec::new(), + poly_breps: Vec::new(), + poly_elements: BTreeMap::new(), faces: None, skip_element: None, hit_any_thing: false, @@ -922,15 +965,29 @@ impl<'s> RayTracer<'s> { &self.surfaces } + /// The number of poly B-reps added so far (`m_sPolyBreps.GetSize()`). + #[must_use] + pub fn poly_brep_count(&self) -> usize { + self.poly_breps.len() + } + + /// The poly B-rep at `index` in the tracer's poly B-rep list (`m_sPolyBreps`), + /// the index a [`RayObject::PolyFace`] holds. + #[must_use] + pub fn poly_brep(&self, index: usize) -> Option<&PolyBrep> { + self.poly_breps.get(index).map(|p| &*p.brep) + } + /// Intersects a ray with the geometry in the grid (`FireRay`): the nearest hit /// (any hit when [`RayTracer::hit_any_thing`]) with ray parameter in /// `[-EFF_ZERO, dMaxRayParameter)`; use `BIG_DOUBLE` for an infinite ray. /// /// # Errors /// - /// Those of the grid walk; [`SolidError::Unported`] from the solver or a polygon - /// element; [`SolidError::InvalidInput`] for a grid element that is neither a - /// surface nor a polygon element or whose surface is not registered. + /// Those of the grid walk and of the poly face tests; [`SolidError::Unported`] from + /// the solver; [`SolidError::InvalidInput`] for a grid element that is neither a + /// surface nor a polygon element, whose surface is not registered or whose poly + /// B-rep is not listed. pub fn fire_ray( &mut self, k: &Kernel, @@ -948,7 +1005,9 @@ impl<'s> RayTracer<'s> { max_distances: Vec::new(), }; - // (The per-context mark locks of the poly B-reps: none can be added.) + // Use pPolyBrep->Context->Mark as a tag for new ray-firing: one new mark per + // ray for every poly face (the faces this ray has tested) + let mut marked: BTreeSet<(usize, PolyFaceId)> = BTreeSet::new(); // Init Grid scratch ray walking data with first Voxel/Ray intersection let hits_nothing = self.grid.setup_3ddda(ray_point, ray_vector)?; @@ -995,10 +1054,62 @@ impl<'s> RayTracer<'s> { // handle PolyBrep Faces let leaf = match item { GridItem::Polygon(node) => { + // get SmGridElement's SmTreeNode - PolyBreps store SmTreeNodes in + // SmGridElements + let index = + *self + .poly_elements + .get(&id) + .ok_or(SolidError::InvalidInput { + operation: OP, + reason: "polygon grid element of no listed poly B-rep", + })?; + let traced = + self.poly_breps + .get_mut(index) + .ok_or(SolidError::InvalidInput { + operation: OP, + reason: "polygon grid element of no listed poly B-rep", + })?; + let tree = traced + .cache + .surface_tree() + .ok_or(SolidError::null_pointer(OP))?; + // Handle polygon ray firing - let hit = find_poly_hit(node, ray_point, ray_vector, hit_t)?; - if let Some(hit) = hit { - hit_t = hit; + let hit = find_poly_hit( + k, + tree, + traced.brep, + node, + PolyRay { + point: ray_point, + vector: ray_vector, + hit_any_thing: self.hit_any_thing, + }, + hit_t, + &mut |face| marked.insert((index, face)), + )?; + if let Some((t, hit_face)) = hit { + hit_t = t; + let hit_pnt = ray_point + ray_vector * hit_t; + let (hit_normal, _) = + traced.brep.face_normal(k, hit_face, false, true)?; + let out = &mut result.solution; + out.solution_type = SolutionType::SingleValue; + out.num_variables = 1; + out.start.parameters[0] = hit_t; // ray parameter + out.start.parameters[1] = BIG_DOUBLE; + out.start.parameters[2] = BIG_DOUBLE; + out.start.parameters[3] = hit_pnt.x; // ray/face intersection point + out.start.parameters[4] = hit_pnt.y; + out.start.parameters[5] = hit_pnt.z; + out.start.parameters[6] = hit_normal.x; // face normal at xsect + out.start.parameters[7] = hit_normal.y; + out.start.parameters[8] = hit_normal.z; + out.num_objects = 1; + out.objects[0] = Some(RayObject::PolyFace(index, hit_face)); + out.objects[1] = Some(RayObject::PolyFace(index, hit_face)); result.hits_something = true; if self.hit_any_thing { break; @@ -1140,19 +1251,66 @@ impl<'s> RayTracer<'s> { self.grid.expand_hierarchy(k, id, &mut cache) } - /// Embeds the faces of a poly B-rep in the grid (`AddPolyBrepToGrid`). + /// Embeds the faces of a poly B-rep in the grid (`AddPolyBrepToGrid`): notifies + /// the poly B-rep of an edit (`env` is its notification environment), builds its + /// `SmBrepCache` and adds every leaf of the cache's surface tree to the grid as a + /// polygon element over the leaf's box expanded by `expansion_tolerance`. The + /// poly B-rep joins the tracer's poly B-rep list, so it stays borrowed. /// /// # Errors /// - /// [`SolidError::Unported`]: `SmPolyBrep` and its `SmBrepCache` are not ported - /// (`query.poly`), so `P` stands for the poly B-rep type to come. - pub fn add_poly_brep_to_grid( + /// Those of [`PolyBrep::notify`], of the cache build and of + /// [`Grid::add_element`]; a null pointer when the cache has no surface tree. + pub fn add_poly_brep_to_grid( &mut self, - _poly_brep: &P, - _expansion_tolerance: f64, + k: &Kernel, + poly_brep: &'s mut PolyBrep, + env: &mut PolyNotifyEnv<'_, R, T>, + expansion_tolerance: f64, ) -> Result<()> { - // UNPORTED(query.poly): SmPolyBrep::Notify - Err(SolidError::unported("SmPolyBrep::Notify", "query.poly")) + const OP: &str = "intersect::ray_tracer::RayTracer::add_poly_brep_to_grid"; + // warn PolyBrep its about to be modifided + let this = Some(env.this); + poly_brep.notify(env, NotifyOperation::PreEdit, this, None, None)?; + + // Compute a Brep Cache - a spatial decomposition of the vertices, edges and + // faces contained in 3 SmTree objects used as a VertexTree, an EdgeTree, and a + // FaceTree (the notification dropped any cache the poly B-rep had) + let cache = make_brep_cache(k, CacheSource::PolyBrep(&*poly_brep), &arena_identity)?; + + // get all Surface Tree Nodes + let tree = cache.surface_tree().ok_or(SolidError::null_pointer(OP))?; + let index = self.poly_breps.len(); + + // For every Surface Tree Node + for node_id in tree.all_tree_nodes() { + let node = tree.node(node_id)?; + + // skip non-leaf nodes + if node.child1.is_some() { + continue; + } + + // Get PNode's boundingBox increased by Tol + let mut bbox = node.bbox; + bbox.expand_absolute(expansion_tolerance); + + // Add leaf node to Grid: an SmGridElement holding the node, listed in every + // voxel that its box meets + let id = self + .grid + .add_element(ElementSource::PolyBrep(node_id), &bbox)?; + self.poly_elements.insert(id, index); + } // end iter every Surface Tree Node + + // Add the PolyBrep to the SmRayTracer PolyBrep List + self.poly_breps.push(TracedPolyBrep { + brep: poly_brep, + cache, + }); + + // all done + Ok(()) } /// `AddSurfaceToGrid` up to the expansion, from the point where the top node of @@ -1430,21 +1588,105 @@ impl<'s> RayRenderer<'s> { } } -/// `sm_FindPolyHit`: the nearest hit of the ray on the poly faces of a polygon element's -/// tree node and its parents, with parameter below `hit_t`: its parameter. +/// The ray of [`find_poly_hit`]. +#[derive(Debug, Clone, Copy)] +struct PolyRay { + /// `crRayPoint`: the ray's start point. + point: Point3, + /// `crRayVector`: the ray's unit direction. + vector: Vec3, + /// `bHitAnyThing`: stop at the first hit. + hit_any_thing: bool, +} + +/// `sm_FindPolyHit`: intersects the ray with the poly faces of `node` (a node of the +/// `SmBrepCache` surface tree `tree` of `brep`) and of its parents. A face hit with ray +/// parameter in `(-EFF_ZERO, hit_t)` lowers `hit_t`; the last such hit is returned +/// with its face (with `hit_any_thing`, the first). `mark` marks a face for this ray +/// and is false when the ray has tested the face before (`IsMarked` / `Mark`), which +/// then is skipped. +/// +/// Upstream recurses on the parent node after the node's own faces; this walks up the +/// parents in a loop in the same order, bounded by the tree's node count. fn find_poly_hit( - _node: NodeId, - _ray_point: Point3, - _ray_vector: Vec3, - _hit_t: f64, -) -> Result> { - // The node's tree is the SmBrepCache surface tree of a poly B-rep and its objects - // are SmPolyFaces (SmPolyFace::ComputeSphereBound, GetNormal, PointInPolygon). - // UNPORTED(query.poly): SmPolyFace::ComputeSphereBound - Err(SolidError::unported( - "SmPolyFace::ComputeSphereBound", - "query.poly", - )) + k: &Kernel, + tree: &Tree, + brep: &mut PolyBrep, + node: NodeId, + ray: PolyRay, + mut hit_t: f64, + mark: &mut dyn FnMut(PolyFaceId) -> bool, +) -> Result> { + const OP: &str = "intersect::ray_tracer::find_poly_hit"; + let mut hit_face = None; + let mut current = Some(node); + let max_depth = tree.all_tree_nodes().len(); + let mut depth = 0usize; + while let Some(node) = current { + // (a parent chain longer than the tree has nodes is a cycle) + depth += 1; + if depth > max_depth { + return Err(SolidError::InvalidInput { + operation: OP, + reason: "tree parent links form a cycle", + }); + } + + // for every object in pNode + for list in tree.node_object_list(node)? { + let Some(CacheObject::PolyFace(poly_face)) = tree.list(list)?.object else { + return Err(SolidError::null_pointer(OP)); + }; + + // skip already processed PolyFaces (and mark this one) + if !mark(poly_face) { + continue; // This face has been processed before + } + + // get PolyFace bounding sphere/Ray Distance + let (sphere_center, sphere_radius) = + brep.face_compute_sphere_bound(poly_face, false)?; + let vec_to_center = sphere_center - ray.point; + let center_t = vec_to_center.dot(ray.vector); + let point_on_ray = ray.point + ray.vector * center_t; + let vec_to = point_on_ray - sphere_center; + let dist_sq = vec_to.length_squared(); + let sph_rad_sq = sphere_radius * sphere_radius; + + // skip PolyFaces whose bounding Sphere does not intersect the ray + if dist_sq > sph_rad_sq { + continue; + } + let (normal, _) = brep.face_normal(k, poly_face, false, true)?; + let poly_loop = brep + .face_outer_poly_loop(poly_face)? + .ok_or(SolidError::null_pointer(OP))?; + let first_edge = brep + .loop_first_poly_edge(poly_loop)? + .ok_or(SolidError::null_pointer(OP))?; + let origin = brep.edge_start_point(first_edge)?; + let Ok(t_param) = gu::line_plane_intersect(ray.point, ray.vector, origin, normal) + else { + continue; + }; + let point = ray.point + ray.vector * t_param; + if point.distance_squared(sphere_center) > sph_rad_sq { + continue; // Intersection is outside of sphere radius. + } + let inside = brep.face_point_in_polygon(k, poly_face, point, true)?; + if inside && t_param < hit_t && t_param > -EFF_ZERO { + hit_t = t_param; + hit_face = Some(poly_face); + if ray.hit_any_thing { + return Ok(hit_face.map(|f| (hit_t, f))); + } + } + } // end iter every pNode Object + + // continue on the parent node + current = tree.node(node)?.parent; + } + Ok(hit_face.map(|f| (hit_t, f))) } #[cfg(test)] @@ -1459,6 +1701,180 @@ mod tests { Grid::new(&bbox, [2, 2, 2]).unwrap() } + /// A poly B-rep of horizontal squares `[2, 8] x [2, 8]` at the heights `zs`, wound + /// counter-clockwise seen from above, and their faces. + fn stacked_squares(k: &Kernel, zs: &[f64]) -> (PolyBrep, Vec) { + let mut b = PolyBrep::new(k, 1.0e-5); + let r = b.add_region().unwrap(); + let s = b.add_shell(r).unwrap(); + let mut faces = Vec::new(); + for &z in zs { + let f = b.add_face(k, s, 1.0e-5, None).unwrap(); + let pts = [ + Vec3::new(2.0, 2.0, z), + Vec3::new(8.0, 2.0, z), + Vec3::new(8.0, 8.0, z), + Vec3::new(2.0, 8.0, z), + ]; + let vs: Vec<_> = pts + .iter() + .map(|&p| b.add_vertex(k, p, 1.0e-5).unwrap()) + .collect(); + let l = b.start_poly_edge_loop(f).unwrap(); + for j in 0..4 { + b.add_poly_edge(k, l, 1.0e-5, pts[j], pts[(j + 1) % 4], vs[j]) + .unwrap(); + } + b.finish_poly_edge_loop(l).unwrap(); + faces.push(f); + } + (b, faces) + } + + /// Adds `brep` to `tracer` with an empty notification environment. + fn add_poly<'s>(k: &Kernel, tracer: &mut RayTracer<'s>, brep: &'s mut PolyBrep) { + use crate::geom::a_object::CacheStore; + use crate::geom::attribute::{AttributeStore, ObjectKey}; + use crate::query::poly::PolyObjectKind; + let mut records: BTreeMap = BTreeMap::new(); + let mut attributes = AttributeStore::new(); + let mut caches: CacheStore<()> = CacheStore::new(); + let mut env = PolyNotifyEnv { + records: &mut records, + attributes: &mut attributes, + caches: &mut caches, + context: None, + this: ObjectRef { + type_code: PolyObjectKind::Brep.type_code(), + key: ObjectKey(1), + }, + }; + tracer + .add_poly_brep_to_grid(k, brep, &mut env, 1.0e-3) + .unwrap(); + } + + #[test] + fn a_poly_brep_adds_one_polygon_element_per_surface_tree_leaf() { + let k = Kernel::default(); + let (mut b, _) = stacked_squares(&k, &[2.0, 6.0]); + let mut tracer = RayTracer::new(grid(), 1.0e-6, 1.0e-9); + add_poly(&k, &mut tracer, &mut b); + assert_eq!(tracer.poly_brep_count(), 1); + let elements = tracer.poly_elements.len(); + assert!(elements >= 1); + let tree = tracer.poly_breps[0].cache.surface_tree().unwrap(); + let leaves = tree + .all_tree_nodes() + .into_iter() + .filter(|&n| tree.node(n).unwrap().child1.is_none()) + .count(); + assert_eq!(elements, leaves); + } + + #[test] + fn a_ray_hits_the_nearest_poly_face() { + let k = Kernel::default(); + let (mut b, faces) = stacked_squares(&k, &[2.0, 6.0]); + let mut tracer = RayTracer::new(grid(), 1.0e-6, 1.0e-9); + add_poly(&k, &mut tracer, &mut b); + let hit = tracer + .fire_ray( + &k, + Vec3::new(5.0, 5.0, 9.5), + Vec3::new(0.0, 0.0, -1.0), + BIG_DOUBLE, + ) + .unwrap(); + assert!(hit.hits_something); + let s = &hit.solution; + assert_eq!(s.solution_type, SolutionType::SingleValue); + assert_eq!(s.num_variables, 1); + assert_eq!(s.num_objects, 1); + assert_eq!(s.objects[0], Some(RayObject::PolyFace(0, faces[1]))); + assert_eq!(s.objects[1], Some(RayObject::PolyFace(0, faces[1]))); + assert_eq!( + s.start.parameters[..9], + [3.5, BIG_DOUBLE, BIG_DOUBLE, 5.0, 5.0, 6.0, 0.0, 0.0, 1.0] + ); + } + + #[test] + fn a_poly_face_behind_the_ray_start_is_not_hit() { + let k = Kernel::default(); + let (mut b, faces) = stacked_squares(&k, &[2.0, 6.0]); + let mut tracer = RayTracer::new(grid(), 1.0e-6, 1.0e-9); + add_poly(&k, &mut tracer, &mut b); + let hit = tracer + .fire_ray( + &k, + Vec3::new(5.0, 5.0, 4.0), + Vec3::new(0.0, 0.0, -1.0), + BIG_DOUBLE, + ) + .unwrap(); + assert!(hit.hits_something); + assert_eq!( + hit.solution.objects[0], + Some(RayObject::PolyFace(0, faces[0])) + ); + assert_eq!(hit.solution.start.parameters[0], 2.0); + } + + #[test] + fn rays_outside_or_along_the_poly_faces_hit_nothing() { + let k = Kernel::default(); + let (mut b, _) = stacked_squares(&k, &[2.0, 6.0]); + let mut tracer = RayTracer::new(grid(), 1.0e-6, 1.0e-9); + add_poly(&k, &mut tracer, &mut b); + // through the faces' planes outside the squares + let miss = tracer + .fire_ray( + &k, + Vec3::new(9.0, 5.0, 9.5), + Vec3::new(0.0, 0.0, -1.0), + BIG_DOUBLE, + ) + .unwrap(); + assert!(!miss.hits_something); + // parallel to the faces, between them + let along = tracer + .fire_ray( + &k, + Vec3::new(0.5, 5.0, 4.0), + Vec3::new(1.0, 0.0, 0.0), + BIG_DOUBLE, + ) + .unwrap(); + assert!(!along.hits_something); + // stopped before the first face + let short = tracer + .fire_ray(&k, Vec3::new(5.0, 5.0, 9.5), Vec3::new(0.0, 0.0, -1.0), 3.0) + .unwrap(); + assert!(!short.hits_something); + } + + #[test] + fn hit_any_thing_stops_at_a_poly_hit() { + let k = Kernel::default(); + let (mut b, faces) = stacked_squares(&k, &[2.0, 6.0]); + let mut tracer = RayTracer::new(grid(), 1.0e-6, 1.0e-9); + tracer.hit_any_thing = true; + add_poly(&k, &mut tracer, &mut b); + let hit = tracer + .fire_ray( + &k, + Vec3::new(5.0, 5.0, 9.5), + Vec3::new(0.0, 0.0, -1.0), + BIG_DOUBLE, + ) + .unwrap(); + assert!(hit.hits_something); + assert!(faces + .iter() + .any(|&f| hit.solution.objects[0] == Some(RayObject::PolyFace(0, f)))); + } + #[test] fn convert_to_parameter_delta_projects_on_the_tangent() { let d = convert_to_parameter_delta(Vec3::new(2.0, 0.0, 0.0), Vec3::new(3.0, 4.0, 0.0)); diff --git a/fixtures/intersect.ray_tracer/poly_breps.json b/fixtures/intersect.ray_tracer/poly_breps.json new file mode 100644 index 0000000000..12b49f1273 --- /dev/null +++ b/fixtures/intersect.ray_tracer/poly_breps.json @@ -0,0 +1,10042 @@ +{ + "cases": [ + { + "name": "tess_test10_extrusion", + "upstream_ref": "tests/prog_test/src/smTessTest.cpp:2716 (test 10: Extrusion.pbp, grid 20^3 over the box expanded by 1.0, SM_ZONE_TOL_3D, 1e-8, AddPolyBrepToGrid 0.001, ray from the centre - 15 (1,1,1)/sqrt 3 to 40; plus hit-any, reverse and axis rays)", + "inputs": { + "surfaces": [ + { + "kind": "plane", + "o": [ + 0, + 0, + 0 + ], + "x": [ + 1, + 0, + 0 + ], + "y": [ + 0, + 1, + 0 + ], + "scale": [ + 1, + 1 + ], + "domain": [ + 0, + 0, + 4, + 4 + ] + } + ], + "grid": { + "bbox": [ + -1.0, + -1.0, + -1.0, + 11.0, + 11.0, + 11.0 + ], + "size": [ + 20, + 20, + 20 + ], + "ray_tol": 1e-05, + "ray_acc": 1e-08 + }, + "ops": [ + { + "op": "add_poly_brep", + "tol": 1.732051154e-05, + "vertices": [ + [ + 1.37074335724702, + 1.56655956629769, + 10.0, + 1.7320511688e-05 + ], + [ + 1.37074335724703, + 1.56655956629769, + 0.0, + 1.7320511688e-05 + ], + [ + 2.23289315903909, + 2.28234796653281, + 10.0, + 1.7320511688e-05 + ], + [ + 2.23289315903909, + 2.28234796653281, + 0.0, + 1.7320511688e-05 + ], + [ + 2.67517120358264, + 2.50726337266332, + 10.0, + 1.7320511688e-05 + ], + [ + 2.67517120358264, + 2.50726337266332, + 0.0, + 1.7320511688e-05 + ], + [ + 2.89498708837321, + 2.57196313369949, + 10.0, + 1.7320511688e-05 + ], + [ + 2.89498708837321, + 2.57196313369949, + 0.0, + 1.7320511688e-05 + ], + [ + 3.11234986307419, + 2.59929541203728, + 10.0, + 1.7320511688e-05 + ], + [ + 3.11234986307419, + 2.59929541203728, + 0.0, + 1.7320511688e-05 + ], + [ + 3.28284294447435, + 2.59245551973401, + 10.0, + 1.7320511688e-05 + ], + [ + 3.28284294447435, + 2.59245551973401, + 0.0, + 1.7320511688e-05 + ], + [ + 3.45144110327034, + 2.56241578255653, + 0.0, + 1.7320511688e-05 + ], + [ + 3.45144110327034, + 2.56241578255653, + 10.0, + 1.7320511688e-05 + ], + [ + 3.78455969272179, + 2.44496074286762, + 10.0, + 1.7320511688e-05 + ], + [ + 3.78455969272179, + 2.44496074286762, + 0.0, + 1.7320511688e-05 + ], + [ + 4.44573523676663, + 2.06611618717527, + 10.0, + 1.7320511688e-05 + ], + [ + 4.44573523676663, + 2.06611618717527, + 0.0, + 1.7320511688e-05 + ], + [ + 5.12158912996071, + 1.65834525358009, + 10.0, + 1.7320511688e-05 + ], + [ + 5.12158912996071, + 1.65834525358009, + 0.0, + 1.7320511688e-05 + ], + [ + 5.47305565584873, + 1.50473224151264, + 10.0, + 1.7320511688e-05 + ], + [ + 5.47305565584873, + 1.50473224151264, + 0.0, + 1.7320511688e-05 + ], + [ + 5.83783400705606, + 1.41723145070192, + 10.0, + 1.7320511688e-05 + ], + [ + 5.83783400705606, + 1.41723145070192, + 0.0, + 1.7320511688e-05 + ], + [ + 6.10509576113763, + 1.4064791528136, + 10.0, + 1.7320511688e-05 + ], + [ + 6.10509576113763, + 1.4064791528136, + 0.0, + 1.7320511688e-05 + ], + [ + 6.37899461997415, + 1.43706751679159, + 10.0, + 1.7320511688e-05 + ], + [ + 6.37899461997415, + 1.43706751679159, + 0.0, + 1.7320511688e-05 + ], + [ + 6.94020026160522, + 1.60650855814332, + 10.0, + 1.7320511688e-05 + ], + [ + 6.94020026160522, + 1.60650855814332, + 0.0, + 1.7320511688e-05 + ], + [ + 8.07071950855174, + 2.26814418900747, + 0.0, + 1.7320511688e-05 + ], + [ + 8.07071950855174, + 2.26814418900747, + 10.0, + 1.7320511688e-05 + ], + [ + 10.0, + 4.0, + 0.0, + 1.7320511688e-05 + ], + [ + 10.0, + 4.0, + 10.0, + 1.7320511688e-05 + ], + [ + 10.0, + 8.0, + 0.0, + 1.7320511688e-05 + ], + [ + 9.63607389517571, + 8.03338926316685, + 10.0, + 1.7320511688e-05 + ], + [ + 10.0, + 8.0, + 10.0, + 1.7320511688e-05 + ], + [ + 8.03338926316685, + 9.63607389517571, + 0.0, + 1.7320511688e-05 + ], + [ + 8.03338926316685, + 9.63607389517571, + 10.0, + 1.7320511688e-05 + ], + [ + 8.14042339787514, + 9.26381058087625, + 10.0, + 1.7320511688e-05 + ], + [ + 8.14042339787514, + 9.26381058087626, + 0.0, + 1.7320511688e-05 + ], + [ + 8.32600897756269, + 8.90558049322971, + 0.0, + 1.7320511688e-05 + ], + [ + 8.3260089775627, + 8.90558049322972, + 10.0, + 1.7320511688e-05 + ], + [ + 8.5857864376269, + 8.5857864376269, + 10.0, + 1.7320511688e-05 + ], + [ + 8.5857864376269, + 8.5857864376269, + 0.0, + 1.7320511688e-05 + ], + [ + 8.90558049322971, + 8.3260089775627, + 0.0, + 1.7320511688e-05 + ], + [ + 8.90558049322972, + 8.3260089775627, + 10.0, + 1.7320511688e-05 + ], + [ + 9.26381058087625, + 8.14042339787514, + 10.0, + 1.7320511688e-05 + ], + [ + 9.63607389517571, + 8.03338926316685, + 0.0, + 1.7320511688e-05 + ], + [ + 9.26381058087625, + 8.14042339787514, + 0.0, + 1.7320511688e-05 + ], + [ + 8.0, + 10.0, + 10.0, + 1.7320511688e-05 + ], + [ + 0.0, + 0.0, + 10.0, + 1.7320511688e-05 + ], + [ + 0.0, + 0.0, + 0.0, + 1.7320511688e-05 + ], + [ + 0.0, + 8.0, + 0.0, + 1.7320511688e-05 + ], + [ + 0.0, + 8.0, + 10.0, + 1.7320511688e-05 + ], + [ + 0.03338926316685, + 8.36392610482429, + 0.0, + 1.7320511688e-05 + ], + [ + 0.03338926316685, + 8.36392610482429, + 10.0, + 1.7320511688e-05 + ], + [ + 0.14042339787514, + 8.73618941912375, + 0.0, + 1.7320511688e-05 + ], + [ + 0.14042339787514, + 8.73618941912375, + 10.0, + 1.7320511688e-05 + ], + [ + 0.3260089775627, + 9.09441950677029, + 0.0, + 1.7320511688e-05 + ], + [ + 0.3260089775627, + 9.09441950677029, + 10.0, + 1.7320511688e-05 + ], + [ + 0.58578643762691, + 9.4142135623731, + 0.0, + 1.7320511688e-05 + ], + [ + 0.58578643762691, + 9.4142135623731, + 10.0, + 1.7320511688e-05 + ], + [ + 0.90558049322971, + 9.6739910224373, + 0.0, + 1.7320511688e-05 + ], + [ + 0.90558049322971, + 9.6739910224373, + 10.0, + 1.7320511688e-05 + ], + [ + 1.26381058087626, + 9.85957660212486, + 0.0, + 1.7320511688e-05 + ], + [ + 1.26381058087625, + 9.85957660212486, + 10.0, + 1.7320511688e-05 + ], + [ + 1.63607389517571, + 9.96661073683315, + 0.0, + 1.7320511688e-05 + ], + [ + 1.63607389517571, + 9.96661073683315, + 10.0, + 1.7320511688e-05 + ], + [ + 2.0, + 10.0, + 0.0, + 1.7320511688e-05 + ], + [ + 2.0, + 10.0, + 10.0, + 1.7320511688e-05 + ], + [ + 5.0, + 10.0, + 10.0, + 1.7320511688e-05 + ], + [ + 8.0, + 10.0, + 0.0, + 1.7320511688e-05 + ], + [ + 5.0, + 10.0, + 0.0, + 1.7320511688e-05 + ], + [ + 6.0, + 6.0, + 0.0, + 1.7320511688e-05 + ], + [ + 6.0, + 6.0, + 10.0, + 1.7320511688e-05 + ], + [ + 8.0, + 6.0, + 0.0, + 1.7320511688e-05 + ], + [ + 8.0, + 6.0, + 10.0, + 1.7320511688e-05 + ], + [ + 8.0, + 4.0, + 0.0, + 1.7320511688e-05 + ], + [ + 8.0, + 4.0, + 10.0, + 1.7320511688e-05 + ], + [ + 6.0, + 4.0, + 0.0, + 1.7320511688e-05 + ], + [ + 6.0, + 4.0, + 10.0, + 1.7320511688e-05 + ], + [ + 5.0, + 7.0, + 0.0, + 1.7320511688e-05 + ], + [ + 5.0, + 7.0, + 10.0, + 1.7320511688e-05 + ], + [ + 4.96661073683323, + 7.36392610482386, + 0.0, + 1.7320511688e-05 + ], + [ + 4.96661073683323, + 7.36392610482386, + 10.0, + 1.7320511688e-05 + ], + [ + 4.85957660212502, + 7.73618941912335, + 0.0, + 1.7320511688e-05 + ], + [ + 4.85957660212502, + 7.73618941912335, + 10.0, + 1.7320511688e-05 + ], + [ + 4.67399102243753, + 8.09441950676994, + 0.0, + 1.7320511688e-05 + ], + [ + 4.67399102243753, + 8.09441950676994, + 10.0, + 1.7320511688e-05 + ], + [ + 4.41421356237337, + 8.41421356237282, + 0.0, + 1.7320511688e-05 + ], + [ + 4.41421356237337, + 8.41421356237282, + 10.0, + 1.7320511688e-05 + ], + [ + 4.09441950677058, + 8.67399102243711, + 0.0, + 1.7320511688e-05 + ], + [ + 4.09441950677058, + 8.67399102243711, + 10.0, + 1.7320511688e-05 + ], + [ + 3.73618941912404, + 8.85957660212474, + 0.0, + 1.7320511688e-05 + ], + [ + 3.73618941912404, + 8.85957660212474, + 10.0, + 1.7320511688e-05 + ], + [ + 3.36392610482456, + 8.9666107368331, + 0.0, + 1.7320511688e-05 + ], + [ + 3.36392610482456, + 8.9666107368331, + 10.0, + 1.7320511688e-05 + ], + [ + 3.00000000000023, + 9.0, + 0.0, + 1.7320511688e-05 + ], + [ + 3.00000000000023, + 9.0, + 10.0, + 1.7320511688e-05 + ], + [ + 2.63607389517593, + 8.96661073683319, + 0.0, + 1.7320511688e-05 + ], + [ + 2.63607389517593, + 8.96661073683319, + 10.0, + 1.7320511688e-05 + ], + [ + 2.26381058087644, + 8.85957660212494, + 0.0, + 1.7320511688e-05 + ], + [ + 2.26381058087645, + 8.85957660212494, + 10.0, + 1.7320511688e-05 + ], + [ + 1.90558049322987, + 8.6739910224374, + 10.0, + 1.7320511688e-05 + ], + [ + 1.90558049322987, + 8.6739910224374, + 0.0, + 1.7320511688e-05 + ], + [ + 1.58578643762702, + 8.41421356237321, + 0.0, + 1.7320511688e-05 + ], + [ + 1.58578643762701, + 8.4142135623732, + 10.0, + 1.7320511688e-05 + ], + [ + 4.96661073683322, + 6.63607389517607, + 10.0, + 1.7320511688e-05 + ], + [ + 4.96661073683321, + 6.63607389517607, + 0.0, + 1.7320511688e-05 + ], + [ + 4.85957660212499, + 6.26381058087658, + 10.0, + 1.7320511688e-05 + ], + [ + 4.85957660212499, + 6.26381058087659, + 0.0, + 1.7320511688e-05 + ], + [ + 4.67399102243749, + 5.90558049323, + 10.0, + 1.7320511688e-05 + ], + [ + 4.67399102243749, + 5.90558049323, + 0.0, + 1.7320511688e-05 + ], + [ + 4.41421356237332, + 5.58578643762712, + 10.0, + 1.7320511688e-05 + ], + [ + 4.41421356237332, + 5.58578643762713, + 0.0, + 1.7320511688e-05 + ], + [ + 4.09441950677052, + 5.32600897756285, + 10.0, + 1.7320511688e-05 + ], + [ + 4.09441950677052, + 5.32600897756285, + 0.0, + 1.7320511688e-05 + ], + [ + 3.73618941912397, + 5.14042339787523, + 0.0, + 1.7320511688e-05 + ], + [ + 3.73618941912397, + 5.14042339787523, + 10.0, + 1.7320511688e-05 + ], + [ + 3.36392610482449, + 5.03338926316689, + 10.0, + 1.7320511688e-05 + ], + [ + 3.36392610482449, + 5.03338926316689, + 0.0, + 1.7320511688e-05 + ], + [ + 3.00000000000017, + 5.0, + 10.0, + 1.7320511688e-05 + ], + [ + 3.00000000000017, + 5.0, + 0.0, + 1.7320511688e-05 + ], + [ + 2.63607389517586, + 5.03338926316682, + 0.0, + 1.7320511688e-05 + ], + [ + 2.63607389517586, + 5.03338926316682, + 10.0, + 1.7320511688e-05 + ], + [ + 2.26381058087638, + 5.14042339787509, + 0.0, + 1.7320511688e-05 + ], + [ + 1.9055804932298, + 5.32600897756264, + 10.0, + 1.7320511688e-05 + ], + [ + 2.26381058087638, + 5.14042339787509, + 10.0, + 1.7320511688e-05 + ], + [ + 1.32600897756277, + 8.09441950677039, + 0.0, + 1.7320511688e-05 + ], + [ + 1.32600897756277, + 8.09441950677039, + 10.0, + 1.7320511688e-05 + ], + [ + 1.14042339787517, + 7.73618941912383, + 0.0, + 1.7320511688e-05 + ], + [ + 1.14042339787517, + 7.73618941912383, + 10.0, + 1.7320511688e-05 + ], + [ + 1.03338926316686, + 7.36392610482435, + 0.0, + 1.7320511688e-05 + ], + [ + 1.03338926316686, + 7.36392610482435, + 10.0, + 1.7320511688e-05 + ], + [ + 1.0, + 7.00000000000003, + 0.0, + 1.7320511688e-05 + ], + [ + 1.0, + 7.00000000000003, + 10.0, + 1.7320511688e-05 + ], + [ + 1.03338926316685, + 6.63607389517572, + 0.0, + 1.7320511688e-05 + ], + [ + 1.03338926316685, + 6.63607389517572, + 10.0, + 1.7320511688e-05 + ], + [ + 1.14042339787515, + 6.26381058087624, + 0.0, + 1.7320511688e-05 + ], + [ + 1.14042339787515, + 6.26381058087624, + 10.0, + 1.7320511688e-05 + ], + [ + 1.32600897756272, + 5.90558049322967, + 0.0, + 1.7320511688e-05 + ], + [ + 1.32600897756272, + 5.90558049322967, + 10.0, + 1.7320511688e-05 + ], + [ + 1.58578643762696, + 5.58578643762685, + 10.0, + 1.7320511688e-05 + ], + [ + 1.90558049322981, + 5.32600897756264, + 0.0, + 1.7320511688e-05 + ], + [ + 1.58578643762696, + 5.58578643762685, + 0.0, + 1.7320511688e-05 + ] + ], + "faces": [ + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 73, + 100, + 102 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 73, + 102, + 69 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 73, + 98, + 100 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 73, + 96, + 98 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 73, + 94, + 96 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 73, + 92, + 94 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 73, + 90, + 92 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 102, + 67, + 69 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 105, + 67, + 102 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 105, + 65, + 67 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 105, + 63, + 65 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 106, + 63, + 105 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 106, + 61, + 63 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 106, + 59, + 61 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 129, + 59, + 106 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 129, + 57, + 59 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 129, + 55, + 57 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 131, + 55, + 129 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 131, + 53, + 55 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 133, + 53, + 131 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 37, + 90, + 73 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 37, + 73, + 72 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 37, + 88, + 90 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 40, + 88, + 37 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 40, + 86, + 88 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 41, + 86, + 40 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 41, + 84, + 86 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 44, + 84, + 41 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 44, + 82, + 84 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 135, + 53, + 133 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 80, + 111, + 74 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 109, + 74, + 111 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 80, + 113, + 111 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 80, + 115, + 113 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 80, + 117, + 115 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 82, + 74, + 109 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 44, + 74, + 82 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 44, + 76, + 74 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 45, + 76, + 44 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 49, + 76, + 45 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 32, + 76, + 49 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 32, + 49, + 48 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 32, + 48, + 34 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 32, + 78, + 76 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 30, + 78, + 32 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 30, + 80, + 78 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 29, + 80, + 30 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 27, + 80, + 29 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 25, + 80, + 27 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 23, + 80, + 25 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 21, + 80, + 23 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 19, + 80, + 21 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 17, + 80, + 19 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 17, + 117, + 80 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 15, + 117, + 17 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 15, + 118, + 117 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 12, + 118, + 15 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 12, + 121, + 118 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 11, + 121, + 12 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 11, + 123, + 121 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 9, + 123, + 11 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 9, + 124, + 123 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 9, + 126, + 124 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 9, + 144, + 126 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 137, + 53, + 135 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 9, + 145, + 144 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 139, + 53, + 137 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 141, + 53, + 139 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 9, + 141, + 145 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 53, + 141, + 9 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 53, + 3, + 1 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 53, + 1, + 52 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 53, + 5, + 3 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 53, + 7, + 5 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + -1.0 + ], + "loops": [ + [ + 53, + 9, + 7 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.66800592910003, + -0.74415595051522, + 0.0 + ], + "loops": [ + [ + 33, + 30, + 32 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.75257580508442, + -0.65850562457851, + 0.0 + ], + "loops": [ + [ + 1, + 51, + 52 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -1e-14, + 3e-14, + 1.0 + ], + "loops": [ + [ + 101, + 71, + 103 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 103, + 71, + 70 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 1e-14, + -1e-14, + 1.0 + ], + "loops": [ + [ + 99, + 71, + 101 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 97, + 71, + 99 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 95, + 71, + 97 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 93, + 71, + 95 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 91, + 71, + 93 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 68, + 103, + 70 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 68, + 104, + 103 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 66, + 104, + 68 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 64, + 104, + 66 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 64, + 107, + 104 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 62, + 107, + 64 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 60, + 107, + 62 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 60, + 130, + 107 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 58, + 130, + 60 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 56, + 130, + 58 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 56, + 132, + 130 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 54, + 132, + 56 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 54, + 134, + 132 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 91, + 38, + 71 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 71, + 38, + 50 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 89, + 38, + 91 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 89, + 39, + 38 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 87, + 39, + 89 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 87, + 42, + 39 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 85, + 42, + 87 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 85, + 43, + 42 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 83, + 43, + 85 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 75, + 43, + 83 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 110, + 81, + 75 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 75, + 108, + 110 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 112, + 81, + 110 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 114, + 81, + 112 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 116, + 81, + 114 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 75, + 83, + 108 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 77, + 43, + 75 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -1e-14, + 0.0, + 1.0 + ], + "loops": [ + [ + 77, + 46, + 43 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 77, + 47, + 46 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 47, + 33, + 35 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 35, + 33, + 36 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 77, + 33, + 47 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 79, + 33, + 77 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 79, + 31, + 33 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 81, + 31, + 79 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 81, + 28, + 31 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 81, + 26, + 28 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -1e-14, + 0.0, + 1.0 + ], + "loops": [ + [ + 81, + 24, + 26 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 1e-14, + 0.0, + 1.0 + ], + "loops": [ + [ + 81, + 22, + 24 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 81, + 20, + 22 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 81, + 18, + 20 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 81, + 16, + 18 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 116, + 16, + 81 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 116, + 14, + 16 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 1e-14, + 0.0, + 1.0 + ], + "loops": [ + [ + 119, + 14, + 116 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 119, + 13, + 14 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 120, + 13, + 119 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -1e-14, + 0.0, + 1.0 + ], + "loops": [ + [ + 120, + 10, + 13 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 122, + 10, + 120 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 1e-14, + 0.0, + 1.0 + ], + "loops": [ + [ + 122, + 8, + 10 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 125, + 8, + 122 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -1e-14, + -1e-14, + 1.0 + ], + "loops": [ + [ + 54, + 136, + 134 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 128, + 8, + 125 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 127, + 8, + 128 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 1e-14, + 1e-14, + 1.0 + ], + "loops": [ + [ + 54, + 138, + 136 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 143, + 8, + 127 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 54, + 140, + 138 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -4e-14, + -2e-14, + 1.0 + ], + "loops": [ + [ + 54, + 142, + 140 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -2e-14, + -1e-14, + 1.0 + ], + "loops": [ + [ + 142, + 8, + 143 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 1e-14, + 1e-14, + 1.0 + ], + "loops": [ + [ + 142, + 54, + 8 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 2, + 54, + 0 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 0, + 54, + 51 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 0.0, + 1.0 + ], + "loops": [ + [ + 4, + 54, + 2 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -1e-14, + -1e-14, + 1.0 + ], + "loops": [ + [ + 6, + 54, + 4 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 1e-14, + 0.0, + 1.0 + ], + "loops": [ + [ + 8, + 54, + 6 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.75257580508442, + -0.65850562457851, + 0.0 + ], + "loops": [ + [ + 1, + 0, + 51 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.63877724434463, + -0.76939172864509, + 0.0 + ], + "loops": [ + [ + 1, + 2, + 0 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.63877724434463, + -0.76939172864509, + 0.0 + ], + "loops": [ + [ + 3, + 2, + 1 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.45329193803261, + -0.89136211435905, + 0.0 + ], + "loops": [ + [ + 3, + 4, + 2 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.45329193803261, + -0.89136211435905, + 0.0 + ], + "loops": [ + [ + 5, + 4, + 3 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.28235924042041, + -0.95930874036944, + 0.0 + ], + "loops": [ + [ + 5, + 6, + 4 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.28235924042041, + -0.95930874036944, + 0.0 + ], + "loops": [ + [ + 7, + 6, + 5 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.12476249135477, + -0.99218663604735, + 0.0 + ], + "loops": [ + [ + 7, + 8, + 6 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.12476249135478, + -0.99218663604735, + 0.0 + ], + "loops": [ + [ + 9, + 8, + 7 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.04008605249515, + -0.99919623117552, + 0.0 + ], + "loops": [ + [ + 9, + 10, + 8 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.04008605249515, + -0.99919623117552, + 0.0 + ], + "loops": [ + [ + 11, + 10, + 9 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.17541104718877, + -0.98449528415536, + 0.0 + ], + "loops": [ + [ + 11, + 13, + 10 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.17541104718877, + -0.98449528415536, + 0.0 + ], + "loops": [ + [ + 12, + 13, + 11 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.33252749918163, + -0.9430935596684, + 0.0 + ], + "loops": [ + [ + 15, + 13, + 12 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.33252749918163, + -0.9430935596684, + 0.0 + ], + "loops": [ + [ + 15, + 14, + 13 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.49715750631478, + -0.86766031021067, + 0.0 + ], + "loops": [ + [ + 15, + 16, + 14 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.49715750631478, + -0.86766031021067, + 0.0 + ], + "loops": [ + [ + 17, + 16, + 15 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.51659816228851, + -0.85622797123204, + 0.0 + ], + "loops": [ + [ + 17, + 18, + 16 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.51659816228851, + -0.85622797123204, + 0.0 + ], + "loops": [ + [ + 19, + 18, + 17 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.40048270795724, + -0.91630431660406, + 0.0 + ], + "loops": [ + [ + 19, + 20, + 18 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.40048270795724, + -0.91630431660406, + 0.0 + ], + "loops": [ + [ + 21, + 20, + 19 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.23325696996704, + -0.97241513046733, + 0.0 + ], + "loops": [ + [ + 21, + 22, + 20 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.23325696996704, + -0.97241513046733, + 0.0 + ], + "loops": [ + [ + 23, + 22, + 21 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.04019881876321, + -0.99919170081123, + 0.0 + ], + "loops": [ + [ + 23, + 24, + 22 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.04019881876321, + -0.99919170081123, + 0.0 + ], + "loops": [ + [ + 25, + 24, + 23 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.11098762052975, + -0.99382178889836, + 0.0 + ], + "loops": [ + [ + 25, + 26, + 24 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.11098762052975, + -0.99382178889836, + 0.0 + ], + "loops": [ + [ + 27, + 26, + 25 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.28903659395272, + -0.95731804921677, + 0.0 + ], + "loops": [ + [ + 27, + 28, + 26 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.28903659395272, + -0.95731804921677, + 0.0 + ], + "loops": [ + [ + 29, + 28, + 27 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.5051042976374, + -0.86305831118658, + 0.0 + ], + "loops": [ + [ + 29, + 31, + 28 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.5051042976374, + -0.86305831118658, + 0.0 + ], + "loops": [ + [ + 30, + 31, + 29 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.66800592910003, + -0.74415595051522, + 0.0 + ], + "loops": [ + [ + 30, + 33, + 31 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 1.0, + 0.0, + 0.0 + ], + "loops": [ + [ + 34, + 33, + 32 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 1.0, + 0.0, + 0.0 + ], + "loops": [ + [ + 33, + 34, + 36 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.27632753120439, + 0.96106352313387, + 0.0 + ], + "loops": [ + [ + 48, + 47, + 35 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.09136364589765, + 0.99581759585192, + 0.0 + ], + "loops": [ + [ + 48, + 36, + 34 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.09136364589765, + 0.99581759585192, + 0.0 + ], + "loops": [ + [ + 48, + 35, + 36 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.99581759585191, + 0.09136364589766, + 0.0 + ], + "loops": [ + [ + 50, + 37, + 72 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.99581759585191, + 0.09136364589766, + 0.0 + ], + "loops": [ + [ + 38, + 37, + 50 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.96106352313387, + 0.27632753120439, + 0.0 + ], + "loops": [ + [ + 38, + 40, + 37 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.96106352313387, + 0.27632753120438, + 0.0 + ], + "loops": [ + [ + 39, + 40, + 38 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.88791993420892, + 0.4599980330767, + 0.0 + ], + "loops": [ + [ + 42, + 40, + 39 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.88791993420892, + 0.45999803307669, + 0.0 + ], + "loops": [ + [ + 42, + 41, + 40 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.77617957795067, + 0.6305119053375, + 0.0 + ], + "loops": [ + [ + 42, + 44, + 41 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.77617957795067, + 0.6305119053375, + 0.0 + ], + "loops": [ + [ + 43, + 44, + 42 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.63051190533749, + 0.77617957795067, + 0.0 + ], + "loops": [ + [ + 46, + 44, + 43 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.6305119053375, + 0.77617957795067, + 0.0 + ], + "loops": [ + [ + 46, + 45, + 44 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.45999803307669, + 0.88791993420892, + 0.0 + ], + "loops": [ + [ + 46, + 49, + 45 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.4599980330767, + 0.88791993420892, + 0.0 + ], + "loops": [ + [ + 47, + 49, + 46 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.27632753120439, + 0.96106352313387, + 0.0 + ], + "loops": [ + [ + 47, + 48, + 49 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 1.0, + 0.0 + ], + "loops": [ + [ + 72, + 71, + 50 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -1.0, + 0.0, + 0.0 + ], + "loops": [ + [ + 54, + 52, + 51 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -1.0, + 0.0, + 0.0 + ], + "loops": [ + [ + 54, + 53, + 52 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.99581759585191, + 0.09136364589766, + 0.0 + ], + "loops": [ + [ + 54, + 55, + 53 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.99581759585191, + 0.09136364589766, + 0.0 + ], + "loops": [ + [ + 56, + 55, + 54 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.96106352313387, + 0.27632753120439, + 0.0 + ], + "loops": [ + [ + 56, + 57, + 55 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.96106352313387, + 0.27632753120439, + 0.0 + ], + "loops": [ + [ + 58, + 57, + 56 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.88791993420892, + 0.4599980330767, + 0.0 + ], + "loops": [ + [ + 58, + 59, + 57 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.88791993420892, + 0.4599980330767, + 0.0 + ], + "loops": [ + [ + 60, + 59, + 58 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.77617957795067, + 0.6305119053375, + 0.0 + ], + "loops": [ + [ + 60, + 61, + 59 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.77617957795067, + 0.6305119053375, + 0.0 + ], + "loops": [ + [ + 62, + 61, + 60 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.63051190533749, + 0.77617957795067, + 0.0 + ], + "loops": [ + [ + 62, + 63, + 61 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.6305119053375, + 0.77617957795067, + 0.0 + ], + "loops": [ + [ + 64, + 63, + 62 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.4599980330767, + 0.88791993420892, + 0.0 + ], + "loops": [ + [ + 64, + 65, + 63 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.4599980330767, + 0.88791993420892, + 0.0 + ], + "loops": [ + [ + 66, + 65, + 64 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.27632753120438, + 0.96106352313387, + 0.0 + ], + "loops": [ + [ + 66, + 67, + 65 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.27632753120439, + 0.96106352313387, + 0.0 + ], + "loops": [ + [ + 68, + 67, + 66 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.09136364589766, + 0.99581759585191, + 0.0 + ], + "loops": [ + [ + 68, + 69, + 67 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.09136364589765, + 0.99581759585192, + 0.0 + ], + "loops": [ + [ + 70, + 69, + 68 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 1.0, + 0.0 + ], + "loops": [ + [ + 70, + 73, + 69 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 1.0, + 0.0 + ], + "loops": [ + [ + 71, + 73, + 70 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 1.0, + 0.0 + ], + "loops": [ + [ + 71, + 72, + 73 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 1.0, + 0.0, + 0.0 + ], + "loops": [ + [ + 74, + 81, + 80 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 1.0, + 0.0, + 0.0 + ], + "loops": [ + [ + 81, + 74, + 75 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + -1.0, + 0.0 + ], + "loops": [ + [ + 76, + 75, + 74 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + -1.0, + 0.0 + ], + "loops": [ + [ + 75, + 76, + 77 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -1.0, + 0.0, + 0.0 + ], + "loops": [ + [ + 78, + 77, + 76 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -1.0, + 0.0, + 0.0 + ], + "loops": [ + [ + 77, + 78, + 79 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 1.0, + 0.0 + ], + "loops": [ + [ + 80, + 79, + 78 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.0, + 1.0, + 0.0 + ], + "loops": [ + [ + 79, + 80, + 81 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.63051190533746, + 0.7761795779507, + 0.0 + ], + "loops": [ + [ + 144, + 143, + 127 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.99581759585192, + 0.09136364589757, + 0.0 + ], + "loops": [ + [ + 109, + 83, + 82 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.99581759585192, + 0.09136364589757, + 0.0 + ], + "loops": [ + [ + 109, + 108, + 83 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.99581759585192, + -0.09136364589755, + 0.0 + ], + "loops": [ + [ + 83, + 84, + 82 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.99581759585192, + -0.09136364589755, + 0.0 + ], + "loops": [ + [ + 85, + 84, + 83 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.96106352313393, + -0.27632753120419, + 0.0 + ], + "loops": [ + [ + 85, + 86, + 84 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.96106352313393, + -0.27632753120418, + 0.0 + ], + "loops": [ + [ + 87, + 86, + 85 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.88791993420902, + -0.45999803307651, + 0.0 + ], + "loops": [ + [ + 87, + 88, + 86 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.88791993420901, + -0.45999803307651, + 0.0 + ], + "loops": [ + [ + 89, + 88, + 87 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.77617957795079, + -0.63051190533734, + 0.0 + ], + "loops": [ + [ + 89, + 90, + 88 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.7761795779508, + -0.63051190533734, + 0.0 + ], + "loops": [ + [ + 91, + 90, + 89 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.63051190533764, + -0.77617957795055, + 0.0 + ], + "loops": [ + [ + 91, + 92, + 90 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.63051190533764, + -0.77617957795056, + 0.0 + ], + "loops": [ + [ + 93, + 92, + 91 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.45999803307684, + -0.88791993420884, + 0.0 + ], + "loops": [ + [ + 93, + 94, + 92 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.45999803307685, + -0.88791993420884, + 0.0 + ], + "loops": [ + [ + 95, + 94, + 93 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.27632753120454, + -0.96106352313383, + 0.0 + ], + "loops": [ + [ + 95, + 96, + 94 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.27632753120454, + -0.96106352313383, + 0.0 + ], + "loops": [ + [ + 97, + 96, + 95 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.09136364589778, + -0.9958175958519, + 0.0 + ], + "loops": [ + [ + 97, + 98, + 96 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.09136364589778, + -0.9958175958519, + 0.0 + ], + "loops": [ + [ + 99, + 98, + 97 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.09136364589755, + -0.99581759585192, + 0.0 + ], + "loops": [ + [ + 99, + 100, + 98 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.09136364589755, + -0.99581759585192, + 0.0 + ], + "loops": [ + [ + 101, + 100, + 99 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.27632753120428, + -0.9610635231339, + 0.0 + ], + "loops": [ + [ + 101, + 102, + 100 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.27632753120429, + -0.9610635231339, + 0.0 + ], + "loops": [ + [ + 103, + 102, + 101 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.45999803307661, + -0.88791993420896, + 0.0 + ], + "loops": [ + [ + 103, + 105, + 102 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.45999803307661, + -0.88791993420896, + 0.0 + ], + "loops": [ + [ + 104, + 105, + 103 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.63051190533743, + -0.77617957795072, + 0.0 + ], + "loops": [ + [ + 107, + 105, + 104 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.63051190533743, + -0.77617957795072, + 0.0 + ], + "loops": [ + [ + 107, + 106, + 105 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.77617957795062, + -0.63051190533755, + 0.0 + ], + "loops": [ + [ + 107, + 129, + 106 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.77617957795063, + -0.63051190533755, + 0.0 + ], + "loops": [ + [ + 130, + 129, + 107 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.96106352313392, + 0.27632753120422, + 0.0 + ], + "loops": [ + [ + 109, + 110, + 108 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.96106352313392, + 0.27632753120422, + 0.0 + ], + "loops": [ + [ + 111, + 110, + 109 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.887919934209, + 0.45999803307654, + 0.0 + ], + "loops": [ + [ + 111, + 112, + 110 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.887919934209, + 0.45999803307654, + 0.0 + ], + "loops": [ + [ + 113, + 112, + 111 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.77617957795077, + 0.63051190533737, + 0.0 + ], + "loops": [ + [ + 113, + 114, + 112 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.77617957795077, + 0.63051190533737, + 0.0 + ], + "loops": [ + [ + 115, + 114, + 113 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.63051190533761, + 0.77617957795058, + 0.0 + ], + "loops": [ + [ + 115, + 116, + 114 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.63051190533761, + 0.77617957795058, + 0.0 + ], + "loops": [ + [ + 117, + 116, + 115 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.45999803307681, + 0.88791993420886, + 0.0 + ], + "loops": [ + [ + 117, + 119, + 116 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.45999803307681, + 0.88791993420886, + 0.0 + ], + "loops": [ + [ + 118, + 119, + 117 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.2763275312045, + 0.96106352313384, + 0.0 + ], + "loops": [ + [ + 121, + 119, + 118 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.2763275312045, + 0.96106352313384, + 0.0 + ], + "loops": [ + [ + 121, + 120, + 119 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.09136364589775, + 0.99581759585191, + 0.0 + ], + "loops": [ + [ + 121, + 122, + 120 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + -0.09136364589775, + 0.99581759585191, + 0.0 + ], + "loops": [ + [ + 123, + 122, + 121 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.09136364589758, + 0.99581759585192, + 0.0 + ], + "loops": [ + [ + 123, + 125, + 122 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.09136364589758, + 0.99581759585192, + 0.0 + ], + "loops": [ + [ + 124, + 125, + 123 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.27632753120432, + 0.96106352313389, + 0.0 + ], + "loops": [ + [ + 126, + 125, + 124 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.27632753120432, + 0.96106352313389, + 0.0 + ], + "loops": [ + [ + 126, + 128, + 125 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.45999803307664, + 0.88791993420895, + 0.0 + ], + "loops": [ + [ + 144, + 128, + 126 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.45999803307664, + 0.88791993420895, + 0.0 + ], + "loops": [ + [ + 144, + 127, + 128 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.88791993420889, + -0.45999803307674, + 0.0 + ], + "loops": [ + [ + 130, + 131, + 129 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.88791993420889, + -0.45999803307674, + 0.0 + ], + "loops": [ + [ + 132, + 131, + 130 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.96106352313386, + -0.27632753120443, + 0.0 + ], + "loops": [ + [ + 132, + 133, + 131 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.96106352313386, + -0.27632753120443, + 0.0 + ], + "loops": [ + [ + 134, + 133, + 132 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.99581759585191, + -0.09136364589768, + 0.0 + ], + "loops": [ + [ + 134, + 135, + 133 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.99581759585191, + -0.09136364589768, + 0.0 + ], + "loops": [ + [ + 136, + 135, + 134 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.99581759585192, + 0.09136364589765, + 0.0 + ], + "loops": [ + [ + 136, + 137, + 135 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.99581759585192, + 0.09136364589765, + 0.0 + ], + "loops": [ + [ + 138, + 137, + 136 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.96106352313387, + 0.27632753120439, + 0.0 + ], + "loops": [ + [ + 138, + 139, + 137 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.96106352313387, + 0.27632753120439, + 0.0 + ], + "loops": [ + [ + 140, + 139, + 138 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.88791993420891, + 0.45999803307671, + 0.0 + ], + "loops": [ + [ + 140, + 141, + 139 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.88791993420891, + 0.45999803307671, + 0.0 + ], + "loops": [ + [ + 142, + 141, + 140 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.77617957795065, + 0.63051190533753, + 0.0 + ], + "loops": [ + [ + 142, + 145, + 141 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.77617957795065, + 0.63051190533752, + 0.0 + ], + "loops": [ + [ + 143, + 145, + 142 + ] + ] + }, + { + "tol": 1.7320511688e-05, + "normal": [ + 0.63051190533746, + 0.7761795779507, + 0.0 + ], + "loops": [ + [ + 143, + 144, + 145 + ] + ] + } + ], + "expansion": 0.001 + }, + { + "op": "fire", + "p": [ + -3.6602540378443873, + -3.6602540378443873, + -3.6602540378443873 + ], + "v": [ + 0.5773502691896258, + 0.5773502691896258, + 0.5773502691896258 + ], + "max_t": 40.0, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + -3.6602540378443873, + -3.6602540378443873, + -3.6602540378443873 + ], + "v": [ + 0.5773502691896258, + 0.5773502691896258, + 0.5773502691896258 + ], + "max_t": 40.0, + "hit_any": true, + "skip": null + }, + { + "op": "fire", + "p": [ + 13.660254037844387, + 13.660254037844387, + 13.660254037844387 + ], + "v": [ + -0.5773502691896258, + -0.5773502691896258, + -0.5773502691896258 + ], + "max_t": 40.0, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + -25.0, + 5.0, + 5.0 + ], + "v": [ + 1.0, + 0.0, + 0.0 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 35.0, + 5.0, + 5.0 + ], + "v": [ + -1.0, + 0.0, + 0.0 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 5.0, + -25.0, + 5.0 + ], + "v": [ + 0.0, + 1.0, + 0.0 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 5.0, + 35.0, + 5.0 + ], + "v": [ + 0.0, + -1.0, + 0.0 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 5.0, + 5.0, + -25.0 + ], + "v": [ + 0.0, + 0.0, + 1.0 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 5.0, + 5.0, + 35.0 + ], + "v": [ + 0.0, + 0.0, + -1.0 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + } + ] + } + }, + { + "name": "poly_stacked_squares", + "upstream_ref": "sm_FindPolyHit: nearest, behind, miss, in-plane, short ray, on-face start, vertex and edge hits", + "inputs": { + "surfaces": [ + { + "kind": "plane", + "o": [ + 0, + 0, + 0 + ], + "x": [ + 1, + 0, + 0 + ], + "y": [ + 0, + 1, + 0 + ], + "scale": [ + 1, + 1 + ], + "domain": [ + 0, + 0, + 4, + 4 + ] + } + ], + "grid": { + "bbox": [ + 0, + 0, + 0, + 10, + 10, + 10 + ], + "size": [ + 4, + 4, + 4 + ], + "ray_tol": 1e-05, + "ray_acc": 1e-08 + }, + "ops": [ + { + "op": "add_poly_brep", + "tol": 1e-05, + "vertices": [ + [ + 2, + 2, + 2.0, + 1e-05 + ], + [ + 8, + 2, + 2.0, + 1e-05 + ], + [ + 8, + 8, + 2.0, + 1e-05 + ], + [ + 2, + 8, + 2.0, + 1e-05 + ], + [ + 2, + 2, + 6.0, + 1e-05 + ], + [ + 8, + 2, + 6.0, + 1e-05 + ], + [ + 8, + 8, + 6.0, + 1e-05 + ], + [ + 2, + 8, + 6.0, + 1e-05 + ] + ], + "faces": [ + { + "tol": 1e-05, + "loops": [ + [ + 0, + 1, + 2, + 3 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 4, + 5, + 6, + 7 + ] + ] + } + ], + "expansion": 0.001 + }, + { + "op": "fire", + "p": [ + 5, + 5, + 9.5 + ], + "v": [ + 0, + 0, + -1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 5, + 5, + 9.5 + ], + "v": [ + 0, + 0, + -1 + ], + "max_t": 1e+20, + "hit_any": true, + "skip": null + }, + { + "op": "fire", + "p": [ + 5, + 5, + 4.0 + ], + "v": [ + 0, + 0, + -1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 5, + 5, + -0.5 + ], + "v": [ + 0, + 0, + 1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 9.0, + 5, + 9.5 + ], + "v": [ + 0, + 0, + -1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 0.5, + 5, + 4.0 + ], + "v": [ + 1, + 0, + 0 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 5, + 5, + 9.5 + ], + "v": [ + 0, + 0, + -1 + ], + "max_t": 3.0, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 5, + 5, + 6.0 + ], + "v": [ + 0, + 0, + -1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 2.0, + 2.0, + 9.5 + ], + "v": [ + 0, + 0, + -1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 5, + 2.0, + 9.5 + ], + "v": [ + 0, + 0, + -1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 0.5, + 0.5, + 9.5 + ], + "v": [ + 0.5773502691896258, + 0.5773502691896258, + -0.5773502691896258 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + } + ] + } + }, + { + "name": "poly_face_with_hole", + "upstream_ref": "PointInPolygon with an inner loop", + "inputs": { + "surfaces": [ + { + "kind": "plane", + "o": [ + 0, + 0, + 0 + ], + "x": [ + 1, + 0, + 0 + ], + "y": [ + 0, + 1, + 0 + ], + "scale": [ + 1, + 1 + ], + "domain": [ + 0, + 0, + 4, + 4 + ] + } + ], + "grid": { + "bbox": [ + 0, + 0, + 0, + 10, + 10, + 10 + ], + "size": [ + 4, + 4, + 4 + ], + "ray_tol": 1e-05, + "ray_acc": 1e-08 + }, + "ops": [ + { + "op": "add_poly_brep", + "tol": 1e-05, + "vertices": [ + [ + 1, + 1, + 5.0, + 1e-05 + ], + [ + 9, + 1, + 5.0, + 1e-05 + ], + [ + 9, + 9, + 5.0, + 1e-05 + ], + [ + 1, + 9, + 5.0, + 1e-05 + ], + [ + 4, + 4, + 5.0, + 1e-05 + ], + [ + 4, + 6, + 5.0, + 1e-05 + ], + [ + 6, + 6, + 5.0, + 1e-05 + ], + [ + 6, + 4, + 5.0, + 1e-05 + ], + [ + 1, + 1, + 1.0, + 1e-05 + ], + [ + 9, + 1, + 1.0, + 1e-05 + ], + [ + 9, + 9, + 1.0, + 1e-05 + ], + [ + 1, + 9, + 1.0, + 1e-05 + ] + ], + "faces": [ + { + "tol": 1e-05, + "loops": [ + [ + 0, + 1, + 2, + 3 + ], + [ + 4, + 5, + 6, + 7 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 8, + 9, + 10, + 11 + ] + ] + } + ], + "expansion": 0.001 + }, + { + "op": "fire", + "p": [ + 5, + 5, + 9.5 + ], + "v": [ + 0, + 0, + -1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 2, + 2, + 9.5 + ], + "v": [ + 0, + 0, + -1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 4, + 5, + 9.5 + ], + "v": [ + 0, + 0, + -1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 5, + 5, + 9.5 + ], + "v": [ + 0, + 0, + -1 + ], + "max_t": 1e+20, + "hit_any": true, + "skip": null + } + ] + } + }, + { + "name": "poly_nonconvex_notch", + "upstream_ref": "PointInPolygon on a non-convex face", + "inputs": { + "surfaces": [ + { + "kind": "plane", + "o": [ + 0, + 0, + 0 + ], + "x": [ + 1, + 0, + 0 + ], + "y": [ + 0, + 1, + 0 + ], + "scale": [ + 1, + 1 + ], + "domain": [ + 0, + 0, + 4, + 4 + ] + } + ], + "grid": { + "bbox": [ + 0, + 0, + 0, + 10, + 10, + 10 + ], + "size": [ + 4, + 4, + 4 + ], + "ray_tol": 1e-05, + "ray_acc": 1e-08 + }, + "ops": [ + { + "op": "add_poly_brep", + "tol": 1e-05, + "vertices": [ + [ + 2, + 2, + 4.0, + 1e-05 + ], + [ + 8, + 2, + 4.0, + 1e-05 + ], + [ + 8, + 5, + 4.0, + 1e-05 + ], + [ + 5, + 5, + 4.0, + 1e-05 + ], + [ + 5, + 8, + 4.0, + 1e-05 + ], + [ + 2, + 8, + 4.0, + 1e-05 + ] + ], + "faces": [ + { + "tol": 1e-05, + "loops": [ + [ + 0, + 1, + 2, + 3, + 4, + 5 + ] + ] + } + ], + "expansion": 0.001 + }, + { + "op": "fire", + "p": [ + 6.5, + 6.5, + 9.5 + ], + "v": [ + 0, + 0, + -1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 3.5, + 6.5, + 9.5 + ], + "v": [ + 0, + 0, + -1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 6.5, + 3.5, + 9.5 + ], + "v": [ + 0, + 0, + -1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 6.5, + 6.5, + 9.5 + ], + "v": [ + -0.5773502691896258, + -0.5773502691896258, + -0.5773502691896258 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + } + ] + } + }, + { + "name": "poly_coincident_breps", + "upstream_ref": "two poly B-reps with the same face: equal ray parameters, the first tested keeps the hit (strict <)", + "inputs": { + "surfaces": [ + { + "kind": "plane", + "o": [ + 0, + 0, + 0 + ], + "x": [ + 1, + 0, + 0 + ], + "y": [ + 0, + 1, + 0 + ], + "scale": [ + 1, + 1 + ], + "domain": [ + 0, + 0, + 4, + 4 + ] + } + ], + "grid": { + "bbox": [ + 0, + 0, + 0, + 10, + 10, + 10 + ], + "size": [ + 4, + 4, + 4 + ], + "ray_tol": 1e-05, + "ray_acc": 1e-08 + }, + "ops": [ + { + "op": "add_poly_brep", + "tol": 1e-05, + "vertices": [ + [ + 2, + 2, + 5.0, + 1e-05 + ], + [ + 8, + 2, + 5.0, + 1e-05 + ], + [ + 8, + 8, + 5.0, + 1e-05 + ], + [ + 2, + 8, + 5.0, + 1e-05 + ] + ], + "faces": [ + { + "tol": 1e-05, + "loops": [ + [ + 0, + 1, + 2, + 3 + ] + ] + } + ], + "expansion": 0.001 + }, + { + "op": "add_poly_brep", + "tol": 1e-05, + "vertices": [ + [ + 2, + 2, + 5.0, + 1e-05 + ], + [ + 8, + 2, + 5.0, + 1e-05 + ], + [ + 8, + 8, + 5.0, + 1e-05 + ], + [ + 2, + 8, + 5.0, + 1e-05 + ] + ], + "faces": [ + { + "tol": 1e-05, + "loops": [ + [ + 0, + 1, + 2, + 3 + ] + ] + } + ], + "expansion": 0.001 + }, + { + "op": "fire", + "p": [ + 5, + 5, + 9.5 + ], + "v": [ + 0, + 0, + -1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 5, + 5, + 0.5 + ], + "v": [ + 0, + 0, + 1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 3, + 7, + 9.5 + ], + "v": [ + 0.19518001458970663, + -0.09759000729485331, + -0.9759000729485331 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + } + ] + } + }, + { + "name": "poly_big_face_over_small_faces", + "upstream_ref": "a face spanning the tree on an inner node: sm_FindPolyHit walks up the parents", + "inputs": { + "surfaces": [ + { + "kind": "plane", + "o": [ + 0, + 0, + 0 + ], + "x": [ + 1, + 0, + 0 + ], + "y": [ + 0, + 1, + 0 + ], + "scale": [ + 1, + 1 + ], + "domain": [ + 0, + 0, + 4, + 4 + ] + } + ], + "grid": { + "bbox": [ + 0, + 0, + 0, + 10, + 10, + 10 + ], + "size": [ + 6, + 6, + 6 + ], + "ray_tol": 1e-05, + "ray_acc": 1e-08 + }, + "ops": [ + { + "op": "add_poly_brep", + "tol": 1e-05, + "vertices": [ + [ + 0.5, + 0.5, + 9.0, + 1e-05 + ], + [ + 9.5, + 0.5, + 9.0, + 1e-05 + ], + [ + 9.5, + 9.5, + 9.0, + 1e-05 + ], + [ + 0.5, + 9.5, + 9.0, + 1e-05 + ], + [ + 1, + 1, + 1.0, + 1e-05 + ], + [ + 1.9, + 1, + 1.0, + 1e-05 + ], + [ + 1.9, + 1.9, + 1.0, + 1e-05 + ], + [ + 1, + 1.9, + 1.0, + 1e-05 + ], + [ + 1, + 2, + 1.25, + 1e-05 + ], + [ + 1.9, + 2, + 1.25, + 1e-05 + ], + [ + 1.9, + 2.9, + 1.25, + 1e-05 + ], + [ + 1, + 2.9, + 1.25, + 1e-05 + ], + [ + 1, + 3, + 1.5, + 1e-05 + ], + [ + 1.9, + 3, + 1.5, + 1e-05 + ], + [ + 1.9, + 3.9, + 1.5, + 1e-05 + ], + [ + 1, + 3.9, + 1.5, + 1e-05 + ], + [ + 1, + 4, + 1.75, + 1e-05 + ], + [ + 1.9, + 4, + 1.75, + 1e-05 + ], + [ + 1.9, + 4.9, + 1.75, + 1e-05 + ], + [ + 1, + 4.9, + 1.75, + 1e-05 + ], + [ + 1, + 5, + 2.0, + 1e-05 + ], + [ + 1.9, + 5, + 2.0, + 1e-05 + ], + [ + 1.9, + 5.9, + 2.0, + 1e-05 + ], + [ + 1, + 5.9, + 2.0, + 1e-05 + ], + [ + 1, + 6, + 2.25, + 1e-05 + ], + [ + 1.9, + 6, + 2.25, + 1e-05 + ], + [ + 1.9, + 6.9, + 2.25, + 1e-05 + ], + [ + 1, + 6.9, + 2.25, + 1e-05 + ], + [ + 1, + 7, + 2.5, + 1e-05 + ], + [ + 1.9, + 7, + 2.5, + 1e-05 + ], + [ + 1.9, + 7.9, + 2.5, + 1e-05 + ], + [ + 1, + 7.9, + 2.5, + 1e-05 + ], + [ + 1, + 8, + 1.0, + 1e-05 + ], + [ + 1.9, + 8, + 1.0, + 1e-05 + ], + [ + 1.9, + 8.9, + 1.0, + 1e-05 + ], + [ + 1, + 8.9, + 1.0, + 1e-05 + ], + [ + 2, + 1, + 1.25, + 1e-05 + ], + [ + 2.9, + 1, + 1.25, + 1e-05 + ], + [ + 2.9, + 1.9, + 1.25, + 1e-05 + ], + [ + 2, + 1.9, + 1.25, + 1e-05 + ], + [ + 2, + 2, + 1.5, + 1e-05 + ], + [ + 2.9, + 2, + 1.5, + 1e-05 + ], + [ + 2.9, + 2.9, + 1.5, + 1e-05 + ], + [ + 2, + 2.9, + 1.5, + 1e-05 + ], + [ + 2, + 3, + 1.75, + 1e-05 + ], + [ + 2.9, + 3, + 1.75, + 1e-05 + ], + [ + 2.9, + 3.9, + 1.75, + 1e-05 + ], + [ + 2, + 3.9, + 1.75, + 1e-05 + ], + [ + 2, + 4, + 2.0, + 1e-05 + ], + [ + 2.9, + 4, + 2.0, + 1e-05 + ], + [ + 2.9, + 4.9, + 2.0, + 1e-05 + ], + [ + 2, + 4.9, + 2.0, + 1e-05 + ], + [ + 2, + 5, + 2.25, + 1e-05 + ], + [ + 2.9, + 5, + 2.25, + 1e-05 + ], + [ + 2.9, + 5.9, + 2.25, + 1e-05 + ], + [ + 2, + 5.9, + 2.25, + 1e-05 + ], + [ + 2, + 6, + 2.5, + 1e-05 + ], + [ + 2.9, + 6, + 2.5, + 1e-05 + ], + [ + 2.9, + 6.9, + 2.5, + 1e-05 + ], + [ + 2, + 6.9, + 2.5, + 1e-05 + ], + [ + 2, + 7, + 1.0, + 1e-05 + ], + [ + 2.9, + 7, + 1.0, + 1e-05 + ], + [ + 2.9, + 7.9, + 1.0, + 1e-05 + ], + [ + 2, + 7.9, + 1.0, + 1e-05 + ], + [ + 2, + 8, + 1.25, + 1e-05 + ], + [ + 2.9, + 8, + 1.25, + 1e-05 + ], + [ + 2.9, + 8.9, + 1.25, + 1e-05 + ], + [ + 2, + 8.9, + 1.25, + 1e-05 + ], + [ + 3, + 1, + 1.5, + 1e-05 + ], + [ + 3.9, + 1, + 1.5, + 1e-05 + ], + [ + 3.9, + 1.9, + 1.5, + 1e-05 + ], + [ + 3, + 1.9, + 1.5, + 1e-05 + ], + [ + 3, + 2, + 1.75, + 1e-05 + ], + [ + 3.9, + 2, + 1.75, + 1e-05 + ], + [ + 3.9, + 2.9, + 1.75, + 1e-05 + ], + [ + 3, + 2.9, + 1.75, + 1e-05 + ], + [ + 3, + 3, + 2.0, + 1e-05 + ], + [ + 3.9, + 3, + 2.0, + 1e-05 + ], + [ + 3.9, + 3.9, + 2.0, + 1e-05 + ], + [ + 3, + 3.9, + 2.0, + 1e-05 + ], + [ + 3, + 4, + 2.25, + 1e-05 + ], + [ + 3.9, + 4, + 2.25, + 1e-05 + ], + [ + 3.9, + 4.9, + 2.25, + 1e-05 + ], + [ + 3, + 4.9, + 2.25, + 1e-05 + ], + [ + 3, + 5, + 2.5, + 1e-05 + ], + [ + 3.9, + 5, + 2.5, + 1e-05 + ], + [ + 3.9, + 5.9, + 2.5, + 1e-05 + ], + [ + 3, + 5.9, + 2.5, + 1e-05 + ], + [ + 3, + 6, + 1.0, + 1e-05 + ], + [ + 3.9, + 6, + 1.0, + 1e-05 + ], + [ + 3.9, + 6.9, + 1.0, + 1e-05 + ], + [ + 3, + 6.9, + 1.0, + 1e-05 + ], + [ + 3, + 7, + 1.25, + 1e-05 + ], + [ + 3.9, + 7, + 1.25, + 1e-05 + ], + [ + 3.9, + 7.9, + 1.25, + 1e-05 + ], + [ + 3, + 7.9, + 1.25, + 1e-05 + ], + [ + 3, + 8, + 1.5, + 1e-05 + ], + [ + 3.9, + 8, + 1.5, + 1e-05 + ], + [ + 3.9, + 8.9, + 1.5, + 1e-05 + ], + [ + 3, + 8.9, + 1.5, + 1e-05 + ], + [ + 4, + 1, + 1.75, + 1e-05 + ], + [ + 4.9, + 1, + 1.75, + 1e-05 + ], + [ + 4.9, + 1.9, + 1.75, + 1e-05 + ], + [ + 4, + 1.9, + 1.75, + 1e-05 + ], + [ + 4, + 2, + 2.0, + 1e-05 + ], + [ + 4.9, + 2, + 2.0, + 1e-05 + ], + [ + 4.9, + 2.9, + 2.0, + 1e-05 + ], + [ + 4, + 2.9, + 2.0, + 1e-05 + ], + [ + 4, + 3, + 2.25, + 1e-05 + ], + [ + 4.9, + 3, + 2.25, + 1e-05 + ], + [ + 4.9, + 3.9, + 2.25, + 1e-05 + ], + [ + 4, + 3.9, + 2.25, + 1e-05 + ], + [ + 4, + 4, + 2.5, + 1e-05 + ], + [ + 4.9, + 4, + 2.5, + 1e-05 + ], + [ + 4.9, + 4.9, + 2.5, + 1e-05 + ], + [ + 4, + 4.9, + 2.5, + 1e-05 + ], + [ + 4, + 5, + 1.0, + 1e-05 + ], + [ + 4.9, + 5, + 1.0, + 1e-05 + ], + [ + 4.9, + 5.9, + 1.0, + 1e-05 + ], + [ + 4, + 5.9, + 1.0, + 1e-05 + ], + [ + 4, + 6, + 1.25, + 1e-05 + ], + [ + 4.9, + 6, + 1.25, + 1e-05 + ], + [ + 4.9, + 6.9, + 1.25, + 1e-05 + ], + [ + 4, + 6.9, + 1.25, + 1e-05 + ], + [ + 4, + 7, + 1.5, + 1e-05 + ], + [ + 4.9, + 7, + 1.5, + 1e-05 + ], + [ + 4.9, + 7.9, + 1.5, + 1e-05 + ], + [ + 4, + 7.9, + 1.5, + 1e-05 + ], + [ + 4, + 8, + 1.75, + 1e-05 + ], + [ + 4.9, + 8, + 1.75, + 1e-05 + ], + [ + 4.9, + 8.9, + 1.75, + 1e-05 + ], + [ + 4, + 8.9, + 1.75, + 1e-05 + ], + [ + 5, + 1, + 2.0, + 1e-05 + ], + [ + 5.9, + 1, + 2.0, + 1e-05 + ], + [ + 5.9, + 1.9, + 2.0, + 1e-05 + ], + [ + 5, + 1.9, + 2.0, + 1e-05 + ], + [ + 5, + 2, + 2.25, + 1e-05 + ], + [ + 5.9, + 2, + 2.25, + 1e-05 + ], + [ + 5.9, + 2.9, + 2.25, + 1e-05 + ], + [ + 5, + 2.9, + 2.25, + 1e-05 + ], + [ + 5, + 3, + 2.5, + 1e-05 + ], + [ + 5.9, + 3, + 2.5, + 1e-05 + ], + [ + 5.9, + 3.9, + 2.5, + 1e-05 + ], + [ + 5, + 3.9, + 2.5, + 1e-05 + ], + [ + 5, + 4, + 1.0, + 1e-05 + ], + [ + 5.9, + 4, + 1.0, + 1e-05 + ], + [ + 5.9, + 4.9, + 1.0, + 1e-05 + ], + [ + 5, + 4.9, + 1.0, + 1e-05 + ], + [ + 5, + 5, + 1.25, + 1e-05 + ], + [ + 5.9, + 5, + 1.25, + 1e-05 + ], + [ + 5.9, + 5.9, + 1.25, + 1e-05 + ], + [ + 5, + 5.9, + 1.25, + 1e-05 + ], + [ + 5, + 6, + 1.5, + 1e-05 + ], + [ + 5.9, + 6, + 1.5, + 1e-05 + ], + [ + 5.9, + 6.9, + 1.5, + 1e-05 + ], + [ + 5, + 6.9, + 1.5, + 1e-05 + ], + [ + 5, + 7, + 1.75, + 1e-05 + ], + [ + 5.9, + 7, + 1.75, + 1e-05 + ], + [ + 5.9, + 7.9, + 1.75, + 1e-05 + ], + [ + 5, + 7.9, + 1.75, + 1e-05 + ], + [ + 5, + 8, + 2.0, + 1e-05 + ], + [ + 5.9, + 8, + 2.0, + 1e-05 + ], + [ + 5.9, + 8.9, + 2.0, + 1e-05 + ], + [ + 5, + 8.9, + 2.0, + 1e-05 + ], + [ + 6, + 1, + 2.25, + 1e-05 + ], + [ + 6.9, + 1, + 2.25, + 1e-05 + ], + [ + 6.9, + 1.9, + 2.25, + 1e-05 + ], + [ + 6, + 1.9, + 2.25, + 1e-05 + ], + [ + 6, + 2, + 2.5, + 1e-05 + ], + [ + 6.9, + 2, + 2.5, + 1e-05 + ], + [ + 6.9, + 2.9, + 2.5, + 1e-05 + ], + [ + 6, + 2.9, + 2.5, + 1e-05 + ], + [ + 6, + 3, + 1.0, + 1e-05 + ], + [ + 6.9, + 3, + 1.0, + 1e-05 + ], + [ + 6.9, + 3.9, + 1.0, + 1e-05 + ], + [ + 6, + 3.9, + 1.0, + 1e-05 + ], + [ + 6, + 4, + 1.25, + 1e-05 + ], + [ + 6.9, + 4, + 1.25, + 1e-05 + ], + [ + 6.9, + 4.9, + 1.25, + 1e-05 + ], + [ + 6, + 4.9, + 1.25, + 1e-05 + ], + [ + 6, + 5, + 1.5, + 1e-05 + ], + [ + 6.9, + 5, + 1.5, + 1e-05 + ], + [ + 6.9, + 5.9, + 1.5, + 1e-05 + ], + [ + 6, + 5.9, + 1.5, + 1e-05 + ], + [ + 6, + 6, + 1.75, + 1e-05 + ], + [ + 6.9, + 6, + 1.75, + 1e-05 + ], + [ + 6.9, + 6.9, + 1.75, + 1e-05 + ], + [ + 6, + 6.9, + 1.75, + 1e-05 + ], + [ + 6, + 7, + 2.0, + 1e-05 + ], + [ + 6.9, + 7, + 2.0, + 1e-05 + ], + [ + 6.9, + 7.9, + 2.0, + 1e-05 + ], + [ + 6, + 7.9, + 2.0, + 1e-05 + ], + [ + 6, + 8, + 2.25, + 1e-05 + ], + [ + 6.9, + 8, + 2.25, + 1e-05 + ], + [ + 6.9, + 8.9, + 2.25, + 1e-05 + ], + [ + 6, + 8.9, + 2.25, + 1e-05 + ], + [ + 7, + 1, + 2.5, + 1e-05 + ], + [ + 7.9, + 1, + 2.5, + 1e-05 + ], + [ + 7.9, + 1.9, + 2.5, + 1e-05 + ], + [ + 7, + 1.9, + 2.5, + 1e-05 + ], + [ + 7, + 2, + 1.0, + 1e-05 + ], + [ + 7.9, + 2, + 1.0, + 1e-05 + ], + [ + 7.9, + 2.9, + 1.0, + 1e-05 + ], + [ + 7, + 2.9, + 1.0, + 1e-05 + ], + [ + 7, + 3, + 1.25, + 1e-05 + ], + [ + 7.9, + 3, + 1.25, + 1e-05 + ], + [ + 7.9, + 3.9, + 1.25, + 1e-05 + ], + [ + 7, + 3.9, + 1.25, + 1e-05 + ], + [ + 7, + 4, + 1.5, + 1e-05 + ], + [ + 7.9, + 4, + 1.5, + 1e-05 + ], + [ + 7.9, + 4.9, + 1.5, + 1e-05 + ], + [ + 7, + 4.9, + 1.5, + 1e-05 + ], + [ + 7, + 5, + 1.75, + 1e-05 + ], + [ + 7.9, + 5, + 1.75, + 1e-05 + ], + [ + 7.9, + 5.9, + 1.75, + 1e-05 + ], + [ + 7, + 5.9, + 1.75, + 1e-05 + ], + [ + 7, + 6, + 2.0, + 1e-05 + ], + [ + 7.9, + 6, + 2.0, + 1e-05 + ], + [ + 7.9, + 6.9, + 2.0, + 1e-05 + ], + [ + 7, + 6.9, + 2.0, + 1e-05 + ], + [ + 7, + 7, + 2.25, + 1e-05 + ], + [ + 7.9, + 7, + 2.25, + 1e-05 + ], + [ + 7.9, + 7.9, + 2.25, + 1e-05 + ], + [ + 7, + 7.9, + 2.25, + 1e-05 + ], + [ + 7, + 8, + 2.5, + 1e-05 + ], + [ + 7.9, + 8, + 2.5, + 1e-05 + ], + [ + 7.9, + 8.9, + 2.5, + 1e-05 + ], + [ + 7, + 8.9, + 2.5, + 1e-05 + ], + [ + 8, + 1, + 1.0, + 1e-05 + ], + [ + 8.9, + 1, + 1.0, + 1e-05 + ], + [ + 8.9, + 1.9, + 1.0, + 1e-05 + ], + [ + 8, + 1.9, + 1.0, + 1e-05 + ], + [ + 8, + 2, + 1.25, + 1e-05 + ], + [ + 8.9, + 2, + 1.25, + 1e-05 + ], + [ + 8.9, + 2.9, + 1.25, + 1e-05 + ], + [ + 8, + 2.9, + 1.25, + 1e-05 + ], + [ + 8, + 3, + 1.5, + 1e-05 + ], + [ + 8.9, + 3, + 1.5, + 1e-05 + ], + [ + 8.9, + 3.9, + 1.5, + 1e-05 + ], + [ + 8, + 3.9, + 1.5, + 1e-05 + ], + [ + 8, + 4, + 1.75, + 1e-05 + ], + [ + 8.9, + 4, + 1.75, + 1e-05 + ], + [ + 8.9, + 4.9, + 1.75, + 1e-05 + ], + [ + 8, + 4.9, + 1.75, + 1e-05 + ], + [ + 8, + 5, + 2.0, + 1e-05 + ], + [ + 8.9, + 5, + 2.0, + 1e-05 + ], + [ + 8.9, + 5.9, + 2.0, + 1e-05 + ], + [ + 8, + 5.9, + 2.0, + 1e-05 + ], + [ + 8, + 6, + 2.25, + 1e-05 + ], + [ + 8.9, + 6, + 2.25, + 1e-05 + ], + [ + 8.9, + 6.9, + 2.25, + 1e-05 + ], + [ + 8, + 6.9, + 2.25, + 1e-05 + ], + [ + 8, + 7, + 2.5, + 1e-05 + ], + [ + 8.9, + 7, + 2.5, + 1e-05 + ], + [ + 8.9, + 7.9, + 2.5, + 1e-05 + ], + [ + 8, + 7.9, + 2.5, + 1e-05 + ], + [ + 8, + 8, + 1.0, + 1e-05 + ], + [ + 8.9, + 8, + 1.0, + 1e-05 + ], + [ + 8.9, + 8.9, + 1.0, + 1e-05 + ], + [ + 8, + 8.9, + 1.0, + 1e-05 + ] + ], + "faces": [ + { + "tol": 1e-05, + "loops": [ + [ + 0, + 1, + 2, + 3 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 4, + 5, + 6, + 7 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 8, + 9, + 10, + 11 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 12, + 13, + 14, + 15 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 16, + 17, + 18, + 19 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 20, + 21, + 22, + 23 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 24, + 25, + 26, + 27 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 28, + 29, + 30, + 31 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 32, + 33, + 34, + 35 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 36, + 37, + 38, + 39 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 40, + 41, + 42, + 43 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 44, + 45, + 46, + 47 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 48, + 49, + 50, + 51 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 52, + 53, + 54, + 55 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 56, + 57, + 58, + 59 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 60, + 61, + 62, + 63 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 64, + 65, + 66, + 67 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 68, + 69, + 70, + 71 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 72, + 73, + 74, + 75 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 76, + 77, + 78, + 79 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 80, + 81, + 82, + 83 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 84, + 85, + 86, + 87 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 88, + 89, + 90, + 91 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 92, + 93, + 94, + 95 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 96, + 97, + 98, + 99 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 100, + 101, + 102, + 103 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 104, + 105, + 106, + 107 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 108, + 109, + 110, + 111 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 112, + 113, + 114, + 115 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 116, + 117, + 118, + 119 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 120, + 121, + 122, + 123 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 124, + 125, + 126, + 127 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 128, + 129, + 130, + 131 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 132, + 133, + 134, + 135 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 136, + 137, + 138, + 139 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 140, + 141, + 142, + 143 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 144, + 145, + 146, + 147 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 148, + 149, + 150, + 151 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 152, + 153, + 154, + 155 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 156, + 157, + 158, + 159 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 160, + 161, + 162, + 163 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 164, + 165, + 166, + 167 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 168, + 169, + 170, + 171 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 172, + 173, + 174, + 175 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 176, + 177, + 178, + 179 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 180, + 181, + 182, + 183 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 184, + 185, + 186, + 187 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 188, + 189, + 190, + 191 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 192, + 193, + 194, + 195 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 196, + 197, + 198, + 199 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 200, + 201, + 202, + 203 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 204, + 205, + 206, + 207 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 208, + 209, + 210, + 211 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 212, + 213, + 214, + 215 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 216, + 217, + 218, + 219 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 220, + 221, + 222, + 223 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 224, + 225, + 226, + 227 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 228, + 229, + 230, + 231 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 232, + 233, + 234, + 235 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 236, + 237, + 238, + 239 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 240, + 241, + 242, + 243 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 244, + 245, + 246, + 247 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 248, + 249, + 250, + 251 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 252, + 253, + 254, + 255 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 256, + 257, + 258, + 259 + ] + ] + } + ], + "expansion": 0.001 + }, + { + "op": "fire", + "p": [ + 1.45, + 1.45, + 0.2 + ], + "v": [ + 0, + 0, + 1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 1.45, + 1.45, + 9.8 + ], + "v": [ + 0, + 0, + -1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 4.5, + 6.5, + 0.2 + ], + "v": [ + 0, + 0, + 1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 4.5, + 6.5, + 9.8 + ], + "v": [ + 0, + 0, + -1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 8.95, + 8.95, + 0.2 + ], + "v": [ + 0, + 0, + 1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 8.95, + 8.95, + 9.8 + ], + "v": [ + 0, + 0, + -1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 5.0, + 5.0, + 0.2 + ], + "v": [ + 0, + 0, + 1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 5.0, + 5.0, + 9.8 + ], + "v": [ + 0, + 0, + -1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 3.2, + 8.4, + 0.2 + ], + "v": [ + 0, + 0, + 1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 3.2, + 8.4, + 9.8 + ], + "v": [ + 0, + 0, + -1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 7.99, + 1.01, + 0.2 + ], + "v": [ + 0, + 0, + 1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 7.99, + 1.01, + 9.8 + ], + "v": [ + 0, + 0, + -1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 0.2, + 0.3, + 0.2 + ], + "v": [ + 0.6917144638660746, + 0.6917144638660746, + 0.20751433915982237 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 9.8, + 9.7, + 9.5 + ], + "v": [ + -0.6788442333021306, + -0.6109598099719176, + -0.40730653998127836 + ], + "max_t": 1e+20, + "hit_any": true, + "skip": null + } + ] + } + }, + { + "name": "poly_and_surface", + "upstream_ref": "a poly face and a surface leaf in one grid", + "inputs": { + "surfaces": [ + { + "kind": "plane", + "o": [ + 0, + 0, + 0 + ], + "x": [ + 1, + 0, + 0 + ], + "y": [ + 0, + 1, + 0 + ], + "scale": [ + 1, + 1 + ], + "domain": [ + 0, + 0, + 4, + 4 + ] + } + ], + "grid": { + "bbox": [ + -1, + -1, + -2, + 5, + 5, + 2 + ], + "size": [ + 4, + 4, + 4 + ], + "ray_tol": 1e-05, + "ray_acc": 1e-08 + }, + "ops": [ + { + "op": "add_surface", + "surface": 0 + }, + { + "op": "add_poly_brep", + "tol": 1e-05, + "vertices": [ + [ + 0.5, + 0.5, + 1.0, + 1e-05 + ], + [ + 3.5, + 0.5, + 1.0, + 1e-05 + ], + [ + 3.5, + 3.5, + 1.0, + 1e-05 + ], + [ + 0.5, + 3.5, + 1.0, + 1e-05 + ], + [ + 0.5, + 0.5, + -1.0, + 1e-05 + ], + [ + 3.5, + 0.5, + -1.0, + 1e-05 + ], + [ + 3.5, + 3.5, + -1.0, + 1e-05 + ], + [ + 0.5, + 3.5, + -1.0, + 1e-05 + ] + ], + "faces": [ + { + "tol": 1e-05, + "loops": [ + [ + 0, + 1, + 2, + 3 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 4, + 5, + 6, + 7 + ] + ] + } + ], + "expansion": 0.001 + }, + { + "op": "fire", + "p": [ + 2, + 2, + 3 + ], + "v": [ + 0, + 0, + -1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 2, + 2, + 0.5 + ], + "v": [ + 0, + 0, + -1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 0.2, + 0.2, + 3 + ], + "v": [ + 0, + 0, + -1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 2, + 2, + -3 + ], + "v": [ + 0, + 0, + 1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + } + ] + } + }, + { + "name": "poly_face_normals", + "upstream_ref": "computed (clockwise) and given face normals in the solution and in the plane intersection", + "inputs": { + "surfaces": [ + { + "kind": "plane", + "o": [ + 0, + 0, + 0 + ], + "x": [ + 1, + 0, + 0 + ], + "y": [ + 0, + 1, + 0 + ], + "scale": [ + 1, + 1 + ], + "domain": [ + 0, + 0, + 4, + 4 + ] + } + ], + "grid": { + "bbox": [ + 0, + 0, + 0, + 10, + 10, + 10 + ], + "size": [ + 4, + 4, + 4 + ], + "ray_tol": 1e-05, + "ray_acc": 1e-08 + }, + "ops": [ + { + "op": "add_poly_brep", + "tol": 1e-05, + "vertices": [ + [ + 2, + 2, + 3.0, + 1e-05 + ], + [ + 8, + 2, + 3.0, + 1e-05 + ], + [ + 8, + 8, + 3.0, + 1e-05 + ], + [ + 2, + 8, + 3.0, + 1e-05 + ], + [ + 2, + 2, + 7.0, + 1e-05 + ], + [ + 8, + 2, + 7.0, + 1e-05 + ], + [ + 8, + 8, + 7.0, + 1e-05 + ], + [ + 2, + 8, + 7.0, + 1e-05 + ] + ], + "faces": [ + { + "tol": 1e-05, + "loops": [ + [ + 3, + 2, + 1, + 0 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 4, + 5, + 6, + 7 + ] + ], + "normal": [ + 0.0, + 0.6, + 0.8 + ] + } + ], + "expansion": 0.001 + }, + { + "op": "fire", + "p": [ + 5, + 5, + 9.5 + ], + "v": [ + 0, + 0, + -1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 5, + 5, + 5.0 + ], + "v": [ + 0, + 0, + -1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 5, + 5, + 0.5 + ], + "v": [ + 0, + 0, + 1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + } + ] + } + }, + { + "name": "poly_degenerate_face", + "upstream_ref": "a collinear face (degenerate normal) above a triangle", + "inputs": { + "surfaces": [ + { + "kind": "plane", + "o": [ + 0, + 0, + 0 + ], + "x": [ + 1, + 0, + 0 + ], + "y": [ + 0, + 1, + 0 + ], + "scale": [ + 1, + 1 + ], + "domain": [ + 0, + 0, + 4, + 4 + ] + } + ], + "grid": { + "bbox": [ + 0, + 0, + 0, + 10, + 10, + 10 + ], + "size": [ + 4, + 4, + 4 + ], + "ray_tol": 1e-05, + "ray_acc": 1e-08 + }, + "ops": [ + { + "op": "add_poly_brep", + "tol": 1e-05, + "vertices": [ + [ + 2, + 2, + 5, + 1e-05 + ], + [ + 5, + 5, + 5, + 1e-05 + ], + [ + 8, + 8, + 5, + 1e-05 + ], + [ + 2, + 2, + 2, + 1e-05 + ], + [ + 8, + 2, + 2, + 1e-05 + ], + [ + 5, + 8, + 2, + 1e-05 + ] + ], + "faces": [ + { + "tol": 1e-05, + "loops": [ + [ + 0, + 1, + 2 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 3, + 4, + 5 + ] + ] + } + ], + "expansion": 0.001 + }, + { + "op": "fire", + "p": [ + 5, + 5, + 9.5 + ], + "v": [ + 0, + 0, + -1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 5, + 4, + 9.5 + ], + "v": [ + 0, + 0, + -1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 4, + 4, + 9.5 + ], + "v": [ + 0.5773502691896258, + 0.5773502691896258, + -0.5773502691896258 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + } + ] + } + }, + { + "name": "poly_expansion_zero", + "upstream_ref": "AddPolyBrepToGrid expansion tolerance", + "inputs": { + "surfaces": [ + { + "kind": "plane", + "o": [ + 0, + 0, + 0 + ], + "x": [ + 1, + 0, + 0 + ], + "y": [ + 0, + 1, + 0 + ], + "scale": [ + 1, + 1 + ], + "domain": [ + 0, + 0, + 4, + 4 + ] + } + ], + "grid": { + "bbox": [ + 0, + 0, + 0, + 10, + 10, + 10 + ], + "size": [ + 4, + 4, + 4 + ], + "ray_tol": 1e-05, + "ray_acc": 1e-08 + }, + "ops": [ + { + "op": "add_poly_brep", + "tol": 1e-05, + "vertices": [ + [ + 2, + 2, + 5.0, + 1e-05 + ], + [ + 8, + 2, + 5.0, + 1e-05 + ], + [ + 8, + 8, + 5.0, + 1e-05 + ], + [ + 2, + 8, + 5.0, + 1e-05 + ] + ], + "faces": [ + { + "tol": 1e-05, + "loops": [ + [ + 0, + 1, + 2, + 3 + ] + ] + } + ], + "expansion": 0.0 + }, + { + "op": "fire", + "p": [ + 5, + 5, + 9.5 + ], + "v": [ + 0, + 0, + -1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 8.5, + 5, + 9.5 + ], + "v": [ + 0, + 0, + -1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + } + ] + } + }, + { + "name": "poly_expansion_large", + "upstream_ref": "AddPolyBrepToGrid expansion tolerance", + "inputs": { + "surfaces": [ + { + "kind": "plane", + "o": [ + 0, + 0, + 0 + ], + "x": [ + 1, + 0, + 0 + ], + "y": [ + 0, + 1, + 0 + ], + "scale": [ + 1, + 1 + ], + "domain": [ + 0, + 0, + 4, + 4 + ] + } + ], + "grid": { + "bbox": [ + 0, + 0, + 0, + 10, + 10, + 10 + ], + "size": [ + 4, + 4, + 4 + ], + "ray_tol": 1e-05, + "ray_acc": 1e-08 + }, + "ops": [ + { + "op": "add_poly_brep", + "tol": 1e-05, + "vertices": [ + [ + 2, + 2, + 5.0, + 1e-05 + ], + [ + 8, + 2, + 5.0, + 1e-05 + ], + [ + 8, + 8, + 5.0, + 1e-05 + ], + [ + 2, + 8, + 5.0, + 1e-05 + ] + ], + "faces": [ + { + "tol": 1e-05, + "loops": [ + [ + 0, + 1, + 2, + 3 + ] + ] + } + ], + "expansion": 2.5 + }, + { + "op": "fire", + "p": [ + 5, + 5, + 9.5 + ], + "v": [ + 0, + 0, + -1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + }, + { + "op": "fire", + "p": [ + 8.5, + 5, + 9.5 + ], + "v": [ + 0, + 0, + -1 + ], + "max_t": 1e+20, + "hit_any": false, + "skip": null + } + ] + } + }, + { + "name": "poly_render_shadows", + "upstream_ref": "SmRayRenderer over poly faces with shadow rays (hit-any-thing)", + "inputs": { + "surfaces": [ + { + "kind": "plane", + "o": [ + 0, + 0, + 0 + ], + "x": [ + 1, + 0, + 0 + ], + "y": [ + 0, + 1, + 0 + ], + "scale": [ + 1, + 1 + ], + "domain": [ + 0, + 0, + 4, + 4 + ] + } + ], + "grid": { + "bbox": [ + -1, + -1, + -2, + 5, + 5, + 5 + ], + "size": [ + 4, + 4, + 4 + ], + "ray_tol": 1e-05, + "ray_acc": 1e-08 + }, + "ops": [ + { + "op": "add_poly_brep", + "tol": 1e-05, + "vertices": [ + [ + 1, + 1, + 1.0, + 1e-05 + ], + [ + 3, + 1, + 1.0, + 1e-05 + ], + [ + 3, + 3, + 1.0, + 1e-05 + ], + [ + 1, + 3, + 1.0, + 1e-05 + ], + [ + 0, + 0, + -1.0, + 1e-05 + ], + [ + 4, + 0, + -1.0, + 1e-05 + ], + [ + 4, + 4, + -1.0, + 1e-05 + ], + [ + 0, + 4, + -1.0, + 1e-05 + ] + ], + "faces": [ + { + "tol": 1e-05, + "loops": [ + [ + 0, + 1, + 2, + 3 + ] + ] + }, + { + "tol": 1e-05, + "loops": [ + [ + 4, + 5, + 6, + 7 + ] + ] + } + ], + "expansion": 0.001 + }, + { + "op": "renderer", + "origin": [ + 0, + 0, + 0 + ], + "x": [ + 1, + 0, + 0 + ], + "y": [ + 0, + 1, + 0 + ], + "view": [ + 0.5, + 0.5, + 4.0, + 3.5, + 3.5, + 4.0 + ], + "nx": 3, + "ny": 3, + "rate": 2 + }, + { + "op": "light", + "v": [ + 0.2822162605150792, + 0.18814417367671948, + 0.9407208683835974 + ], + "point": false + }, + { + "op": "light", + "v": [ + 2, + 2, + 3.5 + ], + "point": true + }, + { + "op": "do_render", + "shadows": true, + "background": [ + 0.1, + 0.2, + 0.3 + ], + "object": [ + 0.8, + 0.5, + 0.2 + ] + }, + { + "op": "render_pixel", + "x": 1, + "y": 1 + }, + { + "op": "render_ray", + "p": [ + 2, + 2, + 3.5 + ], + "v": [ + 0, + 0, + -1 + ] + }, + { + "op": "render_ray", + "p": [ + 3.5, + 3.5, + 3.5 + ], + "v": [ + 0, + 0, + -1 + ] + } + ] + } + } + ] +} diff --git a/harness/cpp/shim/intersect_ray_tracer.cpp b/harness/cpp/shim/intersect_ray_tracer.cpp index 19e563cb35..f1980c2c36 100644 --- a/harness/cpp/shim/intersect_ray_tracer.cpp +++ b/harness/cpp/shim/intersect_ray_tracer.cpp @@ -12,8 +12,12 @@ // from the leaf's domain, box, pseudo box and polar box given as data, computing the mid-point // data from the real surface exactly as Init does (a case can give leaves the surface's own // subdivision does not have). ADD_SURFACE runs the real SmRayTracer::AddSurfaceToGrid, which -// builds the surface cache and makes the leaves from its tree. SmGrid's protected members are -// reached by exposing them while SmGrid.h is included. +// builds the surface cache and makes the leaves from its tree. ADD_POLY_BREP builds a real +// SmPolyBrep (one region, one shell, closed loops; the constructors and AddPolyEdge as the +// query.poly shim uses them) and runs the real SmRayTracer::AddPolyBrepToGrid, which builds its +// SmBrepCache and adds a polygon element per surface-tree leaf; FireRay then reaches +// sm_FindPolyHit. SmGrid's and SmPolyBrep's protected members are reached by exposing them while +// their headers are included. #include "sc_shim.h" #include @@ -43,6 +47,12 @@ #include #undef protected +#define protected public +#define private public +#include +#undef private +#undef protected + #include #include "sc_sm.h" @@ -63,6 +73,14 @@ enum : int32_t OP_DO_RENDER = 8, OP_RENDER_PIXEL = 9, OP_RENDER_RAY = 10, + OP_ADD_POLY_BREP = 11, +}; + +// The poly B-reps of a program and their faces in creation order (the ids of a solution). +struct Polys +{ + std::vector breps; + std::vector> faces; }; struct Bad : std::runtime_error @@ -164,8 +182,10 @@ void put3(std::vector& o, const SmVector3d& v) } // The solution fields of FireRay / LocalSolve: type, variable and object counts, solution value, -// the ten start parameters, the surface index and whether object 1 and node 0 are set. -void put_solution(std::vector& o, const SmSolution& s, const std::vector& surfs) +// the ten start parameters, the surface index, whether object 1 and node 0 are set, and the poly +// B-rep and face indices of object 0 and of object 1 (-1 -1 when not a poly face of the program). +void put_solution(std::vector& o, const SmSolution& s, const std::vector& surfs, + const Polys& polys) { o.push_back((double)s.m_eSolutionType); o.push_back((double)s.m_lNumVariables); @@ -182,6 +202,77 @@ void put_solution(std::vector& o, const SmSolution& s, const std::vector o.push_back(surf); o.push_back(s.m_apObjects[1] == nullptr ? -1.0 : -2.0); o.push_back(s.m_apNodes[0] == nullptr ? 0.0 : 1.0); + for (int j = 0; j < 2; j++) + { + double brep = -1.0; + double face = -1.0; + for (size_t b = 0; b < polys.faces.size(); b++) + for (size_t f = 0; f < polys.faces[b].size(); f++) + if (s.m_apObjects[j] != nullptr && s.m_apObjects[j] == (SmObject*)polys.faces[b][f]) + { + brep = (double)b; + face = (double)f; + } + o.push_back(brep); + o.push_back(face); + } +} + +// `tol n_vertices (x y z tol)* n_faces (tol has_normal nx ny nz n_loops (k v0 .. v(k-1))*)*`: one +// region, one shell, the face normal given to the constructor or not, every loop closed (k edges), +// each edge with its face's tolerance. +SmPolyBrep* read_poly_brep(Rd& rd, const SmContext& ctx, std::vector& faces) +{ + double tol = rd.num(); + SmPolyBrep* b = new (ctx) SmPolyBrep(tol); + try + { + SmPolyRegion* region = new (b) SmPolyRegion(b); + SmPolyShell* shell = new (b) SmPolyShell(region); + int64_t nv = rd.idx(1, 1024); + std::vector vertices; + for (int64_t i = 0; i < nv; ++i) + { + SmVector3d p = rd.vec(); + double t = rd.num(); + SmPolyVertex* v = new (b) SmPolyVertex(p, t); + b->m_pPolyVertexListHead->PostInsert(v); + vertices.push_back(v); + } + int64_t nf = rd.idx(1, 512); + for (int64_t i = 0; i < nf; ++i) + { + double ftol = rd.num(); + bool has_n = rd.flag(); + SmVector3d n = rd.vec(); + SmPolyFace* f = new (b) SmPolyFace(ftol, NULL, NULL, shell, has_n ? &n : NULL); + faces.push_back(f); + int64_t nl = rd.idx(1, 8); + for (int64_t l = 0; l < nl; ++l) + { + int64_t k = rd.idx(1, 32); + std::vector vs; + for (int64_t j = 0; j < k; ++j) + vs.push_back(vertices[(size_t)rd.idx(0, nv - 1)]); + SmPolyLoop* lp = NULL; + f->StartPolyEdgeLoop(lp); + for (int64_t j = 0; j < k; ++j) + { + SmPolyVertex* v0 = vs[(size_t)j]; + SmPolyVertex* v1 = vs[(size_t)((j + 1) % k)]; + SmPolyEdge* e = NULL; + lp->AddPolyEdge(ftol, v0->GetPoint(), v1->GetPoint(), v0, NULL, b, e); + } + lp->FinishPolyEdgeLoop(b); + } + } + } + catch (...) + { + delete b; + throw; + } + return b; } int32_t run(const double* p, int64_t n, std::vector& o) @@ -192,6 +283,7 @@ int32_t run(const double* p, int64_t n, std::vector& o) SmRayTracer* rt = nullptr; SmRayRenderer* renderer = nullptr; std::vector leaves; + Polys polys; int32_t rc = SC_OK; try { @@ -210,7 +302,7 @@ int32_t run(const double* p, int64_t n, std::vector& o) int64_t nops = rd.idx(0, 64); for (int64_t k = 0; k < nops; k++) { - int op = (int)rd.idx(1, 10); + int op = (int)rd.idx(1, 11); switch (op) { case OP_SOLVER_TOL: @@ -296,7 +388,7 @@ int32_t run(const double* p, int64_t n, std::vector& o) SmStatus s = rt->FireRay(rp, rv, maxT, bHit, sol, ge, mn, mx); o.push_back(st(s)); o.push_back(bHit ? 1.0 : 0.0); - put_solution(o, sol, surfs); + put_solution(o, sol, surfs, polys); o.push_back((double)ge.GetSize()); for (ULONG i = 0; i < ge.GetSize(); i++) { @@ -343,7 +435,7 @@ int32_t run(const double* p, int64_t n, std::vector& o) o.push_back(st(s)); o.push_back(bFound ? 1.0 : 0.0); o.push_back(bNeeds ? 1.0 : 0.0); - put_solution(o, sol, surfs); + put_solution(o, sol, surfs, polys); break; } case OP_ADD_SURFACE: @@ -466,6 +558,34 @@ int32_t run(const double* p, int64_t n, std::vector& o) o.push_back((double)renderer->GetPixelsShaded()); break; } + case OP_ADD_POLY_BREP: + { + if (polys.breps.size() >= 4) + throw Bad(); + std::vector faces; + SmPolyBrep* pb = read_poly_brep(rd, sCtx, faces); + polys.breps.push_back(pb); + polys.faces.push_back(faces); + double expansion = rd.num(); + SmTArray before; + g->m_sPolygonElementMgr.GetActiveElements(before); + SmStatus s = rt->AddPolyBrepToGrid(pb, expansion); + SmTArray after; + g->m_sPolygonElementMgr.GetActiveElements(after); + // status, the number of polygon elements it made, and per element its box, sphere + // centre and radius + o.push_back(st(s)); + o.push_back((double)(after.GetSize() - before.GetSize())); + for (ULONG i = before.GetSize(); i < after.GetSize(); i++) + { + SmGridElement* e = (SmGridElement*)after[i]; + put3(o, e->m_sBBox.GetMin()); + put3(o, e->m_sBBox.GetMax()); + put3(o, e->m_sSphereCenter); + o.push_back(e->m_dSphereRadius); + } + break; + } default: throw Bad(); } @@ -481,6 +601,8 @@ int32_t run(const double* p, int64_t n, std::vector& o) delete rt; // deletes the grid, not the surfaces for (SmSurface* s : surfs) delete s; + for (SmPolyBrep* b : polys.breps) + delete b; // and its SmBrepCache return rc; } } // namespace diff --git a/harness/fixturegen/ray_tracer_poly.py b/harness/fixturegen/ray_tracer_poly.py new file mode 100644 index 0000000000..7745600151 --- /dev/null +++ b/harness/fixturegen/ray_tracer_poly.py @@ -0,0 +1,317 @@ +#!/usr/bin/env python3 +"""Writes fixtures/intersect.ray_tracer/poly_breps.json: rays fired at poly B-reps. + +Upstream `tests/prog_test/src/smTessTest.cpp` `my_test_tess` test 10 ("Test ray-firing for +poly brep") reads `pt_TestFiles/Tess/Extrusion.pbp` into an `SmPolyBrep`, makes a 20^3 +`SmGrid` over the poly B-rep's box expanded by 1.0, an `SmRayTracer(ctx, grid, +SM_ZONE_TOL_3D, 1.0e-8)`, calls `AddPolyBrepToGrid(pPolyBrep, 0.001)` and fires one ray from +the box centre moved back 15 along the unit (1, 1, 1), with `dMaxRayParameter` 40 and +`m_bHitAnyThing` FALSE, asserting that it hits. Case `tess_test10_extrusion` is that test: +the file's one region and shell, its 296 faces in file order with their stored tolerances +and normals, each face's loops as the start vertices of their edges (`SmPolyEdgeData` holds +the edge's start vertex; a loop's edges are consecutive from its start edge, and a face's +loops from its first loop), and the vertices with their tolerances. The grid box is the box +of the vertex points expanded by 1.0 (upstream's `SmPolyBrep::CalculateBoundingBox` box is +not reproduced; the grid only places the voxels). Further rays of the case: the same ray +with hit-any-thing, the reverse ray, and rays along the axes through the centre. + +The other cases are built here to reach the branches of `sm_FindPolyHit` and the polygon +branch of `FireRay`: nearest of stacked faces, a face behind the ray start, misses outside +the polygon and in its plane, a hole, a non-convex notch, coincident faces in two poly +B-reps (the first one tested keeps the hit), a big face over many small ones (objects on +inner tree nodes: the walk up the parents), a poly face against a surface, computed and +given normals, a degenerate face, expansion tolerances, and a render with shadows. + +Run from the repo root: `python3 -I harness/fixturegen/ray_tracer_poly.py`. +""" + +import json +import math +import os + +UPSTREAM = "upstream/TestFiles/pt_TestFiles/Tess/Extrusion.pbp" +OUT = "fixtures/intersect.ray_tracer/poly_breps.json" +SM_ZONE_TOL_3D = 1e-05 # as the existing ray tracer fixtures use it +BIG = 1.0e20 # SM_BIG_DOUBLE + + +def read_pbp(path): + """The faces, loops, edges and vertices of an ASCII .pbp file (SmPolyBrepData).""" + with open(path) as f: + lines = [ln.strip() for ln in f] + at = 0 + + def section(title): + nonlocal at + while not lines[at].startswith("//" + title): + at += 1 + at += 1 + count = int(lines[at].split()[0]) + at += 1 + while lines[at].startswith("//"): + at += 1 + return count + + def nums(): + nonlocal at + vals = lines[at].split() + at += 1 + return vals + + tol = float(next(ln for ln in lines if ln.endswith("PolyBrep Tolerance")).split()[0]) + + count = section("PolyFace Data") + faces = [] + for _ in range(count): + idx, ftol, nloops, first = nums()[:4] + n = [float(x) for x in nums()[:3]] + nums() # attributes + faces.append((float(ftol), int(nloops), int(first), n)) + + count = section("PolyLoop Data") + loops = [] + for _ in range(count): + idx, nedges, start = nums()[:3] + nums() + loops.append((int(nedges), int(start))) + + count = section("PolyEdge Data") + edges = [] + for _ in range(count): + vals = nums() + edges.append(int(vals[2])) # m_lVertex: the start vertex + nums() + + count = section("PolyVertex Data") + vertices = [] + for _ in range(count): + vtol = float(nums()[1]) + p = [float(x) for x in nums()[:3]] + nums() + vertices.append(p + [vtol]) + + out_faces = [] + for ftol, nloops, first, n in faces: + fl = [] + for li in range(first, first + nloops): + nedges, start = loops[li] + fl.append([edges[e] for e in range(start, start + nedges)]) + out_faces.append({"tol": ftol, "normal": n, "loops": fl}) + return tol, vertices, out_faces + + +def unit(v): + n = math.sqrt(sum(x * x for x in v)) + return [x / n for x in v] + + +def fire(p, v, max_t=BIG, hit_any=False): + return {"op": "fire", "p": p, "v": v, "max_t": max_t, "hit_any": hit_any, "skip": None} + + +def poly(vertices, faces, tol=1e-5, expansion=1e-3): + return {"op": "add_poly_brep", "tol": tol, "vertices": vertices, "faces": faces, + "expansion": expansion} + + +def face(loops, tol=1e-5, normal=None): + f = {"tol": tol, "loops": loops} + if normal is not None: + f["normal"] = normal + return f + + +def rect(x0, y0, x1, y1, z, tol=1e-5): + """Counter-clockwise from above.""" + return [[x0, y0, z, tol], [x1, y0, z, tol], [x1, y1, z, tol], [x0, y1, z, tol]] + + +def plane_surface(): + return {"kind": "plane", "o": [0, 0, 0], "x": [1, 0, 0], "y": [0, 1, 0], "scale": [1, 1], + "domain": [0, 0, 4, 4]} + + +def grid(bbox, n=4, ray_tol=SM_ZONE_TOL_3D): + return {"bbox": bbox, "size": [n, n, n], "ray_tol": ray_tol, "ray_acc": 1e-08} + + +def case(name, ref, ops, bbox, n=4, surfaces=None): + return {"name": name, "upstream_ref": ref, + "inputs": {"surfaces": surfaces or [plane_surface()], "grid": grid(bbox, n), + "ops": ops}} + + +def tess_test10(): + tol, vertices, faces = read_pbp(UPSTREAM) + lo = [min(v[i] for v in vertices) - 1.0 for i in range(3)] + hi = [max(v[i] for v in vertices) + 1.0 for i in range(3)] + c = [0.5 * (lo[i] + hi[i]) for i in range(3)] + d = unit([1.0, 1.0, 1.0]) + start = [c[i] - d[i] * 15.0 for i in range(3)] + ops = [poly(vertices, faces, tol=tol, expansion=0.001), + fire(start, d, 40.0), + fire(start, d, 40.0, hit_any=True), + fire([c[i] + d[i] * 15.0 for i in range(3)], [-x for x in d], 40.0)] + for axis in range(3): + for sign in (1.0, -1.0): + v = [0.0, 0.0, 0.0] + v[axis] = sign + p = list(c) + p[axis] -= sign * 30.0 + ops.append(fire(p, v)) + return case("tess_test10_extrusion", + "tests/prog_test/src/smTessTest.cpp:2716 (test 10: Extrusion.pbp, grid 20^3 over " + "the box expanded by 1.0, SM_ZONE_TOL_3D, 1e-8, AddPolyBrepToGrid 0.001, ray from " + "the centre - 15 (1,1,1)/sqrt 3 to 40; plus hit-any, reverse and axis rays)", + ops, lo + hi, n=20) + + +def stacked(): + v = rect(2, 2, 8, 8, 2.0) + rect(2, 2, 8, 8, 6.0) + ops = [poly(v, [face([[0, 1, 2, 3]]), face([[4, 5, 6, 7]])]), + fire([5, 5, 9.5], [0, 0, -1]), # nearest: z = 6 + fire([5, 5, 9.5], [0, 0, -1], hit_any=True), + fire([5, 5, 4.0], [0, 0, -1]), # z = 6 behind the start + fire([5, 5, -0.5], [0, 0, 1]), # from below: z = 2 + fire([9.0, 5, 9.5], [0, 0, -1]), # outside the squares + fire([0.5, 5, 4.0], [1, 0, 0]), # parallel, between them + fire([5, 5, 9.5], [0, 0, -1], max_t=3.0), # stops before z = 6 + fire([5, 5, 6.0], [0, 0, -1]), # starts on the face (t = 0) + fire([2.0, 2.0, 9.5], [0, 0, -1]), # through a corner vertex + fire([5, 2.0, 9.5], [0, 0, -1]), # through an edge + fire([0.5, 0.5, 9.5], unit([1, 1, -1]))] # oblique + return case("poly_stacked_squares", "sm_FindPolyHit: nearest, behind, miss, in-plane, " + "short ray, on-face start, vertex and edge hits", ops, [0, 0, 0, 10, 10, 10]) + + +def holed(): + v = rect(1, 1, 9, 9, 5.0) + [[4, 4, 5.0, 1e-5], [4, 6, 5.0, 1e-5], [6, 6, 5.0, 1e-5], + [6, 4, 5.0, 1e-5]] + rect(1, 1, 9, 9, 1.0) + ops = [poly(v, [face([[0, 1, 2, 3], [4, 5, 6, 7]]), face([[8, 9, 10, 11]])]), + fire([5, 5, 9.5], [0, 0, -1]), # through the hole: z = 1 + fire([2, 2, 9.5], [0, 0, -1]), # the holed face + fire([4, 5, 9.5], [0, 0, -1]), # on the hole's edge + fire([5, 5, 9.5], [0, 0, -1], hit_any=True)] + return case("poly_face_with_hole", "PointInPolygon with an inner loop", ops, + [0, 0, 0, 10, 10, 10]) + + +def notch(): + # an L: the notch [5, 8] x [5, 8] is outside + pts = [[2, 2], [8, 2], [8, 5], [5, 5], [5, 8], [2, 8]] + v = [[x, y, 4.0, 1e-5] for x, y in pts] + ops = [poly(v, [face([[0, 1, 2, 3, 4, 5]])]), + fire([6.5, 6.5, 9.5], [0, 0, -1]), # in the notch: miss + fire([3.5, 6.5, 9.5], [0, 0, -1]), + fire([6.5, 3.5, 9.5], [0, 0, -1]), + fire([6.5, 6.5, 9.5], unit([-1, -1, -1]))] + return case("poly_nonconvex_notch", "PointInPolygon on a non-convex face", ops, + [0, 0, 0, 10, 10, 10]) + + +def coincident(): + a = rect(2, 2, 8, 8, 5.0) + ops = [poly(a, [face([[0, 1, 2, 3]])]), + poly(a, [face([[0, 1, 2, 3]])]), + fire([5, 5, 9.5], [0, 0, -1]), + fire([5, 5, 0.5], [0, 0, 1]), + fire([3, 7, 9.5], unit([0.2, -0.1, -1]))] + return case("poly_coincident_breps", "two poly B-reps with the same face: equal ray " + "parameters, the first tested keeps the hit (strict <)", ops, [0, 0, 0, 10, 10, 10]) + + +def big_over_small(): + v = rect(0.5, 0.5, 9.5, 9.5, 9.0) + faces = [face([[0, 1, 2, 3]])] + for i in range(8): + for j in range(8): + base = len(v) + x, y = 1 + i, 1 + j + z = 1.0 + 0.25 * ((i * 8 + j) % 7) + v += rect(x, y, x + 0.9, y + 0.9, z) + faces.append(face([[base, base + 1, base + 2, base + 3]])) + ops = [poly(v, faces)] + for x, y in [(1.45, 1.45), (4.5, 6.5), (8.95, 8.95), (5.0, 5.0), (3.2, 8.4), (7.99, 1.01)]: + ops.append(fire([x, y, 0.2], [0, 0, 1])) + ops.append(fire([x, y, 9.8], [0, 0, -1])) + ops.append(fire([0.2, 0.3, 0.2], unit([1, 1, 0.3]))) + ops.append(fire([9.8, 9.7, 9.5], unit([-1, -0.9, -0.6]), hit_any=True)) + return case("poly_big_face_over_small_faces", "a face spanning the tree on an inner node: " + "sm_FindPolyHit walks up the parents", ops, [0, 0, 0, 10, 10, 10], n=6) + + +def with_surface(): + # the plane z = 0 over [0, 4]^2 and a poly square at z = 1 and z = -1 + v = rect(0.5, 0.5, 3.5, 3.5, 1.0) + rect(0.5, 0.5, 3.5, 3.5, -1.0) + ops = [{"op": "add_surface", "surface": 0}, + poly(v, [face([[0, 1, 2, 3]]), face([[4, 5, 6, 7]])]), + fire([2, 2, 3], [0, 0, -1]), + fire([2, 2, 0.5], [0, 0, -1]), + fire([0.2, 0.2, 3], [0, 0, -1]), + fire([2, 2, -3], [0, 0, 1])] + return case("poly_and_surface", "a poly face and a surface leaf in one grid", ops, + [-1, -1, -2, 5, 5, 2]) + + +def normals(): + v = rect(2, 2, 8, 8, 3.0) + rect(2, 2, 8, 8, 7.0) + # the first wound clockwise (normal -z), the second given a tilted normal + ops = [poly(v, [face([[3, 2, 1, 0]]), face([[4, 5, 6, 7]], normal=unit([0, 0.6, 0.8]))]), + fire([5, 5, 9.5], [0, 0, -1]), + fire([5, 5, 5.0], [0, 0, -1]), + fire([5, 5, 0.5], [0, 0, 1])] + return case("poly_face_normals", "computed (clockwise) and given face normals in the " + "solution and in the plane intersection", ops, [0, 0, 0, 10, 10, 10]) + + +def degenerate(): + v = [[2, 2, 5, 1e-5], [5, 5, 5, 1e-5], [8, 8, 5, 1e-5], [2, 2, 2, 1e-5], [8, 2, 2, 1e-5], + [5, 8, 2, 1e-5]] + ops = [poly(v, [face([[0, 1, 2]]), face([[3, 4, 5]])]), + fire([5, 5, 9.5], [0, 0, -1]), + fire([5, 4, 9.5], [0, 0, -1]), + fire([4, 4, 9.5], unit([1, 1, -1]))] + return case("poly_degenerate_face", "a collinear face (degenerate normal) above a triangle", + ops, [0, 0, 0, 10, 10, 10]) + + +def expansions(): + out = [] + for e, name in [(0.0, "zero"), (2.5, "large")]: + v = rect(2, 2, 8, 8, 5.0) + ops = [poly(v, [face([[0, 1, 2, 3]])], expansion=e), + fire([5, 5, 9.5], [0, 0, -1]), + fire([8.5, 5, 9.5], [0, 0, -1])] + out.append(case(f"poly_expansion_{name}", "AddPolyBrepToGrid expansion tolerance", ops, + [0, 0, 0, 10, 10, 10])) + return out + + +def render(): + v = rect(1, 1, 3, 3, 1.0) + rect(0, 0, 4, 4, -1.0) + ops = [poly(v, [face([[0, 1, 2, 3]]), face([[4, 5, 6, 7]])]), + {"op": "renderer", "origin": [0, 0, 0], "x": [1, 0, 0], "y": [0, 1, 0], + "view": [0.5, 0.5, 4.0, 3.5, 3.5, 4.0], "nx": 3, "ny": 3, "rate": 2}, + {"op": "light", "v": unit([0.3, 0.2, 1]), "point": False}, + {"op": "light", "v": [2, 2, 3.5], "point": True}, + {"op": "do_render", "shadows": True, "background": [0.1, 0.2, 0.3], + "object": [0.8, 0.5, 0.2]}, + {"op": "render_pixel", "x": 1, "y": 1}, + {"op": "render_ray", "p": [2, 2, 3.5], "v": [0, 0, -1]}, + {"op": "render_ray", "p": [3.5, 3.5, 3.5], "v": [0, 0, -1]}] + return case("poly_render_shadows", "SmRayRenderer over poly faces with shadow rays " + "(hit-any-thing)", ops, [-1, -1, -2, 5, 5, 5]) + + +def main(): + cases = [tess_test10(), stacked(), holed(), notch(), coincident(), big_over_small(), + with_surface(), normals(), degenerate(), *expansions(), render()] + os.makedirs(os.path.dirname(OUT), exist_ok=True) + with open(OUT, "w") as f: + json.dump({"cases": cases}, f, indent=1) + f.write("\n") + print(f"wrote {OUT}: {len(cases)} cases") + + +if __name__ == "__main__": + main() diff --git a/plan/units/intersect.ray_tracer.toml b/plan/units/intersect.ray_tracer.toml index 5238a82b35..ff9fd8cc63 100644 --- a/plan/units/intersect.ray_tracer.toml +++ b/plan/units/intersect.ray_tracer.toml @@ -9,4 +9,4 @@ tests = ["prog_test:my_test_surface_closest_point", "prog_test:my_test_drop_poin status = "green" claimed_by = "" claimed_at = "" -notes = "partial: 421 cases, 310 pass, 0 fail, 111 deferred (surface.surface_p8 SmSurface::LocalLineIntersect x108, surface.surface_p6 SmSurface::CalculateContinuities x3 via the surface cache); surface_leaf matches upstream SE on degenerate patches (2 fixtures); SmSurface::EvaluateNormalSection wired to surface.surface_p3 (no case reaches it yet past the deferrals); UNPORTED stubs also for SmFace::PointClassify (query.face_p2), poly types (query.poly); CO-PORTED in intersect.ray_tracer (PR #48): SmSurfaceGridElement data members -> geom::grid::SurfaceGridElement (+ Grid::add_surface_element), SmRayRenderer/SmLightSource -> intersect::ray_tracer; 13 fns; largest: SmLSI3LocalSolver::LocalSolve, SmRayTracer::FireRay, sm_FindAdditionalTangentGuess, sm_FindPolyHit; cycle back-edges (call into later units; stub or co-port): query.poly(7), topology.topology(2), query.face_p2(1); unit-SCC #22 (220 units); 317 upstream test entry points reach this unit Re-run (PR #118, LocalLineIntersect wired by surface.surface_p8): 421 cases, 415 pass, 6 deferred (surface.surface_p2). LocalSolve sets the solution node marker as upstream does. GREEN (wire: AddSurfaceToGrid): RayTracer::add_surface_to_grid builds the surface cache (nodes hold the surface by its tracer index) and expands it with Grid::expand_hierarchy_with, reading each leaf's patch data, pseudo box and polar box from the cache; the shim now runs the real SmRayTracer::AddSurfaceToGrid and the record compares status, leaf count and per leaf the domain, box, mid-point data, pseudo box and polar box (a polar box that stayed uninitialised was caught this way); the generator puts add_surface before the rays; 421 cases, 421 pass, 0 fail, 0 deferred, max_rel_err 0e0, no absolute floors; 6000 extra seeds clean. SurfaceGridElement::init / from_leaf_data (formerly surface_leaf) and Grid::expand_hierarchy (now taking the surface cache) moved to geom::grid with the geom.grid wiring (geom.grid green); add_surface_to_grid calls Grid::expand_hierarchy; unit numbers unchanged: 421 cases, 421 pass, 0 deferred, max_rel_err 0e0." +notes = "partial: 421 cases, 310 pass, 0 fail, 111 deferred (surface.surface_p8 SmSurface::LocalLineIntersect x108, surface.surface_p6 SmSurface::CalculateContinuities x3 via the surface cache); surface_leaf matches upstream SE on degenerate patches (2 fixtures); SmSurface::EvaluateNormalSection wired to surface.surface_p3 (no case reaches it yet past the deferrals); UNPORTED stubs also for SmFace::PointClassify (query.face_p2), poly types (query.poly); CO-PORTED in intersect.ray_tracer (PR #48): SmSurfaceGridElement data members -> geom::grid::SurfaceGridElement (+ Grid::add_surface_element), SmRayRenderer/SmLightSource -> intersect::ray_tracer; 13 fns; largest: SmLSI3LocalSolver::LocalSolve, SmRayTracer::FireRay, sm_FindAdditionalTangentGuess, sm_FindPolyHit; cycle back-edges (call into later units; stub or co-port): query.poly(7), topology.topology(2), query.face_p2(1); unit-SCC #22 (220 units); 317 upstream test entry points reach this unit Re-run (PR #118, LocalLineIntersect wired by surface.surface_p8): 421 cases, 415 pass, 6 deferred (surface.surface_p2). LocalSolve sets the solution node marker as upstream does. GREEN (wire: AddSurfaceToGrid): RayTracer::add_surface_to_grid builds the surface cache (nodes hold the surface by its tracer index) and expands it with Grid::expand_hierarchy_with, reading each leaf's patch data, pseudo box and polar box from the cache; the shim now runs the real SmRayTracer::AddSurfaceToGrid and the record compares status, leaf count and per leaf the domain, box, mid-point data, pseudo box and polar box (a polar box that stayed uninitialised was caught this way); the generator puts add_surface before the rays; 421 cases, 421 pass, 0 fail, 0 deferred, max_rel_err 0e0, no absolute floors; 6000 extra seeds clean. SurfaceGridElement::init / from_leaf_data (formerly surface_leaf) and Grid::expand_hierarchy (now taking the surface cache) moved to geom::grid with the geom.grid wiring (geom.grid green); add_surface_to_grid calls Grid::expand_hierarchy; unit numbers unchanged: 421 cases, 421 pass, 0 deferred, max_rel_err 0e0. WIRED (PR #1273, query.poly back-edges): AddPolyBrepToGrid (SmPolyBrep::Notify SM_NO_PRE_EDIT, the poly B-rep SmBrepCache, one polygon element per surface-tree leaf) and sm_FindPolyHit (sphere reject, LinePlaneIntersect, PointInPolygon 3D, hit-any exit, parent walk as a bounded loop); FireRay's polygon branch now fills the solution as upstream (SM_ST_SINGLE_VALUE, t, BIG uv, point, face normal, the face in objects 0 and 1; it only moved the hit parameter before) and the per-context mark locks are a per-ray tested-face set. The shim builds real SmPolyBreps (add_poly_brep op; the solution record names poly faces); fixtures/intersect.ray_tracer/poly_breps.json (harness/fixturegen/ray_tracer_poly.py): upstream smTessTest test 10 (Extrusion.pbp, 296 faces) and 11 built cases (holes, notch, coincident faces in two poly B-reps, a face on an inner tree node, poly and surface, normals, degenerate face, expansions, render with shadows); the generator appends poly B-reps to about a third of the cases. No UNPORTED stubs left in this unit."