// // 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" #include "opennurbs_internal_defines.h" #if !defined(ON_COMPILING_OPENNURBS) // This check is included in all opennurbs source .c and .cpp files to insure // ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled. // When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined // and the opennurbs .h files alter what is declared and how it is declared. #error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs #endif // ON_Decal static ON_4dPoint UNSET_4D_POINT = ON_4dPoint(ON_UNSET_VALUE, ON_UNSET_VALUE, ON_UNSET_VALUE, ON_UNSET_VALUE); ON_DECAL_CRC ON_DecalCRCFromNode(const ON_XMLNode& node) { return ON_Decal::ComputeDecalCRC(0, node); } static ON_XMLNode* FindDecalNodeByCRC(const ON_XMLNode& decals_node, ON_DECAL_CRC decal_crc) { auto it = decals_node.GetChildIterator(); ON_XMLNode* decal_node = nullptr; while (nullptr != (decal_node = it.GetNextChild())) { if (decal_crc == ON_DecalCRCFromNode(*decal_node)) break; } return decal_node; } static double PrincipalValueAngleRad(double angleRad) { // Returns the given angle modulo 2 * PI. if (angleRad >= ON_2PI) { angleRad = angleRad - floor(angleRad / ON_2PI) * ON_2PI; } else if (angleRad <= 0.0) { angleRad = angleRad + ceil(-angleRad / ON_2PI) * ON_2PI; } if (angleRad < 1e-12) angleRad = 0.0; if (angleRad > ON_2PI) angleRad = ON_2PI; return angleRad; } static void UpdateValuesForTransformation( const ON_Xform& xform, ON_Decal::Mappings mapping, double& radius, double& height, ON_3dPoint& ptOrigin, ON_3dVector& vecUp, ON_3dVector& vecAcross) { ptOrigin.Transform(xform); vecUp.Transform(xform); vecAcross.Transform(xform); ON_ASSERT(ON_Decal::Mappings::UV != mapping); if ((ON_Decal::Mappings::Cylindrical == mapping) || (ON_Decal::Mappings::Spherical == mapping)) { // If spherical we assume uniform scaling so as to preserve the spherical shape. // If cylindrical we assume uniform scaling in x and y so as to preserve the cylindrical shape. const double radius_scale = abs(xform[0][0]); radius *= radius_scale; if (ON_Decal::Mappings::Cylindrical == mapping) { // Transform the height. The unit cylinder stands up with height along the z-axis. ON_3dVector vecHeight = ON_3dVector::ZAxis; vecHeight.Transform(xform); height *= vecHeight.Length(); } vecUp.Unitize(); vecAcross.Unitize(); } } static ON_Decal::Mappings MappingFromString(const ON_wString& s) { if (s == ON_RDK_DECAL_MAPPING_PLANAR) return ON_Decal::Mappings::Planar; else if (s == ON_RDK_DECAL_MAPPING_SPHERICAL) return ON_Decal::Mappings::Spherical; else if (s == ON_RDK_DECAL_MAPPING_CYLINDRICAL) return ON_Decal::Mappings::Cylindrical; else if (s == ON_RDK_DECAL_MAPPING_UV) return ON_Decal::Mappings::UV; else if (s == ON_RDK_DECAL_MAPPING_NONE) return ON_Decal::Mappings::None; ON_ASSERT(false); return ON_Decal::Mappings::None; } class ON_Decal::CImpl : public ON_InternalXMLImpl { public: CImpl(); CImpl(ON_DecalCollection* dc, ON_XMLNode& node); CImpl(ON_DecalCollection* dc, const ON_XMLNode& node); ON_UUID TextureInstanceId(void) const; void SetTextureInstanceId(const ON_UUID& id); Mappings Mapping(void) const; void SetMapping(Mappings v); Projections Projection(void) const; void SetProjection(Projections v); bool MapToInside(void) const; void SetMapToInside(bool v); double Transparency(void) const; void SetTransparency(double v); ON_3dPoint Origin(void) const; void SetOrigin(const ON_3dPoint& v); ON_3dVector VectorUp(void) const; void SetVectorUp(const ON_3dVector& v); ON_3dVector VectorAcross(void) const; void SetVectorAcross(const ON_3dVector& v); double Height(void) const; void SetHeight(double d); double Radius(void) const; void SetRadius(double d); void GetSavedVectorLengths(double& across, double& up) const; void SetSavedVectorLengths(double across, double up); bool IsVisible(void) const; void SetIsVisible(bool b); bool IsTemporary(void) const; void SetIsTemporary(bool b); void GetHorzSweep(double& sta, double& end) const; void SetHorzSweep(double sta, double end); void GetVertSweep(double& sta, double& end) const; void SetVertSweep(double sta, double end); void GetUVBounds(double& min_u, double& min_v, double& max_u, double& max_v) const; void SetUVBounds(double min_u, double min_v, double max_u, double max_v); ON_UUID Id(void) const { return _decal_id; } ON_XMLVariant GetParameter(const wchar_t* param_name, const ON_XMLVariant& def) const; void SetParameter(const wchar_t* param_name, const ON_XMLVariant& value); ON_XMLNode* FindCustomNodeForRenderEngine(const ON_UUID& renderEngineId) const; bool GetTextureMapping(ON_TextureMapping& mappingOut) const; void SetCollectionChanged(void); bool HitTest(const ON_3dPoint& point, const ON_3dVector& normal, ON_2dPoint& uvOut) const; void ApplyTransformation(const ON_Xform& xform); virtual ON_wString NameOfRootNode(void) const override { return ON_RDK_DECAL; } ON_3dVector GetOriginOffset(void) const; static const int unset_bool = 2; mutable struct Cache { double radius = ON_UNSET_VALUE; double height = ON_UNSET_VALUE; double transparency = ON_UNSET_VALUE; ON_UUID texture_instance_id = ON_nil_uuid; ON_3dPoint origin = ON_3dPoint ::UnsetPoint; ON_3dVector vector_up = ON_3dVector::UnsetVector; ON_3dVector vector_across = ON_3dVector::UnsetVector; ON_2dPoint horz_sweep = ON_2dPoint ::UnsetPoint; ON_2dPoint vert_sweep = ON_2dPoint ::UnsetPoint; ON_4dPoint uv_bounds = UNSET_4D_POINT; Mappings mapping = Mappings::None; Projections projection = Projections::None; bool texture_instance_id_set = false; int visible = unset_bool; int temporary = unset_bool; int map_to_inside = unset_bool; } _cache; ON_DecalCollection* _collection = nullptr; ON_UUID _decal_id; bool _read_only = false; bool _cache_only = false; private: bool HitTestUV(const ON_3dPoint& point, ON_3dPoint& uvwOut) const; bool HitTestPlanar(const ON_3dVector& normal, const ON_3dPoint& point, ON_3dPoint& uvwOut) const; bool HitTestCylindrical(const ON_3dVector& normal, const ON_3dPoint& point, ON_3dPoint& uvwOut) const; bool HitTestSpherical(const ON_3dVector& normal, const ON_3dPoint& point, ON_3dPoint& uvwOut) const; bool Writeable(void) const { ON_ASSERT(!_read_only); return !_read_only; } }; ON_Decal::CImpl::CImpl() { ON_CreateUuid(_decal_id); } ON_Decal::CImpl::CImpl(ON_DecalCollection* dc, ON_XMLNode& node) : _collection(dc), ON_InternalXMLImpl(&node) { ON_CreateUuid(_decal_id); } ON_Decal::CImpl::CImpl(ON_DecalCollection* dc, const ON_XMLNode& node) : _collection(dc), _read_only(true), ON_InternalXMLImpl(&const_cast(node)) { ON_CreateUuid(_decal_id); } ON_XMLVariant ON_Decal::CImpl::GetParameter(const wchar_t* param_name, const ON_XMLVariant& def) const { return ON_InternalXMLImpl::GetParameter(L"", param_name, def); } void ON_Decal::CImpl::SetParameter(const wchar_t* param_name, const ON_XMLVariant& value) { ON_ASSERT(!_cache_only); ON_ASSERT(!_read_only); ON_InternalXMLImpl::SetParameter(L"", param_name, value); } ON_UUID ON_Decal::CImpl::TextureInstanceId(void) const { if (!_cache.texture_instance_id_set) { _cache.texture_instance_id = GetParameter(ON_RDK_DECAL_TEXTURE_INSTANCE, ON_nil_uuid).AsUuid(); _cache.texture_instance_id_set = true; } return _cache.texture_instance_id; } void ON_Decal::CImpl::SetTextureInstanceId(const ON_UUID& id) { if (!Writeable()) return; if (!_cache.texture_instance_id_set || (_cache.texture_instance_id != id)) { _cache.texture_instance_id = id; _cache.texture_instance_id_set = true; if (!_cache_only) { SetParameter(ON_RDK_DECAL_TEXTURE_INSTANCE, id); } SetCollectionChanged(); } } ON_Decal::Mappings ON_Decal::CImpl::Mapping(void) const { if (Mappings::None == _cache.mapping) { const ON_wString s = GetParameter(ON_RDK_DECAL_MAPPING, ON_RDK_DECAL_MAPPING_UV).AsString(); _cache.mapping = MappingFromString(s); } return _cache.mapping; } void ON_Decal::CImpl::SetMapping(Mappings m) { if (!Writeable()) return; if (_cache.mapping != m) { _cache.mapping = m; const wchar_t* s = L""; if (!_cache_only) { switch (m) { default: ON_ASSERT(false); case Mappings::None: s = ON_RDK_DECAL_MAPPING_NONE; break; case Mappings::Planar: s = ON_RDK_DECAL_MAPPING_PLANAR; break; case Mappings::Spherical: s = ON_RDK_DECAL_MAPPING_SPHERICAL; break; case Mappings::Cylindrical: s = ON_RDK_DECAL_MAPPING_CYLINDRICAL; break; case Mappings::UV: s = ON_RDK_DECAL_MAPPING_UV; break; } SetParameter(ON_RDK_DECAL_MAPPING, s); } SetCollectionChanged(); } } ON_Decal::Projections ON_Decal::CImpl::Projection(void) const { if (Projections::None == _cache.projection) { const ON_wString s = GetParameter(ON_RDK_DECAL_PROJECTION, ON_RDK_DECAL_PROJECTION_NONE).AsString(); if (s == ON_RDK_DECAL_PROJECTION_FORWARD) _cache.projection = ON_Decal::Projections::Forward; else if (s == ON_RDK_DECAL_PROJECTION_BACKWARD) _cache.projection = ON_Decal::Projections::Backward; else if (s == ON_RDK_DECAL_PROJECTION_BOTH) _cache.projection = ON_Decal::Projections::Both; else if (s == ON_RDK_DECAL_PROJECTION_NONE) _cache.projection = ON_Decal::Projections::None; else ON_ASSERT(false); } return _cache.projection; } void ON_Decal::CImpl::SetProjection(Projections v) { if (!Writeable()) return; if (_cache.projection != v) { _cache.projection = v; const wchar_t* s = L""; if (!_cache_only) { switch (v) { default: ON_ASSERT(false); case ON_Decal::Projections::None: s = ON_RDK_DECAL_PROJECTION_NONE; break; case ON_Decal::Projections::Forward: s = ON_RDK_DECAL_PROJECTION_FORWARD; break; case ON_Decal::Projections::Backward: s = ON_RDK_DECAL_PROJECTION_BACKWARD; break; case ON_Decal::Projections::Both: s = ON_RDK_DECAL_PROJECTION_BOTH; break; } SetParameter(ON_RDK_DECAL_PROJECTION, s); } SetCollectionChanged(); } } bool ON_Decal::CImpl::MapToInside(void) const { if (unset_bool == _cache.map_to_inside) { _cache.map_to_inside = GetParameter(ON_RDK_DECAL_MAP_TO_INSIDE_ON, false).AsBool() ? 1 : 0; } return 0 != _cache.map_to_inside; } void ON_Decal::CImpl::SetMapToInside(bool b) { if (!Writeable()) return; const int i = b ? 1 : 0; if (_cache.map_to_inside != i) { _cache.map_to_inside = i; if (!_cache_only) { SetParameter(ON_RDK_DECAL_MAP_TO_INSIDE_ON, b); } SetCollectionChanged(); } } double ON_Decal::CImpl::Transparency(void) const { if (ON_UNSET_VALUE == _cache.transparency) { _cache.transparency = GetParameter(ON_RDK_DECAL_TRANSPARENCY, 0.0).AsDouble(); } return _cache.transparency; } void ON_Decal::CImpl::SetTransparency(double v) { if (!Writeable()) return; if (_cache.transparency != v) { _cache.transparency = v; if (!_cache_only) { SetParameter(ON_RDK_DECAL_TRANSPARENCY, v); } SetCollectionChanged(); } } ON_3dPoint ON_Decal::CImpl::Origin(void) const { if (ON_3dPoint::UnsetPoint == _cache.origin) { _cache.origin = GetParameter(ON_RDK_DECAL_ORIGIN, ON_3dPoint::Origin).As3dPoint(); } return _cache.origin; } void ON_Decal::CImpl::SetOrigin(const ON_3dPoint& pt) { if (!Writeable()) return; if (_cache.origin != pt) { _cache.origin = pt; if (!_cache_only) { SetParameter(ON_RDK_DECAL_ORIGIN, pt); } SetCollectionChanged(); } } ON_3dVector ON_Decal::CImpl::VectorUp(void) const { if (ON_3dVector::UnsetVector == _cache.vector_up) { _cache.vector_up = GetParameter(ON_RDK_DECAL_VECTOR_UP, ON_3dPoint::Origin).As3dPoint(); } return _cache.vector_up; } void ON_Decal::CImpl::SetVectorUp(const ON_3dVector& v) { if (!Writeable()) return; if (_cache.vector_up != v) { _cache.vector_up = v; if (!_cache_only) { SetParameter(ON_RDK_DECAL_VECTOR_UP, ON_3dPoint(v)); } SetCollectionChanged(); } } ON_3dVector ON_Decal::CImpl::VectorAcross(void) const { if (ON_3dVector::UnsetVector == _cache.vector_across) { _cache.vector_across = GetParameter(ON_RDK_DECAL_VECTOR_ACROSS, ON_3dPoint::Origin).As3dPoint(); } return _cache.vector_across; } void ON_Decal::CImpl::SetVectorAcross(const ON_3dVector& v) { if (!Writeable()) return; if (_cache.vector_across != v) { _cache.vector_across = v; if (!_cache_only) { SetParameter(ON_RDK_DECAL_VECTOR_ACROSS, ON_3dPoint(v)); } SetCollectionChanged(); } } double ON_Decal::CImpl::Height(void) const { if (ON_UNSET_VALUE == _cache.height) { _cache.height = GetParameter(ON_RDK_DECAL_HEIGHT, 1.0).AsDouble(); } return _cache.height; } void ON_Decal::CImpl::SetHeight(double v) { if (!Writeable()) return; if (_cache.height != v) { _cache.height = v; if (!_cache_only) { SetParameter(ON_RDK_DECAL_HEIGHT, v); } SetCollectionChanged(); } } double ON_Decal::CImpl::Radius(void) const { if (ON_UNSET_VALUE == _cache.radius) { _cache.radius = GetParameter(ON_RDK_DECAL_RADIUS, 1.0).AsDouble(); } return _cache.radius; } void ON_Decal::CImpl::SetRadius(double v) { if (!Writeable()) return; if (_cache.radius != v) { _cache.radius = v; if (!_cache_only) { SetParameter(ON_RDK_DECAL_RADIUS, v); } SetCollectionChanged(); } } void ON_Decal::CImpl::GetSavedVectorLengths(double& across, double& up) const { across = GetParameter(ON_RDK_DECAL_SAVED_VECTOR_LENGTH_ACROSS, 0.0); up = GetParameter(ON_RDK_DECAL_SAVED_VECTOR_LENGTH_UP, 0.0); } void ON_Decal::CImpl::SetSavedVectorLengths(double across, double up) { SetParameter(ON_RDK_DECAL_SAVED_VECTOR_LENGTH_ACROSS, across); SetParameter(ON_RDK_DECAL_SAVED_VECTOR_LENGTH_UP, up); } ON_3dVector ON_Decal::CImpl::GetOriginOffset(void) const { ON_3dVector vec = { }; vec.x = 0.5 * VectorAcross().Length(); vec.y = 0.5 * VectorUp().Length(); return vec; } bool ON_Decal::CImpl::IsVisible(void) const { if (unset_bool == _cache.visible) { _cache.visible = GetParameter(ON_RDK_DECAL_IS_VISIBLE, true).AsBool(); } return 0 != _cache.visible; } void ON_Decal::CImpl::SetIsVisible(bool b) { if (!Writeable()) return; const int i = b ? 1 : 0; if (_cache.visible != i) { _cache.visible = i; if (!_cache_only) { SetParameter(ON_RDK_DECAL_IS_VISIBLE, b); } SetCollectionChanged(); } } bool ON_Decal::CImpl::IsTemporary(void) const { if (unset_bool == _cache.temporary) { _cache.temporary = GetParameter(ON_RDK_DECAL_IS_TEMPORARY, false).AsBool(); } return 0 != _cache.temporary; } void ON_Decal::CImpl::SetIsTemporary(bool b) { if (!Writeable()) return; const int i = b ? 1 : 0; if (_cache.temporary != i) { _cache.temporary = i; if (!_cache_only) { SetParameter(ON_RDK_DECAL_IS_TEMPORARY, b); } SetCollectionChanged(); } } void ON_Decal::CImpl::GetHorzSweep(double& sta, double& end) const { if (ON_2dPoint::UnsetPoint == _cache.horz_sweep) { _cache.horz_sweep.x = GetParameter(ON_RDK_DECAL_HORZ_SWEEP_STA, 0.0).AsDouble(); _cache.horz_sweep.y = GetParameter(ON_RDK_DECAL_HORZ_SWEEP_END, 0.0).AsDouble(); } sta = _cache.horz_sweep.x; end = _cache.horz_sweep.y; } void ON_Decal::CImpl::SetHorzSweep(double sta, double end) { if (!Writeable()) return; const ON_2dPoint sweep = ON_2dPoint(sta, end); if (_cache.horz_sweep != sweep) { _cache.horz_sweep = sweep; if (!_cache_only) { SetParameter(ON_RDK_DECAL_HORZ_SWEEP_STA, sta); SetParameter(ON_RDK_DECAL_HORZ_SWEEP_END, end); } SetCollectionChanged(); } } void ON_Decal::CImpl::GetVertSweep(double& sta, double& end) const { if (ON_2dPoint::UnsetPoint == _cache.vert_sweep) { _cache.vert_sweep.x = GetParameter(ON_RDK_DECAL_VERT_SWEEP_STA, 0.0).AsDouble(); _cache.vert_sweep.y = GetParameter(ON_RDK_DECAL_VERT_SWEEP_END, 0.0).AsDouble(); } sta = _cache.vert_sweep.x; end = _cache.vert_sweep.y; } void ON_Decal::CImpl::SetVertSweep(double sta, double end) { if (!Writeable()) return; const ON_2dPoint sweep = ON_2dPoint(sta, end); if (_cache.vert_sweep != sweep) { _cache.vert_sweep = sweep; if (!_cache_only) { SetParameter(ON_RDK_DECAL_VERT_SWEEP_STA, sta); SetParameter(ON_RDK_DECAL_VERT_SWEEP_END, end); } SetCollectionChanged(); } } void ON_Decal::CImpl::GetUVBounds(double& min_u, double& min_v, double& max_u, double& max_v) const { if (UNSET_4D_POINT == _cache.uv_bounds) { _cache.uv_bounds.x = GetParameter(ON_RDK_DECAL_MIN_U, 0.0).AsDouble(); _cache.uv_bounds.y = GetParameter(ON_RDK_DECAL_MIN_V, 0.0).AsDouble(); _cache.uv_bounds.z = GetParameter(ON_RDK_DECAL_MAX_U, 1.0).AsDouble(); _cache.uv_bounds.w = GetParameter(ON_RDK_DECAL_MAX_V, 1.0).AsDouble(); } min_u = _cache.uv_bounds.x; min_v = _cache.uv_bounds.y; max_u = _cache.uv_bounds.z; max_v = _cache.uv_bounds.w; } void ON_Decal::CImpl::SetUVBounds(double min_u, double min_v, double max_u, double max_v) { if (!Writeable()) return; const ON_4dPoint bounds = ON_4dPoint(min_u, min_v, max_u, max_v); if (_cache.uv_bounds != bounds) { _cache.uv_bounds = bounds; if (!_cache_only) { SetParameter(ON_RDK_DECAL_MIN_U, min_u); SetParameter(ON_RDK_DECAL_MIN_V, min_v); SetParameter(ON_RDK_DECAL_MAX_U, max_u); SetParameter(ON_RDK_DECAL_MAX_V, max_v); } SetCollectionChanged(); } } ON_XMLNode* ON_Decal::CImpl::FindCustomNodeForRenderEngine(const ON_UUID& renderEngineId) const { ON_XMLNode* child_node = nullptr; auto it = Node().GetChildIterator(); while (nullptr != (child_node = it.GetNextChild())) { if (child_node->TagName() == ON_RDK_DECAL_CUSTOM) { if (ON_UuidIsNil(renderEngineId)) return child_node; const ON_XMLProperty* prop = child_node->GetNamedProperty(ON_RDK_DECAL_CUSTOM_RENDERER); if ((nullptr != prop) && (prop->GetValue().AsUuid() == renderEngineId)) return child_node; } } return nullptr; } bool ON_Decal::CImpl::GetTextureMapping(ON_TextureMapping& mappingOut) const { using M = ON_Decal::Mappings; switch (Mapping()) { case M::Cylindrical: { // Orthogonal vectors in the end plane of cylinder. const ON_3dVector vecPlaneXAxis = VectorAcross(); const ON_3dVector vecPlaneYAxis = ON_CrossProduct(VectorAcross(), -VectorUp()); // Center for the end of the cylinder. const ON_3dPoint ptCylinderEndCenter = Origin() - VectorUp() * Height() * 0.5; // Plane for the end of the cylinder. ON_Plane plane(ptCylinderEndCenter, vecPlaneXAxis, vecPlaneYAxis); // Circle for the end of the cylinder. ON_Circle circle(plane, Radius()); // The cylinder itself.. ON_Cylinder cylinder(circle, Height()); // Cylindrical mapping without caps. mappingOut.SetCylinderMapping(cylinder, false); return true; } case M::Spherical: { // Orthogonal vectors in the equatorial plane. const ON_3dVector vecPlaneXAxis = VectorAcross(); const ON_3dVector vecPlaneYAxis = ON_CrossProduct(VectorAcross(), -VectorUp()); // Equatorial plane. ON_Plane plane(Origin(), vecPlaneXAxis, vecPlaneYAxis); // The sphere itself. ON_Sphere sphere; sphere.plane = plane; sphere.radius = Radius(); // Spherical mapping. mappingOut.SetSphereMapping(sphere); return true; } case M::Planar: { const ON_3dVector vecAcross = VectorAcross(); const ON_3dVector vecUp = VectorUp(); const ON_Plane plane(Origin(), vecAcross, vecUp); const ON_Interval xInterval(0.0, vecAcross.Length()); const ON_Interval yInterval(0.0, vecUp.Length()); const ON_Interval zInterval(0.0, 1.0); mappingOut.SetPlaneMapping(plane, xInterval, yInterval, zInterval); return true; } default: break; } return false; } void ON_Decal::CImpl::SetCollectionChanged(void) { if (nullptr != _collection) { _collection->SetChanged(); } } bool ON_Decal::CImpl::HitTest(const ON_3dPoint& point, const ON_3dVector& normal, ON_2dPoint& uvOut) const { bool hit = false; ON_3dPoint uvw(0.0, 0.0, 0.0); switch (Mapping()) { case Mappings::UV: hit = HitTestUV ( point, uvw); break; case Mappings::Planar: hit = HitTestPlanar (normal, point, uvw); break; case Mappings::Cylindrical: hit = HitTestCylindrical(normal, point, uvw); break; case Mappings::Spherical: hit = HitTestSpherical (normal, point, uvw); break; default: break; } if (hit) { uvOut.x = uvw.x; uvOut.y = uvw.y; } return hit; } void ON_Decal::CImpl::ApplyTransformation(const ON_Xform& xform) { if (!Writeable()) return; if (xform.IsIdentity()) return; ON_3dPoint origin = Origin(); ON_3dVector up = VectorUp(); ON_3dVector across = VectorAcross(); double radius = 0.0, height = 0.0; const ON_Decal::Mappings mapping = Mapping(); if ((Mappings::Cylindrical == mapping) || (Mappings::Spherical == mapping)) { radius = Radius(); if (Mappings::Cylindrical == mapping) { height = Height(); } } UpdateValuesForTransformation(xform, mapping, radius, height, origin, up, across); SetOrigin(origin); SetVectorUp(up); SetVectorAcross(across); if ((Mappings::Cylindrical == mapping) || (Mappings::Spherical == mapping)) { SetRadius(radius); if (Mappings::Cylindrical == mapping) { SetHeight(height); } } } bool ON_Decal::CImpl::HitTestUV(const ON_3dPoint& point, ON_3dPoint& uvwOut) const { double minU = 0.0, maxU = 1.0, minV = 0.0, maxV = 1.0; GetUVBounds(minU, minV, maxU, maxV); if ((minU <= point.x) && (maxU >= point.x) && (minV <= point.y) && (maxV >= point.y)) { uvwOut.x = (point.x - minU) / (maxU - minU); uvwOut.y = (point.y - minV) / (maxV - minV); return true; } return false; } bool ON_Decal::CImpl::HitTestPlanar(const ON_3dVector& normal, const ON_3dPoint& point, ON_3dPoint& uvwOut) const { const ON_3dVector vecAcross = VectorAcross(); const ON_3dVector vecUp = VectorUp(); const ON_Decal::Projections projection = Projection(); const ON_3dVector vecCrossProduct = ON_CrossProduct(vecAcross, vecUp); const double dotProduct = ON_DotProduct(vecCrossProduct, normal); if ((projection == ON_Decal::Projections::Forward) && (dotProduct < 0)) return false; if ((projection == ON_Decal::Projections::Backward) && (dotProduct > 0)) return false; if (dotProduct == 0.0) return false; // Map world coordinates to uvw. ON_TextureMapping m; GetTextureMapping(m); m.Evaluate(point, normal, &uvwOut); if ((uvwOut.x >= 0.0) && (uvwOut.x <= 1.0) && (uvwOut.y >= 0.0) && (uvwOut.y <= 1.0)) return true; return false; } bool ON_Decal::CImpl::HitTestCylindrical(const ON_3dVector& normal, const ON_3dPoint& point, ON_3dPoint& uvwOut) const { // Check that the face normal is pointing to correct direction. const double faceNormalDotOriginDirection = ON_DotProduct(normal, point - Origin()); const bool bInside = faceNormalDotOriginDirection < 0.0; const bool bMapToInside = MapToInside(); if (bInside && !bMapToInside) return false; if (!bInside && bMapToInside) return false; // Map world coordinates to uvw. ON_TextureMapping m; GetTextureMapping(m); m.Evaluate(point, normal, &uvwOut); // Map u coordinate to longitude. const double point_horz = uvwOut.x * ON_2PI; // Check if decal gets hit. double horz_sta = 0.0, horz_end = 0.0; GetHorzSweep(horz_sta, horz_end); // Longitude relative to decal start longitude. const double relative_sta_horz = 0.0; double relative_end_horz = PrincipalValueAngleRad(horz_end - horz_sta); const double relative_point_horz = PrincipalValueAngleRad(point_horz - horz_sta); // If end and start longitudes have same value then decal is supposed to go around the cylinder. if (relative_end_horz == 0.0) relative_end_horz = ON_2PI; if ((relative_sta_horz <= relative_point_horz) && (relative_end_horz >= relative_point_horz)) { // Scale longitude to texture u-coordinate uvwOut.x = relative_point_horz / relative_end_horz; if (uvwOut.x >= 0.0 && uvwOut.x <= 1.0 && uvwOut.y >= 0.0 && uvwOut.y <= 1.0) return true; } return false; } bool ON_Decal::CImpl::HitTestSpherical(const ON_3dVector& normal, const ON_3dPoint& point, ON_3dPoint& uvwOut) const { // Check that the face normal is pointing to correct direction. const double faceNormalDotOriginDirection = ON_DotProduct(normal, point - Origin()); const bool bInside = faceNormalDotOriginDirection < 0.0; const bool bMapToInside = MapToInside(); if (bInside && !bMapToInside) return false; if (!bInside && bMapToInside) return false; // Map world coordinates to uvw. ON_TextureMapping m; GetTextureMapping(m); m.Evaluate(point, normal, &uvwOut); double horz_sta = 0.0, horz_end = 0.0; GetHorzSweep(horz_sta, horz_end); // Map u coordinate to longitude. const double point_horz = uvwOut.x * ON_2PI; // Longitudes (horz) relative to decal start horz. const double relative_horz_sta = 0.0; double relative_horz_end = PrincipalValueAngleRad(horz_end - horz_sta); const double relative_point_horz = PrincipalValueAngleRad(point_horz - horz_sta); // If end and start horz have same value then decal is supposed to go full circle around the vertical axis. if (relative_horz_end == 0.0) relative_horz_end = ON_2PI; double vert_sta = 0.0, vert_end = 0.0; GetVertSweep(vert_sta, vert_end); const double sta = vert_sta / ON_PI + 0.5; const double end = vert_end / ON_PI + 0.5; // If the mapped texture coordinate is contained in the decal area we have to do some more calculations. if ((relative_horz_sta <= relative_point_horz) && (relative_horz_end >= relative_point_horz) && ((sta <= uvwOut.y) && (end >= uvwOut.y))) { // These are the texture coordinates inside the decal area. const double uDecal = relative_point_horz / relative_horz_end; const double vDecal = (uvwOut.y - sta) / (end - sta); // Now we should have (uDecal,vDecal) in [0,1]x[0,1]. // We have to store the correct calculated values of u and v to uvwOut. uvwOut.x = uDecal; uvwOut.y = vDecal; // Decal is hit. return true; } return false; } ON_Decal::ON_Decal() { _private = new CImpl; } ON_Decal::ON_Decal(ON_XMLNode& node) { ON_ASSERT(node.TagName() == ON_RDK_DECAL); _private = new CImpl(nullptr, node); } ON_Decal::ON_Decal(const ON_XMLNode& node) { ON_ASSERT(node.TagName() == ON_RDK_DECAL); _private = new CImpl(nullptr, node); } ON_Decal::ON_Decal(ON_DecalCollection& coll, ON_XMLNode& node) { ON_ASSERT(node.TagName() == ON_RDK_DECAL); _private = new CImpl(&coll, node); } ON_Decal::ON_Decal(const ON_Decal& other) { _private = new CImpl; operator = (other); } ON_Decal::ON_Decal(ON_DecalCollection& coll, const ON_Decal& other) { _private = new CImpl; _private->_collection = &coll; operator = (other); } ON_Decal::~ON_Decal() { delete _private; _private = nullptr; } const ON_Decal& ON_Decal::operator = (const ON_Decal& d) { if (this != &d) { // Copy the node even if _cache_only is true; otherwise the object won't be made truly equal. _private->Node() = d._private->Node(); _private->_cache = d._private->_cache; } return *this; } bool ON_Decal::operator == (const ON_Decal& d) const { // This only checks if the basic parameters are equal. It ignores any custom data. if (TextureInstanceId() != d.TextureInstanceId()) return false; if (Mapping() != d.Mapping()) return false; if (Projection() != d.Projection()) return false; if (MapToInside() != d.MapToInside()) return false; if (Transparency() != d.Transparency()) return false; if (Origin() != d.Origin()) return false; if (VectorUp() != d.VectorUp()) return false; if (VectorAcross() != d.VectorAcross()) return false; if (Height() != d.Height()) return false; if (Radius() != d.Radius()) return false; double sta1 = 0.0, end1 = 0.0, sta2 = 0.0, end2 = 0.0; GetHorzSweep(sta1, end1); d.GetHorzSweep(sta2, end2); if (!IsDoubleEqual(sta1, sta2)) return false; if (!IsDoubleEqual(end1, end2)) return false; GetVertSweep(sta1, end1); d.GetVertSweep(sta2, end2); if (!IsDoubleEqual(sta1, sta2)) return false; if (!IsDoubleEqual(end1, end2)) return false; double min_u1 = 0.0, min_v1 = 0.0, max_u1 = 0.0, max_v1 = 0.0; double min_u2 = 0.0, min_v2 = 0.0, max_u2 = 0.0, max_v2 = 0.0; GetUVBounds(min_u1, min_v1, max_u1, max_v1); d.GetUVBounds(min_u2, min_v2, max_u2, max_v2); if (!IsDoubleEqual(min_u1, min_u2)) return false; if (!IsDoubleEqual(min_v1, min_v2)) return false; if (!IsDoubleEqual(max_u1, max_u2)) return false; if (!IsDoubleEqual(max_v1, max_v2)) return false; return true; } bool ON_Decal::operator != (const ON_Decal& d) const { return !(operator == (d)); } void ON_Decal::SetCacheOnly(void) { _private->_cache_only = true; } ON_UUID ON_Decal::TextureInstanceId(void) const { return _private->TextureInstanceId(); } void ON_Decal::SetTextureInstanceId(const ON_UUID& id) { _private->SetTextureInstanceId(id); } ON_Decal::Mappings ON_Decal::Mapping(void) const { return _private->Mapping(); } void ON_Decal::SetMapping(Mappings m) { _private->SetMapping(m); } ON_Decal::Projections ON_Decal::Projection(void) const { return _private->Projection(); } void ON_Decal::SetProjection(Projections p) { _private->SetProjection(p); } bool ON_Decal::MapToInside(void) const { return _private->MapToInside(); } void ON_Decal::SetMapToInside(bool b) { _private->SetMapToInside(b); } double ON_Decal::Transparency(void) const { return _private->Transparency(); } void ON_Decal::SetTransparency(double d) { _private->SetTransparency(d); } ON_3dPoint ON_Decal::Origin(void) const { return _private->Origin(); } void ON_Decal::SetOrigin(const ON_3dPoint& pt) { _private->SetOrigin(pt); } ON_3dVector ON_Decal::VectorUp(void) const { return _private->VectorUp(); } void ON_Decal::SetVectorUp(const ON_3dVector& vec) { _private->SetVectorUp(vec); } ON_3dVector ON_Decal::VectorAcross(void) const { return _private->VectorAcross(); } void ON_Decal::SetVectorAcross(const ON_3dVector& vec) { _private->SetVectorAcross(vec); } double ON_Decal::Height(void) const { return _private->Height(); } void ON_Decal::SetHeight(double d) { _private->SetHeight(d); } double ON_Decal::Radius(void) const { return _private->Radius(); } void ON_Decal::SetRadius(double d) { _private->SetRadius(d); } void ON_Decal::GetHorzSweep(double& sta, double& end) const { _private->GetHorzSweep(sta, end); } void ON_Decal::SetHorzSweep(double sta, double end) { _private->SetHorzSweep(sta, end); } void ON_Decal::GetVertSweep(double& sta, double& end) const { _private->GetVertSweep(sta, end); } void ON_Decal::SetVertSweep(double sta, double end) { _private->SetVertSweep(sta, end); } void ON_Decal::GetUVBounds(double& min_u, double& min_v, double& max_u, double& max_v) const { _private->GetUVBounds(min_u, min_v, max_u, max_v); } void ON_Decal::SetUVBounds(double min_u, double min_v, double max_u, double max_v) { _private->SetUVBounds(min_u, min_v, max_u, max_v); } bool ON_Decal::IsVisible(void) const { return _private->IsVisible(); } void ON_Decal::SetIsVisible(bool b) { _private->SetIsVisible(b); } bool ON_Decal::IsTemporary(void) const { return _private->IsTemporary(); } void ON_Decal::SetIsTemporary(bool b) { _private->SetIsTemporary(b); } ON_UUID ON_Decal::Id(void) const { return _private->Id(); } // Copied from IRhRdkDecal::GetTextureMapping -- TODO: Refactor. [JOHN-DECAL-FIX] bool ON_Decal::GetTextureMapping(ON_TextureMapping& mappingOut) const { return _private->GetTextureMapping(mappingOut); } ON_DECAL_CRC ON_Decal::DecalCRC(void) const { return ComputeDecalCRC(0, _private->Node()); } ON__UINT32 ON_Decal::DataCRC(ON__UINT32 current_remainder) const { return ComputeDecalCRC(current_remainder, _private->Node()); } void ON_Decal::GetCustomXML(const ON_UUID& renderEngineId, ON_XMLNode& custom_param_node) const { custom_param_node.Clear(); custom_param_node.SetTagName(ON_RDK_DECAL_CUSTOM_PARAMS); // Find the node for 'renderEngineId'. const ON_XMLNode* custom_node = _private->FindCustomNodeForRenderEngine(renderEngineId); if (nullptr != custom_node) { // Get the parameter node and copy it to 'custom_param_node'. const ON_XMLNode* param_node = custom_node->GetNamedChild(ON_RDK_DECAL_CUSTOM_PARAMS); if (nullptr != param_node) { custom_param_node = *param_node; } } } bool ON_Decal::SetCustomXML(const ON_UUID& renderEngineId, const ON_XMLNode& custom_param_node) { if (custom_param_node.TagName() != ON_RDK_DECAL_CUSTOM_PARAMS) return false; // If there is already a custom node for 'renderEngineId' then delete it. ON_XMLNode* custom_node = _private->FindCustomNodeForRenderEngine(renderEngineId); if (nullptr != custom_node) { ON_XMLNode* parent = custom_node->Parent(); if (nullptr != parent) { delete parent->DetachChild(*custom_node); } } // Attach the new custom node and set its 'renderer' property to be the render engine id. custom_node = _private->Node().AttachChildNode(new ON_XMLNode(ON_RDK_DECAL_CUSTOM)); ON_XMLProperty prop(ON_RDK_DECAL_CUSTOM_RENDERER, renderEngineId); custom_node->SetProperty(prop); // Attach a copy of the custom param node to the custom node. custom_node->AttachChildNode(new ON_XMLNode(custom_param_node)); return true; } void ON_Decal::GetEntireCustomXML(ON_XMLNode& custom_xml) const { // This function only exists to support the RDK. const ON_XMLNode& decal_node = _private->Node(); auto it = decal_node.GetChildIterator(); ON_XMLNode* child_node = nullptr; while (nullptr != (child_node = it.GetNextChild())) { if (child_node->TagName() == ON_RDK_DECAL_CUSTOM) { custom_xml.AttachChildNode(new ON_XMLNode(*child_node)); } } } void ON_Decal::AppendCustomXML(const ON_XMLNode& custom_node) { // This function only exists to support the RDK. ON_ASSERT(custom_node.TagName() == L"entire-custom-xml"); ON_XMLNode* child = custom_node.FirstChild(); while (nullptr != child) { _private->Node().AttachChildNode(new ON_XMLNode(*child)); child = custom_node.NextSibling(); } } bool ON_Decal::HitTest(const ON_3dPoint& point, const ON_3dVector& normal, ON_2dPoint& uvOut) const { return _private->HitTest(point, normal, uvOut); } void ON_Decal::ApplyTransformation(const ON_Xform& xform) { _private->ApplyTransformation(xform); } void ON_Decal::GetSavedVectorLengths(double& across, double& up) const { _private->GetSavedVectorLengths(across, up); } void ON_Decal::SetSavedVectorLengths(double across, double up) { _private->SetSavedVectorLengths(across, up); } ON_3dVector ON_Decal::GetOriginOffset(void) const { return _private->GetOriginOffset(); } /* Class ON_DecalNodeReader This object encapsulates the reading of all decal properties from XML nodes. It is used by the decal CRC calculation in ComputeDecalCRC(). TODO: It could also be used by the ON_Decal XML node access (for Rhino 9). */ class ON_DecalNodeReader { public: ON_DecalNodeReader(const ON_XMLNode* decal_node); ON_XMLVariant Mapping(void) const { return Value(ON_RDK_DECAL_MAPPING, ON_RDK_DECAL_MAPPING_NONE); } ON_XMLVariant Projection(void) const { return Value(ON_RDK_DECAL_PROJECTION, ON_RDK_DECAL_PROJECTION_NONE); } ON_XMLVariant MapToInside(void) const { return Value(ON_RDK_DECAL_MAP_TO_INSIDE_ON, _def.MapToInside()); } ON_XMLVariant Transparency(void) const { return Value(ON_RDK_DECAL_TRANSPARENCY , _def.Transparency()); } ON_XMLVariant TextureInstanceId(void) const { return Value(ON_RDK_DECAL_TEXTURE_INSTANCE, _def.TextureInstanceId()); } ON_XMLVariant Height(void) const { return Value(ON_RDK_DECAL_HEIGHT , _def.Height()); } ON_XMLVariant Radius(void) const { return Value(ON_RDK_DECAL_RADIUS , _def.Radius()); } ON_XMLVariant Origin(void) const { return Value(ON_RDK_DECAL_ORIGIN , _def.Origin()); } ON_XMLVariant VectorUp(void) const { return Value(ON_RDK_DECAL_VECTOR_UP , ON_3dPoint(_def.VectorUp())); } ON_XMLVariant VectorAcross(void) const { return Value(ON_RDK_DECAL_VECTOR_ACROSS , ON_3dPoint(_def.VectorAcross())); } ON_XMLVariant HorzSweepSta(void) const { return Value(ON_RDK_DECAL_HORZ_SWEEP_STA , DefaultHorzSweepSta()); } ON_XMLVariant HorzSweepEnd(void) const { return Value(ON_RDK_DECAL_HORZ_SWEEP_END , DefaultHorzSweepEnd()); } ON_XMLVariant VertSweepSta(void) const { return Value(ON_RDK_DECAL_VERT_SWEEP_STA , DefaultVertSweepSta()); } ON_XMLVariant VertSweepEnd(void) const { return Value(ON_RDK_DECAL_VERT_SWEEP_END , DefaultVertSweepEnd()); } ON_XMLVariant MinU(void) const { return Value(ON_RDK_DECAL_MIN_U , DefaultMinU()); } ON_XMLVariant MinV(void) const { return Value(ON_RDK_DECAL_MIN_V , DefaultMinV()); } ON_XMLVariant MaxU(void) const { return Value(ON_RDK_DECAL_MAX_U , DefaultMaxU()); } ON_XMLVariant MaxV(void) const { return Value(ON_RDK_DECAL_MAX_V , DefaultMaxV()); } ON_XMLVariant IsTemporary(void) const { return Value(ON_RDK_DECAL_IS_TEMPORARY , false); } ON_XMLVariant IsVisible(void) const { return Value(ON_RDK_DECAL_IS_VISIBLE , _def.IsVisible()); } ON_XMLVariant InstanceId(void) const { return Value(ON_RDK_DECAL_INSTANCE_ID , _def.Id()); } private: ON_XMLVariant Value(const wchar_t* wszName, const ON_XMLVariant& vDefault) const; double DefaultHorzSweepSta(void) const { double a, b; _def.GetHorzSweep(a, b); return a; } double DefaultHorzSweepEnd(void) const { double a, b; _def.GetHorzSweep(a, b); return b; } double DefaultVertSweepSta(void) const { double a, b; _def.GetVertSweep(a, b); return a; } double DefaultVertSweepEnd(void) const { double a, b; _def.GetVertSweep(a, b); return b; } double DefaultMinU(void) const { double a, b, c, d; _def.GetUVBounds(a, b, c, d); return a; } double DefaultMinV(void) const { double a, b, c, d; _def.GetUVBounds(a, b, c, d); return b; } double DefaultMaxU(void) const { double a, b, c, d; _def.GetUVBounds(a, b, c, d); return c; } double DefaultMaxV(void) const { double a, b, c, d; _def.GetUVBounds(a, b, c, d); return d; } private: const ON_XMLNode* _decal_node; const ON_Decal _def; }; ON_DecalNodeReader::ON_DecalNodeReader(const ON_XMLNode* decal_node) : _decal_node(decal_node) { ON_ASSERT(_decal_node && (_decal_node->TagName() == ON_RDK_DECAL)); } ON_XMLVariant ON_DecalNodeReader::Value(const wchar_t* wszName, const ON_XMLVariant& vDefault) const { ON_XMLVariant vValue = vDefault; if (nullptr != _decal_node) { const ON_XMLParameters p(*_decal_node); p.GetParam(wszName, vValue); } return vValue; } #if (defined _DEBUG) && (defined HUMAN_READABLE_DECAL_CRC) #define ON_DECAL_PROP_NAME(s) , s static void DecalUpdateCRC(ON_DECAL_CRC& crc, const ON_XMLVariant value, const wchar_t* name) { crc = value.DataCRC(crc); crc._info1 += ON_wString(name) + L"=" + value.AsString() + ON_wString(L" "); crc._info2 += ON_wString(name) + L"=" + value.AsString() + ON_wString(L"\n"); } #else #define ON_DECAL_PROP_NAME(s) static void DecalUpdateCRC(ON_DECAL_CRC& crc, const ON_XMLVariant& value) { crc = value.DataCRC(crc); } #endif static void DecalUpdateCRC_Custom(const ON_XMLNode& decal_node, ON_DECAL_CRC& crc) { // Look for custom data nodes and for each one, find the parameter node and then iterate over its // children and CRC the properties. For now, we will have to rely on the raw XML. A better solution // would be to have the plug-in that created this XML calculate the CRC itself. const ON_wString custom = L"[CUSTOM] "; auto it = decal_node.GetChildIterator(); ON_XMLNode* pChildNode = nullptr; while (nullptr != (pChildNode = it.GetNextChild())) { if (pChildNode->TagName() != ON_RDK_DECAL_CUSTOM) continue; // Not a custom data node. const ON__UINT32 crc_before_custom_params = crc; // Find the custom parameter node. const ON_XMLNode* pParamNode = pChildNode->GetNamedChild(ON_RDK_DECAL_CUSTOM_PARAMS); if (nullptr != pParamNode) { // Iterate over the nodes inside the custom parameter node. const ON_XMLParameters p(*pParamNode); auto* iterator = p.NewIterator(); if (nullptr != iterator) { ON_wString sParamName; ON_XMLVariant vParamValue; while (iterator->Next(sParamName, vParamValue)) { DecalUpdateCRC(crc, vParamValue ON_DECAL_PROP_NAME(custom + sParamName)); } delete iterator; } if (crc != crc_before_custom_params) { // 20th January 2025 John Croudy, https://mcneel.myjetbrains.com/youtrack/issue/RH-71351 // Since the crc has changed, there must be some custom params, so only now do we include the render // engine id. Prior to this, the render engine id was getting included even when there were no custom // params. This caused the UI to overlook decals which were programatically created by clients. const ON_XMLProperty* prop = pChildNode->GetNamedProperty(ON_RDK_DECAL_CUSTOM_RENDERER); if (nullptr != prop) { // Include the render engine id. const ON_UUID uuid = prop->GetValue().AsUuid(); DecalUpdateCRC(crc, uuid ON_DECAL_PROP_NAME(custom + L"render_engine_id")); } } } } } ON_DECAL_CRC ON_Decal::ComputeDecalCRC(ON__UINT32 current_remainder, const ON_XMLNode& decal_node) // Static. { // The CRC of a decal is a unique value based on its state. It's created by CRC-ing all the decal properties // that affect the decal's appearance. We do not include the 'IsTemporary' property in the CRC because whether // or not a decal is temporary has nothing to do with what it looks like. We do however, include the 'IsVisible' // property because a decal being visible or invisible actually affects its appearance. Also, the RDK change // queue relies on the CRC to update the viewport so including the visibility is critical. Furthermore, the // CRC only includes the properties that are relevant for the decal's mapping type. ON_DECAL_CRC crc = current_remainder; if (decal_node.TagName() == ON_RDK_DECAL) { const ON_DecalNodeReader d(&decal_node); DecalUpdateCRC(crc, d.Mapping() ON_DECAL_PROP_NAME(L"mapping")); DecalUpdateCRC(crc, d.IsVisible() ON_DECAL_PROP_NAME(L"visible")); DecalUpdateCRC(crc, d.Transparency() ON_DECAL_PROP_NAME(L"transparency")); DecalUpdateCRC(crc, d.TextureInstanceId() ON_DECAL_PROP_NAME(L"texture_id")); const ON_Decal::Mappings mapping = MappingFromString(d.Mapping().AsString()); if (Mappings::UV == mapping) { DecalUpdateCRC(crc, d.MinU() ON_DECAL_PROP_NAME(L"min_u")); DecalUpdateCRC(crc, d.MinV() ON_DECAL_PROP_NAME(L"min_v")); DecalUpdateCRC(crc, d.MaxU() ON_DECAL_PROP_NAME(L"max_u")); DecalUpdateCRC(crc, d.MaxV() ON_DECAL_PROP_NAME(L"max_v")); } else { DecalUpdateCRC(crc, d.Origin() ON_DECAL_PROP_NAME(L"origin")); DecalUpdateCRC(crc, d.VectorUp() ON_DECAL_PROP_NAME(L"up")); DecalUpdateCRC(crc, d.VectorAcross() ON_DECAL_PROP_NAME(L"across")); if ((Mappings::Cylindrical == mapping) || (Mappings::Spherical == mapping)) { DecalUpdateCRC(crc, d.MapToInside() ON_DECAL_PROP_NAME(L"map_to_inside")); DecalUpdateCRC(crc, d.Radius() ON_DECAL_PROP_NAME(L"radius")); DecalUpdateCRC(crc, d.HorzSweepSta() ON_DECAL_PROP_NAME(L"horz_sweep_sta")); DecalUpdateCRC(crc, d.HorzSweepEnd() ON_DECAL_PROP_NAME(L"horz_sweep_end")); if (Mappings::Cylindrical == mapping) { DecalUpdateCRC(crc, d.Height() ON_DECAL_PROP_NAME(L"height")); } else if (Mappings::Spherical == mapping) { DecalUpdateCRC(crc, d.VertSweepSta() ON_DECAL_PROP_NAME(L"vert_sweep_sta")); DecalUpdateCRC(crc, d.VertSweepEnd() ON_DECAL_PROP_NAME(L"vert_sweep_end")); } } else if (Mappings::Planar == mapping) { DecalUpdateCRC(crc, d.Projection() ON_DECAL_PROP_NAME(L"projection")); } } DecalUpdateCRC_Custom(decal_node, crc); // Make sure it's not nil. if (crc == ON_NIL_DECAL_CRC) crc = 0xFFFFFFFF; } return crc; } // ON_DecalCollection ON_DecalCollection::ON_DecalCollection(ON_3dmObjectAttributes* attr) : m_attr(attr) { } ON_DecalCollection::~ON_DecalCollection() { ClearDecalArray(); } int ON_DecalCollection::FindDecalIndex(const ON_DECAL_CRC decal_crc) const { for (int i = 0; i < m_decals.Count(); i++) { if (m_decals[i]->DecalCRC() == decal_crc) return i; } return -1; } void ON_DecalCollection::ClearDecalArray(void) { // 12th July 2023 John Croudy, https://mcneel.myjetbrains.com/youtrack/issue/RH-75697 // Only call SetChanged() if a decal is actually deleted. const int count = m_decals.Count(); if (count > 0) { for (int i = 0; i < count; i++) { delete m_decals[i]; } m_decals.Destroy(); SetChanged(); } m_cache_valid = false; } const ON_DecalCollection& ON_DecalCollection::operator = (const ON_DecalCollection& coll) { ClearDecalArray(); // Critical. // 25th February 2025 John Croudy, https://mcneel.myjetbrains.com/youtrack/issue/RH-64608 // The commented out code below is, in my opinion, correct. It copies the incoming collection's decals to // this collection, and also copies the 'cache valid' flag. This should result in this collection being // identical to the incoming one, which is what the purpose of operator = is. However, it doesn't work and // caused my changes for RH-64608 to fail under certain conditions. After hours and hours of debugging I // finally ended up carefuly reading the code at the calling site in CRhinoDoc::ModifyObjectAttributes() and // found a ton of complex jiggery-pokery involving not copying the user data when one would expect so as not // to copy stuff that 'shouldn't be copied' etc., etc. -- optimizations no doubt. It seems that these // optimizations stop client code from working as one would expect -- I'm not sure because it's too complex // for me to understand, but I'm going to assume the following: // // TLDR; don't copy the decal array because doing so somehow causes it to get out of sync with the underlying // user data (XML). But still clear the array (above) so it gets re-populated the next time it's used. // //for (int i = 0; i < coll.m_decals.Count(); i++) //{ // ON_Decal* decal = coll.m_decals[i]; // m_decals.Append(new ON_Decal(*this, *decal)); //} // //m_cache_valid = coll.m_cache_valid; return *this; } const ON_SimpleArray& ON_DecalCollection::GetDecalArray(void) { if (!m_cache_valid) { ClearDecalArray(); Populate(); m_cache_valid = true; } return m_decals; } void ON_DecalCollection::Populate(void) { if (nullptr == m_attr) return; ON_DecalObjectAttributesWrapper w(*m_attr); // 25th February 2025 John Croudy, https://mcneel.myjetbrains.com/youtrack/issue/RH-64608 // I'm calling DecalsNodeForWrite() deliberately to circumvent the problem with the user data copy count. // See the comments in ON_XMLUserData::XMLRootForWrite(). If I don't do this, the Python use of this code // doesn't work. I know, it's a hack, but I'm literally at my wits' end trying to work around these problems. const ON_XMLNode* decals_node = w.DecalsNodeForWrite(); // <---- Yes, ForWrite. if (nullptr != decals_node) { // Iterate over the decals under the decals node adding a new decal for each one. ON_ASSERT(m_decals.Count() == 0); auto it = decals_node->GetChildIterator(); ON_XMLNode* decal_node = nullptr; while (nullptr != (decal_node = it.GetNextChild())) { ON_Decal* decal = new ON_Decal(*this, *decal_node); m_decals.Append(decal); } } } void ON_DecalCollection::SetChanged(void) { m_changed = true; } void ON_DecalCollection::InvalidateCache(void) { m_cache_valid = false; } // ON_DecalObjectAttributesWrapper static const wchar_t* decals_path = ON_RDK_USER_DATA_ROOT ON_XML_SLASH ON_RDK_DECALS; class ON_DecalObjectAttributesWrapperPrivate { public: ON_DecalObjectAttributesWrapperPrivate( ON_3dmObjectAttributes& a) : _read_only(false), _attr(a) { } ON_DecalObjectAttributesWrapperPrivate(const ON_3dmObjectAttributes& a) : _read_only(true) , _attr(const_cast(a)) { } const ON_RdkUserData* UserDataForRead(void) const; ON_RdkUserData* UserDataForWrite(void); ON_3dmObjectAttributes& _attr; bool _read_only; }; ON_DecalObjectAttributesWrapper::ON_DecalObjectAttributesWrapper(ON_3dmObjectAttributes& attr) { _private = new ON_DecalObjectAttributesWrapperPrivate(attr); } ON_DecalObjectAttributesWrapper::ON_DecalObjectAttributesWrapper(const ON_3dmObjectAttributes& attr) { _private = new ON_DecalObjectAttributesWrapperPrivate(attr); _private->_read_only = true; } const ON_RdkUserData* ON_DecalObjectAttributesWrapperPrivate::UserDataForRead(void) const { return static_cast(_attr.GetUserData(ON_RdkUserData::Uuid())); } ON_RdkUserData* ON_DecalObjectAttributesWrapperPrivate::UserDataForWrite(void) { if (_read_only) { ON_ASSERT(false); // Attempt to modify read-only decal user data. return nullptr; } ON_RdkUserData* user_data = static_cast(_attr.GetUserData(ON_RdkUserData::Uuid())); if (nullptr == user_data) { user_data = new ON_RdkUserData; if (!_attr.AttachUserData(user_data)) { delete user_data; return nullptr; } } return user_data; } const ON_XMLNode* ON_DecalObjectAttributesWrapper::DecalsNodeForRead(void) const { const ON_RdkUserData* user_data = _private->UserDataForRead(); if (nullptr == user_data) return nullptr; return user_data->XMLRootForRead().GetNodeAtPath(decals_path); } ON_XMLNode* ON_DecalObjectAttributesWrapper::DecalsNodeForWrite(void) { // 22nd January 2025 John Croudy, https://mcneel.myjetbrains.com/youtrack/issue/RH-67878 // Per conversation with Dale Lear, this is bad because we are actually going to change the // user data while it's already attached to the attributes. We're actually expected to delete // the old user data and create new user data with the changes in it, because otherwise Rhino // can't know that it was changed. However, Dale suggested I change ON_XMLUserData instead. ON_RdkUserData* user_data = _private->UserDataForWrite(); if (nullptr == user_data) return nullptr; ON_XMLNode* node = user_data->XMLRootForWrite().CreateNodeAtPath(decals_path); if (nullptr != node) { _private->_attr.UserDataChanged(ON_3dmObjectAttributes::UserDataType::Decals); } return node; } ON_DecalObjectAttributesWrapper::AddDecalResults ON_DecalObjectAttributesWrapper::AddDecal(const ON_Decal& decal_in) { if (_private->_read_only) return AddDecalResults::ReadOnly; ON_XMLNode* decals_node = DecalsNodeForWrite(); if (nullptr == decals_node) return AddDecalResults::Failure; if (nullptr != FindDecalNodeByCRC(*decals_node, decal_in.DecalCRC())) return AddDecalResults::Duplicate; // A decal for this decal CRC already exists. // Create a new XML node for the new decal. ON_XMLNode* decal_node = decals_node->AttachChildNode(new ON_XMLNode(ON_RDK_DECAL)); if (nullptr == decal_node) return AddDecalResults::Failure; // Wrap a decal around the new XML node and copy the incoming decal data into it. ON_Decal decal(*decal_node); decal = decal_in; // Move the new XML node to the head of the list so the decal is created on top of any others. ON_XMLNode* first_node = decals_node->FirstChild(); if (nullptr != first_node) { decal_node->MoveBefore(*first_node); } return AddDecalResults::Success; } bool ON_DecalObjectAttributesWrapper::UpdateDecal(ON_DECAL_CRC decal_crc, const ON_Decal& decal_in) { if (_private->_read_only) return false; ON_XMLNode* decals_node = DecalsNodeForWrite(); if (nullptr == decals_node) return false; ON_XMLNode* decal_node = FindDecalNodeByCRC(*decals_node, decal_crc); if (nullptr == decal_node) return false; // Wrap a decal around the new XML node and copy the incoming decal data into it. ON_Decal decal(*decal_node); decal = decal_in; return true; } bool ON_DecalObjectAttributesWrapper::RemoveDecal(ON_DECAL_CRC decal_crc) { if (ON_NIL_DECAL_CRC == decal_crc) return false; if (_private->_read_only) return false; ON_XMLNode* decals_node = DecalsNodeForWrite(); if (nullptr == decals_node) return false; ON_XMLNode* decal_node = FindDecalNodeByCRC(*decals_node, decal_crc); if (nullptr == decal_node) return false; decal_node->Remove(); return true; } bool ON_DecalObjectAttributesWrapper::RemoveAllDecals(void) { if (_private->_read_only) return false; ON_XMLNode* decals_node = DecalsNodeForWrite(); if (nullptr != decals_node) { decals_node->RemoveAllChildren(); decals_node->RemoveAllProperties(); } return true; } int ON_DecalObjectAttributesWrapper::CountDecalsOnAttributesUserData(void) const { const ON_XMLNode* decals_node = DecalsNodeForRead(); if (nullptr == decals_node) return 0; return decals_node->ChildCount(); } bool ON_DecalObjectAttributesWrapper::CullIdenticalDecals(void) { if (_private->_read_only) return false; ON_XMLNode* decals_node = DecalsNodeForWrite(); if (nullptr == decals_node) return false; auto it = decals_node->GetChildIterator(); ON_XMLNode* decal_node1 = nullptr; while (nullptr != (decal_node1 = it.GetNextChild())) { const ON_DECAL_CRC decal_crc = ON_DecalCRCFromNode(*decal_node1); auto it2 = decals_node->GetChildIterator(); ON_XMLNode* decal_node2 = nullptr; while (nullptr != (decal_node2 = it2.GetNextChild())) { if (decal_node1 == decal_node2) continue; if (decal_crc == ON_DecalCRCFromNode(*decal_node2)) { decal_node2->Remove(); CullIdenticalDecals(); return true; } } } return false; } ON_XMLNode* ON_DecalObjectAttributesWrapper::FindDecalNodeByCRC( ON_XMLNode& decals_node, ON_DECAL_CRC decal_crc) // Static. { return ::FindDecalNodeByCRC(decals_node, decal_crc); } const ON_XMLNode* ON_DecalObjectAttributesWrapper::FindDecalNodeByCRC( const ON_XMLNode& decals_node, ON_DECAL_CRC decal_crc) // Static. { return ::FindDecalNodeByCRC(decals_node, decal_crc); } void ON_DecalObjectAttributesWrapper::GetDecalCRCs(ON_SimpleArray& crcs) const { crcs.Destroy(); const ON_XMLNode* decals_node = DecalsNodeForRead(); if (nullptr != decals_node) { auto it = decals_node->GetChildIterator(); ON_XMLNode* decal_node = nullptr; while (nullptr != (decal_node = it.GetNextChild())) { const ON_DECAL_CRC decal_crc = ON_DecalCRCFromNode(*decal_node); ON_ASSERT(ON_NIL_DECAL_CRC != decal_crc); crcs.Append(decal_crc); } } } const ON_Decal* ON_DecalObjectAttributesWrapper::NewDecalForRead(ON_DECAL_CRC decal_crc) const { if (ON_NIL_DECAL_CRC == decal_crc) return nullptr; const ON_XMLNode* decals_node = DecalsNodeForRead(); if (nullptr == decals_node) return nullptr; const ON_XMLNode* decal_node = FindDecalNodeByCRC(*decals_node, decal_crc); if (nullptr == decal_node) return nullptr; return new ON_Decal(*decal_node); } ON_Decal* ON_DecalObjectAttributesWrapper::NewDecalForWrite(ON_DECAL_CRC decal_crc) { if (_private->_read_only) return nullptr; if (ON_NIL_DECAL_CRC == decal_crc) return nullptr; ON_XMLNode* decals_node = DecalsNodeForWrite(); if (nullptr == decals_node) return nullptr; ON_XMLNode* decal_node = FindDecalNodeByCRC(*decals_node, decal_crc); if (nullptr == decal_node) return nullptr; return new ON_Decal(*decal_node); } bool ON_DecalObjectAttributesWrapper::MoveDecalAfter(ON_DECAL_CRC decal_crc, ON_DECAL_CRC decal_crc_after) { if (_private->_read_only) return false; ON_XMLNode* decals_node = DecalsNodeForWrite(); if (nullptr == decals_node) return false; ON_XMLNode* decal_node_from = FindDecalNodeByCRC(*decals_node, decal_crc); if (nullptr == decal_node_from) return false; // Nil 'decal_crc_after' means move to head of list. if (ON_NIL_DECAL_CRC == decal_crc_after) { ON_XMLNode* first_child = decals_node->FirstChild(); if (nullptr != first_child) { decal_node_from->MoveBefore(*first_child); } } else { ON_XMLNode* decal_node_to = FindDecalNodeByCRC(*decals_node, decal_crc_after); if (nullptr != decal_node_to) { decal_node_from->MoveAfter(*decal_node_to); } } return true; } bool ON_DecalObjectAttributesWrapper::RemoveValue(ON_DECAL_CRC decal_crc, const wchar_t* param_name) { if (_private->_read_only) return false; const auto* decals_node = DecalsNodeForRead(); if (nullptr == decals_node) return false; const auto* decal_node = FindDecalNodeByCRC(*decals_node, decal_crc); if (nullptr == decal_node) return false; const auto* param_node = decals_node->GetNamedChild(param_name); if (nullptr == param_node) return false; // Only get node for write once we are certain the node needs to be removed. auto* decals_node_write = DecalsNodeForWrite(); if (nullptr == decals_node_write) return false; auto* decal_node_write = FindDecalNodeByCRC(*decals_node_write, decal_crc); if (nullptr == decal_node) return false; decal_node_write->GetNamedChild(param_name)->Remove(); return true; }