Files
opennurbs/opennurbs_nurbssurface.cpp
2025-05-13 04:08:15 -07:00

3872 lines
99 KiB
C++

//
// Copyright (c) 1993-2022 Robert McNeel & Associates. All rights reserved.
// OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert
// McNeel & Associates.
//
// THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
// ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF
// MERCHANTABILITY ARE HEREBY DISCLAIMED.
//
// For complete openNURBS copyright information see <http://www.opennurbs.org>.
//
////////////////////////////////////////////////////////////////
#include "opennurbs.h"
#if !defined(ON_COMPILING_OPENNURBS)
// This check is included in all opennurbs source .c and .cpp files to insure
// ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled.
// When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined
// and the opennurbs .h files alter what is declared and how it is declared.
#error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs
#endif
ON_OBJECT_IMPLEMENT(ON_NurbsSurface,ON_Surface,"4ED7D4DE-E947-11d3-BFE5-0010830122F0");
ON_NurbsSurface* ON_NurbsSurface::New()
{
// static function replaces new ON_NurbsSurface();
return new ON_NurbsSurface();
}
ON_NurbsSurface* ON_NurbsSurface::New(
const ON_NurbsSurface& nurbs_surface
)
{
// static function replaces new ON_NurbsSurface(const ON_NurbsSurface& nurbs_surface);
return new ON_NurbsSurface(nurbs_surface);
}
ON_NurbsSurface* ON_NurbsSurface::New(
const ON_BezierSurface& bezier_surface
)
{
// static function replaces new ON_NurbsSurface(const ON_BezierSurface& bezier_surface);
return new ON_NurbsSurface(bezier_surface);
}
ON_NurbsSurface* ON_NurbsSurface::New(
int dimension,
bool bIsRational,
int order0,
int order1,
int cv_count0,
int cv_count1
)
{
// static function replaces new ON_NurbsSurface(dim, is_rat, order0, ..., cv_count1 );
return new ON_NurbsSurface(dimension,bIsRational,order0,order1,cv_count0,cv_count1);
}
ON_NurbsSurface::ON_NurbsSurface()
{
ON__SET__THIS__PTR(m_s_ON_NurbsSurface_ptr);
Initialize();
}
ON_NurbsSurface::ON_NurbsSurface( const ON_NurbsSurface& src )
{
ON__SET__THIS__PTR(m_s_ON_NurbsSurface_ptr);
Initialize();
*this = src;
}
ON_NurbsSurface::ON_NurbsSurface( const ON_BezierSurface& bezier_surface )
{
ON__SET__THIS__PTR(m_s_ON_NurbsSurface_ptr);
Initialize();
*this = bezier_surface;
}
ON_NurbsSurface::ON_NurbsSurface(
int dim, // dimension (>= 1)
bool is_rat, // true to make a rational NURBS
int order0, // order (>= 2)
int order1, // order (>= 2)
int cv_count0, // cv count0 (>= order0)
int cv_count1 // cv count1 (>= order1)
)
{
ON__SET__THIS__PTR(m_s_ON_NurbsSurface_ptr);
Initialize();
Create( dim, is_rat, order0, order1, cv_count0, cv_count1 );
}
ON_NurbsSurface::~ON_NurbsSurface()
{
Destroy();
}
unsigned int ON_NurbsSurface::SizeOf() const
{
unsigned int sz = ON_Surface::SizeOf();
sz += (sizeof(*this) - sizeof(ON_Surface));
sz += m_knot_capacity[0]*sizeof(*m_knot[0]);
sz += m_knot_capacity[1]*sizeof(*m_knot[1]);
sz += m_cv_capacity*sizeof(*m_cv);
return sz;
}
ON__UINT32 ON_NurbsSurface::DataCRC(ON__UINT32 current_remainder) const
{
current_remainder = ON_CRC32(current_remainder,sizeof(m_dim),&m_dim);
current_remainder = ON_CRC32(current_remainder,sizeof(m_is_rat),&m_is_rat);
current_remainder = ON_CRC32(current_remainder,2*sizeof(m_order[0]),&m_order[0]);
current_remainder = ON_CRC32(current_remainder,2*sizeof(m_cv_count[0]),&m_cv_count[0]);
if ( m_cv_count[0] > 0 && m_cv_count[1] > 0
&& m_cv_stride[0] > 0 && m_cv_stride[1] > 0
&& m_cv )
{
size_t sizeof_cv = CVSize()*sizeof(m_cv[0]);
const double* cv = m_cv;
int i, j;
for ( i = 0; i < m_cv_count[0]; i++ )
{
cv = CV(i,0);
for ( j = 0; j < m_cv_count[1]; j++ )
{
current_remainder = ON_CRC32(current_remainder,sizeof_cv,cv);
cv += m_cv_stride[1];
}
}
}
current_remainder = ON_CRC32(current_remainder,KnotCount(0)*sizeof(m_knot[0][0]),m_knot[0]);
current_remainder = ON_CRC32(current_remainder,KnotCount(1)*sizeof(m_knot[1][0]),m_knot[1]);
return current_remainder;
}
bool ON_NurbsSurface::SetDomain(
int dir, // 0 sets first parameter's domain, 1 gets second parameter's domain
double t0,
double t1
)
{
bool rc = false;
if ( m_order[dir] >= 2 && m_cv_count[dir] >= m_order[dir] && nullptr != m_knot[dir] && t0 < t1 ) {
const double k0 = m_knot[dir][m_order[dir]-2];
const double k1 = m_knot[dir][m_cv_count[dir]-1];
if ( k0 == t0 && k1 == t1 )
rc = true;
else if ( k0 < k1 ) {
const double d = (t1-t0)/(k1-k0);
const double km = 0.5*(k0+k1);
const int knot_count = KnotCount(dir);
int i;
for ( i = 0; i < knot_count; i++ ) {
if ( m_knot[dir][i] <= km ) {
m_knot[dir][i] = (m_knot[dir][i] - k0)*d + t0;
}
else {
m_knot[dir][i] = (m_knot[dir][i] - k1)*d + t1;
}
}
rc = true;
DestroySurfaceTree();
}
}
return rc;
}
int ON_NurbsSurface::Dimension() const
{
return m_dim;
}
bool ON_NurbsSurface::IsRational() const
{
return m_is_rat?true:false;
}
int ON_NurbsSurface::CVSize() const
{
return (m_is_rat) ? m_dim+1 : m_dim;
}
int ON_NurbsSurface::Order( int dir ) const
{
return m_order[dir?1:0];
}
int ON_NurbsSurface::Degree( int dir ) const
{
return (m_order[dir?1:0]>=2) ? m_order[dir?1:0]-1 : 0;
}
int ON_NurbsSurface::CVCount( int dir ) const
{
return m_cv_count[dir?1:0];
}
int ON_NurbsSurface::CVCount( void ) const
{
return m_cv_count[0]*m_cv_count[1];
}
int ON_NurbsSurface::KnotCount( int dir ) const
{
return ON_KnotCount( m_order[dir?1:0], m_cv_count[dir?1:0] );
}
double* ON_NurbsSurface::CV( int i, int j ) const
{
const int offset = (i * m_cv_stride[0] + j * m_cv_stride[1]);
return (m_cv && offset >= 0) ? (m_cv + offset) : nullptr;
}
double* ON_NurbsSurface::CV(
ON_2dex cvdex
) const
{
return (cvdex.i >= 0 && cvdex.j >= 0) ? CV(cvdex.i, cvdex.j) : nullptr;
}
double* ON_NurbsSurface::CV(
ON_2udex cvdex
) const
{
return (cvdex.i < 0x7FFFFFFFU && cvdex.j < 0x7FFFFFFFU) ? CV(cvdex.i, cvdex.j) : nullptr;
}
const ON_4dPoint ON_NurbsSurface::ControlPoint(
int i,
int j
) const
{
ON_4dPoint cv;
if (!GetCV(i, j, cv))
cv = ON_4dPoint::Nan;
return cv;
}
const ON_2dex ON_NurbsSurface::ControlPointSpans(int dir, int control_point_index) const
{
if (0 == dir || 1 == dir)
return ON_BsplineControlPointSpans(
this->m_order[dir],
this->m_cv_count[dir],
control_point_index
);
return ON_2dex(0, 0);
}
const ON_Interval ON_NurbsSurface::ControlPointSupport(int dir, int control_point_index) const
{
if (0 == dir || 1 == dir)
return ON_BsplineControlPointSupport(
this->m_order[dir],
this->m_cv_count[dir],
this->m_knot[dir],
control_point_index
);
return ON_Interval::Nan;
}
ON::point_style ON_NurbsSurface::CVStyle() const
{
return m_is_rat ? ON::homogeneous_rational : ON::not_rational;
}
double ON_NurbsSurface::Weight( int i, int j ) const
{
return (m_cv && m_is_rat) ? m_cv[i*m_cv_stride[0] + j*m_cv_stride[1] + m_dim] : 1.0;
}
double ON_NurbsSurface::Knot( int dir, int i ) const
{
return (m_knot[dir?1:0]) ? m_knot[dir?1:0][i] : 0.0;
}
int ON_NurbsSurface::KnotMultiplicity( int dir, int i ) const
{
dir = dir?1:0;
return ON_KnotMultiplicity( m_order[dir], m_cv_count[dir], m_knot[dir], i );
}
const double* ON_NurbsSurface::Knot( int dir ) const
{
return m_knot[dir?1:0];
}
bool ON_NurbsSurface::MakeClampedUniformKnotVector(
int dir,
double delta
)
{
if ( dir < 0 || dir > 1 )
return false;
DestroySurfaceTree();
ReserveKnotCapacity( dir, ON_KnotCount( m_order[dir], m_cv_count[dir] ) );
return ON_MakeClampedUniformKnotVector( m_order[dir], m_cv_count[dir], m_knot[dir], delta );
}
bool ON_NurbsSurface::MakePeriodicUniformKnotVector(
int dir,
double delta
)
{
if ( dir < 0 || dir > 1 )
return false;
DestroySurfaceTree();
ReserveKnotCapacity( dir, ON_KnotCount( m_order[dir], m_cv_count[dir] ) );
return ON_MakePeriodicUniformKnotVector( m_order[dir], m_cv_count[dir], m_knot[dir], delta );
}
double ON_NurbsSurface::SuperfluousKnot( int dir, int end ) const
{ return(m_knot[dir?1:0]) ? ON_SuperfluousKnot(m_order[dir?1:0],m_cv_count[dir?1:0],m_knot[dir?1:0],end) : 0.0;}
bool ON_NurbsSurface::Create(
int dim, // dimension (>= 1)
bool is_rat, // true to make a rational NURBS
int order0, // order (>= 2)
int order1, // order (>= 2)
int cv_count0, // cv count0 (>= order0)
int cv_count1 // cv count1 (>= order1)
)
{
DestroySurfaceTree();
if ( dim < 1 )
return false;
if ( order0 < 2 )
return false;
if ( order1 < 2 )
return false;
if ( cv_count0 < order0 )
return false;
if ( cv_count1 < order1 )
return false;
m_dim = dim;
m_is_rat = (is_rat) ? true : false;
m_order[0] = order0;
m_order[1] = order1;
m_cv_count[0] = cv_count0;
m_cv_count[1] = cv_count1;
m_cv_stride[1] = (m_is_rat) ? m_dim+1 : m_dim;
m_cv_stride[0] = m_cv_stride[1]*m_cv_count[1];
bool rc = ReserveKnotCapacity( 0, KnotCount(0) );
if ( !ReserveKnotCapacity( 1, KnotCount(1) ) )
rc = false;
if ( !ReserveCVCapacity( m_cv_count[0]*m_cv_count[1]*m_cv_stride[1] ) )
rc = false;
return rc;
}
void ON_NurbsSurface::Destroy()
{
double* cv = ( m_cv && m_cv_capacity ) ? m_cv : nullptr;
double* knot0 = ( m_knot[0] && m_knot_capacity[0] ) ? m_knot[0] : nullptr;
double* knot1 = ( m_knot[1] && m_knot_capacity[1] ) ? m_knot[1] : nullptr;
Initialize();
if ( cv )
onfree(cv);
if ( knot0 )
onfree(knot0);
if ( knot1 )
onfree(knot1);
}
void ON_NurbsSurface::EmergencyDestroy()
{
Initialize();
}
void ON_NurbsSurface::Initialize()
{
m_dim = 0;
m_is_rat = 0;
m_order[0] = 0;
m_order[1] = 0;
m_cv_count[0] = 0;
m_cv_count[1] = 0;
m_knot_capacity[0] = 0;
m_knot_capacity[1] = 0;
m_knot[0] = 0;
m_knot[1] = 0;
m_cv_stride[0] = 0;
m_cv_stride[1] = 0;
m_cv_capacity = 0;
m_cv = 0;
}
bool ON_NurbsSurface::ReserveKnotCapacity( int dir, int capacity )
{
if (dir)
dir = 1;
if ( m_knot_capacity[dir] < capacity ) {
if ( m_knot[dir] ) {
if ( m_knot_capacity[dir] ) {
m_knot[dir] = (double*)onrealloc( m_knot[dir], capacity*sizeof(*m_knot[dir]) );
m_knot_capacity[dir] = (m_knot[dir]) ? capacity : 0;
}
// else user supplied m_knot[] array
}
else {
m_knot[dir] = (double*)onmalloc( capacity*sizeof(*m_knot[dir]) );
m_knot_capacity[dir] = (m_knot[dir]) ? capacity : 0;
}
}
return ( m_knot[dir] ) ? true : false;
}
bool ON_NurbsSurface::ReserveCVCapacity( int capacity )
{
if ( m_cv_capacity < capacity ) {
if ( m_cv ) {
if ( m_cv_capacity ) {
m_cv = (double*)onrealloc( m_cv, capacity*sizeof(*m_cv) );
m_cv_capacity = (m_cv) ? capacity : 0;
}
// else user supplied m_cv[] array
}
else {
m_cv = (double*)onmalloc( capacity*sizeof(*m_cv) );
m_cv_capacity = (m_cv) ? capacity : 0;
}
}
return ( m_cv ) ? true : false;
}
static void ON_NurbsSurfaceCopyHelper( const ON_NurbsSurface& src, ON_NurbsSurface& dest )
{
dest.m_dim = src.m_dim;
dest.m_is_rat = src.m_is_rat;
dest.m_order[0] = src.m_order[0];
dest.m_order[1] = src.m_order[1];
dest.m_cv_count[0] = src.m_cv_count[0];
dest.m_cv_count[1] = src.m_cv_count[1];
dest.m_cv_stride[1] = dest.m_is_rat ? dest.m_dim+1 : dest.m_dim;
dest.m_cv_stride[0] = dest.m_cv_count[1]*dest.m_cv_stride[1];
if ( src.m_knot[0] )
{
// copy knot array
dest.ReserveKnotCapacity( 0, dest.KnotCount(0) );
memcpy( dest.m_knot[0], src.m_knot[0], dest.KnotCount(0)*sizeof(*dest.m_knot[0]) );
}
if ( src.m_knot[1] )
{
// copy knot array
dest.ReserveKnotCapacity( 1, dest.KnotCount(1) );
memcpy( dest.m_knot[1], src.m_knot[1], dest.KnotCount(1)*sizeof(*dest.m_knot[1]) );
}
if ( src.m_cv )
{
// copy cv array
dest.ReserveCVCapacity( dest.m_cv_count[0]*dest.m_cv_count[1]*dest.m_cv_stride[1] );
const int dst_cv_size = dest.CVSize()*sizeof(*dest.m_cv);
const int src_stride[2] = {src.m_cv_stride[0],src.m_cv_stride[1]};
if ( src_stride[0] == dest.m_cv_stride[0] && src_stride[1] == dest.m_cv_stride[1] )
{
memcpy( dest.m_cv, src.m_cv, dest.m_cv_count[0]*dest.m_cv_count[1]*dest.m_cv_stride[1]*sizeof(*dest.m_cv) );
}
else
{
const double *src_cv;
double *dst_cv = dest.m_cv;
int i, j;
for ( i = 0; i < dest.m_cv_count[0]; i++ )
{
src_cv = src.CV(i,0);
for ( j = 0; j < dest.m_cv_count[1]; j++ )
{
memcpy( dst_cv, src_cv, dst_cv_size );
dst_cv += dest.m_cv_stride[1];
src_cv += src_stride[1];
}
}
}
}
}
ON_NurbsSurface& ON_NurbsSurface::operator=( const ON_NurbsSurface& src )
{
if ( this != &src )
{
ON_Surface::operator=(src);
ON_NurbsSurfaceCopyHelper(src,*this);
}
return *this;
}
ON_NurbsSurface& ON_NurbsSurface::operator=( const ON_BezierSurface& bezier_surface )
{
int i, j;
DestroySurfaceTree();
m_dim = bezier_surface.m_dim;
m_is_rat = bezier_surface.m_is_rat;
m_order[0] = bezier_surface.m_order[0];
m_order[1] = bezier_surface.m_order[1];
m_cv_count[0] = m_order[0];
m_cv_count[1] = m_order[1];
m_cv_stride[1] = m_is_rat ? (m_dim+1) : m_dim;
m_cv_stride[0] = m_cv_stride[1]*m_cv_count[1];
// copy control points
if ( bezier_surface.m_cv )
{
ReserveCVCapacity(m_cv_count[0]*m_cv_count[1]*m_cv_stride[1]);
const int sizeof_cv = m_cv_stride[1]*sizeof(*m_cv);
const double* src_cv;
double* dst_cv;
for ( i = 0; i < m_order[0]; i++ ) for( j = 0; j < m_order[1]; j++ )
{
src_cv = bezier_surface.CV(i,j);
dst_cv = CV(i,j);
memcpy( dst_cv, src_cv, sizeof_cv );
}
}
// set clamped knots for domain [0,1]
for ( j = 0; j < 2; j++ )
{
const int knot_count = KnotCount(j);
ReserveKnotCapacity( j, knot_count );
for ( i = 0; i <= m_order[j]-2; i++ )
m_knot[j][i] = 0.0;
for ( i = m_order[j]-1; i < knot_count; i++ )
m_knot[j][i] = 1.0;
}
return *this;
}
void ON_NurbsSurface::Dump( ON_TextLog& dump ) const
{
dump.Print( "ON_NurbsSurface dim = %d is_rat = %d\n"
" order = %d X %d cv_count = %d X %d\n",
m_dim, m_is_rat, m_order[0], m_order[1], m_cv_count[0], m_cv_count[1] );
int dir;
for ( dir = 0; dir < 2; dir++ )
{
dump.Print( "Knot Vector %d ( %d knots )\n", dir, KnotCount(dir) );
dump.PrintKnotVector( m_order[dir], m_cv_count[dir], m_knot[dir] );
}
dump.Print( "Control Points %d %s points\n"
" index value\n",
m_cv_count[0]*m_cv_count[1],
(m_is_rat) ? "rational" : "non-rational" );
if ( !m_cv )
{
dump.Print(" nullptr cv array\n");
}
else
{
int i;
char sPreamble[128] = { 0 };
const size_t sPremable_capacity = sizeof(sPreamble) / sizeof(sPreamble[0]);
for ( i = 0; i < m_cv_count[0]; i++ )
{
if ( i > 0 )
dump.Print("\n");
sPreamble[0] = 0;
ON_String::FormatIntoBuffer(sPreamble, sPremable_capacity," CV[%2d]", i);
dump.PrintPointList( m_dim, m_is_rat,
m_cv_count[1], m_cv_stride[1],
CV(i,0),
sPreamble );
}
}
}
bool ON_NurbsSurface::IsValid( ON_TextLog* text_log ) const
{
bool rc = false;
if ( m_dim <= 0 )
{
if ( text_log )
{
text_log->Print("ON_NurbsSurface.m_dim = %d (should be > 0).\n",m_dim);
}
}
else if ( m_cv == nullptr )
{
if ( text_log )
{
text_log->Print("ON_NurbsSurface.m_cv is nullptr.\n",m_dim);
}
}
else
{
rc = true;
for ( int i = 0; i < 2 && rc; i++ )
{
rc = false;
if (m_order[i] < 2 )
{
if ( text_log )
{
text_log->Print("ON_NurbsSurface.m_order[i] = %d (should be >= 2).\n",i,m_order[i]);
}
}
else if (m_cv_count[i] < m_order[i] )
{
if ( text_log )
{
text_log->Print("ON_NurbsSurface.m_cv_count[%d] = %d (should be >= m_order[%d]=%d).\n",i,m_cv_count[i],i,m_order[i]);
}
}
else if (m_knot[i] == nullptr)
{
if ( text_log )
{
text_log->Print("ON_NurbsSurface.m_knot[i] is nullptr.\n");
}
}
else if ( !ON_IsValidKnotVector( m_order[i], m_cv_count[i], m_knot[i], text_log ) )
{
if ( text_log )
{
text_log->Print("ON_NurbsSurface.m_knot[%d] is not a valid knot vector.\n",i);
}
}
else if ( m_cv_stride[i] < CVSize() )
{
if ( text_log )
{
text_log->Print("ON_NurbsSurface.m_cv_stride[%d]=%d is too small (should be >= %d).\n",i,m_cv_stride[i],CVSize());
}
}
else
rc = true;
}
if ( rc )
{
int a0 = CVSize();
int a1 = m_cv_count[0]*a0;
int b1 = CVSize();
int b0 = m_cv_count[1]*b1;
if ( m_cv_stride[0] < a0 || m_cv_stride[1] < a1 )
{
if ( m_cv_stride[0] < b0 || m_cv_stride[1] < b1 )
{
if ( text_log )
{
text_log->Print("ON_NurbsSurface.m_cv_stride[] = {%d,%d} is not valid.\n",m_cv_stride[0],m_cv_stride[1]);
}
rc = false;
}
}
}
if (rc)
{
const int cvdim = CVSize();
for (int i = 0; m_cv_count[0] > i; ++i)
{
for (int j = 0; m_cv_count[1] > j; ++j)
{
const double* cv = CV(i, j);
for (int k = 0; cvdim > k; ++k)
{
if (false == ON_CV_COORDINATE_IS_VALID(cv[k]))
return false;
}
}
}
}
}
return rc;
}
bool ON_NurbsSurface::GetBBox( // returns true if successful
double* boxmin, // minimum
double* boxmax, // maximum
bool bGrowBox // true means grow box
) const
{
return ON_GetPointGridBoundingBox( m_dim, m_is_rat,
m_cv_count[0], m_cv_count[1],
m_cv_stride[0], m_cv_stride[1],
m_cv,
boxmin, boxmax, bGrowBox?true:false );
}
bool ON_NurbsSurface::Transform( const ON_Xform& xform )
{
DestroySurfaceTree();
TransformUserData(xform);
if ( 0 == m_is_rat )
{
if ( xform.m_xform[3][0] != 0.0 || xform.m_xform[3][1] != 0.0 || xform.m_xform[3][2] != 0.0 )
{
MakeRational();
}
}
return ON_TransformPointGrid( m_dim, m_is_rat?true:false, m_cv_count[0], m_cv_count[1], m_cv_stride[0], m_cv_stride[1], m_cv, xform );
}
bool ON_NurbsSurface::IsDeformable() const
{
return true;
}
bool ON_NurbsSurface::MakeDeformable()
{
return true;
}
bool ON_NurbsSurface::Write(
ON_BinaryArchive& file // open binary file
) const
{
// NOTE - check legacy I/O code if changed
bool rc = file.Write3dmChunkVersion(1,0);
if (rc) {
if (rc) rc = file.WriteInt( m_dim );
if (rc) rc = file.WriteInt( m_is_rat );
if (rc) rc = file.WriteInt( m_order[0] );
if (rc) rc = file.WriteInt( m_order[1] );
if (rc) rc = file.WriteInt( m_cv_count[0] );
if (rc) rc = file.WriteInt( m_cv_count[1] );
if (rc) rc = file.WriteInt(0); // reserved1
if (rc) rc = file.WriteInt(0); // reserved2
if (rc) {
ON_BoundingBox bbox; // write invalid bounding box - may be used in future
rc = file.WriteBoundingBox(bbox);
}
int count = m_knot[0] ? KnotCount(0) : 0;
if (rc) rc = file.WriteInt(count);
if (rc) rc = file.WriteDouble( count, m_knot[0] );
count = m_knot[1] ? KnotCount(1) : 0;
if (rc) rc = file.WriteInt(count);
if (rc) rc = file.WriteDouble( count, m_knot[1] );
const int cv_size = CVSize();
count = ( m_cv && cv_size > 0
&& m_cv_count[0] > 0 && m_cv_count[1] > 0
&& m_cv_stride[0] >= cv_size && m_cv_stride[1] >= cv_size)
? m_cv_count[0]*m_cv_count[1]
: 0;
if (rc) rc = file.WriteInt(count);
if (rc && count > 0 ) {
int i, j;
for ( i = 0; i < m_cv_count[0] && rc; i++ ) {
for ( j = 0; j < m_cv_count[1] && rc; j++ ) {
rc = file.WriteDouble( cv_size, CV(i,j) );
}
}
}
}
return rc;
}
bool ON_NurbsSurface::Read(
ON_BinaryArchive& file // open binary file
)
{
DestroySurfaceTree();
// NOTE - check legacy I/O code if changed
int major_version = 0;
int minor_version = 0;
while (file.Read3dmChunkVersion(&major_version, &minor_version))
{
if (1 != major_version)
break;
// common to all 1.x versions
int dim = 0;
if (false == file.ReadInt(&dim))
break;
if (dim < 1)
break;
int is_rat = 0;
if (false == file.ReadInt(&is_rat))
break;
int order0 = 0;
if (false == file.ReadInt( &order0 ))
break;
if (order0 < 2)
break;
int order1 = 0;
if (false == file.ReadInt( &order1 ))
break;
if (order1 < 2)
break;
int cv_count0 = 0;
if (false == file.ReadInt( &cv_count0 ))
break;
if (cv_count0 < order0)
break;
int cv_count1 = 0;
if (false == file.ReadInt( &cv_count1 ))
break;
if (cv_count1 < order1)
break;
// These are sanity checks for overflow.
// Safe fix for RH-83540
// They prevent attempt to allocate large numbers of knots when
// one of the orders or cv_counts red above is from a corrupt file.
const int expected_knot_count0 = ON_KnotCount(order0, cv_count0);
if (expected_knot_count0 < 2)
break;
const int expected_knot_count1 = ON_KnotCount(order1, cv_count1);
if (expected_knot_count1 < 2)
break;
const int expected_cv_count = cv_count0 * cv_count1;
if (expected_cv_count < 4)
break;
const int expected_cv_capacity = (is_rat != 0 ? (dim + 1) : dim) * expected_cv_count;
if (expected_cv_capacity < 4)
break;
int reserved1 = 0;
if (false == file.ReadInt(&reserved1))
break;
int reserved2 = 0;
if (false == file.ReadInt(&reserved2))
break;
ON_BoundingBox ignored_bbox; // read bounding box place holder - may be used in future
if (false == file.ReadBoundingBox(ignored_bbox))
break;
if (false == Create(dim, is_rat, order0, order1, cv_count0, cv_count1))
break;
const int cv_size = CVSize();
if (cv_size < dim)
break;
int knot_count0 = 0;
if (false == file.ReadInt(&knot_count0))
break;
if (knot_count0 != expected_knot_count0)
break;
if (false == ReserveKnotCapacity(0, knot_count0))
break;
if (false == file.ReadDouble(knot_count0, m_knot[0]))
break;
int knot_count1 = 0;
if (false == file.ReadInt(&knot_count1))
break;
if (knot_count1 != expected_knot_count1)
break;
if (false == ReserveKnotCapacity(1, knot_count1))
break;
if (false == file.ReadDouble(knot_count1, m_knot[1]))
break;
int cv_count = 0;
if (false == file.ReadInt(&cv_count))
break;
if (cv_count != expected_cv_count)
break;
if (false == ReserveCVCapacity(cv_count * cv_size))
break;
int i = 0;
for ( i = 0; i < m_cv_count[0]; i++ )
{
int j = 0;
for ( j = 0; j < m_cv_count[1]; j++ )
{
if (false == file.ReadDouble(cv_size, CV(i, j)))
break;
}
if (j != m_cv_count[1])
break;
}
if (i != m_cv_count[0])
break;
// successful read
return true;
}
// Corrupt file
Destroy();
return false;
}
ON_Interval ON_NurbsSurface::Domain( int dir ) const
{
ON_Interval d;
if (dir) dir = 1;
ON_GetKnotVectorDomain( m_order[dir], m_cv_count[dir], m_knot[dir], &d.m_t[0], &d.m_t[1] );
return d;
}
double ON_NurbsSurface::ControlPolygonLength( int dir ) const
{
double max_length = 0.0;
if ( dir >= 0 && dir <= 1 && m_cv_count[0] >= 2 && m_cv_count[1] >= 2 && m_cv != nullptr )
{
double length;
const double* p;
int i;
for( i = 0; i < m_cv_count[1-dir]; i++ )
{
length = 0.0;
p = (dir) ? CV(i,0) : CV(0,i);
ON_GetPolylineLength( m_dim, m_is_rat, m_cv_count[dir], m_cv_stride[dir], p, &length );
if ( length > max_length )
max_length = length;
}
}
return max_length;
}
bool ON_NurbsSurface::GetSurfaceSize(
double* width,
double* height
) const
{
// TODO - get lengths of polygon
bool rc = true;
if ( width )
{
*width = ControlPolygonLength( 0 );
}
if ( height )
{
*height = ControlPolygonLength( 1 );
}
return rc;
}
int ON_NurbsSurface::SpanCount( int dir ) const
{
if (dir) dir = 1;
return ON_KnotVectorSpanCount( m_order[dir], m_cv_count[dir], m_knot[dir] );
}
bool ON_NurbsSurface::GetSpanVector( int dir, double* s ) const
{
if (dir) dir = 1;
return ON_GetKnotVectorSpanVector( m_order[dir], m_cv_count[dir], m_knot[dir], s );
}
bool ON_NurbsSurface::GetParameterTolerance( // returns tminus < tplus: parameters tminus <= s <= tplus
int dir,
double t, // t = parameter in domain
double* tminus, // tminus
double* tplus // tplus
) const
{
bool rc = false;
ON_Interval d = Domain(dir);
double t0 = d.Min();
double t1 = d.Max();
if ( t0 <= t1 ) {
const double* knot = Knot(dir);
const int order = Order(dir);
const int cv_count = CVCount(dir);
if ( t < knot[order-1] )
t1 = knot[order-1];
else if ( t > knot[cv_count-2] )
t0 = knot[cv_count-2];
rc = ON_GetParameterTolerance( t0, t1, t, tminus, tplus );
}
return rc;
}
bool
ON_NurbsSurface::Evaluate( // returns false if unable to evaluate
double s, double t, // evaluation parameter
int der_count, // number of derivatives (>=0)
int v_stride, // v[] array stride (>=Dimension())
double* v, // v[] array of length stride*(ndir+1)
int side, // optional - determines which side to evaluate from
// 0 = default
// 1 = from NE quadrant
// 2 = from NW quadrant
// 3 = from SW quadrant
// 4 = from SE quadrant
int hint[2] // optional - evaluation hint (int) used to speed
// repeated evaluations
) const
{
bool rc = false;
int span_index[2];
span_index[0] = ON_NurbsSpanIndex(m_order[0],m_cv_count[0],m_knot[0],s,(side==2||side==3)?-1:1,(hint)?hint[0]:0);
span_index[1] = ON_NurbsSpanIndex(m_order[1],m_cv_count[1],m_knot[1],t,(side==3||side==4)?-1:1,(hint)?hint[1]:0);
rc = ON_EvaluateNurbsSurfaceSpan(
m_dim, m_is_rat,
m_order[0], m_order[1],
m_knot[0] + span_index[0],
m_knot[1] + span_index[1],
m_cv_stride[0], m_cv_stride[1],
m_cv + (span_index[0]*m_cv_stride[0] + span_index[1]*m_cv_stride[1]),
der_count,
s, t,
v_stride, v
);
if ( hint ) {
hint[0] = span_index[0];
hint[1] = span_index[1];
}
return rc;
}
ON_Curve* ON_NurbsSurface::IsoCurve(
int dir, // 0 first parameter varies and second parameter is constant
// e.g., point on IsoCurve(0,c) at t is srf(t,c)
// 1 first parameter is constant and second parameter varies
// e.g., point on IsoCurve(1,c) at t is srf(c,t)
double c // value of constant parameter
) const
{
ON_Curve* crv = 0;
int i,j,k,Scvsize,span_index;
double* Ncv;
const double* Scv;
if ( (dir == 0 || dir == 1) && IsValid() )
{
Scvsize = CVSize();
ON_NurbsCurve* nurbscrv = new ON_NurbsCurve( m_dim, m_is_rat, m_order[dir], m_cv_count[dir] );
memcpy( nurbscrv->m_knot, m_knot[dir], nurbscrv->KnotCount()*sizeof(*nurbscrv->m_knot) );
span_index = ON_NurbsSpanIndex(m_order[1-dir],m_cv_count[1-dir],m_knot[1-dir],c,1,0);
if ( span_index < 0 )
span_index = 0;
else if ( span_index > m_cv_count[1-dir]-m_order[1-dir] )
span_index = m_cv_count[1-dir]-m_order[1-dir];
ON_NurbsCurve N( Scvsize*nurbscrv->CVCount(), 0, m_order[1-dir], m_order[1-dir] );
memcpy( N.m_knot, m_knot[1-dir]+span_index, N.KnotCount()*sizeof(*N.m_knot) );
for ( i = 0; i < N.m_cv_count; i++ ) {
Ncv = N.CV(i);
for ( j = 0; j < m_cv_count[dir]; j++ ) {
Scv = (dir) ? CV(i+span_index,j) : CV(j,i+span_index);
for ( k = 0; k < Scvsize; k++ )
*Ncv++ = *Scv++;
}
}
N.Evaluate( c, 0, N.Dimension(), nurbscrv->m_cv );
crv = nurbscrv;
}
return crv;
}
// Converts a surface to a high degree NURBS curve.
// Use FromCurve to convert back to a surface.
static ON_NurbsCurve* ToCurve( const ON_NurbsSurface& srf, int dir,
ON_NurbsCurve* crv )
{
double* tmp_cv = nullptr;
if ( dir < 0 || dir > 1 )
return nullptr;
if ( !srf.m_cv )
return nullptr;
if ( !crv )
crv = new ON_NurbsCurve();
int srf_cv_size = srf.CVSize();
if ( !crv->Create(
srf_cv_size*srf.m_cv_count[1-dir], // dim
false, // is_rat
srf.m_order[dir],
srf.m_cv_count[dir]
) )
return nullptr;
if ( crv->m_cv == srf.m_cv )
{
tmp_cv = (double*)onmalloc( crv->m_dim*crv->m_cv_stride*sizeof(tmp_cv[0]) );
crv->m_cv = tmp_cv;
}
double* pdst;
const double* psrc;
int i, j;
size_t sz = srf_cv_size*sizeof(pdst[0]);
for ( i = 0; i < srf.m_cv_count[dir]; i++ )
{
pdst = crv->CV(i);
psrc = dir ? srf.CV(0,i) : srf.CV(i,0);
for ( j = 0; j < srf.m_cv_count[1-dir]; j++ )
{
memcpy( pdst, psrc, sz );
psrc += srf.m_cv_stride[1-dir];
pdst += srf_cv_size;
}
}
if ( tmp_cv )
{
crv->m_cv = srf.m_cv;
memcpy( crv->m_cv, tmp_cv, crv->m_dim*crv->m_cv_stride*sizeof(tmp_cv[0]) );
onfree(tmp_cv);
}
if ( crv->m_knot != srf.m_knot[dir] )
memcpy( crv->m_knot, srf.m_knot[dir], crv->KnotCount()*sizeof(crv->m_knot[0]) );
return crv;
}
// Converts the curve created in ToCurve back into a surface.
// The "srf" parameter must be the surface passed to ToCurve().
static bool FromCurve( ON_NurbsCurve& crv,
ON_NurbsSurface& srf,
int dir )
{
srf.DestroySurfaceTree();
crv.DestroyCurveTree();
if ( dir < 0 || dir > 1 )
return false;
if ( !crv.m_cv )
return false;
if ( crv.m_is_rat )
return false;
int srf_cv_size = srf.CVSize();
if ( srf.m_cv_count[1-dir]*srf_cv_size != crv.m_dim )
return false;
if ( srf.m_cv_capacity > 0 && srf.m_cv && srf.m_cv != crv.m_cv )
{
onfree( srf.m_cv );
}
srf.m_cv_capacity = crv.CVCapacity();
srf.m_cv = crv.m_cv;
crv.m_cv_capacity = 0;
crv.m_cv = 0;
if ( srf.m_knot_capacity[dir] > 0 && srf.m_knot[dir] && srf.m_knot[dir] != crv.m_knot )
{
onfree(srf.m_knot[dir]);
}
srf.m_order[dir] = crv.m_order;
srf.m_cv_count[dir] = crv.m_cv_count;
// transfer knot vector from crv to srf.m_knot[dir]
crv.UnmanageKnotForExperts(
srf.m_knot_capacity[dir],
srf.m_knot[dir]
);
srf.m_cv_stride[dir] = crv.m_cv_stride;
srf.m_cv_stride[1-dir] = srf_cv_size;
return true;
}
bool ON_NurbsSurface::Trim(
int dir,
const ON_Interval& domain
)
{
bool rc = false;
if ( dir < 0 || dir > 1 )
return false;
ON_Interval current_domain = Domain(dir);
if ( current_domain[0] == ON_UNSET_VALUE && current_domain[1] == ON_UNSET_VALUE )
current_domain = domain;
ON_Interval trim_domain;
trim_domain.Intersection(domain, Domain(dir) );
if ( !trim_domain.IsIncreasing() )
return false;
if ( trim_domain[0] == current_domain[0]
&& trim_domain[1] == current_domain[1] )
return true;
DestroySurfaceTree();
ON_NurbsCurve crv;
if ( ToCurve(*this,dir,&crv) )
{
rc = crv.Trim( trim_domain );
if ( rc )
rc = FromCurve( crv, *this, dir );
}
return true;
}
bool ON_NurbsSurface::Extend(
int dir,
const ON_Interval& domain
)
{
bool rc = false;
if ( dir < 0 || dir > 1 )
return false;
if (IsClosed(dir)) return false;
ON_NurbsCurve crv;
if ( ToCurve(*this,dir,&crv) )
{
rc = crv.Extend( domain );
FromCurve( crv, *this, dir );
}
if (rc){
DestroySurfaceTree();
}
return rc;
}
bool ON_NurbsSurface::Split(
int dir,
double c,
ON_Surface*& west_or_south_side,
ON_Surface*& east_or_north_side
) const
{
if ( dir < 0 || dir > 1 )
return false;
if ( !Domain(dir).Includes( c, true ) )
return false;
ON_NurbsSurface* left_srf = 0;
ON_NurbsSurface* right_srf = 0;
if ( west_or_south_side )
{
left_srf = ON_NurbsSurface::Cast( west_or_south_side );
if ( !left_srf )
return false;
left_srf->DestroySurfaceTree();
}
if ( east_or_north_side )
{
right_srf = ON_NurbsSurface::Cast( east_or_north_side );
if ( !right_srf )
return false;
right_srf->DestroySurfaceTree();
}
ON_NurbsCurve crv, left_crv, right_crv;
if ( !ToCurve( *this, dir, &crv ) )
return false;
ON_Curve *left_side, *right_side;
left_side = &left_crv;
right_side = &right_crv;
if ( !crv.Split( c, left_side, right_side ) )
return false;
if ( !left_srf )
left_srf = new ON_NurbsSurface();
if ( left_srf != this )
{
left_srf->m_dim = m_dim;
left_srf->m_is_rat = m_is_rat;
left_srf->m_order[1-dir] = m_order[1-dir];
left_srf->m_cv_count[1-dir] = m_cv_count[1-dir];
left_srf->ReserveKnotCapacity(1-dir,KnotCount(1-dir));
memcpy( left_srf->m_knot[1-dir], m_knot[1-dir], KnotCount(1-dir)*sizeof(m_knot[1-dir][0] ) );
}
if ( !FromCurve( left_crv, *left_srf, dir ) )
{
if ( left_srf != this && left_srf != west_or_south_side )
delete left_srf;
else
left_srf->Destroy();
return false;
}
if ( !right_srf )
right_srf = new ON_NurbsSurface();
if ( right_srf != this )
{
right_srf->m_dim = m_dim;
right_srf->m_is_rat = m_is_rat;
right_srf->m_order[1-dir] = m_order[1-dir];
right_srf->m_cv_count[1-dir] = m_cv_count[1-dir];
right_srf->ReserveKnotCapacity(1-dir,KnotCount(1-dir));
memcpy( right_srf->m_knot[1-dir], m_knot[1-dir], KnotCount(1-dir)*sizeof(m_knot[1-dir][0] ) );
}
if ( !FromCurve( right_crv, *right_srf, dir ) )
{
if ( left_srf != this && left_srf != west_or_south_side )
delete left_srf;
else
left_srf->Destroy();
if ( right_srf != this && right_srf != east_or_north_side )
delete right_srf;
else
right_srf->Destroy();
return false;
}
if ( !west_or_south_side)
west_or_south_side = left_srf;
if ( !east_or_north_side)
east_or_north_side = right_srf;
return true;
}
// virutal ON_Surface::HasNurbForm() override
int ON_NurbsSurface::HasNurbForm() const
{
return 1;
}
// virtual ON_Surface::GetNurbForm() override
int ON_NurbsSurface::GetNurbForm(
ON_NurbsSurface& srf,
double // tolerance
) const
{
// 4 May 2007 Dale Lear
// I'm replacing the call to operator= with a call to
// ON_NurbsSurfaceCopyHelper(). The operator= call
// was copying userdata and that does not happen for
// any other GetNurbForm overrides. Copying userdata
// in GetNurbForm is causing trouble in Make2D and
// other places that are creating NURBS copies in
// worker memory pools.
//srf = *this; // copied user data
ON_NurbsSurfaceCopyHelper(*this,srf); // does not copy user data
return 1;
}
ON_Surface* ON_NurbsSurface::Offset(
double offset_distance,
double tolerance,
double* max_deviation
) const
{
// 3rd party developers who want to enhance openNURBS
// may provide a working offset here.
return nullptr;
}
bool ON_NurbsSurface::IsPlanar(
ON_Plane* plane,
double tolerance
) const
{
ON_Plane pln;
double d;
ON_3dPoint center, cv;
ON_3dVector normal, du, dv;
ON_Interval udom = Domain(0);
ON_Interval vdom = Domain(1);
bool rc = EvNormal( udom.ParameterAt(0.5), vdom.ParameterAt(0.5), center, du, dv, normal );
if ( rc && normal.Length() < 0.9 )
rc = false;
else
{
pln.origin = center;
pln.zaxis = normal;
if ( du.Unitize() )
{
pln.xaxis = du;
pln.yaxis = ON_CrossProduct( pln.zaxis, pln.xaxis );
pln.yaxis.Unitize();
pln.UpdateEquation();
}
else if ( dv.Unitize() )
{
pln.yaxis = dv;
pln.xaxis = ON_CrossProduct( pln.yaxis, pln.zaxis );
pln.xaxis.Unitize();
pln.UpdateEquation();
}
else
{
pln.CreateFromNormal( center, normal );
}
// 7 July 2006 Dale Lear
// Add a slight bias for coordinate axes planes.
// This appears to do more good than harm. The
// issue is keeping customers from getting alarmed
// when a coordinate they know is "zero" turns out to
// be 1e-23.
if ( fabs(pln.zaxis.x) <= ON_ZERO_TOLERANCE
&& fabs(pln.zaxis.y) <= ON_ZERO_TOLERANCE
&& fabs(fabs(pln.zaxis.z)-1.0) <= ON_SQRT_EPSILON
)
{
pln.xaxis.z = 0.0;
pln.yaxis.z = 0.0;
pln.zaxis.x = 0.0;
pln.zaxis.y = 0.0;
pln.zaxis.z = (pln.zaxis.z<0.0) ? -1.0 : 1.0;
pln.UpdateEquation();
}
else if ( fabs(pln.zaxis.y) <= ON_ZERO_TOLERANCE
&& fabs(pln.zaxis.z) <= ON_ZERO_TOLERANCE
&& fabs(fabs(pln.zaxis.x)-1.0) <= ON_SQRT_EPSILON
)
{
pln.xaxis.x = 0.0;
pln.yaxis.x = 0.0;
pln.zaxis.y = 0.0;
pln.zaxis.z = 0.0;
pln.zaxis.x = (pln.zaxis.x<0.0) ? -1.0 : 1.0;
pln.UpdateEquation();
}
else if ( fabs(pln.zaxis.z) <= ON_ZERO_TOLERANCE
&& fabs(pln.zaxis.x) <= ON_ZERO_TOLERANCE
&& fabs(fabs(pln.zaxis.y)-1.0) <= ON_SQRT_EPSILON
)
{
pln.xaxis.y = 0.0;
pln.yaxis.y = 0.0;
pln.zaxis.z = 0.0;
pln.zaxis.x = 0.0;
pln.zaxis.y = (pln.zaxis.y<0.0) ? -1.0 : 1.0;
pln.UpdateEquation();
}
int i, j;
for ( i = 0; i < m_cv_count[0] && rc; i++ )
{
for ( j = 0; j < m_cv_count[1] && rc; j++ )
{
GetCV( i, j, cv );
d = pln.DistanceTo(cv);
if ( fabs(d) > tolerance )
rc = false;
}
}
if ( rc && plane )
*plane = pln;
}
return rc;
}
bool
ON_NurbsSurface::IsClosed( int dir ) const
{
bool bIsClosed = false;
if ( dir >= 0 && dir <= 1 && m_dim > 0 )
{
if ( ON_IsKnotVectorClamped( m_order[dir], m_cv_count[dir], m_knot[dir] ) )
{
const double* corners[4];
corners[0] = CV(0,0);
corners[(0==dir)?1:2] = CV(m_cv_count[0]-1,0);
corners[(0==dir)?2:1] = CV(0,m_cv_count[1]-1);
corners[3] = CV(m_cv_count[0]-1,m_cv_count[1]-1);
if ( ON_PointsAreCoincident(m_dim,m_is_rat,corners[0],corners[1])
&& ON_PointsAreCoincident(m_dim,m_is_rat,corners[2],corners[3])
&& ON_IsPointGridClosed( m_dim, m_is_rat, m_cv_count[0], m_cv_count[1], m_cv_stride[0], m_cv_stride[1], m_cv, dir )
)
{
bIsClosed = true;
}
}
else if ( IsPeriodic(dir) )
{
bIsClosed = true;
}
}
return bIsClosed;
}
bool
ON_NurbsSurface::ChangeSurfaceSeam(
int dir,
double t
)
{
bool rc = true;
if ( dir < 0 || dir > 1 )
return false;
ON_Interval current_domain = Domain(dir);
if( !current_domain.Includes( t) )
rc = false;
if(rc && IsClosed(dir) ){
DestroySurfaceTree();
ON_NurbsCurve crv;
rc = ToCurve(*this, dir, &crv)!=nullptr;
if(rc)
rc = crv.ChangeClosedCurveSeam(t)!=0;
rc = FromCurve(crv,*this, dir) && rc;
}
return rc;
}
bool
ON_NurbsSurface::IsPeriodic( int dir ) const
{
bool bIsPeriodic = false;
if ( dir >= 0 && dir <= 1 )
{
int k;
bIsPeriodic = ON_IsKnotVectorPeriodic( m_order[dir], m_cv_count[dir], m_knot[dir] );
if ( bIsPeriodic )
{
const double *cv0, *cv1;
int i0 = m_order[dir]-2;
int i1 = m_cv_count[dir]-1;
for ( k = 0; k < m_cv_count[1-dir]; k++ )
{
cv0 = (dir)?CV(k,i0):CV(i0,k);
cv1 = (dir)?CV(k,i1):CV(i1,k);
for ( /*empty*/; i0 >= 0; i0--, i1-- )
{
if ( false == ON_PointsAreCoincident( m_dim, m_is_rat, cv0, cv1 ) )
return false;
cv0 -= m_cv_stride[dir];
cv1 -= m_cv_stride[dir];
}
}
}
}
return bIsPeriodic;
}
bool ON_NurbsSurface::GetNextDiscontinuity(
int dir,
ON::continuity c,
double t0,
double t1,
double* t,
int* hint,
int* dtype,
double cos_angle_tolerance,
double curvature_tolerance
) const
{
int tmp_hint[2];
int tmp_dtype=0;
double d, tmp_t;
ON_2dVector st;
ON_Interval span_domain;
ON_3dVector Vm[6], Vp[6];
ON_3dVector Tm, Tp, Km(ON_3dVector::NanVector), Kp(ON_3dVector::NanVector);
ON_3dVector& D1m = Vm[1+dir];
ON_3dVector& D2m = Vm[3+2*dir];
ON_3dVector& D1p = Vp[1+dir];
ON_3dVector& D2p = Vp[3+2*dir];
int ki;
if ( !hint )
{
tmp_hint[0] = 0;
tmp_hint[1] = 0;
hint = &tmp_hint[0];
}
if ( !dtype )
dtype = &tmp_dtype;
if ( !t )
t = &tmp_t;
if ( c == ON::continuity::C0_continuous )
return false;
if ( c == ON::continuity::C0_locus_continuous )
{
return ON_Surface::GetNextDiscontinuity(
dir, c, t0, t1, t, hint, dtype,
cos_angle_tolerance, curvature_tolerance );
}
if ( t0 == t1 )
return false;
// First test for parametric discontinuities. If none are found
// then we will look for locus discontinuities at ends
if ( m_order[dir] <= 2 )
c = ON::PolylineContinuity((int)c); // no need to look a zero 2nd derivatives
const ON::continuity input_c = c; // saved so we can tell if "locus" needs to be dealt with
c = ON::ParametricContinuity((int)c); // strips "locus" from c
bool bEv2ndDer = ( c == ON::continuity::C2_continuous || c == ON::continuity::G2_continuous || c == ON::continuity::Gsmooth_continuous );
bool bTestKappa = ( bEv2ndDer && c != ON::continuity::C2_continuous );
bool bTestTangent = ( bTestKappa || c == ON::continuity::G1_continuous );
int delta_ki = 1;
int delta = ((bEv2ndDer) ? 3 : 2) - m_order[dir];
if ( ON::continuity::Cinfinity_continuous == c )
delta = 0;
ki = ON_NurbsSpanIndex(m_order[dir],m_cv_count[dir],m_knot[dir],t0,1,*hint);
double segtol = (fabs(m_knot[dir][ki]) + fabs(m_knot[dir][ki+1]) + fabs(m_knot[dir][ki+1]-m_knot[dir][ki]))*ON_SQRT_EPSILON;
if ( t0 < t1 )
{
int ii = ki+m_order[dir]-2;
if ( t0 < m_knot[dir][ii+1] && t1 > m_knot[dir][ii+1] && (m_knot[dir][ii+1]-t0) <= segtol && ii+2 < m_cv_count[dir] )
{
t0 = m_knot[dir][ii+1];
ki = ON_NurbsSpanIndex(m_order[dir],m_cv_count[dir],m_knot[dir],t0,1,*hint);
}
*hint = ki;
ki += m_order[dir]-2;
while (ki < m_cv_count[dir]-1 && m_knot[dir][ki] <= t0)
ki += delta_ki;
if (ki >= m_cv_count[dir]-1)
{
if ( input_c != c && t0 < m_knot[dir][m_cv_count[dir]-1] && t1 >= m_knot[dir][m_cv_count[dir]-1] )
{
// have to do locus end test
return ON_Surface::GetNextDiscontinuity( dir, input_c, t0, t1, t, hint, dtype,
cos_angle_tolerance, curvature_tolerance );
}
return false;
}
}
else
{
// (t0 > t1) work backwards
int ii = ki+m_order[dir]-2;
if ( t0 > m_knot[dir][ii] && t1 < m_knot[dir][ii] && (t0-m_knot[dir][ii]) <= segtol && ii > m_order[dir]-2 )
{
t0 = m_knot[dir][ii];
ki = ON_NurbsSpanIndex(m_order[dir],m_cv_count[dir],m_knot[dir],t0,1,*hint);
}
*hint = ki;
ki += m_order[dir]-2;
while (ki < m_order[dir]-2 && m_knot[dir][ki] >= t0)
ki--;
if (ki <= m_order[dir]-2)
{
if ( input_c != c && t0 > m_knot[dir][m_order[dir]-2] && t1 < m_knot[dir][m_order[dir]-2] )
{
// have to do locus end test
return ON_Surface::GetNextDiscontinuity( dir,input_c, t0, t1, t, hint, dtype,
cos_angle_tolerance, curvature_tolerance );
}
return false;
}
delta_ki = -1;
delta = -delta;
}
while (m_knot[dir][ki] < t1)
{
if ( delta_ki > 0 )
{
// t0 < t1 case
while (ki < m_cv_count[dir]-1 && m_knot[dir][ki] == m_knot[dir][ki+1])
ki++;
if (ki >= m_cv_count[dir]-1)
break;
}
else
{
// t0 > t1 case
// 20 March 2003 Dale Lear:
// Added to make t0 > t1 case work
while (ki > m_order[dir]-2 && m_knot[dir][ki] == m_knot[dir][ki-1])
ki--;
if (ki <= m_order[dir]-2)
break;
}
if (m_knot[dir][ki] == m_knot[dir][ki+delta])
{
if ( ON::continuity::Cinfinity_continuous == c )
{
// Cinfinity_continuous is treated as asking for the next knot
*dtype = 3;
*t = m_knot[dir][ki];
return true;
}
st[dir] = m_knot[dir][ki];
int j, j0=0, otherki;
for ( otherki = m_order[1-dir]-2; otherki < m_cv_count[1-dir]-1; otherki++ )
{
span_domain.Set(m_knot[1-dir][otherki],m_knot[1-dir][otherki+1]);
for ( j = j0; j <= 2; j++ )
{
st[1-dir] = span_domain.ParameterAt(0.5*j);
Evaluate( st.x, st.y, bEv2ndDer?2:1, 3, &Vm[0].x, 3, hint);
Evaluate( st.x, st.y, bEv2ndDer?2:1, 3, &Vp[0].x, 1, hint);
if ( bTestTangent )
{
if ( bTestKappa )
{
ON_EvCurvature( D1m, D2m, Tm, Km );
ON_EvCurvature( D1p, D2p, Tp, Kp );
}
else
{
Tm = D1m;
Tp = D1p;
Tm.Unitize();
Tp.Unitize();
}
d = Tm*Tp;
if ( d < cos_angle_tolerance )
{
*dtype = 1;
*t = m_knot[dir][ki];
return true;
}
else if ( bTestKappa )
{
bool bIsCurvatureContinuous = ( ON::continuity::Gsmooth_continuous == c)
? ON_IsGsmoothCurvatureContinuous(Km,Kp,cos_angle_tolerance,curvature_tolerance)
: ON_IsG2CurvatureContinuous(Km,Kp,cos_angle_tolerance,curvature_tolerance);
if ( !bIsCurvatureContinuous )
{
*dtype = 2;
*t = m_knot[dir][ki];
return true;
}
}
}
else
{
if ( !(D1m-D1p).IsTiny(D1m.MaximumCoordinate()*ON_SQRT_EPSILON) )
{
*dtype = 1;
*t = m_knot[dir][ki];
return true;
}
else if ( bEv2ndDer )
{
if ( !(D2m-D2p).IsTiny(D2m.MaximumCoordinate()*ON_SQRT_EPSILON) )
{
*dtype = 2;
*t = m_knot[dir][ki];
return true;
}
}
}
}
j0 = 1;
}
}
ki += delta_ki;
}
// 20 March 2003 Dale Lear:
// If we get here, there are not discontinuities strictly between
// t0 and t1.
bool rc = false;
if ( input_c != c )
{
// use base class for consistent start/end locus testing
rc = ON_Surface::GetNextDiscontinuity( dir, input_c, t0, t1, t, hint, dtype,
cos_angle_tolerance, curvature_tolerance );
}
return rc;
}
bool
ON_NurbsSurface::IsSingular( // true if surface side is collapsed to a point
int side // side of parameter space to test
// 0 = south, 1 = east, 2 = north, 3 = west
) const
{
bool rc = false;
const double* points = 0;
int point_count = 0;
int point_stride = 0;
switch ( side )
{
case 0: // south
rc = IsClamped(1,0)?true:false;
if ( rc )
{
points = CV(0,0);
point_count = m_cv_count[0];
point_stride = m_cv_stride[0];
}
break;
case 1: // east
rc = IsClamped(0,1)?true:false;
if (rc)
{
points = CV(m_cv_count[0]-1,0);
point_count = m_cv_count[1];
point_stride = m_cv_stride[1];
}
break;
case 2: // north
rc = IsClamped(1,1)?true:false;
if (rc)
{
points = CV(0,m_cv_count[1]-1);
point_count = m_cv_count[0];
point_stride = m_cv_stride[0];
}
break;
case 3: // west
rc = IsClamped( 0, 0 )?true:false;
if (rc)
{
points = CV(0,0);
point_count = m_cv_count[1];
point_stride = m_cv_stride[1];
}
break;
default:
rc = false;
break;
}
if (rc)
rc = ON_PointsAreCoincident(m_dim,m_is_rat,point_count,point_stride,points);
return rc;
}
bool
ON_NurbsSurface::SetWeight( int i, int j, double w )
{
DestroySurfaceTree();
bool rc = false;
if (0 == m_is_rat && w > 0.0 && w < ON_UNSET_POSITIVE_VALUE)
{
MakeRational();
}
if ( m_is_rat ) {
double* cv = CV(i,j);
if (cv) {
cv[m_dim] = w;
rc = true;
}
}
else if ( w == 1.0 ) {
rc = true;
}
return rc;
}
bool
ON_NurbsSurface::SetCV( int i, int j, ON::point_style style, const double* Point )
{
DestroySurfaceTree();
bool rc = true;
int k;
double w;
double* cv = CV(i,j);
if ( !cv )
return false;
switch ( style ) {
case ON::not_rational: // input Point is not rational
memcpy( cv, Point, m_dim*sizeof(*cv) );
if ( IsRational() ) {
// NURBS surface is rational - set weight to one
cv[m_dim] = 1.0;
}
break;
case ON::homogeneous_rational: // input Point is homogeneous rational
if ( IsRational() ) {
// NURBS surface is rational
memcpy( cv, Point, (m_dim+1)*sizeof(*cv) );
}
else {
// NURBS surface is not rational
w = (Point[m_dim] != 0.0) ? 1.0/Point[m_dim] : 1.0;
for ( k = 0; k < m_dim; k++ ) {
cv[k] = w*Point[k];
}
}
break;
case ON::euclidean_rational: // input Point is euclidean rational
if ( IsRational() ) {
// NURBS surface is rational - convert euclean point to homogeneous form
w = Point[m_dim];
for ( k = 0; k < m_dim; k++ )
cv[k] = w*Point[k]; // 22 April 2003 - bug fix [i] to [k]
cv[m_dim] = w;
}
else {
// NURBS surface is not rational
memcpy( cv, Point, m_dim*sizeof(*cv) );
}
break;
case ON::intrinsic_point_style: // input Point is euclidean rational
memcpy( cv, Point, CVSize()*sizeof(*cv) );
break;
default:
rc = false;
break;
}
return rc;
}
bool
ON_NurbsSurface::SetCV( int i, int j, const ON_3dPoint& point )
{
DestroySurfaceTree();
bool rc = false;
double* cv = CV(i,j);
if ( cv ) {
cv[0] = point.x;
if ( m_dim > 1 ) {
cv[1] = point.y;
if ( m_dim > 2 )
cv[2] = point.z;
}
if ( m_is_rat ) {
cv[m_dim] = 1.0;
}
rc = true;
}
return rc;
}
bool
ON_NurbsSurface::SetCV( int i, int j, const ON_4dPoint& point )
{
DestroySurfaceTree();
bool rc = false;
double* cv = CV(i,j);
if ( cv ) {
if ( m_is_rat ) {
cv[0] = point.x;
if ( m_dim > 1 ) {
cv[1] = point.y;
if ( m_dim > 2 )
cv[2] = point.z;
}
cv[m_dim] = point.w;
rc = true;
}
else {
double w;
if ( point.w != 0.0 ) {
w = 1.0/point.w;
rc = true;
}
else {
w = 1.0;
}
cv[0] = w*point.x;
if ( m_dim > 1 ) {
cv[1] = w*point.y;
if ( m_dim > 2 ) {
cv[2] = w*point.z;
}
}
}
}
return rc;
}
bool
ON_NurbsSurface::GetCV( int i, int j, ON::point_style style, double* Point ) const
{
const double* cv = CV(i,j);
if ( !cv )
return false;
int dim = Dimension();
double w = ( IsRational() ) ? cv[dim] : 1.0;
switch(style) {
case ON::euclidean_rational:
Point[dim] = w;
// no break here
case ON::not_rational:
if ( w == 0.0 )
return false;
w = 1.0/w;
while(dim--) *Point++ = *cv++ * w;
break;
case ON::homogeneous_rational:
Point[dim] = w;
memcpy( Point, cv, dim*sizeof(*Point) );
break;
default:
return false;
}
return true;
}
bool
ON_NurbsSurface::GetCV( int i, int j, ON_3dPoint& point ) const
{
bool rc = false;
const double* cv = CV(i,j);
if ( cv ) {
if ( m_is_rat ) {
if (cv[m_dim] != 0.0) {
const double w = 1.0/cv[m_dim];
point.x = cv[0]*w;
point.y = (m_dim>1)? cv[1]*w : 0.0;
point.z = (m_dim>2)? cv[2]*w : 0.0;
rc = true;
}
}
else {
point.x = cv[0];
point.y = (m_dim>1)? cv[1] : 0.0;
point.z = (m_dim>2)? cv[2] : 0.0;
rc = true;
}
}
return rc;
}
bool
ON_NurbsSurface::GetCV( int i, int j, ON_4dPoint& point ) const
{
bool rc = false;
if (m_dim > 0 && i >= 0 && j >= 0 && i < m_cv_count[0] && j < m_cv_count[1])
{
const double* cv = CV(i, j);
if (cv) {
point.x = cv[0];
point.y = (m_dim > 1) ? cv[1] : 0.0;
point.z = (m_dim > 2) ? cv[2] : 0.0;
point.w = (m_is_rat) ? cv[m_dim] : 1.0;
rc = true;
}
}
return rc;
}
bool ON_NurbsSurface::SetKnot( int dir, int knot_index, double k )
{
DestroySurfaceTree();
if ( dir ) dir = 1;
if ( knot_index < 0 || knot_index >= KnotCount(dir) )
return false;
m_knot[dir][knot_index] = k;
return true;
}
bool ON_NurbsSurface::IsContinuous(
ON::continuity desired_continuity,
double s,
double t,
int* hint, // default = nullptr,
double point_tolerance, // default=ON_ZERO_TOLERANCE
double d1_tolerance, // default==ON_ZERO_TOLERANCE
double d2_tolerance, // default==ON_ZERO_TOLERANCE
double cos_angle_tolerance, // default==ON_DEFAULT_ANGLE_TOLERANCE_COSINE
double curvature_tolerance // default==ON_SQRT_EPSILON
) const
{
// TODO: speed up by avoiding evaluation at non-multi knots
return ON_Surface::IsContinuous( desired_continuity, s, t, hint,
point_tolerance, d1_tolerance, d2_tolerance,
cos_angle_tolerance, curvature_tolerance );
}
bool
ON_NurbsSurface::Reverse(int dir)
{
if (dir < 0 || dir > 1) return false;
DestroySurfaceTree();
bool rc0 = ON_ReverseKnotVector( m_order[dir], m_cv_count[dir], m_knot[dir] );
bool rc1 = ON_ReversePointGrid( 3, m_is_rat, m_cv_count[0], m_cv_count[1], m_cv_stride[0], m_cv_stride[1], m_cv, dir );
return rc0 && rc1;
}
bool
ON_NurbsSurface::Transpose()
{
DestroySurfaceTree();
int i;
// transpose CV grid
i = m_order[0]; m_order[0] = m_order[1]; m_order[1] = i;
i = m_cv_count[0]; m_cv_count[0] = m_cv_count[1]; m_cv_count[1] = i;
i = m_cv_stride[0]; m_cv_stride[0] = m_cv_stride[1]; m_cv_stride[1] = i;
// swap knot vectors
i = m_knot_capacity[0]; m_knot_capacity[0] = m_knot_capacity[1]; m_knot_capacity[1] = i;
double* k = m_knot[0]; m_knot[0] = m_knot[1]; m_knot[1] = k;
return true;
}
bool
ON_NurbsSurface::SwapCoordinates( int i, int j )
{
DestroySurfaceTree();
bool rc = true;
int k;
if ( m_cv_count[0] <= m_cv_count[1] ) {
for ( k = 0; k < m_cv_count[0]; k++ ) {
if ( !ON_SwapPointListCoordinates( m_cv_count[1], m_cv_stride[1], CV(k,0), i, j ) )
rc = false;
}
}
else {
for ( k = 0; k < m_cv_count[1]; k++ ) {
if ( !ON_SwapPointListCoordinates( m_cv_count[0], m_cv_stride[0], CV(0,k), i, j ) )
rc = false;
}
}
return rc;
}
bool ON_NurbsSurface::SetCVRow(
int row_index,
const ON_3dPoint& point
)
{
DestroySurfaceTree();
int i;
if ( row_index < 0 || row_index > m_cv_count[1] )
return false;
for ( i = 0; i < m_cv_count[0]; i++ ) {
if ( !SetCV( i, row_index, point ) )
return false;
}
return true;
}
bool ON_NurbsSurface::SetCVRow(
int row_index,
int v_stride, // v stride
const double* v // values (same dim and is_rat as surface)
)
{
DestroySurfaceTree();
int i;
unsigned int s;
double* cv;
if ( row_index < 0 || row_index > m_cv_count[1] )
return false;
cv = CV(0,row_index);
if ( !cv )
return false;
if ( v_stride < CVSize() )
return false;
s = CVSize()*sizeof(*cv);
if ( s < m_dim*sizeof(*cv) )
return false;
for ( i = 0; i < m_cv_count[0]; i++ ) {
memcpy( cv, v, s );
cv += m_cv_stride[0];
v += v_stride;
}
return true;
}
bool ON_NurbsSurface::SetCVColumn(
int col_index,
const ON_3dPoint& point
)
{
DestroySurfaceTree();
int j;
if ( col_index < 0 || col_index > m_cv_count[0] )
return false;
for ( j = 0; j < m_cv_count[1]; j++ ) {
if ( !SetCV( col_index, j, point ) )
return false;
}
return true;
}
bool ON_NurbsSurface::SetCVColumn(
int col_index,
int v_stride, // v stride
const double* v // values (same dim and is_rat as surface)
)
{
DestroySurfaceTree();
int i;
unsigned int s;
double* cv;
if ( col_index < 0 || col_index > m_cv_count[0] )
return false;
cv = CV(col_index,0);
if ( !cv )
return false;
if ( v_stride < CVSize() )
return false;
s = CVSize()*sizeof(*cv);
if ( s < m_dim*sizeof(*cv) )
return false;
for ( i = 0; i < m_cv_count[1]; i++ ) {
memcpy( cv, v, s );
cv += m_cv_stride[1];
v += v_stride;
}
return true;
}
double ON_NurbsSurface::GrevilleAbcissa(
int dir, // dir
int gindex // index (0 <= index < CVCount(dir)
) const
{
if (dir)
dir = 1;
return ON_GrevilleAbcissa( m_order[dir], m_knot[dir] + gindex );
}
bool ON_NurbsSurface::GetGrevilleAbcissae(
int dir,
double* g
) const
{
if (dir)
dir = 1;
// The "false" for the 4th parameter is on purpose and should not be
// replaced with this->IsPeriodic(dir). The problem
// being that when the 4th parameter is true, it is not possible
// to determine which subset of the full list of Greville abcissae
// was returned.
return ON_GetGrevilleAbcissae( m_order[dir], m_cv_count[dir], m_knot[dir], false, g );
}
bool ON_NurbsSurface::SetClampedGrevilleKnotVector(
int dir, // dir
int g_stride, // g_stride
const double* g // g[], Greville abcissa
)
{
DestroySurfaceTree();
if ( !m_knot[dir] && m_order[dir] >= 2 && m_cv_count[dir] >= m_order[dir] )
ReserveKnotCapacity(dir,KnotCount(dir));
return ON_GetGrevilleKnotVector( g_stride, g, false, Order(dir), CVCount(dir), m_knot[dir] );
}
bool ON_NurbsSurface::SetPeriodicGrevilleKnotVector(
int dir, // dir
int g_stride, // g_stride
const double* g // g[], Greville abcissa
)
{
DestroySurfaceTree();
if ( !m_knot[dir] && m_order[dir] >= 2 && m_cv_count[dir] >= m_order[dir] )
ReserveKnotCapacity(dir,KnotCount(dir));
return ON_GetGrevilleKnotVector( g_stride, g, true, Order(dir), CVCount(dir), m_knot[dir] );
}
bool ON_NurbsSurface::ZeroCVs()
{
DestroySurfaceTree();
bool rc = false;
int i, j;
if ( m_cv ) {
if ( m_cv_capacity > 0 ) {
memset( m_cv, 0, m_cv_capacity*sizeof(*m_cv) );
if ( m_is_rat ) {
for ( i = 0; i < m_cv_count[0]; i++ ) for ( j = 0; j < m_cv_count[1]; j++ ) {
SetWeight( i, j, 1.0 );
}
}
rc = true;
}
else {
double* cv;
int s = CVSize()*sizeof(*cv);
j = 0;
for ( i = 0; i < m_cv_count[0]; i++ ) for ( j = 0; j < m_cv_count[1]; j++ ) {
cv = CV(i,j);
if ( !cv )
return false;
memset(cv,0,s);
if ( m_is_rat )
cv[m_dim] = 1.0;
}
rc = (i>0 && j>0) ? true : false;
}
}
return rc;
}
bool ON_NurbsSurface::IsClamped( // determine if knot vector is clamped
int dir, // dir 0 = "s", 1 = "t", 2 = both
int end // (default =2) end to check: 0 = start, 1 = end, 2 = start and end
) const
{
bool rc = false;
if ( dir == 0 || dir == 1)
rc = ON_IsKnotVectorClamped( m_order[dir], m_cv_count[dir], m_knot[dir], end );
return rc;
}
bool ON_NurbsSurface::IsNatural(int dir, int end) const
{
if (dir < 0 || dir > 1 || end < 0 || end > 2)
return false;
const ON_Interval domain = Domain(dir);
size_t parameter_count = 0;
double parameter_list[2] = {ON_DBL_QNAN,ON_DBL_QNAN};
if (0 == end || 2 == end)
parameter_list[parameter_count++] = domain[0];
if (1 == end || 2 == end)
parameter_list[parameter_count++] = domain[1];
return ON_NurbsSurface::IsNatural(dir, parameter_count, parameter_list);
}
bool ON_NurbsSurface::IsNatural(
int dir,
size_t parameter_count,
const double* parameter_list
) const
{
if (dir < 0 || dir > 1 || parameter_count < 1 || nullptr == parameter_list)
return false;
const int degree_dir = Degree(dir);
if (degree_dir < 1)
return false;
const ON_Interval domain[2] = { Domain(0), Domain(1) };
ON_SimpleArray<double> g(m_cv_count[1 - dir]);
const int g_count = m_cv_count[1 - dir];
g.SetCount(g_count);
if (false == ON_GetGrevilleAbcissae( // get Greville abcissae from knots
m_order[1 - dir],
m_cv_count[1 - dir],
m_knot[1 - dir],
false,
g.Array()
))
return false;
int gdex0 = 0;
int gdex1 = g_count;
if (0 == dir)
{
// 0 = south, 1 = east, 2 = north, 3 = west
if (IsSingular(0))
++gdex0; // skip south side singular check - 2nd der is tiny and noisy
if (IsSingular(2))
--gdex1; // skip north side singular check - 2nd der is tiny and noisy
}
else
{
// 0 = south, 1 = east, 2 = north, 3 = west
if (IsSingular(3))
++gdex0; // skip west side singular check - 2nd der is tiny and noisy
if (IsSingular(1))
--gdex1; // skip east side singular check - 2nd der is tiny and noisy
}
while (gdex0 < gdex1 && false == domain[1 - dir].Includes(g[gdex0]))
++gdex0;
while (gdex0 < gdex1 && false == domain[1 - dir].Includes(g[gdex1-1]))
--gdex1;
if (gdex0 >= gdex1)
return false;
const int knot_count_dir = KnotCount(dir);
int quadrant;
int cv0dex[2] = {};
int cv2dex[2] = {};
int hint[2] = { 0,0 };
double st[2] = { ON_DBL_QNAN, ON_DBL_QNAN };
ON_3dVector D1[2], D2[2], Duv;
ON_3dPoint CV0, CV2, P;
bool bIsNatural = false;
for (size_t tdex = 0; tdex < parameter_count; ++tdex)
{
const double t = parameter_list[tdex];
if (false == domain[dir].Includes(t))
return false;
for (int side = -1; side <= 1; side += 2)
{
const int span_index = ON_NurbsSpanIndex(m_order[dir], m_cv_count[dir], m_knot[dir], t, side, hint[dir]);
if (span_index < 0 || span_index + 2*degree_dir > knot_count_dir)
return false;
const ON_Interval span_domain(m_knot[dir][span_index + degree_dir-1], m_knot[dir][span_index + degree_dir]);
if (false == span_domain.Includes(t))
return false;
cv0dex[dir] = span_index;
if (t == span_domain[0])
{
quadrant = 1;
cv2dex[dir] = cv0dex[dir] + 2;
}
else if (t == span_domain[1])
{
quadrant = (0 == dir) ? 2 : 4;
cv0dex[dir] += degree_dir;
cv2dex[dir] = cv0dex[dir] - 2;
}
else
{
cv2dex[dir] = cv0dex[dir] + 3;
quadrant = 0;
}
if ( cv0dex[0] < 0 || cv0dex[0] >= m_cv_count[0] || cv0dex[1] < 0 || cv0dex[1] >= m_cv_count[1] )
{
// bug in this code or invalid surface
ON_ERROR("cv0dex out of bounds");
return false;
}
if ( cv2dex[0] < 0 || cv2dex[0] >= m_cv_count[0] || cv2dex[1] < 0 || cv2dex[1] >= m_cv_count[1] )
{
// bug in this code or invalid surface
ON_ERROR("cv1dex out of bounds");
return false;
}
st[dir] = t;
hint[dir] = span_index;
hint[1 - dir] = 0;
for (int gi = gdex0; gi < gdex1; gi++)
{
st[1 - dir] = g[gi];
if (false == domain[1 - dir].Includes(st[1 - dir]))
continue;
if (false == Ev2Der(st[0], st[1], P, D1[0], D1[1], D2[0], Duv, D2[1], quadrant, hint))
return false;
cv0dex[1 - dir] = gi;
cv2dex[1 - dir] = gi;
if (false == GetCV(cv0dex[0], cv0dex[1], CV0))
return false;
if (false == GetCV(cv2dex[0], cv2dex[1], CV2))
return false;
const double d2 = D2[dir].Length();
const double tol = CV0.DistanceTo(CV2)*1.0e-8;
if (false == (d2 <= tol))
return false;
bIsNatural = true;
}
if (false == bIsNatural)
return false;
if (-1 == side && t == span_domain[1] && t < domain[dir][1])
{
if (degree_dir >= 3 && m_knot[dir][span_index + degree_dir] < m_knot[dir][span_index + degree_dir + 2])
break; // other side evaluations are equal
continue;
}
break;
}
}
return bIsNatural;
}
void ON_NurbsSurface::ON_Internal_ConvertToCurve(const ON_NurbsSurface& srf,
int dir, ON_NurbsCurve& crv) {
crv.DestroyCurveTree();
if (dir)
dir = 1;
const int Sdim = srf.CVSize();
const int n = srf.CVCount(1-dir);
const int Ndim = Sdim*n;
const int knot_count = srf.KnotCount(dir);
int i, j;
double *Ncv;
const double *Scv;
crv.m_dim = Ndim;
crv.m_is_rat = 0;
crv.m_order = srf.Order(dir);
crv.m_cv_count = srf.CVCount(dir);
crv.m_cv_stride = crv.m_dim;
crv.ReserveCVCapacity(srf.CVCount(dir)*Ndim);
crv.ReserveKnotCapacity(srf.KnotCount(dir));
if ( crv.m_knot != srf.m_knot[dir] && srf.m_knot[dir] )
{
memcpy( crv.m_knot, srf.m_knot[dir], knot_count*sizeof(crv.m_knot[0]) );
}
if ( crv.m_cv != srf.m_cv && srf.m_cv )
{
if (dir) {
for ( i = 0; i < crv.m_cv_count; i++ ) {
Ncv = crv.CV(i);
for ( j = 0; j < n; j++ ) {
Scv = srf.CV(j,i);
memcpy( Ncv, Scv, Sdim*sizeof(*Ncv) );
Ncv += Sdim;
}
}
}
else {
for ( i = 0; i < crv.m_cv_count; i++ ) {
Ncv = crv.CV(i);
for ( j = 0; j < n; j++ ) {
Scv = srf.CV(i,j);
memcpy( Ncv, Scv, Sdim*sizeof(*Ncv) );
Ncv += Sdim;
}
}
}
}
}
void ON_NurbsSurface::ON_Internal_ConvertFromCurve(ON_NurbsCurve& crv, int dir,
ON_NurbsSurface& srf) {
crv.DestroyCurveTree();
srf.DestroySurfaceTree();
if (dir)
dir = 1;
const int Sdim = srf.CVSize();
srf.m_order[dir] = crv.m_order;
srf.m_cv_count[dir] = crv.m_cv_count;
srf.m_cv_stride[dir] = crv.m_cv_stride;
srf.m_cv_stride[1-dir] = Sdim;
if ( crv.m_cv )
{
if ( srf.m_cv
&& crv.m_cv != srf.m_cv
&& srf.m_cv_capacity > 0
&& srf.m_cv_capacity < crv.m_cv_stride*crv.m_cv_count )
{
// discard surface cvs because there isn't enough room
onfree( srf.m_cv );
srf.m_cv = 0;
srf.m_cv_capacity = 0;
}
if ( srf.m_cv )
{
// use existing surface cvs
memcpy( srf.m_cv, crv.m_cv, crv.m_cv_stride*crv.m_cv_count*sizeof(*srf.m_cv) );
}
else
{
// move curve cvs to surface
srf.m_cv = crv.m_cv;
srf.m_cv_capacity = crv.m_cv_capacity;
crv.m_cv = 0;
crv.m_cv_capacity = 0;
}
crv.m_cv_stride = 0;
}
if ( crv.m_knot && crv.m_knot != srf.m_knot[dir] )
{
if ( srf.m_knot_capacity[dir] > 0 )
{
onfree( srf.m_knot[dir] );
srf.m_knot[dir] = 0;
srf.m_knot_capacity[dir] = 0;
}
// transfer crv.m_knot to srf.m_knot[dir]
crv.UnmanageKnotForExperts(srf.m_knot_capacity[dir], srf.m_knot[dir]);
}
}
bool ON_NurbsSurface::ClampEnd(
int dir, // dir 0 = "s", 1 = "t"
int end// 0 = clamp start, 1 = clamp end, 2 = clamp start and end
)
{
DestroySurfaceTree();
if (dir)
dir = 1;
ON_NurbsCurve crv;
crv.m_knot = m_knot[dir];
ON_Internal_ConvertToCurve(*this,dir,crv);
bool rc = crv.ClampEnd(end);
ON_Internal_ConvertFromCurve(crv,dir,*this);
return rc;
}
bool ON_NurbsSurface::InsertKnot(
int dir, // dir 0 = "s", 1 = "t"
double knot_value,
int knot_multiplicity // default = 1
)
{
DestroySurfaceTree();
bool rc = false;
if ( (dir == 0 || dir == 1) && IsValid() && knot_multiplicity > 0 && knot_multiplicity < Order(dir) )
{
ON_Interval domain = Domain( dir );
if ( knot_value < domain.Min() || knot_value > domain.Max() )
{
ON_ERROR("ON_NurbsSurface::InsertKnot() knot_value not inside domain.");
}
else
{
ON_NurbsCurve crv;
// transfer knot vector from srf.m_knot[dir] to crv
crv.ManageKnotForExperts(m_knot_capacity[dir], m_knot[dir]);
m_knot[dir] = nullptr;
m_knot_capacity[dir] = 0;
crv.ReserveKnotCapacity(CVCount(dir)+knot_multiplicity);
ON_Internal_ConvertToCurve(*this, dir, crv);
rc = crv.InsertKnot(knot_value, knot_multiplicity);
ON_Internal_ConvertFromCurve(crv, dir, *this);
}
}
return rc;
}
bool ON_NurbsSurface::MakeRational()
{
if ( !IsRational() )
{
DestroySurfaceTree();
ON_BezierSurface b;
b.m_dim = m_dim;
b.m_is_rat = m_is_rat;
b.m_order[0] = m_cv_count[0];
b.m_order[1] = m_cv_count[1];
b.m_cv_stride[0] = m_cv_stride[0];
b.m_cv_stride[1] = m_cv_stride[1];
b.m_cv = m_cv;
b.m_cv_capacity = m_cv_capacity;
b.MakeRational();
m_is_rat = b.m_is_rat;
m_cv_stride[0] = b.m_cv_stride[0];
m_cv_stride[1] = b.m_cv_stride[1];
m_cv = b.m_cv;
b.m_cv = 0;
}
return IsRational();
}
bool ON_NurbsSurface::ChangeDimension(
int desired_dimension // desired_dimension
)
{
bool rc = false;
int i, j, k;
if ( desired_dimension < 1 )
return false;
if ( desired_dimension == m_dim )
return true;
DestroySurfaceTree();
if ( desired_dimension < m_dim )
{
if ( m_is_rat ) {
double* cv;
for ( i = 0; i < m_cv_count[0]; i++ )
{
for ( j = 0; j < m_cv_count[1]; j++ )
{
cv = CV(i,j);
cv[desired_dimension] = cv[m_dim];
}
}
}
m_dim = desired_dimension;
rc = true;
}
else
{
const double* old_cv;
double* new_cv;
//const int old_cv_size = m_is_rat ? (m_dim + 1) : m_dim;
const int old_stride0 = m_cv_stride[0];
const int old_stride1 = m_cv_stride[1];
const int cv_size = m_is_rat ? (desired_dimension + 1) : desired_dimension;
int new_stride0 = old_stride0;
int new_stride1 = old_stride1;
if ( cv_size > old_stride0 && cv_size > old_stride1 )
{
new_stride0 = (old_stride0 <= old_stride1) ? cv_size : (cv_size*m_cv_count[1]);
new_stride1 = (old_stride0 <= old_stride1) ? (cv_size*m_cv_count[0]) : cv_size;
ReserveCVCapacity(cv_size*m_cv_count[0]*m_cv_count[1]);
}
if ( old_stride0 <= old_stride1 )
{
for ( j = m_cv_count[1]-1; j >= 0; j-- )
{
for ( i = m_cv_count[0]-1; i >= 0; i-- )
{
old_cv = m_cv + (old_stride0*i + old_stride1*j);
new_cv = m_cv + (new_stride0*i + new_stride1*j);
if ( m_is_rat )
{
new_cv[desired_dimension] = old_cv[m_dim];
}
for ( k = desired_dimension-1; k >= m_dim; k-- )
{
new_cv[k] = 0.0;
}
for ( k = m_dim-1; k >= 0; k-- )
{
new_cv[k] = old_cv[k];
}
}
}
}
else
{
for ( i = m_cv_count[0]-1; i >= 0; i-- )
{
for ( j = m_cv_count[1]-1; j >= 0; j-- )
{
old_cv = m_cv + (old_stride0*i + old_stride1*j);
new_cv = m_cv + (new_stride0*i + new_stride1*j);
if ( m_is_rat )
{
new_cv[desired_dimension] = old_cv[m_dim];
}
for ( k = desired_dimension-1; k >= m_dim; k-- )
{
new_cv[k] = 0.0;
}
for ( k = m_dim-1; k >= 0; k-- )
{
new_cv[k] = old_cv[k];
}
}
}
}
m_cv_stride[0] = new_stride0;
m_cv_stride[1] = new_stride1;
m_dim = desired_dimension;
rc = true;
}
return rc;
}
bool ON_NurbsSurface::IncreaseDegree(
int dir, // dir 0 = "s", 1 = "t"
int desired_degree // desired_degree
)
{
DestroySurfaceTree();
bool rc = false;
if ( (dir == 0 || dir == 1) && IsValid() && desired_degree >= 1 )
{
if ( m_order[dir] == desired_degree+1 )
rc = true;
else
{
ON_NurbsCurve crv;
// transfer knot vector from srf.m_knot[dir] to crv
crv.ManageKnotForExperts(m_knot_capacity[dir], m_knot[dir]);
m_knot[dir] = 0;
m_knot_capacity[dir] = 0;
ON_Internal_ConvertToCurve(*this, dir, crv);
rc = crv.IncreaseDegree(desired_degree);
ON_Internal_ConvertFromCurve(crv, dir, *this);
}
}
return rc;
}
bool ON_NurbsSurface::MakeNonRational()
{
if ( IsRational() )
{
DestroySurfaceTree();
ON_BezierSurface b;
b.m_dim = m_dim;
b.m_is_rat = m_is_rat;
b.m_order[0] = m_cv_count[0];
b.m_order[1] = m_cv_count[1];
b.m_cv_stride[0] = m_cv_stride[0];
b.m_cv_stride[1] = m_cv_stride[1];
b.m_cv = m_cv;
b.MakeNonRational();
m_is_rat = b.m_is_rat;
m_cv_stride[0] = b.m_cv_stride[0];
m_cv_stride[1] = b.m_cv_stride[1];
m_cv = b.m_cv;
b.m_cv = 0;
}
return IsRational() ? false : true;
}
ON_TensorProduct::ON_TensorProduct()
{}
ON_TensorProduct::~ON_TensorProduct()
{}
bool ON_NurbsSurface::TensorProduct(
const ON_NurbsCurve& nurbscurveA,
const ON_NurbsCurve& nurbscurveB,
ON_TensorProduct& tensor
)
{
DestroySurfaceTree();
// The resulting surface will satisfy
// NurbSrf(s,t) = T( NurbA(s), NurbB(t) )
//
// If you want to understand the relationship between multilinear maps
// and tensor products, read chapter 16 of Serge Lang's Algebra book.
// The connection between Lang and tensor product nurb surfaces being
// that NurbA and NurbB are elements of the module of piecewise polynomial
// functions that satisfy certain degree and continuity constraints.
bool rc;
double wA, wB, wC;
const double *cvA, *cvB;
double *cvC;
int i, j, k, cv_countA, cv_countB, dimA, dimB, dimC, is_ratA, is_ratB, is_ratC;
dimA = nurbscurveA.Dimension();
dimB = nurbscurveB.Dimension();
dimC = tensor.DimensionC();
if ( tensor.DimensionA() > dimA ) {
ON_ERROR("ON_NurbsSurface::TensorProduct() - tensor.DimensionA() > dimA");
return false;
}
if ( tensor.DimensionB() > dimB ) {
ON_ERROR("ON_NurbsSurface::TensorProduct() - tensor.DimensionB() > dimB");
return false;
}
is_ratA = nurbscurveA.IsRational();
is_ratB = nurbscurveB.IsRational();
is_ratC = (is_ratA || is_ratB);
cv_countA = nurbscurveA.CVCount();
cv_countB = nurbscurveB.CVCount();
Create( dimC, is_ratC, nurbscurveA.Order(), nurbscurveB.Order(), cv_countA, cv_countB );
if ( m_knot[0] != nurbscurveA.m_knot )
memcpy( m_knot[0], nurbscurveA.m_knot, KnotCount(0)*sizeof(*m_knot[0]) );
if ( m_knot[1] != nurbscurveB.m_knot )
memcpy( m_knot[1], nurbscurveB.m_knot, KnotCount(1)*sizeof(*m_knot[1]) );
for (i = 0; i < cv_countA; i++) {
cvA = nurbscurveA.CV(i);
for (j = 0; j < cv_countB; j++) {
cvB = nurbscurveB.CV(j);
cvC = CV(i,j);
wA = (is_ratA) ? cvA[dimA] : 1.0;
wB = (is_ratB) ? cvB[dimB] : 1.0;
rc = tensor.Evaluate( (wA == 0.0) ? 0.0 : 1.0/wA, cvA,
(wB == 0.0) ? 0.0 : 1.0/wB, cvB,
cvC );
if ( !rc )
return false;
if (is_ratC) {
wC = wA*wB;
for ( k = 0; k < dimC; k++ )
*cvC++ *= wC;
*cvC = wC;
}
}
}
return true;
}
static
bool ON_MakeDegreesCompatible(
ON_NurbsCurve& nurbs_curveA,
ON_NurbsCurve& nurbs_curveB
)
{
bool rc = false;
if ( nurbs_curveA.m_order > nurbs_curveB.m_order )
rc = nurbs_curveB.IncreaseDegree( nurbs_curveA.Degree() )?true:false;
else
rc = nurbs_curveA.IncreaseDegree( nurbs_curveB.Degree() )?true:false;
return (nurbs_curveA.m_order == nurbs_curveB.m_order);
}
static
bool ON_MakeDomainsCompatible(
ON_NurbsCurve& nurbs_curveA,
ON_NurbsCurve& nurbs_curveB
)
{
ON_Interval dA = nurbs_curveA.Domain();
ON_Interval dB = nurbs_curveB.Domain();
bool rc = false;
if ( dA.Length() >= dB.Length() )
rc = (nurbs_curveB.SetDomain(dA[0],dA[1])?true:false);
else
rc = (nurbs_curveA.SetDomain(dB[0],dB[1])?true:false);
return rc;
}
bool ON_NurbsSurface::ON_Internal_MakeKnotVectorsCompatible(
ON_NurbsCurve& nurbs_curveA,
ON_NurbsCurve& nurbs_curveB
)
{
if ( !ON_MakeDegreesCompatible( nurbs_curveA, nurbs_curveB ) )
return false;
if ( !ON_MakeDomainsCompatible( nurbs_curveA, nurbs_curveB ) )
return false;
const int order = nurbs_curveA.m_order;
ON_Interval span;
double ktol, a, b;
int i, ki, multA, multB;
int knot_countA = nurbs_curveA.KnotCount();
int knot_countB = nurbs_curveB.KnotCount();
int max_knot_capacity = knot_countA + knot_countB - 2*(order-1);
bool bPeriodic = false;
if ( nurbs_curveA.IsPeriodic() || nurbs_curveB.IsPeriodic() )
{
bPeriodic = true;
for ( ki = 0; ki < order-2 && bPeriodic; ki++ )
{
if ( nurbs_curveA.m_knot[ki] != nurbs_curveB.m_knot[ki] )
bPeriodic = false;
}
int kiA, kiB;
for (
kiA = nurbs_curveA.m_cv_count,
kiB = nurbs_curveB.m_cv_count;
kiA < knot_countA && kiB < knot_countB && bPeriodic;
kiA++, kiB++ )
{
if ( nurbs_curveA.m_knot[kiA] != nurbs_curveB.m_knot[kiA] )
bPeriodic = false;
}
}
if ( !bPeriodic )
{
if ( !nurbs_curveA.ClampEnd(2) )
return false;
if ( !nurbs_curveB.ClampEnd(2) )
return false;
}
ki = order-1;
while ( (ki < nurbs_curveA.m_cv_count-1 || ki < nurbs_curveB.m_cv_count-1)
&& nurbs_curveA.m_knot[ki-1] == nurbs_curveB.m_knot[ki-1]
&& ki <= nurbs_curveA.m_cv_count-1
&& ki <= nurbs_curveB.m_cv_count-1
)
{
a = nurbs_curveA.m_knot[ki];
if ( a == nurbs_curveA.m_knot[ki-1] )
return false;
b = nurbs_curveB.m_knot[ki];
if ( b == nurbs_curveB.m_knot[ki-1] )
return false;
multA = ON_KnotMultiplicity( order, nurbs_curveA.m_cv_count, nurbs_curveA.m_knot, ki );
multB = ON_KnotMultiplicity( order, nurbs_curveB.m_cv_count, nurbs_curveB.m_knot, ki );
if ( a < b )
{
// insert a in nurbs_curveB
span.Set(nurbs_curveB.m_knot[ki-1], nurbs_curveB.m_knot[ki] );
ktol = ON_SQRT_EPSILON*(span.Length() + fabs(nurbs_curveB.m_knot[ki-1]) + fabs(nurbs_curveB.m_knot[ki]) );
if ( a >= span[1] - ktol )
{
for ( i = ki; i < ki+multB; i++ )
nurbs_curveB.m_knot[i] = a;
}
else
{
nurbs_curveB.ReserveKnotCapacity( max_knot_capacity );
nurbs_curveB.InsertKnot( a, multA );
}
b = nurbs_curveB.m_knot[ki];
multB = ON_KnotMultiplicity( order, nurbs_curveB.m_cv_count, nurbs_curveB.m_knot, ki );
}
else if ( b < a )
{
// insert b in nurbs_curveA
span.Set(nurbs_curveA.m_knot[ki-1], nurbs_curveA.m_knot[ki] );
ktol = ON_SQRT_EPSILON*(span.Length() + fabs(nurbs_curveA.m_knot[ki-1]) + fabs(nurbs_curveA.m_knot[ki]) );
if ( b >= span[1] - ktol )
{
for ( i = ki; i < ki+multA; i++ )
nurbs_curveA.m_knot[i] = b;
}
else
{
nurbs_curveA.ReserveKnotCapacity( max_knot_capacity );
nurbs_curveA.InsertKnot( b, multB );
}
a = nurbs_curveA.m_knot[ki];
multA = ON_KnotMultiplicity( order, nurbs_curveA.m_cv_count, nurbs_curveA.m_knot, ki );
}
if ( a != b )
return false;
if ( a == b )
{
if ( multA < multB )
{
nurbs_curveA.ReserveKnotCapacity( max_knot_capacity );
if ( !nurbs_curveA.InsertKnot( a, multB ) )
return false;
multA = multB;
}
else if ( multB < multA )
{
nurbs_curveB.ReserveKnotCapacity( max_knot_capacity );
if ( !nurbs_curveB.InsertKnot( a, multA ) )
return false;
multB = multA;
}
ki += multA;
}
}
if ( nurbs_curveA.m_cv_count != nurbs_curveB.m_cv_count )
return false;
knot_countA = nurbs_curveA.KnotCount();
for ( ki = 0; ki < knot_countA; ki++ )
{
if ( nurbs_curveA.m_knot[ki] != nurbs_curveB.m_knot[ki] )
return false;
}
return true;
}
int ON_NurbsSurface::CreateRuledSurface(
const ON_Curve& curveA,
const ON_Curve& curveB,
const ON_Interval* curveA_domain,
const ON_Interval* curveB_domain
)
{
DestroySurfaceTree();
int rcA=1, rcB=1;
ON_NurbsCurve nurbs_curveA, nurbs_curveB;
if ( m_cv && m_cv_capacity == 0 )
nurbs_curveA.m_cv = m_cv;
if ( m_knot[0] && m_knot_capacity[0] == 0 )
nurbs_curveA.m_knot = m_knot[0];
rcA = curveA.GetNurbForm( nurbs_curveA, 0.0, curveA_domain );
if ( rcA<=0 )
return 0;
rcB = curveB.GetNurbForm( nurbs_curveB, 0.0, curveB_domain );
if ( rcB<=0 )
return 0;
if ( !ON_Internal_MakeKnotVectorsCompatible( nurbs_curveA, nurbs_curveB ) )
return false;
if ( nurbs_curveA.m_cv_count != nurbs_curveB.m_cv_count )
return 0;
if ( nurbs_curveA.m_order != nurbs_curveB.m_order )
return 0;
int srf_dim = 3;
if ( nurbs_curveA.Dimension() > srf_dim )
srf_dim = nurbs_curveA.Dimension();
if ( nurbs_curveB.Dimension() > srf_dim )
srf_dim = nurbs_curveB.Dimension();
if (nurbs_curveA.Dimension() < srf_dim )
nurbs_curveA.ChangeDimension(srf_dim);
else if (nurbs_curveB.Dimension() < srf_dim )
nurbs_curveB.ChangeDimension(srf_dim);
if ( nurbs_curveA.IsRational() )
nurbs_curveB.MakeRational();
else if ( nurbs_curveB.IsRational() )
nurbs_curveA.MakeRational();
// reserve enough room in nurbs_curveA.m_cv
// for two rows of surface cvs.
const int is_rat = nurbs_curveA.m_is_rat ? 1 : 0;
if ( is_rat )
{
nurbs_curveA.m_is_rat = 0;
nurbs_curveA.m_dim++;
}
nurbs_curveA.ChangeDimension( 2*nurbs_curveA.m_dim );
nurbs_curveA.m_dim = srf_dim;
nurbs_curveA.m_is_rat = is_rat;
// transfer m_cv and m_knot[0] memory from nurbs_curveA to
// this nurbs surface.
if ( m_cv && m_cv_capacity > 0 )
onfree(m_cv);
m_cv = nurbs_curveA.m_cv;
m_cv_capacity = nurbs_curveA.m_cv_capacity;
nurbs_curveA.m_cv_capacity = 0;
if ( m_knot[0] && m_knot_capacity[0] > 0 )
onfree(m_knot[0]);
// transfer knot vector from nurbs_curveA to srf.m_knot[0]
nurbs_curveA.UnmanageKnotForExperts(m_knot_capacity[0], m_knot[0]);
// Fill in linear knots
ReserveKnotCapacity( 1, 2 );
m_knot[1][0] = 0.0;
m_knot[1][1] = 1.0;
m_dim = srf_dim;
m_is_rat = nurbs_curveA.m_is_rat;
m_order[0] = nurbs_curveA.m_order;
m_order[1] = 2;
m_cv_count[0] = nurbs_curveA.m_cv_count;
m_cv_count[1] = 2;
m_cv_stride[0] = nurbs_curveA.m_cv_stride;
m_cv_stride[1] = m_cv_stride[0]/2;
// fill in "B" row of cvs
for ( int i = 0; i < m_cv_count[0]; i++ )
{
SetCV(i,1,ON::intrinsic_point_style, nurbs_curveB.CV(i));
}
return ((rcA<=rcB) ? rcB : rcA);
}
static
ON_3dPoint CornerAt( const ON_Surface& srf, int corner )
{
double s, t;
switch (corner)
{
case 0: // sw
s = srf.Domain(0)[0];
t = srf.Domain(1)[0];
break;
case 1: // se
s = srf.Domain(0)[1];
t = srf.Domain(1)[0];
break;
case 2: // ne
s = srf.Domain(0)[1];
t = srf.Domain(1)[1];
break;
case 3: // nw
s = srf.Domain(0)[0];
t = srf.Domain(1)[1];
break;
default:
return ON_3dPoint::UnsetPoint;
break;
}
return srf.PointAt(s,t);
}
bool ON_NurbsSurface::CollapseSide(
int side,
ON_3dPoint point
)
{
if ( point == ON_3dPoint::UnsetPoint )
{
point = CornerAt(*this,side);
if ( point == ON_3dPoint::UnsetPoint )
return false;
}
if ( !m_cv )
return false;
int i0 = 0;
int i1 = m_cv_count[0];
int j0 = 0;
int j1 = m_cv_count[1];
switch (side)
{
case 0: // south
j1 = j0+1;
break;
case 1: // east
i0 = i1-1;
break;
case 2: // north
j0 = j1-1;
break;
case 3: // west
i1 = i0+1;
break;
default:
return false;
break;
}
if ( i0 >= i1 || j0 >= j1 )
return false;
int i, j;
ON_4dPoint cv;
for ( i = i0; i < i1; i++ ) for ( j = j0; j < j1; j++ )
{
if ( !GetCV(i,j,cv) )
return false;
cv.x = point.x*cv.w;
cv.y = point.y*cv.w;
cv.z = point.z*cv.w;
if ( !SetCV(i,j,cv) )
return false;
}
return true;
}
int ON_NurbsSurface::CreateConeSurface(
ON_3dPoint apex_point,
const ON_Curve& curve,
const ON_Interval* curve_domain
)
{
DestroySurfaceTree();
ON_NurbsCurve nurbs_curve;
if ( m_cv && m_cv_capacity == 0 )
nurbs_curve.m_cv = m_cv;
if ( m_knot[0] && m_knot_capacity[0] == 0 )
nurbs_curve.m_knot = m_knot[0];
int rc = curve.GetNurbForm( nurbs_curve, 0.0, curve_domain );
if (rc>0)
{
// reserve enough room in nurbs_curve.m_cv
// for two rows of surface cvs.
nurbs_curve.ChangeDimension(3);
const int is_rat = nurbs_curve.m_is_rat?1:0;
if ( is_rat )
{
nurbs_curve.m_is_rat = 0;
nurbs_curve.m_dim++;
}
nurbs_curve.ChangeDimension( 2*nurbs_curve.m_dim );
nurbs_curve.m_is_rat = is_rat;
nurbs_curve.m_dim = 3;
// transfer m_cv and m_knot[0] memory from nurbs_curve to
// this nurbs surface.
if ( m_cv && m_cv_capacity > 0 )
onfree(m_cv);
m_cv = nurbs_curve.m_cv;
m_cv_capacity = nurbs_curve.m_cv_capacity;
nurbs_curve.m_cv_capacity = 0;
if ( m_knot[0] && m_knot_capacity[0] > 0 )
onfree(m_knot[0]);
// Transfer knot vector from nurbs_curve to srf.m_knot[0].
nurbs_curve.UnmanageKnotForExperts(m_knot_capacity[0], m_knot[0]);
// Fill in linear knots
ReserveKnotCapacity( 1, 2 );
m_knot[1][0] = 0.0;
m_knot[1][1] = 1.0;
m_dim = 3;
m_is_rat = is_rat;
m_order[0] = nurbs_curve.m_order;
m_order[1] = 2;
m_cv_count[0] = nurbs_curve.m_cv_count;
m_cv_count[1] = 2;
m_cv_stride[0] = nurbs_curve.m_cv_stride;
m_cv_stride[1] = nurbs_curve.m_cv_stride/2;
for ( int i = 0; i < m_cv_count[0]; i++ )
{
SetCV(i,1,apex_point);
if ( is_rat )
{
double* cv = CV(i,1);
double w = Weight(i,0);
cv[0] *= w;
cv[1] *= w;
cv[2] *= w;
cv[3] = w;
}
}
}
else
Destroy();
return rc;
}
ON_NurbsSurface* ON_NurbsSurfaceQuadrilateral(
const ON_3dPoint& P,
const ON_3dPoint& Q,
const ON_3dPoint& R,
const ON_3dPoint& S,
ON_NurbsSurface* nurbs_surface
)
{
if ( !nurbs_surface )
nurbs_surface = new ON_NurbsSurface( 3, false, 2, 2, 2, 2 );
else
nurbs_surface->Create( 3, false, 2, 2, 2, 2 );
nurbs_surface->SetCV(0,0,P);
nurbs_surface->SetCV(1,0,Q);
nurbs_surface->SetCV(1,1,R);
nurbs_surface->SetCV(0,1,S);
double d1 = P.DistanceTo(Q);
double d2 = R.DistanceTo(S);
double d = (d1 >= d2) ? d1 : d2;
if (d <= ON_ZERO_TOLERANCE )
d = 1.0;
nurbs_surface->m_knot[0][0] = 0.0;
nurbs_surface->m_knot[0][1] = d;
d1 = P.DistanceTo(S);
d2 = Q.DistanceTo(R);
d = (d1 >= d2) ? d1 : d2;
if (d <= ON_ZERO_TOLERANCE )
d = 1.0;
nurbs_surface->m_knot[1][0] = 0.0;
nurbs_surface->m_knot[1][1] = d;
return nurbs_surface;
}
bool ON_NurbsSurface::ConvertSpanToBezier(
int span_index0,
int span_index1,
ON_BezierSurface& bezier_surface
) const
{
int i, j;
if ( !m_cv || !m_knot[0] || !m_knot[1] )
return false;
if ( span_index0 < 0 || span_index0 > m_cv_count[0]-m_order[0] )
return false;
if ( span_index1 < 0 || span_index1 > m_cv_count[1]-m_order[1] )
return false;
i = span_index0+m_order[0]-2;
if ( m_knot[0][i] >= m_knot[0][i+1] )
return false;
j = span_index1+m_order[1]-2;
if ( m_knot[1][j] >= m_knot[1][j+1] )
return false;
{
ON_NurbsSurface bispan;
bispan.m_cv = bezier_surface.m_cv;
bispan.m_cv_capacity = bezier_surface.m_cv_capacity;
bispan.Create( m_dim, m_is_rat, m_order[0], m_order[1], m_order[0], m_order[1] );
const int sizeof_cv = CVSize()*sizeof(*bispan.m_cv);
for ( i = 0; i < m_order[0]; i++ ) for ( j = 0; j < m_order[1]; j++ )
{
memcpy( bispan.CV(i,j), CV(span_index0+i,span_index1+j), sizeof_cv );
}
i = span_index0+m_order[0]-2;
j = span_index1+m_order[1]-2;
bool bClamp = false;
if ( m_knot[0][span_index0] != m_knot[0][span_index0+m_order[0]-2] )
bClamp = true;
if ( m_knot[0][span_index0+m_order[0]-1] != m_knot[0][span_index0+2*m_order[0]-3] )
bClamp = true;
if ( m_knot[1][span_index1] != m_knot[1][span_index1+m_order[1]-2] )
bClamp = true;
if ( m_knot[1][span_index1+m_order[1]-1] != m_knot[1][span_index1+2*m_order[1]-3] )
bClamp = true;
if ( bClamp )
{
memcpy( bispan.m_knot[0], m_knot[0]+span_index0, bispan.KnotCount(0)*sizeof(bispan.m_knot[0][0]) );
memcpy( bispan.m_knot[1], m_knot[1]+span_index1, bispan.KnotCount(1)*sizeof(bispan.m_knot[1][0]) );
bispan.ClampEnd(1,2);
bispan.ClampEnd(0,2);
}
bezier_surface.m_dim = bispan.m_dim;
bezier_surface.m_is_rat = bispan.m_is_rat;
bezier_surface.m_order[0] = bispan.m_order[0];
bezier_surface.m_order[1] = bispan.m_order[1];
bezier_surface.m_cv_stride[0] = bispan.m_cv_stride[0];
bezier_surface.m_cv_stride[1] = bispan.m_cv_stride[1];
bezier_surface.m_cv = bispan.m_cv;
bezier_surface.m_cv_capacity = bispan.m_cv_capacity;
bispan.m_cv = 0;
bispan.m_cv_capacity = 0;
}
return true;
}
static bool ValidateHermiteData(
const ON_SimpleArray<double>& u_Parameters,
const ON_SimpleArray<double>& v_Parameters,
const ON_ClassArray<ON_SimpleArray<ON_3dPoint>>& GridPoints,
const ON_ClassArray<ON_SimpleArray<ON_3dVector>>& u_Tangents,
const ON_ClassArray<ON_SimpleArray<ON_3dVector>>& v_Tangents,
const ON_ClassArray<ON_SimpleArray<ON_3dVector>>& TwistVectors)
{
int n = u_Parameters.Count();
int m = v_Parameters.Count();
if (n < 2 || m < 2)
return false;
for (int i = 0; i < u_Parameters.Count() - 1; i++)
if (u_Parameters[i] >= u_Parameters[i + 1])
return false;
for (int j = 0; j < v_Parameters.Count() - 1; j++)
if (v_Parameters[j] >= v_Parameters[j + 1])
return false;
if (GridPoints.Count() != n)
return false;
for (int i = 0; i < GridPoints.Count(); i++)
if (GridPoints[i].Count() != m)
return false;
if (u_Tangents.Count() != n)
return false;
for (int i = 0; i < u_Tangents.Count(); i++)
if (u_Tangents[i].Count() != m)
return false;
if (v_Tangents.Count() != n)
return false;
for (int i = 0; i < v_Tangents.Count(); i++)
if (v_Tangents[i].Count() != m)
return false;
if (TwistVectors.Count() != n)
return false;
for (int i = 0; i < TwistVectors.Count(); i++)
if (TwistVectors[i].Count() != m)
return false;
return true;
}
class ON_NurbsSurface* ON_NurbsSurface::CreateHermiteSurface(
const ON_SimpleArray<double>& u,
const ON_SimpleArray<double>& v,
const ON_ClassArray<ON_SimpleArray<ON_3dPoint>>& GridPoints,
const ON_ClassArray<ON_SimpleArray<ON_3dVector>>& u_Tan,
const ON_ClassArray<ON_SimpleArray<ON_3dVector>>& v_Tan,
const ON_ClassArray<ON_SimpleArray<ON_3dVector>>& Twist,
class ON_NurbsSurface* hsrf)
{
if (!ValidateHermiteData( u, v, GridPoints, u_Tan, v_Tan, Twist ))
return nullptr;
int n = u.Count();
int m = v.Count();
if (hsrf == nullptr)
{
hsrf = ON_NurbsSurface::New();
}
const int dim = 3;
const int order = 4;
/* Surface has double knot in the interior*/
bool rc = hsrf->Create(dim, false, order, order, 2 * n , 2 * m );
if (rc)
{
// Set the knots duble interior knots
hsrf->SetKnot(0, 0, u[0]);
for (int i = 0; i < n; i++)
{
hsrf->SetKnot(0, 2*i + 1, u[i]);
hsrf->SetKnot(0, 2*i + 2, u[i]);
}
hsrf->SetKnot(0, 2*n + 1, u[n-1]);
hsrf->SetKnot(1, 0, v[0]);
for (int j = 0; j< m; j++)
{
hsrf->SetKnot(1, 2 * j + 1, v[j]);
hsrf->SetKnot(1, 2 * j + 2, v[j]);
}
hsrf->SetKnot(1, 2 * m + 1, v[m - 1]);
// Set corner GridPoints
hsrf->SetCV( 0 , 0 , GridPoints[ 0 ][ 0 ]);
hsrf->SetCV( 0 , 2*m-1, GridPoints[ 0 ][m - 1]);
hsrf->SetCV(2*n-1, 0 , GridPoints[n - 1][ 0 ]);
hsrf->SetCV(2*n-1, 2*m-1, GridPoints[n - 1][m - 1]);
// set the points on v - isos edges
for (int j = 0; j < m - 1; j++)
{
double delv = 1.0 / 3.0 * (v[j + 1] - v[j]);
hsrf->SetCV( 0 , 2 * j + 1, GridPoints[ 0 ][ j ] + delv * v_Tan[ 0 ][ j ]);
hsrf->SetCV( 0 , 2 * j + 2, GridPoints[ 0 ][j + 1] - delv * v_Tan[ 0 ][j + 1]);
hsrf->SetCV(2*n-1, 2 * j + 1, GridPoints[n-1][ j ] + delv * v_Tan[n-1][ j ]);
hsrf->SetCV(2*n-1, 2 * j + 2, GridPoints[n-1][j + 1] - delv * v_Tan[n-1][j + 1]);
}
// set the points on u - isos edges
for (int i = 0; i < n - 1; i++)
{
double delu = 1.0 / 3.0 * (u[i + 1] - u[i]);
hsrf->SetCV(2 * i + 1, 0 , GridPoints[ i ][ 0 ] + delu * u_Tan[ i ][ 0 ]);
hsrf->SetCV(2 * i + 2, 0 , GridPoints[i+1][ 0 ] - delu * u_Tan[i+1][ 0 ]);
hsrf->SetCV(2 * i + 1, 2*m-1, GridPoints[ i ][m-1] + delu * u_Tan[ i ][ m-1 ]);
hsrf->SetCV(2 * i + 2, 2*m-1, GridPoints[i+1][m-1] - delu * u_Tan[i+1][ m-1 ]);
}
// set the interior points
for( int i=0; i<n-1; i++)
for (int j = 0; j < m - 1; j++)
{
double delv = 1.0/3.0 * (v[j + 1] - v[j]);
double delu = 1.0/3.0 * (u[i + 1] - u[i]);
double deluv = delu * delv;
hsrf->SetCV(2*i+1, 2*j+1, GridPoints[ i ][ j ] + ( delu * u_Tan[ i ][ j ] + delv * v_Tan[ i ][ j ]) + deluv * Twist[ i ][ j ]);
hsrf->SetCV(2*i+2, 2*j+1, GridPoints[i+1][ j ] + (-delu * u_Tan[i+1][ j ] + delv * v_Tan[i+1][ j ]) - deluv * Twist[i+1][ j ]);
hsrf->SetCV(2*i+1, 2*j+2, GridPoints[ i ][j+1] + ( delu * u_Tan[ i ][j+1] - delv * v_Tan[ i ][j+1]) - deluv * Twist[ i ][j+1]);
hsrf->SetCV(2*i+2, 2*j+2, GridPoints[i+1][j+1] + (-delu * u_Tan[i+1][j+1] - delv * v_Tan[i+1][j+1]) + deluv * Twist[i+1][j+1]);
}
}
if (!rc)
hsrf = nullptr;
return hsrf;
}
ON_HermiteSurface::ON_HermiteSurface()
: m_u_count(0)
, m_v_count(0)
{
}
ON_HermiteSurface::ON_HermiteSurface(int u_count, int v_count)
: m_u_count(0)
, m_v_count(0)
{
Create(u_count, v_count);
}
ON_HermiteSurface::~ON_HermiteSurface()
{
Destroy();
}
bool ON_HermiteSurface::Create(int u_count, int v_count)
{
Destroy();
if (u_count < 2 || v_count < 2)
return false;
m_u_count = u_count;
m_v_count = v_count;
m_u_parameters.SetCapacity(m_u_count);
m_u_parameters.SetCount(m_u_count);
for (int i = 0; i < m_u_count; i++)
m_u_parameters[i] = ON_UNSET_VALUE;
m_v_parameters.SetCapacity(m_v_count);
m_v_parameters.SetCount(m_v_count);
for (int i = 0; i < m_v_count; i++)
m_v_parameters[i] = ON_UNSET_VALUE;
m_grid_points.SetCapacity(m_u_count);
for (int i = 0; i < m_u_count; i++)
{
ON_SimpleArray<ON_3dPoint>& arr = m_grid_points.AppendNew();
arr.SetCapacity(m_v_count);
arr.SetCount(m_v_count);
arr.Zero();
}
m_u_tangents.SetCapacity(m_u_count);
for (int i = 0; i < m_u_count; i++)
{
ON_SimpleArray<ON_3dVector>& arr = m_u_tangents.AppendNew();
arr.SetCapacity(m_v_count);
arr.SetCount(m_v_count);
for (int j = 0; j < m_v_count; j++)
arr[j] = ON_3dPoint::UnsetPoint;
}
m_v_tangents.SetCapacity(m_u_count);
for (int i = 0; i < m_u_count; i++)
{
ON_SimpleArray<ON_3dVector>& arr = m_v_tangents.AppendNew();
arr.SetCapacity(m_v_count);
arr.SetCount(m_v_count);
for (int j = 0; j < m_v_count; j++)
arr[j] = ON_3dVector::UnsetVector;
}
m_twists.SetCapacity(m_u_count);
for (int i = 0; i < m_u_count; i++)
{
ON_SimpleArray<ON_3dVector>& arr = m_twists.AppendNew();
arr.SetCapacity(m_v_count);
arr.SetCount(m_v_count);
for (int j = 0; j < m_v_count; j++)
arr[j] = ON_3dVector::UnsetVector;
}
return true;
}
void ON_HermiteSurface::Destroy()
{
m_u_parameters.Destroy();
m_v_parameters.Destroy();
for (int i = 0; i < m_grid_points.Count(); i++)
m_grid_points[i].Destroy();
m_grid_points.Destroy();
for (int i = 0; i < m_u_tangents.Count(); i++)
m_u_tangents[i].Destroy();
m_u_tangents.Destroy();
for (int i = 0; i < m_v_tangents.Count(); i++)
m_v_tangents[i].Destroy();
m_v_tangents.Destroy();
for (int i = 0; i < m_twists.Count(); i++)
m_twists[i].Destroy();
m_twists.Destroy();
}
bool ON_HermiteSurface::IsValid() const
{
for (int i = 0; i < m_u_parameters.Count(); i++)
{
if (!ON_IsValid(m_u_parameters[i]))
return false;
}
for (int i = 0; i < m_v_parameters.Count(); i++)
{
if (!ON_IsValid(m_v_parameters[i]))
return false;
}
for (int i = 0; i < m_grid_points.Count(); i++)
{
for (int j = 0; j < m_grid_points[i].Count(); j++)
{
if (m_grid_points[i][j].IsUnset())
return false;
}
}
for (int i = 0; i < m_u_tangents.Count(); i++)
{
for (int j = 0; j < m_u_tangents[i].Count(); j++)
{
if (m_u_tangents[i][j].IsUnset())
return false;
}
}
for (int i = 0; i < m_v_tangents.Count(); i++)
{
for (int j = 0; j < m_v_tangents[i].Count(); j++)
{
if (m_v_tangents[i][j].IsUnset())
return false;
}
}
for (int i = 0; i < m_twists.Count(); i++)
{
for (int j = 0; j < m_twists[i].Count(); j++)
{
if (m_twists[i][j].IsUnset())
return false;
}
}
return ValidateHermiteData(
UParameters(),
VParameters(),
GridPoints(),
UTangents(),
VTangents(),
Twists()
);
}
int ON_HermiteSurface::UCount() const
{
return m_u_count;
}
int ON_HermiteSurface::VCount() const
{
return m_v_count;
}
bool ON_HermiteSurface::InBounds(int u, int v) const
{
return (
0 <= u &&
u < m_u_count &&
0 <= v &&
v < m_v_count
);
}
double ON_HermiteSurface::UParameterAt(int u) const
{
double rc = ON_UNSET_VALUE;
if (0 <= u && u < m_u_count)
rc = m_u_parameters[u];
return rc;
}
void ON_HermiteSurface::SetUParameterAt(int u, double param)
{
if (0 <= u && u < m_u_count)
m_u_parameters[u] = param;
}
double ON_HermiteSurface::VParameterAt(int v) const
{
double rc = ON_UNSET_VALUE;
if (0 <= v && v < m_v_count)
rc = m_v_parameters[v];
return rc;
}
void ON_HermiteSurface::SetVParameterAt(int v, double param)
{
if (0 <= v && v < m_v_count)
m_v_parameters[v] = param;
}
ON_3dPoint ON_HermiteSurface::PointAt(int u, int v) const
{
ON_3dPoint rc = ON_3dPoint::UnsetPoint;
if (InBounds(u, v))
rc = m_grid_points[u][v];
return rc;
}
void ON_HermiteSurface::SetPointAt(int u, int v, const ON_3dPoint& point)
{
if (InBounds(u, v))
m_grid_points[u][v] = point;
}
ON_3dVector ON_HermiteSurface::UTangentAt(int u, int v) const
{
ON_3dVector rc = ON_3dVector::UnsetVector;
if (InBounds(u, v))
rc = m_u_tangents[u][v];
return rc;
}
void ON_HermiteSurface::SetUTangentAt(int u, int v, const ON_3dVector& dir)
{
if (InBounds(u, v))
m_u_tangents[u][v] = dir;
}
ON_3dVector ON_HermiteSurface::VTangentAt(int u, int v) const
{
ON_3dVector rc = ON_3dVector::UnsetVector;
if (InBounds(u, v))
rc = m_v_tangents[u][v];
return rc;
}
void ON_HermiteSurface::SetVTangentAt(int u, int v, const ON_3dVector& dir)
{
if (InBounds(u, v))
m_v_tangents[u][v] = dir;
}
ON_3dVector ON_HermiteSurface::TwistAt(int u, int v) const
{
ON_3dVector rc = ON_3dVector::UnsetVector;
if (InBounds(u, v))
rc = m_twists[u][v];
return rc;
}
void ON_HermiteSurface::SetTwistAt(int u, int v, const ON_3dVector& dir)
{
if (InBounds(u, v))
m_twists[u][v] = dir;
}
const ON_SimpleArray<double>& ON_HermiteSurface::UParameters() const
{
return m_u_parameters;
}
const ON_SimpleArray<double>& ON_HermiteSurface::VParameters() const
{
return m_v_parameters;
}
const ON_ClassArray<ON_SimpleArray<ON_3dPoint>>& ON_HermiteSurface::GridPoints() const
{
return m_grid_points;
}
const ON_ClassArray<ON_SimpleArray<ON_3dVector>>& ON_HermiteSurface::UTangents() const
{
return m_u_tangents;
}
const ON_ClassArray<ON_SimpleArray<ON_3dVector>>& ON_HermiteSurface::VTangents() const
{
return m_v_tangents;
}
const ON_ClassArray<ON_SimpleArray<ON_3dVector>>& ON_HermiteSurface::Twists() const
{
return m_twists;
}
ON_NurbsSurface* ON_HermiteSurface::NurbsSurface(ON_NurbsSurface* pNurbsSurface)
{
if (!IsValid())
return nullptr;
return ON_NurbsSurface::CreateHermiteSurface(
UParameters(),
VParameters(),
GridPoints(),
UTangents(),
VTangents(),
Twists(),
pNurbsSurface
);
}