"""
Intercircle is a collection of utilities for intersecting circles, used to get smooth loops around a collection of points and inset & outset loops.

"""

from __future__ import absolute_import
try:
	import psyco
	psyco.full()
except:
	pass
#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module.
import __init__

from fabmetheus_utilities.vector3 import Vector3
from fabmetheus_utilities import euclidean
import math


__author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
__date__ = '$Date: 2008/21/04 $'
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'


globalDecreasingRadiusMultipliers = [1.0, 0.55, 0.35, 0.2]
globalIntercircleMultiplier = 1.04 # 1.02 is enough to stop known intersection


def addCircleIntersectionLoop(circleIntersectionLoop, circleIntersections):
	'Add a circle intersection loop.'
	firstCircleIntersection = circleIntersectionLoop[0]
	circleIntersectionAhead = firstCircleIntersection
	for circleIntersectionIndex in xrange(len(circleIntersections) + 1):
		circleIntersectionAhead = circleIntersectionAhead.getCircleIntersectionAhead()
		if circleIntersectionAhead == firstCircleIntersection or circleIntersectionAhead is None:
			firstCircleIntersection.steppedOn = True
			return
		circleIntersectionAhead.addToList(circleIntersectionLoop)
	firstCircleIntersection.steppedOn = True
	print( 'addCircleIntersectionLoop would have gone into an endless loop, this should never happen.' )
	print( 'circleIntersectionLoop' )
	for circleIntersection in circleIntersectionLoop:
		print(circleIntersection)
		print(circleIntersection.circleNodeAhead)
		print(circleIntersection.circleNodeBehind)
	print('firstCircleIntersection')
	print(firstCircleIntersection)
	print('circleIntersections')
	for circleIntersection in circleIntersections:
		print(circleIntersection)

def addEndCap(begin, end, points, radius):
	'Get circular end cap.'
	beginMinusEnd = begin - end
	beginMinusEndLength = abs(beginMinusEnd)
	if beginMinusEndLength <= 0.0:
		points.append(begin)
		return
	beginMinusEnd *= radius / beginMinusEndLength
	perpendicular = complex(-beginMinusEnd.imag, beginMinusEnd.real)
	numberOfSides = 20 # to end up with close to unit length corners, 5 * 4
	numberOfPositiveSides = numberOfSides / 2
	totalAngle = 0.0
	angle = euclidean.globalTau / float(numberOfSides)
	# dotProductMultiplier to compensate for the corner outset in addInsetPointFromClockwiseTriple
	dotProductMultiplier = 2.0 - 1.0 / math.cos(0.5 * angle)
	for sideIndex in xrange(numberOfPositiveSides + 1):
		circumferentialPoint = math.sin(totalAngle) * beginMinusEnd + math.cos(totalAngle) * perpendicular
		points.append(begin + circumferentialPoint * dotProductMultiplier)
		totalAngle += angle

def addHalfPath(path, points, radius, thresholdRatio=0.9):
	'Add the points from every point on a half path and between points.'
	lessThanRadius = 0.75 * radius
	for pointIndex in xrange(len(path) - 1):
		begin = path[pointIndex]
		center = path[pointIndex + 1]
		centerBegin = getWiddershinsByLength(begin, center, radius)
		if centerBegin is not None:
			addPointsFromSegment(begin + centerBegin, center + centerBegin, points, lessThanRadius, thresholdRatio)
		endIndex = pointIndex + 2
		if endIndex < len(path):
			end = path[endIndex]
			centerEnd = getWiddershinsByLength(center, end, radius)
			if centerBegin is not None and centerEnd is not None:
				centerPerpendicular = 0.5 * (centerBegin + centerEnd)
				points.append(center + centerPerpendicular)
				if euclidean.getCrossProduct(centerBegin, centerEnd) < 0.0:
					points.append(center + centerBegin)
					points.append(center + centerEnd)
			else:
				points.append(center)
	addEndCap(path[0], path[1], points, radius)

def addInsetPointFromClockwiseTriple(begin, center, end, loop, radius):
	'Get inset point with possible intersection from clockwise triple, out from widdershins loop.'
	centerMinusBegin = center - begin
	centerMinusBeginLength = abs(centerMinusBegin)
	centerMinusBeginClockwise = None
	if centerMinusBeginLength > 0.0:
		centerMinusBeginClockwise = complex(centerMinusBegin.imag, -centerMinusBegin.real) / centerMinusBeginLength
	endMinusCenter = end - center
	endMinusCenterLength = abs(endMinusCenter)
	endMinusCenterClockwise = None
	if endMinusCenterLength > 0.0:
		endMinusCenterClockwise = complex(endMinusCenter.imag, -endMinusCenter.real) / endMinusCenterLength
	if centerMinusBeginClockwise is None and endMinusCenterClockwise is None:
		return
	if centerMinusBeginClockwise is None:
		loop.append(center + endMinusCenterClockwise * radius)
		return
	if endMinusCenterClockwise is None:
		loop.append(center + centerMinusBeginClockwise * radius)
		return
	centerClockwise = 0.5 * (centerMinusBeginClockwise + endMinusCenterClockwise)
	dotProduct = euclidean.getDotProduct(centerMinusBeginClockwise, centerClockwise)
	loop.append(center + centerClockwise * radius / max(0.4, abs(dotProduct))) # 0.4 to avoid pointy corners

def addOrbits( distanceFeedRate, loop, orbitalFeedRatePerSecond, temperatureChangeTime, z ):
	'Add orbits with the extruder off.'
	timeInOrbit = 0.0
	while timeInOrbit < temperatureChangeTime:
		for point in loop:
			distanceFeedRate.addGcodeMovementZWithFeedRate( 60.0 * orbitalFeedRatePerSecond, point, z )
		timeInOrbit += euclidean.getLoopLength(loop) / orbitalFeedRatePerSecond

def addOrbitsIfLarge( distanceFeedRate, loop, orbitalFeedRatePerSecond, temperatureChangeTime, z ):
	'Add orbits with the extruder off if the orbits are large enough.'
	if orbitsAreLarge( loop, temperatureChangeTime ):
		addOrbits( distanceFeedRate, loop, orbitalFeedRatePerSecond, temperatureChangeTime, z )

def addPointsFromSegment( pointBegin, pointEnd, points, radius, thresholdRatio=0.9 ):
	'Add point complexes between the endpoints of a segment.'
	if radius <= 0.0:
		print('This should never happen, radius should never be zero or less in addPointsFromSegment in intercircle.')
	thresholdRadius = radius * thresholdRatio # a higher number would be faster but would leave bigger dangling loops and extra dangling loops.
	thresholdDiameter = thresholdRadius + thresholdRadius
	segment = pointEnd - pointBegin
	segmentLength = abs(segment)
	extraCircles = int( math.floor( segmentLength / thresholdDiameter ) )
	if extraCircles < 1:
		return
	if segmentLength == 0.0:
		print('This should never happen, segmentLength = 0.0 in intercircle.')
		print('pointBegin')
		print( pointBegin )
		print( pointEnd )
		return
	if extraCircles < 2:
		lengthIncrement = segmentLength / ( float(extraCircles) + 1.0 )
		segment *= lengthIncrement / segmentLength
		pointBegin += segment
	else:
		pointBegin += segment * thresholdDiameter / segmentLength
		remainingLength = segmentLength - thresholdDiameter - thresholdDiameter
		lengthIncrement = remainingLength / ( float(extraCircles) - 1.0 )
		segment *= lengthIncrement / segmentLength
	for circleIndex in xrange(extraCircles):
		points.append( pointBegin )
		pointBegin += segment

def directLoop(isWiddershins, loop):
	'Direct the loop.'
	if euclidean.isWiddershins(loop) != isWiddershins:
		loop.reverse()

def directLoopLists(isWiddershins, loopLists):
	'Direct the loop lists.'
	for loopList in loopLists:
		directLoops(isWiddershins, loopList)

def directLoops(isWiddershins, loops):
	'Direct the loops.'
	for loop in loops:
		directLoop(isWiddershins, loop)

def getAroundsFromLoop(loop, radius, thresholdRatio=0.9):
	'Get the arounds from the loop.'
	return getAroundsFromPoints(getPointsFromLoop(loop, radius, thresholdRatio), radius)

def getAroundsFromLoops( loops, radius, thresholdRatio=0.9 ):
	'Get the arounds from the loops.'
	return getAroundsFromPoints(getPointsFromLoops(loops, radius, thresholdRatio), radius)

def getAroundsFromPath(path, radius, thresholdRatio=0.9):
	'Get the arounds from the path.'
	radius = abs(radius)
	points = getPointsFromPath(path, radius, thresholdRatio)
	return getAroundsFromPathPoints(points, radius, thresholdRatio=0.9)

def getAroundsFromPathPoints(points, radius, thresholdRatio=0.9):
	'Get the arounds from the path.'
	centers = getCentersFromPoints(points, 0.7854 * radius)
	arounds = []
	for center in centers:
		if euclidean.isWiddershins(center):
			arounds.append(euclidean.getSimplifiedPath(center, radius))
	return arounds

def getAroundsFromPaths(paths, radius, thresholdRatio=0.9):
	'Get the arounds from the path.'
	radius = abs(radius)
	points = []
	for path in paths:
		points += getPointsFromPath(path, radius, thresholdRatio)
	return getAroundsFromPathPoints(points, radius, thresholdRatio=0.9)

def getAroundsFromPoints( points, radius ):
	'Get the arounds from the points.'
	arounds = []
	radius = abs(radius)
	centers = getCentersFromPoints(points, globalIntercircleMultiplier * radius)
	for center in centers:
		inset = getSimplifiedInsetFromClockwiseLoop(center, radius)
		if isLargeSameDirection(inset, center, radius):
			arounds.append(inset)
	return arounds

def getCentersFromCircleNodes( circleNodes, radius ):
	'Get the complex centers of the circle intersection loops from circle nodes.'
	if len( circleNodes ) < 2:
		return []
	circleIntersections = getCircleIntersectionsFromCircleNodes( circleNodes )
	circleIntersectionLoops = getCircleIntersectionLoops( circleIntersections )
	return getCentersFromIntersectionLoops( circleIntersectionLoops, radius )

def getCentersFromIntersectionLoop(circleIntersectionLoop, radius):
	'Get the centers from the intersection loop.'
	loop = []
	for circleIntersection in circleIntersectionLoop:
		loop.append(circleIntersection.circleNodeAhead.actualPoint)
	return loop

def getCentersFromIntersectionLoops( circleIntersectionLoops, radius ):
	'Get the centers from the intersection loops.'
	centers = []
	for circleIntersectionLoop in circleIntersectionLoops:
		centers.append( getCentersFromIntersectionLoop( circleIntersectionLoop, radius ) )
	return centers

def getCentersFromLoop( loop, radius ):
	'Get the centers of the loop.'
	circleNodes = getCircleNodesFromLoop( loop, radius )
	return getCentersFromCircleNodes( circleNodes, radius )

def getCentersFromLoopDirection( isWiddershins, loop, radius ):
	'Get the centers of the loop which go around in the given direction.'
	centers = getCentersFromLoop( loop, radius )
	return getLoopsFromLoopsDirection( isWiddershins, centers )

def getCentersFromPoints(points, radius):
	'Get the centers from the points.'
	circleNodes = getCircleNodesFromPoints(points, abs(radius))
	return getCentersFromCircleNodes(circleNodes, abs(radius))

def getCircleIntersectionLoops( circleIntersections ):
	'Get all the loops going through the circle intersections.'
	circleIntersectionLoops = []
	for circleIntersection in circleIntersections:
		if not circleIntersection.steppedOn:
			circleIntersectionLoop = [ circleIntersection ]
			circleIntersectionLoops.append( circleIntersectionLoop )
			addCircleIntersectionLoop( circleIntersectionLoop, circleIntersections )
	return circleIntersectionLoops

def getCircleIntersectionsFromCircleNodes(circleNodes):
	'Get all the circle intersections which exist between all the circle nodes.'
	if len( circleNodes ) < 1:
		return []
	circleIntersections = []
	index = 0
	pixelTable = {}
	for circleNode in circleNodes:
		euclidean.addElementToPixelListFromPoint(circleNode, pixelTable, circleNode.dividedPoint)
	accumulatedCircleNodeTable = {}
	for circleNodeIndex in xrange(len(circleNodes)):
		circleNodeBehind = circleNodes[circleNodeIndex]
		circleNodeIndexMinusOne = circleNodeIndex - 1
		if circleNodeIndexMinusOne >= 0:
			circleNodeAdditional = circleNodes[circleNodeIndexMinusOne]
			euclidean.addElementToPixelListFromPoint(circleNodeAdditional, accumulatedCircleNodeTable, 0.5 * circleNodeAdditional.dividedPoint)
		withinNodes = circleNodeBehind.getWithinNodes(accumulatedCircleNodeTable)
		for circleNodeAhead in withinNodes:
			circleIntersectionForward = CircleIntersection(circleNodeAhead, index, circleNodeBehind)
			if not circleIntersectionForward.isWithinCircles(pixelTable):
				circleIntersections.append(circleIntersectionForward)
				circleNodeBehind.circleIntersections.append(circleIntersectionForward)
				index += 1
			circleIntersectionBackward = CircleIntersection(circleNodeBehind, index, circleNodeAhead)
			if not circleIntersectionBackward.isWithinCircles(pixelTable):
				circleIntersections.append(circleIntersectionBackward)
				circleNodeAhead.circleIntersections.append(circleIntersectionBackward)
				index += 1
	return circleIntersections

def getCircleNodesFromLoop(loop, radius, thresholdRatio=0.9):
	'Get the circle nodes from every point on a loop and between points.'
	radius = abs(radius)
	points = getPointsFromLoop( loop, radius, thresholdRatio )
	return getCircleNodesFromPoints( points, radius )

def getCircleNodesFromPoints(points, radius):
	'Get the circle nodes from a path.'
	if radius == 0.0:
		print('Warning, radius is 0 in getCircleNodesFromPoints in intercircle.')
		print(points)
		return []
	circleNodes = []
	oneOverRadius = 1.000001 / radius # to avoid problem of accidentally integral radius
	points = euclidean.getAwayPoints(points, radius)
	for point in points:
		circleNodes.append(CircleNode(oneOverRadius, point))
	return circleNodes

def getInsetLoopsFromLoop(loop, radius, thresholdRatio=0.9):
	'Get the inset loops, which might overlap.'
	if radius == 0.0:
		return [loop]
	isInset = radius > 0
	insetLoops = []
	isLoopWiddershins = euclidean.isWiddershins(loop)
	arounds = getAroundsFromLoop(loop, radius, thresholdRatio)
	for around in arounds:
		leftPoint = euclidean.getLeftPoint(around)
		shouldBeWithin = (isInset == isLoopWiddershins)
		if euclidean.isPointInsideLoop(loop, leftPoint) == shouldBeWithin:
			if isLoopWiddershins != euclidean.isWiddershins(around):
				around.reverse()
			insetLoops.append(around)
	return insetLoops

def getInsetLoopsFromLoops(loops, radius):
	'Get the inset loops, which might overlap.'
	insetLoops = []
	for loop in loops:
		insetLoops += getInsetLoopsFromLoop(loop, radius)
	return insetLoops

def getInsetLoopsFromVector3Loop(loop, radius, thresholdRatio=0.9):
	'Get the inset loops from vector3 loop, which might overlap.'
	if len(loop) < 2:
		return [loop]
	loopComplex = euclidean.getComplexPath(loop)
	loopComplexes = getInsetLoopsFromLoop(loopComplex, radius)
	return euclidean.getVector3Paths(loopComplexes, loop[0].z)

def getInsetSeparateLoopsFromLoops(loops, radius, thresholdRatio=0.9):
	'Get the separate inset loops.'
	if radius == 0.0:
		return loops
	isInset = radius > 0
	insetSeparateLoops = []
	arounds = getAroundsFromLoops(loops, abs(radius), thresholdRatio)
	for around in arounds:
		if isInset == euclidean.getIsInFilledRegion(loops, around[0]):
			if isInset:
				around.reverse()
			insetSeparateLoops.append(around)
	return insetSeparateLoops

def getInsetSeparateLoopsFromAroundLoops(loops, radius, radiusAround, thresholdRatio=0.9):
	'Get the separate inset loops.'
	if radius == 0.0:
		return loops
	isInset = radius > 0
	insetSeparateLoops = []
	radius = abs(radius)
	radiusAround = max(abs(radiusAround), radius)
	points = getPointsFromLoops(loops, radiusAround, thresholdRatio)
	centers = getCentersFromPoints(points, globalIntercircleMultiplier * radiusAround)
	for center in centers:
		inset = getSimplifiedInsetFromClockwiseLoop(center, radius)
		if isLargeSameDirection(inset, center, radius):
			if isInset == euclidean.getIsInFilledRegion(loops, inset[0]):
				if isInset:
					inset.reverse()
				insetSeparateLoops.append(inset)
	return insetSeparateLoops

def getIsLarge(loop, radius):
	'Determine if the loop is large enough.'
	return euclidean.getMaximumSpan(loop) > 2.01 * abs(radius)

def getLargestCenterOutsetLoopFromLoop(loop, radius, thresholdRatio=0.9):
	'Get the largest circle outset loop from the loop.'
	if radius == 0.0:
		return loop
	radius = abs(radius)
	points = getPointsFromLoop(loop, radius, thresholdRatio)
	centers = getCentersFromPoints(points, globalIntercircleMultiplier * radius)
	largestCenterOutset = None
	largestOutsetArea = -987654321.0
	for center in centers:
		outset = getSimplifiedInsetFromClockwiseLoop(center, radius)
		if isLargeSameDirection(outset, center, radius):
			if euclidean.isPathInsideLoop(loop, outset) != euclidean.isWiddershins(loop):
				centerOutset = CenterOutset(center, outset)
				outsetArea = abs(euclidean.getAreaLoop(outset))
				if outsetArea > largestOutsetArea:
					largestOutsetArea = outsetArea
					largestCenterOutset = centerOutset
	if largestCenterOutset is None:
		return None
	largestCenterOutset.center = euclidean.getSimplifiedLoop(largestCenterOutset.center, radius)
	return largestCenterOutset

def getLargestCenterOutsetLoopFromLoopRegardless(loop, radius):
	'Get the largest circle outset loop from the loop, even if the radius has to be shrunk and even if there is still no outset loop.'
	global globalDecreasingRadiusMultipliers
	for decreasingRadiusMultiplier in globalDecreasingRadiusMultipliers:
		decreasingRadius = radius * decreasingRadiusMultiplier
		largestCenterOutsetLoop = getLargestCenterOutsetLoopFromLoop(loop, decreasingRadius)
		if largestCenterOutsetLoop is not None:
			return largestCenterOutsetLoop
	return CenterOutset(loop, loop)

def getLargestInsetLoopFromLoop(loop, radius):
	'Get the largest inset loop from the loop.'
	loops = getInsetLoopsFromLoop(loop, radius)
	return euclidean.getLargestLoop(loops)

def getLargestInsetLoopFromLoopRegardless( loop, radius ):
	'Get the largest inset loop from the loop, even if the radius has to be shrunk and even if there is still no inset loop.'
	global globalDecreasingRadiusMultipliers
	for decreasingRadiusMultiplier in globalDecreasingRadiusMultipliers:
		decreasingRadius = radius * decreasingRadiusMultiplier
		largestInsetLoop = getLargestInsetLoopFromLoop( loop, decreasingRadius )
		if len( largestInsetLoop ) > 0:
			return largestInsetLoop
	print('This should never happen, there should always be a largestInsetLoop in getLargestInsetLoopFromLoopRegardless in intercircle.')
	print(loop)
	return loop

def getLoopsFromLoopsDirection( isWiddershins, loops ):
	'Get the loops going round in a given direction.'
	directionalLoops = []
	for loop in loops:
		if euclidean.isWiddershins(loop) == isWiddershins:
			directionalLoops.append(loop)
	return directionalLoops

def getPointsFromLoop(loop, radius, thresholdRatio=0.9):
	'Get the points from every point on a loop and between points.'
	if radius == 0.0:
		print('Warning, radius is 0 in getPointsFromLoop in intercircle.')
		print(loop)
		return loop
	radius = abs(radius)
	points = []
	for pointIndex in xrange(len(loop)):
		pointBegin = loop[pointIndex]
		pointEnd = loop[(pointIndex + 1) % len(loop)]
		points.append( pointBegin )
		addPointsFromSegment( pointBegin, pointEnd, points, radius, thresholdRatio )
	return points

def getPointsFromLoops(loops, radius, thresholdRatio=0.9):
	'Get the points from every point on a loop and between points.'
	points = []
	for loop in loops:
		points += getPointsFromLoop(loop, radius, thresholdRatio)
	return points

def getPointsFromPath(path, radius, thresholdRatio=0.9):
	'Get the points from every point on a path and between points.'
	if len(path) < 1:
		return []
	if len(path) < 2:
		return path
	radius = abs(radius)
	points = []
	addHalfPath(path, points, radius, thresholdRatio)
	addHalfPath(path[: : -1], points, radius, thresholdRatio)
	return points

def getSimplifiedInsetFromClockwiseLoop(loop, radius):
	'Get loop inset from clockwise loop, out from widdershins loop.'
	inset = []
	for pointIndex, begin in enumerate(loop):
		center = loop[(pointIndex + 1) % len(loop)]
		end = loop[(pointIndex + 2) % len(loop)]
		addInsetPointFromClockwiseTriple(begin, center, end, inset, radius)
	return getWithoutIntersections(euclidean.getSimplifiedLoop(inset, radius))

def getWiddershinsByLength(begin, end, length):
	'Get the widdershins by length.'
	endMinusBegin = end - begin
	endMinusBeginLength = abs(endMinusBegin)
	if endMinusBeginLength <= 0.0:
		return None
	endMinusBegin *= length / endMinusBeginLength
	return complex(-endMinusBegin.imag, endMinusBegin.real)

def getWithoutIntersections( loop ):
	'Get loop without intersections.'
	lastLoopLength = len( loop )
	while lastLoopLength > 3:
		removeIntersection( loop )
		if len( loop ) == lastLoopLength:
			return loop
		lastLoopLength = len( loop )
	return loop

def isLargeSameDirection(inset, loop, radius):
	'Determine if the inset is in the same direction as the loop and it is large enough.'
	if euclidean.isWiddershins(inset) != euclidean.isWiddershins(loop):
		return False
	return getIsLarge(inset, radius) and len(inset) > 2

def isLoopIntersectingLoop( anotherLoop, loop ):
	'Determine if the a loop is intersecting another loop.'
	for pointIndex in xrange(len(loop)):
		pointFirst = loop[pointIndex]
		pointSecond = loop[(pointIndex + 1) % len(loop)]
		segment = pointFirst - pointSecond
		normalizedSegment = euclidean.getNormalized(segment)
		segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag)
		segmentFirstPoint = segmentYMirror * pointFirst
		segmentSecondPoint = segmentYMirror * pointSecond
		if euclidean.isLoopIntersectingInsideXSegment( anotherLoop, segmentFirstPoint.real, segmentSecondPoint.real, segmentYMirror, segmentFirstPoint.imag ):
			return True
	return False

def orbitsAreLarge( loop, temperatureChangeTime ):
	'Determine if the orbits are large enough.'
	if len(loop) < 1:
		print('Zero length loop which was skipped over, this should never happen.')
		return False
	return temperatureChangeTime > 1.5

def removeIntersection( loop ):
	'Get loop without the first intersection.'
	for pointIndex, ahead in enumerate(loop):
		behind = loop[ ( pointIndex + len( loop ) - 1 ) % len( loop ) ]
		behindEnd = loop[ ( pointIndex + len( loop ) - 2 ) % len( loop ) ]
		behindMidpoint = 0.5 * ( behind + behindEnd )
		aheadEnd = loop[ (pointIndex + 1) % len( loop ) ]
		aheadMidpoint = 0.5 * ( ahead + aheadEnd )
		normalizedSegment = behind - behindMidpoint
		normalizedSegmentLength = abs( normalizedSegment )
		if normalizedSegmentLength > 0.0:
			normalizedSegment /= normalizedSegmentLength
			segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag)
			behindRotated = segmentYMirror * behind
			behindMidpointRotated = segmentYMirror * behindMidpoint
			aheadRotated = segmentYMirror * ahead
			aheadMidpointRotated = segmentYMirror * aheadMidpoint
			y = behindRotated.imag
			xIntersection = euclidean.getXIntersectionIfExists( aheadRotated, aheadMidpointRotated, y )
			if xIntersection is not None:
				if xIntersection > min( behindMidpointRotated.real, behindRotated.real ) and xIntersection < max( behindMidpointRotated.real, behindRotated.real ):
					intersectionPoint = normalizedSegment * complex( xIntersection, y )
					loop[ ( pointIndex + len( loop ) - 1 ) % len( loop ) ] = intersectionPoint
					del loop[pointIndex]
					return


class BoundingLoop:
	'A class to hold a bounding loop composed of a minimum complex, a maximum complex and an outset loop.'
	def __eq__(self, other):
		'Determine whether this bounding loop is identical to other one.'
		if other is None:
			return False
		return self.minimum == other.minimum and self.maximum == other.maximum and self.loop == other.loop

	def __repr__(self):
		'Get the string representation of this bounding loop.'
		return '%s, %s, %s' % ( self.minimum, self.maximum, self.loop )

	def getFromLoop( self, loop ):
		'Get the bounding loop from a path.'
		self.loop = loop
		self.maximum = euclidean.getMaximumByComplexPath(loop)
		self.minimum = euclidean.getMinimumByComplexPath(loop)
		return self

	def getOutsetBoundingLoop( self, outsetDistance ):
		'Outset the bounding rectangle and loop by a distance.'
		outsetBoundingLoop = BoundingLoop()
		outsetBoundingLoop.maximum = self.maximum + complex( outsetDistance, outsetDistance )
		outsetBoundingLoop.minimum = self.minimum - complex( outsetDistance, outsetDistance )
		greaterThanOutsetDistance = 1.1 * outsetDistance
		centers = getCentersFromLoopDirection( True, self.loop, greaterThanOutsetDistance )
		outsetBoundingLoop.loop = getSimplifiedInsetFromClockwiseLoop( centers[0], outsetDistance )
		return outsetBoundingLoop

	def isEntirelyInsideAnother( self, anotherBoundingLoop ):
		'Determine if this bounding loop is entirely inside another bounding loop.'
		if self.minimum.imag < anotherBoundingLoop.minimum.imag or self.minimum.real < anotherBoundingLoop.minimum.real:
			return False
		if self.maximum.imag > anotherBoundingLoop.maximum.imag or self.maximum.real > anotherBoundingLoop.maximum.real:
			return False
		for point in self.loop:
			if euclidean.getNumberOfIntersectionsToLeft( anotherBoundingLoop.loop, point ) % 2 == 0:
				return False
		return not isLoopIntersectingLoop( anotherBoundingLoop.loop, self.loop ) #later check for intersection on only acute angles

	def isOverlappingAnother( self, anotherBoundingLoop ):
		'Determine if this bounding loop is intersecting another bounding loop.'
		if self.isRectangleMissingAnother( anotherBoundingLoop ):
			return False
		for point in self.loop:
			if euclidean.getNumberOfIntersectionsToLeft( anotherBoundingLoop.loop, point ) % 2 == 1:
				return True
		for point in anotherBoundingLoop.loop:
			if euclidean.getNumberOfIntersectionsToLeft( self.loop, point ) % 2 == 1:
				return True
		return isLoopIntersectingLoop( anotherBoundingLoop.loop, self.loop ) #later check for intersection on only acute angles

	def isOverlappingAnotherInList( self, boundingLoops ):
		'Determine if this bounding loop is intersecting another bounding loop in a list.'
		for boundingLoop in boundingLoops:
			if self.isOverlappingAnother( boundingLoop ):
				return True
		return False

	def isRectangleMissingAnother( self, anotherBoundingLoop ):
		'Determine if the rectangle of this bounding loop is missing the rectangle of another bounding loop.'
		if self.maximum.imag < anotherBoundingLoop.minimum.imag or self.maximum.real < anotherBoundingLoop.minimum.real:
			return True
		return self.minimum.imag > anotherBoundingLoop.maximum.imag or self.minimum.real > anotherBoundingLoop.maximum.real


class CenterOutset:
	'A class to hold a center and an outset.'
	def __init__(self, center, outset):
		'Set the center and outset.'
		self.center = center
		self.outset = outset

	def __repr__(self):
		'Get the string representation of this CenterOutset.'
		return '%s\n%s' % (self.center, self.outset)


class CircleIntersection:
	'An intersection of two complex circles.'
	def __init__( self, circleNodeAhead, index, circleNodeBehind ):
		self.aheadMinusBehind = 0.5 * ( circleNodeAhead.dividedPoint - circleNodeBehind.dividedPoint )
		self.circleNodeAhead = circleNodeAhead
		self.circleNodeBehind = circleNodeBehind
		self.index = index
		self.steppedOn = False
		demichordWidth = math.sqrt( 1.0 - self.aheadMinusBehind.real * self.aheadMinusBehind.real - self.aheadMinusBehind.imag * self.aheadMinusBehind.imag )
		rotatedClockwiseQuarter = complex( self.aheadMinusBehind.imag, - self.aheadMinusBehind.real )
		rotatedClockwiseQuarterLength = abs( rotatedClockwiseQuarter )
		if rotatedClockwiseQuarterLength == 0:
			print('this should never happen, rotatedClockwiseQuarter in getDemichord in intercircle is 0')
			print( circleNodeAhead.dividedPoint )
			print( circleNodeBehind.dividedPoint )
			self.demichord = 0.0
		else:
			self.demichord = rotatedClockwiseQuarter * demichordWidth / rotatedClockwiseQuarterLength
		self.positionRelativeToBehind = self.aheadMinusBehind + self.demichord

	def __repr__(self):
		'Get the string representation of this CircleIntersection.'
		return '%s, %s, %s, %s' % (self.index, self.getAbsolutePosition(), self.circleNodeBehind, self.circleNodeAhead)

	def addToList( self, circleIntersectionPath ):
		'Add this to the circle intersection path, setting stepped on to be true.'
		self.steppedOn = True
		circleIntersectionPath.append(self)

	def getAbsolutePosition(self):
		'Get the absolute position.'
		return self.positionRelativeToBehind + self.circleNodeBehind.dividedPoint

	def getCircleIntersectionAhead(self):
		'Get the first circle intersection on the circle node ahead.'
		circleIntersections = self.circleNodeAhead.circleIntersections
		circleIntersectionAhead = None
		largestDot = -912345678.0
		for circleIntersection in circleIntersections:
			if not circleIntersection.steppedOn:
				circleIntersectionRelativeToMidpoint = euclidean.getNormalized(circleIntersection.positionRelativeToBehind + self.aheadMinusBehind)
				dot = euclidean.getDotProduct(self.demichord, circleIntersectionRelativeToMidpoint)
				if dot > largestDot:
					largestDot = dot
					circleIntersectionAhead = circleIntersection
		if circleIntersectionAhead is None:
			print('Warning, circleIntersectionAhead in getCircleIntersectionAhead in intercircle is None for:')
			print(self.circleNodeAhead.dividedPoint)
			print('circleIntersectionsAhead')
			for circleIntersection in circleIntersections:
				print(circleIntersection.circleNodeAhead.dividedPoint)
			print('circleIntersectionsBehind')
			for circleIntersection in self.circleNodeBehind.circleIntersections:
				print(circleIntersection.circleNodeAhead.dividedPoint)
			print('This may lead to a loop not being sliced.')
			print('If this is a problem, you may as well send a bug report, even though I probably can not fix this particular problem.')
		return circleIntersectionAhead

	def isWithinCircles(self, pixelTable):
		'Determine if this circle intersection is within the circle node circles.'
		absolutePosition = self.getAbsolutePosition()
		squareValues = euclidean.getSquareValuesFromPoint(pixelTable, absolutePosition)
		for squareValue in squareValues:
			if abs(squareValue.dividedPoint - absolutePosition) < 1.0:
				if squareValue != self.circleNodeAhead and squareValue != self.circleNodeBehind:
					return True
		return False


class CircleNode:
	'A complex node of complex circle intersections.'
	def __init__(self, oneOverRadius, point):
		self.actualPoint = point
		self.circleIntersections = []
		self.dividedPoint = point * oneOverRadius
#		self.index = index # when debugging bring back index

	def __repr__(self):
		'Get the string representation of this CircleNode.'
#		return '%s, %s, %s' % (self.index, self.dividedPoint, len(self.circleIntersections)) # when debugging bring back index
		return '%s, %s' % (self.dividedPoint, len(self.circleIntersections))

	def getWithinNodes(self, pixelTable):
		'Get the nodes this circle node is within.'
		withinNodes = []
		squareValues = euclidean.getSquareValuesFromPoint(pixelTable, 0.5 * self.dividedPoint)
		for squareValue in squareValues:
			if abs(self.dividedPoint - squareValue.dividedPoint) < 2.0:
				withinNodes.append(squareValue)
		return withinNodes
