﻿# This file contains the spline motion code contributed by Aenakume, at
# http://lemmasoft.renai.us/forums/viewtopic.php?f=8&t=3977

init -1500 python:
    class _SplineInterpolator(object):

        ANCHORS = {
            'top'    : 0.0,
            'center' : 0.5,
            'bottom' : 1.0,
            'left'   : 0.0,
            'right'  : 1.0,
            }

        def __init__(self, points, anchors=(0.5, 0.5)):

            assert len(points) >= 2, "Need at least a start and end point."

            def setup_coordinate_(c):
                if len(c) == 2:
                    c += anchors
                return [ self.ANCHORS.get(i, i) for i in c ]

            self.points = []

            for p in points:
                length = len(p)

                if isinstance(p[-1], float):
                    length = len(p) - 1
                    point = [ p[-1] ]
                else:
                    length = len(p)
                    point = [ -1 ]

                self.points.append(point + [ setup_coordinate_(p[i]) for i in range(length) ])

            # Make sure start and end times are set, if not already set
            if self.points[0][0] == -1:
                self.points[0][0] = 0.0
            if self.points[-1][0] == -1:
                self.points[-1][0] = 1.0

            # Now we gotta calculate the step times that need calculating
            for start in range(1, len(self.points) - 1):
                if self.points[start][0] != -1:
                    continue

                end = start + 1

                while end < (len(self.points) - 1) and self.points[end][0] == -1:
                    end += 1

                step = (self.points[end][0] - self.points[start - 1][0]) / float(end - start + 1)

                for i in range(start, end):
                    self.points[i][0] = self.points[i - 1][0] + step

            # And finally, sort the list of points by increasing time
            self.points.sort(key=lambda a : a[0])

            self.initialized = None

        def init_values(self, sizes):
            def to_abs_(value, size):
                if isinstance(value, float):
                    return value * size
                else:
                    return value

            def coord_(c):

                if len(c) == 2:
                    c = c + (0, 0)

                return ( to_abs_(c[0], sizes[0]) - to_abs_(c[2], sizes[2]),
                         to_abs_(c[1], sizes[1]) - to_abs_(c[3], sizes[3]) )

            for p in self.points:
                for i in range(1, len(p)):
                    p[i] = coord_(p[i])

            self.initialized = sizes

        def __call__(self, t, sizes):
            # Initialize if necessary
            if not self.initialized == sizes:
                self.init_values(sizes)

            # Now we must determine which segment we are in
            for segment in range(len(self.points)):
                if self.points[segment][0] > t:
                    break

            # If this is the zeroth segment, just start at the start point
            if segment == 0:
                result = self.points[0][1]
            # If this is past the last segment, just leave it at the end point
            elif segment == len(self.points) - 1 and t > self.points[-1][0]:
                result = self.points[-1][1]
            else:
                # Scale t
                t = (t - self.points[segment - 1][0]) / (self.points[segment][0] - self.points[segment - 1][0])

            # Get start and end points
            start = self.points[segment - 1][1]
            end   = self.points[segment][1]

            # Now what kind of interpolation is it?
            if len(self.points[segment]) == 2:   # Straight line
                t_p = 1.0 - t

                result = [ t_p * start[i] + t * end[i] for i in (0,1) ]

            elif len(self.points[segment]) == 3: # Quadratic Bezier
                t_pp = (1.0 - t)**2
                t_p = 2 * t * (1.0 - t)
                t2 = t**2

                result = [ t_pp * start[i] + t_p * self.points[segment][2][i] + t2 * end[i] for i in (0,1) ]

            elif len(self.points[segment]) == 4: # Cubic Bezier
                t_ppp = (1.0 - t)**3
                t_pp = 3 * t * (1.0 - t)**2
                t_p = 3 * t**2 * (1.0 - t)
                t3 = t**3

                result = [ t_ppp * start[i] + t_pp * self.points[segment][2][i] + t_p * self.points[segment][3][i] + t3 * end[i] for i in (0,1) ]

            return ( absolute(result[0]), absolute(result[1]), 0, 0 )

    def SplineMotion(points, time, child=None, anchors=(0.5, 0.5), repeat=False, bounce=False, anim_timebase=False, style='default', time_warp=None, **properties):
        return Motion(_SplineInterpolator(points, anchors), time, child, repeat=repeat, bounce=bounce, anim_timebase=anim_timebase, style=style, time_warp=time_warp, add_sizes=True, **properties)
