Files
solvecraft/harness/cpp/shim/intersect_surface_intersector_p2.cpp
T
bherbruck 305c332ad7 intersect.surface_intersector_p2: review fixes (unguard FlushCurve)
Drop the shim's is_rejected_flush and the runner's flush_rejected: the C++
does not misbehave on non-increasing knots (N_IgesValidate inside
CreateCanonical returns SM_ERR for decreasing knots, accepts equal ones), so
every Flush now runs and the sm_FlushCrv error path is compared. Add 105
review adversarial fixtures, drop the stale EvaluateNormal deferral note, and
record the classify-guard width, the unreachable Tangent/Coincident
classifications and the step-7 deferrals in plan/findings.md.

difftest: 1660 cases, 1508 pass, 0 fail, 152 deferred, max_rel_err 0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BY8UpXKuMci15rAWGXsbsm
2026-10-14 09:18:36 -04:00

664 lines
20 KiB
C++

// 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 <SmBSplineCurve.h>
#include <SmBSplineSurface.h>
#include <SmContext.h>
#include <SmCurve.h>
#include <SmCurveBoundedSurface.h>
#include <SmExtent1d.h>
#include <SmExtent2d.h>
#include <SmExtent3d.h>
#include <SmFilletExecutive.h>
#include <SmLine.h>
#include <SmNurbsCrv.h>
#include <SmNurbsSrf.h>
#include <SmPlane.h>
#include <SmSurface.h>
#include <SmSurfaceCache.h>
#include <SmSurfaceIntersector.h>
#include <SmTArray.h>
#include <SmTList.h>
#include <SmVector2d.h>
#include <SmVector3d.h>
#include <nurbs.h>
#include <cmath>
#include <stdexcept>
#include <vector>
// file function of SmSurfaceIntersector.cpp (external linkage upstream)
SmBoolean sm_IsPointUnique(SmVector3d& rPoint, SmTList<SmTsectPnt>& rStartPoints, SmTArray<SmCurve*>& 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<double>& o, const SmVector3d& v)
{
o.push_back(v.x);
o.push_back(v.y);
o.push_back(v.z);
}
void put_uv(std::vector<double>& 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<double> 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<SmCurve*> 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<double> 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<double>& 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<double>& 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<double>& o, const SmTArray<SmCurve*>& 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<SmTsectPnt*> pts;
std::vector<SmCurve*> 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<double>& o, const Prog& pr, const SmTList<SmTsectPnt>& l) const
{
SmTsectPnt* aData[70];
SmTArray<SmTsectPnt*> 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<double>& 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<SmCurve*>& 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<SmCurve*> c3;
SmTArray<SmCurve*> c0;
SmTArray<SmCurve*> c1;
SmTArray<SmTsectCurveType> types;
SmTArray<double> 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<double>& 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<double>& 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<double> 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;
}
}