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.*;
/**
* A NextText-based interactive poem which is broken down into clusters of text which
* float around in empty space. When a letter is dragged, all the following letters
* in the stanza come into focus and follow the line drawn by the mouse path.
*
*
Interactive artwork by Jason Lewis with Elie Zananiri
* Processing port by Elie Zananiri | Obx Labs | June 2007
*/
// constants
static final float FONT_SIZE = 36.0f;
static final double 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 double ROTATE_SPEED = 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;
void setup() {
size(900, 600, OPENGL);
smooth();
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", 36, true);
textFont(font);
noStroke();
inSnakeActions = new Multiplexer();
colorizeSelected = new Colorize(SELECTED_COLOR, COLOR_SPEED);
inSnakeActions.add(colorizeSelected);
followLeaderPath = new FollowLeaderPath(snakeMousePath, PATH_POINTDISTANCE);
inSnakeActions.add(followLeaderPath);
outOfSnakeActions = new Multiplexer();
colorizeIdle = new Colorize(IDLE_COLOR, 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 Vector3(0, 0, 0));
}
}
}
public void buildLine(String text, Vector3 pos) {
// create a new passage
TextObjectGroup newGroup = new TextObjectGroup(pos);
// create a new word and set its temporary position to 0,0,0
Vector3 wOffset = new Vector3(0, 0, 0);
TextObjectGroup wordGroup = new TextObjectGroup(wOffset);
// each glyph is offset by gOffset from the word location.
// start at 0,0,0
Vector3 gOffset = new Vector3(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 Vector3(random(1)*width, random(1)*height, 0);
// set the word position
Vector3Property wordPos = (Vector3Property)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 Vector3(0,0,0));
gOffset = new Vector3(0,0,0);
// if the glyph is not a space
} else {
// create a new glyph
TextObjectGlyph glyph = new TextObjectGlyph(glyphString.toUpperCase(), font, 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 Vector3(glyph.getLogicalBounds().getWidth(), 0, 0));
gOffset.add(new Vector3(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 Vector3(random(1)*width, random(1)*height, 0);
// set the word position
Vector3Property wordPos = (Vector3Property)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);
renderSnakeMousePath();
book.stepAndDraw();
}
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());
}
}