#!BPY
"""
Name: 'Euler Filter'
Blender: 248
Group: 'Object'
Tooltip: 'Clean up euler rotation ipo curves recorded in the game engine.'
"""

__author__ = ["macouno"]
__url__ = ("http://www.alienhelpdesk.com")
__version__ = "0.1"
__bpydoc__ = """\

Euler Filter.

Select all objects that you recorded motion for in the game engine and run the script.
It will let you know how many errors it found and fixed

"""

# ***** BEGIN GPL LICENSE BLOCK *****
#
# Script copyright (C) macouno 2009
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
# ***** END GPL LICENCE BLOCK *****
# --------------------------------------------------------------------------

import Blender
from Blender import Object, Ipo, Window, Draw

## Set some global vars
CURVES = {}	# a dict of the curves we're checkign
XFLIPS = []	# A list of points on the RotX curve that are flipped
ZFLIPS = [] # A list of points on the RotZ curve that are flipped
FIXED = 0 # The number of fixed curves

## Start the 
def findCurves():
	global FIXED
	
	## Get the selected objects
	ob = Object.GetSelected()
	
	## Loop through the objects
	for o in ob:
		ipo = o.getIpo() # Get the ipo data
		if ipo:
			cur = ipo.getCurves() # Get the curve data
			if cur:
				
				## Reset the globals just in case we do multiple objects
				CURVES = {}
				FLIPS = []
		
				for c in cur:
					n = c.name
					
					## We only want rotation curves
					if n == 'RotX':
						CURVES[0] = c
					elif n == 'RotZ':
						CURVES[1] = c
					elif n == 'RotY':
						CURVES[2] = c
				
				## only if we found all three, lets clean them up
				if len(CURVES) == 3:
					for c in CURVES.values():
						cleanCurve(c)
						
	mes = 'Fixed ' + str(FIXED) + ' errors'
	Draw.PupMenu(mes)

## Clean a euler curve up
def cleanCurve(c):
	global FIXED
	
	## Get all relevant data for this curve
	cTyp = c.name				# Name of the current curve
	oldpoints = c.bezierPoints	# The bezier points on the curve
	newpoints =[]				# All the new points
	prev = 0					# The previous point on the curve
	pDif = 0					# The previous "difference" between keys
	fOffset = 0.0				# The current offset of the curve
	invert = 0					# Whether or not the curve needs inversion
	invertMethod = 0			# The type of inversion we need
	iOffset = 0.0				# The value of inversion offset
	cOffset = 0.0			# The offset to actually apply to a curve

	for p in oldpoints:
	
		pnt = p.pt # Current point
		cDif = 0
		
		## Only if we know a previous point can we do anything
		if not prev:
			newpoints.append([pnt[0], pnt[1]])
		else:
			
			cFr = pnt[0] # Current place on the timeline
			cCo = pnt[1] # Current position
			pCo = prev[1] # Previous position
			pSet = pCo + cOffset # The previously set value
			cDif = (-pCo) + cCo # The difference between the previous and current position
			iOffset = 0.0 # Inversion offset is set every time
			cOffset = 0.0 # Offset for this frame for this point
			
			## For the roty curve we need to do something special
			## If all is well this is evaluated last and we have flips
			## This curve isn't flipped, but inverted relative to -90 or +90 degrees
			if cTyp == 'RotY' and  cFr in XFLIPS and cFr in ZFLIPS:
					
					FIXED += 1
					
					# We need to either invert or stop inverting
					if not invert:
						invert = 1
						
						# Depending on whether we are positive or negative we need a different inversion
						if cCo < 0:
							invertMethod = 0
						else:
							invertMethod = 1
						
					else:
						invert = 0
						iOffset = 0.0
						fOffset = 0.0
			
			# lets see if RotX or RotZ flip
			else:					
			
				# If the previous point is positive and we go down more than 90 degrees... it's a flip
				if pCo > 0.0001 and (cDif < -9):
				
					FIXED += 1
					fOffset += 18
					
					# The current value really should be bigger than the previous one
					# So keep adding 180 degrees until it is
					while pDif > 0.0001 and (cCo + fOffset) < pSet:
						fOffset += 18
					
					if cTyp == 'RotX':
						XFLIPS.append(cFr)
					else:
						ZFLIPS.append(cFr)
			
				# If the previous point is negative and we go up more than 90 degrees it's a flip					
				elif pCo < -0.0001 and (cDif > 9):
				
					FIXED += 1
					fOffset -= 18
					
					# The current value really should be smaller than the previous one
					# So keep subtracting 180 degrees until it is
					while pDif < -0.0001 and (cCo + fOffset) > pSet:
						fOffset -= 18
					
					if cTyp == 'RotX':
						XFLIPS.append(cFr)
					else:
						ZFLIPS.append(cFr)
			
			## Now if we need to invert it's simple, just get the offset relative to -90 or +90 and do negative twice the distance to that value
			if invert:
				
				## Lets get the offset
				if not invertMethod:
					cDif = 9.0 + cCo
					iOffset = (-cDif) * 2
					
				else:
					cDif = -9.0 + cCo
					iOffset = (-cDif) * 2
					
				cOffset = iOffset
				
				## If there's also an flipping offset, lets invert that as well and add it to the inversion offset
				if fOffset:
					cOffset += (-fOffset)
					
			## if we're not inverting the current offset is just the "flipping offset"
			else:
				cOffset = fOffset
			
			## No matter what happened, lets apply the offset to every point
			newpoints.append([pnt[0], (pnt[1] + cOffset)])				
			
		## Set this point as the previous one for the next loop
		prev = pnt
		pDif = cDif
			
	# Apply the new points
	if len(newpoints):
		for i, p in enumerate(newpoints):
			oldpoints[i].pt = [p[0], p[1]]

findCurves()
Window.Redraw(Window.Types.IPO)