/*
* Copyright (c) 2012 Gwyllim Jahn
* http://scripts.crida.net/gh
* Developed By Gwyllim Jahn, Cam Newnham and Mark-Henry Delacrauz
* during the 2012 RMIT nCertainties studio.
*
* This code makes extensive use of several freely distributed
* processing libraries – thankyou especially to toxi.
* This code is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* http://creativecommons.org/licenses/LGPL/2.1/
*
* This code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
*/
//—————————————————–OrbitingParticlesStrands ——————————————————–//
import processing.video.*;
import toxi.physics2d.constraints.*;
import toxi.physics.*;
import toxi.physics.constraints.*;
import toxi.physics2d.behaviors.*;
import toxi.physics.behaviors.*;
import toxi.physics2d.*;
import controlP5.*;
import processing.dxf.*;
import toxi.geom.*;
import org.apache.commons.collections.keyvalue.*;
import org.apache.commons.collections.set.*;
import org.apache.commons.collections.iterators.*;
import org.apache.commons.collections.map.*;
import org.apache.commons.collections.bag.*;
import org.apache.commons.collections.list.*;
import org.apache.commons.collections.bidimap.*;
import org.apache.commons.collections.collection.*;
import org.apache.commons.collections.*;
import org.apache.commons.collections.functors.*;
import peasy.org.apache.commons.math.*;
import peasy.*;
import peasy.org.apache.commons.math.geometry.*;
import processing.opengl.*;
// ———————————————————————————-GLOBALS
//accessable by anything in your script.
VerletPhysics physics = new VerletPhysics();
PeasyCam cam;
ControlP5 controlP5;
ControlWindow controlWindow;
int numTrees = 6;
//number of agents per tree
int numAgents = 75;
boolean record = false;
boolean agentsOn = true;
ArrayList forest = new ArrayList();
ArrayListfibrePop = new ArrayList();
MultiValueMap m = new MultiValueMap();
MultiValueMap particleMap = new MultiValueMap();
int gridRes = 15;
//add the pause variable
Boolean paused = false;
Boolean bundling = false;
//agent variables
int trailLength = 50;
float agentSpeed = 3.5;
int dropRate = 1;
//if a tree is less than this number away from the agent, then it will affect the path of the agent
float attractScale = 25;
//if the closest tree is less than this number away, the agent will try to move to the fringe of
//the trees crown
float fringeScale = 80;
//three variables for how much each type of movement affects the agent
float spin = 0.5;
float push = 0.12;
float pull = 0.18;
//maximum movement for a tree point
float maxTreePull = 3;
float treeSpring = 0.1;
//bundling variables
float cutoff = 20;
float stiffness = 0.1;
float numToMove = .8;
float nodeStiffness = 0.002;
//video
boolean filming = false;
MovieMaker mm;
// ———————————————————————————-SETUP
void setup(){
size(1600,900,OPENGL);
cam =new PeasyCam(this,800);
mm = new MovieMaker(this, width, height, “drawing.mov”, 30, MovieMaker.ANIMATION, MovieMaker.HIGH);
cam.rotateZ(1.57079633);
//setup the forest landscape
//setupRandomLandscape();
importLandscape();
setupP5();
background(255);
}
void draw(){
background(255);
if(record){
beginRaw(DXF,”test.dxf”);
}
if(!bundling){
//update the physics engine
physics.update();
for (TreeAttractor t :forest){
t.run();
}
}else{
for (Fibre f: fibrePop){
f.update();
}
}
if(filming)mm.addFrame();
if(record){
endRaw();
record = false;
}
}
void keyPressed(){
//save a dxf
if (key == ‘e’) {
record = !record;
}
//add the pause function
if (key == ‘p’) {
paused = !paused;
}
if (key == ‘a’) {
agentsOn = !agentsOn;
}
//save a screengrab
if (key == ‘s’) {
// saveFrame(“pic_####.png”);
if(!bundling){
savePts();
}else{
saveBundles();
}
}
if(key == ‘f’){
importFibres();
bundling =true;
}
if(key == ‘ ‘){
filming =!filming;
if(!filming)mm.finish();
}
//reset the sketch
if (key == ‘r’) {
background(255);
importLandscape();
physics = new VerletPhysics();
fibrePop=new ArrayList();
bundling = false;
//setupRandomLandscape();
}
}
//—————————————————–Agents——————————————————–//
//simple agent class to interact with the data
//field and other agents
class Agent {
Vec3D loc,vel;
TreeAttractor p;
float distToTree;
Strand trail;
Agent(Vec3D _loc, Vec3D _vel, TreeAttractor _p){
loc = _loc;
vel = _vel;
p = _p;
//add the agent to the map
m.put(p,this);
distToTree=0;
trail = new Strand(loc);
}
void run(){
//moves the particles based on proximity to trees
if(!paused){
getNearTrees();
constrainToCrown();
//limit height of the agent to roughly the crown height
limitHeight();
updatePos();
}
trail.run();
}
//————————————————————————————-
//Behaviours
//————————————————————————————-
void getNearTrees(){
//setup variables to set new parent
TreeAttractor n =p;
float minD = 10000;
for (TreeAttractor tree:forest){
//get the distance to the tree
float d = loc.distanceTo(tree.loc);
if(dminD=d;
n = tree;
}
if(d
//make the agent interact with the tree
//and colide with debris
vel.addSelf(getField(tree));
}
}
//re set the parent to the nearest tree
m.remove(p,this);
p = n;
m.put(p,this);
// set the node stiffness based on its distance to the tree point
distToTree = p.loc.distanceTo(loc)-p.crownRadius;
if(distToTree distToTree = map(distToTree,0,40,0.01,0.5);
// distToTree = m.getCollection(p).size();
//distToTree = map(distToTree,0,50,0.05,0.7);
//make sure we keep orbiting current tree
vel.addSelf(getField(p));
}
Vec3D getField(TreeAttractor t){
Vec3D toAttr = loc.sub(t.loc);
Vec3D perp = toAttr.cross(new Vec3D(0,0,1));
//create another vector to gently rotate the agents
Vec3D tangent = toAttr.cross(vel);
//random mod for tangent
float mod = random(-0.2,0.2);
tangent.scaleSelf(mod);
perp.addSelf(tangent);
float d = perp.magnitude();
float s = perp.magnitude();
perp.scaleSelf(spin/s);
//perp.normalize();
toAttr.scaleSelf(push/d);
return(perp.addSelf(toAttr));
}
void constrainToCrown(){
//get vector from agent to tree location
Vec3D toCrown = loc.sub(p.loc);
toCrown.invert();
//get distance to edge of crown
float diff = toCrown.magnitude()-p.crownRadius;
//constrain
diff = abs(constrain(diff,-fringeScale,fringeScale));
diff = (fringeScale – diff);
if(diff== 0) diff = 0.1;
//inverse falloff
toCrown.scaleSelf(pull/diff);
vel.addSelf(toCrown);
}
//————————————————————————————-
//Function for limiting the height of the agent. If the agent moves below the height
//of the tree crown, interpolate its vector upwards.
//————————————————————————————-
void limitHeight(){
//if agents z is less than closest trees z
if(loc.z vel.interpolateToSelf(new Vec3D(0,0,1),0.5);
}
}
//————————————————————————————-
//Functions for managing trails and rendering the agent.
//————————————————————————————-
void updatePos(){
//move the agent
vel.normalizeTo(agentSpeed);
loc.addSelf(vel);
trail.update(loc);
}
}
//—————————————————–Controllers——————————————————–//
void setupP5 (){
controlP5 = new ControlP5(this);
controlP5.setAutoDraw(false);
controlWindow = controlP5.addControlWindow(“controlP5window”,100,100,400,320);
controlWindow.hideCoordinates();
Controller mySlider1 = controlP5.addSlider(“spin”,0,1.5).linebreak();
mySlider1.setWindow(controlWindow);
Controller mySlider2 =controlP5.addSlider(“push”,0,0.201).linebreak();
mySlider2.setWindow(controlWindow);
Controller mySlider3 = controlP5.addSlider(“pull”,0,0.201).linebreak();
mySlider3.setWindow(controlWindow);
Controller mySlider4 =controlP5.addSlider(“attractScale”,0,200).linebreak();
mySlider4.setWindow(controlWindow);
Controller mySlider5 =controlP5.addSlider(“fringeScale”,0,200).linebreak();
mySlider5.setWindow(controlWindow);
Controller mySlider6 =controlP5.addSlider(“agentSpeed”,0.1,3.5).linebreak();
mySlider6.setWindow(controlWindow);
Controller mySlider7 =controlP5.addSlider(“maxTreePull”,0,15.00).linebreak();
mySlider7.setWindow(controlWindow);
Controller mySlider9 =controlP5.addSlider(“treeSpring”,0,0.150).linebreak();
mySlider9.setWindow(controlWindow);
Controller mySlider8 =controlP5.addSlider(“latcherGrowthRate”,0,10.5).linebreak();
mySlider8.setWindow(controlWindow);
Controller mySlider10 =controlP5.addSlider(“latcherRotate”,0,0.500).linebreak();
mySlider10.setWindow(controlWindow);
Textlabel field = controlP5.addTextlabel(“Instructions”,”press r to reset simulation with new parameters”,20,300);
field.setWindow(controlWindow);
controlWindow.setTitle(“Spring GUI”);
}
//—————————————————–Debris——————————————————–//
class Debris {
ArrayListchunks;
Vec3D loc;
Boolean stillAdding = true;
Debris (Vec3D _a){
loc = _a;
chunks = new ArrayList();
chunks.add(_a);
}
void render(){
strokeWeight(5);
point (loc.x,loc.y,loc.z);
}
}
//—————————————————–Fibre——————————————————–//
class Fibre{
ArrayListnodes;
//————————————–constructor
Fibre(){
nodes = new ArrayList();
}
//————————————–setters and getters
void addNode(Node n){
nodes.add(n);
}
void update(){
noFill();
beginShape();
for (Node n:nodes){
n.run();
vertex(n.loc.x,n.loc.y,n.loc.z);
}
endShape();
}
}
//—————————————————–Initialisation——————————————————–//
void setupRandomLandscape(){
m = new MultiValueMap();
forest = new ArrayList();
for (int i=0;i
//create a new tree like so:
//TreeAttractor (_loc (x,y,z), _strength, _crownRadius){
TreeAttractor tree = new TreeAttractor(new Vec3D(random(0,300),random(0,300),random(0,50)), random(1,2.5), random(50,70));
forest.add(tree);
}
}
//———————————import functions
void importLandscape() {
forest = new ArrayList();
//load text
String[] txtLines = loadStrings(“trees.txt”);
//splits strand points
String[] arrCoords = split(txtLines[0], ‘_’);
for (int j = 0; j < arrCoords.length; ++j) {
//separates coords
String[] arrToks = split(arrCoords[j], ‘,’);
float xx = Float.valueOf(arrToks[0]);
float yy = Float.valueOf(arrToks[1]);
float zz = Float.valueOf(arrToks[2]);
//create node
Vec3D tmpPt = new Vec3D(xx, yy, zz);
TreeAttractor tree = new TreeAttractor(tmpPt, 1, random(100,120));
forest.add(tree);
}
}
//—————————————————–Node——————————————————–//
class Node {
//location
Vec3D loc;
//parent fibre
Fibre p;
//siblings for spring behaviour
ArrayList siblings;
//neighbours to loop through
ArrayList neighbours;
//defines whether node is part of a bundle or not
Boolean active;
float nodeStiffness;
//————————————–constructor
Node(Vec3D LOC, Fibre P, float STIFF) {
loc = LOC;
p=P;
active = true;
neighbours = new ArrayList();
siblings = new ArrayList();
nodeStiffness = STIFF;
}
//————————————–setters and getters
void activate(Boolean a) {
active = a;
}
void addSibling (Node s) {
siblings.add(s);
}
void setNeighbours(ArrayList N) {
neighbours = N;
}
//————————————–main functions
void run() {
if(active){
attract();
}
//render();
}
void attract() {
ArrayList closest = new ArrayList();
float minD = 999999;
for (Node n: neighbours){
Vec3D diff = loc.sub(n.loc);
float d = diff.magnitude();
if(d>1){
if (d closest.add(diff);
if(closest.size()>numToMove) closest.remove(0);
minD =d;
}
}
}
for (Vec3D v:closest){
float d = v.magnitude();
v.scaleSelf(nodeStiffness/d);
loc.subSelf(v);
spring();
}
}
void spring() {
Vec3D avg = new Vec3D(0,0,0);
for (Node n: siblings){
Vec3D diff = loc.sub(n.loc);
avg.addSelf(diff);
}
avg.scaleSelf(stiffness);
loc.subSelf(avg);
}
void render() {
point(loc.x, loc.y, loc.z);
}
}
//—————————————————–Strand——————————————————–//
class Strand{
ArrayListtrail;
ArrayList pts;
float dropNum;
Strand(Vec3D _loc){
trail = new ArrayList();
pts = new ArrayList();
MapParticle p = new MapParticle(_loc,getCoord(_loc));
p.lock();
pts.add(p);
dropNum = 0;
}
void update(Vec3D _loc){
//update the trail geom every 5 moves
if(dropNum%dropRate==0){
if(trail.size()
MapParticle p = new MapParticle(_loc,getCoord(_loc));
p.lock();
String coord = getCoord(p);
particleMap.put(coord,p);
MapParticle lastP = pts.get(pts.size()-1);
lastP.unlock();
VerletSpring s = new VerletSpring(pts.get(pts.size()-1),p,(p.distanceTo(lastP)),0.5);
physics.addParticle(p);
physics.addSpring(s);
trail.add(s);
pts.add(p);
}else{
physics.removeSpringElements(trail.get(0));
trail.remove(0);
pts.remove(0);
}
dropNum=0;
}
dropNum++;
}
String getCoord(Vec3D p){
String s = “”+floor(p.x/gridRes)+floor(p.y/gridRes)+floor(p.z/gridRes);
return s;
}
void run(){
//render agent trail as lines
strokeWeight(1);
float c =1;
if(agentsOn){
for (VerletSpring l : trail){
float gs = ((trail.size()-c)/trail.size())*255;
stroke(gs);
MapParticle a = (MapParticle) l.a;
a.run();
line (l.a.x,l.a.y,l.a.z,l.b.x,l.b.y,l.b.z);
c+=1;
}
}
}
}
//—————————————————–TrailPt——————————————————–//
class TrailPt {
Vec3D loc;
float d2t;
TrailPt(Vec3D LOC, float D2T){
loc = LOC;
d2t = D2T;
}
}
//—————————————————–TreeAttractor——————————————————–//
class TreeAttractor {
Vec3D loc,initLoc;
float strength;
float crownRadius;
ArrayList bots;
TreeAttractor (Vec3D _loc, float _strength, float _crownRadius){
//assign properties
loc = _loc;
initLoc = loc.copy();;
strength = _strength;
crownRadius = _crownRadius;
bots = new ArrayList();
//spawn robot agents
spawn();
}
void run(){
for (Agent a:bots){
a.run();
}
//move the tree towards all nearby agents
crownSagging();
render();
}
//————————————————————————————-
//Function for creating agents randomly on the sphere of the tree crown (perhaps should be
//a hemisphere)
//————————————————————————————-
void spawn(){
for (int i = 0; i Vec3D fringe = new Vec3D(crownRadius + random(-(crownRadius/3),(crownRadius/3)),0,0);
fringe.rotateZ(random(0,2*PI));
fringe.rotateX(random(0,2*PI));
fringe.rotateY(random(0,2*PI));
fringe.addSelf(loc);
Vec3D vel = new Vec3D(random(-1,1),random(-1,1),random(-1,1));
Agent a = new Agent(fringe,vel,this);
bots.add(a);
}
}
//————————————————————————————-
//Function for moving the centre point of the tree towards the agents that currently have
//this tree as their parent.
//————————————————————————————-
void crownSagging(){
//create zero length default vector
Vec3D moveVel = new Vec3D(0,0,0);
//loop through agents if there are any
if(m.getCollection(this)!=null){
for(Agent a: (ArrayList) m.getCollection(this)){
Vec3D influence = a.loc.sub(loc);
influence.invert();
float d = influence.magnitude();
influence.normalizeTo(maxTreePull/d);
moveVel.addSelf(influence);
}
}
//reduce influence of movevel the further the tree is from original point
Vec3D toOriginalPos = initLoc.sub(loc);
float stretch = toOriginalPos.magnitude();
toOriginalPos.normalizeTo(treeSpring*stretch);
moveVel.addSelf(toOriginalPos);
loc.addSelf(moveVel);
}
void render(){
strokeWeight(0);
point (loc.x,loc.y,loc.z);
}
}
//—————————————————–BundlingInit——————————————————–//
//———————————import functions
void importFibres() {
/*
Set set = m.entrySet();
Iterator it = set.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
for (Agent a :(ArrayList)entry.getValue()){
//creates a strand from the line in the txt file
Fibre fibre = new Fibre();
for (int j=0;j//create node
TrailPt tmpPt = a.trail.get(j);
Node n = new Node(tmpPt.loc.copy(), fibre, tmpPt.d2t);
//lock the node if its the start or end of the trail,
//or if its the 15th node, or if its attr value is less than 0.1
if (j==0 || j==a.trail.size()-1 || j%15 ==0 ||tmpPt.d2t n.activate(false);
}
fibre.addNode(n);
}
//add strand to draw loop
fibrePop.add(fibre);
}
}
//setup the graph of nodes
setupSiblings();
setupGraph();
*/
}
//———————————setup initial graph
void setupSiblings(){
//loop the fibres
for (Fibre f: fibrePop) {
//loop the nodes
for (int i = 1; iNode currentNode = f.nodes.get(i);
Node lastNode = f.nodes.get(i-1);
Node nextNode = f.nodes.get(i+1);
currentNode.addSibling(lastNode);
currentNode.addSibling(nextNode);
}
}
}
void setupGraph() {
//loop the fibres
int count=0;
for (Fibre f: fibrePop) {
//loop the nodes
for (Node n:f.nodes){
ArrayList neighbours = new ArrayList();
//find neighbours
for (Fibre otherF:fibrePop){
//dont use same fibre
if(otherF!= f){
for(Node otherN:otherF.nodes){
float d = n.loc.distanceTo(otherN.loc);
if(d neighbours.add(otherN);
}
}
}
}
n.setNeighbours(neighbours);
}
println(“Percentage Loaded: “+(float(count)/fibrePop.size())*100);
count++;
}
}
//—————————————————–mapParticle——————————————————–//
class MapParticle extends VerletParticle {
String mapCoord;
MapParticle(Vec3D _loc, String _s) {
super (_loc);
mapCoord = _s;
}
void run() {
//get the grid pos and update if necessary
String newMapCoord = getCoord();
if (newMapCoord != mapCoord) {
particleMap.remove(mapCoord, this);
particleMap.put(newMapCoord, this);
mapCoord = newMapCoord;
}
//attract to neighbouring particles
if (particleMap.getCollection(mapCoord)!=null) {
for (MapParticle p: (ArrayList)particleMap.getCollection(mapCoord)) {
attract(p);
}
}
}
String getCoord(){
String s = “”+Math.floor(x/gridRes)+Math.floor(y/gridRes)+Math.floor(z/gridRes);
return s;
}
void attract(MapParticle P) {
Vec3D diff = sub(P);
diff.invert();
float d = diff.magnitude();
if (d>1) {
diff.scaleSelf(nodeStiffness/d);
addVelocity(diff);
}
}
}
//—————————————————–Saving——————————————————–//
void savePts(){
/*
String[] lines = new String[m.totalSize()];
int c=0;
Set set = m.entrySet();
Iterator it = set.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
for (Agent a :(ArrayList)entry.getValue()){
lines[c] = “”;
for(TrailPt t:a.trail){
lines[c] =lines[c]+ (t.loc.x +”,” + t.loc.y + “,” + t.loc.z + “/”);
}
c++;
}
}
saveStrings(“lines.txt”, lines);
*/
}
void saveBundles(){
String[] lines = new String[m.totalSize()];
int c=0;
for (Fibre f :fibrePop){
lines[c] = “”;
for(Node n:f.nodes){
lines[c] =lines[c]+ (n.loc.x +”,” + n.loc.y + “,” + n.loc.z + “/”);
}
c++;
}
saveStrings(“lines.txt”, lines);
}