changeset 0:c667dfe12d47

OK. Ther real deal.
author Robert Tubb <rt300@eecs.qmul.ac.uk>
date Mon, 19 Nov 2012 13:00:42 +0000
parents
children 1d1bf0aac99e
files 2dvector.h 2dvector.mm button.h button.mm dsptools.h dsptools.mm globalForces.h globalForces.mm globalUI.h globalUI.mm lump.h lump.mm main.mm mesh.h mesh.mm scanpath.h scanpath.mm spring.h spring.mm testApp.h testApp.mm uifunctor.h
diffstat 22 files changed, 5814 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/2dvector.h	Mon Nov 19 13:00:42 2012 +0000
@@ -0,0 +1,33 @@
+/*
+ *  2dvector.h
+ *  simplespring
+ *
+ *  Created by Robert Tubb on 01/06/2011.
+ *  Copyright 2011 __MyCompanyName__. All rights reserved.
+ *
+ */
+#ifndef _2DVECTORH
+#define _2DVECTORH
+
+class TwoVector{
+public:
+	double x, y;
+	TwoVector();
+	TwoVector(double ax, double ay);
+
+// public methods	
+	double norm();
+	void setCoord(double ax, double ay);
+	TwoVector minus(TwoVector otherPoint);
+    TwoVector operator-(TwoVector otherPoint);
+    TwoVector operator+(TwoVector otherPoint);
+    
+    TwoVector operator*(TwoVector otherPoint);
+    TwoVector operator*(double scalar); // scalar is right operand
+    TwoVector unitDir();
+    double distanceTo(TwoVector otherPoint);
+	
+
+};
+
+#endif // #ifndef _2DVECTORH
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/2dvector.mm	Mon Nov 19 13:00:42 2012 +0000
@@ -0,0 +1,82 @@
+/*
+ *  2dvector.cpp
+ *  simplespring
+ *
+ *  Created by Robert Tubb on 01/06/2011.
+ *  Copyright 2011 __MyCompanyName__. All rights reserved.
+ *
+ */
+
+#include "2dvector.h"
+#include <iostream>
+
+TwoVector::TwoVector(){
+	x = 0.0;
+	y = 0.0;
+	//cout << "def constr set vector to zeros" << endl;
+}
+
+TwoVector::TwoVector(double ax, double ay){
+	x = ax;
+	y = ay;
+	//cout << "spec constr set vector to " << ax << "," << ay << endl;
+}
+
+double TwoVector::norm(){
+	double norm;
+	norm = sqrt(x * x + y * y);
+	return norm;
+	
+}
+
+void TwoVector::setCoord(double ax, double ay){
+	x = ax;
+	y = ay;
+
+}
+
+TwoVector TwoVector::minus(TwoVector otherPoint){
+    TwoVector diff;
+    diff.setCoord(x - otherPoint.x, y - otherPoint.y);
+    return diff;
+}
+
+TwoVector TwoVector::operator-(TwoVector otherPoint){
+    TwoVector diff;
+    diff.setCoord(x - otherPoint.x, y - otherPoint.y);
+    return diff;
+}
+
+TwoVector TwoVector::operator*(TwoVector otherPoint){ // if multiplying two vectors - elementwise
+    TwoVector diff;
+    diff.setCoord(x * otherPoint.x, y * otherPoint.y);
+    return diff;
+}
+
+TwoVector TwoVector::operator*(double scalar){ // if multiplying two vectors - elementwise
+    TwoVector diff;
+    diff.setCoord(x * scalar, y * scalar);
+    return diff;
+}
+
+TwoVector TwoVector::operator+(TwoVector otherPoint){
+    TwoVector diff;
+    diff.setCoord(otherPoint.x + x, otherPoint.y + y);
+    return diff;
+}
+
+TwoVector TwoVector::unitDir(){
+    TwoVector unit;
+    double theNorm;
+    theNorm = this->norm();
+    
+    unit.setCoord(x/theNorm, y/theNorm);
+    return unit;
+}
+
+double TwoVector::distanceTo(TwoVector otherPoint){
+    TwoVector diff;
+    diff.setCoord(otherPoint.x - x, otherPoint.y - y);
+    return diff.norm();
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/button.h	Mon Nov 19 13:00:42 2012 +0000
@@ -0,0 +1,37 @@
+//
+//  button.h
+//  Wablet
+//
+//  Created by Robert Tubb on 19/03/2012.
+//  Copyright (c) 2012 __MyCompanyName__. All rights reserved.
+//
+
+#ifndef Wablet_button_h
+#define Wablet_button_h
+#include <iostream>
+#include "ofMain.h"
+#include "mesh.h"
+#include "uifunctor.h"
+class testApp;
+
+class ControlButton{
+  
+public:
+    ofColor bcolor, hicolor;
+    
+	bool pressed;
+    int width, height, xpos, ypos;
+    std::string name;
+    Mesh *theMesh;
+    int buttonId;
+    ControlButton(int abuttonId,int awidth,int aheight,int axpos,int aypos, std::string aname, Mesh *atheMeshPtr, ofColor acolor);
+    ~ControlButton();
+    void passCallBack(UIFunctor callBack);
+    void draw();
+    bool checkTouchArea(int axpos,int aypos);
+    void press();
+    void release();
+};
+
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/button.mm	Mon Nov 19 13:00:42 2012 +0000
@@ -0,0 +1,160 @@
+//
+//  button.cpp
+//  Wablet
+//
+//  Created by Robert Tubb on 19/03/2012.
+//  Copyright (c) 2012 __MyCompanyName__. All rights reserved.
+//
+//  button on screen either side of mesh
+#include <iostream>
+#include "button.h"
+#include "globalUI.h"
+#include "globalForces.h"
+extern GlobalUI globalUI;
+extern GlobalForces globalForces;
+
+ControlButton::ControlButton(int abuttonId, int awidth,int aheight,int axpos,int aypos, string aname, Mesh *atheMeshPtr, ofColor acolor){
+    width = awidth;
+    height = aheight;
+    xpos = axpos;
+    ypos = aypos;
+    name = aname;
+    bcolor = acolor;
+    bcolor.setSaturation(150);
+    bcolor.setBrightness(150);
+    hicolor = acolor;
+    hicolor.setSaturation(230);
+    bcolor.setBrightness(230);
+    buttonId = abuttonId;
+    pressed = 0;
+    theMesh = atheMeshPtr;
+    
+    //callingApp = atestApp;
+    
+}
+
+void ControlButton::draw(){
+    //cout << "button draw " << hicolor.r << " \n";
+    if(pressed){
+        ofSetColor(hicolor);
+    }else{
+        ofSetColor(bcolor);
+    }
+    
+    ofRect(xpos,ypos,width,height);
+    
+    ofSetLineWidth(2);
+    ofSetColor(hicolor); 
+    ofLine(xpos,ypos,xpos+width,ypos);
+    ofLine(xpos,ypos,xpos,ypos+height);
+    ofLine(xpos+width,ypos,xpos+width,ypos+height);
+    ofLine(xpos,ypos+height,xpos+width,ypos+height);
+    
+    ofSetColor(0, 0, 0);
+    ofDrawBitmapString(name, xpos+10, ypos+20);
+    
+}
+
+bool ControlButton::checkTouchArea(int axpos,int aypos){
+    if ( (axpos > xpos) && (axpos < xpos+width) && (aypos > ypos) && (aypos < ypos+height)){
+        return true;
+    }else{
+        return false;
+    }
+    
+}
+
+void ControlButton::press(){
+    cout << name << " button has been pressed\n";
+
+	if (name == "still"){
+		globalForces.homingAmt = 0.15;
+	}
+
+	if (name == "drift"){
+		theMesh->toggleSpringForces(false);
+	}	
+	if (name == "tighter"){
+		theMesh->increasePropagationSpeed();
+	}		
+	if (name == "slacker"){
+		theMesh->decreasePropagationSpeed();
+	}	
+
+	if (name == "reset"){
+		theMesh->resetAll();
+	}	
+	if (name=="stickedge"){
+		theMesh->constrain(.0,.0,Mesh::CONSTRAIN_EDGES);
+	}
+
+	if (name == "free"){
+		theMesh->unconstrain();
+	}
+
+    if (name == "drawscan"){
+        theMesh->resetPositions();
+        theMesh->resetVelocities();
+        globalUI.touchMode = globalUI.INSCRIBE_PATH;
+        theMesh->clearScanPath();
+        theMesh->update();
+	}
+    if (name == "deleteMesh"){
+		// cant
+	}
+    
+	if (name == "force"){
+		globalUI.touchMode = globalUI.FORCE_FIELD;
+	}	
+	if (name == "stick"){
+		globalUI.touchMode = globalUI.CONSTRAIN;
+	}		
+    if (name == "unstick"){
+		globalUI.touchMode = globalUI.UNCONSTRAIN;
+	}	
+	if (name == "free"){
+        // nothing
+		//globalUI.touchMode = globalUI.UNCONSTRAIN;
+	}	
+	if (name == "grab"){
+		globalUI.touchMode = globalUI.GRAB;
+	}	
+	if (name == "smooth"){
+		globalForces.avFilterAmt = 0.11;
+	}
+	if (name == "harmonic"){
+        globalUI.touchMode = globalUI.SPATIAL_HARMONIC;
+    }
+    if (name == "gravity"){
+        globalForces.gravityAmt = 4.0;
+    }
+    if(name == "scanmode"){
+        theMesh->scanPath->scanMode = theMesh->scanPath->SPEED;
+    }
+        pressed = true;
+}
+
+void ControlButton::release(){
+    
+    if (name == "homing"){
+		globalForces.homingAmt = 0.0;
+	}
+    if (name == "drift"){
+		theMesh->toggleSpringForces(true);
+	}	
+	if (name == "smooth"){
+		globalForces.avFilterAmt = 0.02;
+	}	
+    if (name == "gravity"){
+        globalForces.gravityAmt = 0.0;
+    }
+    if(name == "scanmode"){
+        theMesh->scanPath->scanMode = theMesh->scanPath->DISPLACEMENT;
+    }
+    if (name == "freeAll"){
+		theMesh->constrain(.0,.0,Mesh::CONSTRAIN_EDGES);
+	}
+    pressed = false;
+    
+    
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dsptools.h	Mon Nov 19 13:00:42 2012 +0000
@@ -0,0 +1,32 @@
+//
+//  dsptools.h
+//  wablet
+//
+//  Created by Robert Tubb on 21/06/2011.
+//  Copyright 2011 __MyCompanyName__. All rights reserved.
+//
+#ifndef _DSPTOOLSH
+#define _DSPTOOLSH
+
+
+class DSPTools{
+public:
+    double ax[3];
+    double by[3];
+
+    
+    double xv[3];
+    double yv[3];
+    
+    DSPTools();
+    double highpass1(double ax);
+    double lowpass1(double ax);
+    void getLPCoefficientsButterworth2Pole(const int samplerate, const double cutoff, double* const ax, double* const by);
+    void getHPCoefficientsButterworth2Pole(double* const ax, double* const by);
+    double butter(double sample);
+    
+};
+
+
+
+#endif
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dsptools.mm	Mon Nov 19 13:00:42 2012 +0000
@@ -0,0 +1,78 @@
+//
+//  dsptools.cpp
+//  wablet
+//
+//  Created by Robert Tubb on 21/06/2011.
+//  Copyright 2011 __MyCompanyName__. All rights reserved.
+//
+
+#include "dsptools.h"
+#include "ofMain.h"
+
+DSPTools::DSPTools(){
+    for(int i = 0; i < 3; i++){
+        xv[i] = 0.0;
+        yv[i] = 0.0;
+    }
+    getHPCoefficientsButterworth2Pole(ax, by);
+}
+
+double DSPTools::highpass1(double ax){
+    static double xm1 = 0.0;
+    double sample;
+    sample = 2*(ax - xm1);
+    xm1 = ax;
+    return sample;    
+    
+}
+double DSPTools::lowpass1(double ax){
+    static double xm1 = 0.0;
+    double sample;
+    sample = 0.5*(ax + xm1);
+    xm1 = ax;
+    return sample;
+    
+}
+
+void DSPTools::getLPCoefficientsButterworth2Pole(const int samplerate, const double cutoff, double* const ax, double* const by)
+{
+    double sqrt2 = 1.4142135623730950488;
+    
+    double QcRaw  = (2 * PI * cutoff) / samplerate; // Find cutoff frequency in [0..PI]
+    double QcWarp = tan(QcRaw); // Warp cutoff frequency
+    
+    double gain = 1 / (1+sqrt2/QcWarp + 2/(QcWarp*QcWarp));
+    by[2] = (1 - sqrt2/QcWarp + 2/(QcWarp*QcWarp)) * gain;
+    by[1] = (2 - 2 * 2/(QcWarp*QcWarp)) * gain;
+    by[0] = 1;
+    ax[0] = 1 * gain;
+    ax[1] = 2 * gain;
+    ax[2] = 1 * gain;
+}
+void DSPTools::getHPCoefficientsButterworth2Pole(double* const ax, double* const by)
+{
+    ax[0] = 0.997987115675119;
+    ax[1] = -1.995974231350238;
+    ax[2] = 0.997987115675119;
+
+    by[0] = 1.000000000000000;
+    by[1] = -1.995970179642828;
+    by[2] = 0.995978283057647;
+}
+
+double DSPTools::butter(double sample)
+{
+
+        xv[2] = xv[1]; 
+        xv[1] = xv[0];
+        xv[0] = sample;
+        yv[2] = yv[1]; 
+        yv[1] = yv[0];
+        
+        yv[0] =   (ax[0] * xv[0] + ax[1] * xv[1] + ax[2] * xv[2]
+                   - by[1] * yv[1]
+                   - by[2] * yv[2]);
+        
+        return yv[0];
+
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/globalForces.h	Mon Nov 19 13:00:42 2012 +0000
@@ -0,0 +1,63 @@
+//
+//  globalForces.h
+//  wablet
+//
+//  Created by Robert Tubb on 18/07/2011.
+//  Copyright 2011 __MyCompanyName__. All rights reserved.
+//
+#ifndef _GLOBALFORCES
+#define _GLOBALFORCES
+
+#include "2dvector.h"
+#include "ofMain.h"
+#include "ofxiPhone.h"
+#include "ofxiPhoneExtras.h"
+#include <list>
+
+struct forceTouchPoint{
+    double x, y;
+    int tid;
+
+    //forceTouchPoint(double ax,double ay,int atid): x(ax),y(ay),tid(atid){};
+    
+};
+
+class GlobalForces{
+
+public: 
+
+    enum excitationTypes {POSITION,VELOCITY};
+    enum excitationShapes {NOISE,GAUSS,SINE};
+
+    excitationTypes excitationType;
+    excitationShapes excitationShape;
+    
+    bool gravityOn, forceTouchOn, pressureOn;
+    // params for adjusting stuff
+    double pressureAmt, gravityAmt, touchStrength, excitationStrength, homingAmt, avFilterAmt;
+    int exciteShapeX, exciteShapeY;
+    // general params
+    double speedLimit, wallBounce;
+    double delt; // time step between frames
+    double volume;
+    
+    forceTouchPoint* forceTouchPoints;
+    TwoVector grav;
+    double pressure;
+    
+    int maxForcePoints;
+    
+    GlobalForces();
+    ~GlobalForces();
+    void update();
+    void setPressure(double aP);
+    void createForceTouchPoint(double ax,double ay, int touchId);
+    void moveForceTouchPoint(double ax,double ay, int touchId);
+    void removeForceTouchPoint(int touchId);
+    TwoVector getAllForceAt(double ax, double ay);
+    
+    
+};
+
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/globalForces.mm	Mon Nov 19 13:00:42 2012 +0000
@@ -0,0 +1,122 @@
+//
+//  globalForces.cpp
+//  wablet
+//
+//  Created by Robert Tubb on 18/07/2011.
+//  Copyright 2011 __MyCompanyName__. All rights reserved.
+//
+
+#include "globalForces.h"
+
+
+//-----------------------------------------------------------
+GlobalForces::GlobalForces(): grav(0.0,0.0){
+    //construct
+    gravityOn = true;
+    
+    speedLimit = 100.0;
+    wallBounce = 0.5;
+    
+    pressureAmt = 0.0;
+    gravityAmt = 0.2;
+    touchStrength = 1.0;
+    homingAmt = 0.0;
+    avFilterAmt = 0.0;
+    
+    pressure = 0.0;
+    volume = 1.0;
+    
+    maxForcePoints = 11;
+    excitationType = POSITION;
+    excitationShape = NOISE;
+    
+    forceTouchPoints = new forceTouchPoint[maxForcePoints];
+    for(int i = 0; i< 11; i++){
+        forceTouchPoints[i].x = 0.0;
+        forceTouchPoints[i].y = 0.0;
+        forceTouchPoints[i].tid = -1;
+        
+    }
+    
+    
+}
+
+//-----------------------------------------------------------
+GlobalForces::~GlobalForces(){
+    //destruct
+    
+}
+void GlobalForces::update(){
+    // gravity 
+    grav.setCoord(ofxAccelerometer.getForce().y * 0.015 * gravityAmt, ofxAccelerometer.getForce().x * 0.015 * gravityAmt);
+    // time step
+    delt = 1/ofGetFrameRate();
+    
+}
+//-----------------------------------------------------------
+// this is actually only called from dropletmesh.update
+void GlobalForces::setPressure(double aP){
+    
+    pressure = pressureAmt*aP;
+}
+//-----------------------------------------------------------
+void GlobalForces::createForceTouchPoint(double ax,double ay, int touchId){
+
+    forceTouchPoints[touchId].x = ax;
+    forceTouchPoints[touchId].y = ay;
+    forceTouchPoints[touchId].tid = touchId; 
+
+    
+    
+}
+//-----------------------------------------------------------
+void GlobalForces::moveForceTouchPoint(double ax,double ay, int touchId){
+    //
+    for(int i = 0;i<maxForcePoints; i++){
+        if(forceTouchPoints[touchId].tid == touchId){
+            forceTouchPoints[touchId].x = ax;
+            forceTouchPoints[touchId].y = ay;            
+        }
+    }
+    
+}
+//-----------------------------------------------------------
+void GlobalForces::removeForceTouchPoint(int touchId){
+    //
+    for(int i = 0;i<maxForcePoints; i++){
+        if(forceTouchPoints[i].tid == touchId){
+            forceTouchPoints[i].tid = -1;          
+        }
+    } 
+}
+//-----------------------------------------------------------
+TwoVector GlobalForces::getAllForceAt(double ax, double ay){
+    //this is called for every mass point so work out most stuff in update()
+    TwoVector force(0.0,0.0);
+    double fmag, r;
+    if(gravityOn){
+        
+        force.x += grav.x;
+        force.y += grav.y;
+    }
+    
+    // now touch points
+     for(int i = 0;i<maxForcePoints; i++){
+         // work out distance to force point and then use 1/r^2 rule 
+         if(forceTouchPoints[i].tid != -1){
+             TwoVector diff(forceTouchPoints[i].x - ax, forceTouchPoints[i].y - ay );
+             r = diff.norm();
+             if (r < 0.03){ // try avoid very small distances giving ridiculous forces
+                 fmag = 0.0;
+             }else{
+                 fmag = touchStrength*1/(1000*(r*r));
+             } 
+            force.x -= fmag*diff.x/r;
+            force.y -= fmag*diff.y/r;
+             
+         }
+     }
+    
+    return force;
+}
+//-----------------------------------------------------------
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/globalUI.h	Mon Nov 19 13:00:42 2012 +0000
@@ -0,0 +1,55 @@
+//
+//  globalUI.h
+//  Wablet
+//
+//  Created by Robert Tubb on 19/03/2012.
+//  Copyright (c) 2012 __MyCompanyName__. All rights reserved.
+//
+
+#ifndef Wablet_globalUI_h
+#define Wablet_globalUI_h
+
+#include "ofMain.h"
+#include "button.h"
+#include"mesh.h"
+#include "uifunctor.h"
+
+class testApp;
+
+class GlobalUI{
+    
+public:
+    ofColor red;
+    ofColor blue;
+    ofColor green;
+    
+    int borderSize; // size of the button strips to left and right
+    int buttonSize; // sqr button size in pixels
+    
+    enum touchModes {GRAB,FORCE_FIELD,SPATIAL_HARMONIC,CONSTRAIN,UNCONSTRAIN,VIBRATE,INSCRIBE_PATH};
+    
+    touchModes touchMode;
+    
+    int numButtons;
+
+    ControlButton **buttons;
+    
+    GlobalUI();
+
+    UIFunctor *theFunctor;
+    Mesh *theMeshPtr;
+    void passCallBack(UIFunctor specFuncA);
+    
+    void makeButtons(Mesh *atheMeshPtr);
+    void draw();
+
+    bool handleTouchDown(int ax, int ay);
+    bool handleTouchUp(int ax, int ay);
+    bool handleTouchMove(int ax, int ay);
+    
+private:
+    bool inUIZone(float ax, float ay);
+    
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/globalUI.mm	Mon Nov 19 13:00:42 2012 +0000
@@ -0,0 +1,140 @@
+//
+//  globalUI.mm
+//  Wablet
+//
+//  Created by Robert Tubb on 19/03/2012.
+//  Copyright (c) 2012 __MyCompanyName__. All rights reserved.
+//
+
+#include <iostream>
+#include "globalUI.h"
+
+GlobalUI::GlobalUI(){
+    // array of buttons, 
+
+    
+    numButtons = 12;
+    borderSize = 128;
+    buttonSize = 100;
+    
+    buttons = new ControlButton *[numButtons];
+
+    touchMode = GRAB;
+
+}
+
+void GlobalUI::passCallBack(UIFunctor specFuncA){
+
+}
+void GlobalUI::makeButtons(Mesh *atheMeshPtr){
+    
+    // set up button size depending on screen resolution
+    
+    int w = ofGetWidth();
+    cout << "width = " << w << "\n";
+    if (w == 768){ // ipad
+        
+    }else if (w == 480){ // iphone
+        borderSize = 80;
+        buttonSize = 70;
+        
+    }else if (w == 1234){ // retinass?
+        
+    }else{
+        // default to what?
+        
+    }
+    
+    theMeshPtr = atheMeshPtr;
+
+    ofColor red(250,0,0);
+    ofColor blue(0,0,250);
+    ofColor green(0,250,0);  
+    
+    buttons[0] = new ControlButton(2,buttonSize,buttonSize,(borderSize-buttonSize)*0.5,(borderSize-buttonSize)*0.5,"still",atheMeshPtr,green);
+    buttons[1] = new ControlButton(0,buttonSize,buttonSize,(borderSize-buttonSize)*0.5,borderSize+(borderSize-buttonSize)*0.5,"smooth",atheMeshPtr,green);
+    buttons[2] = new ControlButton(1,buttonSize,buttonSize,(borderSize-buttonSize)*0.5,borderSize*2+(borderSize-buttonSize)*0.5,"drift",atheMeshPtr,green);
+    buttons[3] = new ControlButton(3,buttonSize,buttonSize,(borderSize-buttonSize)*0.5,borderSize*3+(borderSize-buttonSize)*0.5,"gravity",atheMeshPtr,green);
+    buttons[4] = new ControlButton(4,buttonSize,buttonSize,(borderSize-buttonSize)*0.5,borderSize*4+(borderSize-buttonSize)*0.5,"free",atheMeshPtr,green);
+    buttons[5] = new ControlButton(5,buttonSize,buttonSize,(borderSize-buttonSize)*0.5,borderSize*5+(borderSize-buttonSize)*0.5,"something",atheMeshPtr,green);
+    
+    // touch mode buttons
+    buttons[6] = new ControlButton(6,buttonSize,buttonSize,borderSize+ofGetHeight()+(borderSize-buttonSize)*0.5,(borderSize-buttonSize)*0.5,"grab",atheMeshPtr, blue);
+    buttons[7] = new ControlButton(7,buttonSize,buttonSize,borderSize+ofGetHeight()+(borderSize-buttonSize)*0.5,borderSize+(borderSize-buttonSize)*0.5,"force",atheMeshPtr, blue);
+    buttons[8] = new ControlButton(8,buttonSize,buttonSize,borderSize+ofGetHeight()+(borderSize-buttonSize)*0.5,borderSize*2+(borderSize-buttonSize)*0.5,"stick",atheMeshPtr, blue);
+    buttons[9] = new ControlButton(9,buttonSize,buttonSize,borderSize+ofGetHeight()+(borderSize-buttonSize)*0.5,borderSize*3+(borderSize-buttonSize)*0.5,"unstick",atheMeshPtr, blue);
+    buttons[10] = new ControlButton(10,buttonSize,buttonSize,borderSize+ofGetHeight()+(borderSize-buttonSize)*0.5,borderSize*4+(borderSize-buttonSize)*0.5,"drawscan",atheMeshPtr, red);
+    buttons[11] = new ControlButton(11,buttonSize,buttonSize,borderSize+ofGetHeight()+(borderSize-buttonSize)*0.5,borderSize*5+(borderSize-buttonSize)*0.5,"reset",atheMeshPtr, red);
+}
+
+//--------------------------------------------------------------
+void GlobalUI::draw(){
+    
+    
+    for(int i=0; i<numButtons;i++){
+        buttons[i]->draw();
+    }
+    
+    //ofSetColor(blue); 
+    //ofDrawBitmapString("touch mode", 670, 25);
+}
+//--------------------------------------------------------------
+bool GlobalUI::inUIZone(float x, float y){
+    if (x < borderSize || x > borderSize + ofGetHeight()){
+        return true;
+        
+    }else{
+        return false;
+    }
+}
+
+//--------------------------------------------------------------
+bool GlobalUI::handleTouchDown(int ax, int ay){
+    // returns true if touch is not to be left to the mesh
+    if(inUIZone(ax,ay)){
+        
+        /* not needed now we are using ofxUI
+         
+        for(int i=0; i < numButtons;i++){
+            if(buttons[i]->checkTouchArea(ax,ay)){
+                buttons[i]->press();
+            }
+        }
+         */
+        return true;
+    }else{
+    
+    return false;
+    }   
+}
+//--------------------------------------------------------------
+bool GlobalUI::handleTouchUp(int ax, int ay){
+    // returns true if touch is not to be left to the mesh
+    if(inUIZone(ax,ay)){
+        /* not needed now we are using ofxUI
+         
+         for(int i=0; i < numButtons;i++){
+         if(buttons[i]->checkTouchArea(ax,ay)){
+         buttons[i]->press();
+         }
+         }
+         */
+        return true;
+    }else{
+        
+        return false;
+    }   
+}
+//--------------------------------------------------------------
+bool GlobalUI::handleTouchMove(int ax, int ay){
+    // returns true if touch is not to be left to the mesh
+    if(inUIZone(ax,ay)){
+
+        return true;
+    }else{
+        
+        return false;
+    }   
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lump.h	Mon Nov 19 13:00:42 2012 +0000
@@ -0,0 +1,87 @@
+/*
+ *  lump.h
+ *  simplespring
+ *
+ *  Created by Robert Tubb on 01/06/2011.
+ *  Copyright 2011 __MyCompanyName__. All rights reserved.
+ *
+ */
+#ifndef _LUMPH
+#define _LUMPH
+#include "2dvector.h"
+#include "ofMain.h"
+#include "spring.h"
+
+
+class Spring;
+class Lump {
+private:
+	double mass, inverseMass;
+	bool grabbed, highlighted;
+    const int maxSprings;
+	
+	TwoVector velocity;
+	TwoVector accel;
+	double friction;
+
+public:
+    bool constrained;
+	Spring** attachedSprings; // pointers to all attached springs
+	int numAttachedSprings;
+    
+    enum ConstrainMode {NOT_CONSTRAINED,CONSTRAIN_X,CONSTRAIN_Y,CONSTRAIN_XY};
+    ConstrainMode constrainMode;
+	
+    bool isInScanPath;
+
+	TwoVector position;
+    TwoVector previousPosition;
+	double totalForceMag;
+	double size;
+    int grabID; // which touch is this lump grabbed by?
+		
+	Lump(); // default constructor
+	Lump(double aMass,double aFriction, double positionX, double positionY);
+	// also which spring is it attached to
+    ~Lump();
+	
+	TwoVector applyForce();
+    void averagingFilter(double amt);
+    void homingFilter(double amt);
+    TwoVector averageOfConnected();
+
+    TwoVector zeroRefPos;
+	void draw();
+
+	void attachSpring(Spring* aSpring);
+	void setPosition(double ax, double ay);
+    void setVelocity(double ax, double ay);
+	void constrain();
+    void constrain(ConstrainMode aconstrainMode);
+	void unconstrain();
+	
+	bool isGrabbed();
+	void grab(int aGrabID);
+	void drag(double ax, double ay, int aGrabID);
+	void unGrab();
+	void highlight();
+	void unhighlight();
+	void setInvMass(double aInvMass);
+	double getTotalForceMag();
+    void setZeroRefPos();
+    void setFriction(double aF);
+    
+    Spring * checkConnectedTo(Lump * otherLump);
+    
+    // interface to scan path
+    double scanDisplacement();
+    double scanRadialDisplacement();
+    double scanLumpSpeed();
+    double scanYPos();
+     double scanXPos();
+    void addToScanPath();
+    void removeFromScanPath();
+	 
+};
+
+#endif
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lump.mm	Mon Nov 19 13:00:42 2012 +0000
@@ -0,0 +1,418 @@
+/*
+ *  lump.cpp
+ *  simplespring
+ *
+ *  Created by Robert Tubb on 01/06/2011.
+ *  Copyright 2011 __MyCompanyName__. All rights reserved.
+ *
+ */
+#include "lump.h"
+#include "testApp.h"
+#include <iostream>
+#include "globalForces.h"
+#include "globalUI.h"
+extern GlobalForces globalForces;
+extern GlobalUI globalUI;
+//int Lump::numLumps = 0;
+//--------------------------------------------------------------
+// default constr
+Lump::Lump() : maxSprings(100){
+	//cout << "constructing a default lump" << endl;
+	mass = 10.0;
+	inverseMass = 1.0/mass; // not needed - used csquared for force
+	friction = 0.996;
+	position.x = 0.5;
+	position.y = 0.5;
+	velocity.x = 0.0;
+	velocity.y = 0.0;
+	accel.x = 0.0;
+	accel.y = 0.0;	
+	numAttachedSprings = 0;
+	grabbed = false;
+	highlighted = false;
+	constrained = false;
+	totalForceMag = 0.0;
+	size = 3; //sqrt(mass/3.0);
+    isInScanPath = false;
+    previousPosition.setCoord(0.5,0.5);
+    zeroRefPos.setCoord(0.0,0.0);
+    constrainMode = NOT_CONSTRAINED;
+    
+    attachedSprings = new Spring*[maxSprings];
+    
+    grabID = -1;
+	//myIndex = Lump::numLumps++;
+    //cout << numAttachedSprings << endl;
+	
+}
+//--------------------------------------------------------------
+// arg constructor
+Lump::Lump(double aMass,double aFriction, double positionX, double positionY) : maxSprings(100){
+		// set members
+
+}
+
+//--------------------------------------------------------------
+Lump::~Lump(){
+    
+    delete [] attachedSprings;
+}
+//--------------------------------------------------------------
+void Lump::attachSpring(Spring* aSpring){
+	
+	// stick pointer in array
+	if(numAttachedSprings < maxSprings){
+		attachedSprings[numAttachedSprings] = aSpring;
+		numAttachedSprings++;
+	}else{
+		cout << "cant attach another spring as mass already has " << maxSprings << endl;
+		return;
+	}
+}
+//--------------------------------------------------------------
+Spring * Lump::checkConnectedTo(Lump * otherLump){
+    // loop thru all attached springs looking at other end
+    for(int i = 0; i<numAttachedSprings; i++){
+        if(attachedSprings[i]->getLumpOnOtherEnd(this) == otherLump){
+            return attachedSprings[i];
+        }
+    }
+    return NULL;
+}
+//--------------------------------------------------------------
+void Lump::constrain(){
+    // TODO1 different constrain modes
+	constrained = true;
+    constrainMode = CONSTRAIN_XY;
+    setZeroRefPos();
+	//cout << "constraining lump "<< endl;
+	
+}
+//--------------------------------------------------------------
+void Lump::constrain(ConstrainMode aconstrainMode){
+	constrained = true;
+    constrainMode = aconstrainMode;
+    setZeroRefPos();
+	//cout << "constraining lump "<< endl;
+	
+}
+void Lump::setInvMass(double aInvMass){
+	// use csquared
+	inverseMass = aInvMass;
+}
+//--------------------------------------------------------------
+
+void Lump::draw(){
+/*
+
+
+ */
+
+    if(grabbed){
+
+        int xpos = position.x * ofGetHeight() + globalUI.borderSize;
+        int ypos = position.y * ofGetHeight();
+		// draw a circle round it
+		ofSetColor(255, 0, 0);
+		ofNoFill();
+		ofCircle(xpos, ypos, 35.0);
+		ofFill();
+	}else if(isInScanPath){
+        ofSetColor(0, 200, 20);
+        int xpos = position.x * ofGetHeight() + globalUI.borderSize;
+        int ypos = position.y * ofGetHeight();
+        ofEllipse(xpos,ypos, 6, 6);
+        
+        // code to display restpos and displacement
+        /*
+        ofSetColor(0, 0, 0);
+        int rxpos = zeroRefPos.x * ofGetHeight() + 128;
+        int rypos = zeroRefPos.y * ofGetHeight();
+        ofEllipse(rxpos,rypos, 6, 6);  
+        
+        ofSetColor(100, 100, 100);
+
+        ofLine(rxpos,rypos,xpos,ypos);  
+         */
+        
+    }else if(highlighted){
+		ofSetColor(200, 0, 0);
+        int xpos = position.x * ofGetHeight() + globalUI.borderSize;
+        int ypos = position.y * ofGetHeight();
+        ofEllipse(xpos,ypos, 2, 2);
+	}else if (constrained){
+		ofSetColor(200,23,23);
+	}else{
+        // dont draw 'normal ' lumps
+        return;
+		//ofSetColor(23, 23, 200);
+	}
+}
+
+//--------------------------------------------------------------
+ 
+TwoVector Lump::applyForce(){
+
+    
+	if(grabbed || constrainMode == CONSTRAIN_XY){
+        
+        // don't bother
+        return position;
+    }
+	// called LOTS so optimise
+	// use spring force to calc accel - vel - pos
+
+	TwoVector springForce(0.0,0.0);
+	TwoVector totalForce(0.0,0.0);
+	
+	// sum up force from each attached spring
+	for(int i = 0;i<numAttachedSprings; i++){
+
+        springForce = (attachedSprings[i])->getForce(this);
+        
+		//cout << "spring number " << i << " force x " << springForce.x << endl;
+		totalForce.x += springForce.x;
+		totalForce.y += springForce.y;
+	}
+
+    // get the global forces, gravity and so on
+    totalForce.x += globalForces.getAllForceAt(position.x,position.y).x;
+    totalForce.y += globalForces.getAllForceAt(position.x,position.y).y;
+
+    if (constrainMode != CONSTRAIN_X){
+        accel.x = totalForce.x*inverseMass;	
+    }else{
+        accel.x = 0.0;
+    }
+    if(constrainMode != CONSTRAIN_Y){
+        accel.y = totalForce.y*inverseMass;	
+    }else{
+        accel.y = 0.0;
+    }
+    
+    // DIFFERENCE EQUATIONS HERE. This is the bit that controls the movement!
+
+    // Heun
+    
+    double pvx = velocity.x*friction + accel.x*2/3;
+    double pvy = velocity.y*friction + accel.y*2/3;
+    
+    
+    velocity.x = 0.75*pvx + 0.25*velocity.x;
+    velocity.y = 0.75*pvy + 0.25*velocity.y;
+
+    double px = position.x + velocity.x*2/3;
+    double py = position.y + velocity.y*2/3;
+    
+    
+    position.x = 0.75*px + 0.25*position.x;
+    position.y = 0.75*py + 0.25*position.y;
+     
+
+    	// Newton's 2nd law
+/*
+        velocity.x += accel.x;
+        velocity.x *= friction;
+        position.x += velocity.x;
+
+        velocity.y += accel.y;
+        velocity.y *= friction;
+        position.y += velocity.y;           
+*/
+
+
+    // WALLS
+    if (position.x < 0.0){
+        position.x = -position.x;
+        velocity.x = -velocity.x * globalForces.wallBounce;
+        
+    }  else  if (position.x > 1.0){
+        position.x = 2.0 - position.x;
+        velocity.x = -velocity.x * globalForces.wallBounce;
+        
+    }
+    if (position.y < 0.0){
+        position.y = -position.y;
+        velocity.y = -velocity.y * globalForces.wallBounce;
+        
+    }  else  if (position.y > 1.0){
+        position.y = 2.0 - position.y;
+        velocity.y = -velocity.y * globalForces.wallBounce;
+        
+    }    
+	return position;
+	
+}
+//---------------------------------------------
+void Lump::homingFilter(double amt){
+    // includes a little bit of the zero pos in the position, so sound will exponentially decay 
+
+    if (constrained || grabbed) return;
+
+    position.x = (1 - amt)*position.x + amt*zeroRefPos.x;
+    position.y = (1 - amt)*position.y + amt*zeroRefPos.y;
+    
+}
+//---------------------------------------------
+void Lump::averagingFilter(double amt){
+    // NOT USED AVERAGING FILTER NOW IN MESH
+    // amt is between 0 and 1
+    if (constrained || grabbed) return;
+    double avx = 0.0, avy = 0.0;
+    // average the position of all the attached lumps
+    for(int i = 0;i<numAttachedSprings; i++){
+        
+        Lump* otherLump = attachedSprings[i]->getLumpOnOtherEnd(this);
+        avx += otherLump->position.x;
+        avy += otherLump->position.y;
+	}
+    avx /= numAttachedSprings;
+    avy /= numAttachedSprings;
+    
+    // mix in the average with the 'real'
+    position.x = (1 - amt)*position.x + amt*avx;
+    position.y = (1 - amt)*position.y + amt*avy;
+    
+}
+//---------------------------------------------
+TwoVector Lump::averageOfConnected(){
+    TwoVector av;
+    if (constrained || grabbed) return position; // don't want constrained ones moving
+    //TODO what if edges unconstrained? this is why filtered unconstrained just ends up as a line...
+
+    // average the position of all the attached lumps
+    for(int i = 0;i<numAttachedSprings; i++){
+        
+        Lump* otherLump = attachedSprings[i]->getLumpOnOtherEnd(this);
+        av.x += otherLump->position.x;
+        av.y += otherLump->position.y;
+	}
+    av.x /= numAttachedSprings;
+    av.y /= numAttachedSprings;
+    
+    return av;
+    
+}
+//--------------------------------------------------------------
+void Lump::setPosition(double ax, double ay){
+    // set INITIAL position
+    // Called from mesh set up. not used for updates
+	
+	position.x = ax;
+	position.y = ay;
+	zeroRefPos.x = ax;
+	zeroRefPos.y = ay;
+}
+//--------------------------------------------------------------
+void Lump::setVelocity(double ax, double ay){
+	
+	velocity.x = ax;
+	velocity.y = ay;
+    
+}
+//--------------------------------------------------------------
+
+void Lump::setFriction(double aF){
+    friction = aF;
+}
+//--------------------------------------------------------------
+
+void Lump::setZeroRefPos(){
+    // sets the reference point from which displacement is measured for scan amplitudes
+    zeroRefPos = position;
+}
+//--------------------------------------------------------------
+double Lump::getTotalForceMag(){
+	return totalForceMag;
+}
+//--------------------------------------------------------------
+double Lump::scanDisplacement(){
+    // returns the absolute distance from 'home'
+    return position.distanceTo(zeroRefPos);
+}
+//--------------------------------------------------------------
+double Lump::scanLumpSpeed(){
+    // returns the absolute magnitude of the lumps velocity
+    return velocity.norm();
+}
+//--------------------------------------------------------------
+double Lump::scanYPos(){
+    // returns the y displ
+    return position.y - zeroRefPos.y;
+}
+//--------------------------------------------------------------
+double Lump::scanXPos(){
+    // returns the x displ
+    return position.x - zeroRefPos.x;
+}
+// -------------------
+double Lump::scanRadialDisplacement(){
+    // returns the distance from circle zero line
+    // need to know where the centre point of the circle is and default radius
+
+    //return position.y - 0.5;
+    return position.distanceTo(zeroRefPos);
+    
+}
+//--------------------------------------------------------------
+void Lump::addToScanPath(){
+    isInScanPath = true;
+    setZeroRefPos();
+}
+//--------------------------------------------------------------
+void Lump::removeFromScanPath(){
+    isInScanPath = false;
+
+}
+//--------------------------------------------------------------
+void Lump::grab(int aGrabID){
+	// hover hilight doesn't work on touchscreens if(highlighted) grabbed = true;
+    grabbed = true;
+    grabID = aGrabID;
+    velocity.x = 0.0;
+    velocity.y = 0.0;
+}
+//--------------------------------------------------------------
+void Lump::drag(double ax, double ay, int aGrabID){
+	if(aGrabID == grabID){
+        //cout << "dragging lump ID: " << grabID << endl;
+        position.x = ax;
+        position.y = ay;
+        velocity.x = previousPosition.x - position.x;
+        velocity.y = previousPosition.y - position.y;
+        previousPosition = position; // sets velocity too          
+    } 
+}
+//--------------------------------------------------------------
+void Lump::unGrab(){
+    cout << "ungrabbed something\n";
+	grabbed = false;
+    grabID = -1;
+    velocity.x = 0.0;
+    velocity.y = 0.0;
+}
+//--------------------------------------------------------------
+void Lump::unconstrain(){
+	constrained = false;
+    constrainMode = NOT_CONSTRAINED;
+}
+
+
+//--------------------------------------------------------------
+
+bool Lump::isGrabbed(){
+	return grabbed;
+}
+
+//--------------------------------------------------------------
+
+void Lump::highlight(){
+	highlighted = true;
+}
+
+//--------------------------------------------------------------
+
+void Lump::unhighlight(){
+	highlighted = false;
+}
+//--------------------------------------------------------------
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.mm	Mon Nov 19 13:00:42 2012 +0000
@@ -0,0 +1,13 @@
+#include "ofMain.h"
+#include "testApp.h"
+#include "globalForces.h"
+
+GlobalForces globalForces;
+GlobalUI globalUI;
+
+int main(){
+	//ofSetupOpenGL(480,320, OF_FULLSCREEN);			// <-------- setup the GL context
+    ofSetupOpenGL(1024,768, OF_FULLSCREEN);	
+	ofRunApp(new testApp);
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mesh.h	Mon Nov 19 13:00:42 2012 +0000
@@ -0,0 +1,250 @@
+/*
+ *  mesh.h
+ *  springstructure
+ *
+ *  Created by Robert Tubb on 07/06/2011.
+ *  Copyright 2011 __MyCompanyName__. All rights reserved.
+ *
+ */
+
+#ifndef _MESHH
+#define _MESHH
+
+#include <iostream>
+#include "lump.h"
+#include "scanpath.h"
+#include "globalForces.h"
+
+
+class Mesh{
+public:
+
+    bool radial; // tells us if mesh is radial or not
+	// toggles
+	bool syrup;
+    bool home;
+
+    double propagationSpeed;
+    
+    double k,m,f; // for stability check and asym
+	
+	int numLumps;
+	int numSprings;
+    
+	Lump *lumps;
+	Spring *springs;
+    ScanPath* scanPath;
+    
+    TwoVector centre;
+
+	
+	// modes
+	enum constrainMode { CONSTRAIN_CORNERS, CONSTRAIN_EDGES,CONSTRAIN_EDGES_XY, CONSTRAIN_SINGLE_POINT, CONSTRAIN_SINGLE_POINT_X, CONSTRAIN_SINGLE_POINT_Y, CONSTRAIN_GRAB_REGION,CONSTRAIN_GRAB_REGION_X,CONSTRAIN_GRAB_REGION_Y, CONSTRAIN_ALL_X, CONSTRAIN_ALL_Y};
+	double GRAB_RANGE;
+	 
+	
+
+    // MEMBER FUNCTIONS
+	Mesh();
+	virtual ~Mesh();
+
+    void draw();
+    
+	// INTERACTIONS
+    void resetAll();
+    void resetPositions();
+    void resetVelocities();
+    void resetEverything();
+    
+     // excite via position, velocity force
+    // using shapes : noise, hamming, sine, 
+    void hit(double dax,double day,int velocity = 100, GlobalForces::excitationTypes aEType = GlobalForces::POSITION, 
+             GlobalForces::excitationShapes aEShape = GlobalForces::NOISE); 
+    void spatialHarmonic(int aharm1 = 1, int aharm2 = 0);
+    void damp();
+    void forceField(double dax,double day,double strength);
+    void averagingFilter(double amt);
+    
+	void grab(double x, double y,  int touchID); 
+	void drag(double ax,double ay, int touchID);
+	void unGrab(int touchID);
+    
+	virtual void constrain(double x, double y, constrainMode aMode);	// diff meshes will have diff edges
+	void unconstrain();
+    void unconstrain(double x, double y, constrainMode aMode);
+
+    
+    // PARAMETER SETTINGS
+    void toggleSyrup();
+	void toggleSyrup(bool on);
+    void toggleSpringForces();
+	void toggleSpringForces(bool on);
+    void toggleGravity();
+    void toggleGravity(bool on);
+    void toggleHome(bool on);
+    
+	void setRestLength();
+    void zeroRestLength();
+    
+    void setPropagationSpeed(double aSpeed); // sets k and m to give a certain speed
+    void decreasePropagationSpeed();
+    void increasePropagationSpeed();
+    void setSpringConstant(double aK);
+    void setMass(double aM);
+    void setFriction(double aF);
+    
+    // create varying constants
+    void setSpringConstantAsym(double aAsym);
+    void setMassAsym(double aAsym);
+    void setFrictionAsym(double aAsym);	
+    void setZeroAudioLine();
+
+    // only for mouse cursor
+	void checkHover(double x, double y);
+
+//	SCANPATH STUFF
+	void clearScanPath();
+    void disconnectDraw(); // enables you to draw scanpths anywhere
+	int inscribeScanPath(double ax, double ay);
+    // called by scanpath.update wavetables
+	double getNextSample();
+    
+    virtual void update();
+
+protected:
+
+    int prevLump;
+    
+	// specific mesh shapes override these:
+
+	virtual void makeComponents(int adimension1, int adimension2);
+	virtual void setLumpPositions();
+	virtual void makeConnections();
+	virtual void makeDefaultScanPath();
+
+    // UTILS    
+    TwoVector calculateCentre();
+	void connect(int springnum,int lumpnum);
+    void connect(int springnum,int lumpnum,int alumpnum2);
+	int getNearestLump(double ax,double ay);	
+	
+};
+//---------------------------------------------------------
+class SpiderMesh : public Mesh{
+public:
+	int numSpokes, numRings;
+	
+	SpiderMesh(int aNumSpokes,int aNumRings);
+	
+	virtual void constrain(double x, double y, constrainMode aMode);
+	virtual void makeComponents(int adimension1, int adimension2);
+	virtual void setLumpPositions();
+	virtual void makeConnections();
+	virtual void makeDefaultScanPath();
+};
+//---------------------------------------------------------
+class HoledSpiderMesh : public Mesh{
+public:
+    int numSpokes, numRings;
+	
+    HoledSpiderMesh(int aNumSpokes,int aNumRings);
+	void constrain(double x, double y, constrainMode aMode);
+	void makeComponents(int adimension1, int adimension2);
+	void setLumpPositions();
+	void makeConnections();
+	void makeDefaultScanPath();
+};
+//---------------------------------------------------------
+class SpiderCrossMesh : public Mesh{
+public:
+	int numSpokes, numRings;
+	
+	SpiderCrossMesh(int aNumSpokes,int aNumRings);
+	
+	void constrain(double x, double y, constrainMode aMode);
+	void makeComponents(int adimension1, int adimension2); // override
+	void setLumpPositions(); // same
+	void makeConnections(); // overrride
+	void makeDefaultScanPath(); // same
+};
+//---------------------------------------------------------
+// square grid with all diagonals connected
+class SquareCrossMesh : public Mesh{
+public:
+	// square specific
+	int height;
+	int width;
+	
+	SquareCrossMesh(int height, int width);	
+    ~SquareCrossMesh();
+	void constrain(double x, double y, constrainMode aMode);
+	 void makeComponents(int adimension1, int adimension2);
+	 void setLumpPositions();
+	 void makeConnections();
+	 void makeDefaultScanPath();
+};
+//---------------------------------------------------------
+// simple 1d line
+class LineMesh : public Mesh{
+public:
+	// square specific
+	int numSegments;
+	
+	LineMesh(int aNumSegments);	
+	void constrain(double x, double y, constrainMode aMode);
+    void makeComponents(int adimension1, int adimension2);
+    void setLumpPositions();
+    void makeConnections();
+    void makeDefaultScanPath();
+};
+//---------------------------------------------------------
+// simple 1d line attached to 'ground' (constrained lumps) by other springs...
+class GroundedLineMesh : public Mesh{
+public:
+    int numSegments;
+	
+	GroundedLineMesh(int aNumSegments);	
+	void constrain(double x, double y, constrainMode aMode);
+    void makeComponents(int adimension1, int adimension2);
+    void setLumpPositions();
+    void makeConnections();
+    void makeDefaultScanPath();
+};
+
+//----------------------------------------------------------
+//---------------------------------------------------------
+// circular spring with internal pressure
+class DropletMesh : public Mesh{
+public:
+	// specific
+	int numSegments;
+    double restArea;
+	
+	DropletMesh(int aNumSegments);	
+	void constrain(double x, double y, constrainMode aMode);
+    void makeComponents(int adimension1, int adimension2);
+    void setLumpPositions();
+    void makeConnections();
+    void makeDefaultScanPath();
+    double calculatePressure();
+    double calculateArea();
+    void update();
+};
+//---------------------------------------------------------
+// trangular mesh
+class TriangleMesh : public Mesh{
+public:
+	// square specific
+	int height;
+	int width;
+	
+	TriangleMesh(int height, int width);	
+	void constrain(double x, double y, constrainMode aMode);
+    void makeComponents(int adimension1, int adimension2);
+    void setLumpPositions();
+    void makeConnections();
+    void makeDefaultScanPath();
+};
+//----------------------------------------------------------
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mesh.mm	Mon Nov 19 13:00:42 2012 +0000
@@ -0,0 +1,2050 @@
+/*
+ *  mesh.cpp
+ *  springstructure
+ *
+ *  Created by Robert Tubb on 07/06/2011.
+ *  Copyright 2011 __MyCompanyName__. All rights reserved.
+ *
+ */
+
+#include "mesh.h"
+#include "testApp.h"
+#include "lump.h"
+#include <math.h>
+
+extern GlobalForces globalForces;
+
+//--------------------------------------------------------------
+Mesh::Mesh(){
+	// construct an empty structure
+	numSprings = 0;
+	numLumps = 0;
+    
+    scanPath = new ScanPath();
+
+	syrup = false;
+	GRAB_RANGE = 0.040;
+    propagationSpeed = 0.5; // this will be changed by adjusting k/m
+	cout << "constructed mesh base" << endl;
+    
+    m = 123.0;
+    k = 0.1;
+    f = 0.999;
+
+    prevLump = -1;
+    
+    // some reasonable defaults
+    setSpringConstant(0.8);
+    setMass(1);
+    setFriction(0.999991);
+
+}
+
+
+Mesh::~Mesh(){
+	cout << "destroying mesh" << endl;
+	delete [] lumps; 
+	delete [] springs;
+    delete scanPath;
+}
+///////////
+// interface (called from testApp)
+///////////
+
+
+//--------------------------------------------------------------
+void Mesh::update(){
+	// update all the springs and masses sequentially
+    // INTERP HACK - this bit of code is to debug pops and clicks
+  /*  
+    static int updown = 0;
+    updown++;
+    if(updown > 24){
+        for(int i=0;i<numLumps;i++){
+            lumps[i].position.y = 0.4;
+        }	
+    
+    }else{
+        
+        for(int i=0;i<numLumps;i++){
+            lumps[i].position.y = 0.6;
+        }	
+     
+    }
+    if(updown > 48) updown = 0;
+  	// update the ends of the springs to reflect lump movement
+	
+	for(int i=0;i<numSprings;i++){
+		springs[i].updateEndPoints();
+	}	  
+    
+    return;
+    
+   */
+    
+    globalForces.update();
+	// calculate all the forces in the springs
+	for(int i=0;i<numSprings;i++){
+		springs[i].calculateForce();
+	}	
+    
+	// accelerate the lumps
+	for(int i=0;i<numLumps;i++){
+		lumps[i].applyForce();
+	}	
+    
+    // move filters into apply force? not really, they're not forces.
+    // apply homing filter
+	for(int i=0;i<numLumps;i++){
+		lumps[i].homingFilter(globalForces.homingAmt);
+	}	    
+    // apply averaging filter
+    // 
+    averagingFilter(globalForces.avFilterAmt);
+    
+	// update the ends of the springs to reflect lump movement
+	for(int i=0;i<numSprings;i++){
+		springs[i].updateEndPoints();
+	}	
+    
+    // scan the updated mesh
+    scanPath->updateWavetables();
+}
+//--------------------------------------------------------------
+void Mesh::averagingFilter(double amt){
+    // we need a temporary store for average position, can't update in place with central diffs
+    static TwoVector * avLumpPos = new TwoVector[numLumps];
+    
+    for(int i=0;i<numLumps;i++){
+		avLumpPos[i] = lumps[i].averageOfConnected();
+	}	 
+    // now we can set pos
+    for(int i=0;i<numLumps;i++){
+            // mix in the average with the 'real'
+
+            lumps[i].position.x = (1 - amt)*lumps[i].position.x + amt*avLumpPos[i].x;
+            lumps[i].position.y = (1 - amt)*lumps[i].position.y + amt*avLumpPos[i].y;
+
+	}    
+    
+    
+}
+//--------------------------------------------------------------
+void Mesh::draw(){
+	// draw all the components belonging to mesh
+
+	int i;
+	for(i=0;i<numSprings;i++){
+		springs[i].draw();
+	}
+
+	for(i=0;i<numLumps;i++){
+		lumps[i].draw();
+	}
+
+    // scanPath->draw(); // uncomment if you want to see the output waveform
+
+
+}
+//--------------------------------------------------------------
+TwoVector Mesh::calculateCentre(){
+    // calculates average of all lump positions
+    
+    double x = 0, y = 0;
+    for(int i = 0; i < numLumps; i++){
+        x += lumps[i].position.x;
+        y += lumps[i].position.y;
+        
+    }
+    x = x/numLumps;
+    y = y/numLumps;
+    
+    //cout << "centre : " << x << "," << y << endl;
+    
+    centre.setCoord(x,y);
+    
+    return centre;
+}
+//--------------------------------------------------------------
+void Mesh::resetAll(){
+	// it's this simple
+	resetPositions();
+    resetVelocities();
+    unconstrain();
+    clearScanPath();
+    makeDefaultScanPath();
+    constrain(0.0,0.0,CONSTRAIN_EDGES);
+    constrain(0.0,0.0,CONSTRAIN_CORNERS);
+    
+	// unless we want to reset velocities...?
+}
+//--------------------------------------------------------------
+void Mesh::resetPositions(){
+	// it's this simple
+	setLumpPositions();
+	// unless we want to reset velocities...?
+}
+//--------------------------------------------------------------
+void Mesh::resetVelocities(){
+    for(int i = 0; i < numLumps; i++){
+        lumps[i].setVelocity(0.0,0.0);
+        
+    }    
+}
+void Mesh::toggleHome(bool on){
+    static double prev;
+    
+    if(!home && on){
+        home = on;
+        prev = globalForces.homingAmt;
+        globalForces.homingAmt = 0.3;
+        
+
+    }else if (home && !on){
+        home = on; // ie false
+        globalForces.homingAmt = prev;        
+    }
+}
+//--------------------------------------------------------------
+void Mesh::resetEverything(){
+
+}
+
+//--------------
+void Mesh::toggleSyrup(){
+    if(syrup){
+        toggleSyrup(false);
+    }else{
+        toggleSyrup(true);
+    }
+}
+//--------------
+void Mesh::toggleSyrup(bool on){
+	if (!on){
+		syrup = false;
+		for(int i=0;i<numLumps;i++) lumps[i].setFriction(0.998);
+	}else{
+		syrup = true;
+		for(int i=0;i<numLumps;i++) lumps[i].setFriction(0.90);
+	}
+}
+//----------------------------------------------------------------
+void Mesh::toggleGravity(){
+    globalForces.gravityOn = !globalForces.gravityOn;
+}
+//----------------------------------------------------------------
+void Mesh::toggleGravity(bool on){
+	if (!on){
+        globalForces.gravityOn = true;
+	}else{
+        globalForces.gravityOn = false;
+	}
+}
+
+//----------------------------------------------------------------
+void Mesh::toggleSpringForces(){
+
+		Spring::forcesOn = !Spring::forcesOn;
+
+}
+//----------------------------------------------------------------
+void Mesh::toggleSpringForces(bool on){
+	if (!on){
+		Spring::forcesOn = false;
+
+	}else{
+		Spring::forcesOn = true;
+
+	}
+}
+
+//--------------------------------------------------------------
+void Mesh::unconstrain(){
+	
+	for(int i = 0; i<numLumps; i++){
+		
+		lumps[i].unconstrain();
+		
+	}
+}
+//--------------------------------------------------------------
+void Mesh::unconstrain(double ax, double ay, constrainMode aMode){
+	int i = 0;
+    TwoVector diff;
+	// check if lump is within grab range
+	switch (aMode) {
+		case CONSTRAIN_GRAB_REGION:
+			for(i = 0; i<numLumps; i++){
+				
+				diff.setCoord(lumps[i].position.x - ax, lumps[i].position.y - ay);
+				
+				if (diff.norm() < GRAB_RANGE){
+					lumps[i].unconstrain();
+				}
+			}
+			break;
+		case CONSTRAIN_EDGES:
+            // hmm
+			break;
+			
+		default:
+			break;
+	}
+}
+//--------------------------------------------------------------
+void Mesh::setRestLength(){
+	for(int i = 0; i<numSprings; i++){
+		springs[i].setRestLength();
+	}
+
+}
+//--------------------------------------------------------------
+void Mesh::setZeroAudioLine(){
+	// set zero rest pos to where they are now
+    for(int i = 0; i<numLumps; i++){
+		
+		lumps[i].setZeroRefPos();
+		
+	}
+}
+//--------------------------------------------------------------
+void Mesh::setPropagationSpeed(double aSpeed){
+    
+    propagationSpeed = aSpeed;
+    for(int i = 0; i < numLumps; i++){
+        lumps[i].setInvMass(propagationSpeed*propagationSpeed); // inverse mass is c^2
+    }
+}
+//--------------------------------------------------------------
+void Mesh::increasePropagationSpeed(){
+    
+    propagationSpeed = propagationSpeed + 0.01;
+    for(int i = 0; i < numLumps; i++){
+        lumps[i].setInvMass(propagationSpeed*propagationSpeed); // inverse mass is c^2
+    }
+    cout << "prop speed  = " << propagationSpeed << endl;
+}
+//--------------------------------------------------------------
+void Mesh::decreasePropagationSpeed(){
+    
+    propagationSpeed = propagationSpeed - 0.01;
+    for(int i = 0; i < numLumps; i++){
+        lumps[i].setInvMass(propagationSpeed*propagationSpeed); // inverse mass is c^2
+    }
+    cout << "prop speed  = " << propagationSpeed << endl;
+}
+//--------------------------------------------------------------
+void Mesh::setSpringConstant(double aK){
+    k = aK;
+    if(sqrt(k/m) > 0.9){
+        k = m * 0.9;
+    }
+    cout << "k/m = " << k/m << endl;
+    for(int i = 0; i < numSprings; i++){
+        springs[i].setSpringConstant(k); // inverse mass is c^2
+    }      
+}
+//--------------------------------------------------------------
+void Mesh::setMass(double aM){
+
+    m = aM;
+
+    if(sqrt(k/m) > 0.9){
+        m = k/0.9;
+    }
+    cout << "k/m = " << k/m << endl;
+    double im = 1/aM;
+    for(int i = 0; i < numLumps; i++){
+        lumps[i].setInvMass(im); 
+    }    
+}
+//--------------------------------------------------------------
+void Mesh::setFriction(double newF){
+    f = newF;
+    for(int i = 0; i < numLumps; i++){
+        lumps[i].setFriction(f); 
+    }        
+}
+//--------------------------------------------------------------
+void Mesh::setMassAsym(double aAsym){
+    // use m as bass and add asym linearly across mesh. might be wierd for some meshes!
+    double newm,incr;
+    newm = m;
+    incr = aAsym*m/(numLumps-1);
+    for(int i = 0; i < numLumps; i++){
+        lumps[i].setInvMass(1/newm);
+        newm += incr;
+    }    
+}
+//--------------------------------------------------------------
+void Mesh::setSpringConstantAsym(double aAsym){
+    // from 0 to 1
+    if (aAsym > 1.0){
+        aAsym = 1.0;
+    }
+    if (aAsym < 0.0){
+        aAsym = 0.0;
+    }
+    double newk, incr;
+    newk = k;
+    incr = (1-aAsym)*k/(numSprings-1);
+    for(int i = 0; i < numSprings; i++){
+        springs[i].setSpringConstant(newk);
+        newk -= incr;
+    }    
+}
+
+//--------------------------------------------------------------
+void Mesh::setFrictionAsym(double aAsym){
+    // multiplying it doesn't make sense!
+    double newF,incr;
+    newF = f;
+    incr = aAsym*f/(numLumps-1);
+    for(int i = 0; i < numLumps; i++){
+        lumps[i].setFriction(newF); 
+        newF += incr;
+    }        
+}
+//--------------------------------------------------------------
+void Mesh::checkHover(double ax, double ay){
+	TwoVector diff;
+	bool found = false;
+	// check if lump is within grab range
+	for(int i = 0; i<numLumps; i++){
+		diff.setCoord(lumps[i].position.x - ax, lumps[i].position.y - ay);
+		//cout << "diff " << diff.x << "," << diff.y << endl;
+		if (diff.norm() < GRAB_RANGE){
+			if(!found)	{
+				lumps[i].highlight();
+				found = true;
+			}
+		}else{
+			lumps[i].unhighlight();
+		}
+	}	
+	
+}
+
+//--------------------------------------------------------------
+void Mesh::grab(double ax, double ay, int touchID){
+	// attempt to grab a lump ax and ay should be already normalised
+	
+	TwoVector diff;
+    
+	// find closest
+    int nearest = getNearestLump(ax,ay);
+    diff.setCoord(lumps[nearest].position.x - ax, lumps[nearest].position.y - ay);
+    //cout << "diff " << diff.x << "," << diff.y << endl;
+    if (diff.norm() < GRAB_RANGE){
+        cout << "grabbing lump " << nearest << endl;
+        // move this lump to mouse pos
+        
+        lumps[nearest].grab(touchID);
+    }
+
+	
+}
+//--------------------------------------------------------------
+void Mesh::unGrab(int touchID){
+	
+	for(int i=0; i<numLumps;i++){
+        if(lumps[i].grabID == touchID){ // only release the lumps that were grabbed by this finger
+            lumps[i].unGrab();
+            lumps[i].unhighlight();
+        }
+	}
+}
+
+//--------------------------------------------------------------
+void Mesh::drag(double ax, double ay, int touchID){
+	// maybe set up specific id's for grabbed lumps
+	for(int i=0; i<numLumps;i++){
+        if(lumps[i].grabID == touchID){// only move the lumps that were grabbed by this finger
+            
+            lumps[i].drag(ax,ay,touchID);
+        }
+	}
+}
+//--------------------------------------------------------------
+/* also could:
+ add random velocities
+ etc
+ */
+//////////////////////
+// protected utils
+////////////////////////
+//---------------------------------------------------------------
+void Mesh::connect(int aspringnum,int alumpnum){
+	//cout << "connecting spring " << aspringnum << " to lump " << alumpnum << endl;
+	// should we check valid indexes?
+	lumps[alumpnum].attachSpring(&springs[aspringnum]);
+	springs[aspringnum].attachLump(&lumps[alumpnum]);
+
+}
+//---------------------------------------------------------------
+void Mesh::connect(int aspringnum,int alumpnum,int alumpnum2){
+	// better version - just connects 2 lumps with a spring
+	// should we check valid indexes?
+	lumps[alumpnum].attachSpring(&springs[aspringnum]);
+	springs[aspringnum].attachLump(&lumps[alumpnum]);
+	lumps[alumpnum2].attachSpring(&springs[aspringnum]);
+	springs[aspringnum].attachLump(&lumps[alumpnum2]);
+    
+}
+
+void Mesh::zeroRestLength(){
+    // sets springs rest length to zero - collapses structure
+    for(int i = 0; i < numSprings; i++){
+        springs[i].setRestLength(0.0);
+    }
+}
+//--------------------------------------------------------------
+int Mesh::getNearestLump(double ax, double ay){
+    // not used yet
+	int nearestLumpIndex = -1;
+	TwoVector diff;
+	double distance = 1.0;
+	double minDistance = 1.0;
+
+	for(int i = 0; i<numLumps; i++){
+		diff.setCoord(lumps[i].position.x - ax, lumps[i].position.y - ay);
+		distance = diff.norm();
+		if(distance < minDistance && !lumps[i].constrained){ // don't grab if constrained ?? 
+			minDistance = distance;
+			nearestLumpIndex = i;
+		}
+	}	
+    cout << "nearest lump : " << nearestLumpIndex << endl;
+    
+	return nearestLumpIndex;
+}
+//--------------------------------------------------------------
+void Mesh::clearScanPath(){
+	// in between having clicked and drawn we can't play anything
+    scanPath->clear();
+    prevLump = -1;
+}
+//--------------------------------------------------------------
+void Mesh::disconnectDraw(){
+    prevLump = -1;
+}
+//--------------------------------------------------------------
+int Mesh::inscribeScanPath(double ax, double ay){
+    bool foundPath = false;
+    int targetLump, intermediateLump; 
+    int count = 0;
+    TwoVector halfWay;
+    // is this ridiculously complicated for no reason?
+    
+    targetLump = getNearestLump(ax,ay); // target is the one closest to touch point, prev is last point already on our path
+    
+    while(!foundPath && count < 2048){
+        count++;
+        if(prevLump == -1){ // (scanPath->howManyElements() == 0){ //
+            //first lump
+            foundPath = true;
+            prevLump = targetLump;
+            //cout << "first lump" << endl;
+            return targetLump;
+        }
+        // check if this lump connected to last lump by a spring
+        Spring * connectedToPrev  = lumps[targetLump].checkConnectedTo(&lumps[prevLump]);
+        
+        // if not connectedToLast will be null
+        if (!connectedToPrev){
+            //cout << "setting half way" << endl;
+            // set coords to 1/2 way and recurse
+            double halfdiffx = ((ax-lumps[prevLump].position.x)*0.5);
+            double halfdiffy = ((ay - lumps[prevLump].position.y)*0.5);
+            if (halfdiffx < 0.0001 && halfdiffy < 0.0001){
+                // not found nuffun
+                //cout << "not found a path, quitting" << endl;
+                return -1;
+            }
+            halfWay.setCoord( ax - halfdiffx , ay -  halfdiffy);
+            
+            
+            intermediateLump = inscribeScanPath(halfWay.x,halfWay.y);
+            if(intermediateLump == -1){
+                //cout << "finding intermediate didn't work" << endl;
+                return -1;
+            }
+            // now check that the found intermediate targetLump is connected to the original targetlump 
+            Spring * connectedToNext  = lumps[targetLump].checkConnectedTo(&lumps[prevLump]);
+            if(!connectedToNext){
+                // set prevLump to the interm and try again
+                prevLump = intermediateLump;
+                targetLump = inscribeScanPath(ax,ay);
+                if(targetLump == -1){
+                    //cout << "joining up next one didn't work" << endl;
+                    return -1;
+                }
+            }
+        }else{
+            //cout << "CONNECTED! adding an element" << endl;
+            
+            // hurrah, add the spring and lump to the scanpath in the right order...
+            // get pointer to lump and spring
+            
+            scanPath->addElement(&lumps[targetLump], connectedToPrev);
+            // and set prev lump to that one
+            prevLump = targetLump;
+            foundPath = true;
+            return targetLump; // 
+        }
+        
+    }
+
+    cout << "reached end of inscribe func" << endl;
+
+    return -1;
+}
+
+// 
+void Mesh::hit(double dax, double day,int velocity, GlobalForces::excitationTypes aEType, GlobalForces::excitationShapes aEShape){
+    // set filter to something low
+    globalForces.homingAmt = 0.0;
+    
+    double radius = 0.1, distance; // size of lump
+    double impactStrength = globalForces.excitationStrength*velocity/1000; // velocity as in note loudness
+    double amt = 0.0;
+    TwoVector diff;
+    // get all the lumps and smack em
+    int xamt = globalForces.exciteShapeX;
+    int yamt = globalForces.exciteShapeY;
+
+    switch(aEShape){
+    
+        case GlobalForces::SINE:
+            
+            // applies a sine wave to all lumps, x and y
+            for(int i = 0; i < numLumps; i++){
+                if(!lumps[i].constrained){
+                    lumps[i].position.y = lumps[i].zeroRefPos.y + impactStrength*sin(PI* (2*(xamt-1) + 1) * lumps[i].position.x);
+                    lumps[i].position.x = lumps[i].zeroRefPos.x + impactStrength*sin(PI* (2*(yamt-1) + 1) * lumps[i].position.y);
+                }
+            }
+            
+            break;
+        case GlobalForces::GAUSS:
+            // temporarily not a gauss...
+            for(int i = 0; i < numLumps; i++){
+                diff.setCoord(lumps[i].position.x - dax, lumps[i].position.y - day);
+                distance = diff.norm();
+                if(distance < radius){
+                    amt = impactStrength*(1 + cos(PI*distance/radius)); // a 2d hamming.
+                    lumps[i].position.y += amt;
+                    
+                }     
+            }
+            break;
+        case GlobalForces::NOISE:
+            // 2D noise
+            for(int i = 0; i < numLumps; i++){
+                    lumps[i].position.x += impactStrength*rand();
+                    lumps[i].position.y += impactStrength*rand();
+                    
+            }     
+            break;
+        default:
+            break;
+    }
+    
+    
+}
+
+void Mesh::spatialHarmonic(int aharm, int aharm2){
+    double magdr,theta;
+    TwoVector r, nr, centre, rhat, displacement;
+    centre.setCoord(0.5, 0.5);
+    // applies a sine wave to all lumps, x and y
+    if(radial){
+        // distance from centre is tweaked
+        for(int i = 0; i < numLumps; i++){
+            if(!lumps[i].constrained ){ //&& lumps[i].isInScanPath
+
+                // lots of sins and tans - might be simplifiable?
+                r = lumps[i].position - centre;
+                rhat = r.unitDir();
+                theta = atan2(r.y,r.x);
+                magdr = sin(aharm*theta);
+                
+                // new r for lump:
+                displacement = rhat * 0.1*globalForces.touchStrength*magdr;
+                lumps[i].position = lumps[i].position + displacement;
+                
+            }
+        }
+    }else{
+        for(int i = 0; i < numLumps; i++){
+            if(!lumps[i].constrained){
+                lumps[i].position.y = lumps[i].zeroRefPos.y + globalForces.touchStrength/128.0*sin(PI* (2*(aharm-1) + 1) * lumps[i].position.x);
+                lumps[i].position.x = lumps[i].zeroRefPos.x + globalForces.touchStrength/128.0*sin(PI* (2*(aharm2-1) + 1) * lumps[i].position.y);
+            }
+        }
+    }
+}
+
+void Mesh::damp(){
+    // set friction and filters to something high
+    //globalForces.avFilterAmt = 0.0;
+    globalForces.homingAmt = 0.3;    
+}
+void Mesh::forceField(double dax,double day,double strength){
+    
+    // 
+}
+//--------------------------------------------------------------
+double Mesh::getNextSample(){
+
+	return 0.0;
+
+}
+
+//--------------------------------------------------------------
+// VIRTUAL
+//--------------------------------------------------------------
+void Mesh::makeDefaultScanPath(){
+	// make componets creates the correct number of lumps and springs according to dimensions and mesh type
+	cout << "!!!!Mesh base class makeDefaultScanPath\n";
+}
+
+//--------------------------------------------------------------
+void Mesh::setLumpPositions(){
+    // places lumps in the right positions 
+	cout << "!!!!Mesh base class setLumpPositions\n";
+}
+//--------------------------------------------------------------
+void Mesh::makeConnections(){
+    // connects the lmps and springs. the spring needs to know which lumps are at each end
+    // and lumps need to know which springs attached
+    // calls connect method that does this
+	cout << "!!!!Mesh base class make connections\n";
+}
+//--------------------------------------------------------------
+void Mesh::makeComponents(int aDimension1, int aDimension2){
+	cout << "!!!!Mesh base class makeComponents\n";
+}
+//--------------------------------------------------------------
+void Mesh::constrain(double ax, double ay, constrainMode aMode){
+	cout << "!!!!Mesh base class constrain\n";
+}
+//--------------------------------------------------------------
+
+
+/****************************************/
+//		SquareCrossMesh					*/
+/****************************************/
+//--------------------------------------------------------------
+SquareCrossMesh::SquareCrossMesh(int height, int width): Mesh() {
+	
+	makeComponents(height, width);
+	
+	setLumpPositions();
+	
+	makeConnections();
+	
+	makeDefaultScanPath();
+    
+    constrain(0,0,CONSTRAIN_EDGES_XY);
+    
+    zeroRestLength();
+    
+    radial = false;
+	
+	cout << "constructing SQUARE mesh\n";
+}
+SquareCrossMesh::~SquareCrossMesh(){
+	
+	cout << "destructing SQUARECROSS mesh\n";
+}
+
+//--------------------------------------------------------------
+// make componets for square
+void SquareCrossMesh::makeComponents(int aheight, int awidth){
+	height = aheight;
+	width = awidth;
+	numLumps = height * width;
+                // horz             // vert
+	numSprings = (width-1)*height + (height-1)*width + 2*(height-1)*(width-1);
+	
+	lumps = new Lump[numLumps];
+	cout << "made " << numLumps << " lumps\n";
+	springs = new Spring[numSprings];
+	cout << "made " << numSprings << " springs\n";
+}
+
+//--------------------------------------------------------------
+void SquareCrossMesh::setLumpPositions(){
+	double vspacing = 1.0/(height-1);
+	double hspacing = 1.0/(width-1);
+	int i =0;
+	// set positions
+	for(int row = 0; row < height; row++){ // go down column
+		for(int col = 0;col < width; col++){ // go along row
+			lumps[i].setPosition(hspacing*col,vspacing*row);
+			
+			i++;
+		}
+	}
+}
+//--------------------------------------------------------------
+void SquareCrossMesh::makeConnections(){
+	
+	// could be cleverer in terms of numbering?
+	int lumpnum = 0;
+	int springnum = 0;
+	// attach horizontal 
+	for ( int i=0;i<height;i++){
+		for(int j=0;j<width-1;j++){
+			connect(springnum,lumpnum);
+			lumpnum++;
+			connect(springnum,lumpnum);
+			springnum++;
+			
+		}
+		lumpnum++;
+	}
+	// attach vertical 
+	lumpnum = 0;
+	for ( int i=0;i<width;i++){
+		for(int j=0;j<height-1;j++){
+			connect(springnum,lumpnum);
+			lumpnum+=width;
+			connect(springnum,lumpnum);
+			springnum++;
+			
+		}
+		lumpnum -= (width*(height-1)) - 1;
+	}	
+	// attach diagonal
+	lumpnum = 0;
+	for ( int i=0;i<width-1;i++){
+		for(int j=0;j<height-1;j++){
+			connect(springnum,lumpnum);
+			lumpnum+=(width);
+			connect(springnum,lumpnum+1);
+			springnum++;
+			
+		}
+		lumpnum -= (width*(height-1)) - 1;
+	}	
+	// attach other diagonal
+	lumpnum = 1;
+	for ( int i=0;i<width-1;i++){
+		for(int j=0;j<height-1;j++){
+			connect(springnum,lumpnum);
+			lumpnum+=(width);
+			connect(springnum,lumpnum-1);
+			springnum++;
+			
+		}
+		lumpnum -= (width*(height-1)) - 1;
+	}	
+}
+
+//--------------------------------------------------------------
+void SquareCrossMesh::constrain(double ax, double ay, constrainMode aMode){
+	
+	TwoVector diff;
+	int i = 0;
+	
+	// check if lump is within grab range
+	switch (aMode) {
+		case CONSTRAIN_GRAB_REGION:
+			for(i = 0; i<numLumps; i++){
+				
+				diff.setCoord(lumps[i].position.x - ax, lumps[i].position.y - ay);
+				
+				if (diff.norm() < GRAB_RANGE){
+					lumps[i].constrain();
+				}
+			}
+			break;
+		case CONSTRAIN_EDGES:
+			i = 0;
+			for(int row = 0; row < height; row++){ // go down column
+				for(int col = 0;col < width; col++){ // go along row
+					if(row == 0 || row == height-1 || col == 0 || col == width-1){
+						lumps[i].constrain();
+					}
+					i++;
+				}
+			}	
+			break;
+		case CONSTRAIN_EDGES_XY:
+			i = 0;
+			for(int row = 0; row < height; row++){ // go down column
+				for(int col = 0;col < width; col++){ // go along row
+					if(row == 0 || row == height-1){
+						lumps[i].constrain(Lump::CONSTRAIN_Y);
+					}else if( col == 0 || col == width-1){
+                        lumps[i].constrain(Lump::CONSTRAIN_X);
+                    }
+					i++;
+				}
+			}	
+			break;
+		case CONSTRAIN_CORNERS:
+			i = 0;
+			for(int row = 0; row < height; row++){ // go down column
+				for(int col = 0;col < width; col++){ // go along row
+					if(   (row == 0 && col == 0)
+					   || (row == height-1 && col == 0) 
+					   || (row == 0 && col == width-1) 
+					   || (row == height-1 && col == width-1)){
+						lumps[i].constrain();
+					}
+					i++;
+				}
+			}
+			break;
+			
+		default:
+			break;
+	}
+	
+}
+//--------------------------------------------------------------
+//--------------------------------------------------------------
+void SquareCrossMesh::makeDefaultScanPath(){
+    if (height < 10 || width < 10) return; // not much point
+    
+    int vmarg = 5;
+    int hmarg = 5;
+    
+    int lumpno = vmarg * width + hmarg; // top left corner
+    int springno = vmarg * (width - 1) + hmarg;
+    // do top horz
+    for(int i = 0; i < width - 2*hmarg; i++){
+        scanPath->addElement(&lumps[lumpno], &springs[springno]);
+        lumpno++;
+        springno++;
+    }
+    
+    // do right vert, lumpno starts the same
+                // all horz spr    // left marg rows      // top margin
+    springno  = height*(width-1) + ((height-1) * (width - hmarg)) + vmarg;
+    
+    for(int i = 0; i < height - (2 * vmarg); i++){
+        scanPath->addElement(&lumps[lumpno], &springs[springno]);
+        springno++; // jump to next row
+        lumpno += width;   // ditto
+    }
+    
+    
+    // do bottom horz right to left
+    springno = (height - vmarg) * (width - 1) + (width - hmarg - 1);
+    for(int i = 0; i < width - 2*hmarg; i++){
+        scanPath->addElement(&lumps[lumpno], &springs[springno]);
+        springno--; // jump to next row
+        lumpno--;   // ditto
+    } 
+    
+                 // all horz spr    // left marg rows    // top margin   
+    springno  = height*(width-1) + ((height-1) * hmarg) +  height - vmarg - 1;
+    for(int i = 0; i < height - 2 * vmarg; i++){
+        scanPath->addElement(&lumps[lumpno], &springs[springno]);
+        springno--; // jump to next row
+        lumpno -= width;   // ditto
+    }
+    
+}
+//--------------------------------------------------------------
+
+
+/****************************************/
+//		SpiderMesh					*/
+/****************************************/
+SpiderMesh::SpiderMesh(int aNumSpokes,int aNumRings) : Mesh(){
+	numSpokes = aNumSpokes;
+	numRings = aNumRings;
+    
+    radial = true;
+	
+	makeComponents(numSpokes, numRings);
+	
+	setLumpPositions();
+	
+	makeConnections();
+
+	makeDefaultScanPath();
+    
+    constrain(0.0,0.0,CONSTRAIN_EDGES);
+    constrain(0.0,0.0,CONSTRAIN_CORNERS);
+	
+	cout << "constructed SPIDER mesh\n";
+	
+	
+	
+}
+//--------------------------------------------------------------
+void SpiderMesh::makeComponents(int aDimension1, int aDimension2){
+    // make componets creates the correct number of lumps and springs according to dimensions and mesh type
+	cout << "!!!spider makeComponents\n";
+	numLumps = numSpokes * numRings + 1; // +1 cos one in the middle
+	numSprings = 2 * numSpokes * numRings;
+	
+	lumps = new Lump[numLumps];
+	cout << "made " << numLumps << " lumps\n";
+	springs = new Spring[numSprings];
+	cout << "made " << numSprings << " springs\n";	
+	
+				
+}
+//--------------------------------------------------------------
+void SpiderMesh::setLumpPositions(){
+	cout << "!spider setLumpPositions\n";
+	// here we're assuming that zero spoke is to right
+	// work out sines / cosines for each spoke
+	int l = 1;
+	double hyplen,xpos,ypos;
+	
+	double * cosines = new double[numSpokes];
+	double * sines = new double[numSpokes];
+	double angle = 2 * PI / numSpokes;
+	
+	double ringSpacing = 1.0/(2*numRings);
+	
+	for(int spoke = 0; spoke < numSpokes; spoke++){
+		cosines[spoke] = cos(angle*spoke);
+		sines[spoke] = sin(angle*spoke);
+	}
+		
+	lumps[0].setPosition(0.5,0.5);
+
+	for(int ring = 0; ring < numRings; ring++){
+		for(int spoke = 0; spoke < numSpokes; spoke++){
+			hyplen = ringSpacing * (ring+1);
+			xpos = hyplen * cosines[spoke] + 0.5;
+			ypos = hyplen*sines[spoke] + 0.5;
+			lumps[l].setPosition(xpos,ypos);
+			l++;
+			
+		}
+	}
+	delete [] cosines;
+	delete [] sines;
+			
+}
+//--------------------------------------------------------------
+void SpiderMesh::makeConnections(){
+	// TODO needs to be rewritten to automatically 
+	// add in lumps and springs AS we're connecting
+	//using std vector
+	// this is dumb
+	cout << "!!!spider make connections\n";
+
+	// UNLIKE a spider we're going to make rings first, to make indexing the scan path easier...
+
+	for(int ring = 0; ring < numRings; ring++){
+		for(int spoke = 0; spoke < numSpokes-1; spoke++){
+			//		spring ,				lump
+			connect(numSpokes*ring + spoke, ring*numSpokes + spoke + 1);
+			connect(numSpokes*ring + spoke, ring*numSpokes + spoke + 2);
+		}
+		// remember the last one on the ring goes back to first
+		//		spring ,						lump
+		connect(numSpokes*ring + numSpokes - 1 , ring*numSpokes + numSpokes);
+		connect(numSpokes*ring + numSpokes - 1,  ring*numSpokes + 1);
+	}	
+	// at which point we have used numSpokes*numRings springs
+	int used = numSpokes*numRings;
+	// first ring is different cos it connects to central point
+	for(int spoke = 0; spoke < numSpokes; spoke++){
+		connect(used,0); // inner radial on center
+		connect(used,spoke+1);  // outer radial
+		springs[used].setRestLength(0.0); // need tension on spokes
+		used++;
+	}
+	// now do the rest of the radial spokes
+	for(int ring = 0; ring < numRings-1; ring++){
+		for(int spoke = 0; spoke < numSpokes; spoke++){
+			connect(used,ring*numSpokes+1+spoke); // 
+			connect(used,(ring+1)*numSpokes+1+spoke);  // going to next ring out
+			springs[used].setRestLength(0.0);
+			used++;
+		}
+	}
+}
+
+//--------------------------------------------------------------
+void SpiderMesh::constrain(double ax, double ay, constrainMode aMode){
+	cout << "!!spider constrain\n";
+	
+	TwoVector diff;
+	int i = 0;
+	
+	// check if lump is within grab range
+	switch (aMode) {
+		case CONSTRAIN_GRAB_REGION:
+			for(i = 0; i<numLumps; i++){
+				
+				diff.setCoord(lumps[i].position.x - ax, lumps[i].position.y - ay);
+				
+				if (diff.norm() < GRAB_RANGE){
+					lumps[i].constrain();
+				}
+			}
+			break;
+		case CONSTRAIN_EDGES:
+		{
+			// constrain outer ring
+			int startLump = numSpokes*(numRings-1) + 1;
+			for(int l = startLump; l < startLump + numSpokes; l++){
+				lumps[l].constrain();
+			}
+	
+			break;
+		}
+		case CONSTRAIN_CORNERS:
+			// er... constrain the centre
+			lumps[0].constrain();
+			
+			break;
+			
+		default:
+			break;
+	}
+}
+
+//--------------------------------------------------------------
+
+void SpiderMesh::makeDefaultScanPath(){
+	
+	cout << "!!spider makeDefaultScanPath\n";
+    
+    // add a ring to the path
+    int ringNo = ceil(numRings/2.0);
+    int springno = 0, lumpno = 0;
+    for(int i = 0; i < numSpokes; i++){
+        lumpno = numSpokes*ringNo + 1 + i;
+        springno = numSpokes*ringNo + i;
+        scanPath->addElement(&lumps[lumpno], &springs[springno]);
+    }
+    cout << "!!spider scan path created on ring " << ringNo << "\n";
+     
+    // add a spoke to the path
+/*
+    int springno = 0, lumpno = 0;
+    
+    int springsinrings = numSpokes*numRings;
+    for(int i = 0; i < numRings-1; i++){
+        lumpno = numSpokes*i; // zero, numspokes etc
+        springno = springsinrings + numSpokes*i; // starts from total rings, increments by numspokes etc
+        scanPath->addElement(&lumps[lumpno], &springs[springno]);
+    }    
+*/
+ }
+
+
+//--------------------------------------------------------------
+/****************************************/
+//		Holed Spider    Mesh            */
+/****************************************/
+//--------------------------------------------------------------
+HoledSpiderMesh::HoledSpiderMesh(int aNumSpokes,int aNumRings) : Mesh(){
+	numSpokes = aNumSpokes;
+	numRings = aNumRings;
+    radial = true;
+	
+	makeComponents(numSpokes, numRings);
+	
+	setLumpPositions();
+	
+	makeConnections();
+    
+	makeDefaultScanPath();
+    
+    constrain(0.0,0.0,CONSTRAIN_EDGES);
+    constrain(0.0,0.0,CONSTRAIN_CORNERS);
+	
+	cout << "constructed SPIDER mesh\n";
+}
+
+void HoledSpiderMesh::makeComponents(int aDimension1, int aDimension2){
+    // make componets creates the correct number of lumps and springs according to dimensions and mesh type
+	cout << "HOLED spider makeComponents\n";
+	numLumps = numSpokes * numRings; 
+	numSprings = numSpokes * numRings + numSpokes * (numRings - 1);
+	
+	lumps = new Lump[numLumps];
+	cout << "made " << numLumps << " lumps\n";
+	springs = new Spring[numSprings];
+	cout << "made " << numSprings << " springs\n";	
+	
+    
+}
+//--------------------------------------------------------------
+void HoledSpiderMesh::setLumpPositions(){
+	cout << "HOLED spider setLumpPositions\n";
+	// here we're assuming that zero spoke is to right
+	// work out sines / cosines for each spoke
+	int l = 0;
+	double hyplen,xpos,ypos;
+	
+	double * cosines = new double[numSpokes];
+	double * sines = new double[numSpokes];
+	double angle = 2 * PI / numSpokes;
+	
+	double ringSpacing = 1.0/(2*numRings);
+	
+	for(int spoke = 0; spoke < numSpokes; spoke++){
+		cosines[spoke] = cos(angle*spoke);
+		sines[spoke] = sin(angle*spoke);
+	}
+    
+	for(int ring = 0; ring < numRings; ring++){
+		for(int spoke = 0; spoke < numSpokes; spoke++){
+			hyplen = ringSpacing * (ring+1);
+			xpos = hyplen * cosines[spoke] + 0.5;
+			ypos = hyplen*sines[spoke] + 0.5;
+			lumps[l].setPosition(xpos,ypos);
+			l++;
+			
+		}
+	}
+	delete [] cosines;
+	delete [] sines;
+    
+}
+//--------------------------------------------------------------
+void HoledSpiderMesh::makeConnections(){
+	// TODO needs to be rewritten to automatically 
+	// add in lumps and springs AS we're connecting
+	//using std vector
+	// this is dumb
+	cout << "HOLED spider make connections\n";
+    
+	// UNLIKE a spider we're going to make rings first, to make indexing the scan path easier...
+    
+	for(int ring = 0; ring < numRings; ring++){
+		for(int spoke = 0; spoke < numSpokes-1; spoke++){
+			//		spring ,				lump
+			connect(numSpokes*ring + spoke, ring*numSpokes + spoke + 0);
+			connect(numSpokes*ring + spoke, ring*numSpokes + spoke + 1);
+		}
+		// remember the last one on the ring goes back to first
+		//		spring ,						lump
+		connect(numSpokes*ring + numSpokes - 1 , ring*numSpokes + numSpokes - 1);
+		connect(numSpokes*ring + numSpokes - 1,  ring*numSpokes);
+	}	
+	// at which point we have used numSpokes*numRings springs
+	int used = numSpokes*numRings;
+	// now do the rest of the radial spokes
+	for(int ring = 0; ring < numRings-1; ring++){
+		for(int spoke = 0; spoke < numSpokes; spoke++){
+			connect(used,ring*numSpokes+spoke); // 
+			connect(used,(ring+1)*numSpokes+spoke);  // going to next ring out
+			springs[used].setRestLength(0.0);
+			used++;
+		}
+	}
+}
+
+//--------------------------------------------------------------
+void HoledSpiderMesh::constrain(double ax, double ay, constrainMode aMode){
+	cout << "HOLED spider constrain\n";
+	
+	TwoVector diff;
+	int i = 0;
+	int startLump = 0;
+	// check if lump is within grab range
+	switch (aMode) {
+		case CONSTRAIN_GRAB_REGION:
+			for(i = 0; i<numLumps; i++){
+				
+				diff.setCoord(lumps[i].position.x - ax, lumps[i].position.y - ay);
+				
+				if (diff.norm() < GRAB_RANGE){
+					lumps[i].constrain();
+				}
+			}
+			break;
+		case CONSTRAIN_EDGES:
+		{
+			// constrain outer ring
+			startLump = numSpokes*(numRings-1);
+			for(int l = startLump; l < startLump + numSpokes; l++){
+				lumps[l].constrain();
+			}
+            
+			break;
+		}
+		case CONSTRAIN_CORNERS:
+			// constrain inner ring
+			
+			for(int l = startLump; l < startLump + numSpokes; l++){
+				lumps[l].constrain();
+			}
+            
+			break;
+			
+		default:
+			break;
+	}
+}
+//--------------------------------------------------------------
+
+void HoledSpiderMesh::makeDefaultScanPath(){
+	
+	cout << "HOLED spider makeDefaultScanPath\n";
+    
+     // add a ring to the path
+     int ringNo = floor(numRings/2.0);
+     int springno = 0, lumpno = 0;
+     for(int i = 0; i < numSpokes; i++){
+         lumpno = numSpokes*ringNo + i;
+         springno = numSpokes*ringNo + i;
+         
+         scanPath->addElement(&lumps[lumpno], &springs[springno]);
+     }
+     cout << "HOLED spider scan path created on ring " << ringNo << "\n";
+     
+    // add a spoke to the path
+    /*
+    int springno = 0, lumpno = 0;
+    
+    int springsinrings = numSpokes*numRings;
+    for(int i = 0; i < numRings-1; i++){
+        lumpno = numSpokes*i; // zero, numspokes etc
+        springno = springsinrings + numSpokes*i; // starts from total rings, increments by numspokes etc
+        scanPath->addElement(&lumps[lumpno], &springs[springno]);
+    } 
+     */
+}
+
+//--------------------------------------------------------------
+/****************************************/
+//		Holed Spider Cross   Mesh            */
+/****************************************/
+//--------------------------------------------------------------
+SpiderCrossMesh::SpiderCrossMesh(int aNumSpokes,int aNumRings) : Mesh(){
+	numSpokes = aNumSpokes;
+	numRings = aNumRings;
+    radial = true;
+	
+	makeComponents(numSpokes, numRings);
+	
+	setLumpPositions();
+	
+	makeConnections();
+    
+	makeDefaultScanPath();
+    
+    constrain(0.0,0.0,CONSTRAIN_EDGES);
+    constrain(0.0,0.0,CONSTRAIN_CORNERS);
+	
+	cout << "constructed SpiderCrossMesh mesh\n";
+}
+
+void SpiderCrossMesh::makeComponents(int aDimension1, int aDimension2){
+    // make componets creates the correct number of lumps and springs according to dimensions and mesh type
+	cout << "SpiderCrossMesh spider makeComponents\n";
+	numLumps = numSpokes * numRings; 
+                // rings                // spokes  & diags
+	numSprings = numSpokes * numRings + 3 * numSpokes * (numRings - 1);
+	
+	lumps = new Lump[numLumps];
+	cout << "made " << numLumps << " lumps\n";
+	springs = new Spring[numSprings];
+	cout << "made " << numSprings << " springs\n";	
+	
+    
+}
+//--------------------------------------------------------------
+void SpiderCrossMesh::setLumpPositions(){
+	cout << "SpiderCrossMesh spider setLumpPositions\n";
+	// here we're assuming that zero spoke is to right
+	// work out sines / cosines for each spoke
+	int l = 0;
+	double hyplen,xpos,ypos;
+	
+	double * cosines = new double[numSpokes];
+	double * sines = new double[numSpokes];
+	double angle = 2 * PI / numSpokes;
+	
+	double ringSpacing = 1.0/(2*numRings);
+	
+	for(int spoke = 0; spoke < numSpokes; spoke++){
+		cosines[spoke] = cos(angle*spoke);
+		sines[spoke] = sin(angle*spoke);
+	}
+    
+	for(int ring = 0; ring < numRings; ring++){
+		for(int spoke = 0; spoke < numSpokes; spoke++){
+			hyplen = ringSpacing * (ring+1);
+			xpos = hyplen * cosines[spoke] + 0.5;
+			ypos = hyplen*sines[spoke] + 0.5;
+			lumps[l].setPosition(xpos,ypos);
+			l++;
+			
+		}
+	}
+	delete [] cosines;
+	delete [] sines;
+    
+}
+//--------------------------------------------------------------
+void SpiderCrossMesh::makeConnections(){
+
+	cout << "SpiderCrossMesh spider make connections\n";
+    
+	// UNLIKE a spider we're going to make rings first, to make indexing the scan path easier...
+    
+	for(int ring = 0; ring < numRings; ring++){
+		for(int spoke = 0; spoke < numSpokes-1; spoke++){
+			//		spring ,				lump
+			connect(numSpokes*ring + spoke, ring*numSpokes + spoke + 0);
+			connect(numSpokes*ring + spoke, ring*numSpokes + spoke + 1);
+		}
+		// remember the last one on the ring goes back to first
+		//		spring ,						lump
+		connect(numSpokes*ring + numSpokes - 1 , ring*numSpokes + numSpokes - 1);
+		connect(numSpokes*ring + numSpokes - 1,  ring*numSpokes);
+	}	
+	// at which point we have used numSpokes*numRings springs
+	int used = numSpokes*numRings;
+    
+	// now do radial spokes
+	for(int ring = 0; ring < numRings-1; ring++){
+		for(int spoke = 0; spoke < numSpokes; spoke++){
+			connect(used,ring*numSpokes+spoke); // 
+			connect(used,(ring+1)*numSpokes+spoke);  // going to next ring out
+			springs[used].setRestLength(0.0);
+			used++;
+		}
+	}
+    // now do diag /
+    for(int ring = 0; ring < numRings-1; ring++){
+		for(int spoke = 0; spoke < numSpokes-1; spoke++){
+			connect(used,ring*numSpokes+spoke,(ring+1)*numSpokes+spoke+1); // 
+			springs[used].setRestLength(0.0);
+			used++;
+		}
+        connect(used,ring*numSpokes+numSpokes-1,(ring+1)*numSpokes); // wrap to first 
+        springs[used].setRestLength(0.0);
+        used++;
+	}
+    
+    // now do \ diag
+    for(int ring = 0; ring < numRings-1; ring++){
+        connect(used,ring*numSpokes,(ring+1)*numSpokes+numSpokes-1); // wrap first to last 
+        springs[used].setRestLength(0.0);
+        used++;
+		for(int spoke = 1; spoke < numSpokes; spoke++){
+			connect(used,ring*numSpokes+spoke,(ring+1)*numSpokes+spoke-1); // 
+			springs[used].setRestLength(0.0);
+			used++;
+		}
+
+	}
+    cout << "used: " << used << " numSprings: " << numSprings << endl;
+    
+}
+
+//--------------------------------------------------------------
+void SpiderCrossMesh::constrain(double ax, double ay, constrainMode aMode){
+	cout << "SpiderCrossMesh spider constrain\n";
+	
+	TwoVector diff;
+	int i = 0;
+	int startLump = 0;
+	// check if lump is within grab range
+	switch (aMode) {
+		case CONSTRAIN_GRAB_REGION:
+			for(i = 0; i<numLumps; i++){
+				
+				diff.setCoord(lumps[i].position.x - ax, lumps[i].position.y - ay);
+				
+				if (diff.norm() < GRAB_RANGE){
+					lumps[i].constrain();
+				}
+			}
+			break;
+		case CONSTRAIN_EDGES:
+		{
+			// constrain outer ring
+			startLump = numSpokes*(numRings-1);
+			for(int l = startLump; l < startLump + numSpokes; l++){
+				lumps[l].constrain();
+			}
+            
+			break;
+		}
+		case CONSTRAIN_CORNERS:
+			// constrain inner ring
+			
+			for(int l = startLump; l < startLump + numSpokes; l++){
+				lumps[l].constrain();
+			}
+            
+			break;
+			
+		default:
+			break;
+	}
+}
+//--------------------------------------------------------------
+
+void SpiderCrossMesh::makeDefaultScanPath(){
+	
+	cout << "SpiderCrossMesh spider makeDefaultScanPath\n";
+    
+    // add a ring to the path
+    int ringNo = floor(numRings/2.0);
+    int springno = 0, lumpno = 0;
+    for(int i = 0; i < numSpokes; i++){
+        lumpno = numSpokes*ringNo + i;
+        springno = numSpokes*ringNo + i;
+        scanPath->addElement(&lumps[lumpno], &springs[springno]);
+    }
+    cout << "SpiderCrossMesh spider scan path created on ring " << ringNo << "\n";
+    
+    // add a spoke to the path
+    /*
+     int springno = 0, lumpno = 0;
+     
+     int springsinrings = numSpokes*numRings;
+     for(int i = 0; i < numRings-1; i++){
+     lumpno = numSpokes*i; // zero, numspokes etc
+     springno = springsinrings + numSpokes*i; // starts from total rings, increments by numspokes etc
+     scanPath->addElement(&lumps[lumpno], &springs[springno]);
+     } 
+     */
+}
+
+/****************************************/
+//		Line    Mesh					*/
+/****************************************/
+
+LineMesh::LineMesh(int aNumSegments) : Mesh(){
+	numSegments = aNumSegments;
+    radial = false;
+    
+	makeComponents(aNumSegments, 0);
+	
+	setLumpPositions();
+	
+	makeConnections();
+    
+	makeDefaultScanPath();
+    
+    constrain(0.0,0.0,CONSTRAIN_EDGES);
+    
+    setPropagationSpeed(0.6);
+	
+	cout << "constructed LINE mesh\n";
+	
+}
+//--------------------------------------------------------------
+void LineMesh::makeComponents(int aDimension1, int aDimension2){
+    // make componets creates the correct number of lumps and springs according to dimensions and mesh type
+	cout << "Line class makeComponents\n";
+    // 
+    numLumps = numSegments+1;
+    numSprings = numSegments;
+    lumps = new Lump[numLumps];
+	springs = new Spring[numSprings];
+    
+}
+//--------------------------------------------------------------
+void LineMesh::setLumpPositions(){
+	cout << "LineMesh class setLumpPositions\n";
+    // 
+    double hspacing = (1.0 - 0.05)/numSegments;
+    for(int i = 0; i < numLumps; i++){
+        lumps[i].setPosition(0.025+hspacing*i, 0.5);
+    }
+}
+//--------------------------------------------------------------
+void LineMesh::makeConnections(){
+    // connects the lmps and springs. the spring needs to know which lumps are at each end
+    // and lumps need to know which springs attached
+    // calls connect method that does this
+	cout << "LineMesh class make connections\n";
+    for(int i = 0; i < numSegments; i++){
+        connect(i,i);
+        connect(i,i+1);
+    }
+}
+
+//--------------------------------------------------------------
+void LineMesh::constrain(double ax, double ay, constrainMode aMode){
+	cout << "LineMesh class constrain\n";
+    if (aMode == CONSTRAIN_EDGES || aMode == CONSTRAIN_CORNERS){
+        lumps[0].constrain();
+        lumps[numLumps-1].constrain();
+    }else if (aMode == CONSTRAIN_GRAB_REGION){
+        TwoVector diff;
+        for(int i = 0; i<numLumps; i++){
+            
+            diff.setCoord(lumps[i].position.x - ax, lumps[i].position.y - ay);
+            
+            if (diff.norm() < GRAB_RANGE){
+                lumps[i].constrain();
+            }
+        }
+    }
+}
+//--------------------------------------------------------------
+//--------------------------------------------------------------
+void LineMesh::makeDefaultScanPath(){
+	
+	cout << "LineMesh class makeDefaultScanPath\n";
+    for(int i = 0; i < numSegments; i++){
+        scanPath->addElement(&lumps[i], &springs[i]);
+    }
+}
+//--------------------------------------------------------------
+/****************************************/
+//		GROUNDED LINE  Mesh					*/
+/****************************************/
+
+GroundedLineMesh::GroundedLineMesh(int aNumSegments) : Mesh(){
+	numSegments = aNumSegments;
+    radial = false;
+	makeComponents(aNumSegments, 0);
+	
+	setLumpPositions();
+	
+	makeConnections();
+    
+	makeDefaultScanPath();
+    
+    constrain(0.0,0.0,CONSTRAIN_ALL_X);
+    
+    setPropagationSpeed(0.6);
+	
+	cout << "constructed GROUNDED LINE mesh\n";
+	
+}
+//--------------------------------------------------------------
+void GroundedLineMesh::makeComponents(int aDimension1, int aDimension2){
+    // make componets creates the correct number of lumps and springs according to dimensions and mesh type
+    
+    // THIS IS CRAP should be able to just create new ones and attach them...?
+	cout << "Line class makeComponents\n";
+    // 
+    numLumps = (numSegments+1)*2;
+    numSprings = numSegments*2;
+    lumps = new Lump[numLumps];
+	springs = new Spring[numSprings];
+    
+}
+//--------------------------------------------------------------
+void GroundedLineMesh::setLumpPositions(){
+	cout << "LineMesh class setLumpPositions\n";
+    // 
+
+    double hspacing = (1.0 - 0.05)/numSegments;
+    for(int i = 0; i < (numSegments+1); i++){
+        // moving ones
+        lumps[i].setPosition(0.025+hspacing*i, 0.5);
+        //grounded ones
+        lumps[i + numSegments+1].setPosition(0.025+hspacing*i,0.5*cos(i*hspacing*PI) + 0.5 );
+    }
+    
+
+
+}
+//--------------------------------------------------------------
+void GroundedLineMesh::makeConnections(){
+    // connects the lmps and springs. the spring needs to know which lumps are at each end
+    // and lumps need to know which springs attached
+    // calls connect method that does this
+	cout << "LineMesh class make connections\n";
+    for(int i = 0; i < numSegments; i++){
+        connect(i,i,i+1);
+        connect(i + numSegments,i , i + numSegments+1);
+
+    }
+
+    setRestLength();
+}
+
+//--------------------------------------------------------------
+void GroundedLineMesh::constrain(double ax, double ay, constrainMode aMode){
+	cout << "LineMesh class constrain\n";
+    if (aMode == CONSTRAIN_EDGES || aMode == CONSTRAIN_CORNERS){
+        lumps[0].constrain();
+        lumps[numLumps-1].constrain();
+        
+        for(int i = numSegments; i < numLumps; i++){
+            lumps[i].constrain();
+        }
+    }
+
+    if (aMode == CONSTRAIN_GRAB_REGION){
+        TwoVector diff;
+        for(int i = 0; i<numLumps; i++){
+            
+            diff.setCoord(lumps[i].position.x - ax, lumps[i].position.y - ay);
+            
+            if (diff.norm() < GRAB_RANGE){
+                lumps[i].constrain();
+            }
+        }
+    }
+    if (aMode == CONSTRAIN_ALL_X){
+
+        
+        for(int i = 0; i<numSegments; i++){
+
+            lumps[i].constrain(Lump::CONSTRAIN_X);
+        }
+        for(int i = numSegments; i < numLumps; i++){
+            lumps[i].constrain();
+        }
+        lumps[0].constrain();
+        lumps[numLumps-1].constrain();
+    }
+        
+}
+//--------------------------------------------------------------
+//--------------------------------------------------------------
+void GroundedLineMesh::makeDefaultScanPath(){
+	
+	cout << "LineMesh class makeDefaultScanPath\n";
+    for(int i = 0; i < numSegments; i++){
+        scanPath->addElement(&lumps[i], &springs[i]);
+    }
+}
+//--------------------------------------------------------------
+
+/****************************************/
+//		DROPLET    Mesh					*/
+/****************************************/
+// more of a balloon really - filled with compressible gas
+DropletMesh::DropletMesh(int aNumSegments) : Mesh(){
+    
+    restArea = 1.0;
+    radial = true;
+	numSegments = aNumSegments;
+    
+	makeComponents(aNumSegments, 0);
+	
+	setLumpPositions();
+	
+	makeConnections();
+    
+	makeDefaultScanPath();
+    
+    //constrain(0.0,0.0,CONSTRAIN_EDGES);
+    
+    setPropagationSpeed(0.8);
+    
+    zeroRestLength();
+    globalForces.pressureAmt = 0.4;
+	
+	cout << "constructed DropletMesh mesh\n";
+	
+}
+//--------------------------------------------------------------
+void DropletMesh::makeComponents(int aDimension1, int aDimension2){
+    // make componets creates the correct number of lumps and springs according to dimensions and mesh type
+	cout << "Line class makeComponents\n";
+    // 
+    numLumps = numSegments;
+    numSprings = numSegments;
+    lumps = new Lump[numLumps];
+	springs = new PressureSpring[numSprings];
+    
+}
+//--------------------------------------------------------------
+void DropletMesh::setLumpPositions(){
+	cout << "DropletMesh class setLumpPositions\n";
+    // make a circle centred around 0.5,0.5 radius 0.4
+	int l = 0;
+	double hyplen,xpos,ypos;
+	
+	double * cosines = new double[numSegments];
+	double * sines = new double[numSegments];
+	double angle = 2 * PI / numSegments;
+    
+	for(int spoke = 0; spoke < numSegments; spoke++){
+		cosines[spoke] = cos(angle*spoke);
+		sines[spoke] = sin(angle*spoke);
+	}
+    
+    hyplen = 0.2; // aka radius
+    
+    // 
+	//lumps[0].setPosition(0.5,0.5);
+    
+    for(int spoke = 0; spoke < numSegments; spoke++){
+        
+        xpos = hyplen * cosines[spoke] + 0.5;
+        ypos = hyplen*sines[spoke] + 0.5;
+        lumps[l].setPosition(xpos,ypos);
+        l++;
+        
+    }
+    calculateCentre();
+    restArea = calculateArea() * 3; // this is like the amount of air in there originally, inflate a bit !
+    
+	delete [] cosines;
+	delete [] sines;
+}
+//--------------------------------------------------------------
+void DropletMesh::makeConnections(){
+    // connects the lmps and springs. the spring needs to know which lumps are at each end
+    // and lumps need to know which springs attached
+    // calls connect method that does this
+	cout << "DropletMesh class make connections\n";
+    for(int i = 0; i < numSegments-1; i++){
+        connect(i,i,i+1);
+    }
+    // finally connect end spring to start lump
+    connect(numSegments-1,numSegments-1);
+    connect(numSegments-1,0);
+}
+
+//--------------------------------------------------------------
+void DropletMesh::constrain(double ax, double ay, constrainMode aMode){
+	cout << "DropletMesh class constrain\n";
+    if (aMode == CONSTRAIN_EDGES || aMode == CONSTRAIN_CORNERS){
+        lumps[0].constrain();
+        lumps[numLumps-1].constrain();
+    }  else  if (aMode == CONSTRAIN_GRAB_REGION){
+        TwoVector diff;
+        for(int i = 0; i<numLumps; i++){
+            
+            diff.setCoord(lumps[i].position.x - ax, lumps[i].position.y - ay);
+            
+            if (diff.norm() < GRAB_RANGE){
+                lumps[i].constrain();
+            }
+        }
+    }
+}
+//--------------------------------------------------------------
+//--------------------------------------------------------------
+void DropletMesh::makeDefaultScanPath(){
+	
+	cout << "DropletMesh class makeDefaultScanPath\n";
+    for(int i = 0; i < numSegments; i++){
+        scanPath->addElement(&lumps[i], &springs[i]);
+    }
+    cout << "DropletMesh class makeDefaultScanPath\n";
+}
+//--------------------------------------------------------------
+double DropletMesh::calculateArea(){
+    
+    // how do we calculate area?? answer: average all radii !
+    
+    // calc average radius magnitude
+    double radiusMag = 0.0, avRadiusMag = 0.0;
+    for(int i = 0; i < numLumps; i++){
+        TwoVector radius;
+        radius.setCoord(centre.x - lumps[i].position.x, centre.y - lumps[i].position.y );
+        radiusMag = radius.norm();
+        avRadiusMag += radiusMag;
+    }
+    avRadiusMag /= numLumps;
+    
+    // calc area
+    double area = 2 * PI * avRadiusMag*avRadiusMag;
+    
+    return area;
+    
+}
+
+double DropletMesh::calculatePressure(){
+    // work out pressure on the springs. 
+    // this will be proportional to the difference in the rest area to the current area
+    //this will exert a force proportional to length
+    
+    double da = restArea - calculateArea();
+    // if current area is smaller this will create positive pressure
+    
+    globalForces.setPressure(da);
+    return globalForces.pressure;
+    
+}
+//--------------------------------------------------------------
+void DropletMesh::update(){
+
+    calculateCentre();
+    calculatePressure();
+    Mesh::update(); // schweet
+    
+}
+//--------------------------------------------------------------
+
+/****************************************/
+//		TriangleMesh					*/
+/****************************************/
+//--------------------------------------------------------------
+TriangleMesh::TriangleMesh(int height, int width): Mesh() {
+	radial = false;
+	makeComponents(height, width);
+	
+	setLumpPositions();
+	
+	makeConnections();
+	
+	makeDefaultScanPath();
+    
+    constrain(0,0,CONSTRAIN_EDGES);
+    
+    zeroRestLength();
+	
+	cout << "constructing TRIANGLE mesh\n";
+}
+//--------------------------------------------------------------
+// make componets for tri
+void TriangleMesh::makeComponents(int aheight, int awidth){
+	height = aheight;
+	width = awidth;
+	numLumps = height * width;
+                /* horz   ---            \ diag                  / diag              */
+	numSprings = (width-1) * (height-1) +  width * (height - 1) + (width-1) * (height - 1);
+	
+	lumps = new Lump[numLumps];
+	cout << "made " << numLumps << " lumps\n";
+	springs = new Spring[numSprings];
+	cout << "made " << numSprings << " springs\n";
+}
+
+//--------------------------------------------------------------
+void TriangleMesh::setLumpPositions(){
+	double vspacing = 1.0/(height-1);
+	double hspacing = 1.0/(width - 0.5);
+	int i =0;
+	// set positions
+	for(int row = 0; row < height; row++){ // go down column
+		for(int col = 0;col < width; col++){ // go along row
+            // if even row start from wall
+            if(row%2){ // odd
+                lumps[i].setPosition(hspacing*col + 0.5*hspacing,vspacing*row);
+			}else{ // even
+                lumps[i].setPosition(hspacing*col,vspacing*row);
+            }
+			i++;
+		}
+	}
+}
+//--------------------------------------------------------------
+void TriangleMesh::makeConnections(){
+	
+	// could be cleverer in terms of numbering?
+	int lumpnum = 0;
+	int springnum = 0;
+    
+	// attach horizontal a
+	for ( int row=1;row< height;row++){
+		for(int col=0;col<width-1;col++){
+			connect(springnum,lumpnum,lumpnum+1);
+            lumpnum++;
+            springnum++;
+		}
+        lumpnum++;
+	}
+	
+    /* now do /\/\/\/\/\/\/\ */
+	lumpnum = 0;
+	for ( int row=0;row<height-1;row++){
+        
+        if(row%2 == 0){ 
+            // even ie 0
+            /* start with \  diag */
+            for(int col=0;col<width-1;col++){
+                /* \ */
+                connect(springnum++, lumpnum, lumpnum+ width);
+                lumpnum++;
+                /* / */
+                connect(springnum++, lumpnum, lumpnum+(width-1));
+                
+            }
+            connect(springnum++, lumpnum, lumpnum+width);
+            lumpnum++;
+            
+        }else{         // odd
+            for(int col=0;col<width-1;col++){
+                /* / */
+                connect(springnum++, lumpnum, lumpnum+ width);
+                /* \ */
+                connect(springnum++, lumpnum, lumpnum+(width+1));
+                lumpnum++;
+            }
+            // last r to l
+            /* / */
+            connect(springnum++, lumpnum, lumpnum+(width));
+            lumpnum++;
+            
+        }
+    }
+    
+}	
+
+
+
+//--------------------------------------------------------------
+void TriangleMesh::constrain(double ax, double ay, constrainMode aMode){
+	// exactly the same as square
+	TwoVector diff;
+	int i = 0;
+	
+	// check if lump is within grab range
+	switch (aMode) {
+		case CONSTRAIN_GRAB_REGION:
+			for(i = 0; i<numLumps; i++){
+				
+				diff.setCoord(lumps[i].position.x - ax, lumps[i].position.y - ay);
+				
+				if (diff.norm() < GRAB_RANGE){
+					lumps[i].constrain();
+				}
+			}
+			break;
+		case CONSTRAIN_EDGES:
+			i = 0;
+			for(int row = 0; row < height; row++){ // go down column
+				for(int col = 0;col < width; col++){ // go along row
+					if(row == 0 || row == height-1 || col == 0 || col == width-1){
+						lumps[i].constrain();
+					}
+					i++;
+				}
+			}	
+			break;
+		case CONSTRAIN_CORNERS:
+			i = 0;
+			for(int row = 0; row < height; row++){ // go down column
+				for(int col = 0;col < width; col++){ // go along row
+					if(   (row == 0 && col == 0)
+					   || (row == height-1 && col == 0) 
+					   || (row == 0 && col == width-1) 
+					   || (row == height-1 && col == width-1)){
+						lumps[i].constrain();
+					}
+					i++;
+				}
+			}
+			break;
+			
+		default:
+			break;
+	}
+	
+}
+//--------------------------------------------------------------
+//--------------------------------------------------------------
+void TriangleMesh::makeDefaultScanPath(){
+
+    int vmarg = 4;
+    int hmarg = 4;
+    
+    int lumpno = vmarg * width + hmarg; // top left corner
+    int springno = vmarg * (width - 1) + hmarg;
+    // do top horz
+    for(int i = 0; i < width - 2*hmarg; i++){
+        scanPath->addElement(&lumps[lumpno], &springs[springno]);
+        lumpno++;
+        springno++;
+    }
+
+    
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scanpath.h	Mon Nov 19 13:00:42 2012 +0000
@@ -0,0 +1,76 @@
+/*
+ *  scanpath.h
+ *  springstructure
+ *
+ *  Created by Robert Tubb on 09/06/2011.
+ *  Copyright 2011 __MyCompanyName__. All rights reserved.
+ *
+ */
+
+#ifndef _SCANPATHH
+#define _SCANPATHH
+#include "lump.h"
+#include "spring.h"
+#include <vector>
+
+
+class ScanPath{
+private:
+	int numElements;
+    int maxElements;
+    
+    bool audioAccessing;
+    bool updateAccessing;
+
+	//Spring ** springPath;
+	// springpath - an array of pointers to the springs in the path
+	// lump path - ditto should be same number 
+	Lump ** lumpPath;
+    Spring ** springPath;
+    
+    double * wavetableNew;
+    double * wavetableOld;
+    double * wavetableUpdate;
+
+    double currentLength;
+    
+    double frameInterpolator;
+    double framesPerSample;
+    
+    
+
+    
+public:
+    enum scanModes {DISPLACEMENT,SPEED,SPRING_FORCE,YPOS};
+
+    scanModes scanMode;
+    double restLength;
+    
+	ScanPath();
+	~ScanPath();
+    
+    
+	void inscribe(double ax,double ay);
+	void clear();
+    
+    void draw();
+    void drawCubic();
+	
+    double getTotalLength();
+    int howManyElements();
+    
+
+	void addSpring();
+	void addLump();
+    double getNextSample();
+    double getNextSample(double aPhasor);
+    double getNextSampleCubic(double aPhasor);
+    // redone stuff
+    void initWavetables();
+    void addElement(Lump* aLump, Spring * aSpring);
+
+    void updateWavetables();
+
+};
+
+#endif
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scanpath.mm	Mon Nov 19 13:00:42 2012 +0000
@@ -0,0 +1,336 @@
+/*
+ *  scanpath.cpp
+ *  springstructure
+ *
+ *  Created by Robert Tubb on 09/06/2011.
+ *  Copyright 2011 __MyCompanyName__. All rights reserved.
+ *
+ */
+#include <vector>
+#include "scanpath.h"
+#include "testApp.h"
+
+//----------------------------------------------------------------
+ScanPath::ScanPath() : maxElements(2048) {
+	// construct an empty scanpath
+	// an ' element ' consists of 1 lump and one spring
+
+    
+	numElements = 0; // numElements is a dynamic running count of elements when building - a bit crap
+    springPath = new Spring*[maxElements];
+    lumpPath = new Lump*[maxElements];
+    
+    framesPerSample = ofGetFrameRate()/SAMPLE_RATE;
+    frameInterpolator = 0.0;
+    currentLength = 0.0;
+    restLength = 1.0; // ?
+
+    scanMode = DISPLACEMENT;
+    
+    initWavetables();
+
+}
+//----------------------------------------------------------------
+ScanPath::~ScanPath(){
+	delete [] springPath;
+    delete [] lumpPath;
+    delete [] wavetableNew;
+    delete [] wavetableOld;
+    delete [] wavetableUpdate;
+    cout << "destructed scanpath\n"; 
+}
+
+void ScanPath::clear(){
+    for(int i = 0; i<numElements; i++){
+        springPath[i]->removeFromScanPath();
+        lumpPath[i]->removeFromScanPath();
+    }
+    numElements = 0;
+    // cant work?
+}
+int ScanPath::howManyElements(){
+    return numElements;
+}
+//----------------------------------------------------------------
+void ScanPath::inscribe(double ax, double ay){
+	// look at coordinates, add the closest lump and it's connecting string 
+	// if we're further away from current lump
+	
+	// check end points of connecting springs, pick closest
+}
+void ScanPath::draw(){
+    // draw the actual waveform in the corner
+
+    int width = 768;
+    int height = 128;
+    double sampval = 0.0;
+    int leftsampnum = 0;
+    int rightsampnum = 0;
+    float sampscale = 0.0, prevsampscale = 0.0, interp = 0.0;
+
+     ofSetColor(0, 0, 0);
+    double step = double(numElements)/width; // how much we are stepping thru wave per pixel
+    for(int i = 0; i < width; i++){
+        
+        leftsampnum = floor(i * step); // basic nearest neighbour interpolation
+        rightsampnum = ceil(i*step);
+        interp  = (i*step)-leftsampnum;
+        if(rightsampnum < numElements){
+            sampval = (1 - interp)*wavetableNew[leftsampnum] + interp*wavetableNew[rightsampnum];
+        }
+        sampscale = (sampval * 700) + height/2.0; // centre and scale
+        ofSetLineWidth(2);
+        ofLine(sampscale, i, prevsampscale, i-1); // draw a line from pixel to pixel (?)
+        prevsampscale = sampscale;
+    }
+    
+}
+void drawCubic(){
+    
+}
+//----------------------------------------------------------------
+// add spring
+void ScanPath::addSpring(){
+	// see add element
+}
+//----------------------------------------------------------------
+// add lump
+void ScanPath::addLump(){
+    // see add element
+}
+//----------------------------------------------------------------
+// add lump
+double ScanPath::getTotalLength(){
+    // for interesting modulations...
+    currentLength = 0.0;
+    for(int i = 0; i < numElements; i++){
+        currentLength += springPath[i]->getLength();
+    }
+    return currentLength;
+    
+}
+//----------------------------------------------------------------
+void ScanPath::initWavetables(){
+    wavetableNew = new double[maxElements];
+    wavetableOld = new double[maxElements];
+    wavetableUpdate = new double[maxElements];
+    
+    for(int i = 0; i < maxElements; i++){
+        wavetableOld[i] = 0.0;
+        wavetableNew[i] = 0.0;
+        wavetableUpdate[i] = 0.0;
+    }
+    
+}
+//----------------------------------------------------------------
+void ScanPath::addElement(Lump* aLump, Spring * aSpring){
+    // insert ptr to the lump and spring into array
+    if(numElements >= maxElements){
+        cerr << "cannot add any more to scanpath - max elements: 2048\n";
+        return;
+    }
+    lumpPath[numElements] = aLump;
+    springPath[numElements] = aSpring;
+    
+    aLump->addToScanPath();
+    aSpring->addToScanPath();
+    
+    numElements++;
+}
+//----------------------------------------------------------------
+void ScanPath::updateWavetables(){
+    // swap old , new
+    double * temp;
+
+    switch(scanMode){
+        case DISPLACEMENT:
+            // now fill with new values
+            for(int i = 0; i < numElements; i++){
+
+                wavetableUpdate[i] = lumpPath[i]->scanRadialDisplacement()/1.5;
+                
+            }
+            break;
+        case SPEED:
+            for(int i = 0; i < numElements; i++){
+                wavetableUpdate[i] = lumpPath[i]->scanLumpSpeed();
+            }
+            break;
+        case SPRING_FORCE:
+            for(int i = 0; i < numElements; i++){
+                wavetableUpdate[i] = springPath[i]->getForceMag();
+            }
+            break;
+        case YPOS:
+            for(int i = 0; i < numElements; i++){
+                wavetableUpdate[i] = lumpPath[i]->scanYPos();
+            }
+            break;
+        default:
+            break;
+            
+          
+    }
+ 
+    // reset the interp between frames
+    if(audioAccessing){
+        cout << "buffers swapped while update!\n";
+    }
+    updateAccessing = true;   
+    temp = wavetableOld;
+    wavetableOld = wavetableNew;
+    wavetableNew = wavetableUpdate;
+    wavetableUpdate = temp;
+    updateAccessing = false;
+    
+    frameInterpolator = 0.0;
+    framesPerSample = 2.0*ofGetFrameRate()/SAMPLE_RATE; // attempt to get a reasonable est. of how fast to interp
+    
+}
+//----------------------------------------------------------------
+// get next sample
+double ScanPath::getNextSample(double aPhasor){
+	// move along path, interpolating between points
+    // move between frames too
+    double alongPath = aPhasor*double(numElements);
+    
+    // indexes for interpolated points
+    int n0 = floor(alongPath);
+    int n1 = n0+1;
+    if(n1 >= numElements){
+        n1 = 0;
+    }
+
+    double frac = alongPath - double(n0);
+    
+    audioAccessing = true;
+    if (updateAccessing){
+        cout << "update is accessing while audio is\n";
+    }
+    double oldsample = (1 - frac) * wavetableOld[n0] + frac * wavetableOld[n1];
+    
+    double newsample = (1 - frac) * wavetableNew[n0] + frac * wavetableNew[n1];
+    
+    audioAccessing = false;
+    
+    frameInterpolator += framesPerSample;
+    if(frameInterpolator >= 1.0){
+        //cout << "frame interp > 1\n";
+        frameInterpolator = 1.0; // just stays outputting new
+    }
+    
+    double sample = (1 - frameInterpolator)*oldsample + frameInterpolator*newsample;
+    //cout << sample << endl;
+    // keep within the bounds of acceptability
+    if(sample > 0.99){
+       // cout << "OUCH\n";
+        sample = 0.99;
+    }else if(sample < -0.99){
+        sample = -0.99;
+    }
+    return sample;
+	
+}
+//----------------------------------------------------------------
+// get next sample
+double ScanPath::getNextSample(){
+	// move along wavetable, no interpolation ie: length of path is pitch
+    static int n = 0;
+    double oldsample = wavetableOld[n];
+    
+    double newsample = wavetableNew[n]; 
+    n++;
+    if (n >= numElements){
+        n = 0;
+    }
+
+    frameInterpolator += framesPerSample;
+    if(frameInterpolator >= 1.0){
+        //cout << "frame interp > 1\n";
+        frameInterpolator = 1.0; // just stays outputting new
+    }
+    
+    double sample = (1 - frameInterpolator)*oldsample + frameInterpolator*newsample;
+    return sample*3.0;
+	
+}
+//----------------------------------------------------------------
+//----------------------------------------------------------------
+// get next sample with cubic interpolation
+double ScanPath::getNextSampleCubic(double aPhasor){
+	// move along path, interpolating between points
+    // move between frames too
+    double alongPath = aPhasor*double(numElements);
+    
+    // indexes for interpolated points
+    int n1 = floor(alongPath);
+    
+    int n0 = n1-1;
+    if(n0 < 0){
+        n0 = numElements;
+    }
+    int n2 = n1+1;
+    if(n2 >= numElements){
+        n2 = 0;
+    }
+    int n3 = n2+1;
+    if(n3 >= numElements){
+        n3 = 0;
+    }
+    double frac = alongPath - double(n0);
+    double fracSquared = frac * frac;
+    double fracCubed = fracSquared * frac;
+    double a0,a1,a2,a3;
+    //cout << n0 << endl;
+    // cubic interp
+    /*
+     double y0,double y1,
+     double y2,double y3,
+     double mu)
+     {
+     double a0,a1,a2,a3,mu2;
+     
+     mu2 = mu*mu;
+     a0 = y3 - y2 - y0 + y1;
+     a1 = y0 - y1 - a0;
+     a2 = y2 - y0;
+     a3 = y1;
+     
+     return(a0*mu*mu2+a1*mu2+a2*mu+a3);
+     */
+    a0 = wavetableOld[n3] - wavetableOld[n2] - wavetableOld[n0] + wavetableOld[n1];
+    a1 = wavetableOld[n0] - wavetableOld[n1] - a0; //  y0 - y1 - a0;
+    a2 = wavetableOld[n2] - wavetableOld[n0]; // y2 - y0;
+    a3 = wavetableOld[n1];
+    
+    double oldsample = a0*fracCubed + a1*fracSquared + a2*frac + a3;
+    
+    a0 = wavetableNew[n3] - wavetableNew[n2] - wavetableNew[n0] + wavetableNew[n1];
+    a1 = wavetableNew[n0] - wavetableNew[n1] - a0; //  y0 - y1 - a0;
+    a2 = wavetableNew[n2] - wavetableNew[n0]; // y2 - y0;
+    a3 = wavetableNew[n1];
+    
+    double newsample = a0*fracCubed + a1*fracSquared + a2*frac + a3;
+    
+    frameInterpolator += framesPerSample;
+    if(frameInterpolator >= 1.0){
+        frameInterpolator = 1.0; // just stays outputting new
+    }
+    
+    double sample = (1 - frameInterpolator)*oldsample + frameInterpolator*newsample;
+    //cout << sample << endl;
+    // keep within the bounds of acceptability
+    
+    // beef up
+    sample = sample*3.0;
+    
+    if(sample > 0.99){
+        sample = 0.99;
+    }else if(sample < -0.99){
+        sample = -0.99;
+    }
+    
+    return sample;
+	
+}
+//----------------------------------------------------------------
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spring.h	Mon Nov 19 13:00:42 2012 +0000
@@ -0,0 +1,78 @@
+/*
+ *  spring.h
+ *  simplespring
+ *
+ *  Created by Robert Tubb on 02/06/2011.
+ *  Copyright 2011 __MyCompanyName__. All rights reserved.
+ *
+ */
+
+
+
+#ifndef _SPRINGH
+#define _SPRINGH
+#include "2dvector.h"
+#include "ofMain.h"
+
+class Lump;
+
+class Spring{
+protected:
+	double restLength;
+	double springConst;
+	
+	static double maxForce;
+	static double minForce;
+	
+	static int numSprings;
+
+	int myIndex;
+    
+    double csquared;
+	
+	TwoVector startPoint;
+	TwoVector endPoint;
+	
+	TwoVector force;
+	Lump * startLumpPtr;
+	Lump * endLumpPtr;
+	
+	bool isInScanPath;
+
+	
+public:
+	static bool forcesOn;
+	
+	Spring();
+	Spring(double aStartx, double aStarty, double aEndx, double aEndy, double aK);
+	void updateEndPoints();
+	void attachLump(Lump * aLump);
+    
+    void calculateForce();
+    virtual TwoVector getForce(Lump * aLump);
+
+	void draw();
+	void setRestLength();
+	void setRestLength(double aLength);
+    void setSpringConstant(double aK);
+    double getLength();
+
+	void addToScanPath();
+    void removeFromScanPath();
+    Lump * getLumpOnOtherEnd(Lump * alump);
+	//void checkStuff();
+    
+    // interface for scanner
+	double getForceMag();
+};
+
+class PressureSpring : public Spring{
+public:
+    PressureSpring();
+    TwoVector getForce(Lump * aLump);
+
+};
+
+#endif
+
+	
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spring.mm	Mon Nov 19 13:00:42 2012 +0000
@@ -0,0 +1,225 @@
+/*
+ *  spring.cpp
+ *  simplespring
+ *
+ *  Created by Robert Tubb on 02/06/2011.
+ *  Copyright 2011 __MyCompanyName__. All rights reserved.
+ *
+ */
+
+#include "spring.h"
+#include "lump.h"
+#include "globalForces.h"
+
+#include <iostream>
+#include "globalUI.h"
+extern GlobalForces globalForces;
+extern GlobalUI globalUI;
+// statics
+int Spring::numSprings = 0;
+double Spring::maxForce = 0.0;
+double Spring::minForce = 0.0;
+bool Spring::forcesOn = true;
+
+//---------------------------------------------------------------
+Spring::Spring(){
+	//cout << "constructing a default spring" << endl;
+	
+	startPoint.setCoord(0.0,0.0); // dummy values - shouldn't get used
+	endPoint.setCoord(0.0,0.0);
+	
+	springConst = 0.3; // not needed - assume it's one and use 1/c^2 for mass
+    
+	isInScanPath = false;
+	
+	startLumpPtr = 0;
+	endLumpPtr = 0;
+	restLength = 0.0;
+	myIndex = Spring::numSprings++;
+	maxForce = 0.005;
+	minForce = -0.005;
+	
+	force.x = 0.0;
+	force.y = 0.0;
+	
+	// width of a spring is proportional to how beefy it is
+	//ofSetLineWidth(springConst*2);
+	
+}
+//---------------------------------------------------------------
+Spring::Spring(double aStartx, double aStarty, double aEndx, double aEndy, double aK){
+	// hfsjsfhfshsgr
+
+
+}
+//---------------------------------------------------------------
+void Spring::attachLump(Lump * aLump){
+	// set position of spring start to mass position
+	if(startLumpPtr == 0){
+		startLumpPtr = aLump;
+		
+	}else if(endLumpPtr == 0){
+		endLumpPtr = aLump;
+		//cout << "----------------------------------------end point attached to " << endLumpPtr << endl;
+		updateEndPoints(); // now both ends attached - set the spring start and end points to the relevant lumps
+        setRestLength(); // the default length for the spring can be set
+	}else{
+		cerr << "PERROR can't attach lump to this spring cos it's already attached both ends" << endl;
+	}
+}
+Lump * Spring::getLumpOnOtherEnd(Lump * alump){
+    if (startLumpPtr == alump){
+        return endLumpPtr;
+        
+    }else if (endLumpPtr == alump){
+        return startLumpPtr;
+    }
+    cout << "getLumpOnOtherEnd: this should never happen" << endl;
+
+    return NULL;
+}
+//---------------------------------------------------------------
+void Spring::setRestLength(){
+
+	//set rest length according to what the length is now
+		TwoVector diff(startPoint.x - endPoint.x, startPoint.y - endPoint.y);
+	
+		restLength = diff.norm();	
+
+		//restLength = 0.0;
+
+}
+	
+void Spring::setRestLength(double aLength){
+	restLength = aLength;
+	
+}
+void Spring::setSpringConstant(double aK){
+    springConst = aK;
+}
+double Spring::getLength(){
+    return startPoint.distanceTo(endPoint);
+}
+//---------------------------------------------------------------
+void Spring::updateEndPoints(){
+	// tweak the end points eg: from mass move
+	if (startLumpPtr ==0 || endLumpPtr == 0){
+		cerr << "PERROR spring masses not attached!" << endl; 
+	}else{
+		startPoint = startLumpPtr->position;
+		//cout << "start pt x = " << startPoint.x << ", y = " << startPoint.y << endl;
+		endPoint = endLumpPtr->position;
+	}
+}
+//---------------------------------------------------------------
+void Spring::calculateForce(){
+
+	if(!Spring::forcesOn){
+		force.x = 0.0;
+		force.y = 0.0;
+        return;
+	}
+	// get force from the extension
+	double xe = endPoint.x - startPoint.x;
+    double ye = endPoint.y - startPoint.y;
+	double extendedLength = sqrt(xe*xe + ye*ye);
+	
+    // doing this enables us to get away with only one division etc
+    double FF = springConst*(restLength/extendedLength - 1.0);
+
+    force.x = FF*xe;
+    force.y = FF*ye;
+  
+}
+//---------------------------------------------------------------
+TwoVector Spring::getForce(Lump * aLump){ // pass in the lump that we want the force for
+    
+	// above force refers to force at END force at START negative of this...
+	if(aLump ==  startLumpPtr){
+        TwoVector rForce;
+        rForce.setCoord(-force.x, -force.y);
+        return rForce;
+	}else if(aLump == endLumpPtr ){
+        return force;
+    }
+    cout << "PERROR this lump is not attached to this spring\n";
+
+	return force;
+	
+}
+
+
+//---------------------------------------------------------------
+void Spring::draw(){
+
+	int sxpos = startPoint.x * ofGetHeight() + globalUI.borderSize; // TODO make this nicer
+	int sypos = startPoint.y * ofGetHeight();
+	//cout << "start position = " << sxpos << "," << sypos << endl;
+	int expos = endPoint.x * ofGetHeight() + globalUI.borderSize;
+	int eypos = endPoint.y*ofGetHeight();
+	//cout << "end position = " << expos << "," << eypos << endl;
+
+	if(isInScanPath){
+        ofSetLineWidth(4);
+		ofSetColor(0, 255, 0);
+	}else{
+        ofSetLineWidth(1);
+		ofSetColor(0, 0, 255);
+	}
+    
+	ofLine(sxpos, sypos, expos, eypos);
+}
+//---------------------------------------------------------------
+double Spring::getForceMag(){
+	return force.norm();
+}
+//---------------------------------------------------------------
+void Spring::addToScanPath(){
+	isInScanPath = true;
+}
+//---------------------------------------------------------------
+void Spring::removeFromScanPath(){
+	isInScanPath = false;
+}
+//---------------------------------------------------------------
+/*
+void Spring::checkStuff(){
+	cout << "checking\n";
+}
+*/
+PressureSpring::PressureSpring(){
+    cout << "constructing a pressure spring for droplet mesh\n";
+}
+
+//---------------------------------------------------------------
+TwoVector PressureSpring::getForce(Lump * aLump){ // pass in the lump that we want the force for
+    
+    // PRESSURE STUFF
+    // by convention a positive pressure allways acts to the left as looking from start to end point
+    // ie: Droplet MUST be constructed in clockwise fashion
+    // we rotate the spring vector anti clockwise pi/2 radians this gives us the force vector
+    // x = -y , y = x except damn y coordinate points downwards so handedness is reversed
+    
+    // conveniently this is proportional to length just as pressure would be
+    //cout << "pressure spring get force\n";
+    double xe = endPoint.x - startPoint.x;
+    double ye = endPoint.y - startPoint.y;    
+	// above force refers to force at END force at START negative of this...
+	if(aLump ==  startLumpPtr){
+        TwoVector rForce;
+        rForce.setCoord(-force.x, -force.y);
+        rForce.x += ye * globalForces.pressure * 0.5;
+        rForce.y -= xe * globalForces.pressure * 0.5;
+        return rForce;
+	}else if(aLump == endLumpPtr ){
+        TwoVector rForce;
+        rForce.setCoord(force.x, force.y);
+        rForce.x += ye * globalForces.pressure * 0.5;
+        rForce.y -= xe * globalForces.pressure * 0.5;
+        return rForce;
+    }
+    cout << "PERROR this lump is not attached to this spring\n";
+    
+	return force;
+	
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/testApp.h	Mon Nov 19 13:00:42 2012 +0000
@@ -0,0 +1,121 @@
+// oF 072
+
+#ifndef _TEST_APP
+#define _TEST_APP
+
+#include "ofMain.h"
+#include "ofxiPhone.h"
+#include "ofxiPhoneExtras.h"
+
+#include "2dvector.h"
+#include "lump.h"
+#include "spring.h"
+#include <iostream>
+#include "mesh.h"
+#include "dsptools.h"
+
+#include "ofxUI.h"
+#include "ofxOsc.h"
+#include "globalForces.h"
+#include "globalUI.h"
+
+#define SAMPLE_RATE 44100
+// listen on port 12345
+#define INPORT 12345
+#define OUTPORT 54321
+#define HOST "192.168.1.3"
+#define NUM_MSG_STRINGS 20
+
+// defining this compiles it for ios otherwise osx
+#define IPAD
+
+
+
+class testApp : public ofxiPhoneApp {
+	
+public:
+    int timesOpened;
+	bool audioOn;
+	bool drawingOn;
+	
+	bool paused;
+    //bool inscribeScanPathMode;
+	bool meshWaitingToDelete;
+	Mesh *theMesh;
+    
+    DSPTools mydspTools;
+	
+	double timeStep;
+    
+    double pitch;
+    double phasorIncr;
+    
+
+
+    int numTouches;
+
+	void setup();
+	void update();
+	void draw();
+    void exit();
+    
+    void loadLogXML();
+    void saveLogXML();
+
+	void touchDown(ofTouchEventArgs &touch);
+	void touchMoved(ofTouchEventArgs &touch);
+	void touchUp(ofTouchEventArgs &touch);
+	void touchDoubleTap(ofTouchEventArgs &touch);
+    void addTouch();
+    void removeTouch();
+    
+    void UIcallBack(int buttID);
+
+   
+	void keyPressed  (int key);
+	void keyReleased(int key);
+	void mouseMoved(int x, int y );
+	void mouseDragged(int x, int y, int button);
+	void mousePressed(int x, int y, int button);
+	void mouseReleased(int x, int y, int button);
+
+    void restartAudioStream();
+	void audioRequested 	(float * input, int bufferSize, int nChannels);
+    void audioReceived(float * input, int bufferSize, int nChannels);
+    
+    void handleMessages();
+    void drawMessages();
+    
+    void setupMesh();
+    void regenerateMesh(string meshType, int dim1 = 3, int dim2 = 3);
+    void deleteMesh();
+
+    ofxOscReceiver	receiver;
+    ofxOscSender sender;
+    ofTrueTypeFont		font;
+  
+    int				current_msg_string;
+    string		msg_strings[NUM_MSG_STRINGS];
+    float			timers[NUM_MSG_STRINGS];
+    
+    int				mouseX, mouseY;
+    string			mouseButtonState;
+    
+    ofxiPhoneKeyboard * keyboard;
+    
+    // ofxUI stuff
+    
+    ofxUICanvas *guiL;
+    ofxUICanvas *guiR;
+    void guiLEvent(ofxUIEventArgs &e);
+    void guiREvent(ofxUIEventArgs &e);
+    void setupGui();
+    void drawSidePanels();
+    
+    ofxUILabel *counter;
+     
+};	
+
+
+
+#endif
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/testApp.mm	Mon Nov 19 13:00:42 2012 +0000
@@ -0,0 +1,1302 @@
+#include "testApp.h"
+
+extern GlobalForces globalForces;
+extern GlobalUI globalUI;
+//--------------------------------------------------------------
+void testApp::setup(){	
+    theMesh = NULL;
+	ofxiPhoneSetOrientation(OFXIPHONE_ORIENTATION_LANDSCAPE_RIGHT);
+	ofxAccelerometer.setup();
+
+  	// listen on the given port
+	cout << "listening for osc messages on port " << INPORT << "\n";
+	receiver.setup( INPORT );
+    sender.setup( HOST, OUTPORT );
+
+    ofBackground(0, 0, 0);
+	ofSetFullscreen(true);
+	ofSetFrameRate(60);
+    
+	timeStep = 1/ofGetFrameRate();
+    
+	ofEnableSmoothing();
+    ofEnableAlphaBlending();
+    
+    string url = "http://www.rootnot.co.uk/";
+    ofURLFileLoader fileLoader;
+    ofHttpResponse resp;
+    resp = fileLoader.get(url);
+    cout << "HTTP STATUS  " << resp.status << "\n";
+    cout << "HTTP ERROR  " << resp.error << "\n";
+    cout << "HTTP DATA  " << resp.data << "\n";
+    
+    
+    
+    pitch = 60.0;
+    phasorIncr = pitch/SAMPLE_RATE;
+    globalForces.gravityAmt = 0.0;
+    globalForces.avFilterAmt = 0.01;
+
+    // 
+	ofSoundStreamSetup(2,0,this, SAMPLE_RATE,256, 2);
+    
+    // if we want things to start immediately
+    paused = false;
+    audioOn = true;
+	drawingOn = true;
+
+    
+    setupGui();
+    
+    
+    setupMesh();
+
+}
+
+void testApp::setupMesh(){
+    // SET UP THE MESH STRUCTURE
+    //
+    //
+    // ERRR
+    /*
+    if(audioOn){
+        audioOn = false;
+        meshWaitingToDelete = true;
+        setupMesh();
+    }
+     */
+    static int type = 0;
+    
+    if (type % 4 == 0){
+    // different for iphone
+	if(ofGetWidth() == 480){ //PHONE
+        theMesh = new SpiderCrossMesh(60,5);
+    }else{  //PAD
+        //theMesh = new SpiderCrossMesh(140,5);
+        delete theMesh;
+        theMesh = new SpiderCrossMesh(100,9);
+    }
+    }else if(type % 4 ==1){
+        delete theMesh;
+       theMesh = new SpiderCrossMesh(30,30);
+    }else if(type % 4 ==2){
+        delete theMesh;
+        theMesh = new SpiderCrossMesh(4,4);
+    }else if(type % 4 ==3){
+        delete theMesh;
+         theMesh = new DropletMesh(100);
+    }
+    type++;
+    //theMesh = new LineMesh(123);
+    //theMesh = new GroundedLineMesh(400);
+    //theMesh = new TriangleMesh(40,40);
+    //theMesh = new LineMesh(100);
+    
+    // SET CONSTANTS UP FOR MESH
+    theMesh->setSpringConstant(0.8);
+    theMesh->setMass(1);
+    theMesh->setFriction(0.99991);
+    
+    globalUI.touchMode = globalUI.GRAB;
+    theMesh->scanPath->scanMode = theMesh->scanPath->DISPLACEMENT;
+    numTouches = 0;
+}
+//--------------------------------------------------------------
+//--------------------------------------------------------------
+// GUI stuff
+//--------------------------------------------------------------
+//--------------------------------------------------------------
+void testApp::loadLogXML(){
+    ofxXmlSettings XMLlog;
+
+    string message;
+    if( XMLlog.loadFile(ofxiPhoneGetDocumentsDirectory() + "wabletlogs.xml") ){
+        message = "mySettings.xml loaded from documents folder!";
+
+    }else if( XMLlog.loadFile("Logs/wabletlogs.xml") ){
+        message = "mySettings.xml loaded from data folder!";
+
+
+    }else{
+        message = "unable to load mySettings.xml check data/ folder";
+        return;
+    }
+    cout << message << "\n";
+
+    // get all the values from log
+    timesOpened = XMLlog.getValue("wabletlogs:timesOpened", 0);
+    timesOpened++;
+    
+    stringstream ss;
+    ss << timesOpened;
+    counter->setLabel(ss.str());
+    
+}
+//--------------------------------------------------------------
+void testApp::saveLogXML(){
+    ofxXmlSettings XMLlog;
+    
+    XMLlog.setValue("wabletlogs:timesOpened", timesOpened);
+    
+    if(XMLlog.saveFile(ofxiPhoneGetDocumentsDirectory() + "wabletlogs.xml")){
+        cout << "Saved wabletlogs.XML in iphone documents OK";
+    }else if(XMLlog.saveFile("Logs/wabletlogs.xml")){
+            cout << "Saved wabletlogs.XML in data folder";
+    }else{
+        cout << "wabletlogs file did not save :(";
+    }
+    
+
+    
+}
+//--------------------------------------------------------------
+void testApp::setupGui(){
+    float xInit = OFX_UI_GLOBAL_WIDGET_SPACING;
+    float length = 128-xInit*2;
+    
+
+    float dim = 42;
+    
+    // LEFT GUI ------------------
+    guiL = new ofxUICanvas(0,0,128,ofGetHeight());
+    guiL->addSpacer(length-xInit, 2);
+    guiL->addWidgetDown(new ofxUILabel("Physics", OFX_UI_FONT_LARGE));
+    
+    ofxUIWidget *slider;
+    slider = guiL->addWidgetDown(new ofxUISlider(length,dim,0.0,0.8,0.4,"SPRING K"));
+    slider->setDrawPadding(true);
+    slider->setColorFill(ofColor(0,0,255));
+    slider->setColorFillHighlight(ofColor(0,0,255));
+    
+    slider = guiL->addWidgetDown(new ofxUISlider(length,dim, 0.0, 4.0, 0.0, "GRAVITY"));
+    slider->setDrawPadding(true);
+    slider->setColorFill(ofColor(0,0,255));
+    slider->setColorFillHighlight(ofColor(0,0,255));
+    
+    slider = guiL->addWidgetDown(new ofxUISlider(length,dim,0.0,0.3,0.0,"HOMING"));
+    slider->setDrawPadding(true);
+    slider->setColorFill(ofColor(0,0,255));
+    slider->setColorFillHighlight(ofColor(0,0,255));
+
+    slider = guiL->addWidgetDown(new ofxUISlider(length,dim, 0.0, 0.5, 0.01, "SMOOTHING"));
+    slider->setDrawPadding(true);
+    slider->setColorFill(ofColor(0,0,255));
+    slider->setColorFillHighlight(ofColor(0,0,255));
+    
+
+    
+    guiL->addSpacer(length-xInit, 2);
+    
+    
+    counter = guiL->addLabel("Counter");
+    counter->setLabel("can set");
+    
+    guiL->setWidgetPosition(OFX_UI_WIDGET_POSITION_DOWN);
+	slider = guiL->addSlider("PITCH", 20.0, 100.0, 80.0, length, 370);
+    slider->setDrawPadding(true);
+    slider->setColorFill(ofColor(0,0,255));
+    slider->setColorFillHighlight(ofColor(0,0,255));
+    
+    ofAddListener(guiL->newGUIEvent, this, &testApp::guiLEvent);
+    guiL->loadSettings("GUI/guiSettings.xml");
+    
+    // RIGHT GUI -----------------------
+    
+    guiR = new ofxUICanvas(ofGetWidth()-128, 0, 128, ofGetHeight());
+
+    
+    guiR->addSpacer(length-xInit, 2);
+    
+    vector<string> names;
+	names.push_back("GRAB");
+	names.push_back("FORCE");
+	names.push_back("STICK");
+    names.push_back("UNSTICK");
+    names.push_back("NEWSCAN");
+    names.push_back("SINE");
+    
+    ofxUIRadio* radio;
+    radio = guiR->addRadio("TOUCH MODE", names, OFX_UI_ORIENTATION_VERTICAL, dim, dim);
+    radio->setDrawPadding(true);
+    radio->setColorFill(ofColor(0,0,255));
+    radio->setColorFillHighlight(ofColor(0,0,255));
+    
+    slider = guiR->addWidgetDown(new ofxUISlider(length,dim, -0.2, 4.0, 1.0, "TOUCH AMT"));
+    slider->setDrawPadding(true);
+    slider->setColorFill(ofColor(0,0,255));
+    slider->setColorFillHighlight(ofColor(0,0,255));
+    
+    guiR->addSpacer(length-xInit, 2);
+    
+    guiR->addButton("RESET", false, dim, dim);
+    guiR->addButton("MESH", false, dim, dim);
+    guiR->addToggle("PAUSE", false, dim, dim);
+    
+    ofAddListener(guiR->newGUIEvent, this, &testApp::guiREvent);
+    guiR->loadSettings(ofxiPhoneGetDocumentsDirectory() + "guiSettings.xml");
+    radio->activateToggle("GRAB");
+    
+    // SETUP MESH GUI---------------------------
+    
+    
+    // load gui settings?? 
+    loadLogXML();
+}
+//--------------------------------------------------------------
+void testApp::UIcallBack(int buttID){
+    cout << " BUTT ID " << buttID << "\n";
+    
+}
+//--------------------------------------------------------------
+void testApp::guiREvent(ofxUIEventArgs &e)
+{
+    if(e.widget->getName() == "TOUCH MODE")
+    {
+        cout << "TOUCH MODE";
+        
+    }
+    if(e.widget->getName() == "GRAB"){
+        cout << "GRAB";
+        globalUI.touchMode = globalUI.GRAB;
+    }else if(e.widget->getName() == "FORCE"){
+
+        globalUI.touchMode = globalUI.FORCE_FIELD;
+    }else if(e.widget->getName() == "STICK"){
+        
+        globalUI.touchMode = globalUI.CONSTRAIN;
+    }else if(e.widget->getName() == "UNSTICK"){
+        
+        globalUI.touchMode = globalUI.UNCONSTRAIN;
+    }else if(e.widget->getName() == "NEWSCAN"){
+        
+        theMesh->resetPositions();
+        theMesh->resetVelocities();
+        globalUI.touchMode = globalUI.INSCRIBE_PATH;
+        theMesh->clearScanPath();
+        theMesh->update();
+    }else if(e.widget->getName() == "SINE"){
+        globalUI.touchMode = globalUI.SPATIAL_HARMONIC;
+    }else if(e.widget->getName() == "RESET"){
+        theMesh->resetAll();
+        numTouches = 0;
+    }else if(e.widget->getName() == "MESH"){
+        // ????
+        cout << ((ofxUIButton *)e.widget)->getValue();
+        
+        if( ((ofxUIButton *)e.widget)->getValue() == 1) setupMesh();
+    }else if(e.widget->getName() == "PAUSE"){
+        paused = !paused;
+    }else if(e.widget->getName() == "TOUCH AMT"){
+        ofxUISlider *slider = (ofxUISlider *) e.widget;
+        slider->setDrawPadding(true);
+        globalForces.touchStrength = slider->getScaledValue();
+    }
+    
+}
+//--------------------------------------------------------------
+void testApp::guiLEvent(ofxUIEventArgs &e)
+{
+    if(e.widget->getName() == "SPRING K")
+    {
+        ofxUISlider *slider = (ofxUISlider *) e.widget;
+
+        theMesh->setSpringConstant( slider->getScaledValue() ); 
+    }else if(e.widget->getName() == "GRAVITY")
+    {
+        ofxUISlider *slider = (ofxUISlider *) e.widget;
+        slider->setDrawPadding(true);
+        globalForces.gravityAmt = slider->getScaledValue(); 
+        //radio->getScaledValue() );
+    }else if(e.widget->getName() == "HOMING")
+    {
+        ofxUISlider *slider = (ofxUISlider *) e.widget;
+        globalForces.homingAmt = slider->getScaledValue();
+        //radio->getScaledValue() );
+    }else if(e.widget->getName() == "SMOOTHING")
+    {
+        
+        
+        ofxUISlider *slider = (ofxUISlider *) e.widget;
+        globalForces.avFilterAmt = slider->getScaledValue();
+        //radio->getScaledValue() );
+    }else if(e.widget->getName() == "FRICTION")
+    {
+        
+        
+        ofxUISlider *slider = (ofxUISlider *) e.widget;
+        theMesh->setFriction(slider->getScaledValue());
+        //radio->getScaledValue() );
+    }else if(e.widget->getName() == "PITCH")
+    {
+        
+        
+        ofxUISlider *slider = (ofxUISlider *) e.widget;
+        pitch = slider->getScaledValue();
+        //radio->getScaledValue() );
+    }
+    
+    
+}
+//--------------------------------------------------------------
+//--------------------------------------------------------------
+// App running stuff
+//--------------------------------------------------------------
+//--------------------------------------------------------------
+//--------------------------------------------------------------
+void testApp::exit(){
+	ofSoundStreamClose();
+    
+    // save everything...
+    guiL->saveSettings(ofxiPhoneGetDocumentsDirectory() + "guiSettings.xml");
+    delete guiL;
+    saveLogXML();
+    
+    if(theMesh != NULL){
+        delete theMesh;
+    }
+    
+}
+
+//--------------------------------------------------------------
+void testApp::update(){
+    
+
+    
+	if(!paused){
+        if (theMesh != NULL){
+            theMesh->update();
+            
+        }
+        // BASIC STRING basicString->update();
+    }
+     handleMessages(); // !?!??   
+    //cout << "UPDATE frame rate = " << ofGetFrameRate() << " sec\n";
+}
+
+//--------------------------------------------------------------
+void testApp::draw(){
+	if(drawingOn){
+        if(theMesh != NULL)
+            theMesh->draw();
+
+	}
+	//cout << "frame rate = " << ofGetFrameRate() << " sec\n";
+    //drawMessages();
+
+    drawSidePanels();
+}
+//--------------------------------------------------------------
+// background for UI
+void testApp::drawSidePanels(){
+    ofSetColor(123, 123, 123);
+    ofRect(0, 0, 128, ofGetHeight());
+    ofRect(ofGetWidth()-128, 0, 128, ofGetHeight());
+}
+//--------------------------------------------------------------
+void testApp::drawMessages(){
+    for ( int i=0; i<NUM_MSG_STRINGS; i++ )
+    {
+        ofDrawBitmapString( msg_strings[i], 10, 40+15*i );
+    }
+}
+
+//--------------------------------------------------------------
+//--------------------------------------------------------------
+//------------------------CHANGING MESH----------------------------
+//--------------------------------------------------------------
+//--------------------------------------------------------------
+//--------------------------------------------------------------
+
+void testApp::deleteMesh(){
+     // TODO - OTHER THREADS FUCK THIS UP
+
+    //stop everything
+    ofSoundStreamStop();
+    
+    paused = true;
+    audioOn = false;
+    drawingOn = false;
+    
+    
+    // trash it
+    delete theMesh;
+    theMesh = NULL;
+    
+    cout << "MESH DELETED\n";
+}
+//--------------------------------------------------------------
+void testApp::regenerateMesh(string meshType, int dim1, int dim2){
+    // TODO - OTHER THREADS FUCK THIS UP??
+    if (theMesh != NULL) return;
+    
+    if (meshType == "LineMesh"){
+     
+        theMesh = new LineMesh(dim1);
+    }else if (meshType == "DropletMesh"){
+      
+        theMesh = new DropletMesh(dim1);
+    }else if (meshType == "SpiderMesh"){
+      
+        theMesh = new SpiderMesh(dim1,dim2);
+    }else if (meshType == "SpiderCrossMesh"){
+        
+        theMesh = new SpiderCrossMesh(dim1,dim2);
+    }else if (meshType == "SquareCrossMesh"){
+    
+        theMesh = new SquareCrossMesh(dim1,dim2);
+    }else if (meshType == "TriangleMesh"){
+        
+        theMesh = new TriangleMesh(dim1,dim2);
+    }else if (meshType == "GroundedLineMesh"){
+     
+        theMesh = new GroundedLineMesh(dim1);
+    }else{
+        cout << "OSC message error: unrecognised mesh type" << endl;
+        return;
+    }
+    ofSoundStreamStart();
+    drawingOn = true;
+    paused = false;
+    audioOn = true;
+    cout << "MESH REGENERATED\n";
+
+}
+
+
+
+//--------------------------------------------------------------
+//--------------------------------------------------------------
+//------------------------KEYS (OSX)----------------------------
+//--------------------------------------------------------------
+//--------------------------------------------------------------
+// all this is now osc messages
+void testApp::keyPressed(int key){
+    
+	if (key == 'p'){
+        
+		paused = !paused;
+	}
+	if (key == 's'){
+		theMesh->toggleSyrup(true);
+	}
+	if (key == 'f'){
+		theMesh->toggleSpringForces(false);
+	}	
+	if (key == '='){
+		theMesh->increasePropagationSpeed();
+	}		
+	if (key == '-'){
+		theMesh->decreasePropagationSpeed();
+	}	
+	if (key == 'a'){
+        if(audioOn){
+            audioOn = false;
+        }else{
+            audioOn = true;
+        }
+	}	
+	if (key == 'r'){
+		theMesh->resetPositions();
+        theMesh->resetVelocities();
+
+	}	
+	if (key == 'e'){
+		theMesh->constrain(.0,.0,Mesh::CONSTRAIN_EDGES);
+	}
+	if (key == 'c'){
+		theMesh->constrain(.0,.0,Mesh::CONSTRAIN_CORNERS);
+	}
+	if (key == 'u'){
+		theMesh->unconstrain();
+	}
+	if (key == 'l'){
+		theMesh->setRestLength();
+	}
+	if (key == '0'){
+		theMesh->zeroRestLength();
+	}
+	if (key == 'd'){
+		drawingOn = !drawingOn;
+	}
+}
+
+//---------------------------------------------------------------
+
+void testApp::keyReleased(int key){
+	if (key == 's'){
+		theMesh->toggleSyrup(false);
+	}
+	if (key == 'f'){
+		theMesh->toggleSpringForces(true);
+	}	
+}
+
+
+
+//--------------------------------------------------------------
+//--------------------------------------------------------------
+//------------------------TOUCH---------------------------------
+//--------------------------------------------------------------
+//--------------------------------------------------------------
+
+
+
+
+void testApp::touchDown(ofTouchEventArgs &touch){
+    if(theMesh == NULL) return;
+    
+	double dax, day;
+	// touchDown
+    //cout << "touchDown ID: " << touch.id << endl;
+    if(globalUI.handleTouchDown(touch.x, touch.y)) return;
+
+    
+    addTouch();
+
+
+	
+    dax = (double(touch.x) - globalUI.borderSize)/ofGetHeight();
+	day = double(touch.y)/ofGetHeight();
+    
+    switch (globalUI.touchMode){
+        case globalUI.GRAB:
+            theMesh->grab(dax,day,touch.id);
+            break;
+        case globalUI.INSCRIBE_PATH:
+            // start a new path, with touch id? ie:polyphonic paths!??!
+            break;
+        case globalUI.FORCE_FIELD:
+            globalForces.createForceTouchPoint(dax,day, touch.id);
+            break;
+        case globalUI.SPATIAL_HARMONIC:
+            // work out 
+            theMesh->spatialHarmonic(numTouches, 0);
+            break;
+        case globalUI.CONSTRAIN:
+            theMesh->constrain(dax,day,Mesh::CONSTRAIN_GRAB_REGION);
+            break;
+        case globalUI.UNCONSTRAIN:
+            theMesh->unconstrain(dax,day,Mesh::CONSTRAIN_GRAB_REGION);
+            break;
+        case globalUI.VIBRATE:
+            break;
+        default:
+            
+            break;            
+    }
+
+    
+}
+
+//--------------------------------------------------------------
+void testApp::touchMoved(ofTouchEventArgs &touch){
+    if(theMesh == NULL) return;
+    if(globalUI.handleTouchMove(touch.x, touch.y)) return;
+    //cout << "touchMoved ID: " << touch.id << endl;
+    
+
+	double dax, day;
+	dax = (double(touch.x) - globalUI.borderSize)/ofGetHeight();
+	day = double(touch.y)/ofGetHeight();
+    
+    
+    
+    /*
+    if(kslider->checkForTouch(touch.x, touch.y)){
+        cout << "kslider touched";
+       kslider->adjust(touch.x,touch.y);
+        return;
+    }
+     */
+
+    switch (globalUI.touchMode){
+        case globalUI.GRAB:
+            theMesh->drag(dax,day,touch.id);
+            break;
+        case globalUI.INSCRIBE_PATH:
+            theMesh->inscribeScanPath(dax,day);
+            break;
+        case globalUI.FORCE_FIELD:
+            //theMesh->forceField(dax,day,touch.id);
+            cout << "move force tp\n";
+            globalForces.moveForceTouchPoint(dax,day, touch.id);
+            break;
+        case globalUI.SPATIAL_HARMONIC:
+            // makes no sense
+            break;
+        case globalUI.CONSTRAIN:
+            theMesh->constrain(dax,day,Mesh::CONSTRAIN_GRAB_REGION);
+            break;
+        case globalUI.UNCONSTRAIN:
+            theMesh->unconstrain(dax,day,Mesh::CONSTRAIN_GRAB_REGION);
+            break;
+        case globalUI.VIBRATE:
+            break;
+        default:
+            
+            break;            
+    }
+}
+
+//--------------------------------------------------------------
+void testApp::touchUp(ofTouchEventArgs &touch){
+    if(theMesh == NULL) return;
+    if(globalUI.handleTouchUp(touch.x, touch.y)) return;
+    
+    
+    removeTouch();
+    
+
+    switch (globalUI.touchMode){
+        case globalUI.GRAB:
+            theMesh->unGrab(touch.id);
+            break;
+        case globalUI.INSCRIBE_PATH:
+            theMesh->disconnectDraw();
+            break;
+        case globalUI.FORCE_FIELD:
+            globalForces.removeForceTouchPoint(touch.id);
+            break;
+        case globalUI.SPATIAL_HARMONIC:
+            break;
+        case globalUI.CONSTRAIN:
+            
+            break;
+        case globalUI.UNCONSTRAIN:
+            
+            break;
+        case globalUI.VIBRATE:
+            break;
+        default:
+            
+            break;            
+    }
+
+}
+
+//--------------------------------------------------------------
+void testApp::touchDoubleTap(ofTouchEventArgs &touch){
+    // pretty much useless.
+    /*
+    ofxOscMessage m;
+    m.setAddress( "/test" );
+    m.addIntArg( 1 );
+    m.addFloatArg( 3.5f );
+    m.addStringArg( "hello" );
+    m.addFloatArg( ofGetElapsedTimef() );
+    sender.sendMessage( m );
+     */
+    
+    //ofxiPhoneScreenGrab(NULL);  
+}
+
+
+//--------------------------------------------------------------
+void testApp::addTouch(){
+    numTouches++;
+    if(numTouches >= 5){
+        theMesh->toggleHome(true);
+    }else{
+        theMesh->toggleHome(false);
+    }
+    cout << "numtouches " << numTouches << endl;
+}
+//--------------------------------------------------------------
+void testApp::removeTouch(){
+    numTouches--;
+    
+}
+
+//--------------------------------------------------------------
+//--------------------------------------------------------------
+    // MOUSE  - NOT NEEDED FOR IPAD
+//--------------------------------------------------------------
+//--------------------------------------------------------------
+void testApp::mouseMoved(int x, int y ){
+	//cout << "mouse moved " << x << 'y' << y << '\n';
+    /*
+	double dax, day;
+	
+	// normalise
+	dax = double(x)/ofGetWidth();
+	day = double(y)/ofGetHeight();
+    
+	theMesh->checkHover(dax,day);
+    
+    */
+}
+
+//--------------------------------------------------------------
+void testApp::mouseDragged(int x, int y, int button){
+	//cout << "mouse DRAGGED " << x << 'y' << y << '\n';
+    /*
+	double dax, day;
+	dax = double(x)/ofGetWidth();
+	day = double(y)/ofGetHeight();
+	
+	if(button == 0){
+		if(!paused){
+			theMesh->drag(dax,day, 0);
+		}else{
+			// pause mode - draw a scan path!
+			
+			theMesh->drawScanPath(dax,day);
+			audioOn = false;
+		}
+	}else if(button == 2){
+		
+	}
+     */
+    
+}
+
+//--------------------------------------------------------------
+void testApp::mousePressed(int x, int y, int button){
+    /*
+	double dax, day;
+	// normalise
+	dax = double(x)/ofGetWidth();
+	day = double(y)/ofGetHeight();
+	
+	if(button == 0){
+		if(paused){
+			// draw a path
+			audioOn = false;
+			theMesh->deleteScanPath();
+		}else{
+			theMesh->grab(dax,day, 0);
+		}
+	}else if(button == 2){
+		theMesh->constrain(dax,day,Mesh::CONSTRAIN_GRAB_REGION);
+	}else{
+		cout << "OTHER BUTTON?\n";
+	}
+     */
+    
+    
+}
+
+//--------------------------------------------------------------
+void testApp::mouseReleased(int x, int y, int button){
+	/*
+	if(button == 0){
+		theMesh->unGrab(0);
+	}else if(button == 2){
+	}else{
+		cout << "butt other";
+	}
+     */
+	
+}
+//--------------------------------------------------------------
+//--------------------------------------------------------------
+//-------------------------AUDIO-----------------------------
+//--------------------------------------------------------------
+//--------------------------------------------------------------
+void testApp::restartAudioStream(){
+    ofSoundStreamStart();
+    audioOn = true;
+    
+}
+//--------------------------------------------------------------
+
+void testApp::audioReceived(float * input, int bufferSize, int nChannels){
+    
+}
+//--------------------------------------------------------------
+void testApp::audioRequested 	(float * output, int bufferSize, int nChannels){
+	float sample;
+    static double phasor;
+    static double sinePhasor;
+    
+
+    phasorIncr = pitch/SAMPLE_RATE;
+
+    if(meshWaitingToDelete){
+        meshWaitingToDelete = false;
+        return;
+    }
+
+	if(audioOn && theMesh != NULL){
+		for (int i = 0; i < bufferSize; i++){
+            sinePhasor = sin(2*PI*phasor);
+
+			sample  = float(theMesh->scanPath->getNextSample(phasor));
+            //sample  = float(theMesh->scanPath->getNextSample());
+            // BASIC STRING sample = float(basicString->getNextSample(phasor));
+            
+            // hipass to get rid of DC
+            //sample = mydspTools.highpass1(sample);
+            sample = mydspTools.butter(sample);
+			output[i*nChannels    ] = sample*globalForces.volume;
+			output[i*nChannels + 1] = sample*globalForces.volume;
+            
+            phasor += phasorIncr;
+            
+            if(phasor >= 1.0){
+                phasor -= 1.0;
+            }
+		}	
+	}else{
+		for (int i = 0; i < bufferSize; i++){
+			output[i*nChannels    ] = 0.0;
+			output[i*nChannels + 1] = 0.0;
+		}
+	}
+    
+	
+}
+
+//--------------------------------------------------------------
+//--------------------------------------------------------------
+//-------------------------OSC MSGS--------------------------------
+//--------------------------------------------------------------
+//--------------------------------------------------------------
+
+void testApp::handleMessages(){
+    
+	// hide old messages
+	for ( int i=0; i<NUM_MSG_STRINGS; i++ )
+	{
+		if ( timers[i] < ofGetElapsedTimef() )
+			msg_strings[i] = "";
+	}
+    
+	// check for waiting messages
+	while( receiver.hasWaitingMessages() )
+	{
+		// get the next message
+		ofxOscMessage m;
+		receiver.getNextMessage( &m );
+        //cout << "OSC message: " << m.getAddress() << endl;
+        
+        /****************/
+        /* REGENERATION OF NEW MESH */
+        /****************/
+        
+        // if the mesh is not there check for regenerate , otherwise ignore everything
+        if(theMesh == NULL){
+            if ( m.getAddress() == "regenerate" )
+            {
+                string meshType;
+                int dim1, dim2;
+                int numArgs = m.getNumArgs();
+                cout << "REGEN num args: " << numArgs << endl;
+                // we want to do some error checking here
+                meshType = m.getArgAsString(0);
+                cout << "REGEN meshType: " << meshType << endl;
+                dim1 = m.getArgAsInt32(1);
+                if(numArgs == 3){
+                    dim2 = m.getArgAsInt32(2);
+                }else{
+                    dim2 = 3;
+                }
+                regenerateMesh(meshType,dim1,dim2);
+
+                
+            }else{
+                return;
+            }
+        }
+        else if ( m.getAddress() == "restartNewMesh" )
+		{
+            ofSoundStreamStart();
+            drawingOn = true;
+            paused = false;
+            audioOn = true;
+            
+		}   
+        /****************/
+        /* NOTE HANDLER */
+        /****************/
+        
+		else if ( m.getAddress() == "note" )
+		{
+            int note = m.getArgAsInt32(0);
+        
+            int velocity = m.getArgAsInt32(1);
+            if(velocity > 0){
+                cout << "note pitch = " << pitch << endl;
+                pitch = 440.0/pow(2.0,((69.0 - double(note))/12.0));
+                theMesh->hit(0.5,0.5,velocity,globalForces.excitationType,globalForces.excitationShape);
+            }else{
+                theMesh->damp();
+            }
+            
+            
+ 		}
+        else if ( m.getAddress() == "excitationStrength" )
+		{
+            globalForces.excitationStrength = m.getArgAsFloat(0);
+            
+            
+ 		}
+        
+        else if ( m.getAddress() == "excitationType" )
+		{
+            
+            // set MIDI twanger mode
+            if(m.getArgAsString(0) == "POSITION"){
+                globalForces.excitationType = GlobalForces::POSITION;
+                
+            }else if (m.getArgAsString(0) == "VELOCITY"){
+                cout << "excitationType = VELOCITY" << endl;
+                globalForces.excitationType = GlobalForces::VELOCITY;
+            }
+		}
+        else if ( m.getAddress() == "excitationShape" )
+		{
+            
+            // set MIDI twanger twang shape
+            if(m.getArgAsString(0) == "NOISE"){
+                globalForces.excitationShape = GlobalForces::NOISE;
+                
+            }else if (m.getArgAsString(0) == "GAUSS"){
+                cout << "excitationType = GAUSS" << endl;
+                globalForces.excitationShape = GlobalForces::GAUSS;
+            }else if (m.getArgAsString(0) == "SINE"){
+                cout << "excitationType = SINE" << endl;
+                globalForces.excitationShape = GlobalForces::SINE;
+        
+            }
+            // set size
+            globalForces.exciteShapeX = m.getArgAsInt32(1);
+            globalForces.exciteShapeY = m.getArgAsInt32(2);
+		}
+          
+        // touchModes: {GRAB,FORCE_FIELD,SPATIAL_HARMONIC,CONSTRAIN,VIBRATE,INSCRIBE_PATH};
+		else if ( m.getAddress() == "touchMode" )
+		{
+            
+            // set touch mode
+            if(m.getArgAsString(0) == "GRAB"){
+                globalUI.touchMode = globalUI.GRAB;
+                
+            }else if (m.getArgAsString(0) == "FORCE_FIELD"){
+                cout << "touchMode = FORCE_FIELD" << endl;
+                globalUI.touchMode = globalUI.FORCE_FIELD;
+            }else if (m.getArgAsString(0) == "SPATIAL_HARMONIC"){
+                //cout << "touchMode = SPATIAL_HARMONIC" << endl;
+                globalUI.touchMode = globalUI.SPATIAL_HARMONIC;
+            }else if (m.getArgAsString(0) == "CONSTRAIN"){
+                //cout << "touchMode = CONSTRAIN" << endl;
+                globalUI.touchMode = globalUI.CONSTRAIN;
+            }else if (m.getArgAsString(0) == "VIBRATE"){
+                //cout << "touchMode = VIBRATE" << endl;
+                globalUI.touchMode = globalUI.VIBRATE;
+            }else if (m.getArgAsString(0) == "UNCONSTRAIN"){
+                //cout << "touchMode = UNCONSTRAIN" << endl;
+                globalUI.touchMode = globalUI.UNCONSTRAIN;
+            }else if (m.getArgAsString(0) == "INSCRIBE_PATH"){
+                //cout << "touchMode = INSCRIBE_PATH" << endl;
+                globalUI.touchMode = globalUI.INSCRIBE_PATH;
+                theMesh->clearScanPath();              
+            }
+		}
+       
+        else if ( m.getAddress() == "pause" )
+		{
+            paused = !paused;
+		}
+        else if ( m.getAddress() == "touchStrength" )
+		{
+            for ( int i=0; i<m.getNumArgs(); i++ )
+			{
+                if( m.getArgType( i ) == OFXOSC_TYPE_FLOAT ){
+                    cout << "touchStrength = " << m.getArgAsFloat(i) << endl;
+                    
+                    globalForces.touchStrength = m.getArgAsFloat(i);
+                }
+            }
+		}
+        else if ( m.getAddress() == "homingAmt" )
+		{
+            for ( int i=0; i<m.getNumArgs(); i++ )
+			{
+                if( m.getArgType( i ) == OFXOSC_TYPE_FLOAT ){
+                    globalForces.homingAmt = m.getArgAsFloat(i);
+                }
+            }
+		}
+        else if ( m.getAddress() == "avFilterAmt" )
+		{
+            for ( int i=0; i<m.getNumArgs(); i++ )
+			{
+                if( m.getArgType( i ) == OFXOSC_TYPE_FLOAT ){
+                    globalForces.avFilterAmt = m.getArgAsFloat(i);
+                }
+            }
+		}
+        else if ( m.getAddress() == "scanMode" )
+		{
+            // set scan mode
+            if (m.getArgAsString(0) == "DISPLACEMENT"){
+                cout << "touchMode = DISPLACEMENT" << endl;
+                theMesh->scanPath->scanMode = theMesh->scanPath->DISPLACEMENT;
+            }else if (m.getArgAsString(0) == "SPEED"){
+                
+                cout << "scanMode = SPEED" << endl;
+                theMesh->scanPath->scanMode = theMesh->scanPath->SPEED;
+            }else if (m.getArgAsString(0) == "SPRING_FORCE"){
+                cout << "scanMode = SPRING_FORCE" << endl;
+                theMesh->scanPath->scanMode = theMesh->scanPath->SPRING_FORCE;
+            }else if (m.getArgAsString(0) == "YPOS"){
+                cout << "scanMode = YPOS" << endl;
+                theMesh->scanPath->scanMode = theMesh->scanPath->YPOS;
+            }else if (m.getArgAsString(0) == "OTHER"){
+                cout << "scanMode = OTHER" << endl;
+                //scanMode = OTHER;
+            }
+		}
+		else if ( m.getAddress() == "inscribeScanPath" )
+		{
+            if (globalUI.touchMode != globalUI.INSCRIBE_PATH){
+                theMesh->resetPositions();
+                theMesh->resetVelocities();
+                paused = true;
+                audioOn = false;
+                globalUI.touchMode = globalUI.INSCRIBE_PATH;
+                theMesh->clearScanPath();
+                theMesh->update();
+            }else{
+
+                paused = false;
+                audioOn = true;
+                globalUI.touchMode = globalUI.GRAB;           
+            }
+            
+		}
+		else if ( m.getAddress() == "clearScanPath" )
+		{
+            theMesh->clearScanPath();
+		}
+        else if ( m.getAddress() == "toggleForce" )
+		{
+            theMesh->toggleSpringForces();
+		}
+        else if ( m.getAddress() == "toggleGravity" )
+		{
+            theMesh->toggleGravity();
+		}
+        else if ( m.getAddress() == "resetPositions" )
+		{
+            theMesh->resetPositions();
+		}
+        else if ( m.getAddress() == "resetVelocities" )
+		{
+            theMesh->resetVelocities();
+		}
+
+        
+        else if ( m.getAddress() == "reset" )
+		{
+            theMesh->resetPositions();
+            theMesh->resetVelocities();
+            theMesh->update(); 
+            
+		}
+        else if ( m.getAddress() == "deleteMesh" )
+		{
+            deleteMesh();
+		}
+        else if ( m.getAddress() == "regenerate" )
+		{
+            // ignore it - the mesh is not deleted
+            
+            
+		}
+        else if ( m.getAddress() == "zeroRestLength" )
+		{
+            theMesh->zeroRestLength();
+		}
+        else if ( m.getAddress() == "setThisRestLength" )
+		{
+            theMesh->setRestLength();
+		}
+        else if ( m.getAddress() == "setZeroAudioLine" )
+		{
+            theMesh->setZeroAudioLine();
+		}
+        else if ( m.getAddress() == "speedLimit" )
+		{
+            for ( int i=0; i<m.getNumArgs(); i++ )
+			{
+                if( m.getArgType( i ) == OFXOSC_TYPE_FLOAT ){
+                    globalForces.speedLimit = m.getArgAsFloat(i);
+                }
+            }
+		}
+        else if ( m.getAddress() == "gravityAmt" )
+		{
+            for ( int i=0; i<m.getNumArgs(); i++ )
+			{
+                if( m.getArgType( i ) == OFXOSC_TYPE_FLOAT ){
+                    globalForces.gravityAmt = m.getArgAsFloat(i);
+                }
+            }
+		}
+        else if ( m.getAddress() == "pressureAmt" )
+		{
+            for ( int i=0; i<m.getNumArgs(); i++ )
+			{
+                if( m.getArgType( i ) == OFXOSC_TYPE_FLOAT ){
+                    globalForces.pressureAmt = m.getArgAsFloat(i);
+                }
+            }
+		}    
+        else if ( m.getAddress() == "wallBounce" )
+		{
+            for ( int i=0; i<m.getNumArgs(); i++ )
+			{
+                if( m.getArgType( i ) == OFXOSC_TYPE_FLOAT ){
+                    //params::wallBounce = m.getArgAsFloat(i);
+                }
+            }
+		}
+        else if ( m.getAddress() == "toggleSyrup" )
+		{
+            theMesh->toggleSyrup();
+        }
+        else if ( m.getAddress() == "toggleAudio" )
+		{
+            if(audioOn){
+                ofSoundStreamStop();
+                audioOn = false;
+            }else{
+                restartAudioStream();
+            }
+        
+		}       
+
+        else if ( m.getAddress() == "constrainEdges" )
+		{
+            theMesh->constrain(.0,.0,Mesh::CONSTRAIN_EDGES);
+		}
+        else if ( m.getAddress() == "constrainCorners" )
+		{
+            theMesh->constrain(.0,.0,Mesh::CONSTRAIN_CORNERS    );
+		}
+        else if ( m.getAddress() == "unconstrain" )
+        {
+            theMesh->unconstrain();
+        }
+		else if ( m.getAddress() == "mass" )
+		{
+			for ( int i=0; i<m.getNumArgs(); i++ )
+			{
+                if( m.getArgType( i ) == OFXOSC_TYPE_INT32 ){
+                    theMesh->setMass(m.getArgAsInt32( i ));
+                }
+                else if( m.getArgType( i ) == OFXOSC_TYPE_FLOAT )
+                {
+                    theMesh->setMass(m.getArgAsFloat( i )<0.01?0.01:m.getArgAsFloat( i ) ); 
+                }
+                
+            }
+		}
+		else if ( m.getAddress() == "springk" )
+		{
+			for ( int i=0; i<m.getNumArgs(); i++ )
+			{
+                if( m.getArgType( i ) == OFXOSC_TYPE_INT32 ){
+                    theMesh->setSpringConstant(m.getArgAsInt32( i ));
+                }
+                else if( m.getArgType( i ) == OFXOSC_TYPE_FLOAT )
+                {
+                    theMesh->setSpringConstant(m.getArgAsFloat( i )<0.01?0.01:m.getArgAsFloat( i ) ); 
+                }
+                
+            }
+		}
+		else if ( m.getAddress() == "friction" )
+		{
+			for ( int i=0; i<m.getNumArgs(); i++ )
+			{
+                if( m.getArgType( i ) == OFXOSC_TYPE_INT32 ){
+                    theMesh->setFriction(m.getArgAsInt32( i ));
+                }
+                else if( m.getArgType( i ) == OFXOSC_TYPE_FLOAT )
+                {
+                    theMesh->setFriction(m.getArgAsFloat( i )<0.01?0.01:m.getArgAsFloat( i ) ); 
+                }
+                
+            }
+		}
+        //////////////// asym
+        else if ( m.getAddress() == "massAsym" )
+		{
+			for ( int i=0; i<m.getNumArgs(); i++ )
+			{
+                if( m.getArgType( i ) == OFXOSC_TYPE_FLOAT )
+                {
+                    theMesh->setMassAsym(m.getArgAsFloat( i )<0.01?0.01:m.getArgAsFloat( i ) ); 
+                }
+                
+            }
+		}
+		else if ( m.getAddress() == "springkAsym" )
+		{
+			for ( int i=0; i<m.getNumArgs(); i++ )
+			{
+                if( m.getArgType( i ) == OFXOSC_TYPE_FLOAT )
+                {
+                    theMesh->setSpringConstantAsym(m.getArgAsFloat( i )<0.00?0.00:m.getArgAsFloat( i ) ); 
+                }
+                
+            }
+		}
+		else if ( m.getAddress() == "frictionAsym" )
+		{
+			for ( int i=0; i<m.getNumArgs(); i++ )
+			{
+                if( m.getArgType( i ) == OFXOSC_TYPE_FLOAT )
+                {
+                    theMesh->setFrictionAsym(m.getArgAsFloat( i )<0.01?0.01:m.getArgAsFloat( i ) ); 
+                }
+                
+            }
+		}
+        else if ( m.getAddress() == "volume" )
+		{
+			for ( int i=0; i<m.getNumArgs(); i++ )
+			{
+                if( m.getArgType( i ) == OFXOSC_TYPE_FLOAT )
+                {
+                    cout << "change volume\n";
+                    globalForces.volume = m.getArgAsFloat( i ); 
+                }
+                
+            }
+		}
+        ///////////////////////
+		else
+		{
+			// unrecognized message: display on the bottom of the screen
+			string msg_string;
+			msg_string = m.getAddress();
+			msg_string += ": ";
+			for ( int i=0; i<m.getNumArgs(); i++ )
+			{
+				// get the argument type
+				msg_string += m.getArgTypeName( i );
+				msg_string += ":";
+				// display the argument - make sure we get the right type
+				if( m.getArgType( i ) == OFXOSC_TYPE_INT32 )
+                    pitch = m.getArgAsInt32( i );
+				else if( m.getArgType( i ) == OFXOSC_TYPE_FLOAT )
+					msg_string += ofToString( m.getArgAsFloat( i ) );
+				else if( m.getArgType( i ) == OFXOSC_TYPE_STRING )
+					msg_string += m.getArgAsString( i );
+				else
+					msg_string += "unknown";
+			}
+			// add to the list of strings to display
+			msg_strings[current_msg_string] = msg_string;
+			timers[current_msg_string] = ofGetElapsedTimef() + 5.0f;
+			current_msg_string = ( current_msg_string + 1 ) % NUM_MSG_STRINGS;
+			// clear the next line
+			msg_strings[current_msg_string] = "";
+		}
+        
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/uifunctor.h	Mon Nov 19 13:00:42 2012 +0000
@@ -0,0 +1,56 @@
+//
+//  uifunctor.h
+//  Wablet
+//
+//  Created by Robert Tubb on 22/03/2012.
+//  Copyright (c) 2012 __MyCompanyName__. All rights reserved.
+//
+
+#ifndef Wablet_uifunctor_h
+#define Wablet_uifunctor_h
+
+//------------------- callback functor
+
+// abstract base class
+class UIFunctor
+{
+public:
+    
+    // two possible functions to call member function. virtual cause derived
+    // classes will use a pointer to an object and a pointer to a member function
+    // to make the function call
+    virtual void operator()(int buttID)=0;  // call using operator
+    virtual void Call(int buttID)=0;        // call using function
+};
+
+
+// derived template class - this one is for memeber of testApp class
+template <class testApp> class UISpecificFunctor : public UIFunctor
+{
+private:
+    void (testApp::*fpt)(int);   // pointer to member function
+    testApp* pt2Object;                  // pointer to object
+    
+public:
+    
+    // constructor - takes pointer to an object and pointer to a member and stores
+    // them in two private variables
+    
+    UISpecificFunctor( testApp* _pt2Object, void(testApp::*_fpt)(int))
+    { 
+        pt2Object = _pt2Object;  
+        fpt=_fpt; 
+    };
+    
+    // override operator "()"
+    
+    virtual void operator()(int buttID)
+    { (*pt2Object.*fpt)(buttID);};              // execute member function
+    
+    // override function "Call"
+    virtual void Call(int buttID)
+    { (*pt2Object.*fpt)(buttID);};             // execute member function
+};
+
+
+#endif