diff --git a/opennurbs_archive.cpp b/opennurbs_archive.cpp index 093161a4..373bd60b 100644 --- a/opennurbs_archive.cpp +++ b/opennurbs_archive.cpp @@ -8959,6 +8959,19 @@ bool ON_BinaryArchive::BeginRead3dmTable( unsigned int typecode ) return false; } + const bool bIsTableTypeCode = (TCODE_TABLE == (0xFFFFF000 & tcode)); + if (false == bIsTableTypeCode) + { + // Dale Lear - Nov 8 2022 - ships in Rhino 8.2 + // Fix https://mcneel.myjetbrains.com/youtrack/issue/RH-77657 + // (detecting corruption in table headers prevents hangs). + // This value isn't a table typecode (even from a table added int the future). + // The file is badly corrupt at this point and we have to quit. + ON_ERROR("tcode is not a table typecode. File is badly corrupted."); + this->Internal_ReportCriticalError(); + return false; + } + // A required table is not at the current position in the archive // see if we can find it someplace else in the archive. This can // happen when old code encounters a table that was added later. diff --git a/opennurbs_convex_poly.cpp b/opennurbs_convex_poly.cpp index 6799a4f5..b3234c8d 100644 --- a/opennurbs_convex_poly.cpp +++ b/opennurbs_convex_poly.cpp @@ -589,7 +589,118 @@ bool ON_ConvexPoly::Standardize(ON_4dex& dex, ON_4dPoint& B) return rc; } +/**/ +void ON_ConvexHullRefEx::Initialize(const ON_3dVector* V0, int n) +{ + m_n = n; + m_dim = 3; + m_v = *V0; + m_is_rat = false; + m_stride = 3; +} + +void ON_ConvexHullRefEx::Initialize(const ON_4dPoint* V0, int n) +{ + m_n = n; + m_v = *V0; + m_dim = 3; + m_is_rat = true; + m_stride = 4; +} + +// style must be either not_rational or homogeneous_rational = 2, +void ON_ConvexHullRefEx::Initialize(const double* V0, ON::point_style style, int count) +{ + if (style == ON::homogeneous_rational) + Initialize(reinterpret_cast(V0), count); + else + Initialize(reinterpret_cast(V0), count); +} + +ON_ConvexHullRefEx::ON_ConvexHullRefEx(const ON_3dVector* V0, int n) +{ + m_n = n; + m_dim = 3; + m_v = *V0; + m_is_rat = false; + m_stride = 3; +} + +ON_ConvexHullRefEx::ON_ConvexHullRefEx(const ON_3dPoint* P0, int n) +{ + m_n = n; + m_dim = 3; + m_v = *P0; + m_is_rat = false; + m_stride = 3; +} + +ON_ConvexHullRefEx::ON_ConvexHullRefEx(const ON_4dPoint* V0, int n) +{ + m_n = n; + m_dim = 3; + m_v = *V0; + m_is_rat = true; + m_stride = 4; +} + +ON_ConvexHullRefEx::ON_ConvexHullRefEx(const double* V0, bool is_rat, int n, int dim) +{ + m_n = n; + m_dim = (dim>=0 && dim<4)?dim:0; + m_v = V0; + m_is_rat = is_rat; + m_stride = is_rat ? dim+1 : dim; +} + +ON_ConvexHullRefEx::ON_ConvexHullRefEx(const double* V0, bool is_rat, int n, int dim, int stride) +{ + m_n = n; + m_dim = (dim >= 0 && dim < 4) ? dim : 0; + m_v = V0; + m_is_rat = is_rat; + m_stride = (stride>m_dim+is_rat)? stride: m_dim + is_rat; +} + +ON_3dVector ON_ConvexHullRefEx::Vertex(int j) const +{ + ON_3dVector v(0,0,0); + for (int i = 0; i < m_dim; i++) + v[i] = m_v[j * m_stride + i]; + if (m_is_rat ) + { + double w = m_v[j * m_stride + m_dim]; + if (w) + v *= (1.0 / w); + } + + return v; +} + +int ON_ConvexHullRefEx::SupportIndex(ON_3dVector W, int) const +{ + int j0 = 0; + double dot = Vertex(0) * W; + for (int j = 1; j < m_n; j++) + { + ON_3dVector v = Vertex(j); + double d = v * W; + if (d > dot) + { + dot = d; + j0 = j; + } + } + return j0; +} + +double ON_ConvexHullRefEx::MaximumCoordinate() const +{ + return ON_MaximumCoordinate(m_v, m_dim, m_is_rat, m_n, m_stride); +} + +/* ON_ConvexHullRef is DEEPRECATED because it doesn't work for 2d curves . Use ON_ConvexHellRefEx instead.*/ void ON_ConvexHullRef::Initialize(const ON_3dVector* V0, int n) { m_n = n; @@ -713,7 +824,7 @@ double ON_ConvexHullPoint2::MaximumCoordinate() const bool ON_ConvexPoly::GetClosestPointSeeded(ON_3dPoint P0, ON_4dex& dex, ON_4dPoint& Bary, double atmost ) const { - ON_ConvexHullRef CvxPt(&P0, 1); + ON_ConvexHullRefEx CvxPt(&P0, 1); // TODO don't use ON_ConvexHullRefEx // Set pdex to match the support of dex ON_4dex pdex = dex; for (int i = 0; i < 4; i++) @@ -830,7 +941,7 @@ bool GJK_Simplex::Includes(int aind, int bind) // true if (aind, bind) is a vert // Adex[i]<0 iff Bdex[i]<0 for all i // Adex[i]>=0 for some i for some i // By satisfying this condition Adex and Bdex will define a simplex in A - B -// Note the result of a ClosestPoint calculation Adex and Bdex satisfy these conditions +// Note: As a result of a ClosestPoint calculation Adex and Bdex satisfy these conditions. bool ON_ConvexPoly::GetClosestPointSeeded(const ON_ConvexPoly& B, ON_4dex& Adex, ON_4dex& Bdex, ON_4dPoint& Bary, double atmost) const diff --git a/opennurbs_convex_poly.h b/opennurbs_convex_poly.h index bb376619..4916cefe 100644 --- a/opennurbs_convex_poly.h +++ b/opennurbs_convex_poly.h @@ -337,7 +337,50 @@ Details: // WARNING: Points are referenced not stored for optimal performance in' // some applications. // The list of points must remain alive and in there initial location -// For the duration of this object. +// For the duration of this object. +// +// This is an improved version of ON_ConvexHullRef that includes support for 2d point lists. +class ON_CLASS ON_ConvexHullRefEx : public ON_ConvexPoly +{ +public: + ON_ConvexHullRefEx() { m_n = 0; m_dim = 0; m_is_rat = false; m_stride = 3; }; + ON_ConvexHullRefEx(const ON_3dVector* V0, int count); // a 3d point array + ON_ConvexHullRefEx(const ON_3dPoint* V0, int count); // a 3d point array + ON_ConvexHullRefEx(const ON_4dPoint* V0, int count); // a array of homogeneous points + ON_ConvexHullRefEx(const double* v0, bool is_rat, int n, int dim=3); // v0 is an array of 2d or 3d points in either euclidean or homogeneous coordinates. dim<4. + ON_ConvexHullRefEx(const double* v0, bool is_rat, int n, int dim , int stride); // As above with a stride to the array. dim <4. + + void Initialize(const ON_3dVector* V0, int count); + void Initialize(const ON_4dPoint* V0, int count); + void Initialize(const double* V0, ON::point_style style, int count); // style must be either not_rational or homogeneous_rational = 2, + + int Count() const override { return m_n; } + ON_3dVector Vertex(int j) const override; + + // Support map + virtual int SupportIndex(ON_3dVector W, int i0) const override; + virtual double MaximumCoordinate() const override; + + virtual ~ON_ConvexHullRefEx() override {}; +private: + + int m_n = 0; + int m_dim = 3; // must be <4. + bool m_is_rat = false; + const double* m_v = nullptr; + int m_stride = 3; +}; + +// 3d convex hull defined by an explicit collection of points called vertices. +// Note: vertices need not be extreme points + +// WARNING: Points are referenced not stored for optimal performance in' +// some applications. +// The list of points must remain alive and in there initial location +// For the duration of this object. +// +// GBA 02-Nov-23 This class is DEPRECATED and will be removed in the future. +// Use ON_ConvexHullRefEx instead. class ON_CLASS ON_ConvexHullRef : public ON_ConvexPoly { public: @@ -397,7 +440,7 @@ public: }; private: - ON_ConvexHullRef Ref; + ON_ConvexHullRefEx Ref; ON_SimpleArray m_Vert; }; diff --git a/opennurbs_extensions.cpp b/opennurbs_extensions.cpp index 9ee8a295..cb0c985e 100644 --- a/opennurbs_extensions.cpp +++ b/opennurbs_extensions.cpp @@ -2854,6 +2854,9 @@ bool ONX_Model::IncrementalReadFinish( } } + if (0 != archive.CriticalErrorCount()) + return false; + // STEP 17: OPTIONAL - Read user tables as anonymous goo // If you develop a plug-ins or application that uses OpenNURBS files, // you can store anything you want in a user table. @@ -2937,17 +2940,23 @@ bool ONX_Model::IncrementalReadFinish( } } + if (0 != archive.CriticalErrorCount()) + return false; + // STEP 18: OPTIONAL - check for end mark size_t file_length = 0; - if ( !archive.Read3dmEndMark(&file_length) ) + if (!archive.Read3dmEndMark(&file_length)) { - if ( archive.Archive3dmVersion() != 1 ) + if (archive.Archive3dmVersion() != 1) { // some v1 files are missing end-of-archive markers } } else + { m_3dm_file_byte_count = file_length; + } + return (0 == archive.CriticalErrorCount()); } @@ -3011,8 +3020,11 @@ bool ONX_Model::Read(ON_BinaryArchive& archive, unsigned int table_filter, ON_ModelComponentReference model_geometry_reference; if (!IncrementalReadModelGeometry(archive, bManageComponents, bManageGeometry, bManageAttributes, - model_object_type_filter, model_geometry_reference)) - break; // Catastrophic error. + model_object_type_filter, model_geometry_reference)) + { + // Catastrophic error. + break; + } if (model_geometry_reference.IsEmpty()) break; // No more geometry. @@ -4994,6 +5006,13 @@ bool ONX_ModelPrivate::CreateRenderContentFromXML(ON_XMLNode& model_node, Render ON_RenderContent* rc = NewRenderContentFromNode(*rc_node); if (nullptr != rc) { + // The name currently in the render content came from XML. It's been corrected if it was invalid, + // but it still might be a duplicate of another one in the model, so we have to turn it into an + // unused name if necessary. + const ON_wString name = m_model.m_manifest.UnusedName(rc->ComponentType(), ON_nil_uuid, rc->Name(), + nullptr, nullptr, 0, nullptr); + rc->SetName(name); + const auto ref = m_model.AddModelComponent(*rc); auto* model_rc = ON_RenderContent::Cast(ref.ModelComponent()); if (nullptr != model_rc) diff --git a/opennurbs_extensions.h b/opennurbs_extensions.h index 93fa9c88..1891b2fe 100644 --- a/opennurbs_extensions.h +++ b/opennurbs_extensions.h @@ -781,7 +781,8 @@ public: Description: Easy way to add a render material to the model. Creates a PBR material. Returns: - If mat_name is valid, the material's index is returned. Otherwise ON_UNSET_INT_INDEX is returned. + If mat_name is valid and the function succeeds, the material's index is returned. + Otherwise ON_UNSET_INT_INDEX is returned. */ int AddRenderMaterial( const wchar_t* mat_name @@ -791,7 +792,8 @@ public: Description: Easy way to add a render environment to the model. Creates a basic environment. Returns: - If env_name is valid, the environment's index is returned. Otherwise ON_UNSET_INT_INDEX is returned. + If env_name is valid and the function succeeds, the environment's index is returned. + Otherwise ON_UNSET_INT_INDEX is returned. */ int AddRenderEnvironment( const wchar_t* env_name @@ -799,10 +801,12 @@ public: /* Description: - Easy way to add a render texture to the model. Creates a bitmap texture. The texture's name is derived from - the supplied file name. The file must exist locally or the function will fail. + Easy way to add a render texture to the model. Creates a bitmap texture. + The texture's name is derived from the supplied file name. The file must exist locally + or the function will fail. Returns: - If the function succeeds, the texture's index is returned. Otherwise ON_UNSET_INT_INDEX is returned. + If the function succeeds, the texture's index is returned. + Otherwise ON_UNSET_INT_INDEX is returned. */ int AddRenderTexture( const wchar_t* filename diff --git a/opennurbs_linetype.cpp b/opennurbs_linetype.cpp index 99de9ade..19e22a1b 100644 --- a/opennurbs_linetype.cpp +++ b/opennurbs_linetype.cpp @@ -144,6 +144,20 @@ ON_Linetype& ON_Linetype::operator=(const ON_Linetype& other) } return *this; } + +ON_Linetype* ON_Linetype::DuplicateLinetype() const +{ + ON_Linetype* rc = new ON_Linetype(*this); + if (rc) + { + rc->ClearName(); + rc->ClearId(); + rc->m_is_locked_bits = 0; + } + return rc; +} + + bool ON_Linetype::IsValid( ON_TextLog* text_log ) const { if (false == ON_ModelComponent::IsValid(text_log)) diff --git a/opennurbs_linetype.h b/opennurbs_linetype.h index bc4c122a..585b12a5 100644 --- a/opennurbs_linetype.h +++ b/opennurbs_linetype.h @@ -91,6 +91,12 @@ public: ON_Linetype(const ON_Linetype&); ON_Linetype& operator=(const ON_Linetype&); + /* + Description: + Duplicates this linetype, clears the name, id, and locked bits. + */ + ON_Linetype* DuplicateLinetype() const; + /* Description: Tests that name is set and there is at least one non-zero length segment diff --git a/opennurbs_material.cpp b/opennurbs_material.cpp index 8003f6fe..80582084 100644 --- a/opennurbs_material.cpp +++ b/opennurbs_material.cpp @@ -2304,6 +2304,16 @@ void ON_Texture::SetMappingChannel( m_mapping_channel_id = mapping_channel_id; } +bool ON_Texture::IsWcsProjected() const +{ + return (m_mapping_channel_id == (unsigned int)MAPPING_CHANNEL::wcs_channel); +} + +bool ON_Texture::IsWcsBoxProjected() const +{ + return (m_mapping_channel_id == (unsigned int)MAPPING_CHANNEL::wcs_box_channel); +} + ON_Texture::TYPE ON_Texture::TypeFromUnsigned(unsigned int type_as_unsigned) { switch (type_as_unsigned) diff --git a/opennurbs_point.cpp b/opennurbs_point.cpp index 9cf726a1..88fc7350 100644 --- a/opennurbs_point.cpp +++ b/opennurbs_point.cpp @@ -7871,25 +7871,31 @@ const ON_PlaneEquation operator*(const ON_Xform& xform, const ON_PlaneEquation& // Find the maximum absolute value of a array of (possibly homogeneous) points double ON_MaximumCoordinate(const double* data, int dim, bool is_rat, int count) +{ + return ON_MaximumCoordinate(data, dim, is_rat, count, dim + is_rat); +} + +// Find the maximum absolute value of an array with stride of (possibly homogeneous) points +double ON_MaximumCoordinate(const double* data, int dim, bool is_rat, int count, int stride) { double norm = 0; if (is_rat) { for (int i = 0; i < count; i++) { - double w = fabs(data[i*(dim+1) + dim ]); + double w = fabs(data[i*stride + dim ]); double norm_i = 0; for (int j = 0; j < dim; j++) - norm_i = ON_Max(norm_i, fabs(data[i*(dim+1) + j])); - if (norm_i > norm * w) + norm_i = ON_Max(norm_i, fabs(data[i*stride + j])); + if (norm_i > norm * w && w>0) norm = norm_i / w; } } else { - int n = dim * count; - for (int i = 0; i < n; i++) - norm = ON_Max(norm, fabs(data[i])); + for (int i = 0; i < count; i++) + for (int j=0; j=dim+is_rat /////////////////////////////////////////////////////////////// // diff --git a/opennurbs_public_version.h b/opennurbs_public_version.h index 62ed2a66..919e0577 100644 --- a/opennurbs_public_version.h +++ b/opennurbs_public_version.h @@ -6,7 +6,7 @@ // To update version numbers, edit ..\build\build_dates.msbuild #define RMA_VERSION_MAJOR 8 -#define RMA_VERSION_MINOR 0 +#define RMA_VERSION_MINOR 3 //////////////////////////////////////////////////////////////// // @@ -14,10 +14,10 @@ // first step in each build. // #define RMA_VERSION_YEAR 2023 -#define RMA_VERSION_MONTH 10 -#define RMA_VERSION_DATE 31 -#define RMA_VERSION_HOUR 13 -#define RMA_VERSION_MINUTE 30 +#define RMA_VERSION_MONTH 12 +#define RMA_VERSION_DATE 12 +#define RMA_VERSION_HOUR 3 +#define RMA_VERSION_MINUTE 5 //////////////////////////////////////////////////////////////// // @@ -35,8 +35,8 @@ // 3 = build system release build #define RMA_VERSION_BRANCH 0 -#define VERSION_WITH_COMMAS 8,0,23304,13300 -#define VERSION_WITH_PERIODS 8.0.23304.13300 +#define VERSION_WITH_COMMAS 8,3,23346,3050 +#define VERSION_WITH_PERIODS 8.3.23346.03050 #define COPYRIGHT "Copyright (C) 1993-2023, Robert McNeel & Associates. All Rights Reserved." #define SPECIAL_BUILD_DESCRIPTION "Public OpenNURBS C++ 3dm file IO library." @@ -44,11 +44,11 @@ #define RMA_VERSION_NUMBER_MAJOR_WSTRING L"8" #define RMA_PREVIOUS_VERSION_NUMBER_MAJOR_WSTRING L"7" -#define RMA_VERSION_NUMBER_SR_STRING "SR0" -#define RMA_VERSION_NUMBER_SR_WSTRING L"SR0" +#define RMA_VERSION_NUMBER_SR_STRING "SR3" +#define RMA_VERSION_NUMBER_SR_WSTRING L"SR3" -#define RMA_VERSION_WITH_PERIODS_STRING "8.0.23304.13300" -#define RMA_VERSION_WITH_PERIODS_WSTRING L"8.0.23304.13300" +#define RMA_VERSION_WITH_PERIODS_STRING "8.3.23346.03050" +#define RMA_VERSION_WITH_PERIODS_WSTRING L"8.3.23346.03050" diff --git a/opennurbs_render_content.cpp b/opennurbs_render_content.cpp index 7dca43c7..34ae64c1 100644 --- a/opennurbs_render_content.cpp +++ b/opennurbs_render_content.cpp @@ -340,6 +340,44 @@ void ON_RenderContentPrivate::InternalSetPropertyValue(const wchar_t* name, cons } } +static void EnsureNameValid(ON_wString& name) +{ + // 29th November 2023 John Croudy, https://mcneel.myjetbrains.com/youtrack/issue/RH-78603 + // Component names cannot begin with '(', ')', '[', ']', '{', '}' or ' '. + // Component names should not be multiline. + // I'm also going to disallow ':' because that has a special meaning in the name and + // render content does not use that feature. + + ON_wString name_copy = name; + name = L""; + + bool first = true; + const int len = name_copy.Length(); + for (int i = 0; i < len; i++) + { + wchar_t c = name_copy[i]; + if (first) + { + if ((c == L' ' ) ) continue; + if ((c == L'(' ) || (c == L')' )) continue; + if ((c == L'[' ) || (c == L']' )) continue; + if ((c == L'{' ) || (c == L'}' )) continue; + } + + // Replace control codes with a space. Includes CR/LF. + if (c < L' ') + c = L' '; + + name += c; + first = false; + } + + // Also disallow ':' inside the name. + name.Replace(':', ' '); + + name.TrimLeftAndRight(); +} + void ON_RenderContentPrivate::SetXMLNode(const ON_XMLNode& node) { std::lock_guard lg(m_mutex); @@ -374,8 +412,16 @@ void ON_RenderContentPrivate::SetXMLNode(const ON_XMLNode& node) // Copy the pruned copy of the XML node. This node does not have any child content nodes. m_node = node_copy; - // Copy the XML instance name to the component name. - m_render_content.SetName(GetPropertyValue(ON_RENDER_CONTENT_INSTANCE_NAME).AsString()); + // Copy the XML instance name to the component name after validating it. + ON_wString name = GetPropertyValue(ON_RENDER_CONTENT_INSTANCE_NAME).AsString(); + + // 29th November 2023 John Croudy, https://mcneel.myjetbrains.com/youtrack/issue/RH-78603 + if (!ON_ModelComponent::IsValidComponentName(name)) + { + EnsureNameValid(name); + } + + m_render_content.SetName(name); // Copy the XML instance id to the component id. m_render_content.SetId(GetPropertyValue(ON_RENDER_CONTENT_INSTANCE_ID).AsUuid()); @@ -1721,16 +1767,19 @@ ON_RenderContent* ON_RenderTexture::NewRenderContent(void) const return new ON_RenderTexture; } - -int ONX_Model::AddRenderMaterial(const wchar_t* mat_name) +int ONX_Model::AddRenderMaterial(const wchar_t* candidate_name) { + if (!ON_ModelComponent::IsValidComponentName(candidate_name)) + return ON_UNSET_INT_INDEX; + static ON_UUID uuidPB = { 0x5a8d7b9b, 0xcdc9, 0x49de, { 0x8c, 0x16, 0x2e, 0xf6, 0x4f, 0xb0, 0x97, 0xab } }; ON_RenderMaterial mat; mat.SetTypeId(uuidPB); - const ON_wString unused_name = m_manifest.UnusedName(mat.ComponentType(), ON_nil_uuid, mat_name, nullptr, nullptr, 0, nullptr); - mat.SetName(unused_name); + const ON_wString mat_name = m_manifest.UnusedName(mat.ComponentType(), ON_nil_uuid, candidate_name, + nullptr, nullptr, 0, nullptr); + mat.SetName(mat_name); const ON_ModelComponentReference mcr = AddModelComponent(mat, true); const auto* model_mat = ON_RenderMaterial::Cast(mcr.ModelComponent()); @@ -1743,15 +1792,19 @@ int ONX_Model::AddRenderMaterial(const wchar_t* mat_name) return model_mat->Index(); } -int ONX_Model::AddRenderEnvironment(const wchar_t* env_name) +int ONX_Model::AddRenderEnvironment(const wchar_t* candidate_name) { + if (!ON_ModelComponent::IsValidComponentName(candidate_name)) + return ON_UNSET_INT_INDEX; + static ON_UUID uuidBE = { 0xba51ce00, 0xba51, 0xce00, { 0xba, 0x51, 0xce, 0xba, 0x51, 0xce, 0x00, 0x00 } }; ON_RenderEnvironment env; env.SetTypeId(uuidBE); - const ON_wString unused_name = m_manifest.UnusedName(env.ComponentType(), ON_nil_uuid, env_name, nullptr, nullptr, 0, nullptr); - env.SetName(unused_name); + const ON_wString env_name = m_manifest.UnusedName(env.ComponentType(), ON_nil_uuid, candidate_name, + nullptr, nullptr, 0, nullptr); + env.SetName(env_name); const ON_ModelComponentReference mcr = AddModelComponent(env, true); const auto* model_env = ON_RenderEnvironment::Cast(mcr.ModelComponent()); @@ -1764,28 +1817,27 @@ int ONX_Model::AddRenderEnvironment(const wchar_t* env_name) return model_env->Index(); } -int ONX_Model::AddRenderTexture(const wchar_t* fn) +int ONX_Model::AddRenderTexture(const wchar_t* filename) { - static const ON_UUID uuidBM = { 0x57e0ed08, 0x1907, 0x4529, { 0xb0, 0x1b, 0x0c, 0x4a, 0x24, 0x24, 0x55, 0xfd } }; + const ON_wString clean_filename = ON_FileSystemPath::CleanPath(filename); - const auto filename = ON_FileSystemPath::CleanPath(fn); - - if (!ON_FileSystem::PathExists(filename)) + if (!ON_FileSystem::PathExists(clean_filename)) { ON_ERROR("Failed to add render texture; file does not exist"); return ON_UNSET_INT_INDEX; } + static const ON_UUID uuidBM = { 0x57e0ed08, 0x1907, 0x4529, { 0xb0, 0x1b, 0x0c, 0x4a, 0x24, 0x24, 0x55, 0xfd } }; + ON_RenderTexture tex; tex.SetTypeId(uuidBM); - tex.SetParameter(ON_RENDER_TEXTURE_FILENAME, filename); + tex.SetParameter(ON_RENDER_TEXTURE_FILENAME, clean_filename); - const ON_wString tex_name = ON_FileSystemPath::FileNameFromPath(filename, false); + const ON_wString candidate_name = ON_FileSystemPath::FileNameFromPath(clean_filename, false); + const ON_wString tex_name = m_manifest.UnusedName(tex.ComponentType(), ON_nil_uuid, candidate_name, + nullptr, nullptr, 0, nullptr); tex.SetName(tex_name); - const ON_wString unused_name = m_manifest.UnusedName(tex.ComponentType(), ON_nil_uuid, tex_name, nullptr, nullptr, 0, nullptr); - tex.SetName(unused_name); - const ON_ModelComponentReference mcr = AddModelComponent(tex, true); const auto* model_tex = ON_RenderTexture::Cast(mcr.ModelComponent()); if (nullptr == model_tex) @@ -1797,3 +1849,8 @@ int ONX_Model::AddRenderTexture(const wchar_t* fn) return model_tex->Index(); } + +ON_DECL void ON_EnsureNameValid(ON_wString& name) +{ + EnsureNameValid(name); +} diff --git a/opennurbs_sphere.cpp b/opennurbs_sphere.cpp index 4d269cbe..667cfbfc 100644 --- a/opennurbs_sphere.cpp +++ b/opennurbs_sphere.cpp @@ -34,7 +34,7 @@ ON_Sphere::~ON_Sphere() bool ON_Sphere::IsValid() const { - return ( ON_IsValid(radius) && radius > 0.0 && plane.IsValid() ) ? true : false; + return ( radius > 0.0 && radius < ON_UNSET_POSITIVE_VALUE && plane.IsValid() ) ? true : false; } bool ON_Sphere::Create( const ON_3dPoint& center, double r ) diff --git a/opennurbs_statics.cpp b/opennurbs_statics.cpp index 9c5b225b..8df14aa2 100644 --- a/opennurbs_statics.cpp +++ b/opennurbs_statics.cpp @@ -346,6 +346,19 @@ const ON_SubDHash ON_SubDHash::Empty; const ON_UUID ON_SubD::FastAndSimpleFacePackingId = { 0xc3d8dd54, 0xf8c8, 0x4455, { 0xbb, 0xe, 0x2a, 0x2f, 0x49, 0x88, 0xec, 0x81 } }; +// {9C491E5C-2B46-48AA-BD43-7B18FDC52D58} +const ON_UUID ON_SubD::QuadSphereFacePackingId = +{ 0x9c491e5c, 0x2b46, 0x48aa, { 0xbd, 0x43, 0x7b, 0x18, 0xfd, 0xc5, 0x2d, 0x58 } }; + +// {63CA2FC1-8F6C-4EFC-9A07-C6A26A8C93FB} +const ON_UUID ON_SubD::GlobeSphereFacePackingId = +{ 0x63ca2fc1, 0x8f6c, 0x4efc, { 0x9a, 0x7, 0xc6, 0xa2, 0x6a, 0x8c, 0x93, 0xfb } }; + +// {91FD7018-8BBE-4492-8D2E-E8761C505ACF} +const ON_UUID ON_SubD::CustomFacePackingId = +{ 0x91fd7018, 0x8bbe, 0x4492, { 0x8d, 0x2e, 0xe8, 0x76, 0x1c, 0x50, 0x5a, 0xcf } }; + + // ON_SubD::DefaultFacePackingId must always identitify a built-in face packing // algoritm. If a new built-in algorithm is developed that produces generally // better packings and is as fast and reliable as the current default, then diff --git a/opennurbs_subd.cpp b/opennurbs_subd.cpp index b4532a65..ca9ab345 100644 --- a/opennurbs_subd.cpp +++ b/opennurbs_subd.cpp @@ -8484,7 +8484,7 @@ bool ON_SubDimple::IsValid( bool ON_SubD::IsValid(ON_TextLog* text_logx) const { // If low bit of text_log pointer is 1, then ON_Error is not called when the - // knot vector is invalid. + // subd is invalid. const ON__INT_PTR lowbit = 1; const ON__INT_PTR hightbits = ~lowbit; const bool bSilentError = (0 != (lowbit & ((ON__INT_PTR)text_logx))); @@ -8540,6 +8540,7 @@ static const ON_wString Internal_DescribeWaste(size_t waste, size_t total) return description; } + unsigned int ON_SubD::DumpTopology( ON_2udex vertex_id_range, ON_2udex edge_id_range, @@ -8634,10 +8635,13 @@ unsigned int ON_SubD::DumpTopology( h.Dump(text_log); } - text_log.Print(L"Texture coordinate settings:\n"); + + if (false == bIsTextHash) { - const ON_SubDTextureCoordinateType subd_texture_coordinate_type = this->TextureCoordinateType(); + text_log.Print(L"Texture coordinate settings:\n"); ON_TextLogIndent indent1(text_log); + + const ON_SubDTextureCoordinateType subd_texture_coordinate_type = this->TextureCoordinateType(); const ON_wString subd_texture_coordinate_type_as_string = ON_SubD::TextureCoordinateTypeToString(this->TextureCoordinateType()); text_log.Print(L"TextureCoordinateType() = %ls\n", static_cast(subd_texture_coordinate_type_as_string)); @@ -8724,7 +8728,6 @@ unsigned int ON_SubD::DumpTopology( text_log.Print(mapping_tag.m_mesh_xform); text_log.PopIndent(); } - } const ON_SHA1_Hash subd_texture_settings_hash = this->TextureSettingsHash(); @@ -8732,20 +8735,17 @@ unsigned int ON_SubD::DumpTopology( subd_texture_settings_hash.Dump(text_log); text_log.PrintNewLine(); - if (false == text_log.IsTextHash()) - { - // runtime settings most recentltly used to set fragmant texture coordinates. - const ON_SHA1_Hash frament_texture_settings_hash = this->FragmentTextureCoordinatesTextureSettingsHash(); - text_log.Print(L"FragmentTextureCoordinatesTextureSettingsHash() = "); - if (subd_texture_settings_hash == frament_texture_settings_hash) - text_log.Print(L"TextureSettingsHash()"); - else - frament_texture_settings_hash.Dump(text_log); - text_log.PrintNewLine(); - } + // runtime settings most recentltly used to set fragmant texture coordinates. + const ON_SHA1_Hash frament_texture_settings_hash = this->FragmentTextureCoordinatesTextureSettingsHash(); + text_log.Print(L"FragmentTextureCoordinatesTextureSettingsHash() = "); + if (subd_texture_settings_hash == frament_texture_settings_hash) + text_log.Print(L"TextureSettingsHash()"); + else + frament_texture_settings_hash.Dump(text_log); + text_log.PrintNewLine(); } - if (false == text_log.IsTextHash()) + if (false == bIsTextHash) { text_log.Print(L"Per vertex color settings:\n"); { @@ -8764,19 +8764,22 @@ unsigned int ON_SubD::DumpTopology( bool bIncludeSymmetrySet = false; - text_log.Print(L"Geometry content serial number = %" PRIu64 "\n", geometry_content_sn); - text_log.Print(L"Render content serial number = %" PRIu64 "\n", render_content_sn); - text_log.Print("Heap use:\n"); + if (false == bIsTextHash) { - ON_TextLogIndent indent1(text_log); - size_t sizeof_subd = this->SizeOfAllElements(); - text_log.PrintString(ON_wString(L"Total = ") + ON_wString::ToMemorySize(sizeof_subd) + ON_wString(L".\n")); - const size_t sizeof_frags = this->SizeOfAllMeshFragments(); - text_log.PrintString(ON_wString(L"Mesh fragments = ") + ON_wString::ToMemorySize(sizeof_frags) + ON_wString(L".\n")); - const size_t sizeof_frags_waste = this->SizeOfUnusedMeshFragments(); - text_log.PrintString(ON_wString(L"Reserved but ununsed = ") - + Internal_DescribeWaste(sizeof_frags_waste,sizeof_subd) - + ON_wString(L".\n")); + text_log.Print(L"Geometry content serial number = %" PRIu64 "\n", geometry_content_sn); + text_log.Print(L"Render content serial number = %" PRIu64 "\n", render_content_sn); + text_log.Print("Heap use:\n"); + { + ON_TextLogIndent indent1(text_log); + size_t sizeof_subd = this->SizeOfAllElements(); + text_log.PrintString(ON_wString(L"Total = ") + ON_wString::ToMemorySize(sizeof_subd) + ON_wString(L".\n")); + const size_t sizeof_frags = this->SizeOfAllMeshFragments(); + text_log.PrintString(ON_wString(L"Mesh fragments = ") + ON_wString::ToMemorySize(sizeof_frags) + ON_wString(L".\n")); + const size_t sizeof_frags_waste = this->SizeOfUnusedMeshFragments(); + text_log.PrintString(ON_wString(L"Reserved but ununsed = ") + + Internal_DescribeWaste(sizeof_frags_waste, sizeof_subd) + + ON_wString(L".\n")); + } } text_log.Print(L"Levels:\n"); @@ -10337,6 +10340,72 @@ unsigned int ON_SubDLevel::DumpTopology( text_log.Print(" }\n"); + if (f->PackRectIsSet()) + { + const ON_2dPoint pack_rect_orgin = f->PackRectOrigin(); + const ON_2dPoint pack_rect_size = f->PackRectSize(); + text_log.Print("f.PackId = %u, origin = (%g,%g), size = (%g,%g), rot = %u degrees\n", + f->PackId(), + pack_rect_orgin.x, pack_rect_orgin.y, + pack_rect_size.x, pack_rect_size.y, + f->PackRectRotationDegrees() + ); + + // Dale Lear Oct 2023: + // Use bGridOrder = false so pack_rect corners are in quad order + // and it is possible to clearly see how texture space is assigned + // to triangle and quad faces. + // For quads and triangles, vertex order, pack rect corner ordern and face + // texture point order are identical. + // For triangles, the texture space corresponding to the 4th pack_rect corner + // subrect is not mapped. + // For n-gons with n > 4, the pack rect is subdivide into two rows ofr floor((n+1)/2) + // subrects. + const bool bGridOrder = false; // SEE COMMENT ABOVE for why this should be false. + ON_2dPoint corners[4] = { + f->PackRectCorner(bGridOrder,0), + f->PackRectCorner(bGridOrder,1), + f->PackRectCorner(bGridOrder,2), + f->PackRectCorner(bGridOrder,3) + }; + text_log.Print("f.PackRectCorners[4] = {(%g,%g), (%g,%g), (%g,%g), (%g,%g)}\n", + corners[0].x, corners[0].y, + corners[1].x, corners[1].y, + corners[2].x, corners[2].y, + corners[3].x, corners[3].y + ); + if ( 3 == f->m_edge_count) + { + const ON_TextLogIndent indent2(text_log); + const ON_2dPoint m01 = 0.5 * (corners[0] + corners[1]); + const ON_2dPoint m12 = 0.5 * (corners[1] + corners[2]); + const ON_2dPoint m23 = 0.5 * (corners[2] + corners[3]); + const ON_2dPoint m30 = 0.5 * (corners[3] + corners[0]); + const ON_2dPoint c = pack_rect_orgin + 0.5 * pack_rect_size; + + text_log.Print("3 sub rects:\n"); + const ON_TextLogIndent indent3(text_log); + text_log.Print("{(%g,% g), (% g,% g), (% g,% g), (% g,% g)}\n", + corners[0].x, corners[0].y, + m01.x, m01.y, + c.x, c.y, + m30.x, m30.y + ); + text_log.Print("{(%g,% g), (% g,% g), (% g,% g), (% g,% g)}\n", + m01.x, m01.y, + corners[1].x, corners[1].y, + m12.x, m12.y, + c.x, c.y + ); + text_log.Print("{(%g,% g), (% g,% g), (% g,% g), (% g,% g)}\n", + c.x, c.y, + m12.x, m12.y, + corners[2].x, corners[2].y, + m23.x, m23.y + ); + } + } + if (f->TexturePointsAreSet()) { text_log.Print("f.TexturePoints[%u] = {", face_edge_count); @@ -10356,8 +10425,6 @@ unsigned int ON_SubDLevel::DumpTopology( text_log.Print(" }\n"); } - - bool bNeedComma = false; const ON_Color per_face_color = f->PerFaceColor(); @@ -10385,29 +10452,7 @@ unsigned int ON_SubDLevel::DumpTopology( if (bNeedComma) text_log.PrintNewLine(); - if (f->PackRectIsSet()) - { - bNeedComma = true; - const bool bGridOrder = true; - ON_2dPoint corners[4] = { - f->PackRectCorner(bGridOrder,0), - f->PackRectCorner(bGridOrder,1), - f->PackRectCorner(bGridOrder,2), - f->PackRectCorner(bGridOrder,3) - }; - text_log.Print("f.PackId = %u Pack rectangle corners: (%g,%g), (%g,%g), (%g,%g), (%g,%g)", - f->PackId(), - corners[0].x, corners[0].y, - corners[1].x, corners[1].y, - corners[2].x, corners[2].y, - corners[3].x, corners[3].y - ); - } - else - { - text_log.Print("Pack rectangle is not set."); - } - text_log.PrintNewLine(); + if (false == text_log.IsTextHash()) { @@ -10667,7 +10712,7 @@ size_t ON_SubD::SizeOfUnusedMeshFragments() const //virtual ON__UINT32 ON_SubD::DataCRC(ON__UINT32 current_remainder) const { - return 0; + return this->GeometryHash().CRC32(current_remainder); } //virtual @@ -12840,6 +12885,8 @@ bool ON_SubDFace::IsValidPackRect( bool ON_SubDFace::SetPackRectForExperts(ON_2dPoint pack_rect_origin, ON_2dVector pack_rect_size, int packing_rotation_degrees) { + // r deals with negative values of input packing_rotation_degrees. + const int r = ((packing_rotation_degrees % 360) + 360) % 360; const bool bValidPackRectangle = ON_SubDFace::IsValidPackRect(pack_rect_origin, pack_rect_size, packing_rotation_degrees); if (bValidPackRectangle) { @@ -12849,7 +12896,7 @@ bool ON_SubDFace::SetPackRectForExperts(ON_2dPoint pack_rect_origin, ON_2dVector m_pack_rect_size[1] = pack_rect_size.y; ON_SubDFace::PackStatusBits packing_rotation = ON_SubDFace::PackStatusBits::PackingRotate0; - switch (((packing_rotation_degrees % 360) + 360) % 360) + switch (r) { case 90: packing_rotation = ON_SubDFace::PackStatusBits::PackingRotate90; @@ -14671,6 +14718,28 @@ bool ON_SubDimple::LocalSubdivide( return true; } +const ON_UUID ON_SubD::FacePackingId() const +{ + const ON_SubDimple* dimple = this->SubDimple(); + return (nullptr != dimple) ? dimple->FacePackingId() : ON_nil_uuid; +} + +const ON_UUID ON_SubDimple::FacePackingId() const +{ + return m_face_packing_id; +} + +const ON_SubDHash ON_SubD::FacePackingTopologyHash() const +{ + const ON_SubDimple* dimple = this->SubDimple(); + return (nullptr != dimple) ? dimple->FacePackingTopologyHash() : ON_SubDHash::Empty; +} + +const ON_SubDHash ON_SubDimple::FacePackingTopologyHash() const +{ + return m_face_packing_topology_hash; +} + unsigned int ON_SubDimple::GlobalSubdivide() { if (m_levels.UnsignedCount() <= 0) @@ -14702,10 +14771,15 @@ unsigned int ON_SubDimple::GlobalSubdivide() this->ChangeGeometryContentSerialNumber(bChangePreservesSymmetry); // Add face points - unsigned int max_pack_id = 0U; + bool bSubdividePackRect = ON_nil_uuid != this->FacePackingId(); + unsigned next_pack_id = 0U; for (const ON_SubDFace* f0 = level0.m_face[0]; nullptr != f0; f0 = f0->m_next_face) { - if (f0->PackId() > max_pack_id) max_pack_id = f0->PackId(); + if (bSubdividePackRect && f0->PackRectIsSet()) + { + if (f0->PackId() > next_pack_id) + next_pack_id = f0->PackId(); + } if (false == f0->GetSubdivisionPoint(P)) continue; if (nullptr == f0->m_subd_point1) @@ -14722,6 +14796,17 @@ unsigned int ON_SubDimple::GlobalSubdivide() } } + if (next_pack_id > 0) + { + // next_pack_id MUST be strictly greater than any pack rect id in use. + ++next_pack_id; + } + else + { + // nothing valid to copy + bSubdividePackRect = false; + } + // Add edge points for (const ON_SubDEdge* e0 = level0.m_edge[0]; nullptr != e0; e0 = e0->m_next_edge) { @@ -14799,16 +14884,176 @@ unsigned int ON_SubDimple::GlobalSubdivide() for (const ON_SubDFace* f0 = level0.m_face[0]; nullptr != f0; f0 = f0->m_next_face) { - Internal_GlobalQuadSubdivideFace(f0, max_pack_id); + Internal_GlobalQuadSubdivideFace(f0, bSubdividePackRect, next_pack_id); } return level1_index; } +static bool Internal_SubdivideFacePacking( + const ON_SubDFace* f0, + unsigned f0_edge_count, + unsigned f0_pack_id, + unsigned& next_pack_id, + ON_SubDFace** sub_quads +) +{ + // NOTE WELL: + // 1) The caller must insure input parameters are valid. + // 2) A multi-face pack MUST be a set of quads that form a RECTANGULAR topological grid. + // 3) The primary service provided by picking is insuring packed texture coordinates map + // rectangular regions of texture space to the grids of packed quads. This is important + // for texture painting applications. + + const ON_2dPoint f0_pack_rect_origin = f0->PackRectOrigin(); + if (false == (f0_pack_rect_origin.x >= 0.0 && f0_pack_rect_origin.x < 1.0)) + return false; + if (false == (f0_pack_rect_origin.y >= 0.0 && f0_pack_rect_origin.y < 1.0)) + return false; + + const ON_2dVector f0_pack_rect_size = f0->PackRectSize(); + if (false == (f0_pack_rect_size.x > 0.0 && f0_pack_rect_origin.x + f0_pack_rect_size.x <= 1.0 + ON_EPSILON)) + return false; + if (false == (f0_pack_rect_size.y > 0.0 && f0_pack_rect_origin.y + f0_pack_rect_size.y <= 1.0 + ON_EPSILON)) + return false; + + const unsigned f0_packing_rotation_degrees = f0->PackRectRotationDegrees(); + if (f0_edge_count <= 4) + { + // NOTE WELL: A multi-face pack MUST be a set of quads that form a rectangular topological grid. + // + // When f0 is a quad, the f0 pack region is subdivided into 4 sub rectangles and + // those rectangle are assigned the original pack id. This works because neighboring + // quad faces that belong to the same pack will also be subdivided into 4 quads + // and the requirement that multi-face pack MUST be a set of quads that form a + // rectangular topological grid will be satisified in the globally subdivided SubD. + // + // When f0 is a triangle, f0 necessarily is the on face with its pack id (because it cannot + // possibly be grouped with other quads - see NOTE WELL above). + // In addition, for tiranglular faces, packed textures are assigned to the trianglular face + // in a way that the first two sub quads can become a single two quad pack and can reuse the f0->PackId(). + // The third subquad gets a new pack id. + + const ON_2dVector f1_pack_rect_size(0.5 * f0_pack_rect_size.x, 0.5 * f0_pack_rect_size.y); + unsigned f1_packing_rotation_degrees = (f0_packing_rotation_degrees + 90U) % 360U; + for (unsigned i = 0; i < f0_edge_count; ++i) + { + const unsigned j = (i + 4 - (f0_packing_rotation_degrees / 90)) % 4; + const ON_2dPoint f1_pack_rect_origin( + f0_pack_rect_origin.x + ((j >= 1 && j <= 2) ? 0.5 * f0_pack_rect_size.x : 0.0), + f0_pack_rect_origin.y + ((j >= 2) ? 0.5 * f0_pack_rect_size.y : 0.0) + ); + + // See comment above about pack id assignments + const unsigned f1_pack_id = (3 == f0_edge_count && 2 == i) ? (next_pack_id++) : f0_pack_id; + + ON_SubDFace* f1 = sub_quads[i]; + f1->SetPackIdForExperts(f1_pack_id); + f1->SetPackRectForExperts( + f1_pack_rect_origin, + f1_pack_rect_size, + f1_packing_rotation_degrees); + f1_packing_rotation_degrees = (f1_packing_rotation_degrees + 270U) % 360U; + } + } + else + { + // n-gon with n >= 5 + ON_2dVector ngon_sub_pack_rect_size; + ON_2dVector ngon_sub_pack_rect_delta; + const ON_2udex ngon_grid_size = ON_SubDFace::GetNgonSubPackRectSizeAndDelta( + f0_edge_count, + f0_pack_rect_size, + ngon_sub_pack_rect_size, + ngon_sub_pack_rect_delta + ); + + if (ngon_grid_size.i <= 0 || ngon_grid_size.j <= 0 || ngon_grid_size.i * ngon_grid_size.j < f0_edge_count) + return false; + + //const unsigned horiz_offset_count + // = (0 == (f0_packing_rotation_degrees % 180)) + // ? ngon_grid_size.i + // : ngon_grid_size.j; + + //if (0 != (f0_packing_rotation_degrees % 180)) + //{ + // // subdivision rects coordinates need to be swapped + // // for 90 and 270 rotations of the level 0 ngon packing rect. + // double t; + // + // t = ngon_sub_pack_rect_size.x; + // ngon_sub_pack_rect_size.x = ngon_sub_pack_rect_size.y; + // ngon_sub_pack_rect_size.y = t; + // + // t = ngon_sub_pack_rect_delta.x; + // ngon_sub_pack_rect_delta.x = ngon_sub_pack_rect_delta.y; + // ngon_sub_pack_rect_delta.y = t; + //} + + const bool bGridOrder = true; + + ON_2dPoint face_pack_rect_corners[4]; + if (false == f0->GetFacePackRectCorners(bGridOrder, face_pack_rect_corners)) + return false; + + unsigned f1_packing_rotation_degrees = (f0_packing_rotation_degrees + 270U) % 360U; + for (unsigned i = 0; i < f0_edge_count; ++i) + { + ON_2dPoint subd_quad_rect_corners[4]; + if (false == ON_SubDMeshFragment::GetNgonFaceFragmentPackRectCorners( + f0_edge_count, + i, + bGridOrder, + face_pack_rect_corners, + f0_pack_rect_size, + ngon_grid_size, + ngon_sub_pack_rect_size, + ngon_sub_pack_rect_delta, + subd_quad_rect_corners)) + continue; + + ON_2dPoint f1_pack_rect_origin = subd_quad_rect_corners[0]; + ON_2dPoint f1_pack_rect_max = subd_quad_rect_corners[0]; + for (int j = 1; j < 4; ++j) + { + const double x = subd_quad_rect_corners[j].x; + if (x < f1_pack_rect_origin.x) + f1_pack_rect_origin.x = x; + else if (x > f1_pack_rect_max.x) + f1_pack_rect_max.x = x; + const double y = subd_quad_rect_corners[j].y; + if (y < f1_pack_rect_origin.y) + f1_pack_rect_origin.y = y; + else if (y > f1_pack_rect_max.y) + f1_pack_rect_max.y = y; + } + + // NOTE WELL: A multi-face pack MUST be a set of quads that form a rectangular topological grid. + // + // So, for ngons, the f0 ngon necessarily is the only face with it's pack id because it cannot + // possibly be in a group of QUADS. + // + // Packed textures are assigned to ngons in a way that each subquad must have a distinct pack id. + // The first sub quad is assigned f0->PackId() and the subsequent subquads get a new pack id. + const unsigned f1_pack_id = (0 == i) ? f0_pack_id : next_pack_id++; + ON_SubDFace* f1 = sub_quads[i]; + f1->SetPackIdForExperts(f1_pack_id); + f1->SetPackRectForExperts( + f1_pack_rect_origin, + f1_pack_rect_max - f1_pack_rect_origin, + f1_packing_rotation_degrees); + } + } + + return true; +} + unsigned int ON_SubDimple::Internal_GlobalQuadSubdivideFace( const ON_SubDFace* f0, - unsigned max_pack_id - ) + bool bSubdividePackRect, + unsigned& next_pack_id +) { // This is a private member function. // The caller insures f0 != nullptr. @@ -14822,52 +15067,7 @@ unsigned int ON_SubDimple::Internal_GlobalQuadSubdivideFace( const unsigned int level_zero_face_id = (0 == f0->SubdivisionLevel()) ? f0->m_id : f0->m_level_zero_face_id; // 2022-06-30, Pierre, RH-69170. Try to keep face packs when globally subdividing - unsigned int pack_id = f0->PackId(); - const bool pack_rect{ f0->PackRectIsSet() }; - - // We should never have to make a new pack: if the previous packing was valid, all pack_ids were set. - // If max_pack_id > 0 and one of the faces has pack_id == 0, the packing was invalid - const bool make_new_pack = max_pack_id > 0 && pack_id == 0 && !pack_rect && f0_edge_count == 4; - const bool set_pack_id = max_pack_id > 0 && ((pack_id > 0 && pack_rect) || make_new_pack); - const bool make_ngons_pack = max_pack_id > 0 && pack_id > 0 && pack_rect && f0_edge_count != 4; - unsigned int ngons_quadrant_size[4]{}; - - ON_2dPoint pack_rect_origin{ 0., 0. }; - ON_2dVector pack_rect_size{ .5, .5 }; - unsigned int packing_rotation_degrees{ 0U }; - unsigned int rot_dex{ 0U }; - ON_2dVector pack_rect_offsets[4]{ - ON_2dVector{0., 0.}, ON_2dVector{.5, 0.}, ON_2dVector{.5, .5}, ON_2dVector{0., .5} - }; - if (set_pack_id) - { - if (make_new_pack) - { - pack_id = max_pack_id + 1U; - } - else - { - packing_rotation_degrees = f0->PackRectRotationDegrees(); - rot_dex = (((packing_rotation_degrees / 90) % 4) + 4) % 4; - pack_rect_size = f0->PackRectSize() / 2; - pack_rect_origin = f0->PackRectCorner(false, rot_dex); - pack_rect_offsets[1].x = pack_rect_size.x; - pack_rect_offsets[2].x = pack_rect_size.x; - pack_rect_offsets[2].y = pack_rect_size.y; - pack_rect_offsets[3].y = pack_rect_size.y; - } - if (make_ngons_pack) - { - // TODO: Use ON_SubDFace::GetNgonSubPackRectSizeAndDelta() instead? - pack_id = max_pack_id; // Will be incremented later - const unsigned int k = f0_edge_count / 4; - const unsigned int r = f0_edge_count % 4; - ngons_quadrant_size[0] = k + (r > 0 ? 1 : 0); - ngons_quadrant_size[1] = k + (r == 3 ? 1 : 0); - ngons_quadrant_size[2] = k + (r > 1 ? 1 : 0); - ngons_quadrant_size[3] = k; - } - } + // 2023-10-11 Dale Lear - RH-77662 - fixing bugs in ngon pact rect subdivision - triangles and ngons were not handled correctly. if (nullptr == f0->m_subd_point1) { @@ -14882,7 +15082,6 @@ unsigned int ON_SubDimple::Internal_GlobalQuadSubdivideFace( const_cast(f0)->m_subd_point1 = v; } - ON_SubDEdge* E0[2]; ON__UINT_PTR E0dir[2]; ON_SubDEdge* E1[4]; @@ -14901,6 +15100,32 @@ unsigned int ON_SubDimple::Internal_GlobalQuadSubdivideFace( const double w_2facesector = ON_SubDSectorType::CreaseSectorCoefficient(2); + // The existing pack rect will be subdivided if and only if f0_pack_id > 0 + // and f0 is successfully subdivided into f0_edge_count man quads + const unsigned f0_pack_id + = (bSubdividePackRect && f0->PackRectIsSet() && next_pack_id > 0) + ? f0->PackId() + : 0u; + + // sub_quads_buffer[] is used to avoid allocation overhead + // in the vast majority of cases. + ON_SubDFace* sub_quads_buffer[8]; + ON_SubDFace** sub_quads; + if (f0_pack_id > 0) + { + // sub_quads != nullptr if and only if f0 has texture packing information + // that needs to be subdivided. + sub_quads + = (((size_t)f0_edge_count) <= sizeof(sub_quads_buffer) / sizeof(sub_quads_buffer[0])) + ? sub_quads_buffer + : (ON_SubDFace**)onmalloc(f0_edge_count * sizeof(sub_quads[0])); + } + else + { + // Packing information does not exist or will not be subdivided. + sub_quads = nullptr; + } + for (unsigned int i = 0; i < f0_edge_count; i++) { E0[0] = E0[1]; @@ -14933,7 +15158,7 @@ unsigned int ON_SubDimple::Internal_GlobalQuadSubdivideFace( // The resulting quad edge coefficient is 0.5 = 1/2 + 1/3*cos(pi/2). w = (ON_SubDVertexTag::Crease == E0[0]->m_subd_point1->m_vertex_tag) ? w_2facesector : 0.0; E1[3] = AddEdge(ON_SubDEdgeTag::Smooth, const_cast(f0->m_subd_point1), 0.0, const_cast(E0[0]->m_subd_point1), w); - if (nullptr == FirstE1) + if (0 == i) FirstE1 = E1[3]; } E1dir[3] = 0; @@ -14965,43 +15190,26 @@ unsigned int ON_SubDimple::Internal_GlobalQuadSubdivideFace( f1->SetMaterialChannelIndex(material_channel_index); f1->SetPerFaceColor(per_face_color); f1->m_level_zero_face_id = level_zero_face_id; + if (nullptr != sub_quads) + sub_quads[f1_count] = f1; f1_count++; - - if (set_pack_id) - { - if (make_ngons_pack) - { - // TODO: Use ON_SubDFace::GetNgonSubPackRectSizeAndDelta() instead? - ++pack_id; - const unsigned int quadrant = (i * 4) / f0_edge_count; - const unsigned int prevsizes = ( - quadrant > 0 ? ngons_quadrant_size[0] : 0 - + quadrant > 1 ? ngons_quadrant_size[1] : 0 - + quadrant > 2 ? ngons_quadrant_size[2] : 0); - const unsigned int subidx = i - prevsizes; - const ON_2dVector size{ - quadrant % 2 == 0 ? (pack_rect_size.x / ngons_quadrant_size[quadrant]) : pack_rect_size.x, - quadrant % 2 == 1 ? (pack_rect_size.y / ngons_quadrant_size[quadrant]) : pack_rect_size.y }; - const ON_2dVector offset{ - pack_rect_offsets[(quadrant + 4 - rot_dex) % 4] - + (quadrant % 2 == 0 ? ON_2dVector{ size.x * subidx, 0. } : ON_2dVector{ size.y * subidx, 0. }) }; - f1->SetPackRectForExperts( - pack_rect_origin + offset, - size, packing_rotation_degrees - quadrant * 90U + 90U); - } - else - { - f1->SetPackRectForExperts( - pack_rect_origin + pack_rect_offsets[(i + 4 - rot_dex) % 4], - pack_rect_size, packing_rotation_degrees - i * 90U + 90U); - } - f1->SetPackIdForExperts(pack_id); - if (pack_id > max_pack_id) max_pack_id = pack_id; - } } } - // return number of new faces + if (nullptr != sub_quads) + { + // sub_quads != nullptr if and only if f0 has texture packing information + // that needs to be subdivided. + // + // The existing pack rect needs to be subdivided and assinged to the subdivided faces + if (f0_edge_count == f1_count) + Internal_SubdivideFacePacking(f0, f0_edge_count, f0_pack_id, next_pack_id, sub_quads ); + + if (sub_quads != sub_quads_buffer) + onfree(sub_quads); + } + + // return number of new sub quad faces return f1_count; } diff --git a/opennurbs_subd.h b/opennurbs_subd.h index cb8f5cb7..d2fe90a7 100644 --- a/opennurbs_subd.h +++ b/opennurbs_subd.h @@ -5322,125 +5322,6 @@ public: ); -#if 0 - /* - Description: - Creates a SubD sphere made from triangular faces and 6 valent vertices. - Parameters: - sphere - [in] - Location, size and orientation of the sphere - vertex_location - [in] - If vertex_location = ON_SubDComponentLocation::ControlNet, then the control net - points will be on the surface of the sphere. Otherwise the limit surface - points will be on the sphere. - subdivision_level - [in] - The resulting sphere will have 80*4^tri_subdivision_level triangles. - 0 - 180 triangles - 1 - 320 triangles - 2 - 1280 triangles - ... - destination_subd [out] - - If destination_subd is not null, the SubD sphere is saved in this instance. - Returns: - Pointer to the resulting SubD if successful - Null for error - */ - static ON_SubD* CreateSubDTriSphere( - const ON_Sphere sphere, - ON_SubDComponentLocation vertex_location, - unsigned tri_subdivision_level, - ON_SubD* destination_subd - ); - - /* - Description: - Creates a SubD sphere based on an icosohedran (20 triangular faces and 5 valent vertices). - Parameters: - sphere - [in] - Location, size and orientation of the sphere - vertex_location - [in] - If vertex_location = ON_SubDComponentLocation::ControlNet, then the control net - points will be on the surface of the sphere. Otherwise the limit surface - points will be on the sphere. - subdivision_level - [in] - 0 - 80 triangles (icosohedral control net) - 1 - 60 quad - 2 - 240 quads - ... - destination_subd [out] - - If destination_subd is not null, the SubD sphere is saved in this instance. - Returns: - Pointer to the resulting SubD if successful - Null for error - */ - static ON_SubD* CreateSubDIcosahedran( - const ON_Sphere sphere, - ON_SubDComponentLocation vertex_location, - unsigned subdivision_level, - ON_SubD* destination_subd - ); - - /* - Description: - Creates a SubD sphere based on an dodecahedron (20 triangular faces and 5 valent vertices). - Parameters: - sphere - [in] - Location, size and orientation of the sphere - vertex_location - [in] - If vertex_location = ON_SubDComponentLocation::ControlNet, then the control net - points will be on the surface of the sphere. Otherwise the limit surface - points will be on the sphere. - subdivision_level - [in] - 0 - 5 pentagons (dodecahedral control net) - 1 - 60 quad - 2 - 240 quads - ... - destination_subd [out] - - If destination_subd is not null, the SubD sphere is saved in this instance. - Returns: - Pointer to the resulting SubD if successful - Null for error - */ - static ON_SubD* CreateSubDDodecahedran( - const ON_Sphere sphere, - ON_SubDComponentLocation vertex_location, - unsigned subdivision_level, - ON_SubD* destination_subd - ); - - - /* - Description: - Creates a SubD sphere made with triangles and quads to - resemble a globe with latitude and longitude circles. - Parameters: - sphere - [in] - Location, size and orientation of the sphere - vertex_location - [in] - If vertex_location = ON_SubDComponentLocation::ControlNet, then the control net - points will be on the surface of the sphere. Otherwise the limit surface - points will be on the sphere. - subdivision_level - [in] - The resulting sphere will have 80*4^tri_subdivision_level triangles. - 0 - 180 triangles - 1 - 320 triangles - 2 - 1280 triangles - ... - destination_subd [out] - - If destination_subd is not null, the SubD sphere is saved in this instance. - Returns: - Pointer to the resulting SubD if successful - Null for error - */ - static ON_SubD* CreateSubDGlobe( - const ON_Sphere sphere, - ON_SubDComponentLocation vertex_location, - unsigned latitude_count, - unsigned longitued_count, - ON_SubD* destination_subd - ); - -#endif /* Description: @@ -8512,12 +8393,44 @@ public: /// static const ON_UUID FastAndSimpleFacePackingId; + /// + /// The quad sphere face packing is used by ON_SubD::CreateSubDQuadSphere. + /// It divides the quad sphere into two similar sets (like a baseball cover) and assigns + /// the bottom third of texture space to the first region and the top third to + /// the second region. The middle third is unmapped so that texture distortion is + /// uniform for each quad. + /// {9C491E5C-2B46-48AA-BD43-7B18FDC52D58} + /// + static const ON_UUID QuadSphereFacePackingId; + + /// + /// The globe sphere face packing is used by ON_SubD::CreateSubDGlobeSphere. + /// The equatorial band of quads is assigned a central horizontal strip of + /// texture space while the polar triangle fans are assigned horizontal strips + /// from the bottom and top of texture space. + /// The heights of the horizontal strips of texture space are chosen to minimize + /// distortion as latitude varies. + /// {63CA2FC1-8F6C-4EFC-9A07-C6A26A8C93FB} + /// + static const ON_UUID GlobeSphereFacePackingId; + + + /// + /// The custom face packing is typically used when a subd creation + /// function sets a custom face packing different from the default. + /// Typically this happens when there are quad packs that align + /// well with the overall geometry or to reduce texture distortion. + /// It is used to indicate the built-in automatic face packing + /// was not applied. + /// {91FD7018-8BBE-4492-8D2E-E8761C505ACF} + /// + static const ON_UUID CustomFacePackingId; // ADD NEW PackFaces ids above this comment and below FastAndSimplePackFacesId. /// - /// ON_SubD::DefaultFacePackingId ideitifies the default face packing. + /// ON_SubD::DefaultFacePackingId identifies the default face packing. /// Code that wants to use the built-in face packing that is currently /// the best option for general use, will specify ON_SubD::DefaultFacePackingId. /// @@ -14786,7 +14699,7 @@ public: const class ON_SubDFace* m_next_face = nullptr; // linked list of faces on this level private: - unsigned int m_reserved = 0; + unsigned int m_reserved1 = 0; private: // If non zero, m_pack_id identifies the packed group of faces this faces belongs to. @@ -14803,8 +14716,8 @@ private: // If faces in a quad group are removed from a subd, the pack rects for the entire subd must be recalculated. double m_pack_rect_origin[2] = {ON_DBL_QNAN}; double m_pack_rect_size[2] = {ON_DBL_QNAN}; - unsigned int m_packed_rect_u = 0; - unsigned int m_packed_rect_v = 0; + unsigned int m_reserved2 = 0; // m_packed_rect_u = 0; + unsigned int m_reserved3 = 0; // m_packed_rect_v = 0; enum PackStatusBits : unsigned char { @@ -15327,7 +15240,7 @@ public: then the texture point is set and true is returned. Otherwise, false is returned. Remarks: - To allocate texture point storage, call ON_SubD.AddFaceTexturePointStorage(this). + To allocate texture point storage, call ON_SubD.AllocateFaceTexturePoints(this). Texture points are a mutable property on ON_SubDFace. */ bool SetTexturePoint( @@ -17492,15 +17405,15 @@ public: static const ON_SubDFromMeshParameters InteriorCreases; // Create an interior sub-D crease along all input mesh double edges. - // Look for convex corners at sub-D vertices with 2 edges - // that have an included angle <= 90 degrees. + // Look for convex corners at sub-D vertices with 2 edges or fewer + // that have an included angle <= 120 degrees. static const ON_SubDFromMeshParameters ConvexCornersAndInteriorCreases; // Create an interior sub-D crease along all input mesh double edges. - // Look for convex corners at sub-D vertices with 2 edges - // that have an included angle <= 90 degrees. - // Look for concave corners at sub-D vertices with 3 edges - // that have an included angle >= 270 degrees. + // Look for convex corners at sub-D vertices with 2 edges or fewer + // that have an included angle <= 120 degrees. + // Look for concave corners at sub-D vertices with 3 edges or more + // that have an included angle >= 240 degrees. static const ON_SubDFromMeshParameters ConvexAndConcaveCornersAndInteriorCreases; /////////////////////////////////////////////////////////////////////////////////////// diff --git a/opennurbs_subd_data.h b/opennurbs_subd_data.h index a289d39d..8cfea907 100644 --- a/opennurbs_subd_data.h +++ b/opennurbs_subd_data.h @@ -2642,9 +2642,34 @@ private: Number of quads added. When all input is valid the returned value is >= 4 and equal to face->m_edge_count. */ + + + /// + /// Apply Catmull-Clark subdivision to face + /// + /// + /// + /// If bSubdividePackRect and face->PackRectIsSet() are both true + /// then the pac rect for face is subdivided and assigned to the + /// subdivision quads. + /// Note well: + /// If face is an n-gon (n != 4), the existing pack rect id will be used for the + /// first two adjacent subddividsion quads and floor((n-1)/2) new pack rect ids + /// are generated for each subsequent pair of adjacent quads. When n is odd, the last + /// new pack rect contains a single quad that is assigned a new pack id. + /// This is because pack rects MUST form rectangular regions. + /// The packed texture space assigned to the subdivided quads is identical to + /// the portion assigned before subdivision. Thus packed texture mappings will not + /// change under subdivision. + /// + /// + /// The value to use for the next new pack id if face is an n-gon + /// + /// unsigned int Internal_GlobalQuadSubdivideFace( const ON_SubDFace* face, - unsigned max_pack_id + bool bSubdividePackRect, + unsigned& next_pack_id ); public: diff --git a/opennurbs_textobject.cpp b/opennurbs_textobject.cpp index 05b099d0..0d5c2b14 100644 --- a/opennurbs_textobject.cpp +++ b/opennurbs_textobject.cpp @@ -353,16 +353,20 @@ bool ON_Text::GetTextXform( { ON_Xform tp2sxf; // Text point to view plane rotation ON_3dPoint text_point_3d = Plane().origin; - ON_3dVector text_xdir = textobjectplane.xaxis; - ON_3dVector text_ydir = textobjectplane.yaxis; - ON_3dVector text_zdir = textobjectplane.zaxis; + const ON_3dVector& text_xdir = textobjectplane.xaxis; + const ON_3dVector& text_ydir = textobjectplane.yaxis; + const ON_3dVector& text_zdir = textobjectplane.zaxis; + ON_3dVector view_xdir = view_x; + ON_3dVector view_ydir = view_y; + ON_3dVector view_zdir = view_z; if (nullptr != model_xform) { - text_xdir.Transform(*model_xform); - text_ydir.Transform(*model_xform); - text_zdir.Transform(*model_xform); + auto inverse = model_xform->Inverse(); + view_xdir.Transform(inverse); + view_ydir.Transform(inverse); + view_zdir.Transform(inverse); } - rotation_xf.Rotation(text_point_3d, text_xdir, text_ydir, text_zdir, text_point_3d, view_x, view_y, view_z); + rotation_xf.Rotation(text_point_3d, text_xdir, text_ydir, text_zdir, text_point_3d, view_xdir, view_ydir, view_zdir); text_xform_out = wcs2obj_xf * textscale_xf; text_xform_out = rotation_xf * text_xform_out; return true; diff --git a/opennurbs_texture.h b/opennurbs_texture.h index 8e9e84e0..e11ee821 100644 --- a/opennurbs_texture.h +++ b/opennurbs_texture.h @@ -268,6 +268,30 @@ public: unsigned int mapping_channel_id ); + /* + Description: + Returns true if this texture uses world coordinate system (WCS) projection + for texture mapping. + Remarks: + If this texture is used by an object that has an object coordinate system (OCS) + frame defined on a mapping channel then that OCS frame is used instead of the WCS. + Returns: + True if this texture uses WCS projection. + */ + bool IsWcsProjected() const; + + /* + Description: + Returns true if this texture uses world coordinate system (WCS) box projection + for texture mapping. + Remarks: + If this texture is used by an object that has an object coordinate system (OCS) + frame defined on a mapping channel then that OCS frame is used instead of the WCS. + Returns: + True if this texture uses WCS box projection. + */ + bool IsWcsBoxProjected() const; + // If the m_mapping_channel_id value is one of the built-in // mappings listed in the MAPPING_CHANNEL enum, then that // mapping is used. Otherwise, if an object has rendering diff --git a/opennurbs_xml.cpp b/opennurbs_xml.cpp index b4f8bc0f..d8ffe254 100644 --- a/opennurbs_xml.cpp +++ b/opennurbs_xml.cpp @@ -869,6 +869,17 @@ int ON_XMLVariant::AsInteger(void) const } } +static bool IsValidRealNumber(const ON_wString& s) +{ + if (s.ContainsNoCase(L"nan")) + return false; + + if (s.ContainsNoCase(L"in")) // ind, inf. + return false; + + return true; +} + double ON_XMLVariant::AsDouble(void) const { switch (_private->_type) @@ -877,8 +888,10 @@ double ON_XMLVariant::AsDouble(void) const case Types::Float: return _private->_float_val; case Types::Double: return _private->_double_val; case Types::Integer: return double (_private->_int_val); - case Types::String: return ON_wtof(_private->_string_val); - + case Types::String: + if (IsValidRealNumber(_private->_string_val)) + return ON_wtof(_private->_string_val); + default: return 0.0; } @@ -892,8 +905,10 @@ float ON_XMLVariant::AsFloat(void) const case Types::Float: return _private->_float_val; case Types::Double: return float(_private->_double_val); case Types::Integer: return float(_private->_int_val); - case Types::String: return float(ON_wtof(_private->_string_val)); - + case Types::String: + if (IsValidRealNumber(_private->_string_val)) + return float(ON_wtof(_private->_string_val)); + default: return 0.0f; } @@ -5278,7 +5293,7 @@ void ON_RdkDocumentDefaults::CreateXML(void) // Misc rendering settings. ON_XMLParameters p(rendering); - p.SetParam(ON_RDK_EMBED_SUPPORT_FILES_ON, true); + p.SetParam(ON_RDK_EMBED_SUPPORT_FILES_ON, true); // Only for monitoring. Not loaded. p.SetParam(ON_RDK_DITHERING_ENABLED, false); p.SetParam(ON_RDK_DITHERING_METHOD, ON_RDK_DITHERING_METHOD_FLOYD_STEINBERG); p.SetParam(ON_RDK_CUSTOM_REFLECTIVE_ENVIRONMENT, ON_nil_uuid); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 00000000..46183187 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.16) +project(ON_Test) + +# GoogleTest requires at least C++14 +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set (CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) +set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) + +include(FetchContent) +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/b10fad38c4026a29ea6561ab15fc4818170d1c10.zip +) +# For Windows: Prevent overriding the parent project's compiler/linker settings +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(googletest) + +enable_testing() + +add_subdirectory(../../opennurbs build_opennurbs) + +add_executable( + test_ClassSize + test_ClassSize.cpp +) + +target_link_libraries( + test_ClassSize + GTest::gtest_main + OpenNURBS +) + +include(GoogleTest) +gtest_discover_tests(test_ClassSize) \ No newline at end of file diff --git a/tests/Input/TestCSX.3dm b/tests/Input/TestCSX.3dm new file mode 100644 index 00000000..6d0bccee Binary files /dev/null and b/tests/Input/TestCSX.3dm differ diff --git a/tests/Input/TestClosestPoint.3dm b/tests/Input/TestClosestPoint.3dm new file mode 100644 index 00000000..8d2cfe8d Binary files /dev/null and b/tests/Input/TestClosestPoint.3dm differ diff --git a/tests/Input/TestCurveCurveIntersection.3dm b/tests/Input/TestCurveCurveIntersection.3dm new file mode 100644 index 00000000..e42b7de1 Binary files /dev/null and b/tests/Input/TestCurveCurveIntersection.3dm differ diff --git a/tests/Input/mesh_clspt_test.3dm b/tests/Input/mesh_clspt_test.3dm new file mode 100644 index 00000000..12db65ce Binary files /dev/null and b/tests/Input/mesh_clspt_test.3dm differ diff --git a/tests/TestBase64.cpp b/tests/TestBase64.cpp new file mode 100644 index 00000000..1a50dd90 --- /dev/null +++ b/tests/TestBase64.cpp @@ -0,0 +1,289 @@ +#if 0 + +#include "opennurbs.h" + + +//////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////// +// +// BEGIN TestBase64 command +// + +class CCommandTestBase64 : public CRhinoTestCommand +{ +public: + CCommandTestBase64() {} + ~CCommandTestBase64() {} + UUID CommandUUID() + { + // {2BE047C-CC30-4178-AFA5-136E79A4335C} + static const GUID TestBase64Command_UUID = + { 0x2BE047C, 0xCC30, 0x4178, { 0xAF, 0xA5, 0x13, 0x6E, 0x79, 0xA4, 0x33, 0x5C } }; + return TestBase64Command_UUID; + } + const wchar_t* EnglishCommandName() { return RHINOSTRING_COMMAND_NAME(L"TestBase64"); } + CRhinoCommand::result RunCommand( const CRhinoCommandContext& ); +}; + +// The one and only CCommandTestBase64 object +static class CCommandTestBase64 theTestBase64Command; + +class MyDecode : public ON_DecodeBase64 +{ +public: + const unsigned char* m_buf; + unsigned char* m_buf1; + int m_buf_count; + MyDecode(const unsigned char* buf, unsigned char* buf1); + void Output(); +}; + +MyDecode::MyDecode(const unsigned char* buf, unsigned char* buf1) : m_buf(buf), m_buf1(buf1), m_buf_count(0) +{ +} + +void MyDecode::Output() +{ + memcpy(m_buf1+m_buf_count,m_output,m_output_count); + m_buf_count += m_output_count; + + //for( int i = 0; i < m_output_count; i++ ) + //{ + // m_buf1[m_buf_count] = m_output[i]; + // if ( m_buf[m_buf_count++] != m_output[i] ) + // { + // m_buf_count--; + // SetError(); + // break; + // } + //} +} + +class MyEncode : public ON_EncodeBase64 +{ +public: + char* m_base64; + int m_base64_count; + MyEncode(char* p); + void Output(); +}; + +MyEncode::MyEncode(char* p) : m_base64(p), m_base64_count(0) {} + +void MyEncode::Output() +{ + if ( m_base64_count + m_output_count <= 4000 ) + { + memcpy(m_base64+m_base64_count,m_output,m_output_count); + m_base64_count += m_output_count; + m_base64[m_base64_count] = 0; + } +} + +bool Test64(const char* s, const char* e64) +{ + unsigned char buf[3000]; + char base64[4001]; + const int ec = ON_GetErrorCount(); + + const int buf_count = (int)strlen(s); + if ( buf_count > 3000 ) + return false; + + const int base64_count = 4*(buf_count/3) + ((buf_count%3)?4:0); + + memset(buf,0,sizeof(buf)); + memset(base64,0,sizeof(base64)); + MyEncode encode(base64); + MyDecode decode((unsigned char*)s,buf); + + encode.Begin(); + encode.Encode(s,buf_count); + encode.End(); + + if ( ec != ON_GetErrorCount() ) + return false; + + if ( buf_count != encode.m_encode_count ) + return false; + + if ( base64_count != encode.m_base64_count ) + return false; + + if ( strcmp(base64,e64) ) + return false; + + decode.Begin(); + decode.Decode(e64); + decode.End(); + + if ( ec != ON_GetErrorCount() ) + return false; + + if ( base64_count != decode.m_decode_count ) + return false; + + if ( buf_count != decode.m_buf_count ) + return false; + + if ( strcmp((const char*)buf,s) ) + return false; + + return true; +} + +CRhinoCommand::result CCommandTestBase64::RunCommand( const CRhinoCommandContext& context ) +{ + unsigned char buf[3000]; + unsigned char buf1[3000]; + char base64[4001]; + int ec = ON_GetErrorCount(); + int jjj; + + memset(buf,0,sizeof(buf)); + memset(base64,0,sizeof(base64)); + MyEncode encode(base64); + MyDecode decode(buf,buf1); + + { + bool b; + b = Test64("This is a test", "VGhpcyBpcyBhIHRlc3Q="); + if ( b) + b = Test64("This is a test1", "VGhpcyBpcyBhIHRlc3Qx"); + if (b) + b = Test64("This is a test12","VGhpcyBpcyBhIHRlc3QxMg=="); + if ( !b) + { + ON_ERROR("tests failed"); + } + } + + for ( int buf_count = 1; buf_count <= 64; buf_count++) + { + RhinoApp().Print("buf_count %d\n",buf_count); + const int base64_count = 4*(buf_count/3) + ((buf_count%3)?4:0); + + for ( int buf_index = 0; buf_index < buf_count; buf_index++ ) + for ( int buf_val = 0; buf_val < 256; buf_val++ ) + for ( int val_count = 1; val_count <= 5; val_count++ ) + { + int ii=-99; + ec = ON_GetErrorCount(); + for ( int attempt = 0; attempt <= 2; attempt++ ) + { + if ( attempt ) + { + int tryagain = 123; + } + + memset(buf,0,buf_count+6); + memset(base64,0,base64_count); + + for ( jjj = 0; jjj < val_count; jjj++ ) + buf[buf_index+jjj] = (buf_val+jjj)%256; + + buf1[0] = buf[0] ? 0 : 0xFF; + buf1[buf_count-1] = buf[buf_count-1] ? 0 : 0xFF; + buf1[buf_count] = buf[buf_count]; + + encode.m_base64_count = 0; + encode.Begin(); + encode.Encode(buf,buf_count); + encode.End(); + + + if ( encode.m_encode_count != buf_count ) + { + ON_ERROR("encode - encode.m_encode_count != buf_count"); + continue; + } + if ( encode.m_base64_count != base64_count ) + { + ON_ERROR("encode - encode.m_base64_count != base64_count"); + continue; + } + + for ( ii = 0; ii < base64_count; ii++ ) + { + const char* ss; + int sz; + decode.m_buf_count = 0; + decode.Begin(); + char c = base64[ii]; + + if ( ii > 0 ) + { + base64[ii] = 0; + ss = decode.Decode(base64); + if ( !ss || decode.Error() ) + { + ON_ERROR("decode - first block error"); + break; + } + sz = (int)(ss-base64); + if ( sz != ii || decode.Error() ) + { + ON_ERROR("decode - first block trauma"); + break; + } + base64[ii] = c; + } + + ss = decode.Decode(base64+ii); + if ( !ss || decode.Error() ) + { + ON_ERROR("decode - 2nd block error"); + break; + } + sz = (int)(ss-base64); + if ( sz != base64_count || decode.Error() ) + { + ON_ERROR("decode - 2nd block trauma"); + break; + } + + if ( !decode.End() ) + { + ON_ERROR("decode - error"); + break; + } + + if ( decode.m_decode_count != base64_count ) + { + ON_ERROR("decode decode.m_decode_count != base64_count"); + break; + } + + if ( memcmp(buf,buf1,buf_count+1) ) + { + ON_ERROR("decode failure"); + break; + } + + break; + + if ( ii > 8 && ii < base64_count-8 ) + { + if ( ii < base64_count/2 ) + ii = base64_count/2; + else + ii = base64_count-8; + } + } + if ( 0 == attempt && ec == ON_GetErrorCount() ) + { + break; // no errors - no need to repeat + } + } + } + } + return CRhinoCommand::success; +} + +// +// END TestBase64 command +// +//////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////// + +#endif diff --git a/tests/TestBooleanUnion.cpp b/tests/TestBooleanUnion.cpp new file mode 100644 index 00000000..c3525aae --- /dev/null +++ b/tests/TestBooleanUnion.cpp @@ -0,0 +1,67 @@ +/* $Header: /src4/opennurbs/Tests/TestBooleanUnion.cpp 1 2/11/05 6:32a Dalelear $ */ +/* $NoKeywords: $ */ + +#include "Tests.h" + +void TestBooleanUnion( + const ON_Brep* brepA, const wchar_t* nameA + const ON_Brep* brepB, const wchar_t* nameB + ) +{ + // todo test this union +} + +void TestBooleanUnion( ON_TextLog& text_log ) +{ + TEST_HEADER(text_log,"TestBooleanUnion"); + + ONX_Model model; + int i, pass; + + double sample_start_distance = 0.0; + double sample_stop_distance = 0.0; + + const char* filename = "\\src4\\opennurbs\\Tests\\Input\\TestBooleanUnion.3dm"; + //const char* filename = "\\src4\\opennurbs\\Tests\\Input\\SmallTest.3dm"; + + ON_SimpleArray brep; + ON_ClassArray brep_name; + + if ( model.Read( filename,&text_log) ) + { + brep.Reserve(model.m_object_table.Count()); + brep_name.Reserve(model.m_object_table.Count()); + for ( i = 0; i < model.m_object_table.Count(); i++ ) + { + const ON_Brep* b = ON_Brep::Cast(model.m_object_table[i].m_object); + if ( b ) + { + const wchar_t* name = model.m_object_table[i].m_attributes.m_name; + if ( 0 == name || 0 == *name) + { + name = L"anonymous"; + } + brep_name.AppendNew() = name; + brep.Append(brep); + } + } + } + + const wchar_t *nameA, *nameB; + const ON_Brep *brepA, *brepB; + for ( i = 0; i < brep.Count(); i++ ) + { + brepA = brep[i]; + nameA = brep_name[i]; + for ( j = i+1; j < brep.Count(); j++ ) + { + brepB = brep[j]; + nameB = brep_name[j]; + TestBooleanUnion( brepA,nameA,brepB,nameB); + TestBooleanUnion( brepB,nameB,brepA,nameA); + } + } + +} + + diff --git a/tests/TestClosestPoint.cpp b/tests/TestClosestPoint.cpp new file mode 100644 index 00000000..6c3a8a6e --- /dev/null +++ b/tests/TestClosestPoint.cpp @@ -0,0 +1,3670 @@ +/* $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& 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& ccx + ); + +static void CCXHelper2( const ON_CurveTreeNode* nodeA, const ON_CurveTreeNode* nodeB, + double tol3d, + ON_SimpleArray& 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& 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& ccx + ) +{ + ON_SimpleArray xxx(16); + curveA.IntersectCurve(&curveB,xxx,tol3d); + for(int i=0; im_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; kkm_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& 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& 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& 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& 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& 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(&mesh)->ComputeFaceNormals(); + } + if ( !mesh.HasVertexNormals() ) + { + const_cast(&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& 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 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 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 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& 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& 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& 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; + } + } + } +} + diff --git a/tests/TestCurveCurveIntersection.cpp b/tests/TestCurveCurveIntersection.cpp new file mode 100644 index 00000000..7d5a4347 --- /dev/null +++ b/tests/TestCurveCurveIntersection.cpp @@ -0,0 +1,709 @@ + + +#include "Tests.h" + +class CCCXTest +{ +public: + CCCXTest() : TLX0(), TLX1() { TLX0.SetCapacity(5); TLX1.SetCapacity(5); + TLX2.SetCapacity(5); TLX3.SetCapacity(5);}; + ON_3dPoint P; // curve(t) + + ON_SimpleArray TLX0; + ON_SimpleArray TLX1; + + ON_SimpleArray TLX2; + ON_SimpleArray TLX3; + + IwSolutionArray iwX0; + IwSolutionArray iwX1; + + int i0; // Indicies of crv on the array of tranlated curves + int i1; + + double t0; // these are the parameters of an intersection point + double t1; + double d; // == 0 if P = crv(t) and > 0 otherwise + + double gregt; + double tlt; + double gooset; + + double gregd; + double tld; + double goosed; +}; + +class TestCurve{ +public: + TestCurve( const ON_Curve& orig); + TestCurve( ); + ~TestCurve(); + + void SetCurve( const ON_Curve& orig); + ON_Curve* crv; + + void MakeOthers(); // Makes the Iwbs and TL_NURB and gets trees for all three. + IwBSplineCurve* Iwbs; + + int gnfrc; //getnurbform return code + + TL_NURB Nurb; + + double t0; // base parameter value +}; + +TestCurve::TestCurve( const ON_Curve& orig){ + SetCurve( orig); + gnfrc = 0; + Iwbs = NULL; +} + +TestCurve::TestCurve(){ + crv = NULL; + Iwbs = NULL; +} + +TestCurve::~TestCurve(){ + delete crv; + delete Iwbs; +} + +void TestCurve::SetCurve( const ON_Curve& orig){ + if(crv){ + delete crv; + delete Iwbs; + Iwbs = NULL; + } + crv = orig.DuplicateCurve(); +} + + +void TestCurve::MakeOthers(){ + memset(&Nurb,0,sizeof(Nurb)); + + ON_NurbsCurve nc; + gnfrc = crv->GetNurbForm(nc); + + if(gnfrc<1) + return; + + Nurb.dim = nc.m_dim; + Nurb.is_rat = nc.m_is_rat; + Nurb.order = nc.m_order; + Nurb.cv_count = nc.m_cv_count; + Nurb.cv = nc.m_cv; + Nurb.knot = nc.m_knot; + + + TL_Convert( Nurb, Iwbs ); + + // build GSLib curve tree + IwCacheMgr::GetOrCreateObjectCache(IW_OC_CURVE,Iwbs); + + // build Greg's curve tree + crv->CurveTree(); + +} + +// Get a family of translates of the curve that all go though a supplied Base Point +// On input the array contains a single curve. +// On output the aray contains a list of translates of the original that pass through the base point. +void GetCurveTranslates( const ON_Curve& C0, ON_ClassArray< TestCurve>& C, ON_3dPoint BasePoint ) +{ + int Samples_per_span=7; //7 //4; + const int MaxSamples=150; //150?? + const int MinSamples=35; + + const int span_count = C0.SpanCount(); + if (span_count < 1 ) + { + return; + } + double* span_vector = (double*)onmalloc((span_count+1)*sizeof(*span_vector)); + if ( !C0.GetSpanVector(span_vector)) + return; + + int SpanGrouping = 1; + int SpanRemainder = 0; + if( span_count * Samples_per_span< MinSamples){ + Samples_per_span = MinSamples/span_count + 1; + } + + if( span_count * Samples_per_span> MaxSamples){ + int MaxSpans = MaxSamples/ Samples_per_span +1; + SpanGrouping = span_count/MaxSpans; + SpanRemainder = span_count - SpanGrouping*MaxSpans ; + } + + for (int i = 0; i < span_count; ) + { + int j=(i==0)?0:1; + ON_Interval span_domain(span_vector[i],span_vector[i+SpanGrouping]); + i+=SpanGrouping; + if((SpanRemainder--)>0) + span_domain[1]= span_vector[++i]; + + for ( /* j already init'd*/ ; j <= Samples_per_span; j++ ) + +// TODO: use this to avoid knots +// for ( int j = 1; j < Samples_per_span; j++ ) + { + double t = span_domain.ParameterAt(((double)j)/((double)Samples_per_span)); + + ON_3dPoint P0 = C0.PointAt(t); + ON_3dVector Del = BasePoint - P0; + + TestCurve& Ctrans = C.AppendNew(); + Ctrans.SetCurve( C0); + Ctrans.crv->Translate(Del); + Ctrans.t0 = t; + Ctrans.MakeOthers(); + } + } + + onfree(span_vector); +} + +void TestCCX( ON_TextLog& text_log, + ON_ClassArray< TestCurve > & Crv0 , + ON_ClassArray< TestCurve > & Crv1, + ON_3dPoint BasePoint) +{ + double greg_time = 0.0; + double goose_time = 0.0; + double tl_time = 0.0; + + ON_ClassArray CCX_Result; + int N0 = Crv0.Count(); + int N1 = Crv1.Count(); + int N = N0 * N1; + + CCX_Result.SetCapacity(N); + CCX_Result.SetCount(N); + + // Record expected results + int i0, i1, i; + for ( i0 = 0; i0 < N0 ; i0++ ){ + i = N1*i0; + for ( i1 = 0; i1 < N1 ; i1++, i++ ) + { + CCX_Result[i].i0 = i0; + CCX_Result[i].i1 = i1; + CCX_Result[i].t0 = Crv0[i0].t0; + CCX_Result[i].t1 = Crv1[i1].t0; + } + } + const double intersection_tolerance = .01; + + //////////////////////////////////////////// + // + // Speed Test + // + + // TL's curve/curve intersection + { + TEST_ElapsedTime(); + for ( i0 = 0; i0 < N0 ; i0++ ){ + i = N1*i0; + for ( i1 = 0; i1 < N1 ; i1++, i++ ) + { + TL_CCX( *Crv0[i0].crv, *Crv1[i1].crv, intersection_tolerance, CCX_Result[i].TLX0); + TL_CCX( *Crv1[i1].crv, *Crv0[i0].crv, intersection_tolerance, CCX_Result[i].TLX1); + } + } + tl_time = TEST_ElapsedTime(); + } + + // Gregs new's curve/curve intersection +#if 0 + { + TEST_ElapsedTime(); + for ( i0 = 0; i0 < N0 ; i0++ ){ + i = N1*i0; + for ( i1 = 0; i1 < N1 ; i1++, i++ ) + { + DaleCCX( *Crv0[i0].crv, *Crv1[i1].crv, intersection_tolerance, CCX_Result[i].TLX2); + DaleCCX( *Crv1[i1].crv, *Crv0[i0].crv, intersection_tolerance, CCX_Result[i].TLX3); + } + } + greg_time = TEST_ElapsedTime(); + } +#endif + + // Get a GSLib curve + { + IwStatus iw_rc; + IwPoint3d Q; + + TEST_ElapsedTime(); + for ( i0 = 0; i0 < N0 ; i0++ ){ + i = N1*i0; + for ( i1 = 0; i1 < N1 ; i1++, i++ ){ + + ON_Interval D= Crv0[i0].crv->Domain(); + IwExtent1d Dom0(D.m_t[0],D.m_t[1]); + D= Crv1[i1].crv->Domain(); + IwExtent1d Dom1(D.m_t[0],D.m_t[1]); + + iw_rc = Crv0[i0].Iwbs->GlobalCurveIntersect( + Dom0, + *Crv1[i1].Iwbs, + Dom1, + intersection_tolerance, + CCX_Result[i].iwX0 ); + + if ( iw_rc == IW_SUCCESS && CCX_Result[i].iwX0.GetSize() > 0) + { + //rc = (sSolutions.GetSize() > 1 ) ? TL_SUCCESS+8 : TL_SUCCESS; + //t = X.m_vStart[0]; + } + else { + TL_ERROR("GSLib GlobalCurveIntersect failed"); + } + + iw_rc = Crv1[i1].Iwbs->GlobalCurveIntersect( + Dom1, + *Crv0[i0].Iwbs, + Dom0, + intersection_tolerance, + CCX_Result[i].iwX1 ); + + if ( iw_rc == IW_SUCCESS && CCX_Result[i].iwX1.GetSize() > 0) + { + //rc = (sSolutions.GetSize() > 1 ) ? TL_SUCCESS+8 : TL_SUCCESS; + //t = X.m_vStart[0]; + } + else { + TL_ERROR("GSLib GlobalCurveIntersect failed"); + } + + + } + } + goose_time = TEST_ElapsedTime(); + } + + + //////////////////////////////////////////// + // + // Accuracy Test + // + + + ON_Interval Dom[2]; + bool IsClosed[2]; + Dom[0]= Crv0[0].crv->Domain(); + Dom[1] = Crv1[0].crv->Domain(); + IsClosed[0]=Crv0[0].crv->IsClosed()!=0; + IsClosed[1]=Crv1[0].crv->IsClosed()!=0; + + double min_time = tl_time; + + double tl_error3d =0; + int tl_int_point_cnt=0; + int tl_inoverlap_cnt=0; + + double goose_error3d =0; + int goose_int_point_cnt=0; + int goose_inoverlap_cnt=0; + + double greg_error3d =0; + int greg_int_point_cnt=0; + int greg_inoverlap_cnt=0; + + for(i=0; i=0){ + tl_error3d += bestdist0; + tl_int_point_cnt++; + } + else { + NoGoodResult = true; + } + if(TLInOverlap1 ) + tl_inoverlap_cnt++; + else if( jbest1>=0){ + tl_error3d += bestdist1; + tl_int_point_cnt++; + } + else { + NoGoodResult = true; + } + if(NoGoodResult){ + NoGoodResult = true; // break here + } + + + // Test accuracy of GregCCX results + // 3d error to BasePoint + jbest0=-1; + jbest1=-1; + bestdist1 = ON_DBL_MAX; + bool GregInOverlap0=false; // set true if the ideal intersection point is + // contained in an overlap + bool GregInOverlap1=false; // set true if the ideal intersection point is + // contained in an overlap + for( j=0; j=0){ + greg_error3d += bestdist0; + greg_int_point_cnt++; + } + else { + NoGoodResult = true; + } + if(GregInOverlap1 ) + greg_inoverlap_cnt++; + else if( jbest1>=0){ + greg_error3d += bestdist1; + greg_int_point_cnt++; + } + else { + NoGoodResult = true; + } + if(NoGoodResult){ + NoGoodResult = true; // break here + } + + bool GooseInOverlap0=false; + bool GooseInOverlap1=false; + + jbest0 = -1; + bestdist0 = ON_DBL_MAX; + dist = ON_DBL_MAX; + int count = Test.iwX0.GetSize(); + for( j=0; jPointAt(t0); + ON_3dPoint PB = Crv1[Test.i1].crv->PointAt(t1); + dist = BasePoint.DistanceTo( PA) + + BasePoint.DistanceTo( PB); + if(distPointAt(t0); + ON_3dPoint PB = Crv1[Test.i1].crv->PointAt(t1); + dist = BasePoint.DistanceTo( PA) + + BasePoint.DistanceTo( PB); + if(dist=0){ + goose_error3d += bestdist0; + goose_int_point_cnt++; + } + else + NoGoodResult = true; + + if( GooseInOverlap1) + goose_inoverlap_cnt++; + else if(jbest1>=0){ + goose_error3d += bestdist1; + goose_int_point_cnt++; + } + else + NoGoodResult = true; + + + if(NoGoodResult){ + int jjj=7777; // break here + } + } + + + if ( goose_time < min_time ) + min_time = goose_time; + if ( 0.0 == min_time ) + min_time = 1.0/((double)CLOCKS_PER_SEC); + text_log.Print("Number of global curve curve intersection tests %d\n",N); + text_log.Print("Code: Perfection TL Goose Greg\n"); + text_log.Print("Relative Time: 1.00X %5.2fX %5.2fX %5.2fX\n", tl_time/min_time, goose_time/min_time, greg_time/min_time); + text_log.Print("Absolute Time: 0.0 secs %6.3f secs %6.3f secs %6.3f secs\n", tl_time, goose_time, greg_time); + + text_log.Print("#Points/Overlap: %d/0 %d/%d %d/%d %d/%d \n", 2*N, + tl_int_point_cnt, tl_inoverlap_cnt, + goose_int_point_cnt, goose_inoverlap_cnt, + greg_int_point_cnt, greg_inoverlap_cnt ); + if(tl_int_point_cnt>0 && goose_int_point_cnt>0 && greg_int_point_cnt>0) + text_log.Print("Avg Error: 0.00 %.2g %.2g %.2g \n", tl_error3d/ tl_int_point_cnt, + goose_error3d/goose_int_point_cnt , + greg_error3d/ greg_int_point_cnt ); +} + +void TestCurveCurveIntersection( const ONX_Model& model, ON_TextLog& text_log ) +{ + TEST_HEADER(text_log,"TestCurveCurveIntersection"); + + int i; + + double sample_start_distance = 0.0; + double sample_stop_distance = 0.0; + + ON_ClassArray< ON_ClassArray< TestCurve> > Crv; + ON_ClassArray Name; // Input curve names + + // Base Point for all intersections + ON_3dPoint BasePoint(23.74,-394,15); + int ci=0; + + if ( model.IsValid() ) + { + 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 ) + { + const wchar_t* name = model.m_object_table[i].m_attributes.m_name; + if ( 0 == name || 0 == *name) + { + name = L"anonymous"; + } + Name.Append(name); + + ON_ClassArray& CrvFamily = Crv.AppendNew(); + + GetCurveTranslates(*curve, CrvFamily, BasePoint); + ci++; + + } + } + } + + ON_SimpleArray permute( Name.Count() ); + permute.SetCount( Name.Count()); + Name.Sort( ON::heap_sort, permute, ON_CompareIncreasing ); + + for( int ci=0; ciDump(log); + + int si = dump.Find('\n'); + if(si>0){ + string = dump.Left(si); + dump = dump.Right( dump.Length()-si-1); + si = dump.Find('\n'); + if( si>0) + string += dump.Left(si); + } + text_log.Print(L"Curve %d: name = %s, %s\n",ci, Name[permute[ci]], + string); + text_log.Print("\n"); + } + + for(int i0=0; i0 0; n *= 2 ) + { + w *= 0.5; + for ( j = 1; j < n; j += 2 ) + { + s = j; + s *= w; + t = overlap_domain.ParameterAt(s); + C = curve->PointAt(t); + uv.Set(ON_UNSET_VALUE,ON_UNSET_VALUE); + surface->GetClosestPoint(C,&uv.x,&uv.y); + S = surface->PointAt(uv.x,uv.y); + d = C.DistanceTo(S); + if ( d > maxd ) + { + maxd = d; + } + sample_count--; + } + } + + return maxd; +} + +static +int GetGooseCurveSurfaceIntersection( + const ON_Curve* curve, + const ON_Surface* surface, + const IwBSplineCurve* goose_curve, + const IwBSplineSurface* goose_surface, + double intersection_tolerance, + double overlap_tolerance, + double test_tolerance, + ON_SimpleArray& goose_x + ) +{ + if ( !goose_curve || !goose_surface ) + return 1; + + ON_3dPoint Cpt[2], Spt[2]; + int bogus_events_from_old_csx = 0; + IwSolutionArray G( 100 ); + goose_surface->GlobalCurveIntersect( + goose_surface->GetNaturalUVDomain(), + *goose_curve, + goose_curve->GetNaturalInterval(), + intersection_tolerance, + G + ); + + unsigned int gi; + int i; + double d; + for ( gi = 0; gi < G.GetSize(); gi++ ) + { + int closest_i = -1; + double closest_d = 1.0e300; + + IwSolution& g = G[gi]; + + ON_X_EVENT goose_event; + IwSolutionType iw_xtype = g.m_eSolutionType; + IwPoint3d iwpt; + if ( iw_xtype == IW_ST_RANGE_OF_VALUES && g.m_vStart[0] >= g.m_vEnd[0] ) + { + iw_xtype = IW_ST_SINGLE_VALUE; + g.m_vEnd[0] = g.m_vStart[0]; + g.m_vEnd[1] = g.m_vStart[1]; + g.m_vEnd[2] = g.m_vStart[2]; + } + + switch ( iw_xtype ) + { + case IW_ST_SINGLE_VALUE: + // isolated intersection point + goose_event.m_type = ON_X_EVENT::csx_point; + + goose_event.m_a[0] = goose_event.m_a[1] = g.m_vStart[0]; + goose_event.m_b[0] = goose_event.m_b[2] = g.m_vStart[1]; + goose_event.m_b[1] = goose_event.m_b[3] = g.m_vStart[2]; + + goose_curve->EvaluatePoint(goose_event.m_a[0],iwpt); + goose_event.m_A[0].Set(iwpt.x,iwpt.y,iwpt.z); + goose_event.m_A[1] = goose_event.m_A[0]; + + goose_surface->EvaluatePoint(IwPoint2d(goose_event.m_b[0],goose_event.m_b[1]),iwpt); + goose_event.m_B[0].Set(iwpt.x,iwpt.y,iwpt.z); + goose_event.m_B[1] = goose_event.m_B[0]; + break; + + case IW_ST_RANGE_OF_VALUES: + // overlap region + goose_event.m_type = ON_X_EVENT::csx_overlap; + + goose_event.m_a[0] = g.m_vStart[0]; + goose_event.m_a[1] = g.m_vEnd[0]; + goose_event.m_b[0] = g.m_vStart[1]; + goose_event.m_b[1] = g.m_vStart[2]; + goose_event.m_b[2] = g.m_vEnd[1]; + goose_event.m_b[3] = g.m_vEnd[2]; + + goose_curve->EvaluatePoint(goose_event.m_a[0],iwpt); + goose_event.m_A[0].Set(iwpt.x,iwpt.y,iwpt.z); + goose_curve->EvaluatePoint(goose_event.m_a[1],iwpt); + goose_event.m_A[1].Set(iwpt.x,iwpt.y,iwpt.z); + + goose_surface->EvaluatePoint(IwPoint2d(goose_event.m_b[0],goose_event.m_b[1]),iwpt); + goose_event.m_B[0].Set(iwpt.x,iwpt.y,iwpt.z); + goose_surface->EvaluatePoint(IwPoint2d(goose_event.m_b[2],goose_event.m_b[3]),iwpt); + goose_event.m_B[1].Set(iwpt.x,iwpt.y,iwpt.z); + break; + } + for ( i = 0; i < 2; i++ ) + { + if ( i && ON_X_EVENT::csx_overlap != goose_event.m_type ) + { + goose_event.m_a[1] = goose_event.m_a[0]; + goose_event.m_b[2] = goose_event.m_b[0]; + goose_event.m_b[3] = goose_event.m_b[1]; + goose_event.m_nodeA_t[1] = goose_event.m_nodeA_t[0]; + goose_event.m_nodeB_t[2] = goose_event.m_nodeB_t[0]; + goose_event.m_nodeB_t[3] = goose_event.m_nodeB_t[1]; + } + else + { + goose_event.m_nodeA_t[i] = goose_event.m_a[i]; + curve->GetCurveParameterFromNurbFormParameter(goose_event.m_a[i],&goose_event.m_a[i]); + goose_event.m_nodeB_t[2*i] = goose_event.m_b[2*i]; + goose_event.m_nodeB_t[2*i+1] = goose_event.m_b[2*i+1]; + surface->GetSurfaceParameterFromNurbFormParameter(goose_event.m_b[2*i],goose_event.m_b[2*i+1],&goose_event.m_b[2*i],&goose_event.m_b[2*i+1]); + if ( i && goose_event.m_a[1] <= goose_event.m_a[0] ) + { + // micro overlap?? + bogus_events_from_old_csx++; + ON_X_EVENT::CopyEventPart(goose_event,0,goose_event,1); + goose_event.m_type = ON_X_EVENT::csx_point; + } + } + } + + d = goose_event.m_A[0].DistanceTo(goose_event.m_B[0]); + if ( d > test_tolerance ) + { + bogus_events_from_old_csx++; + ON_X_EVENT::CopyEventPart(goose_event,1,goose_event,0); + goose_event.m_type = ON_X_EVENT::csx_point; + } + + d = goose_event.m_A[1].DistanceTo(goose_event.m_B[1]); + if ( d > test_tolerance ) + { + bogus_events_from_old_csx++; + if ( goose_event.m_a[0] == goose_event.m_a[1] ) + continue; + ON_X_EVENT::CopyEventPart(goose_event,0,goose_event,1); + goose_event.m_type = ON_X_EVENT::csx_point; + } + + // validate goose_event + Cpt[0] = curve->PointAt(goose_event.m_a[0]); + Spt[0] = surface->PointAt(goose_event.m_b[0],goose_event.m_b[1]); + Cpt[1] = curve->PointAt(goose_event.m_a[1]); + Spt[1] = surface->PointAt(goose_event.m_b[2],goose_event.m_b[3]); + d = Cpt[0].DistanceTo(Spt[0]); + if ( d > test_tolerance ) + { + bogus_events_from_old_csx++; + ON_X_EVENT::CopyEventPart(goose_event,1,goose_event,0); + goose_event.m_type = ON_X_EVENT::csx_point; + Cpt[0] = Cpt[1]; + Spt[0] = Spt[1]; + } + + d = Cpt[1].DistanceTo(Spt[1]); + if ( d > test_tolerance ) + { + bogus_events_from_old_csx++; + if ( goose_event.m_a[0] == goose_event.m_a[1] ) + continue; + ON_X_EVENT::CopyEventPart(goose_event,0,goose_event,1); + goose_event.m_type = ON_X_EVENT::csx_point; + } + + if ( ON_X_EVENT::csx_overlap == goose_event.m_type ) + { + d = TestOverlapDistance(ON_Interval(goose_event.m_a[0],goose_event.m_a[1]), + curve,surface,120); + if ( d > test_tolerance ) + { + bogus_events_from_old_csx++; + goose_event.m_type = ON_X_EVENT::csx_point; + ON_X_EVENT& e0 = goose_x.AppendNew(); + e0 = goose_event; + ON_X_EVENT::CopyEventPart( e0, 0, e0, 1 ); + e0.m_type = ON_X_EVENT::csx_point; + ON_X_EVENT::CopyEventPart( goose_event, 1, goose_event, 0 ); + goose_event.m_type = ON_X_EVENT::csx_point; + } + } + + goose_x.Append(goose_event); + } + + return bogus_events_from_old_csx; +} + +static int TestCSXCompareWithGoose( + ON_TextLog& text_log, + CTestCurve& test_curve, + CTestSurface& test_surface, + const ON_SimpleArray& x, + int overlap_sample_count, + double intersection_tolerance, + double overlap_tolerance + ) +{ + int gi; + int j, xi; + double d; + + int missed_event_count = 0; + + intersection_tolerance = ON_X_EVENT::IntersectionTolerance(intersection_tolerance); + overlap_tolerance = ON_X_EVENT::OverlapTolerance(intersection_tolerance,overlap_tolerance); + // testing accuracy + const double test_tolerance = 1.01*intersection_tolerance; + + const ON_Curve* curve = test_curve.m_curve; + const ON_Surface* surface = test_surface.m_surface; + const IwBSplineCurve* goose_curve = test_curve.GooseCurve(); + const IwBSplineSurface* goose_surface = test_surface.GooseSurface(); + + + ON_SimpleArray goose_x(16 + x.Count()); + int bogus_events_from_old_csx = GetGooseCurveSurfaceIntersection( + curve,surface,goose_curve,goose_surface, + intersection_tolerance,overlap_tolerance,test_tolerance, + goose_x + ); + + for ( gi = 0; gi < goose_x.Count(); gi++ ) + { + ON_X_EVENT& goose_event = goose_x[gi]; + for ( j = 0; j < 2; j++ ) + { + if ( j && ON_X_EVENT::csx_overlap != goose_event.m_type) + break; + + double closest_d = 1.0e300; + int closest_i = -1; + + // find the csx event that is closest to goose_event.m_A[j] + for ( xi = 0; xi < x.Count() && closest_d > test_tolerance; xi++ ) + { + const ON_X_EVENT& e = x[xi]; + d = goose_event.m_A[j].DistanceTo(e.m_A[0]); + if ( d < closest_d ) + { + closest_d = d; + closest_i = xi; + } + + if ( ON_X_EVENT::csx_overlap == e.m_type ) + { + if ( d <= test_tolerance && 0 == j && goose_event.m_a[0] < e.m_a[0] ) + { + goose_event.m_a[0] = e.m_a[0]; + } + + d = goose_event.m_A[j].DistanceTo(e.m_A[1]); + if ( d < closest_d ) + { + closest_d = d; + closest_i = xi; + } + if ( d <= test_tolerance && 1 == j && goose_event.m_a[1] > e.m_a[1] ) + { + goose_event.m_a[1] = e.m_a[1]; + } + + if ( e.m_a[0] <= goose_event.m_a[j] && goose_event.m_a[j] <= e.m_a[1] ) + { + // goose event is inside an overlap region + d = test_tolerance; + if ( d < closest_d ) + { + closest_d = d; + closest_i = xi; + } + } + } + } + + if ( closest_d > test_tolerance ) + { + ON_ERROR("ON_Curve::IntersectSurface() missed a point"); + text_log.Print("ERROR: Missed an intersection event at curve(%g) = ",goose_event.m_a[j]); + text_log.Print(goose_event.m_A[j]); + text_log.Print("\n"); + missed_event_count++; + } + } + + if ( ON_X_EVENT::csx_overlap == goose_event.m_type && goose_event.m_a[0] < goose_event.m_a[1] ) + { + // make sure we found this overlap + for ( xi = 0; xi < x.Count(); xi++ ) + { + const ON_X_EVENT& e = x[xi]; + if ( e.m_a[0] <= goose_event.m_a[0] && goose_event.m_a[1] <= e.m_a[1] ) + { + break; + } + + if ( goose_event.m_a[1] - goose_event.m_a[0] <= 1.0e-3 + && e.m_A[0].DistanceTo( goose_event.m_A[0] ) <= test_tolerance + && e.m_A[0].DistanceTo( goose_event.m_A[1] ) <= test_tolerance + && e.m_A[0].DistanceTo( curve->PointAt(0.5*(goose_event.m_a[0]+goose_event.m_a[1])) ) <= test_tolerance + ) + { + // bogus goose micro overlap + break; + } + } + + if ( xi >= x.Count() ) + { + ON_ERROR("ON_Curve::IntersectSurface() missed an overlap."); + text_log.Print("ERROR: Missed an itersection event on curve(%g to %g) = \n",goose_event.m_a[0],goose_event.m_a[1]); + text_log.PushIndent(); + text_log.Print(goose_event.m_A[0]); text_log.Print(" to "); + text_log.Print(goose_event.m_A[1]); + text_log.PopIndent(); + text_log.Print("\n"); + missed_event_count++; + } + } + } + + if ( bogus_events_from_old_csx > 0 ) + { + ON_WARNING("Old TL_CSX had bogus events - make sure TestCSXCompareWithGoose() is working right."); + text_log.Print("ALERT Old TL_CSX had bogus events - make sure TestCSXCompareWithGoose() is working right.\n\n"); + } + + return missed_event_count; +} + +static int TestCSX( ON_TextLog& text_log, + CTestCurve& test_curve, + CTestSurface& test_surface, + int* missed_event_count + ) +{ + int i; + + const ON_Curve* curve = test_curve.m_curve; + + const ON_Surface* surface = test_surface.m_surface; + + *missed_event_count = 0; + + const int overlap_sample_count = bPurify ? 3 : 255; + + const double intersection_tolerance = 0.0; + const double overlap_tolerance = 0.0; + + const ON_CurveTree* ctree = curve->CurveTree(); + if ( !ctree ) + { + text_log.Print("ERROR - curve->CurveTree() = NULL\n"); + return 0; + } + + const ON_SurfaceTree* stree = surface->SurfaceTree(); + if ( !stree ) + { + text_log.Print("ERROR - surface->SurfaceTree() = NULL\n"); + return 0; + } + + ON_SimpleArray x(16); + curve->IntersectSurface(surface,x,intersection_tolerance,overlap_tolerance); + + text_log.Print("%d curve-surface intersection events.\n",x.Count()); + text_log.PushIndent(); + + // dump intersection information + ON_String saved_double_format; + text_log.GetDoubleFormat(saved_double_format); + text_log.SetDoubleFormat("%g"); // so diffs don't flag minute changes + for ( i = 0; i < x.Count(); i++ ) + { + text_log.Print("xevent[%d]:\n",i); + text_log.PushIndent(); + x[i].Dump(text_log); + text_log.PopIndent(); + } + text_log.SetDoubleFormat(saved_double_format); + + // data structure validation + if ( x.Count() > 0 ) + { + text_log.Print("Validating intersection events ...\n"); + text_log.PushIndent(); + bool bIsValid = ON_X_EVENT::IsValidList( + x.Count(), + x.Array(), + &text_log, + intersection_tolerance, + overlap_tolerance, + curve,0, + 0,0, + surface,0,0 + ); + text_log.PopIndent(); + if ( bIsValid ) + { + text_log.Print("... Event is list valid.\n"); + } + } + + if ( !bPurify ) + { + // Compare results with goose to see if there are any missed events. + // (This really slow's down the purify tests - so its skipped in purify builds.) + i = TestCSXCompareWithGoose( + text_log, + test_curve, + test_surface, + x, + overlap_sample_count, + intersection_tolerance, + overlap_tolerance + ); + + *missed_event_count = *missed_event_count + i; + } + + text_log.PopIndent(); + + return x.Count(); +} + +// tool for saving problem cases in file that has a single curve and surface. +bool SaveCSXTest( const char* filename, + const ON_Curve* curve, + const ON_Surface* surface + ) +{ + bool rc = false; + + if ( curve && surface && filename && filename[0] ) + { + ONX_Model model; + + ONX_Model_Object& curve_object = model.m_object_table.AppendNew(); + curve_object.m_object = curve; + curve_object.m_bDeleteObject = false; + + ONX_Model_Object& surface_object = model.m_object_table.AppendNew(); + surface_object.m_object = surface; + surface_object.m_bDeleteObject = false; + + model.Polish(); + rc = model.Write(filename); + } + + return rc; +} + + +static void TestCurveSurfaceIntersectionListObjects( CTestModel& model, ON_TextLog& text_log ) +{ + const int curve_count = model.m_curves.Count(); + const int surface_count = model.m_surfaces.Count(); + + int i; + + const wchar_t* name; + ON_UUID curve_uuid, surface_uuid; + + text_log.Print("%d curves:\n",curve_count); + text_log.PushIndent(); + for ( i = 0; i < curve_count; i++ ) + { + CTestCurve& C = model.m_curves[i]; + curve_uuid = C.m_uuid; + name = C.m_name; + if ( !name || !name[0] ) + name = L"anonymous curve"; + const ON_NurbsCurve& nurbs_curve = C.NurbsCurve(); + text_log.Print("curve[%d] %s\n",i,C.m_curve->ClassId()->ClassName()); + text_log.PushIndent(); + text_log.Print("id: "); text_log.Print(curve_uuid); text_log.Print("\n"); + text_log.Print(L"name: %s\n",name); + text_log.Print("degree = %d, %s, CV count=%d\n", + nurbs_curve.m_order-1, + (nurbs_curve.m_is_rat ? L"rational" : L"non-rational"), + nurbs_curve.m_cv_count + ); + text_log.PopIndent(); + } + text_log.PopIndent(); + + + text_log.Print("\n"); + text_log.Print("%d surfaces:\n",surface_count); + text_log.PushIndent(); + for ( i = 0; i < surface_count; i++ ) + { + CTestSurface& S = model.m_surfaces[i]; + surface_uuid = S.m_uuid; + name = S.m_name; + if ( !name || !name[0] ) + name = L"anonymous surface"; + const ON_NurbsSurface& nurbs_surface = S.NurbsSurface(); + text_log.Print("surface[%d] %s\n",i,S.m_surface->ClassId()->ClassName()); + text_log.PushIndent(); + text_log.Print("id: "); text_log.Print(surface_uuid); text_log.Print("\n"); + text_log.Print(L"name: %s\n",name); + text_log.Print(L"degree = (%d,%d), %s, CV count=(%d,%d)\n", + 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] + ); + text_log.PopIndent(); + + } + text_log.PopIndent(); +} + + +static int TestCurveSurfaceIntersectionAccuracy( CTestModel& model, ON_TextLog& text_log ) +{ + const int curve_count = model.m_curves.Count(); + const int surface_count = model.m_surfaces.Count(); + + ON_UUID curve_uuid, surface_uuid; + + int total_missed_event_count = 0; + int missed_event_count; + + int i, j; + + bool bSaveExample = false; + + int event_count = 0; + + + int bad_curve_index = -1; + int bad_surface_index = -1; + + // intersect every curve with every surface + for ( i = ( bad_curve_index>=0 ? bad_curve_index : 0); i < curve_count; i++ ) + { + CTestCurve& test_curve = model.m_curves[i]; + curve_uuid = test_curve.m_uuid; + + for ( j = (bad_surface_index>=0 ? bad_surface_index : 0); j < surface_count; j++ ) + { + CTestSurface& test_surface = model.m_surfaces[j]; + surface_uuid = test_surface.m_uuid; + + if ( bad_curve_index != -1 && bad_surface_index != -1 ) + { + // use debugger to set bSaveExample = true + ON_String filename; + filename.Format("C:\\csx_bug_%03d_%03d.3dm",bad_curve_index,bad_surface_index); + SaveCSXTest( filename.Array(), test_curve.m_curve, test_surface.m_surface ); + printf("Saved curve[%d] and surface[%d] in %s\n",i,j,filename.Array()); + } + + text_log.Print("curve[%d] ",i); text_log.Print(curve_uuid); text_log.Print("\n"); + text_log.Print("surface[%d] ",j); text_log.Print(surface_uuid); text_log.Print("\n"); + text_log.PushIndent(); + missed_event_count = 0; + event_count += TestCSX( text_log, test_curve, test_surface, &missed_event_count ); + total_missed_event_count += missed_event_count; + text_log.PopIndent(); + text_log.Print("\n"); + + if ( bad_surface_index == j ) + break; + } + + if ( bad_curve_index == i ) + break; + + } + + return total_missed_event_count; +} + +static void TestCurveSurfaceIntersectionSpeed( CTestModel& model, ON_TextLog& text_log ) +{ + const int curve_count = model.m_curves.Count(); + const int surface_count = model.m_surfaces.Count(); + + int i, j; + + // pre-build GSLib trees + int c_count = 0; + for( i = 0; i < curve_count; i++ ) + { + CTestCurve& C = model.m_curves[i]; + const IwBSplineCurve* goose_curve = C.GooseCurve(); + if ( goose_curve ) + { + IwCacheMgr::GetOrCreateObjectCache(IW_OC_CURVE,goose_curve); + c_count++; + } + } + + int s_count = 0; + for ( j = 0; j < surface_count; j++ ) + { + CTestSurface& S = model.m_surfaces[j]; + const IwBSplineSurface* goose_surface = S.GooseSurface(); + if (goose_surface ) + { + s_count++; + IwCacheMgr::GetOrCreateObjectCache(IW_OC_SURFACE,goose_surface); + } + } + + double intersection_tolerance = 0.001; + double dale_time = 0.0; + double goose_time = 0.0; + double min_time = 0.0; + double delta_time; + + int n_count = 0; + int dale_event_count = 0; + int goose_event_count = 0; + + if ( c_count > 0 && s_count > 0 ) + { + n_count = 1; +#if !defined(ON_DEBUG) + while ( n_count*c_count*s_count < 20000 ) + { + n_count++; + } +#endif + int n, test; + + // test 0 = dale, 1 = goose + for ( test = 0; test < 2; test++ ) + { + if ( 1==test && model.m_bPurify ) + { + n_count = 1; + goose_time = dale_time = 0.0; + break; + } + + TEST_ElapsedTime(); + for ( n = 0; n < n_count; n++ ) + { + for( i = 0; i < curve_count; i++ ) + { + CTestCurve& C = model.m_curves[i]; + const ON_Curve* curve = C.m_curve; + const IwBSplineCurve* goose_curve = C.m_goose_curve; + if ( 0 == goose_curve ) + continue; + + for ( j = 0; j < surface_count; j++ ) + { + CTestSurface& S = model.m_surfaces[j]; + const ON_Surface* surface = S.m_surface; + const IwBSplineSurface* goose_surface = S.m_goose_surface; + if ( 0 == goose_surface ) + continue; + + if (test) + { + // goose + IwSolutionArray x(16); + goose_surface->GlobalCurveIntersect( + goose_surface->GetNaturalUVDomain(), + *goose_curve, + goose_curve->GetNaturalInterval(), + intersection_tolerance, + x + ); + if ( !n ) + goose_event_count += x.GetSize(); + } + else + { + // dale + ON_SimpleArray x(16); + curve->IntersectSurface( surface, x, intersection_tolerance ); + if ( !n ) + dale_event_count += x.Count(); + } + } + } + } + delta_time = TEST_ElapsedTime(); + + switch(test) + { + case 0: + dale_time = delta_time; + break; + case 1: + goose_time = delta_time; + break; + } + } + } + + min_time = (dale_time < goose_time) ? dale_time : goose_time; + if ( min_time <= 0.0 ) + min_time = 1.0; + + text_log.Print("%d repetitions of %d intersection tests.\n",n_count,c_count*s_count); + text_log.Print("Test Dale Goose\n"); + if ( model.m_bPurify ) + { + } + else + { + text_log.Print("Seconds %6.2f %6.2f\n",dale_time,goose_time); + text_log.Print("Relative %5.2fX %5.2fX\n",dale_time/min_time,goose_time/min_time); + } + text_log.Print("Events %5d %5d\n",dale_event_count,goose_event_count); +} + +void TestCurveSurfaceIntersection( CTestModel& model, ON_TextLog& text_log ) +{ + TEST_HEADER(text_log,"TestCurveSurfaceIntersection"); + + bool bListObjects = true; + bool bAccuracyTests = true; + bool bSpeedTests = true; + + bPurify = model.m_bPurify; + +#if defined(_DEBUG) + bSpeedTests = false; +#endif; + + int total_missed_event_count = 0; + + if ( bListObjects && !bPurify ) + { + text_log.Print("\n"); + text_log.Print("\n"); + text_log.Print("Curve-Surfaces Intersection Objects:\n"); + text_log.Print("\n"); + text_log.PushIndent(); + TestCurveSurfaceIntersectionListObjects( model, text_log ); + text_log.PopIndent(); + } + + if ( bAccuracyTests ) + { + text_log.Print("\n"); + text_log.Print("\n"); + text_log.Print("Curve-Surface Intersection Accuracy Tests:\n"); + text_log.Print("\n"); + text_log.PushIndent(); + total_missed_event_count = TestCurveSurfaceIntersectionAccuracy( model, text_log ); + text_log.PopIndent(); + } + + if ( bSpeedTests && !bPurify ) + { + text_log.Print("\n"); + text_log.Print("\n"); + text_log.Print("Curve-Surface Intersection Speed Tests:\n"); + text_log.Print("\n"); + text_log.PushIndent(); + TestCurveSurfaceIntersectionSpeed(model,text_log ); + text_log.PopIndent(); + } + + if ( total_missed_event_count > 0 ) + { + text_log.Print( + "Opennurbs may have missed up to %d intersection events \n" + "and these potential misses are counted as errors below.\n", + total_missed_event_count + ); + } +} + + diff --git a/tests/TestNurbsCageEvaluate.cpp b/tests/TestNurbsCageEvaluate.cpp new file mode 100644 index 00000000..5586f7e7 --- /dev/null +++ b/tests/TestNurbsCageEvaluate.cpp @@ -0,0 +1,191 @@ +#include "../opennurbs.h" +#include "../opennurbs_extensions.h" + +#include "Tests.h" + +static +bool TestNurbsCageEvaluateHelper( ON_TextLog& text_log, const ON_NurbsCage& cage ) +{ + ON_Workspace ws; + + int err_count = 0; + int pass_count = 0; + bool ok = true; + const int der_count = 3; // <= 3 + const int v_stride = 3; + ON_NurbsSurface srfst, srfrs, srfrt; + int cage_hint[3] = {0,0,0}; + int srfst_hint[2] = {0,0}; + int srfrs_hint[2] = {0,0}; + int srfrt_hint[2] = {0,0}; + ON_3dVector cageV,stV,rsV,rtV; + + ON_3dVector *cagev, *stv, *rsv, *rtv; + + cagev = (ON_3dVector*)onmalloc(sizeof(*cagev)*(der_count+1)*(der_count+2)*(der_count+3)/6); + stv = (ON_3dVector*)onmalloc(sizeof(*stv)*(der_count+1)*(der_count+2)/2); + rsv = (ON_3dVector*)onmalloc(sizeof(*rsv)*(der_count+1)*(der_count+2)/2); + rtv = (ON_3dVector*)onmalloc(sizeof(*rtv)*(der_count+1)*(der_count+2)/2); + + int i,j,k,ii,jj,kk,di,dj,dk,dc; + double r,s,t,errst,errrt,errrs,tol; + for ( i = cage.m_order[0]-2; ok&&i < cage.m_cv_count[0]-1; i++ ) + { + ON_Interval rspan(cage.m_knot[0][i],cage.m_knot[0][i+1]); + if ( rspan.Length() <= 0.0 ) + continue; + for ( ii = 0; ok && ii <= 4; ii++ ) + { + r = rspan.ParameterAt(0.25*ii); + if (!cage.IsoSurface(0,r,&srfst)) + { + err_count++; + ON_ERROR("ON_NurbsCage::IsoSurface(dir=1,...) failed"); + ok = false; + } + + for ( j = cage.m_order[1]-2; ok && j < cage.m_cv_count[1]-1; j++ ) + { + ON_Interval sspan(cage.m_knot[1][j],cage.m_knot[1][j+1]); + if ( sspan.Length() <= 0.0 ) + continue; + for ( jj = 0; ok && jj <= 4; jj++ ) + { + s = sspan.ParameterAt(0.25*jj); + if ( !cage.IsoSurface(1,s,&srfrt) ) + { + err_count++; + ON_ERROR("ON_NurbsCage::IsoSurface(dir=1,...) failed"); + ok = false; + } + + for ( k = cage.m_order[2]-2; ok && k < cage.m_cv_count[2]-1; k++ ) + { + ON_Interval tspan(cage.m_knot[2][k],cage.m_knot[2][k+1]); + if ( tspan.Length() <= 0.0 ) + continue; + for ( kk = 0; ok && kk <= 4; kk++ ) + { + t = tspan.ParameterAt(0.25*kk); + if( !cage.IsoSurface(2,t,&srfrs) ) + { + err_count++; + ON_ERROR("ON_NurbsCage::IsoSurface(dir=2,...) failed"); + ok = false; + } + + if ( !cage.Evaluate(r,s,t,der_count,v_stride,&cagev[0].x,0,cage_hint)) + { + err_count++; + ON_ERROR("ON_NurbsCage::Evaluate - invalid cage"); + ok = false; + } + + if ( !srfst.Evaluate(s,t,der_count,v_stride,&stv[0].x,0,srfst_hint)) + { + err_count++; + ON_ERROR("ON_NurbsCage::IsoSurface(dir=0,...) returned invalid surface"); + ok = false; + } + + if ( !srfrt.Evaluate(s,t,der_count,v_stride,&rtv[0].x,0,srfrt_hint)) + { + err_count++; + ON_ERROR("ON_NurbsCage::IsoSurface(dir=1,...) returned invalid surface"); + ok = false; + } + + if ( !srfrs.Evaluate(s,t,der_count,v_stride,&rsv[0].x,0,srfrs_hint)) + { + err_count++; + ON_ERROR("ON_NurbsCage::IsoSurface(dir=2,...) returned invalid surface"); + ok = false; + } + + + for ( dc = 0; dc <= der_count; dc++ ) + { + tol = pow(10.0,dc-11); + for ( di = dc; di >= 0; di--) + { + for (dj = dc-di; dj >= 0; dj--) + { + dk = dc-di-dj; + if ( di && dj && dk ) + continue; + cageV = cagev[dc*(dc+1)*(dc+2)/6 + (dj+dk)*(dj+dk+1)/2 + dk]; + stV = (di) ? cageV : stv[(dj+dk)*(dj+dk+1)/2 + dk]; + rtV = (dj) ? cageV : stv[(di+dk)*(di+dk+1)/2 + dk]; + rsV = (dk) ? cageV : stv[(di+dj)*(di+dj+1)/2 + dj]; + errst = (cageV-stV).MaximumCoordinate(); + errrt = (cageV-rtV).MaximumCoordinate(); + errrs = (cageV-rsV).MaximumCoordinate(); + if ( errst > tol && errrt > tol && errrs > tol ) + { + err_count++; + ON_ERROR("ON_NurbsCage::Evaluate - bad value"); + if ( err_count > 20 ) + ok = false; + } + else + { + pass_count++; + } + } + } + } + } + } + } + } + } + } + + onfree(rtv); + onfree(rsv); + onfree(stv); + onfree(cagev); + + return ok; +} + +bool TestNurbsCageEvaluate( ON_TextLog& text_log ) +{ + bool ok = false; + + ONX_Model model; + const char* sFileName = "..\\TEstNurbsCageEvaluate.3dm"; + FILE* fp = ON::OpenFile( sFileName, "rb"); + + bool bModelRead = false; + bool bModelIsValid = false; + + if ( 0 != fp ) + { + ON_BinaryFile archive( ON::read3dm, fp ); + bModelRead = model.Read( archive, &text_log ); + ON::CloseFile( fp ); + } + + if ( !bModelRead ) + { + text_log.Print("TestNurbsCageEvaluate cannot read %s\n",sFileName); + } + else + { + int i; + for ( i = 0; i < model.m_object_table.Count(); i++) + { + const ONX_Model_Object& mo = model.m_object_table[i]; + const ON_NurbsCage* model_cage = ON_NurbsCage::Cast(mo.m_object); + if ( !model_cage ) + continue; + ok = TestNurbsCageEvaluateHelper( text_log, *model_cage ); + if ( !ok ) + break; + } + } + + return ok; +} + diff --git a/tests/TestPrecompiledHeader.cpp b/tests/TestPrecompiledHeader.cpp new file mode 100644 index 00000000..62ffd522 --- /dev/null +++ b/tests/TestPrecompiledHeader.cpp @@ -0,0 +1 @@ +#include "Tests.h" diff --git a/tests/TestQuotientRule.cpp b/tests/TestQuotientRule.cpp new file mode 100644 index 00000000..166b9593 --- /dev/null +++ b/tests/TestQuotientRule.cpp @@ -0,0 +1,451 @@ +#include "../opennurbs.h" +#include "Tests.h" + +class CF +{ +public: + CF() {r = ON_UNSET_VALUE; s = ON_UNSET_VALUE; t = ON_UNSET_VALUE;} + double r, s, t; + double f[6]; // = x/wr + double g[6]; // = y/ws + double h[6]; // = z/wt + double x[6]; + double y[6]; + double z[6]; + double wr[6]; + double ws[6]; + double wt[6]; +}; + +class CV +{ +public: + double F, X, W; +}; + +static class CV func(double r, double s, double t, + ON_3dex ijk) +{ + static CF ev; + if ( r != ev.r ) + { + ev.x[0] = tan(r); + ev.x[1] = 1.0+ev.x[0]*ev.x[0]; + ev.x[2] = 2.0*ev.x[0]*ev.x[1]; + ev.x[3] = 2.0*(ev.x[1]*ev.x[1] + ev.x[0]*ev.x[2]); + ev.x[4] = 2.0*(3.0*ev.x[1]*ev.x[2] + ev.x[0]*ev.x[3]); + ev.x[5] = 2.0*(3.0*ev.x[2]*ev.x[2] + 4.0*ev.x[1]*ev.x[3] + ev.x[0]*ev.x[4]); + ev.r = r; + + ev.wr[0] = 1.0/cos(r); + ev.wr[1] = ev.wr[0]*ev.x[0]; + ev.wr[2] = ev.wr[1]*ev.x[0] + ev.wr[0]*ev.x[1]; + ev.wr[3] = ev.wr[2]*ev.x[0] + 2.0*ev.wr[1]*ev.x[1] + ev.wr[0]*ev.x[2]; + ev.wr[4] = ev.wr[3]*ev.x[0] + 3.0*ev.wr[2]*ev.x[1] + 3.0*ev.wr[1]*ev.x[2] + ev.wr[0]*ev.x[3]; + ev.wr[5] = ev.wr[4]*ev.x[0] + 4.0*ev.wr[3]*ev.x[1] + 6.0*ev.wr[2]*ev.x[2] + 4.0*ev.wr[1]*ev.x[3] + ev.wr[0]*ev.x[4]; + + ev.f[0] = sin(r); + ev.f[1] = cos(r); + ev.f[2] = -ev.f[0]; + ev.f[3] = -ev.f[1]; + ev.f[4] = -ev.f[2]; + ev.f[5] = -ev.f[3]; + } + + if ( s != ev.s ) + { + ev.y[0] = exp(3.0*s); + ev.y[1] = 3.0*ev.y[0]; + ev.y[2] = 9.0*ev.y[0]; + ev.y[3] = 27.0*ev.y[0]; + ev.y[4] = 81.0*ev.y[0]; + ev.y[5] = 243.0*ev.y[0]; + + ev.ws[0] = exp(2.0*s); + ev.ws[1] = 2.0*ev.ws[0]; + ev.ws[2] = 4.0*ev.ws[0]; + ev.ws[3] = 8.0*ev.ws[0]; + ev.ws[4] = 16.0*ev.ws[0]; + ev.ws[5] = 32.0*ev.ws[0]; + + ev.g[0] = exp(s); + ev.g[1] = ev.g[0]; + ev.g[2] = ev.g[0]; + ev.g[3] = ev.g[0]; + ev.g[4] = ev.g[0]; + ev.g[5] = ev.g[0]; + ev.s = s; + } + + if ( t != ev.t ) + { + double t2 = t*t; + double t3 = t*t2; + double t4 = t*t3; + double t5 = t*t4; + double t6 = t*t5; + double t7 = t*t6; + + ev.z[0] = t7; + ev.z[1] = 7.0*t6; + ev.z[2] = 42.0*t5; + ev.z[3] = 210.0*t4; + ev.z[4] = 840.0*t3; + ev.z[5] = 2520.0*t2; + + ev.wt[0] = t5; + ev.wt[1] = 5.0*t4; + ev.wt[2] = 20.0*t3; + ev.wt[3] = 60.0*t2; + ev.wt[4] = 120.0*t; + ev.wt[5] = 120.0; + + ev.h[0] = t2; + ev.h[1] = 2.0*t; + ev.h[2] = 2.0; + ev.h[3] = 0.0; + ev.h[4] = 0.0; + ev.h[5] = 0.0; + ev.t = t; + } + + + CV V; + V.X = ev.x[ijk.i]*ev.y[ijk.j]*ev.z[ijk.k]; + V.W = ev.wr[ijk.i]*ev.ws[ijk.j]*ev.wt[ijk.k]; + V.F = ev.f[ijk.i]*ev.g[ijk.j]*ev.h[ijk.k]; + + return V; +} + +struct TEST_INPUT +{ + int err_count; + int pass_count; + double der_tol[6]; + int der_count; // 0 <= der_count <= 5 + int dim; // dim > 0 + int n; // 0 <= n < dim + int v_stride; // dim < v_stride <= 7 + double v[6*7*8*7]; // >= (dc+1)*(dc+2)*(dc+3)&v_stride dc = max der_count, vs = max v_stride + double a[6*7*8]; // >= (dc+1)*(dc+2)*(dc+3) + ON_3dex ijk[6*7*8]; // >= (dc+1)*(dc+2)*(dc+3) + + double maxe1; + double maxe2; + double maxe3; +}; + +static void TestQR1(double r, double s, double t, + int coord, + TEST_INPUT& TI + ) +{ + CV V; + double e; + + int err_count = 0; + + int ii = (coord==0)?1:0; + int jj = (coord==1)?1:0; + int kk = (coord==2)?1:0; + + int dc; + + for (dc = 0; dc <= TI.der_count; dc++ ) + { + TI.ijk[dc].i = ii*dc; + TI.ijk[dc].j = jj*dc; + TI.ijk[dc].k = kk*dc; + V = func(r,s,t, TI.ijk[dc]); + TI.v[dc*TI.v_stride+TI.n] = V.X; + TI.v[dc*TI.v_stride+TI.dim] = V.W; + TI.a[dc] = V.F; + } + + if ( !ON_EvaluateQuotientRule(TI.dim,TI.der_count,TI.v_stride,TI.v) ) + { + ON_ERROR("ON_EvaluateQuotientRule - error"); + TI.err_count += (TI.der_count+1); + } + else + { + for ( dc = 0; dc <= TI.der_count; dc++ ) + { + e = fabs(TI.a[dc] - TI.v[dc*TI.v_stride+TI.n]); + if ( e > TI.der_tol[dc] && e > ON_SQRT_EPSILON*fabs(TI.a[dc]) ) + { + ON_ERROR("ON_EvaluateQuotientRule - error"); + TI.err_count++; + } + else + { + if ( e > TI.maxe1 ) + TI.maxe1 = e; + TI.pass_count++; + } + } + } +} + + +static void TestQR2(double r, double s, double t, + int coord, + TEST_INPUT& TI + ) +{ + CV V; + double e; + + int imax = (coord==0)?0:1; + int jmax = (coord==1)?0:1; + int kmax = (coord==2)?0:1; + + int i=0; + int j=0; + int k=0; + int mmax=0; + int m,der_count; + + for ( der_count = 0; der_count <= TI.der_count; der_count++ ) + { + for (m = 0; m <= der_count; m++ ) + { + if ( 0 == coord ) + { + TI.ijk[mmax].i = 0; + TI.ijk[mmax].j = der_count-m; + TI.ijk[mmax].k = m; + } + else if ( 1 == coord ) + { + TI.ijk[mmax].i = der_count-m; + TI.ijk[mmax].j = 0; + TI.ijk[mmax].k = m; + } + else + { + TI.ijk[mmax].i = der_count-m; + TI.ijk[mmax].j = m; + TI.ijk[mmax].k = 0; + } + V = func(r,s,t, TI.ijk[mmax]); + TI.v[mmax*TI.v_stride+TI.n] = V.X; + TI.v[mmax*TI.v_stride+TI.dim] = V.W; + TI.a[mmax] = V.F; + mmax++; + } + } + + if ( !ON_EvaluateQuotientRule2(TI.dim,TI.der_count,TI.v_stride,TI.v) ) + { + ON_ERROR("ON_EvaluateQuotientRule2 - error"); + TI.err_count += mmax; + } + else + { + for ( m = 0; m < mmax; m++ ) + { + e = fabs(TI.a[m] - TI.v[m*TI.v_stride+TI.n]); + der_count = TI.ijk[m].i+TI.ijk[m].j+TI.ijk[m].k; + if ( e > TI.der_tol[der_count] && e > ON_SQRT_EPSILON*fabs(TI.a[m]) ) + { + ON_ERROR("ON_EvaluateQuotientRule2 - error"); + TI.err_count++; + } + else + { + if ( e > TI.maxe2 ) + TI.maxe2 = e; + TI.pass_count++; + } + } + } +} + + +static void TestQR3(double r, double s, double t, + TEST_INPUT& TI + ) +{ + CV V; + double e; + + int i=0; + int j=0; + int k=0; + int mmax=0; + int m,der_count; + + for ( der_count = 0; der_count <= TI.der_count; der_count++ ) + { + for ( i = der_count; i >= 0; i-- ) + { + for ( j = der_count-i; j >= 0; j-- ) + { + k = der_count-i-j; + TI.ijk[mmax].i = i; + TI.ijk[mmax].j = j; + TI.ijk[mmax].k = k; + V = func(r,s,t, TI.ijk[mmax]); + TI.v[mmax*TI.v_stride+TI.n] = V.X; + TI.v[mmax*TI.v_stride+TI.dim] = V.W; + TI.a[mmax] = V.F; + mmax++; + } + } + } + + if ( !ON_EvaluateQuotientRule3(TI.dim,TI.der_count,TI.v_stride,TI.v) ) + { + ON_ERROR("ON_EvaluateQuotientRule3 - error"); + TI.err_count += mmax; + } + else + { + for ( m = 0; m < mmax; m++ ) + { + e = fabs(TI.a[m] - TI.v[m*TI.v_stride+TI.n]); + der_count = TI.ijk[m].i+TI.ijk[m].j+TI.ijk[m].k; + if ( e > TI.der_tol[der_count] && e > ON_SQRT_EPSILON*fabs(TI.a[m]) ) + { + ON_ERROR("ON_EvaluateQuotientRule3 - error"); + TI.err_count++; + } + else + { + if ( e > TI.maxe3 ) + TI.maxe3 = e; + TI.pass_count++; + } + } + } +} + +bool TestQuotientRule( ON_TextLog& text_log ) +{ + + double r,s,t; + struct TEST_INPUT TI; + + memset(&TI,0,sizeof(TI)); + TI.der_tol[0] = 1.0e-12; + TI.der_tol[1] = 1.0e-11; + TI.der_tol[2] = 1.0e-10; + TI.der_tol[3] = 1.0e-9; + TI.der_tol[4] = 1.0e-8; + TI.der_tol[5] = 5.0e-7; + + TI.der_count = 0; + TI.dim = 1; + TI.n = 0; + TI.v_stride = TI.dim+1; + bool ok = true; + for (TI.der_count=0; TI.der_count<=5 && ok; TI.der_count++) + for (TI.dim = 1; TI.dim <= 4 && ok; TI.dim++ ) + for (TI.n= 0; TI.n < TI.dim && ok; TI.n++) + for (TI.v_stride=TI.dim+1; TI.v_stride<=7 && ok; TI.v_stride++) + { + memset(TI.v,0,sizeof(TI.v)); + + // one paramter tests + r = 0.25*ON_PI; + s = 0.0; + t = 1.0; + + for ( r = -ON_PI/3.0; r <= ON_PI/3.0 && ok; r += ON_PI/120.0) + { + TestQR1(r,s,t,0,TI); + if (TI.err_count > 100 ) + ok = false; + } + + r = 0.25*ON_PI; + s = 0.0; + t = 1.0; + + for ( s = -2.0; s <= 2.0 && ok; s += 0.1 ) + { + TestQR1(r,s,t,1,TI); + if (TI.err_count > 100 ) + ok = false; + } + + r = 0.25*ON_PI; + s = 0.0; + t = 1.0; + + for ( t = 0.1; t < 2.5 && ok; t += 0.1 ) + { + TestQR1(r,s,t,2,TI); + if (TI.err_count > 100 ) + ok = false; + } + + // two paramter tests + r = 0.25*ON_PI; + s = 0.0; + t = 1.0; + + for ( r = -ON_PI/3.0; r <= ON_PI/3.0 && ok; r += ON_PI/120.0) + { + for ( s = -2.0; s <= 2.0 && ok; s += 0.1 ) + { + TestQR2(r,s,t, 2, TI); + if (TI.err_count > 100 ) + ok = false; + } + } + + r = 0.25*ON_PI; + s = 0.0; + t = 1.0; + + for ( r = -ON_PI/3.0; r <= ON_PI/3.0 && ok; r += ON_PI/120.0) + { + for ( t = 0.1; t < 2.5 && ok; t += 0.1 ) + { + TestQR2(r,s,t, 1, TI); + if (TI.err_count > 100 ) + ok = false; + } + } + + r = 0.25*ON_PI; + s = 0.0; + t = 1.0; + + for ( s = -2.0; s <= 2.0 && ok; s += 0.1 ) + { + for ( t = 0.1; t < 2.5 && ok; t += 0.1 ) + { + TestQR2(r,s,t, 1, TI); + if (TI.err_count > 100 ) + ok = false; + } + } + + // three paramter tests + r = 0.25*ON_PI; + s = 0.0; + t = 1.0; + + for ( r = -ON_PI/3.0; r <= ON_PI/3.0 && ok; r += ON_PI/120.0) + { + for ( s = -2.0; s <= 2.0 && ok; s += 0.1 ) + { + for ( t = 0.1; t < 2.5 && ok; t += 0.1 ) + { + TestQR3(r,s,t, TI); + if (TI.err_count > 100 ) + ok = false; + } + } + } + } + + text_log.Print("pass_count = %d err_count = %d maxe1 = %g maxe2 = %g maxe3 = %g\n", + TI.pass_count,TI.err_count,TI.maxe1,TI.maxe2,TI.maxe3); + + return (TI.err_count == 0); +} diff --git a/tests/TestTree.cpp b/tests/TestTree.cpp new file mode 100644 index 00000000..d3b3fb24 --- /dev/null +++ b/tests/TestTree.cpp @@ -0,0 +1,647 @@ +#include "Tests.h" + + +static const bool bPurify = false; +static const bool bDoCurves = true; +static const bool bDoSurfaces = true; + +bool TestCurveTree(ON_TextLog& text_log, const ON_Curve* curve ) +{ + if ( !curve ) + { + text_log.Print("Curve pointer is NULL.\n"); + return false; + } + + const ON_CurveTree* tree = curve->CurveTree(); + if ( !tree ) + { + text_log.Print("CurveTree() returned NULL.\n"); + return false; + } + + bool rc = tree->IsValid( &text_log, curve ); + if ( !rc ) + { + text_log.Print("Curve tree is not valid\n"); + } + + if ( tree != tree->m_root ) + { + // As of 29 March, I want tree to be = tree->m_root. + // and I'd like to get rid of the m_root field, but + // I don't know if that will work for making + // auxillary tree's over a set of trees. + text_log.Print("Curve tree != tree->m_root - please tell Dale Lear.\n"); + } + + const int span_count = curve->SpanCount(); + ON_SimpleArray t(10*(span_count+1)); + t.SetCount(span_count+1); + curve->GetSpanVector(t.Array()); + int i; + for ( i = 0; i < span_count; i++ ) + { + t.Append(0.5*(t[i]+t[i+1])); + } + + + 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 thickest = 0.0; + double shortest = fabs(ON_UNSET_VALUE); + ON_Line axis; + int notmonocount = 0; + while (leaf) + { + if ( t.Search( leaf->m_domain[0] ) < 0 ) + t.Append(leaf->m_domain[0]); + t.Append( leaf->m_domain.ParameterAt(0.2) ); + t.Append( leaf->m_domain.ParameterAt(0.7) ); + 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.m_r > thickest ) + thickest = leaf->m_bez->m_leafbox.m_r; + if ( leaf->m_bez->m_leafbox.m_r > maxar*len ) + { + maxar = leaf->m_bez->m_leafbox.m_r/len; + maxlen = len; + maxr = leaf->m_bez->m_leafbox.m_r; + } + leaf_count++; + leaf = leaf->NextLeaf(); + } + if ( notmonocount > 0 ) + { + text_log.Print("ON_CurveTree: %d spans, %d leaves (%d are not monotone).\n",span_count,leaf_count,notmonocount); + } + else + { + text_log.Print("ON_CurveTree: %d spans, %d leaves (all are monotone).\n",span_count,leaf_count); + } + + text_log.PushIndent(); + text_log.Print("Shortest: %g\n",shortest); + text_log.Print("Longest: %g\n",longest); + text_log.Print("Thickest: radius = %g\n",thickest); + text_log.Print("Fattest: rad/len = %g (rad = %g, len = %g)\n",maxar,maxr,maxlen); + text_log.PopIndent(); + + // test evaluation + const bool bAdjustParameter = tree->AdjustParameter(); + double x, y, d0, d1, tol0, tol1; + ON_3dVector X[2], Y[2], Xerr[2], Yerr[2]; + double xerr, yerr, d0err=0.0, d1err=0.0; + int error_count = 0; + int test_count = 0; + int hint = 0; + int side = 0; + const ON_CurveTreeNode* evaluation_node; + if ( 2 == curve->Dimension() ) + { + X[0].z = X[1].z = Y[0].z = Y[1].z = 0.0; + } + for ( i = 0; i < t.Count(); i++ ) + { + x = y = t[i]; + if ( i > span_count && bAdjustParameter) + { + // NOTE - the curve paramter = nurbs form paramter + // at all span vector values. + curve->GetCurveParameterFromNurbFormParameter(x,&y); + } + for ( side = -1; side < 1; side++ ) + { + test_count++; + X[0].x = -99.0; + Y[0].x = 99.0; + curve->Evaluate(y,1,3,&Y[0].x,side,&hint); + evaluation_node = tree->Evaluate(x,1,3,&X[0].x,side); + if ( !evaluation_node ) + { + error_count++; + ON_ERROR("TestCurveTree() - tree failed to evaluate."); + d0err = 1.0e100; + d1err = 1.0e100; + xerr = x; + yerr = y; + Xerr[0] = ON_UNSET_POINT; + Xerr[1] = ON_UNSET_POINT; + Yerr[0] = Y[0]; + Yerr[1] = Y[1]; + } + else + { + if ( side < 0 && x == evaluation_node->m_domain[0] ) + { + // x should be at the start of the curve's domain + // and there shouldn't be any leaves before this one. + if ( evaluation_node->PrevLeaf() || x != t[0] ) + { + ON_ERROR("TestCurveTree() - evaluation used wrong node"); + } + } + else if ( side > 0 && x == evaluation_node->m_domain[1] ) + { + // x should be at the end of the curve's domain + // and there shouldn't be any leaves after this one. + if ( evaluation_node->NextLeaf() || x != t[span_count] ) + { + ON_ERROR("TestCurveTree() - evaluation used wrong node"); + } + } + + if ( bAdjustParameter ) + { + // Different parameterizations result in derivatives + // with different lengths. + X[1].Unitize(); + Y[1].Unitize(); + } + tol0 = ON_ZERO_TOLERANCE + ON_SQRT_EPSILON*Y[0].MaximumCoordinate(); + tol1 = ON_ZERO_TOLERANCE + ON_SQRT_EPSILON*Y[1].MaximumCoordinate(); + d0 = (X[0]-Y[0]).Length(); + d1 = (X[1]-Y[1]).Length(); + if ( d0 > tol0 || d1 > tol1 ) + { + rc = false; + error_count++; + if ( d0 > d0err || (d1 > d1err && d0err < 0.01) ) + { + if ( d0 > d0err) d0err = d0; + if ( d1 > d1err) d1err = d1; + xerr = x; + yerr = y; + Xerr[0] = X[0]; + Xerr[1] = X[1]; + Yerr[0] = Y[0]; + Yerr[1] = Y[1]; + } + } + } + } + } + text_log.PushIndent(); + if ( error_count > 0 ) + { + ON_ERROR("TestCurveTree() - tree failed evaluation test."); + text_log.Print("Evaluation test at %d points had %d errors.\n",test_count,error_count); + text_log.PushIndent(); + text_log.Print("curve(%g) = (%g,%g,%g)\n",yerr,Y[0].x,Y[0].y,Y[0].z); + text_log.Print("tree(%g) = (%g,%g,%g)\n",xerr,X[0].x,X[0].y,X[0].z); + text_log.Print("curve D = (%g,%g,%g)\n",Y[1].x,Y[1].y,Y[1].z); + text_log.Print("tree D = (%g,%g,%g)\n",X[1].x,X[1].y,X[1].z); + text_log.PopIndent(); + } + else + { + text_log.Print("Evaluation test at %d points was successful.\n",test_count); + } + text_log.PopIndent(); + + return rc; +} + + +bool TestSurfaceTree(ON_TextLog& text_log, const ON_Surface* surface ) +{ + if ( !surface ) + { + text_log.Print("Surface pointer is NULL.\n"); + return false; + } + + const ON_SurfaceTree* tree = surface->SurfaceTree(); + if ( !tree ) + { + text_log.Print("SurfaceTree() returned NULL.\n"); + return false; + } + + bool rc = tree->IsValid( &text_log, surface ); + if ( !rc ) + { + text_log.Print("Surface tree is not valid.\n"); + } + + int i; + const int span_count0 = surface->SpanCount(0); + ON_SimpleArray s(10*(span_count0 + 1)); + s.SetCount(span_count0+1); + surface->GetSpanVector( 0, s.Array() ); + for ( i = 0; i < span_count0; i++ ) + { + s.Append(0.5*(s[i]+s[i+1])); + } + + int j; + const int span_count1 = surface->SpanCount(1); + ON_SimpleArray t(10*(span_count1 + 1)); + t.SetCount(span_count1+1); + surface->GetSpanVector( 1, t.Array() ); + for ( j = 0; j < span_count1; j++ ) + { + t.Append(0.5*(t[j]+t[j+1])); + } + + const int span_count = span_count0*span_count1; + + 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 thickest = 0.0; + double shortest = fabs(ON_UNSET_VALUE); + ON_3dPoint C[4]; + int notmonocount = 0; + while (leaf) + { + if ( s.Search(leaf->m_domain[0][0]) < 0 ) + s.Append(leaf->m_domain[0][0]); + if ( s.Search(leaf->m_domain[0].ParameterAt(0.3)) < 0 ) + s.Append(leaf->m_domain[0].ParameterAt(0.3)); + if ( s.Search(leaf->m_domain[0].ParameterAt(0.8)) < 0 ) + s.Append(leaf->m_domain[0].ParameterAt(0.8)); + + if ( t.Search(leaf->m_domain[1][0]) < 0 ) + t.Append(leaf->m_domain[1][0]); + if ( t.Search(leaf->m_domain[1].ParameterAt(0.2)) < 0 ) + t.Append(leaf->m_domain[1].ParameterAt(0.2)); + if ( t.Search(leaf->m_domain[1].ParameterAt(0.7)) < 0 ) + t.Append(leaf->m_domain[1].ParameterAt(0.7)); + + + 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 ( 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() > thickest ) + thickest = leaf->m_bez->m_leafbox.Height(); + + 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 spans, %d leaves (%d are not monotone).\n",span_count,leaf_count,notmonocount); + } + else + { + text_log.Print("ON_SurfaceTree: %d spans, %d leaves (all are monotone).\n",span_count,leaf_count); + } + + text_log.PushIndent(); + text_log.Print("Shortest: %g\n",shortest); + text_log.Print("Longest: %g\n",longest); + text_log.Print("Thickest: height = %g\n",thickest); + text_log.Print("Fattest: ht/len = %g (ht = %g, len = %g)\n",maxar,maxr,maxlen); + text_log.PopIndent(); + + // test evaluation + const bool bAdjustParameter = tree->AdjustParameter(); + ON_2dPoint x,y; + double d0, ds, dt, tol0, tol1, tol2; + ON_3dVector X[3], Y[3], Xerr[3], Yerr[3]; + ON_2dVector xerr, yerr; + double d0err=0.0, d1err=0.0; + int error_count = 0; + int test_count = 0; + int hint[2] ={0,0}; + int quadrant = 0; + const ON_SurfaceTreeNode* evaluation_node; + for ( i = 0; i < s.Count(); i++ ) + { + x.x = y.x = s[i]; + for ( j = 0; j < t.Count(); j++ ) + { + x.y = y.y = t[j]; + if (( i > span_count0 || j > span_count1) && bAdjustParameter) + { + // NOTE - the surface paramter = nurbs form paramter + // at all span vector values. + surface->GetSurfaceParameterFromNurbFormParameter(s[i],t[j],&y.x,&y.y); + } + + for (quadrant = 0; quadrant <= 4; quadrant++ ) + { + test_count++; + X[0].x = -99.0; + Y[0].x = 99.0; + surface->Evaluate(y.x,y.y,1,3,&Y[0].x,quadrant,hint); + evaluation_node = tree->Evaluate(x.x,x.y,1,3,&X[0].x,quadrant); + if ( !evaluation_node ) + { + error_count++; + ON_ERROR("TestSuraceTree() - tree failed to evaluate."); + d0err = 1.0e100; + d1err = 1.0e100; + xerr = x; + yerr = y; + Xerr[0] = ON_UNSET_POINT; + Xerr[1] = ON_UNSET_POINT; + Xerr[2] = ON_UNSET_POINT; + Yerr[0] = Y[0]; + Yerr[1] = Y[1]; + Yerr[2] = Y[2]; + } + else + { + if ( x.x == evaluation_node->m_domain[0][0] && x.x > s[0] ) + { + if ( 2 == quadrant || 3 == quadrant ) + { + ON_ERROR("TestSuraceTree() ON_SurfaceTreeNode::Evaluate() used the wrong node."); + } + } + else if ( x.x == evaluation_node->m_domain[0][1] && x.x < s[span_count0] ) + { + if ( 1 == quadrant || 4 == quadrant ) + { + ON_ERROR("TestSuraceTree() ON_SurfaceTreeNode::Evaluate() used the wrong node."); + } + } + + if ( x.y == evaluation_node->m_domain[1][0] && x.y > t[0] ) + { + if ( 3 == quadrant || 4 == quadrant ) + { + ON_ERROR("TestSuraceTree() ON_SurfaceTreeNode::Evaluate() used the wrong node."); + } + } + else if ( x.y == evaluation_node->m_domain[1][1] && x.y < t[span_count1] ) + { + if ( 1 == quadrant || 2 == quadrant ) + { + ON_ERROR("TestSuraceTree() ON_SurfaceTreeNode::Evaluate() used the wrong node."); + } + } + + + if ( bAdjustParameter ) + { + // Different parameterizations result in derivatives + // with different lengths. + if ( X[1].Length() <= ON_ZERO_TOLERANCE && Y[1].Length() <= ON_ZERO_TOLERANCE ) + { + // singular point + // Probably a place where the revolute hits the axis in a surface of revolution + X[1].Zero(); + Y[1].Zero(); + } + else + { + X[1].Unitize(); + Y[1].Unitize(); + } + if ( X[2].Length() <= ON_ZERO_TOLERANCE && Y[2].Length() <= ON_ZERO_TOLERANCE ) + { + // singular point + // Probably a place where the revolute hits the axis in a surface of revolution. + X[2].Zero(); + Y[2].Zero(); + } + else + { + X[2].Unitize(); + Y[2].Unitize(); + } + } + tol0 = ON_ZERO_TOLERANCE + ON_SQRT_EPSILON*Y[0].MaximumCoordinate(); + tol1 = ON_ZERO_TOLERANCE + ON_SQRT_EPSILON*Y[1].MaximumCoordinate(); + tol2 = ON_ZERO_TOLERANCE + ON_SQRT_EPSILON*Y[2].MaximumCoordinate(); + d0 = (X[0]-Y[0]).Length(); + ds = (X[1]-Y[1]).Length(); + dt = (X[2]-Y[2]).Length(); + if ( d0 > tol0 || ds > tol1 || dt > tol2 ) + { + rc = false; + error_count++; + if ( d0 > d0err || (ds > d1err && d0err < 0.01) || (dt > d1err && d0err < 0.01) ) + { + if ( d0 > d0err) d0err = d0; + if ( ds > d1err) d1err = ds; + if ( dt > d1err) d1err = dt; + xerr = x; + yerr = y; + Xerr[0] = X[0]; + Xerr[1] = X[1]; + Xerr[2] = X[2]; + Yerr[0] = Y[0]; + Yerr[1] = Y[1]; + Yerr[2] = Y[2]; + } + } + } + } + } + } + + text_log.PushIndent(); + if ( error_count > 0 ) + { + ON_ERROR("TestSurfaceTree() - tree failed evaluation test."); + text_log.Print("Evaluation test at %d points had %d errors.\n",test_count,error_count); + text_log.PushIndent(); + text_log.Print("surface(%g,%g) = (%g,%g,%g)\n",yerr.x,yerr.y,Y[0].x,Y[0].y,Y[0].z); + text_log.Print("tree(%g,%g) = (%g,%g,%g)\n",xerr.x,xerr.y,X[0].x,X[0].y,X[0].z); + text_log.Print("curve Ds = (%g,%g,%g)\n",Y[1].x,Y[1].y,Y[1].z); + text_log.Print("tree Ds = (%g,%g,%g)\n",X[1].x,X[1].y,X[1].z); + text_log.Print("curve Dt = (%g,%g,%g)\n",Y[2].x,Y[2].y,Y[2].z); + text_log.Print("tree Dt = (%g,%g,%g)\n",X[2].x,X[2].y,X[2].z); + text_log.PopIndent(); + } + else + { + text_log.Print("Evaluation test at %d points was successful.\n",test_count); + } + text_log.PopIndent(); + + return rc; +} + +static +void TestCurveTreeHelper( ON_TextLog& text_log, const ON_Curve* curve, const wchar_t* name ) +{ + TL_NurbsCurve nurbs_curve; + + if ( curve ) + { + if ( 0 == name || 0 == *name) + { + name = L"anonymous"; + } + + curve->GetNurbForm(nurbs_curve); + + text_log.Print(L"Curve class = %S, name = %s\n", + curve->ClassId()->ClassName(), + name + ); + text_log.PushIndent(); + text_log.Print(L"degree = %d, %s, CV count=%d\n", + nurbs_curve.m_order-1, + (nurbs_curve.m_is_rat ? L"rational" : L"non-rational"), + nurbs_curve.m_cv_count + ); + TestCurveTree( text_log, curve ); + text_log.PopIndent(); + } +} + +static +void TestSurfaceTreeHelper( ON_TextLog& text_log, const ON_Surface* surface, const wchar_t* name ) +{ + TL_NurbsSurface nurbs_surface; + + if ( surface ) + { + if ( 0 == name || 0 == *name) + { + name = L"anonymous"; + } + surface->GetNurbForm(nurbs_surface); + + text_log.Print(L"Surface class = %S, name = %s\n", + surface->ClassId()->ClassName(), + name + ); + text_log.PushIndent(); + text_log.Print(L"degree = (%d,%d), %s, CV count= (%d,%d)\n", + 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, surface ); + text_log.PopIndent(); + } +} + +void TestTree( const ONX_Model& model, ON_TextLog& text_log ) +{ + TEST_HEADER(text_log,"TestClosestPoint"); + int i, k; + + ON_wString name; + const wchar_t* attributes_name; + + // 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; + TestCurveTreeHelper(text_log,curve,attributes_name); + 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); + TestCurveTreeHelper(text_log,curve,name); + } + } + + 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); + TestCurveTreeHelper(text_log,curve,name); + } + } + 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; + TestSurfaceTreeHelper(text_log,surface,attributes_name); + 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); + + TestSurfaceTreeHelper(text_log,surface,model.m_object_table[i].m_attributes.m_name); + } + } + continue; + } + } + } +} \ No newline at end of file diff --git a/tests/TestZLib.cpp b/tests/TestZLib.cpp new file mode 100644 index 00000000..8802f3cf --- /dev/null +++ b/tests/TestZLib.cpp @@ -0,0 +1,253 @@ +/* $Header: /src4/opennurbs/Tests/TestZLib.cpp 3 2/24/06 9:46p Dalelear $ */ +/* $NoKeywords: $ */ + +#include "Tests.h" +#include +#include + + +static +bool TestZLibCompressionHelper2( + const wchar_t* filename, + __int64 filesize, + ON_TextLog& text_log + ) +{ + ON_Workspace ws; + + if ( 0 == filename ) + return false; + + if ( filesize > 0xFFFFFFF0 ) + { + text_log.Print("Huge file - cannot compress this\n"); + return false; + } + + //////////////////////////////////////////////////////////////// + // + // Allocate inbuffer[] and read filename into inbuffer[] + // + const size_t insz = (size_t)filesize; + void* inbuffer = ws.GetMemory( insz ); + + if ( 0 == inbuffer ) + { + text_log.Print("onmalloc( %u ) returned NULL\n",insz); + return false; + } + + FILE* fp = _wfopen(filename,L"rb"); + if ( !fp ) + { + ON_ERROR("Unable to read file\n"); + text_log.Print(L"ERROR: wfopen(%s,L\"rb\") failed\n",filename); + return false; + } + size_t freadsz = fread(inbuffer,1,insz,fp); + fclose(fp); + fp = 0; + if ( freadsz != insz ) + { + text_log.Print(L"ERROR: fread(inbuffer,1,%u,%s) = %u\n",insz,filename,freadsz); + return false; + } + + //////////////////////////////////////////////////////////////// + // + // Compress inbuffer[] and save compressed informtion in zlibtest.tmp + // + const wchar_t* tempfile = L"C:\\Temp\\zlibtest.tmp"; + + fp = ON::OpenFile(tempfile,L"wb"); + if ( !fp ) + { + ON_ERROR("Unable to write temp file\n"); + text_log.Print(L"ERROR: ON::OpenFile(%s,L\"wb\") failed\n",tempfile); + return false; + } + + bool bWriteOk = false; + { + ON_BinaryFile file( ON::write, fp ); + bWriteOk = file.WriteCompressedBuffer( insz, inbuffer ); + } + fclose(fp); + fp = 0; + if ( !bWriteOk ) + { + text_log.Print(L"ERROR: WriteCompressedBuffer(%u,...) failed\n",insz); + return false; + } + + //////////////////////////////////////////////////////////////// + // + // Read zlibtest.tmp uncompress its contents into outbuffer[] + // + size_t outsz = 0; + void* outbuffer = 0; + + fp = ON::OpenFile(tempfile,L"rb"); + if ( !fp ) + { + ON_ERROR("Unable to read temp file\n"); + text_log.Print(L"ERROR: ON::OpenFile(%s,L\"rb\") failed\n",tempfile); + return false; + } + + int bFailedCRC = true; + bool bReadOk = false; + { + ON_BinaryFile file( ON::read, fp ); + bReadOk = file.ReadCompressedBufferSize( &outsz ); + if (bReadOk && outsz == insz) + { + outbuffer = ws.GetMemory(outsz); + if ( 0 == outbuffer ) + { + text_log.Print("onmalloc( %u ) returned NULL\n",outsz); + fclose(fp); + return false; + } + else + { + bReadOk = file.ReadCompressedBuffer( outsz, outbuffer, &bFailedCRC ); + } + } + } + fclose(fp); + fp = 0; + if ( !bReadOk ) + { + text_log.Print(L"ERROR: ReadCompressedBuffer(%u,...) failed\n",outsz); + return false; + } + + //////////////////////////////////////////////////////////////// + // + // Compare inbuffer[] and outbuffer[] + // + if ( insz != outsz || memcmp(inbuffer,outbuffer,insz) || bFailedCRC ) + { + ON_ERROR("TestZLibCompressionHelper2 compression failure"); + text_log.Print("ERROR: %s compression failure\n"); + return false; + } + + return true; +} + + +static +void TestZLibCompressionHelper1( + const wchar_t* filespec, + ON_TextLog& text_log, + int& file_counter, + int recursion_counter + ) +{ + const int max_file_count = 0x7FFFFFF0; + if ( file_counter > max_file_count ) + return; + + if ( recursion_counter > 32 ) + { + ON_ERROR("TestZLibCompressionHelper1 recursion_counter > 32"); + return; + } + + if ( 0 == recursion_counter && (0 == filespec || 0 == filespec[0]) ) + { + filespec = L"C:\\Documents and Settings\\dalelear\\My Documents"; + } + + bool bRecurse = false; + struct __stat64 fs; + + ON_wString wildcard = filespec; + ON_wString path; + if ( 0 == recursion_counter ) + { + wildcard.TrimRight(L"/\\"); + } + if ( !wildcard.IsEmpty() ) + { + memset(&fs,0,sizeof(fs)); + if ( 0 == _wstat64( wildcard, &fs ) ) + { + if ( 0 != (_S_IFDIR & fs.st_mode) ) + { + bRecurse = true; + path = wildcard; + path += '\\'; + wildcard = path; + wildcard += '*'; + filespec = wildcard; + } + } + } + + struct _wfinddata64_t fi; + memset(&fi,0,sizeof(fi)); + INT_PTR h = _wfindfirst64( filespec, &fi ); + if ( -1 == h ) + { + text_log.Print(L"ERROR: Compression test found nothing in %s\n",filespec); + return; + } + + int rc = 0; + while ( 0 == rc && file_counter < max_file_count ) + { + if ( 0 != fi.name[0] + && (fi.name[0] != '.' || (0 != fi.name[1] && wcscmp(fi.name,L".."))) + ) + { + // fi.name is not empty and is not "." or ".." + ON_wString fullpath = path; + fullpath += fi.name; + memset(&fs,0,sizeof(fs)); + if ( 0 == _wstat64( fullpath, &fs ) ) + { + bool bIsDir = (0 != (_S_IFDIR & fs.st_mode)); + bool bIsFile = (false == bIsDir && (0 != (_S_IFREG & fs.st_mode))); + if ( bIsDir && bRecurse ) + { + // recurse through subdirectory + TestZLibCompressionHelper1(fullpath,text_log,file_counter,recursion_counter+1); + } + else if ( bIsFile && fs.st_size > 0) + { + // use file for a compression test + if ( !TestZLibCompressionHelper2(fullpath,fs.st_size,text_log) ) + { + text_log.Print(L"ERROR: %s compression test failed\n",fullpath); + } + else + { + file_counter++; + if ( 0 == (file_counter % 1000) ) + { + text_log.Print(L"%6d files tested\n",file_counter); + } + } + } + } + } + rc = _wfindnext64( h, &fi ); + } + + _findclose(h); + + return; +} + +void TestZLibCompression( + const wchar_t* filespec, + ON_TextLog& text_log + ) +{ + int file_counter = 0; + TestZLibCompressionHelper1(filespec,text_log,file_counter,0); +} + diff --git a/tests/Tests.cpp b/tests/Tests.cpp new file mode 100644 index 00000000..837b9ef2 --- /dev/null +++ b/tests/Tests.cpp @@ -0,0 +1,1090 @@ +/* $Header: /src4/opennurbs/Tests/Tests.cpp 26 2/24/06 9:46p Dalelear $ */ +/* $NoKeywords: $ */ + +#include "Tests.h" + + +#pragma warning( push ) +#pragma warning( disable : 4073 ) +#pragma init_seg( lib ) +#pragma warning( pop ) + +#define ON_CRASH_TEST_STATICS +#include "../opennurbs_crashtest.h" +#undef ON_CRASH_TEST_STATICS + +static int starthere() +{ + ON::Begin(); + return 0; +} + +// 13 Nov 2007 Dale Lear - added this comment. +// the init_seg pragma and initializing this global +// force ON::Begin() to be called before main(). +// I can't remember why this was important, but I +// think it had to do with purify. +int starthere__starthere = starthere(); + +class TEST_RESULTS +{ +public: + clock_t clock0; + int error_count0; + int warning_count0; + int matherr_count0; + int fperr_count0; + + ON_TextLog* m_text_log; + + int passed_count; + int failed_count; + + bool ok; +}; + + +CTestObject::CTestObject() +{ + m_model_object_index = 0; + memset(&m_uuid,0,sizeof(m_uuid)); + memset(&m_ci,0,sizeof(m_ci)); +} + +CTestObject::~CTestObject() +{ + Destroy(); +} + +void CTestObject::Destroy() +{ + m_model_object_index = 0; + memset(&m_uuid,0,sizeof(m_uuid)); + m_name.Destroy(); + memset(&m_ci,0,sizeof(m_ci)); +} + + +CTestCurve::CTestCurve() +{ + m_curve = 0; + memset(&m_nurbcrv,0,sizeof(m_nurbcrv)); + m_goose_curve = 0; +} + +CTestCurve::~CTestCurve() +{ + Destroy(); +} + +void CTestCurve::Destroy() +{ + if ( m_goose_curve ) + { + delete m_goose_curve; + m_goose_curve = 0; + } + + if ( m_nurbcrv.cv == m_nurbs_curve.m_cv ) + { + m_nurbcrv.cv = 0; + } + if ( m_nurbcrv.knot == m_nurbs_curve.m_knot ) + { + m_nurbcrv.knot = 0; + } + TL_DestroyNurb(&m_nurbcrv); + + m_nurbs_curve.Destroy(); + + m_curve = 0; + + CTestObject::Destroy(); +} + + +const TL_NurbsCurve& CTestCurve::NurbsCurve() +{ + if ( !m_nurbs_curve.m_cv && m_curve ) + { + m_bAdjustNurbsParameter = ( m_curve->GetNurbForm(m_nurbs_curve) > 1 ); + m_nurbcrv.dim = m_nurbs_curve.m_dim; + m_nurbcrv.is_rat = m_nurbs_curve.m_is_rat; + m_nurbcrv.order = m_nurbs_curve.m_order; + m_nurbcrv.cv_count = m_nurbs_curve.m_cv_count; + m_nurbcrv.cv = m_nurbs_curve.m_cv; + m_nurbcrv.knot = m_nurbs_curve.m_knot; + } + return m_nurbs_curve; +} + +const IwBSplineCurve* CTestCurve::GooseCurve() +{ + if ( !m_goose_curve && m_curve ) + { + NurbsCurve(); + if ( m_nurbcrv.cv && m_nurbcrv.knot ) + { + TL_Convert( m_nurbcrv, m_goose_curve ); + } + } + return m_goose_curve; +} + + + +CTestSurface::CTestSurface() +{ + memset(&m_uuid,0,sizeof(m_uuid)); + m_surface = 0; + memset(&m_nurbsrf,0,sizeof(m_nurbsrf)); + m_goose_surface = 0; +} + +CTestSurface::~CTestSurface() +{ + Destroy(); +} + +void CTestSurface::Destroy() +{ + if ( m_goose_surface ) + { + delete m_goose_surface; + m_goose_surface = 0; + } + + if ( m_nurbsrf.cv == m_nurbs_surface.m_cv ) + { + m_nurbsrf.cv = 0; + } + if ( m_nurbsrf.knot[0] == m_nurbs_surface.m_knot[0] ) + { + m_nurbsrf.knot[0] = 0; + } + if ( m_nurbsrf.knot[1] == m_nurbs_surface.m_knot[1] ) + { + m_nurbsrf.knot[1] = 0; + } + TL_DestroyNurbSrf(&m_nurbsrf); + + m_nurbs_surface.Destroy(); + + m_surface = 0; + + CTestObject::Destroy(); +} + +const TL_NurbsSurface& CTestSurface::NurbsSurface() +{ + if ( !m_nurbs_surface.m_cv && m_surface ) + { + m_bAdjustNurbsParameter = ( m_surface->GetNurbForm(m_nurbs_surface) > 1 ); + m_nurbsrf.dim = m_nurbs_surface.m_dim; + m_nurbsrf.is_rat = m_nurbs_surface.m_is_rat; + m_nurbsrf.order[0] = m_nurbs_surface.m_order[0]; + m_nurbsrf.order[1] = m_nurbs_surface.m_order[1]; + m_nurbsrf.cv_count[0] = m_nurbs_surface.m_cv_count[0]; + m_nurbsrf.cv_count[1] = m_nurbs_surface.m_cv_count[1]; + m_nurbsrf.cv = m_nurbs_surface.m_cv; + m_nurbsrf.knot[0] = m_nurbs_surface.m_knot[0]; + m_nurbsrf.knot[1] = m_nurbs_surface.m_knot[1]; + } + return m_nurbs_surface; +} + +const IwBSplineSurface* CTestSurface::GooseSurface() +{ + if ( !m_goose_surface && m_surface ) + { + NurbsSurface(); + if ( m_nurbsrf.cv && m_nurbsrf.knot[0] && m_nurbsrf.knot[1] ) + { + TL_Convert( m_nurbsrf, m_goose_surface ); + } + } + return m_goose_surface; +} + + +CTestBrep::CTestBrep() +{ + m_brep = 0; +} + +CTestBrep::~CTestBrep() +{ + m_brep = 0; +} + +static int sort_model_helper(const ONX_Model_Object* a, const ONX_Model_Object* b ) +{ + // sort by uuid, then object name, then order in the file + int i = ON_UuidCompare(a->m_attributes.m_uuid,b->m_attributes.m_uuid); + if ( 0 == i ) + { + i = a->m_attributes.m_name.Compare( b->m_attributes.m_name ); + if ( 0 == i ) + { + // compare order in the model + i = ((ab) ? 1 : 0)); + } + } + return i; +} + +CTestModel::CTestModel() +{ + m_bPurify = false; +} + +void CTestModel::GetTestObjects( bool bEdgesAreCurves, bool bTrimsAreCurves ) +{ + m_curves.Destroy(); + m_surfaces.Destroy(); + m_breps.Destroy(); + m_curves.Reserve(32); + m_surfaces.Reserve(64); + m_curves.Reserve(64); + + const ON_Curve* curve; + const ON_Surface* surface; + const TL_Brep* brep; + + int i, j; + + // Sorting the model object table keeps + // the report's object order consistent + m_object_table.HeapSort(sort_model_helper); + + + for (i = 0; i < m_object_table.Count(); i++ ) + { + ONX_Model_Object& model_object = m_object_table[i]; + + curve = ON_Curve::Cast(model_object.m_object); + if ( curve ) + { + CTestCurve& C = m_curves.AppendNew(); + + C.m_uuid = model_object.m_attributes.m_uuid; + C.m_name = model_object.m_attributes.m_name; + if (C.m_name.IsEmpty()) + C.m_name = L"anonymous curve"; + C.m_model_object_index = i; + + C.m_curve = curve; + + continue; + } + + surface = ON_Surface::Cast(model_object.m_object); + if ( surface ) + { + CTestSurface& S = m_surfaces.AppendNew(); + + S.m_uuid = model_object.m_attributes.m_uuid; + S.m_name = model_object.m_attributes.m_name; + if (S.m_name.IsEmpty()) + S.m_name = L"anonymous surface"; + S.m_model_object_index = i; + + S.m_surface = surface; + + continue; + } + + brep = TL_Brep::Promote(ON_Brep::Cast(model_object.m_object)); + if ( brep ) + { + CTestBrep& B = m_breps.AppendNew(); + + B.m_uuid = model_object.m_attributes.m_uuid; + B.m_name = model_object.m_attributes.m_name; + if (B.m_name.IsEmpty()) + B.m_name = L"anonymous brep"; + B.m_model_object_index = i; + + B.m_brep = brep; + + if ( bTrimsAreCurves ) + { + for ( j = 0; j < brep->m_C2.Count(); j++ ) + { + curve = brep->m_C2[j]; + if ( !curve ) + continue; + + CTestCurve& C = m_curves.AppendNew(); + + C.m_uuid = model_object.m_attributes.m_uuid; + C.m_name.Format(L"brep[%d]->m_C2[%d] in %s",m_breps.Count()-1,j,B.m_name.Array()); + C.m_model_object_index = i; + C.m_ci.m_type = ON_COMPONENT_INDEX::brep_trim; + C.m_ci.m_index = j; + + C.m_curve = curve; + } + } + + if ( bEdgesAreCurves ) + { + for ( j = 0; j < brep->m_C3.Count(); j++ ) + { + curve = brep->m_C3[j]; + if ( !curve ) + continue; + + CTestCurve& C = m_curves.AppendNew(); + + C.m_uuid = model_object.m_attributes.m_uuid; + C.m_name.Format(L"brep[%d]->m_C3[%d] in %s",m_breps.Count()-1,j,B.m_name.Array()); + C.m_model_object_index = i; + C.m_ci.m_type = ON_COMPONENT_INDEX::brep_edge; + C.m_ci.m_index = j; + + C.m_curve = curve; + } + } + + for ( j = 0; j < brep->m_S.Count(); j++ ) + { + surface = brep->m_S[j]; + if ( !surface ) + continue; + + CTestSurface& S = m_surfaces.AppendNew(); + + S.m_uuid = model_object.m_attributes.m_uuid; + S.m_name.Format(L"brep[%d]->m_S[%d] in %s\n",m_breps.Count()-1,j,B.m_name.Array()); + S.m_model_object_index = i; + S.m_ci.m_index = j; + + S.m_surface = surface; + } + + continue; + } + } +}; + +//static void ON_TEST_BeginTest( struct tagON_TEST_RESULTS& test_results, const char* func_name ); + +//static void ON_TEST_EndTest( struct tagON_TEST_RESULTS& test_results ); + +//static bool ON_TEST_InitFPErrorHandler(void); + +class CTestList +{ +public: + + CTestList() + { + m_bTestTree = true; + m_bTestClsPtCrv = true; + m_bTestClsPtSrf = true; + m_bTestClsPtMesh = true; + m_bTestCCX = true; + m_bTestCSX = true; + m_bTestSSX = true; + m_bTestZLibCompression = false; + m_bAudit = true; + m_bDump = true; + m_bEdgesAreCurves = true; + m_bTrimsAreCurves = true; + m_bPurify = false; + m_bCrashTestDblZeroDiv = false; + m_bCrashTestDblOverflow = false; + m_bCrashTestDblInvalidOp = false; + + m__bClearTests = true; + + m_default_3dm_filename = L"\\src4\\opennurbs\\tests\\input\\MyTest.3dm"; + m_default_log_filename = L"\\src4\\opennurbs\\tests\\results\\MyResults.txt"; + m_3dm_filename = m_default_3dm_filename; + } + + bool ParseCommandLine( int argc, const char* argv[] ); + + bool m_bTestTree; + bool m_bTestClsPtCrv; + bool m_bTestClsPtSrf; + bool m_bTestClsPtMesh; + bool m_bTestCCX; + bool m_bTestCSX; + bool m_bTestSSX; + bool m_bTestZLibCompression; + bool m_bAudit; + bool m_bDump; + bool m_bPurify; + bool m_bCrashTestDblZeroDiv; + bool m_bCrashTestDblOverflow; + bool m_bCrashTestDblInvalidOp; + + bool m_bEdgesAreCurves; + bool m_bTrimsAreCurves; + + ON_wString m_3dm_filename; + ON_wString m_log_filename; + ON_wString m_zlib_filespec; + +private: + ON_String m_default_3dm_filename; + ON_String m_default_log_filename; + bool m__bClearTests; + bool GetOption( const char* arg, + const char* opt, + ON_wString& value ); + void Clear(); +}; + +void CTestList::Clear() +{ + if ( m__bClearTests ) + { + // do not clear m_bEdgesAreCurves or m_bTrimsAreCurves + m_bTestTree = false; + m_bTestClsPtCrv = false; + m_bTestClsPtSrf = false; + m_bTestClsPtMesh = false; + m_bTestCCX = false; + m_bTestCSX = false; + m_bTestSSX = false; + m_bTestZLibCompression = false; + m_bAudit = false; + m_bDump = false; + m_bCrashTestDblZeroDiv = false; + m_bCrashTestDblOverflow = false; + m_bCrashTestDblInvalidOp = false; + + m__bClearTests = false; + } +} + +bool CTestList::GetOption( const char* arg, + const char* opt, + ON_wString& value ) +{ + int i; + bool rc = false; + + value.Destroy(); + + if ( arg + && ('-' == arg[0] || '/' == arg[0]) + && arg[1] + && opt + && opt[0] + && '-' != opt[0] + && '/' != opt[0] + && ':' != opt[0] + ) + { + arg++; + for ( i = 0; arg[i]; i++ ) + { + if ( ':' == arg[i] ) + break; + } + + if ( strlen(opt) == i && 0 == _strnicmp(arg,opt,i) ) + { + rc = true; + if ( ':' == arg[i] ) + { + value = &arg[i+1]; + } + } + } + + return rc; +} + +bool CTestList::ParseCommandLine( int argc, const char* argv[] ) +{ + int argi; + for ( argi = 1; argi < argc; argi++ ) + { + ON_wString value; + + if ( GetOption(argv[argi],"log",value) ) + { + if ( value.IsEmpty() ) + { + m_log_filename = m_default_log_filename; + } + else + { + m_log_filename = value; + } + continue; + } + + if ( GetOption(argv[argi],"3dm",value) ) + { + if ( value.IsEmpty() ) + { + m_3dm_filename = m_default_3dm_filename; + } + else + { + m_3dm_filename = value; + } + continue; + } + + if ( GetOption(argv[argi],"audit",value) ) + { + Clear(); + m_bAudit = true; + continue; + } + + if ( GetOption(argv[argi],"dump",value) ) + { + Clear(); + m_bDump = true; + continue; + } + + if ( GetOption(argv[argi],"tree",value) ) + { + Clear(); + m_bTestTree = true; + continue; + } + + if ( GetOption(argv[argi],"crashtestzerodiv",value) ) + { + Clear(); + m_bCrashTestDblZeroDiv = true; + continue; + } + + if ( GetOption(argv[argi],"crashtestoverflow",value) ) + { + Clear(); + m_bCrashTestDblOverflow = true; + continue; + } + + if ( GetOption(argv[argi],"crashtestinvalidop",value) ) + { + Clear(); + m_bCrashTestDblInvalidOp = true; + continue; + } + + if ( GetOption(argv[argi],"purify",value) ) + { + Clear(); + m_bPurify = true; + continue; + } + + if ( GetOption(argv[argi],"clspt",value) ) + { + Clear(); + m_bTestClsPtCrv = true; + m_bTestClsPtSrf = true; + m_bTestClsPtMesh = true; + continue; + } + + if ( GetOption(argv[argi],"clsptcrv",value) ) + { + Clear(); + m_bTestClsPtCrv = true; + continue; + } + + if ( GetOption(argv[argi],"clsptsrf",value) ) + { + Clear(); + m_bTestClsPtSrf = true; + continue; + } + + if ( GetOption(argv[argi],"clsptmesh",value) ) + { + Clear(); + m_bTestClsPtMesh = true; + continue; + } + + if ( GetOption(argv[argi],"ccx",value) ) + { + Clear(); + m_bTestCCX = true; + continue; + } + + + if ( GetOption(argv[argi],"csx",value) ) + { + Clear(); + m_bTestCSX = true; + continue; + } + + if ( GetOption(argv[argi],"ssx",value) ) + { + Clear(); + m_bTestSSX = true; + continue; + } + + if ( GetOption(argv[argi],"zlib",value) ) + { + Clear(); + m_bTestZLibCompression = true; + m_zlib_filespec = value; + continue; + } + + if ( GetOption(argv[argi],"notrims",value) ) + { + m_bTrimsAreCurves = false; + continue; + } + + if ( GetOption(argv[argi],"noedges",value) ) + { + m_bEdgesAreCurves = false; + continue; + } + + { + printf("%s command line options\n",argv[0]); + printf(" -? print this message\n"); + printf(" -help print this message\n"); + printf(" -3dm:filename.3dm input 3dm model to test\n"); + printf(" -log:filename.txt output text file for results\n"); + printf(" -purify limited tests for dog slow purify\n"); + printf(" -audit audit 3dm model\n"); + printf(" -dump: dump 3dm model\n"); + printf(" -notrims do not include brep trims with curves\n"); + printf(" -noedge do not include brep edges with curves\n"); + printf(" -tree test trees\n"); + printf(" -clspt test closest point to anything\n"); + printf(" -clsptcrv test closest point to curve\n"); + printf(" -clsptsrf test closest point to surface\n"); + printf(" -clsptmesh test closest point to mesh\n"); + printf(" -ccx test curve-curve intersection\n"); + printf(" -csx test curve and edge-surface intersection\n"); + printf(" -ssx test surface-surface intersection\n"); + printf(" -crashtestzerodiv test 1.0/0.0 exception handling\n"); + printf(" -crashtestoverflow test 1.0e200*2.0e222 exception handling\n"); + printf(" -crashtestinvalidop test log(negative number) exception handling\n"); + printf("\n"); + printf(" If no 3dm filename is specified, then\n"); + printf(" %s is used.\n",m_default_3dm_filename.Array()); + printf(" If no filename is specified with the -log option, then\n"); + printf(" %s is used.\n",m_default_log_filename.Array()); + printf(" If no options are specified, then all tests are performed.\n"); + + return false; + } + } + + return true; +} + +class CAlphaOmega +{ +public: + CAlphaOmega(); + ~CAlphaOmega(); +}; + +CAlphaOmega::CAlphaOmega() +{ + TL_Begin(); + + // Break when error occurs + ON_EnableDebugBreak(true); + + ON_FPU_Init(); + + // Mask minor exceptions (underflow, partial loss of precision, denormal ) + // but crash on the bad exceptions (zero divide, overflow, invalid op) + ON__UINT32 bad_ex = ON_FPU_EX_ZERODIVIDE + | ON_FPU_EX_OVERFLOW + | ON_FPU_EX_INVALID; + ON_FPU_UnmaskExceptions(bad_ex); +} + +CAlphaOmega::~CAlphaOmega() +{ + TL_End(); + + // The ON::End() call cleans up runtime class + // informtion so that Purify doesn't complain about leaks. + // OpenNURBS is completely useless after this call. + ON::End(); +} + + + + +class CCheckForErrors +{ +public: + CCheckForErrors(ON_TextLog& text_log,const wchar_t* testname) + : m_text_log(text_log), + m_test_name(testname) + { + m_error_count1 = m_error_count0 = ON_GetErrorCount(); + m_warning_count1 = m_warning_count0 = ON_GetWarningCount(); + } + + ~CCheckForErrors() + { + m_error_count1 = ON_GetErrorCount()-m_error_count0; + m_warning_count1 = ON_GetWarningCount()-m_warning_count0; + + if ( m_error_count1 > 0 || m_warning_count1 > 0 ) + { + m_text_log.Print("\n"); + m_text_log.Print( L"ERRORS in %s tests: %d openurbs error(s) and %d opennurbs warning(s).\n", + m_test_name.Array(), + m_error_count1, + m_warning_count1); + } + } + + ON_TextLog& m_text_log; + ON_wString m_test_name; + int m_error_count0; + int m_error_count1; + int m_warning_count0; + int m_warning_count1; + +private: + CCheckForErrors(); +}; + + +int main( int argc, const char* argv[]) +{ + // The alpha-omega constructor/destructor handles TL and ON + // library initialization and cleanup. + CAlphaOmega alphaomega; + + // test_list controls what tests are performed. + CTestList test_list; + + // If no 3dm file is specified, you get to use whatever you have in + // your MyTest.3dm file. Do NOT check a MyTest.3dm into SourceSafe + // or you mess up everybody else. If the command line has a + // -3dm:filename option, then m_3dm_filename will be set to the + // filename specified on the command line. + + if ( !test_list.ParseCommandLine(argc,argv) ) + { + return -1; + } + + ///////////////////////////////////////////////////////////////// + // + // Set up text_log + // + FILE* log_fp = 0; // = fopen("C:\\TestResults.txt","w"); + if ( 0 == log_fp ) + { + const wchar_t* log_filename = test_list.m_log_filename; + if ( log_filename && log_filename[0] ) + { + log_fp = _wfopen(log_filename,L"w"); + if ( !log_fp ) + { + printf("Unable to open log file \"%S\".\n",log_filename); + return -1; + } + else + { + printf("Beginning tests.\n Results will be in \"%S\".\n",log_filename); + } + } + } + + ON_TextLog text_log(log_fp); + text_log.SetIndentSize(2); + + TEST_RESULTS test_results; + memset(&test_results,0,sizeof(test_results)); + int repair_count = 0; + + + if ( test_list.m_bCrashTestDblZeroDiv ) + { + CrashTestHelper(6,text_log); + } + if ( test_list.m_bCrashTestDblInvalidOp ) + { + CrashTestHelper(9,text_log); + } + if ( test_list.m_bCrashTestDblOverflow ) + { + CrashTestHelper(10,text_log); + } + + + ///////////////////////////////////////////////////////////////// + // + // zlib test's dont use a 3dm model + // + if ( test_list.m_bTestZLibCompression + //&& !test_list.m_zlib_filespec.IsEmpty() + ) + { + TestZLibCompression( test_list.m_zlib_filespec, text_log ); + } + else if ( !test_list.m_3dm_filename.IsEmpty() ) + { + //ON_TEST_InitFPErrorHandler(); + + test_results.m_text_log = &text_log; + + TestPrintHeader(text_log,argc,argv); + + CTestModel model; + model.m_bPurify = test_list.m_bPurify; + { + CCheckForErrors check_for_errors(text_log,L"read 3dm file"); + if ( !model.Read(test_list.m_3dm_filename,&text_log) ) + { + if ( log_fp ) + printf("Unable to read 3dm file \"%S\".\n",test_list.m_3dm_filename.Array()); + } + + // Sort model object table by uuid and then fill in the + // CTestModel m_curves[], m_surfaces[], and m_breps[] arrays + model.GetTestObjects( test_list.m_bEdgesAreCurves, test_list.m_bTrimsAreCurves ); + } + + if ( test_list.m_bDump ) + { + CCheckForErrors check_for_errors(text_log,L"dump 3dm file"); + model.Dump(text_log); + } + else + { + text_log.Print(L"3dm file: %s (%d objects)\n",test_list.m_3dm_filename.Array(),model.m_object_table.Count()); + } + + if ( test_list.m_bAudit ) + { + CCheckForErrors check_for_errors(text_log,L"audit 3dm file"); + model.Audit(true,&repair_count,&text_log, NULL); + } + + // Sorting the model object table keeps + // the report's object order consistent + model.m_object_table.HeapSort(sort_model_helper); + + if ( test_list.m_bTestTree ) + { + if ( log_fp ) + printf("Testing curve and surface trees.\n"); + CCheckForErrors check_for_errors(text_log,L"curve and surface Tree"); + TestTree(model,text_log); + } + + if ( test_list.m_bTestClsPtCrv ) + { + if ( log_fp ) + printf("Testing closest point to curve.\n"); + CCheckForErrors check_for_errors(text_log,L"closest point to curve"); + TestClosestPoint(model,text_log, true, false, false ); + } + + if ( test_list.m_bTestClsPtSrf ) + { + if ( log_fp ) + printf("Testing closest point to surface.\n"); + CCheckForErrors check_for_errors(text_log,L"closest point to surface"); + TestClosestPoint(model,text_log, false, true, false ); + } + + if ( test_list.m_bTestClsPtMesh ) + { + if ( log_fp ) + printf("Testing closest point to mesh.\n"); + CCheckForErrors check_for_errors(text_log,L"closest point to mesh"); + TestClosestPoint(model,text_log, false, false, true ); + } + + if ( test_list.m_bTestCCX ) + { + if ( log_fp ) + printf("Testing curve-curve intersection.\n"); + CCheckForErrors check_for_errors(text_log,L"curve-curve intersection"); + TestCurveCurveIntersection(model, text_log); + } + + if ( test_list.m_bTestCSX ) + { + if ( log_fp ) + printf("Testing curve-surface intersection.\n"); + CCheckForErrors check_for_errors(text_log,L"curve-surface intersection"); + TestCurveSurfaceIntersection(model,text_log); + } + + if ( test_list.m_bTestSSX ) + { + if ( log_fp ) + printf("Testing surface-surface intersection.\n"); + CCheckForErrors check_for_errors(text_log,L"surface-surface intersection"); + // TestSurfaceSurfaceIntersection(text_log); + text_log.Print("Surface-surface intersection tests not ready.\n"); + } + } + + if (log_fp) + { + printf("Finished tests. Results are in \"%S\".\n",test_list.m_log_filename.Array()); + fclose(log_fp); + log_fp = 0; + } + +#if (_DEBUG) + { + // This getchar() is here so the results window doesnt' vanish + // when the debugger is being used. + printf("\nPress ENTER to finish.\n"); + getchar(); + } +#endif + + return test_results.failed_count+starthere__starthere; +} + +void TestPrintHeader( ON_TextLog& text_log, int argc, const char* argv[] ) +{ + int year = 0; + int month = 1; + int date = 0; + int hour = 0; + int minute = 0; + int second = 0; + TLI_CurrentTime( &year, &month, &date, &hour, &minute, &second ); + if ( year < 1900 ) + { + year += 1900; + if ( year < 1970 ) + year += 1970; + } + char* month_name[12] = {"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"}; + text_log.Print("Test: %s\n",argv[0]); + text_log.PushIndent(); + for ( int argi = 1; argi < argc; argi++ ) + { + text_log.Print("%s\n",argv[argi]); + } + text_log.PopIndent(); + text_log.Print("Test date: %s %2d %d %2d:%02d:%02d UCT\n",month_name[month-1],date,year,hour,minute,second); + const char* compile_time = __DATE__ " " __TIME__; + text_log.Print("Compile date: %s local time\n",compile_time); + text_log.Print("OpenNURBS version: %d\n",ON::Version()); +#if defined(_DEBUG) + text_log.Print("Build settings: Debug\n"); +#else + text_log.Print("Build settings: Release\n"); +#endif + + text_log.Print("\n"); +} + + +void TestPrintFunctionHeader( ON_TextLog& text_log, const char* function_name, const char* file_name, const char* compile_time ) +{ + int year = 0; + int month = 1; + int date = 0; + int hour = 0; + int minute = 0; + int second = 0; + TLI_CurrentTime( &year, &month, &date, &hour, &minute, &second ); + if ( year < 1900 ) + { + year += 1900; + if ( year < 1970 ) + year += 1970; + } + char* month_name[12] = {"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"}; + text_log.Print("Test function: %s in %s\n",function_name,file_name); + text_log.Print("Test date: %s %2d %d %2d:%02d:%02d UCT\n",month_name[month-1],date,year,hour,minute,second); + text_log.Print("Compile date: %s local time\n",compile_time); + + text_log.Print("\n"); +} + +double TEST_ElapsedTime() +{ + static clock_t time0=0; + double elapsed_time; + clock_t time; + time = clock(); + elapsed_time = ((double)(time - time0))/((double)CLOCKS_PER_SEC); + time0 = time; + return elapsed_time; +} + + +/**************************************************************************** + ** + ** _matherr() ( replaces standard math libraries matherr() ) + ** Calls ON_TEST_MathError() whenever a math error occurs. + ** + ****************************************************************************/ + +int _matherr( struct _exception *ExceptStruct ) +{ + /* NOTE: This function ALWAYS returns a "1" so that the + * system's math library will not attempt to print + * some message on stderr/stdout. + */ + switch(ExceptStruct->type) /* <- Good location for a debugger breakpoint */ + { + case _DOMAIN: /* domain error */ + /* CRITICAL MATH ERROR - FIX THIS BUG */ + errno = EDOM; + ON_MathError("rhino.exe","_matherr DOMAIN exception",ExceptStruct->name); + break; + + case _SING: /* function singularity */ + /* CRITICAL MATH ERROR - FIX THIS BUG */ + errno = EDOM; + ON_MathError("rhino.exe","_matherr SING exception",ExceptStruct->name); + break; + + case _OVERFLOW: + /* CRITICAL MATH ERROR - FIX THIS BUG */ + errno = ERANGE; + ON_MathError("rhino.exe","_matherr OVERFLOW exception",ExceptStruct->name); + break; + + case _UNDERFLOW: + /* ignore underflow errors - they frequently happen */ + errno = ERANGE; + break; + + case _TLOSS: /* total loss of significance (like sin(1.0e300)) */ + errno = ERANGE; + ON_MathError("rhino.exe","_matherr TLOSS exception",ExceptStruct->name); + break; + + case _PLOSS: /* partial loss of significance */ + /* do not modify ExceptStruct->retval and hope for the best */ + errno = ERANGE; + ON_MathError("rhino.exe","_matherr PLOSS exception",ExceptStruct->name); + break; + + default: /* undocumented error */ + errno = EDOM; + ON_MathError("rhino.exe","_matherr UNDOCUMENTED ERROR",ExceptStruct->name); + break; + } + return 1; /* suppress operating system's error handling */ +} diff --git a/tests/Tests.h b/tests/Tests.h new file mode 100644 index 00000000..c1e5f254 --- /dev/null +++ b/tests/Tests.h @@ -0,0 +1,257 @@ +#pragma once +#if !defined(OPENNURBS_TESTS_INC_) + +#if !defined(_CRT_SECURE_NO_DEPRECATE) +#define _CRT_SECURE_NO_DEPRECATE +// Visual Studio 2005 issues a C4996 warning for lots of +// standard C runtime functions that take string pointers. +// The _CRT_SECURE_NO_DEPRECATE suppresses these warnings. +#endif + +#pragma warning( push ) +#include "../opennurbs.h" +#pragma warning( pop ) + +#pragma warning( push ) +//#include +//#include +#pragma warning( pop ) + +#pragma warning( push ) +#include "../../tl/tl.h" +#include "../../tl/tl_array.h" +#include "../../tl/tl_gslib.h" +#include "../../tl/tl_init.h" +#include "../../tl/tl_brep.h" +#include "../../tl/tl_nurbscurve.h" +#include "../../tl/tl_nurbssurface.h" +#include "../../tl/tl_tree.h" +#include "../../tl/tl_optimize.h" + +#pragma warning( pop ) + + +#define TL_GSLIB_INTERFACE_CODE +#pragma warning( push ) +#pragma warning( disable : 4267 ) +#pragma warning( disable : 4313 ) +#include "../../tl/tl_gslib_inc.h" +#pragma warning( pop ) +#undef TL_GSLIB_INTERFACE_CODE + + +#if defined(_DEBUG) + +#pragma comment(lib, "../../tl/DebugStaticLib/tl_staticlibd.lib") +#pragma comment(lib, "../../gslib/DebugStaticLib/gslib_staticlibd.lib") +#pragma comment(lib, "../DebugStaticLib/opennurbs_staticd.lib") +#pragma comment(lib, "../zlib/Debug/zlib_d.lib") +#pragma comment(lib, "msvcrtd.lib") + +#pragma comment(linker, "/disallowlib:msvcrt.lib") + +#else + +#pragma comment(lib, "../../tl/ReleaseStaticLib/tl_staticlib.lib") +#pragma comment(lib, "../../gslib/ReleaseStaticLib/gslib_staticlib.lib") +#pragma comment(lib, "../ReleaseStaticLib/opennurbs_static.lib") +#pragma comment(lib, "../zlib/Release/zlib.lib") +#pragma comment(lib, "msvcrt.lib") + +#pragma comment(linker, "/disallowlib:msvcrtd.lib") + +#endif + +#pragma comment(linker, "/disallowlib:libcd.lib") +#pragma comment(linker, "/disallowlib:libcmtd.lib") +#pragma comment(linker, "/disallowlib:libc.lib") +#pragma comment(linker, "/disallowlib:libcmt.lib") + +#pragma comment(linker, "/disallowlib:mfco42ud.lib") +#pragma comment(linker, "/disallowlib:mfcd42ud.lib") +#pragma comment(linker, "/disallowlib:mfcn42ud.lib") +#pragma comment(linker, "/disallowlib:mfc42.lib") +#pragma comment(linker, "/disallowlib:mfcs42.lib") +#pragma comment(linker, "/disallowlib:mfc42ud.lib") +#pragma comment(linker, "/disallowlib:mfcs42ud.lib") +#pragma comment(linker, "/disallowlib:mfc42u.lib") +#pragma comment(linker, "/disallowlib:mfcs42u.lib") +#pragma comment(linker, "/disallowlib:mfco42d.lib") +#pragma comment(linker, "/disallowlib:mfcd42d.lib") +#pragma comment(linker, "/disallowlib:mfcn42d.lib") +#pragma comment(linker, "/disallowlib:mfc42d.lib") +#pragma comment(linker, "/disallowlib:mfcs42d.lib") +#pragma comment(linker, "/disallowlib:mfc42ud.lib") +#pragma comment(linker, "/disallowlib:mfcs42ud.lib") +#pragma comment(linker, "/disallowlib:mfc42u.lib") +#pragma comment(linker, "/disallowlib:mfcs42u.lib") +#pragma comment(linker, "/disallowlib:mfco42d.lib") +#pragma comment(linker, "/disallowlib:mfcd42d.lib") +#pragma comment(linker, "/disallowlib:mfcn42d.lib") +#pragma comment(linker, "/disallowlib:mfc42d.lib") +#pragma comment(linker, "/disallowlib:mfcs42d.lib") +#pragma comment(linker, "/disallowlib:mfc42.lib") +#pragma comment(linker, "/disallowlib:mfcs42.lib") +#pragma comment(linker, "/disallowlib:mfc42u.lib") +#pragma comment(linker, "/disallowlib:mfcs42u.lib") +#pragma comment(linker, "/disallowlib:mfco42ud.lib") +#pragma comment(linker, "/disallowlib:mfcd42ud.lib") +#pragma comment(linker, "/disallowlib:mfcn42ud.lib") +#pragma comment(linker, "/disallowlib:mfc42d.lib") +#pragma comment(linker, "/disallowlib:mfcs42d.lib") +#pragma comment(linker, "/disallowlib:mfc42.lib") +#pragma comment(linker, "/disallowlib:mfcs42.lib") +#pragma comment(linker, "/disallowlib:mfc42ud.lib") +#pragma comment(linker, "/disallowlib:mfcs42ud.lib") + +#pragma comment(linker, "/disallowlib:nafxcwd.lib") +#pragma comment(linker, "/disallowlib:nafxcw.lib") +#pragma comment(linker, "/disallowlib:uafxcwd.lib") +#pragma comment(linker, "/disallowlib:uafxcw.lib") + +// Prohibit any future inclusion of CRTDBG.H by defining _INC_CRTDBG. +#if defined(_INC_CRTDBG) +#error Never include CRTDBG.H in these tests. It breaks purify. +#endif +#define _INC_CRTDBG + + +class CTestObject +{ +public: + CTestObject(); + ~CTestObject(); + + void Destroy(); + + int m_model_object_index; // ONX_Model m_object_table[] index + ON_UUID m_uuid; // 3dm parent object uuid + ON_wString m_name; // 3dm parent object name + ON_COMPONENT_INDEX m_ci; // index of object in parent +}; + +class CTestCurve : public CTestObject +{ +public: + CTestCurve(); + ~CTestCurve(); + + void Destroy(); + + const ON_Curve* m_curve; + + // True if curve and NURBS parameterizations differ + bool m_bAdjustNurbsParameter; + + const TL_NurbsCurve& NurbsCurve(); + + const class IwBSplineCurve* GooseCurve(); + + + TL_NurbsCurve m_nurbs_curve; + TL_NURB m_nurbcrv; + + class IwBSplineCurve* m_goose_curve; +}; + +class CTestSurface : public CTestObject +{ +public: + CTestSurface(); + ~CTestSurface(); + + void Destroy(); + + const ON_Surface* m_surface; + + // True if surface and NURBS parameterizations differ + bool m_bAdjustNurbsParameter; + + const TL_NurbsSurface& NurbsSurface(); + + const class IwBSplineSurface* GooseSurface(); + + + // call NurbsSurface() to get this surface + TL_NurbsSurface m_nurbs_surface; + TL_NURBSRF m_nurbsrf; + + // call GooseSurface() to get this surface + class IwBSplineSurface* m_goose_surface; +}; + +class CTestBrep : public CTestObject +{ +public: + CTestBrep(); + ~CTestBrep(); + + const TL_Brep* m_brep; +}; + +class CTestModel : public ONX_Model +{ +public: + CTestModel(); + + // Digs through the model and harvests curves, surfaces, + // and breps for testing and puts them in the m_curves[], + // m_surfaces[], m_breps[] arrays. + void GetTestObjects( bool bEdgesAreCurve, bool bTrimsAreCurves ); + + ON_ClassArray m_curves; + ON_ClassArray m_surfaces; + ON_ClassArray m_breps; + + bool m_bPurify; // true if tests should be limited because purify is running. +}; + + +double TEST_ElapsedTime(); +//#define ON_TEST_DECL(func) ON_TEST_BeginTest(test_results,#func); test_results->ok = func(*test_results->m_text_log); ON_TEST_EndTest(test_results); + +#define TEST_HEADER(text_log,function_name) TestPrintFunctionHeader( text_log, function_name, __FILE__, __DATE__ " " __TIME__ ); + +void TestPrintHeader( + ON_TextLog& text_log, + int argc, + const char* argv[] + ); + +void TestPrintFunctionHeader( + ON_TextLog& text_log, + const char* function_name, + const char* file_name, + const char* compile_time + ); + +void TestTree( const ONX_Model& model, ON_TextLog& text_log ); +void TestClosestPoint( const ONX_Model& model, + ON_TextLog& text_log, + bool bDoCurves, + bool bDoSurfaces, + bool bDoMeshes ); +void TestCurveCurveIntersection(const ONX_Model& model, ON_TextLog& text_log ); +void TestCurveSurfaceIntersection( CTestModel& model, ON_TextLog& text_log ); +void TestZLibCompression( + const wchar_t* filespec, + ON_TextLog& text_log + ); + + +// quick-n-dirty ccx for testing curve tree +void DaleCCX( const ON_Curve& curveA, + const ON_Curve& curveB, + double tol3d, + ON_SimpleArray& ccx + ); + + +// new FAST! ccx +void GregCCX( const ON_Curve& curveA, + const ON_Curve& curveB, + double tol3d, + ON_SimpleArray& ccx + ); + +#endif diff --git a/tests/Tests.vcproj b/tests/Tests.vcproj new file mode 100644 index 00000000..712ae8a9 --- /dev/null +++ b/tests/Tests.vcproj @@ -0,0 +1,249 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/test_ClassSize.cpp b/tests/test_ClassSize.cpp new file mode 100644 index 00000000..171bc691 --- /dev/null +++ b/tests/test_ClassSize.cpp @@ -0,0 +1,2628 @@ +#include +#include "../opennurbs_public.h" + +TEST(ONTest, ONTestClassSizeON_3dmObjectAttributes) { + + size_t sz = sizeof(ON_3dmObjectAttributes); //368 + + EXPECT_EQ(sz, 368); + +} + +TEST(ONTest, ONTestClassSizeON_3dmRevisionHistory) { + + size_t sz = sizeof(ON_3dmRevisionHistory); //96 + + EXPECT_EQ(sz, 96); + +} + +TEST(ONTest, ONTestClassSizeON_3dmNotes) { + + size_t sz = sizeof(ON_3dmNotes); //32 + + EXPECT_EQ(sz, 32); + +} + +TEST(ONTest, ONTestClassSizeON_3dmApplication) { + + size_t sz = sizeof(ON_3dmApplication); //24 + + EXPECT_EQ(sz, 24); + +} + +TEST(ONTest, ONTestClassSizeON_3dmProperties) { + + size_t sz = sizeof(ON_3dmProperties); //512 + + EXPECT_EQ(sz, 512); + +} + +TEST(ONTest, ONTestClassSizeON_3dmUnitsAndTolerances) { + + size_t sz = sizeof(ON_3dmUnitsAndTolerances); //56 + + EXPECT_EQ(sz, 56); + +} + +TEST(ONTest, ONTestClassSizeON_3dmAnnotationSettings) { + + size_t sz = sizeof(ON_3dmAnnotationSettings); //104 + + EXPECT_EQ(sz, 104); + +} + +TEST(ONTest, ONTestClassSizeON_3dmConstructionPlaneGridDefaults) { + + size_t sz = sizeof(ON_3dmConstructionPlaneGridDefaults); //32 + + EXPECT_EQ(sz, 32); + +} + +TEST(ONTest, ONTestClassSizeON_3dmConstructionPlane) { + + size_t sz = sizeof(ON_3dmConstructionPlane); //168 + + EXPECT_EQ(sz, 168); + +} + +TEST(ONTest, ONTestClassSizeON_3dmViewPosition) { + + size_t sz = sizeof(ON_3dmViewPosition); //40 + + EXPECT_EQ(sz, 40); + +} + +TEST(ONTest, ONTestClassSizeON_3dmViewTraceImage) { + + size_t sz = sizeof(ON_3dmViewTraceImage); //336 + + EXPECT_EQ(sz, 336); + +} + +TEST(ONTest, ONTestClassSizeON_3dmWallpaperImage) { + + size_t sz = sizeof(ON_3dmWallpaperImage); //192 + + EXPECT_EQ(sz, 192); + +} + +TEST(ONTest, ONTestClassSizeON_3dmPageSettings) { + + size_t sz = sizeof(ON_3dmPageSettings); //64 + + EXPECT_EQ(sz, 64); + +} + +TEST(ONTest, ONTestClassSizeON_StandardDisplayModeId) { + + size_t sz = sizeof(ON_StandardDisplayModeId); //1 + + EXPECT_EQ(sz, 1); + +} + +TEST(ONTest, ONTestClassSizeON_3dmView) { + + size_t sz = sizeof(ON_3dmView); //1520 + + EXPECT_EQ(sz, 1520); + +} + +TEST(ONTest, ONTestClassSizeON_3dmRenderSettings) { + + size_t sz = sizeof(ON_3dmRenderSettings); //184 + + EXPECT_EQ(sz, 184); + +} + +TEST(ONTest, ONTestClassSizeON_EarthAnchorPoint) { + + size_t sz = sizeof(ON_EarthAnchorPoint); //152 + + EXPECT_EQ(sz, 152); + +} + +TEST(ONTest, ONTestClassSizeON_3dmIOSettings) { + + size_t sz = sizeof(ON_3dmIOSettings); //8 + + EXPECT_EQ(sz, 8); + +} + +TEST(ONTest, ONTestClassSizeON_3dmSettings) { + + size_t sz = sizeof(ON_3dmSettings); //1560 + + EXPECT_EQ(sz, 1560); + +} + +TEST(ONTest, ONTestClassSizeON_3dmAnimationProperties) { + + size_t sz = sizeof(ON_3dmAnimationProperties); //304 + + EXPECT_EQ(sz, 304); + +} + +TEST(ONTest, ONTestClassSizeON_Annotation) { + + size_t sz = sizeof(ON_Annotation); //792 + + EXPECT_EQ(sz, 792); + +} + +TEST(ONTest, ONTestClassSizeON_TextDot) { + + size_t sz = sizeof(ON_TextDot); //72 + + EXPECT_EQ(sz, 72); + +} + +TEST(ONTest, ONTestClassSizeON_Arc) { + + size_t sz = sizeof(ON_Arc); //152 + + EXPECT_EQ(sz, 152); + +} + +TEST(ONTest, ONTestClassSizeON_ArcCurve) { + + size_t sz = sizeof(ON_ArcCurve); //192 + + EXPECT_EQ(sz, 192); + +} + +TEST(ONTest, ONTestClassSizeON_Buffer) { + + size_t sz = sizeof(ON_Buffer); //64 + + EXPECT_EQ(sz, 64); + +} + +TEST(ONTest, ONTestClassSizeON_3DM_BIG_CHUNK) { + + size_t sz = sizeof(ON_3DM_BIG_CHUNK); //40 + + EXPECT_EQ(sz, 40); + +} + +TEST(ONTest, ONTestClassSizeON_UserDataItemFilter) { + + size_t sz = sizeof(ON_UserDataItemFilter); //40 + + EXPECT_EQ(sz, 40); + +} + +TEST(ONTest, ONTestClassSizeON_ComponentManifest) { + + size_t sz = sizeof(ON_ComponentManifest); //8 + + EXPECT_EQ(sz, 8); + +} + +TEST(ONTest, ONTestClassSizeON_ComponentManifestItem) { + + size_t sz = sizeof(ON_ComponentManifestItem); //80 + + EXPECT_EQ(sz, 80); + +} + +TEST(ONTest, ONTestClassSizeON_ManifestMapItem) { + + size_t sz = sizeof(ON_ManifestMapItem); //48 + + EXPECT_EQ(sz, 48); + +} + +TEST(ONTest, ONTestClassSizeON_ManifestMap) { + + size_t sz = sizeof(ON_ManifestMap); //8 + + EXPECT_EQ(sz, 8); + +} + +TEST(ONTest, ONTestClassSizeON_3dmAnnotationContext) { + + size_t sz = sizeof(ON_3dmAnnotationContext); //64 + + EXPECT_EQ(sz, 64); + +} + +TEST(ONTest, ONTestClassSizeON_3dmArchiveTableStatus) { + + size_t sz = sizeof(ON_3dmArchiveTableStatus); //24 + + EXPECT_EQ(sz, 24); + +} + +TEST(ONTest, ONTestClassSizeON_BinaryArchive) { + + size_t sz = sizeof(ON_BinaryArchive); //512 + + EXPECT_EQ(sz, 512); + +} + +TEST(ONTest, ONTestClassSizeON_3dmGoo) { + + size_t sz = sizeof(ON_3dmGoo); //32 + + EXPECT_EQ(sz, 32); + +} + +TEST(ONTest, ONTestClassSizeON_BinaryFile) { + + size_t sz = sizeof(ON_BinaryFile); //560 + + EXPECT_EQ(sz, 560); + +} + +TEST(ONTest, ONTestClassSizeON_BinaryArchiveBuffer) { + + size_t sz = sizeof(ON_BinaryArchiveBuffer); //520 + + EXPECT_EQ(sz, 520); + +} + +TEST(ONTest, ONTestClassSizeON_Read3dmBufferArchive) { + + size_t sz = sizeof(ON_Read3dmBufferArchive); //576 + + EXPECT_EQ(sz, 576); + +} + +TEST(ONTest, ONTestClassSizeON_Write3dmBufferArchive) { + + size_t sz = sizeof(ON_Write3dmBufferArchive); //592 + + EXPECT_EQ(sz, 592); + +} + +TEST(ONTest, ONTestClassSizeON_DebugWriteArchive) { + + size_t sz = sizeof(ON_DebugWriteArchive); //32 + + EXPECT_EQ(sz, 32); + +} + +TEST(ONTest, ONTestClassSizeON_UuidPair) { + + size_t sz = sizeof(ON_UuidPair); //32 + + EXPECT_EQ(sz, 32); + +} + +TEST(ONTest, ONTestClassSizeON_UuidList) { + + size_t sz = sizeof(ON_UuidList); //32 + + EXPECT_EQ(sz, 32); + +} + +TEST(ONTest, ONTestClassSizeON_UuidIndexList) { + + size_t sz = sizeof(ON_UuidIndexList); //32 + + EXPECT_EQ(sz, 32); + +} + +TEST(ONTest, ONTestClassSizeON_UuidPtrList) { + + size_t sz = sizeof(ON_UuidPtrList); //32 + + EXPECT_EQ(sz, 32); + +} + +TEST(ONTest, ONTestClassSizeON_UuidPairList) { + + size_t sz = sizeof(ON_UuidPairList); //32 + + EXPECT_EQ(sz, 32); + +} + +TEST(ONTest, ONTestClassSizeON_2dexMap) { + + size_t sz = sizeof(ON_2dexMap); //32 + + EXPECT_EQ(sz, 32); + +} + +TEST(ONTest, ONTestClassSizeON_Base64EncodeStream) { + + size_t sz = sizeof(ON_Base64EncodeStream); //64 + + EXPECT_EQ(sz, 64); + +} + +TEST(ONTest, ONTestClassSizeON_DecodeBase64) { + + size_t sz = sizeof(ON_DecodeBase64); //552 + + EXPECT_EQ(sz, 552); + +} + +TEST(ONTest, ONTestClassSizeON_Extrusion) { + + size_t sz = sizeof(ON_Extrusion); //208 + + EXPECT_EQ(sz, 208); + +} + +TEST(ONTest, ONTestClassSizeON_PolynomialCurve) { + + size_t sz = sizeof(ON_PolynomialCurve); //56 + + EXPECT_EQ(sz, 56); + +} + +TEST(ONTest, ONTestClassSizeON_PolynomialSurface) { + + size_t sz = sizeof(ON_PolynomialSurface); //72 + + EXPECT_EQ(sz, 72); + +} + +TEST(ONTest, ONTestClassSizeON_BezierCurve) { + + size_t sz = sizeof(ON_BezierCurve); //32 + + EXPECT_EQ(sz, 32); + +} + +TEST(ONTest, ONTestClassSizeON_BezierSurface) { + + size_t sz = sizeof(ON_BezierSurface); //40 + + EXPECT_EQ(sz, 40); + +} + +TEST(ONTest, ONTestClassSizeON_BezierCage) { + + size_t sz = sizeof(ON_BezierCage); //48 + + EXPECT_EQ(sz, 48); + +} + +TEST(ONTest, ONTestClassSizeON_BezierCageMorph) { + + size_t sz = sizeof(ON_BezierCageMorph); //216 + + EXPECT_EQ(sz, 216); + +} + +TEST(ONTest, ONTestClassSizeON_Bitmap) { + + size_t sz = sizeof(ON_Bitmap); //328 + + EXPECT_EQ(sz, 328); + +} + +TEST(ONTest, ONTestClassSizeON_WindowsBitmap) { + + size_t sz = sizeof(ON_WindowsBitmap); //352 + + EXPECT_EQ(sz, 352); + +} + +TEST(ONTest, ONTestClassSizeON_WindowsBitmapEx) { + + size_t sz = sizeof(ON_WindowsBitmapEx); //352 + + EXPECT_EQ(sz, 352); + +} + +TEST(ONTest, ONTestClassSizeON_EmbeddedBitmap) { + + size_t sz = sizeof(ON_EmbeddedBitmap); //352 + + EXPECT_EQ(sz, 352); + +} + +TEST(ONTest, ONTestClassSizeON_BoundingBox) { + + size_t sz = sizeof(ON_BoundingBox); //48 + + EXPECT_EQ(sz, 48); + +} + +TEST(ONTest, ONTestClassSizeON_BoundingBoxAndHash) { + + size_t sz = sizeof(ON_BoundingBoxAndHash); //72 + + EXPECT_EQ(sz, 72); + +} + +TEST(ONTest, ONTestClassSizeON_BoundingBoxCache) { + + size_t sz = sizeof(ON_BoundingBoxCache); //584 + + EXPECT_EQ(sz, 584); + +} + +TEST(ONTest, ONTestClassSizeON_Box) { + + size_t sz = sizeof(ON_Box); //176 + + EXPECT_EQ(sz, 176); + +} + +TEST(ONTest, ONTestClassSizeON_BrepVertex) { + + size_t sz = sizeof(ON_BrepVertex); //88 + + EXPECT_EQ(sz, 88); + +} + +TEST(ONTest, ONTestClassSizeON_BrepEdge) { + + size_t sz = sizeof(ON_BrepEdge); //136 + + EXPECT_EQ(sz, 136); + +} + +TEST(ONTest, ONTestClassSizeON_BrepTrim) { + + size_t sz = sizeof(ON_BrepTrim); //232 + + EXPECT_EQ(sz, 232); + +} + +TEST(ONTest, ONTestClassSizeON_BrepLoop) { + + size_t sz = sizeof(ON_BrepLoop); //120 + + EXPECT_EQ(sz, 120); + +} + +TEST(ONTest, ONTestClassSizeON_BrepFaceSide) { + + size_t sz = sizeof(ON_BrepFaceSide); //48 + + EXPECT_EQ(sz, 48); + +} + +TEST(ONTest, ONTestClassSizeON_BrepRegion) { + + size_t sz = sizeof(ON_BrepRegion); //120 + + EXPECT_EQ(sz, 120); + +} + +TEST(ONTest, ONTestClassSizeON_BrepEdgeArray) { + + size_t sz = sizeof(ON_BrepEdgeArray); //24 + + EXPECT_EQ(sz, 24); + +} + +TEST(ONTest, ONTestClassSizeON_BrepTrimArray) { + + size_t sz = sizeof(ON_BrepTrimArray); //24 + + EXPECT_EQ(sz, 24); + +} + +TEST(ONTest, ONTestClassSizeON_BrepLoopArray) { + + size_t sz = sizeof(ON_BrepLoopArray); //24 + + EXPECT_EQ(sz, 24); + +} + +TEST(ONTest, ONTestClassSizeON_BrepFaceArray) { + + size_t sz = sizeof(ON_BrepFaceArray); //24 + + EXPECT_EQ(sz, 24); + +} + +TEST(ONTest, ONTestClassSizeON_BrepRegionTopology) { + + size_t sz = sizeof(ON_BrepRegionTopology); //56 + + EXPECT_EQ(sz, 56); + +} + +TEST(ONTest, ONTestClassSizeON_Brep) { + + size_t sz = sizeof(ON_Brep); //312 + + EXPECT_EQ(sz, 312); + +} + +TEST(ONTest, ONTestClassSizeON_Circle) { + + size_t sz = sizeof(ON_Circle); //136 + + EXPECT_EQ(sz, 136); + +} + +TEST(ONTest, ONTestClassSizeON_Color) { + + size_t sz = sizeof(ON_Color); //4 + + EXPECT_EQ(sz, 4); + +} + +TEST(ONTest, ONTestClassSizeON_CompressStream) { + + size_t sz = sizeof(ON_CompressStream); //64 + + EXPECT_EQ(sz, 64); + +} + +TEST(ONTest, ONTestClassSizeON_UncompressStream) { + + size_t sz = sizeof(ON_UncompressStream); //64 + + EXPECT_EQ(sz, 64); + +} + +TEST(ONTest, ONTestClassSizeON_CompressedBuffer) { + + size_t sz = sizeof(ON_CompressedBuffer); //48 + + EXPECT_EQ(sz, 48); + +} + +TEST(ONTest, ONTestClassSizeON_ComponentStatus) { + + size_t sz = sizeof(ON_ComponentStatus); //1 + + EXPECT_EQ(sz, 1); + +} + +TEST(ONTest, ONTestClassSizeON_AggregateComponentStatus) { + + size_t sz = sizeof(ON_AggregateComponentStatus); //32 + + EXPECT_EQ(sz, 32); + +} + +TEST(ONTest, ONTestClassSizeON_UniqueTester) { + + size_t sz = sizeof(ON_UniqueTester); //16 + + EXPECT_EQ(sz, 16); + +} + +TEST(ONTest, ONTestClassSizeON_Cone) { + + size_t sz = sizeof(ON_Cone); //144 + + EXPECT_EQ(sz, 144); + +} + +TEST(ONTest, ONTestClassSizeON_StopWatch) { + + size_t sz = sizeof(ON_StopWatch); //24 + + EXPECT_EQ(sz, 24); + +} + +TEST(ONTest, ONTestClassSizeON_MeshCurveParameters) { + + size_t sz = sizeof(ON_MeshCurveParameters); //80 + + EXPECT_EQ(sz, 80); + +} + +TEST(ONTest, ONTestClassSizeON_Curve) { + + size_t sz = sizeof(ON_Curve); //16 + + EXPECT_EQ(sz, 16); + +} + +TEST(ONTest, ONTestClassSizeON_CurveArray) { + + size_t sz = sizeof(ON_CurveArray); //24 + + EXPECT_EQ(sz, 24); + +} + +TEST(ONTest, ONTestClassSizeON_CurveOnSurface) { + + size_t sz = sizeof(ON_CurveOnSurface); //40 + + EXPECT_EQ(sz, 40); + +} + +TEST(ONTest, ONTestClassSizeON_CurveProxy) { + + size_t sz = sizeof(ON_CurveProxy); //64 + + EXPECT_EQ(sz, 64); + +} + +TEST(ONTest, ONTestClassSizeON_Cylinder) { + + size_t sz = sizeof(ON_Cylinder); //152 + + EXPECT_EQ(sz, 152); + +} + +TEST(ONTest, ONTestClassSizeON_2dex) { + + size_t sz = sizeof(ON_2dex); //8 + + EXPECT_EQ(sz, 8); + +} + +TEST(ONTest, ONTestClassSizeON_2udex) { + + size_t sz = sizeof(ON_2udex); //8 + + EXPECT_EQ(sz, 8); + +} + +TEST(ONTest, ONTestClassSizeON_3dex) { + + size_t sz = sizeof(ON_3dex); //12 + + EXPECT_EQ(sz, 12); + +} + +TEST(ONTest, ONTestClassSizeON_3udex) { + + size_t sz = sizeof(ON_3udex); //12 + + EXPECT_EQ(sz, 12); + +} + +TEST(ONTest, ONTestClassSizeON_4dex) { + + size_t sz = sizeof(ON_4dex); //16 + + EXPECT_EQ(sz, 16); + +} + +TEST(ONTest, ONTestClassSizeON_4udex) { + + size_t sz = sizeof(ON_4udex); //16 + + EXPECT_EQ(sz, 16); + +} + +TEST(ONTest, ONTestClassSizeON_COMPONENT_INDEX) { + + size_t sz = sizeof(ON_COMPONENT_INDEX); //8 + + EXPECT_EQ(sz, 8); + +} + +TEST(ONTest, ONTestClassSizeON_DetailView) { + + size_t sz = sizeof(ON_DetailView); //1608 + + EXPECT_EQ(sz, 1608); + +} + +TEST(ONTest, ONTestClassSizeON_Dimension) { + + size_t sz = sizeof(ON_Dimension); //880 + + EXPECT_EQ(sz, 880); + +} + +TEST(ONTest, ONTestClassSizeON_DimLinear) { + + size_t sz = sizeof(ON_DimLinear); //912 + + EXPECT_EQ(sz, 912); + +} + +TEST(ONTest, ONTestClassSizeON_DimAngular) { + + size_t sz = sizeof(ON_DimAngular); //944 + + EXPECT_EQ(sz, 944); + +} + +TEST(ONTest, ONTestClassSizeON_DimRadial) { + + size_t sz = sizeof(ON_DimRadial); //912 + + EXPECT_EQ(sz, 912); + +} + +TEST(ONTest, ONTestClassSizeON_DimOrdinate) { + + size_t sz = sizeof(ON_DimOrdinate); //936 + + EXPECT_EQ(sz, 936); + +} + +TEST(ONTest, ONTestClassSizeON_Centermark) { + + size_t sz = sizeof(ON_Centermark); //888 + + EXPECT_EQ(sz, 888); + +} + +TEST(ONTest, ONTestClassSizeON_Arrowhead) { + + size_t sz = sizeof(ON_Arrowhead); //20 + + EXPECT_EQ(sz, 20); + +} + +TEST(ONTest, ONTestClassSizeON_TextMask) { + + size_t sz = sizeof(ON_TextMask); //40 + + EXPECT_EQ(sz, 40); + +} + +TEST(ONTest, ONTestClassSizeON_DimStyle) { + + size_t sz = sizeof(ON_DimStyle); //1104 + + EXPECT_EQ(sz, 1104); + +} + +TEST(ONTest, ONTestClassSizeON_DimStyleContext) { + + size_t sz = sizeof(ON_DimStyleContext); //24 + + EXPECT_EQ(sz, 24); + +} + +TEST(ONTest, ONTestClassSizeON_Ellipse) { + + size_t sz = sizeof(ON_Ellipse); //144 + + EXPECT_EQ(sz, 144); + +} + +TEST(ONTest, ONTestClassSizeON_ErrorEvent) { + + size_t sz = sizeof(ON_ErrorEvent); //160 + + EXPECT_EQ(sz, 160); + +} + +TEST(ONTest, ONTestClassSizeON_ErrorLog) { + + size_t sz = sizeof(ON_ErrorLog); //5136 + + EXPECT_EQ(sz, 5136); + +} + +TEST(ONTest, ONTestClassSizeON_FileSystem) { + + size_t sz = sizeof(ON_FileSystem); //1 + + EXPECT_EQ(sz, 1); + +} + +TEST(ONTest, ONTestClassSizeON_FileSystemPath) { + + size_t sz = sizeof(ON_FileSystemPath); //1 + + EXPECT_EQ(sz, 1); + +} + +TEST(ONTest, ONTestClassSizeON_FileStream) { + + size_t sz = sizeof(ON_FileStream); //1 + + EXPECT_EQ(sz, 1); + +} + +TEST(ONTest, ONTestClassSizeON_ContentHash) { + + size_t sz = sizeof(ON_ContentHash); //64 + + EXPECT_EQ(sz, 64); + +} + +TEST(ONTest, ONTestClassSizeON_FileReference) { + + size_t sz = sizeof(ON_FileReference); //184 + + EXPECT_EQ(sz, 184); + +} + +TEST(ONTest, ONTestClassSizeON_FileIterator) { + + size_t sz = sizeof(ON_FileIterator); //56 + + EXPECT_EQ(sz, 56); + +} + +TEST(ONTest, ONTestClassSizeON_FontMetrics) { + + size_t sz = sizeof(ON_FontMetrics); //64 + + EXPECT_EQ(sz, 64); + +} + +TEST(ONTest, ONTestClassSizeON_TextBox) { + + size_t sz = sizeof(ON_TextBox); //32 + + EXPECT_EQ(sz, 32); + +} + +TEST(ONTest, ONTestClassSizeON_OutlineFigurePoint) { + + size_t sz = sizeof(ON_OutlineFigurePoint); //12 + + EXPECT_EQ(sz, 12); + +} + +TEST(ONTest, ONTestClassSizeON_OutlineFigure) { + + size_t sz = sizeof(ON_OutlineFigure); //72 + + EXPECT_EQ(sz, 72); + +} + +TEST(ONTest, ONTestClassSizeON_Outline) { + + size_t sz = sizeof(ON_Outline); //120 + + EXPECT_EQ(sz, 120); + +} + +TEST(ONTest, ONTestClassSizeON_OutlineAccumulator) { + + size_t sz = sizeof(ON_OutlineAccumulator); //96 + + EXPECT_EQ(sz, 96); + +} + +TEST(ONTest, ONTestClassSizeON_FontGlyph) { + + size_t sz = sizeof(ON_FontGlyph); //96 + + EXPECT_EQ(sz, 96); + +} + +TEST(ONTest, ONTestClassSizeON_WindowsDWriteFontInformation) { + + size_t sz = sizeof(ON_WindowsDWriteFontInformation); //696 + + EXPECT_EQ(sz, 696); + +} + +TEST(ONTest, ONTestClassSizeON_FontFaceQuartet) { + + size_t sz = sizeof(ON_FontFaceQuartet); //40 + + EXPECT_EQ(sz, 40); + +} + +TEST(ONTest, ONTestClassSizeON_Font) { + + size_t sz = sizeof(ON_Font); //192 + + EXPECT_EQ(sz, 192); + +} + +TEST(ONTest, ONTestClassSizeON_FontList) { + + size_t sz = sizeof(ON_FontList); //248 + + EXPECT_EQ(sz, 248); + +} + +TEST(ONTest, ONTestClassSizeON_2fPoint) { + + size_t sz = sizeof(ON_2fPoint); //8 + + EXPECT_EQ(sz, 8); + +} + +TEST(ONTest, ONTestClassSizeON_3fPoint) { + + size_t sz = sizeof(ON_3fPoint); //12 + + EXPECT_EQ(sz, 12); + +} + +TEST(ONTest, ONTestClassSizeON_4fPoint) { + + size_t sz = sizeof(ON_4fPoint); //16 + + EXPECT_EQ(sz, 16); + +} + +TEST(ONTest, ONTestClassSizeON_2fVector) { + + size_t sz = sizeof(ON_2fVector); //8 + + EXPECT_EQ(sz, 8); + +} + +TEST(ONTest, ONTestClassSizeON_3fVector) { + + size_t sz = sizeof(ON_3fVector); //12 + + EXPECT_EQ(sz, 12); + +} + +TEST(ONTest, ONTestClassSizeON_FixedSizePool) { + + size_t sz = sizeof(ON_FixedSizePool); //72 + + EXPECT_EQ(sz, 72); + +} + +TEST(ONTest, ONTestClassSizeON_FixedSizePoolIterator) { + + size_t sz = sizeof(ON_FixedSizePoolIterator); //24 + + EXPECT_EQ(sz, 24); + +} + +TEST(ONTest, ONTestClassSizeON_FunctionList) { + + size_t sz = sizeof(ON_FunctionList); //96 + + EXPECT_EQ(sz, 96); + +} + +TEST(ONTest, ONTestClassSizeON_Geometry) { + + size_t sz = sizeof(ON_Geometry); //16 + + EXPECT_EQ(sz, 16); + +} + +TEST(ONTest, ONTestClassSizeON_Group) { + + size_t sz = sizeof(ON_Group); //144 + + EXPECT_EQ(sz, 144); + +} + +TEST(ONTest, ONTestClassSizeON_Hash32TableItem) { + + size_t sz = sizeof(ON_Hash32TableItem); //16 + + EXPECT_EQ(sz, 16); + +} + +TEST(ONTest, ONTestClassSizeON_Hash32Table) { + + size_t sz = sizeof(ON_Hash32Table); //24 + + EXPECT_EQ(sz, 24); + +} + +TEST(ONTest, ONTestClassSizeON_HatchLoop) { + + size_t sz = sizeof(ON_HatchLoop); //16 + + EXPECT_EQ(sz, 16); + +} + +TEST(ONTest, ONTestClassSizeON_HatchLine) { + + size_t sz = sizeof(ON_HatchLine); //64 + + EXPECT_EQ(sz, 64); + +} + +TEST(ONTest, ONTestClassSizeON_HatchPattern) { + + size_t sz = sizeof(ON_HatchPattern); //184 + + EXPECT_EQ(sz, 184); + +} + +TEST(ONTest, ONTestClassSizeON_Hatch) { + + size_t sz = sizeof(ON_Hatch); //208 + + EXPECT_EQ(sz, 208); + +} + +TEST(ONTest, ONTestClassSizeON_CurveRegionBoundaryElement) { + + size_t sz = sizeof(ON_CurveRegionBoundaryElement); //32 + + EXPECT_EQ(sz, 32); + +} + +TEST(ONTest, ONTestClassSizeON_ReferencedComponentSettings) { + + size_t sz = sizeof(ON_ReferencedComponentSettings); //8 + + EXPECT_EQ(sz, 8); + +} + +TEST(ONTest, ONTestClassSizeON_InstanceDefinition) { + + size_t sz = sizeof(ON_InstanceDefinition); //568 + + EXPECT_EQ(sz, 568); + +} + +TEST(ONTest, ONTestClassSizeON_InstanceRef) { + + size_t sz = sizeof(ON_InstanceRef); //208 + + EXPECT_EQ(sz, 208); + +} + +TEST(ONTest, ONTestClassSizeON_2iPoint) { + + size_t sz = sizeof(ON_2iPoint); //8 + + EXPECT_EQ(sz, 8); + +} + +TEST(ONTest, ONTestClassSizeON_2iVector) { + + size_t sz = sizeof(ON_2iVector); //8 + + EXPECT_EQ(sz, 8); + +} + +TEST(ONTest, ONTestClassSizeON_2iBoundingBox) { + + size_t sz = sizeof(ON_2iBoundingBox); //16 + + EXPECT_EQ(sz, 16); + +} + +TEST(ONTest, ONTestClassSizeON_2iSize) { + + size_t sz = sizeof(ON_2iSize); //8 + + EXPECT_EQ(sz, 8); + +} + +TEST(ONTest, ONTestClassSizeON_4iRect) { + + size_t sz = sizeof(ON_4iRect); //16 + + EXPECT_EQ(sz, 16); + +} + +TEST(ONTest, ONTestClassSizeON_Layer) { + + size_t sz = sizeof(ON_Layer); //232 + + EXPECT_EQ(sz, 232); + +} + +TEST(ONTest, ONTestClassSizeON_Leader) { + + size_t sz = sizeof(ON_Leader); //840 + + EXPECT_EQ(sz, 840); + +} + +TEST(ONTest, ONTestClassSizeON_Light) { + + size_t sz = sizeof(ON_Light); //240 + + EXPECT_EQ(sz, 240); + +} + +TEST(ONTest, ONTestClassSizeON_Line) { + + size_t sz = sizeof(ON_Line); //48 + + EXPECT_EQ(sz, 48); + +} + +TEST(ONTest, ONTestClassSizeON_Triangle) { + + size_t sz = sizeof(ON_Triangle); //72 + + EXPECT_EQ(sz, 72); + +} + +TEST(ONTest, ONTestClassSizeON_LineCurve) { + + size_t sz = sizeof(ON_LineCurve); //88 + + EXPECT_EQ(sz, 88); + +} + +TEST(ONTest, ONTestClassSizeON_DisplayMaterialRef) { + + size_t sz = sizeof(ON_DisplayMaterialRef); //32 + + EXPECT_EQ(sz, 32); + +} + +TEST(ONTest, ONTestClassSizeON_LinetypeSegment) { + + size_t sz = sizeof(ON_LinetypeSegment); //16 + + EXPECT_EQ(sz, 16); + +} + +TEST(ONTest, ONTestClassSizeON_Linetype) { + + size_t sz = sizeof(ON_Linetype); //176 + + EXPECT_EQ(sz, 176); + +} + +TEST(ONTest, ONTestClassSizeON_Locale) { + + size_t sz = sizeof(ON_Locale); //160 + + EXPECT_EQ(sz, 160); + +} + +TEST(ONTest, ONTestClassSizeON_Lock) { + + size_t sz = sizeof(ON_Lock); //4 + + EXPECT_EQ(sz, 4); + +} + +TEST(ONTest, ONTestClassSizeON_SerialNumberMap) { + + size_t sz = sizeof(ON_SerialNumberMap); //112 + + EXPECT_EQ(sz, 112); + +} + +TEST(ONTest, ONTestClassSizeON_MappingChannel) { + + size_t sz = sizeof(ON_MappingChannel); //152 + + EXPECT_EQ(sz, 152); + +} + +TEST(ONTest, ONTestClassSizeON_MappingRef) { + + size_t sz = sizeof(ON_MappingRef); //40 + + EXPECT_EQ(sz, 40); + +} + +TEST(ONTest, ONTestClassSizeON_MaterialRef) { + + size_t sz = sizeof(ON_MaterialRef); //60 + + EXPECT_EQ(sz, 60); + +} + +TEST(ONTest, ONTestClassSizeON_Material) { + + size_t sz = sizeof(ON_Material); //312 + + EXPECT_EQ(sz, 312); + +} + +TEST(ONTest, ONTestClassSizeON_PhysicallyBasedMaterial) { + + size_t sz = sizeof(ON_PhysicallyBasedMaterial); //1 + + EXPECT_EQ(sz, 1); + +} + +TEST(ONTest, ONTestClassSizeON_Sum) { + + size_t sz = sizeof(ON_Sum); //28736 + + EXPECT_EQ(sz, 28736); + +} + +TEST(ONTest, ONTestClassSizeON_Evaluator) { + + size_t sz = sizeof(ON_Evaluator); //64 + + EXPECT_EQ(sz, 64); + +} + +TEST(ONTest, ONTestClassSizeON_Matrix) { + + size_t sz = sizeof(ON_Matrix); //72 + + EXPECT_EQ(sz, 72); + +} + +TEST(ONTest, ONTestClassSizeON_MD5_Hash) { + + size_t sz = sizeof(ON_MD5_Hash); //16 + + EXPECT_EQ(sz, 16); + +} + +TEST(ONTest, ONTestClassSizeON_MD5) { + + size_t sz = sizeof(ON_MD5); //120 + + EXPECT_EQ(sz, 120); + +} + +TEST(ONTest, ONTestClassSizeON_MemoryAllocationTracking) { + + size_t sz = sizeof(ON_MemoryAllocationTracking); //8 + + EXPECT_EQ(sz, 8); + +} + +TEST(ONTest, ONTestClassSizeON_MeshParameters) { + + size_t sz = sizeof(ON_MeshParameters); //224 + + EXPECT_EQ(sz, 224); + +} + +TEST(ONTest, ONTestClassSizeON_MeshCurvatureStats) { + + size_t sz = sizeof(ON_MeshCurvatureStats); //64 + + EXPECT_EQ(sz, 64); + +} + +TEST(ONTest, ONTestClassSizeON_MeshFace) { + + size_t sz = sizeof(ON_MeshFace); //16 + + EXPECT_EQ(sz, 16); + +} + +TEST(ONTest, ONTestClassSizeON_MeshTriangle) { + + size_t sz = sizeof(ON_MeshTriangle); //12 + + EXPECT_EQ(sz, 12); + +} + +TEST(ONTest, ONTestClassSizeON_MeshFaceList) { + + size_t sz = sizeof(ON_MeshFaceList); //24 + + EXPECT_EQ(sz, 24); + +} + +TEST(ONTest, ONTestClassSizeON_MeshVertexFaceMap) { + + size_t sz = sizeof(ON_MeshVertexFaceMap); //24 + + EXPECT_EQ(sz, 24); + +} + +TEST(ONTest, ONTestClassSizeON_MeshNgonBuffer) { + + size_t sz = sizeof(ON_MeshNgonBuffer); //80 + + EXPECT_EQ(sz, 80); + +} + +TEST(ONTest, ONTestClassSizeON_MeshNgon) { + + size_t sz = sizeof(ON_MeshNgon); //24 + + EXPECT_EQ(sz, 24); + +} + +TEST(ONTest, ONTestClassSizeON_MeshNgonAllocator) { + + size_t sz = sizeof(ON_MeshNgonAllocator); //168 + + EXPECT_EQ(sz, 168); + +} + +TEST(ONTest, ONTestClassSizeON_MeshTopology) { + + size_t sz = sizeof(ON_MeshTopology); //120 + + EXPECT_EQ(sz, 120); + +} + +TEST(ONTest, ONTestClassSizeON_MeshPartition) { + + size_t sz = sizeof(ON_MeshPartition); //32 + + EXPECT_EQ(sz, 32); + +} + +TEST(ONTest, ONTestClassSizeON_MappingTag) { + + size_t sz = sizeof(ON_MappingTag); //152 + + EXPECT_EQ(sz, 152); + +} + +TEST(ONTest, ONTestClassSizeON_TextureCoordinates) { + + size_t sz = sizeof(ON_TextureCoordinates); //184 + + EXPECT_EQ(sz, 184); + +} + +TEST(ONTest, ONTestClassSizeON_Mesh) { + + size_t sz = sizeof(ON_Mesh); //1760 + + EXPECT_EQ(sz, 1760); + +} + +TEST(ONTest, ONTestClassSizeON_MeshCache) { + + size_t sz = sizeof(ON_MeshCache); //8 + + EXPECT_EQ(sz, 8); + +} + +TEST(ONTest, ONTestClassSizeON_MeshNgonIterator) { + + size_t sz = sizeof(ON_MeshNgonIterator); //128 + + EXPECT_EQ(sz, 128); + +} + +TEST(ONTest, ONTestClassSizeON_MeshComponentRef) { + + size_t sz = sizeof(ON_MeshComponentRef); //32 + + EXPECT_EQ(sz, 32); + +} + +TEST(ONTest, ONTestClassSizeON_ModelComponent) { + + size_t sz = sizeof(ON_ModelComponent); //144 + + EXPECT_EQ(sz, 144); + +} + +TEST(ONTest, ONTestClassSizeON_ModelComponentContentMark) { + + size_t sz = sizeof(ON_ModelComponentContentMark); //40 + + EXPECT_EQ(sz, 40); + +} + +TEST(ONTest, ONTestClassSizeON_ModelComponentTypeIterator) { + + size_t sz = sizeof(ON_ModelComponentTypeIterator); //40 + + EXPECT_EQ(sz, 40); + +} + +TEST(ONTest, ONTestClassSizeON_ModelComponentReference) { + + size_t sz = sizeof(ON_ModelComponentReference); //16 + + EXPECT_EQ(sz, 16); + +} + +TEST(ONTest, ONTestClassSizeON_ModelComponentWeakReference) { + + size_t sz = sizeof(ON_ModelComponentWeakReference); //16 + + EXPECT_EQ(sz, 16); + +} + +TEST(ONTest, ONTestClassSizeON_ModelGeometryComponent) { + + size_t sz = sizeof(ON_ModelGeometryComponent); //176 + + EXPECT_EQ(sz, 176); + +} + +TEST(ONTest, ONTestClassSizeON_NurbsCurve) { + + size_t sz = sizeof(ON_NurbsCurve); //64 + + EXPECT_EQ(sz, 64); + +} + +TEST(ONTest, ONTestClassSizeON_TensorProduct) { + + size_t sz = sizeof(ON_TensorProduct); //8 + + EXPECT_EQ(sz, 8); + +} + +TEST(ONTest, ONTestClassSizeON_NurbsSurface) { + + size_t sz = sizeof(ON_NurbsSurface); //88 + + EXPECT_EQ(sz, 88); + +} + +TEST(ONTest, ONTestClassSizeON_NurbsCage) { + + size_t sz = sizeof(ON_NurbsCage); //112 + + EXPECT_EQ(sz, 112); + +} + +TEST(ONTest, ONTestClassSizeON_MorphControl) { + + size_t sz = sizeof(ON_MorphControl); //688 + + EXPECT_EQ(sz, 688); + +} + +TEST(ONTest, ONTestClassSizeON_CageMorph) { + + size_t sz = sizeof(ON_CageMorph); //40 + + EXPECT_EQ(sz, 40); + +} + +TEST(ONTest, ONTestClassSizeON_ClassId) { + + size_t sz = sizeof(ON_ClassId); //272 + + EXPECT_EQ(sz, 272); + +} + +TEST(ONTest, ONTestClassSizeON_UserString) { + + size_t sz = sizeof(ON_UserString); //16 + + EXPECT_EQ(sz, 16); + +} + +TEST(ONTest, ONTestClassSizeON_Object) { + + size_t sz = sizeof(ON_Object); //16 + + EXPECT_EQ(sz, 16); + +} + +TEST(ONTest, ONTestClassSizeON_CurveProxyHistory) { + + size_t sz = sizeof(ON_CurveProxyHistory); //344 + + EXPECT_EQ(sz, 344); + +} + +TEST(ONTest, ONTestClassSizeON_PolyEdgeHistory) { + + size_t sz = sizeof(ON_PolyEdgeHistory); //120 + + EXPECT_EQ(sz, 120); + +} + +TEST(ONTest, ONTestClassSizeON_HistoryRecord) { + + size_t sz = sizeof(ON_HistoryRecord); //264 + + EXPECT_EQ(sz, 264); + +} + +TEST(ONTest, ONTestClassSizeON_ObjRefEvaluationParameter) { + + size_t sz = sizeof(ON_ObjRefEvaluationParameter); //96 + + EXPECT_EQ(sz, 96); + +} + +TEST(ONTest, ONTestClassSizeON_ObjRef_IRefID) { + + size_t sz = sizeof(ON_ObjRef_IRefID); //400 + + EXPECT_EQ(sz, 400); + +} + +TEST(ONTest, ONTestClassSizeON_ObjRef) { + + size_t sz = sizeof(ON_ObjRef); //224 + + EXPECT_EQ(sz, 224); + +} + +TEST(ONTest, ONTestClassSizeON_BumpFunction) { + + size_t sz = sizeof(ON_BumpFunction); //80 + + EXPECT_EQ(sz, 80); + +} + +TEST(ONTest, ONTestClassSizeON_OffsetSurfaceValue) { + + size_t sz = sizeof(ON_OffsetSurfaceValue); //40 + + EXPECT_EQ(sz, 40); + +} + +TEST(ONTest, ONTestClassSizeON_OffsetSurfaceFunction) { + + size_t sz = sizeof(ON_OffsetSurfaceFunction); //104 + + EXPECT_EQ(sz, 104); + +} + +TEST(ONTest, ONTestClassSizeON_ArithmeticCalculator) { + + size_t sz = sizeof(ON_ArithmeticCalculator); //1024 + + EXPECT_EQ(sz, 1024); + +} + +TEST(ONTest, ONTestClassSizeON_LengthUnitName) { + + size_t sz = sizeof(ON_LengthUnitName); //16 + + EXPECT_EQ(sz, 16); + +} + +TEST(ONTest, ONTestClassSizeON_AngleUnitName) { + + size_t sz = sizeof(ON_AngleUnitName); //16 + + EXPECT_EQ(sz, 16); + +} + +TEST(ONTest, ONTestClassSizeON_ParseSettings) { + + size_t sz = sizeof(ON_ParseSettings); //32 + + EXPECT_EQ(sz, 32); + +} + +TEST(ONTest, ONTestClassSizeON_AerialPhotoImageFrustum) { + + size_t sz = sizeof(ON_AerialPhotoImageFrustum); //96 + + EXPECT_EQ(sz, 96); + +} + +TEST(ONTest, ONTestClassSizeON_AerialPhotoCameraPosition) { + + size_t sz = sizeof(ON_AerialPhotoCameraPosition); //304 + + EXPECT_EQ(sz, 304); + +} + +TEST(ONTest, ONTestClassSizeON_AerialPhotoImage) { + + size_t sz = sizeof(ON_AerialPhotoImage); //440 + + EXPECT_EQ(sz, 440); + +} + +TEST(ONTest, ONTestClassSizeON_Plane) { + + size_t sz = sizeof(ON_Plane); //128 + + EXPECT_EQ(sz, 128); + +} + +TEST(ONTest, ONTestClassSizeON_ClippingPlaneInfo) { + + size_t sz = sizeof(ON_ClippingPlaneInfo); //56 + + EXPECT_EQ(sz, 56); + +} + +TEST(ONTest, ONTestClassSizeON_ClippingPlane) { + + size_t sz = sizeof(ON_ClippingPlane); //184 + + EXPECT_EQ(sz, 184); + +} + +TEST(ONTest, ONTestClassSizeON_PlaneSurface) { + + size_t sz = sizeof(ON_PlaneSurface); //208 + + EXPECT_EQ(sz, 208); + +} + +TEST(ONTest, ONTestClassSizeON_ClippingPlaneSurface) { + + size_t sz = sizeof(ON_ClippingPlaneSurface); //392 + + EXPECT_EQ(sz, 392); + +} + +TEST(ONTest, ONTestClassSizeON_PlugInRef) { + + size_t sz = sizeof(ON_PlugInRef); //120 + + EXPECT_EQ(sz, 120); + +} + +TEST(ONTest, ONTestClassSizeON_Interval) { + + size_t sz = sizeof(ON_Interval); //16 + + EXPECT_EQ(sz, 16); + +} + +TEST(ONTest, ONTestClassSizeON_2dPoint) { + + size_t sz = sizeof(ON_2dPoint); //16 + + EXPECT_EQ(sz, 16); + +} + +TEST(ONTest, ONTestClassSizeON_3dPoint) { + + size_t sz = sizeof(ON_3dPoint); //24 + + EXPECT_EQ(sz, 24); + +} + +TEST(ONTest, ONTestClassSizeON_4dPoint) { + + size_t sz = sizeof(ON_4dPoint); //32 + + EXPECT_EQ(sz, 32); + +} + +TEST(ONTest, ONTestClassSizeON_2dVector) { + + size_t sz = sizeof(ON_2dVector); //16 + + EXPECT_EQ(sz, 16); + +} + +TEST(ONTest, ONTestClassSizeON_3dVector) { + + size_t sz = sizeof(ON_3dVector); //24 + + EXPECT_EQ(sz, 24); + +} + +TEST(ONTest, ONTestClassSizeON_3dRay) { + + size_t sz = sizeof(ON_3dRay); //48 + + EXPECT_EQ(sz, 48); + +} + +TEST(ONTest, ONTestClassSizeON_PlaneEquation) { + + size_t sz = sizeof(ON_PlaneEquation); //32 + + EXPECT_EQ(sz, 32); + +} + +TEST(ONTest, ONTestClassSizeON_SurfaceCurvature) { + + size_t sz = sizeof(ON_SurfaceCurvature); //16 + + EXPECT_EQ(sz, 16); + +} + +TEST(ONTest, ONTestClassSizeON_2dPointArray) { + + size_t sz = sizeof(ON_2dPointArray); //24 + + EXPECT_EQ(sz, 24); + +} + +TEST(ONTest, ONTestClassSizeON_2fPointArray) { + + size_t sz = sizeof(ON_2fPointArray); //24 + + EXPECT_EQ(sz, 24); + +} + +TEST(ONTest, ONTestClassSizeON_3dPointArray) { + + size_t sz = sizeof(ON_3dPointArray); //24 + + EXPECT_EQ(sz, 24); + +} + +TEST(ONTest, ONTestClassSizeON_3fPointArray) { + + size_t sz = sizeof(ON_3fPointArray); //24 + + EXPECT_EQ(sz, 24); + +} + +TEST(ONTest, ONTestClassSizeON_4dPointArray) { + + size_t sz = sizeof(ON_4dPointArray); //24 + + EXPECT_EQ(sz, 24); + +} + +TEST(ONTest, ONTestClassSizeON_4fPointArray) { + + size_t sz = sizeof(ON_4fPointArray); //24 + + EXPECT_EQ(sz, 24); + +} + +TEST(ONTest, ONTestClassSizeON_2dVectorArray) { + + size_t sz = sizeof(ON_2dVectorArray); //24 + + EXPECT_EQ(sz, 24); + +} + +TEST(ONTest, ONTestClassSizeON_2fVectorArray) { + + size_t sz = sizeof(ON_2fVectorArray); //24 + + EXPECT_EQ(sz, 24); + +} + +TEST(ONTest, ONTestClassSizeON_3dVectorArray) { + + size_t sz = sizeof(ON_3dVectorArray); //24 + + EXPECT_EQ(sz, 24); + +} + +TEST(ONTest, ONTestClassSizeON_3fVectorArray) { + + size_t sz = sizeof(ON_3fVectorArray); //24 + + EXPECT_EQ(sz, 24); + +} + +TEST(ONTest, ONTestClassSizeON_3dPointListRef) { + + size_t sz = sizeof(ON_3dPointListRef); //24 + + EXPECT_EQ(sz, 24); + +} + +TEST(ONTest, ONTestClassSizeON_2dSize) { + + size_t sz = sizeof(ON_2dSize); //16 + + EXPECT_EQ(sz, 16); + +} + +TEST(ONTest, ONTestClassSizeON_4dRect) { + + size_t sz = sizeof(ON_4dRect); //32 + + EXPECT_EQ(sz, 32); + +} + +TEST(ONTest, ONTestClassSizeON_WindingNumber) { + + size_t sz = sizeof(ON_WindingNumber); //56 + + EXPECT_EQ(sz, 56); + +} + +TEST(ONTest, ONTestClassSizeON_PeriodicDomain) { + + size_t sz = sizeof(ON_PeriodicDomain); //72 + + EXPECT_EQ(sz, 72); + +} + +TEST(ONTest, ONTestClassSizeON_PointCloud) { + + size_t sz = sizeof(ON_PointCloud); //328 + + EXPECT_EQ(sz, 328); + +} + +TEST(ONTest, ONTestClassSizeON_Point) { + + size_t sz = sizeof(ON_Point); //40 + + EXPECT_EQ(sz, 40); + +} + +TEST(ONTest, ONTestClassSizeON_PointGrid) { + + size_t sz = sizeof(ON_PointGrid); //56 + + EXPECT_EQ(sz, 56); + +} + +TEST(ONTest, ONTestClassSizeON_PolyCurve) { + + size_t sz = sizeof(ON_PolyCurve); //64 + + EXPECT_EQ(sz, 64); + +} + +TEST(ONTest, ONTestClassSizeON_Polyline) { + + size_t sz = sizeof(ON_Polyline); //24 + + EXPECT_EQ(sz, 24); + +} + +TEST(ONTest, ONTestClassSizeON_PolylineCurve) { + + size_t sz = sizeof(ON_PolylineCurve); //72 + + EXPECT_EQ(sz, 72); + +} + +TEST(ONTest, ONTestClassSizeON_ProgressReporter) { + + size_t sz = sizeof(ON_ProgressReporter); //64 + + EXPECT_EQ(sz, 64); + +} + +TEST(ONTest, ONTestClassSizeON_ProgressStepCounter) { + + size_t sz = sizeof(ON_ProgressStepCounter); //40 + + EXPECT_EQ(sz, 40); + +} + +TEST(ONTest, ONTestClassSizeON_Quaternion) { + + size_t sz = sizeof(ON_Quaternion); //32 + + EXPECT_EQ(sz, 32); + +} + +TEST(ONTest, ONTestClassSizeON_RandomNumberGenerator) { + + size_t sz = sizeof(ON_RandomNumberGenerator); //2500 + + EXPECT_EQ(sz, 2500); + +} + +TEST(ONTest, ONTestClassSizeON_RenderingAttributes) { + + size_t sz = sizeof(ON_RenderingAttributes); //24 + + EXPECT_EQ(sz, 24); + +} + +TEST(ONTest, ONTestClassSizeON_ObjectRenderingAttributes) { + + size_t sz = sizeof(ON_ObjectRenderingAttributes); //56 + + EXPECT_EQ(sz, 56); + +} + +TEST(ONTest, ONTestClassSizeON_RevSurface) { + + size_t sz = sizeof(ON_RevSurface); //160 + + EXPECT_EQ(sz, 160); + +} + +TEST(ONTest, ONTestClassSizeON_RTreeMemPool) { + + size_t sz = sizeof(ON_RTreeMemPool); //56 + + EXPECT_EQ(sz, 56); + +} + +TEST(ONTest, ONTestClassSizeON_RTreeIterator) { + + size_t sz = sizeof(ON_RTreeIterator); //528 + + EXPECT_EQ(sz, 528); + +} + +TEST(ONTest, ONTestClassSizeON_RTree) { + + size_t sz = sizeof(ON_RTree); //72 + + EXPECT_EQ(sz, 72); + +} + +TEST(ONTest, ONTestClassSizeON_SHA1_Hash) { + + size_t sz = sizeof(ON_SHA1_Hash); //20 + + EXPECT_EQ(sz, 20); + +} + +TEST(ONTest, ONTestClassSizeON_SHA1) { + + size_t sz = sizeof(ON_SHA1); //128 + + EXPECT_EQ(sz, 128); + +} + +TEST(ONTest, ONTestClassSizeON_SleepLock) { + + size_t sz = sizeof(ON_SleepLock); //4 + + EXPECT_EQ(sz, 4); + +} + +TEST(ONTest, ONTestClassSizeON_SleepLockGuard) { + + size_t sz = sizeof(ON_SleepLockGuard); //16 + + EXPECT_EQ(sz, 16); + +} + +TEST(ONTest, ONTestClassSizeON_Sphere) { + + size_t sz = sizeof(ON_Sphere); //136 + + EXPECT_EQ(sz, 136); + +} + +TEST(ONTest, ONTestClassSizeON_CheckSum) { + + size_t sz = sizeof(ON_CheckSum); //48 + + EXPECT_EQ(sz, 48); + +} + +TEST(ONTest, ONTestClassSizeON_StringBuffer) { + + size_t sz = sizeof(ON_StringBuffer); //32 + + EXPECT_EQ(sz, 32); + +} + +TEST(ONTest, ONTestClassSizeON_wStringBuffer) { + + size_t sz = sizeof(ON_wStringBuffer); //32 + + EXPECT_EQ(sz, 32); + +} + +TEST(ONTest, ONTestClassSizeON_String) { + + size_t sz = sizeof(ON_String); //8 + + EXPECT_EQ(sz, 8); + +} + +TEST(ONTest, ONTestClassSizeON_wString) { + + size_t sz = sizeof(ON_wString); //8 + + EXPECT_EQ(sz, 8); + +} + +TEST(ONTest, ONTestClassSizeON_NameHash) { + + size_t sz = sizeof(ON_NameHash); //40 + + EXPECT_EQ(sz, 40); + +} + +TEST(ONTest, ONTestClassSizeON_UnitSystem) { + + size_t sz = sizeof(ON_UnitSystem); //24 + + EXPECT_EQ(sz, 24); + +} + +TEST(ONTest, ONTestClassSizeON_LengthValue) { + + size_t sz = sizeof(ON_LengthValue); //48 + + EXPECT_EQ(sz, 48); + +} + +TEST(ONTest, ONTestClassSizeON_AngleValue) { + + size_t sz = sizeof(ON_AngleValue); //24 + + EXPECT_EQ(sz, 24); + +} + +TEST(ONTest, ONTestClassSizeON_ScaleValue) { + + size_t sz = sizeof(ON_ScaleValue); //128 + + EXPECT_EQ(sz, 128); + +} + +TEST(ONTest, ONTestClassSizeON_SubDVertexPtr) { + + size_t sz = sizeof(ON_SubDVertexPtr); //8 + + EXPECT_EQ(sz, 8); + +} + +TEST(ONTest, ONTestClassSizeON_SubDEdgePtr) { + + size_t sz = sizeof(ON_SubDEdgePtr); //8 + + EXPECT_EQ(sz, 8); + +} + +TEST(ONTest, ONTestClassSizeON_SubDFacePtr) { + + size_t sz = sizeof(ON_SubDFacePtr); //8 + + EXPECT_EQ(sz, 8); + +} + +TEST(ONTest, ONTestClassSizeON_SubDComponentPtr) { + + size_t sz = sizeof(ON_SubDComponentPtr); //8 + + EXPECT_EQ(sz, 8); + +} + +TEST(ONTest, ONTestClassSizeON_SubDComponentRefList) { + + size_t sz = sizeof(ON_SubDComponentRefList); //72 + + EXPECT_EQ(sz, 72); + +} + +TEST(ONTest, ONTestClassSizeON_SubDSectorType) { + + size_t sz = sizeof(ON_SubDSectorType); //40 + + EXPECT_EQ(sz, 40); + +} + +TEST(ONTest, ONTestClassSizeON_SumSurface) { + + size_t sz = sizeof(ON_SumSurface); //104 + + EXPECT_EQ(sz, 104); + +} + +TEST(ONTest, ONTestClassSizeON_Surface) { + + size_t sz = sizeof(ON_Surface); //16 + + EXPECT_EQ(sz, 16); + +} + +TEST(ONTest, ONTestClassSizeON_SurfaceProperties) { + + size_t sz = sizeof(ON_SurfaceProperties); //80 + + EXPECT_EQ(sz, 80); + +} + +TEST(ONTest, ONTestClassSizeON_SurfaceArray) { + + size_t sz = sizeof(ON_SurfaceArray); //24 + + EXPECT_EQ(sz, 24); + +} + +TEST(ONTest, ONTestClassSizeON_SurfaceProxy) { + + size_t sz = sizeof(ON_SurfaceProxy); //32 + + EXPECT_EQ(sz, 32); + +} + +TEST(ONTest, ONTestClassSizeON_Terminator) { + + size_t sz = sizeof(ON_Terminator); //56 + + EXPECT_EQ(sz, 56); + +} + +TEST(ONTest, ONTestClassSizeON_TextContent) { + + size_t sz = sizeof(ON_TextContent); //216 + + EXPECT_EQ(sz, 216); + +} + +TEST(ONTest, ONTestClassSizeON_TextStyle) { + + size_t sz = sizeof(ON_TextStyle); //208 + + EXPECT_EQ(sz, 208); + +} + +TEST(ONTest, ONTestClassSizeON_TextContext) { + + size_t sz = sizeof(ON_TextContext); //1 + + EXPECT_EQ(sz, 1); + +} + +TEST(ONTest, ONTestClassSizeON_TextLog) { + + size_t sz = sizeof(ON_TextLog); //120 + + EXPECT_EQ(sz, 120); + +} + +TEST(ONTest, ONTestClassSizeON_TextLogIndent) { + + size_t sz = sizeof(ON_TextLogIndent); //16 + + EXPECT_EQ(sz, 16); + +} + +TEST(ONTest, ONTestClassSizeON_TextHash) { + + size_t sz = sizeof(ON_TextHash); //480 + + EXPECT_EQ(sz, 480); + +} + +TEST(ONTest, ONTestClassSizeON_Text) { + + size_t sz = sizeof(ON_Text); //792 + + EXPECT_EQ(sz, 792); + +} + +TEST(ONTest, ONTestClassSizeON_StackedText) { + + size_t sz = sizeof(ON_StackedText); //32 + + EXPECT_EQ(sz, 32); + +} + +TEST(ONTest, ONTestClassSizeON_TextRun) { + + size_t sz = sizeof(ON_TextRun); //240 + + EXPECT_EQ(sz, 240); + +} + +TEST(ONTest, ONTestClassSizeON_TextRunArray) { + + size_t sz = sizeof(ON_TextRunArray); //24 + + EXPECT_EQ(sz, 24); + +} + +TEST(ONTest, ONTestClassSizeON_Texture) { + + size_t sz = sizeof(ON_Texture); //512 + + EXPECT_EQ(sz, 512); + +} + +TEST(ONTest, ONTestClassSizeON_TextureMapping) { + + size_t sz = sizeof(ON_TextureMapping); //560 + + EXPECT_EQ(sz, 560); + +} + +TEST(ONTest, ONTestClassSizeON_ComponentAttributes) { + + size_t sz = sizeof(ON_ComponentAttributes); //1 + + EXPECT_EQ(sz, 1); + +} + +TEST(ONTest, ONTestClassSizeON_Torus) { + + size_t sz = sizeof(ON_Torus); //144 + + EXPECT_EQ(sz, 144); + +} + +TEST(ONTest, ONTestClassSizeON_UserData) { + + size_t sz = sizeof(ON_UserData); //200 + + EXPECT_EQ(sz, 200); + +} + +TEST(ONTest, ONTestClassSizeON_UnknownUserData) { + + size_t sz = sizeof(ON_UnknownUserData); //240 + + EXPECT_EQ(sz, 240); + +} + +TEST(ONTest, ONTestClassSizeON_ObsoleteUserData) { + + size_t sz = sizeof(ON_ObsoleteUserData); //216 + + EXPECT_EQ(sz, 216); + +} + +TEST(ONTest, ONTestClassSizeON_UserStringList) { + + size_t sz = sizeof(ON_UserStringList); //224 + + EXPECT_EQ(sz, 224); + +} + +TEST(ONTest, ONTestClassSizeON_UserDataHolder) { + + size_t sz = sizeof(ON_UserDataHolder); //16 + + EXPECT_EQ(sz, 16); + +} + +TEST(ONTest, ONTestClassSizeON_DocumentUserStringList) { + + size_t sz = sizeof(ON_DocumentUserStringList); //16 + + EXPECT_EQ(sz, 16); + +} + +TEST(ONTest, ONTestClassSizeON_UuidIndex) { + + size_t sz = sizeof(ON_UuidIndex); //20 + + EXPECT_EQ(sz, 20); + +} + +TEST(ONTest, ONTestClassSizeON_UuidPtr) { + + size_t sz = sizeof(ON_UuidPtr); //24 + + EXPECT_EQ(sz, 24); + +} + +TEST(ONTest, ONTestClassSizeON_Viewport) { + + size_t sz = sizeof(ON_Viewport); //584 + + EXPECT_EQ(sz, 584); + +} + +TEST(ONTest, ONTestClassSizeON_Workspace) { + + size_t sz = sizeof(ON_Workspace); //16 + + EXPECT_EQ(sz, 16); + +} + +TEST(ONTest, ONTestClassSizeON_Xform) { + + size_t sz = sizeof(ON_Xform); //128 + + EXPECT_EQ(sz, 128); + +} + +TEST(ONTest, ONTestClassSizeON_ClippingRegion) { + + size_t sz = sizeof(ON_ClippingRegion); //784 + + EXPECT_EQ(sz, 784); + +} + +TEST(ONTest, ONTestClassSizeON_ClippingRegionPoints) { + + size_t sz = sizeof(ON_ClippingRegionPoints); //48 + + EXPECT_EQ(sz, 48); + +} + +TEST(ONTest, ONTestClassSizeON_PickPoint) { + + size_t sz = sizeof(ON_PickPoint); //72 + + EXPECT_EQ(sz, 72); + +} + +TEST(ONTest, ONTestClassSizeON_Localizer) { + + size_t sz = sizeof(ON_Localizer); //88 + + EXPECT_EQ(sz, 88); + +} + +TEST(ONTest, ONTestClassSizeON_SpaceMorph) { + + size_t sz = sizeof(ON_SpaceMorph); //32 + + EXPECT_EQ(sz, 32); + +} + +