From 5f656414c08cf5e71291d7fa218f3d5b9147d5b3 Mon Sep 17 00:00:00 2001 From: Bozo the Builder Date: Wed, 15 Nov 2023 14:18:19 -0800 Subject: [PATCH] Sync changes from upstream repository --- opennurbs_3dm_settings.cpp | 3 + opennurbs_brep.h | 2 + opennurbs_color.cpp | 87 +++++++----- opennurbs_color.h | 3 + opennurbs_embedded_file.cpp | 39 +++++- opennurbs_embedded_file.h | 4 + opennurbs_extensions.cpp | 7 +- opennurbs_planesurface.cpp | 103 ++++++++++---- opennurbs_planesurface.h | 4 +- opennurbs_public_version.h | 16 +-- opennurbs_sha1.cpp | 15 +- opennurbs_subd.cpp | 60 +++++--- opennurbs_subd.h | 252 ++++++++++++++++++++++++++++------ opennurbs_subd_copy.cpp | 4 +- opennurbs_subd_data.cpp | 263 +++++++++++++++++++++++++++--------- opennurbs_subd_data.h | 37 ++++- opennurbs_subd_eval.cpp | 1 - opennurbs_subd_fragment.cpp | 99 ++++++++++++-- opennurbs_subd_texture.cpp | 10 +- opennurbs_text.cpp | 28 ++-- opennurbs_viewport.cpp | 4 +- opennurbs_xform.h | 44 ++++-- opennurbs_xml.cpp | 85 +++++++++--- opennurbs_xml.h | 1 + 24 files changed, 878 insertions(+), 293 deletions(-) diff --git a/opennurbs_3dm_settings.cpp b/opennurbs_3dm_settings.cpp index 4345c6c7..63413aba 100644 --- a/opennurbs_3dm_settings.cpp +++ b/opennurbs_3dm_settings.cpp @@ -1003,6 +1003,8 @@ const ON_3dmRenderSettingsPrivate& ON_3dmRenderSettingsPrivate::operator = (cons _sun ->OnInternalXmlChanged(p._sun ); _post_effects ->OnInternalXmlChanged(p._post_effects ); +#ifdef _DEBUG // See https://mcneel.myjetbrains.com/youtrack/issue/RH-77284 + // Check that all the document objects now have matching properties. ON_ASSERT(*_ground_plane == *p._ground_plane); ON_ASSERT(*_dithering == *p._dithering); @@ -1016,6 +1018,7 @@ const ON_3dmRenderSettingsPrivate& ON_3dmRenderSettingsPrivate::operator = (cons // We can't check post effects because they may be different in terms of the _is_populated flag. // After a lot of thinking, I simply cannot figure out how to solve this. //_ASSERT(*_post_effects == *p._post_effects); +#endif } return *this; diff --git a/opennurbs_brep.h b/opennurbs_brep.h index 3f180e66..1cc081e1 100644 --- a/opennurbs_brep.h +++ b/opennurbs_brep.h @@ -1809,6 +1809,8 @@ public: mesh_list - [out] meshes are appended to this array. Returns: Number of meshes appended to mesh_list[] array. + Note: + This function is not thread safe. */ int CreateMesh( const ON_MeshParameters& mp, diff --git a/opennurbs_color.cpp b/opennurbs_color.cpp index 5de13a69..351fa690 100644 --- a/opennurbs_color.cpp +++ b/opennurbs_color.cpp @@ -310,45 +310,60 @@ void ON_Color::SetHSV( double value // value ) { - int i; - double f, p, q, t, r, g, b; - if ( saturation <= 1.0/256.0 ) { - r = value; - g = value; - b = value; - } - else { - hue *= 3.0 / ON_PI; // (6.0 / 2.0 * ON_PI); - i = (int)floor(hue); - if ( i < 0 || i > 5 ) { - hue = fmod(hue,6.0); - if ( hue < 0.0 ) - hue += 6.0; - i = (int)floor(hue); - } - f = hue - i; - p = value * ( 1.0 - saturation); - q = value * ( 1.0 - ( saturation * f) ); - t = value * ( 1.0 - ( saturation * ( 1.0 - f) ) ); - switch( i) + if ( hue > ON_UNSET_FLOAT && hue < ON_UNSET_POSITIVE_FLOAT + && saturation > ON_UNSET_FLOAT && saturation < ON_UNSET_POSITIVE_FLOAT + && value > ON_UNSET_FLOAT && value < ON_UNSET_POSITIVE_FLOAT + ) + { + double r, g, b; + if (value < 0.0) + value = 0.0; + else if (value > 1.0) + value = 1.0; + if (saturation <= 1.0 / 256.0) { - case 0: - r = value; g = t; b = p; break; - case 1: - r = q; g = value; b = p; break; - case 2: - r = p; g = value; b = t; break; - case 3: - r = p; g = q; b = value; break; - case 4: - r = t; g = p; b = value; break; - case 5: - r = value; g = p; b = q; break; - default: - r = 0; g = 0; b = 0; break; // to keep lint quiet + r = value; + g = value; + b = value; } + else + { + if (saturation > 1.0) + saturation = 1.0; + hue *= 3.0 / ON_PI; // (6.0 / 2.0 * ON_PI); + int i = (int)floor(hue); + if (i < 0 || i > 5) { + hue = fmod(hue, 6.0); + if (hue < 0.0) + hue += 6.0; + i = (int)floor(hue); + } + const double f = hue - i; + const double p = value * (1.0 - saturation); + const double q = value * (1.0 - (saturation * f)); + const double t = value * (1.0 - (saturation * (1.0 - f))); + switch (i) + { + case 0: + r = value; g = t; b = p; break; + case 1: + r = q; g = value; b = p; break; + case 2: + r = p; g = value; b = t; break; + case 3: + r = p; g = q; b = value; break; + case 4: + r = t; g = p; b = value; break; + case 5: + r = value; g = p; b = q; break; + default: + r = 0; g = 0; b = 0; break; // to keep lint quiet + } + } + SetFractionalRGB(r, g, b); } - SetFractionalRGB(r,g,b); + else + *this = ON_Color::UnsetColor; } diff --git a/opennurbs_color.h b/opennurbs_color.h index 16689447..f759f1c8 100644 --- a/opennurbs_color.h +++ b/opennurbs_color.h @@ -260,6 +260,9 @@ public: /// /// Specify a color using HSV (hue, saturation, value). + /// If h, s or v are not in the open interval + /// (ON_UNSET_FLOAT,ON_UNSET_POSITIVE_FLOAT), + /// then this color is set to ON_Color::UnsetColor. /// /// /// hue in radians diff --git a/opennurbs_embedded_file.cpp b/opennurbs_embedded_file.cpp index 34321343..62b8d06d 100644 --- a/opennurbs_embedded_file.cpp +++ b/opennurbs_embedded_file.cpp @@ -38,6 +38,7 @@ public: std::unique_ptr m_buffer; size_t m_length = 0; size_t m_compressed_length = 0; + bool m_error = false; }; bool LoadFile(const wchar_t* filename); @@ -66,6 +67,8 @@ const ON_EmbeddedFile::CImpl::Data& ON_EmbeddedFile::CImpl::Data::operator = (co m_length = m_compressed_length = 0; } + m_error = d.m_error; + return *this; } @@ -103,6 +106,8 @@ bool ON_EmbeddedFile::CImpl::LoadFile(const wchar_t* filename) // Read the file data into the buffer. const bool bOK = (ON_FileStream::Read(pFile, d.m_length, d.m_buffer.get()) == d.m_length); + d.m_error = !bOK; + // Close the file. ON_FileStream::Close(pFile); @@ -111,6 +116,9 @@ bool ON_EmbeddedFile::CImpl::LoadFile(const wchar_t* filename) bool ON_EmbeddedFile::CImpl::SaveFile(const wchar_t* filename) const { + if (m_data.m_error) + return false; // Can't save when in error state. + if (0 == m_data.m_length) return false; // Not loaded. @@ -209,14 +217,19 @@ bool ON_EmbeddedFile::LoadFromBuffer(ON_Buffer& buf) d.SetLength(size_t(buf.Size())); // Load the buffer from 'buf'. - if (buf.Read(d.m_length, d.m_buffer.get()) != d.m_length) - return false; + if (buf.Read(d.m_length, d.m_buffer.get()) == d.m_length) + return true; - return true; + m_impl->m_data.m_error = true; + + return false; } bool ON_EmbeddedFile::SaveToBuffer(ON_Buffer& buf) const { + if (m_impl->m_data.m_error) + return false; // Can't save when in error state. + // Write the data to 'buf'. buf.Write(m_impl->m_data.m_length, m_impl->m_data.m_buffer.get()); @@ -230,14 +243,20 @@ bool ON_EmbeddedFile::Read(ON_BinaryArchive& archive) // Read the full file path of the original file. ON_wString filename; if (!archive.ReadString(filename)) + { + m_impl->m_data.m_error = true; return false; + } m_impl->m_orig_file = ON_FileSystemPath::CleanPath(filename); // Read the original (uncompressed) size of the compressed buffer. size_t uncompressed_size = 0; if (!archive.ReadCompressedBufferSize(&uncompressed_size)) + { + m_impl->m_data.m_error = true; return false; + } // Allocate a buffer for the uncompressed data. auto& d = m_impl->m_data; @@ -249,7 +268,10 @@ bool ON_EmbeddedFile::Read(ON_BinaryArchive& archive) const ON__UINT64 pos_before = archive.CurrentPosition(); if (!archive.ReadCompressedBuffer(uncompressed_size, d.m_buffer.get(), &bFailedCRC) && !bFailedCRC) - return false; + { + m_impl->m_data.m_error = true; + return false; + } d.m_compressed_length = size_t(archive.CurrentPosition() - pos_before); @@ -259,6 +281,8 @@ bool ON_EmbeddedFile::Read(ON_BinaryArchive& archive) bool ON_EmbeddedFile::Write(ON_BinaryArchive& archive) const { auto& d = m_impl->m_data; + if (d.m_error) + return false; // Can't write when in error state. // Write the original filename to the archive. if (!archive.WriteString(m_impl->m_orig_file)) @@ -281,6 +305,11 @@ size_t ON_EmbeddedFile::CompressedLength(void) const return m_impl->m_data.m_compressed_length; } +bool ON_EmbeddedFile::Error(void) const +{ + return m_impl->m_data.m_error; +} + bool ON_EmbeddedFile::Clear(void) { m_impl->m_orig_file.Empty(); @@ -288,6 +317,8 @@ bool ON_EmbeddedFile::Clear(void) m_impl->m_data.SetLength(0); m_impl->m_data.m_compressed_length = 0; + m_impl->m_data.m_error = false; + return true; } diff --git a/opennurbs_embedded_file.h b/opennurbs_embedded_file.h index ea979f7d..c8056b82 100644 --- a/opennurbs_embedded_file.h +++ b/opennurbs_embedded_file.h @@ -72,6 +72,10 @@ public: // loaded by LoadFromFile() or LoadFromBuffer(), this method returns zero. virtual size_t CompressedLength(void) const; + // Returns true if the embedded file was loaded, but the load failed. This should only happen + // if a buffer or archive being loaded is corrupted. + bool Error(void) const; + // Clears the embedded file data. Returns true if successful, else false. virtual bool Clear(void); diff --git a/opennurbs_extensions.cpp b/opennurbs_extensions.cpp index 4bb9b215..9ee8a295 100644 --- a/opennurbs_extensions.cpp +++ b/opennurbs_extensions.cpp @@ -5429,11 +5429,10 @@ bool ONX_ModelPrivate::GetEntireRDKDocument(const ONX_Model_UserData& docud, ON_ // Create an ON_EmbeddedFile object for each embedded file. for (int i = 0; i < num_embedded_files; i++) { + // We keep the embedded file object even if it fails to load; then it will have an error flag set. + // See ON_EmbeddedFile::Error(). ON_EmbeddedFile ef; - - if (!ef.Read(archive)) - return false; - + ef.Read(archive); model->AddModelComponent(ef); } } diff --git a/opennurbs_planesurface.cpp b/opennurbs_planesurface.cpp index eb8b18cd..12820578 100644 --- a/opennurbs_planesurface.cpp +++ b/opennurbs_planesurface.cpp @@ -991,9 +991,74 @@ bool ON_ClippingPlaneData::HasDefaultContent() const return true; } -static ON_ClassArray g_data_list; +class ON_ClippingPlaneDataList +{ +public: + ON_ClippingPlaneDataList() = default; + ~ON_ClippingPlaneDataList(); + + ON_ClippingPlaneData* AppendNew(); + void DeleteEntry(unsigned int sn); + ON_ClippingPlaneData* FromSerialNumber(unsigned int sn); +private: + ON_SimpleArray m_list; +}; + +static ON_ClippingPlaneDataList g_data_list; static ON_SleepLock g_data_list_lock; +ON_ClippingPlaneDataList::~ON_ClippingPlaneDataList() +{ + for(int i=0; im_sn == sn) + { + delete data; + m_list.Remove(i); + return; + } + } +} + +ON_ClippingPlaneData* ON_ClippingPlaneDataList::AppendNew() +{ + static unsigned int serial_number = 1; + ON_ClippingPlaneData* data = new ON_ClippingPlaneData(); + m_list.Append(data); + data->m_sn = serial_number++; + return data; +} + +ON_ClippingPlaneData* ON_ClippingPlaneDataList::FromSerialNumber(unsigned int sn) +{ + if (0==sn) + return nullptr; + + // TODO: use binary search + int count = m_list.Count(); + for (int i=0; im_sn == sn) + return data; + } + return nullptr; +} + +/* static int CompareClippingPlaneData(const ON_ClippingPlaneData* a, const ON_ClippingPlaneData* b) { if (a && b) @@ -1010,25 +1075,16 @@ static int CompareClippingPlaneData(const ON_ClippingPlaneData* a, const ON_Clip return 1; return 0; } - -static int ClippingPlaneDataIndex(unsigned int serialNumber) -{ - if (0==serialNumber) - return -1; - ON_ClippingPlaneData data; - data.m_sn = serialNumber; - int index = g_data_list.BinarySearch(&data, CompareClippingPlaneData); - return index; -} + */ static void DeleteClippingPlaneData(ON_ClippingPlaneDataStore& dataStore) { - if (dataStore.m_sn>0) + const unsigned int serial_number = dataStore.m_sn; + if (serial_number>0) { bool bReturnLock = g_data_list_lock.GetLock(); - int index = ClippingPlaneDataIndex(dataStore.m_sn); dataStore.m_sn = 0; - g_data_list.Remove(index); + g_data_list.DeleteEntry(serial_number); if(bReturnLock) g_data_list_lock.ReturnLock(); } @@ -1040,8 +1096,7 @@ static ON_ClippingPlaneData* GetClippingPlaneData(unsigned int sn) return nullptr; bool bReturnLock = g_data_list_lock.GetLock(); - int index = ClippingPlaneDataIndex(sn); - ON_ClippingPlaneData* rc = g_data_list.At(index); + ON_ClippingPlaneData* rc = g_data_list.FromSerialNumber(sn); if(bReturnLock) g_data_list_lock.ReturnLock(); return rc; @@ -1050,19 +1105,13 @@ static ON_ClippingPlaneData* GetClippingPlaneData(unsigned int sn) static ON_ClippingPlaneData* GetClippingPlaneData(ON_ClippingPlaneDataStore& dataStore, bool createIfMissing) { bool bReturnLock = g_data_list_lock.GetLock(); - int index = ClippingPlaneDataIndex(dataStore.m_sn); - ON_ClippingPlaneData* rc = g_data_list.At(index); + + ON_ClippingPlaneData* rc = g_data_list.FromSerialNumber(dataStore.m_sn); if (nullptr==rc && createIfMissing) { - unsigned int serial_number = 1; - const ON_ClippingPlaneData* last = g_data_list.Last(); - if (last) - serial_number = last->m_sn + 1; - - ON_ClippingPlaneData& data = g_data_list.AppendNew(); - data.m_sn = serial_number; - dataStore.m_sn = data.m_sn; - rc = g_data_list.Last(); + rc = g_data_list.AppendNew(); + if (rc) + dataStore.m_sn = rc->m_sn; } if(bReturnLock) g_data_list_lock.ReturnLock(); diff --git a/opennurbs_planesurface.h b/opennurbs_planesurface.h index 77de5bef..aa0f0076 100644 --- a/opennurbs_planesurface.h +++ b/opennurbs_planesurface.h @@ -438,7 +438,7 @@ public: domain. Parameters: dir - [in] 0 sets plane's x coordinate extents - 0 sets plane's y coordinate extents + 1 sets plane's y coordinate extents extents - [in] increasing interval bSynchDomain - [in] if true, the corresponding evaluation interval domain is set so that it matches the extents interval @@ -458,7 +458,7 @@ public: Gets the extents of the rectangle. Parameters: dir - [in] 0 gets plane's x coordinate extents - 0 gets plane's y coordinate extents + 1 gets plane's y coordinate extents Returns: Increasing interval See Also: diff --git a/opennurbs_public_version.h b/opennurbs_public_version.h index 6f80424b..62ed2a66 100644 --- a/opennurbs_public_version.h +++ b/opennurbs_public_version.h @@ -14,10 +14,10 @@ // first step in each build. // #define RMA_VERSION_YEAR 2023 -#define RMA_VERSION_MONTH 8 -#define RMA_VERSION_DATE 22 -#define RMA_VERSION_HOUR 12 -#define RMA_VERSION_MINUTE 13 +#define RMA_VERSION_MONTH 10 +#define RMA_VERSION_DATE 31 +#define RMA_VERSION_HOUR 13 +#define RMA_VERSION_MINUTE 30 //////////////////////////////////////////////////////////////// // @@ -35,8 +35,8 @@ // 3 = build system release build #define RMA_VERSION_BRANCH 0 -#define VERSION_WITH_COMMAS 8,0,23234,12130 -#define VERSION_WITH_PERIODS 8.0.23234.12130 +#define VERSION_WITH_COMMAS 8,0,23304,13300 +#define VERSION_WITH_PERIODS 8.0.23304.13300 #define COPYRIGHT "Copyright (C) 1993-2023, Robert McNeel & Associates. All Rights Reserved." #define SPECIAL_BUILD_DESCRIPTION "Public OpenNURBS C++ 3dm file IO library." @@ -47,8 +47,8 @@ #define RMA_VERSION_NUMBER_SR_STRING "SR0" #define RMA_VERSION_NUMBER_SR_WSTRING L"SR0" -#define RMA_VERSION_WITH_PERIODS_STRING "8.0.23234.12130" -#define RMA_VERSION_WITH_PERIODS_WSTRING L"8.0.23234.12130" +#define RMA_VERSION_WITH_PERIODS_STRING "8.0.23304.13300" +#define RMA_VERSION_WITH_PERIODS_WSTRING L"8.0.23304.13300" diff --git a/opennurbs_sha1.cpp b/opennurbs_sha1.cpp index afe72cd6..3b9991e3 100644 --- a/opennurbs_sha1.cpp +++ b/opennurbs_sha1.cpp @@ -23,26 +23,17 @@ ON_SHA1_Hash::ON_SHA1_Hash() { - ON__UINT32* p = (ON__UINT32*)m_digest; - p[0] = 0U; - p[1] = 0U; - p[2] = 0U; - p[3] = 0U; - p[4] = 0U; + memset(m_digest, 0, sizeof(m_digest)); } bool operator==(const ON_SHA1_Hash& a, const ON_SHA1_Hash& b) { - const ON__UINT32* ai = (const ON__UINT32*)&a; - const ON__UINT32* bi = (const ON__UINT32*)&b; - return (ai[0] == bi[0] && ai[1] == bi[1] && ai[2] == bi[2] && ai[3] == bi[3] && ai[4] == bi[4]); + return memcmp(a.m_digest, b.m_digest, sizeof(a.m_digest)) == 0; } bool operator!=(const ON_SHA1_Hash& a, const ON_SHA1_Hash& b) { - const ON__UINT32* ai = (const ON__UINT32*)&a; - const ON__UINT32* bi = (const ON__UINT32*)&b; - return (ai[0] != bi[0] || ai[1] != bi[1] || ai[2] != bi[2] || ai[3] != bi[3] || ai[4] != bi[4]); + return memcmp(a.m_digest, b.m_digest, sizeof(a.m_digest)) != 0; } const ON_String ON_SHA1_Hash::ToUTF8String( diff --git a/opennurbs_subd.cpp b/opennurbs_subd.cpp index ab3a3eb0..b4532a65 100644 --- a/opennurbs_subd.cpp +++ b/opennurbs_subd.cpp @@ -8747,11 +8747,11 @@ unsigned int ON_SubD::DumpTopology( if (false == text_log.IsTextHash()) { - text_log.Print(L"Fragment per vertex color settings:\n"); + text_log.Print(L"Per vertex color settings:\n"); { ON_TextLogIndent indent1(text_log); - text_log.Print(L"FragmentColorsMappingTag() = "); - const ON_MappingTag colors_tag = this->FragmentColorsMappingTag(); + text_log.Print(L"ColorsMappingTag() = "); + const ON_MappingTag colors_tag = this->ColorsMappingTag(); const ON_TextLog::LevelOfDetail lod = text_log.DecreaseLevelOfDetail(); colors_tag.Dump(text_log); text_log.SetLevelOfDetail(lod); @@ -10366,11 +10366,10 @@ unsigned int ON_SubDLevel::DumpTopology( if (bNeedComma) text_log.Print(", "); else - text_log.Print("Per face"); + text_log.Print("Per face "); bNeedComma = true; - text_log.Print("face color=("); - text_log.PrintColor(per_face_color); - text_log.Print(")"); + text_log.Print("color="); + per_face_color.ToText(ON_Color::TextFormat::HashRGBa, 0, true, text_log); } const int per_face_material_channel_index = f->MaterialChannelIndex(); @@ -10379,9 +10378,9 @@ unsigned int ON_SubDLevel::DumpTopology( if (bNeedComma) text_log.Print(", "); else - text_log.Print("Per face"); + text_log.Print("Per face "); bNeedComma = true; - text_log.Print(" material channel index=%d", per_face_material_channel_index); + text_log.Print("material channel index=%d", per_face_material_channel_index); } if (bNeedComma) text_log.PrintNewLine(); @@ -17429,27 +17428,39 @@ bool ON_SubD::CopyEvaluationCacheForExperts(const ON_SubD& src) { const ON_SubDimple* src_subdimple = src.m_subdimple_sp.get(); ON_SubDimple* this_subdimple = m_subdimple_sp.get(); - return (nullptr != src_subdimple && nullptr != this_subdimple) ? this_subdimple->CopyEvaluationCacheForExperts(*src_subdimple) : false; + const bool bCopied + = (nullptr != src_subdimple && nullptr != this_subdimple) + ? this_subdimple->CopyEvaluationCacheForExperts(*src_subdimple) + : false; + if (bCopied) + { + if (this->HasFragmentTextureCoordinates()) + this_subdimple->Internal_SetFragmentTextureCoordinatesTextureSettingsHash(src_subdimple->FragmentColorsSettingsHash()); + if (this->HasFragmentColors()) + this_subdimple->Internal_SetFragmentColorsSettingsHash(src_subdimple->FragmentColorsSettingsHash()); + } + return bCopied; } bool ON_SubDimple::CopyEvaluationCacheForExperts(const ON_SubDimple& src) { const ON_SubDLevel* src_level = src.ActiveLevelConstPointer(); ON_SubDLevel* this_level = this->ActiveLevelPointer(); - bool bFragmentsWereCopied = false; - const bool bCopied = (nullptr != src_level && nullptr != this_level) ? this_level->CopyEvaluationCacheForExperts(this->m_heap , *src_level, src.m_heap, bFragmentsWereCopied) : false; - if (bFragmentsWereCopied) + unsigned fragment_status = 0u; + const bool bCopied = (nullptr != src_level && nullptr != this_level) ? this_level->CopyEvaluationCacheForExperts(this->m_heap , *src_level, src.m_heap, fragment_status) : false; + if (0 != (1u& fragment_status)) { - this->m_fragment_colors_mapping_tag = src.m_fragment_colors_mapping_tag; - this->m_fragment_texture_settings_hash = src.m_fragment_texture_settings_hash; - this->m_fragment_colors_settings_hash = src.m_fragment_colors_settings_hash; + if (2 != (1u & fragment_status)) + this->m_fragment_texture_settings_hash = src.m_fragment_texture_settings_hash; + if (8 != (1u & fragment_status)) + this->m_fragment_colors_settings_hash = src.m_fragment_colors_settings_hash; } return bCopied; } -bool ON_SubDLevel::CopyEvaluationCacheForExperts( ON_SubDHeap& this_heap, const ON_SubDLevel& src, const ON_SubDHeap& src_heap, bool& bFragmentsWereCopied) +bool ON_SubDLevel::CopyEvaluationCacheForExperts( ON_SubDHeap& this_heap, const ON_SubDLevel& src, const ON_SubDHeap& src_heap, unsigned& copy_status) { - bFragmentsWereCopied = false; + copy_status = 0u; // Validate conditions for coping the cached evaluation information if ( this == &src @@ -17713,8 +17724,17 @@ bool ON_SubDLevel::CopyEvaluationCacheForExperts( ON_SubDHeap& this_heap, const this_face->SetSavedSubdivisionPoint(subdivision_point); if (nullptr == this_face->MeshFragments() && nullptr != src_face->MeshFragments()) { - if (nullptr != this_heap.CopyMeshFragments(src_face, subd_display_density, this_face)) - bFragmentsWereCopied = true; + const ON_SubDMeshFragment* this_frag = this_heap.CopyMeshFragments(src_face, subd_display_density, this_face); + if (nullptr != this_frag) + { + copy_status |= 1u; + if (this_frag->TextureCoordinateCount() > 0) + copy_status |= 2u; + if (this_frag->CurvatureCount() > 0) + copy_status |= 4u; + if (this_frag->ColorCount() > 0) + copy_status |= 8u; + } } } } diff --git a/opennurbs_subd.h b/opennurbs_subd.h index 880a07a2..cb8f5cb7 100644 --- a/opennurbs_subd.h +++ b/opennurbs_subd.h @@ -8171,6 +8171,69 @@ public: */ void ClearFragmentTextureCoordinatesTextureSettingsHash() const; + /// + /// Determing if this SubD's mesh fragments have per vertex texture coordinates. + /// + /// + /// If this SubD has mesh fragments with per vertex texture coordinates, then true is returned. + /// Otherwise false is returned. + /// + bool HasFragmentTextureCoordinates() const; + + /// + /// This tag identifies the method and computation used to set the + /// per vertex texture coordinates on the fragments. The tag is persistent so + /// that the texture coordinates can be recomputed from the id in situations where + /// fragments need to be recalculated. + /// + /// + /// If this SubD has mesh fragments with per vertex texture coordinates and + /// texture_mapping_tag = TextureMappingTag(), then true is returned. + /// Otherwise false is returned. + /// + bool HasFragmentTextureCoordinates( + ON_MappingTag texture_mapping_tag + ) const; + + /// + /// This hash uniquely identifies the method and computation used to + /// set the per vertex texture coordinates on the fragments. The hash is a runtime + /// value that has meaning only when fragments with per vertex texture coordinates + /// exist. + /// + /// + /// If this SubD has mesh fragments with per vertex texture coordinates and + /// texture_settings_hash = TextureSettingsHash(), then true is returned. + /// Otherwise false is returned. + /// + bool HasFragmentTextureCoordinates( + ON_SHA1_Hash texture_settings_hash + ) const; + + /// + /// This hash uniquely identifies the method and computation used to + /// set the per vertex texture coordinates on the fragments. The hash is a runtime + /// value that has meaning only when fragments with per vertex texture coordinates + /// exist. + /// + /// + /// This tag identifies the method and computation used to set the + /// per vertex texture coordinates on the fragments. The tag is persistent so + /// that the texture coordinates can be recomputed from the id in situations where + /// fragments need to be recalculated. + /// + /// + /// If this SubD has mesh fragments with per vertex texture coordinates and + /// texture_settings_hash = TextureSettingsHash() and + /// texture_mapping_tag = TextureMappingTag(), then true is returned. + /// Otherwise false is returned. + /// + bool HasFragmentTextureCoordinates( + ON_SHA1_Hash texture_settings_hash, + ON_MappingTag texture_mapping_tag + ) const; + + private: /* Description: @@ -8247,17 +8310,27 @@ public: /// bool HasFragmentColors() const; - /// + /// + /// This tag identifies the method and computation used to set the + /// per vertex colors on the fragments. The tag is persistent so + /// that the colors can be recomputed from the id in situations where + /// fragments need to be recalculated. + /// /// /// If this SubD has mesh fragments with per vertex colors and /// color_tag = FragmentColorsMappingTag(), then true is returned. /// Otherwise false is returned. /// bool HasFragmentColors( - ON_MappingTag color_tag + ON_MappingTag color_mapping_tag ) const; - /// + /// + /// This hash uniquely identifies the method and computation used to + /// set the per vertex colors on the fragments. The has is a runtime + /// value that has meaning only when fragments with per vertex colors + /// exist. + /// /// /// If this SubD has mesh fragments with per vertex colors and /// color_settings_hash = FragmentColorsSettingsHash(), then true is returned. @@ -8267,17 +8340,27 @@ public: ON_SHA1_Hash color_settings_hash ) const; - /// - /// + /// + /// This hash uniquely identifies the method and computation used to + /// set the per vertex colors on the fragments. The has is a runtime + /// value that has meaning only when fragments with per vertex colors + /// exist. + /// + /// + /// This tag identifies the method and computation used to set the + /// per vertex colors on the fragments. The tag is persistent so + /// that the colors can be recomputed from the id in situations where + /// fragments need to be recalculated. + /// /// /// If this SubD has mesh fragments with per vertex colors and /// color_settings_hash = FragmentColorsSettingsHash() and - /// color_tag = FragmentColorsMappingTag(), then true is returned. + /// color_mapping_tag = ColorsMappingTag(), then true is returned. /// Otherwise false is returned. /// bool HasFragmentColors( ON_SHA1_Hash color_settings_hash, - ON_MappingTag color_tag + ON_MappingTag color_mapping_tag ) const; @@ -8293,33 +8376,39 @@ public: bool bClearFragmentColorsMappingTag ); + + /* + Returns: + This mapping tag ideitifies the color mapping used to set fragment per vertex colors. + */ + const ON_MappingTag ColorsMappingTag() const; + + /* + Description: + Set the colors mapping tag. + Remarks: + Calling this->SetColorsMappingTag() does not change existing cached + fragment vertex colors. At an appropriate time, call this->SetFragmentColorsFromCallback() + to update fragment vertex colors on any cached fragments. + + The color mapping tag and per vertex colors are mutable properties. + They can be changed by rendering applications as needed. + */ + void SetColorsMappingTag(const class ON_MappingTag&) const; + + /* Returns: hash identifying the way the fragment vertex colors were set. */ const ON_SHA1_Hash FragmentColorsSettingsHash() const; - /* - Returns: - The current fragment vertex colors mapping tag. - */ + ON_DEPRECATED_MSG("Use ON_SubD::ColorsMappingTag()") const ON_MappingTag FragmentColorsMappingTag() const; - /* - Description: - Set the fragment colors mapping tag. - Remarks: - Calling this->SetFragmentColorsMappingTag() does not change existing cached - fragment vertex colors. At an appropriate time, call this->SetFragmentColorsFromCallback() - to update fragment vertex colors on any cached fragments. - - SubD fragment vertex tag and colors are a mutable property. - They can be changed by rendering applications as needed. - */ + ON_DEPRECATED_MSG("Use ON_SubD::SetColorsMappingTag()") void SetFragmentColorsMappingTag(const class ON_MappingTag&) const; - - public: /* Description: @@ -8594,7 +8683,7 @@ public: subd must point to an ON_SubD that was constructed on the heap using an operator new call with a public ON_SubD constructor. Returns: - a pointer to the managed subd or nullptr subd in not valid. + a pointer to the managed subd or nullptr subd if not valid. Example: ON_SubD* subd = new ON_SubD(...); ON_SubDRef subr; @@ -10766,9 +10855,46 @@ public: */ ON_ComponentStatus Status() const; + /// + /// This simple version + /// transforms the points and normals and unconditionally + /// makes no changes to the curvatures, texture coordinates and colors. + /// + /// Typically lots of fragments are being transformed and + /// the type and context of the transformation determines + /// if texture coordinate, curvature and color inforation should be + /// preserved or destroyed. It is better to determine the answers to these + /// questions and call the version of Transform with + /// the bKeepTextures, bKeepCurvatures and bKeepColors parameters. + /// For example if the transformation is an isometry and the colors + /// are set from the curvatures, then curvatures and colors should be + /// kept. If the transformation is not an isometry, the curvatures should + /// be destroyed. + /// If the texture coordinates are set from grid location + /// (fake surface paramaters), the the texture coordinates should be kept. + /// If transform is not an identity and the texture coordinates come from a + /// world object mapping, the should generally be destroyed. + /// + /// + /// bool Transform( const ON_Xform& xform - ); + ); + + /// + /// + /// + /// + /// + /// + /// + /// + bool Transform( + bool bKeepTextures, + bool bKeepCurvatures, + bool bKeepColors, + const ON_Xform& xform + ); ON_SubDMeshFragment* m_next_fragment; ON_SubDMeshFragment* m_prev_fragment; @@ -12511,7 +12637,7 @@ protected: mutable double m_saved_subd_point1[3]; // saved subdivision point private: - // Reserved for future use for attributes that apply to allSubD components (ON_SubDVertex, ON_SubDEdge, and ON_SubDFace). + // Reserved for future use for attributes that apply to all SubD components (ON_SubDVertex, ON_SubDEdge, and ON_SubDFace). ON__UINT64 m_reserved8bytes1; ON__UINT64 m_reserved8bytes2; ON__UINT64 m_reserved8bytes3; @@ -12770,9 +12896,8 @@ public: bTransformationSavedSubdivisionPoint - [in] If the transformation is being applied to every vertex, edge and face in every level of a subdivision object, and the transformation - is an isometry (rotation, translation, ...), a uniform scale, or a - composition of these types, then set - bTransformationSavedSubdivisionPoint = true to apply the + is an orientation preserving isometry (rotation, translation, ...), + then set bTransformationSavedSubdivisionPoint = true to apply the transformation to saved subdivision and saved limit point information. In all other cases, set bTransformationSavedSubdivisionPoint = false and any saved subdivision points or saved limit points will be @@ -13308,7 +13433,7 @@ public: two attached edges are attached to one face, the remaining edges are attached to two faces. Returns: - True if the vertex has interior vertex toplology. + True if the vertex has boundary vertex toplology. Remarks: Tags are ignored. This property is often used during construction and modification when tags are not set. @@ -13716,11 +13841,10 @@ public: Parameters: bTransformationSavedSubdivisionPoint - [in] - If the transformation is being applied to every vertex, edge and + If the transformation is being applied to every vertex, edge and face in every level of a subdivision object, and the transformation - is an isometry (rotation, translation, ...), a uniform scale, or a - composition of these types, then set - bTransformationSavedSubdivisionPoint = true to apply the + is an orientation preserving isometry (rotation, translation, ...), + then set bTransformationSavedSubdivisionPoint = true to apply the transformation to saved subdivision and saved limit point information. In all other cases, set bTransformationSavedSubdivisionPoint = false and any saved subdivision points or saved limit points will be @@ -14610,9 +14734,8 @@ public: bTransformationSavedSubdivisionPoint - [in] If the transformation is being applied to every vertex, edge and face in every level of a subdivision object, and the transformation - is an isometry (rotation, translation, ...), a uniform scale, or a - composition of these types, then set - bTransformationSavedSubdivisionPoint = true to apply the + is an orientation preserving isometry (rotation, translation, ...), + then set bTransformationSavedSubdivisionPoint = true to apply the transformation to saved subdivision and saved limit point information. In all other cases, set bTransformationSavedSubdivisionPoint = false and any saved subdivision points or saved limit points will be @@ -15108,18 +15231,71 @@ public: const class ON_SubDMeshFragment* MeshFragments() const; + /// + /// The face's control net center point is the average of the face's + /// vertex control net points. This is the same point as the face's + /// subdivision point. + /// + /// + /// The average of the face's vertex control net points + /// const ON_3dPoint ControlNetCenterPoint() const; + /// + /// When the face's control net polygon is planar, the face's + /// control net normal is a unit vector perpindicular to the plane + /// that points outwards. If the control net polygon is not + /// planar, the control net normal is control net normal is a unit + /// vector that is the average of the control polygon's corner normals. + /// + /// + /// A unit vector that is normal to planar control net polygons and a good + /// compromise for nonplanar control net polygons. + /// const ON_3dVector ControlNetCenterNormal() const; + /// + /// The face's control net center frame is a plane + /// with normal equal to this->ControlNetCenterNormal() + /// and origin equal to this->ControlNetCenterPoint(). + /// The x and y axes of the frame have no predictable relationship + /// to the face or SubD control net topology. + /// + /// + /// A plane with unit normal equal to this->ControlNetCenterNormal() + /// and origin equal to this->ControlNetCenterPoint(). + /// If the face is not valid, ON_Plane::NanPlane is returned. + /// const ON_Plane ControlNetCenterFrame() const; + /// + /// True if the control net polygon is convex with respect to the + /// plane this->ControlNetCenterFrame(). + /// bool IsConvex() const; + /// + /// True if the control net polygon is not convex with respect to the + /// plane this->ControlNetCenterFrame(). + /// bool IsNotConvex() const; + /// + /// Determine if the face's control net polygon is planar. + /// + /// + /// + /// True if the face's control net polygon is planar. + /// bool IsPlanar(double planar_tolerance = ON_ZERO_TOLERANCE) const; + /// + /// Determine if the face's control net polygon is not planar. + /// + /// + /// + /// True if the face's control net polygon is not planar. + /// bool IsNotPlanar(double planar_tolerance = ON_ZERO_TOLERANCE) const; public: diff --git a/opennurbs_subd_copy.cpp b/opennurbs_subd_copy.cpp index b64f2943..fc0b723e 100644 --- a/opennurbs_subd_copy.cpp +++ b/opennurbs_subd_copy.cpp @@ -866,13 +866,13 @@ ON_SubDimple::ON_SubDimple(const ON_SubDimple& src) m_subd_appearance = src.m_subd_appearance; m_texture_coordinate_type = src.m_texture_coordinate_type; m_texture_mapping_tag = src.m_texture_mapping_tag; + m_colors_mapping_tag = src.m_colors_mapping_tag; // NOTE WELL: (Dale Lear Aug 2023) - // Fragment settings like the three m_fragment_... values + // Fragment settings like the two m_fragment_... values // should be copied only if the mesh fragments are copied // and that happens conditionally and happens later // if ON_SubDimple::CopyEvaluationCacheForExperts() is called. - // NO // m_fragment_colors_mapping_tag = src.m_fragment_colors_mapping_tag; // NO // m_fragment_texture_settings_hash = src.m_fragment_texture_settings_hash; // NO // m_fragment_colors_settings_hash = src.m_fragment_colors_settings_hash; diff --git a/opennurbs_subd_data.cpp b/opennurbs_subd_data.cpp index 784a0715..ba7eef62 100644 --- a/opennurbs_subd_data.cpp +++ b/opennurbs_subd_data.cpp @@ -50,9 +50,11 @@ void ON_SubDimple::Clear() m_subd_appearance = ON_SubD::DefaultSubDAppearance; m_texture_coordinate_type = ON_SubDTextureCoordinateType::Unset; m_texture_mapping_tag = ON_MappingTag::Unset; - m_fragment_colors_mapping_tag = ON_MappingTag::Unset; + m_colors_mapping_tag = ON_MappingTag::Unset; + m_fragment_texture_settings_hash = ON_SHA1_Hash::EmptyContentHash; m_fragment_colors_settings_hash = ON_SHA1_Hash::EmptyContentHash; + for (unsigned i = 0; i < m_levels.UnsignedCount(); ++i) { ON_SubDLevel* level = m_levels[i]; @@ -638,27 +640,33 @@ bool ON_SubDVertex::Transform( { TransformPoint(&xform.m_xform[0][0],m_P); - Internal_TransformComponentBase(bTransformationSavedSubdivisionPoint, xform); - - // TODO: - // If the vertex - // is tagged as ON_SubDVertexTag::Corner - // and bTransformationSavedSubdivisionPoint is true, - // and the corner sector(s) contains interior smooth edges, - // and the transformation changes the angle between a corner sector's crease boundary, - // then the sector's interior smooth edge's m_sector_coefficient[] could change - // and invalidate the subdivison points and limit points. - // This is only possible for uncommon (in practice) transformations - // and corner sectors and will require a fair bit of testing for - // now it's easier to simply set bTransformationSavedSubdivisionPoint to false - // at a higher level when these types of transformations are encountered. - if ( bTransformationSavedSubdivisionPoint && Internal_SurfacePointFlag() ) + if (bTransformationSavedSubdivisionPoint) { - for (const ON_SubDSectorSurfacePoint* lp = &m_limit_point; nullptr != lp; lp = lp->m_next_sector_limit_point) - const_cast(lp)->Transform(xform); + // Transform saved subdivision point + Internal_TransformComponentBase(bTransformationSavedSubdivisionPoint, xform); + + // NOTE WELL: + // If the vertex + // is tagged as ON_SubDVertexTag::Corner + // and bTransformationSavedSubdivisionPoint is true, + // and the corner sector(s) contains interior smooth edges, + // and the transformation changes the angle between a corner sector's crease boundary, + // then the sector's interior smooth edge's m_sector_coefficient[] could change + // and invalidate the subdivison points and limit points. + // This is only possible for uncommon (in practice) transformations + // and corner sectors and will require a fair bit of testing for + // now it's easier to simply set bTransformationSavedSubdivisionPoint to false + // at a higher level when these types of transformations are encountered. + if (bTransformationSavedSubdivisionPoint && Internal_SurfacePointFlag()) + { + for (const ON_SubDSectorSurfacePoint* lp = &m_limit_point; nullptr != lp; lp = lp->m_next_sector_limit_point) + const_cast(lp)->Transform(xform); + } + else + Internal_ClearSurfacePointFlag(); } else - Internal_ClearSurfacePointFlag(); + this->ClearSavedSubdivisionPoints(); return true; } @@ -669,6 +677,10 @@ void ON_SubDVertex::UnsetControlNetPoint() m_P[1] = ON_DBL_QNAN; m_P[2] = ON_DBL_QNAN; ClearSavedSubdivisionPoints(); + // With a nan control net point, there is no need for an expensive unset + // of the neighborhod because the caller will either later pass + // bClearNeighborhoodCache=true to ON_SubDVertex::SetControlNetPoint(...,bClearNeighborhoodCache) + // or deal with cleaning up the cached evaluations in some other way. } bool ON_SubDVertex::SetControlNetPoint( @@ -679,54 +691,115 @@ bool ON_SubDVertex::SetControlNetPoint( if (false == control_net_point.IsValid()) return false; - if (!(m_P[0] == control_net_point.x && m_P[1] == control_net_point.y && m_P[2] == control_net_point.z)) + if (false == (m_P[0] == control_net_point.x && m_P[1] == control_net_point.y && m_P[2] == control_net_point.z)) { m_P[0] = control_net_point.x; m_P[1] = control_net_point.y; m_P[2] = control_net_point.z; ClearSavedSubdivisionPoints(); - if (bClearNeighborhoodCache) + for(;;) { - for (unsigned short vei = 0; vei < m_edge_count; vei++) + if (false == bClearNeighborhoodCache) + break; + + if (this->m_edge_count <= 0 || nullptr == this->m_edges) + break; + + // need to clear 2 rings of faces around "this" vertex. + + const bool bThisVertexIsACorner = ON_SubDVertexTag::Corner == this->m_vertex_tag; + for (unsigned short vei = 0; vei < this->m_edge_count; vei++) { - ON_SubDEdge* edge = ON_SUBD_EDGE_POINTER(m_edges[vei].m_ptr); + const ON__UINT_PTR edgeptr = this->m_edges[vei].m_ptr; + const ON_SubDEdge* edge = ON_SUBD_EDGE_POINTER(edgeptr); if (nullptr == edge) continue; - edge->ClearSavedSubdivisionPoints(); - ON_SubDFacePtr* fptr = edge->m_face2; - for (unsigned short efi = 0; efi < edge->m_face_count; efi++, fptr++) - { - if (2 == efi) - { - fptr = edge->m_facex; - if (nullptr == fptr) - break; - } - ON_SubDFace* face = ON_SUBD_FACE_POINTER(fptr->m_ptr); - if (nullptr == face) - continue; - face->ClearSavedSubdivisionPoints(); + edge->ON_SubDComponentBase::Internal_ClearSubdivisionPointAndSurfacePointFlags(); + // v1 = vertex opposite this on edge + const ON_SubDVertex* v1 = edge->m_vertex[1 - ON_SUBD_EDGE_DIRECTION(edgeptr)]; + if (nullptr == v1) + continue; - ON_SubDEdgePtr* eptr = face->m_edge4; - for (unsigned short fei = 0; fei < face->m_edge_count; fei++, eptr++) + v1->ClearSavedSubdivisionPoints(); + + if (ON_SubDVertexTag::Smooth != v1->m_vertex_tag) + continue; + if (false == bThisVertexIsACorner) + continue; + if (false == edge->IsSmooth()) + continue; + // When a corner vertex is moved, the sector coefficients + // for smooth edges can change because the corner sector + // coefficient depends on the angle between the creases that + // bound the sector. For any other tag, the sector + // coefficients depend only on the topology and tags and + // moving a control net point does not change those. + edge->UnsetSectorCoefficientsForExperts(); + } + + if (this->m_face_count <= 0 || nullptr == this->m_faces) + break; + + //const ON_SubDFace* face = this->m_faces[m_face_count - 1]; + for(unsigned short vfi = 0; vfi < m_face_count; vfi++) + { + //const ON_SubDFace* prevface = face; + const ON_SubDFace* face = this->m_faces[vfi]; + if (nullptr == face) + continue; + + // face->ON_SubDComponentBase::Internal_ClearSubdivisionPointAndSurfacePointFlags() is fast + face->ON_SubDComponentBase::Internal_ClearSubdivisionPointAndSurfacePointFlags(); + + const ON_SubDEdgePtr* face_eptr = face->m_edge4; + for (unsigned short fei = 0; fei < face->m_edge_count; fei++, face_eptr++) + { + if (4 == fei) { - if (4 == fei) - { - eptr = face->m_edgex; - if (nullptr == eptr) - break; - } - ON_SubDEdge* fedge = ON_SUBD_EDGE_POINTER(eptr->m_ptr); - if (nullptr == fedge) - continue; - ON_SubDVertex* fvertex = const_cast(fedge->m_vertex[ON_SUBD_EDGE_DIRECTION(eptr->m_ptr)]); - if (nullptr == fvertex) - continue; - fvertex->ClearSavedSubdivisionPoints(); + face_eptr = face->m_edgex; + if (nullptr == face_eptr) + break; + } + const ON__UINT_PTR e1ptr = face_eptr->m_ptr; + ON_SubDEdge* e1 = ON_SUBD_EDGE_POINTER(e1ptr); + if (nullptr == e1) + continue; + + // e1->ON_SubDComponentBase::Internal_ClearSubdivisionPointAndSurfacePointFlags() is fast. + // There is no need to unset e1 sector coefficients. + e1->ON_SubDComponentBase::Internal_ClearSubdivisionPointAndSurfacePointFlags(); + const ON_SubDVertex* v1 = e1->m_vertex[ON_SUBD_EDGE_DIRECTION(e1ptr)]; + if (this == v1 || nullptr == v1) + continue; + v1->ClearSavedSubdivisionPoints(); + + if (v1->m_edge_count <= 0 || nullptr == v1->m_edges) + continue; + for (unsigned short v1ei = 0; v1ei < v1->m_edge_count; v1ei++) + { + // e2 is sometime in ring 1, sometimes between ring 1 and 2, + // and sometimes in ring 2, but this is enough to clear the + // ring 2 edges that can be modified by moving "this" vertex. + const ON_SubDEdge* e2 = ON_SUBD_EDGE_POINTER(v1->m_edges[v1ei].m_ptr); + if (nullptr != e2) + e2->ON_SubDComponentBase::Internal_ClearSubdivisionPointAndSurfacePointFlags(); + } + + if (v1->m_face_count <= 0 || nullptr == v1->m_faces) + continue; + for (unsigned short v1fi = 0; v1fi < v1->m_face_count; v1fi++) + { + // f2 is sometimes in ring 1 and sometimes in ring 2, but this + // is enough to clear the ring 2 faces that can be modified by + // moving "this" vertex. + const ON_SubDFace* f2 = v1->m_faces[v1fi]; + if (nullptr != f2) + f2->ON_SubDComponentBase::Internal_ClearSubdivisionPointAndSurfacePointFlags(); } } } + break; } } @@ -764,15 +837,27 @@ bool ON_SubDFace::Transform( const class ON_Xform& xform ) { - Internal_TransformComponentBase(bTransformationSavedSubdivisionPoint, xform); - - if (bTransformationSavedSubdivisionPoint && Internal_SurfacePointFlag() ) + if (bTransformationSavedSubdivisionPoint) { - for (ON_SubDMeshFragment* f = m_mesh_fragments; nullptr != f; f = f->m_next_fragment) - f->Transform(xform); + Internal_TransformComponentBase(true, xform); + + if (Internal_SurfacePointFlag()) + { + // bTransformationSavedSubdivisionPoint = true means xform is an isometry. + // If its more complicated than this, the calling code should + // reset or addjut colors as needed based on information in the + // SubD's texture coordinate mapping tag and color mapping tag. + // Note that both of those tags have their transformation updated + // so intelligent decisions can be made at a higher level where + // there is enough context to make the correct decision. + for (ON_SubDMeshFragment* f = m_mesh_fragments; nullptr != f; f = f->m_next_fragment) + f->Transform(true, true, true, xform); + } + else + Internal_ClearSurfacePointFlag(); } else - Internal_ClearSurfacePointFlag(); + this->ClearSavedSubdivisionPoints(); return true; } @@ -871,7 +956,10 @@ bool ON_SubDimple::Transform( // In all other cases, set bTransformationSavedSubdivisionPoint = false // and any saved subdivision points or saved limit points will be // deleted. - const bool bTransformationSavedSubdivisionPoint = false; // todo - set this correctly + const bool bTransformationSavedSubdivisionPoint = (1 == xform.IsRigid()); + + bool bHasTextures = false; + bool bHasColors = false; for (unsigned int level_index = 0; level_index < level_count; level_index++) { @@ -887,8 +975,25 @@ bool ON_SubDimple::Transform( rc = false; break; } + + if (level->m_face_count > 0 && level->m_face) + { + const ON_SubDMeshFragment* frag = level->m_face[0]->MeshFragments(); + if (nullptr != frag) + { + if (frag->TextureCoordinateCount() > 0) + bHasTextures = true; + if (frag->ColorCount() > 0) + bHasColors = true; + } + } } + if (bHasTextures) + this->m_texture_mapping_tag.Transform(xform); + if (bHasColors) + this->m_colors_mapping_tag.Transform(xform); + // SubD has been moved - geometry changed and we need to bump the geometry content serial number. this->ChangeGeometryContentSerialNumber(false); @@ -951,7 +1056,17 @@ bool ON_SubDimple::Transform( bool ON_SubDMeshFragment::Transform( const ON_Xform& xform - ) +) +{ + return this->Transform(true, true, true, xform); +} + +bool ON_SubDMeshFragment::Transform( + bool bKeepCurvatures, + bool bKeepTextureCoordinates, + bool bKeepColors, + const ON_Xform& xform +) { const unsigned count = PointCount(); if (0 == count) @@ -996,6 +1111,31 @@ bool ON_SubDMeshFragment::Transform( } } ON_GetPointListBoundingBox(3,0,count,(int)m_P_stride,m_P,&m_surface_bbox.m_min.x,&m_surface_bbox.m_max.x,false); + + if (false == bKeepTextureCoordinates) + { + this->SetTextureCoordinatesExistForExperts(false); + double* p = &this->m_ctrlnetT[0][0]; + double* p1 = p + sizeof(this->m_ctrlnetT) / sizeof(this->m_ctrlnetT[0][0]); + while (p < p1) + *p++ = ON_DBL_QNAN; + } + if (false == bKeepCurvatures) + { + this->SetCurvaturesExistForExperts(false); + this->m_ctrlnetK[0] = ON_SurfaceCurvature::Nan; + this->m_ctrlnetK[1] = ON_SurfaceCurvature::Nan; + this->m_ctrlnetK[2] = ON_SurfaceCurvature::Nan; + this->m_ctrlnetK[3] = ON_SurfaceCurvature::Nan; + } + if (false == bKeepColors) + { + this->SetColorsExistForExperts(false); + this->m_ctrlnetC[0] = ON_Color::UnsetColor; + this->m_ctrlnetC[1] = ON_Color::UnsetColor; + this->m_ctrlnetC[2] = ON_Color::UnsetColor; + this->m_ctrlnetC[3] = ON_Color::UnsetColor; + } return true; } @@ -1003,11 +1143,12 @@ bool ON_SubDMeshImpl::Transform( const ON_Xform& xform ) { + const bool bIsometry = (1 == xform.IsRigid()); m_bbox = ON_BoundingBox::EmptyBoundingBox; ON_BoundingBox bbox = ON_BoundingBox::EmptyBoundingBox; for ( const ON_SubDMeshFragment* fragment = m_first_fragment; nullptr != fragment; fragment = fragment->m_next_fragment) { - if ( false == const_cast(fragment)->Transform(xform) ) + if ( false == const_cast(fragment)->Transform(bIsometry, bIsometry, bIsometry, xform) ) return ON_SUBD_RETURN_ERROR(false); if ( fragment == m_first_fragment ) bbox = fragment->m_surface_bbox; diff --git a/opennurbs_subd_data.h b/opennurbs_subd_data.h index daf85149..a289d39d 100644 --- a/opennurbs_subd_data.h +++ b/opennurbs_subd_data.h @@ -1160,11 +1160,24 @@ public: void ClearEvaluationCache() const; + /// + /// + /// + /// + /// + /// + /// + /// If (0 != 1(&)copy_status, then fragments were copied. + /// If (0 != 2(&)copy_status, then fragment per vertex texture coordinates were copied. + /// If (0 != 4(&)copy_status, then fragment per vertex principal curvatures were copied. + /// If (0 != 8(&)copy_status, then fragment per vertex colors were copied. + /// + /// bool CopyEvaluationCacheForExperts( class ON_SubDHeap& this_heap, const ON_SubDLevel& src, const class ON_SubDHeap& src_heap, - bool& bFragmentsWereCopied + unsigned& copy_status ); void ClearTopologicalAttributes() const @@ -2491,21 +2504,33 @@ public: m_fragment_colors_settings_hash = hash; } - const ON_MappingTag FragmentColorsMappingTag() const; - void SetFragmentColorsMappingTag(const ON_MappingTag& mapping_tag) const; + const ON_MappingTag ColorsMappingTag() const; + void SetColorsMappingTag(const ON_MappingTag& mapping_tag) const; private: mutable ON_SubDComponentLocation m_subd_appearance = ON_SubD::DefaultSubDAppearance; mutable ON_SubDTextureCoordinateType m_texture_coordinate_type = ON_SubDTextureCoordinateType::Unset; unsigned short m_reserved = 0; - mutable ON_MappingTag m_texture_mapping_tag; - mutable ON_MappingTag m_fragment_colors_mapping_tag; - // hash of the settings used to create the current fragment texture coordinates + // m_texture_mapping_tag identifies the mapping used to set the fragment per vertex texture coordinates. + // If m_texture_mapping_tag is set and fragment per vertex texture coordinates are missing, + // m_texture_mapping_tag.m_mapping_id specifies the method used to update the texture coordinates. + mutable ON_MappingTag m_texture_mapping_tag; + + // m_colors_mapping_tag identifies the mapping used to set the fragment per vertex colors. + // If m_colors_mapping_tag is set and fragment per vertex colors are missing, + // m_colors_mapping_tag.m_mapping_id specifies the method used to update the colors. + // Per vertex colors are used for false color analysis modes (curvature, draft angle) and + // other specific uses. + mutable ON_MappingTag m_colors_mapping_tag; + + // hash of the settings used to create the current fragment texture coordinates. + // This hash has meaning only when fragments with per vertex texture coordinates exist. mutable ON_SHA1_Hash m_fragment_texture_settings_hash = ON_SHA1_Hash::EmptyContentHash; // hash of the settings used to create the current fragment vertex colors + // This hash has meaning only when fragments with per vertex colors exist. mutable ON_SHA1_Hash m_fragment_colors_settings_hash = ON_SHA1_Hash::EmptyContentHash; ON_SimpleArray< ON_SubDLevel* > m_levels; diff --git a/opennurbs_subd_eval.cpp b/opennurbs_subd_eval.cpp index 3d43aa93..04de3cf7 100644 --- a/opennurbs_subd_eval.cpp +++ b/opennurbs_subd_eval.cpp @@ -1089,7 +1089,6 @@ bool ON_SubDVertex::SurfacePointIsSet() const void ON_SubDEdge::ClearSavedSubdivisionPoints() const { - // considering using a global pool for the limit curve cache - not yet. ON_SubDComponentBase::Internal_ClearSubdivisionPointAndSurfacePointFlags(); } diff --git a/opennurbs_subd_fragment.cpp b/opennurbs_subd_fragment.cpp index 6eb9f2fd..ba788e39 100644 --- a/opennurbs_subd_fragment.cpp +++ b/opennurbs_subd_fragment.cpp @@ -635,11 +635,19 @@ bool ON_SubD::SetFragmentColorsFromCallback( const ON_SurfaceCurvature& K) ) const { - if (bLazySet + if (bLazySet && fragment_colors_settings_hash == FragmentColorsSettingsHash() - && fragment_colors_mapping_tag == FragmentColorsMappingTag() + && fragment_colors_mapping_tag == ColorsMappingTag() + && this->HasFragmentColors() ) + { + // This subd has fragments with per vertex colors. + // The settings used to create those colors exactly + // match the settings that color_callback() will use + // to assign colors. The caller said to be lazy, so + // assume the existing colors are correct and return. return true; + } bool bFragmentVetexColorsSet = false; const ON_SubDimple* subdimple = this->SubDimple(); @@ -665,13 +673,13 @@ bool ON_SubD::SetFragmentColorsFromCallback( if (bFragmentVetexColorsSet) { subdimple->Internal_SetFragmentColorsSettingsHash(fragment_colors_settings_hash); - SetFragmentColorsMappingTag(fragment_colors_mapping_tag); + SetColorsMappingTag(fragment_colors_mapping_tag); ChangeRenderContentSerialNumber(); } else { subdimple->Internal_SetFragmentColorsSettingsHash(ON_SHA1_Hash::EmptyContentHash); - this->SetFragmentColorsMappingTag(ON_MappingTag::Unset); + this->SetColorsMappingTag(ON_MappingTag::Unset); } } @@ -680,25 +688,31 @@ bool ON_SubD::SetFragmentColorsFromCallback( bool ON_SubD::HasFragmentColors() const { + bool bHasColors = false; const ON_SubDimple* subdimple = this->SubDimple(); if (nullptr != subdimple) { ON_SubDMeshFragmentIterator fragit(*this); for (const ON_SubDMeshFragment* frag = fragit.FirstFragment(); nullptr != frag; frag = fragit.NextFragment()) { - if (frag->ColorCount() > 0) - return true; + if (0 == frag->ColorCount()) + return false; + // NOTE WELL: + // All fragments need to be tested as lasy updates of the + // surface mesh cache result in the update fragments having + // no colors while the preexisting fragments have colors. + bHasColors = true; // fragments with colors exist } } - return false; + return bHasColors; } bool ON_SubD::HasFragmentColors( - ON_MappingTag color_tag + ON_MappingTag color_mapping_tag ) const { return - this->FragmentColorsMappingTag() == color_tag + this->ColorsMappingTag() == color_mapping_tag && this->HasFragmentColors(); } @@ -713,16 +727,63 @@ bool ON_SubD::HasFragmentColors( bool ON_SubD::HasFragmentColors( ON_SHA1_Hash color_settings_hash, - ON_MappingTag color_tag + ON_MappingTag color_mapping_tag ) const { return this->FragmentColorsSettingsHash() == color_settings_hash - && this->FragmentColorsMappingTag() == color_tag + && this->ColorsMappingTag() == color_mapping_tag && this->HasFragmentColors(); } + +bool ON_SubD::HasFragmentTextureCoordinates() const +{ + const ON_SubDimple* subdimple = this->SubDimple(); + if (nullptr != subdimple) + { + ON_SubDMeshFragmentIterator fragit(*this); + for (const ON_SubDMeshFragment* frag = fragit.FirstFragment(); nullptr != frag; frag = fragit.NextFragment()) + { + if (frag->ColorCount() > 0) + return true; + } + } + return false; +} + +bool ON_SubD::HasFragmentTextureCoordinates( + ON_MappingTag texture_mapping_tag +) const +{ + return + this->TextureMappingTag(true) == texture_mapping_tag + && this->HasFragmentTextureCoordinates(); +} + +bool ON_SubD::HasFragmentTextureCoordinates( + ON_SHA1_Hash texture_settings_hash +) const +{ + return + this->TextureSettingsHash() == texture_settings_hash + && this->HasFragmentTextureCoordinates(); +} + +bool ON_SubD::HasFragmentTextureCoordinates( + ON_SHA1_Hash texture_settings_hash, + ON_MappingTag texture_mapping_tag +) const +{ + return + this->TextureSettingsHash() == texture_settings_hash + && this->TextureMappingTag(true) == texture_mapping_tag + && this->HasFragmentTextureCoordinates(); +} + + + void ON_SubD::ClearFragmentColors( bool bClearFragmentColorsMappingTag ) @@ -741,7 +802,7 @@ void ON_SubD::ClearFragmentColors( if (bClearFragmentColorsMappingTag) { subdimple->Internal_SetFragmentColorsSettingsHash(ON_SHA1_Hash::EmptyContentHash); - this->SetFragmentColorsMappingTag(ON_MappingTag::Unset); + this->SetColorsMappingTag(ON_MappingTag::Unset); } if (bFragmentsChanged) this->ChangeRenderContentSerialNumber(); @@ -755,16 +816,26 @@ const ON_SHA1_Hash ON_SubD::FragmentColorsSettingsHash() const } void ON_SubD::SetFragmentColorsMappingTag(const ON_MappingTag& colors_mapping_tag) const +{ + return SetColorsMappingTag(colors_mapping_tag); +} + +void ON_SubD::SetColorsMappingTag(const ON_MappingTag& colors_mapping_tag) const { const ON_SubDimple* dimple = SubDimple(); if (nullptr != dimple) - dimple->SetFragmentColorsMappingTag(colors_mapping_tag); + dimple->SetColorsMappingTag(colors_mapping_tag); } const ON_MappingTag ON_SubD::FragmentColorsMappingTag() const +{ + return ColorsMappingTag(); +} + +const ON_MappingTag ON_SubD::ColorsMappingTag() const { const ON_SubDimple* dimple = SubDimple(); - return (nullptr != dimple) ? dimple->FragmentColorsMappingTag() : ON_MappingTag::Unset; + return (nullptr != dimple) ? dimple->ColorsMappingTag() : ON_MappingTag::Unset; } const ON_3dPoint ON_SubDMeshFragment::ControlNetQuadPoint( diff --git a/opennurbs_subd_texture.cpp b/opennurbs_subd_texture.cpp index 40100857..46ec7b8c 100644 --- a/opennurbs_subd_texture.cpp +++ b/opennurbs_subd_texture.cpp @@ -256,16 +256,16 @@ const ON_MappingTag ON_SubDimple::TextureMappingTag(bool bIgnoreTextureCoordinat #pragma endregion #endif -const ON_MappingTag ON_SubDimple::FragmentColorsMappingTag() const +const ON_MappingTag ON_SubDimple::ColorsMappingTag() const { - return m_fragment_colors_mapping_tag; + return m_colors_mapping_tag; } -void ON_SubDimple::SetFragmentColorsMappingTag(const ON_MappingTag& mapping_tag) const +void ON_SubDimple::SetColorsMappingTag(const ON_MappingTag& mapping_tag) const { - if (0 != ON_MappingTag::CompareAll(this->m_fragment_colors_mapping_tag, mapping_tag)) + if (0 != ON_MappingTag::CompareAll(this->m_colors_mapping_tag, mapping_tag)) { - this->m_fragment_colors_mapping_tag = mapping_tag; + this->m_colors_mapping_tag = mapping_tag; ChangeRenderContentSerialNumber(); } } diff --git a/opennurbs_text.cpp b/opennurbs_text.cpp index 697ec81b..dd5d6291 100644 --- a/opennurbs_text.cpp +++ b/opennurbs_text.cpp @@ -1133,19 +1133,15 @@ int ON_TextContent::Dimension() const const ON_BoundingBox ON_TextContent::TextContentBoundingBox() const { ON_TextRunArray* runs = const_cast(this)->TextRuns(false); - const int run_count - = (nullptr != runs) - ? runs->Count() - : 0; + const int run_count = (nullptr != runs) ? runs->Count() : 0; - if (run_count <= 0) - return ON_BoundingBox::EmptyBoundingBox; - - const ON_SHA1_Hash text_content_hash = TextContentHash(); - if ( - m_text_content_bbox.IsValid() - && m_text_content_bbox_hash == text_content_hash - ) + // 28 Aug 2023 S. Baer + // Computing a content hash takes longer than just computing the bounding box + // for simple single run situations. Only use the cached bbox when there are + // more than one run. + if ( run_count > 1 && + m_text_content_bbox.IsValid() && + m_text_content_bbox_hash == TextContentHash()) { return m_text_content_bbox; } @@ -1162,8 +1158,8 @@ const ON_BoundingBox ON_TextContent::TextContentBoundingBox() const if (nullptr == run) continue; - if (ON_TextRun::RunType::kText == (*runs)[i]->Type() || - ON_TextRun::RunType::kField == (*runs)[i]->Type()) + if (ON_TextRun::RunType::kText == run->Type() || + ON_TextRun::RunType::kField == run->Type()) { ON_BoundingBox runbox = run->BoundingBox(); if (false == runbox.IsValid()) @@ -1204,10 +1200,10 @@ const ON_BoundingBox ON_TextContent::TextContentBoundingBox() const bbox.m_min.z = 0.0; bbox.m_max.z = 0.0; - if (bbox.IsValid()) + if (run_count>1 && bbox.IsValid()) { m_text_content_bbox = bbox; - m_text_content_bbox_hash = text_content_hash; + m_text_content_bbox_hash = TextContentHash(); } return bbox; diff --git a/opennurbs_viewport.cpp b/opennurbs_viewport.cpp index 3a7ad8be..9c140f5c 100644 --- a/opennurbs_viewport.cpp +++ b/opennurbs_viewport.cpp @@ -4964,8 +4964,8 @@ void ON_Viewport::GetPerspectiveClippingPlaneConstraints( if ( depth_buffer_bit_depth >= 32 ) { - nof = 0.0001; - n = 0.001; + nof = 0.0005; // Changed to match 24 bit defaults: https://mcneel.myjetbrains.com/youtrack/issue/RH-77623 + n = 0.005; // Do not change back unless you've fixed the problems shown in the YT somewhere else. } else if ( depth_buffer_bit_depth >= 24 ) { diff --git a/opennurbs_xform.h b/opennurbs_xform.h index d6fcd341..ef9cf9d3 100644 --- a/opennurbs_xform.h +++ b/opennurbs_xform.h @@ -277,23 +277,41 @@ public: /* Description: - A rigid transformation can be broken into a proper rotation and a translation. - while an isometry transformation could also include a reflection. - Parameters: - *this - must be IsAffine(). - Translation - [out] Translation vector - Rotation - [out] Proper Rotation transformation, ie. R*Transpose(R)=I and det(R)=1 - Details: - If X.DecomposeRigid(T, R) is 1 then X ~ TranslationTransformation(T)*R - -1 X ~ ON_Xform(-1) *TranslationTransformation(T)*R - where ~ means approximates to within tolerance. - DecomposeRigid will find the closest rotation to the linear part of this transformation. + NOTE: A better name for this fuction would be IsIsometry(). + + An "isometry" transformation is an affine transformation that preserves + distances. Isometries include transformation like reflections + that reverse orientation. + + A "rigid" transformation is an isometry that preserves orientation + and can be broken into a proper rotation and a translation. Returns: - +1: This transformation is an rigid transformation. - -1: This transformation is an orientation reversing isometry. + +1: This transformation is an orientation preserving isometry transformation ("rigid and determinant = 1). + -1: This transformation is an orientation reversing isometry (determinant = -1). 0 : This transformation is not an orthogonal transformation. */ int IsRigid(double tolerance = ON_ZERO_TOLERANCE) const; + + + /* + Description: + A rigid transformation preserves distances and orientation + and can be broken into a proper rotation and a translation. + An isometry transformation preserves distance and may include a reflection. + Parameters: + *this - must be IsAffine(). + Translation - [out] Translation vector + Rotation - [out] Proper Rotation transformation, ie. R*Transpose(R)=I and det(R)=1 + Details: + If X.DecomposeRigid(T, R) is 1 then X ~ TranslationTransformation(T)*R + -1 X ~ ON_Xform(-1) *TranslationTransformation(T)*R + where ~ means approximates to within tolerance. + DecomposeRigid will find the closest rotation to the linear part of this transformation. + Returns: + +1: This transformation is an rigid transformation. + -1: This transformation is an orientation reversing isometry. + 0 : This transformation is not an orthogonal transformation. + */ int DecomposeRigid(ON_3dVector& Translation, ON_Xform& Rotation, double tolerance = ON_ZERO_TOLERANCE) const; /* diff --git a/opennurbs_xml.cpp b/opennurbs_xml.cpp index 0860c678..b4f8bc0f 100644 --- a/opennurbs_xml.cpp +++ b/opennurbs_xml.cpp @@ -294,8 +294,8 @@ public: const ON_wString& ConvertDoubleArrayToString(int count) const { - constexpr int maxCount = 16; - if ((count == 0) || (count > maxCount)) + constexpr int maxCount = array_val_max; + if ((count < 1) || (count > maxCount)) return _string_val; constexpr int maxLen = 30; @@ -329,6 +329,8 @@ public: mutable ON_Buffer* _buffer = nullptr; mutable ON_wString _string_val; + + static constexpr int array_val_max = 16; union { bool _bool_val; @@ -338,7 +340,7 @@ public: ON_Xform m_xform; time_t _time_val; ON_UUID _uuid_val; - double _array_val[16] = { 0 }; + double _array_val[array_val_max] = { 0 }; }; ON::LengthUnitSystem _units = ON::LengthUnitSystem::None; @@ -559,7 +561,7 @@ bool ON_XMLVariant::operator == (const ON_XMLVariant& v) const return _private->_time_val == v._private->_time_val; case Types::Matrix: - for (int i = 0; i < 16; i++) + for (int i = 0; i < _private->array_val_max; i++) { if (_private->_array_val[i] != v._private->_array_val[i]) return false; @@ -975,18 +977,18 @@ ON_Xform ON_XMLVariant::AsXform(void) const switch (_private->_type) { case Types::Matrix: - break; + return _private->m_xform; case Types::String: if (_private->_string_val.IsValidMatrix()) - StringToPoint(16); //////////////////////////////////// Risky - break; + { + StringToPoint(16); + return _private->m_xform; + } default: return ON_Xform::Zero4x4; } - - return _private->m_xform; } ON_4fColor ON_XMLVariant::AsColor(void) const @@ -1208,27 +1210,66 @@ ON_XMLVariant::operator ON_Buffer() const void ON_XMLVariant::StringToPoint(int numValues) const { - if ((numValues < 0) || (numValues > 16)) - return; + // 22nd September 2022 John Croudy, https://mcneel.myjetbrains.com/youtrack/issue/RH-77182 + // This function crashed on the Mac inside wcstod_l() which must be getting called from the call to + // ON_wtof(). The only way that function can fail is if either the input pointers are invalid or the + // input string or locale is corrupted. I don't have any control over the locale (I don't even know + // how it's being accessed on the Mac), so all I can do is check the input string. It's unlikely that + // an incorrectly formatted string (i.e., not a float) would cause a crash, but I can do some simple + // validation to try and avoid trouble. - ON_wString s = _private->_string_val + L",0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"; - auto* p = s.Array(); + bool good = true; - for (int i = 0; i < numValues; i++) + if ((numValues < 0) || (numValues > _private->array_val_max)) { - while (iswspace(*p)) + good = false; + } + else + if (_private->_string_val.IsEmpty()) + { + good = false; + } + + if (good) + { + ON_wString s = _private->_string_val + L",0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"; + + auto* p = static_cast(s); + for (int i = 0; i < numValues; i++) { + // Skip white space. iswspace() returns 0 for the terminator so we can't go off the end of the buffer. + while (0 != iswspace(*p)) + { + p++; + } + + // Quick and simple sanity check at the start of a float value. + if (isdigit(*p) || (*p == '.') || (*p == '+') || (*p == '-')) + { + _private->_array_val[i] = ON_wtof(p); + } + + // Because we've appended a fixed string containing commas, we know the pointer can't go off + // the end of the buffer while checking for a comma. + while (*p != L',') + { + p++; + } + + // 'p' is now pointing to a comma. + ON_ASSERT(*p == L','); + + // After incrementing it, the worst case scenario is that it's pointing to the terminator. p++; } - - _private->_array_val[i] = ON_wtof(p); - - while (*p != L',') + } + else + { + // Bad input; clear the result to all zeroes. + for (int i = 0; i < _private->array_val_max; i++) { - p++; + _private->_array_val[i] = 0.0; } - - p++; } } diff --git a/opennurbs_xml.h b/opennurbs_xml.h index 053b93fc..9eb4c6b4 100644 --- a/opennurbs_xml.h +++ b/opennurbs_xml.h @@ -236,6 +236,7 @@ typedef bool (*ON_XMLRecurseChildrenCallback)(class ON_XMLNode*, void*); #define ON_PBR_MATERIAL_CLEARCOAT_BUMP L"pbr-clearcoat-bump" #define ON_PBR_MATERIAL_CLEARCOAT_ROUGHNESS L"pbr-clearcoat-roughness" #define ON_PBR_MATERIAL_EMISSION_COLOR L"pbr-emission" +#define ON_PBR_MATERIAL_EMISSION_MULTIPLIER L"emission-multiplier" #define ON_PBR_MATERIAL_METALLIC L"pbr-metallic" #define ON_PBR_MATERIAL_OPACITY L"pbr-opacity" #define ON_PBR_MATERIAL_OPACITY_IOR L"pbr-opacity-ior"