mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-08 13:01:52 +00:00
c92c03e83b
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
240 lines
9.4 KiB
C++
240 lines
9.4 KiB
C++
// SPDX-License-Identifier: Apache-2.0
|
|
// difftest shim for unit `nlib.basis_basic`.
|
|
// Wraps routines of upstream source/SMLib/src/NL_BasisBasic.cpp:
|
|
// N_BasisFindGlobalMax, N_BasisFindAllSpanMaxima, N_BasisReparam,
|
|
// N_GetCompatibleKnotArray, N_GetCompatibleKnotVectorToTol (+ ST_notdone),
|
|
// N_BasisDerivs, N_MakeKnotsCompatible.
|
|
//
|
|
// Status convention (as nlib_knots.cpp): 0 = ok; when the upstream routine returns
|
|
// its error flag, the NLib error number NL_ERROR.eno (cleared before the call; 1000
|
|
// if the flag was set without a number); SC_ERR_* for shim problems.
|
|
// The Rust wrapper (crates/solidcore-ffi/src/units/nlib_basis_basic.rs) validates
|
|
// every argument first; the checks here are a second line of defence.
|
|
//
|
|
// Defensive copies: upstream N_GetCompatibleKnotVectorToTol may read U[-1] of a knot
|
|
// vector, and N_MakeKnotsCompatible may read a few entries past U[m] on unusual
|
|
// inputs; the knot buffers handed to them carry a leading slot / trailing +inf slots
|
|
// so those reads stay inside allocated memory (they never change the results).
|
|
#include "sc_shim.h"
|
|
|
|
#include <cmath>
|
|
#include <limits>
|
|
#include <vector>
|
|
|
|
#include <nurbs.h>
|
|
#include <NL_Globals.h>
|
|
|
|
namespace
|
|
{
|
|
int32_t nl_status(NL_FLAG err)
|
|
{
|
|
if (err == NL_NO)
|
|
return SC_OK;
|
|
return NL_ERROR.eno != 0 ? (int32_t)NL_ERROR.eno : 1000;
|
|
}
|
|
|
|
NL_KNOTVECTOR make_knt(const double* knots, int64_t n_knots)
|
|
{
|
|
NL_KNOTVECTOR knt;
|
|
N_KnotVectorFromRealArray(&knt, const_cast<NL_REAL*>(knots), (NL_INDEX)(n_knots - 1));
|
|
return knt;
|
|
}
|
|
|
|
bool valid_flag(int32_t flag) { return flag == NL_LEFT || flag == NL_RIGHT; }
|
|
|
|
bool basis_domain(int64_t n_knots, int32_t degree)
|
|
{
|
|
return degree >= 0 && degree <= 1000 && n_knots >= 2 * (int64_t)degree + 2;
|
|
}
|
|
|
|
const int64_t MC_PAD = 8; // trailing +inf entries for N_MakeKnotsCompatible
|
|
} // namespace
|
|
|
|
// N_BasisFindGlobalMax(knt, i, p, tol) -> out[0] = max, out[1] = u.
|
|
SC_EXPORT int32_t sc_nlib_basis_basic_global_max(const double* knots, int64_t n_knots, int64_t index,
|
|
int32_t degree, double tol, double* out)
|
|
{
|
|
if (!basis_domain(n_knots, degree) || index < 0)
|
|
return SC_ERR_ARG;
|
|
SC_TRY
|
|
NL_KNOTVECTOR knt = make_knt(knots, n_knots);
|
|
NL_REAL mx = 0.0, u = 0.0;
|
|
N_ErrClear();
|
|
NL_FLAG err = N_BasisFindGlobalMax(&knt, (NL_INDEX)index, (NL_DEGREE)degree, tol, &mx, &u);
|
|
out[0] = mx;
|
|
out[1] = u;
|
|
return nl_status(err);
|
|
SC_CATCH
|
|
}
|
|
|
|
// N_BasisFindAllSpanMaxima(knt, i, p, tol) -> out_min[0..=p], out_max[0..=p], out_u.
|
|
SC_EXPORT int32_t sc_nlib_basis_basic_span_maxima(const double* knots, int64_t n_knots, int64_t index,
|
|
int32_t degree, double tol, double* out_min,
|
|
double* out_max, int64_t cap, double* out_u)
|
|
{
|
|
if (!basis_domain(n_knots, degree) || index < 0 || cap < degree + 1)
|
|
return SC_ERR_ARG;
|
|
SC_TRY
|
|
NL_KNOTVECTOR knt = make_knt(knots, n_knots);
|
|
NL_REAL u = 0.0;
|
|
N_ErrClear();
|
|
NL_FLAG err = N_BasisFindAllSpanMaxima(&knt, (NL_INDEX)index, (NL_DEGREE)degree, tol, out_min, out_max, &u);
|
|
*out_u = u;
|
|
return nl_status(err);
|
|
SC_CATCH
|
|
}
|
|
|
|
// N_BasisReparam(knt, p, [ul, ur]) on a copy of knots -> out_knots[0..n_knots).
|
|
SC_EXPORT int32_t sc_nlib_basis_basic_reparam(const double* knots, int64_t n_knots, int32_t degree,
|
|
double ul, double ur, double* out_knots)
|
|
{
|
|
if (n_knots < 1 || degree < 0 || degree > 32767 || n_knots < (int64_t)degree + 1)
|
|
return SC_ERR_ARG;
|
|
SC_TRY
|
|
for (int64_t i = 0; i < n_knots; ++i)
|
|
out_knots[i] = knots[i];
|
|
NL_KNOTVECTOR knt = make_knt(out_knots, n_knots);
|
|
NL_INTERVAL I;
|
|
N_CreateInterval(&I, ul, ur);
|
|
N_BasisReparam(&knt, (NL_DEGREE)degree, I);
|
|
return SC_OK;
|
|
SC_CATCH
|
|
}
|
|
|
|
// N_GetCompatibleKnotVectorToTol(knt[0..n_vec), n_vec-1, tol) (or, with use_array != 0,
|
|
// N_GetCompatibleKnotArray, tol ignored). Input vectors are concatenated in `flat` with
|
|
// lengths `lens`. Writes the (adjusted) input knots to out_knots (same layout), the new
|
|
// knots of vector r to out_new[r*new_cap ..] with count out_new_lens[r].
|
|
SC_EXPORT int32_t sc_nlib_basis_basic_compatible(const double* flat, const int64_t* lens, int64_t n_vec,
|
|
double tol, int32_t use_array, double* out_knots,
|
|
double* out_new, int64_t new_cap, int64_t* out_new_lens)
|
|
{
|
|
if (n_vec < 1)
|
|
return SC_ERR_ARG;
|
|
int64_t total = 0;
|
|
for (int64_t r = 0; r < n_vec; ++r)
|
|
{
|
|
if (lens[r] < 1 || lens[r] < n_vec - 1)
|
|
return SC_ERR_ARG;
|
|
total += lens[r] - 1;
|
|
}
|
|
if (new_cap < total + 1)
|
|
return SC_ERR_ARG;
|
|
SC_TRY
|
|
// One leading slot per vector: upstream may read U[-1].
|
|
std::vector<std::vector<double>> bufs((size_t)n_vec);
|
|
std::vector<NL_KNOTVECTOR> kv((size_t)n_vec);
|
|
std::vector<NL_KNOTVECTOR*> kp((size_t)n_vec);
|
|
int64_t off = 0;
|
|
for (int64_t r = 0; r < n_vec; ++r)
|
|
{
|
|
bufs[r].assign((size_t)lens[r] + 1, 0.0);
|
|
for (int64_t i = 0; i < lens[r]; ++i)
|
|
bufs[r][(size_t)i + 1] = flat[off + i];
|
|
off += lens[r];
|
|
N_KnotVectorFromRealArray(&kv[r], bufs[r].data() + 1, (NL_INDEX)(lens[r] - 1));
|
|
kp[r] = &kv[r];
|
|
}
|
|
NL_STACKS SK;
|
|
N_InitNurbs(&SK);
|
|
NL_KNOTVECTOR** knx = nullptr;
|
|
N_ErrClear();
|
|
NL_FLAG err = use_array != 0 ? N_GetCompatibleKnotArray(kp.data(), (NL_INDEX)(n_vec - 1), &knx, &SK)
|
|
: N_GetCompatibleKnotVectorToTol(kp.data(), (NL_INDEX)(n_vec - 1), tol, &knx, &SK);
|
|
int32_t st = nl_status(err);
|
|
off = 0;
|
|
for (int64_t r = 0; r < n_vec; ++r)
|
|
{
|
|
for (int64_t i = 0; i < lens[r]; ++i)
|
|
out_knots[off + i] = bufs[r][(size_t)i + 1];
|
|
off += lens[r];
|
|
out_new_lens[r] = 0;
|
|
}
|
|
if (st == SC_OK && knx != nullptr)
|
|
{
|
|
for (int64_t r = 0; r < n_vec; ++r)
|
|
{
|
|
NL_INDEX m = -1;
|
|
NL_REAL* U = nullptr;
|
|
N_KnotVectorGetKnots(knx[r], &m, &U);
|
|
int64_t cnt = (int64_t)m + 1;
|
|
if (cnt > new_cap)
|
|
{
|
|
st = SC_ERR_BUFFER;
|
|
break;
|
|
}
|
|
for (int64_t i = 0; i < cnt; ++i)
|
|
out_new[r * new_cap + i] = U[i];
|
|
out_new_lens[r] = cnt;
|
|
}
|
|
}
|
|
N_EndNurbs(&SK);
|
|
return st;
|
|
SC_CATCH
|
|
}
|
|
|
|
// N_BasisDerivs(knt, p, u, flg, der) -> out_nd[k*(p+1) + j] (k = 0..=der), out_span.
|
|
SC_EXPORT int32_t sc_nlib_basis_basic_derivs(const double* knots, int64_t n_knots, int32_t degree, double u,
|
|
int32_t flag, int64_t der, double* out_nd, int64_t cap,
|
|
int64_t* out_span)
|
|
{
|
|
if (!basis_domain(n_knots, degree) || !valid_flag(flag) || der < 0 || der > 1000 ||
|
|
cap < (der + 1) * ((int64_t)degree + 1))
|
|
return SC_ERR_ARG;
|
|
SC_TRY
|
|
NL_KNOTVECTOR knt = make_knt(knots, n_knots);
|
|
std::vector<NL_REAL*> rows((size_t)der + 1);
|
|
for (int64_t k = 0; k <= der; ++k)
|
|
rows[(size_t)k] = out_nd + k * ((int64_t)degree + 1);
|
|
NL_INDEX spn = -1;
|
|
N_ErrClear();
|
|
NL_FLAG err = N_BasisDerivs(&knt, (NL_DEGREE)degree, u, (NL_FLAG)flag, (NL_INDEX)der, rows.data(), &spn);
|
|
*out_span = (int64_t)spn;
|
|
return nl_status(err);
|
|
SC_CATCH
|
|
}
|
|
|
|
// N_MakeKnotsCompatible(R, S, p, q) on copies -> out_r, out_s (rescaled knots),
|
|
// out_xr[0..*out_nxr), out_xs[0..*out_nxs) (capacity cap each).
|
|
SC_EXPORT int32_t sc_nlib_basis_basic_make_compatible(const double* r, int64_t n_r, const double* s, int64_t n_s,
|
|
int32_t p, int32_t q, double* out_r, double* out_s,
|
|
double* out_xr, int64_t* out_nxr, double* out_xs,
|
|
int64_t* out_nxs, int64_t cap)
|
|
{
|
|
if (n_r < 1 || n_s < 1 || p < 0 || q < 0 || p > 32767 || q > 32767 || n_r < (int64_t)p + 1 ||
|
|
n_s < (int64_t)q + 1 || cap < n_r + n_s + 2 * MC_PAD)
|
|
return SC_ERR_ARG;
|
|
SC_TRY
|
|
const double inf = std::numeric_limits<double>::infinity();
|
|
std::vector<double> rb((size_t)(n_r + MC_PAD), inf), sb((size_t)(n_s + MC_PAD), inf);
|
|
for (int64_t i = 0; i < n_r; ++i)
|
|
rb[(size_t)i] = r[i];
|
|
for (int64_t i = 0; i < n_s; ++i)
|
|
sb[(size_t)i] = s[i];
|
|
// Upstream writes the new knots into caller memory without a bound: give it room
|
|
// for every index it can reach (each write advances i or j past a real knot).
|
|
std::vector<double> xr((size_t)cap), xs((size_t)cap);
|
|
NL_KNOTVECTOR knr = make_knt(rb.data(), n_r), kns = make_knt(sb.data(), n_s);
|
|
NL_KNOTVECTOR kxr, kxs;
|
|
N_KnotVectorFromRealArray(&kxr, xr.data(), (NL_INDEX)(cap - 1));
|
|
N_KnotVectorFromRealArray(&kxs, xs.data(), (NL_INDEX)(cap - 1));
|
|
N_ErrClear();
|
|
N_MakeKnotsCompatible(&knr, &kns, (NL_DEGREE)p, (NL_DEGREE)q, &kxr, &kxs);
|
|
NL_INDEX mxr = -1, mxs = -1;
|
|
NL_REAL* U = nullptr;
|
|
N_KnotVectorGetKnots(&kxr, &mxr, &U);
|
|
N_KnotVectorGetKnots(&kxs, &mxs, &U);
|
|
for (int64_t i = 0; i < n_r; ++i)
|
|
out_r[i] = rb[(size_t)i];
|
|
for (int64_t i = 0; i < n_s; ++i)
|
|
out_s[i] = sb[(size_t)i];
|
|
*out_nxr = (int64_t)mxr + 1;
|
|
*out_nxs = (int64_t)mxs + 1;
|
|
for (int64_t i = 0; i <= mxr; ++i)
|
|
out_xr[i] = xr[(size_t)i];
|
|
for (int64_t i = 0; i <= mxs; ++i)
|
|
out_xs[i] = xs[(size_t)i];
|
|
return SC_OK;
|
|
SC_CATCH
|
|
}
|