mirror of
https://github.com/mcneel/opennurbs.git
synced 2026-03-08 16:55:53 +08:00
Co-authored-by: Bozo <bozo@mcneel.com> Co-authored-by: croudyj <croudyj@gmail.com> Co-authored-by: Dale Fugier <dale@mcneel.com> Co-authored-by: Dale Lear <dalelear@mcneel.com> Co-authored-by: Joshua Kennedy <joshuakennedy102@gmail.com> Co-authored-by: Jussi Aaltonen <jussi@mcneel.com> Co-authored-by: kike-garbo <kike@mcneel.com> Co-authored-by: Luis Fraguada <luis@mcneel.com> Co-authored-by: piac <giulio@mcneel.com> Co-authored-by: Pierre Cuvilliers <pierre@mcneel.com>
3671 lines
97 KiB
C++
3671 lines
97 KiB
C++
/* $Header: /src4/opennurbs/Tests/TestClosestPoint.cpp 36 10/07/05 4:27p Dalelear $ */
|
|
/* $NoKeywords: $ */
|
|
|
|
#include "Tests.h"
|
|
|
|
static const bool bPurify = false;
|
|
static const bool bDoPoints = true;
|
|
static const bool bSmallTest = false;
|
|
static const bool bDoGooseTests = false; // SLOW
|
|
|
|
static void CCXHelper3( const ON_CurveTreeNode* nodeA, const ON_CurveTreeNode* nodeB,
|
|
double tol3d,
|
|
ON_SimpleArray<TL_CX_EVENT>& ccx
|
|
)
|
|
{
|
|
// third level checker for CCX - both nodes are bottom level
|
|
// leaves with m_bez != NULL
|
|
const ON_CurveTreeBezier& bezA = *nodeA->m_bez;
|
|
const ON_CurveTreeBezier& bezB = *nodeB->m_bez;
|
|
double a, b;
|
|
ON_3dPoint A, B;
|
|
bezA.m_leafbox.GetClosestPointSeed( bezB.m_leafbox,&a,&b );
|
|
|
|
// TODO - call 2d solver here
|
|
|
|
// make sure results are ok.
|
|
A = bezA.PointAt(a);
|
|
B = bezB.PointAt(b);
|
|
if ( A.DistanceTo(B) <= tol3d )
|
|
{
|
|
TL_CX_EVENT& x = ccx.AppendNew();
|
|
x.A[0] = A;
|
|
x.A[1] = A;
|
|
x.B[0] = B;
|
|
x.B[1] = B;
|
|
x.a[0] = x.a[1] = nodeA->m_domain.ParameterAt(a);
|
|
x.b[0] = x.b[1] = x.b[2] = x.b[3] = nodeB->m_domain.ParameterAt(b);
|
|
x.type = TL_CCX_POINT;
|
|
x.cnodeA[0] = nodeA;
|
|
x.cnodeB[0] = nodeB;
|
|
x.x[0] = A.DistanceTo(B);
|
|
}
|
|
}
|
|
|
|
static void CCXHelper1( const ON_CurveTreeNode* nodeA, const ON_CurveTreeNode* nodeB,
|
|
double tol3d,
|
|
ON_SimpleArray<TL_CX_EVENT>& ccx
|
|
);
|
|
|
|
static void CCXHelper2( const ON_CurveTreeNode* nodeA, const ON_CurveTreeNode* nodeB,
|
|
double tol3d,
|
|
ON_SimpleArray<TL_CX_EVENT>& ccx
|
|
)
|
|
{
|
|
// second level checker for CCX - both nodes have m_bez != NULL and valid leaf boxes
|
|
// but they may have down nodes
|
|
if ( nodeA->m_down[0] || nodeA->m_down[1] )
|
|
{
|
|
if ( (nodeB->m_down[0] || nodeB->m_down[1])
|
|
&& (nodeA->m_bez->m_leafbox.m_L.Length() <= nodeB->m_bez->m_leafbox.m_L.Length()) )
|
|
{
|
|
// nodeB is bigger, split it
|
|
CCXHelper1( nodeA, nodeB->m_down[0], tol3d, ccx );
|
|
CCXHelper1( nodeA, nodeB->m_down[1], tol3d, ccx );
|
|
}
|
|
else
|
|
{
|
|
// nodeA is bigger, split t
|
|
CCXHelper1( nodeA->m_down[0], nodeB, tol3d, ccx );
|
|
CCXHelper1( nodeA->m_down[1], nodeB, tol3d, ccx );
|
|
}
|
|
}
|
|
else if ( nodeB->m_down[0] || nodeB->m_down[1] )
|
|
{
|
|
CCXHelper1( nodeA, nodeB->m_down[0], tol3d, ccx );
|
|
CCXHelper1( nodeA, nodeB->m_down[1], tol3d, ccx );
|
|
}
|
|
else
|
|
{
|
|
// time to look fot intersections on leaves
|
|
CCXHelper3( nodeA, nodeB, tol3d, ccx );
|
|
}
|
|
}
|
|
|
|
static void CCXHelper1( const ON_CurveTreeNode* nodeA, const ON_CurveTreeNode* nodeB,
|
|
double tol3d,
|
|
ON_SimpleArray<TL_CX_EVENT>& ccx
|
|
)
|
|
{
|
|
// first level checker for CCX
|
|
double a,b;
|
|
if ( !nodeA )
|
|
return;
|
|
if ( !nodeB )
|
|
return;
|
|
if ( nodeA->IsFartherThan(tol3d,nodeB) )
|
|
{
|
|
return;
|
|
}
|
|
if ( nodeA->m_bez )
|
|
{
|
|
if ( nodeB->m_bez )
|
|
{
|
|
CCXHelper2( nodeA, nodeB, tol3d, ccx );
|
|
}
|
|
else
|
|
{
|
|
CCXHelper1(nodeA,nodeB->m_down[0],tol3d,ccx);
|
|
CCXHelper1(nodeA,nodeB->m_down[1],tol3d,ccx);
|
|
}
|
|
}
|
|
else if ( nodeB->m_bez )
|
|
{
|
|
if ( nodeA->m_bez )
|
|
{
|
|
CCXHelper2( nodeA, nodeB, tol3d, ccx );
|
|
}
|
|
else
|
|
{
|
|
CCXHelper1(nodeA->m_down[0],nodeB,tol3d,ccx);
|
|
CCXHelper1(nodeA->m_down[1],nodeB,tol3d,ccx);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
a = nodeA->m_bbox.Diagonal().LengthSquared();
|
|
b = nodeB->m_bbox.Diagonal().LengthSquared();
|
|
if ( a <= b )
|
|
{
|
|
CCXHelper1(nodeA,nodeB->m_down[0],tol3d,ccx);
|
|
CCXHelper1(nodeA,nodeB->m_down[1],tol3d,ccx);
|
|
}
|
|
else
|
|
{
|
|
CCXHelper1(nodeA->m_down[0],nodeB,tol3d,ccx);
|
|
CCXHelper1(nodeA->m_down[1],nodeB,tol3d,ccx);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void DaleCCX( const ON_Curve& curveA,
|
|
const ON_Curve& curveB,
|
|
double tol3d,
|
|
ON_SimpleArray<TL_CX_EVENT>& ccx
|
|
)
|
|
{
|
|
ON_SimpleArray<ON_X_EVENT> xxx(16);
|
|
curveA.IntersectCurve(&curveB,xxx,tol3d);
|
|
for(int i=0; i<xxx.Count(); i++){
|
|
ON_X_EVENT& x = xxx[i];
|
|
if( x.m_type==ON_X_EVENT::ccx_point || x.m_type==ON_X_EVENT::ccx_overlap){
|
|
TL_CX_EVENT tlX;
|
|
if( x.m_type==ON_X_EVENT::ccx_point)
|
|
tlX.type = TL_CCX_POINT;
|
|
else
|
|
tlX.type = TL_CCX_OVERLAP;
|
|
|
|
tlX.A[0]=x.m_A[0];
|
|
tlX.A[1]=x.m_A[1];
|
|
tlX.B[0]=x.m_B[0];
|
|
tlX.B[1]=x.m_B[1];
|
|
tlX.a[0]=x.m_a[0];
|
|
tlX.a[1]=x.m_a[1];
|
|
tlX.b[0]=x.m_b[0];
|
|
tlX.b[1]=x.m_b[1];
|
|
tlX.b[2]=x.m_b[2];
|
|
tlX.b[3]=x.m_b[3];
|
|
ccx.Append(tlX);
|
|
}
|
|
}
|
|
|
|
return;
|
|
|
|
//ON_3dPoint A, B;
|
|
//double d;
|
|
const ON_CurveTree* treeA = curveA.CurveTree();
|
|
const ON_CurveTree* treeB = curveB.CurveTree();
|
|
|
|
//bool bRedo = true;
|
|
//int i = ccx.Count();
|
|
CCXHelper1( treeA->m_root, treeB->m_root, tol3d, ccx );
|
|
|
|
/*
|
|
while ( i < ccx.Count() )
|
|
{
|
|
bRedo = false;
|
|
TL_CX_EVENT& e = ccx[i++];
|
|
A = curveA.PointAt(e.a[0]);
|
|
B = curveB.PointAt(e.b[0]);
|
|
d = A.DistanceTo(B);
|
|
if ( d > tol3d )
|
|
bRedo = true;
|
|
}
|
|
|
|
if ( bRedo )
|
|
{
|
|
CCXHelper1( treeA->m_root, treeB->m_root, tol3d, ccx );
|
|
}
|
|
*/
|
|
}
|
|
|
|
|
|
|
|
|
|
class EF
|
|
{
|
|
public:
|
|
const ON_CurveTree* ct;
|
|
const ON_CurveTreeNode* node;
|
|
ON_3dPoint P;
|
|
ON_BezierCurve b;
|
|
double d;
|
|
double s; // bezier parameter
|
|
double t; // curve parameter
|
|
int local_solves;
|
|
};
|
|
|
|
|
|
class Greg_farg
|
|
{
|
|
public:
|
|
const ON_BezierCurve* bez;
|
|
ON_3dPoint P;
|
|
double GregLocalSolveAbsThresh2;
|
|
};
|
|
|
|
static
|
|
int GregDistance2CrvPtHess(void* fdata, int n, const double* x, double& fx, double* df, double** Hess){
|
|
Greg_farg* farg = (Greg_farg*) fdata;
|
|
ON_3dVector CEval[3];
|
|
ON_3dVector& C=CEval[0];
|
|
ON_3dVector& Ct=CEval[1];
|
|
ON_3dVector& Ctt=CEval[2];
|
|
ON_BOOL32 rc = farg->bez->Evaluate(*x,(Hess)?2:1,3,CEval[0]);
|
|
for(int i=farg->bez->Dimension(); i<3; i++)
|
|
CEval[0][i]=CEval[1][i]=CEval[2][i] = 0.0;
|
|
C -= farg->P;
|
|
fx = C*C;
|
|
if(df)
|
|
*df = 2 * C* Ct;
|
|
if(Hess && *Hess)
|
|
Hess[0][0] = 2.0 * ( Ct*Ct + Ctt*C);
|
|
if( fx <= farg->GregLocalSolveAbsThresh2 )
|
|
return 2;
|
|
return rc?1:-1;
|
|
}
|
|
|
|
int ClosestForkHelper_dbrent( EF& ef, const ON_CurveTreeNode* node )
|
|
{
|
|
const ON_CurveTreeBezier* bez = node->m_bez;
|
|
|
|
ON_3dPoint Q;
|
|
double x, w, t;
|
|
int i;
|
|
|
|
ef.local_solves++;
|
|
|
|
// set "t" to best guess based on the line.
|
|
bez->m_leafbox.GetClosestPointSeed(ef.P,&t);
|
|
|
|
if ( 2 == bez->m_order && !bez->m_is_rat )
|
|
{
|
|
Q = bez->PointAt(t);
|
|
w = Q.DistanceTo(ef.P);
|
|
if ( ef.d < 0.0 || w < ef.d )
|
|
{
|
|
ef.d = w;
|
|
ef.s = t;
|
|
ef.t = node->m_domain.ParameterAt(ef.s);
|
|
ef.node = node;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if ( ef.b.m_dim != bez->m_dim || ef.b.m_is_rat != bez->m_is_rat || ef.b.m_order != bez->m_order )
|
|
{
|
|
ef.b.m_dim = bez->m_dim;
|
|
ef.b.m_is_rat = bez->m_is_rat;
|
|
ef.b.m_cv_stride = bez->m_cv_stride;
|
|
ef.b.m_order = bez->m_order;
|
|
ef.b.ReserveCVCapacity(ef.b.m_cv_stride*ef.b.m_order);
|
|
}
|
|
double *cv = ef.b.m_cv;
|
|
memcpy( cv, bez->m_cv, ef.b.m_cv_stride*ef.b.m_order*sizeof(*cv));
|
|
i = ef.b.m_order;
|
|
if ( ef.b.m_is_rat )
|
|
{
|
|
while(i--)
|
|
{
|
|
w = cv[3];
|
|
x = 1.0/w;
|
|
cv[0] = w*(x*cv[0] - ef.P.x);
|
|
cv[1] = w*(x*cv[1] - ef.P.y);
|
|
cv[2] = w*(x*cv[2] - ef.P.z);
|
|
cv += 4;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while(i--)
|
|
{
|
|
*cv++ -= ef.P.x;
|
|
*cv++ -= ef.P.y;
|
|
*cv++ -= ef.P.z;
|
|
}
|
|
}
|
|
|
|
i = TL_EvBezierLocalExtremum(
|
|
0, // means look for a local min
|
|
ef.b.m_dim, ef.b.m_is_rat, ef.b.m_order,
|
|
ef.b.m_cv,
|
|
0.0,1.0, // search entire bezier from 0.0 <= s <= 1.0
|
|
0.0, // stop search if a distance of 0.0 is found
|
|
0.5*ON_SQRT_EPSILON, // step tol
|
|
&t);
|
|
|
|
Q = bez->PointAt(t);
|
|
x = Q.DistanceTo(ef.P);
|
|
|
|
if ( x < ef.d || ef.d < 0.0 )
|
|
{
|
|
ef.s = t;
|
|
ef.t = node->m_domain.ParameterAt(ef.s);
|
|
ef.d = x;
|
|
ef.node = node;
|
|
i = 1;
|
|
}
|
|
else
|
|
{
|
|
i = 0;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
class CGooseEv : public IwEvalFunctionObject
|
|
{
|
|
public:
|
|
const ON_CurveTreeBezier& m_bez;
|
|
const IwPoint3d m_crTestPoint;
|
|
double m_dSquaredTolerance;
|
|
IwSolverOperationType m_eSolverOperation;
|
|
IwBoolean m_bHaveScale;
|
|
ULONG m_lNumScale;
|
|
|
|
CGooseEv(const EF& ef, const ON_CurveTreeNode* node,
|
|
double dSquaredTolerance, IwSolverOperationType eSolverOperation)
|
|
: m_bez(*node->m_bez),
|
|
m_crTestPoint(ef.P.x,ef.P.y,ef.P.z),
|
|
m_dSquaredTolerance(dSquaredTolerance),
|
|
m_eSolverOperation(eSolverOperation),
|
|
m_bHaveScale(false), m_lNumScale(0) {}
|
|
|
|
IwStatus Evaluate(double dT,
|
|
double & rdFOfT,
|
|
double & rdFPrimeOfT,
|
|
IwBoolean & rbFoundAnswer);
|
|
};
|
|
|
|
IwStatus CGooseEv::Evaluate(double dT,
|
|
double & rdFOfT,
|
|
double & rdFPrimeOfT,
|
|
IwBoolean & rbFoundAnswer)
|
|
{
|
|
rbFoundAnswer = false;
|
|
IwPoint3d sPVV[3];
|
|
|
|
// put pt, 1rst, 2nd der in sPVV
|
|
m_bez.Evaluate(dT,2,3,&sPVV[0].x);
|
|
//SER(m_crCurve.Evaluate(dT,2,true,sPVV));
|
|
|
|
IwVector3d sVec = sPVV[0] - m_crTestPoint;
|
|
|
|
// Scale the problem down to a reasonable size (0-100)
|
|
if (!m_bHaveScale) {
|
|
while (sPVV[1].LengthSquared() > 1.0e4) {
|
|
m_lNumScale ++;
|
|
sVec = sVec / 10.0;
|
|
sPVV[0] = sPVV[0] / 10.0;
|
|
sPVV[1] = sPVV[1] / 10.0;
|
|
sPVV[2] = sPVV[2] / 10.0;
|
|
}
|
|
m_bHaveScale = true;
|
|
}
|
|
else {
|
|
for (ULONG kk=0; kk<m_lNumScale; kk++) {
|
|
sVec = sVec / 10.0;
|
|
sPVV[0] = sPVV[0] / 10.0;
|
|
sPVV[1] = sPVV[1] / 10.0;
|
|
sPVV[2] = sPVV[2] / 10.0;
|
|
}
|
|
}
|
|
|
|
double sTanLenSq = sPVV[1].LengthSquared();
|
|
// if we have a zero length tangent then we must fail
|
|
if (sTanLenSq < IW_EFF_ZERO_SQ) {
|
|
return IW_ERR;
|
|
}
|
|
|
|
rdFOfT = sVec.Dot(sPVV[1]);
|
|
rdFPrimeOfT = sPVV[2].Dot(sVec) + sPVV[1].Dot(sPVV[1]);
|
|
if (m_eSolverOperation == IW_SO_MINIMIZE) {
|
|
// Let sign of FOfT rule
|
|
rdFPrimeOfT = iwos_Fabs(rdFPrimeOfT);
|
|
}
|
|
else if (m_eSolverOperation == IW_SO_MAXIMIZE) {
|
|
// Let - sign of FofT rule
|
|
rdFPrimeOfT = - iwos_Fabs(rdFPrimeOfT);
|
|
}
|
|
|
|
// Now test to see if geometric tolerance satisfied
|
|
double sVecLenSq = sVec.LengthSquared();
|
|
if (sVecLenSq < m_dSquaredTolerance) {
|
|
if (m_eSolverOperation == IW_SO_MAXIMIZE) {
|
|
return IW_ERR;
|
|
}
|
|
rbFoundAnswer = true;
|
|
return IW_SUCCESS;
|
|
}
|
|
|
|
// Find square of projection of Vec onto the Tangent of curve.
|
|
// Test this to see if we satisfy our tolerance
|
|
double dProjectionDistanceSquared = (rdFOfT * rdFOfT) / (sTanLenSq * sVecLenSq);
|
|
if (dProjectionDistanceSquared < m_dSquaredTolerance) {
|
|
rbFoundAnswer = true;
|
|
return IW_SUCCESS;
|
|
}
|
|
|
|
return IW_SUCCESS;
|
|
}
|
|
|
|
int ClosestForkHelper_hybrid( EF& ef, const ON_CurveTreeNode* node )
|
|
{
|
|
double t;
|
|
|
|
node->m_bez->m_leafbox.GetClosestPointSeed(ef.P,&t);
|
|
|
|
CGooseEv sEFO(ef,node,IW_EFF_ZERO_SQ,IW_SO_MINIMIZE);
|
|
IwLocalSolve1d sLS(sEFO, IwExtent1d(0.0,1.0), false);
|
|
IwBoolean bFoundSolution;
|
|
double dFoundT;
|
|
if (sLS.SolveIt(t,1.0e-8,bFoundSolution,dFoundT) != IW_SUCCESS)
|
|
{
|
|
// Goose failed - use foolproof method.
|
|
ClosestForkHelper_dbrent(ef,node);
|
|
}
|
|
else
|
|
{
|
|
ON_3dPoint Q = node->m_bez->PointAt(dFoundT);
|
|
double d = Q.DistanceTo(ef.P);
|
|
if ( ef.d < 0.0 || d < ef.d )
|
|
{
|
|
ef.s = dFoundT;
|
|
ef.t = node->m_domain.ParameterAt(ef.s);
|
|
ef.d = d;
|
|
ef.node = node;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static bool ClosestFork( EF& ef, double d, ON_CurveTreeNode* node )
|
|
{
|
|
double downd[2];
|
|
int i;
|
|
bool rc = false;
|
|
|
|
if ( ef.d > 0.0 )
|
|
{
|
|
if ( d < 0.0 )
|
|
{
|
|
d = node->MinimumDistanceLowerBound(ef.P);
|
|
}
|
|
if ( d > ef.d )
|
|
return false;
|
|
}
|
|
|
|
if ( node->m_down[0] )
|
|
{
|
|
if ( node->m_down[1] )
|
|
{
|
|
downd[0] = node->m_down[0]->MinimumDistanceLowerBound(ef.P);
|
|
downd[1] = node->m_down[1]->MinimumDistanceLowerBound(ef.P);
|
|
i = ( downd[0] <= downd[1] ) ? 0 : 1;
|
|
if ( ef.d < 0.0 || downd[i] < ef.d )
|
|
{
|
|
rc = ClosestFork( ef, downd[i], node->m_down[i] );
|
|
if ( ef.d < 0.0 || downd[1-i] < ef.d )
|
|
{
|
|
if ( ClosestFork( ef, downd[1-i], node->m_down[1-i] ) )
|
|
{
|
|
rc = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rc = ClosestFork( ef, ON_UNSET_VALUE, node->m_down[0] );
|
|
}
|
|
}
|
|
else if ( node->m_down[1] )
|
|
{
|
|
rc = ClosestFork( ef, ON_UNSET_VALUE, node->m_down[1] );
|
|
}
|
|
else if ( node->m_bez )
|
|
{
|
|
// it's a leaf
|
|
if ( ClosestForkHelper_hybrid(ef,node) )
|
|
{
|
|
rc = true;
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
class CPolishUp : public ON_NurbsCurve
|
|
{
|
|
public:
|
|
ON_3dPoint m_P;
|
|
};
|
|
|
|
static void fff(void* farg,double t,double* x,double* dx)
|
|
{
|
|
ON_3dVector V[3];
|
|
double d;
|
|
CPolishUp* p = (CPolishUp*)farg;
|
|
|
|
ON_EvaluateNurbsSpan(
|
|
p->m_dim, p->m_is_rat, p->m_order,
|
|
p->m_knot,
|
|
p->m_cv_stride, p->m_cv,
|
|
dx?2:1, //der_count,
|
|
t,
|
|
3, //v_stride,
|
|
&V[0].x
|
|
);
|
|
|
|
V[0].x -= p->m_P.x;
|
|
V[0].y -= p->m_P.y;
|
|
V[0].z -= p->m_P.z;
|
|
|
|
d = V[0].x*V[1].x + V[0].y*V[1].y + V[0].z*V[1].z;
|
|
*x = d;
|
|
if ( dx )
|
|
{
|
|
*dx = V[0].x*V[2].x + V[0].y*V[2].y + V[0].z*V[2].z -(V[1].x*V[1].x + V[1].y*V[1].y + V[1].z*V[1].z);
|
|
}
|
|
}
|
|
|
|
class CCurveTestPoint
|
|
{
|
|
public:
|
|
int test_point_index;
|
|
|
|
ON_3dPoint P; // test point = C + d*(variable unit vector)
|
|
double t;
|
|
double d;
|
|
|
|
// The information below is filled in during accuracy testing phase
|
|
|
|
enum FUNC_CRV_CLSPT
|
|
{
|
|
FUNC_CRV_CLSPT_NONE = 0,
|
|
FUNC_CRV_CLSPT_BASELINE = 1, // Dale's new tree with hybrid newton/debrent local solve
|
|
FUNC_CRV_CLSPT_ON_TREETEST = 2, // Used to determine where code is spending time
|
|
FUNC_CRV_CLSPT_ON_CRV = 3, // Generic ON_Curve::GetClosestPoint()
|
|
FUNC_CRV_CLSPT_ON_NURBS = 4, // OpenNurbs ON_NurbsCurve::GetClosestPoint()
|
|
FUNC_CRV_CLSPT_GOOSE = 5, // old gslib
|
|
FUNC_CRV_CLSPT_COUNT = 6
|
|
};
|
|
|
|
// paramter of closest point
|
|
double crv_t[FUNC_CRV_CLSPT_COUNT];
|
|
|
|
// distance from P to
|
|
double crv_d[FUNC_CRV_CLSPT_COUNT];
|
|
|
|
// best result from a test
|
|
double best_t;
|
|
double best_d;
|
|
};
|
|
|
|
|
|
void GetCurvePointCloud( const ON_Curve& curve,
|
|
double sample_start_distance,
|
|
double sample_stop_distance,
|
|
ON_SimpleArray<CCurveTestPoint>& CPT )
|
|
{
|
|
// appends a cloud of points to pts.
|
|
int hint = 0;
|
|
int i,j,k,n,m;
|
|
|
|
ON_Plane plane;
|
|
ON_3dPoint P;
|
|
ON_3dVector T;
|
|
ON_Interval span_domain;
|
|
double r, a, t;
|
|
|
|
if ( sample_start_distance >= sample_stop_distance )
|
|
sample_start_distance = sample_stop_distance;
|
|
|
|
int span_count = curve.SpanCount();
|
|
if (span_count < 1 )
|
|
{
|
|
return;
|
|
}
|
|
double* span_vector = (double*)onmalloc((span_count+1)*sizeof(*span_vector));
|
|
if ( !curve.GetSpanVector(span_vector))
|
|
return;
|
|
|
|
int degree = curve.Degree();
|
|
|
|
int span_sample_count = 4*3*5*7;
|
|
if ( sample_stop_distance <= 0.0 )
|
|
{
|
|
k = 1;
|
|
}
|
|
else
|
|
{
|
|
k = 0;
|
|
for ( r = sample_start_distance; r <= sample_stop_distance*(1.0+ON_SQRT_EPSILON); r *= 2.0 )
|
|
{
|
|
k++;
|
|
}
|
|
if ( k < 1 )
|
|
k = 1;
|
|
k *= 4;
|
|
}
|
|
|
|
|
|
while ( span_count*span_sample_count*k < 30000 )
|
|
{
|
|
span_sample_count *= 2;
|
|
}
|
|
|
|
if ( degree <= 2 && span_count <= 4 )
|
|
{
|
|
while ( span_count*span_sample_count*k < 60000 )
|
|
span_sample_count *= 2;
|
|
}
|
|
|
|
if ( bPurify )
|
|
{
|
|
// reduce sample counts so purify will finish somtime today
|
|
span_sample_count = 3;
|
|
sample_stop_distance = sample_start_distance;
|
|
}
|
|
|
|
CPT.Reserve(span_count*span_sample_count*17);
|
|
|
|
for (i = 0; i < span_count; i++ )
|
|
{
|
|
span_domain.Set(span_vector[i],span_vector[i+1]);
|
|
for ( j = i?0:1; j <= span_sample_count; j++ )
|
|
{
|
|
t = span_domain.ParameterAt(((double)j)/((double)span_sample_count));
|
|
curve.EvTangent(t,P,T,1,&hint);
|
|
plane.CreateFromNormal(P,T);
|
|
|
|
if ( sample_stop_distance <= 0.0 )
|
|
{
|
|
CCurveTestPoint& CP = CPT.AppendNew();
|
|
CP.test_point_index = CPT.Count()-1;
|
|
CP.P = P;
|
|
CP.t = t;
|
|
CP.d = 0.0;
|
|
CP.best_t = ON_UNSET_VALUE;
|
|
CP.best_d = ON_UNSET_VALUE;
|
|
for( m = 0; m < CCurveTestPoint::FUNC_CRV_CLSPT_COUNT; m++ )
|
|
{
|
|
CP.crv_d[m] = ON_UNSET_VALUE;
|
|
CP.crv_t[m] = ON_UNSET_VALUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
k = 0;
|
|
for ( r = sample_start_distance; r <= sample_stop_distance*(1.0+ON_SQRT_EPSILON); r *= 2.0 )
|
|
{
|
|
for ( n = 0; n <= 4; n++ )
|
|
{
|
|
CCurveTestPoint& CP = CPT.AppendNew();
|
|
CP.test_point_index = CPT.Count()-1;
|
|
a = (k/6.0 + n*0.5)*ON_PI;
|
|
CP.P = plane.origin + r*(cos(a)*plane.xaxis + sin(a)*plane.yaxis);
|
|
CP.t = t;
|
|
CP.d = P.DistanceTo(CP.P);
|
|
CP.best_t = ON_UNSET_VALUE;
|
|
CP.best_d = ON_UNSET_VALUE;
|
|
for( m = 0; m < CCurveTestPoint::FUNC_CRV_CLSPT_COUNT; m++ )
|
|
{
|
|
CP.crv_d[m] = ON_UNSET_VALUE;
|
|
CP.crv_t[m] = ON_UNSET_VALUE;
|
|
}
|
|
if ( 0.0 == r )
|
|
break;
|
|
}
|
|
k++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
onfree(span_vector);
|
|
|
|
// FOR DEBUGGING A CLOSEST POINT TO CURVE BAD POINT
|
|
int singleton_index = -1;
|
|
if ( singleton_index >= 0 && singleton_index < CPT.Count() )
|
|
{
|
|
CPT[0] = CPT[singleton_index];
|
|
CPT.SetCount(1);
|
|
}
|
|
}
|
|
|
|
class CClosestPointToCurveTestResults
|
|
{
|
|
public:
|
|
bool m_bSpeedTest;
|
|
bool m_bAccuracyTest;
|
|
double m_elapsed_time;
|
|
double m_speed_factor; // =m_elapsed_time/best_time. 1 = best, > 1 means slower than the best
|
|
|
|
double m_on_curve_3d_error; // average absolute 3d distance error for points on the curve
|
|
double m_on_curve_t_error; // relative t error for points on the curve
|
|
|
|
double m_off_curve_3d_error; // average relative error for points away from the curve
|
|
double m_off_curve_t_error; // relative t error for points on the curve
|
|
|
|
int m_test_count; // total number of tests
|
|
int m_failure_count; // number of failures to return any answer
|
|
|
|
int m_error_count; // number of calls to ON_Error
|
|
int m_warning_count; // number of calls to ON_Warning
|
|
|
|
int m_on_test_count;
|
|
int m_on_best_count;
|
|
int m_on_sloppy_count; // number of sloppy answers for points on the curve
|
|
int m_on_wrong_count; // number of wrong answers for points on the curve
|
|
|
|
int m_off_test_count;
|
|
int m_off_best_count; // number of times this got the best answer
|
|
int m_off_sloppy_count; // number of sloppy answers for points off of the curve
|
|
int m_off_wrong_count; // number of wrong answers for points off of the curve
|
|
|
|
};
|
|
|
|
class CClosestPointToSurfaceTestResults
|
|
{
|
|
public:
|
|
bool m_bSpeedTest;
|
|
bool m_bAccuracyTest;
|
|
double m_elapsed_time;
|
|
double m_speed_factor; // =m_elapsed_time/best_time. 1 = best, > 1 means slower than the best
|
|
|
|
double m_on_surface_3d_error; // average absolute 3d distance error for points on the surface
|
|
ON_2dVector m_on_surface_uv_error; // relative t error for points on the surface
|
|
|
|
double m_off_surface_3d_error; // average relative error for points away from the surface
|
|
ON_2dVector m_off_surface_uv_error; // relative t error for points on the surface
|
|
|
|
int m_test_count; // total number of tests
|
|
int m_failure_count; // number of failures to return any answer
|
|
|
|
int m_error_count; // number of calls to ON_Error
|
|
int m_warning_count; // number of calls to ON_Warning
|
|
|
|
int m_on_test_count;
|
|
int m_on_best_count;
|
|
int m_on_sloppy_count; // number of sloppy answers for points on the surface
|
|
int m_on_wrong_count; // number of wrong answers for points on the surface
|
|
|
|
int m_off_test_count;
|
|
int m_off_best_count; // number of times this got the best answer
|
|
int m_off_sloppy_count; // number of sloppy answers for points off of the surface
|
|
int m_off_wrong_count; // number of wrong answers for points off of the surface
|
|
|
|
};
|
|
|
|
class CClosestPointToMeshTestResults
|
|
{
|
|
public:
|
|
bool m_bSpeedTest;
|
|
bool m_bAccuracyTest;
|
|
double m_mesh_elapsed_time;
|
|
double m_surface_elapsed_time;
|
|
double m_speed_factor; // =m_mesh_elapsed_time/m_surface_elapsed_time; (bigger = slower)
|
|
|
|
double m_on_mesh_3d_error; // average absolute 3d distance error for points on the mesh
|
|
|
|
double m_off_mesh_3d_error; // average relative error for points away from the mesh
|
|
|
|
int m_test_count; // total number of tests
|
|
int m_failure_count; // number of failures to return any answer
|
|
|
|
int m_error_count; // number of calls to ON_Error
|
|
int m_warning_count; // number of calls to ON_Warning
|
|
|
|
int m_on_test_count;
|
|
int m_on_best_count;
|
|
int m_on_sloppy_count; // number of sloppy answers for points on the mesh
|
|
int m_on_wrong_count; // number of wrong answers for points on the mesh
|
|
|
|
int m_off_test_count;
|
|
int m_off_best_count; // number of times this got the best answer
|
|
int m_off_sloppy_count; // number of sloppy answers for points off of the mesh
|
|
int m_off_wrong_count; // number of wrong answers for points off of the mesh
|
|
};
|
|
|
|
|
|
class CClosestPointToCurveTest
|
|
{
|
|
public:
|
|
virtual void Setup( const ON_Curve* curve ) = 0;
|
|
|
|
virtual int GetClosestPoint( ON_3dPoint P, double* t ) = 0;
|
|
|
|
// returns time in seconds
|
|
double SpeedTest( int test_point_count, const CCurveTestPoint* tp);
|
|
|
|
// returns error count
|
|
int AccuracyTest( int test_point_count, CCurveTestPoint* tp );
|
|
|
|
void CalculateResults( const ON_Curve& curve,
|
|
int test_point_count,
|
|
const CCurveTestPoint* tp,
|
|
double best_time
|
|
);
|
|
|
|
void Print( ON_TextLog& text_log, int test_count );
|
|
|
|
CCurveTestPoint::FUNC_CRV_CLSPT m_func;
|
|
|
|
//const ON_NurbsCurve* m_nurbs_curve;
|
|
const ON_Curve* m_curve;
|
|
|
|
CClosestPointToCurveTestResults m_results;
|
|
|
|
void SetupHelper( CCurveTestPoint::FUNC_CRV_CLSPT func, const ON_Curve* curve );
|
|
};
|
|
|
|
void CClosestPointToCurveTest::SetupHelper( CCurveTestPoint::FUNC_CRV_CLSPT func, const ON_Curve* curve )
|
|
{
|
|
m_func = func;
|
|
m_curve = curve;
|
|
memset(&m_results,0,sizeof(m_results));
|
|
m_results.m_elapsed_time = ON_UNSET_VALUE;
|
|
}
|
|
|
|
double CClosestPointToCurveTest::SpeedTest(int point_count, const CCurveTestPoint* tp)
|
|
{
|
|
double t;
|
|
int i;
|
|
clock_t time0, time1;
|
|
|
|
int error_count0 = ON_GetErrorCount();
|
|
int warning_count0 = ON_GetWarningCount();
|
|
|
|
i = point_count;
|
|
time0 = clock();
|
|
while(i--)
|
|
{
|
|
t = ON_UNSET_VALUE;
|
|
GetClosestPoint(tp->P,&t);
|
|
tp++;
|
|
}
|
|
time1 = clock();
|
|
|
|
m_results.m_elapsed_time = ((double)(time1 - time0))/((double)CLOCKS_PER_SEC);
|
|
|
|
m_results.m_bSpeedTest = true;
|
|
|
|
m_results.m_error_count += (ON_GetErrorCount()-error_count0);
|
|
m_results.m_warning_count += (ON_GetWarningCount()-warning_count0);
|
|
|
|
return m_results.m_elapsed_time;
|
|
}
|
|
|
|
int CClosestPointToCurveTest::AccuracyTest(int point_count, CCurveTestPoint* tp)
|
|
{
|
|
ON_3dPoint Q;
|
|
double d;
|
|
int rc = 0;
|
|
int i;
|
|
double t;
|
|
|
|
int error_count0 = ON_GetErrorCount();
|
|
int warning_count0 = ON_GetWarningCount();
|
|
|
|
m_results.m_bAccuracyTest = true;
|
|
m_results.m_test_count = point_count;
|
|
|
|
for ( i = 0; i < point_count; i++ )
|
|
{
|
|
t = ON_UNSET_VALUE;
|
|
if( !GetClosestPoint(tp[i].P,&t) )
|
|
{
|
|
rc++;
|
|
tp[i].crv_t[m_func] = ON_UNSET_VALUE;
|
|
tp[i].crv_d[m_func] = ON_UNSET_VALUE;
|
|
m_results.m_failure_count++;
|
|
}
|
|
else
|
|
{
|
|
Q = m_curve->PointAt(t);
|
|
d = Q.DistanceTo(tp[i].P);
|
|
tp[i].crv_t[m_func] = t;
|
|
tp[i].crv_d[m_func] = d;
|
|
if ( ON_UNSET_VALUE == tp[i].best_d || d < tp[i].best_d )
|
|
{
|
|
tp[i].best_d = d;
|
|
tp[i].best_t = t;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_results.m_error_count += (ON_GetErrorCount()-error_count0);
|
|
m_results.m_warning_count += (ON_GetWarningCount()-warning_count0);
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
void CClosestPointToCurveTest::CalculateResults( const ON_Curve& curve,
|
|
int test_point_count,
|
|
const CCurveTestPoint* tp,
|
|
double best_time
|
|
)
|
|
{
|
|
int badi = -1;
|
|
double badd = 0.0;
|
|
m_results.m_speed_factor = (m_results.m_bSpeedTest && best_time>0.0) ? m_results.m_elapsed_time/best_time : 0.0;
|
|
if ( m_results.m_bAccuracyTest )
|
|
{
|
|
ON_Sum on_3d_error;
|
|
ON_Sum off_3d_error;
|
|
ON_Sum on_t_error;
|
|
ON_Sum off_t_error;
|
|
double d_err, t_err;
|
|
int i;
|
|
int hint = 0;
|
|
ON_3dPoint P;
|
|
ON_3dVector D;
|
|
for ( i = 0; i < test_point_count; i++ )
|
|
{
|
|
const CCurveTestPoint& T = tp[i];
|
|
|
|
if ( !ON_IsValid(T.crv_t[m_func]) )
|
|
continue;
|
|
|
|
curve.Ev1Der( T.best_t, P, D, 1, &hint );
|
|
|
|
t_err = D.Length();
|
|
t_err = ( t_err > ON_ZERO_TOLERANCE ) ? 1.0/t_err : 0.0;
|
|
t_err *= fabs(T.crv_t[m_func] - T.best_t);
|
|
|
|
d_err = (T.best_d < T.d) ? T.best_d : T.d;
|
|
|
|
d_err = T.crv_d[m_func] - d_err;
|
|
if ( 0.0 == T.d || 0.0 == T.best_d )
|
|
{
|
|
m_results.m_on_test_count++;
|
|
|
|
if ( T.crv_d[m_func] <= T.best_d )
|
|
{
|
|
m_results.m_on_best_count++;
|
|
t_err = 0.0;
|
|
}
|
|
if ( d_err > 1.0e-4 )
|
|
{
|
|
m_results.m_on_wrong_count++;
|
|
if ( CCurveTestPoint::FUNC_CRV_CLSPT_GOOSE != m_func && d_err > badd )
|
|
{
|
|
badd = d_err;
|
|
badi = T.test_point_index;
|
|
}
|
|
}
|
|
else if ( d_err > 1.0e-6 )
|
|
{
|
|
m_results.m_on_sloppy_count++;
|
|
}
|
|
else
|
|
{
|
|
on_3d_error.Plus(d_err);
|
|
on_t_error.Plus(t_err);
|
|
}
|
|
}
|
|
else if ( T.best_d > 0.0 )
|
|
{
|
|
m_results.m_off_test_count++;
|
|
|
|
if( T.crv_d[m_func] <= T.best_d )
|
|
{
|
|
m_results.m_off_best_count++;
|
|
t_err = 0.0;
|
|
}
|
|
d_err /= T.best_d;
|
|
if ( d_err > 0.15 )
|
|
{
|
|
m_results.m_off_wrong_count++;
|
|
}
|
|
else if ( d_err > 0.05 )
|
|
{
|
|
m_results.m_off_sloppy_count++;
|
|
}
|
|
else
|
|
{
|
|
off_3d_error.Plus(d_err);
|
|
off_t_error.Plus(t_err);
|
|
}
|
|
}
|
|
}
|
|
|
|
i = on_3d_error.SummandCount();
|
|
if ( i > 0 )
|
|
{
|
|
d_err = on_3d_error.Total()/((double)i);
|
|
t_err = on_t_error.Total()/((double)i);
|
|
m_results.m_on_curve_3d_error = d_err;
|
|
m_results.m_on_curve_t_error = t_err;
|
|
}
|
|
|
|
i = off_3d_error.SummandCount();
|
|
if ( i > 0 )
|
|
{
|
|
d_err = off_3d_error.Total()/((double)i);
|
|
t_err = off_t_error.Total()/((double)i);
|
|
m_results.m_off_curve_3d_error = d_err;
|
|
m_results.m_off_curve_t_error = t_err;
|
|
}
|
|
}
|
|
|
|
if ( badi>=0)
|
|
{
|
|
printf("TEST POINT %d had error of = %g. This is a ClosestPointToCurve BUG.\n",badi,badd);
|
|
}
|
|
}
|
|
|
|
void CClosestPointToCurveTest::Print( ON_TextLog& text_log, int test_count )
|
|
{
|
|
const char* format = "%s %s %5d %4.1fX (%6.3f secs) %3d%% %.1g %.1g";
|
|
|
|
const char* name = 0;
|
|
if ( test_count > 0 )
|
|
{
|
|
text_log.Print("Name Test Point Speed Accuracy\n");
|
|
text_log.Print(" count sloth time best 3d err t err\n");
|
|
name = "Perfect ";
|
|
}
|
|
else
|
|
{
|
|
switch(m_func)
|
|
{
|
|
case CCurveTestPoint::FUNC_CRV_CLSPT_BASELINE:
|
|
name = "Baseline ";
|
|
break;
|
|
case CCurveTestPoint::FUNC_CRV_CLSPT_ON_TREETEST:
|
|
name = "Tree Test ";
|
|
break;
|
|
case CCurveTestPoint::FUNC_CRV_CLSPT_ON_CRV:
|
|
name = "ON_Curve ";
|
|
break;
|
|
case CCurveTestPoint::FUNC_CRV_CLSPT_ON_NURBS:
|
|
name = "NURBS form ";
|
|
break;
|
|
case CCurveTestPoint::FUNC_CRV_CLSPT_GOOSE:
|
|
name = "Goose ";
|
|
break;
|
|
default:
|
|
name = "Anonymous ";
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
if ( test_count > 0 )
|
|
{
|
|
text_log.Print(format,
|
|
name,
|
|
" ",
|
|
test_count, // m_results.m_test_count,
|
|
1.0, //m_results.m_speed_factor,
|
|
0.0, //m_results.m_elapsed_time,
|
|
100,//test_count, //m_results.m_best_count,
|
|
1e-18, //m_results.m_on_curve_3d_error,
|
|
1e-18, //m_results.m_on_curve_t_error,
|
|
0, //m_results.m_on_wrong_count,
|
|
0 //m_results.m_on_sloppy_count
|
|
);
|
|
text_log.Print("\n");
|
|
}
|
|
else
|
|
{
|
|
if ( m_results.m_on_test_count > 0 )
|
|
{
|
|
int best = (int)floor(100.0*((double)m_results.m_on_best_count)/((double)m_results.m_test_count));
|
|
text_log.Print(format,
|
|
name,
|
|
"on ",
|
|
m_results.m_on_test_count,
|
|
m_results.m_speed_factor,
|
|
m_results.m_elapsed_time,
|
|
best,//m_results.m_on_best_count,
|
|
m_results.m_on_curve_3d_error,
|
|
m_results.m_on_curve_t_error
|
|
);
|
|
if( m_results.m_failure_count > 0 )
|
|
text_log.Print(" %d FAILURES",m_results.m_failure_count);
|
|
|
|
if( m_results.m_error_count > 0 )
|
|
text_log.Print(" %d ON_ERRORs",m_results.m_error_count);
|
|
if( m_results.m_warning_count > 0 )
|
|
text_log.Print(" %d ON_WARNINGSs",m_results.m_warning_count);
|
|
|
|
if( m_results.m_on_wrong_count > 0 )
|
|
text_log.Print(" %d WRONG ANSWERS",m_results.m_on_wrong_count);
|
|
if( m_results.m_on_sloppy_count > 0 )
|
|
text_log.Print(" %d sloppys answers",m_results.m_on_sloppy_count);
|
|
text_log.Print("\n");
|
|
}
|
|
|
|
if ( m_results.m_off_test_count > 0 )
|
|
{
|
|
int best = (int)floor(100.0*((double)m_results.m_off_best_count)/((double)m_results.m_test_count));
|
|
text_log.Print(format,
|
|
name,
|
|
"off ",
|
|
m_results.m_off_test_count,
|
|
m_results.m_speed_factor,
|
|
m_results.m_elapsed_time,
|
|
best, //m_results.m_off_best_count,
|
|
m_results.m_off_curve_3d_error,
|
|
m_results.m_off_curve_t_error
|
|
);
|
|
if( m_results.m_failure_count > 0 )
|
|
text_log.Print(" %d FAILURES",m_results.m_failure_count);
|
|
|
|
if( m_results.m_error_count > 0 )
|
|
text_log.Print(" %d ON_ERRORs",m_results.m_error_count);
|
|
if( m_results.m_warning_count > 0 )
|
|
text_log.Print(" %d ON_WARNINGSs",m_results.m_warning_count);
|
|
|
|
if( m_results.m_off_wrong_count > 0 )
|
|
text_log.Print(" %d WRONG ANSWERS",m_results.m_off_wrong_count);
|
|
if( m_results.m_off_sloppy_count > 0 )
|
|
text_log.Print(" %d sloppys answers",m_results.m_off_sloppy_count);
|
|
text_log.Print("\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
class CSurfaceTestPoint
|
|
{
|
|
public:
|
|
int test_point_index;
|
|
|
|
ON_3dPoint P; // test point = C + d*(variable unit vector)
|
|
ON_2dPoint uv;
|
|
double d;
|
|
|
|
// The information below is filled in during accuracy testing phase
|
|
|
|
enum FUNC_SRF_CLSPT
|
|
{
|
|
FUNC_SRF_CLSPT_NONE = 0,
|
|
FUNC_SRF_CLSPT_NEWON = 1, //
|
|
FUNC_SRF_CLSPT_GOOSE = 2, // old gslib
|
|
FUNC_SRF_CLSPT_COUNT = 3
|
|
};
|
|
|
|
// paramter of closest point
|
|
ON_2dPoint srf_uv[FUNC_SRF_CLSPT_COUNT];
|
|
|
|
// distance from P to
|
|
double srf_d[FUNC_SRF_CLSPT_COUNT];
|
|
|
|
// best result from a test
|
|
ON_2dPoint best_uv;
|
|
double best_d;
|
|
};
|
|
|
|
|
|
|
|
|
|
class CMeshTestPoint
|
|
{
|
|
public:
|
|
int test_point_index;
|
|
|
|
ON_MESH_POINT S; // seed point on mesh
|
|
ON_3dVector N; // normal for offsets
|
|
ON_3dPoint P; // test point
|
|
|
|
ON_MESH_POINT M; // best answer we found
|
|
|
|
double S_d; // distance from S to P.
|
|
double M_d; // distance from M to P.
|
|
};
|
|
|
|
ON_3dPoint FacePoint( const ON_MESH_POINT& S )
|
|
{
|
|
ON_3dPoint P(ON_UNSET_VALUE,ON_UNSET_VALUE,ON_UNSET_VALUE);
|
|
if ( S.m_mesh )
|
|
{
|
|
if ( S.m_face_index >= 0 && S.m_face_index < S.m_mesh->m_F.Count() )
|
|
{
|
|
ON_3dPoint V[4];
|
|
const int* fvi = S.m_mesh->m_F[S.m_face_index].vi;
|
|
const ON_3fPoint* mesh_V = S.m_mesh->m_V.Array();
|
|
V[0] = mesh_V[fvi[0]];
|
|
V[1] = mesh_V[fvi[1]];
|
|
V[2] = mesh_V[fvi[2]];
|
|
V[3] = mesh_V[fvi[3]];
|
|
const double* t = S.m_t;
|
|
P.x = t[0]*V[0].x + t[1]*V[1].x + t[2]*V[2].x + t[3]*V[3].x;
|
|
P.y = t[0]*V[0].y + t[1]*V[1].y + t[2]*V[2].y + t[3]*V[3].y;
|
|
P.z = t[0]*V[0].z + t[1]*V[1].z + t[2]*V[2].z + t[3]*V[3].z;
|
|
}
|
|
}
|
|
return P;
|
|
}
|
|
|
|
static
|
|
bool GetMeshPointCloudVertexHelper(
|
|
const ON_Mesh& mesh,
|
|
const ON_MeshTopology& top,
|
|
int topvi,
|
|
ON_SimpleArray<CMeshTestPoint>& CPT
|
|
)
|
|
{
|
|
const ON_MeshTopologyVertex& V = top.m_topv[topvi];
|
|
if ( V.m_tope_count < 1 || V.m_v_count < 1 )
|
|
return false;
|
|
|
|
const ON_MeshTopologyEdge& E = top.m_tope[V.m_topei[0]];
|
|
if ( E.m_topf_count < 1 )
|
|
return false;
|
|
|
|
int fi = E.m_topfi[0];
|
|
const int* fvi = mesh.m_F[fi].vi;
|
|
int j;
|
|
for ( j = 0; j < 4; j++ )
|
|
{
|
|
int vi = fvi[j];
|
|
if ( top.m_topv_map[vi] == topvi )
|
|
{
|
|
CMeshTestPoint& tp = CPT.AppendNew();
|
|
tp.test_point_index = CPT.Count()-1;
|
|
tp.N = mesh.m_N[vi];
|
|
tp.S.m_P = mesh.m_V[vi];
|
|
tp.S.m_face_index = fi;
|
|
tp.S.m_t[j] = 1.0;
|
|
tp.S.m_mesh = &mesh;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
static
|
|
bool GetMeshPointCloudEdgeHelper(
|
|
const ON_Mesh& mesh,
|
|
const ON_MeshTopology& top,
|
|
int topei,
|
|
int edge_sample_count,
|
|
ON_SimpleArray<CMeshTestPoint>& CPT
|
|
)
|
|
{
|
|
const ON_MeshTopologyEdge& E = top.m_tope[topei];
|
|
if ( E.m_topf_count < 1 )
|
|
return false;
|
|
const int fi = E.m_topfi[0];
|
|
const ON_MeshTopologyFace& F0 = top.m_topf[fi];
|
|
int fei;
|
|
for ( fei = 0; fei < 4; fei++ )
|
|
{
|
|
if ( F0.m_topei[fei] == topei )
|
|
{
|
|
int fvi0 = ( F0.IsTriangle() )
|
|
? ((fei+2)%3)
|
|
: ((fei+3)%4);
|
|
int fvi1 = fei;
|
|
const int* fvi = mesh.m_F[fi].vi;
|
|
ON_Line L;
|
|
L.from = mesh.m_V[fvi[fvi0]];
|
|
L.to = mesh.m_V[fvi[fvi1]];
|
|
|
|
ON_3dVector edgeN = mesh.m_FN[fi];
|
|
int j;
|
|
for ( j = 1; j < E.m_topf_count; j++ )
|
|
{
|
|
edgeN = edgeN + ON_3dVector(mesh.m_FN[E.m_topfi[j]]);
|
|
}
|
|
edgeN.Unitize();
|
|
|
|
for ( j = 1; j <= edge_sample_count; j++ )
|
|
{
|
|
double t = ((double)j)/((double)(edge_sample_count+1));
|
|
CMeshTestPoint& CP = CPT.AppendNew();
|
|
CP.test_point_index = CPT.Count()-1;
|
|
CP.N = edgeN;
|
|
CP.S.m_P = L.PointAt(t);
|
|
CP.S.m_face_index = fi;
|
|
CP.S.m_t[fvi0] = 1.0-t;
|
|
CP.S.m_t[fvi1] = t;
|
|
CP.S.m_mesh = &mesh;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static
|
|
bool GetMeshPointCloudFaceHelper(
|
|
const ON_Mesh& mesh,
|
|
const ON_MeshTopology& top,
|
|
int fi,
|
|
int face_sample_count,
|
|
int edge_sample_count,
|
|
ON_SimpleArray<CMeshTestPoint>& CPT
|
|
)
|
|
{
|
|
ON_MeshFace f = mesh.m_F[fi];
|
|
ON_3dVector N = mesh.m_FN[fi];
|
|
ON_3dPoint V[4];
|
|
int tri[2][3];
|
|
int edge[2];
|
|
V[0] = mesh.m_V[f.vi[0]];
|
|
V[1] = mesh.m_V[f.vi[1]];
|
|
V[2] = mesh.m_V[f.vi[2]];
|
|
V[3] = mesh.m_V[f.vi[3]];
|
|
|
|
int tricount;
|
|
if ( f.IsTriangle() )
|
|
{
|
|
tri[0][0] = tri[1][0] = 0;
|
|
tri[0][1] = tri[1][1] = 1;
|
|
tri[0][2] = tri[1][2] = 2;
|
|
tricount = 1;
|
|
edge[0] = edge[1] = -1;
|
|
}
|
|
else
|
|
{
|
|
tricount = 2;
|
|
double d0 = V[0].DistanceTo(V[2]);
|
|
double d1 = V[1].DistanceTo(V[3]);
|
|
if ( d0 <= d1 )
|
|
{
|
|
tri[0][0] = 0;
|
|
tri[0][1] = 1;
|
|
tri[0][2] = 2;
|
|
tri[1][0] = 0;
|
|
tri[1][1] = 2;
|
|
tri[1][2] = 3;
|
|
edge[0] = 0;
|
|
edge[1] = 2;
|
|
}
|
|
else
|
|
{
|
|
tri[0][0] = 0;
|
|
tri[0][1] = 1;
|
|
tri[0][2] = 3;
|
|
tri[1][0] = 1;
|
|
tri[1][1] = 2;
|
|
tri[1][2] = 3;
|
|
edge[0] = 1;
|
|
edge[1] = 3;
|
|
}
|
|
}
|
|
|
|
double r, s, t;
|
|
|
|
int i, a, b, n;
|
|
|
|
for( n = 3; (n-2)*(n-1) < face_sample_count*2; n++ )
|
|
{
|
|
// empty
|
|
}
|
|
|
|
|
|
for ( i = 0; i < tricount; i++ )
|
|
{
|
|
for ( a = 1; a < n-1; a++ )
|
|
{
|
|
for ( b = 1; a+b < n; b++ )
|
|
{
|
|
r = ((double)a)/((double)n);
|
|
s = ((double)b)/((double)n);
|
|
t = 1.0-r-s;
|
|
CMeshTestPoint& tp = CPT.AppendNew();
|
|
tp.test_point_index = CPT.Count()-1;
|
|
tp.N = N;
|
|
tp.S.m_t[tri[i][0]] = r;
|
|
tp.S.m_t[tri[i][1]] = s;
|
|
tp.S.m_t[tri[i][2]] = t;
|
|
tp.S.m_face_index = fi;
|
|
tp.S.m_mesh = &mesh;
|
|
tp.S.m_P = r*V[tri[i][0]] + s*V[tri[i][1]] + t*V[tri[i][2]];
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( edge[0] != edge[1] )
|
|
{
|
|
for ( i = 1; i <= edge_sample_count; i++ )
|
|
{
|
|
t = ((double)i)/((double)(edge_sample_count+1));
|
|
s = 1.0-t;
|
|
CMeshTestPoint& tp = CPT.AppendNew();
|
|
tp.test_point_index = CPT.Count()-1;
|
|
tp.N = N;
|
|
tp.S.m_t[edge[0]] = s;
|
|
tp.S.m_t[edge[1]] = t;
|
|
tp.S.m_face_index = fi;
|
|
tp.S.m_mesh = &mesh;
|
|
tp.S.m_P = s*V[edge[0]] + t*V[edge[1]];
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void GetMeshPointCloud( const ON_Mesh& mesh,
|
|
double sample_start_distance,
|
|
double sample_stop_distance,
|
|
ON_SimpleArray<CMeshTestPoint>& CPT )
|
|
{
|
|
// appends a cloud of points to pts.
|
|
const int i0 = CPT.Count();
|
|
|
|
int i,n;
|
|
double r;
|
|
|
|
if ( sample_stop_distance < 0.0 )
|
|
sample_stop_distance = 0.0;
|
|
if ( sample_start_distance >= sample_stop_distance )
|
|
sample_start_distance = sample_stop_distance;
|
|
|
|
const ON_MeshTopology& top = mesh.Topology();
|
|
const int face_count = mesh.m_F.Count();
|
|
const int vertex_count = top.m_topv.Count();
|
|
const int edge_count = top.m_tope.Count();
|
|
const int quad_count = mesh.QuadCount();
|
|
const int triangle_count = face_count + quad_count;
|
|
|
|
int face_n = 4;
|
|
int triangle_sample_count = ((face_n-2)*(face_n-1))/2;
|
|
int edge_sample_count = 1;
|
|
int vertical_count = 0;
|
|
if ( sample_start_distance > 0.0 )
|
|
{
|
|
for ( r = sample_start_distance; r <= sample_stop_distance*(1.0+ON_SQRT_EPSILON); r *= 2.0 )
|
|
{
|
|
if ( 0.0 != r )
|
|
vertical_count += 2;
|
|
}
|
|
}
|
|
if ( vertical_count < 1 )
|
|
vertical_count = 1;
|
|
|
|
int test_point_count = (triangle_sample_count*triangle_count + edge_sample_count*(edge_count+quad_count) + vertex_count)*vertical_count;
|
|
|
|
while ( test_point_count < 1000 )
|
|
{
|
|
face_n++;
|
|
triangle_sample_count = ((face_n-2)*(face_n-1))/2;
|
|
if ( (face_n % 2) )
|
|
edge_sample_count++;
|
|
|
|
test_point_count = (triangle_sample_count*triangle_count + edge_sample_count*(edge_count+quad_count) + vertex_count)*vertical_count;
|
|
|
|
if ( face_n >= 7 )
|
|
break;
|
|
}
|
|
|
|
const int max_test_point_count = 750000;
|
|
|
|
if ( test_point_count > max_test_point_count )
|
|
{
|
|
while ( edge_sample_count > 1 && test_point_count > max_test_point_count )
|
|
{
|
|
edge_sample_count--;
|
|
test_point_count = (triangle_sample_count*triangle_count + edge_sample_count*(edge_count+quad_count) + vertex_count)*vertical_count;
|
|
}
|
|
while ( triangle_sample_count > 3 && test_point_count > max_test_point_count )
|
|
{
|
|
face_n--;
|
|
triangle_sample_count = ((face_n-2)*(face_n-1))/2;
|
|
test_point_count = (triangle_sample_count*triangle_count + edge_sample_count*(edge_count+quad_count) + vertex_count)*vertical_count;
|
|
}
|
|
while ( vertical_count > 2 )
|
|
{
|
|
sample_start_distance *= 2.0;
|
|
vertical_count -= 2;
|
|
test_point_count = (triangle_sample_count*triangle_count + edge_sample_count*(edge_count+quad_count) + vertex_count)*vertical_count;
|
|
}
|
|
while ( edge_sample_count > 0 && test_point_count > max_test_point_count )
|
|
{
|
|
edge_sample_count--;
|
|
test_point_count = (triangle_sample_count*triangle_count + edge_sample_count*(edge_count+quad_count) + vertex_count)*vertical_count;
|
|
}
|
|
while ( triangle_sample_count > 1 && test_point_count > max_test_point_count )
|
|
{
|
|
face_n--;
|
|
triangle_sample_count = ((face_n-2)*(face_n-1))/2;
|
|
test_point_count = (triangle_sample_count*triangle_count + edge_sample_count*(edge_count+quad_count) + vertex_count)*vertical_count;
|
|
}
|
|
}
|
|
|
|
if ( bPurify )
|
|
{
|
|
// reduce sample counts so purify will finish somtime today
|
|
triangle_sample_count = 1;
|
|
edge_sample_count = 0;
|
|
sample_stop_distance = sample_start_distance;
|
|
vertical_count = (sample_stop_distance>0.0) ? 2 : 1;
|
|
test_point_count = (triangle_sample_count*triangle_count + edge_sample_count*(edge_count+quad_count) + vertex_count)*vertical_count;
|
|
}
|
|
|
|
CPT.Reserve( test_point_count );
|
|
|
|
if ( !mesh.HasFaceNormals() )
|
|
{
|
|
const_cast<ON_Mesh*>(&mesh)->ComputeFaceNormals();
|
|
}
|
|
if ( !mesh.HasVertexNormals() )
|
|
{
|
|
const_cast<ON_Mesh*>(&mesh)->ComputeVertexNormals();
|
|
}
|
|
|
|
// get a test point at each vertex
|
|
for ( i = 0; i < vertex_count; i++ )
|
|
{
|
|
GetMeshPointCloudVertexHelper( mesh, top, i, CPT );
|
|
}
|
|
|
|
// get test points along each edge
|
|
if ( edge_sample_count > 0 )
|
|
{
|
|
for ( i = 0; i < edge_count; i++ )
|
|
{
|
|
GetMeshPointCloudEdgeHelper( mesh, top, i, edge_sample_count, CPT );
|
|
}
|
|
}
|
|
|
|
// get test points along each face
|
|
if ( triangle_sample_count > 0 )
|
|
{
|
|
for (i = 0; i < face_count; i++)
|
|
{
|
|
GetMeshPointCloudFaceHelper( mesh, top, i, triangle_sample_count, edge_sample_count, CPT );
|
|
}
|
|
}
|
|
|
|
CMeshTestPoint TP;
|
|
const int i1 = CPT.Count();
|
|
for ( i = i0; i < i1; i++ )
|
|
{
|
|
CMeshTestPoint& CP0 = CPT[i];
|
|
CP0.P = FacePoint( CP0.S );
|
|
CP0.S_d = CP0.P.DistanceTo(CP0.S.m_P);
|
|
CP0.M_d = ON_UNSET_VALUE;
|
|
ON_3dVector D = CP0.P - CP0.S.m_P;
|
|
if ( fabs(D.x) >= (1.0+fabs(CP0.P.x))*ON_EPSILON
|
|
&& fabs(D.y) >= (1.0+fabs(CP0.P.y))*ON_EPSILON
|
|
&& fabs(D.z) >= (1.0+fabs(CP0.P.z))*ON_EPSILON )
|
|
{
|
|
ON_ERROR("GetMeshPointCloud - created bogus input values");
|
|
}
|
|
TP = CP0;
|
|
|
|
bool bAppend = false;
|
|
if ( 0.0 < sample_start_distance )
|
|
{
|
|
int level_count;
|
|
r = sample_start_distance;
|
|
for ( level_count = 0; level_count < vertical_count; level_count += 2 )
|
|
{
|
|
for ( n = 0; n <= 1; n++ )
|
|
{
|
|
CMeshTestPoint& CP = bAppend ? CPT.AppendNew() : CP0;
|
|
CP = TP;
|
|
if ( bAppend )
|
|
CP.test_point_index = CPT.Count()-1;
|
|
CP.P = TP.P + (n?-r:r)*TP.N;
|
|
CP.S_d = CP.S.m_P.DistanceTo(CP.P);
|
|
bAppend = true;
|
|
}
|
|
r *= 2.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
void GetSurfacePointCloud( const ON_Surface& surface,
|
|
double sample_start_distance,
|
|
double sample_stop_distance,
|
|
ON_SimpleArray<CSurfaceTestPoint>& CPT )
|
|
{
|
|
// appends a cloud of points to pts.
|
|
int hint[2] = {0,0};
|
|
int j,jj,n,m,spani,spanj;
|
|
|
|
ON_3dPoint P;
|
|
ON_3dVector N;
|
|
ON_Interval span_domain[2];
|
|
double r, s, t;
|
|
|
|
if ( sample_start_distance >= sample_stop_distance )
|
|
sample_start_distance = sample_stop_distance;
|
|
|
|
int span_count0 = surface.SpanCount(0);
|
|
if (span_count0 < 1 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
int span_count1 = surface.SpanCount(1);
|
|
if (span_count1 < 1 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
double* span_vector0 = (double*)onmalloc((span_count0+span_count1+2)*sizeof(*span_vector0));
|
|
double* span_vector1 = span_vector0 + (span_count0+1);
|
|
|
|
if ( !surface.GetSpanVector(0,span_vector0))
|
|
return;
|
|
|
|
if ( !surface.GetSpanVector(1,span_vector1))
|
|
return;
|
|
|
|
int degree0 = surface.Degree(0);
|
|
int degree1 = surface.Degree(1);
|
|
|
|
int span_sample_count = 4*3*5;
|
|
int vertical_count = 1;
|
|
if ( sample_stop_distance > 0.0 )
|
|
{
|
|
for ( r = sample_start_distance; r <= sample_stop_distance*(1.0+ON_SQRT_EPSILON); r *= 2.0 )
|
|
{
|
|
if ( 0.0 != r )
|
|
vertical_count += 2;
|
|
}
|
|
}
|
|
|
|
while ( span_count0*span_count1*span_sample_count*span_sample_count < 1000 )
|
|
span_sample_count *= 2;
|
|
|
|
if ( 1 == degree0 && 1 == degree1 && span_count0*span_count1*span_sample_count*span_sample_count < 10000 )
|
|
{
|
|
while ( span_count0*span_count1*span_sample_count*span_sample_count < 10000 )
|
|
span_sample_count *= 2;
|
|
}
|
|
else if ( span_sample_count > 5 )
|
|
{
|
|
while ( span_sample_count > 5 && span_count0*span_count1*span_sample_count*span_sample_count > 1000000 )
|
|
{
|
|
span_sample_count /= 2;
|
|
}
|
|
while ( span_sample_count > 5 && span_count0*span_count1*span_sample_count*span_sample_count > 200000 )
|
|
{
|
|
span_sample_count *= 2;
|
|
span_sample_count /= 3;
|
|
}
|
|
}
|
|
|
|
if ( bPurify )
|
|
{
|
|
// reduce sample counts so purify will finish somtime today
|
|
span_sample_count = 3;
|
|
sample_stop_distance = sample_start_distance;
|
|
}
|
|
|
|
CPT.Reserve(span_count0*span_count1*span_sample_count*span_sample_count*vertical_count);
|
|
|
|
for (spani = 0; spani < span_count0; spani++ )
|
|
{
|
|
span_domain[0].Set(span_vector0[spani],span_vector0[spani+1]);
|
|
for ( j = spani?1:0; j <= span_sample_count; j++ )
|
|
{
|
|
s = span_domain[0].ParameterAt(((double)j)/((double)span_sample_count));
|
|
for ( spanj = 0; spanj < span_count1; spanj++ )
|
|
{
|
|
span_domain[1].Set(span_vector1[spanj],span_vector1[spanj+1]);
|
|
for ( jj = spanj?1:0; jj <= span_sample_count; jj++ )
|
|
{
|
|
t = span_domain[1].ParameterAt(((double)jj)/((double)span_sample_count));
|
|
surface.EvNormal(s,t,P,N,1,hint);
|
|
|
|
if ( sample_stop_distance <= 0.0 )
|
|
{
|
|
CSurfaceTestPoint& CP = CPT.AppendNew();
|
|
CP.test_point_index = CPT.Count()-1;
|
|
CP.P = P;
|
|
CP.uv.Set(s,t);
|
|
CP.d = 0.0;
|
|
CP.best_uv.Set(ON_UNSET_VALUE,ON_UNSET_VALUE);
|
|
CP.best_d = ON_UNSET_VALUE;
|
|
for( m = 0; m < CSurfaceTestPoint::FUNC_SRF_CLSPT_COUNT; m++ )
|
|
{
|
|
CP.srf_d[m] = ON_UNSET_VALUE;
|
|
CP.srf_uv[m].Set(ON_UNSET_VALUE,ON_UNSET_VALUE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for ( r = sample_start_distance; r <= sample_stop_distance*(1.0+ON_SQRT_EPSILON); r *= 2.0 )
|
|
{
|
|
for ( n = 0; n <= 1; n++ )
|
|
{
|
|
CSurfaceTestPoint& CP = CPT.AppendNew();
|
|
CP.test_point_index = CPT.Count()-1;
|
|
CP.P = P + (n?-r:r)*N;
|
|
CP.uv.Set(s,t);
|
|
CP.d = P.DistanceTo(CP.P);
|
|
CP.best_uv.Set(ON_UNSET_VALUE,ON_UNSET_VALUE);
|
|
CP.best_d = ON_UNSET_VALUE;
|
|
for( m = 0; m < CSurfaceTestPoint::FUNC_SRF_CLSPT_COUNT; m++ )
|
|
{
|
|
CP.srf_d[m] = ON_UNSET_VALUE;
|
|
CP.srf_uv[m].Set(ON_UNSET_VALUE,ON_UNSET_VALUE);
|
|
}
|
|
if ( 0.0 == r )
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
onfree(span_vector0);
|
|
|
|
// FOR DEBUGGING A CLOSEST POINT TO SURFACE BAD POINT
|
|
int singleton_index = -1;
|
|
if ( singleton_index >= 0 && singleton_index < CPT.Count() )
|
|
{
|
|
CPT[0] = CPT[singleton_index];
|
|
CPT.SetCount(1);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
class CClosestPointToMeshTest
|
|
{
|
|
public:
|
|
bool GetClosestPoint( ON_3dPoint P, ON_MESH_POINT* mp );
|
|
|
|
// returns time in seconds
|
|
double SpeedTest( int test_point_count, const CMeshTestPoint* tp);
|
|
|
|
// returns error count
|
|
int AccuracyTest( int test_point_count, CMeshTestPoint* tp );
|
|
|
|
void CalculateResults( int test_point_count,
|
|
const CMeshTestPoint* tp
|
|
);
|
|
|
|
void Print( ON_TextLog& text_log, int test_count );
|
|
|
|
void SetupHelper( const ON_Mesh* mesh, const ON_Surface* surface );
|
|
|
|
const ON_Mesh* m_mesh;
|
|
const ON_MeshTree* m_mesh_tree;
|
|
|
|
const ON_Surface* m_surface;
|
|
const ON_SurfaceTree* m_surface_tree;
|
|
|
|
CClosestPointToMeshTestResults m_results;
|
|
};
|
|
|
|
bool CClosestPointToMeshTest::GetClosestPoint( ON_3dPoint P, ON_MESH_POINT* mp )
|
|
{
|
|
return m_mesh_tree->GetClosestPoint( P, mp );
|
|
}
|
|
|
|
void CClosestPointToMeshTest::SetupHelper( const ON_Mesh* mesh, const ON_Surface* surface )
|
|
{
|
|
m_mesh = mesh;
|
|
m_mesh_tree = m_mesh ? m_mesh->MeshTree() : 0;
|
|
|
|
m_surface = surface;
|
|
m_surface_tree = m_surface ? m_surface->SurfaceTree() : 0;
|
|
|
|
memset(&m_results,0,sizeof(m_results));
|
|
}
|
|
|
|
|
|
|
|
double CClosestPointToMeshTest::SpeedTest(int point_count, const CMeshTestPoint* tp0)
|
|
{
|
|
ON_MESH_POINT mp;
|
|
const CMeshTestPoint* tp;;
|
|
|
|
double s, t;
|
|
int i;
|
|
clock_t time0, time1;
|
|
|
|
int error_count0 = ON_GetErrorCount();
|
|
int warning_count0 = ON_GetWarningCount();
|
|
|
|
m_results.m_mesh_elapsed_time = 0.0;
|
|
m_results.m_surface_elapsed_time = 0.0;
|
|
|
|
if ( m_mesh_tree )
|
|
{
|
|
tp = tp0;
|
|
i = point_count;
|
|
|
|
time0 = clock();
|
|
while(i--)
|
|
{
|
|
s = t = ON_UNSET_VALUE;
|
|
m_mesh_tree->GetClosestPoint(tp->P,&mp);
|
|
tp++;
|
|
}
|
|
time1 = clock();
|
|
|
|
m_results.m_mesh_elapsed_time = ((double)(time1 - time0))/((double)CLOCKS_PER_SEC);
|
|
}
|
|
|
|
if ( m_surface_tree )
|
|
{
|
|
tp = tp0;
|
|
i = point_count;
|
|
|
|
time0 = clock();
|
|
while(i--)
|
|
{
|
|
s = t = ON_UNSET_VALUE;
|
|
m_surface_tree->GetClosestPoint(tp->P,&s,&t);
|
|
tp++;
|
|
}
|
|
time1 = clock();
|
|
|
|
m_results.m_surface_elapsed_time = ((double)(time1 - time0))/((double)CLOCKS_PER_SEC);
|
|
}
|
|
|
|
m_results.m_bSpeedTest = true;
|
|
|
|
m_results.m_error_count += (ON_GetErrorCount()-error_count0);
|
|
m_results.m_warning_count += (ON_GetWarningCount()-warning_count0);
|
|
|
|
return m_results.m_mesh_elapsed_time;
|
|
}
|
|
|
|
int CClosestPointToMeshTest::AccuracyTest(int point_count, CMeshTestPoint* tp)
|
|
{
|
|
ON_3dPoint Q;
|
|
double d;
|
|
int rc = 0;
|
|
int i;
|
|
|
|
|
|
int error_count0 = ON_GetErrorCount();
|
|
int warning_count0 = ON_GetWarningCount();
|
|
|
|
m_results.m_bAccuracyTest = true;
|
|
m_results.m_test_count = 0;
|
|
|
|
if ( m_mesh_tree )
|
|
{
|
|
m_results.m_test_count = point_count;
|
|
|
|
|
|
|
|
for ( i = 0; i < point_count; i++ )
|
|
{
|
|
if( !m_mesh_tree->GetClosestPoint(tp[i].P,&tp[i].M) )
|
|
{
|
|
rc++;
|
|
memset(&tp[i].M,0,sizeof(tp[i].M));
|
|
tp[i].M_d = ON_UNSET_VALUE;
|
|
m_results.m_failure_count++;
|
|
}
|
|
else
|
|
{
|
|
Q = FacePoint(tp[i].M);
|
|
d = Q.DistanceTo(tp[i].P);
|
|
tp[i].M_d = d;
|
|
ON_3dVector D = Q - tp[i].M.m_P;
|
|
if ( fabs(D.x) >= (1.0+fabs(Q.x))*ON_EPSILON
|
|
&& fabs(D.y) >= (1.0+fabs(Q.y))*ON_EPSILON
|
|
&& fabs(D.z) >= (1.0+fabs(Q.z))*ON_EPSILON )
|
|
{
|
|
ON_String msg;
|
|
msg.Format(
|
|
"Bogus ON_MESH_POINT tp[%d].M.m_P value returned from ON_MeshTree::GetClosestPoint()\n",
|
|
i);
|
|
ON_ERROR(msg);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
m_results.m_error_count += (ON_GetErrorCount()-error_count0);
|
|
m_results.m_warning_count += (ON_GetWarningCount()-warning_count0);
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
void CClosestPointToMeshTest::CalculateResults(
|
|
int test_point_count,
|
|
const CMeshTestPoint* tp
|
|
)
|
|
{
|
|
int badi = -1;
|
|
double badd = 0.0;
|
|
|
|
m_results.m_speed_factor = (m_results.m_bSpeedTest && m_results.m_surface_elapsed_time > 0.0)
|
|
? m_results.m_mesh_elapsed_time/m_results.m_surface_elapsed_time
|
|
: 0.0;
|
|
|
|
if ( m_results.m_bAccuracyTest )
|
|
{
|
|
ON_Sum on_3d_error;
|
|
ON_Sum off_3d_error;
|
|
double best_d, d_err;
|
|
int i;
|
|
ON_3dPoint P;
|
|
for ( i = 0; i < test_point_count; i++ )
|
|
{
|
|
const CMeshTestPoint& T = tp[i];
|
|
|
|
if ( !T.M.m_mesh )
|
|
continue;
|
|
|
|
best_d = (T.M_d < T.S_d) ? T.M_d : T.S_d;
|
|
|
|
d_err = T.M_d - best_d;
|
|
if ( 0.0 == best_d )
|
|
{
|
|
m_results.m_on_test_count++;
|
|
|
|
if ( T.M_d <= best_d )
|
|
{
|
|
m_results.m_on_best_count++;
|
|
}
|
|
if ( d_err > 1.0e-4 )
|
|
{
|
|
m_results.m_on_wrong_count++;
|
|
if ( d_err > badd )
|
|
{
|
|
badd = d_err;
|
|
badi = T.test_point_index;
|
|
}
|
|
}
|
|
else if ( d_err > 1.0e-6 )
|
|
{
|
|
m_results.m_on_sloppy_count++;
|
|
}
|
|
else
|
|
{
|
|
on_3d_error.Plus(d_err);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_results.m_off_test_count++;
|
|
|
|
if( T.M_d <= best_d )
|
|
{
|
|
m_results.m_off_best_count++;
|
|
}
|
|
d_err /= best_d;
|
|
if ( d_err > 0.15 )
|
|
{
|
|
m_results.m_off_wrong_count++;
|
|
}
|
|
else if ( d_err > 0.05 )
|
|
{
|
|
m_results.m_off_sloppy_count++;
|
|
}
|
|
else
|
|
{
|
|
off_3d_error.Plus(d_err);
|
|
}
|
|
}
|
|
}
|
|
|
|
i = on_3d_error.SummandCount();
|
|
if ( i > 0 )
|
|
{
|
|
d_err = on_3d_error.Total()/((double)i);
|
|
m_results.m_on_mesh_3d_error = d_err;
|
|
}
|
|
|
|
i = off_3d_error.SummandCount();
|
|
if ( i > 0 )
|
|
{
|
|
d_err = off_3d_error.Total()/((double)i);
|
|
m_results.m_off_mesh_3d_error = d_err;
|
|
}
|
|
}
|
|
|
|
if ( badi>=0)
|
|
{
|
|
printf("TEST POINT %d had error of = %g. This is a ClosestPointToSurface BUG.\n",badi,badd);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void CClosestPointToMeshTest::Print( ON_TextLog& text_log, int test_count )
|
|
{
|
|
const char* format = "%s %s %5d %4.1fX (%6.3f secs) %3d%% %.1g";
|
|
|
|
text_log.Print("Name Test Point mesh/ Mesh Accuracy\n");
|
|
text_log.Print(" count srf time best 3d err\n");
|
|
text_log.Print(format,
|
|
"Perfect ",
|
|
" ",
|
|
test_count, // m_results.m_test_count,
|
|
1.0, //m_results.m_speed_factor,
|
|
0.0, //m_results.m_elapsed_time,
|
|
100,//test_count, //m_results.m_best_count,
|
|
1e-18 //m_results.m_on_surface_3d_error,
|
|
);
|
|
text_log.Print("\n");
|
|
|
|
if ( m_results.m_on_test_count > 0 )
|
|
{
|
|
int best = (int)floor(100.0*((double)m_results.m_on_best_count)/((double)m_results.m_test_count));
|
|
text_log.Print(format,
|
|
"mesh ",
|
|
"on ",
|
|
m_results.m_on_test_count,
|
|
m_results.m_speed_factor,
|
|
m_results.m_mesh_elapsed_time,
|
|
best,
|
|
m_results.m_on_mesh_3d_error
|
|
);
|
|
if( m_results.m_failure_count > 0 )
|
|
text_log.Print(" %d FAILURES",m_results.m_failure_count);
|
|
if( m_results.m_error_count > 0 )
|
|
text_log.Print(" %d ON_ERRORs",m_results.m_error_count);
|
|
if( m_results.m_warning_count > 0 )
|
|
text_log.Print(" %d ON_WARNINGSs",m_results.m_warning_count);
|
|
|
|
if( m_results.m_on_wrong_count > 0 )
|
|
text_log.Print(" %d WRONG ANSWERS",m_results.m_on_wrong_count);
|
|
if( m_results.m_on_sloppy_count > 0 )
|
|
text_log.Print(" %d sloppys answers",m_results.m_on_sloppy_count);
|
|
text_log.Print("\n");
|
|
}
|
|
|
|
if ( m_results.m_off_test_count > 0 )
|
|
{
|
|
int best = (int)floor(100.0*((double)m_results.m_off_best_count)/((double)m_results.m_test_count));
|
|
text_log.Print(format,
|
|
"mesh ",
|
|
"off ",
|
|
m_results.m_off_test_count,
|
|
m_results.m_speed_factor,
|
|
m_results.m_mesh_elapsed_time,
|
|
best,
|
|
m_results.m_off_mesh_3d_error
|
|
);
|
|
if( m_results.m_failure_count > 0 )
|
|
text_log.Print(" %d FAILURES",m_results.m_failure_count);
|
|
if( m_results.m_error_count > 0 )
|
|
text_log.Print(" %d ON_ERRORs",m_results.m_error_count);
|
|
if( m_results.m_warning_count > 0 )
|
|
text_log.Print(" %d ON_WARNINGSs",m_results.m_warning_count);
|
|
if( m_results.m_off_wrong_count > 0 )
|
|
text_log.Print(" %d WRONG ANSWERS",m_results.m_off_wrong_count);
|
|
if( m_results.m_off_sloppy_count > 0 )
|
|
text_log.Print(" %d sloppys answers",m_results.m_off_sloppy_count);
|
|
text_log.Print("\n");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
class CClosestPointToSurfaceTest
|
|
{
|
|
public:
|
|
virtual void Setup( ON_NurbsSurface* nurbs_surface ) = 0;
|
|
|
|
virtual int GetClosestPoint( ON_3dPoint P, double* s, double* t ) = 0;
|
|
|
|
// returns time in seconds
|
|
double SpeedTest( int test_point_count, const CSurfaceTestPoint* tp);
|
|
|
|
// returns error count
|
|
int AccuracyTest( int test_point_count, CSurfaceTestPoint* tp );
|
|
|
|
void CalculateResults( const ON_NurbsSurface& nurbs_surface,
|
|
int test_point_count,
|
|
const CSurfaceTestPoint* tp,
|
|
double best_time
|
|
);
|
|
|
|
void Print( ON_TextLog& text_log, int test_count );
|
|
|
|
CSurfaceTestPoint::FUNC_SRF_CLSPT m_func;
|
|
|
|
const ON_NurbsSurface* m_nurbs_surface;
|
|
|
|
CClosestPointToSurfaceTestResults m_results;
|
|
|
|
void SetupHelper( CSurfaceTestPoint::FUNC_SRF_CLSPT func, ON_NurbsSurface* nurbs_surface );
|
|
};
|
|
|
|
void CClosestPointToSurfaceTest::SetupHelper( CSurfaceTestPoint::FUNC_SRF_CLSPT func, ON_NurbsSurface* nurbs_surface )
|
|
{
|
|
m_func = func;
|
|
m_nurbs_surface = nurbs_surface;
|
|
memset(&m_results,0,sizeof(m_results));
|
|
m_results.m_elapsed_time = ON_UNSET_VALUE;
|
|
}
|
|
|
|
double CClosestPointToSurfaceTest::SpeedTest(int point_count, const CSurfaceTestPoint* tp)
|
|
{
|
|
double s, t;
|
|
int i;
|
|
clock_t time0, time1;
|
|
|
|
int error_count0 = ON_GetErrorCount();
|
|
int warning_count0 = ON_GetWarningCount();
|
|
|
|
i = point_count;
|
|
|
|
time0 = clock();
|
|
while(i--)
|
|
{
|
|
s = t = ON_UNSET_VALUE;
|
|
GetClosestPoint(tp->P,&s,&t);
|
|
tp++;
|
|
}
|
|
time1 = clock();
|
|
|
|
m_results.m_elapsed_time = ((double)(time1 - time0))/((double)CLOCKS_PER_SEC);
|
|
|
|
m_results.m_bSpeedTest = true;
|
|
|
|
m_results.m_error_count += (ON_GetErrorCount()-error_count0);
|
|
m_results.m_warning_count += (ON_GetWarningCount()-warning_count0);
|
|
|
|
return m_results.m_elapsed_time;
|
|
}
|
|
|
|
int CClosestPointToSurfaceTest::AccuracyTest(int point_count, CSurfaceTestPoint* tp)
|
|
{
|
|
ON_3dPoint Q;
|
|
double d;
|
|
int rc = 0;
|
|
int i;
|
|
double s,t;
|
|
|
|
|
|
int error_count0 = ON_GetErrorCount();
|
|
int warning_count0 = ON_GetWarningCount();
|
|
|
|
m_results.m_bAccuracyTest = true;
|
|
m_results.m_test_count = point_count;
|
|
|
|
for ( i = 0; i < point_count; i++ )
|
|
{
|
|
s = t = ON_UNSET_VALUE;
|
|
if( !GetClosestPoint(tp[i].P,&s,&t) )
|
|
{
|
|
rc++;
|
|
tp[i].srf_uv[m_func].Set(ON_UNSET_VALUE,ON_UNSET_VALUE);
|
|
tp[i].srf_d[m_func] = ON_UNSET_VALUE;
|
|
m_results.m_failure_count++;
|
|
}
|
|
else
|
|
{
|
|
Q = m_nurbs_surface->PointAt(s,t);
|
|
d = Q.DistanceTo(tp[i].P);
|
|
tp[i].srf_uv[m_func].Set(s,t);
|
|
tp[i].srf_d[m_func] = d;
|
|
if ( ON_UNSET_VALUE == tp[i].best_d || d < tp[i].best_d )
|
|
{
|
|
tp[i].best_d = d;
|
|
tp[i].best_uv.Set(s,t);
|
|
}
|
|
}
|
|
}
|
|
|
|
m_results.m_error_count += (ON_GetErrorCount()-error_count0);
|
|
m_results.m_warning_count += (ON_GetWarningCount()-warning_count0);
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
void CClosestPointToSurfaceTest::CalculateResults( const ON_NurbsSurface& nurbs_surface,
|
|
int test_point_count,
|
|
const CSurfaceTestPoint* tp,
|
|
double best_time
|
|
)
|
|
{
|
|
int badi = -1;
|
|
double badd = 0.0;
|
|
|
|
m_results.m_speed_factor = (m_results.m_bSpeedTest && best_time>0.0) ? m_results.m_elapsed_time/best_time : 0.0;
|
|
if ( m_results.m_bAccuracyTest )
|
|
{
|
|
ON_Sum on_3d_error;
|
|
ON_Sum off_3d_error;
|
|
ON_Sum on_uvx_error, on_uvy_error;
|
|
ON_Sum off_uvx_error, off_uvy_error;
|
|
double d_err;
|
|
ON_2dVector uv_err;
|
|
int i;
|
|
int hint[2] = {0,0};
|
|
ON_3dPoint P;
|
|
ON_3dVector Du, Dv;
|
|
for ( i = 0; i < test_point_count; i++ )
|
|
{
|
|
const CSurfaceTestPoint& T = tp[i];
|
|
|
|
if ( !T.srf_uv[m_func].IsValid() )
|
|
continue;
|
|
|
|
nurbs_surface.Ev1Der( T.best_uv.x, T.best_uv.y, P, Du, Dv, 1, hint );
|
|
|
|
uv_err.Set( Du.Length(), Dv.Length() );
|
|
uv_err.x = ( uv_err.x > ON_ZERO_TOLERANCE ) ? 1.0/uv_err.x : 0.0;
|
|
uv_err.y = ( uv_err.y > ON_ZERO_TOLERANCE ) ? 1.0/uv_err.y : 0.0;
|
|
uv_err.x *= fabs(T.srf_uv[m_func].x - T.best_uv.x);
|
|
uv_err.y *= fabs(T.srf_uv[m_func].y - T.best_uv.y);
|
|
|
|
d_err = (T.best_d < T.d) ? T.best_d : T.d;
|
|
|
|
d_err = T.srf_d[m_func] - d_err;
|
|
if ( 0.0 == T.d || 0.0 == T.best_d )
|
|
{
|
|
m_results.m_on_test_count++;
|
|
|
|
if ( T.srf_d[m_func] <= T.best_d )
|
|
{
|
|
m_results.m_on_best_count++;
|
|
uv_err.Set(0.0,0.0);
|
|
}
|
|
if ( d_err > 1.0e-4 )
|
|
{
|
|
m_results.m_on_wrong_count++;
|
|
if ( CSurfaceTestPoint::FUNC_SRF_CLSPT_GOOSE != m_func && d_err > badd )
|
|
{
|
|
badd = d_err;
|
|
badi = T.test_point_index;
|
|
}
|
|
}
|
|
else if ( d_err > 1.0e-6 )
|
|
{
|
|
m_results.m_on_sloppy_count++;
|
|
}
|
|
else
|
|
{
|
|
on_3d_error.Plus(d_err);
|
|
on_uvx_error.Plus(uv_err.x);
|
|
on_uvy_error.Plus(uv_err.y);
|
|
}
|
|
}
|
|
else if ( T.best_d > 0.0 )
|
|
{
|
|
m_results.m_off_test_count++;
|
|
|
|
if( T.srf_d[m_func] <= T.best_d )
|
|
{
|
|
m_results.m_off_best_count++;
|
|
uv_err.Set(0.0,0.0);
|
|
}
|
|
d_err /= T.best_d;
|
|
if ( d_err > 0.15 )
|
|
{
|
|
m_results.m_off_wrong_count++;
|
|
}
|
|
else if ( d_err > 0.05 )
|
|
{
|
|
m_results.m_off_sloppy_count++;
|
|
}
|
|
else
|
|
{
|
|
off_3d_error.Plus(d_err);
|
|
off_uvx_error.Plus(uv_err.x);
|
|
off_uvy_error.Plus(uv_err.y);
|
|
}
|
|
}
|
|
}
|
|
|
|
i = on_3d_error.SummandCount();
|
|
if ( i > 0 )
|
|
{
|
|
d_err = on_3d_error.Total()/((double)i);
|
|
uv_err.x = on_uvx_error.Total()/((double)i);
|
|
uv_err.y = on_uvy_error.Total()/((double)i);
|
|
m_results.m_on_surface_3d_error = d_err;
|
|
m_results.m_on_surface_uv_error = uv_err;
|
|
}
|
|
|
|
i = off_3d_error.SummandCount();
|
|
if ( i > 0 )
|
|
{
|
|
d_err = off_3d_error.Total()/((double)i);
|
|
uv_err.x = off_uvx_error.Total()/((double)i);
|
|
uv_err.y = off_uvy_error.Total()/((double)i);
|
|
m_results.m_off_surface_3d_error = d_err;
|
|
m_results.m_off_surface_uv_error = uv_err;
|
|
}
|
|
}
|
|
|
|
if ( badi>=0)
|
|
{
|
|
printf("TEST POINT %d had error of = %g. This is a ClosestPointToSurface BUG.\n",badi,badd);
|
|
}
|
|
}
|
|
|
|
void CClosestPointToSurfaceTest::Print( ON_TextLog& text_log, int test_count )
|
|
{
|
|
const char* format = "%s %s %5d %4.1fX (%6.3f secs) %3d%% %.1g %.1g,%.1g";
|
|
|
|
const char* name = 0;
|
|
if ( test_count > 0 )
|
|
{
|
|
text_log.Print("Name Test Point Speed Accuracy\n");
|
|
text_log.Print(" count sloth time best 3d err uv err\n");
|
|
name = "Perfect ";
|
|
}
|
|
else
|
|
{
|
|
switch(m_func)
|
|
{
|
|
case CSurfaceTestPoint::FUNC_SRF_CLSPT_NEWON:
|
|
name = "OpenNURBS ";
|
|
break;
|
|
case CSurfaceTestPoint::FUNC_SRF_CLSPT_GOOSE:
|
|
name = "Goose ";
|
|
break;
|
|
default:
|
|
name = "Anonymous ";
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
if ( test_count > 0 )
|
|
{
|
|
text_log.Print(format,
|
|
name,
|
|
" ",
|
|
test_count, // m_results.m_test_count,
|
|
1.0, //m_results.m_speed_factor,
|
|
0.0, //m_results.m_elapsed_time,
|
|
100,//test_count, //m_results.m_best_count,
|
|
1e-18, //m_results.m_on_surface_3d_error,
|
|
1e-18, //m_results.m_on_surface_uv_error.x,
|
|
1e-18 //m_results.m_on_surface_uv_error.y,
|
|
);
|
|
text_log.Print("\n");
|
|
}
|
|
else
|
|
{
|
|
if ( m_results.m_on_test_count > 0 )
|
|
{
|
|
int best = (int)floor(100.0*((double)m_results.m_on_best_count)/((double)m_results.m_test_count));
|
|
text_log.Print(format,
|
|
name,
|
|
"on ",
|
|
m_results.m_on_test_count,
|
|
m_results.m_speed_factor,
|
|
m_results.m_elapsed_time,
|
|
best,//m_results.m_on_best_count,
|
|
m_results.m_on_surface_3d_error,
|
|
m_results.m_on_surface_uv_error.x,
|
|
m_results.m_on_surface_uv_error.y
|
|
);
|
|
if( m_results.m_failure_count > 0 )
|
|
text_log.Print(" %d FAILURES",m_results.m_failure_count);
|
|
if( m_results.m_error_count > 0 )
|
|
text_log.Print(" %d ON_ERRORs",m_results.m_error_count);
|
|
if( m_results.m_warning_count > 0 )
|
|
text_log.Print(" %d ON_WARNINGSs",m_results.m_warning_count);
|
|
|
|
if( m_results.m_on_wrong_count > 0 )
|
|
text_log.Print(" %d WRONG ANSWERS",m_results.m_on_wrong_count);
|
|
if( m_results.m_on_sloppy_count > 0 )
|
|
text_log.Print(" %d sloppys answers",m_results.m_on_sloppy_count);
|
|
text_log.Print("\n");
|
|
}
|
|
|
|
if ( m_results.m_off_test_count > 0 )
|
|
{
|
|
int best = (int)floor(100.0*((double)m_results.m_off_best_count)/((double)m_results.m_test_count));
|
|
text_log.Print(format,
|
|
name,
|
|
"off ",
|
|
m_results.m_off_test_count,
|
|
m_results.m_speed_factor,
|
|
m_results.m_elapsed_time,
|
|
best, //m_results.m_off_best_count,
|
|
m_results.m_off_surface_3d_error,
|
|
m_results.m_off_surface_uv_error.x,
|
|
m_results.m_off_surface_uv_error.y
|
|
);
|
|
if( m_results.m_failure_count > 0 )
|
|
text_log.Print(" %d FAILURES",m_results.m_failure_count);
|
|
if( m_results.m_error_count > 0 )
|
|
text_log.Print(" %d ON_ERRORs",m_results.m_error_count);
|
|
if( m_results.m_warning_count > 0 )
|
|
text_log.Print(" %d ON_WARNINGSs",m_results.m_warning_count);
|
|
if( m_results.m_off_wrong_count > 0 )
|
|
text_log.Print(" %d WRONG ANSWERS",m_results.m_off_wrong_count);
|
|
if( m_results.m_off_sloppy_count > 0 )
|
|
text_log.Print(" %d sloppys answers",m_results.m_off_sloppy_count);
|
|
text_log.Print("\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Dale's closest point
|
|
//
|
|
class C_ON_Curve_PointTest : public CClosestPointToCurveTest
|
|
{
|
|
public:
|
|
virtual void Setup( const ON_Curve* curve );
|
|
virtual int GetClosestPoint(ON_3dPoint P, double* t);
|
|
const ON_Curve* m_curve;
|
|
};
|
|
|
|
void C_ON_Curve_PointTest::Setup( const ON_Curve* curve )
|
|
{
|
|
m_curve = curve;
|
|
m_curve->CurveTree(); // prebuild curve tree
|
|
SetupHelper(CCurveTestPoint::FUNC_CRV_CLSPT_ON_CRV,curve);
|
|
}
|
|
|
|
int C_ON_Curve_PointTest::GetClosestPoint( ON_3dPoint P, double* t )
|
|
{
|
|
return m_curve->GetClosestPoint(P,t);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Dale's closest point using ON_NurbsCurve
|
|
//
|
|
class CNurbsFormCurvePointTest : public CClosestPointToCurveTest
|
|
{
|
|
public:
|
|
virtual void Setup( const ON_Curve* curve );
|
|
virtual int GetClosestPoint(ON_3dPoint P, double* t);
|
|
TL_NurbsCurve m_nurbs_curve;
|
|
};
|
|
|
|
void CNurbsFormCurvePointTest::Setup( const ON_Curve* curve )
|
|
{
|
|
curve->GetNurbForm(m_nurbs_curve);
|
|
m_nurbs_curve.CurveTree(); // prebuild curve tree
|
|
SetupHelper(CCurveTestPoint::FUNC_CRV_CLSPT_ON_NURBS,&m_nurbs_curve);
|
|
}
|
|
|
|
int CNurbsFormCurvePointTest::GetClosestPoint( ON_3dPoint P, double* t )
|
|
{
|
|
return m_curve->GetClosestPoint(P,t);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Dale's closest point using ON_CurveTree
|
|
//
|
|
class CCurveTreePointTest : public CClosestPointToCurveTest
|
|
{
|
|
public:
|
|
virtual void Setup( const ON_Curve* curve );
|
|
virtual int GetClosestPoint(ON_3dPoint P, double* t);
|
|
TL_NurbsCurve m_nurbs_curve;
|
|
const ON_CurveTree* m_tree;
|
|
};
|
|
|
|
void CCurveTreePointTest::Setup( const ON_Curve* curve )
|
|
{
|
|
curve->GetNurbForm(m_nurbs_curve);
|
|
m_tree = m_nurbs_curve.CurveTree(); // prebuild curve tree
|
|
SetupHelper(CCurveTestPoint::FUNC_CRV_CLSPT_ON_TREETEST,&m_nurbs_curve);
|
|
}
|
|
|
|
int CCurveTreePointTest::GetClosestPoint( ON_3dPoint P, double* t )
|
|
{
|
|
m_tree->m__GetClosestPointOnCurveTree(m_tree->m_root, P, t, NULL, 0.0, NULL );
|
|
return 1;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// A "hack" closest point that provides a goal
|
|
//
|
|
class CBaselineCurvePointTest : public CClosestPointToCurveTest
|
|
{
|
|
public:
|
|
virtual void Setup( const ON_Curve* curve );
|
|
virtual int GetClosestPoint(ON_3dPoint P, double* t);
|
|
TL_NurbsCurve m_nurbs_curve;
|
|
};
|
|
|
|
void CBaselineCurvePointTest::Setup( const ON_Curve* curve )
|
|
{
|
|
curve->GetNurbForm(m_nurbs_curve);
|
|
m_nurbs_curve.CurveTree();
|
|
SetupHelper(CCurveTestPoint::FUNC_CRV_CLSPT_BASELINE,&m_nurbs_curve);
|
|
}
|
|
|
|
int CBaselineCurvePointTest::GetClosestPoint( ON_3dPoint P, double* t )
|
|
{
|
|
// NOTE - This doesn't handle subdomains and far point global checking
|
|
// See ON_Curve::GetClosestPoint
|
|
static
|
|
EF ef;
|
|
memset(&ef,0,sizeof(ef));
|
|
ef.d = ON_UNSET_VALUE;
|
|
ef.t = ON_UNSET_VALUE;
|
|
ef.P = P;
|
|
ef.ct = m_nurbs_curve.CurveTree();
|
|
|
|
bool rc = ClosestFork(ef,ON_UNSET_VALUE,ef.ct->m_root);
|
|
|
|
if (rc)
|
|
{
|
|
*t = ef.t;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Goose closest point
|
|
//
|
|
|
|
class CGooseCurvePointTest : public CClosestPointToCurveTest
|
|
{
|
|
public:
|
|
CGooseCurvePointTest();
|
|
~CGooseCurvePointTest();
|
|
|
|
virtual void Setup( const ON_Curve* curve );
|
|
virtual int GetClosestPoint( ON_3dPoint P, double* t );
|
|
|
|
TL_NurbsCurve m_nurbs_curve;
|
|
ON_3dPoint P0, P1, P2;
|
|
TL_NURB N;
|
|
IwPoint3d Q;
|
|
IwExtent1d Interval;
|
|
IwBSplineCurve* pC;
|
|
};
|
|
|
|
CGooseCurvePointTest::CGooseCurvePointTest()
|
|
{
|
|
memset(&N,0,sizeof(N));
|
|
pC =0;
|
|
}
|
|
|
|
CGooseCurvePointTest::~CGooseCurvePointTest()
|
|
{
|
|
N.cv = 0;
|
|
N.knot = 0;
|
|
TL_DestroyNurb(&N);
|
|
delete pC;
|
|
}
|
|
|
|
void CGooseCurvePointTest::Setup( const ON_Curve* curve )
|
|
{
|
|
curve->GetNurbForm(m_nurbs_curve);
|
|
m_nurbs_curve.CurveTree();
|
|
SetupHelper(CCurveTestPoint::FUNC_CRV_CLSPT_GOOSE,&m_nurbs_curve);
|
|
|
|
P0 = m_nurbs_curve.PointAtStart();
|
|
P1 = m_nurbs_curve.PointAt(m_nurbs_curve.Domain().ParameterAt(0.5));
|
|
P2 = m_nurbs_curve.PointAtEnd();
|
|
|
|
memset(&N,0,sizeof(N));
|
|
N.dim = m_nurbs_curve.m_dim;
|
|
N.is_rat = m_nurbs_curve.m_is_rat;
|
|
N.order = m_nurbs_curve.m_order;
|
|
N.cv_count = m_nurbs_curve.m_cv_count;
|
|
N.cv = m_nurbs_curve.m_cv;
|
|
N.knot = m_nurbs_curve.m_knot;
|
|
|
|
TL_Convert( N, pC );
|
|
Interval = pC->GetNaturalInterval();
|
|
IwCacheMgr::GetOrCreateObjectCache(IW_OC_CURVE,pC);
|
|
}
|
|
|
|
int CGooseCurvePointTest::GetClosestPoint( ON_3dPoint P, double* t )
|
|
{
|
|
int rc;
|
|
double dist_tol, d;
|
|
IwStatus iw_rc;
|
|
|
|
IwSolutionArray sSolutions;
|
|
|
|
dist_tol = P.DistanceTo(P0);
|
|
d = P.DistanceTo(P1);
|
|
if ( d < dist_tol )
|
|
dist_tol = d;
|
|
d = P.DistanceTo(P2);
|
|
if ( d < dist_tol )
|
|
dist_tol = d;
|
|
dist_tol *= (1.0+ON_SQRT_EPSILON);
|
|
|
|
Q.x = P.x;
|
|
Q.y = P.y;
|
|
Q.z = P.z;
|
|
|
|
iw_rc = pC->GlobalPointSolve(
|
|
Interval,
|
|
IW_SO_MINIMIZE,
|
|
Q,
|
|
dist_tol, NULL, NULL,
|
|
IW_SR_SINGLE,
|
|
sSolutions
|
|
);
|
|
|
|
if ( iw_rc == IW_SUCCESS && sSolutions.GetSize() > 0)
|
|
{
|
|
*t = sSolutions[0].m_vStart[0];
|
|
rc = 1;
|
|
}
|
|
else
|
|
{
|
|
rc = 0;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
|
|
void TestClosestPointToThisCurve( ON_TextLog& text_log,
|
|
const ON_Curve* curve,
|
|
double sample_start_distance,
|
|
double sample_stop_distance
|
|
)
|
|
{
|
|
CClosestPointToCurveTest* tests[20];
|
|
int test_count = 0;
|
|
int i;
|
|
bool bSpeedTest = true;
|
|
bool bAccuracyTest = true;
|
|
|
|
// The commented out ones are slower than newdale_test and newon_test
|
|
CBaselineCurvePointTest baseline_test;
|
|
C_ON_Curve_PointTest on_curve_test;
|
|
CNurbsFormCurvePointTest on_nurbsform_test;
|
|
CCurveTreePointTest on_tree_test;
|
|
CGooseCurvePointTest goose_test;
|
|
|
|
baseline_test.Setup(curve);
|
|
goose_test.Setup(curve);
|
|
on_curve_test.Setup(curve);
|
|
on_nurbsform_test.Setup(curve);
|
|
on_tree_test.Setup(curve);
|
|
|
|
tests[test_count++] = &baseline_test;
|
|
tests[test_count++] = &on_tree_test;
|
|
tests[test_count++] = &on_curve_test;
|
|
tests[test_count++] = &on_nurbsform_test;
|
|
if ( bDoGooseTests )
|
|
tests[test_count++] = &goose_test;
|
|
|
|
// get test points
|
|
ON_SimpleArray<CCurveTestPoint> TP;
|
|
GetCurvePointCloud( *curve, sample_start_distance, sample_stop_distance, TP );
|
|
CCurveTestPoint* tp = TP.Array();
|
|
int test_point_count = TP.Count();
|
|
|
|
if ( 0.0 == sample_stop_distance )
|
|
{
|
|
text_log.Print("Testing %d points exactly on the curve.\n",
|
|
test_point_count);
|
|
}
|
|
else if ( sample_start_distance == sample_stop_distance )
|
|
{
|
|
text_log.Print("Testing %d points about %g units off of the curve.\n",
|
|
test_point_count,
|
|
sample_start_distance);
|
|
}
|
|
else
|
|
{
|
|
text_log.Print("Testing %d points from %g to %g units off of the curve.\n",
|
|
test_point_count,
|
|
sample_start_distance,sample_stop_distance);
|
|
}
|
|
|
|
double best_time = 0.0;
|
|
|
|
// execution time tests
|
|
if ( bSpeedTest )
|
|
{
|
|
|
|
for ( i = 0; i < test_count; i++ )
|
|
{
|
|
tests[i]->SpeedTest(test_point_count,tp);
|
|
}
|
|
|
|
for ( i = 0; i < test_count; i++ )
|
|
{
|
|
if ( 0.0 == best_time || (tests[i]->m_results.m_elapsed_time > 0.0 && tests[i]->m_results.m_elapsed_time < best_time ))
|
|
{
|
|
best_time = tests[i]->m_results.m_elapsed_time;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( bAccuracyTest )
|
|
{
|
|
for ( i = 0; i < test_count; i++ )
|
|
{
|
|
tests[i]->AccuracyTest(test_point_count,tp);
|
|
}
|
|
}
|
|
|
|
for ( i = 0; i < test_count; i++ )
|
|
{
|
|
tests[i]->CalculateResults(*curve,test_point_count,tp,best_time);
|
|
}
|
|
|
|
// print title
|
|
tests[0]->Print(text_log,test_point_count);
|
|
|
|
// print results for each test
|
|
for ( i = 0; i < test_count; i++ )
|
|
{
|
|
tests[i]->Print(text_log,0);
|
|
}
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Dale's new surface closest point
|
|
//
|
|
class CDaleSurfacePointTest : public CClosestPointToSurfaceTest
|
|
{
|
|
public:
|
|
virtual void Setup( ON_NurbsSurface* nurbs_surface );
|
|
virtual int GetClosestPoint(ON_3dPoint P, double* s, double* t);
|
|
};
|
|
|
|
void CDaleSurfacePointTest::Setup( ON_NurbsSurface* nurbs_surface )
|
|
{
|
|
SetupHelper(CSurfaceTestPoint::FUNC_SRF_CLSPT_NEWON,nurbs_surface);
|
|
nurbs_surface->SurfaceTree(); // prebuild surface tree
|
|
}
|
|
|
|
int CDaleSurfacePointTest::GetClosestPoint( ON_3dPoint P, double* s, double* t )
|
|
{
|
|
return m_nurbs_surface->GetClosestPoint(P,s,t);
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Goose closest point
|
|
//
|
|
|
|
class CGooseSurfacePointTest : public CClosestPointToSurfaceTest
|
|
{
|
|
public:
|
|
CGooseSurfacePointTest();
|
|
~CGooseSurfacePointTest();
|
|
|
|
virtual void Setup( ON_NurbsSurface* nurbs_surface );
|
|
virtual int GetClosestPoint( ON_3dPoint P, double* s, double* t );
|
|
|
|
ON_3dPoint srfP; // surface midpoint
|
|
TL_NURBSRF N;
|
|
IwPoint3d Q;
|
|
IwExtent2d Interval;
|
|
IwBSplineSurface* pS;
|
|
};
|
|
|
|
CGooseSurfacePointTest::CGooseSurfacePointTest()
|
|
{
|
|
memset(&N,0,sizeof(N));
|
|
pS =0;
|
|
}
|
|
|
|
CGooseSurfacePointTest::~CGooseSurfacePointTest()
|
|
{
|
|
N.cv = 0;
|
|
N.knot[0] = 0;
|
|
N.knot[1] = 0;
|
|
TL_DestroyNurbSrf(&N);
|
|
delete pS;
|
|
}
|
|
|
|
void CGooseSurfacePointTest::Setup( ON_NurbsSurface* nurbs_surface )
|
|
{
|
|
SetupHelper(CSurfaceTestPoint::FUNC_SRF_CLSPT_GOOSE,nurbs_surface);
|
|
|
|
srfP = nurbs_surface->PointAt(nurbs_surface->Domain(0).ParameterAt(0.5),nurbs_surface->Domain(1).ParameterAt(0.5));
|
|
|
|
memset(&N,0,sizeof(N));
|
|
N.dim = nurbs_surface->m_dim;
|
|
N.is_rat = nurbs_surface->m_is_rat;
|
|
N.order[0] = nurbs_surface->m_order[0];
|
|
N.order[1] = nurbs_surface->m_order[1];
|
|
N.cv_count[0] = nurbs_surface->m_cv_count[0];
|
|
N.cv_count[1] = nurbs_surface->m_cv_count[1];
|
|
N.cv = nurbs_surface->m_cv;
|
|
N.knot[0] = nurbs_surface->m_knot[0];
|
|
N.knot[1] = nurbs_surface->m_knot[1];
|
|
|
|
TL_Convert( N, pS );
|
|
Interval = pS->GetNaturalUVDomain();
|
|
IwCacheMgr::GetOrCreateObjectCache(IW_OC_SURFACE,pS);
|
|
}
|
|
|
|
int CGooseSurfacePointTest::GetClosestPoint( ON_3dPoint P, double* s, double* t )
|
|
{
|
|
int rc;
|
|
double dist_tol;
|
|
IwStatus iw_rc;
|
|
|
|
IwSolutionArray sSolutions;
|
|
|
|
dist_tol = (1.0+ON_SQRT_EPSILON)*P.DistanceTo(P);
|
|
|
|
Q.x = P.x;
|
|
Q.y = P.y;
|
|
Q.z = P.z;
|
|
|
|
iw_rc = pS->GlobalPointSolve(
|
|
Interval,
|
|
IW_SO_MINIMIZE,
|
|
Q,
|
|
dist_tol,
|
|
NULL,// NULL,
|
|
IW_SR_SINGLE,
|
|
sSolutions
|
|
);
|
|
|
|
if ( iw_rc == IW_SUCCESS && sSolutions.GetSize() > 0)
|
|
{
|
|
*s = sSolutions[0].m_vStart[0];
|
|
*t = sSolutions[0].m_vStart[1];
|
|
rc = 1;
|
|
}
|
|
else
|
|
{
|
|
rc = 0;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
void TestClosestPointToThisSurface( ON_TextLog& text_log,
|
|
ON_NurbsSurface& nurbs_surface,
|
|
double sample_start_distance,
|
|
double sample_stop_distance
|
|
)
|
|
{
|
|
CClosestPointToSurfaceTest* tests[20];
|
|
int test_count = 0;
|
|
int i;
|
|
bool bSpeedTest = true;
|
|
bool bAccuracyTest = true;
|
|
|
|
CDaleSurfacePointTest dale_test;
|
|
CGooseSurfacePointTest goose_test;
|
|
|
|
dale_test.Setup(&nurbs_surface);
|
|
goose_test.Setup(&nurbs_surface);
|
|
|
|
tests[test_count++] = &dale_test;
|
|
//tests[test_count++] = &goose_test;
|
|
|
|
// get test points
|
|
ON_SimpleArray<CSurfaceTestPoint> TP;
|
|
GetSurfacePointCloud( nurbs_surface, sample_start_distance, sample_stop_distance, TP );
|
|
CSurfaceTestPoint* tp = TP.Array();
|
|
int test_point_count = TP.Count();
|
|
|
|
if ( 0.0 == sample_stop_distance )
|
|
{
|
|
text_log.Print("Testing %d points exactly on the surface.\n",
|
|
test_point_count);
|
|
}
|
|
else if ( sample_start_distance == sample_stop_distance )
|
|
{
|
|
text_log.Print("Testing %d points about %g units off of the surface.\n",
|
|
test_point_count,
|
|
sample_start_distance);
|
|
}
|
|
else
|
|
{
|
|
text_log.Print("Testing %d points from %g to %g units off of the surface.\n",
|
|
test_point_count,
|
|
sample_start_distance,sample_stop_distance);
|
|
}
|
|
|
|
double best_time = 0.0;
|
|
|
|
// execution time tests
|
|
if ( bSpeedTest )
|
|
{
|
|
|
|
for ( i = 0; i < test_count; i++ )
|
|
{
|
|
tests[i]->SpeedTest(test_point_count,tp);
|
|
}
|
|
|
|
for ( i = 0; i < test_count; i++ )
|
|
{
|
|
if ( 0.0 == best_time || (tests[i]->m_results.m_elapsed_time > 0.0 && tests[i]->m_results.m_elapsed_time < best_time ))
|
|
{
|
|
best_time = tests[i]->m_results.m_elapsed_time;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( bAccuracyTest )
|
|
{
|
|
for ( i = 0; i < test_count; i++ )
|
|
{
|
|
tests[i]->AccuracyTest(test_point_count,tp);
|
|
}
|
|
}
|
|
|
|
for ( i = 0; i < test_count; i++ )
|
|
{
|
|
tests[i]->CalculateResults(nurbs_surface,test_point_count,tp,best_time);
|
|
}
|
|
|
|
// print title
|
|
tests[0]->Print(text_log,test_point_count);
|
|
|
|
// print results for each test
|
|
for ( i = 0; i < test_count; i++ )
|
|
{
|
|
tests[i]->Print(text_log,0);
|
|
}
|
|
}
|
|
|
|
void TestClosestPointToThisMesh( ON_TextLog& text_log,
|
|
const ON_Mesh& mesh,
|
|
const ON_Surface* srf,
|
|
double sample_start_distance,
|
|
double sample_stop_distance
|
|
)
|
|
{
|
|
CClosestPointToMeshTest* tests[20];
|
|
int test_count = 0;
|
|
int i;
|
|
bool bSpeedTest = true;
|
|
bool bAccuracyTest = true;
|
|
|
|
// get test points
|
|
ON_SimpleArray<CMeshTestPoint> TP;
|
|
GetMeshPointCloud( mesh, sample_start_distance, sample_stop_distance, TP );
|
|
CMeshTestPoint* tp = TP.Array();
|
|
int test_point_count = TP.Count();
|
|
|
|
CClosestPointToMeshTest first_test;
|
|
|
|
first_test.SetupHelper( &mesh, srf );
|
|
|
|
tests[test_count++] = &first_test;
|
|
|
|
if ( 0.0 == sample_stop_distance )
|
|
{
|
|
text_log.Print("Testing %d points exactly on the mesh.\n",
|
|
test_point_count);
|
|
}
|
|
else if ( sample_start_distance == sample_stop_distance )
|
|
{
|
|
text_log.Print("Testing %d points about %g units off of the mesh.\n",
|
|
test_point_count,
|
|
sample_start_distance);
|
|
}
|
|
else
|
|
{
|
|
text_log.Print("Testing %d points from %g to %g units off of the mesh.\n",
|
|
test_point_count,
|
|
sample_start_distance,sample_stop_distance);
|
|
}
|
|
|
|
double best_time = 0.0;
|
|
|
|
// execution time tests
|
|
if ( bSpeedTest )
|
|
{
|
|
|
|
for ( i = 0; i < test_count; i++ )
|
|
{
|
|
tests[i]->SpeedTest(test_point_count,tp);
|
|
}
|
|
|
|
}
|
|
|
|
if ( bAccuracyTest )
|
|
{
|
|
for ( i = 0; i < test_count; i++ )
|
|
{
|
|
tests[i]->AccuracyTest(test_point_count,tp);
|
|
}
|
|
}
|
|
|
|
for ( i = 0; i < test_count; i++ )
|
|
{
|
|
tests[i]->CalculateResults(test_point_count,tp);
|
|
}
|
|
|
|
// print title
|
|
tests[0]->Print(text_log,test_point_count);
|
|
}
|
|
|
|
void TestCurveTree(ON_TextLog& text_log, ON_NurbsCurve& nurbs_curve)
|
|
{
|
|
const ON_CurveTree* tree = nurbs_curve.CurveTree();
|
|
if ( !tree->IsValid( &text_log, &nurbs_curve ) )
|
|
{
|
|
text_log.Print("Curve tree is not valid\n");
|
|
}
|
|
|
|
if (true)
|
|
{
|
|
ON_CurveTreeNode* leaf = tree->FirstLeaf();
|
|
int leaf_count=0;
|
|
double maxar = 0.0;
|
|
double maxr = 0.0;
|
|
double maxlen = 0.0;
|
|
double longest = 0.0;
|
|
double shortest = fabs(ON_UNSET_VALUE);
|
|
ON_Line axis;
|
|
int notmonocount = 0;
|
|
while (leaf)
|
|
{
|
|
if( !leaf->m_bez->m_leafbox.m_bMono )
|
|
notmonocount++;
|
|
axis.from = leaf->m_bez->PointAt(0.0);
|
|
axis.to = leaf->m_bez->PointAt(1.0);
|
|
double len = axis.Length();
|
|
if (len > longest )
|
|
longest = len;
|
|
if ( len < shortest )
|
|
shortest = len;
|
|
if ( leaf->m_bez->m_leafbox.Radius() > maxar*len )
|
|
{
|
|
maxar = leaf->m_bez->m_leafbox.Radius()/len;
|
|
maxlen = len;
|
|
maxr = leaf->m_bez->m_leafbox.Radius();
|
|
}
|
|
leaf_count++;
|
|
leaf = leaf->NextLeaf();
|
|
}
|
|
if ( notmonocount > 0 )
|
|
{
|
|
text_log.Print("ON_CurveTree: %d leaves (%d are not monotone).\n",leaf_count,notmonocount);
|
|
}
|
|
else
|
|
{
|
|
text_log.Print("ON_CurveTree: %d leaves (all are monotone).\n",leaf_count);
|
|
}
|
|
text_log.PushIndent();
|
|
text_log.Print("Longest: %g\n",longest);
|
|
text_log.Print("Shortest: %g\n",shortest);
|
|
if ( maxar > 0.0 )
|
|
text_log.Print("Thickest: rad/len = %g (rad = %g, len = %g)\n",maxar,maxr,maxlen);
|
|
text_log.PopIndent();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void TestSurfaceTree(ON_TextLog& text_log, ON_NurbsSurface& nurbs_surface)
|
|
{
|
|
const ON_SurfaceTree* tree = nurbs_surface.SurfaceTree();
|
|
if ( !tree->IsValid( &text_log, &nurbs_surface ) )
|
|
{
|
|
text_log.Print("Surface tree is not valid.\n");
|
|
}
|
|
|
|
if (true)
|
|
{
|
|
ON_SurfaceTreeNode* leaf = tree->FirstLeaf();
|
|
int leaf_count=0;
|
|
double maxar = 0.0;
|
|
double maxr = 0.0;
|
|
double maxlen = 0.0;
|
|
double longest = 0.0;
|
|
double shortest = fabs(ON_UNSET_VALUE);
|
|
ON_3dPoint C[4];
|
|
int notmonocount = 0;
|
|
while (leaf)
|
|
{
|
|
if( !leaf->m_bez->m_leafbox.m_bMono )
|
|
notmonocount++;
|
|
C[0] = leaf->m_bez->PointAt(0.0,0.0);
|
|
C[1] = leaf->m_bez->PointAt(1.0,0.0);
|
|
C[2] = leaf->m_bez->PointAt(1.0,1.0);
|
|
C[3] = leaf->m_bez->PointAt(0.0,1.0);
|
|
|
|
double len = C[3].DistanceTo(C[0]);
|
|
|
|
for ( int i = 0; i < 3; i++ )
|
|
{
|
|
double x = C[i].DistanceTo(C[i+1]);
|
|
if ( x > len )
|
|
len = x;
|
|
}
|
|
|
|
if (len > longest )
|
|
longest = len;
|
|
|
|
if ( len < shortest )
|
|
shortest = len;
|
|
|
|
if ( leaf->m_bez->m_leafbox.Height() > maxar*len )
|
|
{
|
|
maxlen = len;
|
|
maxr = leaf->m_bez->m_leafbox.Height();
|
|
maxar = maxr/len;
|
|
}
|
|
leaf_count++;
|
|
leaf = leaf->NextLeaf();
|
|
}
|
|
|
|
if ( notmonocount > 0 )
|
|
{
|
|
text_log.Print("ON_SurfaceTree: %d leaves (%d are not monotone).\n",leaf_count,notmonocount);
|
|
}
|
|
else
|
|
{
|
|
text_log.Print("ON_SurfaceTree: %d leaves (all are monotone).\n",leaf_count);
|
|
}
|
|
|
|
text_log.PushIndent();
|
|
text_log.Print("Longest: %g\n",longest);
|
|
text_log.Print("Shortest: %g\n",shortest);
|
|
if ( maxar > 0.0 )
|
|
text_log.Print("Thickest: ht/len = %g (ht = %g, len = %g)\n",maxar,maxr,maxlen);
|
|
text_log.PopIndent();
|
|
}
|
|
}
|
|
|
|
class CTestMeshTreeHelper
|
|
{
|
|
public:
|
|
CTestMeshTreeHelper();
|
|
|
|
void Test( const ON_MeshTreeNode* node, int depth );
|
|
|
|
void Report( ON_TextLog& text_log ) const;
|
|
|
|
double m_tree_diagonal;
|
|
int m_one_branch_node_count;
|
|
int m_two_branch_node_count;
|
|
int m_leaf_count;
|
|
int m_max_depth;
|
|
int m_max_branched_node_fcount;
|
|
int m_min_leaf_fcount;
|
|
int m_max_leaf_fcount;
|
|
double m_min_leaf_diagonal;
|
|
double m_max_leaf_diagonal;
|
|
double m_min_diagonal_ratio; // maximum (child node diag)/(parent node diag).
|
|
double m_max_diagonal_ratio; // maximum (child node diag)/(parent node diag).
|
|
};
|
|
|
|
CTestMeshTreeHelper::CTestMeshTreeHelper()
|
|
{
|
|
memset(this,0,sizeof(*this));
|
|
m_tree_diagonal = ON_UNSET_VALUE;
|
|
m_min_leaf_diagonal = 1.0e150;
|
|
m_min_leaf_fcount = -1;
|
|
m_min_diagonal_ratio = 1.0e150;
|
|
}
|
|
|
|
|
|
void CTestMeshTreeHelper::Test( const ON_MeshTreeNode* node, int depth)
|
|
{
|
|
if ( !node )
|
|
return;
|
|
|
|
int down_count = 0;
|
|
|
|
const double node_diagonal = node->m_bbox.Diagonal().Length();
|
|
|
|
if ( ON_UNSET_VALUE == m_tree_diagonal )
|
|
{
|
|
m_tree_diagonal = node_diagonal;
|
|
}
|
|
|
|
if ( depth > m_max_depth )
|
|
{
|
|
m_max_depth = depth;
|
|
}
|
|
|
|
if ( node->m_down[0] )
|
|
{
|
|
down_count++;
|
|
double d = node->m_down[0]->m_bbox.Diagonal().Length()/node_diagonal;
|
|
if ( d > m_max_diagonal_ratio )
|
|
{
|
|
m_max_diagonal_ratio = d;
|
|
}
|
|
if ( d < m_min_diagonal_ratio )
|
|
{
|
|
m_min_diagonal_ratio = d;
|
|
}
|
|
Test( node->m_down[0], depth+1 );
|
|
}
|
|
|
|
if ( node->m_down[1] )
|
|
{
|
|
down_count++;
|
|
double d = node->m_down[1]->m_bbox.Diagonal().Length()/node_diagonal;
|
|
if ( d > m_max_diagonal_ratio )
|
|
{
|
|
m_max_diagonal_ratio = d;
|
|
}
|
|
if ( d < m_min_diagonal_ratio )
|
|
{
|
|
m_min_diagonal_ratio = d;
|
|
}
|
|
Test( node->m_down[1], depth+1 );
|
|
}
|
|
|
|
if ( down_count )
|
|
{
|
|
if ( node->m_fcount > m_max_branched_node_fcount )
|
|
{
|
|
m_max_branched_node_fcount = node->m_fcount;
|
|
}
|
|
if ( 1 == down_count )
|
|
m_one_branch_node_count++;
|
|
else
|
|
m_two_branch_node_count++;
|
|
}
|
|
else
|
|
{
|
|
m_leaf_count++;
|
|
if ( 1 == m_leaf_count )
|
|
{
|
|
m_max_leaf_fcount = m_min_leaf_fcount = node->m_fcount;
|
|
}
|
|
else if ( node->m_fcount > m_max_leaf_fcount )
|
|
{
|
|
m_max_leaf_fcount = node->m_fcount;
|
|
}
|
|
else if ( node->m_fcount < m_min_leaf_fcount )
|
|
{
|
|
m_min_leaf_fcount = node->m_fcount;
|
|
}
|
|
|
|
if ( node_diagonal > m_max_leaf_diagonal )
|
|
{
|
|
m_max_leaf_diagonal = node_diagonal;
|
|
}
|
|
if ( node_diagonal < m_min_leaf_diagonal )
|
|
{
|
|
m_min_leaf_diagonal = node_diagonal;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void CTestMeshTreeHelper::Report( ON_TextLog& textlog ) const
|
|
{
|
|
textlog.Print("%d one branch nodes\n",m_one_branch_node_count);
|
|
textlog.Print("%d two branch nodes\n",m_two_branch_node_count);
|
|
textlog.Print("%d leaf nodes\n",m_leaf_count);
|
|
textlog.Print("%d total nodes\n",m_leaf_count+m_two_branch_node_count+m_one_branch_node_count);
|
|
textlog.Print("Maximum depth: %d\n",m_max_depth);
|
|
textlog.Print("Maximum branched node face count: %d\n",m_max_branched_node_fcount);
|
|
textlog.Print("Diagonal reduction (child/parent): %g to %g\n",
|
|
m_min_diagonal_ratio,
|
|
m_max_diagonal_ratio);
|
|
textlog.Print("Leaf node face count: %d to %d\n",m_min_leaf_fcount,m_max_leaf_fcount);
|
|
textlog.Print("Leaf diagonal size: %g to %g\n",m_min_leaf_diagonal,m_max_leaf_diagonal);
|
|
}
|
|
|
|
void TestMeshTree(ON_TextLog& text_log, const ON_Mesh& mesh)
|
|
{
|
|
const ON_MeshTree* tree = mesh.MeshTree();
|
|
|
|
if ( !tree )
|
|
{
|
|
text_log.Print("mesh.MeshTree() returned NULL.\n");
|
|
ON_ERROR("mesh.MeshTree() returned NULL.");
|
|
return;
|
|
}
|
|
|
|
if ( !tree->IsValid( &text_log ) )
|
|
{
|
|
text_log.Print("Mesh tree is not valid.\n");
|
|
ON_ERROR("mesh.MeshTree() is not valid.");
|
|
}
|
|
else
|
|
{
|
|
text_log.Print("Mesh tree is valid.\n");
|
|
}
|
|
|
|
if (true)
|
|
{
|
|
CTestMeshTreeHelper testmeshhelper;
|
|
testmeshhelper.Test(tree,1);
|
|
|
|
text_log.PushIndent();
|
|
testmeshhelper.Report(text_log);
|
|
text_log.PopIndent();
|
|
}
|
|
|
|
}
|
|
|
|
void TestClosestPointToCurveHelper( ON_TextLog& text_log, const ON_Curve* curve, const wchar_t* name, const ON_SimpleArray<ON_3dPoint>& point_list )
|
|
{
|
|
TL_NurbsCurve nurbs_curve;
|
|
int pass;
|
|
double sample_start_distance = 0.0;
|
|
double sample_stop_distance = 0.0;
|
|
|
|
if ( curve )
|
|
{
|
|
if ( 0 == name || 0 == *name)
|
|
{
|
|
name = L"anonymous";
|
|
}
|
|
|
|
curve->GetNurbForm(nurbs_curve);
|
|
|
|
text_log.Print(L"Curve class = %S, name = %s, degree = %d, %s, CV count=%d\n",
|
|
curve->ClassId()->ClassName(),
|
|
name,
|
|
nurbs_curve.m_order-1,
|
|
(nurbs_curve.m_is_rat ? L"rational" : L"non-rational"),
|
|
nurbs_curve.m_cv_count
|
|
);
|
|
|
|
TestCurveTree( text_log, nurbs_curve );
|
|
|
|
for ( pass = 0; pass < 3; pass++ )
|
|
{
|
|
switch(pass)
|
|
{
|
|
case 0:
|
|
sample_start_distance = 0.0;
|
|
sample_stop_distance = 0.0;
|
|
break;
|
|
|
|
case 1:
|
|
sample_start_distance = 1.0/pow(2.0,8);
|
|
sample_stop_distance = 1.0/pow(2.0,5);
|
|
break;
|
|
|
|
case 2:
|
|
sample_start_distance = 0.25;
|
|
sample_stop_distance = 1.0;
|
|
break;
|
|
}
|
|
|
|
text_log.PushIndent();
|
|
|
|
text_log.Print("\n");
|
|
|
|
TestClosestPointToThisCurve( text_log, curve, sample_start_distance, sample_stop_distance );
|
|
|
|
text_log.PopIndent();
|
|
}
|
|
|
|
text_log.Print("\n");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void TestClosestPointToSurfaceHelper( ON_TextLog& text_log, const ON_Surface* surface, const wchar_t* name, const ON_SimpleArray<ON_3dPoint>& point_list )
|
|
{
|
|
TL_NurbsSurface nurbs_surface;
|
|
int pass;
|
|
double sample_start_distance = 0.0;
|
|
double sample_stop_distance = 0.0;
|
|
|
|
if ( surface )
|
|
{
|
|
if ( 0 == name || 0 == *name)
|
|
{
|
|
name = L"anonymous";
|
|
}
|
|
|
|
surface->GetNurbForm(nurbs_surface);
|
|
|
|
text_log.Print(L"Surface name = %s, degree = (%d,%d) %s, CV count=(%d,%d)\n",
|
|
name,
|
|
nurbs_surface.m_order[0]-1,nurbs_surface.m_order[1]-1,
|
|
(nurbs_surface.m_is_rat ? L"rational" : L"non-rational"),
|
|
nurbs_surface.m_cv_count[0],nurbs_surface.m_cv_count[1]
|
|
);
|
|
|
|
TestSurfaceTree( text_log, nurbs_surface );
|
|
|
|
for ( pass = 0; pass < 3; pass++ )
|
|
{
|
|
switch(pass)
|
|
{
|
|
case 0:
|
|
sample_start_distance = 0.0;
|
|
sample_stop_distance = 0.0;
|
|
break;
|
|
|
|
case 1:
|
|
sample_start_distance = 1.0/pow(2.0,8);
|
|
sample_stop_distance = 1.0/pow(2.0,5);
|
|
break;
|
|
|
|
case 2:
|
|
sample_start_distance = 0.25;
|
|
sample_stop_distance = 1.0;
|
|
break;
|
|
}
|
|
|
|
text_log.PushIndent();
|
|
|
|
text_log.Print("\n");
|
|
|
|
TestClosestPointToThisSurface( text_log, nurbs_surface, sample_start_distance, sample_stop_distance );
|
|
|
|
text_log.PopIndent();
|
|
}
|
|
|
|
text_log.Print("\n");
|
|
}
|
|
}
|
|
|
|
void TestClosestPointToMeshHelper( ON_TextLog& text_log,
|
|
const ON_Mesh* mesh,
|
|
const ON_Surface* surface,
|
|
const wchar_t* name,
|
|
const ON_SimpleArray<ON_3dPoint>& point_list
|
|
)
|
|
{
|
|
int pass;
|
|
double sample_start_distance = 0.0;
|
|
double sample_stop_distance = 0.0;
|
|
|
|
if ( mesh )
|
|
{
|
|
if ( 0 == name || 0 == *name)
|
|
{
|
|
name = L"anonymous";
|
|
}
|
|
|
|
text_log.Print(L"Mesh name = %s\n",
|
|
name);
|
|
text_log.PushIndent();
|
|
text_log.Print(L"vertex count = %d\n",
|
|
mesh->VertexCount()
|
|
);
|
|
text_log.Print(L"face count = %d (%d tris, %d quads)\n",
|
|
mesh->FaceCount(),
|
|
mesh->TriangleCount(),
|
|
mesh->QuadCount()
|
|
);
|
|
text_log.PopIndent();
|
|
|
|
TestMeshTree( text_log, *mesh );
|
|
|
|
for ( pass = 0; pass < 3; pass++ )
|
|
{
|
|
switch(pass)
|
|
{
|
|
case 0:
|
|
sample_start_distance = 0.0;
|
|
sample_stop_distance = 0.0;
|
|
break;
|
|
|
|
case 1:
|
|
sample_start_distance = 1.0/pow(2.0,8);
|
|
sample_stop_distance = 1.0/pow(2.0,5);
|
|
break;
|
|
|
|
case 2:
|
|
sample_start_distance = 0.25;
|
|
sample_stop_distance = 1.0;
|
|
break;
|
|
}
|
|
|
|
text_log.PushIndent();
|
|
|
|
text_log.Print("\n");
|
|
|
|
TestClosestPointToThisMesh( text_log, *mesh, surface, sample_start_distance, sample_stop_distance );
|
|
|
|
text_log.PopIndent();
|
|
}
|
|
|
|
text_log.Print("\n");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void TestClosestPoint( const ONX_Model& model, ON_TextLog& text_log,
|
|
bool bDoCurves,
|
|
bool bDoSurfaces,
|
|
bool bDoMeshes )
|
|
{
|
|
TEST_HEADER(text_log,"TestClosestPoint");
|
|
|
|
int i, k;
|
|
|
|
ON_wString name;
|
|
const wchar_t* attributes_name;
|
|
|
|
ON_SimpleArray< ON_3dPoint > points(1024);
|
|
// first do curves
|
|
if (bDoPoints)
|
|
{
|
|
for ( i = 0; i < model.m_object_table.Count(); i++ )
|
|
{
|
|
const ON_Point* point = ON_Point::Cast(model.m_object_table[i].m_object);
|
|
if ( point )
|
|
{
|
|
points.Append(point->point);
|
|
continue;
|
|
}
|
|
|
|
|
|
const ON_PointCloud* pointcloud = ON_PointCloud::Cast(model.m_object_table[i].m_object);
|
|
if ( pointcloud )
|
|
{
|
|
points.Append( pointcloud->m_P.Count(), pointcloud->m_P.Array() );
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// first do curves
|
|
if ( bDoCurves)
|
|
{
|
|
for ( i = 0; i < model.m_object_table.Count(); i++ )
|
|
{
|
|
const ON_Curve* curve = ON_Curve::Cast(model.m_object_table[i].m_object);
|
|
if ( curve )
|
|
{
|
|
attributes_name = model.m_object_table[i].m_attributes.m_name;
|
|
TestClosestPointToCurveHelper(text_log,curve,attributes_name,points);
|
|
continue;
|
|
}
|
|
|
|
|
|
const ON_Brep* brep = ON_Brep::Cast(model.m_object_table[i].m_object);
|
|
if ( brep )
|
|
{
|
|
for ( k = 0; k < brep->m_C3.Count(); k++ )
|
|
{
|
|
curve = brep->m_C3[k];
|
|
if ( curve )
|
|
{
|
|
attributes_name = model.m_object_table[i].m_attributes.m_name;
|
|
if ( !attributes_name )
|
|
attributes_name = L"anonymous";
|
|
name.Format(L"%s - brep.m_C3[%d]",attributes_name,k);
|
|
TestClosestPointToCurveHelper(text_log,curve,name,points);
|
|
}
|
|
}
|
|
|
|
for ( k = 0; k < brep->m_C2.Count(); k++ )
|
|
{
|
|
curve = brep->m_C2[k];
|
|
if ( curve )
|
|
{
|
|
attributes_name = model.m_object_table[i].m_attributes.m_name;
|
|
if ( !attributes_name )
|
|
attributes_name = L"anonymous";
|
|
name.Format(L"%s - brep.m_C2[%d]",attributes_name,k);
|
|
TestClosestPointToCurveHelper(text_log,curve,name,points);
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// then do surfaces
|
|
if ( bDoSurfaces )
|
|
{
|
|
for ( i = 0; i < model.m_object_table.Count(); i++ )
|
|
{
|
|
const ON_Surface* surface = ON_Surface::Cast(model.m_object_table[i].m_object);
|
|
if ( surface )
|
|
{
|
|
attributes_name = model.m_object_table[i].m_attributes.m_name;
|
|
TestClosestPointToSurfaceHelper(text_log,surface,attributes_name,points);
|
|
continue;
|
|
}
|
|
|
|
const ON_Brep* brep = ON_Brep::Cast(model.m_object_table[i].m_object);
|
|
if ( brep )
|
|
{
|
|
for ( k = 0; k < brep->m_S.Count(); k++ )
|
|
{
|
|
surface = brep->m_S[k];
|
|
if ( surface )
|
|
{
|
|
attributes_name = model.m_object_table[i].m_attributes.m_name;
|
|
if ( !attributes_name )
|
|
attributes_name = L"anonymous";
|
|
name.Format(L"%s - brep.m_S[%d]",attributes_name,k);
|
|
|
|
TestClosestPointToSurfaceHelper(text_log,surface,model.m_object_table[i].m_attributes.m_name,points);
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// then do meshes
|
|
if ( bDoMeshes )
|
|
{
|
|
for ( i = 0; i < model.m_object_table.Count(); i++ )
|
|
{
|
|
const ON_Mesh* mesh = ON_Mesh::Cast(model.m_object_table[i].m_object);
|
|
if ( mesh )
|
|
{
|
|
attributes_name = model.m_object_table[i].m_attributes.m_name;
|
|
TestClosestPointToMeshHelper(text_log,mesh,0,attributes_name,points);
|
|
continue;
|
|
}
|
|
|
|
const ON_Brep* brep = ON_Brep::Cast(model.m_object_table[i].m_object);
|
|
if ( brep )
|
|
{
|
|
for ( k = 0; k < brep->m_F.Count(); k++ )
|
|
{
|
|
mesh = brep->m_F[k].Mesh( ON::render_mesh );
|
|
if ( mesh )
|
|
{
|
|
attributes_name = model.m_object_table[i].m_attributes.m_name;
|
|
if ( !attributes_name )
|
|
attributes_name = L"anonymous";
|
|
name.Format(L"%s - brep.m_F[%d] render mesh",attributes_name,k);
|
|
|
|
TestClosestPointToMeshHelper(
|
|
text_log,
|
|
mesh,
|
|
brep->m_F[k].SurfaceOf(),
|
|
model.m_object_table[i].m_attributes.m_name,points);
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|