summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Redfern <tim@eclectronics.org>2012-08-21 15:44:11 +0100
committerTim Redfern <tim@eclectronics.org>2012-08-21 15:44:11 +0100
commit621674d6e9fb0024645cd0020c6afb51e4b4a7e9 (patch)
treea9e4fbda9ff8afd13dab0228de83c7041101947c
parentfbc66a871153a8b85006e49f755cc8fb540781f7 (diff)
had enough
-rw-r--r--gaunt01/src/main.cpp16
-rw-r--r--gaunt01/src/testApp.cpp1263
2 files changed, 1279 insertions, 0 deletions
diff --git a/gaunt01/src/main.cpp b/gaunt01/src/main.cpp
new file mode 100644
index 0000000..1f3ac12
--- /dev/null
+++ b/gaunt01/src/main.cpp
@@ -0,0 +1,16 @@
+#include "ofMain.h"
+#include "testApp.h"
+#include "ofAppGlutWindow.h"
+
+//========================================================================
+int main( ){
+
+ ofAppGlutWindow window;
+ ofSetupOpenGL(&window, 1024,768, OF_FULLSCREEN ); // <-------- 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
+ // pass in width and height too:
+ ofRunApp( new testApp());
+
+}
diff --git a/gaunt01/src/testApp.cpp b/gaunt01/src/testApp.cpp
new file mode 100644
index 0000000..beefce0
--- /dev/null
+++ b/gaunt01/src/testApp.cpp
@@ -0,0 +1,1263 @@
+#include "testApp.h"
+
+//--------------------------------------------------------------
+//units ~ 10cm
+//
+/*
+Can use a floating point image or array to accumulate the screen and generate averaged background?
+
+Is this too much work for every frame? Should it be put in a seperate thread?
+
+*/
+
+void testApp::setup(){
+
+ printf("setup: %ix%i on screen %ix%i\n",ofGetWidth(),ofGetHeight(),ofGetScreenWidth(),ofGetScreenHeight());
+
+ int windowMode = ofGetWindowMode();
+ if(windowMode == OF_FULLSCREEN){
+ this->windowWidth = ofGetScreenWidth();
+ this->windowHeight = ofGetScreenHeight();
+ }
+ else if(windowMode == OF_WINDOW){
+ this->windowWidth = ofGetWidth();
+ this->windowHeight = ofGetHeight();
+ }
+
+ mirror=true;
+
+ bLearnBakground = true;
+ cam_angle=0;
+ threshold = 80;
+
+ loadSettings("settings.xml");
+
+ vidGrabber.setVerbose(true);
+ if (vidGrabber.initGrabber(640,480)) {
+ hasCamera=true;
+ useCamera=true;
+ }
+ else
+ {
+ hasCamera=false;
+ useCamera=false;
+ vidPlayer.loadMovie(testmovie); //footage/ camera needs to be the same res as opencv planes and output
+ vidPlayer.setLoopState(OF_LOOP_NORMAL);
+ vidPlayer.play();
+ }
+
+ /*
+ accumImg.allocate(640,480);
+
+ bgImg.allocate(640,480);
+ bgImg.setUseTexture(true);
+
+
+
+ grayImage.allocate(640,480);
+ grayBg.allocate(640,480);
+ */
+
+ colorImg.allocate(640,480);
+ colorImg.setUseTexture(true);
+ currentFrame.allocate(CAM_WIDTH_FG, CAM_HEIGHT_FG);
+ background.allocate(CAM_WIDTH_FG, CAM_HEIGHT_FG);
+ background.setUseTexture(true);
+
+ grayFrame.allocate(CAM_WIDTH_FG, CAM_HEIGHT_FG);
+ grayBg.allocate(CAM_WIDTH_FG, CAM_HEIGHT_FG);
+ grayDiff.allocate(CAM_WIDTH_FG, CAM_HEIGHT_FG);
+
+ mogoutput.allocate(CAM_WIDTH_FG, CAM_HEIGHT_FG);
+
+ learningRate = 0.01f;
+ bFirstFrame=true;
+
+ diffchannel=chan_V;
+ hsvback = cvCreateImage(cvGetSize(currentFrame.getCvImage()), currentFrame.getCvImage()->depth, currentFrame.getCvImage()->nChannels);
+ //backchan = cvCreateImage(cvGetSize(currentFrame.getCvImage()), 8, 1);
+
+ removeShadows=false;
+ shadowThreshold=10;
+ ////////////////////////////
+
+ blobsManager.normalizePercentage = 0.7;
+ blobsManager.giveLowestPossibleIDs = false;
+ blobsManager.maxUndetectedTime = 500;
+ blobsManager.minDetectedTime = 500;
+ blobsManager.debugDrawCandidates = true;
+
+ ofVec3f centre=ofVec3f(windowWidth/2,0,0);
+ ofVec3f normal=ofVec3f(0,0,-1);
+ ray=ofRay();
+ plane=ofPlane(centre,normal);
+ plane.color=ofColor(255,255,255);
+
+ projector=ofProjector(2.0f, ofVec2f(0.0f, 0.0f),windowWidth,windowHeight); //1.535f
+ projector.setPosition(windowWidth/2,windowHeight/2,-windowWidth);
+ projector.lookAt(ofVec3f(windowWidth/2,windowHeight/2,0),ofVec3f(0, -1, 0));
+
+ cam=ofCamera();
+ cam.setPosition(windowWidth/2,windowHeight/2,-windowWidth);
+ cam.lookAt(ofVec3f(windowWidth/2,windowHeight/2,0),ofVec3f(0, -1, 0));
+ cam.setFov(41.1); //39.85); //53.13);
+ cam.cacheMatrices(); //stop error messages - changed API?
+
+ testpts=new ofVec3f[4];
+
+ bounds=new ofPlane[4];
+
+ //trapDoor=trapdoor(screen2plane(ofVec2f(windowWidth,0)),screen2plane(ofVec2f(windowWidth,windowHeight)),35);
+ trapdoorSize=35;
+ trapdoorSlotSize=50;
+
+
+ trapdoorTime=10.0; //time per trapdoor;
+
+
+
+ mode=PLAY;
+
+ drawStats=false;
+
+ bgnum=1000;
+ firstframe=true;
+
+ light.setPosition(windowWidth,0,windowHeight);
+ light.enable();
+
+ drawingborder=false;
+
+ billboards=new ofImage[4];
+ billboards[0].loadImage("GUI_title.png");
+ billboards[1].loadImage("GUI_objective.png");
+ billboards[2].loadImage("GUI_gotya.png");
+ billboards[3].loadImage("GUI_instructions.png");
+
+ for (int i=0;i<4;i++) {
+ billboards[i].setAnchorPercent(0.5,0.5);
+ }
+ scaleFactor=ofVec2f(windowWidth/1280.0f,windowHeight/768.0f);
+
+ gameState=TITLES; //PLAYING; //TITLES; //
+
+ segTimes[TITLES]=4.0;
+ segTimes[EXPLAIN]=5.0;
+ segTimes[PLAYING]=60.0;
+ segTimes[GOTCHA]=4.0;
+
+ gameStart=ofGetElapsedTimef();
+
+ sounds=new ofSoundPlayer[1];
+ sounds[0].loadSound("arp5.mp3"); //game start
+
+
+ doorsounds=new ofSoundPlayer[4];
+ doorsounds[0].loadSound("creeky door short1.wav");
+ doorsounds[1].loadSound("creeky door short2.wav");
+ doorsounds[2].loadSound("creeky door short3.wav");
+ doorsounds[3].loadSound("voice falling down hole.wav");
+
+ cam.begin();
+
+
+ //mog=cv::BackgroundSubtractorMOG(100,10,.1,.1);
+
+
+ cam.end();
+
+}
+
+ofVec2f testApp::screen2plane(ofVec2f screenpos){
+ ofVec3f p;
+ ray=projector.castPixel(screenpos.x,screenpos.y);
+ bool hit = plane.intersect(ray,p);
+ return ofVec2f(p.x,pow(pow(p.y,2)+pow(p.z,2),0.5f));
+}
+ofVec3f testApp::plane2world(ofVec2f planepos){
+ return ofVec3f(planepos.x,planepos.y,0);
+}
+bool testApp::rectsCross(ofRectangle rect1,ofRectangle rect2) {
+ bool overlap=true; //must overlap in x and y
+ if (rect1.x<rect2.x) {
+ if (rect1.x+rect1.width<rect2.x) overlap = false;
+ }
+ else if (rect2.x<rect1.x) {
+ if (rect2.x+rect2.width<rect1.x) overlap = false;
+ }
+ if (overlap) { //still possible
+ if (rect1.y<rect2.y) {
+ if (rect1.y+rect1.height<rect2.y) overlap = false;
+ }
+ else if (rect2.y<rect1.y) {
+ if (rect2.y+rect2.height<rect1.y) overlap = false;
+ }
+ }
+ return overlap;
+}
+
+void testApp::tessGround(int num){
+ //updates ground and makes texture coords;
+
+ if (num==0) {
+ groundlines.clear();
+ ofPolyline pol;
+ pol.addVertex(0,0);
+ pol.addVertex(windowWidth,0);
+ pol.addVertex(windowWidth,windowHeight);
+ pol.addVertex(0,windowHeight);
+ pol.close();
+ groundlines.push_back(pol);
+ }
+
+ vector<ofVec2f> corners=trapdoors[num].getCorners();
+ ofPolyline pol2;
+ for (int i=0;i<corners.size();i++) {
+
+ ofVec3f cw=ofVec3f(corners[i].x,corners[i].y,0).rotated(cam_angle,ofVec3f(1,0,0));
+ ofVec3f s=cam.worldToScreen(cam.worldToScreen(cw));
+ //printf("corner %i: %f,%f to %f,%f,%f\n",i,corners[i].x,corners[i].y,s.x/s.z,s.y/s.z,s.z);
+ pol2.addVertex(s.x,s.y);
+ }
+ pol2.close();
+ groundlines.push_back(pol2);
+
+ ground=ofMesh();
+ tess.tessellateToMesh(groundlines,OF_POLY_WINDING_ODD,ground,true);
+ for (int i=0;i<ground.getNumVertices();i++) {
+ ofVec3f v=ground.getVertex(i);
+ ground.addTexCoord(ofVec2f(v.x/windowWidth,v.y/windowHeight));
+ }
+
+}
+
+void testApp::makeGround(int doornumber){
+ //create ground mesh with hole for trapdoor
+
+ //IS NOT WORKING!??
+ //it is building a hole somewhere.. whats.. the.. problem..
+ ground=ofMesh();
+ ground.addVertex(ofVec3f(0,0,0));
+ ground.addTexCoord(ofVec2f(0,0));
+ ground.addVertex(ofVec3f(windowWidth,0,0));
+ ground.addTexCoord(ofVec2f(1,0));
+ ground.addVertex(ofVec3f(windowWidth,windowHeight,0));
+ ground.addTexCoord(ofVec2f(1,1));
+ ground.addVertex(ofVec3f(0,windowHeight,0));
+ ground.addTexCoord(ofVec2f(0,1));
+
+ vector<ofVec2f> corners=trapdoors[doornumber].getCorners();
+ ofVec2f screenCorners[4];
+
+ for (int i=0;i<corners.size();i++) {
+
+ ofNode axis;
+ ofNode c;
+ c.setParent(axis);
+
+ c.setPosition(corners[i].x,corners[i].y,0);
+
+ axis.rotate(cam_angle,1,0,0);
+
+ ofVec3f p=c.getGlobalPosition();
+
+ testpts[i]=p;
+ ofVec3f s=cam.worldToScreen(p);
+ //printf("corner %i: %f,%f,%f\n",i,s.x,s.y,s.z);
+ screenCorners[i]=ofVec2f(s.x,s.y);
+
+ ground.addVertex(s);
+ ground.addTexCoord(ofVec2f(s.x/windowWidth,s.y/windowHeight));
+ }
+ //join a quad for each side
+ // the 2 rear sides should always point towards the rear
+ for (int i=0;i<3;i++) {
+ ground.addTriangle(i,i+4,i+5);
+ ground.addTriangle(i+5,i+1,i);
+ }
+ ground.addTriangle(3,7,0);
+ ground.addTriangle(7,4,0);
+ float x=min(screenCorners[0].x,screenCorners[3].x);
+ float y=min(screenCorners[0].y,screenCorners[1].y);
+ float w=max(screenCorners[1].x,screenCorners[2].x)-x;
+ float h=max(screenCorners[2].y,screenCorners[3].y)-y;
+ //trapDoor.setBoundingRect(x,y,w,h);
+}
+
+
+void testApp::updatePlane(){
+ //setup screen for new incline angle
+ plane.setNormal(ofVec3f(0,sin(cam_angle*DEG_TO_RAD),-cos(cam_angle*DEG_TO_RAD)));
+
+ //bird intersect planes
+ ofVec2f l=ofVec2f(windowWidth/20,windowHeight/2);
+ ofRay r=projector.castPixel(l.x,l.y);
+ ofVec3f p;
+ 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);
+ 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));
+
+ l=ofVec2f(19*windowWidth/20,windowHeight/2);
+ r=projector.castPixel(l.x,l.y);
+ 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));
+
+ l=ofVec2f(windowWidth/2,19*(windowHeight/20));
+ r=projector.castPixel(l.x,l.y);
+ 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));
+
+ l=ofVec2f(windowWidth/2,windowHeight/2);
+ r=projector.castPixel(l.x,l.y);
+ if (plane.intersect(r,centre)) printf("found centre point at %f,%f,%f\n",centre.x,centre.y,centre.z);
+ else printf("centre point not found\n");
+ ofVec3f c=centre.rotated(cam_angle,ofVec3f(-1,0,0));
+
+ Bird.setBounds(bounds);
+ Bird.setCentre(ofVec2f(c.x,c.y));
+
+
+// vector<trapdoor> trapdoors;
+// float trapdoorSize;
+// float trapdoorSlotSize;
+// int numtrapdoorSlots;
+
+//create all trapdoors at once, deactivated.
+//shuffle them
+//at each timeout, pick the next door to activate (check if within bounds) and rebuild ground with holes
+//on update, check all active doors against all players
+//when a falling in sequence is over, start again
+
+ trapdoors.clear();
+
+ l=ofVec2f(windowWidth/2,19*windowHeight/20);
+ r=projector.castPixel(l.x,l.y);
+ plane.intersect(r,p);
+ float closestY=p.rotated(cam_angle,ofVec3f(-1,0,0)).y;
+ numtrapdoorSlots=closestY/trapdoorSlotSize;
+
+ //get middle position in the slot on the ground visible at front of screen
+ ofVec3f rp=ofVec3f(windowWidth/2,closestY-(0.5*trapdoorSlotSize),0);
+ //translate to the screen
+ ofVec3f sp=cam.worldToScreen(rp.rotated(cam_angle,ofVec3f(1,0,0)));
+
+ //printf("front slot: %f,%f to %f,%f,%f\n",rp.x,rp.y,sp.x,sp.y,sp.z);
+
+ //get point at left of this line
+ //project back on ground
+ ofVec2f gb=screen2plane(ofVec2f(windowWidth/8,sp.y));
+ r=projector.castPixel(sp.x,sp.y);
+ plane.intersect(r,p);
+ float range=(((windowWidth/2)-p.x)*2);
+
+ for (int i=0;i<numtrapdoorSlots;i++) {
+ //get middle position in the slot on the ground visible on screen
+ ofVec3f rp=ofVec3f(windowWidth/2,closestY-((i+0.5)*trapdoorSlotSize),0);
+ //randomise horizontal pos
+ rp.x=ofRandom(trapdoorSlotSize*5)-(trapdoorSlotSize*2.5)+(+windowWidth/2);
+
+ //gb.x=ofRandom((windowWidth-gb.x)*2)+gb.x;
+ if (border.size()<3||!OutsidePolygon(border,ofPoint(rp.x,rp.y))) {
+ trapdoors.push_back(trapdoor(ofVec2f(rp.x,rp.y),trapdoorSize));
+ }
+ }
+ random_shuffle ( trapdoors.begin(), trapdoors.end() );
+
+ trapdoorCounter=0;
+ trapdoors[trapdoorCounter].active=true;
+
+ //makeGround(0);
+
+
+
+
+ tessGround(0);
+
+ /*
+ //create ground mesh with hole for trapdoor
+
+ //IS NOT WORKING!??
+ //it is building a hole somewhere.. whats.. the.. problem..
+ /*
+ ground=ofMesh();
+ ground.addVertex(ofVec3f(0,0,0));
+ ground.addTexCoord(ofVec2f(0,0));
+ ground.addVertex(ofVec3f(windowWidth,0,0));
+ ground.addTexCoord(ofVec2f(1,0));
+ ground.addVertex(ofVec3f(windowWidth,windowHeight,0));
+ ground.addTexCoord(ofVec2f(1,1));
+ ground.addVertex(ofVec3f(0,windowHeight,0));
+ ground.addTexCoord(ofVec2f(0,1));
+
+ vector<ofVec2f> corners=trapdoors[0].getCorners();
+ ofVec2f screenCorners[4];
+
+ for (int i=0;i<corners.size();i++) {
+
+ ofNode axis;
+ ofNode c;
+ c.setParent(axis);
+
+ c.setPosition(corners[i].x,corners[i].y,0);
+
+ axis.rotate(cam_angle,1,0,0);
+
+ ofVec3f p=c.getGlobalPosition();
+
+ testpts[i]=p;
+ ofVec3f s=cam.worldToScreen(p);
+ //printf("corner %i: %f,%f,%f\n",i,s.x,s.y,s.z);
+ screenCorners[i]=ofVec2f(s.x,s.y);
+
+ ground.addVertex(s);
+ ground.addTexCoord(ofVec2f(s.x/windowWidth,s.y/windowHeight));
+ }
+ //join a quad for each side
+ // the 2 rear sides should always point towards the rear
+ for (int i=0;i<3;i++) {
+ ground.addTriangle(i,i+4,i+5);
+ ground.addTriangle(i+5,i+1,i);
+ }
+ ground.addTriangle(3,7,0);
+ ground.addTriangle(7,4,0);
+ float x=min(screenCorners[0].x,screenCorners[3].x);
+ float y=min(screenCorners[0].y,screenCorners[1].y);
+ float w=max(screenCorners[1].x,screenCorners[2].x)-x;
+ float h=max(screenCorners[2].y,screenCorners[3].y)-y;
+ //trapDoor.setBoundingRect(x,y,w,h);
+*/
+
+}
+
+//--------------------------------------------------------------
+void testApp::update(){
+
+ bool bNewFrame = false;
+
+ if (useCamera) {
+ vidGrabber.grabFrame();
+ bNewFrame = vidGrabber.isFrameNew();
+ }else {
+ vidPlayer.idleMovie();
+ bNewFrame = vidPlayer.isFrameNew();
+ }
+
+ if (bNewFrame){
+
+ if (useCamera) {
+ colorImg.setFromPixels(vidGrabber.getPixels(), 640,480);
+ //accumImg.setFromPixels(vidGrabber.getPixels(), 640,480);
+ }else {
+ colorImg.setFromPixels(vidPlayer.getPixels(), 640,480);
+ //accumImg.setFromPixels(vidPlayer.getPixels(), 640,480);
+ }
+
+ if (mirror) colorImg.mirror(false,true);
+
+ colorImg.updateTexture();
+
+ /*
+
+ grayImage = colorImg;
+
+ if (firstframe) {
+ accumImg=grayImage;
+ firstframe=false;
+ sounds[0].play();
+ }
+ else {
+ accumImg.addWeighted( grayImage, 1.0/bgnum );
+ //accumImg/=(1.0+(0.25/bgnum));
+ }
+
+ bgImg=accumImg;
+ bgImg.updateTexture();
+
+ grayDiff.clear();
+ grayDiff.allocate(640,480);
+
+ // take the abs value of the difference between background and incoming and then threshold:
+ grayDiff.absDiff(bgImg, grayImage);
+ grayDiff.threshold(threshold);
+ //grayDiff.adaptiveThreshold( threshold); //int blockSize, int offset=0,bool invert=false, bool gauss=false);
+ //grayDiff.erode_3x3();
+ grayDiff.resize(windowWidth,windowHeight);
+
+ // find contours which are between the size of 20 pixels and 1/3 the w*h pixels.
+ // also, find holes is set to true so we will get interior contours as well....
+
+ //hard coded size threshold of 100 pix
+ contourFinder.findContours(grayDiff, 200, (640*480)/3, 20, false); // don't find holes
+
+ */
+
+ cv::Mat img = colorImg.getCvImage();
+
+
+
+
+ //if (frameno%1==0) { //I THINK THIS APPROACH IS OK to attempt to lower cpu hit from accumulating?
+
+ //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 imgroi = img(roi);
+
+ if (bFirstFrame) {
+ img.convertTo(accumulator, CV_32FC3);
+ bFirstFrame=false;
+ }
+
+ cv::Mat im3;
+ img.convertTo(im3, CV_32FC3);
+
+
+
+ //accumulator;
+ accumulateWeighted(im3, accumulator, max(1.0f/(ofGetElapsedTimef()*30.0f),learningRate));
+ accumulator.convertTo(outmat,CV_8UC3);
+
+ IplImage* tmp = new IplImage(outmat);
+ background=tmp;
+ background.updateTexture();
+
+ //printf("tmp: %ix%i channels: %i depth:%i\n",tmp->width,tmp->height,tmp->nChannels,tmp->depth);
+
+
+ //get correct channel into backchan
+
+
+
+ vector<cv::Mat> chans;
+
+ //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(backchan);
+ break;
+ case chan_G:
+ split(outmat,chans);
+ chans[1].copyTo(backchan);
+ break;
+ case chan_B:
+ split(outmat,chans);
+ chans[2].copyTo(backchan);
+ break;
+ case chan_H:
+ split(hsvback,chans);
+ chans[0].copyTo(backchan);
+ break;
+ case chan_S:
+ split(hsvback,chans);
+ chans[1].copyTo(backchan);
+ break;
+ case chan_V:
+ split(hsvback,chans);
+ chans[2].copyTo(backchan);
+ break;
+ }
+
+
+ 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
+
+ slicesBack[2].copyTo(valBack); // get the value channel
+ slicesBack[1].copyTo(satBack); // get the sat channel
+
+ 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;
+ }
+
+
+ //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;
+
+ grayDiff.clear();
+ grayDiff.allocate(640,480);
+
+ // take the abs value of the difference between background and incoming and then threshold:
+ grayDiff.absDiff(grayBg, grayFrame);
+ grayDiff.threshold(threshold);
+ //grayDiff.adaptiveThreshold( threshold,20,true,false); //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
+
+ */
+
+
+
+ if (mode==CHECKACCUM) colorImg=grayDiff;
+
+ grayDiff.resize(windowWidth,windowHeight); //wasteful??
+
+
+
+ contourFinder.findContours(grayDiff, 500, (640*480)/3, 20,false); // find holes
+
+ blobsManager.update(contourFinder.blobs);
+ //check players against blob ids - bland do ray casting, update players
+ //ids are always in order
+ //players can be a map vs ID
+ //check if a key exists in a map - map::count
+ //do we purge them or just stop drawing them? is it a problem inheriting tesselator re memory?
+ //if we keep them we can have a 'loserboard'
+ set<int> ids;
+ for(int i=0;i<blobsManager.blobs.size();i++){
+ ofxCvBlob blob = blobsManager.blobs.at(i);
+ ids.insert(blobsManager.blobs.at(i).id);
+ ofRectangle r=blob.boundingRect;
+ ofVec2f blobBase=ofVec2f(r.x+(r.width/2),r.y+r.height-(r.width/2));
+ ofVec3f pp,rp;
+ ray=projector.castPixel(blobBase.x,blobBase.y);
+ bool hit = plane.intersect(ray,pp);
+ rp=pp.getRotated(cam_angle,ofVec3f(-1,0,0));
+ players[blobsManager.blobs.at(i).id].setScreenPosition(blobBase);
+ players[blobsManager.blobs.at(i).id].setWorldPosition(ofVec3f(rp.x,rp.y,0));
+ players[blobsManager.blobs.at(i).id].update(blob); //create model
+ }
+ map<int,player>::iterator it;
+ for (it=players.begin();it!=players.end();it++) {
+ if (ids.find(it->first)==ids.end()||(border.size()>3&&OutsidePolygon(border,ofPoint(it->second.getWorldPosition().x,it->second.getWorldPosition().y)))) it->second.active=false;
+ else it->second.active=true;
+ }
+ }
+ for (int i=0;i<trapdoorCounter;i++) {
+ if (trapdoors[i].checkUpdate(players)) {
+ //makeGround(i);
+ int whichsound=(int)ofRandom(2.9999999);
+ doorsounds[whichsound].play();
+ doorsounds[3].play(); //falling down hole
+ if (gameState=PLAYING) {
+ gameState=GOTCHA;
+ gameStart=ofGetElapsedTimef();
+ }
+ }
+ }
+
+ if (gameState>EXPLAIN) Bird.update(players,cam_angle);
+
+}
+
+//--------------------------------------------------------------
+void testApp::draw(){
+
+ glDisable(GL_LIGHTING);
+ ofBackground(0,0,0);
+ cam.begin();
+ glDisable(GL_DEPTH_TEST);
+ ofSetHexColor(0xffffff);
+ glDisable(GL_BLEND);
+
+ if (gameState<PLAYING||mode==CHECKACCUM) colorImg.draw(0,0,windowWidth,windowHeight);
+
+ else {
+
+ //trapdoor logic
+ if (ofGetElapsedTimef()-trapdoorTimer>trapdoorTime) {
+ if (trapdoors.size()>trapdoorCounter+1) {
+ trapdoorCounter++;
+ trapdoorTimer=ofGetElapsedTimef();
+ tessGround(trapdoorCounter);
+ }
+ //else updatePlane();
+ }
+
+ ofSetHexColor(0xffffff);
+ ofPushMatrix();
+ ofRotate(cam_angle,1,0,0);
+ //trapDoor.draw();
+ for (int i=0;i<=trapdoorCounter;i++) {
+ trapdoors[i].draw();
+ }
+ ofPopMatrix();
+
+//should be in front with holes being recreated for activated trapdoors
+ ofSetHexColor(0xffffff);
+ if (mode==CALIBRATE) bindTexture(colorImg);
+ else bindTexture(background);
+ ground.draw();
+ if (mode==CALIBRATE) unbindTexture(colorImg);
+ else unbindTexture(background);
+
+ ofPushMatrix();
+ ofRotate(cam_angle,1,0,0);
+ Bird.drawShadow();
+ ofPopMatrix();
+
+ glDisable(GL_DEPTH_TEST);
+ ofSetHexColor(0xffffff);
+ ofPushMatrix();
+ ofRotate(cam_angle,1,0,0);
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ for (int i=0;i<=trapdoorCounter;i++) {
+ trapdoors[i].drawSplash(cam_angle);
+
+ }
+
+ glDisable(GL_BLEND);
+ ofPopMatrix();
+
+ if (mode!=CHECKACCUM) {
+ glDisable(GL_DEPTH_TEST);
+ ofSetHexColor(0xffffff);
+ bindTexture(colorImg); //colorImg.getTextureReference().bind();
+ map<int,player>::iterator it;
+ for(int i=0;i<blobsManager.blobs.size();i++){
+ //if(players[blobsManager.blobs.at(i).id].active)
+ players[blobsManager.blobs.at(i).id].draw();
+ }
+ unbindTexture(colorImg);
+ }
+
+ glEnable(GL_LIGHTING);
+
+ ofPushMatrix();
+ ofRotate(cam_angle,1,0,0);
+ Bird.draw();
+ ofPopMatrix();
+
+ glDisable(GL_LIGHTING);
+ }
+
+ float gameTime=ofGetElapsedTimef()-gameStart;
+
+ switch(gameState) {
+ case TITLES:
+ case EXPLAIN:
+ if (gameTime>segTimes[gameState]) {
+ gameState++;
+ gameStart=ofGetElapsedTimef();
+ gameTime=0.0f;
+ updatePlane();
+ }
+ break;
+ case PLAYING:
+ if (gameTime>segTimes[gameState]) {
+ gameState=TITLES;
+ sounds[0].play();
+ gameStart=ofGetElapsedTimef();
+ gameTime=0.0f;
+ trapdoorTimer=ofGetElapsedTimef();
+ }
+ break;
+ case GOTCHA:
+ if (gameTime>segTimes[gameState]) {
+ gameState=PLAYING;
+ gameStart=ofGetElapsedTimef();
+ gameTime=0.0f;
+ updatePlane(); //for new trapdoors
+ }
+ break;
+ }
+
+ float segElapsed=pow(gameTime/segTimes[gameState],2);
+
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ switch(gameState) {
+ case TITLES:
+ billboards[0].draw(windowWidth/6
+ ,scaleFactor.y*((-billboards[0].height/2)+(billboards[0].height*pow(sin(segElapsed*PI),0.4)))
+ ,billboards[0].width*scaleFactor.x
+ ,billboards[0].height*scaleFactor.y);
+ break;
+ /*
+ case CREDIT:
+ billboards[1].draw(windowWidth/6
+ ,windowHeight+(scaleFactor.y*((billboards[1].height/2)-(billboards[1].height*pow(sin(segElapsed*PI),0.4))))
+ ,billboards[1].width*scaleFactor.x
+ ,billboards[1].height*scaleFactor.y);
+ break;
+ */
+ case EXPLAIN:
+ billboards[1].draw(windowWidth/2
+ ,scaleFactor.y*((-billboards[1].height/2)+(billboards[1].height*pow(sin(segElapsed*PI),0.4)))
+ ,billboards[1].width*scaleFactor.x
+ ,billboards[1].height*scaleFactor.y);
+ break;
+ case PLAYING:
+ break;
+ case GOTCHA:
+ billboards[2].draw(windowWidth/2
+ ,scaleFactor.y*((-billboards[2].height/2)+(billboards[2].height*pow(sin(segElapsed*PI),0.4)))
+ ,billboards[2].width*scaleFactor.x
+ ,billboards[2].height*scaleFactor.y);
+ break;
+ }
+ glDisable(GL_BLEND);
+
+
+ switch(mode) {
+ case PLAY:
+
+
+
+ break;
+ case CALIBRATE:
+ case CHECKACCUM:
+
+ ofSetHexColor(0xffffff);
+ ofPushMatrix();
+ ofRotate(cam_angle,1,0,0);
+ for (float i=0;i<=windowWidth;i+=windowWidth/10) {
+ glBegin(GL_LINES);
+ glVertex3f(i,0,0);
+ glVertex3f(i,windowWidth,0);
+ glEnd();
+ glBegin(GL_LINES);
+ glVertex3f(0,i,0);
+ glVertex3f(windowWidth,i,0);
+ glEnd();
+ }
+
+ ofVec2f pp=screen2plane(pos);
+ //ofSphere(pp.x,pp.y,0,5);
+ ofPopMatrix();
+
+ ofPushMatrix();
+ ofRotate(cam_angle,1,0,0);
+ for (int i=0;i<trapdoors.size();i++) {
+ trapdoors[i].drawDebug();
+ }
+ //trapDoor.drawDebug();
+ ofPopMatrix();
+
+ if (gameState==PLAYING) Bird.drawDebug();
+
+ ofSetHexColor(0x77ff77);
+
+
+ ofVec3f bp;
+ for (int i=0;i<4;i++) {
+ bounds[i].draw();
+ 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];
+ sprintf(numStr, "%4.1f", Bird.centrehead);
+ ofVec3f sc=cam.worldToScreen(Bird.position);
+ ofDrawBitmapString(numStr, sc.x, sc.y);
+
+ for(int i=0;i<Bird.playpos.size();i++) {
+ if (abs(Bird.playhead[i])<30) ofSetHexColor(0x77ffff);
+ else ofSetHexColor(0x555555);
+ ofPushMatrix();
+ ofRotate(cam_angle,1,0,0);
+
+ ofLine(Bird.position,Bird.playpos[i]);
+
+ ofPopMatrix();
+ char numStr[10];
+ sprintf(numStr, "%4.1f", Bird.playdist[i]);
+ ofVec3f sc=cam.worldToScreen(ofVec3f((Bird.position.x+Bird.playpos[i].x)/2,(Bird.position.y+Bird.playpos[i].y)/2,(Bird.position.z+Bird.playpos[i].z)/2).rotated(cam_angle,ofVec3f(1,0,0)));
+ ofDrawBitmapString(numStr, sc.x, sc.y);
+ }
+
+ for(int i=0;i<blobsManager.blobs.size();i++){
+
+ ofxCvBlob blob = blobsManager.blobs.at(i);
+ blob.draw(0,0);
+ ofPoint p=blob.centroid;
+ ofSetHexColor(0xffff00);
+ char numStr[16];
+ sprintf(numStr, "%i", blobsManager.blobs[i].id);
+ ofDrawBitmapString(numStr, blob.boundingRect.x, blob.boundingRect.y);
+
+ /*
+ ofPushMatrix();
+ ofRotate(cam_angle,1,0,0);
+ ofTranslate(players[blobsManager.blobs.at(i).id].getWorldPosition());
+ ofBox(0,-10,0,20);
+ //TODO get this into plane axis
+ ofPopMatrix();
+ */
+
+ //
+ }
+ if (border.size()>1) {
+ ofSetHexColor(0x00ff00);
+ ofPushMatrix();
+ ofRotate(cam_angle,1,0,0);
+ for (int i=0;i<border.size();i++) {
+ ofLine(border[i],border[(i+1)%border.size()]);
+ }
+ ofPopMatrix();
+ }
+
+
+
+
+ break;
+ }
+ if (drawStats||mode==CALIBRATE) {
+ ofSetHexColor(0xffffff);
+ char reportStr[1024];
+ sprintf(reportStr, "threshold %i\nfps: %3.1f\n%s",threshold,ofGetFrameRate(),removeShadows?"Removing shadows":"NOT removing shadows"); //\nnum blobs found %i, fps: %f", threshold, contourFinder.nBlobs, ofGetFrameRate());
+ ofDrawBitmapString(reportStr, 10, windowHeight-35);
+ /*
+ char numStr[16];
+ for(int i=0;i<blobsManager.blobs.size();i++){
+ sprintf(numStr, "%i", blobsManager.blobs[i].id);
+ ofDrawBitmapString(numStr, 10+(i*30), windowHeight-55);
+ }
+ */
+ }
+ cam.end();
+ if (drawInstructions) {
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ billboards[3].draw(windowWidth/2,windowHeight/2);
+ glDisable(GL_BLEND);
+ }
+}
+
+//--------------------------------------------------------------
+void testApp::keyPressed(int key){
+
+ switch (key){
+ case '+':
+ threshold ++;
+ if (threshold > 255) threshold = 255;
+ break;
+ case '-':
+ threshold --;
+ if (threshold < 0) threshold = 0;
+ break;
+ case 'a':
+ cam_angle+=1;
+ updatePlane();
+ break;
+ case 'z':
+ case 'Z':
+ cam_angle-=1;
+ updatePlane();
+ break;
+ case 'q':
+ case 'Q':
+ drawStats=!drawStats;
+ break;
+ case 'm':
+ case 'M':
+ mirror=!mirror;
+ break;
+ case 's':
+ case 'S':
+ saveSettings("settings.xml");
+ break;
+ case 'i':
+ case 'I':
+ drawInstructions=!drawInstructions;
+ break;
+ case '9':
+ mode=PLAY;
+ break;
+ case '0':
+ mode=CALIBRATE;
+ break;
+ case '8':
+ mode=CHECKACCUM;
+ break;
+
+ case '1':
+ diffchannel = chan_R;
+ break;
+ case '2':
+ diffchannel = chan_G;
+ break;
+ case '3':
+ diffchannel = chan_B;
+ break;
+ case '4':
+ diffchannel = chan_H;
+ break;
+ case '5':
+ diffchannel = chan_S;
+ break;
+ case '6':
+ diffchannel = chan_V;
+ break;
+
+
+ case 'r':
+ case 'R':
+ removeShadows=!removeShadows;
+ printf(removeShadows?"removing shadows\n":"not removing shadows\n");
+ break;
+
+/*
+ case '1':
+ if (Bird.currentseq!="hover") {
+ //mesh.sequences["trans_flaphover"].stopAt(0.3);
+ //mesh.sequences["trans_flaphover"].start();
+ Bird.model.sequences[Bird.currentseq].fadeout(0.5);
+ Bird.model.sequences["hover"].fadein(0.5);
+ Bird.currentseq="hover";
+ }
+ break;
+ case '2':
+ if (Bird.currentseq!="flap") {
+ //mesh.sequences["trans_hoverflap"].stopAt(0.3);
+ //mesh.sequences["trans_hoverflap"].start();
+ Bird.model.sequences[Bird.currentseq].fadeout(0.5);
+ Bird.model.sequences["flap"].fadein(0.5);
+ Bird.currentseq="flap";
+ }
+ break;
+ case '3':
+ if (Bird.currentseq!="swoop") {
+ //mesh.sequences["trans_hoverflap"].stopAt(0.3);
+ //mesh.sequences["trans_hoverflap"].start();
+ Bird.model.sequences[Bird.currentseq].fadeout(0.25);
+ Bird.model.sequences["swoop_trans"].fadein(0.25);
+ Bird.model.sequences["swoop_trans"].stopTime=ofGetElapsedTimef()+1.0;
+ Bird.model.sequences["swoop"].startAt(1.0);
+ Bird.currentseq="swoop";
+ }
+ break;
+ case '4':
+ if (Bird.currentseq!="attack") {
+ //mesh.sequences["trans_hoverflap"].stopAt(0.3);
+ //mesh.sequences["trans_hoverflap"].start();
+ Bird.model.sequences[Bird.currentseq].fadeout(0.2);
+ Bird.model.sequences["attack_trans"].fadein(0.2);
+ Bird.model.sequences["attack_trans"].stopTime=ofGetElapsedTimef()+0.6;
+ Bird.model.sequences["attack"].startAt(0.6);
+ Bird.currentseq="attack";
+ }
+ break;
+ */
+ /*
+ case 'y':
+ light.setPosition(light.getX(),light.getY()-100,light.getZ());
+ printf("light at %f,%f,%f\n",light.getX(),light.getY(),light.getZ());
+ break;
+ case 'n':
+ light.setPosition(light.getX(),light.getY()+100,light.getZ());
+ printf("light at %f,%f,%f\n",light.getX(),light.getY(),light.getZ());
+ break;
+ case 'g':
+ light.setPosition(light.getX()-100,light.getY(),light.getZ());
+ printf("light at %f,%f,%f\n",light.getX(),light.getY(),light.getZ());
+ break;
+ case 'j':
+ light.setPosition(light.getX()+100,light.getY(),light.getZ());
+ printf("light at %f,%f,%f\n",light.getX(),light.getY(),light.getZ());
+ break;
+ case 'u':
+ light.setPosition(light.getX(),light.getY(),light.getZ()+100);
+ printf("light at %f,%f,%f\n",light.getX(),light.getY(),light.getZ());
+ break;
+ case 'b':
+ light.setPosition(light.getX(),light.getY(),light.getZ()-100);
+ printf("light at %f,%f,%f\n",light.getX(),light.getY(),light.getZ());
+ break;
+ */
+ case 'b':
+ if (!drawingborder) {
+ border.clear();
+ drawingborder=true;
+ }
+ else drawingborder=false;
+ break;
+ case ' ':
+ ofSaveFrame();
+ printf("[%8.2f] saved an image\n",ofGetElapsedTimef());
+ break;
+ /*
+ case '>':
+ gameState=(gameState+1)%4;
+ gameStart=ofGetElapsedTimef();
+ break;
+
+ */
+ }
+}
+
+//--------------------------------------------------------------
+void testApp::keyReleased(int key){
+
+}
+
+//--------------------------------------------------------------
+void testApp::mouseMoved(int x, int y ){
+
+}
+
+//--------------------------------------------------------------
+void testApp::mouseDragged(int x, int y, int button){
+
+}
+
+//--------------------------------------------------------------
+void testApp::mousePressed(int x, int y, int button){
+
+}
+
+//--------------------------------------------------------------
+void testApp::mouseReleased(int x, int y, int button){
+ pos=ofVec2f(x,y);
+ ofVec2f sp=screen2plane(pos);
+ if (drawingborder) {
+ border.push_back(sp);
+ }
+ printf("ray:%i,%i hit plane:%f,%f,%f\n",x,y,sp.x,sp.y);
+}
+
+//--------------------------------------------------------------
+void testApp::windowResized(int w, int h){
+
+}
+
+//--------------------------------------------------------------
+void testApp::gotMessage(ofMessage msg){
+
+}
+
+//--------------------------------------------------------------
+void testApp::dragEvent(ofDragInfo dragInfo){
+
+}
+
+void testApp::loadSettings(string filename){
+ if( !XML.loadFile(filename) ){
+ printf("unable to load %s check data/ folder\n",filename.c_str());
+ }else{
+ cam_angle=ofToInt(XML.getAttribute("gauntlet","cam_angle","none",0));
+ threshold=ofToInt(XML.getAttribute("gauntlet","threshold","none",0));
+ diffchannel=ofToInt(XML.getAttribute("gauntlet","keyChannel","none",0));
+ learningRate=ofToFloat(XML.getAttribute("gauntlet","learningRate","none",0));
+ removeShadows=ofToInt(XML.getAttribute("gauntlet","remove_shadows","none",0))==1;
+ testmovie=XML.getAttribute("gauntlet","testmovie","camoutput.move",0);
+ if(XML.pushTag("bounds")) {
+ for (int i=0;i<XML.getNumTags("vertex");i++){
+ border.push_back(ofVec2f(ofToFloat(XML.getAttribute("vertex","x","0",i)),ofToFloat(XML.getAttribute("vertex","y","0",i))));
+ }
+ XML.popTag();
+ }
+ printf("loaded %s\n",filename.c_str());
+ }
+}
+//--------------------------------------------------------------
+void testApp::saveSettings(string filename){
+ XML.setAttribute("gauntlet","cam_angle",ofToString(cam_angle),0);
+ XML.setAttribute("gauntlet","threshold",ofToString(threshold),0);
+ XML.setAttribute("gauntlet","keyChannel",ofToString(diffchannel),0);
+ XML.setAttribute("gauntlet","remove_shadows",ofToString(removeShadows),0);
+ if (XML.tagExists("bounds")) XML.removeTag("bounds");
+ XML.addTag("bounds");
+ if(XML.pushTag("bounds")) {
+ for (int i=0;i<border.size();i++){
+ XML.addTag("vertex");
+ XML.setAttribute("vertex","x",ofToString(border[i].x),i);
+ XML.setAttribute("vertex","y",ofToString(border[i].y),i);
+ }
+ XML.popTag();
+ }
+ XML.saveFile(filename);
+ printf("saved %s\n",filename.c_str());
+}