/* -*- 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 <drawinglayer/primitive2d/sceneprimitive2d.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/polygon/b2dpolygon.hxx>
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <drawinglayer/attribute/sdrlightattribute3d.hxx>
#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx>
#include <drawinglayer/primitive2d/groupprimitive2d.hxx>
#include <processor3d/zbufferprocessor3d.hxx>
#include <processor3d/shadow3dextractor.hxx>
#include <drawinglayer/geometry/viewinformation2d.hxx>
#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
#include <svtools/optionsdrawinglayer.hxx>
#include <processor3d/geometry2dextractor.hxx>
#include <basegfx/raster/bzpixelraster.hxx>
#include <utility>
#include <vcl/BitmapTools.hxx>
#include <vcl/skia/SkiaHelper.hxx>
#include <comphelper/threadpool.hxx>
#include <comphelper/lok.hxx>
#include <officecfg/Office/Common.hxx>

using namespace com::sun::star;

namespace
{
    Bitmap BPixelRasterToBitmap(const basegfx::BZPixelRaster& rRaster, sal_uInt16 mnAntiAlialize)
    {
        Bitmap aRetval;
        const sal_uInt32 nWidth(mnAntiAlialize ? rRaster.getWidth()/mnAntiAlialize : rRaster.getWidth());
        const sal_uInt32 nHeight(mnAntiAlialize ? rRaster.getHeight()/mnAntiAlialize : rRaster.getHeight());

        if(nWidth && nHeight)
        {
            const Size aDestSize(nWidth, nHeight);
            vcl::bitmap::RawBitmap aContent(aDestSize, 32);

            if(mnAntiAlialize)
            {
                const sal_uInt16 nDivisor(mnAntiAlialize * mnAntiAlialize);

                for(sal_uInt32 y(0); y < nHeight; y++)
                {
                    for(sal_uInt32 x(0); x < nWidth; x++)
                    {
                        sal_uInt16 nRed(0);
                        sal_uInt16 nGreen(0);
                        sal_uInt16 nBlue(0);
                        sal_uInt16 nAlpha(0);
                        sal_uInt32 nIndex(rRaster.getIndexFromXY(x * mnAntiAlialize, y * mnAntiAlialize));

                        for(sal_uInt32 c(0); c < mnAntiAlialize; c++)
                        {
                            for(sal_uInt32 d(0); d < mnAntiAlialize; d++)
                            {
                                const basegfx::BPixel& rPixel(rRaster.getBPixel(nIndex++));
                                nRed += rPixel.getRed();
                                nGreen += rPixel.getGreen();
                                nBlue += rPixel.getBlue();
                                nAlpha += rPixel.getAlpha();
                            }

                            nIndex += rRaster.getWidth() - mnAntiAlialize;
                        }

                        nAlpha /= nDivisor;

                        if(nAlpha)
                        {
                            aContent.SetPixel(y, x, Color(ColorAlpha,
                                static_cast<sal_uInt8>(nAlpha),
                                static_cast<sal_uInt8>(nRed / nDivisor),
                                static_cast<sal_uInt8>(nGreen / nDivisor),
                                static_cast<sal_uInt8>(nBlue / nDivisor) ));
                        }
                        else
                            aContent.SetPixel(y, x, Color(ColorAlpha, 0, 0, 0, 0));
                    }
                }
            }
            else
            {
                sal_uInt32 nIndex(0);

                for(sal_uInt32 y(0); y < nHeight; y++)
                {
                    for(sal_uInt32 x(0); x < nWidth; x++)
                    {
                        const basegfx::BPixel& rPixel(rRaster.getBPixel(nIndex++));

                        if(rPixel.getAlpha())
                        {
                            aContent.SetPixel(y, x, Color(ColorAlpha, rPixel.getAlpha(), rPixel.getRed(), rPixel.getGreen(), rPixel.getBlue()));
                        }
                        else
                            aContent.SetPixel(y, x, Color(ColorAlpha, 0, 0, 0, 0));
                    }
                }
            }

            aRetval = vcl::bitmap::CreateFromData(std::move(aContent));

            // #i101811# set PrefMapMode and PrefSize at newly created Bitmap
            aRetval.SetPrefMapMode(MapMode(MapUnit::MapPixel));
            aRetval.SetPrefSize(Size(nWidth, nHeight));
        }

        return aRetval;
    }
} // end of anonymous namespace

namespace drawinglayer::primitive2d
{
        bool ScenePrimitive2D::impGetShadow3D() const
        {
            // create on demand
            if(!mbShadow3DChecked && !getChildren3D().empty())
            {
                basegfx::B3DVector aLightNormal;
                const double fShadowSlant(getSdrSceneAttribute().getShadowSlant());
                const basegfx::B3DRange aScene3DRange(getChildren3D().getB3DRange(getViewInformation3D()));

                if(!maSdrLightingAttribute.getLightVector().empty())
                {
                    // get light normal from first light and normalize
                    aLightNormal = maSdrLightingAttribute.getLightVector()[0].getDirection();
                    aLightNormal.normalize();
                }

                // create shadow extraction processor
                processor3d::Shadow3DExtractingProcessor aShadowProcessor(
                    getViewInformation3D(),
                    getObjectTransformation(),
                    aLightNormal,
                    fShadowSlant,
                    aScene3DRange);

                // process local primitives
                aShadowProcessor.process(getChildren3D());

                // fetch result and set checked flag
                const_cast< ScenePrimitive2D* >(this)->maShadowPrimitives = aShadowProcessor.getPrimitive2DSequence();
                const_cast< ScenePrimitive2D* >(this)->mbShadow3DChecked = true;
            }

            // return if there are shadow primitives
            return !maShadowPrimitives.empty();
        }

        void ScenePrimitive2D::calculateDiscreteSizes(
            const geometry::ViewInformation2D& rViewInformation,
            basegfx::B2DRange& rDiscreteRange,
            basegfx::B2DRange& rVisibleDiscreteRange,
            basegfx::B2DRange& rUnitVisibleRange) const
        {
            // use unit range and transform to discrete coordinates
            rDiscreteRange = basegfx::B2DRange(0.0, 0.0, 1.0, 1.0);
            rDiscreteRange.transform(rViewInformation.getObjectToViewTransformation() * getObjectTransformation());

            // clip it against discrete Viewport (if set)
            rVisibleDiscreteRange = rDiscreteRange;

            if(!rViewInformation.getViewport().isEmpty())
            {
                rVisibleDiscreteRange.intersect(rViewInformation.getDiscreteViewport());
            }

            if(rVisibleDiscreteRange.isEmpty())
            {
                rUnitVisibleRange = rVisibleDiscreteRange;
            }
            else
            {
                // create UnitVisibleRange containing unit range values [0.0 .. 1.0] describing
                // the relative position of rVisibleDiscreteRange inside rDiscreteRange
                const double fDiscreteScaleFactorX(basegfx::fTools::equalZero(rDiscreteRange.getWidth()) ? 1.0 : 1.0 / rDiscreteRange.getWidth());
                const double fDiscreteScaleFactorY(basegfx::fTools::equalZero(rDiscreteRange.getHeight()) ? 1.0 : 1.0 / rDiscreteRange.getHeight());

                const double fMinX(basegfx::fTools::equal(rVisibleDiscreteRange.getMinX(), rDiscreteRange.getMinX())
                    ? 0.0
                    : (rVisibleDiscreteRange.getMinX() - rDiscreteRange.getMinX()) * fDiscreteScaleFactorX);
                const double fMinY(basegfx::fTools::equal(rVisibleDiscreteRange.getMinY(), rDiscreteRange.getMinY())
                    ? 0.0
                    : (rVisibleDiscreteRange.getMinY() - rDiscreteRange.getMinY()) * fDiscreteScaleFactorY);

                const double fMaxX(basegfx::fTools::equal(rVisibleDiscreteRange.getMaxX(), rDiscreteRange.getMaxX())
                    ? 1.0
                    : (rVisibleDiscreteRange.getMaxX() - rDiscreteRange.getMinX()) * fDiscreteScaleFactorX);
                const double fMaxY(basegfx::fTools::equal(rVisibleDiscreteRange.getMaxY(), rDiscreteRange.getMaxY())
                    ? 1.0
                    : (rVisibleDiscreteRange.getMaxY() - rDiscreteRange.getMinY()) * fDiscreteScaleFactorY);

                rUnitVisibleRange = basegfx::B2DRange(fMinX, fMinY, fMaxX, fMaxY);
            }
        }

        Primitive2DReference ScenePrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& rViewInformation) const
        {
            Primitive2DContainer aContainer;
            // create 2D shadows from contained 3D primitives. This creates the shadow primitives on demand and tells if
            // there are some or not. Do this at start, the shadow might still be visible even when the scene is not
            if(impGetShadow3D())
            {
                // test visibility
                const basegfx::B2DRange aShadow2DRange(maShadowPrimitives.getB2DRange(rViewInformation));
                const basegfx::B2DRange aViewRange(
                    rViewInformation.getViewport());

                if(aViewRange.isEmpty() || aShadow2DRange.overlaps(aViewRange))
                {
                    // add extracted 2d shadows (before 3d scene creations itself)
                    aContainer.append(maShadowPrimitives);
                }
            }

            // get the involved ranges (see helper method calculateDiscreteSizes for details)
            basegfx::B2DRange aDiscreteRange;
            basegfx::B2DRange aVisibleDiscreteRange;
            basegfx::B2DRange aUnitVisibleRange;

            calculateDiscreteSizes(rViewInformation, aDiscreteRange, aVisibleDiscreteRange, aUnitVisibleRange);

            if(aVisibleDiscreteRange.isEmpty())
                return new GroupPrimitive2D(std::move(aContainer));

            // test if discrete view size (pixel) maybe too big and limit it
            double fViewSizeX(aVisibleDiscreteRange.getWidth());
            double fViewSizeY(aVisibleDiscreteRange.getHeight());
            const double fViewVisibleArea(fViewSizeX * fViewSizeY);
            const double fMaximumVisibleArea(officecfg::Office::Common::Drawinglayer::Quadratic3DRenderLimit::get());
            double fReduceFactor(1.0);

            if(fViewVisibleArea > fMaximumVisibleArea)
            {
                fReduceFactor = sqrt(fMaximumVisibleArea / fViewVisibleArea);
                fViewSizeX *= fReduceFactor;
                fViewSizeY *= fReduceFactor;
            }

            if(rViewInformation.getReducedDisplayQuality())
            {
                // when reducing the visualisation is allowed (e.g. an OverlayObject
                // only needed for dragging), reduce resolution extra
                // to speed up dragging interactions
                const double fArea(fViewSizeX * fViewSizeY);
                if (fArea != 0.0)
                {
                    double fReducedVisualisationFactor(1.0 / (sqrt(fArea) * (1.0 / 170.0)));

                    if(fReducedVisualisationFactor > 1.0)
                    {
                        fReducedVisualisationFactor = 1.0;
                    }
                    else if(fReducedVisualisationFactor < 0.20)
                    {
                        fReducedVisualisationFactor = 0.20;
                    }

                    if(fReducedVisualisationFactor != 1.0)
                    {
                        fReduceFactor *= fReducedVisualisationFactor;
                    }
                }
            }

            // determine the oversample value
            static const sal_uInt16 nDefaultOversampleValue(3);
            sal_uInt16 nOversampleValue(SvtOptionsDrawinglayer::IsAntiAliasing() ? nDefaultOversampleValue : 0);

            geometry::ViewInformation3D aViewInformation3D(getViewInformation3D());
            {
                // calculate a transformation from DiscreteRange to evtl. rotated/sheared content.
                // Start with full transformation from object to discrete units
                basegfx::B2DHomMatrix aObjToUnit(rViewInformation.getObjectToViewTransformation() * getObjectTransformation());

                // bring to unit coordinates by applying inverse DiscreteRange
                aObjToUnit.translate(-aDiscreteRange.getMinX(), -aDiscreteRange.getMinY());
                if (aDiscreteRange.getWidth() != 0.0 && aDiscreteRange.getHeight() != 0.0)
                {
                    aObjToUnit.scale(1.0 / aDiscreteRange.getWidth(), 1.0 / aDiscreteRange.getHeight());
                }

                // calculate transformed user coordinate system
                const basegfx::B2DPoint aStandardNull(0.0, 0.0);
                const basegfx::B2DPoint aUnitRangeTopLeft(aObjToUnit * aStandardNull);
                const basegfx::B2DVector aStandardXAxis(1.0, 0.0);
                const basegfx::B2DVector aUnitRangeXAxis(aObjToUnit * aStandardXAxis);
                const basegfx::B2DVector aStandardYAxis(0.0, 1.0);
                const basegfx::B2DVector aUnitRangeYAxis(aObjToUnit * aStandardYAxis);

                if(!aUnitRangeTopLeft.equal(aStandardNull) || !aUnitRangeXAxis.equal(aStandardXAxis) || !aUnitRangeYAxis.equal(aStandardYAxis))
                {
                    // build transformation from unit range to user coordinate system; the unit range
                    // X and Y axes are the column vectors, the null point is the offset
                    basegfx::B2DHomMatrix aUnitRangeToUser;

                    aUnitRangeToUser.set3x2(
                        aUnitRangeXAxis.getX(), aUnitRangeYAxis.getX(), aUnitRangeTopLeft.getX(),
                        aUnitRangeXAxis.getY(), aUnitRangeYAxis.getY(), aUnitRangeTopLeft.getY());

                    // decompose to allow to apply this to the 3D transformation
                    basegfx::B2DVector aScale, aTranslate;
                    double fRotate, fShearX;
                    aUnitRangeToUser.decompose(aScale, aTranslate, fRotate, fShearX);

                    // apply before DeviceToView and after Projection, 3D is in range [-1.0 .. 1.0] in X,Y and Z
                    // and not yet flipped in Y
                    basegfx::B3DHomMatrix aExtendedProjection(aViewInformation3D.getProjection());

                    // bring to unit coordinates, flip Y, leave Z unchanged
                    aExtendedProjection.scale(0.5, -0.5, 1.0);
                    aExtendedProjection.translate(0.5, 0.5, 0.0);

                    // apply extra; Y is flipped now, go with positive shear and rotate values
                    aExtendedProjection.scale(aScale.getX(), aScale.getY(), 1.0);
                    aExtendedProjection.shearXZ(fShearX, 0.0);
                    aExtendedProjection.rotate(0.0, 0.0, fRotate);
                    aExtendedProjection.translate(aTranslate.getX(), aTranslate.getY(), 0.0);

                    // back to state after projection
                    aExtendedProjection.translate(-0.5, -0.5, 0.0);
                    aExtendedProjection.scale(2.0, -2.0, 1.0);

                    aViewInformation3D = geometry::ViewInformation3D(
                        aViewInformation3D.getObjectTransformation(),
                        aViewInformation3D.getOrientation(),
                        aExtendedProjection,
                        aViewInformation3D.getDeviceToView(),
                        aViewInformation3D.getViewTime(),
                        aViewInformation3D.getExtendedInformationSequence());
                }
            }

            // calculate logic render size in world coordinates for usage in renderer
            const basegfx::B2DHomMatrix& aInverseOToV(rViewInformation.getInverseObjectToViewTransformation());
            const double fLogicX((aInverseOToV * basegfx::B2DVector(aDiscreteRange.getWidth() * fReduceFactor, 0.0)).getLength());
            const double fLogicY((aInverseOToV * basegfx::B2DVector(0.0, aDiscreteRange.getHeight() * fReduceFactor)).getLength());

            // generate ViewSizes
            double fFullViewSizeX((rViewInformation.getObjectToViewTransformation() * basegfx::B2DVector(fLogicX, 0.0)).getLength());
            double fFullViewSizeY((rViewInformation.getObjectToViewTransformation() * basegfx::B2DVector(0.0, fLogicY)).getLength());

            // generate RasterWidth and RasterHeight for visible part
            sal_Int32 nRasterWidth(basegfx::fround(fFullViewSizeX * aUnitVisibleRange.getWidth()) + 1);
            sal_Int32 nRasterHeight(basegfx::fround(fFullViewSizeY * aUnitVisibleRange.getHeight()) + 1);

            if(!rViewInformation.getReducedDisplayQuality() && comphelper::LibreOfficeKit::isActive())
            {
                // for this purpose allow reduced 3D quality and make a compromise
                // between quality and speed. This is balanced between those two
                // targets, fine-tuning/experimenting can be done with the values
                // below.

                // define some values which allow fine-tuning this feature
                static const double fMin(80.0);
                static const double fSqareMin(fMin * fMin);
                static const double fMax(800.0);
                static const double fSqareMax(fMax * fMax);
                static const double fMaxReduction(0.65);

                // get the square pixels (work on pixel numbers to get same
                // behaviour independent of width/height relations)
                const double fSquarePixels(nRasterWidth * nRasterHeight);

                if (fSquarePixels > fSqareMin)
                {
                    // only reduce at all when more than fSqareMin pixels needed
                    double fReduction(fMaxReduction);

                    if (fSquarePixels < fSqareMax)
                    {
                        // range between fSqareMin and fSqareMax, calculate a
                        // linear interpolated reduction based on square root
                        fReduction = sqrt(fSquarePixels); // [fMin .. fMax]
                        fReduction = fReduction - fMin; // [0 .. (fMax - fMin)]
                        fReduction = fReduction / (fMax - fMin); // [0 .. 1]
                        fReduction = 1.0 - (fReduction * (1.0 - fMaxReduction)); // [1 .. fMaxReduction]

                        // reduce oversampling for this range
                        if(nOversampleValue > 2)
                            nOversampleValue--;
                    }
                    else
                    {
                        // more than fSqareMax pixels, disable oversampling
                        nOversampleValue = 0;
                    }

                    // adapt needed values to reduction
                    nRasterWidth = basegfx::fround(fReduction * nRasterWidth);
                    nRasterHeight = basegfx::fround(fReduction * nRasterHeight);
                    fFullViewSizeX *= fReduction;
                    fFullViewSizeY *= fReduction;
                }
            }

            if(!(nRasterWidth && nRasterHeight))
                return new GroupPrimitive2D(std::move(aContainer));

            // create view unit buffer
            basegfx::BZPixelRaster aBZPixelRaster(
                nOversampleValue ? nRasterWidth * nOversampleValue : nRasterWidth,
                nOversampleValue ? nRasterHeight * nOversampleValue : nRasterHeight);

            // check for parallel execution possibilities
            // Skia does not like being touched from multiple threads
            // at the same time, and methods like DefaultProcessor3D::impRenderBitmapTexturePrimitive3D
            // are going to do that.
            static bool bMultithreadAllowed = !SkiaHelper::isVCLSkiaEnabled(); // loplugin:constvars:ignore
            sal_Int32 nThreadCount(0);
            comphelper::ThreadPool& rThreadPool(comphelper::ThreadPool::getSharedOptimalPool());

            if(bMultithreadAllowed)
            {
                nThreadCount = rThreadPool.getWorkerCount();

                if(nThreadCount > 1)
                {
                    // at least use 10px per processor, so limit number of processors to
                    // target pixel size divided by 10 (which might be zero what is okay)
                    nThreadCount = std::min(nThreadCount, nRasterHeight / 10);
                }
            }

            if(nThreadCount > 1)
            {
                class Executor : public comphelper::ThreadTask
                {
                private:
                    std::unique_ptr<processor3d::ZBufferProcessor3D> mpZBufferProcessor3D;
                    const primitive3d::Primitive3DContainer&    mrChildren3D;

                public:
                    explicit Executor(
                        std::shared_ptr<comphelper::ThreadTaskTag> const & rTag,
                        std::unique_ptr<processor3d::ZBufferProcessor3D> pZBufferProcessor3D,
                        const primitive3d::Primitive3DContainer& rChildren3D)
                    :   comphelper::ThreadTask(rTag),
                        mpZBufferProcessor3D(std::move(pZBufferProcessor3D)),
                        mrChildren3D(rChildren3D)
                    {
                    }

                    virtual void doWork() override
                    {
                        mpZBufferProcessor3D->process(mrChildren3D);
                        mpZBufferProcessor3D->finish();
                        mpZBufferProcessor3D.reset();
                    }
                };

                const sal_uInt32 nLinesPerThread(aBZPixelRaster.getHeight() / nThreadCount);
                std::shared_ptr<comphelper::ThreadTaskTag> aTag = comphelper::ThreadPool::createThreadTaskTag();

                for(sal_Int32 a(0); a < nThreadCount; a++)
                {
                    std::unique_ptr<processor3d::ZBufferProcessor3D> pNewZBufferProcessor3D(new processor3d::ZBufferProcessor3D(
                        aViewInformation3D,
                        getSdrSceneAttribute(),
                        getSdrLightingAttribute(),
                        aUnitVisibleRange,
                        nOversampleValue,
                        fFullViewSizeX,
                        fFullViewSizeY,
                        aBZPixelRaster,
                        nLinesPerThread * a,
                        a + 1 == nThreadCount ? aBZPixelRaster.getHeight() : nLinesPerThread * (a + 1)));
                    std::unique_ptr<Executor> pExecutor(new Executor(aTag, std::move(pNewZBufferProcessor3D), getChildren3D()));
                    rThreadPool.pushTask(std::move(pExecutor));
                }

                rThreadPool.waitUntilDone(aTag);
            }
            else
            {
                // use default 3D primitive processor to create Bitmap for aUnitVisiblePart and process
                processor3d::ZBufferProcessor3D aZBufferProcessor3D(
                    aViewInformation3D,
                    getSdrSceneAttribute(),
                    getSdrLightingAttribute(),
                    aUnitVisibleRange,
                    nOversampleValue,
                    fFullViewSizeX,
                    fFullViewSizeY,
                    aBZPixelRaster,
                    0,
                    aBZPixelRaster.getHeight());

                aZBufferProcessor3D.process(getChildren3D());
                aZBufferProcessor3D.finish();
            }

            const_cast< ScenePrimitive2D* >(this)->maOldRenderedBitmap = BPixelRasterToBitmap(aBZPixelRaster, nOversampleValue);
            const Size aBitmapSizePixel(maOldRenderedBitmap.GetSizePixel());

            if(!(aBitmapSizePixel.getWidth() && aBitmapSizePixel.getHeight()))
                return new GroupPrimitive2D(std::move(aContainer));

            // create transform for the created bitmap in discrete coordinates first.
            basegfx::B2DHomMatrix aNew2DTransform;

            aNew2DTransform.set(0, 0, aVisibleDiscreteRange.getWidth());
            aNew2DTransform.set(1, 1, aVisibleDiscreteRange.getHeight());
            aNew2DTransform.set(0, 2, aVisibleDiscreteRange.getMinX());
            aNew2DTransform.set(1, 2, aVisibleDiscreteRange.getMinY());

            // transform back to world coordinates for usage in primitive creation
            aNew2DTransform *= aInverseOToV;

            // create bitmap primitive and add
            aContainer.push_back(
                new BitmapPrimitive2D(
                    maOldRenderedBitmap,
                    aNew2DTransform));

            // test: Allow to add an outline in the debugger when tests are needed
            static bool bAddOutlineToCreated3DSceneRepresentation(false); // loplugin:constvars:ignore

            if(bAddOutlineToCreated3DSceneRepresentation)
            {
                basegfx::B2DPolygon aOutline(basegfx::utils::createUnitPolygon());
                aOutline.transform(aNew2DTransform);
                aContainer.push_back(new PolygonHairlinePrimitive2D(std::move(aOutline), basegfx::BColor(1.0, 0.0, 0.0)));
            }
            return new GroupPrimitive2D(std::move(aContainer));
        }

        Primitive2DContainer ScenePrimitive2D::getGeometry2D() const
        {
            Primitive2DContainer aRetval;

            // create 2D projected geometry from 3D geometry
            if(!getChildren3D().empty())
            {
                // create 2D geometry extraction processor
                processor3d::Geometry2DExtractingProcessor aGeometryProcessor(
                    getViewInformation3D(),
                    getObjectTransformation());

                // process local primitives
                aGeometryProcessor.process(getChildren3D());

                // fetch result
                aRetval = aGeometryProcessor.getPrimitive2DSequence();
            }

            return aRetval;
        }

        Primitive2DContainer ScenePrimitive2D::getShadow2D() const
        {
            Primitive2DContainer aRetval;

            // create 2D shadows from contained 3D primitives
            if(impGetShadow3D())
            {
                // add extracted 2d shadows (before 3d scene creations itself)
                aRetval = maShadowPrimitives;
            }

            return aRetval;
        }

        bool ScenePrimitive2D::tryToCheckLastVisualisationDirectHit(const basegfx::B2DPoint& rLogicHitPoint, bool& o_rResult) const
        {
            if(maOldRenderedBitmap.IsEmpty() || maOldUnitVisiblePart.isEmpty())
                return false;

            basegfx::B2DHomMatrix aInverseSceneTransform(getObjectTransformation());
            aInverseSceneTransform.invert();
            const basegfx::B2DPoint aRelativePoint(aInverseSceneTransform * rLogicHitPoint);

            if(!maOldUnitVisiblePart.isInside(aRelativePoint))
                return false;

            // calculate coordinates relative to visualized part
            double fDivisorX(maOldUnitVisiblePart.getWidth());
            double fDivisorY(maOldUnitVisiblePart.getHeight());

            if(basegfx::fTools::equalZero(fDivisorX))
            {
                fDivisorX = 1.0;
            }

            if(basegfx::fTools::equalZero(fDivisorY))
            {
                fDivisorY = 1.0;
            }

            const double fRelativeX((aRelativePoint.getX() - maOldUnitVisiblePart.getMinX()) / fDivisorX);
            const double fRelativeY((aRelativePoint.getY() - maOldUnitVisiblePart.getMinY()) / fDivisorY);

            // combine with real BitmapSizePixel to get bitmap coordinates
            const Size aBitmapSizePixel(maOldRenderedBitmap.GetSizePixel());
            const sal_Int32 nX(basegfx::fround(fRelativeX * aBitmapSizePixel.Width()));
            const sal_Int32 nY(basegfx::fround(fRelativeY * aBitmapSizePixel.Height()));

            // try to get a statement about transparency in that pixel
            o_rResult = (0 != maOldRenderedBitmap.GetPixelColor(nX, nY).GetAlpha());
            return true;
        }

        ScenePrimitive2D::ScenePrimitive2D(
            primitive3d::Primitive3DContainer aChildren3D,
            attribute::SdrSceneAttribute  aSdrSceneAttribute,
            attribute::SdrLightingAttribute aSdrLightingAttribute,
            basegfx::B2DHomMatrix aObjectTransformation,
            geometry::ViewInformation3D aViewInformation3D)
        :   BufferedDecompositionPrimitive2D(),
            mxChildren3D(std::move(aChildren3D)),
            maSdrSceneAttribute(std::move(aSdrSceneAttribute)),
            maSdrLightingAttribute(std::move(aSdrLightingAttribute)),
            maObjectTransformation(std::move(aObjectTransformation)),
            maViewInformation3D(std::move(aViewInformation3D)),
            mbShadow3DChecked(false),
            mfOldDiscreteSizeX(0.0),
            mfOldDiscreteSizeY(0.0)
        {
            // activate callback to flush buffered decomposition content
            activateFlushOnTimer();
        }

        bool ScenePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
        {
            if(BufferedDecompositionPrimitive2D::operator==(rPrimitive))
            {
                const ScenePrimitive2D& rCompare = static_cast<const ScenePrimitive2D&>(rPrimitive);

                return (getChildren3D() == rCompare.getChildren3D()
                    && getSdrSceneAttribute() == rCompare.getSdrSceneAttribute()
                    && getSdrLightingAttribute() == rCompare.getSdrLightingAttribute()
                    && getObjectTransformation() == rCompare.getObjectTransformation()
                    && getViewInformation3D() == rCompare.getViewInformation3D());
            }

            return false;
        }

        basegfx::B2DRange ScenePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const
        {
            // transform unit range to discrete coordinate range
            basegfx::B2DRange aRetval(0.0, 0.0, 1.0, 1.0);
            aRetval.transform(rViewInformation.getObjectToViewTransformation() * getObjectTransformation());

            // force to discrete expanded bounds (it grows, so expanding works perfectly well)
            aRetval.expand(basegfx::B2DTuple(floor(aRetval.getMinX()), floor(aRetval.getMinY())));
            aRetval.expand(basegfx::B2DTuple(ceil(aRetval.getMaxX()), ceil(aRetval.getMaxY())));

            // transform back from discrete (view) to world coordinates
            aRetval.transform(rViewInformation.getInverseObjectToViewTransformation());

            // expand by evtl. existing shadow primitives
            if(impGetShadow3D())
            {
                const basegfx::B2DRange aShadow2DRange(maShadowPrimitives.getB2DRange(rViewInformation));

                if(!aShadow2DRange.isEmpty())
                {
                    aRetval.expand(aShadow2DRange);
                }
            }

            return aRetval;
        }

        void ScenePrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const
        {
            // get the involved ranges (see helper method calculateDiscreteSizes for details)
            basegfx::B2DRange aDiscreteRange;
            basegfx::B2DRange aUnitVisibleRange;
            bool bNeedNewDecomposition(false);
            bool bDiscreteSizesAreCalculated(false);

            if(hasBuffered2DDecomposition())
            {
                basegfx::B2DRange aVisibleDiscreteRange;
                calculateDiscreteSizes(rViewInformation, aDiscreteRange, aVisibleDiscreteRange, aUnitVisibleRange);
                bDiscreteSizesAreCalculated = true;

                // needs to be painted when the new part is not part of the last
                // decomposition
                if(!maOldUnitVisiblePart.isInside(aUnitVisibleRange))
                {
                    bNeedNewDecomposition = true;
                }

                // display has changed and cannot be reused when resolution got bigger. It
                // can be reused when resolution got smaller, though.
                if(!bNeedNewDecomposition)
                {
                    if(basegfx::fTools::more(aDiscreteRange.getWidth(), mfOldDiscreteSizeX) ||
                        basegfx::fTools::more(aDiscreteRange.getHeight(), mfOldDiscreteSizeY))
                    {
                        bNeedNewDecomposition = true;
                    }
                }
            }

            if(bNeedNewDecomposition)
            {
                // conditions of last local decomposition have changed, delete
                const_cast< ScenePrimitive2D* >(this)->setBuffered2DDecomposition(nullptr);
            }

            if(!hasBuffered2DDecomposition())
            {
                if(!bDiscreteSizesAreCalculated)
                {
                    basegfx::B2DRange aVisibleDiscreteRange;
                    calculateDiscreteSizes(rViewInformation, aDiscreteRange, aVisibleDiscreteRange, aUnitVisibleRange);
                }

                // remember last used NewDiscreteSize and NewUnitVisiblePart
                ScenePrimitive2D* pThat = const_cast< ScenePrimitive2D* >(this);
                pThat->mfOldDiscreteSizeX = aDiscreteRange.getWidth();
                pThat->mfOldDiscreteSizeY = aDiscreteRange.getHeight();
                pThat->maOldUnitVisiblePart = aUnitVisibleRange;
            }

            // use parent implementation
            BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation);
        }

        // provide unique ID
        sal_uInt32 ScenePrimitive2D::getPrimitive2DID() const
        {
            return PRIMITIVE2D_ID_SCENEPRIMITIVE2D;
        }

} // end of namespace

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
