Monday, October 15, 2012

Project description

"Capitoline Brutus". Bronze, Roman artwork 
of the Republican Era, 4th-3rd centuries BC
The art of the portrait flourished in Ancient Greek and especially Roman sculpture, where sitters demanded individualized and realistic portraits, even unflattering ones. During the 4th century, the portrait began to retreat in favor of an idealized symbol of what that person looked like (Wikepedia).

But the way we used to “create” portraits 16 centuries ago is quite different from the way we “make” portraits in our technology-oriented century. We have mobile cameras (e.g. iphone) that take pictures using filter-based software (e.g. instagram), which is sent to social networks (e.g. facebook) in order to broadcast a residual image of ourselves.

Datatraits takes a step further. It uses recorded and real-time queries (data feeds) redirected from Twitter, Facebook, Wikipedia, Google (G. Images, G. Books, Blogspot, G. News), among others, in order to “build” an algorithmic portrait of 12 poets, twelve poets that have influenced my artistic practice and life. 

These poets are Heraclitus (c. 535 – c. 475 BCE), Rumi (30 September 1207 – 17 December 1273), Novalis (May 2, 1772 – March 25, 1801), Walt Whitman (May 31, 1819 – March 26, 1892), Konstantinos Cavafy (April 29, 1863 – April 29, 1933), Fernando Pessoa (June 13, 1888 – November 30, 1935), Antonin Artaud (September 4, 1896 – March 4, 1948), Octavio Paz (March 31, 1914 – April 19, 1998), Juan Liscano (July 7, 1914 - February 17, 2001), Alejandra Pizarnik (April 29, 1936 – September 25, 1972), Roberto Juarroz (October 5, 1925 - March 31, 1995), and Yolanda Pantin (October 10, 1954).

The way I am going to depict  each character differs from the way we display a photograph or a realistic representation. Instead, I will program a structure that will output a symbolical expression of each author.  The next  example is a video capture of a datatrait of Quetzalcoatl. This datatrait was recently commissioned by LACMA and belongs to a group of works named Quetzalcoatl 2.0.1.2


Additionally, I will document my creative and programming process, so people can understand the complexity that lies beneath each project.  The resulting datatraits will be compatible with most web browsers and tablets, including iPads, iPhones, and other mobile devices. 

Datatraits extends my interest in representing and visualizing data, as well as poetry. Works such as

Maximum Security (1998-2004)


ArtBoom (1999-2012)




and 

Verses Versus Verses (2008)




are just a few examples. 

Monday, October 1, 2012

Datatrait documentation sample >> Walt Whitman

final result*



*still in progress
process.step 1 >>


1. Labyrinth in Chartres Cathedral.


2. Rendering of the Labyrinth


3. Vectorization of the Image and some programming experimentation.


4. Production of a 2D labyrinth in a wall using poems by Walt Whitman.  


5. Production of a 2D labyrinth in a wall using poems by Wal Whitman (detail).

process.step 2 >>

I initiated the journey of depicting Whitman's labyrinth without knowing that I was going to get trapped on it. To my surprise, there was no exit and no return. I spent many nights finding the best way to visualize a medieval labyrinth that will hold his verses, sentences and words broken into letters that somehow will move through the labyrinth. Interactivity was another concern. I spent several nights trying to develop a ‘leader’ property behavior to attract a leader and its siblings on a given path. Eventually, I found a solid way to do it. Here is a description of the entire project:

1. Labyrinth: The poems are broken down into clusters of text which float around on an empty space. When a letter is dragged, all the following letters come into focus and follow the line drawn by the mouse path.

2. FollowLeaderPath: This class produces the attraction between a leader and its sibling on a given path. It fetches a path from the SnakeMousePath input source and affects the glyph that has the 'Leader' property set to true. The leader and its siblings are then attracted to their respective position on the path with equal distance between them. The siblings are affected through the leader to minimize computation. One way was to look at each glyph to see if they were a sibling of a leader, then compute their position backwards on the path. Much simpler the way it is now, find the leader and then compute it and its right neighbours.
3. IWillFollowAction: Adds the Leader and Follower properties to the set of basic properties

4. IsInSnake: Condition that checks if a {link net.nexttext.TextObject} is in the snake or not.

5. Path: This is the vectorial (bezierVertex) representation of the Labyrinth

6. RotateBack: Behaviour that rotates a TextObject back to 0 degrees.

7. SnakeMousePath: An interface for the mouse. It listens to every mouse event and stores them in a list as {@link MouseEvent} objects. It also keeps the current status of the mouse buttons and the current x and y position. SnakeMousePath saves an array of the points when the mouse is dragged. The points are spaced by a given distance and the array is reset when the button is released.. It also registers and releases the 'leader', by cycling through the glyphs in the book and checking if any collide with the mouse coordinates when dragged.

8. Swim: Behaviour that moves a TextObject on random trajectories around its parent's center, creating a swimming effect.


And, of course, the code:
import java.awt.Color;
import java.awt.Rectangle;
import java.awt.geom.Point2D;

//import processing.opengl.*;

import net.nexttext.*;
import net.nexttext.behaviour.*;
import net.nexttext.behaviour.standard.*;
import net.nexttext.behaviour.control.*;
import net.nexttext.property.*;

// constants
static final float FONT_SIZE = 16.0f;
static final float PATH_POINTDISTANCE = 20.0;
static final Color IDLE_COLOR = new Color(0.5f, 0.5f, 0.5f, 0.1f);
static final Color SELECTED_COLOR = new Color(1.0f, 1.0f, 1.0f, 1.0f);
static final Color STROKE_COLOR = new Color(0.0f, 0.0f, 0.0f, 0.0f);
static final float COLOR_SPEED = 10.0f;
static final float ROTATE_SPEED = (float)Math.PI/40;

// variables
Book book;
SnakeMousePath snakeMousePath;
PFont font;

AbstractBehaviour topBehaviour;
Repeat repeat;
Condition isInSnake;

Multiplexer inSnakeActions;
Multiplexer outOfSnakeActions;

Action colorizeSelected;
Action followLeaderPath;

Action colorizeIdle;
Action swim;
Action rotateBack;

Path path;

void setup() {
  size(1000, 1000);
  smooth();
  newPath();  
  book = new Book(this);

  // create the mouse path input source and add it to the manager
  snakeMousePath = new SnakeMousePath(this, book, PATH_POINTDISTANCE);
  book.getInputs().add("SnakeMousePath", snakeMousePath);
  
  font = createFont("GillSans-UltraBold.ttf", FONT_SIZE, true);
  textFont(font);
  noStroke();
  
  inSnakeActions = new Multiplexer();
  colorizeSelected = new Colorize(#000000, COLOR_SPEED);
  inSnakeActions.add(colorizeSelected);
  followLeaderPath = new FollowLeaderPath(snakeMousePath, PATH_POINTDISTANCE);
  inSnakeActions.add(followLeaderPath);

  outOfSnakeActions = new Multiplexer();
  colorizeIdle = new Colorize(#ffffff, COLOR_SPEED);
  outOfSnakeActions.add(colorizeIdle);
  swim = new Swim(new Rectangle(width, height));
  outOfSnakeActions.add(swim);
  rotateBack = new RotateBack(ROTATE_SPEED);
  outOfSnakeActions.add(rotateBack);

  isInSnake = new IsInSnake(inSnakeActions, outOfSnakeActions);
  repeat = new Repeat(isInSnake, 0);
  topBehaviour = new Behaviour(repeat);
  book.addBehaviour(topBehaviour);

  buildBook();

}

public void buildBook() {
  // load the text file
  String lines[] = loadStrings("iwf.txt");
  for (int i=0; i < lines.length; i++) {
    if (lines[i].length() != 0) {
      buildLine(lines[i], new PVector(0, 0, 0));
    }  
  }
}

public void buildLine(String text, PVector pos) {
  // create a new passage
  TextObjectGroup newGroup = new TextObjectGroup(pos);

  // create a new word and set its temporary position to 0,0,0
  PVector wOffset = new PVector(0, 0, 0);
  TextObjectGroup wordGroup = new TextObjectGroup(wOffset);

  // each glyph is offset by gOffset from the word location.
  // start at 0,0,0
  PVector gOffset = new PVector(0, 0, 0);

  for (int i=0; i < text.length(); i++) {
    // get the glyph at position i
    String glyphString = text.substring(i,i+1);

    // if glyph is a space (end of word)
    if (glyphString.compareTo(" ") == 0) {
      // get a random position for the word in the window
      wOffset =  new PVector(random(1)*width, random(1)*height, 0);

      // set the word position
      PVectorProperty wordPos = (PVectorProperty)wordGroup.getProperty("Position");
      wordPos.add(wOffset);

      // attach the word to the passage
      newGroup.attachChild(wordGroup);

      // create a new word and rest the glyph offset
      wordGroup = new TextObjectGroup(new PVector(0,0,0));
      gOffset = new PVector(0,0,0);

    // if the glyph is not a space
    } else {
      // create a new glyph
      TextObjectGlyph glyph = new TextObjectGlyph(glyphString.toUpperCase(), font, FONT_SIZE, gOffset);

      // initalize the glyph's colour
      ColorProperty colorProperty = glyph.getColor();
      Color glyphColor = IDLE_COLOR;
      colorProperty.set(glyphColor);
      colorProperty.setOriginal(glyphColor);
      
      // initalize the glyph's stroke colour
      ColorProperty strokeColorProperty = glyph.getStrokeColor();
      Color glyphStrokeColor = STROKE_COLOR;
      strokeColorProperty.set(glyphStrokeColor);
      strokeColorProperty.setOriginal(glyphStrokeColor);

      // intialize the "Follower" property
      BooleanProperty followProperty = new BooleanProperty(false);
      glyph.init("Follower", followProperty);

      // translate the glyph offset the its width and spacing
      gOffset.add(new PVector((float)glyph.getLogicalBounds().getWidth(), 0, 0));
      gOffset.add(new PVector(2, 0));

      // attach the glyph to the word
      wordGroup.attachChild(glyph);   

      // register the glyph with the behaviour
      topBehaviour.addObject(glyph);
    }
  }

  // get a random position for the word in the window
  wOffset =  new PVector(random(1)*width, random(1)*height, 0);

  // set the word position
  PVectorProperty wordPos = (PVectorProperty)wordGroup.getProperty("Position");
  wordPos.add(wOffset);

  // attach the last word to the passage
  newGroup.attachChild(wordGroup);

  // attach the passage to the book
  book.attachText(newGroup);
}

void draw() {
  background(0);
path.display();
  renderSnakeMousePath();
  book.stepAndDraw();
}

void newPath() {
  // A path is a series of connected points
  // A more sophisticated path might be a curve
  path = new Path();
  float offset = 60;
  path.addPoint(offset,offset);
  path.addPoint(width-offset,offset);
  path.addPoint(width-offset,height-offset);
  path.addPoint(width/2,height-offset*3);
  path.addPoint(offset,height-offset);
}

public void renderSnakeMousePath() {
  noFill();
  stroke(255, 255, 255, 50);

  // get the path
  ArrayList path = snakeMousePath.getPath();

  beginShape();
  // render curves for every set of three points, using the middle one as the control point
  Point2D point1;
  for (int i=0; i < path.size(); i++) {
    point1 = (Point2D)path.get(i);
    curveVertex((float)point1.getX(), (float)point1.getY());
  }
  endShape();

  // draw "webs" at every corner
  Point2D point2;
  for (int i=0; i < path.size()-2; i+=1) {
    point1 = (Point2D)path.get(i);
    point2 = (Point2D)path.get(i+2);
    line((int)point1.getX(), (int)point1.getY(), (int)point2.getX(), (int)point2.getY());
  }
}
import net.nexttext.*;
import net.nexttext.property.*;
import processing.core.PVector;

import java.awt.*;
import java.util.ArrayList;


public class FollowLeaderPath extends IWillFollowAction {
    // constants
    static final int GLYPH_SPACER = 1;
    static final int WORD_SPACER = 15;

    // mouse path input source
    SnakeMousePath isrcMousePath;
    float pathPointDistance;

    
    /** 
     * Creates a new instance of FollowLeaderPath
     *
     * @param isrcMousePath the SnakeMousePath input source 
     * @param pathPointDistance the minimum distance between two consecutive points on the path
     */
    public FollowLeaderPath(SnakeMousePath isrcMousePath, float pathPointDistance) {
        this.isrcMousePath = isrcMousePath;
        this.pathPointDistance = pathPointDistance;
    }
    
    
    /** 
     * If the TextObject is the 'Leader', moves it and its right siblings on the 
     * path, rotating each TextObject to match the slope of the path segment it 
     * is lying on.
     *
     * @param to the TextObject to act upon
     */
    public ActionResult behave(TextObject to) {
        // get a copy of the path
        ArrayList path = isrcMousePath.getPath();

        // if there is no path, return
        if (path.size() == 0) 
            return new ActionResult(false, false, false);
        
        // if the text object is the leader then attract it and its sibling towards the path
        if (isLeader(to).get()) {
            // start at the end of the path (most recent mouse position)
            int pathIndex = path.size() - 1;

            int pathPixelDistance = 0;  // distance in pixels from the end of the path where this object belongs
            PVector pathPosition;  // current path position, derived from pathIndex

            // start at the object itself as it is the leader
            TextObject rightSibling = to;
            while (rightSibling != null) {
                // find the point on the path prior to the position for this object
                int prevPathIndex = (path.size()-1)-(int)(pathPixelDistance/pathPointDistance);
                if (prevPathIndex <= 0) 
                    break;
                
                // calculate the vector to the previous path point
                Point prevPathPoint = (Point)path.get(prevPathIndex);
                PVector prevPathVector3 = new PVector(prevPathPoint.x, prevPathPoint.y);
                
                // calculate the vector to the next path point
                Point nextPathPoint = (Point)path.get(prevPathIndex - 1);
                PVector nextPathVector3 = new PVector(nextPathPoint.x, nextPathPoint.y);

                // calculate the vector from the previous path point to the object's desired location on the path
                pathPosition = nextPathVector3.get();
                pathPosition.sub(prevPathVector3);
                pathPosition.mult((pathPixelDistance % pathPointDistance) / pathPointDistance);
                pathPosition.add(prevPathVector3);
                
                // move the glyph towards the new position on the path
                PVectorProperty position = (PVectorProperty)rightSibling.getProperty("Position");  
                PVector positionAbs = rightSibling.getPositionAbsolute();
                 
                // calculate the offset from the current position
                PVector move = new PVector(pathPosition.x - positionAbs.x,
                                           pathPosition.y - positionAbs.y);

                // move towards new position by a max of 20 
                if (move.mag() > 20.0) {
                    move.normalize();
                    move.mult(20.0f);
                }

                // translate position
                position.add(move);

                // find the glyph angle by calculating the slope of the points 
                // on the path located before and after the position of the glyph
                NumberProperty rotation = (NumberProperty)rightSibling.getProperty("Rotation");
                if (pathIndex > 0) {
                    float rotate = 0;
                    if (pathIndex < path.size()-1) {
                        rotate = (float)Math.atan2(nextPathPoint.y-prevPathPoint.y,
                                            nextPathPoint.x-prevPathPoint.x);
                    } else if (pathIndex == path.size()-1) {
                        rotate = (float)Math.atan2(prevPathPoint.y-nextPathPoint.y,
                                            prevPathPoint.x-nextPathPoint.x);
                    }

                    // limit the rotations to the 1st and 4th quadrants 
                    // so that the glyphs are always readable
                    if (rotate < 0) 
                        rotate += Math.PI*2;
                    if ((rotate < Math.PI/2*3) && (rotate > Math.PI/2)) 
                        rotate += Math.PI;
                    if (rotate > Math.PI*2) 
                        rotate -= Math.PI*2;

                    rotation.set(rotate);
                }
                
                // if we are moving from right to left, set the offset for the next object based on the current object's width
                if (nextPathVector3.x >= prevPathVector3.x) {
                    pathPixelDistance += GLYPH_SPACER + ((TextObjectGlyph) rightSibling).getLogicalBounds().getWidth();
                }
                
                // get the next sibling
                if (rightSibling.getRightSibling() == null) {
                    if (rightSibling.getParent() == null) {
                        // no parent, we are done
                        rightSibling = null;
                    } else {
                        TextObjectGroup rightParent = (TextObjectGroup)rightSibling.getParent().getRightSibling();
                        if (rightParent == null) {
                            // no more words, we are done
                            rightSibling = null;
                        } else {
                            // go to the first glyph of the next word
                            rightSibling = rightParent.getLeftMostChild();
                            pathPixelDistance += WORD_SPACER;
                        }
                    }
                } else {
                    // go to the next glyph of the current word
                    rightSibling = rightSibling.getRightSibling();
                }
                
                // if we are moving from left to right, set the offset for the next object based on its own width
                if ((rightSibling != null) && (nextPathVector3.x < prevPathVector3.x)) {
                    pathPixelDistance += GLYPH_SPACER + ((TextObjectGlyph) rightSibling).getLogicalBounds().getWidth(); 
                }
            }
        } 

        return new ActionResult(false, false, false);
    }
}
import net.nexttext.TextObject;
import net.nexttext.property.BooleanProperty;
import net.nexttext.behaviour.physics.PhysicsAction;

import java.util.Map;

public class IWillFollowAction extends PhysicsAction {
    
//return Map containing the properties
        public Map getRequiredProperties() {
        Map properties = super.getRequiredProperties();

        BooleanProperty leader = new BooleanProperty(false);
        properties.put("Leader", leader);
        
        BooleanProperty follower = new BooleanProperty(false);
        properties.put("Follower", follower);

        return properties;
    }

    
// Gets the value of the Leader property
// param to the concerned TextObject
// return BooleanProperty whether this TextObject has the Leader property or not

    public BooleanProperty isLeader(TextObject to) {
        return (BooleanProperty)to.getProperty("Leader");
    }
    
// Gets the value of the Follower property. 
// param to the concerned TextObject
// return BooleanProperty whether this TextObject has the Follower property or not

    public BooleanProperty isFollower(TextObject to) {
        return (BooleanProperty)to.getProperty("Follower");
    }
}
import net.nexttext.TextObject;
import net.nexttext.behaviour.Action;
import net.nexttext.behaviour.control.Condition;
import net.nexttext.property.BooleanProperty;


public class IsInSnake extends Condition {
        
    /** 
     * Creates a new instance of IsInSnake
     *
     * @param trueAction the actions to perform if the TextObject is in the snake
     * @param falseAction the actions to perform if the TextObject is not in the snake
     */
    public IsInSnake(Action trueAction, Action falseAction) {
        super(trueAction, falseAction);
    }
    
    
    /** 
     * Checks whether or not the condition holds on the TextObject
     *
     * @param to the concerned TextObject
     *
     * @return the outcome of the condition
     */
    public boolean condition(TextObject to) {
        BooleanProperty followProperty = (BooleanProperty)to.getProperty("Follower");
       if (followProperty.get()) 
            return true;
        return false;
    }
}
// Path 

class Path {

  // A Path is an arraylist of points (PVector objects)
  ArrayList points;
  // A path has a radius, i.e how far is it ok for the boid to wander off
  float radius;

  Path() {
    // Arbitrary radius of 20
    radius = 20;
    points = new ArrayList();
  }

  // Add a point to the path
  void addPoint(float x, float y) {
    PVector point = new PVector(x,y);
    points.add(point);
  }

  // Draw the path
  void display() {

    // Draw the radius as thick lines and circles

    // Draw end points
    for (int i = 0; i < points.size(); i++) {
      PVector point = (PVector) points.get(i);
      fill(175);
      noStroke();
      ellipse(point.x,point.y,radius*2,radius*2);
    }

    // Draw Polygon around path
    for (int i = 0; i < points.size(); i++) {
      PVector start = (PVector) points.get(i);
      // We're assuming path is a circle in this example
      PVector end = (PVector) points.get((i+1)%points.size());
      PVector line = PVector.sub(end,start);
      PVector normal = new PVector(line.y,-line.x);
      normal.normalize();
      normal.mult(radius);

      // Polygon has four vertices
      PVector a = PVector.add(start, normal);
      PVector b = PVector.add(end, normal);
      PVector c = PVector.sub(end, normal);
      PVector d = PVector.sub(start, normal);

      fill(175);
      noStroke();
      beginShape();
      vertex(a.x,a.y);
      vertex(b.x,b.y);
      vertex(c.x,c.y);
      vertex(d.x,d.y);
      endShape();
    }

    // Draw Regular Line
  pushMatrix();
        noStroke();
        background(255);
        fill(0);
        beginShape();
    vertex( 499 , 970 );
        bezierVertex( 494 , 916 , 498 , 853 , 497 , 796);
        bezierVertex( 371 , 784 , 287 , 724 , 242 , 628);
        bezierVertex( 231 , 606 , 222 , 577 , 218 , 547);
        bezierVertex( 216 , 536 , 214 , 521 , 222 , 520);
        bezierVertex( 231 , 519 , 230 , 533 , 232 , 544);
        bezierVertex( 241 , 609 , 272 , 661 , 310 , 699);
        bezierVertex( 356 , 745 , 411 , 775 , 496 , 780);
        bezierVertex( 498 , 713 , 496 , 642 , 497 , 573);
        bezierVertex( 458 , 566 , 427 , 530 , 440 , 485);
        bezierVertex( 447 , 460 , 469 , 444 , 497 , 438);
        bezierVertex( 497 , 419 , 497 , 399 , 497 , 380);
        bezierVertex( 437 , 386 , 393 , 422 , 381 , 477);
        bezierVertex( 370 , 526 , 391 , 572 , 419 , 598);
        bezierVertex( 421 , 599 , 442 , 610 , 436 , 620);
        bezierVertex( 428 , 632 , 400 , 599 , 396 , 594);
        bezierVertex( 378 , 572 , 366 , 545 , 364 , 507);
        bezierVertex( 345 , 505 , 323 , 505 , 303 , 505);
        bezierVertex( 308 , 588 , 338 , 633 , 389 , 670);
        bezierVertex( 396 , 675 , 404 , 681 , 415 , 686);
        bezierVertex( 424 , 690 , 441 , 694 , 439 , 703);
        bezierVertex( 436 , 714 , 413 , 701 , 406 , 698);
        bezierVertex( 375 , 682 , 347 , 656 , 330 , 632);
        bezierVertex( 304 , 597 , 284 , 546 , 290 , 484);
        bezierVertex( 299 , 391 , 358 , 326 , 439 , 300);
        bezierVertex( 453 , 295 , 470 , 289 , 473 , 297);
        bezierVertex( 477 , 308 , 450 , 311 , 445 , 313);
        bezierVertex( 364 , 337 , 317 , 399 , 303 , 489);
        bezierVertex( 321 , 492 , 343 , 492 , 364 , 492);
        bezierVertex( 371 , 451 , 387 , 419 , 414 , 397);
        bezierVertex( 435 , 380 , 467 , 364 , 504 , 364);
        bezierVertex( 568 , 364 , 616 , 405 , 636 , 454);
        bezierVertex( 639 , 462 , 641 , 474 , 634 , 475);
        bezierVertex( 624 , 477 , 622 , 458 , 619 , 451);
        bezierVertex( 600 , 410 , 565 , 385 , 512 , 380);
        bezierVertex( 510 , 397 , 512 , 418 , 511 , 437);
        bezierVertex( 539 , 443 , 567 , 463 , 571 , 493);
        bezierVertex( 575 , 523 , 563 , 545 , 543 , 560);
        bezierVertex( 543 , 581 , 542 , 604 , 544 , 624);
        bezierVertex( 579 , 613 , 604 , 591 , 620 , 557);
        bezierVertex( 622 , 552 , 627 , 524 , 637 , 527);
        bezierVertex( 648 , 530 , 634 , 559 , 631 , 566);
        bezierVertex( 613 , 603 , 583 , 627 , 544 , 641);
        bezierVertex( 544 , 661 , 545 , 681 , 545 , 702);
        bezierVertex( 590 , 693 , 622 , 673 , 649 , 645);
        bezierVertex( 675 , 618 , 697 , 583 , 704 , 540);
        bezierVertex( 716 , 469 , 690 , 415 , 660 , 378);
        bezierVertex( 633 , 345 , 598 , 319 , 546 , 308);
        bezierVertex( 538 , 306 , 529 , 306 , 529 , 299);
        bezierVertex( 529 , 285 , 558 , 296 , 565 , 298);
        bezierVertex( 654 , 324 , 708 , 389 , 720 , 491);
        bezierVertex( 738 , 492 , 762 , 490 , 782 , 490);
        bezierVertex( 775 , 409 , 744 , 352 , 698 , 308);
        bezierVertex( 653 , 265 , 594 , 232 , 511 , 230);
        bezierVertex( 428 , 228 , 362 , 262 , 318 , 303);
        bezierVertex( 278 , 340 , 247 , 388 , 235 , 452);
        bezierVertex( 234 , 458 , 235 , 472 , 223 , 473);
        bezierVertex( 215 , 466 , 219 , 455 , 221 , 447);
        bezierVertex( 235 , 383 , 266 , 334 , 305 , 296);
        bezierVertex( 350 , 251 , 412 , 222 , 493 , 216);
        bezierVertex( 495 , 199 , 493 , 177 , 494 , 158);
        bezierVertex( 391 , 161 , 316 , 201 , 260 , 256);
        bezierVertex( 205 , 310 , 163 , 386 , 156 , 486);
        bezierVertex( 148 , 593 , 195 , 682 , 245 , 738);
        bezierVertex( 285 , 783 , 340 , 820 , 409 , 840);
        bezierVertex( 415 , 842 , 435 , 844 , 433 , 854);
        bezierVertex( 431 , 863 , 414 , 857 , 405 , 854);
        bezierVertex( 335 , 833 , 278 , 794 , 236 , 749);
        bezierVertex( 182 , 690 , 144 , 612 , 142 , 507);
        bezierVertex( 124 , 505 , 105 , 506 , 86 , 505);
        bezierVertex( 88 , 627 , 130 , 711 , 191 , 781);
        bezierVertex( 241 , 838 , 310 , 884 , 401 , 907);
        bezierVertex( 406 , 908 , 435 , 913 , 435 , 921);
        bezierVertex( 435 , 933 , 407 , 924 , 400 , 922);
        bezierVertex( 312 , 900 , 241 , 856 , 189 , 801);
        bezierVertex( 131 , 739 , 81 , 650 , 72 , 538);
        bezierVertex( 60 , 385 , 126 , 271 , 202 , 196);
        bezierVertex( 236 , 162 , 271 , 138 , 320 , 114);
        bezierVertex( 353 , 98 , 405 , 79 , 456 , 75);
        bezierVertex( 476 , 73 , 475 , 78 , 475 , 81);
        bezierVertex( 476 , 92 , 444 , 92 , 437 , 93);
        bezierVertex( 337 , 109 , 263 , 154 , 206 , 211);
        bezierVertex( 138 , 279 , 94 , 362 , 86 , 490);
        bezierVertex( 100 , 491 , 122 , 492 , 141 , 492);
        bezierVertex( 150 , 383 , 189 , 308 , 248 , 249);
        bezierVertex( 306 , 191 , 383 , 150 , 490 , 142);
        bezierVertex( 499 , 141 , 508 , 141 , 518 , 142);
        bezierVertex( 622 , 148 , 703 , 190 , 759 , 246);
        bezierVertex( 808 , 295 , 846 , 357 , 862 , 444);
        bezierVertex( 863 , 452 , 870 , 475 , 859 , 476);
        bezierVertex( 848 , 476 , 850 , 457 , 849 , 448);
        bezierVertex( 836 , 360 , 789 , 294 , 736 , 245);
        bezierVertex( 679 , 192 , 608 , 162 , 509 , 156);
        bezierVertex( 507 , 174 , 509 , 196 , 508 , 215);
        bezierVertex( 602 , 218 , 665 , 255 , 716 , 306);
        bezierVertex( 759 , 349 , 795 , 413 , 796 , 495);
        bezierVertex( 796 , 581 , 769 , 645 , 726 , 694);
        bezierVertex( 684 , 742 , 629 , 777 , 553 , 790);
        bezierVertex( 553 , 809 , 553 , 828 , 553 , 847);
        bezierVertex( 647 , 833 , 710 , 796 , 762 , 739);
        bezierVertex( 806 , 690 , 839 , 634 , 850 , 550);
        bezierVertex( 851 , 543 , 851 , 520 , 860 , 521);
        bezierVertex( 869 , 522 , 867 , 542 , 865 , 554);
        bezierVertex( 854 , 635 , 817 , 702 , 772 , 750);
        bezierVertex( 717 , 809 , 650 , 847 , 554 , 862);
        bezierVertex( 552 , 879 , 554 , 900 , 553 , 918);
        bezierVertex( 666 , 902 , 746 , 854 , 810 , 787);
        bezierVertex( 869 , 725 , 921 , 629 , 921 , 509);
        bezierVertex( 921 , 386 , 877 , 294 , 815 , 226);
        bezierVertex( 757 , 162 , 669 , 105 , 561 , 91);
        bezierVertex( 553 , 90 , 534 , 93 , 534 , 82);
        bezierVertex( 534 , 70 , 562 , 77 , 574 , 79);
        bezierVertex( 674 , 96 , 748 , 142 , 807 , 198);
        bezierVertex( 882 , 269 , 945 , 386 , 935 , 537);
        bezierVertex( 927 , 651 , 872 , 746 , 810 , 808);
        bezierVertex( 760 , 858 , 700 , 896 , 623 , 918);
        bezierVertex( 598 , 925 , 571 , 933 , 542 , 933);
        bezierVertex( 537 , 922 , 539 , 909 , 539 , 896);
        bezierVertex( 539 , 858 , 538 , 815 , 540 , 779);
        bezierVertex( 547 , 774 , 556 , 775 , 564 , 774);
        bezierVertex( 628 , 761 , 680 , 725 , 717 , 683);
        bezierVertex( 755 , 639 , 781 , 580 , 782 , 505);
        bezierVertex( 761 , 504 , 742 , 506 , 722 , 506);
        bezierVertex( 716 , 598 , 671 , 661 , 604 , 696);
        bezierVertex( 583 , 707 , 559 , 718 , 533 , 717);
        bezierVertex( 528 , 705 , 530 , 691 , 530 , 677);
        bezierVertex( 530 , 636 , 528 , 593 , 529 , 552);
        bezierVertex( 537 , 545 , 545 , 541 , 550 , 532);
        bezierVertex( 574 , 493 , 540 , 446 , 495 , 453);
        bezierVertex( 446 , 460 , 438 , 531 , 479 , 552);
        bezierVertex( 488 , 557 , 499 , 555 , 510 , 561);
        bezierVertex( 512 , 620 , 510 , 683 , 511 , 744);
        bezierVertex( 585 , 740 , 632 , 715 , 673 , 675);
        bezierVertex( 706 , 642 , 734 , 599 , 742 , 541);
        bezierVertex( 744 , 529 , 742 , 521 , 751 , 522);
        bezierVertex( 763 , 523 , 752 , 562 , 750 , 570);
        bezierVertex( 737 , 619 , 714 , 655 , 683 , 686);
        bezierVertex( 641 , 728 , 589 , 753 , 512 , 759);
        bezierVertex( 510 , 823 , 512 , 891 , 511 , 956);
        bezierVertex( 582 , 960 , 644 , 940 , 695 , 916);
        bezierVertex( 745 , 892 , 791 , 861 , 827 , 823);
        bezierVertex( 898 , 746 , 957 , 645 , 957 , 502);
        bezierVertex( 957 , 363 , 896 , 254 , 825 , 183);
        bezierVertex( 749 , 108 , 650 , 55 , 509 , 49);
        bezierVertex( 507 , 67 , 509 , 89 , 508 , 108);
        bezierVertex( 629 , 114 , 711 , 154 , 777 , 216);
        bezierVertex( 839 , 275 , 892 , 358 , 900 , 469);
        bezierVertex( 909 , 596 , 867 , 687 , 811 , 754);
        bezierVertex( 763 , 811 , 697 , 860 , 611 , 883);
        bezierVertex( 601 , 885 , 579 , 895 , 579 , 882);
        bezierVertex( 579 , 873 , 598 , 872 , 609 , 869);
        bezierVertex( 728 , 835 , 814 , 755 , 858 , 648);
        bezierVertex( 874 , 608 , 887 , 565 , 887 , 507);
        bezierVertex( 870 , 505 , 849 , 507 , 831 , 506);
        bezierVertex( 821 , 663 , 744 , 755 , 626 , 806);
        bezierVertex( 620 , 810 , 609 , 813 , 600 , 816);
        bezierVertex( 591 , 819 , 580 , 825 , 573 , 817);
        bezierVertex( 573 , 805 , 588 , 804 , 595 , 802);
        bezierVertex( 658 , 783 , 708 , 748 , 746 , 701);
        bezierVertex( 788 , 647 , 820 , 580 , 814 , 482);
        bezierVertex( 809 , 397 , 769 , 331 , 723 , 284);
        bezierVertex( 684 , 245 , 626 , 210 , 556 , 198);
        bezierVertex( 548 , 197 , 529 , 199 , 529 , 189);
        bezierVertex( 529 , 177 , 549 , 183 , 558 , 185);
        bezierVertex( 633 , 198 , 694 , 234 , 736 , 277);
        bezierVertex( 789 , 332 , 822 , 395 , 830 , 492);
        bezierVertex( 849 , 492 , 868 , 492 , 887 , 492);
        bezierVertex( 879 , 370 , 835 , 289 , 767 , 227);
        bezierVertex( 735 , 198 , 697 , 170 , 651 , 151);
        bezierVertex( 605 , 133 , 549 , 119 , 487 , 123);
        bezierVertex( 380 , 129 , 297 , 175 , 237 , 232);
        bezierVertex( 182 , 284 , 139 , 359 , 125 , 457);
        bezierVertex( 124 , 465 , 126 , 471 , 117 , 471);
        bezierVertex( 105 , 471 , 112 , 446 , 114 , 436);
        bezierVertex( 129 , 348 , 171 , 278 , 222 , 227);
        bezierVertex( 286 , 163 , 374 , 113 , 493 , 109);
        bezierVertex( 495 , 91 , 493 , 69 , 494 , 50);
        bezierVertex( 355 , 52 , 260 , 108 , 184 , 179);
        bezierVertex( 108 , 249 , 57 , 348 , 47 , 483);
        bezierVertex( 42 , 550 , 58 , 618 , 78 , 671);
        bezierVertex( 99 , 724 , 128 , 770 , 162 , 807);
        bezierVertex( 233 , 885 , 323 , 941 , 454 , 956);
        bezierVertex( 456 , 938 , 454 , 916 , 455 , 896);
        bezierVertex( 301 , 872 , 200 , 791 , 144 , 667);
        bezierVertex( 130 , 637 , 119 , 604 , 113 , 563);
        bezierVertex( 111 , 553 , 106 , 530 , 115 , 529);
        bezierVertex( 127 , 527 , 126 , 552 , 127 , 561);
        bezierVertex( 140 , 645 , 176 , 714 , 225 , 764);
        bezierVertex( 284 , 824 , 350 , 868 , 454 , 880);
        bezierVertex( 456 , 864 , 454 , 844 , 455 , 827);
        bezierVertex( 375 , 811 , 315 , 779 , 268 , 728);
        bezierVertex( 223 , 679 , 187 , 615 , 180 , 531);
        bezierVertex( 171 , 419 , 219 , 331 , 274 , 275);
        bezierVertex( 313 , 235 , 373 , 197 , 444 , 185);
        bezierVertex( 456 , 183 , 470 , 181 , 471 , 189);
        bezierVertex( 472 , 198 , 455 , 198 , 445 , 200);
        bezierVertex( 375 , 214 , 322 , 249 , 282 , 289);
        bezierVertex( 232 , 339 , 202 , 399 , 194 , 489);
        bezierVertex( 207 , 492 , 232 , 491 , 251 , 492);
        bezierVertex( 256 , 423 , 283 , 369 , 323 , 329);
        bezierVertex( 366 , 286 , 422 , 254 , 502 , 250);
        bezierVertex( 614 , 255 , 690 , 310 , 731 , 392);
        bezierVertex( 737 , 404 , 743 , 415 , 748 , 432);
        bezierVertex( 750 , 440 , 760 , 472 , 751 , 476);
        bezierVertex( 740 , 480 , 741 , 463 , 739 , 453);
        bezierVertex( 728 , 400 , 698 , 357 , 660 , 324);
        bezierVertex( 622 , 291 , 575 , 268 , 511 , 266);
        bezierVertex( 508 , 287 , 511 , 306 , 511 , 326);
        bezierVertex( 605 , 336 , 675 , 396 , 683 , 494);
        bezierVertex( 689 , 574 , 641 , 632 , 590 , 661);
        bezierVertex( 584 , 665 , 574 , 673 , 567 , 664);
        bezierVertex( 566 , 656 , 575 , 653 , 580 , 650);
        bezierVertex( 629 , 624 , 665 , 580 , 669 , 507);
        bezierVertex( 652 , 505 , 630 , 507 , 611 , 506);
        bezierVertex( 607 , 529 , 602 , 550 , 590 , 567);
        bezierVertex( 584 , 575 , 575 , 587 , 564 , 585);
        bezierVertex( 559 , 570 , 575 , 564 , 580 , 556);
        bezierVertex( 590 , 541 , 599 , 517 , 594 , 488);
        bezierVertex( 589 , 464 , 573 , 441 , 553 , 428);
        bezierVertex( 548 , 425 , 531 , 422 , 533 , 413);
        bezierVertex( 536 , 400 , 554 , 413 , 560 , 417);
        bezierVertex( 588 , 434 , 602 , 456 , 609 , 492);
        bezierVertex( 628 , 492 , 648 , 492 , 667 , 492);
        bezierVertex( 663 , 445 , 645 , 409 , 615 , 384);
        bezierVertex( 588 , 361 , 548 , 338 , 497 , 341);
        bezierVertex( 422 , 346 , 373 , 389 , 351 , 450);
        bezierVertex( 348 , 459 , 344 , 473 , 339 , 474);
        bezierVertex( 327 , 475 , 332 , 458 , 335 , 448);
        bezierVertex( 359 , 379 , 413 , 337 , 497 , 326);
        bezierVertex( 496 , 306 , 495 , 287 , 495 , 266);
        bezierVertex( 428 , 270 , 377 , 296 , 338 , 333);
        bezierVertex( 300 , 369 , 272 , 419 , 266 , 484);
        bezierVertex( 259 , 555 , 285 , 614 , 317 , 654);
        bezierVertex( 351 , 696 , 394 , 726 , 455 , 739);
        bezierVertex( 457 , 724 , 456 , 695 , 455 , 677);
        bezierVertex( 394 , 656 , 349 , 616 , 332 , 548);
        bezierVertex( 330 , 541 , 325 , 523 , 334 , 522);
        bezierVertex( 345 , 520 , 344 , 543 , 346 , 548);
        bezierVertex( 361 , 601 , 401 , 643 , 454 , 661);
        bezierVertex( 456 , 642 , 454 , 619 , 455 , 599);
        bezierVertex( 427 , 576 , 404 , 561 , 400 , 516);
        bezierVertex( 395 , 468 , 422 , 433 , 452 , 415);
        bezierVertex( 460 , 410 , 472 , 403 , 476 , 411);
        bezierVertex( 480 , 419 , 468 , 422 , 467 , 423);
        bezierVertex( 436 , 440 , 409 , 468 , 415 , 520);
        bezierVertex( 419 , 556 , 445 , 574 , 470 , 590);
        bezierVertex( 470 , 617 , 470 , 647 , 470 , 675);
        bezierVertex( 470 , 703 , 474 , 731 , 468 , 755);
        bezierVertex( 408 , 749 , 359 , 719 , 323 , 682);
        bezierVertex( 282 , 641 , 253 , 585 , 251 , 507);
        bezierVertex( 233 , 505 , 214 , 506 , 195 , 505);
        bezierVertex( 194 , 597 , 230 , 666 , 276 , 715);
        bezierVertex( 323 , 765 , 385 , 802 , 468 , 815);
        bezierVertex( 470 , 839 , 470 , 866 , 470 , 893);
        bezierVertex( 470 , 920 , 472 , 948 , 467 , 970);
        bezierVertex( 397 , 967 , 337 , 948 , 287 , 922);
        bezierVertex( 188 , 871 , 111 , 791 , 68 , 682);
        bezierVertex( 46 , 626 , 29 , 560 , 32 , 489);
        bezierVertex( 38 , 347 , 97 , 245 , 172 , 170);
        bezierVertex( 230 , 112 , 303 , 69 , 393 , 47);
        bezierVertex( 409 , 43 , 425 , 41 , 442 , 39);
        bezierVertex( 611 , 17 , 750 , 88 , 833 , 171);
        bezierVertex( 908 , 246 , 971 , 355 , 971 , 504);
        bezierVertex( 971 , 651 , 909 , 760 , 833 , 837);
        bezierVertex( 794 , 876 , 747 , 909 , 693 , 933);
        bezierVertex( 639 , 957 , 575 , 975 , 499 , 970);
    endShape( CLOSE );
    popMatrix();

  }

}
import net.nexttext.TextObject;
import net.nexttext.property.NumberProperty;



public class RotateBack extends IWillFollowAction {
  
    /**
     * 
     * Creates a new instance of RotateBack
     */
    public RotateBack() {
        properties().init("speed", new NumberProperty((float)Math.PI / 40));
    }

    
    /**
     * 
     * Creates a new instance of RotateBack with custom speed
     * 
     * @param speed rotation speed
     */
    public RotateBack(float speed) {
        properties().init("speed", new NumberProperty(speed));
    }

    
    /** 
     * Rotates a TextObject back to 0 degrees.
     *
     * @param to the TextObject to act upon
     */
    public ActionResult behave(TextObject to) {
        float speed = ((NumberProperty)properties().get("speed")).get();

        // get the rotation
        NumberProperty rotation = to.getRotation();
        
        // if the TextObject is currently in quadrant 1, rotate CCW
        if ((rotation.get() > 0) && (rotation.get() <= Math.PI/2)) {
            rotation.set(rotation.get() - speed);
            if (rotation.get() < 0) { 
                rotation.set(0); 
            }
        // if the TextObject is currently in quadrant 4, rotate CW
        } else if ((rotation.get() >= Math.PI/2*3) && (rotation.get() <= Math.PI*2)) {
            rotation.set(rotation.get() + speed);
            if (rotation.get() > Math.PI*2) { 
                rotation.set(0); 
            }
        }
    
        return new ActionResult(false, false, false);
    }  
}
import java.util.ArrayList;
import java.awt.Point;
import java.awt.event.MouseEvent;

import net.nexttext.Book;
import net.nexttext.TextObject;
import net.nexttext.TextObjectGroup;
import net.nexttext.TextObjectGlyph;
import net.nexttext.TextObjectGlyphIterator;
import net.nexttext.input.InputSource;
import net.nexttext.property.BooleanProperty;

import processing.core.*;
        

/**
 * An interface for the mouse. It listens to every mouse event and stores them in 
 * a list as {@link MouseEvent} objects.  It also keeps the current status of 
 * the mouse buttons and the current x and y position.</p>
 * 
 * <p>SnakeMousePath saves an array of the points when the mouse is dragged.
 * The points are spaced by a given distance and the array is reset
 * when the button is released.</p>
 *
 * <p>SnakeMousePath also registers and releases the 'Leader', by cycling through
 * the glyphs in the book and checking if any collide with the mouse coordinates
 * when dragged.</p> 
 */
public class SnakeMousePath extends InputSource {
  
    double pointDistance;
    ArrayList path = new ArrayList();
    
    Book book;
    TextObject theLeader = null;
    
    public SnakeMousePath(PApplet pApplet, Book theBook) {
        pApplet.registerMouseEvent(this);
        book = theBook;
        pointDistance = 10.0;
    }

    
    /**
     * Creates a new instance of SnakeMousePath with custom point distance
     *
     * @param component the component the mouse is added to
     * @param theBook the book the mouse is added to
     * @param ptDist the point distance
     */
    public SnakeMousePath(PApplet pApplet, Book theBook, double ptDist) {
        pApplet.registerMouseEvent(this);
        book = theBook;
        pointDistance = ptDist;
    }
    
    public void mouseEvent(MouseEvent event) {
        switch (event.getID()) {
            case MouseEvent.MOUSE_PRESSED:
                mousePressed(event);
                break;
            case MouseEvent.MOUSE_RELEASED:
                mouseReleased(event);
                break;
            case MouseEvent.MOUSE_DRAGGED:
                mouseDragged(event);
                break;
        }
    }

    
    /**
     * Adds the current point to the path if the mouse button 1 is pressed
     *
     * @param event the mouse event
     */
    public void mousePressed(MouseEvent event) {
        synchronized (path) {
            if (event.getButton() == MouseEvent.BUTTON1) {
                // button 1 was pressed, add the point to the path    
                path.add(new Point(event.getX(), event.getY()));
            }
        }
    }

    
    /**
     * Clears the path if the mouse button 1 is released
     *
     * @param event the mouse event
     */
    public void mouseReleased(MouseEvent event) {
        synchronized (path) {
            // button 1 was released, clear the path
            if (event.getButton() == MouseEvent.BUTTON1) {
                path.clear();
                
                 if (theLeader != null) {
                    // clear the 'Leader' property
                    ((BooleanProperty)theLeader.getProperty("Leader")).set(false);
                    setFollower(theLeader, false);

                    // reset the leader pointer
                    theLeader = null;
                }
            }
        } 
    }
    
    
    /**
     * Updates the local mouse coordinates and adds points to the path
     *
     * @param event the mouse event
     */
    public void mouseDragged(MouseEvent event) {
        int distance;
        synchronized (path) {
            Point lastPoint = (Point)path.get(path.size()-1);
            distance = (int)lastPoint.distance(event.getX(), event.getY());
            
            // add interpolating points up until the current point over the distance from the last path point
            for (int i=0; i < (int)(distance/pointDistance); i++) {
                path.add(new Point((int)((double)(event.getX() - lastPoint.x)/distance*pointDistance*(i+1) + lastPoint.x),
                                   (int)((double)(event.getY() - lastPoint.y)/distance*pointDistance*(i+1) + lastPoint.y)));
            }
        }
        
        // look for a collision with a glyph if there is no leader already set
        if ((theLeader == null) && (distance >= pointDistance)) {
           TextObjectGlyph currGlyph;
           
           TextObjectGlyphIterator i = book.getTextRoot().glyphIterator();
           while (i.hasNext()) {
               currGlyph = i.next();
               
               if (currGlyph.getBoundingPolygon().contains(event.getX(), event.getY())) {
                   // set the leader pointer
                   theLeader = currGlyph;
                   
                   // set the 'Leader' property to true
                   ((BooleanProperty)currGlyph.getProperty("Leader")).set(true);
                   setFollower(currGlyph, true);
                   
                   break;
               }
           }
        }
    }
    
    
    /** 
     * Recursively sets the 'Follower' property for the TextObject and its right siblings
     *
     * @param to the TextObject to act upon
     * @param follow the value of the Follower property
     */
    public void setFollower(TextObject to, boolean follow) {
     if (to == null) return ;
        
     BooleanProperty followProperty = (BooleanProperty)to.getProperty("Follower");
   followProperty.set(follow);

        TextObject rightSibling = to.getRightSibling();
        if (rightSibling == null) {
            if (to.getParent() == null) return;
            else {
                TextObjectGroup rightParent = (TextObjectGroup)to.getParent().getRightSibling();
                if (rightParent == null) return;
                rightSibling = rightParent.getLeftMostChild();
            }
        }

        setFollower(rightSibling, follow);
    }

    
    /**
     * Gets a copy of the path
     *
     * @return the path
     */
    public ArrayList getPath() {
        synchronized (path) {
            ArrayList copy = new ArrayList();
            copy.addAll(path);
            
            return copy;
        }
    }
}
import net.nexttext.TextObject;
import net.nexttext.TextObjectGroup;
import net.nexttext.Locatable;
import net.nexttext.property.NumberProperty;
import net.nexttext.property.PVectorProperty;

import processing.core.PVector;

import java.awt.Rectangle;


/**
 * Behaviour that moves a TextObject on random trajectories around its parent's 
 * center, creating a swimming effect.
 */
public class Swim extends IWillFollowAction {

    static final String REVISION = "$CVSHeader$";
    
    static final float THRESHOLD = 0.1f;
    
    Rectangle bounds;
    
    // when setting a new target, this is the max distance away from the 
    // current position that it can be
    float segmentX;  
    float segmentY;

    
    /** 
     * Creates a new instance of Swim
     * 
     * @param bounds the bounding rectangle
     */
    public Swim(Rectangle bounds) {
        this.bounds = bounds;
        
        properties().init("speedX", new NumberProperty(0.5f));
        properties().init("speedY", new NumberProperty(0.5f));
      
        segmentX = 25.0f;
        segmentY = 25.0f;
    }
    
    
    /** 
     * Creates a new instance of Swim with custom speed and segment length
     * 
     * @param bounds the bounding rectangle
     * @param speedX horizontal float speed
     * @param speedY vertical float speed
     * @param segmentX max horizontal segment length
     * @param segmentY max vertical segment length
     */
    public Swim(Rectangle bounds, float speedX, float speedY, float segmentX, float segmentY) {
        this.bounds = bounds;
        
        properties().init("speedX", new NumberProperty(speedX));
        properties().init("speedY", new NumberProperty(speedY));
        
        this.segmentX = segmentX;
        this.segmentY = segmentY;
    }
 
    
    /** 
     * Moves a TextObject in small increments on a trajectory.
     *
     * @param to the TextObject to act upon
     */
    public ActionResult behave(TextObject to) {
        float x, y;            
            
        // get the position
        PVectorProperty positionProperty = (PVectorProperty)to.getProperty("Position");
        PVector position = positionProperty.get();
        
        // if not yet set, init the target at the TextObject's current position
        if (to.getProperty("Target") == null)
            to.init("Target", new PVectorProperty(position));
        
        
        // get the target position
        PVector target = ((PVectorProperty)to.getProperty("Target")).get();

        // calculate the difference from the current position to the target position
        float targetDiffX = target.x-position.x;
        float targetDiffY = target.y-position.y;
        
        // if the TextObject has reached its target, set a new one
        if ((Math.abs(targetDiffX) < THRESHOLD) && (Math.abs(targetDiffY) < THRESHOLD)) {
            // get the distance from the parent's (word) center
            float parentDiffX = to.getParent().getCenter().x - to.getCenter().x;
            float parentDiffY = to.getParent().getCenter().y - to.getCenter().y;
    
            // new targets are always in the direction of the parent's center
            if ((-segmentX <= parentDiffX) && (parentDiffX <= segmentX))
                x = (float)Math.random()*2*segmentX-segmentX;
            else if (-segmentX > parentDiffX)
                x = (float)Math.random()*segmentX;
            else
                x = (float)Math.random()*-segmentX;
                        
            if ((-segmentY <= parentDiffY) && (parentDiffY <= segmentY))
                y = (float)Math.random()*2*segmentY-segmentY;
            else if (-segmentY > parentDiffY)
                y = (float)Math.random()*segmentY;
            else
                y = (float)Math.random()*-segmentY;
            
            // make sure the next target is inside the window
            PVector nextTarget = new PVector(x, y);
            PVector nextAbsTarget = to.getLocation();
            nextAbsTarget.add(nextTarget);
            if (nextAbsTarget.x <= 0)
                nextTarget.add(new PVector(segmentX, 0));
            else if (nextAbsTarget.x >= bounds.width)
                nextTarget.add(new PVector(-segmentX, 0));
            
            if (nextAbsTarget.y <= 0)
                nextTarget.add(new PVector(0, segmentY));
            else if (nextAbsTarget.y >= bounds.height)
                nextTarget.add(new PVector(0, -segmentY));
                        
            setTarget(to, new PVector(nextTarget.x, nextTarget.y, nextTarget.z));
            
        // if the TextObject has not reached its target, move towards it    
        } else {
            // get the speed
            float speedX = ((NumberProperty)properties().get("speedX")).get();
            float speedY = ((NumberProperty)properties().get("speedY")).get();
        
            // move the TextObject towards the target by a random amount
            if (targetDiffX < 0)
                x = (float)(Math.random()*Math.max(targetDiffX, -speedX));
            else
                x = (float)(Math.random()*Math.min(targetDiffX, speedX));
            
            if (targetDiffY < 0)
                y = (float)(Math.random()*Math.max(targetDiffY, -speedY));
            else
                y = (float)(Math.random()*Math.min(targetDiffY, speedY));
            
            // translate to the new position
            positionProperty.add(new PVector(x, y));
        }
        
        return new ActionResult(false, false, false);
    }
    
   
    /**
     * Sets a new target destination for the TextObject, depending on its 
     * current position and its parent's current position
     *
     * @param to the TextObject to consider
     * @param target the new target
     */
    public void setTarget(TextObject to, PVector target) {
        PVectorProperty targetProperty = (PVectorProperty)to.getProperty("Target");
        targetProperty.set(target);
    }

}