Trajectory

Trajectories can be used for two purposes:

  • for an animation object to follow a given path (trajectory)

  • for an animated queue (AnimateQueue) to use a given path (trajectory) to place animation objects on, in contrast to the usual placement on a straight line.

You can define a trajectory with * TrajectoryPolygon * TrajectoryCircle * TrajectoryStandstill * TrajectoryMerged

Once a trajectory is created, it essentially provided an x, y and angle method that can be called with the time t.

If we for instance say

sim.AnimateRectangle(
   (-20, -10, 20, 10),
   x=lambda t: traj.x(t),
   y=lambda t: traj.y(t),
   angle=lambda t: traj.angle(t),
)

or -simpler-

sim.AnimateRectangle(
   (-20, -10, 20, 10),
   x=traj.x,
   y=traj.y,
   angle=traj.angle,
)

The defined rectangle will now move according to specification, which includes (optionally) an initial speed, a maximum speed as well as acceleration and deceleration rates.

Here’s an animated view of a complicated trajectory, which includes acceleration, deceleration, various speeds, orientation change and stand still.

_images/traj.gif

TrajectoryPolygon

Polygon trajectories are defined like

traj = sim.TrajectoryPolygon(polygon=(0, 0, 0, 700, 700, 700), vmax=50)

This defines a simple movement along two line segments that will be traversed with a constant speed of 50.

If we then say

sim.AnimateRectangle(
   (-20, -10, 20, 10),
   x=lambda t: traj.x(t),
   y=lambda t: traj.y(t),
   angle=lambda t: traj.angle(t),
)

This illustrates the movement:

_images/traj1.gif

We can specify the initial speed (v0), the end speed (v1) as well the acceleration rate (acc) and deceleraration rate (dec).

So, for instance, if we specify

traj = sim.TrajectoryPolygon(
   polygon=(0, 0, 0, 700, 700, 700), v0=0, v1=0, vmax=50, acc=10, dec=10
)

the movement will slowly start and end slowly as well. Like this

_images/traj2.gif

The polygon can be optionally traversed along a spline. There are two versions:

  • bezier

  • catmull-rom

Both methods will make a smooth path, but bezier usually gives better results.

Like the above trajectory but then with a bezier spline

traj = sim.TrajectoryPolygon(polygon=(0, 0, 0, 700, 700, 700), vmax=50, spline="bezier")

results in

_images/traj3.gif

Like the above trajectory but then with a catmull-rom spline

traj = sim.TrajectoryPolygon(polygon=(0, 0, 0, 700, 700, 700), vmax=50, spline="catmull-rom")

results in

_images/traj4.gif

TrajectoryCircle

This is to define a circle(segment) to be followed.

For instance for half a circle

env_init()
traj = sim.TrajectoryCircle(
   radius=350, x_center=350, y_center=0, angle0=180, angle1=0, vmax=50
)

resulting in

_images/traj5.gif

Again, here v0, v1, acc and dec can be specified.

Merging trajectories (TrajectoryMerged)

It is possible and very usual to combine a number of trajectories into one trajectory which can be followed.

For instance if we want to make a rounded corner on the above up/left movement, we can do

traj0 = sim.TrajectoryPolygon(
   polygon=(0, 0, 0, 600), v0=0, v1=25, vmax=50, acc=10, dec=10
)
traj1 = sim.TrajectoryCircle(
   radius=100, x_center=100, y_center=600, angle0=180, angle1=90, vmax=25
)

traj2 = sim.TrajectoryPolygon(
   polygon=(100, 700, 700, 700), v0=25, v1=0, vmax=50, acc=10, dec=10
)
traj = sim.TrajectoryMerged((traj0, traj1, traj2))

Note that we have specified a lower speed in the curve and that we decelerate before the bend and accelerate after it.

Visualized:

_images/traj6.gif

Instead of TrajectoryMerged, we can also use the + operator

traj = traj0 + traj1 + traj2

or even

traj = sum((traj0, traj1, traj2))

TrajectoryStandstill

Sometimes we need to wait for a certain duration in a trajectory.

This when TrajectoryStandstill can be useful.

Suppose that in the above up/left movement we want to wait for 2 seconds in the corner. Then we can say

traj = (
   sim.TrajectoryPolygon(polygon=(0, 0, 0, 700), v0=0, v1=0, vmax=50, acc=10, dec=10)
   + sim.TrajectoryStandstill(xy=(0, 700), duration=2)
   + sim.TrajectoryPolygon(
      polygon=(0, 700, 700, 700), v0=0, v1=0, vmax=50, acc=10, dec=10
   )
)

Resulting in:

_images/traj7.gif

Note that the user has to make sure that the movements ‘connnect’ properly.

Overriding the orientation

Normally the angle returned by angle(t) will be the direction of the movement. Each of the trajectory methods have a orientation parameter that can override this standard functionality:

  • if a callable, it will get the current angle and should return an angle (e.g. +90)

  • if a float, that’s the orientation to use

Here’s an example

traj = sim.TrajectoryPolygon(polygon=(0, 0, 0, 700, 700, 700), orientation = lambda angle: angle + 90, vmax=50)

resulting in

_images/traj8.gif

(note the direction of the arrows!)

Trajectory methods

There are a number of methods for trajectories that provide information:

  • x(t) x-coordinate at time t

  • y(t) y-coordinate at time t

  • orientation(t) orientation at time t

  • duration() duration of trajectory

  • length() total length

  • length(t) length travelled at time t

  • rendered_polygon() polygon that can be used with AnimatePolygon to show the path

Start time

If nothing is specified, the trajectory is assumed to start at the time of definition. However, when you specify t0=, that will be used for a non merged trajectory.

In case of a merged trajectory, the start time will be the one given in the first (sub)trajectory. All other times are ignored.

So,

traj0 = sim.TrajectoryPolygon(polygon=(0, 0, 0, 600), vmax=50, t0=10)
traj1 = sim.TrajectoryCircle(radius=100, x_center=100, y_center=600, angle0=180, angle1=90, t0=5)
traj2 = sim.TrajectoryPolygon(polygon=(100, 700, 700, 700), vmax=50, t0=20)
traj = sim.TrajectoryMerged((traj0, traj1, traj2))

will start its movements at t=10 and ignore the specified times for traj1 and traj2.

Usage in AnimateQueue

Normally, a queue is animated in a specified direction (e, s, w or n), like

q.animate(x=100, y=100, direction="e")

which might show like:

_images/traj9.png

(remember by default an animation object is 50 unit wide)

But if we use trajectory= instead of direction=, we can put the components along a specified trajectory

traj = sim.TrajectoryCircle(radius=150, x_center=150, y_center=0, angle0=180, angle1=0,t0=0)
q.animate(x=100, y=100, trajectory=traj)

will show like:

_images/traj10.png

(remember that the width of the default Component.animation_objects is 50.)

And then we can even place animation objects along a ‘stacked’ trajectory

traj0 = sim.TrajectoryPolygon((100, 0, 250, 0),t0=0)
traj1 = sim.TrajectoryPolygon((100, 50, 250, 50))
traj2 = sim.TrajectoryPolygon((100, 100, 250, 100))
traj = traj0 + traj1 + traj2

q.animate(x=100, y=100, trajectory=traj)

resulting in:

_images/traj11.png

Note

For queue animation purposes, no speeds, acceleration or deceleration rates should be specified. And make sure that t0 (of at least the first part) of the trajectory is 0.

The value fed into trajectory is the cumulative x-dimension of the animation_objects() And that is used to get the x, y and angle of the animation objects.