Add pointlog2svg utility for the thesis
[numtypysics.git] / Scene.cpp
blob5f0c795fbc7aed2deb893db6282356a12d5c78bb
1 /*
2 * This file is part of NumptyPhysics
3 * Copyright (C) 2008 Tim Edmonds
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
16 #include "Common.h"
17 #include "Array.h"
18 #include "Config.h"
19 #include "Scene.h"
21 #include <sstream>
22 #include <fstream>
25 Transform::Transform( float32 scale, float32 rotation, const Vec2& translation )
27 set( scale, rotation, translation );
30 void Transform::set( float32 scale, float32 rotation, const Vec2& translation )
32 if ( scale==0.0f && rotation==0.0f && translation==Vec2(0,0) ) {
33 m_bypass = true;
34 } else {
35 m_rot.Set( rotation );
36 m_pos = translation;
37 m_rot.col1.x *= scale;
38 m_rot.col1.y *= scale;
39 m_rot.col2.x *= scale;
40 m_rot.col2.y *= scale;
41 m_invrot = m_rot.Invert();
42 m_bypass = false;
46 Transform worldToScreen( 0.5f, M_PI/2, Vec2(240,0) );
48 void configureScreenTransform( int w, int h )
50 SCREEN_WIDTH = w;
51 SCREEN_HEIGHT = h;
52 FULLSCREEN_RECT = Rect(0,0,w-1,h-1);
53 if ( w==WORLD_WIDTH && h==WORLD_HEIGHT ) { //unity
54 worldToScreen.set( 0.0f, 0.0f, Vec2(0,0) );
55 } else {
56 float rot = 0.0f;
57 Vec2 tr(0,0);
58 if ( h > w ) { //portrait
59 rot = M_PI/2;
60 tr = Vec2( w, 0 );
61 b2Swap( h, w );
63 float scalew = (float)w/(float)WORLD_WIDTH;
64 float scaleh = (float)h/(float)WORLD_HEIGHT;
65 if ( scalew < scaleh ) {
66 worldToScreen.set( scalew, rot, tr );
67 } else {
68 worldToScreen.set( scaleh, rot, tr );
74 class Stroke
76 public:
78 private:
79 struct JointDef : public b2RevoluteJointDef
81 JointDef( b2Body* b1, b2Body* b2, const b2Vec2& pt )
83 Initialize( b1, b2, pt );
84 maxMotorTorque = 10.0f;
85 motorSpeed = 0.0f;
86 enableMotor = true;
90 struct BoxDef : public b2PolygonDef
92 void init( const Vec2& p1, const Vec2& p2, int attr )
94 b2Vec2 barOrigin = p1;
95 b2Vec2 bar = p2 - p1;
96 bar *= 1.0f/PIXELS_PER_METREf;
97 barOrigin *= 1.0f/PIXELS_PER_METREf;;
98 SetAsBox( bar.Length()/2.0f, 0.1f,
99 0.5f*bar + barOrigin, vec2Angle( bar ));
100 // SetAsBox( bar.Length()/2.0f+b2_toiSlop, b2_toiSlop*2.0f,
101 // 0.5f*bar + barOrigin, vec2Angle( bar ));
102 friction = 0.3f;
103 if ( attr & ATTRIB_GROUND ) {
104 density = 0.0f;
105 } else if ( attr & ATTRIB_GOAL ) {
106 density = 100.0f;
107 } else if ( attr & ATTRIB_TOKEN ) {
108 density = 3.0f;
109 friction = 0.1f;
110 } else {
111 density = 5.0f;
113 restitution = 0.2f;
117 public:
118 Stroke( const Path& path, int attributes )
119 : m_rawPath(path)
121 m_colour = brushColours[DEFAULT_BRUSH];
122 m_attributes = attributes;
123 m_origin = m_rawPath.point(0);
124 m_rawPath.translate( -m_origin );
125 setPlayer(0);
126 reset();
129 Stroke( const std::string& str )
131 setPlayer(0);
132 int col = 0;
133 m_colour = brushColours[DEFAULT_BRUSH];
134 m_attributes = 0;
135 m_origin = Vec2(400,240);
136 reset();
137 const char *s = str.c_str();
138 while ( *s && *s!=':' && *s!='\n' ) {
139 switch ( *s ) {
140 case 't': setAttribute( ATTRIB_TOKEN ); setPlayer(0); break;
141 case 'T': setAttribute( ATTRIB_TOKEN ); setPlayer(1); break;
142 case 'g': setAttribute( ATTRIB_GOAL ); setPlayer(0); break;
143 case 'G': setAttribute( ATTRIB_GOAL ); setPlayer(1); break;
144 case 'f': setAttribute( ATTRIB_GROUND ); break;
145 case 's': setAttribute( ATTRIB_SLEEPING ); break;
146 case 'd': setAttribute( ATTRIB_DECOR ); break;
147 default:
148 if ( *s >= '0' && *s <= '9' ) {
149 col = col*10 + *s -'0';
151 break;
153 s++;
155 if ( col >= 0 && col < NUM_BRUSHES ) {
156 m_colour = brushColours[col];
158 if ( *s++ == ':' ) {
159 m_rawPath = Path(s);
161 if ( m_rawPath.size() < 2 ) {
162 throw "invalid stroke def";
164 m_origin = m_rawPath.point(0);
165 m_rawPath.translate( -m_origin );
166 setAttribute( ATTRIB_DUMMY );
169 void reset( b2World* world=NULL )
171 if (m_body && world) {
172 world->DestroyBody( m_body );
174 m_body = NULL;
175 m_xformAngle = 7.0f;
176 m_drawnBbox.tl = m_origin;
177 m_drawnBbox.br = m_origin;
178 m_jointed[0] = m_jointed[1] = false;
179 m_shapePath = m_rawPath;
180 m_hide = 0;
181 m_drawn = false;
184 std::string asString()
186 std::stringstream s;
187 s << 'S';
188 if ( hasAttribute(ATTRIB_TOKEN) ) s<<'t';
189 if ( hasAttribute(ATTRIB_GOAL) ) s<<'g';
190 if ( hasAttribute(ATTRIB_GROUND) ) s<<'f';
191 if ( hasAttribute(ATTRIB_SLEEPING) ) s<<'s';
192 if ( hasAttribute(ATTRIB_DECOR) ) s<<'d';
193 for ( int i=0; i<NUM_BRUSHES; i++ ) {
194 if ( m_colour==brushColours[i] ) s<<i;
196 s << ":";
197 transform();
198 for ( int i=0; i<m_xformedPath.size(); i++ ) {
199 const Vec2& p = m_xformedPath.point(i);
200 s <<' '<< p.x << ',' << p.y;
202 s << std::endl;
203 return s.str();
206 void setAttribute( Attribute a )
208 m_attributes |= a;
209 if ( m_attributes & ATTRIB_TOKEN ) m_colour = brushColours[RED_BRUSH];
210 else if ( m_attributes & ATTRIB_GOAL ) m_colour = brushColours[YELLOW_BRUSH];
213 void setPlayer( int player )
215 m_player = player;
218 int getPlayer()
220 return m_player;
223 void clearAttribute( Attribute a )
225 m_attributes &= ~a;
228 bool hasAttribute( Attribute a )
230 return (m_attributes&a) != 0;
232 void setColour( int c )
234 m_colour = c;
237 void createBodies( b2World& world )
239 process();
240 if ( hasAttribute( ATTRIB_DECOR ) ){
241 return; //decorators have no physical embodiment
243 int n = m_shapePath.numPoints();
244 if ( n > 1 ) {
245 b2BodyDef bodyDef;
246 bodyDef.position = m_origin;
247 bodyDef.position *= 1.0f/PIXELS_PER_METREf;
248 bodyDef.userData = this;
249 if ( m_attributes & ATTRIB_SLEEPING ) {
250 bodyDef.isSleeping = true;
252 m_body = world.CreateBody( &bodyDef );
253 for ( int i=1; i<n; i++ ) {
254 BoxDef boxDef;
255 boxDef.init( m_shapePath.point(i-1),
256 m_shapePath.point(i),
257 m_attributes );
258 m_body->CreateShape( &boxDef );
260 m_body->SetMassFromShapes();
263 transform();
266 void setNoMass()
268 static b2MassData m;
269 if (m_body) {
270 m_body->SetMass(&m);
274 void setDefaultMass()
276 if (m_body) {
277 m_body->SetMassFromShapes();
281 bool maybeCreateJoint( b2World& world, Stroke* other )
283 if ( (m_attributes&ATTRIB_CLASSBITS)
284 != (other->m_attributes&ATTRIB_CLASSBITS) ) {
285 return false; // can only joint matching classes
286 } else if ( hasAttribute(ATTRIB_GROUND) ) {
287 return true; // no point jointing grounds
288 } else if ( m_body && other->body() ) {
289 transform();
290 int n = m_xformedPath.numPoints();
291 for ( int end=0; end<2; end++ ) {
292 if ( !m_jointed[end] ) {
293 const Vec2& p = m_xformedPath.point( end ? n-1 : 0 );
294 if ( other->distanceTo( p ) <= JOINT_TOLERANCE ) {
295 //printf("jointed end %d d=%f\n",end,other->distanceTo( p ));
296 b2Vec2 pw = p;
297 pw *= 1.0f/PIXELS_PER_METREf;
298 JointDef j( m_body, other->m_body, pw );
299 world.CreateJoint( &j );
300 m_jointed[end] = true;
305 if ( m_body ) {
306 return m_jointed[0] && m_jointed[1];
308 return true; ///nothing to do
311 void draw( Canvas& canvas )
313 if ( m_hide < HIDE_STEPS ) {
314 bool thick = (canvas.width() > 400);
315 transform();
316 canvas.drawPath( m_screenPath, canvas.makeColour(m_colour), thick );
317 m_drawn = true;
319 m_drawnBbox = m_screenBbox;
322 void addPoint( const Vec2& pp )
324 Vec2 p = pp; p -= m_origin;
325 Vec2 p2 = m_rawPath.point( m_rawPath.numPoints()-1 );
327 /* avoiding assertions in b2PolygonShape.cpp when using MT input */
328 int xdiff = p.x - p2.x;
329 int ydiff = p.y - p2.y;
330 double h = xdiff * xdiff + ydiff * ydiff;
332 if (h < B2_FLT_EPSILON * B2_FLT_EPSILON)
334 fprintf(stderr, "ignoring nearby points\n");
335 return;
338 if ( p == m_rawPath.point( m_rawPath.numPoints()-1 ) ) {
339 } else {
340 m_rawPath.append( p );
341 m_drawn = false;
345 void origin( const Vec2& p )
347 // todo
348 if ( m_body ) {
349 b2Vec2 pw = p;
350 pw *= 1.0f/PIXELS_PER_METREf;
351 m_body->SetXForm( pw, m_body->GetAngle() );
353 m_origin = p;
356 b2Body* body() { return m_body; }
358 float32 distanceTo( const Vec2& pt )
360 float32 best = 100000.0;
361 transform();
362 for ( int i=1; i<m_xformedPath.numPoints(); i++ ) {
363 Segment s( m_xformedPath.point(i-1), m_xformedPath.point(i) );
364 float32 d = s.distanceTo( pt );
365 //printf(" d[%d]=%f %d,%d\n",i,d,m_rawPath.point(i-1).x,m_rawPath.point(i-1).y);
366 if ( d < best ) {
367 best = d;
370 return best;
373 Rect screenBbox()
375 transform();
376 return m_screenBbox;
379 Rect lastDrawnBbox()
381 return m_drawnBbox;
384 Rect worldBbox()
386 return m_xformedPath.bbox();
389 bool isDirty()
391 return (!m_drawn || transform()) && !hasAttribute(ATTRIB_DELETED);
394 void hide()
396 if ( m_hide==0 ) {
397 m_hide = 1;
399 if (m_body) {
400 // stash the body where no-one will find it
401 m_body->SetXForm( b2Vec2(0.0f,SCREEN_HEIGHT*2.0f), 0.0f );
402 m_body->SetLinearVelocity( b2Vec2(0.0f,0.0f) );
403 m_body->SetAngularVelocity( 0.0f );
408 bool hidden()
410 return m_hide >= HIDE_STEPS;
413 int numPoints()
415 return m_rawPath.numPoints();
418 private:
419 static float32 vec2Angle( b2Vec2 v )
421 return b2Atan2(v.y, v.x);
424 void process()
426 float32 thresh = SIMPLIFY_THRESHOLDf;
427 m_rawPath.simplify( thresh );
428 m_shapePath = m_rawPath;
430 while ( m_shapePath.numPoints() > MULTI_VERTEX_LIMIT ) {
431 thresh += SIMPLIFY_THRESHOLDf;
432 m_shapePath.simplify( thresh );
436 bool transform()
438 // distinguish between xformed raw and shape path as needed
439 if ( m_hide ) {
440 if ( m_hide < HIDE_STEPS ) {
441 //printf("hide %d\n",m_hide);
442 Vec2 o = m_screenBbox.centroid();
443 m_screenPath -= o;
444 m_screenPath.scale( 0.99 );
445 m_screenPath += o;
446 m_screenBbox = m_screenPath.bbox();
447 m_hide++;
448 return true;
450 } else if ( m_body ) {
451 if ( hasAttribute( ATTRIB_DECOR ) ) {
452 return false; // decor never moves
453 } else if ( hasAttribute( ATTRIB_GROUND )
454 && m_xformAngle == m_body->GetAngle() ) {
455 return false; // ground strokes never move.
456 } else if ( m_xformAngle != m_body->GetAngle()
457 || ! (m_xformPos == m_body->GetPosition()) ) {
458 //printf("transform stroke - rot or pos\n");
459 b2Mat22 rot( m_body->GetAngle() );
460 b2Vec2 orig = PIXELS_PER_METREf * m_body->GetPosition();
461 m_xformedPath = m_rawPath;
462 m_xformedPath.rotate( rot );
463 m_xformedPath.translate( Vec2(orig) );
464 m_xformAngle = m_body->GetAngle();
465 m_xformPos = m_body->GetPosition();
466 worldToScreen.transform( m_xformedPath, m_screenPath );
467 m_screenBbox = m_screenPath.bbox();
468 } else {
469 //printf("transform none\n");
470 return false;
472 } else {
473 //printf("transform no body\n");
474 m_xformedPath = m_rawPath;
475 m_xformedPath.translate( m_origin );
476 worldToScreen.transform( m_xformedPath, m_screenPath );
477 m_screenBbox = m_screenPath.bbox();
478 return false;
480 return true;
483 Path m_rawPath;
484 int m_colour;
485 int m_attributes;
486 int m_player;
487 Vec2 m_origin;
488 Path m_shapePath;
489 Path m_xformedPath;
490 Path m_screenPath;
491 float32 m_xformAngle;
492 b2Vec2 m_xformPos;
493 Rect m_screenBbox;
494 Rect m_drawnBbox;
495 bool m_drawn;
496 b2Body* m_body;
497 bool m_jointed[2];
498 int m_hide;
501 Scene::Scene( bool noWorld )
502 : m_world( NULL ),
503 m_bgImage( NULL ),
504 m_protect( 0 )
506 if ( !noWorld ) {
507 b2AABB worldAABB;
508 worldAABB.lowerBound.Set(-100.0f, -100.0f);
509 worldAABB.upperBound.Set(100.0f, 100.0f);
511 b2Vec2 gravity(0.0f, 10.0f*PIXELS_PER_METREf/GRAVITY_FUDGEf);
512 bool doSleep = true;
513 m_world = new b2World(worldAABB, gravity, doSleep);
514 m_world->SetContactListener( this );
518 Scene::~Scene()
520 clear();
521 if ( m_world ) {
522 step();
523 delete m_world;
527 Stroke* Scene::newStroke( const Path& p, int colour, int attribs ) {
528 Stroke *s = new Stroke(p, attribs);
529 switch ( colour ) {
530 case 0: s->setAttribute( ATTRIB_TOKEN ); break;
531 case 1: s->setAttribute( ATTRIB_GOAL ); break;
532 default: s->setColour( brushColours[colour] ); break;
534 s->setPlayer(0);
535 m_strokes.append( s );
536 return s;
539 bool Scene::deleteStroke( Stroke *s ) {
540 if ( s ) {
541 int i = m_strokes.indexOf(s);
542 if ( i >= m_protect ) {
543 reset(s);
544 m_strokes.erase( m_strokes.indexOf(s) );
545 return true;
548 return false;
552 void Scene::extendStroke( Stroke* s, const Vec2& pt )
554 if ( s ) {
555 s->addPoint( pt );
559 void Scene::moveStroke( Stroke* s, const Vec2& origin )
561 if ( s ) {
562 int i = m_strokes.indexOf(s);
563 if ( i >= m_protect ) {
564 s->origin( origin );
569 void Scene::setNoMass(Stroke* s)
571 s->setNoMass();
574 void Scene::setDefaultMass(Stroke* s)
576 s->setDefaultMass();
579 int Scene::numPoints(Stroke* s)
581 return s->numPoints();
584 bool Scene::activate( Stroke *s )
586 if ( s->numPoints() > 1 ) {
587 s->createBodies( *m_world );
588 createJoints( s );
589 return true;
591 return false;
594 void Scene::activateAll()
596 m_num_goal = 0;
597 m_num_token = 0;
598 m_num_player = 0;
599 m_winner = -1;
601 for ( int i=0; i < m_strokes.size(); i++ ) {
602 m_strokes[i]->createBodies( *m_world );
603 if (m_strokes[i]->hasAttribute(ATTRIB_TOKEN)) { m_num_token++;}
604 if (m_strokes[i]->hasAttribute(ATTRIB_GOAL)) { m_num_goal++; }
605 if (m_strokes[i]->getPlayer() + 1 > m_num_player) { m_num_player = m_strokes[i]->getPlayer() + 1; }
607 for ( int i=0; i < m_strokes.size(); i++ ) {
608 createJoints( m_strokes[i] );
612 void Scene::createJoints( Stroke *s )
614 for ( int j=m_strokes.size()-1; j>=0; j-- ) {
615 if ( s != m_strokes[j] ) {
616 //printf("try join to %d\n",j);
617 s->maybeCreateJoint( *m_world, m_strokes[j] );
618 m_strokes[j]->maybeCreateJoint( *m_world, s );
623 void Scene::step()
625 m_world->Step( ITERATION_TIMESTEPf, SOLVER_ITERATIONS );
626 // clean up delete strokes
627 for ( int i=0; i< m_strokes.size(); i++ ) {
628 if ( m_strokes[i]->hasAttribute(ATTRIB_DELETED) ) {
629 m_strokes[i]->clearAttribute(ATTRIB_DELETED);
630 m_strokes[i]->hide();
633 // check for token respawn
634 for ( int i=0; i < m_strokes.size(); i++ ) {
635 if ( m_strokes[i]->hasAttribute( ATTRIB_TOKEN )
636 && !BOUNDS_RECT.intersects( m_strokes[i]->worldBbox() ) ) {
637 reset( m_strokes[i] );
638 activate( m_strokes[i] );
643 // b2ContactListener callback when a new contact is detected
644 void Scene::Add(const b2ContactPoint* point)
646 // check for completion
647 //if (c->GetManifoldCount() > 0) {
648 Stroke* s1 = (Stroke*)point->shape1->GetBody()->GetUserData();
649 Stroke* s2 = (Stroke*)point->shape2->GetBody()->GetUserData();
651 if ( s1 && s2 ) {
652 if ( s2->hasAttribute(ATTRIB_TOKEN) ) {
653 b2Swap( s1, s2 );
655 if ( s1->hasAttribute(ATTRIB_TOKEN)
656 && s2->hasAttribute(ATTRIB_GOAL) ) {
657 s2->setAttribute(ATTRIB_DELETED);
658 if (m_num_goal == 1) {
659 m_winner = s1->getPlayer();
665 bool Scene::isCompleted()
667 /* if we only have one goal, m_winner has already been set */
668 if ((m_num_goal == 1) && (m_winner != -1)) {
669 return true;
672 else if (m_num_goal > 1)
674 int left[m_num_player];
675 for(int n=0; n<m_num_player; n++) {
676 left[n] = 0;
679 for ( int i=0; i < m_strokes.size(); i++ ) {
680 if ( m_strokes[i]->hasAttribute( ATTRIB_GOAL )
681 && !m_strokes[i]->hidden() ) {
682 left[m_strokes[i]->getPlayer()]++;
686 for ( int n=0; n<m_num_player; n++) {
687 if (left[n] == 0) {
688 m_winner = n;
689 return true;
694 return false;
697 int Scene::getWinner()
699 return m_num_player == 1 ? -1 : m_winner;
702 Rect Scene::dirtyArea()
704 Rect r(0,0,0,0),temp;
705 int numDirty = 0;
706 for ( int i=0; i<m_strokes.size(); i++ ) {
707 if ( m_strokes[i]->isDirty() ) {
708 // acumulate new areas to draw
709 temp = m_strokes[i]->screenBbox();
710 if ( !temp.isEmpty() ) {
711 if ( numDirty==0 ) {
712 r = temp;
713 } else {
714 r.expand( m_strokes[i]->screenBbox() );
716 // plus prev areas to erase
717 r.expand( m_strokes[i]->lastDrawnBbox() );
718 numDirty++;
722 if ( !r.isEmpty() ) {
723 // expand to allow for thick lines
724 r.tl.x--; r.tl.y--;
725 r.br.x++; r.br.y++;
727 return r;
730 void Scene::draw( Canvas& canvas, const Rect& area )
732 if ( m_bgImage ) {
733 canvas.setBackground( m_bgImage );
734 } else {
735 canvas.setBackground( 0 );
737 canvas.clear( area );
738 Rect clipArea = area;
739 clipArea.tl.x--;
740 clipArea.tl.y--;
741 clipArea.br.x++;
742 clipArea.br.y++;
743 for ( int i=0; i<m_strokes.size(); i++ ) {
744 if ( area.intersects( m_strokes[i]->screenBbox() ) ) {
745 m_strokes[i]->draw( canvas );
748 //canvas.drawRect( area, 0xffff0000, false );
751 void Scene::reset( Stroke* s )
753 for ( int i=0; i<m_strokes.size(); i++ ) {
754 if (s==NULL || s==m_strokes[i]) {
755 m_strokes[i]->reset(m_world);
760 Stroke* Scene::strokeAtPoint( const Vec2 pt, float32 max )
762 Stroke* best = NULL;
763 for ( int i=0; i<m_strokes.size(); i++ ) {
764 float32 d = m_strokes[i]->distanceTo( pt );
765 //printf("stroke %d dist %f\n",i,d);
766 if ( d < max ) {
767 max = d;
768 best = m_strokes[i];
771 return best;
774 void Scene::clear()
776 reset();
777 while ( m_strokes.size() ) {
778 delete m_strokes[0];
779 m_strokes.erase(0);
781 if ( m_world ) {
782 //step is required to actually destroy bodies and joints
783 m_world->Step( ITERATION_TIMESTEPf, SOLVER_ITERATIONS );
787 void Scene::setGravity( const std::string& s )
789 float32 x,y;
790 if ( sscanf( s.c_str(), "%f,%f", &x, &y )==2) {
791 if ( m_world ) {
792 b2Vec2 g(x,y);
793 g *= PIXELS_PER_METREf/GRAVITY_FUDGEf;
794 m_world->SetGravity( g );
796 } else {
797 fprintf(stderr,"invalid gravity vector\n");
801 bool Scene::load( unsigned char *buf, int bufsize )
803 std::string s( (const char*)buf, bufsize );
804 std::stringstream in( s, std::ios::in );
805 return load( in );
808 bool Scene::load( const std::string& file )
810 std::ifstream in( file.c_str(), std::ios::in );
811 return load( in );
814 bool Scene::load( std::istream& in )
816 clear();
817 if ( g_bgImage==NULL ) {
818 g_bgImage = new Image("paper.png");
819 g_bgImage->scale( SCREEN_WIDTH, SCREEN_HEIGHT );
821 m_bgImage = g_bgImage;
822 std::string line;
823 while ( !in.eof() ) {
824 getline( in, line );
825 parseLine( line );
827 protect();
828 return true;
831 void Scene::trimWhitespace( std::string& s )
833 static const char* whitespace = " \t";
834 size_t start = s.find_first_not_of(whitespace);
835 size_t end = s.find_last_not_of(whitespace);
837 if (std::string::npos == start || std::string::npos == end) {
838 s = "";
839 } else {
840 s = s.substr(start, end-start+1);
844 bool Scene::parseLine( const std::string& line )
846 try {
847 switch( line[0] ) {
848 case 'T': m_title = line.substr(line.find(':')+1);
849 trimWhitespace(m_title); return true;
850 case 'B': m_bg = line.substr(line.find(':')+1); return true;
851 case 'A': m_author = line.substr(line.find(':')+1);
852 trimWhitespace(m_author);
853 /* The original levels have their author set
854 * to "test". Remove the author in that case. */
855 if (m_author.compare("test") == 0) {
856 m_author = "";
857 } return true;
858 case 'S': {
859 Stroke *s = new Stroke(line);
860 m_strokes.append(s);
861 return true;
863 case 'G': setGravity(line.substr(line.find(':')+1));return true;
865 } catch ( const char* e ) {
866 printf("Stroke error: %s\n",e);
868 return false;
871 void Scene::protect( int n )
873 m_protect = (n==-1 ? m_strokes.size() : n );
876 bool Scene::save( const std::string& file )
878 printf("saving to %s\n",file.c_str());
879 std::ofstream o( file.c_str(), std::ios::out );
880 if ( o.is_open() ) {
881 o << "Title: "<<m_title<<std::endl;
882 o << "Author: "<<m_author<<std::endl;
883 o << "Background: "<<m_bg<<std::endl;
884 for ( int i=0; i<m_strokes.size(); i++ ) {
885 o << m_strokes[i]->asString();
887 o.close();
888 return true;
889 } else {
890 return false;
895 Image *Scene::g_bgImage = NULL;