/*
This project approaches idea of a puppet from a general core, where a puppet is seen
(and not unlike a marionette) as a rigid structure consisting of smaller blocks
connected to each other by strings. As one object's position is changed, so are the positions of the
connected blocks. Therefore this piece is not a puppet in itself, but can be seen as a kind of puppet
tool - where through slicing images into segments and overlaying them an illusion of physical change and
time is achieved. (For example, an environment and an object in a static image acquire a character in the presented photographs).
Controls:
p - change picture
c - clear
d - dot model
h - help
1-5 - change the layout of connecting strings (1 and 4 are concrete; 2,3 are half random; 5 is random)
+/- - change the stiffness of the spring by 0.025 (if seen in the dot model the stiffness of
spring is represented by thickness of the line)
Changes since Wednesday version:
- More informative instruction screen that appears longer (4 seconds, enough to read it 3 or 4 times)
- Thickness of a line connecting dots in the dot model represents stiffness of sprints (the more stiff
the weightier the line)
- Center dots appear only when mouse is over (FixedSpring.isOver()) the image rectangle containing them
(no matter what the stacking order is)
*/
PImage img;
PImage[] imgc = new PImage[3];
PImage instruct;
int[][] connections;
FixedSpring[] s;
int[][] rectinfo = new int[4][0];
float stiffness = 0.15;
int numBlocks = 0;
int numconnect = 1;
int curX, curY, startx, starty, swidth, sheight, counter;
int framestarted = 0;
boolean drag = false;
int state = 0;
boolean blocks = true;
void setup() {
size(600, 600);
frameRate(60);
background(43);
noStroke();
noFill();
smooth();
imgc[0] = loadImage("face.jpg");
imgc[1] = loadImage("burberry.jpg");
imgc[2] = loadImage("wood1.jpg");
img = imgc[0];
instruct = loadImage("instruct.gif");
updateLength(rectinfo);
}
void draw() {
switch(state) {
case 1:
fill(43);
rect(0,0,width,height);
for(int i=0; i<s.length; i++) {
s[i].update();
}
for(int i=0; i<s.length; i++) {
s[i].display();
}
for(int i=0; i<s.length; i++) {
s[i].isover(); //Draw center dots if mouse is over
}
if(frameCount < (framestarted + 240)) { //Instructions screen
image(instruct, width-105, 215);
}
break;
default:
image(img, 0, 0);
drawRects();
drawDrag();
//Show instructions
if(frameCount < (framestarted + 240)) { //Instructions screen
image(instruct, width-105, 215);
}
break;
}
}
void mousePressed() {
if(state == 1) {
for(int i=0; i<s.length; i++) {
s[i].rollover();
}
}
else {
rectinfo[0][numBlocks] = constrain(mouseX, 0, width);
rectinfo[1][numBlocks] = constrain(mouseY, 0, height);
}
}
void mouseReleased() {
if(state == 1) {
for(int i=0; i<s.length; i++) {
s[i].letgo();
}
}
else {
rectinfo[2][numBlocks] = constrain(mouseX, 0, width);
rectinfo[3][numBlocks] = constrain(mouseY, 0, height);
if(abs(rectinfo[2][numBlocks]-rectinfo[0][numBlocks]) > 5 && abs(rectinfo[3][numBlocks]-rectinfo[1][numBlocks]) > 5) {
//Swap x and y values of end points to make sure that width and height are always positive (in order to use get() function)
if((rectinfo[2][numBlocks]-rectinfo[0][numBlocks]) < 0) {
int k = rectinfo[2][numBlocks];
rectinfo[2][numBlocks] = rectinfo[0][numBlocks];
rectinfo[0][numBlocks] = k;
}
if((rectinfo[3][numBlocks]-rectinfo[1][numBlocks]) < 0) {
int k = rectinfo[3][numBlocks];
rectinfo[3][numBlocks] = rectinfo[1][numBlocks];
rectinfo[1][numBlocks] = k;
}
//Increase the amount of blocks to represent physical amount of rectnagles drawn and increase the size of array
numBlocks++;
updateLength(rectinfo);
}
drag = false;
}
}
void mouseDragged() {
drag = true;
}
void keyPressed() {
if(key == 'd' || key == 'D') { //where d stands for dots
blocks = !(blocks);
}
else if(key == ' ' && state == 0 && numBlocks > 1) {
intializeRect();
state = 1;
}
else if(key == 'c' || key == 'C') { // clear out the screen
clearSystem();
}
else if(key == 'p' || key == 'P') {
counter++;
counter%=3;
img = imgc[counter];
}
}
void keyReleased() {
if(key == '=' || key == '+') {
stiffness+=0.025;
stiffness = constrain(stiffness, 0.05, 0.225);
}
else if(key == '_' || key == '-') {
stiffness-=0.025;
stiffness = constrain(stiffness, 0.05, 0.225);
}
else if(key == 'h' || key == 'H') {
framestarted = frameCount;
}
else if(key == '1' || key == '2' || key == '3' || key == '4' || key == '5') {
if(state == 1) {
numconnect = int(key) - 48;
generateConnections(numconnect);
calcLength();
}
}
}
//General Control - clear system
void clearSystem() {
rectinfo = new int[4][0];
updateLength(rectinfo);
numBlocks = 0;
drag = false;
state = 0;
}
//Functions used by the slice tool
void updateLength(int[][] d) {
for (int i=0; i<d.length; i++) {
d[i] = expand(d[i], d[i].length+1);
}
}
void drawRects() {
if (rectinfo[0].length > 1) {
for(int i=0;i<rectinfo[0].length-1; i++) {
fill(240,120);
rect(rectinfo[0][i],rectinfo[1][i],rectinfo[2][i]-rectinfo[0][i],rectinfo[3][i]-rectinfo[1][i]);
noFill();
}
}
}
void drawDrag() {
if(drag == true) {
stroke(240,240);
rect(rectinfo[0][numBlocks],rectinfo[1][numBlocks], mouseX-rectinfo[0][numBlocks], mouseY-rectinfo[1][numBlocks]);
noStroke();
}
}
//Function that creates an array of objects
void intializeRect() {
s = new FixedSpring[numBlocks];
image(img, 0, 0);
// x, y, mass, id, fillcolor, width, height
for(int i=0; i<s.length; i++) {
startx = rectinfo[0][i];
starty = rectinfo[1][i];
swidth = rectinfo[2][i]-rectinfo[0][i];
sheight = rectinfo[3][i]-rectinfo[1][i];
s[i] = new FixedSpring(startx, starty, 1.0, i, swidth, sheight);
}
generateConnections(numconnect);
calcLength();
}
void calcLength() {
for(int i=0; i<s.length; i++) {
for(int j=0; j<connections[i].length; j++) {
int curID = connections[i][j];
float howlong = dist(s[i].x,s[i].y,s[curID].x,s[curID].y);
s[i].setLength(howlong, curID);
s[curID].setLength(howlong, i);
}
}
}
//Function that generates connection between the blocks on the screen
void generateConnections(int nc) {
connections = new int[numBlocks][0];
switch(nc) {
case 2:
for(int i=0; i<connections.length; i++) {
connections[i] = expand(connections[i],2);
int l=0;
int k;
for(int j=0; j<connections[i].length; j++) {
do {
k = int(random(0,numBlocks));
} while (k==l);
connections[i][j] = k;
l = k;
}
}
break;
case 3:
for(int i=0; i<connections.length; i++) {
connections[i] = expand(connections[i],3);
int l=0;
int k;
for(int j=0; j<connections[i].length; j++) {
do {
k = int(random(0,numBlocks));
} while (k==l);
connections[i][j] = k;
l = k;
}
}
break;
case 4:
for(int i=0; i<connections.length; i++) {
connections[i] = expand(connections[i],2);
for(int j=0; j<connections[i].length; j++) {
connections[i][j] = (i + 1 + 2*j)%(numBlocks);
}
}
break;
case 5:
for(int i=0; i<connections.length; i++) {
int connectsize = int(random(1,numBlocks+1));
connections[i] = expand(connections[i],connectsize);
for(int j=0; j<connections[i].length; j++) {
connections[i][j] = int(random(1,numBlocks));
}
}
break;
default:
for(int i=0; i<connections.length; i++) {
connections[i] = expand(connections[i],2);
for(int j=0; j<connections[i].length; j++) {
if(i==0 && j==0) {
connections[0][0] = (numBlocks - 1);
}
else {
connections[i][j] = (i - 1 + 2*j)%(numBlocks);
}
}
}
break;
}
}
class FixedSpring extends Spring2D {
float[] springLength = new float[numBlocks];
int id, bgcolor, mbgcolor, over;
int twidth, theight;
PImage tiles;
FixedSpring (float xpos, float ypos, float m, int ii, int sw, int sh) {
super((xpos + 0.5*sw), (ypos + 0.5*sh), m);
id = ii;
float bright = 0;
for(int i=0; i<10; i++) {
for(int j=0; j<10; j++) {
color cclr = get(int(xpos + 0.5*sw - 5 + j),int(ypos + 0.5*sh - 5 + i));
bright += brightness(cclr);
}
}
bright /= 100;
if (bright > 127) {
mbgcolor = 35;
}
else {
mbgcolor = 240;
}
bgcolor = mbgcolor;
twidth = sw;
theight = sh;
tiles = get(int(xpos),int(ypos),sw,sh);
}
void setLength(float s, int linkId) {
springLength[linkId] = s;
}
void update() {
if (over == 1 && mousePressed == true) {
super.moveThis(mouseX, mouseY);
}
else
{
// Calculate the target position
for(int i=0; i<connections[id].length; i++) {
float newx = s[connections[id][i]].x;
float newy = s[connections[id][i]].y;
float dx = x - newx;
float dy = y - newy;
float angle = atan2(dy, dx);
float targetX = newx + cos(angle) * springLength[connections[id][i]];
float targetY = newy + sin(angle) * springLength[connections[id][i]];
// Activate update method from Spring2D
super.update(targetX, targetY);
// Constrain to display window
x = constrain(x, 0, width);
y = constrain(y, 0, height);
}
}
}
void display() {
noStroke();
if(blocks == true) {
image(tiles, x-0.5*twidth, y-0.5*theight);
}
else {
stroke(255);
strokeWeight(.4 + (0.25 - stiffness)*4);
for(int i=0; i<connections[id].length; i++) {
line(x, y, s[connections[id][i]].x, s[connections[id][i]].y);
}
noStroke();
fill(210);
ellipse(x, y, radius*1.25, radius*1.25);
}
}
void isover() {
if (mouseX > (x-0.5*twidth) && mouseX < (x+0.5*twidth) && mouseY > (y-0.5*theight) && mouseY < (y+0.5*theight) && blocks == true) {
fill(bgcolor);
ellipse(x,y,radius,radius);
noFill();
}
}
void rollover() {
if (mouseX > (x - 10) && mouseX < (x + 10) && mouseY > (y - 10) && mouseY < (y + 10)) {
over = 1;
}
}
void letgo() {
bgcolor = mbgcolor;
over = 0;
}
}
class Spring2D {
float vy, vx, changex, changey;
float y, x;
float xint, yint;
float mass;
float radius = 5;
float damping = 0.85;
Spring2D(float xpos, float ypos, float m) {
x = xpos;
xint = xpos;
yint = ypos;
y = ypos;
mass = m;
}
void moveThis(float newerx, float newery) {
changex = (newerx - x)*0.25;
changey = (newery - y)*0.25;
x += changex;
y += changey;
}
void update(float targetX, float targetY) {
float forceX = (xint + targetX - 2*x) * stiffness;
float ax = forceX / mass;
vx = damping * (vx + ax);
x += vx;
float forceY = (yint + targetY - 2*y) * stiffness;
float ay = forceY / mass;
vy = damping * (vy + ay);
y += vy;
}
}