diff --git a/source/MRMesh/MRObject.cpp b/source/MRMesh/MRObject.cpp index 53560ef1f9fa..bbfb2e1d7674 100644 --- a/source/MRMesh/MRObject.cpp +++ b/source/MRMesh/MRObject.cpp @@ -6,6 +6,7 @@ #include "MRBox.h" #include "MRHeapBytes.h" #include "MRphmap.h" +#include "MRViewportProperty.h" #include "MRPch/MRJson.h" #include "MRPch/MRSpdlog.h" @@ -169,6 +170,31 @@ size_t ObjectChildrenHolder::heapBytes() const return res; } +struct Object::Data +{ + ViewportProperty xf; +}; + +Object::DataPtr::DataPtr() : p_( std::make_unique() ) +{ +} + +Object::DataPtr::DataPtr( const DataPtr& b ) : p_( std::make_unique( *b.p_ ) ) +{ +} + +Object::DataPtr& Object::DataPtr::operator =( const DataPtr& b ) +{ + p_ = std::make_unique( *b.p_ ); + return *this; +} + +Object::DataPtr::DataPtr( DataPtr&& b ) noexcept = default; + +Object::DataPtr& Object::DataPtr::operator =( DataPtr&& b ) noexcept = default; + +Object::DataPtr::~DataPtr() = default; + std::shared_ptr Object::find( const std::string_view & name ) const { for ( const auto & child : children_ ) @@ -177,9 +203,14 @@ std::shared_ptr Object::find( const std::string_view & name ) cons return {}; // not found among recognized children } +const AffineXf3f& Object::xf( ViewportId id, bool* isDef ) const +{ + return data_->xf.get( id, isDef ); +} + void Object::setXf( const AffineXf3f& xf, ViewportId id ) { - if ( xf_.get( id ) == xf ) + if ( data_->xf.get( id ) == xf ) return; if ( xf.A.det() == 0 ) { @@ -187,30 +218,35 @@ void Object::setXf( const AffineXf3f& xf, ViewportId id ) spdlog::warn( "Object transform is degenerate" ); return; } - xf_.set( xf, id ); + data_->xf.set( xf, id ); sendWorldXfChangedSignal_(); needRedraw_ = true; } void Object::resetXf( ViewportId id ) { - if ( !xf_.reset( id ) ) + if ( !data_->xf.reset( id ) ) return; sendWorldXfChangedSignal_(); needRedraw_ = true; } +const ViewportProperty& Object::xfsForAllViewports() const +{ + return data_->xf; +} + void Object::setXfsForAllViewports( ViewportProperty xf ) { - if ( xf_ == xf ) + if ( data_->xf == xf ) return; - xf_ = std::move( xf ); + data_->xf = std::move( xf ); needRedraw_ = true; } AffineXf3f Object::worldXf( ViewportId id, bool * isDef ) const { - auto xf = xf_.get( id, isDef ); + auto xf = data_->xf.get( id, isDef ); auto parent = this->parent(); while ( parent ) { @@ -225,7 +261,7 @@ AffineXf3f Object::worldXf( ViewportId id, bool * isDef ) const void Object::setWorldXf( const AffineXf3f& worldxf, ViewportId id ) { - setXf( xf_.get( id ) * worldXf( id ).inverse() * worldxf ); + setXf( data_->xf.get( id ) * worldXf( id ).inverse() * worldxf ); } void Object::applyScale( float ) @@ -544,7 +580,7 @@ void Object::serializeFields_( Json::Value& root ) const root["ParentLocked"] = parentLocked_; // xf - serializeToJson( xf_.get(), root["XF"] ); + serializeToJson( data_->xf.get(), root["XF"] ); // Type root["Type"].append( Object::StaticTypeName() ); // will be appended in derived calls @@ -581,7 +617,7 @@ void Object::deserializeFields_( const Json::Value& root ) if ( root["Selected"].isBool() ) selected_ = root["Selected"].asBool(); if ( !root["XF"].isNull() ) - deserializeFromJson( root["XF"], xf_.get() ); + deserializeFromJson( root["XF"], data_->xf.get() ); if ( root["Locked"].isBool() ) locked_ = root["Locked"].asBool(); if ( const auto& json = root["ParentLocked"]; json.isBool() ) diff --git a/source/MRMesh/MRObject.h b/source/MRMesh/MRObject.h index 7afdd3ea9d9c..5077debacb36 100644 --- a/source/MRMesh/MRObject.h +++ b/source/MRMesh/MRObject.h @@ -1,12 +1,10 @@ #pragma once -#include "MRAffineXf3.h" #include "MRExpected.h" #include "MRProgressCallback.h" #include "MRSignal.h" -#include "MRViewportProperty.h" +#include "MRViewportId.h" -#include #include #include #include @@ -102,13 +100,13 @@ class MRMESH_CLASS Object : public ObjectChildrenHolder /// this space to parent space transformation (to world space if no parent) for default or given viewport /// \param isDef receives true if the object has default transformation in this viewport (same as xf() returns) - const AffineXf3f & xf( ViewportId id = {}, bool * isDef = nullptr ) const { return xf_.get( id, isDef ); } + MRMESH_API const AffineXf3f & xf( ViewportId id = {}, bool * isDef = nullptr ) const; MRMESH_API virtual void setXf( const AffineXf3f& xf, ViewportId id = {} ); /// forgets specific transform in given viewport (or forgets all specific transforms for {} input) MRMESH_API virtual void resetXf( ViewportId id = {} ); /// returns xfs for all viewports, combined into a single object - const ViewportProperty & xfsForAllViewports() const { return xf_; } + MRMESH_API const ViewportProperty & xfsForAllViewports() const; /// modifies xfs for all viewports at once MRMESH_API virtual void setXfsForAllViewports( ViewportProperty xf ); @@ -312,7 +310,6 @@ class MRMESH_CLASS Object : public ObjectChildrenHolder MRMESH_API virtual void deserializeFields_( const Json::Value& root ); std::string name_; - ViewportProperty xf_; ViewportMask visibilityMask_ = ViewportMask::all(); // Prefer to not read directly. Use the getter, as it can be overridden. bool locked_ = false; bool parentLocked_ = false; @@ -329,6 +326,29 @@ class MRMESH_CLASS Object : public ObjectChildrenHolder // Emits `worldXfChangedSignal`, but derived classes can add additional behavior to it. MRMESH_API virtual void onWorldXfChanged_(); +private: + struct Data; + + /// std::unique_ptr with value semantics, which keeps Object's copy and move + /// operations defaulted despite Data being incomplete here + class DataPtr + { + public: + MRMESH_API DataPtr(); + MRMESH_API DataPtr( const DataPtr& b ); + MRMESH_API DataPtr& operator =( const DataPtr& b ); + MRMESH_API DataPtr( DataPtr&& b ) noexcept; + MRMESH_API DataPtr& operator =( DataPtr&& b ) noexcept; + MRMESH_API ~DataPtr(); + Data& operator *() { return *p_; } + const Data& operator *() const { return *p_; } + Data* operator ->() { return p_.get(); } + const Data* operator ->() const { return p_.get(); } + private: + std::unique_ptr p_; + }; + DataPtr data_; + private: struct MapSharedObjects; Expected>>> serializeRecursive_( const std::filesystem::path& path, Json::Value& root, diff --git a/source/MRMesh/MRVisualObject.h b/source/MRMesh/MRVisualObject.h index 694208cdd6c1..05e4f19d1a07 100644 --- a/source/MRMesh/MRVisualObject.h +++ b/source/MRMesh/MRVisualObject.h @@ -5,6 +5,7 @@ #include "MRColor.h" #include "MRUniquePtr.h" #include "MREnums.h" +#include "MRViewportProperty.h" #include #include diff --git a/source/MRTest/MRObjectTests.cpp b/source/MRTest/MRObjectTests.cpp index 9d5fbf417499..fd75817ac6b5 100644 --- a/source/MRTest/MRObjectTests.cpp +++ b/source/MRTest/MRObjectTests.cpp @@ -1,4 +1,5 @@ #include "MRMesh/MRObject.h" +#include "MRMesh/MRAffineXf3.h" #include namespace MR @@ -33,4 +34,29 @@ TEST( MRMesh, DataModelRemoveChild ) EXPECT_EQ( parent, nullptr ); } +TEST( MRMesh, ObjectCloneAndSwap ) +{ + Object a; + a.setName( "a" ); + a.setXf( AffineXf3f::translation( { 1.f, 2.f, 3.f } ) ); + a.select( true ); + a.setLocked( true ); + + auto clone = a.clone(); + EXPECT_EQ( clone->name(), "a" ); + EXPECT_EQ( clone->xf(), a.xf() ); + EXPECT_TRUE( clone->isSelected() ); + EXPECT_TRUE( clone->isLocked() ); + + Object b; + b.setName( "b" ); + a.swap( b ); + EXPECT_EQ( a.name(), "b" ); + EXPECT_EQ( b.name(), "a" ); + EXPECT_EQ( a.xf(), AffineXf3f() ); + EXPECT_EQ( b.xf(), clone->xf() ); + EXPECT_FALSE( a.isSelected() ); + EXPECT_TRUE( b.isSelected() ); +} + } //namespace MR