Module simple_3dviz.behaviours.trajectory
This module defines and implements the Trajectory interface which provides a way to generate a sequence of values for use with behaviours. For instance we can generate quadratic bezier trajectories for moving the camera or lights.
Expand source code
"""This module defines and implements the Trajectory interface which provides a
way to generate a sequence of values for use with behaviours. For instance we
can generate quadratic bezier trajectories for moving the camera or lights."""
from bisect import bisect_right
import numpy as np
from pyrr import matrix33
class Trajectory(object):
"""The main trajectory interface takes a percentage and provides a
value (most often 3d position but also color etc.)."""
def get_value(self, t):
"""Given t in [0, 1] return the value of the trajectory. Some
trajectories are periodic which means they support t > 1."""
raise NotImplementedError()
class StartStopTrajectory(Trajectory):
"""Start and stop the decorated trajectory for the given values of t by
mapping the interval to 0-1 and everything outside the interval to either 0
or 1."""
def __init__(self, trajectory, start=0.0, stop=1.0):
self._trajectory = trajectory
self._start = start
self._stop = stop
def get_value(self, t):
tt = (t - self._start) / (self._stop - self._start)
return self._trajectory.get_value(min(1.0, max(0.0, tt)))
class Linear(Trajectory):
"""From a to b the fastest way possible :-)."""
def __init__(self, a, b):
self._a = a
self._b = b
def get_value(self, t):
if not (0 <= t <= 1):
raise ValueError(("Linear trajectory is defined only for "
"t in [0, 1]"))
return self._a + t * (self._b - self._a)
class Join(Trajectory):
"""Join several trajectories into one large trajectory using a fixed
percentage for each.
The passed in trajectories should be tuples of weight, trajectory. For
instance:
t = Join([
(0.1, Linear(0, 1)),
(0.5, Linear(1, 0)),
(0.1, Linear(0, -1)),
(0.5, Linear(-1, 0))
])
NOTE: The weights don't have to sum to 1.
"""
def __init__(self, trajectories):
self._weights = [t[0] for t in trajectories]
for i in range(1, len(self._weights)):
self._weights[i] = self._weights[i] + self._weights[i-1]
self._trajectories = [t[1] for t in trajectories]
if not all(isinstance(t, Trajectory) for t in self._trajectories):
raise ValueError(("The trajectories passed should implement the "
"Trajectory interface"))
def get_value(self, t):
if not (0 <= t <= 1):
raise ValueError(("Join trajectory is defined only for "
"t in [0, 1]"))
# Find the correct trajectory
w = t*self._weights[-1]
index = bisect_right(self._weights, w)
if index == len(self._trajectories):
return self._trajectories[-1].get_value(1.)
# Find the completion percentage of that trajectory
max_weight = self._weights[index]
prev_weight = 0. if index==0 else self._weights[index-1]
new_t = (w-prev_weight)/(max_weight-prev_weight)
return self._trajectories[index].get_value(new_t)
class Repeat(Trajectory):
"""Repeat a trajectory assuming that the end point is the same as the start
point.
For instance can be used with Lines as follows:
Repeat(Lines([0, 0, 0], [0, 1, 0], [1, 1, 0], [1, 0, 0], [0, 0, 0]))
"""
def __init__(self, trajectory):
self._trajectory = trajectory
def get_value(self, t):
return self._trajectory.get_value(t % 1)
class BackAndForth(Trajectory):
"""Run a trajectory forwards and backwards continuously."""
def __init__(self, trajectory):
self._trajectory = trajectory
def get_value(self, t):
t = t % 2
return self._trajectory.get_value(2-t if t > 1 else t)
class Circle(Trajectory):
"""Circle implements a clockwise circular trajectory in 3d space.
The circle is defined by the center, a 3d point and a normal vector. The
start of the circle is the 3d point that rotates around the normal
vector.
"""
def __init__(self, center, point, normal):
self._center = np.asarray(center, dtype=np.float64)
self._point = np.asarray(point, dtype=np.float64)
self._normal = np.asarray(normal, dtype=np.float64)
radial = self._point - self._center
cosangle = (
self._normal.dot(radial) /
np.sqrt(self._normal.dot(self._normal)) /
np.sqrt(radial.dot(radial))
)
if cosangle > 0.01:
raise ValueError(("The radial vector and the normal vector "
"are not perpendicular thus do not define "
"a circle"))
def get_value(self, t):
R = matrix33.create_from_axis_rotation(self._normal, 2*t*np.pi)
return self._center + R.dot(self._point-self._center)
class QuadraticBezier(Trajectory):
"""A simple quadratic bezier curve.
https://en.wikipedia.org/wiki/B%C3%A9zier_curve
"""
def __init__(self, A, B, C):
self._A = A
self._B = B
self._C = C
def get_value(self, t):
if not (0 <= t <= 1):
raise ValueError(("QuadraticBezier trajectory is defined only for "
"t in [0, 1]"))
return (1-t)**2 * self._A + 2*(1-t)*t * self._B + t**2 * self._C
def Lines(*points):
"""Lines is a helper function to create joins of linear trajectories.
Lines accepts points as positional arguments or as a single iterable. The
arguments are cast to numpy arrays and then a trajectory is created.
"""
if len(points) > 1:
points = np.asarray(points, dtype=np.float64)
else:
points = np.asarray(points[0], dtype=np.float64)
assert len(points) > 1, "Lines expects at least two points"
return Join([
(1., Linear(points[i], points[i+1]))
for i in range(len(points)-1)
])
def QuadraticBezierCurves(*points):
"""QuadraticBezierCurves is a helper function to create joins of quadratic
bezier curves.
Same as Lines it accepts points either as positional arguments or a single
iterable.
"""
if len(points) > 1:
points = np.asarray(points, dtype=np.float64)
else:
points = np.asarray(points[0], dtype=np.float64)
assert len(points) > 2, "QuadraticBezierCurves expects at least 3 points"
assert (len(points) % 2) == 1, ("QuadraticBezierCurves expects an odd "
"number of points")
return Join([
(1., QuadraticBezier(points[i], points[i+1], points[i+2]))
for i in range(0, len(points)-2, 2)
])
Functions
def Lines(*points)
-
Lines is a helper function to create joins of linear trajectories.
Lines accepts points as positional arguments or as a single iterable. The arguments are cast to numpy arrays and then a trajectory is created.
Expand source code
def Lines(*points): """Lines is a helper function to create joins of linear trajectories. Lines accepts points as positional arguments or as a single iterable. The arguments are cast to numpy arrays and then a trajectory is created. """ if len(points) > 1: points = np.asarray(points, dtype=np.float64) else: points = np.asarray(points[0], dtype=np.float64) assert len(points) > 1, "Lines expects at least two points" return Join([ (1., Linear(points[i], points[i+1])) for i in range(len(points)-1) ])
def QuadraticBezierCurves(*points)
-
QuadraticBezierCurves is a helper function to create joins of quadratic bezier curves.
Same as Lines it accepts points either as positional arguments or a single iterable.
Expand source code
def QuadraticBezierCurves(*points): """QuadraticBezierCurves is a helper function to create joins of quadratic bezier curves. Same as Lines it accepts points either as positional arguments or a single iterable. """ if len(points) > 1: points = np.asarray(points, dtype=np.float64) else: points = np.asarray(points[0], dtype=np.float64) assert len(points) > 2, "QuadraticBezierCurves expects at least 3 points" assert (len(points) % 2) == 1, ("QuadraticBezierCurves expects an odd " "number of points") return Join([ (1., QuadraticBezier(points[i], points[i+1], points[i+2])) for i in range(0, len(points)-2, 2) ])
Classes
class BackAndForth (trajectory)
-
Run a trajectory forwards and backwards continuously.
Expand source code
class BackAndForth(Trajectory): """Run a trajectory forwards and backwards continuously.""" def __init__(self, trajectory): self._trajectory = trajectory def get_value(self, t): t = t % 2 return self._trajectory.get_value(2-t if t > 1 else t)
Ancestors
Inherited members
class Circle (center, point, normal)
-
Circle implements a clockwise circular trajectory in 3d space.
The circle is defined by the center, a 3d point and a normal vector. The start of the circle is the 3d point that rotates around the normal vector.
Expand source code
class Circle(Trajectory): """Circle implements a clockwise circular trajectory in 3d space. The circle is defined by the center, a 3d point and a normal vector. The start of the circle is the 3d point that rotates around the normal vector. """ def __init__(self, center, point, normal): self._center = np.asarray(center, dtype=np.float64) self._point = np.asarray(point, dtype=np.float64) self._normal = np.asarray(normal, dtype=np.float64) radial = self._point - self._center cosangle = ( self._normal.dot(radial) / np.sqrt(self._normal.dot(self._normal)) / np.sqrt(radial.dot(radial)) ) if cosangle > 0.01: raise ValueError(("The radial vector and the normal vector " "are not perpendicular thus do not define " "a circle")) def get_value(self, t): R = matrix33.create_from_axis_rotation(self._normal, 2*t*np.pi) return self._center + R.dot(self._point-self._center)
Ancestors
Inherited members
class Join (trajectories)
-
Join several trajectories into one large trajectory using a fixed percentage for each.
The passed in trajectories should be tuples of weight, trajectory. For instance:
t = Join([ (0.1, Linear(0, 1)), (0.5, Linear(1, 0)), (0.1, Linear(0, -1)), (0.5, Linear(-1, 0)) ])
NOTE: The weights don't have to sum to 1.
Expand source code
class Join(Trajectory): """Join several trajectories into one large trajectory using a fixed percentage for each. The passed in trajectories should be tuples of weight, trajectory. For instance: t = Join([ (0.1, Linear(0, 1)), (0.5, Linear(1, 0)), (0.1, Linear(0, -1)), (0.5, Linear(-1, 0)) ]) NOTE: The weights don't have to sum to 1. """ def __init__(self, trajectories): self._weights = [t[0] for t in trajectories] for i in range(1, len(self._weights)): self._weights[i] = self._weights[i] + self._weights[i-1] self._trajectories = [t[1] for t in trajectories] if not all(isinstance(t, Trajectory) for t in self._trajectories): raise ValueError(("The trajectories passed should implement the " "Trajectory interface")) def get_value(self, t): if not (0 <= t <= 1): raise ValueError(("Join trajectory is defined only for " "t in [0, 1]")) # Find the correct trajectory w = t*self._weights[-1] index = bisect_right(self._weights, w) if index == len(self._trajectories): return self._trajectories[-1].get_value(1.) # Find the completion percentage of that trajectory max_weight = self._weights[index] prev_weight = 0. if index==0 else self._weights[index-1] new_t = (w-prev_weight)/(max_weight-prev_weight) return self._trajectories[index].get_value(new_t)
Ancestors
Inherited members
class Linear (a, b)
-
From a to b the fastest way possible :-).
Expand source code
class Linear(Trajectory): """From a to b the fastest way possible :-).""" def __init__(self, a, b): self._a = a self._b = b def get_value(self, t): if not (0 <= t <= 1): raise ValueError(("Linear trajectory is defined only for " "t in [0, 1]")) return self._a + t * (self._b - self._a)
Ancestors
Inherited members
class QuadraticBezier (A, B, C)
-
A simple quadratic bezier curve.
Expand source code
class QuadraticBezier(Trajectory): """A simple quadratic bezier curve. https://en.wikipedia.org/wiki/B%C3%A9zier_curve """ def __init__(self, A, B, C): self._A = A self._B = B self._C = C def get_value(self, t): if not (0 <= t <= 1): raise ValueError(("QuadraticBezier trajectory is defined only for " "t in [0, 1]")) return (1-t)**2 * self._A + 2*(1-t)*t * self._B + t**2 * self._C
Ancestors
Inherited members
class Repeat (trajectory)
-
Repeat a trajectory assuming that the end point is the same as the start point.
For instance can be used with Lines as follows:
Repeat(Lines([0, 0, 0], [0, 1, 0], [1, 1, 0], [1, 0, 0], [0, 0, 0]))
Expand source code
class Repeat(Trajectory): """Repeat a trajectory assuming that the end point is the same as the start point. For instance can be used with Lines as follows: Repeat(Lines([0, 0, 0], [0, 1, 0], [1, 1, 0], [1, 0, 0], [0, 0, 0])) """ def __init__(self, trajectory): self._trajectory = trajectory def get_value(self, t): return self._trajectory.get_value(t % 1)
Ancestors
Inherited members
class StartStopTrajectory (trajectory, start=0.0, stop=1.0)
-
Start and stop the decorated trajectory for the given values of t by mapping the interval to 0-1 and everything outside the interval to either 0 or 1.
Expand source code
class StartStopTrajectory(Trajectory): """Start and stop the decorated trajectory for the given values of t by mapping the interval to 0-1 and everything outside the interval to either 0 or 1.""" def __init__(self, trajectory, start=0.0, stop=1.0): self._trajectory = trajectory self._start = start self._stop = stop def get_value(self, t): tt = (t - self._start) / (self._stop - self._start) return self._trajectory.get_value(min(1.0, max(0.0, tt)))
Ancestors
Inherited members
class Trajectory
-
The main trajectory interface takes a percentage and provides a value (most often 3d position but also color etc.).
Expand source code
class Trajectory(object): """The main trajectory interface takes a percentage and provides a value (most often 3d position but also color etc.).""" def get_value(self, t): """Given t in [0, 1] return the value of the trajectory. Some trajectories are periodic which means they support t > 1.""" raise NotImplementedError()
Subclasses
Methods
def get_value(self, t)
-
Given t in [0, 1] return the value of the trajectory. Some trajectories are periodic which means they support t > 1.
Expand source code
def get_value(self, t): """Given t in [0, 1] return the value of the trajectory. Some trajectories are periodic which means they support t > 1.""" raise NotImplementedError()