/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace com::sun::star; // Migrate Marking class Impl3DMirrorConstructOverlay { // The OverlayObjects sdr::overlay::OverlayObjectList maObjects; // the view const E3dView& mrView; // the object count size_t mnCount; // the unmirrored polygons basegfx::B2DPolyPolygon* mpPolygons; // the overlay geometry from selected objects drawinglayer::primitive2d::Primitive2DContainer maFullOverlay; // Copy assignment is forbidden and not implemented. Impl3DMirrorConstructOverlay (const Impl3DMirrorConstructOverlay &) = delete; Impl3DMirrorConstructOverlay & operator= (const Impl3DMirrorConstructOverlay &) = delete; public: explicit Impl3DMirrorConstructOverlay(const E3dView& rView); ~Impl3DMirrorConstructOverlay(); void SetMirrorAxis(Point aMirrorAxisA, Point aMirrorAxisB); }; Impl3DMirrorConstructOverlay::Impl3DMirrorConstructOverlay(const E3dView& rView) : mrView(rView), mnCount(rView.GetMarkedObjectCount()), mpPolygons(nullptr) { if(!mnCount) return; if(mrView.IsSolidDragging()) { SdrPageView* pPV = rView.GetSdrPageView(); if(pPV && pPV->PageWindowCount()) { for(size_t a = 0; a < mnCount; ++a) { SdrObject* pObject = mrView.GetMarkedObjectByIndex(a); if(pObject) { // use the view-independent primitive representation (without // evtl. GridOffset, that may be applied to the DragEntry individually) pObject->GetViewContact().getViewIndependentPrimitive2DContainer(maFullOverlay); } } } } else { mpPolygons = new basegfx::B2DPolyPolygon[mnCount]; for(size_t a = 0; a < mnCount; ++a) { SdrObject* pObject = mrView.GetMarkedObjectByIndex(a); mpPolygons[mnCount - (a + 1)] = pObject->TakeXorPoly(); } } } Impl3DMirrorConstructOverlay::~Impl3DMirrorConstructOverlay() { // The OverlayObjects are cleared using the destructor of OverlayObjectList. // That destructor calls clear() at the list which removes all objects from the // OverlayManager and deletes them. delete[] mpPolygons; } void Impl3DMirrorConstructOverlay::SetMirrorAxis(Point aMirrorAxisA, Point aMirrorAxisB) { // get rid of old overlay objects maObjects.clear(); // create new ones for(sal_uInt32 a(0); a < mrView.PaintWindowCount(); a++) { SdrPaintWindow* pCandidate = mrView.GetPaintWindow(a); const rtl::Reference< sdr::overlay::OverlayManager >& xTargetOverlay = pCandidate->GetOverlayManager(); if(xTargetOverlay.is()) { // build transformation: translate and rotate so that given edge is // on x axis, them mirror in y and translate back const basegfx::B2DVector aEdge(aMirrorAxisB.X() - aMirrorAxisA.X(), aMirrorAxisB.Y() - aMirrorAxisA.Y()); basegfx::B2DHomMatrix aMatrixTransform(basegfx::utils::createTranslateB2DHomMatrix( -aMirrorAxisA.X(), -aMirrorAxisA.Y())); aMatrixTransform.rotate(-atan2(aEdge.getY(), aEdge.getX())); aMatrixTransform.scale(1.0, -1.0); aMatrixTransform.rotate(atan2(aEdge.getY(), aEdge.getX())); aMatrixTransform.translate(aMirrorAxisA.X(), aMirrorAxisA.Y()); if(mrView.IsSolidDragging()) { if(!maFullOverlay.empty()) { drawinglayer::primitive2d::Primitive2DContainer aContent(maFullOverlay); if(!aMatrixTransform.isIdentity()) { // embed in transformation group drawinglayer::primitive2d::Primitive2DReference aTransformPrimitive2D(new drawinglayer::primitive2d::TransformPrimitive2D(aMatrixTransform, std::move(aContent))); aContent = drawinglayer::primitive2d::Primitive2DContainer { aTransformPrimitive2D }; } // if we have full overlay from selected objects, embed with 50% transparence, the // transformation is added to the OverlayPrimitive2DSequenceObject drawinglayer::primitive2d::Primitive2DReference aUnifiedTransparencePrimitive2D(new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(std::move(aContent), 0.5)); aContent = drawinglayer::primitive2d::Primitive2DContainer { aUnifiedTransparencePrimitive2D }; std::unique_ptr pNew(new sdr::overlay::OverlayPrimitive2DSequenceObject(std::move(aContent))); xTargetOverlay->add(*pNew); maObjects.append(std::move(pNew)); } } else { for(size_t b = 0; b < mnCount; ++b) { // apply to polygon basegfx::B2DPolyPolygon aPolyPolygon(mpPolygons[b]); aPolyPolygon.transform(aMatrixTransform); std::unique_ptr pNew(new sdr::overlay::OverlayPolyPolygonStripedAndFilled( aPolyPolygon)); xTargetOverlay->add(*pNew); maObjects.append(std::move(pNew)); } } } } } E3dView::E3dView( SdrModel& rSdrModel, OutputDevice* pOut) : SdrView(rSdrModel, pOut) { InitView(); } // DrawMarkedObj override, since possibly only a single 3D object is to be // drawn void E3dView::DrawMarkedObj(OutputDevice& rOut) const { // Does 3D objects exist which scenes are not selected? bool bSpecialHandling = false; E3dScene *pScene = nullptr; const size_t nCnt = GetMarkedObjectCount(); for(size_t nObjs = 0; nObjs < nCnt; ++nObjs) { SdrObject *pObj = GetMarkedObjectByIndex(nObjs); if(auto pCompoundObject = dynamic_cast(pObj)) { // related scene pScene = pCompoundObject->getRootE3dSceneFromE3dObject(); if(nullptr != pScene && !IsObjMarked(pScene)) { bSpecialHandling = true; } } // Reset all selection flags if(auto p3dObject = dynamic_cast< const E3dObject*>(pObj)) { pScene = p3dObject->getRootE3dSceneFromE3dObject(); if(nullptr != pScene) { pScene->SetSelected(false); } } } if(bSpecialHandling) { // Set selection flag to "not selected" for scenes related to all 3D // objects for(size_t nObjs = 0; nObjs < nCnt; ++nObjs) { SdrObject *pObj = GetMarkedObjectByIndex(nObjs); if(auto pCompoundObject = dynamic_cast(pObj)) { // related scene pScene = pCompoundObject->getRootE3dSceneFromE3dObject(); if(nullptr != pScene) { pScene->SetSelected(false); } } } for(size_t nObjs = 0; nObjs < nCnt; ++nObjs) { SdrObject *pObj = GetMarkedObjectByIndex(nObjs); if(auto p3DObj = dynamic_cast(pObj)) { // Select object p3DObj->SetSelected(true); pScene = p3DObj->getRootE3dSceneFromE3dObject(); } } if(nullptr != pScene) { // code from parent SortMarkedObjects(); pScene->SetDrawOnlySelected(true); pScene->SingleObjectPainter(rOut); pScene->SetDrawOnlySelected(false); } // Reset selection flag for(size_t nObjs = 0; nObjs < nCnt; ++nObjs) { SdrObject *pObj = GetMarkedObjectByIndex(nObjs); if(auto pCompoundObject = dynamic_cast(pObj)) { // related scene pScene = pCompoundObject->getRootE3dSceneFromE3dObject(); if(nullptr != pScene) { pScene->SetSelected(false); } } } } else { // call parent SdrExchangeView::DrawMarkedObj(rOut); } } // override get model, since in some 3D objects an additional scene // must be pushed in std::unique_ptr E3dView::CreateMarkedObjModel() const { // Does 3D objects exist which scenes are not selected? bool bSpecialHandling(false); const size_t nCount(GetMarkedObjectCount()); E3dScene *pScene = nullptr; for(size_t nObjs = 0; nObjs < nCount; ++nObjs) { const SdrObject* pObj = GetMarkedObjectByIndex(nObjs); if(!bSpecialHandling) if(auto pCompoundObj = dynamic_cast< const E3dCompoundObject*>(pObj)) { // if the object is selected, but it's scene not, // we need special handling pScene = pCompoundObj->getRootE3dSceneFromE3dObject(); if(nullptr != pScene && !IsObjMarked(pScene)) { bSpecialHandling = true; } } if(auto p3dObject = dynamic_cast< const E3dObject*>(pObj)) { // reset all selection flags at 3D objects pScene = p3dObject->getRootE3dSceneFromE3dObject(); if(nullptr != pScene) { pScene->SetSelected(false); } } } if(!bSpecialHandling) { // call parent return SdrView::CreateMarkedObjModel(); } std::unique_ptr pNewModel; tools::Rectangle aSelectedSnapRect; // set 3d selection flags at all directly selected objects // and collect SnapRect of selected objects for(size_t nObjs = 0; nObjs < nCount; ++nObjs) { SdrObject *pObj = GetMarkedObjectByIndex(nObjs); if(auto p3DObj = dynamic_cast(pObj)) { // mark object, but not scenes p3DObj->SetSelected(true); aSelectedSnapRect.Union(p3DObj->GetSnapRect()); } } // create new mark list which contains all indirectly selected3d // scenes as selected objects SdrMarkList aOldML(GetMarkedObjectList()); SdrMarkList aNewML; SdrMarkList& rCurrentMarkList = const_cast(this)->GetMarkedObjectListWriteAccess(); rCurrentMarkList = aNewML; for(size_t nObjs = 0; nObjs < nCount; ++nObjs) { SdrObject *pObj = aOldML.GetMark(nObjs)->GetMarkedSdrObj(); if(auto p3dObject = dynamic_cast< E3dObject* >(pObj)) { pScene = p3dObject->getRootE3dSceneFromE3dObject(); if(nullptr != pScene && !IsObjMarked(pScene) && GetSdrPageView()) { const_cast(this)->MarkObj(pScene, GetSdrPageView(), false, true); } } } // call parent. This will copy all scenes and the selection flags at the 3D objects. So // it will be possible to delete all non-selected 3d objects from the cloned 3d scenes pNewModel = SdrView::CreateMarkedObjModel(); if(pNewModel) { for(sal_uInt16 nPg(0); nPg < pNewModel->GetPageCount(); nPg++) { const SdrPage* pSrcPg=pNewModel->GetPage(nPg); const size_t nObjCount(pSrcPg->GetObjCount()); for(size_t nOb = 0; nOb < nObjCount; ++nOb) { const SdrObject* pSrcOb=pSrcPg->GetObj(nOb); if(auto p3dscene = dynamic_cast< const E3dScene* >( pSrcOb)) { pScene = const_cast(p3dscene); // delete all not intentionally cloned 3d objects pScene->removeAllNonSelectedObjects(); // reset select flags and set SnapRect of all selected objects pScene->SetSelected(false); pScene->SetSnapRect(aSelectedSnapRect); } } } } // restore old selection rCurrentMarkList = aOldML; return pNewModel; } // When pasting objects have to integrated if a scene is inserted, but // not the scene itself bool E3dView::Paste( const SdrModel& rMod, const Point& rPos, SdrObjList* pLst, SdrInsertFlags nOptions) { bool bRetval = false; // Get list Point aPos(rPos); SdrObjList* pDstList = pLst; ImpGetPasteObjList(aPos, pDstList); if(!pDstList) return false; // Get owner of the list E3dScene* pDstScene(dynamic_cast< E3dScene* >(pDstList->getSdrObjectFromSdrObjList())); if(nullptr != pDstScene) { BegUndo(SvxResId(RID_SVX_3D_UNDO_EXCHANGE_PASTE)); // Copy all objects from E3dScenes and insert them directly for(sal_uInt16 nPg(0); nPg < rMod.GetPageCount(); nPg++) { const SdrPage* pSrcPg=rMod.GetPage(nPg); const size_t nObjCount(pSrcPg->GetObjCount()); // calculate offset for paste tools::Rectangle aR = pSrcPg->GetAllObjBoundRect(); Point aDist(aPos - aR.Center()); // Insert sub-objects for scenes for(size_t nOb = 0; nOb < nObjCount; ++nOb) { const SdrObject* pSrcOb = pSrcPg->GetObj(nOb); if(auto p3dscene = dynamic_cast< const E3dScene* >(pSrcOb)) { E3dScene* pSrcScene = const_cast(p3dscene); ImpCloneAll3DObjectsToDestScene(pSrcScene, pDstScene, aDist); } } } EndUndo(); } else { // call parent bRetval = SdrView::Paste(rMod, rPos, pLst, nOptions); } return bRetval; } // Service routine used from local Clone() and from SdrCreateView::EndCreateObj(...) bool E3dView::ImpCloneAll3DObjectsToDestScene(E3dScene const * pSrcScene, E3dScene* pDstScene, Point /*aOffset*/) { bool bRetval(false); if(pSrcScene && pDstScene) { for(size_t i = 0; i < pSrcScene->GetSubList()->GetObjCount(); ++i) { E3dCompoundObject* pCompoundObj = dynamic_cast< E3dCompoundObject* >(pSrcScene->GetSubList()->GetObj(i)); if(pCompoundObj) { E3dCompoundObject* pNewCompoundObj(pCompoundObj->CloneSdrObject(pDstScene->getSdrModelFromSdrObject())); if(pNewCompoundObj) { // get dest scene's current range in 3D world coordinates const basegfx::B3DHomMatrix aSceneToWorldTrans(pDstScene->GetFullTransform()); basegfx::B3DRange aSceneRange(pDstScene->GetBoundVolume()); aSceneRange.transform(aSceneToWorldTrans); // get new object's implied object transformation const basegfx::B3DHomMatrix aNewObjectTrans(pNewCompoundObj->GetTransform()); // get new object's range in 3D world coordinates in dest scene // as if it were already added const basegfx::B3DHomMatrix aObjectToWorldTrans(aSceneToWorldTrans * aNewObjectTrans); basegfx::B3DRange aObjectRange(pNewCompoundObj->GetBoundVolume()); aObjectRange.transform(aObjectToWorldTrans); // get scale adaptation const basegfx::B3DVector aSceneScale(aSceneRange.getRange()); const basegfx::B3DVector aObjectScale(aObjectRange.getRange()); double fScale(1.0); // if new object's size in X,Y or Z is bigger that 80% of dest scene, adapt scale // to not change the scene by the inserted object const double fSizeFactor(0.5); if(aObjectScale.getX() * fScale > aSceneScale.getX() * fSizeFactor) { const double fObjSize(aObjectScale.getX() * fScale); const double fFactor((aSceneScale.getX() * fSizeFactor) / (basegfx::fTools::equalZero(fObjSize) ? 1.0 : fObjSize)); fScale *= fFactor; } if(aObjectScale.getY() * fScale > aSceneScale.getY() * fSizeFactor) { const double fObjSize(aObjectScale.getY() * fScale); const double fFactor((aSceneScale.getY() * fSizeFactor) / (basegfx::fTools::equalZero(fObjSize) ? 1.0 : fObjSize)); fScale *= fFactor; } if(aObjectScale.getZ() * fScale > aSceneScale.getZ() * fSizeFactor) { const double fObjSize(aObjectScale.getZ() * fScale); const double fFactor((aSceneScale.getZ() * fSizeFactor) / (basegfx::fTools::equalZero(fObjSize) ? 1.0 : fObjSize)); fScale *= fFactor; } // get translation adaptation const basegfx::B3DPoint aSceneCenter(aSceneRange.getCenter()); const basegfx::B3DPoint aObjectCenter(aObjectRange.getCenter()); // build full modification transform. The object's transformation // shall be modified, so start at object coordinates; transform to 3d world coor basegfx::B3DHomMatrix aModifyingTransform(aObjectToWorldTrans); // translate to absolute center in 3d world coor aModifyingTransform.translate(-aObjectCenter.getX(), -aObjectCenter.getY(), -aObjectCenter.getZ()); // scale to dest size in 3d world coor aModifyingTransform.scale(fScale, fScale, fScale); // translate to dest scene center in 3d world coor aModifyingTransform.translate(aSceneCenter.getX(), aSceneCenter.getY(), aSceneCenter.getZ()); // transform from 3d world to dest object coordinates basegfx::B3DHomMatrix aWorldToObject(aObjectToWorldTrans); aWorldToObject.invert(); aModifyingTransform = aWorldToObject * aModifyingTransform; // correct implied object transform by applying changing one in object coor pNewCompoundObj->SetTransform(aModifyingTransform * aNewObjectTrans); // fill and insert new object pNewCompoundObj->NbcSetLayer(pCompoundObj->GetLayer()); pNewCompoundObj->NbcSetStyleSheet(pCompoundObj->GetStyleSheet(), true); pDstScene->InsertObject(pNewCompoundObj); bRetval = true; // Create undo if( GetModel()->IsUndoEnabled() ) AddUndo(GetModel()->GetSdrUndoFactory().CreateUndoNewObject(*pNewCompoundObj)); } } } } return bRetval; } bool E3dView::IsConvertTo3DObjPossible() const { bool bAny3D(false); bool bGroupSelected(false); bool bRetval(true); for(size_t a=0; !bAny3D && a(pObj) != nullptr) { rAny3D = true; } else { if(pObj->IsGroupObject()) { SdrObjListIter aIter(*pObj, SdrIterMode::DeepNoGroups); while(aIter.IsMore()) { SdrObject* pNewObj = aIter.Next(); ImpIsConvertTo3DPossible(pNewObj, rAny3D, rGroupSelected); } rGroupSelected = true; } } } void E3dView::ImpChangeSomeAttributesFor3DConversion(SdrObject* pObj) { if(dynamic_cast( pObj) == nullptr) return; const SfxItemSet& rSet = pObj->GetMergedItemSet(); const SvxColorItem& rTextColorItem = rSet.Get(EE_CHAR_COLOR); if(rTextColorItem.GetValue() != COL_BLACK) return; //For black text objects, the color set to gray if(pObj->getSdrPageFromSdrObject()) { // if black is only default attribute from // pattern set it hard so that it is used in undo. pObj->SetMergedItem(SvxColorItem(COL_BLACK, EE_CHAR_COLOR)); // add undo now if( GetModel()->IsUndoEnabled() ) AddUndo(GetModel()->GetSdrUndoFactory().CreateUndoAttrObject(*pObj)); } pObj->SetMergedItem(SvxColorItem(COL_GRAY, EE_CHAR_COLOR)); } void E3dView::ImpChangeSomeAttributesFor3DConversion2(SdrObject* pObj) { auto pPathObj = dynamic_cast( pObj); if(!pPathObj) return; const SfxItemSet& rSet = pObj->GetMergedItemSet(); sal_Int32 nLineWidth = rSet.Get(XATTR_LINEWIDTH).GetValue(); drawing::LineStyle eLineStyle = rSet.Get(XATTR_LINESTYLE).GetValue(); drawing::FillStyle eFillStyle = rSet.Get(XATTR_FILLSTYLE).GetValue(); if(pPathObj->IsClosed() && eLineStyle == drawing::LineStyle_SOLID && !nLineWidth && eFillStyle != drawing::FillStyle_NONE) { if(pObj->getSdrPageFromSdrObject() && GetModel()->IsUndoEnabled() ) { AddUndo(GetModel()->GetSdrUndoFactory().CreateUndoAttrObject(*pObj)); } pObj->SetMergedItem(XLineStyleItem(drawing::LineStyle_NONE)); pObj->SetMergedItem(XLineWidthItem(0)); } } void E3dView::ImpCreateSingle3DObjectFlat(E3dScene* pScene, SdrObject* pObj, bool bExtrude, double fDepth, basegfx::B2DHomMatrix const & rLatheMat) { // Single PathObject, transform this SdrPathObj* pPath = dynamic_cast( pObj ); if(!pPath) return; E3dDefaultAttributes aDefault = Get3DDefaultAttributes(); if(bExtrude) { aDefault.SetDefaultExtrudeCharacterMode(true); } else { aDefault.SetDefaultLatheCharacterMode(true); } // Get Itemset of the original object SfxItemSet aSet(pObj->GetMergedItemSet()); drawing::FillStyle eFillStyle = aSet.Get(XATTR_FILLSTYLE).GetValue(); // line style turned off aSet.Put(XLineStyleItem(drawing::LineStyle_NONE)); //Determining if FILL_Attribute is set. if(!pPath->IsClosed() || eFillStyle == drawing::FillStyle_NONE) { // This SdrPathObj is not filled, leave the front and rear face out. // Moreover, a two-sided representation necessary. aDefault.SetDefaultExtrudeCloseFront(false); aDefault.SetDefaultExtrudeCloseBack(false); aSet.Put(makeSvx3DDoubleSidedItem(true)); // Set fill attribute aSet.Put(XFillStyleItem(drawing::FillStyle_SOLID)); // Fill color must be the color line, because the object was // previously just a line Color aColorLine = aSet.Get(XATTR_LINECOLOR).GetColorValue(); aSet.Put(XFillColorItem(OUString(), aColorLine)); } // Create a new extrude object E3dObject* p3DObj = nullptr; if(bExtrude) { p3DObj = new E3dExtrudeObj(pObj->getSdrModelFromSdrObject(), aDefault, pPath->GetPathPoly(), fDepth); } else { // rLatheMat expects coordinates with y-axis up, pPath uses y-axis down basegfx::B2DHomMatrix aFlipVerticalMat(1.0, 0.0, 0.0, 0.0, -1.0, 0.0); basegfx::B2DPolyPolygon aPolyPoly2D(pPath->GetPathPoly()); aPolyPoly2D.transform(aFlipVerticalMat); aPolyPoly2D.transform(rLatheMat); // ctor E3dLatheObj expects coordinates with y-axis down aPolyPoly2D.transform(aFlipVerticalMat); p3DObj = new E3dLatheObj(pObj->getSdrModelFromSdrObject(), aDefault, aPolyPoly2D); } // Set attribute p3DObj->NbcSetLayer(pObj->GetLayer()); p3DObj->SetMergedItemSet(aSet); p3DObj->NbcSetStyleSheet(pObj->GetStyleSheet(), true); // Insert a new extrude object pScene->InsertObject(p3DObj); } void E3dView::ImpCreate3DObject(E3dScene* pScene, SdrObject* pObj, bool bExtrude, double fDepth, basegfx::B2DHomMatrix const & rLatheMat) { if(!pObj) return; // change text color attribute for not so dark colors if(pObj->IsGroupObject()) { SdrObjListIter aIter(*pObj, SdrIterMode::DeepWithGroups); while(aIter.IsMore()) { SdrObject* pGroupMember = aIter.Next(); ImpChangeSomeAttributesFor3DConversion(pGroupMember); } } else ImpChangeSomeAttributesFor3DConversion(pObj); // convert completely to path objects SdrObject* pNewObj1 = pObj->ConvertToPolyObj(false, false).release(); if(!pNewObj1) return; // change text color attribute for not so dark colors if(pNewObj1->IsGroupObject()) { SdrObjListIter aIter(*pNewObj1, SdrIterMode::DeepWithGroups); while(aIter.IsMore()) { SdrObject* pGroupMember = aIter.Next(); ImpChangeSomeAttributesFor3DConversion2(pGroupMember); } } else ImpChangeSomeAttributesFor3DConversion2(pNewObj1); // convert completely to path objects SdrObject* pNewObj2 = pObj->ConvertToContourObj(pNewObj1, true); if(pNewObj2) { // add all to flat scene if(pNewObj2->IsGroupObject()) { SdrObjListIter aIter(*pNewObj2, SdrIterMode::DeepWithGroups); while(aIter.IsMore()) { SdrObject* pGroupMember = aIter.Next(); ImpCreateSingle3DObjectFlat(pScene, pGroupMember, bExtrude, fDepth, rLatheMat); } } else ImpCreateSingle3DObjectFlat(pScene, pNewObj2, bExtrude, fDepth, rLatheMat); // delete object in between if (pNewObj2 != pObj && pNewObj2 != pNewObj1) SdrObject::Free( pNewObj2 ); } // delete object in between if (pNewObj1 != pObj) SdrObject::Free( pNewObj1 ); } void E3dView::ConvertMarkedObjTo3D(bool bExtrude, const basegfx::B2DPoint& rPnt1, const basegfx::B2DPoint& rPnt2) { if(!AreObjectsMarked()) return; // Create undo if(bExtrude) BegUndo(SvxResId(RID_SVX_3D_UNDO_EXTRUDE)); else BegUndo(SvxResId(RID_SVX_3D_UNDO_LATHE)); SdrModel& rSdrModel(GetSdrMarkByIndex(0)->GetMarkedSdrObj()->getSdrModelFromSdrObject()); // Create a new scene for the created 3D object E3dScene* pScene = new E3dScene(rSdrModel); // Determine rectangle and possibly correct it tools::Rectangle aRect = GetAllMarkedRect(); if(aRect.GetWidth() <= 1) aRect.SetSize(Size(500, aRect.GetHeight())); if(aRect.GetHeight() <= 1) aRect.SetSize(Size(aRect.GetWidth(), 500)); // Determine the depth relative to the size of the selection double fDepth = 0.0; double fRot3D = 0.0; basegfx::B2DHomMatrix aLatheMat; if(bExtrude) { double fW = static_cast(aRect.GetWidth()); double fH = static_cast(aRect.GetHeight()); fDepth = sqrt(fW*fW + fH*fH) / 6.0; } if(!bExtrude) { // Create transformation for the polygons rotating body if (rPnt1 != rPnt2) { // Rotation around control point #1 with set angle // for 3D coordinates basegfx::B2DPoint aDiff(rPnt1 - rPnt2); fRot3D = atan2(aDiff.getY(), aDiff.getX()) - M_PI_2; if(basegfx::fTools::equalZero(fabs(fRot3D))) fRot3D = 0.0; if(fRot3D != 0.0) { aLatheMat = basegfx::utils::createRotateAroundPoint(rPnt2, -fRot3D) * aLatheMat; } } if (rPnt2.getX() != 0.0) { // Translation to Y=0 - axis aLatheMat.translate(-rPnt2.getX(), 0.0); } else { aLatheMat.translate(static_cast(-aRect.Left()), 0.0); } // Form the inverse matrix to determine the target expansion basegfx::B2DHomMatrix aInvLatheMat(aLatheMat); aInvLatheMat.invert(); // SnapRect extension enables mirroring in the axis of rotation for(size_t a=0; aGetMarkedSdrObj(); tools::Rectangle aTurnRect = pObj->GetSnapRect(); basegfx::B2DPoint aRot; Point aRotPnt; aRot = basegfx::B2DPoint(aTurnRect.Left(), -aTurnRect.Top()); aRot *= aLatheMat; aRot.setX(-aRot.getX()); aRot *= aInvLatheMat; aRotPnt = Point(static_cast(aRot.getX() + 0.5), static_cast(-aRot.getY() - 0.5)); aRect.Union(tools::Rectangle(aRotPnt, aRotPnt)); aRot = basegfx::B2DPoint(aTurnRect.Left(), -aTurnRect.Bottom()); aRot *= aLatheMat; aRot.setX(-aRot.getX()); aRot *= aInvLatheMat; aRotPnt = Point(static_cast(aRot.getX() + 0.5), static_cast(-aRot.getY() - 0.5)); aRect.Union(tools::Rectangle(aRotPnt, aRotPnt)); aRot = basegfx::B2DPoint(aTurnRect.Right(), -aTurnRect.Top()); aRot *= aLatheMat; aRot.setX(-aRot.getX()); aRot *= aInvLatheMat; aRotPnt = Point(static_cast(aRot.getX() + 0.5), static_cast(-aRot.getY() - 0.5)); aRect.Union(tools::Rectangle(aRotPnt, aRotPnt)); aRot = basegfx::B2DPoint(aTurnRect.Right(), -aTurnRect.Bottom()); aRot *= aLatheMat; aRot.setX(-aRot.getX()); aRot *= aInvLatheMat; aRotPnt = Point(static_cast(aRot.getX() + 0.5), static_cast(-aRot.getY() - 0.5)); aRect.Union(tools::Rectangle(aRotPnt, aRotPnt)); } } // Walk through the selection and convert it into 3D, complete with // Conversion to SdrPathObject, also fonts for(size_t a=0; aGetMarkedSdrObj(); ImpCreate3DObject(pScene, pObj, bExtrude, fDepth, aLatheMat); } if(pScene->GetSubList() && pScene->GetSubList()->GetObjCount() != 0) { // Arrange all created objects by depth if(bExtrude) DoDepthArrange(pScene, fDepth); // Center 3D objects in the middle of the overall rectangle basegfx::B3DPoint aCenter(pScene->GetBoundVolume().getCenter()); basegfx::B3DHomMatrix aMatrix; aMatrix.translate(-aCenter.getX(), -aCenter.getY(), -aCenter.getZ()); pScene->SetTransform(aMatrix * pScene->GetTransform()); // Initialize scene pScene->NbcSetSnapRect(aRect); basegfx::B3DRange aBoundVol = pScene->GetBoundVolume(); InitScene(pScene, static_cast(aRect.GetWidth()), static_cast(aRect.GetHeight()), aBoundVol.getDepth()); // Insert scene instead of the first selected object and throw away // all the old objects SdrObject* pRepObj = GetMarkedObjectByIndex(0); SdrPageView* pPV = GetSdrPageViewOfMarkedByIndex(0); MarkObj(pRepObj, pPV, true); ReplaceObjectAtView(pRepObj, *pPV, pScene, false); DeleteMarked(); MarkObj(pScene, pPV); // Rotate Rotation body around the axis of rotation if(!bExtrude && fRot3D != 0.0) { basegfx::B3DHomMatrix aRotate; aRotate.rotate(0.0, 0.0, fRot3D); pScene->SetTransform(aRotate * pScene->GetTransform()); } // Set default rotation { basegfx::B3DHomMatrix aRotate; aRotate.rotate(basegfx::deg2rad(20.0), 0.0, 0.0); // E3DModifySceneSnapRectUpdater updates the 2D representation of the scene. // It prepares things in ctor and acts in dtor. E3DModifySceneSnapRectUpdater aUpdater(pScene->getSdrObjectFromSdrObjList()); pScene->SetTransform(aRotate * pScene->GetTransform()); } } else { // No 3D object was created, throw away everything // always use SdrObject::Free(...) for SdrObjects (!) SdrObject* pTemp(pScene); SdrObject::Free(pTemp); } EndUndo(); } //Arrange all created extrude objects by depth namespace { struct E3dDepthNeighbour { E3dExtrudeObj* mpObj; basegfx::B2DPolyPolygon maPreparedPolyPolygon; E3dDepthNeighbour(E3dExtrudeObj* pObj, basegfx::B2DPolyPolygon const & rPreparedPolyPolygon) : mpObj(pObj), maPreparedPolyPolygon(rPreparedPolyPolygon) { } }; struct E3dDepthLayer { E3dDepthLayer* mpDown; std::vector mvNeighbours; E3dDepthLayer() : mpDown(nullptr) { } }; } void E3dView::DoDepthArrange(E3dScene const * pScene, double fDepth) { if(!(pScene && pScene->GetSubList() && pScene->GetSubList()->GetObjCount() > 1)) return; SdrObjList* pSubList = pScene->GetSubList(); SdrObjListIter aIter(pSubList, SdrIterMode::Flat); E3dDepthLayer* pBaseLayer = nullptr; E3dDepthLayer* pLayer = nullptr; sal_Int32 nNumLayers = 0; while(aIter.IsMore()) { E3dExtrudeObj* pExtrudeObj = dynamic_cast< E3dExtrudeObj* >(aIter.Next()); if(pExtrudeObj) { const basegfx::B2DPolyPolygon aExtrudePoly( basegfx::utils::prepareForPolygonOperation(pExtrudeObj->GetExtrudePolygon())); const SfxItemSet& rLocalSet = pExtrudeObj->GetMergedItemSet(); const drawing::FillStyle eLocalFillStyle = rLocalSet.Get(XATTR_FILLSTYLE).GetValue(); const Color aLocalColor = rLocalSet.Get(XATTR_FILLCOLOR).GetColorValue(); // sort in ExtrudeObj if(pLayer) { // do we have overlap with an object of this layer? bool bOverlap(false); for(const auto& rAct : pLayer->mvNeighbours) { // do rAct.mpObj and pExtrudeObj overlap? Check by // using logical AND clipping const basegfx::B2DPolyPolygon aAndPolyPolygon( basegfx::utils::solvePolygonOperationAnd( aExtrudePoly, rAct.maPreparedPolyPolygon)); if(aAndPolyPolygon.count() != 0) { // second criteria: is another fillstyle or color used? const SfxItemSet& rCompareSet = rAct.mpObj->GetMergedItemSet(); drawing::FillStyle eCompareFillStyle = rCompareSet.Get(XATTR_FILLSTYLE).GetValue(); if(eLocalFillStyle == eCompareFillStyle) { if(eLocalFillStyle == drawing::FillStyle_SOLID) { Color aCompareColor = rCompareSet.Get(XATTR_FILLCOLOR).GetColorValue(); if(aCompareColor == aLocalColor) { continue; } } else if(eLocalFillStyle == drawing::FillStyle_NONE) { continue; } } bOverlap = true; break; } } if(bOverlap) { // yes, start a new layer pLayer->mpDown = new E3dDepthLayer; pLayer = pLayer->mpDown; nNumLayers++; pLayer->mvNeighbours.emplace_back(pExtrudeObj, aExtrudePoly); } else { // no, add to current layer pLayer->mvNeighbours.emplace(pLayer->mvNeighbours.begin(), pExtrudeObj, aExtrudePoly); } } else { // first layer ever pBaseLayer = new E3dDepthLayer; pLayer = pBaseLayer; nNumLayers++; pLayer->mvNeighbours.emplace_back(pExtrudeObj, aExtrudePoly); } } } // number of layers is done if(nNumLayers > 1) { // need to be arranged double fMinDepth = fDepth * 0.8; double fStep = (fDepth - fMinDepth) / static_cast(nNumLayers); pLayer = pBaseLayer; while(pLayer) { // move along layer for(auto& rAct : pLayer->mvNeighbours) { // adapt extrude value rAct.mpObj->SetMergedItem(SfxUInt32Item(SDRATTR_3DOBJ_DEPTH, sal_uInt32(fMinDepth + 0.5))); } // next layer pLayer = pLayer->mpDown; fMinDepth += fStep; } } // cleanup while(pBaseLayer) { pLayer = pBaseLayer->mpDown; delete pBaseLayer; pBaseLayer = pLayer; } } // Start drag, create for 3D objects before possibly drag method bool E3dView::BegDragObj(const Point& rPnt, OutputDevice* pOut, SdrHdl* pHdl, short nMinMov, SdrDragMethod* pForcedMeth) { if(Is3DRotationCreationActive() && GetMarkedObjectCount()) { // Determine all selected polygons and return the mirrored helper overlay mpMirrorOverlay->SetMirrorAxis(maRef1, maRef2); } else { bool bOwnActionNecessary; if (pHdl == nullptr) { bOwnActionNecessary = true; } else if (pHdl->IsVertexHdl() || pHdl->IsCornerHdl()) { bOwnActionNecessary = true; } else { bOwnActionNecessary = false; } if(bOwnActionNecessary && GetMarkedObjectCount() > 0) { E3dDragConstraint eConstraint = E3dDragConstraint::XYZ; bool bThereAreRootScenes = false; bool bThereAre3DObjects = false; const size_t nCnt = GetMarkedObjectCount(); for(size_t nObjs = 0; nObjs < nCnt; ++nObjs) { SdrObject *pObj = GetMarkedObjectByIndex(nObjs); if(pObj) { if( auto pScene = dynamic_cast< const E3dScene* >(pObj) ) if( pScene->getRootE3dSceneFromE3dObject() == pObj ) bThereAreRootScenes = true; if(dynamic_cast< const E3dObject* >(pObj) != nullptr) { bThereAre3DObjects = true; } } } if( bThereAre3DObjects ) { meDragHdl = ( pHdl == nullptr ? SdrHdlKind::Move : pHdl->GetKind() ); switch ( meDragMode ) { case SdrDragMode::Rotate: case SdrDragMode::Shear: { switch ( meDragHdl ) { case SdrHdlKind::Left: case SdrHdlKind::Right: { eConstraint = E3dDragConstraint::X; } break; case SdrHdlKind::Upper: case SdrHdlKind::Lower: { eConstraint = E3dDragConstraint::Y; } break; case SdrHdlKind::UpperLeft: case SdrHdlKind::UpperRight: case SdrHdlKind::LowerLeft: case SdrHdlKind::LowerRight: { eConstraint = E3dDragConstraint::Z; } break; default: break; } // do not mask the allowed rotations eConstraint &= E3dDragConstraint::XYZ; pForcedMeth = new E3dDragRotate(*this, GetMarkedObjectList(), eConstraint, IsSolidDragging()); } break; case SdrDragMode::Move: { if(!bThereAreRootScenes) { pForcedMeth = new E3dDragMove(*this, GetMarkedObjectList(), meDragHdl, eConstraint, IsSolidDragging()); } } break; // later on case SdrDragMode::Mirror: case SdrDragMode::Crook: case SdrDragMode::Transparence: case SdrDragMode::Gradient: default: { } break; } } } } return SdrView::BegDragObj(rPnt, pOut, pHdl, nMinMov, pForcedMeth); } // Set current 3D drawing object, create the scene for this E3dScene* E3dView::SetCurrent3DObj(E3dObject* p3DObj) { DBG_ASSERT(p3DObj != nullptr, "Who puts in a NULL-pointer here"); // get transformed BoundVolume of the object basegfx::B3DRange aVolume(p3DObj->GetBoundVolume()); aVolume.transform(p3DObj->GetTransform()); double fW(aVolume.getWidth()); double fH(aVolume.getHeight()); tools::Rectangle aRect(0,0, static_cast(fW), static_cast(fH)); E3dScene* pScene = new E3dScene(p3DObj->getSdrModelFromSdrObject()); InitScene(pScene, fW, fH, aVolume.getMaxZ() + ((fW + fH) / 4.0)); pScene->InsertObject(p3DObj); pScene->NbcSetSnapRect(aRect); return pScene; } void E3dView::InitScene(E3dScene* pScene, double fW, double fH, double fCamZ) { Camera3D aCam(pScene->GetCamera()); aCam.SetAutoAdjustProjection(false); aCam.SetViewWindow(- fW / 2, - fH / 2, fW, fH); basegfx::B3DPoint aLookAt; double fDefaultCamPosZ = GetDefaultCamPosZ(); basegfx::B3DPoint aCamPos(0.0, 0.0, fCamZ < fDefaultCamPosZ ? fDefaultCamPosZ : fCamZ); aCam.SetPosAndLookAt(aCamPos, aLookAt); aCam.SetFocalLength(GetDefaultCamFocal()); pScene->SetCamera(aCam); } void E3dView::Start3DCreation() { if (!GetMarkedObjectCount()) return; //positioned tools::Long nOutMin = 0; tools::Long nOutMax = 0; tools::Long nMinLen = 0; tools::Long nObjDst = 0; tools::Long nOutHgt = 0; OutputDevice* pOut = GetFirstOutputDevice(); // first determine representation boundaries if (pOut != nullptr) { nMinLen = pOut->PixelToLogic(Size(0,50)).Height(); nObjDst = pOut->PixelToLogic(Size(0,20)).Height(); tools::Long nDst = pOut->PixelToLogic(Size(0,10)).Height(); nOutMin = -pOut->GetMapMode().GetOrigin().Y(); nOutMax = pOut->GetOutputSize().Height() - 1 + nOutMin; nOutMin += nDst; nOutMax -= nDst; if (nOutMax - nOutMin < nDst) { nOutMin += nOutMax + 1; nOutMin /= 2; nOutMin -= (nDst + 1) / 2; nOutMax = nOutMin + nDst; } nOutHgt = nOutMax - nOutMin; tools::Long nTemp = nOutHgt / 4; if (nTemp > nMinLen) nMinLen = nTemp; } // and then attach the marks at the top and bottom of the object basegfx::B2DRange aR; for(size_t nMark = 0; nMark < GetMarkedObjectCount(); ++nMark) { SdrObject* pMark = GetMarkedObjectByIndex(nMark); basegfx::B2DPolyPolygon aXPP(pMark->TakeXorPoly()); aR.expand(basegfx::utils::getRange(aXPP)); } basegfx::B2DPoint aCenter(aR.getCenter()); tools::Long nMarkHgt = basegfx::fround(aR.getHeight()) - 1; tools::Long nHgt = nMarkHgt + nObjDst * 2; if (nHgt < nMinLen) nHgt = nMinLen; tools::Long nY1 = basegfx::fround(aCenter.getY()) - (nHgt + 1) / 2; tools::Long nY2 = nY1 + nHgt; if (pOut && (nMinLen > nOutHgt)) nMinLen = nOutHgt; if (pOut) { if (nY1 < nOutMin) { nY1 = nOutMin; if (nY2 < nY1 + nMinLen) nY2 = nY1 + nMinLen; } if (nY2 > nOutMax) { nY2 = nOutMax; if (nY1 > nY2 - nMinLen) nY1 = nY2 - nMinLen; } } maRef1.setX( basegfx::fround(aR.getMinX()) ); // Initial move axis 2/100mm to the left maRef1.setY( nY1 ); maRef2.setX( maRef1.X() ); maRef2.setY( nY2 ); // Turn on marks SetMarkHandles(nullptr); //HMHif (bVis) ShowMarkHdl(); if (AreObjectsMarked()) MarkListHasChanged(); // Show mirror polygon IMMEDIATELY const SdrHdlList &aHdlList = GetHdlList(); mpMirrorOverlay.reset(new Impl3DMirrorConstructOverlay(*this)); mpMirrorOverlay->SetMirrorAxis(aHdlList.GetHdl(SdrHdlKind::Ref1)->GetPos(), aHdlList.GetHdl(SdrHdlKind::Ref2)->GetPos()); } // what happens with a mouse movement when the object is created? void E3dView::MovAction(const Point& rPnt) { if(Is3DRotationCreationActive()) { SdrHdl* pHdl = GetDragHdl(); if (pHdl) { SdrHdlKind eHdlKind = pHdl->GetKind(); // reacts only due to a mirror axis if ((eHdlKind == SdrHdlKind::Ref1) || (eHdlKind == SdrHdlKind::Ref2) || (eHdlKind == SdrHdlKind::MirrorAxis)) { const SdrHdlList &aHdlList = GetHdlList (); // delete the mirrored polygon, mirrors the original and draws // it anew SdrView::MovAction (rPnt); mpMirrorOverlay->SetMirrorAxis( aHdlList.GetHdl (SdrHdlKind::Ref1)->GetPos(), aHdlList.GetHdl (SdrHdlKind::Ref2)->GetPos()); } } else { SdrView::MovAction (rPnt); } } else { SdrView::MovAction (rPnt); } } // The End. Create object and any child objects through ImpCreate3DLathe. // With the parameter value sal_True (SDefault: sal_False) is simply a // rotation body created, without letting the user set the position of the // axis. It is sufficient with this call, if an object is selected. // (No initialization necessary) void E3dView::End3DCreation(bool bUseDefaultValuesForMirrorAxes) { ResetCreationActive(); if(!AreObjectsMarked()) return; if(bUseDefaultValuesForMirrorAxes) { tools::Rectangle aRect = GetAllMarkedRect(); if(aRect.GetWidth() <= 1) aRect.SetSize(Size(500, aRect.GetHeight())); if(aRect.GetHeight() <= 1) aRect.SetSize(Size(aRect.GetWidth(), 500)); basegfx::B2DPoint aPnt1(aRect.Left(), -aRect.Top()); basegfx::B2DPoint aPnt2(aRect.Left(), -aRect.Bottom()); ConvertMarkedObjTo3D(false, aPnt1, aPnt2); } else { // Turn off helper overlay // Determine from the handle positions and the displacement of // the points const SdrHdlList &aHdlList = GetHdlList(); Point aMirrorRef1 = aHdlList.GetHdl(SdrHdlKind::Ref1)->GetPos(); Point aMirrorRef2 = aHdlList.GetHdl(SdrHdlKind::Ref2)->GetPos(); basegfx::B2DPoint aPnt1(aMirrorRef1.X(), -aMirrorRef1.Y()); basegfx::B2DPoint aPnt2(aMirrorRef2.X(), -aMirrorRef2.Y()); ConvertMarkedObjTo3D(false, aPnt1, aPnt2); } } E3dView::~E3dView () { } void E3dView::ResetCreationActive () { mpMirrorOverlay.reset(); } void E3dView::InitView () { mpMirrorOverlay = nullptr; } bool E3dView::IsBreak3DObjPossible() const { const size_t nCount = GetMarkedObjectCount(); if (nCount > 0) { for (size_t i = 0; i < nCount; ++i) { SdrObject* pObj = GetMarkedObjectByIndex(i); if (auto p3dObject = dynamic_cast< E3dObject* >(pObj)) { if(!p3dObject->IsBreakObjPossible()) return false; } else { return false; } } } else { return false; } return true; } void E3dView::Break3DObj() { if(!IsBreak3DObjPossible()) return; // ALL selected objects are changed const size_t nCount = GetMarkedObjectCount(); BegUndo(SvxResId(RID_SVX_3D_UNDO_BREAK_LATHE)); for(size_t a=0; a(GetMarkedObjectByIndex(a)); BreakSingle3DObj(pObj); } DeleteMarked(); EndUndo(); } void E3dView::BreakSingle3DObj(E3dObject* pObj) { if(dynamic_cast< const E3dScene* >(pObj) != nullptr) { SdrObjList* pSubList = pObj->GetSubList(); SdrObjListIter aIter(pSubList, SdrIterMode::Flat); while(aIter.IsMore()) { E3dObject* pSubObj = static_cast(aIter.Next()); BreakSingle3DObj(pSubObj); } } else { SdrAttrObj* pNewObj = pObj->GetBreakObj().release(); if (pNewObj) { if (InsertObjectAtView(pNewObj, *GetSdrPageView(), SdrInsertFlags::DONTMARK)) { pNewObj->SetChanged(); pNewObj->BroadcastObjectChange(); } } } } void E3dView::CheckPossibilities() { // call parent SdrView::CheckPossibilities(); // Set other flags if(!(m_bGroupPossible || m_bUnGroupPossible || m_bGrpEnterPossible)) return; const size_t nMarkCnt = GetMarkedObjectCount(); bool bCompound = false; bool b3DObject = false; for(size_t nObjs = 0; (nObjs < nMarkCnt) && !bCompound; ++nObjs) { SdrObject *pObj = GetMarkedObjectByIndex(nObjs); if(dynamic_cast< const E3dCompoundObject* >(pObj)) bCompound = true; if(dynamic_cast< const E3dObject* >(pObj)) b3DObject = true; } // So far: there are two or more of any objects selected. See if // compound objects are involved. If yes, ban grouping. if(m_bGroupPossible && bCompound) m_bGroupPossible = false; if(m_bUnGroupPossible && b3DObject) m_bUnGroupPossible = false; if(m_bGrpEnterPossible && bCompound) m_bGrpEnterPossible = false; } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */