/* -*- 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 <svx/svdopath.hxx>
#include <svx/svditer.hxx>
#include <svx/svdmodel.hxx>
#include <svx/svdpagv.hxx>
#include <editeng/colritem.hxx>
#include <editeng/eeitem.hxx>
#include <svx/svdview.hxx>
#include <svx/strings.hrc>
#include <svx/dialmgr.hxx>
#include <svx/obj3d.hxx>
#include <svx/lathe3d.hxx>
#include <extrud3d.hxx>
#include <dragmt3d.hxx>
#include <svx/scene3d.hxx>
#include <svx/view3d.hxx>
#include <svx/svdundo.hxx>
#include <svx/xflclit.hxx>
#include <svx/xlnclit.hxx>
#include <svx/xfillit0.hxx>
#include <svx/xlineit0.hxx>
#include <basegfx/range/b2drange.hxx>
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <svx/xlnwtit.hxx>
#include <svx/sdr/overlay/overlaypolypolygon.hxx>
#include <svx/sdr/overlay/overlaymanager.hxx>
#include <svx/sdrpaintwindow.hxx>
#include <svx/sdr/contact/viewcontact.hxx>
#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
#include <svx/sdr/overlay/overlayprimitive2dsequenceobject.hxx>
#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
#include <svx/e3dsceneupdater.hxx>
#include <utility>
 
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),
    mpPolygons(nullptr)
{
    const SdrMarkList& rMarkList = mrView.GetMarkedObjectList();
    mnCount = rMarkList.GetMarkCount();
    if(!mnCount)
        return;
 
    if(mrView.IsSolidDragging())
    {
        SdrPageView* pPV = rView.GetSdrPageView();
 
        if(pPV && pPV->PageWindowCount())
        {
            for(size_t a = 0; a < mnCount; ++a)
            {
                SdrObject* pObject = rMarkList.GetMark(a)->GetMarkedSdrObj();
 
                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 = rMarkList.GetMark(a)->GetMarkedSdrObj();
            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
                        aContent = drawinglayer::primitive2d::Primitive2DContainer {
                            new drawinglayer::primitive2d::TransformPrimitive2D(aMatrixTransform, std::move(aContent))
                        };
                    }
 
                    // if we have full overlay from selected objects, embed with 50% transparence, the
                    // transformation is added to the OverlayPrimitive2DSequenceObject
                    aContent = drawinglayer::primitive2d::Primitive2DContainer {
                        new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(std::move(aContent), 0.5)
                    };
 
                    std::unique_ptr<sdr::overlay::OverlayPrimitive2DSequenceObject> 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<sdr::overlay::OverlayPolyPolygonStripedAndFilled> pNew(new sdr::overlay::OverlayPolyPolygonStripedAndFilled(
                        std::move(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 SdrMarkList& rMarkList = GetMarkedObjectList();
    const size_t nCnt = rMarkList.GetMarkCount();
    for(size_t nObjs = 0; nObjs < nCnt; ++nObjs)
    {
        SdrObject *pObj = rMarkList.GetMark(nObjs)->GetMarkedSdrObj();
        if(auto pCompoundObject = dynamic_cast<E3dCompoundObject*>(pObj))
        {
            // related scene
            pScene = pCompoundObject->getRootE3dSceneFromE3dObject();
 
            if(nullptr != pScene && !IsObjMarked(pScene))
            {
                bSpecialHandling = true;
            }
        }
        // Reset all selection flags
        if(auto p3dObject = DynCastE3dObject(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 = rMarkList.GetMark(nObjs)->GetMarkedSdrObj();
            if(auto pCompoundObject = dynamic_cast<E3dCompoundObject*>(pObj))
            {
                // related scene
                pScene = pCompoundObject->getRootE3dSceneFromE3dObject();
 
                if(nullptr != pScene)
                {
                    pScene->SetSelected(false);
                }
            }
        }
 
        for(size_t nObjs = 0; nObjs < nCnt; ++nObjs)
        {
            SdrObject *pObj = rMarkList.GetMark(nObjs)->GetMarkedSdrObj();
            if(auto p3DObj = DynCastE3dObject(pObj))
            {
                // Select object
                p3DObj->SetSelected(true);
                pScene = p3DObj->getRootE3dSceneFromE3dObject();
            }
        }
 
        if(nullptr != pScene)
        {
            // code from parent
            rMarkList.ForceSort();
 
            pScene->SetDrawOnlySelected(true);
            pScene->SingleObjectPainter(rOut);
            pScene->SetDrawOnlySelected(false);
        }
 
        // Reset selection flag
        for(size_t nObjs = 0; nObjs < nCnt; ++nObjs)
        {
            SdrObject *pObj = rMarkList.GetMark(nObjs)->GetMarkedSdrObj();
            if(auto pCompoundObject = dynamic_cast<E3dCompoundObject*>(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<SdrModel> E3dView::CreateMarkedObjModel() const
{
    // Does 3D objects exist which scenes are not selected?
    bool bSpecialHandling(false);
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    const size_t nCount(rMarkList.GetMarkCount());
    E3dScene *pScene = nullptr;
 
    for(size_t nObjs = 0; nObjs < nCount; ++nObjs)
    {
        const SdrObject* pObj = rMarkList.GetMark(nObjs)->GetMarkedSdrObj();
 
        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 = DynCastE3dObject(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<SdrModel> 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 = rMarkList.GetMark(nObjs)->GetMarkedSdrObj();
 
        if(auto p3DObj = dynamic_cast<E3dCompoundObject*>(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(rMarkList);
    SdrMarkList aNewML;
    SdrMarkList& rCurrentMarkList = const_cast<E3dView*>(this)->GetMarkedObjectListWriteAccess();
    rCurrentMarkList = aNewML;
 
    for(size_t nObjs = 0; nObjs < nCount; ++nObjs)
    {
        SdrObject *pObj = aOldML.GetMark(nObjs)->GetMarkedSdrObj();
 
        if(auto p3dObject = DynCastE3dObject(pObj))
        {
            pScene = p3dObject->getRootE3dSceneFromE3dObject();
 
            if(nullptr != pScene && !IsObjMarked(pScene) && GetSdrPageView())
            {
                const_cast<E3dView*>(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);
 
            for (const rtl::Reference<SdrObject>& pSrcOb : *pSrcPg)
            {
                if(const E3dScene* p3dscene = DynCastE3dScene( pSrcOb.get()))
                {
                    pScene = const_cast<E3dScene*>(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(DynCastE3dScene(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);
 
            // calculate offset for paste
            tools::Rectangle aR = pSrcPg->GetAllObjBoundRect();
            Point aDist(aPos - aR.Center());
 
            // Insert sub-objects for scenes
            for (const rtl::Reference<SdrObject>& pSrcOb : *pSrcPg)
            {
                if(const E3dScene* p3dscene = DynCastE3dScene(pSrcOb.get()))
                {
                    E3dScene* pSrcScene = const_cast<E3dScene*>(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 (const rtl::Reference<SdrObject>& pObj : *pSrcScene->GetSubList())
        {
            E3dCompoundObject* pCompoundObj = dynamic_cast< E3dCompoundObject* >(pObj.get());
 
            if(pCompoundObj)
            {
                rtl::Reference<E3dCompoundObject> pNewCompoundObj = SdrObject::Clone(*pCompoundObj, 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.get());
                    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);
 
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    for(size_t a=0; !bAny3D && a<rMarkList.GetMarkCount(); ++a)
    {
        SdrObject *pObj = rMarkList.GetMark(a)->GetMarkedSdrObj();
        if(pObj)
        {
            ImpIsConvertTo3DPossible(pObj, bAny3D, bGroupSelected);
        }
    }
 
    bRetval = !bAny3D
        && (
           IsConvertToPolyObjPossible()
        || IsConvertToPathObjPossible()
        || IsImportMtfPossible());
    return bRetval;
}
 
void E3dView::ImpIsConvertTo3DPossible(SdrObject const * pObj, bool& rAny3D,
    bool& rGroupSelected) const
{
    if(!pObj)
        return;
 
    if(DynCastE3dObject(pObj))
    {
        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(DynCastSdrTextObj( 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<const SdrPathObj*>( 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<SdrPathObj*>( 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
    rtl::Reference<E3dObject> p3DObj;
    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, std::move(aPolyPoly2D));
    }
 
    // Set attribute
    p3DObj->NbcSetLayer(pObj->GetLayer());
 
    p3DObj->SetMergedItemSet(aSet);
 
    p3DObj->NbcSetStyleSheet(pObj->GetStyleSheet(), true);
 
    // Insert a new extrude object
    pScene->InsertObject(p3DObj.get());
}
 
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
    rtl::Reference<SdrObject> pNewObj1 = pObj->ConvertToPolyObj(false, false);
 
    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.get());
 
    // convert completely to path objects
    rtl::Reference<SdrObject> pNewObj2 = pObj->ConvertToContourObj(pNewObj1.get(), 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.get(), bExtrude, fDepth, rLatheMat);
    }
}
 
void E3dView::ConvertMarkedObjTo3D(bool bExtrude, const basegfx::B2DPoint& rPnt1, const basegfx::B2DPoint& rPnt2)
{
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    if(rMarkList.GetMarkCount() == 0)
        return;
 
    // Create undo
    if(bExtrude)
        BegUndo(SvxResId(RID_SVX_3D_UNDO_EXTRUDE));
    else
        BegUndo(SvxResId(RID_SVX_3D_UNDO_LATHE));
 
    SdrModel& rSdrModel(rMarkList.GetMark(0)->GetMarkedSdrObj()->getSdrModelFromSdrObject());
 
    // Create a new scene for the created 3D object
    rtl::Reference<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)
    {
        fDepth = std::hypot(aRect.GetWidth(), aRect.GetHeight()) / 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<double>(-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; a<rMarkList.GetMarkCount(); ++a)
        {
            SdrMark* pMark = rMarkList.GetMark(a);
            SdrObject* pObj = pMark->GetMarkedSdrObj();
            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<tools::Long>(aRot.getX() + 0.5), static_cast<tools::Long>(-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<tools::Long>(aRot.getX() + 0.5), static_cast<tools::Long>(-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<tools::Long>(aRot.getX() + 0.5), static_cast<tools::Long>(-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<tools::Long>(aRot.getX() + 0.5), static_cast<tools::Long>(-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; a<rMarkList.GetMarkCount(); ++a)
    {
        SdrMark* pMark = rMarkList.GetMark(a);
        SdrObject* pObj = pMark->GetMarkedSdrObj();
 
        ImpCreate3DObject(pScene.get(), pObj, bExtrude, fDepth, aLatheMat);
    }
 
    if(pScene->GetSubList() && pScene->GetSubList()->GetObjCount() != 0)
    {
        // Arrange all created objects by depth
        if(bExtrude)
            DoDepthArrange(pScene.get(), 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.get(), static_cast<double>(aRect.GetWidth()), static_cast<double>(aRect.GetHeight()), aBoundVol.getDepth());
 
        // Insert scene instead of the first selected object and throw away
        // all the old objects
        SdrMark* pMark = rMarkList.GetMark(0);
        if (pMark)
        {
            SdrObject* pRepObj = pMark->GetMarkedSdrObj();
            SdrPageView* pPV = pMark->GetPageView();
            MarkObj(pRepObj, pPV, true);
            ReplaceObjectAtView(pRepObj, *pPV, pScene.get(), false);
            DeleteMarked();
            MarkObj(pScene.get(), 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
        pScene.clear();
 
    EndUndo();
}
 
//Arrange all created extrude objects by depth
 
namespace {
 
struct E3dDepthNeighbour
{
    E3dExtrudeObj*              mpObj;
    basegfx::B2DPolyPolygon     maPreparedPolyPolygon;
 
    E3dDepthNeighbour(E3dExtrudeObj* pObj, basegfx::B2DPolyPolygon aPreparedPolyPolygon)
    :   mpObj(pObj),
        maPreparedPolyPolygon(std::move(aPreparedPolyPolygon))
    {
    }
};
 
struct E3dDepthLayer
{
    E3dDepthLayer*              mpDown;
    std::vector<E3dDepthNeighbour> 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<double>(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)
{
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    if(Is3DRotationCreationActive() && rMarkList.GetMarkCount())
    {
        // 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 && rMarkList.GetMarkCount() > 0)
        {
            E3dDragConstraint eConstraint = E3dDragConstraint::XYZ;
            bool bThereAreRootScenes = false;
            bool bThereAre3DObjects = false;
            const size_t nCnt = rMarkList.GetMarkCount();
            for(size_t nObjs = 0; nObjs < nCnt; ++nObjs)
            {
                SdrObject *pObj = rMarkList.GetMark(nObjs)->GetMarkedSdrObj();
                if(pObj)
                {
                    if( const E3dScene* pScene = DynCastE3dScene(pObj) )
                        if( pScene->getRootE3dSceneFromE3dObject() == pObj )
                            bThereAreRootScenes = true;
 
                    if(DynCastE3dObject(pObj))
                    {
                        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, rMarkList, eConstraint, IsSolidDragging());
                    }
                    break;
 
                    case SdrDragMode::Move:
                    {
                        if(!bThereAreRootScenes)
                        {
                            pForcedMeth = new E3dDragMove(*this, rMarkList, 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
rtl::Reference<E3dScene> E3dView::SetCurrent3DObj(E3dObject* p3DObj)
{
    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<tools::Long>(fW), static_cast<tools::Long>(fH));
 
    rtl::Reference<E3dScene> pScene = new E3dScene(p3DObj->getSdrModelFromSdrObject());
 
    InitScene(pScene.get(), 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()
{
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    if (!rMarkList.GetMarkCount())
        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 < rMarkList.GetMarkCount(); ++nMark)
    {
        SdrObject* pMark = rMarkList.GetMark(nMark)->GetMarkedSdrObj();
        basegfx::B2DPolyPolygon aXPP(pMark->TakeXorPoly());
        aR.expand(basegfx::utils::getRange(aXPP));
    }
 
    basegfx::B2DPoint aCenter(aR.getCenter());
    tools::Long      nMarkHgt = basegfx::fround<tools::Long>(aR.getHeight()) - 1;
    tools::Long      nHgt     = nMarkHgt + nObjDst * 2;
 
    if (nHgt < nMinLen) nHgt = nMinLen;
 
    tools::Long nY1 = basegfx::fround<tools::Long>(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<tools::Long>(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 (rMarkList.GetMarkCount() != 0) 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();
 
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    if(rMarkList.GetMarkCount() == 0)
        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 SdrMarkList& rMarkList = GetMarkedObjectList();
    const size_t nCount = rMarkList.GetMarkCount();
 
    if (nCount > 0)
    {
        for (size_t i = 0; i < nCount; ++i)
        {
            SdrObject* pObj = rMarkList.GetMark(i)->GetMarkedSdrObj();
 
            if (auto p3dObject = DynCastE3dObject(pObj))
            {
                if(!p3dObject->IsBreakObjPossible())
                    return false;
            }
            else
            {
                return false;
            }
        }
    }
    else
    {
        return false;
    }
 
    return true;
}
 
void E3dView::Break3DObj()
{
    if(!IsBreak3DObjPossible())
        return;
 
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    // ALL selected objects are changed
    const size_t nCount = rMarkList.GetMarkCount();
 
    BegUndo(SvxResId(RID_SVX_3D_UNDO_BREAK_LATHE));
    for(size_t a=0; a<nCount; ++a)
    {
        E3dObject* pObj = static_cast<E3dObject*>(rMarkList.GetMark(a)->GetMarkedSdrObj());
        BreakSingle3DObj(pObj);
    }
    DeleteMarked();
    EndUndo();
}
 
void E3dView::BreakSingle3DObj(E3dObject* pObj)
{
    if(DynCastE3dScene(pObj))
    {
        SdrObjList* pSubList = pObj->GetSubList();
        SdrObjListIter aIter(pSubList, SdrIterMode::Flat);
 
        while(aIter.IsMore())
        {
            E3dObject* pSubObj = static_cast<E3dObject*>(aIter.Next());
            BreakSingle3DObj(pSubObj);
        }
    }
    else
    {
        rtl::Reference<SdrAttrObj> pNewObj = pObj->GetBreakObj();
        if (pNewObj)
        {
            if (InsertObjectAtView(pNewObj.get(), *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 SdrMarkList& rMarkList = GetMarkedObjectList();
    const size_t nMarkCnt = rMarkList.GetMarkCount();
    bool bCompound = false;
    bool b3DObject = false;
    for(size_t nObjs = 0; (nObjs < nMarkCnt) && !bCompound; ++nObjs)
    {
        SdrObject *pObj = rMarkList.GetMark(nObjs)->GetMarkedSdrObj();
        if(dynamic_cast< const E3dCompoundObject* >(pObj))
            bCompound = true;
        if(DynCastE3dObject(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: */

V530 The return value of function 'Union' is required to be utilized.

V530 The return value of function 'Union' is required to be utilized.

V530 The return value of function 'Union' is required to be utilized.

V530 The return value of function 'Union' is required to be utilized.

V530 The return value of function 'Union' is required to be utilized.