// CSCI 5611 Project 2: Cloth Simulation// Copyright 2020 Emma Spindler and Parker Patterson. All rights reserved.// <spindl038> <patte591>// Summer 2020
// Portions of this code are borrowed from Stephen J. Guy's "Swinging Rope" example for CSCI 5611
// Simulation Parametersfloat floor; // floor = height; see setup()float gravity = 200;int maxNodes = 100;int numVertices = 80; int numRopes = numVertices;int numNodes = numVertices;PVector cameraPos;
boolean leftForceActive;boolean rightForceActive;boolean upForceActive;boolean downForceActive;float userForceSens = 1;
// Visualsboolean ballMode = false; // toggle with mouse1, mouse2PImage clothtex; // cloth texturefloat radius = 4;float sw = .5; // stroke weight
// Springs and massesfloat restLen; // restLen = floor*0.4/numVertices; see setup() (change this later)float mass = .2; //TRY-IT: How does changing mass affect resting length of the rope?float k = 200; //TRY-IT: How does changing k affect resting length of the rope?float kv = 50; //TRY-IT: How big can you make kv?float friction = .1;
// Obstacle//PShape sphere; float sphereRadius = 100;float sphereColor[] = {mouseX * 2, mouseY, 160};Vec3 spherePos = new Vec3(0,0,0);
// Top-left corner of clothfloat clothTopLeftX = 26;float clothTopLeftY = 26;float clothTopLeftZ = 26;
//Inital positions and velocities of massesfloat ballX[][] = new float[maxNodes][numRopes];float ballY[][] = new float[maxNodes][numRopes];float ballZ[][] = new float[maxNodes][numRopes];float velY[][] = new float[maxNodes][numRopes];float velX[][] = new float[maxNodes][numRopes];float velZ[][] = new float[maxNodes][numRopes];float accX[][] = new float[maxNodes][numRopes];float accY[][] = new float[maxNodes][numRopes];float accZ[][] = new float[maxNodes][numRopes];
PVector pos = new PVector();PVector vel = new PVector();PVector acc = new PVector();
void setup() { size(575, 700, P3D); camera(width/2, height/2, 700, // camera position width/2, height/2, 0, // center of scene 0, 1, 0); // environment parameters floor = height; restLen = .2*floor/numVertices; surface.setTitle("Stretchy Burlap"); clothtex = loadImage("cloth.jpg"); // obstacle initialization sphereColor[0] = mouseX * 2; sphereColor[1] = mouseY; sphereColor[2] = 255 - mouseX; spherePos.x = mouseX - .9*(width/2 - mouseX); spherePos.y = mouseY - .9*(height/2 - mouseY); spherePos.z = -350; //sphere = createShape(SPHERE, sphereRadius); //sphere.setFill(color(sphereColor[0],sphereColor[1],sphereColor[2]));
pos = new PVector(25, 25); vel = new PVector(0, 0); acc = new PVector(0, 0); leftForceActive = false; rightForceActive = false; upForceActive = false; downForceActive = false; for (int j = 0; j < numNodes; j++){ for (int i = 0; i < numRopes; i++){ if (j == 0){ // special case to make top row straight // TODO: simulation might be faster if we didn't have to make this check every loop ballX[j][i] = clothTopLeftX + (1/numVertices + 1)*7*i; ballY[j][i] = clothTopLeftY + restLen*j; ballZ[j][i] = clothTopLeftZ - 15*i; } else { ballX[j][i] = clothTopLeftX + (1/numVertices + 1)*7*i - random(2); ballY[j][i] = clothTopLeftY + restLen*j - random(2); ballZ[j][i] = clothTopLeftZ - 15*i - random(1); } velX[j][i] = 0; velY[j][i] = 0; velZ[j][i] = 0; } }}
void draw() { background(26); noCursor(); for(int i = 0; i < 20; i++) update(1/(60*frameRate)); // Lighting specular(127,127,127); // Main light source ambientLight(26,26,26); lightSpecular(127,127,127); // ?? directionalLight(200, 200, 200, -1, 1, -1); // obstacle pushMatrix(); //stroke(255, 50); translate(spherePos.x, spherePos.y, spherePos.z); //rotateX(mouseY * 0.05); //rotateY(mouseX * 0.05); fill(sphereColor[0], sphereColor[1], sphereColor[2]); //sphereDetail(mouseX / 4); sphere(sphereRadius); popMatrix(); //cloth if (ballMode){ fill(100); for (int j = 0; j < numNodes-1; j++){ for (int i = 0; i < numRopes-1; i++){ pushMatrix(); stroke(127); strokeWeight(sw); line(ballX[j][i],ballY[j][i],ballZ[j][i],ballX[j+1][i],ballY[j+1][i],ballZ[j+1][i]); line(ballX[j][i],ballY[j][i],ballZ[j][i],ballX[j][i+1],ballY[j][i+1],ballZ[j][i+1]); noStroke(); translate(ballX[j+1][i],ballY[j+1][i],ballZ[j+1][i]); sphere(radius); popMatrix(); } } } else { noStroke(); for (int i = 0; i < numNodes - 1; i++){ beginShape(TRIANGLE_STRIP); // TRIANGLE_STRIP takes two rows of points and tiles them with triangles texture(clothtex); for (int j = 0; j < numRopes - 1; j++){ vertex(ballX[i][j], ballY[i][j], ballZ[i][j], j*clothtex.width/numRopes, i*clothtex.height/numNodes); vertex(ballX[i+1][j], ballY[i+1][j], ballZ[i+1][j], j*clothtex.width/numRopes, (i+1)*clothtex.height/numNodes); } endShape(); } }}
void update(float dt){ sphereColor[0] += mouseX * 2; sphereColor[1] = mouseY; sphereColor[2] = 255 - mouseX; spherePos.x = mouseX - .9*(width/2 - mouseX); spherePos.y = mouseY - .9*(height/2 - mouseY); spherePos.z = -450; //camera.Update(dt); // Compute (damped) Hooke's law for each spring // vertical forces for (int j = 0; j < numNodes - 1; j++){ for (int i = 0; i < numRopes; i++){ float stringdx = (ballX[j+1][i] - ballX[j][i]); float stringdy = (ballY[j+1][i] - ballY[j][i]); float stringdz = (ballZ[j+1][i] - ballZ[j][i]); float stringLen = sqrt(stringdx*stringdx + stringdy*stringdy + stringdz*stringdz); float stringF = -k*(stringLen - restLen); // vertical force float stringdirX = stringdx/stringLen; float stringdirY = stringdy/stringLen; float stringdirZ = stringdz/stringLen; float projVbot = velX[j][i]*stringdirX + velY[j][i]*stringdirY + velZ[j][i]*stringdirZ; float projVtop = velX[j+1][i]*stringdirX + velY[j+1][i]*stringdirY + velZ[j+1][i]*stringdirZ; float dampF = -kv*(projVtop - projVbot);
float forceX = (stringF+dampF)*stringdirX; float forceY = (stringF+dampF)*stringdirY; float forceZ = (stringF+dampF)*stringdirZ;
accX[j][i] -= .5*forceX/mass; accY[j][i] -= .5*forceY/mass; accZ[j][i] -= .5*forceZ/mass; accX[j+1][i] = .5*forceX/mass; accY[j+1][i] = .5*forceY/mass; accZ[j+1][i] = .5*forceZ/mass; } } // horizontal forces for (int j = 0; j < numNodes; j++){ for (int i = 0; i < numRopes - 1; i++){ float stringdx = (ballX[j][i+1] - ballX[j][i]); float stringdy = (ballY[j][i+1] - ballY[j][i]); float stringdz = (ballZ[j][i+1] - ballZ[j][i]); float stringLen = sqrt(stringdx*stringdx + stringdy*stringdy + stringdz*stringdz); float stringF = -k*(stringLen - restLen); float stringdirX = stringdx/stringLen; float stringdirY = stringdy/stringLen; float stringdirZ = stringdz/stringLen; float projVbot = velX[j][i]*stringdirX + velY[j][i]*stringdirY + velZ[j][i]*stringdirZ; float projVtop = velX[j][i+1]*stringdirX + velY[j][i+1]*stringdirY + velZ[j+1][i]*stringdirZ; float dampF = -kv*(projVtop - projVbot); float forceX = (stringF+dampF)*stringdirX; float forceY = (stringF+dampF)*stringdirY; float forceZ = (stringF+dampF)*stringdirZ; accX[j][i] -= .5*forceX/mass; accY[j][i] -= .5*forceY/mass; accZ[j][i] -= .5*forceZ/mass; accX[j][i+1] += .5*forceX/mass; accY[j][i+1] += .5*forceY/mass; accZ[j+1][i] += .5*forceZ/mass; } }
//Eulerian integration for (int j = 1; j < numNodes; j++){ for (int i = 0; i < numRopes; i++){ velX[j][i] += accX[j][i]*dt; ballX[j][i] += velX[j][i]*dt; velY[j][i] += (accY[j][i]+gravity)*dt; ballY[j][i] += velY[j][i]*dt;// } }
//Collision detection and response // obstacle for (int j = 0; j <numNodes; j++){ for (int i = 0; i < numRopes; i++){ Vec3 nodePos = new Vec3(ballX[j][i], ballY[j][i], ballZ[j][i]); // get PVector version of node's position float distance = spherePos.distanceTo(nodePos); // calculate distance if (distance < sphereRadius + .09){ // if the distance between the sphere's edge and the node is less than .09: Vec3 sphereNormal = spherePos.minus(nodePos); sphereNormal.normalize(); // calculate sphere normal Vec3 nodeVel = new Vec3(velX[j][i], velY[j][i], velZ[j][i]); // get PVector version of node's velocity Vec3 bounce = sphereNormal.times(dot(nodeVel, sphereNormal)); // calculate bounce vector velX[j][i] = -1.5*bounce.x; // update velocity velY[j][i] = -1.5*bounce.y; velZ[j][i] = -1.5*bounce.z; Vec3 spheretoPoint = nodePos.minus(spherePos); spheretoPoint.normalize(); spheretoPoint = spheretoPoint.times(sphereRadius); Vec3 worldPeace = new Vec3((spherePos.x + spheretoPoint.x), (spherePos.y+spheretoPoint.y), (spherePos.z+spheretoPoint.z)); ballX[j][i] = worldPeace.x; // update position of node ballY[j][i] = worldPeace.y; ballZ[j][i] = worldPeace.z; } } } // floor for (int j = 0; j <numNodes; j++){ for (int i = 0; i < numRopes; i++){ if (ballY[j][i]+radius > floor){ velY[j][i] *= -.9; ballY[j][i] = floor - radius; } } }}
void mousePressed(){ if (mouseButton == LEFT) ballMode = !ballMode;}