Files
solvecraft/harness/cpp/shim/surface_surface_point_solve_p3.cpp
T
Brett Lawson c281589321 difftest: coverage cases for eleven green surface units; hyperbola generators
From the llvm-cov run of `difftest --green`: 136 fixture cases
(fixtures/<unit>/coverage.json, each citing the upstream branch and the
Rust line it covers) execute about 70 previously uncovered spans in
offset_surface (zero first derivatives replaced by base tangents, a
hyperboloid base), sphere (coincident/concentric spheres, degenerate
arcs, tangency marks, cone and box rejects, pole and seam inversion),
surface_p5 (singular iso curve, CoverOtherCoincidentSurface branches,
IdenticalSolutions at a pole), surface_p4 (curve/surface crossing
classification on dome, saddle and cylinder points), surf_of_extrusion_p1
(coincident generators touching or disjoint), surface_p6 (solutions at a
pole), surface_point_solve_p2 (cross-seam solutions), torus (horn torus,
axis-start minor circle), curve_bounded_surface (== stops) and
surf_of_revolution_p1/p3 (revolutions of a real SmHyperbola).

The surf_of_revolution_p1 runner, FFI and shim gain a hyperbola
generator; the five shims sharing that generator block parse it too.
Fuzzing it showed SmHyperbola's constructor ignoring a failed MakeNurb
(NULL or non-finite NURB, then a segfault): the shims refuse such a
hyperbola, and plan/upstream-issues.md UB-46 records the trigger.
Generated cases are unchanged.

All eleven units PASS with 0 failures and 0 deferred (max_rel_err 0);
--perturb SENSITIVE on all; --fuzz 200 (p1, p3, offset): FUZZ-PASS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 12:10:34 -07:00

447 lines
14 KiB
C++

// SPDX-License-Identifier: Apache-2.0
// Native differential shim for surface.surface_point_solve_p3.
// The file-local upstream classes and two SmSurface methods are renamed before
// including SmSurfacePointSolve.cpp, as in p1/p2. Surface construction and wire
// formats follow the p2 shim; the five calls below execute unmodified upstream code.
#define GlobalPointSolve_1 sc_spsp3_GlobalPointSolve_1
#define LocalPointSolve_1 sc_spsp3_LocalPointSolve_1
#include "sc_shim.h"
#include "StdAfx.h"
#include <SmBSplineCurve.h>
#include <SmBSplineSurface.h>
#include <SmCircle.h>
#include <SmBrep.h>
#include <SmContext.h>
#include <SmCurveBoundedSurface.h>
#include <SmExtent1d.h>
#include <SmExtent2d.h>
#include <SmHyperbola.h>
#include <SmExtent3d.h>
#include <SmFace.h>
#include <SmGeomUtility.h>
#include <SmGraphicsExtern.h>
#include <SmGraphicsOutput.h>
#include <SmIsoCurve.h>
#include <SmLine.h>
#include <SmLocalSolve1d.h>
#include <SmNurbsSrf.h>
#include <SmOffsetSurface.h>
#include <SmPlane.h>
#include <SmSTEPSurface.h>
#include <SmSolutionArray.h>
#include <SmSurfOfRevolution.h>
#include <SmSurface.h>
#include <SmSurfaceCache.h>
#include <SmTArray.h>
#include <SmVector2d.h>
#include <SmVector3d.h>
#include <nurbs.h>
#include <cmath>
#include <stdexcept>
#include <vector>
#define SmSurfacePointSolver sc_spsp3_SmSurfacePointSolver
#define SmSurfPtGlobalSolver sc_spsp3_SmSurfPtGlobalSolver
#define SmSurfPtSolveStepper sc_spsp3_SmSurfPtSolveStepper
#define SmSrfPtErrorNormType sc_spsp3_SmSrfPtErrorNormType
#define GetTrianglesDist sc_spsp3_GetTrianglesDist
#define WellBehavedControlNet sc_spsp3_WellBehavedControlNet
#define private public
#define protected public
#include "../../../upstream/source/SMLib/src/SmSurfacePointSolve.cpp"
#include "sc_sm.h"
using sc::flg;
using sc::put_uv;
using sc::st;
#undef private
#undef protected
namespace sc_spsp3
{
struct Rd
{
const double* in;
int64_t at;
double num() { return in[at++]; }
int64_t idx() { return (int64_t)in[at++]; }
bool flag() { return in[at++] != 0.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 domain()
{
double a = num();
double b = num();
double c = num();
double d = num();
return SmExtent2d(a, b, c, d);
}
};
gw_SURFACE* read_nurb(Rd& rd)
{
gw_INDEX n = (gw_INDEX)rd.idx();
gw_INDEX m = (gw_INDEX)rd.idx();
gw_DEGREE p = (gw_DEGREE)rd.idx();
gw_DEGREE q = (gw_DEGREE)rd.idx();
gw_INDEX r = (gw_INDEX)rd.idx();
gw_INDEX s = (gw_INDEX)rd.idx();
gw_SURFACE* sur = sm_AllocateNurbSurface(n, m, p, q, r, s);
for (gw_INDEX i = 0; i <= n; i++)
for (gw_INDEX j = 0; j <= m; j++)
{
sur->net->Pw[i][j].x = rd.num();
sur->net->Pw[i][j].y = rd.num();
sur->net->Pw[i][j].z = rd.num();
sur->net->Pw[i][j].w = rd.num();
}
for (gw_INDEX i = 0; i <= r; i++)
sur->knu->U[i] = rd.num();
for (gw_INDEX i = 0; i <= s; i++)
sur->knv->U[i] = rd.num();
return sur;
}
// `oob form nurb`.
SmBSplineSurface* read_bspline(Rd& rd, const SmContext& ctx)
{
bool oob = rd.flag();
SmBSplineSurfaceForm form = (SmBSplineSurfaceForm)rd.idx();
SmBSplineSurface* s = new (ctx) SmBSplineSurface(read_nurb(rd), FALSE, NULL);
s->GetOutOfBoundsEnabled() = oob ? TRUE : FALSE;
s->SetBSplineSurfaceForm(form);
return s;
}
// Reaches the protected SmSurfOfRevolution constructor (as the surf_of_revolution_p1 shim).
struct RevPeek : SmSurfOfRevolution
{
RevPeek(SmBSplineCurve* c, const SmPoint3d& o, const SmVector3d& x, const SmVector3d& y,
const SmExtent2d& d, SmBoolean swap)
: SmSurfOfRevolution(c, o, x, y, d, swap)
{
}
};
// `n p m`, `n+1` points `x y z w`, `m+1` knots: SmBSplineCurve(gw_CURVE*, 3, FALSE).
SmBSplineCurve* read_curve(Rd& rd, const SmContext& ctx)
{
gw_INDEX n = (gw_INDEX)rd.idx();
gw_DEGREE p = (gw_DEGREE)rd.idx();
gw_INDEX m = (gw_INDEX)rd.idx();
gw_CURVE* cur = sm_AllocateNurbCurve(n, p, m);
for (gw_INDEX i = 0; i <= n; i++)
{
cur->pol->Pw[i].x = rd.num();
cur->pol->Pw[i].y = rd.num();
cur->pol->Pw[i].z = rd.num();
cur->pol->Pw[i].w = rd.num();
}
for (gw_INDEX i = 0; i <= m; i++)
cur->knt->U[i] = rd.num();
return new (ctx) SmBSplineCurve(cur, 3, FALSE);
}
// An `SmHyperbola` whose constructor made a finite NURB. The constructor ignores a
// failing `MakeNurb` (`SE`), leaving `m_pNurb` NULL or NaN for semi axes too large or too
// small next to the centre's coordinates, which every later use dereferences (the FFI
// cannot predict the rounding): such an input is refused as a harness error.
SmBSplineCurve* checked_hyperbola(SmHyperbola* h)
{
const gw_CURVE* cur = h->GetGwNurbPointer();
bool ok = cur != NULL && cur->pol != NULL;
for (gw_INDEX i = 0; ok && i <= cur->pol->n; i++)
{
const gw_CPOINT& p = cur->pol->Pw[i];
ok = std::isfinite(p.x) && std::isfinite(p.y) && std::isfinite(p.z) && std::isfinite(p.w);
}
if (!ok)
{
delete h;
throw std::invalid_argument("hyperbola generator: no finite NURB");
}
return h;
}
// A generator `kind ...` (layout of the surf_of_revolution_p1 shim): 0 a B-spline curve,
// 1 a line `a[3] b[3]` (its NURB made), 2 a circle `c[3] x[3] y[3] d0 d1 r`, 3 a
// hyperbola `c[3] x[3] y[3] d0 d1 a b`.
SmBSplineCurve* read_gen(Rd& rd, const SmContext& ctx)
{
int64_t kind = rd.idx();
if (kind == 0)
return read_curve(rd, ctx);
if (kind == 1)
{
SmPoint3d a = rd.vec();
SmPoint3d b = rd.vec();
SmLine* l = new (ctx) SmLine(a, b, 3, &ctx);
l->GetOrCreateGwNurbPointer();
return l;
}
if (kind == 3)
{
SmPoint3d c = rd.vec();
SmVector3d x = rd.vec();
SmVector3d y = rd.vec();
double d0 = rd.num();
double d1 = rd.num();
double a = rd.num();
double b = rd.num();
return checked_hyperbola(new (ctx) SmHyperbola(c, x, y, SmExtent1d(d0, d1), a, b, 3, &ctx));
}
SmPoint3d c = rd.vec();
SmVector3d x = rd.vec();
SmVector3d y = rd.vec();
double d0 = rd.num();
double d1 = rd.num();
double r = rd.num();
return new (ctx) SmCircle(c, x, y, SmExtent1d(d0, d1), r, 3, &ctx);
}
// A surface of revolution `mode gen oob swap` then for mode 0 (CreateCanonical) `P[3]
// A[3]`, for mode 1 (the constructor and MakeNurb) `O[3] X[3] Y[3] D[4]` (layout of the
// surf_of_revolution_p1 shim). Writes the construction status; NULL on failure.
SmSurfOfRevolution* read_rev(Rd& rd, const SmContext& ctx, SmStatus& stt)
{
int64_t mode = rd.idx();
SmBSplineCurve* gen = read_gen(rd, ctx);
bool oob = rd.flag();
bool swap = rd.flag();
SmSurfOfRevolution* s = NULL;
if (mode == 0)
{
SmPoint3d p = rd.vec();
SmVector3d a = rd.vec();
stt = SmSurfOfRevolution::CreateCanonical(ctx, gen, p, a, s, swap ? TRUE : FALSE);
}
else
{
SmPoint3d org = rd.vec();
SmVector3d x = rd.vec();
SmVector3d y = rd.vec();
SmExtent2d d = rd.domain();
s = new (ctx) RevPeek(gen, org, x, y, d, swap ? TRUE : FALSE);
stt = s->MakeNurb();
}
if (stt != SM_SUCCESS)
{
if (s)
delete s; // owns gen
else
delete gen;
return NULL;
}
s->SetOutOfBoundsEnabled(oob ? TRUE : FALSE); // the surface and its generator
return s;
}
// The construction of a surface of revolution failed (OP_REV_INFO reports it first).
struct BuildFailed
{
};
// A surface `kind ...`: 0 B-spline `oob form nurb`; 1 bounded plane `o x y sx sy D`;
// 2 curve-bounded surface (implicit outer, no boundary curves) over a B-spline block;
// 3 surface of revolution (read_rev; throws BuildFailed when it cannot be built).
SmSurface* read_surface(Rd& rd, const SmContext& ctx)
{
int64_t kind = rd.idx();
if (kind == 3)
{
SmStatus stt = SM_SUCCESS;
SmSurfOfRevolution* r = read_rev(rd, ctx, stt);
if (r == NULL)
throw BuildFailed();
return r;
}
if (kind == 1)
{
SmPoint3d o = rd.vec();
SmVector3d x = rd.vec();
SmVector3d y = rd.vec();
double sx = rd.num();
double sy = rd.num();
SmExtent2d d = rd.domain();
return new (ctx) SmPlane(o, x, y, SmVector2d(sx, sy), d, &ctx);
}
SmBSplineSurface* b = read_bspline(rd, ctx);
if (kind == 0)
return b;
// the curve-bounded surface owns (deletes) its basis
SmTArray<SmCurve*> c3d(ctx), cuv(ctx);
return new (ctx) SmCurveBoundedSurface(*b, &c3d, &cuv, TRUE);
}
// `W S`: the surface, wrapped in an SmSTEPSurface owning it when W = 1.
SmSurface* read_wrapped(Rd& rd, const SmContext& ctx)
{
bool wrap = rd.flag();
SmSurface* s = read_surface(rd, ctx);
if (!wrap)
return s;
return new (ctx) SmSTEPSurface(*s, TRUE);
}
// The problem: `W S D op test tol`.
struct Problem
{
SmSurface* srf;
SmExtent2d dom;
SmSolverOperationType op;
SmPoint3d test;
double tol;
};
Problem read_problem(Rd& rd, const SmContext& ctx)
{
Problem p;
p.srf = read_wrapped(rd, ctx);
p.dom = rd.domain();
p.op = (SmSolverOperationType)rd.idx();
p.test = rd.vec();
p.tol = rd.num();
return p;
}
// `(u v value nobj nvar obj0_is_srf)` of each solution, after their count.
void put_solutions(std::vector<double>& o, SmSolutionArray& sols, const SmSurface* srf)
{
o.push_back((double)sols.GetSize());
for (ULONG i = 0; i < sols.GetSize(); i++)
{
const SmSolution& s = sols[i];
o.push_back((double)s.m_eSolutionType);
o.push_back(s.m_vStart.m_adParameters[0]);
o.push_back(s.m_vStart.m_adParameters[1]);
o.push_back(s.m_vStart.m_dSolutionValue);
o.push_back((double)s.m_lNumObjects);
o.push_back((double)s.m_lNumVariables);
o.push_back(flg(s.m_apObjects[0] == (const SmObject*)srf));
}
}
void put_state(std::vector<double>& o, SmSurfPtSolveStepper& s)
{
put_uv(o, s.UV());
o.push_back((double)s.BoundHitsU());
o.push_back((double)s.BoundHitsV());
o.push_back(flg(s.NRStepSuspect() != FALSE));
o.push_back((double)s.IterCount());
}
void put_solution(std::vector<double>& o, const SmSolution& s, const SmSurface* surface)
{
o.push_back((double)s.m_eSolutionType);
o.push_back(s.m_vStart.m_adParameters[0]);
o.push_back(s.m_vStart.m_adParameters[1]);
o.push_back(s.m_vStart.m_dSolutionValue);
o.push_back((double)s.m_lNumObjects);
o.push_back((double)s.m_lNumVariables);
o.push_back(flg(s.m_apObjects[0] == (const SmObject*)surface));
}
void run(int op, Rd& rd, const SmContext& ctx, std::vector<double>& o)
{
Problem p = read_problem(rd, ctx);
SmBSplineSurface* b = dynamic_cast<SmBSplineSurface*>(p.srf);
bool before = b && b->GetOutOfBoundsEnabled();
if (op == 0)
{
bool has_target = rd.flag();
double target = rd.num();
SmSolutionRequestedType request = (SmSolutionRequestedType)rd.idx();
SmSolutionArray solutions;
SmStatus result = p.srf->sc_spsp3_GlobalPointSolve_1(p.dom, p.op, p.test, p.tol,
has_target ? &target : NULL, request, solutions);
o.push_back(st(result));
put_solutions(o, solutions, p.srf);
o.push_back(flg(!b || (b->GetOutOfBoundsEnabled() != FALSE) == before));
}
else if (op == 1)
{
SmPoint2d guess = rd.uv();
SmBoolean found = FALSE;
SmSolution solution;
SmTerminationReasonType reason = SM_TR_STILL_ITERATING;
int iterations = -1;
SmStatus result = p.srf->sc_spsp3_LocalPointSolve_1(p.dom, p.op, p.test, guess,
found, solution, reason, iterations);
o.push_back(st(result));
o.push_back(flg(found != FALSE));
o.push_back((double)reason);
o.push_back((double)iterations);
put_solution(o, solution, p.srf);
o.push_back(flg(!b || (b->GetOutOfBoundsEnabled() != FALSE) == before));
}
else
{
SmPoint2d uv = rd.uv();
double tight = rd.num();
SmPoint2d next_uv = rd.uv();
int iter = (int)rd.idx();
int hu = (int)rd.idx(), hv = (int)rd.idx();
SmSurfPtSolveStepper previous(p.srf, p.dom, p.test, uv, tight, p.tol, p.op);
previous.SetIterCount(iter);
previous.m_iBndHitU = hu;
previous.m_iBndHitV = hv;
SmSurfPtSolveStepper next(previous);
next.SetUV(next_uv);
if (op == 2)
{
SmBoolean found = FALSE;
SmStatus result = next.FindBoundarySolution(found);
o.push_back(st(result));
o.push_back(flg(found != FALSE));
}
else if (op == 3)
{
SmTerminationReasonType reason = SM_TR_STILL_ITERATING;
SmStatus result = next.FindNextStep(previous, reason);
o.push_back(st(result));
o.push_back((double)reason);
}
else
{
SmBoolean found = TRUE;
SmBoolean processed = next.CheckCrease(previous, found);
o.push_back(0.0);
o.push_back(flg(processed != FALSE));
o.push_back(flg(found != FALSE));
}
put_state(o, next);
}
delete p.srf;
}
} // namespace sc_spsp3
SC_EXPORT int32_t sc_surface_surface_point_solve_p3_eval(int32_t op, const double* in,
int64_t n_in, double* out, int64_t cap, int64_t* out_len)
{
if (!in || n_in < 1 || !out_len || op < 0 || op > 4) return SC_ERR_ARG;
SC_TRY
SmContext ctx;
std::vector<double> output;
sc_spsp3::Rd reader{in, 0};
sc_spsp3::run(op, reader, ctx, output);
return sc_copy_out(output.data(), (int64_t)output.size(), out, cap, out_len);
SC_CATCH
}