Files
solvecraft/harness/cpp/shim/intersect_ray_tracer.cpp
T
zorak1103 db779b8780 wire: intersect.ray_tracer AddSurfaceToGrid (unit to green)
RayTracer::add_surface_to_grid returned Unported(smsurf_GetSurfaceCache)
because Grid::expand_hierarchy holds a bare surface handle. It now builds the
surface cache (nodes hold the surface by its tracer index), clones its tree and
expands the hierarchy element with Grid::expand_hierarchy_with, reading each
leaf's patch data, pseudo box and polar box from the cache, as
SmSurfaceHierarchyElement::ExpandHierarchy / SmSurfaceGridElement::Init do.
add_surface_with_bbox now returns the HierarchyId and no longer expands.

The C++ shim now runs the real SmRayTracer::AddSurfaceToGrid and both sides
record its status and, per leaf, the domain, box, mid-point data, pseudo box and
polar box, so the case is compared instead of vacuous (this caught a cache built
without a surface handle leaving every polar box uninitialised). Ray-walk ids are
reported as positions among the add_leaf leaves. The generator puts add_surface
before the rays; the fixture case is renamed add_surface_to_grid_then_fire and
gets two fire ops.

difftest intersect.ray_tracer: 421 cases, 421 pass, 0 deferred, max_rel_err 0.
blend.hcr (second caller): 386 pass / 48 deferred -> 396 pass / 38 deferred.
Grid::expand_hierarchy on a bare handle (geom.grid) stays a dispatch gap.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 13:03:11 +02:00

499 lines
18 KiB
C++

// SPDX-License-Identifier: Apache-2.0
// difftest shim for unit `intersect.ray_tracer`: SmRayTracer and SmLSI3LocalSolver
// (source/SMLib/src/SmRayTracer.cpp, source/SMLib/inc/SmRayTracer.h).
//
// One entry point, sc_intersect_ray_tracer_run(in, n_in, out, cap, out_len): builds real
// SmPlane / SmBSplineSurface surfaces, an SmGrid, and an SmRayTracer owning the grid, then
// runs ops against them and returns one flat record. The layout is documented in
// crates/solidcore-ffi/src/units/intersect_ray_tracer.rs, which validates every input; reads are
// bounds checked here too (a bad program is SC_ERR_ARG).
//
// Surface leaves: ADD_LEAF fills an SmSurfaceGridElement the way SmGrid::AddElement + Init do,
// 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.
#include "sc_shim.h"
#include <SmAxis2Placement.h>
#include <SmBSplineSurface.h>
#include <SmContext.h>
#include <SmExtent1d.h>
#include <SmExtent2d.h>
#include <SmExtent3d.h>
#include <SmMemBlockMgr.h>
#include <SmNurbsSrf.h>
#include <SmPlane.h>
#include <SmPolarBox.h>
#include <SmPseudoBox.h>
#include <SmSArray.h>
#include <SmSolutionArray.h>
#include <SmSurface.h>
#include <SmTArray.h>
#include <SmTree.h>
#include <SmVector2d.h>
#include <SmVector3d.h>
#include <nurbs.h>
#include <stdexcept>
#include <vector>
#define protected public
#include <SmGrid.h>
#undef protected
#include <SmRayTracer.h>
#include "sc_sm.h"
using sc::st;
namespace
{
enum : int32_t
{
OP_SOLVER_TOL = 1,
OP_ADD_LEAF = 2,
OP_FIRE = 3,
OP_LOCAL = 4,
OP_ADD_SURFACE = 5,
OP_RENDERER = 6,
OP_LIGHT = 7,
OP_DO_RENDER = 8,
OP_RENDER_PIXEL = 9,
OP_RENDER_RAY = 10,
};
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);
}
SmVector2d uv()
{
double x = num();
double y = num();
return SmVector2d(x, y);
}
SmExtent2d dom4()
{
double a = num();
double b = num();
double c = num();
double d = num();
return SmExtent2d(a, b, c, d);
}
SmExtent3d ext6()
{
double a = num();
double b = num();
double c = num();
double d = num();
double e = num();
double f = num();
return SmExtent3d(a, b, c, d, e, f);
}
};
// `1 n m p q cpts(x y z w)` clamped Bezier-knot B-spline surface: degree p x q, (p+1)(q+1) points,
// knots 0..0 1..1; or `kind 1 o x y scale[2] dom[4]`, an SmPlane.
SmSurface* read_surface(Rd& rd, const SmContext& ctx)
{
int64_t kind = rd.idx(0, 1);
if (kind == 0)
{
gw_DEGREE p = (gw_DEGREE)rd.idx(1, 3);
gw_DEGREE q = (gw_DEGREE)rd.idx(1, 3);
gw_INDEX n = p;
gw_INDEX m = q;
gw_SURFACE* sur = sm_AllocateNurbSurface(n, m, p, q, n + p + 1, m + q + 1);
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 <= n + p + 1; i++)
sur->knu->U[i] = i <= p ? 0.0 : 1.0;
for (gw_INDEX i = 0; i <= m + q + 1; i++)
sur->knv->U[i] = i <= q ? 0.0 : 1.0;
return new (ctx) SmBSplineSurface(sur, FALSE, &ctx);
}
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);
}
void put3(std::vector<double>& o, const SmVector3d& v)
{
o.push_back(v.x);
o.push_back(v.y);
o.push_back(v.z);
}
// 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<double>& o, const SmSolution& s, const std::vector<SmSurface*>& surfs)
{
o.push_back((double)s.m_eSolutionType);
o.push_back((double)s.m_lNumVariables);
o.push_back((double)s.m_lNumObjects);
o.push_back(s.m_vStart.m_dSolutionValue);
for (int i = 0; i < 10; i++)
o.push_back(s.m_vStart[i]);
double surf = -1.0;
for (size_t i = 0; i < surfs.size(); i++)
if (s.m_apObjects[0] == (SmObject*)surfs[i])
surf = (double)i;
if (surf < 0.0 && s.m_apObjects[0] != nullptr)
surf = -2.0;
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);
}
int32_t run(const double* p, int64_t n, std::vector<double>& o)
{
Rd rd{p, n, 0};
SmContext sCtx;
std::vector<SmSurface*> surfs;
SmRayTracer* rt = nullptr;
SmRayRenderer* renderer = nullptr;
std::vector<SmSurfaceGridElement*> leaves;
int32_t rc = SC_OK;
try
{
int64_t nsurf = rd.idx(1, 4);
for (int64_t i = 0; i < nsurf; i++)
surfs.push_back(read_surface(rd, sCtx));
SmExtent3d gbox = rd.ext6();
ULONG lSize[3];
for (int i = 0; i < 3; i++)
lSize[i] = (ULONG)rd.idx(1, 20);
double rayTol = rd.num();
double rayAcc = rd.num();
SmGrid* g = new (sCtx) SmGrid(gbox, lSize);
rt = new (sCtx) SmRayTracer(sCtx, g, rayTol, rayAcc);
int64_t nops = rd.idx(0, 64);
for (int64_t k = 0; k < nops; k++)
{
int op = (int)rd.idx(1, 10);
switch (op)
{
case OP_SOLVER_TOL:
rt->SetSolverTolerance(rd.num());
break;
case OP_ADD_LEAF:
{
SmSurface* surf = surfs.at((size_t)rd.idx(0, nsurf - 1));
SmExtent2d dom = rd.dom4();
SmExtent3d bbox = rd.ext6();
SmExtent3d pseudoExt = rd.ext6();
bool rotated = rd.flag();
SmPseudoBox pseudo(pseudoExt);
if (rotated)
{
SmVector3d bx = rd.vec();
SmVector3d by = rd.vec();
SmVector3d bz = rd.vec();
SmExtent1d ix(pseudoExt.GetMin(0), pseudoExt.GetMax(0));
SmExtent1d iy(pseudoExt.GetMin(1), pseudoExt.GetMax(1));
SmExtent1d iz(pseudoExt.GetMin(2), pseudoExt.GetMax(2));
pseudo = SmPseudoBox(bx, by, bz, ix, iy, iz);
}
int64_t np = rd.idx(1, 8);
SmPolarBox polar(rd.vec());
for (int64_t i = 1; i < np; i++)
polar.AddVector3d(rd.vec(), NULL);
SmExtent3d elemBox = rd.ext6();
if (leaves.size() >= 64)
throw Bad();
// SmGrid::AddElement(SM_GE_SURFACE) with SmSurfaceGridElement::Init
SmSurfaceGridElement* pSGE = (SmSurfaceGridElement*)g->m_sSurfaceElementMgr.GetNewElement();
pSGE->m_lMark = 0;
pSGE->m_lType = SmSurfaceGridElement_TYPE;
pSGE->m_pSurface = surf;
pSGE->m_sUVDomain = dom;
pSGE->m_sBBox = bbox;
pSGE->m_sPseudoBox = pseudo;
pSGE->m_sPolarBox = polar;
SmPoint2d sUVMid = pSGE->m_sUVDomain.Evaluate(0.5, 0.5);
SmVector3d sEval[2][2];
surf->Evaluate(sUVMid, 1, 1, TRUE, TRUE, TRUE, sEval[0]);
pSGE->m_sMidPoint = sEval[0][0];
pSGE->m_sMidDU = sEval[1][0];
pSGE->m_sMidDV = sEval[0][1];
pSGE->m_sMidNormal = pSGE->m_sMidDU * pSGE->m_sMidDV;
if (pSGE->m_sMidNormal.LengthSquared() > SM_EFF_ZERO)
pSGE->m_sMidNormal.Unitize();
else
surf->EvaluateNormal(sUVMid, TRUE, TRUE, pSGE->m_sMidNormal);
SmGridElement* pGE = pSGE;
elemBox.ComputeSphereBound(pGE->m_sSphereCenter, pGE->m_dSphereRadius);
pGE->m_sBBox = elemBox;
SmVoxel* sVData[256];
SmTArray<SmVoxel*> sVoxels(256, sVData);
g->FindVoxelsInBox(elemBox, sVoxels);
for (ULONG i = 0; i < sVoxels.GetSize(); i++)
{
SmVoxel* pVoxel = sVoxels[i];
if (pVoxel->m_pElements == NULL)
pVoxel->m_pElements = new (sCtx) SmTArray<SmGridElement*>(sCtx);
pVoxel->m_pElements->Add(pGE);
}
leaves.push_back(pSGE);
break;
}
case OP_FIRE:
{
SmPoint3d rp = rd.vec();
SmVector3d rv = rd.vec();
double maxT = rd.num();
rt->m_bHitAnyThing = rd.flag();
int64_t skip = rd.idx(-1, 63);
if (skip >= (int64_t)leaves.size())
throw Bad();
rt->m_pSkipElement = skip < 0 ? NULL : leaves[(size_t)skip];
SmBoolean bHit = FALSE;
SmSolution sol;
SmTArray<SmGridElement*> ge;
SmTArray<double> mn, mx;
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);
o.push_back((double)ge.GetSize());
for (ULONG i = 0; i < ge.GetSize(); i++)
{
double id = -1.0;
for (size_t j = 0; j < leaves.size(); j++)
if (ge[i] == (SmGridElement*)leaves[j])
id = (double)j;
o.push_back(id);
}
for (ULONG i = 0; i < mn.GetSize(); i++)
o.push_back(mn[i]);
for (ULONG i = 0; i < mx.GetSize(); i++)
o.push_back(mx[i]);
break;
}
case OP_LOCAL:
{
int64_t li = rd.idx(0, 63);
if (li >= (int64_t)leaves.size())
throw Bad();
SmPoint3d lp = rd.vec();
SmVector3d lv = rd.vec();
double d3d = rd.num();
double distTol = rd.num();
double acc = rd.num();
bool rayFire = rd.flag();
bool haveT = rd.flag();
double tmin = rd.num();
double tmax = rd.num();
bool hasIvl = rd.flag();
double i0 = rd.num();
double i1 = rd.num();
SmSurfaceGridElement* leaf = leaves[(size_t)li];
SmExtent1d ivl(i0, i1);
SmLSI3LocalSolver solver(NULL, leaf->m_sUVDomain, lp, lv, hasIvl ? &ivl : NULL, rayFire, distTol,
acc);
solver.m_d3dTolerance = d3d;
solver.m_bHaveTMinTMax = haveT;
solver.m_dTMinOfBBox = tmin;
solver.m_dTMaxOfBBox = tmax;
SmBoolean bNeeds = TRUE, bFound = TRUE;
SmSolution sol;
SmStatus s = solver.LocalSolve(leaf, bNeeds, bFound, sol);
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);
break;
}
case OP_ADD_SURFACE:
{
SmSurface* surf = surfs.at((size_t)rd.idx(0, nsurf - 1));
SmTArray<void*> before;
g->m_sSurfaceElementMgr.GetActiveElements(before);
SmStatus s = rt->AddSurfaceToGrid(surf);
SmTArray<void*> after;
g->m_sSurfaceElementMgr.GetActiveElements(after);
// status, the number of surface leaves it made, and per leaf (51 values, layout in
// the ffi module) the domain, box, mid-point data, pseudo box and polar box (the
// leaves are not in `leaves`: ids of a ray walk say -1)
o.push_back(st(s));
o.push_back((double)(after.GetSize() - before.GetSize()));
for (ULONG i = before.GetSize(); i < after.GetSize(); i++)
{
SmSurfaceGridElement* e = (SmSurfaceGridElement*)after[i];
o.push_back(e->m_sUVDomain.GetMin().x);
o.push_back(e->m_sUVDomain.GetMin().y);
o.push_back(e->m_sUVDomain.GetMax().x);
o.push_back(e->m_sUVDomain.GetMax().y);
put3(o, e->m_sBBox.GetMin());
put3(o, e->m_sBBox.GetMax());
put3(o, e->m_sMidPoint);
put3(o, e->m_sMidDU);
put3(o, e->m_sMidDV);
put3(o, e->m_sMidNormal);
// the pseudo box: basis and intervals
SmVector3d b[3];
e->m_sPseudoBox.GetBasis(b[0], b[1], b[2]);
for (int j = 0; j < 3; j++)
put3(o, b[j]);
SmExtent1d iv[3];
e->m_sPseudoBox.GetIntervals(iv[0], iv[1], iv[2]);
for (int j = 0; j < 3; j++)
{
o.push_back(iv[j].GetMin());
o.push_back(iv[j].GetMax());
}
// the polar box: state, and domain and basis once it has a bound
int ps = (int)e->m_sPolarBox.GetPolarBoxState();
o.push_back((double)ps);
bool bounded = ps >= SM_PS_SINGLE_POINT && ps <= SM_PS_REGION;
SmExtent2d pd = e->m_sPolarBox.GetDomain();
o.push_back(bounded ? pd.GetMin().x : 0.0);
o.push_back(bounded ? pd.GetMin().y : 0.0);
o.push_back(bounded ? pd.GetMax().x : 0.0);
o.push_back(bounded ? pd.GetMax().y : 0.0);
for (ULONG j = 0; j < 3; j++)
{
if (bounded)
put3(o, *e->m_sPolarBox.GetBasis(j));
else
put3(o, SmVector3d(0.0, 0.0, 0.0));
}
}
break;
}
case OP_RENDERER:
{
if (renderer != nullptr)
throw Bad();
SmPoint3d o = rd.vec();
SmVector3d x = rd.vec();
SmVector3d y = rd.vec();
SmAxis2Placement place(o, x, y);
SmExtent3d view = rd.ext6();
ULONG nx = (ULONG)rd.idx(1, 8);
ULONG ny = (ULONG)rd.idx(1, 8);
ULONG rate = (ULONG)rd.idx(1, 3);
renderer = new (sCtx) SmRayRenderer(rt, place, view, nx, ny);
renderer->SetSampleRate(rate);
break;
}
case OP_LIGHT:
{
if (renderer == nullptr)
throw Bad();
SmVector3d v = rd.vec();
bool pt = rd.flag();
renderer->AddLightSource(new (sCtx) SmLightSource(v, pt));
break;
}
case OP_DO_RENDER:
{
if (renderer == nullptr)
throw Bad();
bool shadows = rd.flag();
SmVector3d bg = rd.vec();
SmVector3d obj = rd.vec();
SmStatus s = renderer->DoRender(shadows, bg, obj);
o.push_back(st(s));
o.push_back((double)renderer->GetPixelsShaded());
break;
}
case OP_RENDER_PIXEL:
{
if (renderer == nullptr)
throw Bad();
ULONG px = (ULONG)rd.idx(0, 7);
ULONG py = (ULONG)rd.idx(0, 7);
SmVector3d c;
SmStatus s = renderer->RenderPixel(px, py, c);
o.push_back(st(s));
put3(o, c);
o.push_back((double)renderer->GetPixelsShaded());
break;
}
case OP_RENDER_RAY:
{
if (renderer == nullptr)
throw Bad();
SmVector3d rp = rd.vec();
SmVector3d rv = rd.vec();
SmVector3d c;
SmStatus s = renderer->RenderRay(rp, rv, c);
o.push_back(st(s));
put3(o, c);
o.push_back((double)renderer->GetPixelsShaded());
break;
}
default:
throw Bad();
}
}
}
catch (const Bad&)
{
rc = SC_ERR_ARG;
}
if (renderer != nullptr)
delete renderer; // deletes the tracer and the lights
else
delete rt; // deletes the grid, not the surfaces
for (SmSurface* s : surfs)
delete s;
return rc;
}
} // namespace
SC_EXPORT int32_t sc_intersect_ray_tracer_run(const double* in, int64_t n_in, double* out, int64_t cap,
int64_t* out_len)
{
SC_TRY
std::vector<double> o;
int32_t s = run(in, n_in, o);
if (s != SC_OK)
return s;
return sc_copy_out(o.data(), (int64_t)o.size(), out, cap, out_len);
SC_CATCH
}