/**************************************************************
 * 
 * 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/processor2d/hittestprocessor2d.hxx>
#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
#include <drawinglayer/primitive2d/polygonprimitive2d.hxx>
#include <drawinglayer/primitive2d/polypolygonprimitive2d.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
#include <drawinglayer/primitive2d/maskprimitive2d.hxx>
#include <drawinglayer/primitive2d/sceneprimitive2d.hxx>
#include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx>
#include <basegfx/matrix/b3dhommatrix.hxx>
#include <drawinglayer/processor3d/cutfindprocessor3d.hxx>
#include <drawinglayer/primitive2d/hiddengeometryprimitive2d.hxx>
#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>

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

namespace drawinglayer
{
	namespace processor2d
	{
		HitTestProcessor2D::HitTestProcessor2D(const geometry::ViewInformation2D& rViewInformation,
			const basegfx::B2DPoint& rLogicHitPosition,
			double fLogicHitTolerance,
            bool bHitTextOnly)
		:	BaseProcessor2D(rViewInformation),
			maDiscreteHitPosition(),
			mfDiscreteHitTolerance(0.0),
			mbHit(false),
			mbHitToleranceUsed(false),
			mbUseInvisiblePrimitiveContent(true),
            mbHitTextOnly(bHitTextOnly)
		{
			// init hit tolerance
			mfDiscreteHitTolerance = fLogicHitTolerance;

			if(basegfx::fTools::less(mfDiscreteHitTolerance, 0.0))
			{
				// ensure input parameter for hit tolerance is >= 0.0
				mfDiscreteHitTolerance = 0.0;
			}
			else if(basegfx::fTools::more(mfDiscreteHitTolerance, 0.0))
			{
				// generate discrete hit tolerance
				mfDiscreteHitTolerance = (getViewInformation2D().getObjectToViewTransformation() 
					* basegfx::B2DVector(mfDiscreteHitTolerance, 0.0)).getLength();
			}

			// gererate discrete hit position
			maDiscreteHitPosition = getViewInformation2D().getObjectToViewTransformation() * rLogicHitPosition;

			// check if HitTolerance is used
			mbHitToleranceUsed = basegfx::fTools::more(getDiscreteHitTolerance(), 0.0);
		}

		HitTestProcessor2D::~HitTestProcessor2D()
		{
		}

		bool HitTestProcessor2D::checkHairlineHitWithTolerance(
			const basegfx::B2DPolygon& rPolygon,
			double fDiscreteHitTolerance)
		{
			basegfx::B2DPolygon aLocalPolygon(rPolygon);
			aLocalPolygon.transform(getViewInformation2D().getObjectToViewTransformation());

			// get discrete range
			basegfx::B2DRange aPolygonRange(aLocalPolygon.getB2DRange());

			if(basegfx::fTools::more(fDiscreteHitTolerance, 0.0))
			{
				aPolygonRange.grow(fDiscreteHitTolerance);
			}

			// do rough range test first
			if(aPolygonRange.isInside(getDiscreteHitPosition()))
			{
				// check if a polygon edge is hit
				return basegfx::tools::isInEpsilonRange(
					aLocalPolygon, 
					getDiscreteHitPosition(), 
					fDiscreteHitTolerance);
			}

			return false;
		}

		bool HitTestProcessor2D::checkFillHitWithTolerance(
			const basegfx::B2DPolyPolygon& rPolyPolygon,
			double fDiscreteHitTolerance)
		{
			bool bRetval(false);
			basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolyPolygon);
			aLocalPolyPolygon.transform(getViewInformation2D().getObjectToViewTransformation());

			// get discrete range
			basegfx::B2DRange aPolygonRange(aLocalPolyPolygon.getB2DRange());
			const bool bDiscreteHitToleranceUsed(basegfx::fTools::more(fDiscreteHitTolerance, 0.0));

			if(bDiscreteHitToleranceUsed)
			{
				aPolygonRange.grow(fDiscreteHitTolerance);
			}

			// do rough range test first
			if(aPolygonRange.isInside(getDiscreteHitPosition()))
			{
				// if a HitTolerance is given, check for polygon edge hit in epsilon first
				if(bDiscreteHitToleranceUsed && 
					basegfx::tools::isInEpsilonRange(
						aLocalPolyPolygon, 
						getDiscreteHitPosition(), 
						fDiscreteHitTolerance))
				{
					bRetval = true;
				}

				// check for hit in filled polyPolygon
				if(!bRetval && basegfx::tools::isInside(
					aLocalPolyPolygon,
					getDiscreteHitPosition(),
					true))
				{
					bRetval = true;
				}
			}

			return bRetval;
		}

		void HitTestProcessor2D::check3DHit(const primitive2d::ScenePrimitive2D& rCandidate)
		{
			// calculate relative point in unified 2D scene
            const basegfx::B2DPoint aLogicHitPosition(getViewInformation2D().getInverseObjectToViewTransformation() * getDiscreteHitPosition());

            // use bitmap check in ScenePrimitive2D
            bool bTryFastResult(false);

            if(rCandidate.tryToCheckLastVisualisationDirectHit(aLogicHitPosition, bTryFastResult))
            {
                mbHit = bTryFastResult;
            }
            else
            {
                basegfx::B2DHomMatrix aInverseSceneTransform(rCandidate.getObjectTransformation());
                aInverseSceneTransform.invert();
                const basegfx::B2DPoint aRelativePoint(aInverseSceneTransform * aLogicHitPosition);

                // check if test point is inside scene's unified area at all
                if(aRelativePoint.getX() >= 0.0 && aRelativePoint.getX() <= 1.0 
                    && aRelativePoint.getY() >= 0.0 && aRelativePoint.getY() <= 1.0)
                {
                    // get 3D view information
    			    const geometry::ViewInformation3D& rObjectViewInformation3D = rCandidate.getViewInformation3D();

                    // create HitPoint Front and Back, transform to object coordinates
                    basegfx::B3DHomMatrix aViewToObject(rObjectViewInformation3D.getObjectToView());
                    aViewToObject.invert();
                    const basegfx::B3DPoint aFront(aViewToObject * basegfx::B3DPoint(aRelativePoint.getX(), aRelativePoint.getY(), 0.0));
                    const basegfx::B3DPoint aBack(aViewToObject * basegfx::B3DPoint(aRelativePoint.getX(), aRelativePoint.getY(), 1.0));

                    if(!aFront.equal(aBack))
                    {
        			    const primitive3d::Primitive3DSequence& rPrimitives = rCandidate.getChildren3D();

                        if(rPrimitives.hasElements())
                        {
                            // make BoundVolume empty and overlapping test for speedup
                            const basegfx::B3DRange aObjectRange(
                                drawinglayer::primitive3d::getB3DRangeFromPrimitive3DSequence(
                                    rPrimitives, rObjectViewInformation3D));

                            if(!aObjectRange.isEmpty())
                            {
                                const basegfx::B3DRange aFrontBackRange(aFront, aBack);

                                if(aObjectRange.overlaps(aFrontBackRange))
                                {
                                    // bound volumes hit, geometric cut tests needed
                                    drawinglayer::processor3d::CutFindProcessor aCutFindProcessor(
                                        rObjectViewInformation3D, 
                                        aFront, 
                                        aBack, 
                                        true);
                                    aCutFindProcessor.process(rPrimitives);

                                    mbHit = (0 != aCutFindProcessor.getCutPoints().size());
                                }
                            }
                        }
                    }
                }

			    // This is needed to check hit with 3D shadows, too. HitTest is without shadow
			    // to keep compatible with previous versions. Keeping here as reference
			    //
                // if(!getHit())
                // {
                //     // if scene has shadow, check hit with shadow, too
		        //     const primitive2d::Primitive2DSequence xExtracted2DSceneShadow(rCandidate.getShadow2D(getViewInformation2D()));
			    //
                //     if(xExtracted2DSceneShadow.hasElements())
	            //     {
		        //         // process extracted 2D content
		        //         process(xExtracted2DSceneShadow);
	            //     }
                // }
                
                if(!getHit())
                {
		            // empty 3D scene; Check for border hit
		            basegfx::B2DPolygon aOutline(basegfx::tools::createUnitPolygon());
                    aOutline.transform(rCandidate.getObjectTransformation());

		            mbHit = checkHairlineHitWithTolerance(aOutline, getDiscreteHitTolerance());
                }

			    // This is what the previous version did. Keeping it here for reference
			    //
			    // // 2D Scene primitive containing 3D stuff; extract 2D contour in world coordinates
	            // // This may be refined later to an own 3D HitTest renderer which processes the 3D
	            // // geometry directly
	            // const primitive2d::ScenePrimitive2D& rScenePrimitive2DCandidate(static_cast< const primitive2d::ScenePrimitive2D& >(rCandidate));
	            // const primitive2d::Primitive2DSequence xExtracted2DSceneGeometry(rScenePrimitive2DCandidate.getGeometry2D());
	            // const primitive2d::Primitive2DSequence xExtracted2DSceneShadow(rScenePrimitive2DCandidate.getShadow2D(getViewInformation2D()));
			    //
	            // if(xExtracted2DSceneGeometry.hasElements() || xExtracted2DSceneShadow.hasElements())
	            // {
		        //     // process extracted 2D content
		        //     process(xExtracted2DSceneGeometry);
		        //     process(xExtracted2DSceneShadow);
	            // }
	            // else
	            // {
		        //     // empty 3D scene; Check for border hit
		        //     const basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D()));
                //     if(!aRange.isEmpty())
                //     {
	            //          const basegfx::B2DPolygon aOutline(basegfx::tools::createPolygonFromRect(aRange));
	            //          mbHit = checkHairlineHitWithTolerance(aOutline, getDiscreteHitTolerance());
                //     }
	            // }
            }
		}

		void HitTestProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate)
		{
            if(getHit())
			{
				// stop processing as soon as a hit was recognized
				return;
			}

			switch(rCandidate.getPrimitive2DID())
			{
				case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D :
				{
					// remember current ViewInformation2D
					const primitive2d::TransformPrimitive2D& rTransformCandidate(static_cast< const primitive2d::TransformPrimitive2D& >(rCandidate));
					const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D());

					// create new local ViewInformation2D containing transformation
					const geometry::ViewInformation2D aViewInformation2D(
						getViewInformation2D().getObjectTransformation() * rTransformCandidate.getTransformation(), 
						getViewInformation2D().getViewTransformation(), 
						getViewInformation2D().getViewport(),
						getViewInformation2D().getVisualizedPage(),
						getViewInformation2D().getViewTime(),
						getViewInformation2D().getExtendedInformationSequence());
					updateViewInformation(aViewInformation2D);

					// process child content recursively
					process(rTransformCandidate.getChildren());

					// restore transformations
					updateViewInformation(aLastViewInformation2D);

					break;
				}
				case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D :
				{
                    if(!getHitTextOnly())
                    {
					    // create hairline in discrete coordinates
					    const primitive2d::PolygonHairlinePrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonHairlinePrimitive2D& >(rCandidate));
    				
					    // use hairline test
    					mbHit = checkHairlineHitWithTolerance(rPolygonCandidate.getB2DPolygon(), getDiscreteHitTolerance());
                    }
					
					break;
				}
				case PRIMITIVE2D_ID_POLYGONMARKERPRIMITIVE2D :
				{
                    if(!getHitTextOnly())
                    {
					    // handle marker like hairline; no need to decompose in dashes
					    const primitive2d::PolygonMarkerPrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonMarkerPrimitive2D& >(rCandidate));
				
    					// use hairline test
    					mbHit = checkHairlineHitWithTolerance(rPolygonCandidate.getB2DPolygon(), getDiscreteHitTolerance());
                    }
					
					break;
				}
				case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D :
				{
                    if(!getHitTextOnly())
                    {
					    // handle stroke evtl. directly; no need to decompose to filled polygon outlines
					    const primitive2d::PolygonStrokePrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonStrokePrimitive2D& >(rCandidate));
					    const attribute::LineAttribute& rLineAttribute = rPolygonCandidate.getLineAttribute();

					    if(basegfx::fTools::more(rLineAttribute.getWidth(), 0.0))
					    {
						    if(basegfx::B2DLINEJOIN_MITER == rLineAttribute.getLineJoin())
						    {
							    // if line is mitered, use decomposition since mitered line
							    // geometry may use more space than the geometry grown by half line width
							    process(rCandidate.get2DDecomposition(getViewInformation2D()));
						    }
						    else
						    {
							    // for all other B2DLINEJOIN_* do a hairline HitTest with expanded tolerance
							    const basegfx::B2DVector aDiscreteHalfLineVector(getViewInformation2D().getObjectToViewTransformation() 
								    * basegfx::B2DVector(rLineAttribute.getWidth() * 0.5, 0.0));
							    mbHit = checkHairlineHitWithTolerance(
								    rPolygonCandidate.getB2DPolygon(), 
								    getDiscreteHitTolerance() + aDiscreteHalfLineVector.getLength());
						    }
					    }
					    else
					    {
						    // hairline; fallback to hairline test. Do not decompose
						    // since this may decompose the hairline to dashes
						    mbHit = checkHairlineHitWithTolerance(rPolygonCandidate.getB2DPolygon(), getDiscreteHitTolerance());
					    }
                    }
					
					break;
				}
				case PRIMITIVE2D_ID_POLYGONWAVEPRIMITIVE2D :
				{
                    if(!getHitTextOnly())
                    {
					    // do not use decompose; just handle like a line with width
					    const primitive2d::PolygonWavePrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonWavePrimitive2D& >(rCandidate));
					    double fLogicHitTolerance(0.0);
					
					    // if WaveHeight, grow by it
					    if(basegfx::fTools::more(rPolygonCandidate.getWaveHeight(), 0.0))
					    {
						    fLogicHitTolerance += rPolygonCandidate.getWaveHeight();
					    }

					    // if line width, grow by it
					    if(basegfx::fTools::more(rPolygonCandidate.getLineAttribute().getWidth(), 0.0))
					    {
						    fLogicHitTolerance += rPolygonCandidate.getLineAttribute().getWidth() * 0.5;
					    }

					    const basegfx::B2DVector aDiscreteHalfLineVector(getViewInformation2D().getObjectToViewTransformation() 
						    * basegfx::B2DVector(fLogicHitTolerance, 0.0));
    					
					    mbHit = checkHairlineHitWithTolerance(
						    rPolygonCandidate.getB2DPolygon(), 
						    getDiscreteHitTolerance() + aDiscreteHalfLineVector.getLength());
                    }

					break;
				}
				case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D :
				{
                    if(!getHitTextOnly())
                    {
					    // create filled polyPolygon in discrete coordinates
					    const primitive2d::PolyPolygonColorPrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolyPolygonColorPrimitive2D& >(rCandidate));
    					
					    // use fill hit test
    					mbHit = checkFillHitWithTolerance(rPolygonCandidate.getB2DPolyPolygon(), getDiscreteHitTolerance());
                    }

					break;
				}
				case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D :
				{
					// sub-transparence group
					const primitive2d::TransparencePrimitive2D& rTransCandidate(static_cast< const primitive2d::TransparencePrimitive2D& >(rCandidate));

					// Currently the transparence content is not taken into account; only
					// the children are recursively checked for hit. This may be refined for
					// parts where the content is completely transparent if needed.
					process(rTransCandidate.getChildren());
					
					break;
				}
				case PRIMITIVE2D_ID_MASKPRIMITIVE2D :
				{
					// create mask in discrete coordinates; only recursively continue
					// with content when HitTest position is inside the mask
					const primitive2d::MaskPrimitive2D& rMaskCandidate(static_cast< const primitive2d::MaskPrimitive2D& >(rCandidate));
					
					// use fill hit test
					if(checkFillHitWithTolerance(rMaskCandidate.getMask(), getDiscreteHitTolerance()))
					{
						// recursively HitTest children
						process(rMaskCandidate.getChildren());
					}

					break;
				}
				case PRIMITIVE2D_ID_SCENEPRIMITIVE2D :
				{
                    if(!getHitTextOnly())
                    {
				        const primitive2d::ScenePrimitive2D& rScenePrimitive2D(
                            static_cast< const primitive2d::ScenePrimitive2D& >(rCandidate));
						check3DHit(rScenePrimitive2D);
                    }

					break;
				}
                case PRIMITIVE2D_ID_WRONGSPELLPRIMITIVE2D :
				case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D :
				case PRIMITIVE2D_ID_GRIDPRIMITIVE2D :
				case PRIMITIVE2D_ID_HELPLINEPRIMITIVE2D :
                {
					// ignorable primitives
					break;
				}
				case PRIMITIVE2D_ID_SHADOWPRIMITIVE2D :
                {
					// Ignore shadows; we do not want to have shadows hittable.
					// Remove this one to make shadows hittable on demand.
					break;
				}
				case PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D :
				case PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D :
                {
                    // for text use the BoundRect of the primitive itself
				    const basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D()));

                    if(!aRange.isEmpty())
                    {
				        const basegfx::B2DPolygon aOutline(basegfx::tools::createPolygonFromRect(aRange));
				        mbHit = checkFillHitWithTolerance(basegfx::B2DPolyPolygon(aOutline), getDiscreteHitTolerance());
                    }

					break;
				}
				case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D :
                {
                    if(!getHitTextOnly())
                    {
                        // The recently added BitmapEx::GetTransparency() makes it easy to extend
                        // the BitmapPrimitive2D HitTest to take the contained BotmapEx and it's
                        // transparency into account
					    const basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D()));
                        
                        if(!aRange.isEmpty())
                        {
    					    const primitive2d::BitmapPrimitive2D& rBitmapCandidate(static_cast< const primitive2d::BitmapPrimitive2D& >(rCandidate));
            			    const BitmapEx& rBitmapEx = rBitmapCandidate.getBitmapEx();
                            const Size& rSizePixel(rBitmapEx.GetSizePixel());

                            if(rSizePixel.Width() && rSizePixel.Height())
                            {
                                basegfx::B2DHomMatrix aBackTransform(
                                    getViewInformation2D().getObjectToViewTransformation() * 
                                    rBitmapCandidate.getTransform());
                                aBackTransform.invert();
                                
                                const basegfx::B2DPoint aRelativePoint(aBackTransform * getDiscreteHitPosition());
                                const basegfx::B2DRange aUnitRange(0.0, 0.0, 1.0, 1.0);

                                if(aUnitRange.isInside(aRelativePoint))
                                {
                                    const sal_Int32 nX(basegfx::fround(aRelativePoint.getX() * rSizePixel.Width()));
                                    const sal_Int32 nY(basegfx::fround(aRelativePoint.getY() * rSizePixel.Height()));

                                    mbHit = (0xff != rBitmapEx.GetTransparency(nX, nY));
                                }
                            }
                            else
                            {
                                // fallback to standard HitTest
					            const basegfx::B2DPolygon aOutline(basegfx::tools::createPolygonFromRect(aRange));
					            mbHit = checkFillHitWithTolerance(basegfx::B2DPolyPolygon(aOutline), getDiscreteHitTolerance());
                            }
                        }
                    }

                    break;
                }
				case PRIMITIVE2D_ID_METAFILEPRIMITIVE2D :
				case PRIMITIVE2D_ID_CONTROLPRIMITIVE2D :
				case PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D :
				case PRIMITIVE2D_ID_FILLHATCHPRIMITIVE2D :
				case PRIMITIVE2D_ID_PAGEPREVIEWPRIMITIVE2D :
                case PRIMITIVE2D_ID_MEDIAPRIMITIVE2D:
                {
                    if(!getHitTextOnly())
                    {
					    // Class of primitives for which just the BoundRect of the primitive itself
					    // will be used for HitTest currently.
					    //
					    // This may be refined in the future, e.g:
					    // - For Bitamps, the mask and/or transparence information may be used
					    // - For MetaFiles, the MetaFile content may be used
					    const basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D()));
                        
                        if(!aRange.isEmpty())
                        {
					        const basegfx::B2DPolygon aOutline(basegfx::tools::createPolygonFromRect(aRange));
					        mbHit = checkFillHitWithTolerance(basegfx::B2DPolyPolygon(aOutline), getDiscreteHitTolerance());
                        }
                    }

					break;
				}
				case PRIMITIVE2D_ID_HIDDENGEOMETRYPRIMITIVE2D :
				{
					// HiddenGeometryPrimitive2D; 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 primitive2d::HiddenGeometryPrimitive2D& rHiddenGeometry(static_cast< const primitive2d::HiddenGeometryPrimitive2D& >(rCandidate));
       			    const primitive2d::Primitive2DSequence& rChildren = rHiddenGeometry.getChildren();

                    if(rChildren.hasElements())
                    {
                        if(getUseInvisiblePrimitiveContent())
					    {
                            process(rChildren);
					    }
                    }
					
					break;
				}
				case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D :
				{
                    if(!getHitTextOnly())
                    {
					    const primitive2d::PointArrayPrimitive2D& rPointArrayCandidate(static_cast< const primitive2d::PointArrayPrimitive2D& >(rCandidate));
					    const std::vector< basegfx::B2DPoint >& rPositions = rPointArrayCandidate.getPositions();
					    const sal_uInt32 nCount(rPositions.size());

					    for(sal_uInt32 a(0); !getHit() && a < nCount; a++)
					    {
						    const basegfx::B2DPoint aPosition(getViewInformation2D().getObjectToViewTransformation() * rPositions[a]);
						    const basegfx::B2DVector aDistance(aPosition - getDiscreteHitPosition());

						    if(aDistance.getLength() <= getDiscreteHitTolerance())
						    {
							    mbHit = true;
						    }
					    }
                    }
					
					break;
				}
				default :
				{
					// process recursively
					process(rCandidate.get2DDecomposition(getViewInformation2D()));
					
					break;
				}
			}
		}

	} // end of namespace processor2d
} // end of namespace drawinglayer

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