diff options
Diffstat (limited to 'gaunt01/src')
| -rw-r--r-- | gaunt01/src/bird.cpp | 71 | ||||
| -rw-r--r-- | gaunt01/src/bird.h | 14 | ||||
| -rw-r--r-- | gaunt01/src/morphmesh.cpp | 186 | ||||
| -rw-r--r-- | gaunt01/src/morphmesh.h | 29 | ||||
| -rw-r--r-- | gaunt01/src/testApp.cpp | 117 | ||||
| -rw-r--r-- | gaunt01/src/testApp.h | 5 | ||||
| -rw-r--r-- | gaunt01/src/trapdoor.cpp | 12 | ||||
| -rw-r--r-- | gaunt01/src/trapdoor.h | 6 |
8 files changed, 362 insertions, 78 deletions
diff --git a/gaunt01/src/bird.cpp b/gaunt01/src/bird.cpp index 9418b65..e27a2a2 100644 --- a/gaunt01/src/bird.cpp +++ b/gaunt01/src/bird.cpp @@ -11,19 +11,27 @@ finally drawAnimated() bird::bird() { - model.loadMesh("Bird-test.xml"); - texture.loadImage("TextureBird.jpg"); + if (model.loadMesh("Bird-poses.xml")) printf("mesh loaded with %i vertices, %i face indices, %i targets\n",model.getNumVertices(),model.getNumIndices(),model.getNumTargets()); + else printf("mesh XML file not parsed\n"); - //starting pos - position=ofVec3f(ofGetWidth()/2,ofGetHeight()/3,ofGetHeight()/2); - heading=ofVec3f(-1,0,0); - direction=ofVec3f(-1,0,0); - velocity=ofGetWidth()/100; + if (model.loadSeqs("Bird-anim.xml")) printf("animation loaded with %i sequences\n",model.getNumSequences()); + else printf("animation XML file not parsed\n"); - turnAngle=0; - diveAngle=0; + model.sequences["flap"].start(); + currentseq="hover"; - lastTime=ofGetElapsedTimef(); + texture.loadImage("TextureBird.jpg"); + + //starting pos + position=ofVec3f(ofGetWidth()/2,ofGetHeight(),-ofGetHeight()/4); + heading=-90; + direction=ofVec3f(0,-1,0); //director for a heading of 0, level + velocity=ofGetWidth()/50; + + turnRate=20; + diveRate=0; + + lastTime=ofGetElapsedTimef(); } bird::~bird() @@ -32,23 +40,38 @@ bird::~bird() } void bird::update(const vector<ofVec3f>& players){ - float time=ofGetElapsedTimef(); - float timeSeg=time-lastTime; - lastTime=time; - position+=direction*velocity*timeSeg; + float time=ofGetElapsedTimef(); + float timeSeg=time-lastTime; + lastTime=time; + heading+=turnRate*timeSeg; + + position-=direction.rotated(heading,ofVec3f(0,0,-1))*velocity*timeSeg; //.rotate(heading,ofVec3f(0,1,0)) } void bird::draw(){ - glEnable(GL_DEPTH_TEST); + glEnable(GL_DEPTH_TEST); + ofPushMatrix(); + ofTranslate(position); + ofRotate(90,0,0,1); + ofRotate(90,-1,0,0); + ofRotate(heading+90,0,1,0); + ofSetHexColor(0xffffff); + ofScale(.15,.15,.15); + bindTexture(texture); + model.drawAnimated(); + unbindTexture(texture); + ofPopMatrix(); + glDisable(GL_DEPTH_TEST); +} +void bird::drawShadow(){ ofPushMatrix(); - ofTranslate(position); - //ofRotate(direction); - ofRotate(90,0,0,1); - ofRotate(90,-1,0,0); - //ofRotate(180,1,0,0); - bindTexture(texture); - model.draw(); - unbindTexture(texture); + ofTranslate(position); + ofRotate(90,0,0,1); + ofRotate(90,-1,0,0); + ofRotate(heading+90,0,1,0); + ofSetHexColor(0x000000); + ofTranslate(0,(-ofGetHeight()/4)+5,0); + ofScale(.15,0,.15); + model.drawAnimated(); ofPopMatrix(); - glDisable(GL_DEPTH_TEST); } diff --git a/gaunt01/src/bird.h b/gaunt01/src/bird.h index 4114ad3..140a102 100644 --- a/gaunt01/src/bird.h +++ b/gaunt01/src/bird.h @@ -37,21 +37,27 @@ class bird virtual ~bird(); void update(const vector<ofVec3f>& players); void draw(); + void drawShadow(); + + string currentseq; + morphmesh model; protected: private: ofVec3f position; - ofVec3f heading; + float heading; ofVec3f direction; float velocity; //per second - float turnAngle; //per second - float diveAngle; //per second + float turnRate; //per second + float diveRate; //per second float lastTime; - morphmesh model; + ofImage texture; + + }; #endif // BIRD_H diff --git a/gaunt01/src/morphmesh.cpp b/gaunt01/src/morphmesh.cpp index e68e526..a126002 100644 --- a/gaunt01/src/morphmesh.cpp +++ b/gaunt01/src/morphmesh.cpp @@ -1,9 +1,5 @@ #include "morphmesh.h" -sequence::sequence() -{ -} - track::track() { } @@ -19,17 +15,89 @@ float track::evaluate(float time) { //interpolate key track if (time<=keys.begin()->first) return keys.begin()->second; - if (time>keys.rbegin()->first) return keys.rbegin()->second; - multimap< float,float >::iterator key2=keys.upper_bound( time ); - multimap< float,float >::iterator key1=key2--; + if (time>=keys.rbegin()->first) return keys.rbegin()->second; + map< float,float >::iterator key2=keys.upper_bound( time ); + map< float,float >::iterator key1=key2--; float interval=key2->first-key1->first; return (((1.0-((key2->first-time)/interval))*key2->second)+((1.0-((time-key1->first)/interval))*key1->second)); } +sequence::sequence(string _name,float _length,float _fadeinTime,float _fadeoutTime) +{ + name=_name; + length=_length; + startTime=0; + stopTime=0; + fadeinTime=_fadeinTime; + fadeoutTime=_fadeoutTime; + active=false; +} + +//----------------------------------------------------- +// stopTime & startTime are absolute +// +// all other time markers are relative to the sequence +// +// +void sequence::reset(){ + active=false; + fadeinTime=-1; + fadeoutTime=-1; + startTime=0; + stopTime=0; +} +void sequence::start(){ + reset(); + active=true; + startTime=ofGetElapsedTimef(); +} +void sequence::startAt(float time){ + reset(); + active=true; + startTime=ofGetElapsedTimef()+time; +} +void sequence::stopAt(float time){ + reset(); + active=true; + stopTime=ofGetElapsedTimef()+time; +} +void sequence::fadeout(float time){ + if(active==true) { + reset(); + active=true; + startTime=ofGetElapsedTimef(); + fadeoutTime=0; + stopTime=ofGetElapsedTimef()+time; + } +} +void sequence::fadein(float time){ + reset(); + active=true; + fadeinTime=time; + startTime=ofGetElapsedTimef(); +} +void sequence::stop(){ + reset(); +} + +vector<morphWeight> sequence::evaluate(float time){ + float now=ofGetElapsedTimef(); + float seqtime=time-startTime; + float looptime=fmod(seqtime,length); + float fadeWeight=(seqtime<fadeinTime?seqtime/fadeinTime:seqtime>fadeoutTime&&fadeoutTime>-1?1.0-(seqtime/(stopTime-startTime)):1.0); + vector<morphWeight> weights; + map<string,track>::iterator i; + for( i = tracks.begin(); i != tracks.end(); i++ ) { + weights.push_back(morphWeight(i->first,(i->second.evaluate(looptime))*fadeWeight)); + } + return weights; +} + morphmesh::morphmesh() { loaded=false; - /* + + /* //testing track function track testrack; testrack.keys.insert( pair<float,float>(0.0,2.0) ); @@ -38,7 +106,7 @@ morphmesh::morphmesh() for (float f=-0.4;f<1.4;f+=0.1) { printf("%f : %f\n",f,testrack.evaluate(f)); } - */ + */ } morphmesh::morphmesh(string filename) @@ -51,6 +119,9 @@ morphmesh::morphmesh(string filename) int morphmesh::getNumTargets(){ return morphs.size(); } +int morphmesh::getNumSequences(){ + return sequences.size(); +} void morphmesh::draw() { draw(0); } @@ -64,26 +135,106 @@ void morphmesh::draw(string target){ addVertices(morphs[target]); ofMesh::draw(); } -void morphmesh::draw(const vector<string>& targets, const vector<float>& weights){ +void morphmesh::draw(const vector<morphWeight>& weights){ clearVertices(); //normalise weights - int targetsNum=min(targets.size(),morphs.size()); float totalWeights=0; - for (int i=0;i<targetsNum;i++) totalWeights+=weights[i]; + for (int i=0;i<weights.size();i++) totalWeights+=weights[i].weight; float weightFactor=1.0/totalWeights; float bx,by,bz; - for (int j=0;j<morphs[targets[0]].size();j++) { + for (int j=0;j<morphs.begin()->second.size();j++) { bx=by=bz=0; - for (int i=0;i<targetsNum;i++) { - bx+=morphs[targets[i]][j].x*weights[i]; - by+=morphs[targets[i]][j].y*weights[i]; - bz+=morphs[targets[i]][j].z*weights[i]; + for (int i=0;i<weights.size();i++) { + bx+=morphs[weights[i].name][j].x*weights[i].weight; + by+=morphs[weights[i].name][j].y*weights[i].weight; + bz+=morphs[weights[i].name][j].z*weights[i].weight; } addVertex(ofVec3f(bx*weightFactor,by*weightFactor,bz*weightFactor)); } ofMesh::draw(); } +void morphmesh::drawAnimated(){ + float time=ofGetElapsedTimef(); + vector<morphWeight> weights; + map<string,sequence>::iterator i; + for( i = sequences.begin(); i != sequences.end(); i++ ) { + if (i->second.active) { + vector<morphWeight> newWeights=i->second.evaluate(time); + weights.insert(weights.end(),newWeights.begin(),newWeights.end()); + if (i->second.stopTime>0&&time>i->second.stopTime) i->second.active=false; + } + } + draw(weights); +} + + +bool morphmesh::loadSeqs(string filename){ + loaded=false; + ofxXmlSettings XML; + if( !XML.loadFile(filename) ){ + printf("unable to load %s check data/ folder\n",filename.c_str()); + }else{ + if(XML.pushTag("Gauntletanim")) { + int numSeqs=XML.getNumTags("Sequence"); + vector<string> seqnames; + for (int i=0;i<numSeqs;i++) seqnames.push_back(XML.getAttribute("Sequence","Name","none",i)); + for (int i=0;i<numSeqs;i++) { + float length=ofToFloat(XML.getAttribute("Sequence","Length","0",i)); + float fadeinTime=ofToFloat(XML.getAttribute("Sequence","Fadein","0",i)); + float fadeoutTime=ofToFloat(XML.getAttribute("Sequence","Fadeout","0",i)); + sequence seq=sequence(seqnames[i],length,fadeinTime,fadeoutTime); + + XML.pushTag("Sequence",i); + int numTargs=XML.getNumTags("Target"); + vector<string> targnames; + for (int j=0;j<numTargs;j++) targnames.push_back(XML.getAttribute("Target","Name","none",j)); + for (int j=0;j<numTargs;j++) { + XML.pushTag("Target",j); + + vector<float> times; + string keystring=XML.getAttribute("Keys","Times","none",0); + stringstream ks(keystring); + istream_iterator<string> kbegin(ks); + istream_iterator<string> kend; + vector<string> kstrings(kbegin, kend); + + vector<float> values; + string valstring=XML.getAttribute("Keys","Values","none",0); + stringstream vs(valstring); + istream_iterator<string> vbegin(vs); + istream_iterator<string> vend; + vector<string> vstrings(vbegin, vend); + + track tr; + + for (int k=0;k<min(kstrings.size(),vstrings.size());k++) tr.keys[ofToFloat(kstrings[k])]=ofToFloat(vstrings[k]); + + seq.tracks[targnames[j]]=tr; + + XML.popTag(); + } + sequences[seqnames[i]]=seq; + /* + printf("testing sequence: %s\n",seqnames[i].c_str()); + for (int k=0;k<targnames.size();k++) { + printf("track: %s\n",targnames[k].c_str()); + for (float l=-.2;l<=2.2;l+=0.1) { + printf("%f : %f\n",l,sequences[seqnames[i]].tracks[targnames[k]].evaluate(l)); + } + } + */ + loaded=true; + XML.popTag(); + } + XML.popTag(); + } + } + return loaded; +} + + + bool morphmesh::loadMesh(string filename){ loaded=false; ofxXmlSettings XML; @@ -99,7 +250,6 @@ bool morphmesh::loadMesh(string filename){ XML.pushTag("Mesh",i); if (XML.pushTag("AttributeList")) { vector<ofVec3f> verts; - string vertstring=XML.getAttribute("Attribute","Data","none",0); stringstream ss(vertstring); istream_iterator<string> begin(ss); diff --git a/gaunt01/src/morphmesh.h b/gaunt01/src/morphmesh.h index 18463a8..bc22c3d 100644 --- a/gaunt01/src/morphmesh.h +++ b/gaunt01/src/morphmesh.h @@ -36,8 +36,9 @@ These could be parameterised on top of the rest? */ struct morphWeight { - string name; - float weight; + morphWeight(string _name="",float _weight=1.0) {name=_name;weight=_weight;} + string name; + float weight; }; struct key @@ -52,8 +53,7 @@ class track //a single morph channel. keys a single morph target within a single sequence public: track(); - string target; - multimap<float,float> keys; + map<float,float> keys; float evaluate(float time); }; @@ -67,15 +67,23 @@ class sequence //sequencer keeps track of playback time and start/stop of sequences //tracks is a map of time,value pairs - built in interpolation via multimap public: - sequence(); + sequence(string _name="",float _length=1.0,float _fadeinTime=-2.0,float _fadeoutTime=-2.0); + string name; map<string,track> tracks; - vector<morphWeight> evaluate(); - private: + vector<morphWeight> evaluate(float time); + void reset(); + void start(); + void stop(); + void startAt(float time); + void stopAt(float time); + void fadein(float time); + void fadeout(float time); float length; float startTime; + float stopTime; float fadeinTime; float fadeoutTime; - bool loop,active; + bool active; }; @@ -90,10 +98,11 @@ class morphmesh : public ofMesh void draw(); void draw(int target); void draw(string target); - void draw(const vector<string>& targets, const vector<float>& weights); + void draw(const vector<morphWeight>& weights); void drawAnimated(); //evaluates all active sequences via iterator and builds a collection of targets and weights for above int getNumTargets(); - map< string,sequence > sequences; //public for direct access + int getNumSequences(); + map<string,sequence> sequences; //public for direct access protected: private: map< string,vector<ofVec3f> > morphs; diff --git a/gaunt01/src/testApp.cpp b/gaunt01/src/testApp.cpp index 172190f..6660103 100644 --- a/gaunt01/src/testApp.cpp +++ b/gaunt01/src/testApp.cpp @@ -3,6 +3,16 @@ //-------------------------------------------------------------- //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(){ bLearnBakground = true; @@ -24,12 +34,23 @@ void testApp::setup(){ useCamera=false; vidPlayer.play(); } - +
+/* colorImg.allocate(640,480); colorImg.setUseTexture(true); grayImage.allocate(640,480); grayBg.allocate(640,480); - grayDiff.allocate(640,480); + grayDiff.allocate(640,480);
+ */
+ colorImg.allocate(640,480); + colorImg.setUseTexture(true);
+ + grayImage.allocate(ofGetWidth(),ofGetHeight()); + grayBg.allocate(ofGetWidth(),ofGetHeight()); + grayDiff.allocate(ofGetWidth(),ofGetHeight());
+
+
+ blobsManager.normalizePercentage = 0.7; blobsManager.giveLowestPossibleIDs = false; @@ -60,6 +81,7 @@ void testApp::setup(){ mode=PLAY; + drawStats=false; } @@ -167,7 +189,9 @@ void testApp::update(){ colorImg.setFromPixels(vidPlayer.getPixels(), 640,480); } +
grayImage = colorImg; + grayImage.resize(ofGetWidth(),ofGetHeight()); if (bLearnBakground == true){ grayBg = grayImage; // the = sign copys the pixels from grayImage into grayBg (operator overloading) bLearnBakground = false; @@ -176,6 +200,8 @@ void testApp::update(){ // take the abs value of the difference between background and incoming and then threshold: grayDiff.absDiff(grayBg, grayImage); grayDiff.threshold(threshold); + //grayDiff.adaptiveThreshold( threshold); //int blockSize, int offset=0,bool invert=false, bool gauss=false); + grayDiff.erode_3x3(); // 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.... @@ -219,6 +245,13 @@ void testApp::draw(){ ground.draw(); unbindTexture(colorImg); + Bird.update(players); + + ofPushMatrix(); + ofRotate(cam_angle,1,0,0); + Bird.drawShadow(); + ofPopMatrix(); + /* glEnable(GL_BLEND); glBlendFunc(GL_CONSTANT_ALPHA,GL_ONE); @@ -228,7 +261,7 @@ void testApp::draw(){ */ for (int i = 0; i < contourFinder.nBlobs; i++){ ofxCvBlob blob=contourFinder.blobs[i]; - if (rectsCross(blob.boundingRect,trapDoor.getBoundingRect())){ + //if (rectsCross(blob.boundingRect,trapDoor.getBoundingRect())){ //ALWAYS draw players? //create outline mesh playeroutline=ofPolyline(blob.pts); tesselator.tessellateToMesh(playeroutline,OF_POLY_WINDING_NONZERO,player,true); @@ -241,15 +274,20 @@ void testApp::draw(){ //get screen basepoint ofRectangle r=blob.boundingRect; ofVec2f blobBase=ofVec2f(r.x+(r.width/2),r.y+r.height-(r.width/2)); - if (trapDoor.getInnerRect().inside(blobBase.x,blobBase.y)) trapDoor.trigger(); - - colorImg.getTextureReference().bind(); - player.draw(); //trapdoor.getoffset()); - colorImg.getTextureReference().unbind(); + if (trapDoor.getInnerRect().inside(blobBase.x,blobBase.y)) { + trapDoor.trigger(); + + ofPushMatrix(); + ofTranslate(0,0,trapDoor.getFalldist()*ofGetScreenHeight()); + colorImg.getTextureReference().bind(); + player.draw(); //trapdoor.getoffset()); + colorImg.getTextureReference().unbind(); + ofPopMatrix(); + } } - } + //} + - Bird.update(players); ofPushMatrix(); ofRotate(cam_angle,1,0,0); @@ -332,14 +370,16 @@ void testApp::draw(){ } - // finally, a report: - ofSetHexColor(0xffffff); - char reportStr[1024]; - sprintf(reportStr, "threshold %i\nnum blobs found %i, fps: %f", threshold, contourFinder.nBlobs, ofGetFrameRate()); - ofDrawBitmapString(reportStr, 10, ofGetHeight()-40); + break; } + if (drawStats||mode==CALIBRATE) { + ofSetHexColor(0xffffff); + char reportStr[1024]; + sprintf(reportStr, "threshold %i\nnum blobs found %i, fps: %f", threshold, contourFinder.nBlobs, ofGetFrameRate()); + ofDrawBitmapString(reportStr, 10, ofGetHeight()-40); + } cam.end(); } @@ -366,15 +406,58 @@ void testApp::keyPressed(int key){ cam_angle-=1; updatePlane(); break; + case 'q': + drawStats=!drawStats; + break; case 's': saveSettings("settings.xml"); break; - case '1': + case '9': mode=PLAY; break; - case '2': + case '0': mode=CALIBRATE; 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; } } diff --git a/gaunt01/src/testApp.h b/gaunt01/src/testApp.h index 9ee79db..50187bd 100644 --- a/gaunt01/src/testApp.h +++ b/gaunt01/src/testApp.h @@ -52,7 +52,8 @@ class testApp : public ofBaseApp{ ofVideoGrabber vidGrabber; ofVideoPlayer vidPlayer; - ofxCvColorImage colorImg; + ofxCvColorImage colorImg;
+ ofxCvFloatImage accumImg; ofxCvGrayscaleImage grayImage; ofxCvGrayscaleImage grayBg; @@ -87,5 +88,7 @@ class testApp : public ofBaseApp{ bird Bird; + bool drawStats; + }; diff --git a/gaunt01/src/trapdoor.cpp b/gaunt01/src/trapdoor.cpp index e1c37ec..3da9755 100644 --- a/gaunt01/src/trapdoor.cpp +++ b/gaunt01/src/trapdoor.cpp @@ -17,9 +17,7 @@ trapdoor::trapdoor(ofVec2f _boundTR,ofVec2f _boundBR,ofVec2f _doorSize) boundTR=_boundTR; boundBR=_boundBR; - size=_doorSize; - - start(); + size=_doorSize; start(); } trapdoor::~trapdoor() { @@ -35,6 +33,7 @@ void trapdoor::start(){ float y=boundTR.y+((boundBR.y-boundTR.y)*v); startPos(ofVec2f(x,y)); + } void trapdoor::startPos(ofVec2f pos){ @@ -43,6 +42,7 @@ void trapdoor::startPos(ofVec2f pos){ doorAngle=0; doorSpeed=0; opening=false; + triggeredTime=-1; //for (int i=0;i<4;i++) sounds[i].stop(); } @@ -66,6 +66,7 @@ ofRectangle trapdoor::getInnerRect() { return ofRectangle(boundingRect.x+(boundingRect.width/4),boundingRect.y+(boundingRect.height/4),boundingRect.width/2,boundingRect.height/2); } void trapdoor::trigger() { + triggeredTime=ofGetElapsedTimef(); startTime=ofGetElapsedTimef()-10; } @@ -77,6 +78,11 @@ ofVec2f trapdoor::bounds2UV(ofVec2f point){ return ofVec2f(u,v); } +float trapdoor::getFalldist(){ + if (triggerTime>0) return (ofGetElapsedTimef()-triggeredTime); + else return 0; +} + bool trapdoor::checkUpdate(const vector<ofVec3f>& players) { float segTime=(ofGetElapsedTimef()-startTime); if (segTime>10) { diff --git a/gaunt01/src/trapdoor.h b/gaunt01/src/trapdoor.h index 389e4ad..8cff4f4 100644 --- a/gaunt01/src/trapdoor.h +++ b/gaunt01/src/trapdoor.h @@ -27,13 +27,15 @@ class trapdoor vector<ofVec2f> getCorners(); ofVec2f bounds2UV(ofVec2f pt); - + void setBoundingRect(float x,float y, float width,float height); ofRectangle getBoundingRect(); ofRectangle getInnerRect(); void trigger(); float getoffset(); + float getFalldist(); + protected: private: morphmesh surround; @@ -59,6 +61,8 @@ class trapdoor bool opening; + float triggeredTime; + }; #endif // TRAPDOOR_H |
