// SPDX-License-Identifier: Apache-2.0 // difftest shim for unit `intersect.surface_intersector_p2`: the SmSurfaceIntersector members // ComputePointValues, ClassifyBoundaryStartPointIntersection, FlushCurve, DoBoundaryPlaneTest // and ComputeLawPointValues, and the file function sm_IsPointUnique // (source/SMLib/src/SmSurfaceIntersector.cpp), run on a real SmSurfaceIntersector over // SmPlane / SmBSplineSurface / SmCurveBoundedSurface objects (a derived class only exposes the // protected state). // // One entry point, sc_intersect_surface_intersector_p2_run(in, n_in, out, cap, out_len). `in` is // a program (layout in crates/solidcore-ffi/src/units/intersect_surface_intersector_p2.rs); // every read is bounds checked here (a bad program is SC_ERR_ARG). Ops whose upstream behaviour // is undefined or may not terminate are not run and report REJECTED (see is_rejected_*). #include "sc_shim.h" #include "StdAfx.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // file function of SmSurfaceIntersector.cpp (external linkage upstream) SmBoolean sm_IsPointUnique(SmVector3d& rPoint, SmTList& rStartPoints, SmTArray& r3DCurves, double d3DTol); namespace { enum Op : int32_t { OP_STATE = 1, OP_POINT = 2, OP_ADD_CURVE = 3, OP_ADD_START = 4, OP_CPV = 5, OP_CLASSIFY = 6, OP_FLUSH = 7, OP_UNIQUE = 8, OP_BPT = 9, OP_LAW = 10, OP_MEMBERS = 11, OP_LAST = 11, }; // Op status of an op the shim does not run. const double REJECTED = -2.0; struct Bad : std::runtime_error { Bad() : std::runtime_error("bad program") {} }; struct Rd { const double* in; int64_t n; int64_t at; double num() { if (at >= n) throw Bad(); return in[at++]; } int64_t idx(int64_t lo, int64_t hi) { double d = num(); if (!(d >= (double)lo && d <= (double)hi) || d != (double)(int64_t)d) throw Bad(); return (int64_t)d; } bool flag() { return idx(0, 1) != 0; } SmVector3d vec() { double x = num(); double y = num(); double z = num(); return SmVector3d(x, y, z); } SmPoint2d uv() { double x = num(); double y = num(); return SmPoint2d(x, y); } SmExtent2d dom4() { double a = num(); double b = num(); double c = num(); double d = num(); return SmExtent2d(a, b, c, d); } }; double st(SmStatus s) { return s == SM_SUCCESS ? 0.0 : (double)s; } double flg(SmBoolean b) { return b ? 1.0 : 0.0; } void put_vec(std::vector& o, const SmVector3d& v) { o.push_back(v.x); o.push_back(v.y); o.push_back(v.z); } void put_uv(std::vector& o, const SmVector2d& v) { o.push_back(v.x); o.push_back(v.y); } // `0 o x y scale dom` (SmPlane) or `1 p q nu nv net(x y z w) u v` (SmBSplineSurface). SmSurface* read_basic_surface(Rd& rd, const SmContext& ctx) { int64_t kind = rd.idx(0, 1); if (kind == 0) { SmPoint3d o = rd.vec(); SmVector3d x = rd.vec(); SmVector3d y = rd.vec(); SmVector2d s = rd.uv(); SmExtent2d d = rd.dom4(); return new (ctx) SmPlane(o, x, y, s, d, &ctx); } gw_DEGREE p = (gw_DEGREE)rd.idx(1, 8); gw_DEGREE q = (gw_DEGREE)rd.idx(1, 8); gw_INDEX nu = (gw_INDEX)rd.idx(2, 17); gw_INDEX nv = (gw_INDEX)rd.idx(2, 17); if (p >= nu || q >= nv) throw Bad(); gw_INDEX n = nu - 1; gw_INDEX m = nv - 1; gw_INDEX r = n + p + 1; gw_INDEX s = m + q + 1; std::vector v; for (int64_t i = 0; i < 4 * (int64_t)nu * nv + r + 1 + s + 1; i++) v.push_back(rd.num()); gw_SURFACE* sur = sm_AllocateNurbSurface(n, m, p, q, r, s); size_t k = 0; for (gw_INDEX i = 0; i <= n; i++) for (gw_INDEX j = 0; j <= m; j++) { sur->net->Pw[i][j].x = v[k++]; sur->net->Pw[i][j].y = v[k++]; sur->net->Pw[i][j].z = v[k++]; sur->net->Pw[i][j].w = v[k++]; } for (gw_INDEX i = 0; i <= r; i++) sur->knu->U[i] = v[k++]; for (gw_INDEX i = 0; i <= s; i++) sur->knv->U[i] = v[k++]; return new (ctx) SmBSplineSurface(sur, FALSE, &ctx); } // A basic surface (`0 ..` / `1 ..`) or `3 basic`: an SmCurveBoundedSurface (implicit outer, no // boundary curves) over it, which owns (deletes) its basis. (`2` marks B = A.) SmSurface* read_surface(Rd& rd, const SmContext& ctx) { if (rd.at < rd.n && rd.in[rd.at] == 3.0) { rd.at++; SmSurface* basis = read_basic_surface(rd, ctx); SmTArray c3d(ctx), cuv(ctx); return new (ctx) SmCurveBoundedSurface(*basis, &c3d, &cuv, TRUE); } return read_basic_surface(rd, ctx); } // `0 a b` (SmLine::CreateLineSegment, dim 3) or `1 dim p ncp cpts(x y z w) knots`. SmCurve* read_curve(Rd& rd, const SmContext& ctx) { int64_t kind = rd.idx(0, 1); if (kind == 0) { SmPoint3d a = rd.vec(); SmPoint3d b = rd.vec(); SmLine* l = NULL; if (SmLine::CreateLineSegment(ctx, 3, a, b, l) != SM_SUCCESS || l == NULL) throw Bad(); return l; } ULONG dim = (ULONG)rd.idx(2, 3); int64_t p = rd.idx(1, 8); int64_t ncp = rd.idx(2, 33); if (p >= ncp) throw Bad(); int64_t n = ncp - 1; int64_t m = n + p + 1; std::vector v; for (int64_t i = 0; i < 4 * ncp + m + 1; i++) v.push_back(rd.num()); gw_CURVE* cur = sm_AllocateNurbCurve((gw_INDEX)n, (gw_DEGREE)p, (gw_INDEX)m); for (int64_t i = 0; i <= n; i++) { cur->pol->Pw[i].x = v[4 * i]; cur->pol->Pw[i].y = v[4 * i + 1]; cur->pol->Pw[i].z = v[4 * i + 2]; cur->pol->Pw[i].w = v[4 * i + 3]; } for (int64_t i = 0; i <= m; i++) cur->knt->U[i] = v[4 * ncp + i]; SmBSplineCurve* c = new (ctx) SmBSplineCurve(dim, (ULONG)cur->pol->n, (ULONG)cur->knt->m, (short)cur->p, cur->pol->Pw, cur->knt->U, &ctx); smos_Free(cur); return c; } // A point: `type ptype[4] uv0 duv0 uv1 duv1 srf0[3][3] srf1[3][3] crv_pos crv_deriv param // angle deviation user[4]` (80 values). void read_point(Rd& rd, SmTsectPnt& t) { t.m_ePointType = (SmIntersectionPointType)rd.idx(0, 7); for (int s = 0; s < 2; s++) for (int d = 0; d < 2; d++) t.m_eUVParamType[s][d] = (SmExtentPointType)rd.idx(0, 5); for (int s = 0; s < 2; s++) for (int d = 0; d < 2; d++) t.m_vUVCurvePV[s][d] = rd.uv(); for (int s = 0; s < 2; s++) for (int i = 0; i < 3; i++) for (int j = 0; j < 3; j++) t.m_vSurfacePV[s][i][j] = rd.vec(); t.m_v3DCurvePV[0] = rd.vec(); t.m_v3DCurvePV[1] = rd.vec(); t.m_dCurveParameter = rd.num(); t.m_dTangentPlaneAngleRad = rd.num(); t.m_dDeviation = rd.num(); for (int i = 0; i < SM_MAX_USER_DOUBLES; i++) t.m_adUserDoubles[i] = rd.num(); } void put_point(std::vector& o, const SmTsectPnt& t) { o.push_back((double)t.m_ePointType); for (int s = 0; s < 2; s++) for (int d = 0; d < 2; d++) o.push_back((double)t.m_eUVParamType[s][d]); for (int s = 0; s < 2; s++) for (int d = 0; d < 2; d++) put_uv(o, t.m_vUVCurvePV[s][d]); for (int s = 0; s < 2; s++) for (int i = 0; i < 3; i++) for (int j = 0; j < 3; j++) put_vec(o, t.m_vSurfacePV[s][i][j]); put_vec(o, t.m_v3DCurvePV[0]); put_vec(o, t.m_v3DCurvePV[1]); o.push_back(t.m_dCurveParameter); o.push_back(t.m_dTangentPlaneAngleRad); o.push_back(t.m_dDeviation); for (int i = 0; i < SM_MAX_USER_DOUBLES; i++) o.push_back(t.m_adUserDoubles[i]); } // Reads the protected STEP flags through pointers to members named in a derived class. struct PeekC : SmBSplineCurve { static SmKnotType SmBSplineCurve::*kt() { return &PeekC::m_eKnotType; } static SmBSplineCurveForm SmBSplineCurve::*form() { return &PeekC::m_eBSplineCurveForm; } }; // `type dim knot_type form has [n p m cpts(x y z w) knots] ivl[2]` of a B-spline curve. void put_bspline(std::vector& o, const SmCurve* c) { o.push_back((double)c->GetType()); o.push_back((double)c->GetDim()); if (!c->IsKindOf(SmBSplineCurve_TYPE)) throw Bad(); const SmBSplineCurve* b = (const SmBSplineCurve*)c; o.push_back((double)(b->*PeekC::kt())); o.push_back((double)(b->*PeekC::form())); const gw_CURVE* g = b->GetGwNurbPointer(); o.push_back(g ? 1.0 : 0.0); if (g) { o.push_back((double)g->pol->n); o.push_back((double)g->p); o.push_back((double)g->knt->m); for (gw_INDEX i = 0; i <= g->pol->n; i++) { o.push_back(g->pol->Pw[i].x); o.push_back(g->pol->Pw[i].y); o.push_back(g->pol->Pw[i].z); o.push_back(g->pol->Pw[i].w); } for (gw_INDEX i = 0; i <= g->knt->m; i++) o.push_back(g->knt->U[i]); } SmExtent1d ivl = c->GetNaturalInterval(); o.push_back(ivl.GetMin()); o.push_back(ivl.GetMax()); } // `count curves..` from index `from` of an array. void put_curves(std::vector& o, const SmTArray& a, ULONG from) { o.push_back((double)(a.GetSize() - from)); for (ULONG i = from; i < a.GetSize(); i++) put_bspline(o, a[i]); } struct Prog { const SmContext& ctx; std::vector pts; std::vector curves; ~Prog() { for (SmTsectPnt* p : pts) delete p; for (SmCurve* c : curves) delete c; } SmTsectPnt* pt(Rd& rd) { int64_t i = rd.idx(0, (int64_t)pts.size() - 1); return pts[(size_t)i]; } static bool linked(const SmTsectPnt* p) { return p->m_pNext != NULL || p->m_pPrev != NULL; } double index_of(const SmTsectPnt* p) const { for (size_t i = 0; i < pts.size(); i++) if (pts[i] == p) return (double)i; return -2.0; } }; bool finite2(const SmVector2d& v) { return std::isfinite(v.x) && std::isfinite(v.y); } // ClassifyBoundaryStartPointIntersection walks the gap function with steps growing by 5 until // a sample leaves a domain: with both UV tangents zero (or a non-finite UV value, which every // domain "contains") the walk may never end. bool is_rejected_classify(const SmTsectPnt& t) { for (int s = 0; s < 2; s++) if (!finite2(t.m_vUVCurvePV[s][0]) || !finite2(t.m_vUVCurvePV[s][1])) return true; bool zero0 = t.m_vUVCurvePV[0][1].x == 0.0 && t.m_vUVCurvePV[0][1].y == 0.0; bool zero1 = t.m_vUVCurvePV[1][1].x == 0.0 && t.m_vUVCurvePV[1][1].y == 0.0; return zero0 && zero1; } // The intersector with its protected state reachable. struct Peek : SmSurfaceIntersector { Peek(const SmSurface& a, const SmExtent2d& da, const SmSurface& b, const SmExtent2d& db, SmBoolean f) : SmSurfaceIntersector(a, da, b, db, f) { } void set_context(const SmContext& c) { m_cpContext = &c; } void set_members(double trace_dir, double approx_tol, double start_tol) { m_dCurveTraceDirection = trace_dir; m_dThisApproxTol3d = approx_tol; m_dStartParallelTolRadians = start_tol; } void put_list(std::vector& o, const Prog& pr, const SmTList& l) const { SmTsectPnt* aData[70]; SmTArray a(70, aData); l.GetAllNodes(a); o.push_back((double)a.GetSize()); for (ULONG i = 0; i < a.GetSize(); i++) o.push_back(pr.index_of(a[i])); } // `approx_tol trace_dir start_tol curve_list start_list npoints points` void put_state(std::vector& o, const Prog& pr) const { o.push_back(m_dThisApproxTol3d); o.push_back(m_dCurveTraceDirection); o.push_back(m_dStartParallelTolRadians); put_list(o, pr, m_vCurvePoints); put_list(o, pr, m_vStartPoints); o.push_back((double)pr.pts.size()); for (const SmTsectPnt* p : pr.pts) put_point(o, *p); } SmBoolean unique(SmVector3d& p, SmTArray& c, double tol) { return sm_IsPointUnique(p, m_vStartPoints, c, tol); } }; // The accumulated FlushCurve outputs: `out3d` starts with the program's curves. struct Outs { SmCurve* a3[8]; SmCurve* a0[8]; SmCurve* a1[8]; SmTsectCurveType at[8]; double ad[8]; SmTArray c3; SmTArray c0; SmTArray c1; SmTArray types; SmTArray devs; ULONG first; Outs() : c3(8, a3), c0(8, a0), c1(8, a1), types(8, at), devs(8, ad), first(0) {} ~Outs() { for (ULONG i = first; i < c3.GetSize(); i++) delete c3[i]; for (ULONG i = 0; i < c0.GetSize(); i++) delete c0[i]; for (ULONG i = 0; i < c1.GetSize(); i++) delete c1[i]; } void put(std::vector& o) const { put_curves(o, c3, first); put_curves(o, c0, 0); put_curves(o, c1, 0); o.push_back((double)types.GetSize()); for (ULONG i = 0; i < types.GetSize(); i++) o.push_back((double)types[i]); o.push_back((double)devs.GetSize()); for (ULONG i = 0; i < devs.GetSize(); i++) o.push_back(devs[i]); } }; void run_op(int32_t op, Rd& rd, Prog& pr, Peek& si, Outs& outs, std::vector& o) { switch (op) { case OP_STATE: o.push_back(0.0); si.put_state(o, pr); return; case OP_POINT: { SmTsectPnt* p = pr.pt(rd); o.push_back(0.0); put_point(o, *p); return; } case OP_ADD_CURVE: case OP_ADD_START: { SmTsectPnt* p = pr.pt(rd); if (Prog::linked(p)) { o.push_back(REJECTED); return; } o.push_back(st(op == OP_ADD_CURVE ? si.AddPointToCurve(*p) : si.AddStartPoint(*p))); return; } case OP_CPV: { SmPoint2d uvs[2]; uvs[0] = rd.uv(); uvs[1] = rd.uv(); SmTsectPnt* p = pr.pt(rd); bool has_prev = rd.flag(); SmTsectPnt* prev = pr.pt(rd); bool has_step = rd.flag(); double step = rd.num(); if (has_prev && prev == p) throw Bad(); o.push_back(st(si.ComputePointValues(uvs, *p, has_prev ? prev : NULL, has_step ? &step : NULL))); put_point(o, *p); return; } case OP_CLASSIFY: { SmTsectPnt* p = pr.pt(rd); if (is_rejected_classify(*p)) { o.push_back(REJECTED); return; } SmBoolean deg = 7; SmStatus s = si.ClassifyBoundaryStartPointIntersection(*p, deg); o.push_back(st(s)); if (s == SM_SUCCESS) o.push_back(flg(deg)); return; } case OP_FLUSH: { SmStatus s = si.FlushCurve(outs.c3, outs.c0, outs.c1, outs.types, outs.devs); o.push_back(st(s)); if (s == SM_SUCCESS) outs.put(o); return; } case OP_UNIQUE: { SmVector3d p = rd.vec(); double tol = rd.num(); o.push_back(0.0); o.push_back(flg(si.unique(p, outs.c3, tol))); return; } case OP_BPT: { double tol = rd.num(); SmPoint3d bmin = rd.vec(); SmPoint3d bmax = rd.vec(); SmExtent2d d1 = rd.dom4(); SmExtent2d d2 = rd.dom4(); SmExtent3d box(bmin.x, bmin.y, bmin.z, bmax.x, bmax.y, bmax.z); SmPatchBoundaryBoundingPlanes bp1(si.GetSurface(0), d1); SmPatchBoundaryBoundingPlanes bp2(si.GetSurface(1), d2); Outs local; SmBoundaryPlaneTestResult res = SM_BR_UNKNOWN; SmStatus s = si.DoBoundaryPlaneTest(bp1, bp2, tol, box, local.c3, local.types, local.devs, res); o.push_back(st(s)); if (s == SM_SUCCESS) { o.push_back((double)res); local.put(o); } return; } case OP_LAW: { double param = rd.num(); SmPoint2d uv0 = rd.uv(); SmPoint2d uv1 = rd.uv(); double delta = rd.num(); SmVector3d normals[2]; normals[0] = rd.vec(); normals[1] = rd.vec(); bool avg = rd.flag(); bool orient = rd.flag(); if (pr.curves.empty()) throw Bad(); SmCurve* crv = pr.curves[(size_t)rd.idx(0, (int64_t)pr.curves.size() - 1)]; double a = rd.num(); double b = rd.num(); SmExtent1d ivl(a, b); double so[2]; so[0] = rd.num(); so[1] = rd.num(); bool skip = rd.flag(); SmTsectPnt* p = pr.pt(rd); SmFilletLaw law; o.push_back(st(si.ComputeLawPointValues(param, uv0, uv1, delta, normals, avg ? TRUE : FALSE, law, orient ? TRUE : FALSE, *crv, ivl, so, skip ? TRUE : FALSE, *p))); put_point(o, *p); return; } case OP_MEMBERS: { double td = rd.num(); double at = rd.num(); double sp = rd.num(); si.set_members(td, at, sp); o.push_back(0.0); return; } default: throw Bad(); } } } // namespace // Program: `surfA surfB` (surfB `2` = the same object as A), `dom1[4] dom2[4] fillet`, // `ncurves curves`, `npoints points`, `nops ops`. Output: per op `op status [values]`, then the // final state. SC_EXPORT int32_t sc_intersect_surface_intersector_p2_run(const double* in, int64_t n_in, double* out, int64_t cap, int64_t* out_len) { if (in == NULL || n_in < 2) return SC_ERR_ARG; try { SmContext ctx; std::vector o; Rd rd{in, n_in, 0}; SmSurface* a = read_surface(rd, ctx); SmSurface* b = NULL; bool same = false; if (rd.at < rd.n && rd.in[rd.at] == 2.0) { rd.at++; same = true; b = a; } else b = read_surface(rd, ctx); SmExtent2d d1 = rd.dom4(); SmExtent2d d2 = rd.dom4(); bool fillet = rd.flag(); Prog pr{ctx, {}, {}}; int64_t nc = rd.idx(0, 8); for (int64_t i = 0; i < nc; i++) pr.curves.push_back(read_curve(rd, ctx)); int64_t np = rd.idx(0, 64); for (int64_t i = 0; i < np; i++) { SmTsectPnt* t = new SmTsectPnt(); pr.pts.push_back(t); read_point(rd, *t); } int64_t nops = rd.idx(0, 64); { Peek si(*a, d1, *b, d2, fillet ? TRUE : FALSE); si.set_context(ctx); Outs outs; for (SmCurve* c : pr.curves) outs.c3.Add(c); outs.first = outs.c3.GetSize(); for (int64_t i = 0; i < nops; i++) { int32_t op = (int32_t)rd.idx(1, OP_LAST); o.push_back((double)op); run_op(op, rd, pr, si, outs, o); } si.put_state(o, pr); // unlink every point before the lists go away for (SmTsectPnt* t : pr.pts) t->ReSet(); } if (!same) delete b; delete a; return sc_copy_out(o.data(), (int64_t)o.size(), out, cap, out_len); } catch (const Bad&) { return SC_ERR_ARG; } catch (...) { return SC_ERR_EXCEPTION; } }