/* -*- 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(aXPP.getB2DRange());
    }

    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: */
