/**************************************************************
 * 
 * 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
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_drawinglayer.hxx"

#include <drawinglayer/processor3d/cutfindprocessor3d.hxx>
#include <drawinglayer/primitive3d/drawinglayer_primitivetypes3d.hxx>
#include <drawinglayer/primitive3d/transformprimitive3d.hxx>
#include <drawinglayer/primitive3d/hatchtextureprimitive3d.hxx>
#include <drawinglayer/primitive3d/polypolygonprimitive3d.hxx>
#include <basegfx/polygon/b3dpolygon.hxx>
#include <basegfx/polygon/b3dpolygontools.hxx>
#include <basegfx/polygon/b3dpolypolygontools.hxx>
#include <drawinglayer/primitive3d/hiddengeometryprimitive3d.hxx>

//////////////////////////////////////////////////////////////////////////////

namespace drawinglayer
{
	namespace processor3d
	{
        CutFindProcessor::CutFindProcessor(const geometry::ViewInformation3D& rViewInformation, 
            const basegfx::B3DPoint& rFront, 
            const basegfx::B3DPoint& rBack,
            bool bAnyHit)
        :   BaseProcessor3D(rViewInformation),
            maFront(rFront),
            maBack(rBack),
            maResult(),
            maCombinedTransform(),
            mbAnyHit(bAnyHit),
			mbUseInvisiblePrimitiveContent(true)
        {
        }

        void CutFindProcessor::processBasePrimitive3D(const primitive3d::BasePrimitive3D& rCandidate)
        {
			if(getAnyHit() && maResult.size())
			{
				// stop processing as soon as a hit was recognized
				return;
			}

            // it is a BasePrimitive3D implementation, use getPrimitive3DID() call for switch
			switch(rCandidate.getPrimitive3DID())
			{
				case PRIMITIVE3D_ID_TRANSFORMPRIMITIVE3D :
				{
					// transform group. 
					const primitive3d::TransformPrimitive3D& rPrimitive = static_cast< const primitive3d::TransformPrimitive3D& >(rCandidate);

					// remember old and transform front, back to object coordinates
					const basegfx::B3DPoint aLastFront(maFront);
					const basegfx::B3DPoint aLastBack(maBack);
					basegfx::B3DHomMatrix aInverseTrans(rPrimitive.getTransformation());
					aInverseTrans.invert();
					maFront *= aInverseTrans;
					maBack *= aInverseTrans;

					// remember current and create new transformation; add new object transform from right side
					const geometry::ViewInformation3D aLastViewInformation3D(getViewInformation3D());
					const geometry::ViewInformation3D aNewViewInformation3D(
						aLastViewInformation3D.getObjectTransformation() * rPrimitive.getTransformation(),
						aLastViewInformation3D.getOrientation(),
						aLastViewInformation3D.getProjection(),
						aLastViewInformation3D.getDeviceToView(),
						aLastViewInformation3D.getViewTime(),
						aLastViewInformation3D.getExtendedInformationSequence());
					updateViewInformation(aNewViewInformation3D);
					
                    // #i102956# remember needed back-transform for found cuts (combine from right side)
                    const basegfx::B3DHomMatrix aLastCombinedTransform(maCombinedTransform);
                    maCombinedTransform = maCombinedTransform * rPrimitive.getTransformation();

					// let break down
					process(rPrimitive.getChildren());

					// restore transformations and front, back
                    maCombinedTransform = aLastCombinedTransform;
					updateViewInformation(aLastViewInformation3D);
					maFront = aLastFront;
					maBack = aLastBack;
					break;
				}
				case PRIMITIVE3D_ID_POLYGONHAIRLINEPRIMITIVE3D :
				{
					// PolygonHairlinePrimitive3D, not used for hit test with planes, ignore. This
					// means that also thick line expansion will not be hit-tested as
					// PolyPolygonMaterialPrimitive3D
                    break;
				}
				case PRIMITIVE3D_ID_HATCHTEXTUREPRIMITIVE3D :
				{
					// #i97321#
					// For HatchTexturePrimitive3D, do not use the decomposition since it will produce
					// clipped hatch lines in 3D. It can be used when the hatch also has a filling, but for
					// simplicity, just use the children which are the PolyPolygonMaterialPrimitive3D
					// which define the hatched areas anyways; for HitTest this is more than adequate
					const primitive3d::HatchTexturePrimitive3D& rPrimitive = static_cast< const primitive3d::HatchTexturePrimitive3D& >(rCandidate);
					process(rPrimitive.getChildren());
					break;
				}
				case PRIMITIVE3D_ID_HIDDENGEOMETRYPRIMITIVE3D :
				{
					// HiddenGeometryPrimitive3D; the default decomposition would return an empty seqence,
					// so force this primitive to process it's children directly if the switch is set
					// (which is the default). Else, ignore invisible content
				    const primitive3d::HiddenGeometryPrimitive3D& rHiddenGeometry(static_cast< const primitive3d::HiddenGeometryPrimitive3D& >(rCandidate));
       			    const primitive3d::Primitive3DSequence& rChildren = rHiddenGeometry.getChildren();

                    if(rChildren.hasElements())
                    {
                        if(getUseInvisiblePrimitiveContent())
					    {
                            process(rChildren);
					    }
                    }

                    break;
				}
                case PRIMITIVE3D_ID_UNIFIEDTRANSPARENCETEXTUREPRIMITIVE3D :
                {
					const primitive3d::UnifiedTransparenceTexturePrimitive3D& rPrimitive = static_cast< const primitive3d::UnifiedTransparenceTexturePrimitive3D& >(rCandidate);
       			    const primitive3d::Primitive3DSequence rChildren = rPrimitive.getChildren();

                    if(rChildren.getLength())
                    {
    			        if(1.0 <= rPrimitive.getTransparence())
                        {
                            // not visible, but use for HitTest
					        if(getUseInvisiblePrimitiveContent())
					        {
           						process(rChildren);
                            }
                        }
                        else if(rPrimitive.getTransparence() >= 0.0 && rPrimitive.getTransparence() < 1.0)
			            {
                            // visible; use content
    						process(rChildren);
                        }
                    }

                    break;
                }
				case PRIMITIVE3D_ID_POLYPOLYGONMATERIALPRIMITIVE3D :
				{
					// PolyPolygonMaterialPrimitive3D
					const primitive3d::PolyPolygonMaterialPrimitive3D& rPrimitive = static_cast< const primitive3d::PolyPolygonMaterialPrimitive3D& >(rCandidate);

                    if(!maFront.equal(maBack))
                    {
           			    const basegfx::B3DPolyPolygon& rPolyPolygon = rPrimitive.getB3DPolyPolygon();
                        const sal_uInt32 nPolyCount(rPolyPolygon.count());

                        if(nPolyCount)
                        {
           			        const basegfx::B3DPolygon aPolygon(rPolyPolygon.getB3DPolygon(0));
                            const sal_uInt32 nPointCount(aPolygon.count());
                            
                            if(nPointCount > 2)
                            {
                                const basegfx::B3DVector aPlaneNormal(aPolygon.getNormal());

                                if(!aPlaneNormal.equalZero())
                                {
                                    const basegfx::B3DPoint aPointOnPlane(aPolygon.getB3DPoint(0));
                                    double fCut(0.0);

                                    if(basegfx::tools::getCutBetweenLineAndPlane(aPlaneNormal, aPointOnPlane, maFront, maBack, fCut))
                                    {
                                        const basegfx::B3DPoint aCutPoint(basegfx::interpolate(maFront, maBack, fCut));

                                        if(basegfx::tools::isInside(rPolyPolygon, aCutPoint, false))
                                        {
                                            // #i102956# add result. Do not forget to do this in the coordinate
                                            // system the processor get started with, so use the collected
                                            // combined transformation from processed TransformPrimitive3D's
                                            maResult.push_back(maCombinedTransform * aCutPoint);
                                        }
                                    }
                                }
                            }
                        }
                    }
                    
                    break;
				}
				default :
				{
					// process recursively
					process(rCandidate.get3DDecomposition(getViewInformation3D()));
					break;
				}
            }
        }
	} // end of namespace processor3d
} // end of namespace drawinglayer

//////////////////////////////////////////////////////////////////////////////
// eof
