1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
|
"""
This module provides `Paths` to create animation effects with Sprites. For more details see
http://asciimatics.readthedocs.io/en/latest/animation.html
"""
from abc import ABCMeta, abstractmethod
def _spline(t, p0, p1, p2, p3):
"""
Catmull-Rom cubic spline to interpolate 4 given points.
:param t: Time index through the spline (must be 0-1).
:param p0: The previous point in the curve (for continuity).
:param p1: The first point to interpolate.
:param p2: The second point to interpolate.
:param p3: The last point to interpolate.
"""
return (
t * ((2 - t) * t - 1) * p0 +
(t * t * (3 * t - 5) + 2) * p1 +
t * ((4 - 3 * t) * t + 1) * p2 +
(t - 1) * t * t * p3) / 2
class _AbstractPath(metaclass=ABCMeta):
"""
Class to represent the motion of a Sprite.
The Screen will reset() the Path before iterating through each position
using next_pos() and checking whether it has reached the end using
is_finished().
"""
def __init__(self):
"""
To define a Path, use the methods to jump to a location, wait or move
between points.
"""
self._steps = []
self._index = None
self._rec_x = 0
self._rec_y = 0
@abstractmethod
def reset(self):
"""
Reset the Path for use next time.
"""
@abstractmethod
def next_pos(self):
"""
:return: The next position tuple (x, y) for the Sprite on this path.
"""
@abstractmethod
def is_finished(self):
"""
:return: Whether this path has got to the end.
"""
class Path(_AbstractPath):
"""
Class to record and play back the motion of a Sprite.
The Screen will reset() the Path before iterating through each position
using next_pos() and checking whether it has reached the end using
is_finished().
"""
def __init__(self):
"""
To define a Path, use the methods to jump to a location, wait or move
between points.
"""
super().__init__()
self._steps = []
self._index = 0
self._rec_x = 0
self._rec_y = 0
self.reset()
def reset(self):
"""
Reset the Path for use next time.
"""
self._index = 0
def next_pos(self):
"""
:return: The next position tuple (x, y) for the Sprite on this path.
"""
result = None
if self._index <= len(self._steps):
result = self._steps[self._index]
self._index += 1
return result
def is_finished(self):
"""
:return: Whether this path has got to the end.
"""
return self._index >= len(self._steps)
def _add_step(self, pos):
"""
Add a step to the end of the current recorded path.
:param pos: The position tuple (x, y) to add to the list.
"""
self._steps.append(pos)
self._rec_x = pos[0]
self._rec_y = pos[1]
def wait(self, delay):
"""
Wait at the current location for the specified number of iterations.
:param delay: The time to wait (in animation frames).
"""
for _ in range(0, delay):
self._add_step((self._rec_x, self._rec_y))
def jump_to(self, x, y):
"""
Jump straight to the newly specified location - i.e. teleport there and
don't create a path to get there.
:param x: X coord for the end position.
:param y: Y coord for the end position.
"""
self._add_step((x, y))
def move_straight_to(self, x, y, steps):
"""
Move straight to the newly specified location - i.e. create a straight
line Path from the current location to the specified point.
:param x: X coord for the end position.
:param y: Y coord for the end position.
:param steps: How many steps to take for the move.
"""
start_x = self._rec_x
start_y = self._rec_y
for i in range(1, steps + 1):
self._add_step((
int(start_x + (x - start_x) / float(steps) * i),
int(start_y + (y - start_y) / float(steps) * i)))
def move_round_to(self, points, steps):
"""
Follow a path pre-defined by a set of at least 4 points. This Path will
interpolate the points into a curve and follow that curve.
:param points: The list of points that defines the path.
:param steps: The number of steps to take to follow the path.
"""
# Spline interpolation needs a before and after point for the curve.
# Duplicate the first and last points to handle this. We also need
# to move from the current position to the first specified point.
points.insert(0, (self._rec_x, self._rec_y))
points.insert(0, (self._rec_x, self._rec_y))
points.append(points[-1])
# Convert the points into an interpolated set of more detailed points.
steps_per_spline = steps // (len(points) - 3)
for j in range(1, len(points) - 2):
for t in range(1, steps_per_spline + 1):
y = _spline(float(t) / steps_per_spline,
float(points[j - 1][1]),
float(points[j][1]),
float(points[j + 1][1]),
float(points[j + 2][1]))
x = int(points[j][0] + ((points[j + 1][0] - points[j][0]) *
float(t) / steps_per_spline))
self._add_step((x, int(y)))
class DynamicPath(_AbstractPath, metaclass=ABCMeta):
"""
Class to create a dynamic path that reacts to events
The Screen will reset() the Path before iterating through each position
using next_pos() and checking whether it has reached the end using
is_finished().
"""
def __init__(self, screen, x, y):
"""
To implement a DynamicPath, override the :py:meth:`.process_event()`
method to react to any user input.
"""
super().__init__()
self._screen = screen
self._x = self._start_x = x
self._y = self._start_y = y
self.reset()
def reset(self):
"""
Reset the Path for use next time.
"""
self._x = self._start_x
self._y = self._start_y
def next_pos(self):
"""
:return: The next position tuple (x, y) for the Sprite on this path.
"""
return self._x, self._y
def is_finished(self):
"""
:return: Whether this path has got to the end.
"""
return False
@abstractmethod
def process_event(self, event):
"""
Process any mouse event.
:param event: The event that was triggered.
:returns: None if the Effect processed the event, else the original
event.
"""
|