// // Copyright (c) 1993-2022 Robert McNeel & Associates. All rights reserved. // OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert // McNeel & Associates. // // THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY. // ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF // MERCHANTABILITY ARE HEREBY DISCLAIMED. // // For complete openNURBS copyright information see . // //////////////////////////////////////////////////////////////// #include "opennurbs.h" #if !defined(ON_COMPILING_OPENNURBS) // This check is included in all opennurbs source .c and .cpp files to insure // ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled. // When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined // and the opennurbs .h files alter what is declared and how it is declared. #error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs #endif //////////////////////////////////////////////////////////////// // Class ON_Material //////////////////////////////////////////////////////////////// ON_OBJECT_IMPLEMENT(ON_Material,ON_ModelComponent,"60B5DBBC-E660-11d3-BFE4-0010830122F0"); const ON_Material* ON_Material::FromModelComponentRef( const class ON_ModelComponentReference& model_component_reference, const ON_Material* none_return_value ) { const ON_Material* p = ON_Material::Cast(model_component_reference.ModelComponent()); return (nullptr != p) ? p : none_return_value; } // Default constructor ON_Material::ON_Material() ON_NOEXCEPT : ON_ModelComponent(ON_ModelComponent::Type::RenderMaterial) {} ON_Material::ON_Material( const ON_Material& src) : ON_ModelComponent(ON_ModelComponent::Type::RenderMaterial,src) { Internal_CopyFrom(src); } void ON_Material::Internal_CopyFrom( const ON_Material& src ) { #define ON_COPY_SRC(m) m=src.m ON_COPY_SRC(m_rdk_material_instance_id); ON_COPY_SRC(m_ambient); ON_COPY_SRC(m_diffuse); ON_COPY_SRC(m_emission); ON_COPY_SRC(m_specular); ON_COPY_SRC(m_reflection); ON_COPY_SRC(m_transparent); ON_COPY_SRC(m_bShareable); ON_COPY_SRC(m_bDisableLighting); ON_COPY_SRC(m_bUseDiffuseTextureAlphaForObjectTransparencyTexture); ON_COPY_SRC(m_bFresnelReflections); ON_COPY_SRC(m_reflectivity); ON_COPY_SRC(m_shine); ON_COPY_SRC(m_transparency); ON_COPY_SRC(m_reflection_glossiness); ON_COPY_SRC(m_refraction_glossiness); ON_COPY_SRC(m_index_of_refraction); ON_COPY_SRC(m_fresnel_index_of_refraction); ON_COPY_SRC(m_textures); ON_COPY_SRC(m_material_channel); ON_COPY_SRC(m_plugin_id); #undef ON_COPY_SRC } bool ON_Material::IsValid( ON_TextLog* text_log ) const { return true; } void ON_Material::Dump( ON_TextLog& dump ) const { ON_ModelComponent::Dump(dump); dump.Print("ambient rgb = "); dump.PrintRGB( m_ambient ); dump.Print("\n"); dump.Print("diffuse rgb = "); dump.PrintRGB( m_diffuse ); dump.Print("\n"); dump.Print("emmisive rgb = "); dump.PrintRGB( m_emission ); dump.Print("\n"); dump.Print("specular rgb = "); dump.PrintRGB( m_specular ); dump.Print("\n"); dump.Print("reflection rgb = "); dump.PrintRGB( m_reflection ); dump.Print("\n"); dump.Print("transparent rgb = "); dump.PrintRGB( m_transparent ); dump.Print("\n"); dump.Print("shine = %g%%\n",100.0*m_shine/ON_Material::MaxShine ); dump.Print("transparency = %g%%\n",100.0*m_transparency); dump.Print("reflectivity = %g%%\n",100.0*m_reflectivity); dump.Print("index of refraction = %g\n",m_index_of_refraction); dump.Print("plug-in id = "); dump.Print(m_plugin_id); dump.Print("\n"); int i; for( i = 0; i < m_textures.Count(); i++ ) { dump.Print("texture[%d]:\n",i); dump.PushIndent(); m_textures[i].Dump(dump); dump.PopIndent(); } } ON_UUID ON_Material::MaterialPlugInId() const { return m_plugin_id; } void ON_Material::SetMaterialPlugInId( ON_UUID plugin_id ) { if (m_plugin_id != plugin_id) { m_plugin_id = plugin_id; IncrementContentVersionNumber(); } } bool ON_Material::Write( ON_BinaryArchive& archive ) const { if (archive.Archive3dmVersion() < 60) return Internal_WriteV5(archive); const int major_version = 1; const int minor_version = 0; if (!archive.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK,major_version,minor_version)) return false; bool rc = false; for (;;) { const unsigned int attributes_filter = ON_ModelComponent::Attributes::IndexAttribute | ON_ModelComponent::Attributes::IdAttribute | ON_ModelComponent::Attributes::NameAttribute; if ( !WriteModelComponentAttributes(archive,attributes_filter)) break; if ( !archive.WriteUuid(m_plugin_id) ) break; if ( !archive.WriteColor( m_ambient ) ) break; if ( !archive.WriteColor( m_diffuse ) ) break; if ( !archive.WriteColor( m_emission ) ) break; if ( !archive.WriteColor( m_specular ) ) break; if ( !archive.WriteColor( m_reflection ) ) break; if ( !archive.WriteColor( m_transparent ) ) break; if ( !archive.WriteDouble( m_index_of_refraction ) ) break; if ( !archive.WriteDouble( m_reflectivity ) ) break; if ( !archive.WriteDouble( m_shine ) ) break; if (!archive.WriteDouble(m_transparency)) break; // array of textures written in a way that user data persists { const int textures_major_version = 1; const int textures_minor_version = 0; if (!archive.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK, textures_major_version, textures_minor_version)) break; bool textures_chunk_rc = false; for (;;) { const unsigned int count = m_textures.Count(); if (!archive.WriteInt(count)) break; unsigned int i; for (i = 0; i < count; i++) { if (!archive.WriteObject(&m_textures[i])) break; } if (i < count) break; textures_chunk_rc = true; break; } if (!archive.EndWrite3dmChunk()) textures_chunk_rc = false; if ( !textures_chunk_rc ) break; } if ( !archive.WriteArray(m_material_channel) ) break; if ( !archive.WriteBool(m_bShareable) ) break; if ( !archive.WriteBool(m_bDisableLighting)) break; if ( !archive.WriteBool(m_bFresnelReflections)) break; if ( !archive.WriteDouble(m_reflection_glossiness)) break; if ( !archive.WriteDouble(m_refraction_glossiness)) break; if ( !archive.WriteDouble(m_fresnel_index_of_refraction)) break; if ( !archive.WriteUuid(m_rdk_material_instance_id)) break; if ( !archive.WriteBool(m_bUseDiffuseTextureAlphaForObjectTransparencyTexture)) break; rc = true; break; } if (!archive.EndWrite3dmChunk()) rc = false; return rc; } bool ON_Material::Read( ON_BinaryArchive& archive ) { *this = ON_Material::Unset; if (archive.Archive3dmVersion() < 60) return Internal_ReadV5(archive); if (archive.ArchiveOpenNURBSVersion() < 2348833910) return Internal_ReadV5(archive); bool rc = false; int major_version = 0; int minor_version = 0; if (!archive.BeginRead3dmChunk(TCODE_ANONYMOUS_CHUNK,&major_version,&minor_version)) return false; for (;;) { if (1 != major_version) break; if ( !ReadModelComponentAttributes(archive)) break; if ( !archive.ReadUuid(m_plugin_id) ) break; if ( !archive.ReadColor( m_ambient ) ) break; if ( !archive.ReadColor( m_diffuse ) ) break; if ( !archive.ReadColor( m_emission ) ) break; if ( !archive.ReadColor( m_specular ) ) break; if ( !archive.ReadColor( m_reflection ) ) break; if ( !archive.ReadColor( m_transparent ) ) break; if ( !archive.ReadDouble( &m_index_of_refraction ) ) break; if ( !archive.ReadDouble( &m_reflectivity ) ) break; if ( !archive.ReadDouble( &m_shine ) ) break; if (!archive.ReadDouble(&m_transparency)) break; // array of textures read in a way that user data persists { int textures_major_version = 0; int textures_minor_version = 0; if (!archive.BeginRead3dmChunk(TCODE_ANONYMOUS_CHUNK, &textures_major_version, &textures_minor_version)) break; bool textures_chunk_rc = false; for (;;) { if (1 != textures_major_version) break; unsigned int count = 0; if ( !archive.ReadInt(&count) ) break; m_textures.SetCount(0); m_textures.Reserve(count); unsigned int i; for (i = 0; i < count; i++) { if (!archive.ReadObject(m_textures.AppendNew())) break; } if (i < count) break; textures_chunk_rc = true; break; } if (!archive.EndRead3dmChunk()) textures_chunk_rc = false; if (!textures_chunk_rc) break; } if ( !archive.ReadArray(m_material_channel) ) break; if ( !archive.ReadBool(&m_bShareable) ) break; if ( !archive.ReadBool(&m_bDisableLighting)) break; if ( !archive.ReadBool(&m_bFresnelReflections)) break; if ( !archive.ReadDouble(&m_reflection_glossiness)) break; if ( !archive.ReadDouble(&m_refraction_glossiness)) break; if ( !archive.ReadDouble(&m_fresnel_index_of_refraction)) break; if ( !archive.ReadUuid(m_rdk_material_instance_id)) break; if ( !archive.ReadBool(&m_bUseDiffuseTextureAlphaForObjectTransparencyTexture)) break; rc = true; break; } if (!archive.EndRead3dmChunk()) rc = false; return rc; } bool ON_Material::Internal_WriteV5( ON_BinaryArchive& file ) const { bool rc = false; if ( file.Archive3dmVersion() <= 3 ) { // V2 or V3 file format rc = Internal_WriteV3(file); } else { // V4 file format // The chunk version 2.0 prevents old V3 IO code from attempting // to read this material rc = file.Write3dmChunkVersion(2,0); // never change the 2,0 // version 1.2 field (20061113*) // version 1.3 fields (20100917*) // version 1.4 fields 6.0.2013-11-6 // version 1.5 fields 6.0.2014-05-16 (RDK material instance id) // version 1.6 fields 6.0.2014-07-11 (m_bUseDiffuseTextureAlphaForObjectTransparencyTexture) const int minor_version = (file.Archive3dmVersion() >= 60) ? 6 : 3; if (rc) rc = file.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK,1,minor_version); if (rc) { for(;;) { if ( rc ) rc = file.WriteUuid(Id()); if ( rc ) rc = file.Write3dmReferencedComponentIndex(*this); if ( rc ) rc = file.WriteString(Name()); if ( rc ) rc = file.WriteUuid(m_plugin_id); if ( rc ) rc = file.WriteColor( m_ambient ); if ( rc ) rc = file.WriteColor( m_diffuse ); if ( rc ) rc = file.WriteColor( m_emission ); if ( rc ) rc = file.WriteColor( m_specular ); if ( rc ) rc = file.WriteColor( m_reflection ); if ( rc ) rc = file.WriteColor( m_transparent ); if ( rc ) rc = file.WriteDouble( m_index_of_refraction ); if ( rc ) rc = file.WriteDouble( m_reflectivity ); if ( rc ) rc = file.WriteDouble( m_shine ); if ( rc ) rc = file.WriteDouble( m_transparency ); if ( !rc ) break; // array of textures written in a way that user data persists rc = file.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK,1,0); if (rc) { int i, count = m_textures.Count(); rc = file.WriteInt(count); for ( i = 0; i < count && rc; i++ ) { rc = file.WriteObject(&m_textures[i]); } if ( !file.EndWrite3dmChunk() ) rc = false; } //version 1.1 field ON_wString obsolete_flamingo_library = ON_wString::EmptyString; if (rc) rc = file.WriteString(obsolete_flamingo_library); // version 1.2 field (20061113) if (rc) rc = file.WriteArray(m_material_channel); // version 1.3 fields (20100917*) rc = file.WriteBool(m_bShareable); if (!rc) break; rc = file.WriteBool(m_bDisableLighting); if (!rc) break; if ( minor_version >= 4 ) { rc = file.WriteBool(m_bFresnelReflections); if (!rc) break; rc = file.WriteDouble(m_reflection_glossiness); if (!rc) break; rc = file.WriteDouble(m_refraction_glossiness); if (!rc) break; rc = file.WriteDouble(m_fresnel_index_of_refraction); if (!rc) break; if (minor_version >= 5) { rc = file.WriteUuid(m_rdk_material_instance_id); if (!rc) break; } if (minor_version >= 6) { rc = file.WriteBool(m_bUseDiffuseTextureAlphaForObjectTransparencyTexture); if (!rc) break; } } break; } if (!file.EndWrite3dmChunk() ) rc = false; } } if (rc && file.Archive3dmVersion() < 60) { if (RdkMaterialInstanceIdIsNotNil()) { // For V5 files and earlier, we need to save // the RDK material instance id as user data. // Because ON_RdkMaterialInstanceIdObsoleteUserData.DeleteAfterWrite() // returns true, the user data we are attaching here will be deleted // after it is written. ON_RdkMaterialInstanceIdObsoleteUserData* ud = new ON_RdkMaterialInstanceIdObsoleteUserData(); ud->m_rdk_material_instance_id = RdkMaterialInstanceId(); const_cast(this)->AttachUserData(ud); } } return rc; } bool ON_Material::Internal_ReadV5( ON_BinaryArchive& file ) { int major_version = 0; int minor_version = 0; bool rc = file.Read3dmChunkVersion(&major_version,&minor_version); if (rc) { if ( 1 == major_version ) { rc = Internal_ReadV3(file,minor_version); } else if ( 2 == major_version ) { // fancy V4 material // V4 file format // The chunk version 2.0 prevents old V3 IO code from attempting // to read this material rc = file.BeginRead3dmChunk(TCODE_ANONYMOUS_CHUNK,&major_version,&minor_version); if (rc) { for(;;) { ON_UUID material_id = Id(); if ( rc ) rc = file.ReadUuid(material_id); if (rc) SetId(material_id); int material_index = Index(); if ( rc ) rc = file.ReadInt(&material_index); if (rc) SetIndex(material_index); ON_wString material_name; if ( rc ) rc = file.ReadString(material_name); if (rc) SetName(material_name); if ( rc ) rc = file.ReadUuid(m_plugin_id); if ( rc ) rc = file.ReadColor( m_ambient ); if ( rc ) rc = file.ReadColor( m_diffuse ); if ( rc ) rc = file.ReadColor( m_emission ); if ( rc ) rc = file.ReadColor( m_specular ); if ( rc ) rc = file.ReadColor( m_reflection ); if ( rc ) rc = file.ReadColor( m_transparent ); if ( rc && file.ArchiveOpenNURBSVersion() < 200912010 && 128 == m_transparent.Red() && 128 == m_transparent.Green() && 128 == m_transparent.Blue() ) { // Prior to 1 Dec 2009 the ON_Material::Defaults() set // m_transparent to 128,128,128. This was the wrong // value for the default. This "hack" is here to // make it appear that the default was always white. m_transparent = m_diffuse; } if ( rc ) rc = file.ReadDouble( &m_index_of_refraction ); if ( rc ) rc = file.ReadDouble( &m_reflectivity ); if ( rc ) rc = file.ReadDouble( &m_shine ); if ( rc ) rc = file.ReadDouble( &m_transparency ); if ( !rc ) break; // array of textures read in a way that user data persists int texmajver = 0; int texminver = 0; rc = file.BeginRead3dmChunk(TCODE_ANONYMOUS_CHUNK,&texmajver,&texminver); if (rc) { if ( 1 == texmajver ) { int i, count = 0; rc = file.ReadInt(&count); if (rc) m_textures.Reserve(count); for ( i = 0; i < count && rc; i++ ) { int trc = file.ReadObject(m_textures.AppendNew()); if (trc <= 0) { // http://mcneel.myjetbrains.com/youtrack/issue/RH-30204 // Part of fixing crash reading corrupt file sem2observe copy.3dm rc = false; m_textures.Remove(); } else if ( trc > 1 ) m_textures.Remove(); } } if ( !file.EndRead3dmChunk() ) rc = false; } if ( rc && minor_version >= 1 ) { ON_wString obsolete_flamingo_library; rc = file.ReadString(obsolete_flamingo_library); if ( !rc ) break; if ( minor_version >= 2 ) { // version 1.2 field (20061113) rc = file.ReadArray(m_material_channel); if (!rc) break; if ( minor_version >= 3 ) { // version 1.3 fields (20100917*) rc = file.ReadBool(&m_bShareable); if (!rc) break; rc = file.ReadBool(&m_bDisableLighting); if (!rc) break; if ( minor_version >= 4 ) { rc = file.ReadBool(&m_bFresnelReflections); if (!rc) break; rc = file.ReadDouble(&m_reflection_glossiness); if (!rc) break; rc = file.ReadDouble(&m_refraction_glossiness); if (!rc) break; rc = file.ReadDouble(&m_fresnel_index_of_refraction); if (!rc) break; if (minor_version >= 5) { // version 1.5 field 6.0.2014-05-16 rc = file.ReadUuid(m_rdk_material_instance_id); if (!rc) break; } if (minor_version >= 6) { // version 1.6 field 6.0.2014-07-11 rc = file.ReadBool(&m_bUseDiffuseTextureAlphaForObjectTransparencyTexture); if (!rc) break; } } } } } break; } if (!file.EndRead3dmChunk() ) rc = false; } } } return rc; } bool ON_Material::Internal_WriteV3( ON_BinaryArchive& file ) const { int i; // V2 and V3 file format bool rc = file.Write3dmChunkVersion(1,1); if ( rc ) rc = file.WriteColor( m_ambient ); if ( rc ) rc = file.WriteColor( m_diffuse ); if ( rc ) rc = file.WriteColor( m_emission ); if ( rc ) rc = file.WriteColor( m_specular ); if ( rc ) rc = file.WriteDouble( Shine() ); if (rc) rc = file.WriteDouble(m_transparency); if (rc) rc = file.WriteChar((unsigned char)1); // OBSOLETE // m_casts_shadows if (rc) rc = file.WriteChar((unsigned char)1); // OBSOLETE // m_shows_shadows if (rc) rc = file.WriteChar((unsigned char)0); // OBSOLETE // m_wire_mode if (rc) rc = file.WriteChar((unsigned char)2); // OBSOLETE // m_wire_density if (rc) rc = file.WriteColor(ON_Color(0, 0, 0)); // OBSOLETE // m_wire_color if (rc) { // OBSOLETE - line style info never used short s = 0; if (rc) rc = file.WriteShort(s); if (rc) rc = file.WriteShort(s); if (rc) rc = file.WriteDouble(0.0); if (rc) rc = file.WriteDouble(1.0); } ON_wString filename; int j = 0; i = FindTexture(nullptr, ON_Texture::TYPE::bitmap_texture); if (i >= 0) { const ON_Texture& tmap = m_textures[i]; const ON_wString tmap_filename = tmap.m_image_file_reference.FullPath(); if (tmap_filename.Length() > 0) { filename = tmap_filename; j = (ON_Texture::MODE::decal_texture == tmap.m_mode) ? 2 : 1; } } // OBSOLETE // if ( rc ) rc = file.WriteString( TextureBitmapFileName() ); // OBSOLETE // i = TextureMode(); // OBSOLETE // if ( rc ) rc = file.WriteInt( i ); // OBSOLETE // if ( rc ) rc = file.WriteInt( m_texture_bitmap_index ); if (rc) rc = file.WriteString(filename); if (rc) rc = file.WriteInt(j); if (rc) rc = file.WriteInt(0); filename.Destroy(); j = 0; double bump_scale = 1.0; i = FindTexture(nullptr, ON_Texture::TYPE::bump_texture); if (i >= 0) { const ON_Texture& tmap = m_textures[i]; const ON_wString tmap_filename = tmap.m_image_file_reference.FullPath(); if (tmap_filename.Length() > 0) { filename = tmap_filename; j = (ON_Texture::MODE::decal_texture == tmap.m_mode) ? 2 : 1; bump_scale = tmap.m_bump_scale[1]; } } // OBSOLETE //if ( rc ) rc = file.WriteString( BumpBitmapFileName() ); // OBSOLETE //i = BumpMode(); // OBSOLETE //if ( rc ) rc = file.WriteInt( i ); // OBSOLETE //if ( rc ) rc = file.WriteInt( m_bump_bitmap_index ); // OBSOLETE //if ( rc ) rc = file.WriteDouble( m_bump_scale ); if (rc) rc = file.WriteString(filename); if (rc) rc = file.WriteInt(j); if (rc) rc = file.WriteInt(0); if (rc) rc = file.WriteDouble(bump_scale); filename.Destroy(); j = 0; i = FindTexture(nullptr, ON_Texture::TYPE::emap_texture); if (i >= 0) { const ON_Texture& tmap = m_textures[i]; const ON_wString tmap_filename = tmap.m_image_file_reference.FullPath(); if (tmap_filename.Length() > 0) { filename = tmap_filename; j = (ON_Texture::MODE::decal_texture == tmap.m_mode) ? 2 : 1; } } // OBSOLETE //if ( rc ) rc = file.WriteString( EmapBitmapFileName() ); // OBSOLETE //i = EmapMode(); // OBSOLETE //if ( rc ) rc = file.WriteInt( i ); // OBSOLETE //if ( rc ) rc = file.WriteInt( m_emap_bitmap_index ); if (rc) rc = file.WriteString(filename); if (rc) rc = file.WriteInt(j); if (rc) rc = file.WriteInt(0); if (rc) rc = file.Write3dmReferencedComponentIndex(*this); if (rc) rc = file.WriteUuid(m_plugin_id); const ON_wString obsolete_flamingo_library = ON_wString::EmptyString; if (rc) rc = file.WriteString(obsolete_flamingo_library); if (rc) rc = file.WriteString(Name()); // 1.1 fields if (rc) rc = file.WriteUuid(Id()); if (rc) rc = file.WriteColor(m_reflection); if (rc) rc = file.WriteColor(m_transparent); if (rc) rc = file.WriteDouble(m_index_of_refraction); return rc; } bool ON_Material::Internal_ReadV3( ON_BinaryArchive& file, int minor_version ) { double shine = 0.0, transparency = 0.0; int i, j; bool rc = true; { // common to all version 1.x formats if ( rc ) rc = file.ReadColor( m_ambient ); if ( rc ) rc = file.ReadColor( m_diffuse ); if ( rc ) rc = file.ReadColor( m_emission ); if ( rc ) rc = file.ReadColor( m_specular ); if ( rc ) rc = file.ReadDouble( &shine ); if ( rc ) SetShine(shine); if ( rc ) rc = file.ReadDouble( &transparency ); if ( rc ) SetTransparency(transparency); unsigned char obsolete_uc; if ( rc ) rc = file.ReadChar( &obsolete_uc ); // m_casts_shadows if ( rc ) rc = file.ReadChar( &obsolete_uc ); // m_shows_shadows if ( rc ) rc = file.ReadChar( &obsolete_uc ); // m_wire_mode if ( rc ) rc = file.ReadChar( &obsolete_uc ); // m_wire_density ON_Color obsolete_color; if ( rc ) rc = file.ReadColor( obsolete_color ); // m_wire_color if (rc) { // OBSOLETE if ( rc ) rc = file.ReadLineStyle( m_wire_style ); short s; double x; if (rc) rc = file.ReadShort(&s); if (rc) rc = file.ReadShort(&s); if (rc) rc = file.ReadDouble(&x); if (rc) rc = file.ReadDouble(&x); } ON_wString str; if ( rc ) rc = file.ReadString( str ); //sTextureBitmapFileName i = 0; j = 0; if ( rc ) rc = file.ReadInt( &i ); // OBSOLETE // if ( rc ) SetTextureMode( ON::TextureMode(i) ); if ( rc ) rc = file.ReadInt( &j );//&m_texture_bitmap_index if ( rc && !str.IsEmpty() ) { ON_Texture& texture = m_textures[AddTexture(static_cast< const wchar_t* >(str),ON_Texture::TYPE::bitmap_texture)]; if ( 2 == i ) { texture.m_mode = ON_Texture::MODE::decal_texture; } else { texture.m_mode = ON_Texture::MODE::modulate_texture; } } if ( rc ) rc = file.ReadString( str ); // sBumpBitmapFileName if ( rc ) rc = file.ReadInt( &i ); // OBSOLETE // if ( rc ) SetBumpMode( ON::TextureMode(i) ); if ( rc ) rc = file.ReadInt( &j );//&m_bump_bitmap_index ); double bump_scale = 0.0; if ( rc ) rc = file.ReadDouble( &bump_scale ); if ( rc && !str.IsEmpty() ) { ON_Texture& texture = m_textures[AddTexture(static_cast< const wchar_t* >(str),ON_Texture::TYPE::bump_texture)]; if ( 2 == i ) { texture.m_mode = ON_Texture::MODE::decal_texture; } else { texture.m_mode = ON_Texture::MODE::modulate_texture; } texture.m_bump_scale.Set(0.0,bump_scale); } if ( rc ) rc = file.ReadString( str ); // sEmapBitmapFileName if ( rc ) rc = file.ReadInt( &i ); // OBSOLETE // if ( rc ) SetEmapMode( ON::TextureMode(i) ); if ( rc ) rc = file.ReadInt( &j ); //&m_emap_bitmap_index; if ( rc && !str.IsEmpty() ) { ON_Texture& texture = m_textures[AddTexture(static_cast< const wchar_t* >(str),ON_Texture::TYPE::emap_texture)]; if ( 2 == i ) { texture.m_mode = ON_Texture::MODE::decal_texture; } else { texture.m_mode = ON_Texture::MODE::modulate_texture; } } int material_index = Index(); if ( rc ) rc = file.ReadInt( &material_index ); if (rc) SetIndex(material_index); if ( rc ) rc = file.ReadUuid( m_plugin_id ); ON_wString obsolete_flamingo_library; if ( rc ) rc = file.ReadString( obsolete_flamingo_library ); ON_wString material_name; if ( rc ) rc = file.ReadString( material_name ); if (rc) SetName(material_name); if ( minor_version >= 1 ) { // 1.1 fields ON_UUID material_id = Id(); if (rc) rc = file.ReadUuid( material_id ); SetId(material_id); if (rc) rc = file.ReadColor( m_reflection); if (rc) rc = file.ReadColor( m_transparent); if (rc) rc = file.ReadDouble( &m_index_of_refraction ); } else { // old material needs a valid non-nil id. SetId(); } } return rc; } ON::object_type ON_Material::ObjectType() const { return ON::material_object; } int ON_Material::FindTexture( const wchar_t* filename, ON_Texture::TYPE type, int i0 ) const { int i, count = m_textures.Count(); for (i = ((i0 < 0) ? 0 : (i0+1)); i < count; i++ ) { if ( type != m_textures[i].m_type && type != ON_Texture::TYPE::no_texture_type ) { continue; } const ON_wString texture_file_name = m_textures[i].m_image_file_reference.FullPath(); if ( filename && texture_file_name.ComparePath(filename) ) { continue; } return i; } return -1; } double ON_Material::FresnelReflectionCoefficient( double fresnel_index_of_refraction, const double N[3], const double R[3] ) { double x,y,z,c,g; for(;;) { x = N[0]-R[0]; y = N[1]-R[1]; z = N[2]-R[2]; c = ON_Length3d(x,y,z); if ( !(c > ON_DBL_MIN) ) break; c = (N[0]*x + N[1]*y + N[2]*z)/c; // c = N o (N - R) / |N - R| g = fresnel_index_of_refraction*fresnel_index_of_refraction + c*c - 1.0; g = (g > 0.0) ? sqrt(g) : 0.0; // unsafe (potiential divide by zero, overflow, ...) and inefficient // return ( ((g-c)*(g-c))/(2*(g+c)*(g+c)) ) * (1.0f + ( ((c*(g+c)-1.0)*(c*(g+c)-1.0))/((c*(g+c)+1.0)*(c*(g+c)+1.0)) ) ); x = g+c; if ( !(x != 0.0) ) break; // x is NAN or zero z = (g-c)/x; // z = (g-c)/(g+c) if ( fabs(z) <= 1.0e-154 ) return 0.0; // z*z below will be zero or denormalized x *= c; // x = c*(g+c) y = x + 1.0; // y = c*(g+c) + 1.0 if ( !(y != 0.0) ) break; // y is NAN or zero y = (x - 1.0)/y; // y = (c*(g+c) - 1.0)/(c*(g+c) + 1.0) x = 0.5*z*z*(1.0 + y*y); if ( !(x == x) ) break; // x is NAN if ( !ON_IS_FINITE(x) ) break; // x is infinity return x; // x is } return 1.0; // error occured } double ON_Material::FresnelReflectionCoefficient( ON_3dVector N, ON_3dVector R ) const { return m_bFresnelReflections ? ON_Material::FresnelReflectionCoefficient(m_fresnel_index_of_refraction,&N.x,&R.x) : 1.0; } int ON_Material::FindTexture( ON_UUID texture_id ) const { int i, count = m_textures.Count(); for (i = 0; i < count; i++ ) { if ( !ON_UuidCompare(&texture_id,&m_textures[i].m_texture_id) ) return i; } return -1; } int ON_Material::DeleteTexture(const wchar_t* filename,ON_Texture::TYPE type ) { int deleted_count = 0; int i; if ( !filename && type == ON_Texture::TYPE::no_texture_type ) { deleted_count = m_textures.Count(); m_textures.Destroy(); } else { for ( i = m_textures.Count()-1; i >= 0; i--) { if ( type != ON_Texture::TYPE::no_texture_type && type != m_textures[i].m_type ) continue; if ( filename && m_textures[i].m_image_file_reference.FullPath().ComparePath(filename) ) continue; m_textures.Remove(i); deleted_count++; } } return deleted_count; } int ON_Material::AddTexture( const ON_Texture& tx ) { // has to copy user data int i = FindTexture( static_cast< const wchar_t* >(tx.m_image_file_reference.FullPath()), tx.m_type ); if ( i < 0 ) { i = m_textures.Count(); m_textures.Append(tx); } else { m_textures[i] = tx; } if ( ON_UuidIsNil(m_textures[i].m_texture_id) ) { ON_CreateUuid(m_textures[i].m_texture_id); } return i; } int ON_Material::AddTexture(const wchar_t* filename,ON_Texture::TYPE type) { int ti = FindTexture(nullptr,type); if ( ti < 0 ) { ti = m_textures.Count(); m_textures.AppendNew(); } if (ti >= 0 ) { m_textures[ti].m_image_file_reference.SetFullPath(filename,false); m_textures[ti].m_type = type; m_textures[ti].m_mode = ON_Texture::MODE::modulate_texture; m_textures[ti].m_magfilter = ON_Texture::FILTER::linear_filter; ON_CreateUuid(m_textures[ti].m_texture_id); } return ti; } // Shine values are in range 0.0 to ON_Material::GetMaxShine() double ON_Material::Shine() const { return m_shine; } void ON_Material::SetShine( double shine ) { if (shine == shine) // NANs fail this test { if (shine < 0.0) m_shine = 0.0; else if (shine > ON_Material::MaxShine) m_shine = ON_Material::MaxShine; else m_shine = (float)shine; } } // Transparency values are in range 0.0 = opaque to 1.0 = transparent double ON_Material::Transparency( ) const { return m_transparency; } void ON_Material::SetTransparency( double transparency ) { if ( transparency < 0.0 ) m_transparency = 0.0; else if ( transparency > 1.0) m_transparency = 1.0; else m_transparency = transparency; } // Transparency values are in range 0.0 = opaque to 1.0 = transparent double ON_Material::Reflectivity( ) const { return m_reflectivity; } void ON_Material::SetReflectivity( double reflectivity ) { if ( reflectivity < 0.0 ) m_reflectivity = 0.0; else if ( reflectivity > 1.0) m_reflectivity = 1.0; else m_reflectivity = reflectivity; } const ON_UUID ON_Material::MaterialChannelIdFromIndex( int material_channel_index ) const { for (;;) { if (material_channel_index <= 0) break; const int count = m_material_channel.Count(); if (count <= 0) break; const ON_UuidIndex* a = m_material_channel.Array(); for ( const ON_UuidIndex* a1 = a + count; a < a1; ++a ) { if (material_channel_index == a->m_i) return a->m_id; } break; } return ON_nil_uuid; } int ON_Material::MaterialChannelIndexFromId( ON_UUID material_channel_id ) const { for (;;) { if (ON_nil_uuid == material_channel_id) break; const unsigned count = m_material_channel.UnsignedCount(); if (0 == count) break; const ON_UuidIndex* a = m_material_channel.Array(); for ( const ON_UuidIndex* a1 = a + count; a < a1; ++a) { if (material_channel_id == a->m_id) return a->m_i; } break; } return 0; } int ON_Material::MaterialChannelIndexFromId( ON_UUID material_channel_id, bool bAddIdIfNotPresent ) { for (;;) { if (ON_nil_uuid == material_channel_id) break; int unused_index = 0; const int count = m_material_channel.Count(); if (count > 0) { const ON_UuidIndex* a = m_material_channel.Array(); for (const ON_UuidIndex* a1 = a + count; a < a1; ++a) { if (material_channel_id == a->m_id) return a->m_i; if (a->m_i > unused_index) unused_index = a->m_i; } } if (false == bAddIdIfNotPresent) break; if (count >= ON_Material::MaximumMaterialChannelIndex) break; // some rogue actor filled the m_material_channel[] array. ++unused_index; if ( unused_index <= 0 || unused_index > ON_Material::MaximumMaterialChannelIndex) { // int overflow or too big for a material channel index for (unused_index = 1; unused_index <= count + 1; ++unused_index) { if (ON_nil_uuid == MaterialChannelIdFromIndex(unused_index)) break; } } const ON_UuidIndex ui(material_channel_id, unused_index); m_material_channel.Append(ui); return ui.m_i; } return 0; } bool operator==( const ON_Material& a, const ON_Material& b ) { return (0 == ON_Material::Compare(a,b)); } bool operator!=( const ON_Material& a, const ON_Material& b ) { return (0 != ON_Material::Compare(a,b)); } static int CompareNans(double a, double b) { if (a == a) { if (b == b) { return ( ( a < b ) ? -1 : ((a > b) ? 1 : 0 ) ); } // a is not a NAN, b is a NAN return -1; // a < b - NAN's are last } else if (b == b) { // a is a NAN, b is not a NAN return -1; // b < a - NAN's are last } return 0; // a and b are both NaNs } static int CompareDouble( double a, double b ) { return ( ( a < b ) ? -1 : ((a > b) ? 1 : (a==b ? 0 : CompareNans(a,b)) ) ); } static int CompareXform( const ON_Xform& a, const ON_Xform& b ) { int i,j; const double* da = &a.m_xform[0][0]; const double* db = &b.m_xform[0][0]; i = 16; j = 0; while ( i-- && !j) { j = CompareDouble(*da++,*db++); } return j; } int ON_Texture::Compare(const ON_Texture& a, const ON_Texture& b) { int rc = ON_UuidCompare(&a.m_texture_id, &b.m_texture_id); while (!rc) { if (a.m_mapping_channel_id < b.m_mapping_channel_id) rc = -1; else if (a.m_mapping_channel_id > b.m_mapping_channel_id) rc = 1; if (rc) break; rc = a.m_image_file_reference.FullPath().ComparePath(static_cast< const wchar_t* >(b.m_image_file_reference.FullPath())); if (rc) break; rc = ((int)(a.m_bOn ? 1 : 0)) - ((int)(b.m_bOn ? 1 : 0)); if (rc) break; rc = ((int)a.m_type) - ((int)b.m_type); if (rc) break; rc = ((int)a.m_mode) - ((int)b.m_mode); if (rc) break; rc = ((int)a.m_minfilter) - ((int)b.m_minfilter); if (rc) break; rc = ((int)a.m_magfilter) - ((int)b.m_magfilter); if (rc) break; rc = ((int)a.m_wrapu) - ((int)b.m_wrapu); if (rc) break; rc = ((int)a.m_wrapv) - ((int)b.m_wrapv); if (rc) break; rc = ((int)a.m_wrapw) - ((int)b.m_wrapw); if (rc) break; rc = CompareXform(a.m_uvw, b.m_uvw); if (rc) break; rc = a.m_border_color.Compare(b.m_border_color); if (rc) break; rc = a.m_transparent_color.Compare(b.m_transparent_color); if (rc) break; rc = ON_Interval::Compare(a.m_bump_scale, b.m_bump_scale); if (rc) break; rc = CompareDouble(a.m_blend_constant_A, b.m_blend_constant_A); if (rc) break; rc = CompareDouble(a.m_blend_A0, b.m_blend_A0); if (rc) break; rc = CompareDouble(a.m_blend_A1, b.m_blend_A1); if (rc) break; rc = CompareDouble(a.m_blend_A2, b.m_blend_A2); if (rc) break; rc = CompareDouble(a.m_blend_A3, b.m_blend_A3); if (rc) break; rc = CompareDouble(a.m_blend_RGB0, b.m_blend_RGB0); if (rc) break; rc = CompareDouble(a.m_blend_RGB1, b.m_blend_RGB1); if (rc) break; rc = CompareDouble(a.m_blend_RGB2, b.m_blend_RGB2); if (rc) break; rc = CompareDouble(a.m_blend_RGB3, b.m_blend_RGB3); if (rc) break; rc = ((int)(a.m_bTreatAsLinear ? 1 : 0)) - ((int)(b.m_bTreatAsLinear ? 1 : 0)); if (rc) break; break; } return rc; } int ON_Texture::CompareAppearance(const ON_Texture& a, const ON_Texture& b) { int rc = 0; while (!rc) { if (a.m_mapping_channel_id < b.m_mapping_channel_id) rc = -1; else if (a.m_mapping_channel_id > b.m_mapping_channel_id) rc = 1; if (rc) break; rc = a.m_image_file_reference.FullPath().ComparePath(static_cast< const wchar_t* >(b.m_image_file_reference.FullPath())); if (rc) break; rc = ((int)(a.m_bOn ? 1 : 0)) - ((int)(b.m_bOn ? 1 : 0)); if (rc) break; rc = ((int)a.m_type) - ((int)b.m_type); if (rc) break; rc = ((int)a.m_mode) - ((int)b.m_mode); if (rc) break; rc = ((int)a.m_minfilter) - ((int)b.m_minfilter); if (rc) break; rc = ((int)a.m_magfilter) - ((int)b.m_magfilter); if (rc) break; rc = ((int)a.m_wrapu) - ((int)b.m_wrapu); if (rc) break; rc = ((int)a.m_wrapv) - ((int)b.m_wrapv); if (rc) break; rc = ((int)a.m_wrapw) - ((int)b.m_wrapw); if (rc) break; rc = CompareXform(a.m_uvw, b.m_uvw); if (rc) break; rc = a.m_border_color.Compare(b.m_border_color); if (rc) break; rc = a.m_transparent_color.Compare(b.m_transparent_color); if (rc) break; rc = ON_Interval::Compare(a.m_bump_scale, b.m_bump_scale); if (rc) break; rc = CompareDouble(a.m_blend_constant_A, b.m_blend_constant_A); if (rc) break; rc = CompareDouble(a.m_blend_A0, b.m_blend_A0); if (rc) break; rc = CompareDouble(a.m_blend_A1, b.m_blend_A1); if (rc) break; rc = CompareDouble(a.m_blend_A2, b.m_blend_A2); if (rc) break; rc = CompareDouble(a.m_blend_A3, b.m_blend_A3); if (rc) break; rc = CompareDouble(a.m_blend_RGB0, b.m_blend_RGB0); if (rc) break; rc = CompareDouble(a.m_blend_RGB1, b.m_blend_RGB1); if (rc) break; rc = CompareDouble(a.m_blend_RGB2, b.m_blend_RGB2); if (rc) break; rc = CompareDouble(a.m_blend_RGB3, b.m_blend_RGB3); if (rc) break; rc = ((int)(a.m_bTreatAsLinear ? 1 : 0)) - ((int)(b.m_bTreatAsLinear ? 1 : 0)); if (rc) break; break; } return rc; } int ON_Material::Compare( const ON_Material& a, const ON_Material& b ) { int rc = CompareNameAndIds(a,b); if (0 == rc) rc = CompareAppearance(a,b); return rc; } int ON_Material::CompareNameAndIds( const ON_Material& a, const ON_Material& b ) { // do NOT test index or id const ON_UUID aid = a.Id(); const ON_UUID bid = b.Id(); int rc = ON_UuidCompare( &aid, &bid ); if (rc) return rc; rc = a.Name().CompareOrdinal( static_cast< const wchar_t* >(b.Name()), false ); if (rc) return rc; rc = ON_UuidCompare(&a.m_rdk_material_instance_id, &b.m_rdk_material_instance_id); if (rc) return rc; const int tcount = a.m_textures.Count(); rc = tcount - b.m_textures.Count(); for (int i = 0; i < tcount && 0 == rc; i++) { rc = ON_UuidCompare(a.m_textures[i].m_texture_id, b.m_textures[i].m_texture_id); } return rc; } int ON_Material::CompareColorAttributes( const ON_Material& a, const ON_Material& b ) { const auto a_pbr = a.PhysicallyBased(); const auto b_pbr = b.PhysicallyBased(); if (a_pbr && !b_pbr) return -1; if (!a_pbr && b_pbr) return 1; if (a_pbr && b_pbr) { int rc = a_pbr->BaseColor().Compare(a_pbr->BaseColor()); if (rc) return rc; rc = ((int)a_pbr->BRDF()) - ((int)b_pbr->BRDF()); if (rc) return rc; rc = CompareDouble(a_pbr->Subsurface(), b_pbr->Subsurface()); if (0 != rc) return rc; rc = a_pbr->SubsurfaceScatteringColor().Compare(b_pbr->SubsurfaceScatteringColor()); if (0 != rc) return rc; rc = CompareDouble(a_pbr->SubsurfaceScatteringRadius(), b_pbr->SubsurfaceScatteringRadius()); if (0 != rc) return rc; rc = CompareDouble(a_pbr->Metallic(), b_pbr->Metallic()); if (0 != rc) return rc; rc = CompareDouble(a_pbr->Specular(), b_pbr->Specular()); if (0 != rc) return rc; rc = CompareDouble(a_pbr->SpecularTint(), b_pbr->SpecularTint()); if (0 != rc) return rc; rc = CompareDouble(a_pbr->Roughness(), b_pbr->Roughness()); if (0 != rc) return rc; rc = CompareDouble(a_pbr->Sheen(), b_pbr->Sheen()); if (0 != rc) return rc; rc = CompareDouble(a_pbr->SheenTint(), b_pbr->SheenTint()); if (0 != rc) return rc; rc = CompareDouble(a_pbr->Anisotropic(), b_pbr->Anisotropic()); if (0 != rc) return rc; rc = CompareDouble(a_pbr->AnisotropicRotation(), b_pbr->AnisotropicRotation()); if (0 != rc) return rc; rc = CompareDouble(a_pbr->Clearcoat(), b_pbr->Clearcoat()); if (0 != rc) return rc; rc = CompareDouble(a_pbr->ClearcoatRoughness(), b_pbr->ClearcoatRoughness()); if (0 != rc) return rc; rc = CompareDouble(a_pbr->Opacity(), b_pbr->Opacity()); if (0 != rc) return rc; rc = CompareDouble(a_pbr->OpacityIOR(), b_pbr->OpacityIOR()); if (0 != rc) return rc; rc = CompareDouble(a_pbr->OpacityRoughness(), b_pbr->OpacityRoughness()); if (0 != rc) return rc; rc = CompareDouble(a_pbr->Alpha(), b_pbr->Alpha()); if (0 != rc) return rc; rc = a_pbr->Emission().Compare(b_pbr->Emission()); return rc; } int rc = a.m_ambient.Compare(b.m_ambient); if (rc) return rc; rc = a.m_diffuse.Compare( b.m_diffuse ); if (rc) return rc; rc = a.m_emission.Compare( b.m_emission ); if (rc) return rc; rc = a.m_specular.Compare( b.m_specular ); if (rc) return rc; rc = a.m_reflection.Compare( b.m_reflection ); if (rc) return rc; rc = a.m_transparent.Compare( b.m_transparent ); if (rc) return rc; rc = CompareDouble(a.m_transparency,b.m_transparency); if (rc) return rc; rc = ((int)a.m_bDisableLighting) - ((int)b.m_bDisableLighting); return rc; } int ON_Material::CompareReflectionAttributes(const ON_Material& a, const ON_Material& b) { const auto a_pbr = a.PhysicallyBased(); const auto b_pbr = b.PhysicallyBased(); if (a_pbr && !b_pbr) return -1; if (!a_pbr && b_pbr) return 1; if (a_pbr && b_pbr) { int rc = CompareDouble(a_pbr->Metallic(), b_pbr->Metallic()); if (0 != rc) return rc; rc = CompareDouble(a_pbr->Specular(), b_pbr->Specular()); if (0 != rc) return rc; rc = CompareDouble(a_pbr->SpecularTint(), b_pbr->SpecularTint()); if (0 != rc) return rc; rc = CompareDouble(a_pbr->Roughness(), b_pbr->Roughness()); if (0 != rc) return rc; rc = CompareDouble(a_pbr->Anisotropic(), b_pbr->Anisotropic()); if (0 != rc) return rc; rc = CompareDouble(a_pbr->AnisotropicRotation(), b_pbr->AnisotropicRotation()); if (0 != rc) return rc; rc = CompareDouble(a_pbr->Clearcoat(), b_pbr->Clearcoat()); if (0 != rc) return rc; rc = CompareDouble(a_pbr->ClearcoatRoughness(), b_pbr->ClearcoatRoughness()); return rc; } int rc = a.m_reflection.Compare(b.m_reflection); if (0 != rc) return rc; rc = CompareDouble(a.m_index_of_refraction, b.m_index_of_refraction); if (0 != rc) return rc; rc = CompareDouble(a.m_reflectivity, b.m_reflectivity); if (0 != rc) return rc; rc = CompareDouble(a.m_shine, b.m_shine); if (0 != rc) return rc; rc = (a.m_bFresnelReflections ? 1 : 0) - (b.m_bFresnelReflections ? 1 : 0); if (0 != rc) return rc; if (a.m_bFresnelReflections) { rc = CompareDouble(a.m_fresnel_index_of_refraction, b.m_fresnel_index_of_refraction); if (0 != rc) return rc; } rc = CompareDouble(a.m_reflection_glossiness, b.m_reflection_glossiness); if (0 != rc) return rc; rc = CompareDouble(a.m_refraction_glossiness, b.m_refraction_glossiness); return rc; } int ON_Material::CompareTextureAttributes(const ON_Material& a, const ON_Material& b) { // do NOT test index or id const int tcount = a.m_textures.Count(); int rc = tcount - b.m_textures.Count(); for (int i = 0; i < tcount && 0 == rc; i++) { rc = ON_Texture::Compare(a.m_textures[i], b.m_textures[i]); } if (0 == rc) rc = ((int)a.m_bUseDiffuseTextureAlphaForObjectTransparencyTexture) - ((int)b.m_bUseDiffuseTextureAlphaForObjectTransparencyTexture); return rc; } int ON_Material::CompareTextureAttributesAppearance(const ON_Material& a, const ON_Material& b) { // do NOT test index or id const int tcount = a.m_textures.Count(); int rc = tcount - b.m_textures.Count(); for (int i = 0; i < tcount && 0 == rc; i++) { rc = ON_Texture::CompareAppearance(a.m_textures[i], b.m_textures[i]); } if (0 == rc) rc = ((int)a.m_bUseDiffuseTextureAlphaForObjectTransparencyTexture) - ((int)b.m_bUseDiffuseTextureAlphaForObjectTransparencyTexture); return rc; } int ON_Material::CompareAppearance( const ON_Material& a, const ON_Material& b ) { int rc = CompareColorAttributes(a,b); if ( 0 == rc ) rc = CompareReflectionAttributes(a,b); if ( 0 == rc ) rc = CompareTextureAttributesAppearance(a,b); // This was added because the plugin uuid for a material gets changed to // universal uuid by RhRdkCreateImportedMaterial. In the case of import_vrml // that happens just before it's added to the material table. That breaks the // comparison unless you know that and change your plugin id before checking // to see if the material exists. This is a change that Dale L did for me to work // around that. if (0 == rc) { static const ON_UUID uuidUniversal = { // {99999999-9999-9999-9999-999999999999} 0x99999999, 0x9999, 0x9999, { 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99 } }; if ( 0 != ON_UuidCompare(&uuidUniversal, &a.m_plugin_id) && 0 != ON_UuidCompare(&uuidUniversal, &b.m_plugin_id) ) { rc = ON_UuidCompare(&a.m_plugin_id, &b.m_plugin_id); } } return rc; } ON_UUID ON_Material::RdkMaterialInstanceId() const { return m_rdk_material_instance_id; } /* Description: Set this material's RDK material id. Parameters: rdk_material_id - [in] RDK material id value. Remarks: The RDK material id identifies a material definition managed by the RDK (rendering development kit). Multiple materials in a Rhino or opennurbs model can reference the same RDK material. */ void ON_Material::SetRdkMaterialInstanceId( ON_UUID rdk_material_instance_id ) { m_rdk_material_instance_id = rdk_material_instance_id; } bool ON_Material::RdkMaterialInstanceIdIsNotNil() const { return (!(ON_nil_uuid == m_rdk_material_instance_id)); } bool ON_Material::RdkMaterialInstanceIdIsNil() const { return (ON_nil_uuid == m_rdk_material_instance_id); } // NO ON_OBJECT_IMPLEMENT(ON_RdkMaterialInstanceIdObsoleteUserData, ON_ObsoleteUserData, ...) // because ON_RdkMaterialInstanceIdObsoleteUserData is derived from ON_ObsoleteUserData. // ON_RdkUserData class id value // = ON_RdkMaterialInstanceIdObsoleteUserData::ClassId()->Uuid() // = AFA82772-1525-43dd-A63C-C84AC5806911 // From ON_RdkUserData ON_OBJECT_IMPLEMENT macro // file: src4\opennurbs\opennurbs_xml.cpp // // {AFA82772-1525-43dd-A63C-C84AC5806911} const ON_UUID ON_RdkMaterialInstanceIdObsoleteUserData::m_archive_class_id_ctor = { 0xAFA82772, 0x1525, 0x43dd, { 0xA6, 0x3C, 0xC8, 0x4A, 0xC5, 0x80, 0x69, 0x11 } }; // ON_RdkUserData::m_userdata_uuid value // = ON_RdkUserData::Uuid() // = ON_RdkUserData::m_Uuid // = B63ED079-CF67-416c-800D-22023AE1BE21 // From // file: src4\opennurbs\opennurbs_xml.cpp // // {B63ED079-CF67-416c-800D-22023AE1BE21} const ON_UUID ON_RdkMaterialInstanceIdObsoleteUserData::m_archive_userdata_uuid_ctor = { 0xb63ed079, 0xcf67, 0x416c, { 0x80, 0xd, 0x22, 0x2, 0x3a, 0xe1, 0xbe, 0x21 } }; // ON_RdkUserData::m_application_uuid value // = CRhRdkRhinoPlugIn::RhinoPlugInUuid() // = CRhRdkRhinoPlugIn::m_uuidRhinoPlugIn // = 16592D58-4A2F-401D-BF5E-3B87741C1B1B // From // file: src4/rhino4/Plug-ins/RDK/RDK/RhRcmRhinoPlugIn.cpp // // {16592D58-4A2F-401D-BF5E-3B87741C1B1B} const ON_UUID ON_RdkMaterialInstanceIdObsoleteUserData::m_archive_application_uuid_ctor = { 0x16592D58, 0x4A2F, 0x401D, { 0xBF, 0x5E, 0x3B, 0x87, 0x74, 0x1C, 0x1B, 0x1B } }; const unsigned int ON_RdkMaterialInstanceIdObsoleteUserData::m_userdata_copycount_ctor = 1; const ON_Xform ON_RdkMaterialInstanceIdObsoleteUserData::m_userdata_xform_ctor(ON_Xform::IdentityTransformation); ON_RdkMaterialInstanceIdObsoleteUserData::ON_RdkMaterialInstanceIdObsoleteUserData() : m_rdk_material_instance_id(ON_nil_uuid) { /////////////////////////////////////////////////////////// // ON_UserData field initialization: // The values of m_userdata_uuid and m_application_uuid match those used by ON_RdkUserData. m_userdata_uuid = ON_RdkMaterialInstanceIdObsoleteUserData::m_archive_userdata_uuid_ctor; m_application_uuid = ON_RdkMaterialInstanceIdObsoleteUserData::m_archive_application_uuid_ctor; // Values for the 3dm archive. m_userdata_copycount = ON_RdkMaterialInstanceIdObsoleteUserData::m_userdata_copycount_ctor; m_userdata_xform = ON_RdkMaterialInstanceIdObsoleteUserData::m_userdata_xform_ctor; /////////////////////////////////////////////////////////// // ON_ObsoleteUserData field initialization: m_archive_class_uuid = ON_RdkMaterialInstanceIdObsoleteUserData::m_archive_class_id_ctor; } ON_RdkMaterialInstanceIdObsoleteUserData::~ON_RdkMaterialInstanceIdObsoleteUserData() {} ON_RdkMaterialInstanceIdObsoleteUserData::ON_RdkMaterialInstanceIdObsoleteUserData(const ON_RdkMaterialInstanceIdObsoleteUserData& src) : ON_ObsoleteUserData(src) , m_rdk_material_instance_id(src.m_rdk_material_instance_id) {} ON_RdkMaterialInstanceIdObsoleteUserData& ON_RdkMaterialInstanceIdObsoleteUserData::operator=(const ON_RdkMaterialInstanceIdObsoleteUserData& src) { if (this != &src) { ON_ObsoleteUserData::operator=(src); m_rdk_material_instance_id = src.m_rdk_material_instance_id; } return *this; } bool ON_RdkMaterialInstanceIdObsoleteUserData::GetDescription(ON_wString& description) { description = "RDK material instance id as user data for writing pre V6 file formats."; return true; } bool ON_RdkMaterialInstanceIdObsoleteUserData::WriteToArchive( const class ON_BinaryArchive& archive, const class ON_Object* parent_object ) const { return (ON_UuidIsNotNil(m_rdk_material_instance_id) && archive.Archive3dmVersion() <= 50); } bool ON_RdkMaterialInstanceIdObsoleteUserData::DeleteAfterWrite( const ON_BinaryArchive& archive, const ON_Object* parent_object ) const { // Returning true will cause the ON_BinaryArchive::WriteObject() // plumbing to delete this user data. return true; } bool ON_RdkMaterialInstanceIdObsoleteUserData::DeleteAfterRead( const ON_BinaryArchive& archive, ON_Object* parent_object ) const { // move the RDK material id value from the V5 user data to // the V6 ON_Material.m_rdk_material_instance_id field. ON_Material* mat = ON_Material::Cast(parent_object); if (mat && mat->RdkMaterialInstanceIdIsNil()) mat->SetRdkMaterialInstanceId(m_rdk_material_instance_id); // Returning true will cause the ON_BinaryArchive::ReadObject() // plumbing to delete this user data. return true; } bool ON_RdkMaterialInstanceIdObsoleteUserData::IsRdkMaterialInstanceIdUserData( ON_UUID class_id, ON_UUID userdata_id, ON_UUID app_id, ON_Object* object ) { return ( nullptr != ON_Material::Cast(object) && class_id == ON_RdkMaterialInstanceIdObsoleteUserData::m_archive_class_id_ctor && userdata_id == ON_RdkMaterialInstanceIdObsoleteUserData::m_archive_userdata_uuid_ctor && app_id == ON_RdkMaterialInstanceIdObsoleteUserData::m_archive_application_uuid_ctor ); } static const char* ParsePastWhiteSpace(const char* s) { for (char c = s ? *s++ : 0; 0 != c; c = *s++) { if (32 == c) continue; if (c >= 9 && c <= 13) continue; break; } return s; } static char ToUpper(char c) { if (c >= 'a' && c <= 'z') c -= 0x20; return c; } static const char* ParsePast(const char* token, const char* s) { if (0 == token || token[0] <= 32) return 0; s = ParsePastWhiteSpace(s); if (0 == s || s[0] <= 32) return 0; for (/*empty init*/; ToUpper(*s) == ToUpper(*token); s++, token++) { if (0 == *s) return s; } return 0; } bool ON_RdkMaterialInstanceIdObsoleteUserData::Read( ON_BinaryArchive& archive ) { m_rdk_material_instance_id = ON_nil_uuid; bool rc = false; for (;;) { int version = 0; if (!archive.ReadInt(&version)) break; if (2 != version) break; int s_length = 0; if (!archive.ReadInt(&s_length)) break; if (s_length < 0 || s_length > 1024) break; if (0 == s_length) { rc = true; break; } ON_String str((char)0, (int)s_length); if (str.Length() < s_length) break; const char* s = str.Array(); if (0 == s) break; if (!archive.ReadByte(s_length, (void*)s)) break; // parse the xml the CRhRdkUserData::Write() function // put into V5 files and V6 files written before May 16, 2014. s = ParsePast("<", s); s = ParsePast("xml", s); s = ParsePast(">", s); s = ParsePast("<", s); s = ParsePast("render-content-manager-data", s); s = ParsePast(">", s); s = ParsePast("<", s); s = ParsePast("material", s); s = ParsePast("instance-id", s); s = ParsePast("=", s); s = ParsePast("\"", s); s = ParsePastWhiteSpace(s); s = ON_ParseUuidString(s, &m_rdk_material_instance_id); s = ParsePast("\"", s); rc = (0 != s); break; } return rc; } bool ON_RdkMaterialInstanceIdObsoleteUserData::Write( ON_BinaryArchive& archive ) const { // write xml the CRhRdkUserData::Write() function // put into V5 files and V6 files written before May 16, 2014. char s__rdk_material_instance_id[37]; ON_String s = "\n" "\n" "\n" "\n" ""; const int s_length = (int)s.Length(); const char* s_array = s.Array(); bool rc = archive.WriteInt(2) // "2" was a version number used in V5 files && archive.WriteInt(s_length) && archive.WriteByte(s_length, s_array); return rc; } ON_Color ON_Material::Ambient() const { return m_ambient & 0x00FFFFFF; } ON_Color ON_Material::Diffuse( ) const { return m_diffuse & 0x00FFFFFF; } ON_Color ON_Material::Emission( ) const { return m_emission & 0x00FFFFFF; } ON_Color ON_Material::Specular() const { return m_specular & 0x00FFFFFF; } void ON_Material::SetAmbient( ON_Color c ) { if (m_ambient != c) { m_ambient = c; IncrementContentVersionNumber(); } } void ON_Material::SetDiffuse( ON_Color c ) { if (m_diffuse != c) { m_diffuse = c; IncrementContentVersionNumber(); } } void ON_Material::SetEmission( ON_Color c ) { if (m_emission != c) { m_emission = c; IncrementContentVersionNumber(); } } void ON_Material::SetSpecular( ON_Color c ) { if (m_specular != c) { m_specular = c; IncrementContentVersionNumber(); } } bool ON_Material::Shareable() const { return m_bShareable; } void ON_Material::SetShareable( bool bShareable ) { if (bShareable != m_bShareable) { m_bShareable = bShareable?true:false; IncrementContentVersionNumber(); } } bool ON_Material::DisableLighting() const { return m_bDisableLighting; } void ON_Material::SetDisableLighting( bool bDisableLighting ) { if (bDisableLighting != m_bDisableLighting) { m_bDisableLighting = bDisableLighting?true:false; IncrementContentVersionNumber(); } } //Very simple preview color function that better supports metals and dialectrics. ON_Color ON_Material::PreviewColor(void) const { if (Transparency() > 0.5) { return m_transparent; } if (!FresnelReflections()) { if (Reflectivity() > 0.5) { return m_reflection; } } return Diffuse( ); } bool ON_Material::UseDiffuseTextureAlphaForObjectTransparencyTexture() const { return m_bUseDiffuseTextureAlphaForObjectTransparencyTexture; } void ON_Material::SetUseDiffuseTextureAlphaForObjectTransparencyTexture( bool bUseDiffuseTextureAlphaForObjectTransparencyTexture ) { if (bUseDiffuseTextureAlphaForObjectTransparencyTexture != m_bUseDiffuseTextureAlphaForObjectTransparencyTexture) { m_bUseDiffuseTextureAlphaForObjectTransparencyTexture = bUseDiffuseTextureAlphaForObjectTransparencyTexture?true:false; IncrementContentVersionNumber(); } } bool ON_Material::FresnelReflections() const { return m_bFresnelReflections; } void ON_Material::SetFresnelReflections( bool bFresnelReflections ) { if (bFresnelReflections != m_bFresnelReflections) { m_bFresnelReflections = bFresnelReflections?true:false; IncrementContentVersionNumber(); } } //////////////////////////////////////////////////////////////// // Class ON_Texture //////////////////////////////////////////////////////////////// ON_OBJECT_IMPLEMENT(ON_Texture,ON_Object,"D6FF106D-329B-4f29-97E2-FD282A618020"); bool ON_Texture::IsValid( ON_TextLog* text_log ) const { if ( ON_Texture::TYPE::no_texture_type == m_type ) { if ( text_log ) { text_log->Print("ON_Texture m_type has invalid value.\n"); } return false; } // TODO ... return true; } // overrides virtual ON_Object::Dump void ON_Texture::Dump( ON_TextLog& ) const { } // overrides virtual ON_Object::SizeOf unsigned int ON_Texture::SizeOf() const { unsigned int sz = ON_Object::SizeOf(); sz += sizeof(*this) - sizeof(ON_Object); return sz; } // overrides virtual ON_Object::Write bool ON_Texture::Write( ON_BinaryArchive& binary_archive ) const { const int major_version = 1; const int minor_version = binary_archive.Archive3dmVersion() >= 60 ? (binary_archive.Archive3dmVersion() >= 70 ? 2 : 1) : 0; bool rc = binary_archive.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK,major_version,minor_version); if (rc) { for(;;) { // 1.0 values rc = binary_archive.WriteUuid(m_texture_id); if (!rc) break; rc = binary_archive.WriteInt(m_mapping_channel_id); if (!rc) break; rc = m_image_file_reference.FullPath().IsNotEmpty() ? binary_archive.WriteString(m_image_file_reference.FullPath()) : binary_archive.WriteString(m_image_file_reference.RelativePath()); if (!rc) break; rc = binary_archive.WriteBool(m_bOn); if (!rc) break; rc = binary_archive.WriteInt(static_cast(m_type)); if (!rc) break; rc = binary_archive.WriteInt(static_cast(m_mode)); if (!rc) break; rc = binary_archive.WriteInt(static_cast(m_minfilter)); if (!rc) break; rc = binary_archive.WriteInt(static_cast(m_magfilter)); if (!rc) break; rc = binary_archive.WriteInt(static_cast(m_wrapu)); if (!rc) break; rc = binary_archive.WriteInt(static_cast(m_wrapv)); if (!rc) break; rc = binary_archive.WriteInt(static_cast(m_wrapw)); if (!rc) break; rc = binary_archive.WriteXform(m_uvw); if (!rc) break; rc = binary_archive.WriteColor(m_border_color); if (!rc) break; rc = binary_archive.WriteColor(m_transparent_color); if (!rc) break; rc = binary_archive.WriteUuid(m_transparency_texture_id); if (!rc) break; rc = binary_archive.WriteInterval(m_bump_scale); if (!rc) break; rc = binary_archive.WriteDouble(m_blend_constant_A); if (!rc) break; rc = binary_archive.WriteDouble(m_blend_A0); if (!rc) break; rc = binary_archive.WriteDouble(m_blend_A1); if (!rc) break; rc = binary_archive.WriteDouble(m_blend_A2); if (!rc) break; rc = binary_archive.WriteDouble(m_blend_A3); if (!rc) break; rc = binary_archive.WriteColor(m_blend_constant_RGB); if (!rc) break; rc = binary_archive.WriteDouble(m_blend_RGB0); if (!rc) break; rc = binary_archive.WriteDouble(m_blend_RGB1); if (!rc) break; rc = binary_archive.WriteDouble(m_blend_RGB2); if (!rc) break; rc = binary_archive.WriteDouble(m_blend_RGB3); if (!rc) break; rc = binary_archive.WriteInt(m_blend_order); if (!rc) break; if ( minor_version <= 0 ) break; // version 1.1 added m_image_file_reference rc = m_image_file_reference.Write(true,binary_archive); if (!rc) break; if ( minor_version <= 1 ) break; // version 1.2 added m_bTreatAsLinear rc = binary_archive.WriteBool(m_bTreatAsLinear); if (!rc) break; break; } if ( !binary_archive.EndWrite3dmChunk() ) rc = false; } return rc; } static ON_Texture::MAPPING_CHANNEL Internal_BuiltInMappingChannelFromUnsigned( unsigned int built_in_mapping_channel_as_unsigned, bool bEnableErrorMessage ) { switch (built_in_mapping_channel_as_unsigned) { ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::MAPPING_CHANNEL::tc_channel); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::MAPPING_CHANNEL::default_channel); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::MAPPING_CHANNEL::screen_based_channel); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::MAPPING_CHANNEL::wcs_channel); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::MAPPING_CHANNEL::wcs_box_channel); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::MAPPING_CHANNEL::environment_map_box_channel); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::MAPPING_CHANNEL::environment_map_light_probe_channel); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::MAPPING_CHANNEL::environment_map_spherical_channel); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::MAPPING_CHANNEL::environment_map_cube_map_channel); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::MAPPING_CHANNEL::environment_map_vcross_cube_map_channel); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::MAPPING_CHANNEL::environment_map_hcross_cube_map_channel); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::MAPPING_CHANNEL::environment_map_hemispherical_channel); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::MAPPING_CHANNEL::environment_map_emap_channel); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::MAPPING_CHANNEL::srfp_channel); //ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::MAPPING_CHANNEL::emap_channel); } if (bEnableErrorMessage) { ON_ERROR("Invalid built_in_mapping_channel_as_unsigned value."); } return ON_Texture::MAPPING_CHANNEL::tc_channel; } ON_Texture::MAPPING_CHANNEL ON_Texture::BuiltInMappingChannelFromUnsigned( unsigned int built_in_mapping_channel_as_unsigned ) { return Internal_BuiltInMappingChannelFromUnsigned( built_in_mapping_channel_as_unsigned, true ); } const ON_SHA1_Hash ON_Texture::ContentHash() const { ON_SHA1 sha1; sha1.AccumulateUnsigned32(m_mapping_channel_id); sha1.AccumulateSubHash(m_image_file_reference.FullPathHash()); sha1.AccumulateBool(m_bOn); sha1.AccumulateUnsigned32(static_cast(m_type)); sha1.AccumulateUnsigned32(static_cast(m_mode)); sha1.AccumulateUnsigned32(static_cast(m_minfilter)); sha1.AccumulateUnsigned32(static_cast(m_magfilter)); sha1.AccumulateUnsigned32(static_cast(m_wrapu)); sha1.AccumulateUnsigned32(static_cast(m_wrapv)); sha1.AccumulateUnsigned32(static_cast(m_wrapw)); sha1.AccumulateTransformation(m_uvw); sha1.AccumulateUnsigned32(m_border_color); sha1.AccumulateUnsigned32(m_transparent_color); sha1.AccumulateId(m_transparency_texture_id); sha1.AccumulateDouble(m_bump_scale.m_t[0]); sha1.AccumulateDouble(m_bump_scale.m_t[1]); sha1.AccumulateDouble(m_blend_constant_A); sha1.AccumulateDouble(m_blend_A0); sha1.AccumulateDouble(m_blend_A1); sha1.AccumulateDouble(m_blend_A2); sha1.AccumulateDouble(m_blend_A3); sha1.AccumulateDouble(m_blend_constant_RGB); sha1.AccumulateDouble(m_blend_RGB0); sha1.AccumulateDouble(m_blend_RGB1); sha1.AccumulateDouble(m_blend_RGB2); sha1.AccumulateDouble(m_blend_RGB3); sha1.AccumulateInteger32(m_blend_order); sha1.AccumulateBool(m_bTreatAsLinear); return sha1.Hash(); } bool ON_Texture::IsBuiltInMappingChannel( unsigned int mapping_channel_id ) { ON_Texture::MAPPING_CHANNEL mc = Internal_BuiltInMappingChannelFromUnsigned(mapping_channel_id,false); return (static_cast(mc) == mapping_channel_id); } void ON_Texture::SetBuiltInMappingChannel( ON_Texture::MAPPING_CHANNEL built_in_mapping_channel_as_unsigned ) { SetMappingChannel( static_cast(built_in_mapping_channel_as_unsigned) ); } void ON_Texture::SetMappingChannel( unsigned int mapping_channel_id ) { 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_3dPoint ON_Texture::WcsBoxMapping(const ON_3dPoint& pt, const ON_3dVector& n) { // This code is moved here from CRhRdkTexture::WcsBoxMapping int side0 = 0; const ON_3dPoint& rst(pt); // set side0 = side closest to the point int side1 = (std::abs(rst.x) >= std::abs(rst.y)) ? 0 : 1; if (std::abs(rst.z) > std::abs(((double*)&rst.x)[side1])) side1 = 2; double t1 = (&rst.x)[side1]; if (t1 < 0.0) side0 = 2 * side1 + 1; else side0 = 2 * side1 + 2; side1 = (std::abs(n.x) >= std::abs(n.y)) ? 0 : 1; if (std::abs(n.z) > std::abs((&n.x)[side1])) { side1 = 2; } t1 = n[side1]; if (0.0 != t1) { if (t1 < 0.0) side0 = 2 * side1 + 1; else if (t1 > 0.0) side0 = 2 * side1 + 2; } // side flag // 1 = left side (x=-1) // 2 = right side (x=+1) // 3 = back side (y=-1) // 4 = front side (y=+1) // 5 = bottom side (z=-1) // 6 = top side (z=+1) ON_3dPoint v; switch (side0) { case 1: v.x = -pt.y; v.y = pt.z; v.z = pt.x; break; case 2: v.x = pt.y; v.y = pt.z; v.z = pt.x; break; case 3: v.x = pt.x; v.y = pt.z; v.z = pt.y; break; case 4: v.x = -pt.x; v.y = pt.z; v.z = pt.y; break; case 5: v.x = -pt.x; v.y = pt.y; v.z = pt.z; break; case 6: default: v.x = pt.x; v.y = pt.y; v.z = pt.z; break; } return v; } ON_Texture::TYPE ON_Texture::TypeFromUnsigned(unsigned int type_as_unsigned) { switch (type_as_unsigned) { ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::TYPE::no_texture_type); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::TYPE::diffuse_texture); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::TYPE::bump_texture); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::TYPE::emap_texture); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::TYPE::opacity_texture); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::TYPE::pbr_subsurface_texture); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::TYPE::pbr_subsurface_scattering_texture); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::TYPE::pbr_subsurface_scattering_radius_texture); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::TYPE::pbr_metallic_texture); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::TYPE::pbr_specular_texture); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::TYPE::pbr_specular_tint_texture); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::TYPE::pbr_roughness_texture); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::TYPE::pbr_anisotropic_texture); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::TYPE::pbr_anisotropic_rotation_texture); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::TYPE::pbr_sheen_texture); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::TYPE::pbr_sheen_tint_texture); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::TYPE::pbr_clearcoat_texture); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::TYPE::pbr_clearcoat_roughness_texture); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::TYPE::pbr_clearcoat_bump_texture); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::TYPE::pbr_opacity_ior_texture); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::TYPE::pbr_opacity_roughness_texture); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::TYPE::pbr_emission_texture); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::TYPE::pbr_ambient_occlusion_texture); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::TYPE::pbr_displacement_texture); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::TYPE::pbr_alpha_texture); } ON_ERROR("Invalid type_as_unsigned value."); return ON_Texture::TYPE::no_texture_type; } ON_Texture::MODE ON_Texture::ModeFromUnsigned( unsigned int mode_as_unsigned ) { switch(mode_as_unsigned) { ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::MODE::no_texture_mode); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::MODE::modulate_texture); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::MODE::decal_texture); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::MODE::blend_texture); } ON_ERROR("Invalid mode_as_unsigned value."); return ON_Texture::MODE::no_texture_mode; } ON_Texture::FILTER ON_Texture::FilterFromUnsigned( unsigned int filter_as_unsigned ) { switch(filter_as_unsigned) { ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::FILTER::nearest_filter); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::FILTER::linear_filter); } ON_ERROR("Invalid filter_as_unsigned value."); return ON_Texture::FILTER::linear_filter; } ON_Texture::WRAP ON_Texture::WrapFromUnsigned( unsigned int wrap_as_unsigned ) { switch(wrap_as_unsigned) { ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::WRAP::repeat_wrap); ON_ENUM_FROM_UNSIGNED_CASE(ON_Texture::WRAP::clamp_wrap); } ON_ERROR("Invalid wrap_as_unsigned value."); return ON_Texture::WRAP::repeat_wrap; } void ON_Texture::SetRepeat(const ON_2dVector& repeat) { ON_3dVector offset, rotation, r; m_uvw.DecomposeTextureMapping(offset, r, rotation); m_uvw = ON_Xform::TextureMapping(offset, ON_3dVector(repeat.x, repeat.y, 0.0), rotation); } ON_2dVector ON_Texture::Repeat() const { ON_3dVector offset, rotation, repeat; m_uvw.DecomposeTextureMapping(offset, repeat, rotation); return ON_2dVector(repeat.x, repeat.y); } void ON_Texture::SetOffset(const ON_2dVector& offset) { ON_3dVector o, rotation, repeat; m_uvw.DecomposeTextureMapping(o, repeat, rotation); m_uvw = ON_Xform::TextureMapping(ON_3dVector(offset.x, offset.y, 0.0), repeat, rotation); } ON_2dVector ON_Texture::Offset() const { ON_3dVector offset, rotation, repeat; m_uvw.DecomposeTextureMapping(offset, repeat, rotation); return ON_2dVector(offset.x, offset.y); } void ON_Texture::SetRotation(double rotation) { ON_3dVector offset, r, repeat; m_uvw.DecomposeTextureMapping(offset, repeat, r); m_uvw = ON_Xform::TextureMapping(offset, repeat, ON_3dPoint(0.0, 0.0, rotation * ON_DEGREES_TO_RADIANS)); } double ON_Texture::Rotation() const { ON_3dVector offset, rotation, repeat; m_uvw.DecomposeTextureMapping(offset, repeat, rotation); return rotation.z * ON_RADIANS_TO_DEGREES; } bool ON_Texture::Read( ON_BinaryArchive& binary_archive ) { *this = ON_Texture::Default; int major_version = 0; int minor_version = 0; bool rc = binary_archive.BeginRead3dmChunk(TCODE_ANONYMOUS_CHUNK,&major_version,&minor_version); if (rc) { if ( 1 != major_version ) { rc = false; } else { unsigned int i; for(;;) { // 1.0 values rc = binary_archive.ReadUuid( m_texture_id ); if (!rc) break; rc = binary_archive.ReadInt( &m_mapping_channel_id ); if (!rc) break; ON_wString filename; rc = binary_archive.ReadString(filename); if (!rc) break; if (ON_FileSystemPath::IsRelativePath(filename)) m_image_file_reference.SetRelativePath(filename); else m_image_file_reference.SetFullPath(filename,false); rc = binary_archive.ReadBool(&m_bOn); if (!rc) break; rc = binary_archive.ReadInt(&i); if (!rc) break; m_type = ON_Texture::TypeFromUnsigned(i); rc = binary_archive.ReadInt(&i); if (!rc) break; m_mode = ON_Texture::ModeFromUnsigned(i); rc = binary_archive.ReadInt(&i); if (!rc) break; m_minfilter = ON_Texture::FilterFromUnsigned(i); rc = binary_archive.ReadInt(&i); if (!rc) break; m_magfilter = ON_Texture::FilterFromUnsigned(i); rc = binary_archive.ReadInt(&i); if (!rc) break; m_wrapu = ON_Texture::WrapFromUnsigned(i); rc = binary_archive.ReadInt(&i); if (!rc) break; m_wrapv = ON_Texture::WrapFromUnsigned(i); rc = binary_archive.ReadInt(&i); if (!rc) break; m_wrapw = ON_Texture::WrapFromUnsigned(i); rc = binary_archive.ReadXform(m_uvw); if (!rc) break; rc = binary_archive.ReadColor(m_border_color); if (!rc) break; rc = binary_archive.ReadColor(m_transparent_color); if (!rc) break; rc = binary_archive.ReadUuid(m_transparency_texture_id); if (!rc) break; rc = binary_archive.ReadInterval(m_bump_scale); if (!rc) break; rc = binary_archive.ReadDouble(&m_blend_constant_A); if (!rc) break; rc = binary_archive.ReadDouble(&m_blend_A0); if (!rc) break; rc = binary_archive.ReadDouble(&m_blend_A1); if (!rc) break; rc = binary_archive.ReadDouble(&m_blend_A2); if (!rc) break; rc = binary_archive.ReadDouble(&m_blend_A3); if (!rc) break; rc = binary_archive.ReadColor(m_blend_constant_RGB); if (!rc) break; rc = binary_archive.ReadDouble(&m_blend_RGB0); if (!rc) break; rc = binary_archive.ReadDouble(&m_blend_RGB1); if (!rc) break; rc = binary_archive.ReadDouble(&m_blend_RGB2); if (!rc) break; rc = binary_archive.ReadDouble(&m_blend_RGB3); if (!rc) break; rc = binary_archive.ReadInt(&m_blend_order); if (!rc) break; if ( minor_version <= 0 ) break; rc = m_image_file_reference.Read(binary_archive); if (!rc) break; if (minor_version <= 1) break; rc = binary_archive.ReadBool(&m_bTreatAsLinear); if (!rc) break; break; } } if ( !binary_archive.EndRead3dmChunk() ) rc = false; } return rc; } ON_OBJECT_IMPLEMENT(ON_TextureMapping,ON_ModelComponent,"32EC997A-C3BF-4ae5-AB19-FD572B8AD554"); const ON_TextureMapping* ON_TextureMapping::FromModelComponentRef( const class ON_ModelComponentReference& model_component_reference, const ON_TextureMapping* none_return_value ) { const ON_TextureMapping* p = ON_TextureMapping::Cast(model_component_reference.ModelComponent()); return (nullptr != p) ? p : none_return_value; } ON_TextureMapping::TYPE ON_TextureMapping::TypeFromUnsigned( unsigned int type_as_unsigned ) { switch (type_as_unsigned) { ON_ENUM_FROM_UNSIGNED_CASE(ON_TextureMapping::TYPE::no_mapping); ON_ENUM_FROM_UNSIGNED_CASE(ON_TextureMapping::TYPE::srfp_mapping); ON_ENUM_FROM_UNSIGNED_CASE(ON_TextureMapping::TYPE::plane_mapping); ON_ENUM_FROM_UNSIGNED_CASE(ON_TextureMapping::TYPE::cylinder_mapping); ON_ENUM_FROM_UNSIGNED_CASE(ON_TextureMapping::TYPE::sphere_mapping); ON_ENUM_FROM_UNSIGNED_CASE(ON_TextureMapping::TYPE::box_mapping); ON_ENUM_FROM_UNSIGNED_CASE(ON_TextureMapping::TYPE::mesh_mapping_primitive); ON_ENUM_FROM_UNSIGNED_CASE(ON_TextureMapping::TYPE::srf_mapping_primitive); ON_ENUM_FROM_UNSIGNED_CASE(ON_TextureMapping::TYPE::brep_mapping_primitive); ON_ENUM_FROM_UNSIGNED_CASE(ON_TextureMapping::TYPE::ocs_mapping); ON_ENUM_FROM_UNSIGNED_CASE(ON_TextureMapping::TYPE::false_colors); } ON_ERROR("Invalid type_as_unsigned value."); return ON_TextureMapping::TYPE::no_mapping; } const ON_wString ON_TextureMapping::TypeToString(ON_TextureMapping::TYPE texture_mapping_type) { switch (texture_mapping_type) { ON_ENUM_TO_WIDE_STRING_CASE(ON_TextureMapping::TYPE::no_mapping); ON_ENUM_TO_WIDE_STRING_CASE(ON_TextureMapping::TYPE::srfp_mapping); ON_ENUM_TO_WIDE_STRING_CASE(ON_TextureMapping::TYPE::plane_mapping); ON_ENUM_TO_WIDE_STRING_CASE(ON_TextureMapping::TYPE::cylinder_mapping); ON_ENUM_TO_WIDE_STRING_CASE(ON_TextureMapping::TYPE::sphere_mapping); ON_ENUM_TO_WIDE_STRING_CASE(ON_TextureMapping::TYPE::box_mapping); ON_ENUM_TO_WIDE_STRING_CASE(ON_TextureMapping::TYPE::mesh_mapping_primitive); ON_ENUM_TO_WIDE_STRING_CASE(ON_TextureMapping::TYPE::srf_mapping_primitive); ON_ENUM_TO_WIDE_STRING_CASE(ON_TextureMapping::TYPE::brep_mapping_primitive); ON_ENUM_TO_WIDE_STRING_CASE(ON_TextureMapping::TYPE::ocs_mapping); ON_ENUM_TO_WIDE_STRING_CASE(ON_TextureMapping::TYPE::false_colors); ON_ENUM_TO_WIDE_STRING_CASE(ON_TextureMapping::TYPE::wcs_projection); ON_ENUM_TO_WIDE_STRING_CASE(ON_TextureMapping::TYPE::wcsbox_projection); } ON_ERROR("Invalid texture_mapping_type value."); return ON_wString::EmptyString; } ON_TextureMapping::PROJECTION ON_TextureMapping::ProjectionFromUnsigned( unsigned int projection_as_unsigned ) { switch (projection_as_unsigned) { ON_ENUM_FROM_UNSIGNED_CASE(ON_TextureMapping::PROJECTION::no_projection); ON_ENUM_FROM_UNSIGNED_CASE(ON_TextureMapping::PROJECTION::clspt_projection); ON_ENUM_FROM_UNSIGNED_CASE(ON_TextureMapping::PROJECTION::ray_projection); } ON_ERROR("Invalid projection_as_unsigned value."); return ON_TextureMapping::PROJECTION::no_projection; } const ON_wString ON_TextureMapping::ProjectionToString(ON_TextureMapping::PROJECTION texture_mapping_projection) { switch (texture_mapping_projection) { ON_ENUM_TO_WIDE_STRING_CASE(ON_TextureMapping::PROJECTION::no_projection); ON_ENUM_TO_WIDE_STRING_CASE(ON_TextureMapping::PROJECTION::clspt_projection); ON_ENUM_TO_WIDE_STRING_CASE(ON_TextureMapping::PROJECTION::ray_projection); } ON_ERROR("Invalid texture_mapping_projection value."); return ON_wString::EmptyString; } ON_TextureMapping::TEXTURE_SPACE ON_TextureMapping::TextureSpaceFromUnsigned( unsigned int texture_space_as_unsigned ) { switch (texture_space_as_unsigned) { ON_ENUM_FROM_UNSIGNED_CASE(ON_TextureMapping::TEXTURE_SPACE::single); ON_ENUM_FROM_UNSIGNED_CASE(ON_TextureMapping::TEXTURE_SPACE::divided); } ON_ERROR("Invalid texture_space_as_unsigned value."); return ON_TextureMapping::TEXTURE_SPACE::single; } const ON_wString ON_TextureMapping::SpaceToString(ON_TextureMapping::TEXTURE_SPACE texture_mapping_space) { switch (texture_mapping_space) { ON_ENUM_TO_WIDE_STRING_CASE(ON_TextureMapping::TEXTURE_SPACE::single); ON_ENUM_TO_WIDE_STRING_CASE(ON_TextureMapping::TEXTURE_SPACE::divided); } ON_ERROR("Invalid texture_mapping_space value."); return ON_wString::EmptyString; } ON_TextureMapping::ON_TextureMapping() ON_NOEXCEPT : ON_ModelComponent(ON_ModelComponent::Type::TextureMapping) {} ON_TextureMapping::~ON_TextureMapping() { Internal_Destroy(); } ON_TextureMapping::ON_TextureMapping( const ON_TextureMapping& src) : ON_ModelComponent(ON_ModelComponent::Type::TextureMapping,src) { Internal_CopyFrom(src); } ON_TextureMapping& ON_TextureMapping::operator=(const ON_TextureMapping& src) { if ( this != &src ) { Internal_Destroy(); ON_ModelComponent::operator=(src); Internal_CopyFrom(src); } return *this; } void ON_TextureMapping::Internal_Destroy() { PurgeUserData(); m_mapping_primitive.reset(); } void ON_TextureMapping::Internal_CopyFrom(const ON_TextureMapping& src) { m_type = src.m_type; m_projection = src.m_projection; m_bCapped = src.m_bCapped; m_texture_space = src.m_texture_space; m_Pxyz = src.m_Pxyz; m_Nxyz = src.m_Nxyz; m_uvw = src.m_uvw; m_mapping_primitive = src.m_mapping_primitive; } bool ON_TextureMapping::IsValid( ON_TextLog* text_log ) const { if ( m_type != ON_TextureMapping::TypeFromUnsigned( static_cast(m_type) ) ) { if ( text_log ) { text_log->Print("ON_TextureMapping m_type = %d is not a valid value.\n",m_type); } return false; } if ( m_projection != ON_TextureMapping::ProjectionFromUnsigned( static_cast(m_projection) ) ) { if ( text_log ) { text_log->Print("ON_TextureMapping m_projection = %d is not a valid value.\n",m_projection); } return false; } if (m_texture_space != ON_TextureMapping::TextureSpaceFromUnsigned( static_cast(m_texture_space))) { if (text_log) { text_log->Print("ON_TextureMapping m_texture_space = %d is not a valid value.\n",m_texture_space); } return false; } return true; } void ON_TextureMapping::Dump( ON_TextLog& text_log ) const { ON_ModelComponent::Dump(text_log); text_log.PushIndent(); const ON_wString type(ON_TextureMapping::TypeToString(m_type)); text_log.Print("m_type = %ls\n",static_cast(type)); const ON_wString projection(ON_TextureMapping::ProjectionToString(m_projection)); text_log.Print("m_projection = %ls\n",static_cast(projection)); const ON_wString texture_space(ON_TextureMapping::SpaceToString(m_texture_space)); text_log.Print("m_texture_space = %ls\n",static_cast(texture_space)); text_log.Print("m_Pxyz =\n"); text_log.PushIndent(); text_log.Print(m_Pxyz); text_log.PopIndent(); text_log.Print("m_Nxyz =\n"); text_log.PushIndent(); text_log.Print(m_Nxyz); text_log.PopIndent(); text_log.Print("m_uvw =\n"); text_log.PushIndent(); text_log.Print(m_uvw); text_log.PopIndent(); text_log.PopIndent(); } unsigned int ON_TextureMapping::SizeOf() const { unsigned int sz = ON_Object::SizeOf(); sz = sizeof(*this) - sizeof(ON_Object); return sz; } ON_Xform ON_Texture::GetPictureShrinkSurfaceTransformation( const class ON_Brep* original, const class ON_Brep* shrunk, const ON_Xform* error_return ) { const class ON_Surface* original_srf = (nullptr != original && 1 == original->m_F.Count()) ? original->m_F[0].SurfaceOf() : nullptr; const class ON_Surface* shrunk_srf = (nullptr != shrunk && 1 == shrunk->m_F.Count()) ? shrunk->m_F[0].SurfaceOf() : nullptr; return ON_Texture::GetPictureShrinkSurfaceTransformation( original_srf, shrunk_srf, error_return ); } ON_Xform ON_Texture::GetPictureShrinkSurfaceTransformation( const class ON_Surface* original, const class ON_Surface* shrunk, const ON_Xform* error_return ) { ON_Interval original_udomain; ON_Interval original_vdomain; ON_Interval shrunk_udomain; ON_Interval shrunk_vdomain; if (nullptr != original) { original_udomain = original->Domain(0); original_vdomain = original->Domain(1); } if (nullptr != shrunk) { shrunk_udomain = shrunk->Domain(0); shrunk_vdomain = shrunk->Domain(1); } return ON_Texture::GetPictureShrinkSurfaceTransformation( original_udomain, original_vdomain, shrunk_udomain, shrunk_vdomain, error_return ); } ON_Xform ON_Texture::GetPictureShrinkSurfaceTransformation( const class ON_Interval& original_udomain, const class ON_Interval& original_vdomain, const class ON_Interval& shrunk_udomain, const class ON_Interval& shrunk_vdomain, const ON_Xform* error_return ) { if (nullptr == error_return) error_return = &ON_Xform::Nan; if (false == original_udomain.IsIncreasing()) return *error_return; if (false == original_vdomain.IsIncreasing()) return *error_return; if (false == shrunk_udomain.IsIncreasing()) return *error_return; if (false == shrunk_vdomain.IsIncreasing()) return *error_return; if (false == original_udomain.Includes(shrunk_udomain, false)) return *error_return; if (false == original_vdomain.Includes(shrunk_vdomain, false)) return *error_return; if (false == original_udomain.Includes(shrunk_udomain, true) && false == original_vdomain.Includes(shrunk_vdomain, true)) return *error_return; const ON_3dPoint p0(original_udomain.NormalizedParameterAt(shrunk_udomain[0]), original_vdomain.NormalizedParameterAt(shrunk_vdomain[0]),0.0); const ON_3dPoint p1(original_udomain.NormalizedParameterAt(shrunk_udomain[1]), original_vdomain.NormalizedParameterAt(shrunk_vdomain[1]),0.0); if (!(0.0 <= p0.x && p0.x < p1.x && p1.x <= 1.0)) return *error_return; if (!(0.0 <= p0.y && p0.y < p1.y && p1.y <= 1.0)) return *error_return; const double sx = shrunk_udomain.Length() / original_udomain.Length(); if (!(sx > 0.0 && sx <= 1.0)) return *error_return; const double sy = shrunk_vdomain.Length() / original_vdomain.Length(); if (!(sx > 0.0 && sx <= 1.0)) return *error_return; // The new brep has a smaller surface. // Adust the texture transform to use the proper subset of the old picture image texture. ON_Xform x = ON_Xform::TranslationTransformation(p0 - ON_3dPoint::Origin) * ON_Xform::ScaleTransformation(ON_3dPoint::Origin, sx, sy, 1.0); return x; } bool ON_TextureMapping::ReverseTextureCoordinate( int dir ) { bool rc = false; if ( 0 <= dir && dir <= 3 ) { ON_Xform x(ON_Xform::IdentityTransformation); x.m_xform[dir][dir] = -1.0; x.m_xform[dir][3] = 1.0; m_uvw = x*m_uvw; rc = true; } return rc; } bool ON_TextureMapping::SwapTextureCoordinate( int i, int j ) { bool rc = false; if (i!=j && 0 <= i && i <= 3 && 0 <= j && j <= 3) { ON_Xform x(ON_Xform::IdentityTransformation); x.m_xform[i][i] = x.m_xform[j][j] = 0.0; x.m_xform[i][j] = x.m_xform[j][i] = 1.0; m_uvw = x*m_uvw; rc = true; } return rc; } bool ON_TextureMapping::TileTextureCoordinate( int dir, double count, double offset ) { bool rc = false; if ( 0 <= dir && dir <= 3 && 0.0 != count && ON_IsValid(count) && ON_IsValid(offset) ) { ON_Xform x(ON_Xform::IdentityTransformation); x.m_xform[dir][dir] = count; x.m_xform[dir][3] = offset; m_uvw = x*m_uvw; rc = true; } return rc; } bool ON_Texture::ReverseTextureCoordinate( int dir ) { bool rc = false; if ( 0 <= dir && dir <= 3 ) { ON_Xform x(ON_Xform::IdentityTransformation); x.m_xform[dir][dir] = -1.0; x.m_xform[dir][3] = 1.0; m_uvw = x*m_uvw; rc = true; } return rc; } bool ON_Texture::SwapTextureCoordinate( int i, int j ) { bool rc = false; if (i!=j && 0 <= i && i <= 3 && 0 <= j && j <= 3) { ON_Xform x(ON_Xform::IdentityTransformation); x.m_xform[i][i] = x.m_xform[j][j] = 0.0; x.m_xform[i][j] = x.m_xform[j][i] = 1.0; m_uvw = x*m_uvw; rc = true; } return rc; } bool ON_Texture::TileTextureCoordinate( int dir, double count, double offset ) { bool rc = false; if ( 0 <= dir && dir <= 3 && 0.0 != count && ON_IsValid(count) && ON_IsValid(offset) ) { ON_Xform x(ON_Xform::IdentityTransformation); x.m_xform[dir][dir] = count; x.m_xform[dir][3] = offset; m_uvw = x*m_uvw; rc = true; } return rc; } bool ON_Texture::IsTiled( int dir, double* count, double* offset ) const { if ( count ) *count = 1.0; if ( offset ) *offset = 0.0; if ( 0 <= dir && dir <= 3 ) { int row0=-1, row, col; for ( row = 0; row < 3; row++ ) { for ( col = 0; col < 3; col++ ) { if ( col != dir && 0.0 != m_uvw.m_xform[row][col] ) break; } if ( 3 == col ) { if ( -1 == row0 ) { row0 = row; } else return false; } } if ( row0 >= 0 ) { if (count) *count = m_uvw.m_xform[row0][dir]; if ( offset ) *offset = m_uvw.m_xform[row0][3]; return true; } } return false; } static const double on__overflow_tol = 1.0e100; static int BestHitHelper(double t0, double t1) { return ((t0 < 0.0 && t1 > t0) || (0.0 <= t1 && t1 < t0)) ? 1 : 0; } static int IntersectBoxRayHelper(const ON_3dPoint& rst, const ON_3dVector& n, int dir, double* s) { /* returns: 0 = ray parallel to sides 1 = ray hit left side (x=-1) 2 = ray hit right side (x=+1) 3 = ray hit back side (y=-1) 4 = ray hit front side (y=+1) 5 = ray hit bottom side (z=-1) 6 = ray hit top side (z=+1) */ double nx = (&n.x)[dir]; ON_3dPoint Q; double t,t0,t1; // protect against overflow t = fabs(nx)*on__overflow_tol; t0 = (-1.0 - (&rst.x)[dir]); t1 = ( 1.0 - (&rst.x)[dir]); if ( fabs(t0) >= t || fabs(t1) >= t ) { *s = ON_UNSET_VALUE; return 0; } t0 /= nx; Q = rst + t0*n; if ( dir ) { t = Q.x; Q.x = Q[dir]; Q[dir] = t; } if ( fabs(Q.x+1.0) > ON_SQRT_EPSILON || Q.y < -(1.0+ON_SQRT_EPSILON) || Q.y > (1.0+ON_SQRT_EPSILON) || Q.z < -(1.0+ON_SQRT_EPSILON) || Q.z > (1.0+ON_SQRT_EPSILON) ) { // The ray's intersection with the plane missed the // (-1,+1)x(-1,+1) square that is the side of the box. t0 = ON_UNSET_VALUE; } t1 /= nx; Q = rst + t1*n; if ( dir ) { t = Q.x; Q.x = Q[dir]; Q[dir] = t; } if ( fabs(Q.x-1.0) > ON_SQRT_EPSILON || Q.y < -(1.0+ON_SQRT_EPSILON) || Q.y > (1.0+ON_SQRT_EPSILON) || Q.z < -(1.0+ON_SQRT_EPSILON) || Q.z > (1.0+ON_SQRT_EPSILON) ) { // The ray's intersection with the plane missed the // (-1,+1)x(-1,+1) square that is the side of the box. t1 = ON_UNSET_VALUE; if ( ON_UNSET_VALUE == t0 ) { *s = ON_UNSET_VALUE; return 0; } } int rc; if ( ON_UNSET_VALUE == t0 || 1 == BestHitHelper(t0,t1) ) { rc = 2 + 2*dir; *s = t1; } else { rc = 1 + 2*dir; *s = t0; } return rc; } int ON_TextureMapping::EvaluatePlaneMapping( const ON_3dPoint& P, const ON_3dVector& N, ON_3dPoint* T ) const { // The matrix m_Pxyz transforms the world coordinate // "mapping rectangle" into the rectangle // -1 <= r <= 1, -1 <= s <= 1, and (-1 <= t <= 1) ON_3dPoint rst(m_Pxyz*P); if ( ON_TextureMapping::PROJECTION::ray_projection == m_projection ) { ON_3dVector n(m_Nxyz*N); if ( fabs(rst.z) < fabs(n.z)*on__overflow_tol ) { double t = -rst.z/n.z; rst.x = rst.x + t*n.x; rst.y = rst.y + t*n.y; } } // convert -1 <= r <= 1, -1 <= s <= 1 // to normalized texture coordinate rst.x = 0.5*rst.x + 0.5; rst.y = 0.5*rst.y + 0.5; // Apply texture coordinate transformation *T = m_uvw*rst; //See docs - if m_bCapped is false, then planar is truely flat. if (!m_bCapped) T->z = 0.0; return 1; } int ON_TextureMapping::EvaluateSphereMapping( const ON_3dPoint& P, const ON_3dVector& N, ON_3dPoint* T ) const { // The matrix m_Pxyz transforms the world coordinate // "mapping sphere" into the sphere centered at // rst = (0,0,0) with radius 1.0. ON_3dPoint rst(m_Pxyz*P); const double r = ((const ON_3dVector*)(&rst.x))->Length(); double t0, t1; if ( ON_TextureMapping::PROJECTION::ray_projection == m_projection ) { ON_3dVector n(m_Nxyz*N); // Shoot a ray from P in the direction N and see if it // hits the sphere. int rc = ON_SolveQuadraticEquation( (n.x*n.x+n.y*n.y+n.z*n.z), 2.0*(rst.x*n.x+rst.y*n.y+rst.z*n.z), (rst.x*rst.x+rst.y*rst.y+rst.z*rst.z) - 1.0, &t0, &t1 ); if (rc >= 0 ) { if ( 2 != rc && 1 == BestHitHelper(t0,t1) ) { t0 = t1; } rst = rst + t0*n; } } // convert sphere 3d location to longitude, latitude, radius double longitude = (0.0 != rst.y || 0.0 != rst.x) ? atan2(rst.y,rst.x) : 0.0; double latitude = (0.0 != rst.z) ? atan2(rst.z,((const ON_2dVector*)(&rst.x))->Length()) : 0.0; if ( latitude > ON_PI ) latitude -= 2.0*ON_PI; // convert longitude to normalized texture coordinate rst.x = 0.5*longitude/ON_PI; if ( rst.x < -ON_EPSILON ) rst.x += 1.0; else if (rst.x < 0.0) rst.x = 0.0; else if (rst.x > 1.0) rst.x = 1.0; // convert longitude to normalized texture coordinate rst.y = latitude/ON_PI + 0.5; if ( rst.y <= 0.0 ) rst.y = 0.0; else if ( rst.y > 1.0 ) rst.y = 1.0; // radius is already normalized rst.z = r; // apply texture coordinate transformation *T = m_uvw*rst; return 1; } int ON_TextureMapping::EvaluateCylinderMapping( const ON_3dPoint& P, const ON_3dVector& N, ON_3dPoint* T ) const { // The matrix m_Pxyz transforms the world coordinate // "mapping cylinder" into the cylinder centered at // rst = (0,0,0) with radius 1.0. The axis runs // from rst = (0,0,-1) to rst = (0,0,+1). ON_3dPoint rst(m_Pxyz*P); ON_3dPoint Q; const double r = ((const ON_2dVector*)(&rst.x))->Length(); double t, t0, t1; int side0, side1; PROJECTION mapping_proj = m_projection; side0 = 0; if ( ON_TextureMapping::PROJECTION::ray_projection == mapping_proj ) { ON_3dVector n(m_Nxyz*N); t = 0.0; if ( m_bCapped ) { // shoot at caps // The < t check prevents overflow when the // ray is nearly parallel to the cap. t = fabs(n.z)*on__overflow_tol; if ( fabs(1.0+rst.z) < t && fabs(1.0-rst.z) < t ) { side0 = 2; side1 = 3; t0 = (-1.0 - rst.z)/n.z; Q = rst + t0*n; if ( fabs(1.0+Q.z) > ON_SQRT_EPSILON || (Q.x*Q.x + Q.y*Q.y) > 1.0 + 2.0*ON_SQRT_EPSILON + ON_EPSILON ) { // The ray's intersection with the bottom plane missed the // radius 1 disk that is the bottom of the cylinder. side0 = 0; } t1 = ( 1.0 - rst.z)/n.z; Q = rst + t1*n; if ( fabs(1.0-Q.z) > ON_SQRT_EPSILON || (Q.x*Q.x + Q.y*Q.y) > 1.0 + 2.0*ON_SQRT_EPSILON + ON_EPSILON ) { // The ray's intersection with the top plane missed the // radius 1 disk that is the top of the cylinder. side1 = 0; } if ( 0 == side0 || 1 == BestHitHelper(t0,t1) ) { side0 = side1; t = t1; } else { t = t0; } } } // shoot ray at the cylinder wall int rc = ON_SolveQuadraticEquation( (n.x*n.x+n.y*n.y), 2.0*(rst.x*n.x+rst.y*n.y), (rst.x*rst.x+rst.y*rst.y) - 1.0, &t0, &t1 ); if (rc >= 0 ) { if ( 2 != rc && 1 == BestHitHelper(t0,t1) ) { t0 = t1; } if ( 0 == side0 ) { // Either the caps are missing or the ray missed the caps. // The best hit is the cylinder wall. side0 = 1; rst = rst + t0*n; } else if ( 1 != BestHitHelper(t0,t) ) { // The cylinder is capped and the ray hit the cap, // hit the infinite cylinder wall, and the wall // hit is "first". If the ray hits the finite // cylinder wall, the I will use the wall hit. t1 = rst.z + t0*n.z; if ( t1 >= -(1.0+ON_SQRT_EPSILON) && t1 <= 1.0+ON_SQRT_EPSILON ) { // use the hit on the cylinder wall side0 = 1; rst.x = rst.x + t0*n.x; rst.y = rst.y + t0*n.y; rst.x = t1; } } } if ( side0 > 1 ) { // best hit is on a cap rst = rst + t*n; } } if ( m_bCapped && 0 == side0 ) { if ( fabs(rst.z) > 1.0+ON_SQRT_EPSILON ) { if ( fabs(rst.z) > r ) { side0 = (rst.z < 0.0) ? 2 : 3; } } else if ( r <= 1.001 ) { // The point is inside the capped cylinder. // Use normal to dermine which surface to use // for closest point test. ON_3dVector n(m_Nxyz*N); if ( ( fabs(n.z) > fabs(n.x) && fabs(n.z) > fabs(n.y) ) ) { side0 = (n.z < 0.0) ? 2 : 3; } } } if ( 2 == side0 || 3 == side0 ) { // The cylinder is capped and P maps to // the top (1 == side0) or bottom (2 == side0) if ( 2 == side0 ) { // This is the same convention as box mapping. // Put another way, if you change the mapping // between box and cylinder, you get the same // picture on the top and bottom. rst.x = -rst.x; } if ( ON_TextureMapping::TEXTURE_SPACE::divided == m_texture_space ) { if ( r >= 1.0-ON_SQRT_EPSILON ) { rst.x /= (r+ON_SQRT_EPSILON); rst.y /= (r+ON_SQRT_EPSILON); } } else if ( r > 1.0 ) { rst.x /= r; rst.y /= r; } // convert to normalized texture coordinates rst.x = 0.5*rst.x + 0.5; if ( rst.x < 0.0) rst.x = 0.0; else if (rst.x > 1.0) rst.x = 1.0; rst.y = 0.5*rst.y + 0.5; if ( rst.y < 0.0) rst.y = 0.0; else if (rst.y > 1.0) rst.y = 1.0; if ( ON_TextureMapping::TEXTURE_SPACE::divided == m_texture_space ) { // bottom uses 4/6 <= x <= 5/6 region of the texture map. // top uses 5/6 <= x <= 1 region of the texture map. rst.x = (2.0 + side0 + rst.x)/6.0; } } else { // P maps to side of the cylinder. // // convert longitude to normalized texture coordinate t = (0.0 != rst.y || 0.0 != rst.x) ? atan2(rst.y,rst.x) : 0.0; rst.x = 0.5*t/ON_PI; if ( rst.x < -ON_EPSILON ) rst.x += 1.0; else if (rst.x < 0.0 ) rst.x = 0.0; else if (rst.x > 1.0 ) rst.x = 1.0; if ( ON_TextureMapping::TEXTURE_SPACE::divided == m_texture_space ) { // side uses 0 <= x <= 2/3 region of the texture map rst.x *= 2.0; rst.x /= 3.0; } // convert height to normalized texture coordinate rst.y = 0.5*rst.z + 0.5; if ( m_bCapped ) { // clamp height if ( rst.y < 0.0 ) rst.y = 0.0; else if ( rst.y > 1.0 ) rst.y = 1.0; } side0 = 1; } rst.z = r; *T = m_uvw*rst; return side0; } int ON_TextureMapping::EvaluateBoxMapping( const ON_3dPoint& P, const ON_3dVector& N, ON_3dPoint* T ) const { // The matrix m_Pxyz transforms the world coordinate // "mapping cylinder" into the cylinder centered at // rst = (0,0,0) with radius 1.0. The axis runs // from rst = (0,0,-1) to rst = (0,0,+1). ON_3dPoint rst(m_Pxyz*P); ON_3dVector n(m_Nxyz*N); n.Unitize(); int side0, side1; double t0, t1; side0 = 0; t0 = 0.0; // side flag // 1 = left side (x=-1) // 2 = right side (x=+1) // 3 = back side (y=-1) // 4 = front side (y=+1) // 5 = bottom side (z=-1) // 6 = top side (z=+1) if ( ON_TextureMapping::PROJECTION::ray_projection == m_projection ) { if ( m_bCapped ) { // intersect ray with top and bottom side0 = IntersectBoxRayHelper(rst,n,2,&t0); } // intersect ray with front and back side1 = IntersectBoxRayHelper(rst,n,0,&t1); if ( 0 == side0 || 1 == BestHitHelper(t0,t1) ) { side0 = side1; t0 = t1; } // intersect ray with left and right side1 = IntersectBoxRayHelper(rst,n,1,&t1); if ( 0 == side0 || 1 == BestHitHelper(t0,t1) ) { side0 = side1; t0 = t1; } if ( 0 != side0 ) { // ray hit the box rst = rst + t0*n; } } if ( 0 == side0 ) { // set side0 = side closest to the point side1 = (fabs(rst.x) >= fabs(rst.y)) ? 0 : 1; if ( m_bCapped && fabs(rst.z) > fabs(((double*)&rst.x)[side1]) ) side1 = 2; t1 = (&rst.x)[side1]; if ( t1 < 0.0 ) { side0 = 2*side1 + 1; } else { side0 = 2*side1 + 2; } //if ( fabs(t1) <= 1.0+ON_SQRT_EPSILON )... //// The point is inside the box. If the normal //// is not zero, then use it to choose the side //// used for the closest point projection. side1 = ( fabs(n.x) >= fabs(n.y) ) ? 0 : 1; if ( m_bCapped && fabs(n.z) > fabs((&n.x)[side1])) { side1 = 2; } t1 = n[side1]; if ( 0.0 != t1 ) { if ( t1 < 0.0 ) side0 = 2*side1 + 1; else if ( t1 > 0.0 ) side0 = 2*side1 + 2; } } double shift = 0.0; // side flag // 1 = left side (x=-1) // 2 = right side (x=+1) // 3 = back side (y=-1) // 4 = front side (y=+1) // 5 = bottom side (z=-1) // 6 = top side (z=+1) switch(side0) { case 1: // x = -1 rst.x = -rst.y; rst.y = rst.z; shift = 3.0; break; case 2: // x = +1 rst.x = rst.y; rst.y = rst.z; shift = 1.0; break; case 3: // y = -1 rst.y = rst.z; shift = 0.0; break; case 4: // y = +1 rst.x = -rst.x; rst.y = rst.z; shift = 2.0; break; case 5: // z = -1 rst.x = -rst.x; shift = 4.0; break; case 6: // z = +1 shift = 5.0; break; } // normalize texture coordinates rst.x = 0.5*rst.x + 0.5; rst.y = 0.5*rst.y + 0.5; rst.z = 0.0; if( ON_TextureMapping::TEXTURE_SPACE::divided == m_texture_space) { rst.x = (shift + rst.x)/(m_bCapped ? 6.0 : 4.0); } *T = m_uvw*rst; return side0; } static bool ProjectToFaceSpace(const ON_3dPoint & ptV0, const ON_3dPoint & ptV1, const ON_3dPoint & ptV2, const ON_3dPoint & ptP, double & xOut, double & yOut) { const double m00 = (ptV1 - ptV0).LengthSquared(); const double m01 = ON_DotProduct(ptV1 - ptV0, ptV2 - ptV0); const double d0 = ON_DotProduct(ptP - ptV0, ptV1 - ptV0); const double m10 = m01; const double m11 = (ptV2 - ptV0).LengthSquared(); const double d1 = ON_DotProduct(ptP - ptV0, ptV2 - ptV0); double pivot_ratio = 0.0; if (2 == ON_Solve2x2(m00, m01, m10, m11, d0, d1, &xOut, &yOut, &pivot_ratio)) { if (pivot_ratio < 0.01) { const ON_3dPoint ptProjection = ptV0 + xOut * (ptV1 - ptV0) + yOut * (ptV2 - ptV0); ON_3dVector vtOffsetDir = ptP - ptProjection; const double offsetLength = vtOffsetDir.LengthAndUnitize(); if (offsetLength < 1e-12) return true; ON_3dVector vtPlaneNormal = ON_CrossProduct(ptV2 - ptV0, ptV2 - ptV1); if (false == vtPlaneNormal.Unitize()) return false; const double dOffsetDirDotPlaneNormal = ON_DotProduct(vtPlaneNormal, vtOffsetDir); if (-0.99 < dOffsetDirDotPlaneNormal && dOffsetDirDotPlaneNormal < 0.99) return false; } return true; } return false; } int ON_TextureMapping::Evaluate( const ON_3dPoint& P, const ON_3dVector& N, ON_3dPoint* T, const ON_Xform& P_xform, const ON_Xform& N_xform ) const { int rc; ON_3dPoint Q = P_xform*P; if ( ON_TextureMapping::PROJECTION::ray_projection == m_projection ) { // need a transformed normal ON_3dVector V = N_xform*N; V.Unitize(); rc = Evaluate(Q,V,T); } else { // normal is ignored rc = Evaluate(Q,N,T); } return rc; } int ON_TextureMapping::Evaluate( const ON_3dPoint& P, const ON_3dVector& N, ON_3dPoint* T ) const { int rc; switch(m_type) { case ON_TextureMapping::TYPE::srfp_mapping: *T = m_uvw * P; // Do NOT apply m_Pxyz here. rc = 1; break; case ON_TextureMapping::TYPE::sphere_mapping: rc = EvaluateSphereMapping(P,N,T); break; case ON_TextureMapping::TYPE::cylinder_mapping: rc = EvaluateCylinderMapping(P,N,T); break; case ON_TextureMapping::TYPE::box_mapping: rc = EvaluateBoxMapping(P,N,T); break; case ON_TextureMapping::TYPE::mesh_mapping_primitive: rc = 0; break; case ON_TextureMapping::TYPE::srf_mapping_primitive: rc = 0; break; case ON_TextureMapping::TYPE::brep_mapping_primitive: rc = 0; break; case ON_TextureMapping::TYPE::wcs_projection: *T = m_Pxyz * P; rc = 1; break; case ON_TextureMapping::TYPE::wcsbox_projection: *T = ON_Texture::WcsBoxMapping(m_Pxyz * P, m_Nxyz * N); rc = 1; break; default: rc = EvaluatePlaneMapping(P,N,T); break; } return rc; } const ON_Object* ON_TextureMapping::CustomMappingPrimitive(void) const { return m_mapping_primitive.get(); } const std::shared_ptr& ON_TextureMapping::SharedCustomMappingPrimitive(void) const { return m_mapping_primitive; } //Returns a valid mesh if the custom mapping primitive is a mesh. Otherwise nullptr. //Implementation is return ON_Mesh::Cast(CustomMappingPrimitive()); const ON_Mesh* ON_TextureMapping::CustomMappingMeshPrimitive(void) const { return ON_Mesh::Cast(CustomMappingPrimitive()); } //Returns a valid brep if the custom mapping primitive is a brep. Otherwise nullptr. //Implementation is return ON_Brep::Cast(CustomMappingPrimitive()); const ON_Brep* ON_TextureMapping::CustomMappingBrepPrimitive(void) const { return ON_Brep::Cast(CustomMappingPrimitive()); } //Returns a valid surface if the custom mapping primitive is a surface. Otherwise nullptr. //Implementation is return ON_Surface::Cast(CustomMappingPrimitive()); const ON_Surface* ON_TextureMapping::CustomMappingSurfacePrimitive(void) const { return ON_Surface::Cast(CustomMappingPrimitive()); } class MappingCRCCache : public ON_UserData { ON_OBJECT_DECLARE(MappingCRCCache); public: MappingCRCCache(int mapping_crc) { m_application_uuid = ON_opennurbs_id; m_userdata_uuid = ON_CLASS_ID(MappingCRCCache); m_userdata_copycount = 1; m_mapping_crc = mapping_crc; } MappingCRCCache() { m_application_uuid = ON_opennurbs_id; m_userdata_uuid = ON_CLASS_ID(MappingCRCCache); m_userdata_copycount = 1; m_mapping_crc = -1; } MappingCRCCache(const MappingCRCCache& src) : ON_UserData(src) { m_userdata_copycount = src.m_userdata_copycount; m_mapping_crc = src.m_mapping_crc; } static const int current_version; auto& operator=(const MappingCRCCache& src) { ON_UserData::operator = (src); return *this; } #if defined(ON_COMPILER_CLANG) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Winconsistent-missing-override" #endif virtual bool GetDescription(ON_wString& description) { description = L"MappingCRCCache"; return true; } virtual bool Archive() const { return true; } virtual bool Write(ON_BinaryArchive& binary_archive) const { if (binary_archive.WriteInt(current_version)) { if (binary_archive.WriteInt(m_mapping_crc)) { return true; } } return false; } virtual bool Read(ON_BinaryArchive& binary_archive) { int nVersion = 1; if (binary_archive.ReadInt(&nVersion) && current_version == nVersion) { if (binary_archive.ReadInt(&m_mapping_crc)) { return true; } } return false; } #if defined(ON_COMPILER_CLANG) #pragma clang diagnostic pop #endif public: int m_mapping_crc = -1; }; ON_OBJECT_IMPLEMENT(MappingCRCCache, ON_UserData, "5A4971F3-AA73-493C-A385-2F7EB4288989"); const int MappingCRCCache::current_version = 1; static ON__UINT32 MappingCRC(const ON_Object* pMappingPrimitive) { const ON__UINT32 crc32_in = 0x12341234; if (const auto* p = ON_Mesh::Cast(pMappingPrimitive)) { ON__UINT32 crc32 = p->DataCRC(crc32_in); if (p->HasTextureCoordinates()) { // 25 August 2010 Dale Lear // Including m_T[] in crc32 per Jussi and Andy email discussion. // This is probably correct because users will expect the // "picture" on the mesh to be applied to the target in // a visual way. const ON_2fPoint* tex = p->m_T.Array(); crc32 = ON_CRC32(crc32, p->m_T.Count() * sizeof(tex[0]), tex); } // January 7th 2021 Jussi // Added crc of origin to change the mapping crc to tricker re-mapping // of custom mesh mapped objects. This is to make the improvements to // CMeshClosestPointMapper::MatchFaceTC effective on existing models. crc32 = ON_CRC32(crc32, sizeof(ON_3dPoint), &ON_3dPoint::UnsetPoint); return crc32; } if (const auto* p = ON_Brep::Cast(pMappingPrimitive)) { return p->DataCRC(crc32_in); // 25 August 2010 Dale Lear // Should brep's render meshes be included in the crc? // The texture that is being mapped is actually // being applied to the brep by the render mesh's // m_T[] values and some users will want to see // the "picture" on the brep mapped to the // "picture" on the // target. } if (const auto* p = ON_Surface::Cast(pMappingPrimitive)) { return p->DataCRC(crc32_in); } return crc32_in; } void ON_TextureMapping::SetCustomMappingPrimitive(ON_Object* pMappingObject) { if (nullptr != pMappingObject) { const auto mapping_crc = ::MappingCRC(pMappingObject); if (auto* pUD = static_cast(pMappingObject->GetUserData(ON_CLASS_ID(MappingCRCCache)))) { pUD->m_mapping_crc = mapping_crc; } pMappingObject->AttachUserData(new MappingCRCCache(mapping_crc)); } m_mapping_primitive.reset(pMappingObject); } ON__UINT32 ON_TextureMapping::MappingCRC() const { // include any member that can change values returned by Evaluate ON__UINT32 crc32 = 0x12345678; crc32 = ON_CRC32(crc32,sizeof(m_type),&m_type); if ( ON_TextureMapping::TYPE::srfp_mapping != m_type ) { // As of 21 June 2006 m_Pxyz cannot effect ON_TextureMapping::TYPE::srfp_mapping, // so it shouldn't be included in the CRC for srfp_mappings. crc32 = ON_CRC32(crc32,sizeof(m_projection), &m_projection); crc32 = ON_CRC32(crc32,sizeof(m_texture_space), &m_texture_space); crc32 = ON_CRC32(crc32,sizeof(m_bCapped), &m_bCapped); crc32 = ON_CRC32(crc32,sizeof(m_Pxyz), &m_Pxyz); // do not include m_Nxyz here - it won't help and may hurt if (m_mapping_primitive) { const auto* pUD = static_cast(m_mapping_primitive->GetUserData(ON_CLASS_ID(MappingCRCCache))); if (pUD) { crc32 = ON_CRC32(crc32, sizeof(MappingCRCCache::m_mapping_crc), &pUD->m_mapping_crc); } else { //Existing files. const auto mapping_crc = ::MappingCRC(m_mapping_primitive.get()); const_cast(m_mapping_primitive.get())->AttachUserData(new MappingCRCCache(mapping_crc)); crc32 = ON_CRC32(crc32, sizeof(mapping_crc), &mapping_crc); } } } else { // Jussi, Aug 18 2022, RH-69752: Surface mapping uvw transform has changed. // Changing crc makes sure existing models will show the correct mapping. crc32++; } crc32 = ON_CRC32(crc32,sizeof(m_uvw), &m_uvw); return crc32; } bool ON_TextureMapping::RequiresVertexNormals() const { if ( ON_TextureMapping::TYPE::srfp_mapping == m_type ) return false; if(m_projection == ON_TextureMapping::PROJECTION::ray_projection) return true; if(m_type == ON_TextureMapping::TYPE::box_mapping) return true; if(m_type == ON_TextureMapping::TYPE::cylinder_mapping && m_bCapped) return true; if (m_type == ON_TextureMapping::TYPE::wcsbox_projection) return true; return false; } bool ON_TextureMapping::IsPeriodic(void) const { return (m_type == ON_TextureMapping::TYPE::sphere_mapping || m_type == ON_TextureMapping::TYPE::cylinder_mapping); } bool ON_TextureMapping::HasMatchingTextureCoordinates( const ON_Mesh& mesh, const ON_Xform* mesh_xform ) const { bool rc = (mesh.HasTextureCoordinates()) ? HasMatchingTextureCoordinates(mesh.m_Ttag,mesh_xform) : false; return rc; } bool ON_TextureMapping::HasMatchingCachedTextureCoordinates( const ON_Mesh& mesh, const ON_Xform* mesh_xform ) const { bool rc = false; for (int i = 0; i < mesh.m_TC.Count(); i++) { if (mesh.VertexCount() == mesh.m_TC[i].m_T.Count() && HasMatchingTextureCoordinates(mesh.m_TC[i].m_tag, mesh_xform)) { rc = true; break; } } return rc; } bool ON_TextureMapping::HasMatchingTextureCoordinates( const ON_MappingTag& tag, const ON_Xform* mesh_xform ) const { bool rc = false; // DO NOT COMPARE m_mapping_id's in this function. // This function returns true if the tc values // calculated by the mapping will be the same // as the mapping that was used to set the tag. if ( tag.m_mapping_crc == MappingCRC() ) { rc = true; // zero transformations indicate the mapping // values are independent of the 3d location // of the mesh. The ON_TextureMapping::TYPE::srfp_mapping != m_type // check is used because these mappings are // always independent of 3d location but // the transformations are often set. if ( ON_TextureMapping::TYPE::srfp_mapping != m_type && mesh_xform && mesh_xform->IsValid() && !mesh_xform->IsZero() && !tag.m_mesh_xform.IsZero() ) { // compare xforms - these can have a bit of slop const double* a = &mesh_xform->m_xform[0][0]; const double* b = &tag.m_mesh_xform.m_xform[0][0]; for ( int i = 16; i--; /*empty*/ ) { if ( fabs(*a++ - *b++) > ON_SQRT_EPSILON ) { rc = false; break; } } } } return rc; } static bool GetSPTCHelper( const ON_Mesh& mesh, const ON_TextureMapping& mapping, float* tc, int tc_stride ) { const int vcnt = mesh.m_V.Count(); if ( vcnt <= 0 ) return false; if ( !mesh.HasSurfaceParameters() ) return false; const ON_2dPoint* S = mesh.m_S.Array(); if ( !S ) return false; int i; double u, v, a, b; // srf_udom and srf_vdom record the range // of parameters saved in the m_S[] array. ON_Interval srf_udom = mesh.m_srf_domain[0]; ON_Interval srf_vdom = mesh.m_srf_domain[1]; if ( !srf_udom.IsIncreasing() || !srf_vdom.IsIncreasing() ) { // Attempt to calculate it from m_S[]. srf_udom.m_t[0] = srf_udom.m_t[1] = S[0].x; srf_vdom.m_t[0] = srf_vdom.m_t[1] = S[0].y; for ( i = 1; i < vcnt; i++ ) { u = S[i].x; if (u < srf_udom.m_t[0]) srf_udom.m_t[0] = u; else if (u > srf_udom.m_t[1]) srf_udom.m_t[1] = u; v = S[i].y; if (v < srf_vdom.m_t[0]) srf_vdom.m_t[0] = v; else if (v > srf_vdom.m_t[1]) srf_vdom.m_t[1] = v; } if ( !srf_udom.IsIncreasing() || !srf_vdom.IsIncreasing() ) { return false; } } bool bHaveUVWXform = mapping.m_uvw.IsValid() && !mapping.m_uvw.IsIdentity() && !mapping.m_uvw.IsZero(); if ( mesh.HasPackedTextureRegion() ) { // Packed textures are not compatible with the use // of m_uvw. m_uvw is ignored in this block // of code on purpose. //SEE BELOW const ON_Interval tex_udom = mesh.m_packed_tex_domain[0]; const ON_Interval tex_vdom = mesh.m_packed_tex_domain[1]; for ( i = 0; i < vcnt; i++) { u = S[i].x; v = S[i].y; // (u, v) = known surface parameter if ( mesh.m_packed_tex_rotate ) { // verify this by checking with mesher a = 1.0 - srf_vdom.NormalizedParameterAt( v ); b = srf_udom.NormalizedParameterAt( u ); } else { a = srf_udom.NormalizedParameterAt( u ); b = srf_vdom.NormalizedParameterAt( v ); } // When textures are packed, tex_udom and tex_vdom // are subintervals of (0,1). u = tex_udom.ParameterAt(a); v = tex_vdom.ParameterAt(b); // Jussi, Aug 18 2022, RH-69752: Apply uvw transform in texture space if (bHaveUVWXform) { const ON_2dPoint si = mapping.m_uvw * ON_2dPoint(u, v); u = si.x; v = si.y; } tc[0] = (float)u; tc[1] = (float)v; tc += tc_stride; } } else if ( bHaveUVWXform ) { const ON_Xform xform(mapping.m_uvw); ON_3dPoint P; for ( i = 0; i < vcnt; i++) { // normalize surface parameter P.x = srf_udom.NormalizedParameterAt( S[i].x ); P.y = srf_vdom.NormalizedParameterAt( S[i].y ); P.z = 0.0; // apply m_uvw transformation P = xform*P; tc[0] = (float)P.x; tc[1] = (float)P.y; tc += tc_stride; } } else { // m_srf_tex_rotate is ignored on purpose. // It only applies if the texture is packed. for ( i = 0; i < vcnt; i++) { // tc = normalized surface parameter a = srf_udom.NormalizedParameterAt( S[i].x ); b = srf_vdom.NormalizedParameterAt( S[i].y ); tc[0] = (float)a; tc[1] = (float)b; tc += tc_stride; } } return true; } #if !defined(OPENNURBS_PLUS) class ON__MTCBDATA { // DO NOT PUT THIS CLASS IN A HEADER FILE // IT IS A PRIVATE HELPER CLASS. public: const ON_Mesh* m_pMesh; ON_3dPoint m_pt; ON_RTreeSphere m_sphere; int m_face_index; double m_t[4]; double m_dist; bool m_bRestart; }; bool ON_CALLBACK_CDECL MTCB(void* a_context, ON__INT_PTR a_id) { ON__MTCBDATA* pData = (ON__MTCBDATA*)a_context; if (nullptr == pData) return true; pData->m_bRestart = false; const int fi = (int)a_id; if (fi < 0 || fi >= pData->m_pMesh->m_F.Count()) return true; const ON_MeshFace& face = pData->m_pMesh->m_F[fi]; const int fcc = face.IsTriangle() ? 3 : 4; for (int fci = 0; fci < fcc; fci++) { // Distance to vertex at corner fci const ON_3dPoint ptV = pData->m_pMesh->Vertex(face.vi[fci]); const double distV = ptV.DistanceTo(pData->m_pt); if (distV < pData->m_dist) { pData->m_dist = distV; pData->m_face_index = fi; pData->m_t[0] = pData->m_t[1] = pData->m_t[2] = pData->m_t[3] = 0.0; pData->m_t[fci] = 1.0; } // Distance to edge between corners fci-1 and fci const int fcip = (fci + fcc - 1) % fcc; const ON_3dPoint ptPV = pData->m_pMesh->Vertex(face.vi[fcip]); ON_Line edge(ptPV, ptV); double t = 0.0; if (edge.ClosestPointTo(pData->m_pt, &t)) { if (t >= 0.0 && t <= 1.0) { const ON_3dPoint ptE = edge.PointAt(t); const double distE = ptE.DistanceTo(pData->m_pt); if (distE < pData->m_dist) { pData->m_dist = distE; pData->m_face_index = fi; pData->m_t[0] = pData->m_t[1] = pData->m_t[2] = pData->m_t[3] = 0.0; pData->m_t[fci] = t; pData->m_t[fcip] = 1.0 - t; } } } } int tris = 1; ON_3dex fcis[2] = { {0, 1, 2}, ON_3dex::Unset }; //int fcis[2][3] = {{0, 1, 2}, {ON_UNSET_INT_INDEX, ON_UNSET_INT_INDEX, ON_UNSET_INT_INDEX}}; if (face.IsQuad()) { fcis[1] = { 0, 3, 2 }; tris = 2; } for (int ti = 0; ti < tris; ti++) { const int vis[3] = { face.vi[fcis[ti].i] , face.vi[fcis[ti].j], face.vi[fcis[ti].k] }; const ON_3dPoint pts[3] = { pData->m_pMesh->Vertex(vis[0]), pData->m_pMesh->Vertex(vis[1]), pData->m_pMesh->Vertex(vis[2]) }; double t0 = 0.0, t1 = 0.0; if (ProjectToFaceSpace(pts[0], pts[1], pts[2], pData->m_pt, t0, t1)) { if (0 <= t0 && 0 <= t1 && t0 + t1 <= 1.0) { const ON_3dPoint ptF = pts[0] + t0 * (pts[1] - pts[0]) + t1 * (pts[2] - pts[0]); const double distF = ptF.DistanceTo(pData->m_pt); if (distF < pData->m_dist) { pData->m_dist = distF; pData->m_face_index = fi; pData->m_t[0] = pData->m_t[1] = pData->m_t[2] = pData->m_t[3] = 0.0; pData->m_t[fcis[ti].i] = 1.0 - t0 - t1; pData->m_t[fcis[ti].j] = t0; pData->m_t[fcis[ti].k] = t1; } } } } if (pData->m_dist < pData->m_sphere.m_radius) { pData->m_sphere.m_radius = pData->m_dist; pData->m_bRestart = true; return false; } return true; } #endif bool ON_TextureMapping::GetTextureCoordinates( const ON_Mesh& mesh, ON_SimpleArray& T, const ON_Xform* mesh_xform, bool bLazy, ON_SimpleArray* Tside ) const { if ( Tside ) Tside->SetCount(0); int i; const int vcnt = mesh.m_V.Count(); if ( vcnt <= 0 ) return false; if ( bLazy ) { int tci, tccount = mesh.m_TC.Count(); for ( tci = 0; tci < tccount; tci++ ) { if ( vcnt == mesh.m_TC[tci].m_T.Count() ) { if ( HasMatchingTextureCoordinates(mesh.m_TC[tci].m_tag,mesh_xform) ) { T = mesh.m_TC[tci].m_T; return true; } } } if ( HasMatchingTextureCoordinates(mesh,mesh_xform ) ) { T.Reserve(vcnt); T.SetCount(vcnt); const ON_2fPoint* f = mesh.m_T.Array(); ON_3fPoint* d = T.Array(); for ( i = vcnt; i--; f++, d++ ) { d->x = f->x; d->y = f->y; d->z = 0.0f; } return true; } } bool rc = false; if ( ON_TextureMapping::TYPE::srfp_mapping == m_type ) { // uv textures from surface parameterization T.Reserve(vcnt); T.SetCount(vcnt); T.Zero(); rc = GetSPTCHelper(mesh,*this,&T[0].x,3); } else { ON_3dPoint P, tc; ON_3dVector N(0.0,0.0,0.0); const ON_3fPoint* mesh_V = mesh.m_V.Array(); const ON_3fVector* mesh_N = mesh.HasVertexNormals() ? mesh.m_N.Array() : 0; T.Reserve(vcnt); T.SetCount(vcnt); int* Tsd = 0; if ( Tside ) { Tside->Reserve(vcnt); Tside->SetCount(vcnt); Tsd = Tside->Array(); memset(Tsd,0,vcnt*sizeof(Tsd[0])); } ON_Xform P_xform(ON_Xform::IdentityTransformation), N_xform(ON_Xform::IdentityTransformation); const double* PT = 0; const double* NT = 0; if ( mesh_xform ) { if ( mesh_xform->IsZero() || mesh_xform->IsIdentity() ) { // ignore transformation mesh_xform = 0; } else if ( 0.0 != mesh_xform->GetMappingXforms(P_xform,N_xform) ) { PT = &P_xform[0][0]; NT = &N_xform[0][0]; } else { mesh_xform = 0; } } const float* f; double w; int sd; if (ON_TextureMapping::PROJECTION::clspt_projection == m_projection && (ON_TextureMapping::TYPE::mesh_mapping_primitive == m_type || ON_TextureMapping::TYPE::brep_mapping_primitive == m_type) && nullptr != m_mapping_primitive) { rc = false; if (ON_TextureMapping::TYPE::mesh_mapping_primitive == m_type) { const ON_Xform matP = nullptr != PT ? m_Pxyz * ON_Xform(PT) : m_Pxyz; const ON_Xform matN = nullptr != NT ? m_Nxyz * ON_Xform(NT) : m_Nxyz; const ON_Mesh* pMesh = CustomMappingMeshPrimitive(); if (nullptr != pMesh) { ON_RTree faceTree; if (faceTree.CreateMeshFaceTree(pMesh)) { for (int vi = 0; vi < mesh.VertexCount(); vi++) { T[vi] = ON_3fPoint::Origin; const ON_3dPoint vtx(mesh.m_V[vi]); const ON_3dPoint ptV = matP * vtx; const ON_3fVector vtVN = mesh.HasVertexNormals() ? ON_3fVector(matN * ON_3dVector(mesh.m_N[vi])) : ON_3fVector::ZeroVector; ON__MTCBDATA data; data.m_sphere.m_point[0] = ptV.x; data.m_sphere.m_point[1] = ptV.y; data.m_sphere.m_point[2] = ptV.z; data.m_sphere.m_radius = pMesh->BoundingBox().FarPoint(ptV).DistanceTo(ptV); data.m_pMesh = pMesh; data.m_pt = ptV; data.m_dist = DBL_MAX; do { faceTree.Search(&data.m_sphere, MTCB, (void*)&data); } while (data.m_bRestart); if (0 <= data.m_face_index && data.m_face_index < pMesh->m_F.Count()) { const ON_MeshFace& face = pMesh->m_F[data.m_face_index]; const ON_3dPoint ptUV = data.m_t[0] * ON_3dPoint(pMesh->m_T[face.vi[0]]) + data.m_t[1] * ON_3dPoint(pMesh->m_T[face.vi[1]]) + data.m_t[2] * ON_3dPoint(pMesh->m_T[face.vi[2]]) + data.m_t[3] * ON_3dPoint(pMesh->m_T[face.vi[3]]); const ON_3dPoint vtc = m_uvw * ptUV; T[vi].Set((float)vtc.x, (float)vtc.y, 0.0f); } } } } } } else if ( mesh_N && ( ON_TextureMapping::PROJECTION::ray_projection == m_projection || ON_TextureMapping::TYPE::box_mapping == m_type || ON_TextureMapping::TYPE::cylinder_mapping == m_type || ON_TextureMapping::TYPE::mesh_mapping_primitive == m_type || ON_TextureMapping::TYPE::wcsbox_projection == m_type ) ) { // calculation uses mesh vertex normal if ( PT && NT ) { // need to transform vertex and normal // before calculating texture coordinates for (i = 0; i < vcnt; i++) { f = &mesh_V[i].x; w = PT[12]*f[0] + PT[13]*f[1] + PT[14]*f[2] + PT[15]; w = (0.0 != w) ? 1.0/w : 1.0; P.x = w*(PT[0]*f[0] + PT[1]*f[1] + PT[ 2]*f[2] + PT[ 3]); P.y = w*(PT[4]*f[0] + PT[5]*f[1] + PT[ 6]*f[2] + PT[ 7]); P.z = w*(PT[8]*f[0] + PT[9]*f[1] + PT[10]*f[2] + PT[11]); f = &mesh_N[i].x; N.x = PT[0]*f[0] + PT[1]*f[1] + PT[ 2]*f[2]; N.y = PT[4]*f[0] + PT[5]*f[1] + PT[ 6]*f[2]; N.z = PT[8]*f[0] + PT[9]*f[1] + PT[10]*f[2]; N.Unitize(); sd = Evaluate(P,N,&tc); T[i] = tc; if ( Tsd ) Tsd[i] = sd; } } else { // mesh vertex and normal are ok for (i = 0; i < vcnt; i++) { P = mesh_V[i]; N = mesh_N[i]; sd = Evaluate(P,N,&tc); T[i] = tc; if ( Tsd ) Tsd[i] = sd; } } } else if ( PT ) { // normal is not used // mesh vertex needs to be transformed for ( i = 0; i < vcnt; i++ ) { f = &mesh_V[i].x; w = PT[12]*f[0] + PT[13]*f[1] + PT[14]*f[2] + PT[15]; w = (0.0 != w) ? 1.0/w : 1.0; P.x = w*(PT[0]*f[0] + PT[1]*f[1] + PT[ 2]*f[2] + PT[ 3]); P.y = w*(PT[4]*f[0] + PT[5]*f[1] + PT[ 6]*f[2] + PT[ 7]); P.z = w*(PT[8]*f[0] + PT[9]*f[1] + PT[10]*f[2] + PT[11]); sd = Evaluate(P,N,&tc); T[i] = tc; if ( Tsd ) Tsd[i] = sd; } } else { // normal is not used and mesh vertex is ok for ( i = 0; i < vcnt; i++ ) { P = mesh_V[i]; sd = Evaluate(P,N,&tc); T[i] = tc; if ( Tsd ) Tsd[i] = sd; } } rc = true; } return rc; } static void ThreeToTwoHelper( const ON_SimpleArray& T3, ON_SimpleArray& T2 ) { int i = T3.Count(); const ON_3fPoint* t3 = T3.Array(); T2.Reserve(i); T2.SetCount(i); ON_2fPoint* t2 = T2.Array(); while(i--) { t2->x = t3->x; t2->y = t3->y; t2++; t3++; } } bool ON_TextureMapping::GetTextureCoordinates( const ON_Mesh& mesh, ON_SimpleArray& T, const ON_Xform* mesh_xform, bool bLazy, ON_SimpleArray* Tside ) const { bool rc = false; if ( Tside ) Tside->SetCount(0); if ( bLazy ) { if ( HasMatchingTextureCoordinates(mesh,mesh_xform ) ) { if ( T.Array() != mesh.m_T.Array() ) { // different arrays - copy T = mesh.m_T; } return true; } else { int vcnt = mesh.m_V.Count(); int tci, tccount = mesh.m_TC.Count(); for ( tci = 0; tci < tccount; tci++ ) { if ( vcnt == mesh.m_TC[tci].m_T.Count() ) { if ( HasMatchingTextureCoordinates(mesh.m_TC[tci].m_tag,mesh_xform) ) { // copy T3d[] results to T[] ThreeToTwoHelper(mesh.m_TC[tci].m_T,T); return true; } } } } } if ( ON_TextureMapping::TYPE::srfp_mapping == m_type ) { if (mesh.HasSurfaceParameters()) { // uv textures from surface parameterization T.Reserve(mesh.m_V.Count()); T.SetCount(mesh.m_V.Count()); T.Zero(); rc = GetSPTCHelper(mesh, *this, &T[0].x, 2); } else { //In this case, we're just going to leave the TC array in place rc = false; } } else { T.SetCount(0); ON_SimpleArray T3; if ( GetTextureCoordinates(mesh, T3, mesh_xform, false, Tside ) ) { // copy T3d[] results to T[] ThreeToTwoHelper(T3,T); rc = true; } } return rc; } //bool ON_Mesh::GetSurfaceParameterTextureXform( // class ON_Xform& StoT // ) const // //{ // bool rc = false; // StoT.Identity(); // // // Gets default mesh mapping // const ON_Interval surface_u_domain(m_srf_domain[0]); // const ON_Interval surface_v_domain(m_srf_domain[1]); // const ON_Interval texture_u_domain(m_tex_domain[0]); // const ON_Interval texture_v_domain(m_tex_domain[1]); // bool bRotateTexture = m_srf_tex_rotate; // if ( surface_u_domain.IsInterval() // && surface_v_domain.IsInterval() // && texture_u_domain.IsInterval() // && texture_v_domain.IsInterval() // ) // { // double du = 1.0/surface_u_domain.Length(); // double dv = 1.0/surface_v_domain.Length(); // ON_Xform x1(1.0), x2(1.0), x3(1.0); // x1.m_xform[0][0] = du; x1.m_xform[0][3] = -surface_u_domain[0]*du; // x1.m_xform[1][1] = dv; x1.m_xform[1][3] = -surface_v_domain[0]*dv; // if ( bRotateTexture ) // { // x2.m_xform[0][0] = 0.0; x2.m_xform[0][1] = -1.0; x2.m_xform[0][3] = 1.0; // x2.m_xform[1][0] = 1.0; x2.m_xform[1][1] = 0.0; // } // x3.m_xform[0][0] = texture_u_domain.Length(); x3.m_xform[0][3] = texture_u_domain[0]; // x3.m_xform[1][1] = texture_v_domain.Length(); x3.m_xform[1][3] = texture_v_domain[0]; // // // transforms surface(u,v) to texture(u,v) // StoT = x3*x2*x1; // // rc = true; // } // return rc; //} class ON__CMeshFaceTC { // DO NOT PUT THIS CLASS IN A HEADER FILE // IT IS A PRIVATE HELPER CLASS. public: int fi; int quad[4]; float Tx[4]; bool bSetT[4]; }; class ON__CChangeTextureCoordinateHelper { // DO NOT PUT THIS CLASS IN A HEADER FILE // IT IS A PRIVATE HELPER CLASS. public: ON__CChangeTextureCoordinateHelper( ON_Mesh& mesh, int newvcnt, float*& mesh_T ); ~ON__CChangeTextureCoordinateHelper(); int DupVertex(int vi); void ChangeTextureCoordinate(int* Fvi, int fvi, float x, float y, float* mesh_T, int mesh_T_stride ); int m_tci; ON_Mesh& m_mesh; ON_3dPointArray* m_mesh_dV = nullptr; bool m_bHasVertexNormals; bool m_bHasVertexTextures; bool m_bHasVertexColors; bool m_bHasSurfaceParameters; bool m_bHasPrincipalCurvatures; bool m_bHasHiddenVertices; bool m_bHasCachedTextures; ON_SimpleArray< ON_TextureCoordinates* > m_TC; // m_vuse[] is an array of length = original number of // vertices in m_mesh and m_vuse[vi] = number of faces // that reference vertex vi. If this vertex needs to be // split, vuse[vi] is decremented. The ultimate goal // is to split a few times as needed so we don't // bloat the mesh with repeated calls to changing // texture maps. m_vuse[] is set the first time // DupVertex() is called. int m_vuse_count; ON_SimpleArray< unsigned int > m_vuse; private: // no implementation ON__CChangeTextureCoordinateHelper(const ON__CChangeTextureCoordinateHelper&); ON__CChangeTextureCoordinateHelper& operator=(const ON__CChangeTextureCoordinateHelper&); }; void ON__CChangeTextureCoordinateHelper::ChangeTextureCoordinate(int* Fvi, int fvi, float x, float y, float* mesh_T, int mesh_T_stride ) { int oldvi = Fvi[fvi]; float* T = mesh_T+(oldvi*mesh_T_stride); if ( x != T[0] || (y != ON_UNSET_FLOAT && y != T[1]) ) { int newvi = DupVertex(oldvi); T = mesh_T + (newvi*mesh_T_stride); T[0] = x; if ( y != ON_UNSET_FLOAT ) T[1] = y; if ( 2 == fvi && oldvi == Fvi[3] ) { Fvi[2] = newvi; Fvi[3] = newvi; } else { Fvi[fvi] = newvi; } } } ON__CChangeTextureCoordinateHelper::ON__CChangeTextureCoordinateHelper( ON_Mesh& mesh, int newvcnt, float*& mesh_T ) : m_mesh(mesh) , m_mesh_dV(0) , m_vuse_count(0) { // adding vertices invalidates this cached information. m_mesh.DestroyTopology(); m_mesh.DestroyPartition(); m_mesh.DestroyTree(); m_tci = -1; const int vcnt = m_mesh.m_V.Count(); // It is critical to reserve enough room in the arrays // before duplication starts. Otherwise, during duplication, // a dyanamic array can be reallocated, which will make // saved array base pointers will be invalid, and you crash // the next time they are used. m_mesh.m_V.Reserve(vcnt+newvcnt); if ( m_mesh.HasDoublePrecisionVertices() ) { m_mesh_dV = &m_mesh.m_dV; m_mesh_dV->Reserve(vcnt+newvcnt); } else { m_mesh.DestroyDoublePrecisionVertices(); } m_bHasVertexNormals = m_mesh.HasVertexNormals(); if ( m_bHasVertexNormals ) m_mesh.m_N.Reserve(vcnt+newvcnt); m_bHasVertexTextures = m_mesh.HasTextureCoordinates(); if ( m_bHasVertexTextures ) { float* p = (float*)m_mesh.m_T.Array(); m_mesh.m_T.Reserve(vcnt+newvcnt); if ( p == mesh_T ) mesh_T = (float*)m_mesh.m_T.Array(); } m_bHasVertexColors = m_mesh.HasVertexColors(); if ( m_bHasVertexColors ) m_mesh.m_C.Reserve(vcnt+newvcnt); m_bHasSurfaceParameters = m_mesh.HasSurfaceParameters(); if ( m_bHasSurfaceParameters ) m_mesh.m_S.Reserve(vcnt+newvcnt); m_bHasPrincipalCurvatures = m_mesh.HasPrincipalCurvatures(); if ( m_bHasPrincipalCurvatures ) m_mesh.m_K.Reserve(vcnt+newvcnt); m_bHasHiddenVertices = (0 != m_mesh.HiddenVertexArray()); if ( m_bHasHiddenVertices ) m_mesh.m_H.Reserve(vcnt+newvcnt); // Set m_TC[] to be the subset of m_mesh.m_TC[] that is // valid for duplication. m_bHasCachedTextures = false; int tci, tccount = m_mesh.m_TC.Count(); m_TC.Reserve(tccount); for ( tci = 0; tci < tccount; tci++ ) { ON_TextureCoordinates& tc = m_mesh.m_TC[tci]; if ( vcnt == tc.m_T.Count() ) { m_bHasCachedTextures = true; float* p = (float*)tc.m_T.Array(); tc.m_T.Reserve(vcnt+newvcnt); if ( p == mesh_T ) mesh_T = (float*)tc.m_T.Array(); m_TC.Append( &tc ); } } } ON__CChangeTextureCoordinateHelper::~ON__CChangeTextureCoordinateHelper() { if ( nullptr != m_mesh_dV ) { m_mesh_dV = 0; } } int ON__CChangeTextureCoordinateHelper::DupVertex(int vi) { if ( 0 == m_vuse_count ) { // m_vuse[] is an array of length = original number of // vertices in m_mesh and m_vuse[vi] = number of faces // that reference vertex vi. If this vertex needs to be // split, vuse[vi] is decremented. The ultimate goal // is to split a few times as needed so we don't // bloat the mesh with repeated calls to changing // texture maps. m_vuse[] is set the first time // DupVertex() is called. m_vuse_count = m_mesh.m_V.Count(); m_vuse.Reserve(m_vuse_count); m_vuse.SetCount(m_vuse_count); m_vuse.Zero(); for ( int fi = 0; fi < m_mesh.m_F.Count(); fi++ ) { const int* Fvi = m_mesh.m_F[fi].vi; int i = Fvi[0]; if ( i >= 0 && i < m_vuse_count ) m_vuse[i]++; i = Fvi[1]; if ( i >= 0 && i < m_vuse_count ) m_vuse[i]++; i = Fvi[2]; if ( i >= 0 && i < m_vuse_count ) m_vuse[i]++; i = Fvi[3]; if ( Fvi[2] != i && i >= 0 && i < m_vuse_count ) m_vuse[i]++; } } if ( vi >= 0 && vi < m_vuse_count ) { if ( m_vuse[vi] <= 1 ) return vi; // only one face uses this vertex - no need to dup the vertex // otherwise we will duplicate this vertex, reducing its use count by 1. m_vuse[vi]--; } m_mesh.m_V.AppendNew(); *m_mesh.m_V.Last() = m_mesh.m_V[vi]; if ( 0 != m_mesh_dV ) { m_mesh_dV->AppendNew(); *(m_mesh_dV->Last()) = m_mesh_dV->operator[](vi); } if ( m_bHasVertexTextures ) { m_mesh.m_T.AppendNew(); *m_mesh.m_T.Last() = m_mesh.m_T[vi]; } if ( m_bHasVertexNormals ) { m_mesh.m_N.AppendNew(); *m_mesh.m_N.Last() = m_mesh.m_N[vi]; } if ( m_bHasVertexColors ) { m_mesh.m_C.AppendNew(); *m_mesh.m_C.Last() = m_mesh.m_C[vi]; } if ( m_bHasSurfaceParameters ) { m_mesh.m_S.AppendNew(); *m_mesh.m_S.Last() = m_mesh.m_S[vi]; } if ( m_bHasPrincipalCurvatures ) { m_mesh.m_K.AppendNew(); *m_mesh.m_K.Last() = m_mesh.m_K[vi]; } if ( m_bHasHiddenVertices ) { m_mesh.m_H.AppendNew(); if ( 0 != (*m_mesh.m_H.Last() = m_mesh.m_H[vi]) ) m_mesh.m_hidden_count++; } if ( m_bHasCachedTextures ) { // Note: This m_TC[] is the subset of m_mesh.m_TC[] // that need to be duped. The constructor // insures that m_TC[i] is not nullptr and // has the right count and capacity. // // DO NOT REFERENCE m_mesh.m_TC[] in this block. int tccount = m_TC.Count(); for ( int i = 0; i < tccount; i++ ) { ON_SimpleArray& T = m_TC[i]->m_T; T.AppendNew(); *T.Last() = T[vi]; } } return m_mesh.m_V.Count()-1; } static float PoleFix( float t0, float t1 ) { float t = ( ON_UNSET_FLOAT == t0 ) ? t1 : ((ON_UNSET_FLOAT == t1 ) ? t0 : (0.5f*(t0+t1))); return t; } static int IntersectBoxSideRayHelper(int side, const ON_3dPoint& rst, const ON_3dVector& n, double* s) { /* returns: 0 = ray parallel to sides 1 = ray hit left side (x=-1) 2 = ray hit right side (x=+1) 3 = ray hit back side (y=-1) 4 = ray hit front side (y=+1) 5 = ray hit bottom side (z=-1) 6 = ray hit top side (z=+1) */ double nx; ON_3dPoint Q; double t,t0,t1; int dir; switch(side) { case 1: // = left side (x=-1) t1 = -1.0; dir = 0; break; case 2: // right side (x=+1) t1 = 1.0; dir = 0; break; case 3: // back side (y=-1) t1 = -1.0; dir = 1; break; case 4: // front side (y=+1) t1 = 1.0; dir = 1; break; case 5: // bottom side (z=-1) t1 = -1.0; dir = 2; break; case 6: // top side (z=+1) t1 = 1.0; dir = 2; break; default: *s = ON_UNSET_VALUE; return 0; break; } // protect against overflow nx = (&n.x)[dir]; t0 = (t1 - (&rst.x)[dir]); if ( fabs(t0) >= fabs(nx)*on__overflow_tol ) { *s = ON_UNSET_VALUE; return 0; } t0 /= nx; Q = rst + t0*n; if ( dir ) { t = Q.x; Q.x = Q[dir]; Q[dir] = t; } if ( fabs(Q.x-t1) > ON_SQRT_EPSILON || fabs(Q.y) > 1.0e8 || fabs(Q.z) > 1.0e8 ) { *s = ON_UNSET_VALUE; return 0; } *s = t0; return side; } static bool EvBoxSideTextureCoordinateHelper2( int side, const ON_TextureMapping& box_mapping, const ON_3dPoint& P, const ON_3dVector& N, ON_3dPoint* T ) { // side flag // 1 = left side (x=-1) // 2 = right side (x=+1) // 3 = back side (y=-1) // 4 = front side (y=+1) // 5 = bottom side (z=-1) // 6 = top side (z=+1) // The matrix m_Pxyz transforms the world coordinate // "mapping cylinder" into the cylinder centered at // rst = (0,0,0) with radius 1.0. The axis runs // from rst = (0,0,-1) to rst = (0,0,+1). ON_3dPoint rst(box_mapping.m_Pxyz*P); ON_3dVector n(box_mapping.m_Nxyz*N); n.Unitize(); // side flag // 1 = left side (x=-1) // 2 = right side (x=+1) // 3 = back side (y=-1) // 4 = front side (y=+1) // 5 = bottom side (z=-1) // 6 = top side (z=+1) if ( ON_TextureMapping::PROJECTION::ray_projection == box_mapping.m_projection ) { double s; if ( side == IntersectBoxSideRayHelper(side, rst, n, &s) ) { // ray hit the box side rst = rst + s*n; } } double shift = 0.0; // side flag // 1 = left side (x=-1) // 2 = right side (x=+1) // 3 = back side (y=-1) // 4 = front side (y=+1) // 5 = bottom side (z=-1) // 6 = top side (z=+1) switch(side) { case 1: // x = -1 rst.x = -rst.y; rst.y = rst.z; shift = 3.0; break; case 2: // x = +1 rst.x = rst.y; rst.y = rst.z; shift = 1.0; break; case 3: // y = -1 rst.y = rst.z; shift = 0.0; break; case 4: // y = +1 rst.x = -rst.x; rst.y = rst.z; shift = 2.0; break; case 5: // z = -1 rst.x = -rst.x; shift = 4.0; break; case 6: // z = +1 shift = 5.0; break; default: return 0; break; } // normalize texture coordinates rst.x = 0.5*rst.x + 0.5; rst.y = 0.5*rst.y + 0.5; rst.z = 0.0; if( ON_TextureMapping::TEXTURE_SPACE::divided == box_mapping.m_texture_space) { rst.x = (shift + rst.x)/(box_mapping.m_bCapped ? 6.0 : 4.0); } *T = box_mapping.m_uvw*rst; return true; } static bool EvBoxSideTextureCoordinateHelper1( const ON_Mesh& mesh, const ON_Xform* mesh_xform, int vi, int side, const ON_TextureMapping& box_mapping, float* Tx, float* Ty ) { bool rc = false; ON_3dPoint P, tc; ON_3dVector N(0.0,0.0,0.0); const ON_3fPoint* mesh_V = mesh.m_V.Array(); const ON_3fVector* mesh_N = mesh.HasVertexNormals() ? mesh.m_N.Array() : 0; ON_Xform P_xform(ON_Xform::IdentityTransformation), N_xform(ON_Xform::IdentityTransformation); const double* PT = 0; const double* NT = 0; if ( mesh_xform ) { if ( mesh_xform->IsZero() || mesh_xform->IsIdentity() ) { // ignore transformation mesh_xform = 0; } else if ( 0.0 != mesh_xform->GetMappingXforms(P_xform,N_xform) ) { PT = &P_xform[0][0]; NT = &N_xform[0][0]; } else { mesh_xform = 0; } } const float* f; double w; if ( mesh_N && ON_TextureMapping::PROJECTION::ray_projection == box_mapping.m_projection ) { // calculation uses mesh vertex normal if ( PT && NT ) { // need to transform vertex and normal // before calculating texture coordinates f = &mesh_V[vi].x; w = PT[12]*f[0] + PT[13]*f[1] + PT[14]*f[2] + PT[15]; w = (0.0 != w) ? 1.0/w : 1.0; P.x = w*(PT[0]*f[0] + PT[1]*f[1] + PT[ 2]*f[2] + PT[ 3]); P.y = w*(PT[4]*f[0] + PT[5]*f[1] + PT[ 6]*f[2] + PT[ 7]); P.z = w*(PT[8]*f[0] + PT[9]*f[1] + PT[10]*f[2] + PT[11]); f = &mesh_N[vi].x; N.x = PT[0]*f[0] + PT[1]*f[1] + PT[ 2]*f[2]; N.y = PT[4]*f[0] + PT[5]*f[1] + PT[ 6]*f[2]; N.z = PT[8]*f[0] + PT[9]*f[1] + PT[10]*f[2]; N.Unitize(); } else { // mesh vertex and normal are ok P = mesh_V[vi]; N = mesh_N[vi]; } } else if ( PT ) { // normal is not used // mesh vertex needs to be transformed f = &mesh_V[vi].x; w = PT[12]*f[0] + PT[13]*f[1] + PT[14]*f[2] + PT[15]; w = (0.0 != w) ? 1.0/w : 1.0; P.x = w*(PT[0]*f[0] + PT[1]*f[1] + PT[ 2]*f[2] + PT[ 3]); P.y = w*(PT[4]*f[0] + PT[5]*f[1] + PT[ 6]*f[2] + PT[ 7]); P.z = w*(PT[8]*f[0] + PT[9]*f[1] + PT[10]*f[2] + PT[11]); } else { // normal is not used and mesh vertex is ok P = mesh_V[vi]; } rc = EvBoxSideTextureCoordinateHelper2(side,box_mapping,P,N,&tc); if (rc) { rc = tc.IsValid(); if (rc) { *Tx = (float)tc.x; *Ty = (float)tc.y; } } return rc; } class ON__CNewMeshFace { public: int fi; int newvcnt; bool bNewV[4]; ON_2fPoint tc[4]; }; static float TcDistanceHelper(const ON_2fPoint& tc) { float dx = (tc.x > 0.5f) ? (1.0f-tc.x) : tc.x; if ( dx < 0.0f) return 0.0f; float dy = (tc.y > 0.5f) ? (1.0f-tc.y) : tc.y; if ( dy < 0.0f) return 0.0f; return (dx < dy) ? dx : dy; } static void AdjustSingleBoxTextureCoordinatesHelper( ON_Mesh& mesh, const ON_Xform* mesh_xform, float* mesh_T, int mesh_T_stride, const int* Tsd, const ON_TextureMapping& box_mapping ) { const int vcnt = mesh.m_V.Count(); const int fcnt = mesh.m_F.Count(); if ( vcnt < 3 || fcnt < 1 || vcnt != mesh.m_T.Count() || !Tsd ) return; const ON_MeshFace* mesh_F = mesh.m_F.Array(); const int* Fvi; int j, k, fi, sd[4], fvicnt, side, newvcnt=0; ON__CNewMeshFace mf; ON_2fPoint tc; ON_SimpleArray mflist(512); float d; for ( fi = 0; fi < fcnt; fi++ ) { Fvi = mesh_F[fi].vi; sd[0] = Tsd[Fvi[0]]; sd[1] = Tsd[Fvi[1]]; sd[2] = Tsd[Fvi[2]]; sd[3] = Tsd[Fvi[3]]; if ( sd[0] == sd[1] && sd[0] == sd[2] && sd[0] == sd[3] ) { // all texture coords are on same side of box continue; } fvicnt = (Fvi[2] != Fvi[3]) ? 4 : 3; memset(&mf,0,sizeof(mf)); mf.tc[0] = mesh_T + (Fvi[0]*mesh_T_stride); mf.tc[1] = mesh_T + (Fvi[1]*mesh_T_stride); mf.tc[2] = mesh_T + (Fvi[2]*mesh_T_stride); mf.tc[3] = mesh_T + (Fvi[3]*mesh_T_stride); // find the side we will use for this face side = sd[0]; d = TcDistanceHelper(mf.tc[0]); for ( j = 1; j < fvicnt; j++ ) { float d1 = TcDistanceHelper(mf.tc[j]); if (d1 > d) { side = sd[j]; d = d1; } } // Jussi, 5th September 2011: // This 'continue' only works for faces having one or more of its tc's in (0,1)x(0,1). // I have commented it out as a fix to RR 90329. //if ( d <= 0.0f ) // continue; for ( j = 0; j < fvicnt; j++ ) { if ( sd[j] != side ) { // calculate new tc for this side if ( EvBoxSideTextureCoordinateHelper1( mesh, mesh_xform, Fvi[j], side, box_mapping, &tc.x,&tc.y) ) { if ( tc.x != mf.tc[j].x || tc.y != mf.tc[j].y ) { mf.tc[j] = tc; mf.bNewV[j] = true; mf.newvcnt++; } } else break; } } if ( j >= fvicnt && mf.newvcnt > 0 ) { mf.fi = fi; newvcnt += mf.newvcnt; mflist.Append(mf); } } if ( newvcnt <= 0 ) return; ON__CChangeTextureCoordinateHelper helper(mesh,vcnt+newvcnt,mesh_T); const int mflist_count = mflist.Count(); for ( k = 0; k < mflist_count; k++ ) { mf = mflist[k]; int* fvi = mesh.m_F[mf.fi].vi; fvicnt = (fvi[2]!=fvi[3]) ? 4 : 3; for ( j = 0; j < fvicnt; j++ ) { if ( mf.bNewV[j] ) { helper.ChangeTextureCoordinate(fvi,j,mf.tc[j].x,mf.tc[j].y,mesh_T,mesh_T_stride); } } } } static void AdjustMeshPeriodicTextureCoordinatesHelper( ON_Mesh& mesh, const ON_Xform* mesh_xform, float* mesh_T, int mesh_T_stride, const int* Tsd, double two_pi_tc, const ON_TextureMapping& mapping ) { // This helper adjusts texture coordinates on faces that // span the seam on mapping spheres and cylinders and // resolves the mulitiple valued problem that // exists at the poles of sphere mappings. const int vcnt = mesh.m_V.Count(); const int fcnt = mesh.m_F.Count(); if ( vcnt < 3 || fcnt < 1 || vcnt != mesh.m_T.Count() ) return; // see if any texture coordinate adjustment is necessary const ON_TextureMapping::TYPE mapping_type = mapping.m_type; const bool bSphereCheck = ( ON_TextureMapping::TYPE::sphere_mapping == mapping_type ); const bool bCylinderCheck = (Tsd && ON_TextureMapping::TYPE::cylinder_mapping == mapping_type); const bool bBoxCheck = (Tsd && ON_TextureMapping::TYPE::box_mapping == mapping_type); if ( bBoxCheck && ON_TextureMapping::TEXTURE_SPACE::single == mapping.m_texture_space ) { AdjustSingleBoxTextureCoordinatesHelper( mesh, mesh_xform, mesh_T, mesh_T_stride, Tsd, mapping ); return; } ON_Workspace ws; int* quad = ws.GetIntMemory(vcnt); // ~ws will free quad memory float* Tx = (float*)ws.GetMemory(vcnt*sizeof(Tx[0])); float t; int vi, ti, q=0; int ftc_count = 0; const float ang0 = (float)(0.25*two_pi_tc); const float ang1 = (float)(0.75*two_pi_tc); for ( vi = ti = 0; vi < vcnt; vi++, ti += mesh_T_stride ) { quad[vi] = 0; Tx[vi] = mesh_T[ti]; if ( bCylinderCheck ) { if ( 1 != Tsd[vi] ) continue; } else if ( bBoxCheck ) { if ( 1 != Tsd[vi] && 3 != Tsd[vi] ) continue; } else if ( bSphereCheck ) { t = mesh_T[ti+1]; // t = "v" texture coordinate if ( t < 0.001f ) { quad[vi] = 8; q |= 8; // south pole point ftc_count++; continue; } if ( t > 0.999f ) { quad[vi] = 8; q |= 8; // north pole point ftc_count++; continue; } } t = Tx[vi]; // t = "u" texture coordinate if ( t < ang0 ) { quad[vi] = 1; q |= 1; // longitude < pi/2 ftc_count++; } else if ( t > ang1 ) { quad[vi] = 4; q |= 4; // longitude > 3pi/2 ftc_count++; } } if ( 0 == q || 1 == q || 4 == q ) { // nothing needs to be adjusted return; } // 4*ftc_count = (over) estimate of the number of faces that // will be changed. ON_SimpleArray ftc_list(ftc_count*4 + 128); ftc_count = 0; const ON_MeshFace* F = mesh.m_F.Array(); const int* Fvi; int fi; ON__CMeshFaceTC ftc; memset(&ftc,0,sizeof(ftc)); float t0, t1; for ( fi = 0; fi < fcnt; fi++ ) { Fvi = F[fi].vi; ftc.quad[0] = quad[Fvi[0]]; ftc.quad[1] = quad[Fvi[1]]; ftc.quad[2] = quad[Fvi[2]]; ftc.quad[3] = quad[Fvi[3]]; q = (ftc.quad[0] | ftc.quad[1] | ftc.quad[2] | ftc.quad[3]); if ( 0 == q || 1 == q || 4 == q ) { // no adjustments need to be made continue; } // ftc.fi will be set to fi if a texture coordinate needs to be adjusted ftc.fi = -1; ftc.Tx[0] = Tx[Fvi[0]]; ftc.Tx[1] = Tx[Fvi[1]]; ftc.Tx[2] = Tx[Fvi[2]]; ftc.Tx[3] = Tx[Fvi[3]]; if ( 0 != (8&q) ) { // see if check for north/south sphere mapping poles and fix them if ( 8 == ftc.quad[0] ) { t0 = (8 == ftc.quad[3]) ? ON_UNSET_FLOAT : ftc.Tx[3]; t1 = (8 == ftc.quad[1]) ? ON_UNSET_FLOAT : ftc.Tx[1]; if ( ON_UNSET_FLOAT != t0 || ON_UNSET_FLOAT != t1 ) { ftc.Tx[0] = PoleFix(t0,t1); ftc.quad[0] = ((ftc.Tx[0] < ang0) ? 1 : ((ftc.Tx[0] > ang1) ? 4 : 0)); q |= ftc.quad[0]; ftc.fi = fi; } } if ( 8 == ftc.quad[1] ) { t0 = (8 == ftc.quad[0]) ? ON_UNSET_FLOAT : ftc.Tx[0]; t1 = (8 == ftc.quad[2]) ? ON_UNSET_FLOAT : ftc.Tx[2]; if ( ON_UNSET_FLOAT != t0 || ON_UNSET_FLOAT != t1 ) { ftc.Tx[1] = PoleFix(t0,t1); ftc.quad[1] = ((ftc.Tx[1] < ang0) ? 1 : ((ftc.Tx[1] > ang1) ? 4 : 0)); q |= ftc.quad[1]; ftc.fi = fi; } } if ( 8 == ftc.quad[2] ) { int k = (Fvi[2] == Fvi[3]) ? 0 : 3; t0 = (8 == ftc.quad[1]) ? ON_UNSET_FLOAT : ftc.Tx[1]; t1 = (8 == ftc.quad[k]) ? ON_UNSET_FLOAT : ftc.Tx[k]; if ( ON_UNSET_FLOAT != t0 || ON_UNSET_FLOAT != t1 ) { ftc.Tx[2] = PoleFix(t0,t1); ftc.quad[2] = ((ftc.Tx[2] < ang0) ? 1 : ((ftc.Tx[2] > ang1) ? 4 : 0)); if ( !k ) { ftc.Tx[3] = ftc.Tx[2]; ftc.quad[3] = ftc.quad[2]; } q |= ftc.quad[2]; ftc.fi = fi; } } if ( 8 == ftc.quad[3] && Fvi[2] != Fvi[3] ) { t0 = (8 == ftc.quad[2]) ? ON_UNSET_FLOAT : ftc.Tx[2]; t1 = (8 == ftc.quad[0]) ? ON_UNSET_FLOAT : ftc.Tx[0]; if ( ON_UNSET_FLOAT != t0 || ON_UNSET_FLOAT != t1 ) { ftc.Tx[3] = PoleFix(t0,t1); ftc.quad[3] = ((ftc.Tx[3] < ang0) ? 1 : ((ftc.Tx[3] > ang1) ? 4 : 0)); q |= ftc.quad[3]; ftc.fi = fi; } } } if ( 5 == (5&q) ) { // The face has corners on both sides of the seam if ( two_pi_tc == 1.0 ) { if ( 1 == ftc.quad[0] ) {ftc.Tx[0] += 1.0f; ftc.fi = fi;} if ( 1 == ftc.quad[1] ) {ftc.Tx[1] += 1.0f; ftc.fi = fi;} if ( 1 == ftc.quad[2] ) {ftc.Tx[2] += 1.0f; ftc.fi = fi;} if ( 1 == ftc.quad[3] ) {ftc.Tx[3] += 1.0f; ftc.fi = fi;} } else { // With divided textures, wrapping the texture coordinate // does not work because it wraps into a region of the // texture not use by this "side". In this case, the // only thing to do is to pick the best end of the texture // map and clamp the tcs that hang over. If the mesh // has edges near the texture seam, the picture will // still look ok. float f0=0.0f, f1=0.0f, twopitc = (float)two_pi_tc; //int f0cnt=0, f1cnt=0; if ( 1 == ftc.quad[0] ) f0 += ftc.Tx[0]; else if ( 4 == ftc.quad[0] ) f1 += twopitc-ftc.Tx[0]; if ( 1 == ftc.quad[1] ) f0 += ftc.Tx[1]; else if ( 4 == ftc.quad[1] ) f1 += twopitc-ftc.Tx[1]; if ( 1 == ftc.quad[2] ) f0 += ftc.Tx[2]; else if ( 4 == ftc.quad[2] ) f1 += twopitc-ftc.Tx[2]; if (Fvi[2] != Fvi[3]) { if ( 1 == ftc.quad[3] ) f0 += ftc.Tx[3]; else if ( 4 == ftc.quad[3] ) f1 += twopitc-ftc.Tx[3]; } if (f0 >= f1 ) { // "most" of the face is on the left side of the texture // If a vertex is on the right side, clamp its tc to 0. if ( 4 == ftc.quad[0] ) {ftc.Tx[0] = 0.0f; ftc.fi = fi;} if ( 4 == ftc.quad[1] ) {ftc.Tx[1] = 0.0f; ftc.fi = fi;} if ( 4 == ftc.quad[2] ) {ftc.Tx[2] = 0.0f; ftc.fi = fi;} if ( 4 == ftc.quad[3] ) {ftc.Tx[3] = 0.0f; ftc.fi = fi;} } else { // "most" of the face is on the right side of the texture // If a vertex is on the left side, clamp its tc to two_pi_tc. if ( 1 == ftc.quad[0] ) {ftc.Tx[0] = twopitc; ftc.fi = fi;} if ( 1 == ftc.quad[1] ) {ftc.Tx[1] = twopitc; ftc.fi = fi;} if ( 1 == ftc.quad[2] ) {ftc.Tx[2] = twopitc; ftc.fi = fi;} if ( 1 == ftc.quad[3] ) {ftc.Tx[3] = twopitc; ftc.fi = fi;} } } } if ( ftc.fi >= 0 ) { // face will require special handling ftc_list.Append(ftc); } } ftc_count = ftc_list.Count(); if ( ftc_count <= 0 ) return; // Count the number of new vertices that will be added. int ftci; int newvcnt = 0; for ( ftci = 0; ftci < ftc_count; ftci++ ) { ON__CMeshFaceTC& ftc_local = ftc_list[ftci]; Fvi = F[ftc_local.fi].vi; if ( ftc_local.Tx[0] != Tx[Fvi[0]] ) { ftc_local.bSetT[0] = true; newvcnt++; } if ( ftc_local.Tx[1] != Tx[Fvi[1]] ) { ftc_local.bSetT[1] = true; newvcnt++; } if ( ftc_local.Tx[2] != Tx[Fvi[2]] ) { ftc_local.bSetT[2] = true; newvcnt++; } if ( Fvi[2] != Fvi[3] ) { if ( ftc_local.Tx[3] != Tx[Fvi[3]] ) { ftc_local.bSetT[3] = true; newvcnt++; } } } if ( newvcnt <= 0 ) return; F = 0; // Setting them to nullptr makes sure anybody who // tries to use them below will crash. // reserve room for new vertex information ON__CChangeTextureCoordinateHelper helper(mesh,newvcnt,mesh_T); // add vertices and update mesh faces for ( ftci = 0; ftci < ftc_count; ftci++ ) { const ON__CMeshFaceTC& ftc_local = ftc_list[ftci]; int* meshFvi = mesh.m_F[ftc_local.fi].vi; if ( ftc_local.bSetT[0] ) { helper.ChangeTextureCoordinate(meshFvi,0,ftc_local.Tx[0],ON_UNSET_FLOAT,mesh_T,mesh_T_stride); } if ( ftc_local.bSetT[1] ) { helper.ChangeTextureCoordinate(meshFvi,1,ftc_local.Tx[1],ON_UNSET_FLOAT,mesh_T,mesh_T_stride); } if ( ftc_local.bSetT[2] ) { helper.ChangeTextureCoordinate(meshFvi,2,ftc_local.Tx[2],ON_UNSET_FLOAT,mesh_T,mesh_T_stride); } if ( ftc_local.bSetT[3] ) { helper.ChangeTextureCoordinate(meshFvi,3,ftc_local.Tx[3],ON_UNSET_FLOAT,mesh_T,mesh_T_stride); } } } static bool SeamCheckHelper( const ON_TextureMapping& mp, double& two_pi_tc, ON_SimpleArray& Tside, ON_SimpleArray*& Tsd ) { bool bSeamCheck = false; switch(mp.m_type) { case ON_TextureMapping::TYPE::box_mapping: if ( ON_TextureMapping::TEXTURE_SPACE::divided == mp.m_texture_space ) { if ( mp.m_bCapped ) two_pi_tc = 2.0/3.0; Tsd = &Tside; bSeamCheck = true; } else if ( ON_TextureMapping::TEXTURE_SPACE::single == mp.m_texture_space ) { Tsd = &Tside; bSeamCheck = true; } break; case ON_TextureMapping::TYPE::cylinder_mapping: if ( ON_TextureMapping::TEXTURE_SPACE::divided == mp.m_texture_space ) { two_pi_tc = 2.0/3.0; Tsd = &Tside; } bSeamCheck = true; break; case ON_TextureMapping::TYPE::sphere_mapping: bSeamCheck = true; break; default: // intentionally skip other enum values break; } return bSeamCheck; } //If there are unused vertices, this may not work correctly - but it is very fast. static inline bool HasSharedVertices(const ON_Mesh& mesh) { return mesh.m_V.Count() < ((mesh.TriangleCount() * 3) + (mesh.QuadCount() * 4)); } const ON_TextureCoordinates* ON_Mesh::SetCachedTextureCoordinates( const class ON_TextureMapping& mapping, const class ON_Xform* mesh_xform, bool bLazy ) { return SetCachedTextureCoordinatesEx(mapping, mesh_xform, bLazy, true); } const ON_TextureCoordinates* ON_Mesh::SetCachedTextureCoordinatesEx( const class ON_TextureMapping& mapping, const class ON_Xform* mesh_xform, bool bLazy, bool bSeamCheck ) { if ( mapping.RequiresVertexNormals() && !HasVertexNormals() ) ComputeVertexNormals(); ON_TextureMapping mp = mapping; double two_pi_tc = 1.0; ON_SimpleArray Tside; ON_SimpleArray* Tsd = 0; if (bSeamCheck) { bSeamCheck = SeamCheckHelper(mp, two_pi_tc, Tside, Tsd) && HasSharedVertices(*this); if (bSeamCheck) mp.m_uvw = ON_Xform::IdentityTransformation; } ON_TextureCoordinates* TC = 0; { for ( int i = 0; i < m_TC.Count(); i++ ) { if ( m_TC[i].m_tag.m_mapping_id == mapping.Id() ) { TC = &m_TC[i]; break; } } } if ( bLazy && TC && mapping.HasMatchingTextureCoordinates( TC->m_tag, mesh_xform ) ) return TC; if ( !TC ) { m_TC.AppendNew(); TC = m_TC.Last(); } // Use mp instead of mapping to call GetTextureCoordinates() // because m_uvw must be the identity if we have seams. if ( !mp.GetTextureCoordinates( *this,TC->m_T,mesh_xform,false,Tsd) ) { int tci = (int)(TC - m_TC.Array()); m_TC.Remove(tci); return 0; } TC->m_tag.Set(mapping); if ( mesh_xform && mesh_xform->IsValid() && !mesh_xform->IsIdentity() && !mesh_xform->IsZero() ) { TC->m_tag.m_mesh_xform = *mesh_xform; } TC->m_dim = 2; if ( bSeamCheck && m_F.Count() > 0 && TC->m_T.Count() == m_V.Count() ) { float* mesh_T = (float*)TC->m_T.Array(); int mesh_T_stride = sizeof(TC->m_T[0])/sizeof(mesh_T[0]); if ( Tsd && Tside.Count() != m_V.Count() ) Tsd = 0; AdjustMeshPeriodicTextureCoordinatesHelper( *this, mesh_xform, mesh_T, mesh_T_stride, Tsd ? Tside.Array() : 0, two_pi_tc, mp ); mesh_T = 0; // when the array is grown, the pointer may become invalid if ( !mapping.m_uvw.IsIdentity() && !mapping.m_uvw.IsZero() ) { // Apply the uvw transformation that is on mapping // to the texture coordinates. ON_3dPoint T; int vi, vcnt = TC->m_T.Count(); ON_3fPoint* meshT = TC->m_T.Array(); for ( vi = 0; vi < vcnt; vi++ ) { T = meshT[vi]; T = mapping.m_uvw*T; meshT[vi] = T; } } } return TC; } bool ON_Mesh::SetTextureCoordinates( const class ON_TextureMapping& mapping, const class ON_Xform* mesh_xform, bool bLazy ) { return SetTextureCoordinatesEx(mapping, mesh_xform, bLazy, true); } bool ON_Mesh::SetTextureCoordinatesEx( const class ON_TextureMapping& mapping, const class ON_Xform* mesh_xform, bool bLazy, bool bSeamCheck ) { if ( mapping.RequiresVertexNormals() && !HasVertexNormals() ) ComputeVertexNormals(); InvalidateTextureCoordinateBoundingBox(); ON_SimpleArray Tside; ON_SimpleArray* Tsd = 0; ON_TextureMapping mp = mapping; double two_pi_tc = 1.0; if (bSeamCheck) { bSeamCheck = SeamCheckHelper(mp, two_pi_tc, Tside, Tsd) && HasSharedVertices(*this); if (bSeamCheck) mp.m_uvw = ON_Xform::IdentityTransformation; } // Use mp instead of mapping to call GetTextureCoordinates() // because m_uvw must be the identity if we have seams. bool rc = mp.GetTextureCoordinates(*this,m_T,mesh_xform,bLazy,Tsd); if (Tsd != nullptr && Tsd->Count() == 0) { // Tsd array is needed for seam check but it was not filled by GetTextureCoordinates. // This happened because matching texture coordinates were found. These coordinates // were already once seam checked. So seam check can be skipped now. bSeamCheck = false; } if (rc) { // update the texture coordinate tag m_Ttag.Set(mapping); if ( mesh_xform && mesh_xform->IsValid() && !mesh_xform->IsIdentity() && !mesh_xform->IsZero() ) { m_Ttag.m_mesh_xform = *mesh_xform; } } if ( rc && bSeamCheck && HasTextureCoordinates() && m_F.Count() > 0 ) { float* mesh_T = (float*)m_T.Array(); int mesh_T_stride = sizeof(m_T[0])/sizeof(mesh_T[0]); if ( Tsd && Tside.Count() != m_V.Count() ) Tsd = 0; AdjustMeshPeriodicTextureCoordinatesHelper( *this, mesh_xform, mesh_T, mesh_T_stride, Tsd ? Tside.Array() : 0, two_pi_tc, mp ); mesh_T = 0; // when the array is grown, the pointer may become invalid if ( !mapping.m_uvw.IsIdentity() && !mapping.m_uvw.IsZero() ) { // Apply the uvw transformation that is on mapping // to the texture coordinates. ON_2fPoint* meshT = m_T.Array(); ON_3dPoint T; int vi, vcnt = m_T.Count(); for ( vi = 0; vi < vcnt; vi++ ) { T.x = meshT[vi].x; T.y = meshT[vi].y; T.z = 0.0; T = mapping.m_uvw*T; meshT[vi].x = (float)T.x; meshT[vi].y = (float)T.y; } } } return rc; } void ON_Mesh::InvalidateCachedTextureCoordinates(bool bOnlyInvalidateCachedSurfaceParameterMapping) { if (bOnlyInvalidateCachedSurfaceParameterMapping) { for (int i = m_TC.Count() - 1; i >= 0; i--) { if (m_TC[i].m_tag.m_mapping_type == ON_TextureMapping::TYPE::srfp_mapping) { m_TC.Remove(i); } } } else { m_TC.Destroy(); } } ON_MappingChannel::ON_MappingChannel() { Default(); } void ON_MappingChannel::Default() { memset(this,0,sizeof(*this)); m_mapping_channel_id = 1; m_mapping_index = -1; m_object_xform = ON_Xform::IdentityTransformation; } int ON_MappingChannel::Compare( const ON_MappingChannel& other ) const { int rc = m_mapping_channel_id - other.m_mapping_channel_id; if (!rc) rc = ON_UuidCompare(m_mapping_id,other.m_mapping_id); if (!rc) rc = m_object_xform.Compare(other.m_object_xform); return rc; } bool ON_MappingChannel::Write( ON_BinaryArchive& archive ) const { bool rc = archive.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK,1,1); if (rc) { rc = archive.WriteInt(m_mapping_channel_id); if (rc) rc = archive.WriteUuid(m_mapping_id); // 1.1 field added 6 June 2006 if (rc) rc = archive.WriteXform(m_object_xform); if ( !archive.EndWrite3dmChunk() ) rc = false; } return rc; } bool ON_MappingChannel::Read( ON_BinaryArchive& archive ) { Default(); int major_version = 0; int minor_version = 0; bool rc = archive.BeginRead3dmChunk(TCODE_ANONYMOUS_CHUNK,&major_version,&minor_version); if (rc) { rc = (1 == major_version); if (rc) rc = archive.ReadInt(&m_mapping_channel_id); if (rc) rc = archive.ReadUuid(m_mapping_id); if ( rc && minor_version >= 1 ) { // 1.1 field added 6 June 2006 if (rc) rc = archive.ReadXform(m_object_xform); if (rc && archive.ArchiveOpenNURBSVersion() < 200610030 && m_object_xform.IsZero() ) { // Between versions 200606060 and 200610030, // there was a bug that created some mapping // channels with zero transformations. This // if clause finds those and sets them to the // identity. m_object_xform = ON_Xform::IdentityTransformation; } } if ( !archive.EndRead3dmChunk() ) rc = false; } return rc; } ON_MaterialRef::ON_MaterialRef() { Default(); } ON_MappingRef::ON_MappingRef() { Default(); } void ON_MaterialRef::Default() { memset(this,0,sizeof(*this)); // runtme index value of -1 means not set m_material_index = -1; m_material_backface_index = -1; m_material_source = ON::material_from_layer; } void ON_MappingRef::Default() { m_plugin_id = ON_nil_uuid; m_mapping_channels.Destroy(); } int ON_MaterialRef::Compare( const ON_MaterialRef& other ) const { int rc = ON_UuidCompare(m_plugin_id,other.m_plugin_id); if (rc) rc = ((int)m_material_source) - ((int)other.m_material_source); if (!rc) rc = ON_UuidCompare(m_material_id,other.m_material_id); if (!rc) rc = ON_UuidCompare(m_material_backface_id,other.m_material_backface_id); return rc; } int ON_MappingRef::Compare( const ON_MappingRef& other ) const { int rc = ON_UuidCompare(m_plugin_id,other.m_plugin_id); if ( !rc) { const int count = m_mapping_channels.Count(); rc = count - other.m_mapping_channels.Count(); if (!rc) { for ( int i = 0; i < count && !rc; i++ ) { rc = m_mapping_channels[i].Compare(other.m_mapping_channels[i]); } } } return rc; } bool ON_MaterialRef::Write( ON_BinaryArchive& archive ) const { bool rc = archive.BeginWrite3dmChunk( TCODE_ANONYMOUS_CHUNK, 1, 1 ); if (rc) { if (rc) rc = archive.WriteUuid( m_plugin_id ); if (rc) rc = archive.WriteUuid( m_material_id ); // 23 May 2006 Dale lear // m_mapping_channels[] was removed from ON_MaterialRef. // To keep from breaking the file format, I need to // write a zero as the array length. // //if (rc) rc = archive.WriteArray( m_mapping_channels ); if (rc) rc = archive.WriteInt(0); // 23 May 2006 added if (rc) rc = archive.WriteUuid( m_material_backface_id ); if (rc) rc = archive.WriteInt( m_material_source ); if ( !archive.EndWrite3dmChunk() ) rc = false; } return rc; } bool ON_MaterialRef::Read( ON_BinaryArchive& archive ) { Default(); int major_version = 0; int minor_version = 0; bool rc = archive.BeginRead3dmChunk( TCODE_ANONYMOUS_CHUNK, &major_version, &minor_version ); if (rc) { rc = (1 == major_version); if (rc) rc = archive.ReadUuid( m_plugin_id ); if (rc) rc = archive.ReadUuid( m_material_id ); // 23 May 2006 Dale lear // m_mapping_channels[] was removed from ON_MaterialRef. // To keep from breaking the file format, I need to // write a zero as the array length. ON_SimpleArray obsolete_mapping_channels; if (rc) rc = archive.ReadArray( obsolete_mapping_channels ); if ( minor_version >= 1 ) { if (rc) rc = archive.ReadUuid( m_material_backface_id ); int i = m_material_source; if (rc) rc = archive.ReadInt( &i ); if (rc) m_material_source = (unsigned char)ON::ObjectMaterialSource(i); } if ( !archive.EndRead3dmChunk() ) rc = false; } return rc; } void ON_MaterialRef::SetMaterialSource( ON::object_material_source material_source ) { m_material_source = (unsigned char)material_source; } ON::object_material_source ON_MaterialRef::MaterialSource() const { return ON::ObjectMaterialSource(m_material_source); } bool ON_MappingRef::Write( ON_BinaryArchive& archive ) const { bool rc = archive.BeginWrite3dmChunk( TCODE_ANONYMOUS_CHUNK, 1, 0 ); if (rc) { if (rc) rc = archive.WriteUuid( m_plugin_id ); if (rc) rc = archive.WriteArray( m_mapping_channels ); if ( !archive.EndWrite3dmChunk() ) rc = false; } return rc; } bool ON_MappingRef::Read( ON_BinaryArchive& archive ) { Default(); int major_version = 0; int minor_version = 0; bool rc = archive.BeginRead3dmChunk( TCODE_ANONYMOUS_CHUNK, &major_version, &minor_version ); if (rc) { rc = (1 == major_version); if (rc) rc = archive.ReadUuid( m_plugin_id ); if (rc) rc = archive.ReadArray( m_mapping_channels ); if ( !archive.EndRead3dmChunk() ) rc = false; } return rc; } bool ON_MappingRef::Transform( const ON_Xform& xform ) { int count = m_mapping_channels.Count(); if ( count > 0 ) { for ( ON_MappingChannel* mapping_channel = m_mapping_channels.Array(); count--; mapping_channel++ ) { mapping_channel->m_object_xform = xform*mapping_channel->m_object_xform; } } return true; } ON_ObjectRenderingAttributes::ON_ObjectRenderingAttributes() { Default(); } ON_RenderingAttributes::ON_RenderingAttributes() { Default(); } void ON_ObjectRenderingAttributes::Default() { ON_RenderingAttributes::Default(); m_mappings.Destroy(); m_bCastsShadows = true; m_bReceivesShadows = true; m_bits = 0; m_reserved1 = 0; } void ON_RenderingAttributes::Default() { m_materials.Destroy(); } void ON_ObjectRenderingAttributes::EnableAdvancedTexturePreview(bool b) { if ( b ) m_bits |= 1; // set bit 1 else m_bits &= 0xFE; // clear bit 1 } bool ON_ObjectRenderingAttributes::AdvancedTexturePreview() const { return (0 != (1 & m_bits)) ? true : false; } bool ON_RenderingAttributes::IsValid( ON_TextLog* text_log ) const { // plug-in uuids must be unique int count; if( (count = m_materials.Count()) > 1 ) { const ON_MaterialRef* mr = m_materials.Array(); ON_UUID plugin_id; int i, j; for ( i = 0; i < count-1; i++ ) { plugin_id = mr[i].m_plugin_id; for ( j = i+1; j < count; j++ ) { if ( !ON_UuidCompare(&plugin_id,&mr[j].m_plugin_id ) ) { if( text_log ) { text_log->Print("ON_RenderingAttributes error: m_materials[%d] and m_materials[%d] have the same plug-in id.\n",i,j); } return false; } } } } return true; } bool ON_ObjectRenderingAttributes::IsValid( ON_TextLog* text_log ) const { if ( !ON_RenderingAttributes::IsValid(text_log) ) return false; // plug-in uuids must be unique int count; if( (count = m_mappings.Count()) > 1 ) { const ON_MappingRef* mr = m_mappings.Array(); ON_UUID plugin_id; int i, j; for ( i = 0; i < count-1; i++ ) { plugin_id = mr[i].m_plugin_id; for ( j = i+1; j < count; j++ ) { if ( !ON_UuidCompare(&plugin_id,&mr[j].m_plugin_id ) ) { if( text_log ) { text_log->Print("ON_ObjectRenderingAttributes error: m_mappings[%d] and m_mappings[%d] have the same plug-in id.\n",i,j); } return false; } } } } return true; } int ON_RenderingAttributes::Compare( const ON_RenderingAttributes& other ) const { const int count = m_materials.Count(); int rc = count - other.m_materials.Count(); if (!rc) { int i; for ( i = 0; i < count && !rc; i++ ) { rc = m_materials[i].Compare(other.m_materials[i]); } } return rc; } const ON_MaterialRef* ON_RenderingAttributes::MaterialRef( const ON_UUID& plugin_id ) const { int count; if ( (count = m_materials.Count()) > 0 ) { for ( const ON_MaterialRef* mr = m_materials.Array(); count--; mr++ ) { if ( plugin_id == mr->m_plugin_id ) return mr; } } return 0; } int ON_ObjectRenderingAttributes::Compare( const ON_ObjectRenderingAttributes& other ) const { int rc = ON_RenderingAttributes::Compare(other); if (!rc) { int i; const int count = m_mappings.Count(); rc = other.m_mappings.Count() - count; for ( i = 0; i < count && !rc; i++ ) { rc = m_mappings[i].Compare(other.m_mappings[i]); } if ( !rc ) { rc = ((int)(m_bCastsShadows?1:0)) - ((int)(other.m_bCastsShadows?1:0)); if ( !rc ) { rc = ((int)(m_bReceivesShadows?1:0)) - ((int)(other.m_bReceivesShadows?1:0)); } if ( !rc ) { rc = ((int)(AdvancedTexturePreview()?1:0)) - ((int)(other.AdvancedTexturePreview()?1:0)); } } } return rc; } bool ON_ObjectRenderingAttributes::Transform( const ON_Xform& xform ) { int i; if ( (i = m_mappings.Count()) > 0 ) { for( ON_MappingRef* mr = m_mappings.Array(); i--; mr++ ) mr->Transform(xform); } return true; } const ON_MappingRef* ON_ObjectRenderingAttributes::MappingRef( const ON_UUID& plugin_id ) const { int count; if ( (count = m_mappings.Count()) > 0 ) { for ( const ON_MappingRef* mr = m_mappings.Array(); count--; mr++ ) { if ( plugin_id == mr->m_plugin_id ) return mr; } } //ALB 2013.12.03 //Fixes http://mcneel.myjetbrains.com/youtrack/issue/RH-5730 //I'm sick of this bug being considered irrelavent, and since I've decided to go out of my way to //Sort out as many mapping problems as I can, I'm fixing this one like this. if (m_mappings.Count() > 0) { return &m_mappings[0]; } return 0; } ON_MappingRef* ON_ObjectRenderingAttributes::AddMappingRef( const ON_UUID& plugin_id ) { ON_MappingRef* mr = 0; int count; if ( (count = m_mappings.Count()) > 0 ) { for ( mr = const_cast(m_mappings.Array()); count--; mr++ ) { if ( plugin_id == mr->m_plugin_id ) break; } } if ( !mr ) { mr = &m_mappings.AppendNew(); mr->m_plugin_id = plugin_id; } return mr; } bool ON_ObjectRenderingAttributes::DeleteMappingRef( const ON_UUID& plugin_id ) { const ON_MappingRef* mr = MappingRef(plugin_id); if ( mr ) m_mappings.Remove( (int)(mr - m_mappings.Array()) ); // safe ptr to in conversion return (0 != mr); } //static int ON_ObjectRenderingAttributes::OCSMappingChannelId(void) { return 100000; } const ON_MappingChannel* ON_ObjectRenderingAttributes::MappingChannel( const ON_UUID& plugin_id, const ON_UUID& mapping_id ) const { const ON_MappingRef* mr = MappingRef(plugin_id); if ( mr ) { int count; if ( (count = mr->m_mapping_channels.Count()) > 0 ) { for ( const ON_MappingChannel* mc = mr->m_mapping_channels.Array(); count--; mc++ ) { if ( mapping_id == mc->m_mapping_id ) return mc; } } } return 0; } const ON_MappingChannel* ON_ObjectRenderingAttributes::MappingChannel( const ON_UUID& plugin_id, int mapping_channel_id ) const { const ON_MappingRef* mr = MappingRef(plugin_id); if ( mr ) { int count; if ( (count = mr->m_mapping_channels.Count()) > 0 ) { for ( const ON_MappingChannel* mc = mr->m_mapping_channels.Array(); count--; mc++ ) { if ( mapping_channel_id == mc->m_mapping_channel_id ) return mc; } } } return 0; } bool ON_ObjectRenderingAttributes::AddMappingChannel( const ON_UUID& plugin_id, int mapping_channel_id, const ON_UUID& mapping_id ) { ON_MappingRef* mr = const_cast(MappingRef(plugin_id)); if ( !mr ) { mr = &m_mappings.AppendNew(); mr->m_plugin_id = plugin_id; ON_MappingChannel& mc = mr->m_mapping_channels.AppendNew(); mc.m_mapping_channel_id = mapping_channel_id; mc.m_mapping_id = mapping_id; mc.m_mapping_index = -1; // 27th October 2011 John Croudy - constructor is not called by AppendNew(). mc.m_object_xform = ON_Xform::IdentityTransformation; return true; } return mr->AddMappingChannel(mapping_channel_id,mapping_id); } bool ON_ObjectRenderingAttributes::DeleteMappingChannel( const ON_UUID& plugin_id, int mapping_channel_id ) { ON_MappingRef* mr = const_cast(MappingRef(plugin_id)); return mr ? mr->DeleteMappingChannel(mapping_channel_id) : false; } bool ON_ObjectRenderingAttributes::DeleteMappingChannel( const ON_UUID& plugin_id, const ON_UUID& mapping_id ) { ON_MappingRef* mr = const_cast(MappingRef(plugin_id)); return mr ? mr->DeleteMappingChannel(mapping_id) : false; } bool ON_ObjectRenderingAttributes::ChangeMappingChannel( const ON_UUID& plugin_id, int old_mapping_channel_id, int new_mapping_channel_id ) { ON_MappingRef* mr = const_cast(MappingRef(plugin_id)); return mr ? mr->ChangeMappingChannel(old_mapping_channel_id,new_mapping_channel_id) : false; } const ON_MappingChannel* ON_MappingRef::MappingChannel( const ON_UUID& mapping_id ) const { int count; if ( (count = m_mapping_channels.Count()) > 0 ) { for ( const ON_MappingChannel* mc = m_mapping_channels.Array(); count--; mc++ ) { if ( mapping_id == mc->m_mapping_id ) return mc; } } return 0; } const ON_MappingChannel* ON_MappingRef::MappingChannel( int mapping_channel_id ) const { int count; if ( (count = m_mapping_channels.Count()) > 0 ) { for ( const ON_MappingChannel* mc = m_mapping_channels.Array(); count--; mc++ ) { if ( mapping_channel_id == mc->m_mapping_channel_id ) return mc; } } return 0; } bool ON_MappingRef::AddMappingChannel( int mapping_channel_id, const ON_UUID& mapping_id ) { int i; if ( (i = m_mapping_channels.Count()) > 0 ) { for ( const ON_MappingChannel* mc = m_mapping_channels.Array(); i--; mc++ ) { if ( mapping_channel_id == mc->m_mapping_channel_id ) { // a matching mapping channel id exists // return true if mapping_id matches return ( 0 == ON_UuidCompare(&mapping_id,&mc->m_mapping_id) ); } } } ON_MappingChannel& mc = m_mapping_channels.AppendNew(); mc.m_mapping_channel_id = mapping_channel_id; mc.m_mapping_id = mapping_id; mc.m_mapping_index = -1; // 27th October 2011 John Croudy - constructor is not called by AppendNew(). mc.m_object_xform = ON_Xform::IdentityTransformation; return true; } bool ON_MappingRef::DeleteMappingChannel(int mapping_channel_id) { const ON_MappingChannel* mc = MappingChannel(mapping_channel_id); if ( mc ) { m_mapping_channels.Remove((int)(mc - m_mapping_channels.Array())); } return ( 0 != mc); } bool ON_MappingRef::DeleteMappingChannel(const ON_UUID& mapping_id) { const ON_MappingChannel* mc = MappingChannel(mapping_id); if ( mc ) { m_mapping_channels.Remove((int)(mc - m_mapping_channels.Array())); } return ( 0 != mc); } bool ON_MappingRef::ChangeMappingChannel( int old_mapping_channel_id, int new_mapping_channel_id ) { ON_MappingChannel* mc = const_cast(MappingChannel(old_mapping_channel_id)); if ( mc ) { mc->m_mapping_channel_id = new_mapping_channel_id; } return ( 0 != mc ); } bool ON_RenderingAttributes::Write( ON_BinaryArchive& archive ) const { bool rc = archive.BeginWrite3dmChunk( TCODE_ANONYMOUS_CHUNK, 1, 0 ); if ( !rc ) return false; for(;;) { rc = archive.WriteArray(m_materials); if ( !rc ) break; break; } if ( !archive.EndWrite3dmChunk() ) rc = false; return rc; } bool ON_RenderingAttributes::Read( ON_BinaryArchive& archive ) { Default(); int major_version = 0; int minor_version = 0; bool rc = archive.BeginRead3dmChunk( TCODE_ANONYMOUS_CHUNK, &major_version, &minor_version ); if (!rc) return false; for(;;) { rc = ( 1 == major_version ); if (!rc) break; rc = archive.ReadArray(m_materials); if (!rc) break; break; } if ( !archive.EndRead3dmChunk() ) rc = false; return rc; } bool ON_ObjectRenderingAttributes::Write( ON_BinaryArchive& archive ) const { bool rc = archive.BeginWrite3dmChunk( TCODE_ANONYMOUS_CHUNK, 1, 3 ); if ( !rc ) return false; for(;;) { // DO NOT CALL ON_RenderingAttributes::Write rc = archive.WriteArray(m_materials); if ( !rc ) break; rc = archive.WriteArray(m_mappings); if ( !rc ) break; // version 1.2 fields added 20061129 rc = archive.WriteBool(m_bCastsShadows); if ( !rc ) break; rc = archive.WriteBool(m_bReceivesShadows); if ( !rc ) break; // version 1.3 fields added 20101019 bool b = AdvancedTexturePreview(); rc = archive.WriteBool(b); if ( !rc ) break; break; } if ( !archive.EndWrite3dmChunk() ) rc = false; return rc; } bool ON_ObjectRenderingAttributes::Read( ON_BinaryArchive& archive ) { Default(); int major_version = 0; int minor_version = 0; bool rc = archive.BeginRead3dmChunk( TCODE_ANONYMOUS_CHUNK, &major_version, &minor_version ); if (!rc) return false; for(;;) { rc = ( 1 == major_version && minor_version >= 1 ); if (!rc) break; // DO NOT CALL ON_RenderingAttributes::Read if (rc) rc = archive.ReadArray(m_materials); if (!rc) break; if (rc) rc = archive.ReadArray(m_mappings); if (!rc) break; if ( minor_version <= 1 ) break; // version 1.2 fields added 20061129 rc = archive.ReadBool(&m_bCastsShadows); if ( !rc ) break; rc = archive.ReadBool(&m_bReceivesShadows); if ( !rc ) break; if ( minor_version <= 2 ) break; // version 1.3 fields added 20101019 bool b = AdvancedTexturePreview(); rc = archive.ReadBool(&b); if ( !rc ) break; // Jussi 20120430: We don't want to enable advanced texture preview by default. It will be // turned on when needed (depending on active render plug-in etc). //EnableAdvancedTexturePreview(b); break; } if ( !archive.EndRead3dmChunk() ) rc = false; return rc; } bool ON_TextureMapping::SetSurfaceParameterMapping(void) { // The nullptr check is wierd. // Speculation: A reference was null and somebody added this // as a hack to prevent a crash. if ( false == ON_IsNullPtr(this) ) { *this = ON_TextureMapping::SurfaceParameterTextureMapping; SetId(); // new id } return true; } bool ON_TextureMapping::SetPlaneMapping( const ON_Plane& plane, const ON_Interval& dx, const ON_Interval& dy, const ON_Interval& dz ) { *this = ON_TextureMapping::Unset; // Don't call plane.IsValid(), because the plane // equation does not matter and many developers // forget to set it correctly. if ( !plane.origin.IsValid() ) return false; if ( !ON_IsRightHandFrame( plane.xaxis, plane.yaxis, plane.zaxis ) ) return false; if ( !dx.IsValid() || !dy.IsValid() || !dz.IsValid() ) return false; ON_3dPoint C = plane.PointAt(dx.Mid(),dy.Mid(),dz.Mid()); C.x = (0.0 == C.x) ? 0.0 : -C.x; C.y = (0.0 == C.y) ? 0.0 : -C.y; C.z = (0.0 == C.z) ? 0.0 : -C.z; ON_3dVector xaxis = plane.xaxis; ON_3dVector yaxis = plane.yaxis; ON_3dVector zaxis = plane.zaxis; // Any "cleanup" needs to be done here // to xaxis, yaxis, zaxis. double sx,sy,sz; if ( 0.0 == (sx = dx.Length())) sx = 2.0; if ( 0.0 == (sy = dy.Length())) sy = 2.0; if ( 0.0 == (sz = dz.Length())) sz = 2.0; // The plane mapping matrix m_Pxyz transforms the // world coordinate rectangle to a (-1<=r<=1, // on plane to a // 1 X 1 square in the xy plane centered at the // origin. // m_Pxyz = surface point transformation ON_3dVector X = (2.0/sx)*xaxis; ON_3dVector Y = (2.0/sy)*yaxis; ON_3dVector Z = (2.0/sz)*zaxis; m_Pxyz.m_xform[0][0] = X.x; m_Pxyz.m_xform[0][1] = X.y; m_Pxyz.m_xform[0][2] = X.z; m_Pxyz.m_xform[0][3] = (X.x*C.x + X.y*C.y + X.z*C.z); m_Pxyz.m_xform[1][0] = Y.x; m_Pxyz.m_xform[1][1] = Y.y; m_Pxyz.m_xform[1][2] = Y.z; m_Pxyz.m_xform[1][3] = (Y.x*C.x + Y.y*C.y + Y.z*C.z); m_Pxyz.m_xform[2][0] = Z.x; m_Pxyz.m_xform[2][1] = Z.y; m_Pxyz.m_xform[2][2] = Z.z; m_Pxyz.m_xform[2][3] = (Z.x*C.x + Z.y*C.y + Z.z*C.z); m_Pxyz.m_xform[3][0] = 0.0; m_Pxyz.m_xform[3][1] = 0.0; m_Pxyz.m_xform[3][2] = 0.0; m_Pxyz.m_xform[3][3] = 1.0; // m_Nxyz = surface normal transformation // = inverse transpose of upper 3x3 of m_Pxyz X = (0.5*sx)*xaxis; Y = (0.5*sy)*yaxis; Z = (0.5*sz)*zaxis; m_Nxyz.m_xform[0][0] = X.x; m_Nxyz.m_xform[0][1] = X.y; m_Nxyz.m_xform[0][2] = X.z; m_Nxyz.m_xform[0][3] = 0.0; m_Nxyz.m_xform[1][0] = Y.x; m_Nxyz.m_xform[1][1] = Y.y; m_Nxyz.m_xform[1][2] = Y.z; m_Nxyz.m_xform[1][3] = 0.0; m_Nxyz.m_xform[2][0] = Z.x; m_Nxyz.m_xform[2][1] = Z.y; m_Nxyz.m_xform[2][2] = Z.z; m_Nxyz.m_xform[2][3] = 0.0; m_Nxyz.m_xform[3][0] = 0.0; m_Nxyz.m_xform[3][1] = 0.0; m_Nxyz.m_xform[3][2] = 0.0; m_Nxyz.m_xform[3][3] = 1.0; m_type = ON_TextureMapping::TYPE::plane_mapping; SetId(); #if defined(ON_DEBUG) { ON_Plane p; p.xaxis = (2.0/sx)*plane.xaxis; p.yaxis = (2.0/sy)*plane.yaxis; p.zaxis = (2.0/sz)*plane.zaxis; p.origin.Set(-C.x,-C.y,-C.z); p.UpdateEquation(); ON_Xform P_dbg, N_dbg; P_dbg.Rotation(p,ON_xy_plane); P_dbg.GetSurfaceNormalXform(N_dbg); for ( int i = 0; i < 4; i++ ) { for ( int j = 0; j < 4; j++ ) { if ( fabs(m_Pxyz[i][j] - P_dbg[i][j]) >= ON_SQRT_EPSILON*(fabs(m_Pxyz[i][j])+128.0) ) { ON_ERROR("m_Pxyz is nor right\n"); break; } if ( fabs(m_Nxyz[i][j] - N_dbg[i][j]) >= ON_SQRT_EPSILON*(fabs(m_Nxyz[i][j])+128.0) ) { ON_ERROR("m_Nxyz is nor right\n"); break; } } } } #endif return true; } bool ON_TextureMapping::SetOcsMapping( const ON_Plane& plane ) { const ON_Interval interval(0.0, 1.0); const auto rc = SetPlaneMapping(plane, interval, interval, interval); m_type = ON_TextureMapping::TYPE::plane_mapping; return rc; } bool ON_TextureMapping::SetBoxMapping(const ON_Plane& plane, ON_Interval dx, ON_Interval dy, ON_Interval dz, bool bCapped ) { bool rc = SetPlaneMapping(plane,dx,dy,dz); if (rc) { m_bCapped = bCapped; m_type = ON_TextureMapping::TYPE::box_mapping; } return rc; } bool ON_TextureMapping::SetCylinderMapping(const ON_Cylinder& cylinder, bool bIsCapped) { ON_Interval dr, dh; if ( !ON_IsValid(cylinder.circle.radius ) ) return false; double r = cylinder.circle.radius; if ( 0.0 == r ) r = 1.0; dr.Set(-r,r); dh.Set(cylinder.height[0],cylinder.height[1]); if ( dh[0] == dh[1] ) { if ( ON_UNSET_VALUE == dh[0] ) { dh.Set(-1.0,1.0); } else { dh.m_t[0] -= 1.0; dh.m_t[0] += 1.0; } } if ( !dh.IsValid() ) return false; bool rc = SetBoxMapping(cylinder.circle.plane,dr,dr,dh,bIsCapped); if (rc) { m_type = ON_TextureMapping::TYPE::cylinder_mapping; } return rc; } bool ON_TextureMapping::SetSphereMapping(const ON_Sphere& sphere) { ON_Interval dr(-sphere.radius,sphere.radius); bool rc = SetBoxMapping(sphere.plane,dr,dr,dr,false); if (rc) { m_type = ON_TextureMapping::TYPE::sphere_mapping; } return rc; } bool ON_TextureMapping::GetMappingPlane(ON_Plane& plane, ON_Interval& dx, ON_Interval& dy, ON_Interval& dz ) const { ON_Xform xform(m_Pxyz); ON_3dVector S(((ON_3dVector*)&xform.m_xform[0])->Length(), ((ON_3dVector*)&xform.m_xform[1])->Length(), ((ON_3dVector*)&xform.m_xform[2])->Length()); if ( 0.0 == S.x ) return false; S.x = 1.0/S.x; if ( 0.0 == S.y ) return false; S.y = 1.0/S.y; if ( 0.0 == S.z ) return false; S.z = 1.0/S.z; xform.m_xform[0][0] *= S.x; xform.m_xform[0][1] *= S.x; xform.m_xform[0][2] *= S.x; xform.m_xform[0][3] *= S.x; xform.m_xform[1][0] *= S.y; xform.m_xform[1][1] *= S.y; xform.m_xform[1][2] *= S.y; xform.m_xform[1][3] *= S.y; xform.m_xform[2][0] *= S.z; xform.m_xform[2][1] *= S.z; xform.m_xform[2][2] *= S.z; xform.m_xform[2][3] *= S.z; xform.m_xform[3][0] = 0.0; xform.m_xform[3][1] = 0.0; xform.m_xform[3][2] = 0.0; xform.m_xform[3][3] = 1.0; ON_Xform inv(xform); if ( !inv.Invert() ) return false; plane.origin.Set(inv.m_xform[0][3],inv.m_xform[1][3],inv.m_xform[2][3]); xform.m_xform[0][3] = 0.0; xform.m_xform[1][3] = 0.0; xform.m_xform[2][3] = 0.0; plane.xaxis = &xform.m_xform[0][0]; plane.yaxis = &xform.m_xform[1][0]; plane.zaxis = &xform.m_xform[2][0]; plane.UpdateEquation(); dx.Set(-S.x,S.x); dy.Set(-S.y,S.y); dz.Set(-S.z,S.z); return plane.IsValid(); } bool ON_TextureMapping::GetMappingBox(ON_Plane& plane, ON_Interval& dx, ON_Interval& dy, ON_Interval& dz) const { return GetMappingPlane(plane, dx, dy, dz); } bool ON_TextureMapping::GetMappingCylinder(ON_Cylinder& cylinder) const { ON_Interval dx, dy, dz; ON_Plane plane; bool rc = GetMappingPlane(cylinder.circle.plane, dx, dy, dz); if (rc) { double r0 = 0.5*dx.Length(); double r1 = 0.5*dy.Length(); cylinder.circle.radius = (r0 == r1) ? r0 : 0.5*(r0+r1); cylinder.height[0] = dz[0]; cylinder.height[1] = dz[1]; } return rc && cylinder.IsValid(); } bool ON_TextureMapping::GetMappingSphere(ON_Sphere& sphere) const { ON_Interval dx, dy, dz; bool rc = GetMappingPlane(sphere.plane, dx, dy, dz); if (rc) { double r0 = 0.5*dx.Length(); double r1 = 0.5*dy.Length(); double r2 = 0.5*dz.Length(); sphere.radius = (r0 == r1 && r0 == r2) ? r0 : (r0+r1+r2)/3.0; } return rc && sphere.IsValid(); } /////////////////////////////////////////////////////////////////////////////// // // Class ON_PBRMaterial // #define SUPPORT_PBR_USERDATA_SERIALIZATION class ON_PhysicallyBasedMaterialUserData : public ON_UserData { private: ON_OBJECT_DECLARE(ON_PhysicallyBasedMaterialUserData); #if defined(ON_COMPILER_CLANG) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Winconsistent-missing-override" #endif bool GetDescription(ON_wString & description) { description = L"ON_PhysicallyBasedMaterialUserData"; return true; } #if defined(ON_COMPILER_CLANG) #pragma clang diagnostic pop #endif public: ON_PhysicallyBasedMaterialUserData() { m_userdata_uuid = ON_CLASS_ID(ON_PhysicallyBasedMaterialUserData); m_application_uuid = ON_opennurbs6_id; m_userdata_copycount = 1; } ~ON_PhysicallyBasedMaterialUserData() {} ON_PhysicallyBasedMaterialUserData(const ON_PhysicallyBasedMaterialUserData& src) : ON_UserData(src) { m_userdata_copycount = src.m_userdata_copycount; m_parameters = src.m_parameters; } ON_PhysicallyBasedMaterialUserData& operator=(const ON_PhysicallyBasedMaterialUserData& src) { if (this != &src) { ON_UserData::operator = (src); m_parameters = src.m_parameters; } return *this; } private: // ON_Object overrides #if defined SUPPORT_PBR_USERDATA_SERIALIZATION bool Write(ON_BinaryArchive& archive) const override { ON_ASSERT(IsValid()); const int chunk_version = 2; if (false == archive.BeginWrite3dmAnonymousChunk(chunk_version)) return false; bool rc = m_parameters.Write(archive, chunk_version); if (!archive.EndWrite3dmChunk()) { rc = false; } return rc; } bool Read(ON_BinaryArchive& archive) override { int chunk_version = 0; if (false == archive.BeginRead3dmAnonymousChunk(&chunk_version)) return false; bool rc = false; if (chunk_version == 1 || chunk_version == 2) { rc = m_parameters.Read(archive, chunk_version); } if (!archive.EndRead3dmChunk()) { rc = false; } ON_ASSERT(IsValid()); return rc; } bool Archive() const override { return true; } #else bool Archive() const override { return false; } #endif bool IsValid(class ON_TextLog* text_log = nullptr) const override { if (!m_parameters.IsValid(text_log)) return false; return ON_UserData::IsValid(text_log); } public: struct Parameters { #if defined SUPPORT_PBR_USERDATA_SERIALIZATION bool Write(ON_BinaryArchive& binary_archive, int version) const { if (!binary_archive.WriteColor(base_color)) return false; if (!binary_archive.WriteInt((int)brdf)) return false; if (!binary_archive.WriteDouble(subsurface)) return false; if (!binary_archive.WriteColor(subsurface_scattering_color)) return false; if (!binary_archive.WriteDouble(subsurface_scattering_radius)) return false; if (!binary_archive.WriteDouble(metallic)) return false; if (!binary_archive.WriteDouble(specular)) return false; if (!binary_archive.WriteDouble(specular_tint)) return false; if (!binary_archive.WriteDouble(roughness)) return false; if (!binary_archive.WriteDouble(anisotropic)) return false; if (!binary_archive.WriteDouble(anisotropic_rotation)) return false; if (!binary_archive.WriteDouble(sheen)) return false; if (!binary_archive.WriteDouble(sheen_tint)) return false; if (!binary_archive.WriteDouble(clearcoat)) return false; if (!binary_archive.WriteDouble(clearcoat_roughness)) return false; if (!binary_archive.WriteDouble(opacity_IOR)) return false; if (!binary_archive.WriteDouble(opacity)) return false; if (!binary_archive.WriteDouble(opacity_roughness)) return false; if (!binary_archive.WriteColor(emission)) return false; if (version >= 2) { if (!binary_archive.WriteDouble(alpha)) return false; } return true; } bool Read(ON_BinaryArchive& binary_archive, int version) { if (!binary_archive.ReadColor(base_color)) return false; if (!binary_archive.ReadInt((int*)&brdf)) return false; if (!binary_archive.ReadDouble(&subsurface)) return false; if (!binary_archive.ReadColor(subsurface_scattering_color)) return false; if (!binary_archive.ReadDouble(&subsurface_scattering_radius)) return false; if (!binary_archive.ReadDouble(&metallic)) return false; if (!binary_archive.ReadDouble(&specular)) return false; if (!binary_archive.ReadDouble(&specular_tint)) return false; if (!binary_archive.ReadDouble(&roughness)) return false; if (!binary_archive.ReadDouble(&anisotropic)) return false; if (!binary_archive.ReadDouble(&anisotropic_rotation)) return false; if (!binary_archive.ReadDouble(&sheen)) return false; if (!binary_archive.ReadDouble(&sheen_tint)) return false; if (!binary_archive.ReadDouble(&clearcoat)) return false; if (!binary_archive.ReadDouble(&clearcoat_roughness)) return false; if (!binary_archive.ReadDouble(&opacity_IOR)) return false; if (!binary_archive.ReadDouble(&opacity)) return false; if (!binary_archive.ReadDouble(&opacity_roughness)) return false; if (!binary_archive.ReadColor(emission)) return false; if (version >= 2) { if (!binary_archive.ReadDouble(&alpha)) return false; } return true; } #endif bool IsValid(class ON_TextLog* text_log = nullptr) const { //!base_color.IsValid() means that PBR is not supported. //if (!base_color.IsValid()) return false; if (ON_UNSET_VALUE == subsurface) return false; if (!subsurface_scattering_color.IsValid()) return false; if (ON_IS_UNSET_DOUBLE(subsurface_scattering_radius)) return false; if (ON_IS_UNSET_DOUBLE(metallic)) return false; if (ON_IS_UNSET_DOUBLE(specular)) return false; if (ON_IS_UNSET_DOUBLE(specular_tint)) return false; if (ON_IS_UNSET_DOUBLE(roughness)) return false; if (ON_IS_UNSET_DOUBLE(anisotropic)) return false; if (ON_IS_UNSET_DOUBLE(anisotropic_rotation)) return false; if (ON_IS_UNSET_DOUBLE(sheen)) return false; if (ON_IS_UNSET_DOUBLE(sheen_tint)) return false; if (ON_IS_UNSET_DOUBLE(clearcoat)) return false; if (ON_IS_UNSET_DOUBLE(clearcoat_roughness)) return false; if (ON_IS_UNSET_DOUBLE(opacity_IOR)) return false; if (ON_IS_UNSET_DOUBLE(opacity)) return false; if (ON_IS_UNSET_DOUBLE(opacity_roughness)) return false; if (!emission.IsValid()) return false; if (ON_IS_UNSET_DOUBLE(alpha)) return false; return true; } ON_4fColor base_color = ON_4fColor::Unset; double subsurface = 0.0; ON_4fColor subsurface_scattering_color = ON_Color::White; double subsurface_scattering_radius = 0.0; double metallic = 0.0; double specular = 0.5; double specular_tint = 0.0; double roughness = 1.0; double anisotropic = 0.0; double anisotropic_rotation = 0.0; double sheen = 0.0; double sheen_tint = 0.0; double clearcoat = 0.0; double clearcoat_roughness = 0.0; double opacity_IOR = 1.52; double opacity = 1.0; double opacity_roughness = 0.0; ON_4fColor emission = ON_Color::Black; ON_PhysicallyBasedMaterial::BRDFs brdf = ON_PhysicallyBasedMaterial::BRDFs::GGX; double alpha = 1.0; } m_parameters; }; ON_UUID ON_Material::PhysicallyBasedUserdataId(void) { // {5694E1AC-40E6-44F4-9CA9-3B6D0E8C4440} static const ON_UUID id = { 0x5694e1ac, 0x40e6, 0x44f4,{ 0x9c, 0xa9, 0x3b, 0x6d, 0xe, 0x8c, 0x44, 0x40 } }; return id; } ON_OBJECT_IMPLEMENT(ON_PhysicallyBasedMaterialUserData, ON_UserData, "5694E1AC-40E6-44F4-9CA9-3B6D0E8C4440"); class ON_PhysicallyBasedMaterial::Impl { public: Impl(ON_Material& mat) : material(&mat), m_pDummy(nullptr) { } ~Impl() { delete m_pDummy; } bool UserDataExists(void) const { const auto pUD = material->GetUserData(ON_CLASS_ID(ON_PhysicallyBasedMaterialUserData)); return nullptr != pUD; } void RemoveUserData(void) const { auto pUD = material->GetUserData(ON_CLASS_ID(ON_PhysicallyBasedMaterialUserData)); if (material->DetachUserData(pUD)) { delete pUD; } } const ON_PhysicallyBasedMaterialUserData& UserData() const { const auto pUD = material->GetUserData(ON_CLASS_ID(ON_PhysicallyBasedMaterialUserData)); if (pUD) { return static_cast(*pUD); } if (m_pDummy) { return *m_pDummy; } //This is the const version m_pDummy = new ON_PhysicallyBasedMaterialUserData(); return *m_pDummy; } ON_PhysicallyBasedMaterialUserData& UserData() { auto pUD = material->GetUserData(ON_CLASS_ID(ON_PhysicallyBasedMaterialUserData)); if (pUD) { return static_cast(*pUD); } ON_PhysicallyBasedMaterialUserData* p = nullptr; if (nullptr == m_pDummy) { p = new ON_PhysicallyBasedMaterialUserData(); } else { p = m_pDummy; m_pDummy = nullptr; } material->AttachUserData(p); return *p; } ON_Material* material; mutable ON_PhysicallyBasedMaterialUserData* m_pDummy; }; ON_4fColor ON_PhysicallyBasedMaterial::BaseColor(void) const { return Implementation().UserData().m_parameters.base_color; } void ON_PhysicallyBasedMaterial::SetBaseColor(const ON_4fColor& c) { Implementation().UserData().m_parameters.base_color = c; } ON_PhysicallyBasedMaterial::BRDFs ON_PhysicallyBasedMaterial::BRDF(void) const { return Implementation().UserData().m_parameters.brdf; } void ON_PhysicallyBasedMaterial::SetBRDF(const BRDFs& b) { Implementation().UserData().m_parameters.brdf = b; } double ON_PhysicallyBasedMaterial::Subsurface(void) const { return Implementation().UserData().m_parameters.subsurface; } void ON_PhysicallyBasedMaterial::SetSubsurface(double d) { Implementation().UserData().m_parameters.subsurface = d; } ON_4fColor ON_PhysicallyBasedMaterial::SubsurfaceScatteringColor(void) const { return Implementation().UserData().m_parameters.subsurface_scattering_color; } void ON_PhysicallyBasedMaterial::SetSubsurfaceScatteringColor(const ON_4fColor& c) { Implementation().UserData().m_parameters.subsurface_scattering_color = c; } double ON_PhysicallyBasedMaterial::SubsurfaceScatteringRadius(void) const { return Implementation().UserData().m_parameters.subsurface_scattering_radius; } void ON_PhysicallyBasedMaterial::SetSubsurfaceScatteringRadius(double d) { Implementation().UserData().m_parameters.subsurface_scattering_radius = d; } double ON_PhysicallyBasedMaterial::Metallic(void) const { return Implementation().UserData().m_parameters.metallic; } void ON_PhysicallyBasedMaterial::SetMetallic(double d) { Implementation().UserData().m_parameters.metallic = d; } double ON_PhysicallyBasedMaterial::Specular(void) const { return Implementation().UserData().m_parameters.specular; } double ON_PhysicallyBasedMaterial::ReflectiveIOR(void) const { const double d2 = Specular() * 0.08; return sqrt(d2); } void ON_PhysicallyBasedMaterial::SetReflectiveIOR(double ior) { const double d = (ior - 1.0) / (ior + 1.0); SetSpecular(d*d / 0.08); } void ON_PhysicallyBasedMaterial::SetSpecular(double d) { Implementation().UserData().m_parameters.specular = d; } double ON_PhysicallyBasedMaterial::SpecularTint(void) const { return Implementation().UserData().m_parameters.specular_tint; } void ON_PhysicallyBasedMaterial::SetSpecularTint(double d) { Implementation().UserData().m_parameters.specular_tint = d; } double ON_PhysicallyBasedMaterial::Roughness(void) const { return Implementation().UserData().m_parameters.roughness; } void ON_PhysicallyBasedMaterial::SetRoughness(double d) { Implementation().UserData().m_parameters.roughness = d; } double ON_PhysicallyBasedMaterial::Anisotropic(void) const { return Implementation().UserData().m_parameters.anisotropic; } void ON_PhysicallyBasedMaterial::SetAnisotropic(double d) { Implementation().UserData().m_parameters.anisotropic = d; } double ON_PhysicallyBasedMaterial::AnisotropicRotation(void) const { return Implementation().UserData().m_parameters.anisotropic_rotation; } void ON_PhysicallyBasedMaterial::SetAnisotropicRotation(double d) { Implementation().UserData().m_parameters.anisotropic_rotation = d; } double ON_PhysicallyBasedMaterial::Sheen(void) const { return Implementation().UserData().m_parameters.sheen; } void ON_PhysicallyBasedMaterial::SetSheen(double d) { Implementation().UserData().m_parameters.sheen = d; } double ON_PhysicallyBasedMaterial::SheenTint(void) const { return Implementation().UserData().m_parameters.sheen_tint; } void ON_PhysicallyBasedMaterial::SetSheenTint(double d) { Implementation().UserData().m_parameters.sheen_tint = d; } double ON_PhysicallyBasedMaterial::Clearcoat(void) const { return Implementation().UserData().m_parameters.clearcoat; } void ON_PhysicallyBasedMaterial::SetClearcoat(double d) { Implementation().UserData().m_parameters.clearcoat = d; } double ON_PhysicallyBasedMaterial::ClearcoatRoughness(void) const { return Implementation().UserData().m_parameters.clearcoat_roughness; } void ON_PhysicallyBasedMaterial::SetClearcoatRoughness(double d) { Implementation().UserData().m_parameters.clearcoat_roughness = d; } double ON_PhysicallyBasedMaterial::OpacityIOR(void) const { return Implementation().UserData().m_parameters.opacity_IOR; } void ON_PhysicallyBasedMaterial::SetOpacityIOR(double d) { Implementation().UserData().m_parameters.opacity_IOR = d; } double ON_PhysicallyBasedMaterial::Opacity(void) const { return Implementation().UserData().m_parameters.opacity; } void ON_PhysicallyBasedMaterial::SetOpacity(double d) { Implementation().UserData().m_parameters.opacity = d; } double ON_PhysicallyBasedMaterial::OpacityRoughness(void) const { return Implementation().UserData().m_parameters.opacity_roughness; } void ON_PhysicallyBasedMaterial::SetOpacityRoughness(double d) { Implementation().UserData().m_parameters.opacity_roughness = d; } bool ON_PhysicallyBasedMaterial::IsValid(class ON_TextLog* text_log) const { return Implementation().UserData().m_parameters.IsValid(text_log); } ON_4fColor ON_PhysicallyBasedMaterial::Emission(void) const { return Implementation().UserData().m_parameters.emission; } void ON_PhysicallyBasedMaterial::SetEmission(ON_4fColor d) { Implementation().UserData().m_parameters.emission = d; } double ON_PhysicallyBasedMaterial::Alpha(void) const { return Implementation().UserData().m_parameters.alpha; } void ON_PhysicallyBasedMaterial::SetAlpha(double d) { Implementation().UserData().m_parameters.alpha = d; } ON_PhysicallyBasedMaterial::ON_PhysicallyBasedMaterial(const ON_Material& src) { //Placement new - create the impl in the stack allocated space. new (&_impl) Impl(const_cast(src)); } ON_PhysicallyBasedMaterial::~ON_PhysicallyBasedMaterial() { //delete _pImpl; //Call the dtor, don't release the memory Implementation().~Impl(); } ON_PhysicallyBasedMaterial::ON_PhysicallyBasedMaterial(const ON_PhysicallyBasedMaterial& src) { new (&_impl) Impl(*src.Implementation().material); } bool ON_Material::IsPhysicallyBased(void) const { //Optimized version const auto pUD = static_cast(GetUserData(ON_CLASS_ID(ON_PhysicallyBasedMaterialUserData))); if (nullptr == pUD) return false; //https://mcneel.myjetbrains.com/youtrack/issue/RH-68577 return pUD->m_parameters.base_color.IsValid(); //return nullptr != PhysicallyBased(); } std::shared_ptr ON_Material::PhysicallyBased(void) { auto ptr = std::make_shared(*this); if (nullptr != ptr && ON_PhysicallyBasedMaterial_Supported(*ptr)) { return ptr; } return nullptr; } const std::shared_ptr ON_Material::PhysicallyBased(void) const { auto ptr = std::make_shared(*this); if (nullptr != ptr && ON_PhysicallyBasedMaterial_Supported(*ptr)) { return ptr; } return nullptr; } void ON_Material::ToPhysicallyBased(void) { if (IsPhysicallyBased()) return; //This should always be valid. auto pbr = std::make_shared(*this); ON_ASSERT(pbr != nullptr); const bool bMetal = m_transparency < 0.01 && !m_bFresnelReflections && m_reflectivity > 0.99; const bool bGlass = m_transparency > 0.99; pbr->SetBaseColor(bMetal ? m_reflection : bGlass ? m_transparent : m_diffuse); pbr->SetMetallic(bMetal ? 1.0 : 0.0); pbr->SetRoughness(bMetal ? m_reflection_glossiness : 1.0 - m_reflectivity); pbr->SetOpacity(1.0 - m_transparency); pbr->SetOpacityIOR(m_index_of_refraction); ON_ASSERT(IsPhysicallyBased()); } const ON_PhysicallyBasedMaterial::Impl& ON_PhysicallyBasedMaterial::Implementation(void) const { return *reinterpret_cast(_impl); } ON_PhysicallyBasedMaterial::Impl& ON_PhysicallyBasedMaterial::Implementation(void) { return *reinterpret_cast(_impl); } bool ON_PhysicallyBasedMaterial_Supported(const ON_PhysicallyBasedMaterial& material) { if (!material.Implementation().UserDataExists()) return false; return material.BaseColor().IsValid(); } bool ON_PhysicallyBasedMaterial::UseBaseColorTextureAlphaForObjectAlphaTransparencyTexture() const { ON_Material& mat = *Implementation().material; return mat.UseDiffuseTextureAlphaForObjectTransparencyTexture(); } void ON_PhysicallyBasedMaterial::SetUseBaseColorTextureAlphaForObjectAlphaTransparencyTexture(bool b) { ON_Material& mat = *Implementation().material; mat.SetUseDiffuseTextureAlphaForObjectTransparencyTexture(b); } void ON_PhysicallyBasedMaterial::ToLegacy(void) { ON_Material& mat = *Implementation().material; Implementation().~Impl(); new (&_impl) Impl(const_cast(mat)); Implementation().RemoveUserData(); } void ON_PhysicallyBasedMaterial::SynchronizeLegacyMaterial(void) { auto& mat = *Implementation().material; const bool bIsMetal = Metallic() > 0.5; mat.SetDiffuse(bIsMetal ? ON_Color::Black : (ON_Color)BaseColor()); const double reflectivity = bIsMetal ? 1.0 : 1.0 - Roughness(); mat.SetFresnelReflections(!bIsMetal); mat.SetReflectivity(reflectivity); mat.SetTransparency(1.0 - Opacity()); mat.SetAmbient(ON_Color::Black); //Gloss mat.SetShine(ON_Material::MaxShine * reflectivity); if (bIsMetal) { mat.SetSpecular(BaseColor()); mat.m_reflection = BaseColor(); } else { const int bf = (int)(255.0 * reflectivity); mat.SetSpecular(ON_Color(bf, bf, bf)); mat.m_reflection = ON_Color::White; } mat.m_reflection_glossiness = Roughness(); mat.m_refraction_glossiness = OpacityRoughness(); mat.SetEmission(Emission()); mat.m_index_of_refraction = OpacityIOR(); //No need to do the textures, because the ones that are supported are in the right channels already. } int ON_PhysicallyBasedMaterial::FindTexture(const wchar_t* filename, ON_Texture::TYPE type, int i0) const { return Material().FindTexture(filename, type, i0); } int ON_PhysicallyBasedMaterial::AddTexture(const ON_Texture& tx) { return Material().AddTexture(tx); } int ON_PhysicallyBasedMaterial::AddTexture(const wchar_t* filename, ON_Texture::TYPE type) { return Material().AddTexture(filename, type); } int ON_PhysicallyBasedMaterial::DeleteTexture(const wchar_t* filename, ON_Texture::TYPE type) { return Material().DeleteTexture(filename, type); } ON_Material& ON_PhysicallyBasedMaterial::Material(void) { return *Implementation().material; } const ON_Material& ON_PhysicallyBasedMaterial::Material(void) const { return *Implementation().material; } ON_wString ON_PhysicallyBasedMaterial::ParametersNames::Anisotropic(void) { return ON_PBR_MATERIAL_ANISOTROPIC ; } ON_wString ON_PhysicallyBasedMaterial::ParametersNames::AnisotropicRotation(void) { return ON_PBR_MATERIAL_ANISOTROPIC_ROTATION ; } ON_wString ON_PhysicallyBasedMaterial::ParametersNames::BaseColor(void) { return ON_PBR_MATERIAL_BASE_COLOR ; } ON_wString ON_PhysicallyBasedMaterial::ParametersNames::BRDF(void) { return ON_PBR_MATERIAL_BRDF ; } ON_wString ON_PhysicallyBasedMaterial::ParametersNames::Clearcoat(void) { return ON_PBR_MATERIAL_CLEARCOAT ; } ON_wString ON_PhysicallyBasedMaterial::ParametersNames::ClearcoatBump(void) { return ON_PBR_MATERIAL_CLEARCOAT_BUMP ; } ON_wString ON_PhysicallyBasedMaterial::ParametersNames::ClearcoatRoughness(void) { return ON_PBR_MATERIAL_CLEARCOAT_ROUGHNESS ; } ON_wString ON_PhysicallyBasedMaterial::ParametersNames::Emission(void) { return ON_PBR_MATERIAL_EMISSION_COLOR ; } ON_wString ON_PhysicallyBasedMaterial::ParametersNames::Metallic(void) { return ON_PBR_MATERIAL_METALLIC ; } ON_wString ON_PhysicallyBasedMaterial::ParametersNames::Opacity(void) { return ON_PBR_MATERIAL_OPACITY ; } ON_wString ON_PhysicallyBasedMaterial::ParametersNames::OpacityIor(void) { return ON_PBR_MATERIAL_OPACITY_IOR ; } ON_wString ON_PhysicallyBasedMaterial::ParametersNames::OpacityRoughness(void) { return ON_PBR_MATERIAL_OPACITY_ROUGHNESS ; } ON_wString ON_PhysicallyBasedMaterial::ParametersNames::Roughness(void) { return ON_PBR_MATERIAL_ROUGHNESS ; } ON_wString ON_PhysicallyBasedMaterial::ParametersNames::Sheen(void) { return ON_PBR_MATERIAL_SHEEN ; } ON_wString ON_PhysicallyBasedMaterial::ParametersNames::SheenTint(void) { return ON_PBR_MATERIAL_SHEEN_TINT ; } ON_wString ON_PhysicallyBasedMaterial::ParametersNames::Specular(void) { return ON_PBR_MATERIAL_SPECULAR ; } ON_wString ON_PhysicallyBasedMaterial::ParametersNames::SpecularTint(void) { return ON_PBR_MATERIAL_SPECULAR_TINT ; } ON_wString ON_PhysicallyBasedMaterial::ParametersNames::Subsurface(void) { return ON_PBR_MATERIAL_SUBSURFACE ; } ON_wString ON_PhysicallyBasedMaterial::ParametersNames::SubsurfaceScatteringColor(void) { return ON_PBR_MATERIAL_SUBSURFACE_SCATTERING_COLOR ; } ON_wString ON_PhysicallyBasedMaterial::ParametersNames::SubsurfaceScatteringRadius(void) { return ON_PBR_MATERIAL_SUBSURFACE_SCATTERING_RADIUS; }