summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Redfern <tim@eclectronics.org>2012-06-13 14:52:22 +0100
committerTim Redfern <tim@eclectronics.org>2012-06-13 14:52:22 +0100
commite05bc2828bc213f1e78256f976284bad80722e1a (patch)
treee682759db2c253a4649fd6fac96c46be0d386300
parent8bc09d4264575d2752374413a180bf9dc1b3b035 (diff)
avoiding edges version
-rw-r--r--cvtest/cvtest.layout4
-rw-r--r--cvtest/src/testApp.cpp165
-rw-r--r--cvtest/src/testApp.h5
-rw-r--r--gaunt01/src/bird.cpp81
-rw-r--r--gaunt01/src/bird.h37
-rw-r--r--gaunt01/src/main.cpp2
-rw-r--r--gaunt01/src/testApp.cpp37
7 files changed, 257 insertions, 74 deletions
diff --git a/cvtest/cvtest.layout b/cvtest/cvtest.layout
index 01f197d..bc9e1d9 100644
--- a/cvtest/cvtest.layout
+++ b/cvtest/cvtest.layout
@@ -11,9 +11,9 @@
<Cursor position="361" topLine="0" />
</File>
<File name="src/testApp.cpp" open="1" top="1" tabpos="3">
- <Cursor position="3136" topLine="91" />
+ <Cursor position="2817" topLine="79" />
</File>
<File name="src/testApp.h" open="1" top="0" tabpos="2">
- <Cursor position="1113" topLine="18" />
+ <Cursor position="1113" topLine="17" />
</File>
</CodeBlocks_layout_file>
diff --git a/cvtest/src/testApp.cpp b/cvtest/src/testApp.cpp
index cf5061d..cfde019 100644
--- a/cvtest/src/testApp.cpp
+++ b/cvtest/src/testApp.cpp
@@ -25,7 +25,7 @@ void testApp::setup(){
}
- vidPlayer.loadMovie("cam-grass-01.mov"); //camoutput3.mov"); // //footage/ camera needs to be the same res as opencv planes and output
+ vidPlayer.loadMovie("camoutput3.mov"); //cam-grass-01.mov"); // //footage/ camera needs to be the same res as opencv planes and output
vidPlayer.setLoopState(OF_LOOP_NORMAL);
vidPlayer.play();
@@ -39,7 +39,7 @@ void testApp::setup(){
mogoutput.allocate(CAM_WIDTH_FG, CAM_HEIGHT_FG);
- learningRate = 0.1f;
+ learningRate = 0.01f;
bFirstFrame=true;
threshold=10.0;
@@ -80,7 +80,10 @@ void testApp::setup(){
diffchannel=chan_V;
hsvback = cvCreateImage(cvGetSize(currentFrame.getCvImage()), currentFrame.getCvImage()->depth, currentFrame.getCvImage()->nChannels);
- //outchan = cvCreateImage(cvGetSize(currentFrame.getCvImage()), 8, 1);
+ //backchan = cvCreateImage(cvGetSize(currentFrame.getCvImage()), 8, 1);
+
+ removeShadows=false;
+ shadowThreshold=10;
}
@@ -98,12 +101,12 @@ void testApp::update(){
currentFrame.setFromPixels(vidPlayer.getPixels(), CAM_WIDTH_FG, CAM_HEIGHT_FG);
+ cv::Mat img = currentFrame.getCvImage();
-
- if (frameno%10==0) { //I THINK THIS APPROACH IS OK
+ if (frameno%1==0) { //I THINK THIS APPROACH IS OK
//cv::Rect roi(0, 0, 32, 32); //doesn't seem that easy to apply the ROI weighted and you still have to convert a whole image each frame?
- cv::Mat img = currentFrame.getCvImage();
+
//cv::Mat imgroi = img(roi);
if (bFirstFrame) {
@@ -126,70 +129,140 @@ void testApp::update(){
//printf("tmp: %ix%i channels: %i depth:%i\n",tmp->width,tmp->height,tmp->nChannels,tmp->depth);
- //get correct channel into outchan
+ //get correct channel into backchan
vector<cv::Mat> chans;
- if (diffchannel>chan_B) cvtColor(outmat, hsvback, CV_BGR2HSV);
+ //to remove shadows, need hsv of foreground and background
+ if (diffchannel>chan_B||removeShadows) cvtColor(outmat, hsvback, CV_BGR2HSV);
switch (diffchannel) {
case chan_R:
split(outmat,chans);
- chans[0].copyTo(outchan);
+ chans[0].copyTo(backchan);
break;
case chan_G:
split(outmat,chans);
- chans[1].copyTo(outchan);
+ chans[1].copyTo(backchan);
break;
case chan_B:
split(outmat,chans);
- chans[2].copyTo(outchan);
+ chans[2].copyTo(backchan);
break;
case chan_H:
split(hsvback,chans);
- chans[0].copyTo(outchan);
+ chans[0].copyTo(backchan);
break;
case chan_S:
split(hsvback,chans);
- chans[1].copyTo(outchan);
+ chans[1].copyTo(backchan);
break;
case chan_V:
split(hsvback,chans);
- chans[2].copyTo(outchan);
+ chans[2].copyTo(backchan);
break;
}
- tmp = new IplImage(outchan);
- //printf("tmp: %ix%i channels: %i depth:%i\n",tmp->width,tmp->height,tmp->nChannels,tmp->depth);
- //printf("grayBg: %ix%i channels: %i depth:%i\n",grayBg.getCvImage()->width,grayBg.getCvImage()->height,grayBg.getCvImage()->nChannels,grayBg.getCvImage()->depth);
+ tmp = new IplImage(backchan);
+ grayBg = tmp;
+
+ }
+ //first, optionally remove shadows from FG
+ //possibly use 1/4 screen res?
+
+ //to remove shadows, need hsv of foreground and background
+ if (diffchannel>chan_B||removeShadows) cvtColor(img, hsvfront, CV_BGR2HSV);
+
+ cv::Mat outimg;
+
+ if (removeShadows) {
+ vector<cv::Mat> slicesFront, slicesBack;
+ cv::Mat valFront, valBack, satFront, satBack;
+ // split image to H,S and V images
+ split(hsvfront, slicesFront);
+ split(hsvback, slicesBack);
+ slicesFront[2].copyTo(valFront); // get the value channel
+ slicesFront[1].copyTo(satFront); // get the sat channel
- grayBg = tmp;
+ slicesBack[2].copyTo(valBack); // get the value channel
+ slicesBack[1].copyTo(satBack); // get the sat channel
- }
- grayFrame = currentFrame;
+ int x,y;
+ for(x=0; x<currentFrame.getWidth(); ++x) {
+ for(y=0; y<currentFrame.getHeight(); ++y) {
+ bool sat = ((satFront.at<cv::Vec3b>(y,x)[0] > satBack.at<cv::Vec3b>(y,x)[0]-shadowThreshold) &&
+ (satFront.at<cv::Vec3b>(y,x)[0] < satBack.at<cv::Vec3b>(y,x)[0]+shadowThreshold));
+
+ if(sat && (valFront.at<cv::Vec3b>(y,x)[0] < valBack.at<cv::Vec3b>(y,x)[0])) {
+ hsvfront.at<cv::Vec3b>(y,x)[0]= hsvback.at<cv::Vec3b>(y,x)[0];
+ hsvfront.at<cv::Vec3b>(y,x)[1]= hsvback.at<cv::Vec3b>(y,x)[1];
+ hsvfront.at<cv::Vec3b>(y,x)[2]= hsvback.at<cv::Vec3b>(y,x)[2];
+ }
+
+ }
+ }
+ //convert back into RGB if necessary
+
+ if (diffchannel<chan_H) cvtColor(hsvfront, outimg, CV_HSV2BGR);
+ else outimg=hsvfront;
+
+ }else {
+ outimg=img;
+ }
- // take the abs value of the difference between background and incoming and then threshold:
- grayDiff.absDiff(grayBg, grayFrame);
- grayDiff.threshold(threshold);
- //grayFrame.adaptiveThreshold( threshold,10,false,true); //int blockSize, int offset=0,bool invert=false, bool gauss=false);
- //grayDiff.erode_3x3();
- //grayDiff.resize(windowWidth,windowHeight);
+
+ //select correct channel for comparison and put into grayFrame
+
+ vector<cv::Mat> chans;
+ split(outimg,chans);
+
+ switch (diffchannel) {
+ case chan_R:
+ chans[0].copyTo(frontchan);
+ break;
+ case chan_G:
+ chans[1].copyTo(frontchan);
+ break;
+ case chan_B:
+ chans[2].copyTo(frontchan);
+ break;
+ case chan_H:
+ chans[0].copyTo(frontchan);
+ break;
+ case chan_S:
+ chans[1].copyTo(frontchan);
+ break;
+ case chan_V:
+ chans[2].copyTo(frontchan);
+ break;
+ }
+
+ IplImage* tmp = new IplImage(outmat);
+ tmp = new IplImage(frontchan);
+ grayFrame = tmp;
+
+ // take the abs value of the difference between background and incoming and then threshold:
+ grayDiff.absDiff(grayBg, grayFrame);
+ grayDiff.threshold(threshold);
+ //grayFrame.adaptiveThreshold( threshold,10,false,true); //int blockSize, int offset=0,bool invert=false, bool gauss=false);
+ //grayDiff.erode_3x3();
+ //grayDiff.resize(windowWidth,windowHeight);
/*
- //MOG
- mog(img, outmat, mogf);
-
- // Complement the image
- //cv::threshold(outmat, output, threshold, 255, cv::THRESH_BINARY_INV);
- IplImage* tmp1 = new IplImage(outmat);
- //printf("tmp: %ix%i channels: %i depth:%i\n",tmp->width,tmp->height,tmp->nChannels,tmp->depth);
- //printf("grayDiff: %ix%i channels: %i depth:%i\n",grayDiff.getCvImage()->width,grayDiff.getCvImage()->height,grayDiff.getCvImage()->nChannels,grayDiff.getCvImage()->depth);
- grayDiff=tmp1; //copy to ofx
+ //MOG
+ mog(img, outmat, mogf);
+
+ // Complement the image
+ //cv::threshold(outmat, output, threshold, 255, cv::THRESH_BINARY_INV);
+ IplImage* tmp1 = new IplImage(outmat);
+ //printf("tmp: %ix%i channels: %i depth:%i\n",tmp->width,tmp->height,tmp->nChannels,tmp->depth);
+ //printf("grayDiff: %ix%i channels: %i depth:%i\n",grayDiff.getCvImage()->width,grayDiff.getCvImage()->height,grayDiff.getCvImage()->nChannels,grayDiff.getCvImage()->depth);
+ grayDiff=tmp1; //copy to ofx
*/
- contourFinder.findContours(grayDiff, 200, (640*480)/3, 20, false); // find holes
+ contourFinder.findContours(grayDiff, 200, (640*480)/3, 20, false); // find holes
@@ -280,11 +353,11 @@ void testApp::draw(){
}
- ofSetHexColor(0xffffff);
+ ofSetHexColor(0xff0000);
char reportStr[1024];
- sprintf(reportStr, "fps: %f\nthreshold: %f", ofGetFrameRate(),threshold);
+ sprintf(reportStr, "fps: %f\nthreshold: %f\nshadow threshold: %i", ofGetFrameRate(),threshold,shadowThreshold);
//sprintf(reportStr, "fps: %f\nmog: %f", ofGetFrameRate(),mogf);
- ofDrawBitmapString(reportStr, 1100, 440);
+ ofDrawBitmapString(reportStr, 1100, 420);
}
@@ -292,7 +365,7 @@ void testApp::draw(){
//--------------------------------------------------------------
void testApp::keyPressed(int key){
switch (key){
- case '+':
+ case '=':
threshold ++;
mogf +=.001;
if (threshold > 255) threshold = 255;
@@ -302,6 +375,14 @@ void testApp::keyPressed(int key){
mogf-=.001;
if (threshold < 0) threshold = 0;
break;
+ case '+':
+ shadowThreshold ++;
+ if (shadowThreshold > 255) threshold = 255;
+ break;
+ case '_':
+ shadowThreshold --;
+ if (shadowThreshold < 0) threshold = 0;
+ break;
case '1':
diffchannel = chan_R;
break;
@@ -320,6 +401,10 @@ void testApp::keyPressed(int key){
case '6':
diffchannel = chan_V;
break;
+ case 's':
+ removeShadows=!removeShadows;
+ printf(removeShadows?"removing shadows\n":"not removing shadows\n");
+ break;
}
}
diff --git a/cvtest/src/testApp.h b/cvtest/src/testApp.h
index 4207bb9..32d7158 100644
--- a/cvtest/src/testApp.h
+++ b/cvtest/src/testApp.h
@@ -47,7 +47,7 @@ class testApp : public ofBaseApp{
//try to accumulate background using SHORT datatype ie CV_16UC / 4 (would 4 be faster, 64 bits)
- cv::Mat accumulator,outmat,hsvback,outchan,output; // background accumulation
+ cv::Mat accumulator,outmat,hsvback,hsvfront,backchan,frontchan,output; // background accumulation
int frameno;
ofxCvColorImage currentFrame;
@@ -72,6 +72,9 @@ class testApp : public ofBaseApp{
int diffchannel;
+ bool removeShadows;
+ int shadowThreshold;
+
/*
ofxOpenCvUtilsForeground* fg;
ofxOpenCvUtilsMeanShift* meanShift;
diff --git a/gaunt01/src/bird.cpp b/gaunt01/src/bird.cpp
index 49567bc..18f8227 100644
--- a/gaunt01/src/bird.cpp
+++ b/gaunt01/src/bird.cpp
@@ -37,13 +37,18 @@ bird::bird()
centrePoint=ofVec2f(ofGetWidth()/2,600); //quick and dirty
}
+void bird::setBounds(ofPlane* _bounds) {
+ bounds=_bounds;
+}
bird::~bird()
{
//dtor
}
-void bird::update(map<int,player>& players, float angle,vector<ofPoint> border){
+void bird::update(map<int,player>& players, float angle){
+
+ //movement basics
float time=ofGetElapsedTimef();
float timeSeg=time-lastTime;
lastTime=time;
@@ -53,9 +58,77 @@ void bird::update(map<int,player>& players, float angle,vector<ofPoint> border){
position-=direction.rotated(heading,ofVec3f(0,0,-1))*velocity*timeSeg; //.rotate(heading,ofVec3f(0,1,0))
+ //tending to straighten unless avoiding an edge
+ //turnRate *= 0.995;
+
+ //create, -->draw a line representing the birds heading
+
//absolute ray pointer
+ //put on ground
+ //do intersection with outline?
+ //ray: can intersect with another ray
+ //so: make a ray for each segment of polygon and intersect, find the nearest?
+ //ray:: intersect returns a ray- shortest line between lines
+ //or: project bird & heading onto ground
+ //get intersection with each line of poly bounds
+ //or maybe just use screen edges- quicker
+ //find the shortest
+ //depending on the angle it makes, decide whether to turn left or right to avoid boundary or get back within it
+
+ //similar for people - use similar 2d algorithm
+ //deal with elevation seperately
+
+ //bounds is already an array of 4 planes representing edges of screen- does this work?
pointer=ofRay(position.rotated(angle,ofVec3f(1,0,0)),-direction.rotated(heading,ofVec3f(0,0,-1)).rotated(angle,ofVec3f(1,0,0))*1000.0f,false);
+ //intersect with bounds and find shortest distance to edge of world
+ float shortest=1000000.0f;
+ vector <ofVec3f> pts;
+ int shnum=-1;
+ int bdnum=0;
+ for (int i=0;i<4;i++) {
+ ofVec3f p;
+ if (bounds[i].intersect(pointer,p)) {
+ pts.push_back(p);
+ if (position.rotated(angle,ofVec3f(1,0,0)).distance(p)<shortest) {
+ shortest=position.rotated(angle,ofVec3f(1,0,0)).distance(p);
+ shnum=pts.size()-1;
+ bdnum=i;
+ }
+ }
+ }
+ if (shnum>-1) {
+ edgepoint=pts[shnum];
+ edgelength=shortest;
+ ofVec3f bv=bounds[bdnum].getNormal().rotated(angle,ofVec3f(-1,0,0));
+ edgeangle=((atan2(bv.y,bv.x)*RAD_TO_DEG)+90)+heading;
+ while (edgeangle>180) edgeangle -=360;
+ while (edgeangle<-180) edgeangle +=360;
+ }
+ else {
+ printf("error: no bird bounds intersection\n");
+ }
+
+ //decide whether we are running out of space and if so, which way to turn#
+ //turning tendency is more acute when we are more perpendicular, and closer, to the edge
+ //mayeb work out how many frames left before we crash
+
+ //turn formula max(0,90-abs(turnangle))*sign(turnangle)
+ //avoiding edges is a bit of a nightmare
+ //what about a force attracting to the middle of the screen?
+
+ turnRate=(turnRate*.99)+(0.05f*max(0.0f,1.0f-(pow(edgelength*.0100f,2.0f)))*max(0.0f,90.0f-abs(edgeangle))*sign(edgeangle));
+
+ //strip it right back
+ //bird flying in circle
+
+ //bird cruising while avoiding edges
+ //bird floowing people while avoiding edges
+ //bird changing height
+ //morph targets
+
+ /*
+
ofRay relpointer=ofRay(position,-direction.rotated(heading,ofVec3f(0,0,-1))*1000.0f,false);
playang.clear();
@@ -102,6 +175,7 @@ void bird::update(map<int,player>& players, float angle,vector<ofPoint> border){
}
if (nearest>-1) turnRate=-playhead[nearest];
}
+ */
}
void bird::draw(){
@@ -135,6 +209,11 @@ void bird::drawDebug(){
if (leaving) ofSetHexColor(0xff0000);
else ofSetHexColor(0xff00ff);
ofLine(pointer.s,pointer.s+1000*pointer.t);
+ ofSphere(edgepoint,2.0f);
+
+ char numStr[64];
+ sprintf(numStr, "close: %4.1f\nangle: %4.1f\nheading: %4.1f", edgelength,edgeangle,heading);
+ ofDrawBitmapString(numStr,10,10);
}
diff --git a/gaunt01/src/bird.h b/gaunt01/src/bird.h
index 3ab0da9..c1f3ac7 100644
--- a/gaunt01/src/bird.h
+++ b/gaunt01/src/bird.h
@@ -22,22 +22,24 @@
-build basic of time/speed/heading update/draw
-basic anim cycle
-
-
-
+
+
+
MAKING THE BIRD CHASE THE PLAYERS
-
+
find distance to edge of play - alter turn behaviour depending on how bird needs to head
detect player in FOV
turn & swoop
pick player and remove
-
+
planes frustrum belongs to camera
bird checks ray dist to planes
avoids nearest +ve
*/
+#define sign(x) ((x > 0) - (x < 0))
+
#include "ofMain.h"
#include "morphmesh.h"
#include "normBindTexture.h"
@@ -51,34 +53,41 @@ class bird
public:
bird();
virtual ~bird();
- void update(map<int,player>& players,float angle,vector<ofPoint> border);
+ void update(map<int,player>& players,float angle);
void draw();
void drawShadow();
void drawDebug();
+ void setBounds(ofPlane* _bounds);
+
string currentseq;
morphmesh model;
-
+
ofRay pointer;
ofVec3f position;
ofVec2f centrePoint; //2d coords of centre of screen
-
+
+ ofVec3f edgepoint; //point where the bird will hit the edge of the screen
+ float edgelength,edgeangle;
+
vector<float> playang;
vector<float> playhead;
vector<float> playdip;
vector<float> playdist;
vector<ofVec3f> playpos;
-
+
+ ofPlane* bounds;
+
bool leaving;
float centrehead;
-
+
float heading;
-
+
protected:
private:
-
+
float fieldofview;
-
+
ofVec3f direction;
float velocity; //per second
@@ -89,7 +98,7 @@ class bird
ofImage texture;
-
+
};
diff --git a/gaunt01/src/main.cpp b/gaunt01/src/main.cpp
index 1f3ac12..d8ec5c8 100644
--- a/gaunt01/src/main.cpp
+++ b/gaunt01/src/main.cpp
@@ -6,7 +6,7 @@
int main( ){
ofAppGlutWindow window;
- ofSetupOpenGL(&window, 1024,768, OF_FULLSCREEN ); // <-------- setup the GL context
+ ofSetupOpenGL(&window, 1024,768, OF_WINDOW ); // <-------- setup the GL context
printf("%ix%i on screen %ix%i\n",ofGetWidth(),ofGetHeight(),ofGetScreenWidth(),ofGetScreenHeight());
// this kicks off the running of my app
// can be OF_WINDOW or OF_FULLSCREEN
diff --git a/gaunt01/src/testApp.cpp b/gaunt01/src/testApp.cpp
index 853a23f..6ac3d26 100644
--- a/gaunt01/src/testApp.cpp
+++ b/gaunt01/src/testApp.cpp
@@ -113,7 +113,7 @@ void testApp::setup(){
}
scaleFactor=ofVec2f(windowWidth/1280.0f,windowHeight/768.0f);
- gameState=TITLES;
+ gameState=PLAYING; //TITLES;
segTimes[TITLES]=4.0;
segTimes[CREDIT]=2.5;
@@ -137,6 +137,7 @@ void testApp::setup(){
cam.end();
updatePlane();
+ Bird.setBounds(bounds);
}
@@ -265,27 +266,32 @@ void testApp::updatePlane(){
ofVec2f l=ofVec2f(windowWidth/20,windowHeight/2);
ofRay r=projector.castPixel(l.x,l.y);
ofVec3f p;
- plane.intersect(r,p);
- ofVec3f pn=(p-projector.getGlobalPosition()).getPerpendicular(ofVec3f(0,1,0));
- bounds[0]=ofPlane(p,pn,pn,ofVec2f(1000,1000));
+ if (plane.intersect(r,p)) printf("found ground plane intersection 1 at %f,%f,%f\n",p.x,p.y,p.z);
+ else printf("bound plane 1 not found\n");
+ ofVec3f pn=(p-projector.getGlobalPosition()).getPerpendicular(ofVec3f(0,-1,0));
+ bounds[0]=ofPlane(p,pn); //,pn,ofVec2f(1000,1000));
l=ofVec2f(windowWidth/2,windowHeight/20);
r=projector.castPixel(l.x,l.y);
- plane.intersect(r,p);
+ if (plane.intersect(r,p)) printf("found ground plane intersection 2 at %f,%f,%f\n",p.x,p.y,p.z);
+ else printf("bound plane 2 not found\n");
pn=(p-projector.getGlobalPosition()).getPerpendicular(ofVec3f(1,0,0));
- bounds[1]=ofPlane(p,pn,-pn,ofVec2f(1000,1000));
+ bounds[1]=ofPlane(p,pn); //,-pn,ofVec2f(1000,1000));
l=ofVec2f(19*windowWidth/20,windowHeight/2);
r=projector.castPixel(l.x,l.y);
- plane.intersect(r,p);
+ if (plane.intersect(r,p)) printf("found ground plane intersection 3 at %f,%f,%f\n",p.x,p.y,p.z);
+ else printf("bound plane 3 not found\n");
pn=(p-projector.getGlobalPosition()).getPerpendicular(ofVec3f(0,1,0));
- bounds[2]=ofPlane(p,pn,-pn,ofVec2f(1000,1000));
+ bounds[2]=ofPlane(p,pn); //,-pn,ofVec2f(1000,1000));
- l=ofVec2f(windowWidth/2,19*windowHeight/20);
+ l=ofVec2f(windowWidth/2,19*(windowHeight/20));
r=projector.castPixel(l.x,l.y);
- plane.intersect(r,p);
- pn=(p-projector.getGlobalPosition()).getPerpendicular(ofVec3f(1,0,0));
- bounds[1]=ofPlane(p,pn,-pn,ofVec2f(1000,1000));
+ if (plane.intersect(r,p)) printf("found ground plane intersection 4 at %f,%f,%f\n",p.x,p.y,p.z);
+ else printf("bound plane 4 not found\n");
+ pn=(p-projector.getGlobalPosition()).getPerpendicular(ofVec3f(-1,0,0));
+ bounds[3]=ofPlane(p,pn); //,-pn,ofVec2f(1000,1000));
+
// vector<trapdoor> trapdoors;
// float trapdoorSize;
@@ -504,7 +510,7 @@ void testApp::update(){
}
}
- Bird.update(players,cam_angle,border);
+ Bird.update(players,cam_angle);
}
@@ -693,13 +699,14 @@ void testApp::draw(){
ofVec3f bp;
for (int i=0;i<4;i++) {
bounds[i].draw();
- if (
- bounds[i].intersect(Bird.pointer,bp)) {
+ if (bounds[i].intersect(Bird.pointer,bp)) {
char numStr[16];
sprintf(numStr, "%4.1f", (bp-Bird.position.rotated(cam_angle,ofVec3f(1,0,0))).length());
ofVec3f sc=cam.worldToScreen(bp);
ofDrawBitmapString(numStr, sc.x, sc.y);
}
+ //ofLine(bounds[i].getCenter(),bounds[i].getCenter()+(bounds[i].getNormal()*100));
+ //normals sorted
}
ofSetHexColor(0xffff77);
char numStr[16];