// SPDX-License-Identifier: Apache-2.0 // difftest shim for unit `surface.surface_p5`: the SmSurface members owned by the unit // (source/SMLib/src/SmSurface.cpp, source/SMLib/inc/SmSurface.h), called through `SmSurface*` // on SmBSplineSurface, SmPlane and SmCurveBoundedSurface objects (virtual dispatch as // upstream), plus the file-static functions sm_FilterSolution and sm_SpLocalSolve and the // IdenticalSolutions overrides of the four surface global solvers. // // Static functions and classes local to SmSurface.cpp cannot be linked against, so they are // VERBATIM copies of upstream SmSurface.cpp (commit c9c5979), inside the namespace sc_srf_p5 // (line ranges noted at each block). // sm_SpLocalSolve takes the surface sm_GetSurfaceToEvaluate would return instead of the tree // node and Bezier patch (lines marked ADAPTED), as the port does. The four solver classes keep // their data members and IdenticalSolutions; constructors set m_pSurf from the tree's owner and // LocalSolve is an SM_ERR stub (it belongs to other units). // // One entry point, sc_surface_surface_p5_eval(op, in, n_in, out, cap, out_len). Layouts are // documented in crates/solidcore-ffi/src/units/surface_surface_p5.rs, which validates every // input. A surface block is as in unit surface.surface_p7. `out` receives the op status // (0 = SM_SUCCESS, else the SmStatus value) and the op's outputs. #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 "sc_sm.h" using sc::put_uv; using sc::put_vec; namespace sc_srf_p5 { // clang-format off #include "surface_sp_local_solve.hpp" // ---- verbatim: SmSurface.cpp lines 12733-13043: sm_FilterSolution static SmStatus sm_FilterSolution (const SmSurface * cpBSS, // in : target surface const SmExtent2d & crUVDomain, // in : surface intersection domain const SmCurve & crCurve, // in : target curve const SmExtent1d & crInterval, // in : curve intersection interval const SmContinuityType aeUConts[2], // in : Surface U boundary continuity type const SmContinuityType aeVConts[2], // in : Surface V boundary continuity type const SmContinuityType aeTConts[2], // in : Curve ends continuity type const SmSolution & crSolution, // in : Current solution SmBoolean & rbKeepSolution) // out: TRUE = Keep this solution { // init output rbKeepSolution = TRUE; #ifdef SM_DEBUG_CODE SmBoolean bDebugMe = FALSE ; // draw if(bDebugMe) { SmFace *pFace = (SmFace *)cpBSS->GetFace() ; SmEdge *pEdge = (SmEdge *)crCurve.GetEdge() ; SmBrep *pBrep1 = pFace ? pFace->GetBrep() : NULL ; SmBrep *pBrep2 = pEdge ? pEdge->GetBrep() : NULL ; smgfx_Erase() ; smgfx_SetLook(1,2, 0,0,1) ; if(pBrep1) pBrep1->Draw(TRUE) ; sm_GraphicsLoop() ; smgfx_SetLook(1,2, 0,1,0) ; if(pBrep2) pBrep2->Draw(TRUE) ; sm_GraphicsLoop() ; smgfx_SetLook(1,2, 0,1,1) ; if(cpBSS) cpBSS->DrawUV() ; sm_GraphicsLoop() ; smgfx_SetLook(3,4, 1,1,0) ; crCurve.Draw() ; sm_GraphicsLoop() ; smgfx_SetLook(5,6, 1,0,0) ; crSolution.Draw() ; sm_GraphicsLoop() ; sm_GraphicsLoop() ; } #endif // SM_DEBUG_CODE // keep answers good to Zero if (crSolution.m_vStart.m_dSolutionValue < SM_EFF_ZERO) return SM_SUCCESS; // keep answers good to scaledZero // get solution CurvePoint and a random NearMid SurfacePoint SmVector3d sCurvePV[2]; // sCurvePV[0] = crCurve 3dPoint // sCurvePV[1] = crCurve 3dTangent SmPoint3d sRandomPoint; SER(crCurve.Evaluate(crSolution.m_vStart[0],1,TRUE,sCurvePV, TRUE)); // NonZeroTangent values SER(cpBSS->EvaluatePoint(crUVDomain.Evaluate(0.456,0.6789),sRandomPoint)); // Scale Zero to current size. double dScaledZero = 10.0 * SM_EFF_ZERO * (1.0 + smos_Max(sCurvePV[0].GetMaxDimension(), sRandomPoint.GetMaxDimension())); // keep answers good to scaledZero if (crSolution.m_vStart.m_dSolutionValue < dScaledZero) { // keep solutions that pass this more generous test return SM_SUCCESS; } // arrive here when we have an inexact answer // inexact solutions are only found at interval discontinuities // get solution SurfacePoint: sUV, sSurfacePoint, sDU, sDV, and sNorm SmPoint2d sUV(crSolution.m_vStart[1],crSolution.m_vStart[2]); SmVector3d sDU, sDV; SmPoint3d sSurfacePoint; SER(cpBSS->Evaluate1stDerivatives(sUV,TRUE,TRUE,sSurfacePoint,sDU,sDV)); SmVector3d sNorm = sDU * sDV; if (sNorm.LengthSquared() < SM_EFF_ZERO_SQ) { SER(cpBSS->EvaluateNormal(sUV,TRUE,TRUE,sNorm)); } SER(sNorm.Unitize()); // get SurfacePoint to CurvePoint Gap Vector (the error) SmVector3d sDiff = sCurvePV[0] - sSurfacePoint; // see if solution lies on curve end-point boundaries SmBoolean bC0CurveMin = SM_ARE_SAME(crInterval.GetMin(),crSolution.m_vStart[0]) ; SmBoolean bC0CurveMax = SM_ARE_SAME(crInterval.GetMax(),crSolution.m_vStart[0]) ; // Keep Solutions whose gaps are // within 5 degrees of parallel to SurfaceNormal vector and // within 5 degrees of perpendicular to CurveTangent vector // A tangent Curve/Surface closest point solution if ( sNorm.IsParallelTo(sDiff,5.0) && sCurvePV[1].IsPerpendicularTo(sDiff,5.0)) { // This is a modification to this rule added for the particular // case where a curve-subdivision end-point solution is tangent // to the surface's SurfacePlane but the solution happens at a // local maximum rather than a local minimum of the curve/surface // gap function. It may be that the modification needs to be generalized // for cases on the boundaries of surfaces. But for now - just // worry about boundaries of curves. // The idea is that tangent conditions represent an extremum to the // gap function between the curve and surface and we only want to // keep gap minima and exclude gap maxima. A gap maximum with a small // tolerance is likely to have a better intersection point nearby. // // Judge this by estimating where the curve might intersect the // surface based on the shape of the curve and surface at the // current solution. If the distances to those intersections are // within the size of the current curve and surface then // assume this is a local maximum and don't keep the solution. // when the curve is continuous through this solution if( (bC0CurveMin && aeTConts[0] >= SM_CT_G1_G2) || (bC0CurveMax && aeTConts[1] >= SM_CT_G1_G2)) { // get the surface's normal section evaluation SmVector3d sTangentPlaneDirection, sSurfaceNormal ; double dSurfaceCurvature ; cpBSS->EvaluateNormalSection(sUV, TRUE, TRUE, sCurvePV[1], sTangentPlaneDirection, dSurfaceCurvature, sSurfaceNormal) ; // compute the curve's position, tangent, curvature, and normal values SmVector3d aCurveFrenet[3] ; // aCurveFrenet[0] = position // aCurveFrenet[1] = Mag = Speed, direction = tangent vector // aCurveFrenet[2] = Mag = K, direction = normal vector crCurve.EvaluateGeometric(crSolution.m_vStart[0], 2, TRUE, aCurveFrenet) ; // look for lines double dCurveCurvature = aCurveFrenet[2].Length() ; SmBoolean bIsCurveLine = smos_Fabs(dCurveCurvature) < SM_EFF_ZERO_SQRT ; SmBoolean bIsSurfaceLine = smos_Fabs(dSurfaceCurvature) < SM_EFF_ZERO_SQRT ; // handle line cases now if(bIsCurveLine && bIsSurfaceLine) { rbKeepSolution = TRUE ; return SM_SUCCESS ; } else if(bIsCurveLine) { // keep solutions when surf is bending away from curve // with SmVector3d sDiff = sCurvePV[0] - sSurfacePoint; rbKeepSolution = (dSurfaceCurvature * sDiff.Dot(sSurfaceNormal)) > 0.0 ? FALSE : TRUE ; return SM_SUCCESS ; } else if(bIsSurfaceLine) { // keep solutions when curve is bending away from surface // with SmVector3d sDiff = sCurvePV[0] - sSurfacePoint; rbKeepSolution = sDiff.Dot(aCurveFrenet[2]) > 0.0 ? TRUE : FALSE ; return SM_SUCCESS ; } // arrive here when both SurfaceNormalSection and Curve are curving at the solution // note: The surface curve lies in the SurfaceNormal/SurfaceTangent plane // The curve lies in the plane defined by its tangent and normal vectors. // The gap vector is within tolerance of being parallel to the surface normal vector. // The surfaceSectionPlane and the CurvePlane won't generally be parallel to one another. // Because the SectionNormalPlane was defined by the CurveTangent the two // planes will always line up like two pages in a book - they share a common // axis which will be parallel to the curve and the SurfaceSectionCurve's tangents. // // To make this test, project the curve to the SurfaceSectionPlane which changes // its curvature value at the solution point. Use this projected curvature // value to decide if the gap function at the solution is getting bigger or smaller // for small deviations from the solution point. // Compute the curvature value for the curve after it's been projected // to the surfaceSectionPlane. This will be a signed curvature value, // coincidentaly just like the surfaceSectionCurve's signed curvature value, // Positive = Curve center of curvature is offset from curve in the sSurfaceNormal direction // Negative = Curve center of curvature is offset from curve opposite to the sSurfaceNormal direction // The curve is bending towards or away from the surface depending on this sign and the // direction of the gap vector compared to the surface normal vector - lot's of stuff to track. dCurveCurvature = aCurveFrenet[2].Dot(sSurfaceNormal) ; // keep the solution when the two curves are bending away from one another // or when the curves are both bending in the same direction keep the solution // when the 'inside' curve is bending faster than the 'outside' curve so that // the gap function is locally increasing not decreasing. // Let g = gap vector // n = surface normal // Kc = signed curve curvature normal after projection onto SurfaceSectionPlane // Ks = signed surfaceSection curvature normal // cases // g.n | sign(Ks) | sign(Kc) | description | keep test | // ------+----------+----------+----------------------------------------------------+-------------------+ // neg | pos | pos | both curves bending the same way | abs(Kc) < abs(Ks) | // pos | neg | neg | surfaceSectionCurve is inside and Curve is outside | | // ------+----------+----------+----------------------------------------------------+-------------------+ // neg | pos | neg | both curves are bending away from one another | TRUE | // pos | neg | pos | | | // ------+----------+----------+----------------------------------------------------+-------------------+ // neg | neg | pos | both curves are bending towards one another | FALSE | // pos | pos | neg | | | // ------+----------+----------+----------------------------------------------------+-------------------+ // neg | neg | neg | both curves bending the same way | abs(Kc) > abs(Ks) | // pos | pos | pos | Curve is inside and surfaceSectionCurve is outside | | // ------+----------+----------+----------------------------------------------------+-------------------+ // This assumes that the gap distance is small compared to the radius of curvature values. // Otherwise a small correction is needed in the inequality tests. SmBoolean bPosGDotN = sDiff.Dot(sSurfaceNormal) > 0.0 ; SmBoolean bPosKs = dSurfaceCurvature > 0.0 ; SmBoolean bPosKc = dCurveCurvature > 0.0 ; SmBoolean bKcBiggerThanKs = smos_Fabs(dCurveCurvature) > smos_Fabs(dSurfaceCurvature) ; if ( (!bPosGDotN && bPosKs && bPosKc) || ( bPosGDotN && !bPosKs && !bPosKc)) { rbKeepSolution = !bKcBiggerThanKs ; } else if( (!bPosGDotN && bPosKs && !bPosKc) || ( bPosGDotN && !bPosKs && bPosKc)) { rbKeepSolution = TRUE ; } else if( (!bPosGDotN && !bPosKs && bPosKc) || ( bPosGDotN && bPosKs && !bPosKc)) { rbKeepSolution = FALSE ; } else if( (!bPosGDotN && !bPosKs && !bPosKc) || ( bPosGDotN && bPosKs && bPosKc)) { rbKeepSolution = bKcBiggerThanKs ; } return SM_SUCCESS ; } // end end-curve point solution review return SM_SUCCESS; } // If here it is not a tangent Curve/Surface closest point solution case. // The curve has come within tolerance distance of a boundary. There // are 6 boundaries to watch, the Curve min/max boundaries and the // Surface U/V min/max boundaries. // Keep solutions that are bounded by discontinuity boundaries. // Toss solutions bounded by continuous boundaries because the actual // solution will be found on another SolveBranch using the neighboring // bit of domain. // see if solution lies on surface curve-boundaries SmBoolean bC0SurfUMin = SM_ARE_SAME( crUVDomain.GetMin().x, crSolution.m_vStart[1] ); SmBoolean bC0SurfUMax = SM_ARE_SAME( crUVDomain.GetMax().x, crSolution.m_vStart[1] ); SmBoolean bC0SurfVMin = SM_ARE_SAME( crUVDomain.GetMin().y, crSolution.m_vStart[2] ); SmBoolean bC0SurfVMax = SM_ARE_SAME( crUVDomain.GetMax().y, crSolution.m_vStart[2] ); // see which way the gap vector points relative to the curve and surface tangent vectors double dCurveDotDiff = sCurvePV[1].Dot(sDiff) ; double dSurfUDotDiff = sDiff.Dot(sDU) ; double dSurfVDotDiff = sDiff.Dot(sDV) ; // when the solution is on a discontinuity boundary keep it if the gap vector points out of the shape domain // Solutions on continuous boundaries are tossed because they will be picked up by another SolveBranch // In addition, for a hit within tol of a seam, protocol is to return only // one solution: the one that's slightly interior and presumably precise. // The imprecise one right on the boundary is rejected. // For our purposes here, a seam becomes a continuous boundary. [B161] // However, checking seams is somewhat expensive, // so let's not do it if we don't have to. Check it after this test. SmBoolean bCurveSave = (aeTConts[0] <= SM_CT_C0 && bC0CurveMin && dCurveDotDiff > 0.0) || (aeTConts[1] <= SM_CT_C0 && bC0CurveMax && dCurveDotDiff < 0.0) ; SmBoolean bSurfUSave = (aeUConts[0] <= SM_CT_C0 && bC0SurfUMin && dSurfUDotDiff < 0.0) || (aeUConts[1] <= SM_CT_C0 && bC0SurfUMax && dSurfUDotDiff > 0.0) ; SmBoolean bSurfVSave = (aeVConts[0] <= SM_CT_C0 && bC0SurfVMin && dSurfVDotDiff < 0.0) || (aeVConts[1] <= SM_CT_C0 && bC0SurfVMax && dSurfVDotDiff > 0.0) ; // pass any boundary solution that works on a boundary // solutions on corners work when any of the following conditions are met // Save solutions at discontinuous Curve C0 boundaries when SurfToCurve gap points out of the curve interval // Save solutions at discontinuous Surface U boundaries when CurveToSurf gap points out of the surface domain // Save solutions at discontinuous Surface V boundaries when CurveToSurf gap points out of the surface domain rbKeepSolution = (bCurveSave || bSurfUSave || bSurfVSave) ? TRUE : FALSE ; // Ok, now check seams, if we have to. if ( rbKeepSolution ) { if ( bCurveSave ) { // Can't use passed-in interval, it's just a cache subset. SmExtent1d sCrvIvl = crCurve.GetNaturalInterval(); if ( crCurve.IsClosed( sCrvIvl ) ) { bCurveSave = FALSE; } rbKeepSolution = (bCurveSave || bSurfUSave || bSurfVSave) ? TRUE : FALSE; } } // Can't use passed-in interval, it's just a cache subset. SmExtent2d sSrfDomain = cpBSS->GetNaturalUVDomain(); if ( rbKeepSolution ) { if ( bSurfUSave ) { if ( cpBSS->IsClosed( sSrfDomain, SM_SP_U ) ) { bSurfUSave = FALSE; } rbKeepSolution = (bCurveSave || bSurfUSave || bSurfVSave) ? TRUE : FALSE; } } if ( rbKeepSolution ) { if ( bSurfVSave ) { if ( cpBSS->IsClosed( sSrfDomain, SM_SP_V ) ) { bSurfVSave = FALSE; } rbKeepSolution = (bCurveSave || bSurfUSave || bSurfVSave) ? TRUE : FALSE; } } return SM_SUCCESS; } // end sm_FilterSolution // ---- the solver classes (data members of SmSurface.cpp; constructors and LocalSolve adapted) class SmSPGlobalSolver : public SmGlobalSolver { public: const SmSurface * m_pSurf; const SmExtent2d & m_crUVDomain; SmSPGlobalSolver(SmTree *pTree, const SmExtent2d & crUVDomain) : SmGlobalSolver(pTree, 2), m_pSurf(SM_CAST_PTR(SmSurface, pTree->GetOwnerObject())), m_crUVDomain(crUVDomain) {} void SetTol(double dTol) { m_d3dTolerance = dTol; } void SetSurf(const SmSurface *pSurf) { m_pSurf = pSurf; } virtual SmStatus LocalSolve(SmTreeNode *[SM_GS_MAX_TREES], SmBoolean &) { return SM_ERR; } virtual SmSolutionPairType IdenticalSolutions(const SmSolution & crSol1, const SmSolution & crSol2, const SmSolution *&pPreferredSol) const; }; class SmLSIGlobalSolver : public SmGlobalSolver { public: const SmSurface * m_pSurf; const SmExtent2d & m_crUVDomain; SmLSIGlobalSolver(SmTree *pTree, const SmExtent2d & crUVDomain) : SmGlobalSolver(pTree, 2), m_pSurf(SM_CAST_PTR(SmSurface, pTree->GetOwnerObject())), m_crUVDomain(crUVDomain) {} void SetTol(double dTol) { m_d3dTolerance = dTol; } void SetSurf(const SmSurface *pSurf) { m_pSurf = pSurf; } virtual SmStatus LocalSolve(SmTreeNode *[SM_GS_MAX_TREES], SmBoolean &) { return SM_ERR; } virtual SmSolutionPairType IdenticalSolutions(const SmSolution & crSol1, const SmSolution & crSol2, const SmSolution *&pPreferredSol) const; }; class SmCSGlobalSolver : public SmGlobalSolver { public: const SmSurface * m_pSurf; const SmExtent1d & m_crInterval; const SmExtent2d & m_crUVDomain; SmCSGlobalSolver(SmTree *pTree, const SmExtent1d & crInterval, const SmExtent2d & crUVDomain) : SmGlobalSolver(pTree, 3), m_pSurf(SM_CAST_PTR(SmSurface, pTree->GetOwnerObject())), m_crInterval(crInterval), m_crUVDomain(crUVDomain) {} void SetTol(double dTol) { m_d3dTolerance = dTol; } void SetSurf(const SmSurface *pSurf) { m_pSurf = pSurf; } virtual SmStatus LocalSolve(SmTreeNode *[SM_GS_MAX_TREES], SmBoolean &) { return SM_ERR; } virtual SmSolutionPairType IdenticalSolutions(const SmSolution & crSol1, const SmSolution & crSol2, const SmSolution *&pPreferredSol) const; }; class SmCSIGlobalSolver : public SmGlobalSolver { public: const SmSurface * m_pSurf; SmBoolean m_bFoundCoincidence; const SmExtent1d & m_crInterval; const SmExtent2d & m_crUVDomain; SmCSIGlobalSolver(SmTree *pTree, const SmExtent1d & crInterval, const SmExtent2d & crUVDomain) : SmGlobalSolver(pTree, 3), m_pSurf(SM_CAST_PTR(SmSurface, pTree->GetOwnerObject())), m_bFoundCoincidence(FALSE), m_crInterval(crInterval), m_crUVDomain(crUVDomain) {} void SetTol(double dTol) { m_d3dTolerance = dTol; } void SetSurf(const SmSurface *pSurf) { m_pSurf = pSurf; } virtual SmStatus LocalSolve(SmTreeNode *[SM_GS_MAX_TREES], SmBoolean &) { return SM_ERR; } virtual SmSolutionPairType IdenticalSolutions(const SmSolution & crSol1, const SmSolution & crSol2, const SmSolution *&pPreferredSol) const; }; // ---- verbatim: SmSurface.cpp lines 19879-19962: SmSPGlobalSolver::IdenticalSolutions SmSolutionPairType SmSPGlobalSolver::IdenticalSolutions // rtn: oneof SM_SP_IDENTICAL // SM_SP_DIFFCOUNTS // SM_SP_DIFFOBJECTS // SM_SP_DIFFPARAMETERS (const SmSolution & crSol1, // in : 1st target solution const SmSolution & crSol2, // in : 2nd target solution const SmSolution *&pPreferredSol) // out: When identical, set to the preferred solution or NULL for none. // NULL to ignore, default:[NULL] const { // init output pPreferredSol = NULL ; // If the parent thinks they're identical, then they are. SmSolutionPairType eSPType = SmGlobalSolver::IdenticalSolutions( crSol1, crSol2, pPreferredSol ) ; if(SM_SP_IDENTICAL == eSPType) { return SM_SP_IDENTICAL; } if(SM_SP_DIFFPARAMETERS == eSPType) { #ifdef SM_DEBUG_CODE SmBoolean bDebugMe = FALSE ; if(bDebugMe) { crSol1.Dump() ; crSol2.Dump() ; } #endif // SM_DEBUG_CODE // At a singularity, the same 3d point can have different uv values. SmPoint2d sUV1( crSol1.m_vStart[0], crSol1.m_vStart[1] ); SmPoint2d sUV2( crSol2.m_vStart[0], crSol2.m_vStart[1] ); // Check positions. SmPoint3d sPt1, sPt2; m_pSurf->EvaluatePoint( sUV1, sPt1 ); m_pSurf->EvaluatePoint( sUV2, sPt2 ); double dTol = m_d3dTolerance; // untested: see how it works in practice if ( ! sPt1.CloserThan( dTol, sPt2 ) ) { return SM_SP_DIFFPARAMETERS; } // 3d points are the same. Need to check seams and poles. // Check poles first, it's a cheaper check. // If either is nonsingular, they're distinct. SmSurfParamType eSingDir; if ( ! m_pSurf->IsSingularity( sUV1, eSingDir ) ) { return SM_SP_DIFFPARAMETERS; } if ( ! m_pSurf->IsSingularity( sUV2, eSingDir ) ) { return SM_SP_DIFFPARAMETERS; } // We're at a pole. Distinct only if they're both on a seam. // When one is on a seam prefer that. Else prefer the best solution. // (We already know that their uv's are different, so if they're // both on seams, they must be on opposite sides of it.) SmBoolean bOnSeam1 = m_pSurf->IsOnSeam( sUV1 ) ; SmBoolean bOnSeam2 = m_pSurf->IsOnSeam( sUV2 ) ; if( bOnSeam1 && !bOnSeam2) { pPreferredSol = &crSol1 ; return SM_SP_IDENTICAL ; } if(!bOnSeam1 && bOnSeam2) { pPreferredSol = &crSol2 ; return SM_SP_IDENTICAL ; } if(!bOnSeam1 && !bOnSeam2) { pPreferredSol = (crSol1.m_vStart.m_dSolutionValue < crSol2.m_vStart.m_dSolutionValue) ? &crSol1 : &crSol2 ; return SM_SP_IDENTICAL ; } // // We're at a pole. Distinct only if they're also on a seam. // // (We already know that their uv's are different.) // if ( ! m_pSurf->IsOnSeam( sUV1 ) ) // { return SM_SP_IDENTICAL; } // if ( ! m_pSurf->IsOnSeam( sUV2 ) ) // { return SM_SP_IDENTICAL; } } // Identical positions, at a pole on a seam: // Call them distinct points. return eSPType; } // end SmSPGlobalSolver::IdenticalSolutions // ---- verbatim: SmSurface.cpp lines 18843-18930: SmLSIGlobalSolver::IdenticalSolutions SmSolutionPairType SmLSIGlobalSolver::IdenticalSolutions // rtn: oneof SM_SP_IDENTICAL // SM_SP_DIFFCOUNTS // SM_SP_DIFFOBJECTS // SM_SP_DIFFPARAMETERS (const SmSolution & crSol1, // in : 1st target solution const SmSolution & crSol2, // in : 2nd target solution const SmSolution *&pPreferredSol) // out: When identical, set to the preferred solution or NULL for none. // NULL to ignore, default:[NULL] const { // init output pPreferredSol = NULL ; // If the parent thinks they're identical, then they are. SmSolutionPairType eSPType = SmGlobalSolver::IdenticalSolutions( crSol1, crSol2, pPreferredSol ) ; if(SM_SP_IDENTICAL == eSPType) { return SM_SP_IDENTICAL; } if(SM_SP_DIFFPARAMETERS == eSPType) { if( crSol1.m_eSolutionType == SM_ST_SINGLE_VALUE && crSol2.m_eSolutionType == SM_ST_SINGLE_VALUE) { #ifdef SM_DEBUG_CODE SmBoolean bDebugMe = FALSE ; if(bDebugMe) { crSol1.Dump() ; crSol2.Dump() ; } #endif // SM_DEBUG_CODE // At a singularity, the same 3d point can have different uv values. SmPoint2d sUV1( crSol1.m_vStart[1], crSol1.m_vStart[2] ); SmPoint2d sUV2( crSol2.m_vStart[1], crSol2.m_vStart[2] ); // Check positions. SmPoint3d sPt1, sPt2; m_pSurf->EvaluatePoint( sUV1, sPt1 ); m_pSurf->EvaluatePoint( sUV2, sPt2 ); double dTol = m_d3dTolerance; // untested: see how it works in practice if ( ! sPt1.CloserThan( dTol, sPt2 ) ) { return SM_SP_DIFFPARAMETERS; } // 3d points are the same. Need to check seams and poles. // Check poles first, it's a cheaper check. // If either is nonsingular, they're distinct. SmSurfParamType eSingDir; if ( ! m_pSurf->IsSingularity( sUV1, eSingDir ) ) { return SM_SP_DIFFPARAMETERS; } if ( ! m_pSurf->IsSingularity( sUV2, eSingDir ) ) { return SM_SP_DIFFPARAMETERS; } // We're at a pole. Distinct only if they're both on a seam. // When one is on a seam prefer that. Else prefer the best solution. // (We already know that their uv's are different, so if they're // both on seams, they must be on opposite sides of it.) SmBoolean bOnSeam1 = m_pSurf->IsOnSeam( sUV1 ) ; SmBoolean bOnSeam2 = m_pSurf->IsOnSeam( sUV2 ) ; if( bOnSeam1 && !bOnSeam2) { pPreferredSol = &crSol1 ; return SM_SP_IDENTICAL ; } if(!bOnSeam1 && bOnSeam2) { pPreferredSol = &crSol2 ; return SM_SP_IDENTICAL ; } if(!bOnSeam1 && !bOnSeam2) { pPreferredSol = (crSol1.m_vStart.m_dSolutionValue < crSol2.m_vStart.m_dSolutionValue) ? &crSol1 : &crSol2 ; return SM_SP_IDENTICAL ; } // // We're at a pole. Distinct only if they're also on a seam. // // (We already know that their uv's are different.) // if ( ! m_pSurf->IsOnSeam( sUV1 ) ) // need trick here to tell caller to save the one on the seam // { return SM_SP_IDENTICAL; } // if ( ! m_pSurf->IsOnSeam( sUV2 ) ) // { return SM_SP_IDENTICAL; } } } // Identical positions, at a pole on a seam: // Call them distinct points. return eSPType; } // end SmLSIGlobalSolver::IdenticalSolutions // ---- verbatim: SmSurface.cpp lines 17670-17757: SmCSGlobalSolver::IdenticalSolutions SmSolutionPairType SmCSGlobalSolver::IdenticalSolutions // rtn: oneof SM_SP_IDENTICAL // SM_SP_DIFFCOUNTS // SM_SP_DIFFOBJECTS // SM_SP_DIFFPARAMETERS (const SmSolution & crSol1, // in : 1st target solution const SmSolution & crSol2, // in : 2nd target solution const SmSolution *&pPreferredSol) // out: When identical, set to the preferred solution or NULL for none. // NULL to ignore, default:[NULL] const { // init output pPreferredSol = NULL ; // If the parent thinks they're identical, then they are. SmSolutionPairType eSPType = SmGlobalSolver::IdenticalSolutions( crSol1, crSol2, pPreferredSol ) ; if(SM_SP_IDENTICAL == eSPType) { return SM_SP_IDENTICAL; } if(SM_SP_DIFFPARAMETERS == eSPType) { if( crSol1.m_eSolutionType == SM_ST_SINGLE_VALUE && crSol2.m_eSolutionType == SM_ST_SINGLE_VALUE) { #ifdef SM_DEBUG_CODE SmBoolean bDebugMe = FALSE ; if(bDebugMe) { crSol1.Dump() ; crSol2.Dump() ; } #endif // SM_DEBUG_CODE // At a singularity, the same 3d point can have different uv values. SmPoint2d sUV1( crSol1.m_vStart[1], crSol1.m_vStart[2] ); SmPoint2d sUV2( crSol2.m_vStart[1], crSol2.m_vStart[2] ); // Check positions. SmPoint3d sPt1, sPt2; m_pSurf->EvaluatePoint( sUV1, sPt1 ); m_pSurf->EvaluatePoint( sUV2, sPt2 ); double dTol = m_d3dTolerance; // untested: see how it works in practice if ( ! sPt1.CloserThan( dTol, sPt2 ) ) { return SM_SP_DIFFPARAMETERS; } // 3d points are the same. Need to check seams and poles. // Check poles first, it's a cheaper check. // If either is nonsingular, they're distinct. SmSurfParamType eSingDir; if ( ! m_pSurf->IsSingularity( sUV1, eSingDir ) ) { return SM_SP_DIFFPARAMETERS; } if ( ! m_pSurf->IsSingularity( sUV2, eSingDir ) ) { return SM_SP_DIFFPARAMETERS; } // We're at a pole. Distinct only if they're both on a seam. // When one is on a seam prefer that. Else prefer the best solution. // (We already know that their uv's are different, so if they're // both on seams, they must be on opposite sides of it.) SmBoolean bOnSeam1 = m_pSurf->IsOnSeam( sUV1 ) ; SmBoolean bOnSeam2 = m_pSurf->IsOnSeam( sUV2 ) ; if( bOnSeam1 && !bOnSeam2) { pPreferredSol = &crSol1 ; return SM_SP_IDENTICAL ; } if(!bOnSeam1 && bOnSeam2) { pPreferredSol = &crSol2 ; return SM_SP_IDENTICAL ; } if(!bOnSeam1 && !bOnSeam2) { pPreferredSol = (crSol1.m_vStart.m_dSolutionValue < crSol2.m_vStart.m_dSolutionValue) ? &crSol1 : &crSol2 ; return SM_SP_IDENTICAL ; } // // We're at a pole. Distinct only if they're also on a seam. // // (We already know that their uv's are different.) // if ( ! m_pSurf->IsOnSeam( sUV1 ) ) // { return SM_SP_IDENTICAL; } // if ( ! m_pSurf->IsOnSeam( sUV2 ) ) // { return SM_SP_IDENTICAL; } } } // Identical positions, at a pole on a seam: // Call them distinct points. return eSPType; } // end SmCSGlobalSolver::IdenticalSolutions // ---- verbatim: SmSurface.cpp lines 14784-14869: SmCSIGlobalSolver::IdenticalSolutions SmSolutionPairType SmCSIGlobalSolver::IdenticalSolutions // rtn: oneof SM_SP_IDENTICAL // SM_SP_DIFFCOUNTS // SM_SP_DIFFOBJECTS // SM_SP_DIFFPARAMETERS (const SmSolution & crSol1, // in : 1st target solution const SmSolution & crSol2, // in : 2nd target solution const SmSolution *&pPreferredSol) // out: When identical, set to the preferred solution or NULL for none. // NULL to ignore, default:[NULL] const { // init output pPreferredSol = NULL ; // If the parent thinks they're identical, then they are. SmSolutionPairType eSPType = SmGlobalSolver::IdenticalSolutions( crSol1, crSol2, pPreferredSol ) ; if(SM_SP_IDENTICAL == eSPType) { return SM_SP_IDENTICAL; } if(SM_SP_DIFFPARAMETERS == eSPType) { if( crSol1.m_eSolutionType == SM_ST_SINGLE_VALUE && crSol2.m_eSolutionType == SM_ST_SINGLE_VALUE) { #ifdef SM_DEBUG_CODE SmBoolean bDebugMe = FALSE ; if(bDebugMe) { crSol1.Dump() ; crSol2.Dump() ; } #endif // SM_DEBUG_CODE // At a singularity, the same 3d point can have different uv values. SmPoint2d sUV1( crSol1.m_vStart[1], crSol1.m_vStart[2] ); SmPoint2d sUV2( crSol2.m_vStart[1], crSol2.m_vStart[2] ); // Check positions. SmPoint3d sPt1, sPt2; m_pSurf->EvaluatePoint( sUV1, sPt1 ); m_pSurf->EvaluatePoint( sUV2, sPt2 ); double dTol = m_d3dTolerance; // untested: see how it works in practice if ( ! sPt1.CloserThan( dTol, sPt2 ) ) { return SM_SP_DIFFPARAMETERS; } // 3d points are the same. Need to check seams and poles. // Check poles first, it's a cheaper check. // If either is nonsingular, they're distinct. SmSurfParamType eSingDir; if ( ! m_pSurf->IsSingularity( sUV1, eSingDir ) ) { return SM_SP_DIFFPARAMETERS; } if ( ! m_pSurf->IsSingularity( sUV2, eSingDir ) ) { return SM_SP_DIFFPARAMETERS; } // We're at a pole. Distinct only if they're both on a seam. // When one is on a seam prefer that. Else prefer the best solution. // (We already know that their uv's are different, so if they're // both on seams, they must be on opposite sides of it.) SmBoolean bOnSeam1 = m_pSurf->IsOnSeam( sUV1 ) ; SmBoolean bOnSeam2 = m_pSurf->IsOnSeam( sUV2 ) ; if( bOnSeam1 && !bOnSeam2) { pPreferredSol = &crSol1 ; return SM_SP_IDENTICAL ; } if(!bOnSeam1 && bOnSeam2) { pPreferredSol = &crSol2 ; return SM_SP_IDENTICAL ; } if(!bOnSeam1 && !bOnSeam2) { pPreferredSol = (crSol1.m_vStart.m_dSolutionValue < crSol2.m_vStart.m_dSolutionValue) ? &crSol1 : &crSol2 ; return SM_SP_IDENTICAL ; } // if ( !bOnSeam1 ) // { return SM_SP_IDENTICAL; } // if ( !bOnSeam2 ) // { return SM_SP_IDENTICAL; } } } // Identical positions, at a pole on a seam: // Call them distinct points. return eSPType; } // end SmCSIGlobalSolver::IdenticalSolutions // clang-format on } // namespace sc_srf_p5 namespace { 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; } SmBoolean bflag() { return flag() ? TRUE : FALSE; } SmVector3d vec() { double x = num(); double y = num(); double z = num(); return SmVector3d(x, y, z); } SmPoint2d uv() { double u = num(); double v = num(); return SmPoint2d(u, v); } SmExtent2d dom() { double u0 = num(); double v0 = num(); double u1 = num(); double v1 = num(); return SmExtent2d(SmPoint2d(u0, v0), SmPoint2d(u1, v1)); } }; double st(SmStatus s) { return s == SM_SUCCESS ? 0.0 : (double)s; } double flg(SmBoolean b) { return b ? 1.0 : 0.0; } const SmContext& the_context() { static SmContext* p = new SmContext(); return *p; } SmSurface* read_surface(Rd& rd, const SmContext& ctx) { int64_t kind = rd.idx(); if (kind == 2) { // a curve-bounded surface without boundary curves over a basis surface block SmSurface* basis = read_surface(rd, ctx); bool implicit = rd.flag(); SmTArray c3d, cuv; return new (ctx) SmCurveBoundedSurface(*basis, &c3d, &cuv, implicit ? TRUE : FALSE); } if (kind == 1) { SmPoint3d o = rd.vec(); SmVector3d n = rd.vec(); return new (ctx) SmPlane(o, n, &ctx); } bool oob = rd.flag(); SmBSplineSurfaceForm form = (SmBSplineSurfaceForm)rd.idx(); 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(); SmBSplineSurface* b = new (ctx) SmBSplineSurface(sur, FALSE, NULL); b->GetOutOfBoundsEnabled() = oob ? TRUE : FALSE; b->SetBSplineSurfaceForm(form); return b; } SmBSplineCurve* read_curve(Rd& rd, const SmContext& ctx) { ULONG dim = (ULONG)rd.idx(); 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, dim, FALSE); } const double* opt_target(Rd& rd, double& store) { bool has = rd.flag(); store = rd.num(); return has ? &store : NULL; } std::vector read_vecs(Rd& rd) { int64_t n = rd.idx(); std::vector v; for (int64_t i = 0; i < n; i++) v.push_back(rd.vec()); return v; } // ---- ops (keep in sync with crates/solidcore-ffi/src/units/surface_surface_p5.rs) ---------- enum : int32_t { OP_IS_SING = 0, OP_NORMAL_SING = 1, OP_ISO = 2, OP_LPS = 3, OP_LPS0 = 4, OP_DROP = 5, OP_LSSI = 6, OP_COVER = 7, OP_FILTER = 8, OP_SP_LOCAL = 9, OP_IDENT = 10, OP_MAXDIST = 11, }; void put_solution(std::vector& o, const SmSolution& s, int n) { o.push_back(s.m_vStart.m_dSolutionValue); for (int i = 0; i < n; i++) o.push_back(s.m_vStart[i]); } void read_solution(Rd& rd, SmSolution& s) { s.m_eSolutionType = (SmSolutionType)rd.idx(); s.m_lNumVariables = (ULONG)rd.idx(); s.m_vStart.m_dSolutionValue = rd.num(); for (int i = 0; i < 3; i++) s.m_vStart[i] = rd.num(); } void run(int32_t op, Rd& rd, std::vector& o) { const SmContext& ctx = the_context(); SmSurface* s = read_surface(rd, ctx); switch (op) { case OP_IS_SING: { SmPoint2d uv = rd.uv(); double tol = rd.num(); SmBoolean pt_only = rd.bflag(); SmSurfParamType dir = SM_SP_UNKNOWN; SmBoolean r = s->IsSingularity(uv, dir, tol, pt_only); o.push_back(0.0); o.push_back(flg(r)); o.push_back((double)dir); break; } case OP_NORMAL_SING: { SmPoint2d uv = rd.uv(); double tol = rd.num(); SmVector3d n(SM_UNDEF_DOUBLE, SM_UNDEF_DOUBLE, SM_UNDEF_DOUBLE); SmStatus r = s->EvaluateNormalAtSingularity(uv, n, tol); o.push_back(st(r)); if (r == SM_SUCCESS) put_vec(o, n); break; } case OP_ISO: { SmSurfParamType param = (SmSurfParamType)rd.idx(); double iso = rd.num(); double tol = rd.num(); bool has_dom = rd.flag(); SmExtent2d dom = rd.dom(); bool want_uv = rd.flag(); SmBSplineCurve* c = NULL; double gap = 0.0; SmCurve* uvc = NULL; SmStatus r = s->CreateIsoParametricCurve(ctx, param, iso, SmApproxTol3d(tol), c, has_dom ? &dom : NULL, &gap, want_uv ? &uvc : NULL); o.push_back(st(r)); if (r == SM_SUCCESS && c != NULL) { SmExtent1d ivl = c->GetNaturalInterval(); o.push_back(gap); o.push_back(uvc != NULL ? 1.0 : 0.0); o.push_back(ivl.GetMin()); o.push_back(ivl.GetMax()); for (int i = 0; i <= 8; i++) { SmPoint3d p; c->EvaluatePoint(ivl.Evaluate(i / 8.0), p); put_vec(o, p); } } delete c; delete uvc; break; } case OP_LPS: case OP_LPS0: { SmExtent2d d = rd.dom(); SmSolverOperationType sop = (SmSolverOperationType)rd.idx(); SmPoint3d p = rd.vec(); SmPoint2d g = rd.uv(); SmBoolean found = FALSE; SmSolution sol; SmTerminationReasonType reason = SM_TR_UNDEFINED; int iters = 0; SmStatus r = (op == OP_LPS) ? s->LocalPointSolve(d, sop, p, g, found, sol) : s->LocalPointSolve_0(d, sop, p, g, found, sol, reason, iters); o.push_back(st(r)); if (r != SM_SUCCESS) break; o.push_back(flg(found)); if (found) put_solution(o, sol, 2); if (op == OP_LPS0) { o.push_back((double)reason); o.push_back((double)iters); } break; } case OP_DROP: { SmExtent2d d = rd.dom(); SmPoint3d p = rd.vec(); bool has_guess = rd.flag(); SmPoint2d g = rd.uv(); SmSolverOperationType sop = (SmSolverOperationType)rd.idx(); bool has_tgt = rd.flag(); SmVector3d tgt = rd.vec(); bool want = rd.flag(); SmBoolean ok = FALSE, multi = FALSE; SmPoint2d uv(SM_UNDEF_DOUBLE, SM_UNDEF_DOUBLE); double gap = 0.0; SmSolutionArray sols; SmStatus r = s->DropPoint(p, d, has_guess ? &g : NULL, ok, uv, gap, multi, sop, has_tgt ? &tgt : NULL, want ? &sols : NULL); o.push_back(st(r)); if (r == SM_SUCCESS) { o.push_back(flg(ok)); o.push_back(uv.x); o.push_back(uv.y); o.push_back(gap); o.push_back(flg(multi)); o.push_back(want ? (double)sols.GetSize() : -1.0); } break; } case OP_LSSI: { SmExtent2d d1 = rd.dom(); SmSurface* s2 = read_surface(rd, ctx); SmExtent2d d2 = rd.dom(); SmSurface* s3 = read_surface(rd, ctx); SmExtent2d d3 = rd.dom(); SmPoint3d g1 = rd.vec(); bool has2 = rd.flag(); SmPoint3d g2 = rd.vec(); SmBoolean found = FALSE; SmSolution sol; SmStatus r = s->LocalSurfaceSurfaceIntersect(d1, *s2, d2, *s3, d3, g1, has2 ? &g2 : NULL, found, sol); o.push_back(st(r)); if (r == SM_SUCCESS) { o.push_back(flg(found)); if (found) put_solution(o, sol, 6); } delete s2; delete s3; break; } case OP_COVER: { SmSurface* s2 = read_surface(rd, ctx); double tol = rd.num(); bool merged = rd.flag(); double max_dist = 0.0; SmSurface* cover = NULL; SmExtent2d md; SmStatus r = s->CoverOtherCoincidentSurface(s2, tol, max_dist, cover, NULL, NULL, merged ? &md : NULL); o.push_back(st(r)); if (r == SM_SUCCESS) { o.push_back(cover == NULL ? -1.0 : (cover == s ? 0.0 : 1.0)); o.push_back(max_dist); } delete s2; break; } case OP_FILTER: { SmExtent2d d = rd.dom(); SmBSplineCurve* c = read_curve(rd, ctx); double i0 = rd.num(), i1 = rd.num(); SmContinuityType uc[2], vc[2], tc[2]; uc[0] = (SmContinuityType)rd.idx(); uc[1] = (SmContinuityType)rd.idx(); vc[0] = (SmContinuityType)rd.idx(); vc[1] = (SmContinuityType)rd.idx(); tc[0] = (SmContinuityType)rd.idx(); tc[1] = (SmContinuityType)rd.idx(); SmSolution sol; sol.m_eSolutionType = SM_ST_SINGLE_VALUE; sol.m_lNumVariables = 3; sol.m_vStart.m_dSolutionValue = rd.num(); sol.m_vStart[0] = rd.num(); sol.m_vStart[1] = rd.num(); sol.m_vStart[2] = rd.num(); SmBoolean keep = TRUE; SmStatus r = sc_srf_p5::sm_FilterSolution(s, d, *c, SmExtent1d(i0, i1), uc, vc, tc, sol, keep); o.push_back(st(r)); o.push_back(flg(keep)); delete c; break; } case OP_SP_LOCAL: { SmExtent2d d = rd.dom(); SmContinuityType uc[2], vc[2]; uc[0] = (SmContinuityType)rd.idx(); uc[1] = (SmContinuityType)rd.idx(); vc[0] = (SmContinuityType)rd.idx(); vc[1] = (SmContinuityType)rd.idx(); SmPoint3d p = rd.vec(); double tol = rd.num(); SmSolverOperationType sop = (SmSolverOperationType)rd.idx(); SmBoolean solve_surf = rd.bflag(), proc_bnd = rd.bflag(); SmSolutionArray sols; SmStatus r = sc_srf_p5::sm_SpLocalSolve(s, d, uc, vc, p, tol, sop, solve_surf, proc_bnd, sols); o.push_back(st(r)); o.push_back((double)sols.GetSize()); for (ULONG i = 0; i < sols.GetSize(); i++) put_solution(o, sols[i], 2); break; } case OP_IDENT: { int64_t kind = rd.idx(); double tol = rd.num(); SmSolution a, b; read_solution(rd, a); read_solution(rd, b); SmTree tree((SmObject*)s); SmExtent2d dom = s->GetNaturalUVDomain(); SmExtent1d ivl(0.0, 1.0); const SmSolution* pref = NULL; SmSolutionPairType pr = SM_SP_DIFFCOUNTS; if (kind == 0) { sc_srf_p5::SmSPGlobalSolver gs(&tree, dom); gs.SetTol(tol); gs.SetSurf(s); pr = gs.IdenticalSolutions(a, b, pref); } else if (kind == 1) { sc_srf_p5::SmLSIGlobalSolver gs(&tree, dom); gs.SetTol(tol); gs.SetSurf(s); pr = gs.IdenticalSolutions(a, b, pref); } else if (kind == 2) { sc_srf_p5::SmCSGlobalSolver gs(&tree, ivl, dom); gs.SetTol(tol); gs.SetSurf(s); pr = gs.IdenticalSolutions(a, b, pref); } else { sc_srf_p5::SmCSIGlobalSolver gs(&tree, ivl, dom); gs.SetTol(tol); gs.SetSurf(s); pr = gs.IdenticalSolutions(a, b, pref); } o.push_back(0.0); o.push_back((double)pr); o.push_back(pref == NULL ? -1.0 : (pref == &a ? 0.0 : 1.0)); break; } case OP_MAXDIST: { SmExtent2d d = rd.dom(); SmBSplineCurve* c = read_curve(rd, ctx); double a = rd.num(), b = rd.num(); ULONG ns = (ULONG)rd.idx(); bool has_needed = rd.flag(); double needed = rd.num(); double found = 0.0, ct = 0.0; SmPoint2d sp(0.0, 0.0); SmStatus r = c->SurfaceMaxDistanceBetween(SmExtent1d(a, b), *s, d, ns, has_needed ? &needed : NULL, found, &ct, &sp); o.push_back(st(r)); if (r == SM_SUCCESS) { o.push_back(found); o.push_back(ct); o.push_back(sp.x); o.push_back(sp.y); } delete c; break; } default: o.push_back((double)SC_ERR_ARG); break; } delete s; } } // namespace SC_EXPORT int32_t sc_surface_surface_p5_eval(int32_t op, const double* in, int64_t n_in, double* out, int64_t cap, int64_t* out_len) { if (in == NULL || n_in < 1 || out_len == NULL) return SC_ERR_ARG; SC_TRY Rd rd{in, 0}; std::vector o; run(op, rd, o); if (rd.at != n_in) { *out_len = 0; return SC_ERR_ARG; } return sc_copy_out(o.data(), (int64_t)o.size(), out, cap, out_len); SC_CATCH }