package advanced.touchTail;
import java.awt.Polygon;
import org.mt4j.util.MTColor;
import org.mt4j.util.math.ToolsMath;
import org.mt4j.util.math.Vector3D;
import processing.core.PApplet;
/**
* The Class TailGesture.
*
* Yellowtail by Golan Levin (www.flong.com)
* Yellowtail (1998-2000) is an interactive software system for the gestural creation
* and performance of real-time abstract animation. Yellowtail repeats a user's strokes end-over-end,
* enabling simultaneous specification of a line's shape and quality of movement.
* Each line repeats according to its own period,
* producing an ever-changing and responsive display of lively, worm-like textures.
*/
public class TailGesture {
// private float damp = 5.0f; //ORIGINAL
private float damp = 5.0f;
private float dampInv = 1.0f / damp;
private float damp1 = damp - 1;
private int w;
private int h;
private int capacity;
private float INIT_TH = 14; //ORIGINAL
// private float INIT_TH = 24;
private float thickness = INIT_TH;
Vector3D path[];
int crosses[];
Polygon polygons[];
int nPoints;
int nPolys;
float jumpDx;
float jumpDy;
boolean exists;
private MTColor color;
public TailGesture(int mw, int mh) {
w = mw;
h = mh;
capacity = 600;
path = new Vector3D[capacity];
polygons = new Polygon[capacity];
crosses = new int[capacity];
for (int i=0;i<capacity;i++) {
polygons[i] = new Polygon();
polygons[i].npoints = 4;
path[i] = new Vector3D();
crosses[i] = 0;
}
nPoints = 0;
nPolys = 0;
exists = false;
jumpDx = 0;
jumpDy = 0;
// this.color = new MTColor(Tools3D.getRandom(50, 255), Tools3D.getRandom(50, 255), Tools3D.getRandom(50, 255), 255);
this.color = new MTColor(ToolsMath.getRandom(0, 255), ToolsMath.getRandom(0, 255), ToolsMath.getRandom(0, 255), 255);
}
public void clear() {
nPoints = 0;
exists = false;
thickness = INIT_TH;
}
public void clearPolys() {
nPolys = 0;
}
public void addPoint(float x, float y) {
if (nPoints >= capacity) {
// there are all sorts of possible solutions here,
// but for abject simplicity, I don't do anything.
}
else {
float v = distToLast(x, y);
float p = getPressureFromVelocity(v);
path[nPoints++].setXYZ(x,y,p);
if (nPoints > 1) {
exists = true;
jumpDx = path[nPoints-1].x - path[0].x;
jumpDy = path[nPoints-1].y - path[0].y;
}
}
}
private float getPressureFromVelocity(float v) {
final float scale = 18;
final float minP = 0.02f;
final float oldP = (nPoints > 0) ? path[nPoints-1].z : 0;
return ((minP + PApplet.max(0, 1.0f - v/scale)) + (damp1*oldP))*dampInv;
}
private void setPressures() {
// pressures vary from 0...1
float pressure;
Vector3D tmp;
float t = 0;
float u = 1.0f / (nPoints - 1)*PApplet.TWO_PI;
for (int i = 0; i < nPoints; i++) {
pressure = PApplet.sqrt((1.0f - PApplet.cos(t)) * 0.5f);
path[i].z = pressure;
t += u;
}
}
public float distToLast(float ix, float iy) {
if (nPoints > 0) {
Vector3D v = path[nPoints-1];
float dx = v.x - ix;
float dy = v.y - iy;
return PApplet.mag(dx, dy);
}
else {
return 30;
}
}
public void compile() {
// compute the polygons from the path of Vector3D's
if (exists) {
clearPolys();
Vector3D p0, p1, p2;
float radius0, radius1;
float ax, bx, cx, dx;
float ay, by, cy, dy;
int axi, bxi, cxi, dxi, axip, axid;
int ayi, byi, cyi, dyi, ayip, ayid;
float p1x, p1y;
float dx01, dy01, hp01, si01, co01;
float dx02, dy02, hp02, si02, co02;
float dx13, dy13, hp13, si13, co13;
float taper = 1.0f;
int nPathPoints = nPoints - 1;
int lastPolyIndex = nPathPoints - 1;
float npm1finv = 1.0f / PApplet.max(1, nPathPoints - 1);
// handle the first point
p0 = path[0];
p1 = path[1];
radius0 = p0.z * thickness;
dx01 = p1.x - p0.x;
dy01 = p1.y - p0.y;
hp01 = PApplet.sqrt(dx01*dx01 + dy01*dy01);
if (hp01 == 0) {
hp02 = 0.0001f;
}
co01 = radius0 * dx01 / hp01;
si01 = radius0 * dy01 / hp01;
ax = p0.x - si01;
ay = p0.y + co01;
bx = p0.x + si01;
by = p0.y - co01;
int xpts[];
int ypts[];
int LC = 20;
int RC = w-LC;
int TC = 20;
int BC = h-TC;
float mint = 0.618f;
float tapow = 0.4f;
// handle the middle points
int i = 1;
Polygon apoly;
for (i = 1; i < nPathPoints; i++) {
taper = PApplet.pow((lastPolyIndex-i) * npm1finv, tapow);
p0 = path[i-1];
p1 = path[i ];
p2 = path[i+1];
p1x = p1.x;
p1y = p1.y;
radius1 = Math.max(mint, taper * p1.z * thickness);
// assumes all segments are roughly the same length...
dx02 = p2.x - p0.x;
dy02 = p2.y - p0.y;
hp02 = (float) Math.sqrt(dx02*dx02 + dy02*dy02);
if (hp02 != 0) {
hp02 = radius1/hp02;
}
co02 = dx02 * hp02;
si02 = dy02 * hp02;
// translate the integer coordinates to the viewing rectangle
axi = axip = (int)ax;
ayi = ayip = (int)ay;
axi=(axi<0)?(w-((-axi)%w)):axi%w;
axid = axi-axip;
ayi=(ayi<0)?(h-((-ayi)%h)):ayi%h;
ayid = ayi-ayip;
// set the vertices of the polygon
apoly = polygons[nPolys++];
xpts = apoly.xpoints;
ypts = apoly.ypoints;
xpts[0] = axi = axid + axip;
xpts[1] = bxi = axid + (int) bx;
xpts[2] = cxi = axid + (int)(cx = p1x + si02);
xpts[3] = dxi = axid + (int)(dx = p1x - si02);
ypts[0] = ayi = ayid + ayip;
ypts[1] = byi = ayid + (int) by;
ypts[2] = cyi = ayid + (int)(cy = p1y - co02);
ypts[3] = dyi = ayid + (int)(dy = p1y + co02);
// keep a record of where we cross the edge of the screen
crosses[i] = 0;
if ((axi<=LC)||(bxi<=LC)||(cxi<=LC)||(dxi<=LC)) {
crosses[i]|=1;
}
if ((axi>=RC)||(bxi>=RC)||(cxi>=RC)||(dxi>=RC)) {
crosses[i]|=2;
}
if ((ayi<=TC)||(byi<=TC)||(cyi<=TC)||(dyi<=TC)) {
crosses[i]|=4;
}
if ((ayi>=BC)||(byi>=BC)||(cyi>=BC)||(dyi>=BC)) {
crosses[i]|=8;
}
//swap data for next time
ax = dx;
ay = dy;
bx = cx;
by = cy;
}
// handle the last point
p2 = path[nPathPoints];
apoly = polygons[nPolys++];
xpts = apoly.xpoints;
ypts = apoly.ypoints;
xpts[0] = (int)ax;
xpts[1] = (int)bx;
xpts[2] = (int)(p2.x);
xpts[3] = (int)(p2.x);
ypts[0] = (int)ay;
ypts[1] = (int)by;
ypts[2] = (int)(p2.y);
ypts[3] = (int)(p2.y);
}
}
public void smooth() {
// average neighboring points
final float weight = 18;
final float scale = 1.0f / (weight + 2);
int nPointsMinusTwo = nPoints - 2;
Vector3D lower, upper, center;
for (int i = 1; i < nPointsMinusTwo; i++) {
lower = path[i-1];
center = path[i];
upper = path[i+1];
center.x = (lower.x + weight*center.x + upper.x)*scale;
center.y = (lower.y + weight*center.y + upper.y)*scale;
}
}
public MTColor getColor() {
return color;
}
public void setColor(MTColor color) {
this.color = color;
}
}