////////////////////////////////////////////////////////////////////////////////////////////////
//                    2d geometry classes - implements 2d kurve offset for use in dll
//
////////////////////////////////////////////////////////////////////////////////////////////////

/*==============================
Copyright (c) 2003 g.j.hawkesford 

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products
   derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
==============================*/

#include "geometry.h"
using namespace geoff_geometry;

namespace geoff_geometry {
	static Kurve eliminateLoops(const Kurve& k , const Kurve& originalk, double offset, int& ret);
	static bool DoesIntersInterfere(const Point& pInt, const Kurve& k, double offset);

	int Kurve::Offset(vector<Kurve*>&OffsetKurves, double offset, int direction, int method, int& ret)const {

		switch(method) {
	case NO_ELIMINATION:
	case BASIC_OFFSET:
		{
			Kurve* ko = new Kurve;
			int n = OffsetMethod1(*ko, offset, direction, method, ret);
			OffsetKurves.push_back(ko);
			return n;
		}

	default:
		FAILURE(L"Requested Offsetting Method not available");
		}
		return 0;
	}

	int Kurve::OffsetMethod1(Kurve& kOffset, double off, int direction,  int method, int& ret)const
	{
		// offset kurve with simple span elimination
		// direction 1 = left,  -1 = right

		// ret  = 0		- kurve offset ok
		//		= 1		- kurve has differential scale (not allowed)
		//		= 2		- offset failed
		//      = 3		- offset too large
		if(this == &kOffset) FAILURE(L"Illegal Call - 'this' must not be kOffset");
		double offset = (direction == GEOFF_LEFT)?off : -off;

		if(fabs(offset) < geoff_geometry::TOLERANCE || m_nVertices < 2) {
			kOffset = *this;
			ret = 0;
			return 1;
		}

		Span curSpan, curSpanOff;	// current & offset spans
		Span prevSpanOff;			// previous offset span
		Point p0, p1;				// Offset span intersections

		// offset Kurve
		kOffset = Matrix(*this);

		if(m_mirrored) offset = -offset;
		int RollDir = ( off < 0 ) ? direction : - direction;				// Roll arc direction

		double scalex;
		if(!GetScale(scalex)) {
			ret = 1;
			return 0;	// differential scale
		}
		offset /= scalex;

		bool bClosed = Closed();
		int nspans = nSpans();
		if(bClosed) {
			Get(nspans, curSpan, true);						// assign previus span for closed

			prevSpanOff = curSpan.Offset(offset);
			nspans++; // read first again
		}

		for(int spannumber = 1; spannumber <= nspans; spannumber++) {
			if(spannumber > nSpans())
				Get(1, curSpan, true);						// closed kurve - read first span again
			else
				Get(spannumber, curSpan, true);

			if(!curSpan.NullSpan) {
				int numint = 0;
				curSpanOff = curSpan.Offset(offset);
				curSpanOff.ID = 0;
				if(!kOffset.m_started) {
					kOffset.Start(curSpanOff.p0);
					kOffset.AddSpanID(0);
				}

				if(spannumber > 1) {
					// see if tangent
					double d = curSpanOff.p0.Dist(prevSpanOff.p1);
					if((d > geoff_geometry::TOLERANCE) && (curSpanOff.NullSpan == false && prevSpanOff.NullSpan == false)) {
						// see if offset spans intersect

						double cp = prevSpanOff.ve ^ curSpanOff.vs;
						bool inters = (cp > 0 && direction == GEOFF_LEFT) || (cp < 0 && direction == GEOFF_RIGHT);

						if(inters) {
							double t[4];
							numint = prevSpanOff.Intof(curSpanOff, p0, p1, t);
						}

						if(numint == 1) {
							// intersection - modify previous endpoint
							kOffset.Replace(kOffset.m_nVertices-1, prevSpanOff.dir, p0, prevSpanOff.pc, prevSpanOff.ID);
						}
						else {
							// 0 or 2 intersections, add roll around (remove -ve loops in elimination function)
							if(kOffset.Add(RollDir, curSpanOff.p0, curSpan.p0, false))	kOffset.AddSpanID(ROLL_AROUND);
						}
					}
				}

				// add span
				if(spannumber < m_nVertices) {
					curSpanOff.ID = spannumber;
					kOffset.Add(curSpanOff, false);
				}
				else if(numint == 1)		// or replace the closed first span
					kOffset.Replace(0, 0, p0, Point(0, 0), 0);

			}
			if(!curSpanOff.NullSpan)prevSpanOff = curSpanOff;
		}		// end of main pre-offsetting loop


#ifdef _DEBUG
//testDraw->AddKurve("", &kOffset, 0, GREEN);
//		outXML oxml(L"c:\\temp\\eliminateLoops.xml");
//		oxml.startElement(L"eliminateLoops");
//		oxml.Write(kOffset, L"kOffset");
//		oxml.endElement();
#endif
		// eliminate loops
		if(method == NO_ELIMINATION) {
			ret = 0;
			return 1;
		}
		kOffset = eliminateLoops(kOffset, *this, offset, ret);

		if(ret == 0 && bClosed) {
			// check for inverted offsets of closed kurves
			if(kOffset.Closed()) {
				double a = Area();
				int dir = (a < 0);
				double ao = kOffset.Area();
				int dirOffset = ao < 0;

				if(dir != dirOffset)
					ret = 3;
				else {
					// check area change compatible with offset direction - catastrophic failure
					bool bigger = (a > 0 && offset > 0) || (a < 0 && offset < 0);
					if(bigger && fabs(ao) < fabs(a)) ret = 2;
				}
			}
			else
				ret = 2;			// started closed but now open??
		}
		return (ret == 0)?1 : 0;
	}


	static Kurve eliminateLoops(const Kurve& k , const Kurve& originalk, double offset, int& ret) {
		// a simple loop elimination routine based on first offset ideas in Peps
		// this needs extensive work for future
		// start point musn't disappear & only one valid offset is determined
		//
		// ret = 0 for ok
		// ret = 2 for impossible geometry
		
		Span sp0, sp1;
		Point pInt, pIntOther;

		Kurve ko;											// eliminated output
		ko = Matrix(k);
		int kinVertex = 0;

		while(kinVertex <= k.nSpans()) {
			bool clipped = false ;                                       // not in a clipped section (assumption with this simple method)

			sp0.dir = k.Get(kinVertex, sp0.p0, sp0.pc);
			sp0.ID = k.GetSpanID(kinVertex++);
			if (kinVertex == 1)	{
				ko.Start(sp0.p0);							// start point mustn't dissappear for this simple method
				ko.AddSpanID(sp0.ID);
			}
			if (kinVertex <= k.nSpans()) {   // any more?
				int ksaveVertex = kinVertex ;
				sp0.dir = k.Get(kinVertex, sp0.p1, sp0.pc);	// first span
				sp0.ID = k.GetSpanID(kinVertex++);

				sp0.SetProperties(true);

				int ksaveVertex1 = kinVertex;									// mark position AA		
				if (kinVertex <= k.nSpans()) {	// get the next but one span			
					sp1.dir = k.Get(kinVertex, sp1.p0, sp1.pc);
					sp1.ID = k.GetSpanID(kinVertex++);
					int ksaveVertex2 = kinVertex;								// mark position BB

					int fwdCount = 0;
					while(kinVertex <= k.nSpans()) {					
						sp1.dir = k.Get(kinVertex, sp1.p1, sp1.pc);			// check span
						sp1.ID = k.GetSpanID(kinVertex++);
						sp1.SetProperties(true);
			
						double t[4];
						int numint = sp0.Intof(sp1, pInt, pIntOther, t);			// find span intersections
						if(numint && sp0.p0.Dist(pInt) < geoff_geometry::TOLERANCE ) numint=0;	// check that intersection is not at the start of the check span					
						if(numint ) {

							if(numint == 2) {
								// choose first intercept on sp0
								Span spd = sp0;
								spd.p1 = pInt;
								spd.SetProperties(true);
								double dd = spd.length;

								spd.p1 = pIntOther;
								spd.SetProperties(true);
								if(dd > spd.length) pInt = pIntOther;
								numint = 1;

							}
							ksaveVertex = ksaveVertex1 ;

							clipped = true ;			// in a clipped section		
							if(DoesIntersInterfere(pInt, originalk, offset) == false) {
								sp0.p1 = pInt;			// ok so truncate this span to the intersection
								clipped = false;		// end of clipped section
								break;
							}
							// no valid intersection found so carry on
						}
						sp1.p0 = sp1.p1 ;		// next
						ksaveVertex1 = ksaveVertex2 ;							// pos AA = BB
						ksaveVertex2 = kinVertex;								// mark 

						if((kinVertex > k.nSpans() || fwdCount++ > 25) && clipped == false) break;
					}
				}

				if(clipped) {
					ret = 2;	// still in a clipped section - error

					return ko;
				}

				ko.Add(sp0, false);

				kinVertex = ksaveVertex;
			}
		}
		ret = 0;

		return ko; // no more spans - seems ok
	}


	static bool DoesIntersInterfere(const Point& pInt, const Kurve& k, double offset)  {
		// check that intersections don't interfere with the original kurve 
		Span sp;
		Point dummy;
		int kCheckVertex = 0;
		k.Get(kCheckVertex++, sp.p0, sp.pc);

		offset = fabs(offset) - geoff_geometry::TOLERANCE;
		while(kCheckVertex <= k.nSpans()) {
			sp.dir = k.Get(kCheckVertex++, sp.p1, sp.pc);
			sp.SetProperties(true);
			// check for interference 
			if(Dist(sp, pInt, dummy) < offset) return true;
			sp.p0 = sp.p1;
		}
		return false;	// intersection is ok
	}
}


static struct iso {
		 Span sp;
		 Span off;
	} isodata;
static void isoRadius(Span& before, Span& blend, Span& after, double radius);

int Kurve::OffsetISOMethod(Kurve& kOut, double off, int direction, bool BlendAll)const {
		// produces a special offset Kurve - observing so-called ISO radii
		// eg line/arc/line tangent - keep arc radius constant
		// this method also considers arc/arc/arc etc.
		// interior radius must be smallest of triplet for above.

		// parameters:-
		// Output	kOut		resulting kurve
		// Input	off			offset amount
		// Input	direction	offset direction (LEFT or RIGHT)
		// Input	BlendAall	if false only consider ISO radius for LINE/ARC/LINE
		//						if true consider all blended radii (ARC/ARC/ARC etc.)		
		double offset = (direction == GEOFF_LEFT)?off : -off;
		if(FEQZ(off) || nSpans() < 1) {
			kOut = *this;
			return 1;
		}
		double cptol = 1.0e-05;
		std::vector<iso> spans;
		for(int i = 0; i < nSpans(); i++) {	// store all spans and offsets
			Get(i+1, isodata.sp, true, true);
			isodata.off = isodata.sp.Offset(offset);
			spans.push_back(isodata);
		}

		for(int i = 0; i < nSpans() - 1; i++)		// calculate intersections for none tangent spans
			if(fabs(spans[i].off.ve ^ spans[i+1].off.vs) > cptol)	spans[i].off.JoinSeparateSpans(spans[i+1].off);

		for(int i = 1; i < nSpans() - 1; i++) {			// deal with isoradii
			if(spans[i].off.dir) {
				if(BlendAll) {						// interior radius should be smaller than neighbours
					if(spans[i-1].sp.dir)
						if(spans[i-1].sp.radius < spans[i].sp.radius) continue;
					if(spans[i+1].sp.dir)
						if(spans[i+1].sp.radius < spans[i].sp.radius) continue;
				}
				else {
					if((spans[i-1].off.dir || spans[i+1].off.dir)) continue;		// linear neighbours only
				}

				if((fabs(spans[i-1].sp.ve ^ spans[i].sp.vs) < cptol) && (fabs(spans[i].sp.ve ^ spans[i+1].sp.vs) < cptol)) {
					// isoradius - calculate the new offset radius and modify neighbouring spans
					isoRadius(spans[i-1].off, spans[i].off, spans[i+1].off, spans[i].sp.radius);
				}
			}
		}

		kOut.Start(spans[0].off.p0);										// start point
		for(int i = 0; i < nSpans(); i++)
			kOut.Add(spans[i].off.dir, spans[i].off.p1, spans[i].off.pc);	// output all spans
		return 1;
	}

static void isoRadius(Span& before, Span& blend, Span& after, double radius) {
	// calculate the new offset radius and modify neighbouring spans
	int direction = ((before.ve ^ after.vs) > 0)? 1 : -1;				// offset direction
	Span beforeOff = before.Offset(direction * radius);
	Span afterOff = after.Offset(direction * radius);
	int turnLeft = ((before.ve ^ after.vs) > 0)? 1 : -1;
	if(before.dir == LINEAR) {
		CLine b(beforeOff);
		if(after.dir == LINEAR) {
			CLine a(afterOff);
			blend.pc = b.Intof(a);
		}
		else {
			Circle a(afterOff);
			b.Intof(turnLeft * after.dir, a, blend.pc);
		}
	}
	else {
		Circle b(beforeOff);

		if(after.dir == LINEAR) {
			CLine a(afterOff);
			a.Intof(-turnLeft * before.dir, b, blend.pc);
		}
		else {
			// arc arc
			Circle a(afterOff);
			int leftright = ((Vector2d(b.pc, blend.pc) ^ Vector2d(b.pc, a.pc)) < 0)? 1 : -1;
			b.Intof(leftright, a, blend.pc);
		}
	}
	before.p1 = blend.p0 = before.Near(blend.pc);
	after.p0 = blend.p1 = after.Near(blend.pc);
}
