Hi phi.lho,
thanks for the response! this is a great starting point which I'll look to develop.... I've posted below the sketch I'm working on which, if you're interested will give you a better understanding of my project. The original sketch I posted (the 2D grid) was just to get some quick feedback on how I might go about achieving the desired variation in the x direction, which you kindly provided. I'd really appreciate you taking a look at what I've done so far, and if possible give me a few pointers.
My goal is to create a script which creates variation in both the x and z axis across 2 opposite surfaces (or as I have called them in my scripts....meshes). As you can see in the script I have created an undulating surface by using attractors that affect the location of the particles. This seems to work well and provides the variation in the z direction but is probably an unusual way to achieve the effect....What do you think? I am now going to try and include variation in the x direction.
I am then going to try and link the 2 surfaces with branch-type structures which are formed when the two surfaces come within a desired range of each other (similar to electrical contact between the two surfaces, metaphorically speaking!)....although i am yet to investigate this. Apologies if my explanation isn't very clear!
Any ideas would be much appreciated!
Thanks, Farley
// IMPORT EXTERNAL LIBRARIES
import processing.pdf.*;
import processing.opengl.*; // openGL library
import peasy.*; // the camera library
import controlP5.*; // the interface library
/*------------------------------------------------------------------
*** GLOBAL VARIABLES ***
------------------------------------------------------------------*/
// camera
PeasyCam theCamera;
boolean record = false;
boolean export = false;
// inteface
ControlP5 theGUI;
ControlWindow theGUIWindow;
// particle variables
int numParticlesX = 28;
int numParticlesY = 70;
// x and y increments
int xInc = 12;
int yInc = 12;
// environment
int bBoxX = numParticlesX * xInc;
int bBoxY = numParticlesY * yInc;
int bBoxZ = 100;
// spring variables
//PVector gravity = new PVector(0, 0, 0);
float SPRING_RESTLENGTH = 8.0; // the length a spring is at rest
float SPRING_DAMPING = 0.3; // equiv hydraulic damping of a piston
// ArrayLists of attractors
ArrayList attList1 = new ArrayList();
ArrayList attList2 = new ArrayList();
ArrayList attList3 = new ArrayList();
int numAttractors = 20;
int numMidAttractors = 2;
float meshHeight = bBoxZ + bBoxX;
float midAttHeight = bBoxZ + ((meshHeight - bBoxZ)/2);
float attRange = midAttHeight - bBoxZ;
// declare meshes
Mesh bottomMesh;
Mesh topMesh;
/*------------------------------------------------------------------
*** GLOBAL SETUP ***
------------------------------------------------------------------*/
void setup() {
//----------general
size(900, 600, OPENGL);
smooth();
//----------make the camera
theCamera = new PeasyCam(this, bBoxX*1.65);
theCamera.lookAt(bBoxX/2, bBoxY/2, bBoxZ/2);
//----------setup interface
theGUI = new ControlP5(this);
theGUIWindow = theGUI.addControlWindow("GUI_WINDOW", 100, 100, 380, 50);
theGUIWindow.hideCoordinates();
Controller slider_springLength = theGUI.addSlider( "SPRING_RESTLENGTH", 1.0, 20.0, SPRING_RESTLENGTH, 15, 0, 250, 15 );
slider_springLength.setWindow(theGUIWindow);
Controller slider_springDamping = theGUI.addSlider( "SPRING_DAMPING", 0.1, 2.0, SPRING_DAMPING, 15, 35, 250, 15 );
slider_springDamping.setWindow(theGUIWindow);
//----------make mesh
initMesh();
}
/*------------------------------------------------------------------
*** GLOBAL CONTINUOUS DRAW ***
------------------------------------------------------------------*/
void draw() {
//----------general
background(250);
if (record) {
beginRaw(PDF, "3D####.pdf");
}
//----------draw meshes
topMesh.createMesh();
topMesh.createSprings();
topMesh.createFaces();
bottomMesh.createMesh();
bottomMesh.createSprings();
bottomMesh.createFaces();
//----------draw attractors
for (int i = 0; i < attList1.size(); ++i) {
Attractor myAtt = (Attractor) attList1.get(i);
myAtt.run();
myAtt.display();
}
for (int i = 0; i < attList2.size(); ++i) {
Attractor myAtt = (Attractor) attList2.get(i);
myAtt.run();
myAtt.display();
}
for (int i = 0; i < attList3.size(); ++i) {
Attractor myAtt = (Attractor) attList3.get(i);
myAtt.run();
myAtt.display();
}
if (export) {
topMesh.exportToTxt(1);
bottomMesh.exportToTxt(2);
}
if (record) {
endRaw();
record = false;
}
}
/*-------------------------------------------------------------
*** MAKE PARTICLES, SPRINGS AND ATTRACTORS ***
--------------------------------------------------------------*/
void initMesh() {
bottomMesh = new Mesh(bBoxZ, attList1, attList3);
topMesh = new Mesh(meshHeight, attList2, attList3);
}
void keyPressed() {
if (key == 'p' || key == 'P') {
record = true;
export = true;
}
}
class Mesh {
/*------------------------------------------------------------------
*** CLASS VARIABLES ***
------------------------------------------------------------------*/
float tmpMeshHeight;
Particle[][] particleList;
ArrayList springList;
Spring s0, s1;
ArrayList faceList;
Face f0;
ArrayList tmpAttList1;
ArrayList tmpAttList2;
Attractor tmpAtt;
PrintWriter output;
/*------------------------------------------------------------------
*** CLASS CONSTRUCTOR ***
------------------------------------------------------------------*/
Mesh(float meshHeight_, ArrayList attList_, ArrayList attList2_) {
tmpMeshHeight = meshHeight_;
particleList = new Particle[numParticlesX][numParticlesY];
springList = new ArrayList();
faceList = new ArrayList();
tmpAttList1 = attList_;
tmpAttList2 = attList2_;
//-----initialise particles
for (int i=0; i<numParticlesX; ++i) {
for (int j=0; j<numParticlesY; ++j) {
// make a vector
PVector tmpPos = new PVector(i*xInc, j*yInc, tmpMeshHeight);
// make a particle
Particle newP = new Particle(tmpPos, tmpAttList1, tmpAttList2);
// fix perimeter particles
if ( (i < numParticlesX && j==0) || (i < numParticlesX && j==numParticlesY-1) ) {
newP.fixMe();
}
// store the particle in our list
particleList[i][j] = newP;
}
}
//-----initialise springs
for (int i=0; i<numParticlesX; ++i) {
for (int j=0; j<numParticlesY; ++j) {
if ( i>0 ) {
s0 = new Spring( particleList[i][j], particleList[i-1][j] );
//springList.add(s0);
particleList[i][j].addSpring(s0);
particleList[i-1][j].addSpring(s0);
}
if ( j>0 ) {
s1 = new Spring( particleList[i][j], particleList[i][j-1] );
springList.add(s1);
particleList[i][j].addSpring(s1);
particleList[i][j-1].addSpring(s1);
}
}
}
//-----initialise faces
for (int i=0; i<numParticlesX; ++i) {
for (int j=0; j<numParticlesY; ++j) {
if (i>0 && j>0) {
f0 = new Face( particleList[i][j], particleList[i][j-1], particleList[i-1][j-1], particleList[i-1][j], tmpMeshHeight );
faceList.add(f0);
}
}
}
// print how many springs we made
println("Number of springs " + springList.size() );
//-----initialise surface attractors
for (int i = 0; i < numAttractors; ++i) {
//if (i < numAttractors) {
PVector aPos = new PVector(random(0, bBoxX), random(150, bBoxY-150), tmpMeshHeight);
PVector aVel = new PVector(random(0.5, 2.5), random(0.5, 2.5), 0);
tmpAtt = new Attractor(aPos, aVel, 20);
tmpAttList1.add(tmpAtt);
}
//-----initialise middle attractors
for (int i = 0; i < numMidAttractors; ++i) {
//if (i < numAttractors) {
PVector aPos = new PVector(random(0, bBoxX), random(150, bBoxY-150), midAttHeight+30);
PVector aVel = new PVector(random(1, 2), random(1, 2), 0);
tmpAtt = new Attractor(aPos, aVel, 150);
tmpAttList2.add(tmpAtt);
}
}
/*------------------------------------------------------------------
*** CLASS FUNCTIONS ***
------------------------------------------------------------------*/
void createMesh() {
//----------update particle physics
for (int i=0; i<numParticlesX; ++i) {
for (int j=0; j<numParticlesY; ++j) {
particleList[i][j].solveForce();
}
}
//----------update particle positions
for (int i=0; i<numParticlesX; ++i) {
for (int j=0; j<numParticlesY; ++j) {
particleList[i][j].update();
}
}
}
void createSprings() {
for (int i=0; i<springList.size(); i++) {
// get a spring from the arraylist
Spring currS = (Spring) springList.get(i);
currS.showMe();
}
}
void createFaces() {
for (int i=0; i<faceList.size(); i++) {
// get a spring from the arraylist
Face currF = (Face) faceList.get(i);
currF.showMe();
}
}
void exportToTxt(int num_) {
int num = num_;
output = createWriter("data/points" + num + ".txt");
for (int i = 0; i < numParticlesX; i++) {
for (int j = 0; j < numParticlesY; j++) {
output.println (particleList[i][j].pos.x + "," + particleList[i][j].pos.y + "," + particleList[i][j].pos.z);
}
}
output.flush();
output.close();
println("points have been exported");
exit();
}
}
class Particle {
/*------------------------------------------------------------------
*** CLASS VARIABLES ***
------------------------------------------------------------------*/
int pID;
PVector pos;
PVector vel;
PVector acc;
float pSize;
boolean pFixed;
ArrayList conSprings = new ArrayList();
ArrayList tmpAttList1;
ArrayList tmpAttList2;
/*------------------------------------------------------------------
*** CLASS CONSTRUCTOR ***
------------------------------------------------------------------*/
Particle(PVector STARTPOS, ArrayList attList1_, ArrayList attList2_) {
// assign all the class variables
pos = STARTPOS;
vel = new PVector();
acc = new PVector();
pSize = 10.0;
pID = 0;
pFixed = false;
tmpAttList1 = attList1_;
tmpAttList2 = attList2_;
}
/*------------------------------------------------------------------
*** CLASS FUNCTIONS ***
------------------------------------------------------------------*/
//----------MOVE AND DRAW TO SCREEN
void update() {
if (pFixed==false) {
updatePos();
attraction();
attractionB();
}
//showMe();
}
//----------SOLVE THE SPRINGS
void solveForce() {
if (pFixed==false) {
Particle otherP;
PVector sumForces = new PVector();
//sumForces.add(gravity);
sumForces.add(acc);
// loop thru our connections
for (int i = 0; i<conSprings.size(); ++i) {
// get a spring
Spring s = (Spring) conSprings.get(i);
// get other end of spring
if ( s.p00 == this ) {
otherP = s.p01;
}
else {
otherP = s.p00;
}
// calculate the push/pull of spring
PVector vecBet = PVector.sub(otherP.pos, pos);
float currLen = vecBet.mag();
float desiredLen = currLen-SPRING_RESTLENGTH;
vecBet.normalize();
vecBet.mult(desiredLen/2);
// add this force to our sum of forces
sumForces.add(vecBet);
}
// apply a damping to the force
sumForces.mult(SPRING_DAMPING);
// add the resultant force to our vel
vel.add(sumForces);
}
}
void updatePos() {
vel.limit(2);
pos.add(0, 0, vel.z);
acc = new PVector(0, 0, 0);
/*pos.add(vel); // move the particle
vel.mult(0); */ // reset the vel for next time
}
//----------A typical class function!!
void showMe() {
// set appearance
noFill();
if (pFixed==true) {
// draw as 'FIXED' geometry
strokeWeight(1);
stroke(0, 0, 250, 100);
line(pos.x-pSize, pos.y, pos.z, pos.x+pSize, pos.y, pos.z);
line(pos.x, pos.y-pSize, pos.z, pos.x, pos.y+pSize, pos.z);
line(pos.x, pos.y, pos.z-pSize, pos.x, pos.y, pos.z+pSize);
}
else {
// draw as 'FREE' geometry
strokeWeight(0.5);
stroke(100);
line(pos.x-pSize/2, pos.y, pos.z, pos.x+pSize/2, pos.y, pos.z);
line(pos.x, pos.y-pSize/2, pos.z, pos.x, pos.y+pSize/2, pos.z);
line(pos.x, pos.y, pos.z-pSize/2, pos.x, pos.y, pos.z+pSize/2);
}
}
//----------Make the particle fixed / free
void fixMe() {
pFixed=true;
}
void freeMe() {
pFixed=false;
}
//----------Add a spring to my list of springs
void addSpring(Spring S) {
// store this spring
conSprings.add(S);
}
void attraction() {
PVector steer = new PVector();
int count = 0;
for (int i = 0; i < tmpAttList1.size(); i++) {
// get a PVector from the list
Attractor tmpAtt = (Attractor) tmpAttList1.get(i);
float distance = PVector.dist(tmpAtt.aPos, pos);
if (distance > 0 && distance < attRange+21) {
PVector vecBetween = PVector.sub(tmpAtt.aPos, pos);
// get the length of the vector
vecBetween.normalize();
steer.add(vecBetween);
count++;
}
}
if (count > 0) {
steer.mult(1.0/count);
}
steer.mult(2);
acc.add(steer);
}
void attractionB() {
PVector steer = new PVector();
int count = 0;
for (int i = 0; i < tmpAttList2.size(); i++) {
// get a PVector from the list
Attractor tmpAtt = (Attractor) tmpAttList2.get(i);
float distance = PVector.dist(tmpAtt.aPos, pos);
if (distance > 0 && distance < attRange+31) {
PVector vecBetween = PVector.sub(tmpAtt.aPos, pos);
// get the length of the vector
vecBetween.normalize();
steer.add(vecBetween);
count++;
}
}
if (count > 0) {
steer.mult(1.0/count);
}
steer.mult(2);
acc.add(steer);
}
}
class Attractor {
/*------------------------------------------------------------------
*** CLASS VARIABLES ***
------------------------------------------------------------------*/
PVector aPos;
PVector aVel;
float pSize;
int range;
/*------------------------------------------------------------------
*** CLASS CONSTRUCTOR ***
------------------------------------------------------------------*/
Attractor(PVector aPos_, PVector aVel_, int range_) {
// assign all the class variables
aPos = aPos_;
aVel = aVel_;
pSize = 10.0;
range = range_;
}
/*------------------------------------------------------------------
*** CLASS FUNCTIONS ***
------------------------------------------------------------------*/
void run() {
move();
bounce();
}
void move() {
aPos.add(aVel);
}
void bounce() {
if (aPos.x > bBoxX) {
aVel.x *= -1;
}
if (aPos.x < 0) {
aVel.x *= -1;
}
if (aPos.y > bBoxY-range) {
aVel.y *= -1;
}
if (aPos.y < range) {
aVel.y *= -1;
}
}
void display() {
strokeWeight(0.5);
stroke(180);
//point(aPos.x, aPos.y, aPos.z);
line(aPos.x-pSize, aPos.y, aPos.z, aPos.x+pSize, aPos.y, aPos.z);
line(aPos.x, aPos.y-pSize, aPos.z, aPos.x, aPos.y+pSize, aPos.z);
line(aPos.x, aPos.y, aPos.z-pSize, aPos.x, aPos.y, aPos.z+pSize);
}
}
class Spring {
/*------------------------------------------------------------------
*** CLASS VARIABLES ***
------------------------------------------------------------------*/
// declare all the class variables
Particle p00;
Particle p01;
/*------------------------------------------------------------------
*** CLASS CONSTRUCTOR ***
------------------------------------------------------------------*/
Spring (Particle INPUT_00, Particle INPUT_01) {
// assign all the class variables
p00 = INPUT_00;
p01 = INPUT_01;
}
/*------------------------------------------------------------------
*** CLASS FUNCTIONS ***
------------------------------------------------------------------*/
//----------Draw myself
void showMe() {
stroke(180);
strokeWeight(0.5);
noFill();
line(
p00.pos.x, p00.pos.y, p00.pos.z,
p01.pos.x, p01.pos.y, p01.pos.z
);
}
}
class Face {
/*------------------------------------------------------------------
*** CLASS VARIABLES ***
------------------------------------------------------------------*/
// declare all the class variables
Particle p00;
Particle p01;
Particle p02;
Particle p03;
float tmpMeshHeight;
float Dist1;
float Dist2;
float DistA;
float DistB;
float DistC;
float colValG;
float colValB;
float colValR;
/*------------------------------------------------------------------
*** CLASS CONSTRUCTOR ***
------------------------------------------------------------------*/
Face (Particle INPUT_00, Particle INPUT_01, Particle INPUT_02, Particle INPUT_03, float meshHeight_) {
// assign all the class variables
p00 = INPUT_00;
p01 = INPUT_01;
p02 = INPUT_02;
p03 = INPUT_03;
tmpMeshHeight = meshHeight_;
}
/*------------------------------------------------------------------
*** CLASS FUNCTIONS ***
------------------------------------------------------------------*/
//----------Draw myself
void showMe() {
Dist1 = dist(p00.pos.x, p00.pos.y, tmpMeshHeight, p00.pos.x, p00.pos.y, midAttHeight);
Dist2 = dist(p00.pos.x, p00.pos.y, p00.pos.z, p00.pos.x, p00.pos.y, tmpMeshHeight);
DistA = dist(p00.pos.x, p00.pos.y, p00.pos.z, p00.pos.x, p00.pos.y, tmpMeshHeight);
DistB = dist(p00.pos.x, p00.pos.y, (tmpMeshHeight+midAttHeight)/2, p00.pos.x, p00.pos.y, p00.pos.z);
DistC = dist(p00.pos.x, p00.pos.y, midAttHeight, p00.pos.x, p00.pos.y, p00.pos.z);
if (Dist2 < Dist1/4) {
// green fades in
colValG = map(DistA, 0, Dist1/4, 0, 255);
fill(0, colValG, 255);
}
else if (Dist2 < Dist1/2) {
// blue fades out
colValB = map(DistB, 0, Dist1/4, 0, 255);
fill(0, 255, colValB);
}
else if (Dist2 < Dist1/2 + Dist1/4) {
// red fades in
colValR = map(DistB, 0, Dist1/4, 0, 255);
fill(colValR, 255, 0);
}
else {
//green fades out
colValG = map(DistC, 0, Dist1/4, 0, 255);
fill(255, colValG, 0);
}
/*else if (Dist1 < (Dist3/2 + Dist3/4)) {
// red fades in
colValR = map(Dist1, (Dist3/2 + Dist3/4), Dist3/4, 0, 255);
fill(colValR, 255, 0);
}
else {
// green fades out
colValG = map(Dist2, 0, (Dist3/2 + Dist3/4), 0, 255);
fill(255, colValG, 0);
}*/
noStroke();
beginShape();
vertex(p00.pos.x, p00.pos.y, p00.pos.z);
vertex(p01.pos.x, p01.pos.y, p01.pos.z);
vertex(p02.pos.x, p02.pos.y, p02.pos.z);
vertex(p03.pos.x, p03.pos.y, p03.pos.z);
endShape();
}
}