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.
14  *
15  */
16 
17 #include "Common.h"
18 #include "Array.h"
19 #include "Config.h"
20 #include "Game.h"
21 #include "Overlay.h"
22 #include "Path.h"
23 #include "Canvas.h"
24 #include "Levels.h"
25 #include "Http.h"
26 #include "Os.h"
27 
28 #include <SDL/SDL.h>
29 #include <SDL/SDL_image.h>
30 
31 #include <cstdio>
32 #include <iostream>
33 #include <sstream>
34 #include <fstream>
35 #include <memory.h>
36 #include <errno.h>
37 #include <sys/stat.h>
38 
39 using namespace std;
40 
41 unsigned char levelbuf[64*1024];
42 
43 class Transform
44 {
45 public:
Transform(float32 scale,float32 rotation,const Vec2 & translation)46   Transform( float32 scale, float32 rotation, const Vec2& translation )
47   {
48     set( scale, rotation, translation );
49   }
set(float32 scale,float32 rotation,const Vec2 & translation)50   void set( float32 scale, float32 rotation, const Vec2& translation )
51   {
52     if ( scale==0.0f && rotation==0.0f && translation==Vec2(0,0) ) {
53       m_bypass = true;
54     } else {
55       m_rot.Set( rotation );
56       m_pos = translation;
57       m_rot.col1.x *= scale;
58       m_rot.col1.y *= scale;
59       m_rot.col2.x *= scale;
60       m_rot.col2.y *= scale;
61       m_invrot = m_rot.Invert();
62       m_bypass = false;
63     }
64   }
transform(const Path & pin,Path & pout)65   inline void transform( const Path& pin, Path& pout ) {
66     pout = pin;
67     if ( !m_bypass ) {
68       pout.rotate( m_rot );
69       pout.translate( m_pos );
70     }
71   }
transform(Vec2 & vec)72   inline void transform( Vec2& vec ) {
73     if ( !m_bypass ) {
74       vec = Vec2( b2Mul( m_rot, vec ) ) + m_pos;
75     }
76   }
inverseTransform(Vec2 & vec)77   inline void inverseTransform( Vec2& vec ) {
78     if ( !m_bypass ) {
79       vec = Vec2( b2Mul( m_invrot, vec-m_pos ) );
80     }
81   }
82 private:
Transform()83   Transform() {}
84   bool m_bypass;
85   b2Mat22 m_rot;
86   b2Mat22 m_invrot;
87   Vec2 m_pos;
88 };
89 
90 Transform worldToScreen( 0.5f, M_PI/2, Vec2(240,0) );
91 
configureScreenTransform(int w,int h)92 void configureScreenTransform( int w, int h )
93 {
94   SCREEN_WIDTH = w;
95   SCREEN_HEIGHT = h;
96   FULLSCREEN_RECT = Rect(0,0,w-1,h-1);
97   if ( w==WORLD_WIDTH && h==WORLD_HEIGHT ) { //unity
98     worldToScreen.set( 0.0f, 0.0f, Vec2(0,0) );
99   } else {
100     float rot = 0.0f;
101     Vec2 tr(0,0);
102     if ( h > w ) { //portrait
103       rot = M_PI/2;
104       tr = Vec2( w, 0 );
105       b2Swap( h, w );
106     }
107     float scalew = (float)w/(float)WORLD_WIDTH;
108     float scaleh = (float)h/(float)WORLD_HEIGHT;
109     if ( scalew < scaleh ) {
110       worldToScreen.set( scalew, rot, tr );
111     } else {
112       worldToScreen.set( scaleh, rot, tr );
113     }
114   }
115 }
116 
117 class Stroke
118 {
119 public:
120   typedef enum {
121     ATTRIB_DUMMY = 0,
122     ATTRIB_GROUND = 1,
123     ATTRIB_TOKEN = 2,
124     ATTRIB_GOAL = 4,
125     ATTRIB_DECOR = 8,
126     ATTRIB_SLEEPING = 16,
127     ATTRIB_HIDDEN = 32,
128     ATTRIB_DELETED = 64,
129     ATTRIB_CLASSBITS = ATTRIB_TOKEN | ATTRIB_GOAL
130   } Attribute;
131 
132 private:
133   struct JointDef : public b2RevoluteJointDef
134   {
JointDefStroke::JointDef135     JointDef( b2Body* b1, b2Body* b2, const b2Vec2& pt )
136     {
137       Initialize( b1, b2, pt );
138       maxMotorTorque = 10.0f;
139       motorSpeed = 0.0f;
140       enableMotor = true;
141     }
142   };
143 
144   struct BoxDef : public b2PolygonDef
145   {
initStroke::BoxDef146     void init( const Vec2& p1, const Vec2& p2, int attr )
147     {
148       b2Vec2 barOrigin = p1;
149       b2Vec2 bar = p2 - p1;
150       bar *= 1.0f/PIXELS_PER_METREf;
151       barOrigin *= 1.0f/PIXELS_PER_METREf;;
152       SetAsBox( bar.Length()/2.0f, 0.1f,
153 		0.5f*bar + barOrigin, vec2Angle( bar ));
154       //      SetAsBox( bar.Length()/2.0f+b2_toiSlop, b2_toiSlop*2.0f,
155       //	0.5f*bar + barOrigin, vec2Angle( bar ));
156       friction = 0.3f;
157       if ( attr & ATTRIB_GROUND ) {
158 	density = 0.0f;
159       } else if ( attr & ATTRIB_GOAL ) {
160 	density = 100.0f;
161       } else if ( attr & ATTRIB_TOKEN ) {
162 	density = 3.0f;
163 	friction = 0.1f;
164       } else {
165 	density = 5.0f;
166       }
167       restitution = 0.2f;
168     }
169   };
170 
171 public:
Stroke(const Path & path)172   Stroke( const Path& path )
173     : m_rawPath(path)
174   {
175     m_colour = brushColours[DEFAULT_BRUSH];
176     m_attributes = 0;
177     m_origin = m_rawPath.point(0);
178     m_rawPath.translate( -m_origin );
179     reset();
180   }
181 
Stroke(const string & str)182   Stroke( const string& str )
183   {
184     int col = 0;
185     m_colour = brushColours[DEFAULT_BRUSH];
186     m_attributes = 0;
187     m_origin = Vec2(400,240);
188     reset();
189     const char *s = str.c_str();
190     while ( *s && *s!=':' && *s!='\n' ) {
191       switch ( *s ) {
192       case 't': setAttribute( ATTRIB_TOKEN ); break;
193       case 'g': setAttribute( ATTRIB_GOAL ); break;
194       case 'f': setAttribute( ATTRIB_GROUND ); break;
195       case 's': setAttribute( ATTRIB_SLEEPING ); break;
196       case 'd': setAttribute( ATTRIB_DECOR ); break;
197       default:
198 	if ( *s >= '0' && *s <= '9' ) {
199 	  col = col*10 + *s -'0';
200 	}
201 	break;
202       }
203       s++;
204     }
205     if ( col >= 0 && col < NUM_BRUSHES ) {
206       m_colour = brushColours[col];
207     }
208     if ( *s++ == ':' ) {
209       float32 x,y;
210       while ( sscanf( s, "%f,%f", &x, &y )==2) {
211 	m_rawPath.append( Vec2((int)x,(int)y) );
212 	while ( *s && *s!=' ' && *s!='\t' ) s++;
213 	while ( *s==' ' || *s=='\t' ) s++;
214       }
215     }
216     if ( m_rawPath.size() < 2 ) {
217       throw "invalid stroke def";
218     }
219     m_origin = m_rawPath.point(0);
220     m_rawPath.translate( -m_origin );
221     setAttribute( ATTRIB_DUMMY );
222   }
223 
reset(b2World * world=NULL)224   void reset( b2World* world=NULL )
225   {
226     if (m_body && world) {
227       world->DestroyBody( m_body );
228     }
229     m_body = NULL;
230     m_xformAngle = 7.0f;
231     m_drawnBbox.tl = m_origin;
232     m_drawnBbox.br = m_origin;
233     m_jointed[0] = m_jointed[1] = false;
234     m_shapePath = m_rawPath;
235     m_hide = 0;
236     m_drawn = false;
237   }
238 
asString()239   string asString()
240   {
241     stringstream s;
242     s << 'S';
243     if ( hasAttribute(ATTRIB_TOKEN) )    s<<'t';
244     if ( hasAttribute(ATTRIB_GOAL) )     s<<'g';
245     if ( hasAttribute(ATTRIB_GROUND) )   s<<'f';
246     if ( hasAttribute(ATTRIB_SLEEPING) ) s<<'s';
247     if ( hasAttribute(ATTRIB_DECOR) )    s<<'d';
248     for ( int i=0; i<NUM_BRUSHES; i++ ) {
249       if ( m_colour==brushColours[i] )  s<<i;
250     }
251     s << ":";
252     transform();
253     for ( int i=0; i<m_xformedPath.size(); i++ ) {
254       const Vec2& p = m_xformedPath.point(i);
255       s <<' '<< p.x << ',' << p.y;
256     }
257     s << endl;
258     return s.str();
259   }
260 
setAttribute(Stroke::Attribute a)261   void setAttribute( Stroke::Attribute a )
262   {
263     m_attributes |= a;
264     if ( m_attributes & ATTRIB_TOKEN )     m_colour = brushColours[RED_BRUSH];
265     else if ( m_attributes & ATTRIB_GOAL ) m_colour = brushColours[YELLOW_BRUSH];
266   }
267 
clearAttribute(Stroke::Attribute a)268   void clearAttribute( Stroke::Attribute a )
269   {
270     m_attributes &= ~a;
271   }
272 
hasAttribute(Stroke::Attribute a)273   bool hasAttribute( Stroke::Attribute a )
274   {
275     return (m_attributes&a) != 0;
276   }
setColour(int c)277   void setColour( int c )
278   {
279     m_colour = c;
280   }
281 
createBodies(b2World & world)282   void createBodies( b2World& world )
283   {
284     process();
285     if ( hasAttribute( ATTRIB_DECOR ) ){
286       return; //decorators have no physical embodiment
287     }
288     int n = m_shapePath.numPoints();
289     if ( n > 1 ) {
290       b2BodyDef bodyDef;
291       bodyDef.position = m_origin;
292       bodyDef.position *= 1.0f/PIXELS_PER_METREf;
293       bodyDef.userData = this;
294       if ( m_attributes & ATTRIB_SLEEPING ) {
295 	bodyDef.isSleeping = true;
296       }
297       m_body = world.CreateBody( &bodyDef );
298       for ( int i=1; i<n; i++ ) {
299 	BoxDef boxDef;
300 	boxDef.init( m_shapePath.point(i-1),
301 		     m_shapePath.point(i),
302 		     m_attributes );
303 	m_body->CreateShape( &boxDef );
304       }
305       m_body->SetMassFromShapes();
306 
307     }
308     transform();
309   }
310 
maybeCreateJoint(b2World & world,Stroke * other)311   bool maybeCreateJoint( b2World& world, Stroke* other )
312   {
313     if ( (m_attributes&ATTRIB_CLASSBITS)
314 	 != (other->m_attributes&ATTRIB_CLASSBITS) ) {
315       return false; // can only joint matching classes
316     } else if ( hasAttribute(ATTRIB_GROUND) ) {
317       return true; // no point jointing grounds
318     } else if ( m_body && other->body() ) {
319       transform();
320       int n = m_xformedPath.numPoints();
321       for ( int end=0; end<2; end++ ) {
322 	if ( !m_jointed[end] ) {
323 	  const Vec2& p = m_xformedPath.point( end ? n-1 : 0 );
324 	  if ( other->distanceTo( p ) <= JOINT_TOLERANCE ) {
325 	    //printf("jointed end %d d=%f\n",end,other->distanceTo( p ));
326 	    b2Vec2 pw = p;
327 	    pw *= 1.0f/PIXELS_PER_METREf;
328 	    JointDef j( m_body, other->m_body, pw );
329 	    world.CreateJoint( &j );
330 	    m_jointed[end] = true;
331 	  }
332 	}
333       }
334     }
335     if ( m_body ) {
336       return m_jointed[0] && m_jointed[1];
337     }
338     return true; ///nothing to do
339   }
340 
draw(Canvas & canvas)341   void draw( Canvas& canvas )
342   {
343     if ( m_hide < HIDE_STEPS ) {
344       bool thick = (canvas.width() > 400);
345       transform();
346       canvas.drawPath( m_screenPath, canvas.makeColour(m_colour), thick );
347       m_drawn = true;
348     }
349     m_drawnBbox = m_screenBbox;
350   }
351 
addPoint(const Vec2 & pp)352   void addPoint( const Vec2& pp )
353   {
354     Vec2 p = pp; p -= m_origin;
355     if ( p == m_rawPath.point( m_rawPath.numPoints()-1 ) ) {
356     } else {
357       m_rawPath.append( p );
358       m_drawn = false;
359     }
360   }
361 
origin(const Vec2 & p)362   void origin( const Vec2& p )
363   {
364     // todo
365     if ( m_body ) {
366       b2Vec2 pw = p;
367       pw *= 1.0f/PIXELS_PER_METREf;
368       m_body->SetXForm( pw, m_body->GetAngle() );
369     }
370     m_origin = p;
371   }
372 
body()373   b2Body* body() { return m_body; }
374 
distanceTo(const Vec2 & pt)375   float32 distanceTo( const Vec2& pt )
376   {
377     float32 best = 100000.0;
378     transform();
379     for ( int i=1; i<m_xformedPath.numPoints(); i++ ) {
380       Segment s( m_xformedPath.point(i-1), m_xformedPath.point(i) );
381       float32 d = s.distanceTo( pt );
382       //printf("  d[%d]=%f %d,%d\n",i,d,m_rawPath.point(i-1).x,m_rawPath.point(i-1).y);
383       if ( d < best ) {
384         best = d;
385       }
386     }
387     return best;
388   }
389 
screenBbox()390   Rect screenBbox()
391   {
392     transform();
393     return m_screenBbox;
394   }
395 
lastDrawnBbox()396   Rect lastDrawnBbox()
397   {
398     return m_drawnBbox;
399   }
400 
worldBbox()401   Rect worldBbox()
402   {
403     return m_xformedPath.bbox();
404   }
405 
isDirty()406   bool isDirty()
407   {
408     return !m_drawn || transform();
409   }
410 
hide()411   void hide()
412   {
413     if ( m_hide==0 ) {
414       m_hide = 1;
415 
416       if (m_body) {
417 	// stash the body where no-one will find it
418 	m_body->SetXForm( b2Vec2(0.0f,SCREEN_HEIGHT*2.0f), 0.0f );
419 	m_body->SetLinearVelocity( b2Vec2(0.0f,0.0f) );
420 	m_body->SetAngularVelocity( 0.0f );
421       }
422     }
423   }
424 
hidden()425   bool hidden()
426   {
427     return m_hide >= HIDE_STEPS;
428   }
429 
numPoints()430   int numPoints()
431   {
432     return m_rawPath.numPoints();
433   }
434 
435 private:
vec2Angle(b2Vec2 v)436   static float32 vec2Angle( b2Vec2 v )
437   {
438     return b2Atan2(v.y, v.x);
439   }
440 
process()441   void process()
442   {
443     float32 thresh = 0.1*SIMPLIFY_THRESHOLDf;
444     m_rawPath.simplify( thresh );
445     m_shapePath = m_rawPath;
446 
447     while ( m_shapePath.numPoints() > MULTI_VERTEX_LIMIT ) {
448       thresh += SIMPLIFY_THRESHOLDf;
449       m_shapePath.simplify( thresh );
450     }
451   }
452 
transform()453   bool transform()
454   {
455     // distinguish between xformed raw and shape path as needed
456     if ( m_hide ) {
457       if ( m_hide < HIDE_STEPS ) {
458 	//printf("hide %d\n",m_hide);
459 	Vec2 o = m_screenBbox.centroid();
460 	m_screenPath -= o;
461 	m_screenPath.scale( 0.99 );
462 	m_screenPath += o;
463 	m_screenBbox = m_screenPath.bbox();
464 	m_hide++;
465 	return true;
466       }
467     } else if ( m_body ) {
468       if ( hasAttribute( ATTRIB_DECOR ) ) {
469 	return false; // decor never moves
470       } else if ( hasAttribute( ATTRIB_GROUND )
471 		  && m_xformAngle == m_body->GetAngle() ) {
472 	return false; // ground strokes never move.
473       } else if ( m_xformAngle != m_body->GetAngle()
474 	   ||  ! (m_xformPos == m_body->GetPosition()) ) {
475 	//printf("transform stroke - rot or pos\n");
476 	b2Mat22 rot( m_body->GetAngle() );
477 	b2Vec2 orig = PIXELS_PER_METREf * m_body->GetPosition();
478 	m_xformedPath = m_rawPath;
479 	m_xformedPath.rotate( rot );
480 	m_xformedPath.translate( Vec2(orig) );
481 	m_xformAngle = m_body->GetAngle();
482 	m_xformPos = m_body->GetPosition();
483 	worldToScreen.transform( m_xformedPath, m_screenPath );
484 	m_screenBbox = m_screenPath.bbox();
485       } else {
486 	//printf("transform none\n");
487 	return false;
488       }
489     } else {
490       //printf("transform no body\n");
491       m_xformedPath = m_rawPath;
492       m_xformedPath.translate( m_origin );
493       worldToScreen.transform( m_xformedPath, m_screenPath );
494       m_screenBbox = m_screenPath.bbox();
495       return false;
496     }
497     return true;
498   }
499 
500   Path      m_rawPath;
501   int       m_colour;
502   int       m_attributes;
503   Vec2      m_origin;
504   Path      m_shapePath;
505   Path      m_xformedPath;
506   Path      m_screenPath;
507   float32   m_xformAngle;
508   b2Vec2    m_xformPos;
509   Rect      m_screenBbox;
510   Rect      m_drawnBbox;
511   bool      m_drawn;
512   b2Body*   m_body;
513   bool      m_jointed[2];
514   int       m_hide;
515 };
516 
517 
518 class Scene : private b2ContactListener
519 {
520 public:
521 
Scene(bool noWorld=false)522   Scene( bool noWorld=false )
523     : m_world( NULL ),
524       m_bgImage( NULL ),
525       m_protect( 0 )
526   {
527     if ( !noWorld ) {
528       b2AABB worldAABB;
529       worldAABB.lowerBound.Set(-100.0f, -100.0f);
530       worldAABB.upperBound.Set(100.0f, 100.0f);
531 
532       b2Vec2 gravity(0.0f, 10.0f);
533       bool doSleep = true;
534       m_world = new b2World(worldAABB, gravity, doSleep);
535       m_world->SetContactListener( this );
536     }
537   }
538 
~Scene()539   ~Scene()
540   {
541     clear();
542     if ( m_world ) {
543       step();
544       delete m_world;
545     }
546   }
547 
numStrokes()548   int numStrokes()
549   {
550     return m_strokes.size();
551   }
552 
newStroke(const Path & p)553   Stroke* newStroke( const Path& p ) {
554     Stroke *s = new Stroke(p);
555     m_strokes.append( s );
556     return s;
557   }
558 
deleteStroke(Stroke * s)559   void deleteStroke( Stroke *s ) {
560     //printf("delete stroke %p\n",s);
561     if ( s ) {
562       int i = m_strokes.indexOf(s);
563       if ( i >= m_protect ) {
564 	reset(s);
565 	m_strokes.erase( m_strokes.indexOf(s) );
566       }
567     }
568   }
569 
570 
activate(Stroke * s)571   void activate( Stroke *s )
572   {
573     //printf("activate stroke\n");
574     s->createBodies( *m_world );
575     createJoints( s );
576   }
577 
activateAll()578   void activateAll()
579   {
580     for ( int i=0; i < m_strokes.size(); i++ ) {
581       m_strokes[i]->createBodies( *m_world );
582     }
583     for ( int i=0; i < m_strokes.size(); i++ ) {
584       createJoints( m_strokes[i] );
585     }
586   }
587 
createJoints(Stroke * s)588   void createJoints( Stroke *s )
589   {
590     for ( int j=m_strokes.size()-1; j>=0; j-- ) {
591       if ( s != m_strokes[j] ) {
592 	//printf("try join to %d\n",j);
593 	s->maybeCreateJoint( *m_world, m_strokes[j] );
594 	m_strokes[j]->maybeCreateJoint( *m_world, s );
595       }
596     }
597   }
598 
step()599   void step()
600   {
601     m_world->Step( ITERATION_TIMESTEPf, SOLVER_ITERATIONS );
602     // clean up delete strokes
603     for ( int i=0; i< m_strokes.size(); i++ ) {
604       if ( m_strokes[i]->hasAttribute(Stroke::ATTRIB_DELETED) ) {
605 	m_strokes[i]->clearAttribute(Stroke::ATTRIB_DELETED);
606 	m_strokes[i]->hide();
607       }
608     }
609     // check for token respawn
610     for ( int i=0; i < m_strokes.size(); i++ ) {
611       if ( m_strokes[i]->hasAttribute( Stroke::ATTRIB_TOKEN )
612 	   && !BOUNDS_RECT.intersects( m_strokes[i]->worldBbox() ) ) {
613 	printf("RESPAWN token %d\n",i);
614 	reset( m_strokes[i] );
615 	activate( m_strokes[i] );
616       }
617     }
618   }
619 
620   // b2ContactListener callback when a new contact is detected
Add(const b2ContactPoint * point)621   virtual void Add(const b2ContactPoint* point)
622   {
623     // check for completion
624     //if (c->GetManifoldCount() > 0) {
625     Stroke* s1 = (Stroke*)point->shape1->GetBody()->GetUserData();
626     Stroke* s2 = (Stroke*)point->shape2->GetBody()->GetUserData();
627     if ( s1 && s2 ) {
628       if ( s2->hasAttribute(Stroke::ATTRIB_TOKEN) ) {
629 	b2Swap( s1, s2 );
630       }
631       if ( s1->hasAttribute(Stroke::ATTRIB_TOKEN)
632 	   && s2->hasAttribute(Stroke::ATTRIB_GOAL) ) {
633 	s2->setAttribute(Stroke::ATTRIB_DELETED);
634 	printf("SUCCESS!! level complete\n");
635       }
636     }
637   }
638 
isCompleted()639   bool isCompleted()
640   {
641     for ( int i=0; i < m_strokes.size(); i++ ) {
642       if ( m_strokes[i]->hasAttribute( Stroke::ATTRIB_GOAL )
643 	   && !m_strokes[i]->hidden() ) {
644 	return false;
645       }
646     }
647     //printf("completed!\n");
648     return true;
649   }
650 
dirtyArea()651   Rect dirtyArea()
652   {
653     Rect r(0,0,0,0),temp;
654     int numDirty = 0;
655     for ( int i=0; i<m_strokes.size(); i++ ) {
656       if ( m_strokes[i]->isDirty() ) {
657 	// acumulate new areas to draw
658 	temp = m_strokes[i]->screenBbox();
659 	if ( !temp.isEmpty() ) {
660 	  if ( numDirty==0 ) {
661 	    r = temp;
662 	  } else {
663 	    r.expand( m_strokes[i]->screenBbox() );
664 	  }
665 	  // plus prev areas to erase
666 	  r.expand( m_strokes[i]->lastDrawnBbox() );
667 	  numDirty++;
668 	}
669       }
670     }
671     if ( !r.isEmpty() ) {
672       // expand to allow for thick lines
673       r.tl.x--; r.tl.y--;
674       r.br.x++; r.br.y++;
675     }
676     return r;
677   }
678 
draw(Canvas & canvas,const Rect & area)679   void draw( Canvas& canvas, const Rect& area )
680   {
681     if ( m_bgImage ) {
682       canvas.setBackground( m_bgImage );
683     } else {
684       canvas.setBackground( 0 );
685     }
686     canvas.clear( area );
687     Rect clipArea = area;
688     clipArea.tl.x--;
689     clipArea.tl.y--;
690     clipArea.br.x++;
691     clipArea.br.y++;
692     for ( int i=0; i<m_strokes.size(); i++ ) {
693       if ( area.intersects( m_strokes[i]->screenBbox() ) ) {
694 	m_strokes[i]->draw( canvas );
695       }
696     }
697     //canvas.drawRect( area, 0xffff0000, false );
698   }
699 
reset(Stroke * s=NULL)700   void reset( Stroke* s=NULL )
701   {
702     for ( int i=0; i<m_strokes.size(); i++ ) {
703       if (s==NULL || s==m_strokes[i]) {
704 	m_strokes[i]->reset(m_world);
705       }
706     }
707   }
708 
strokeAtPoint(const Vec2 pt,float32 max)709   Stroke* strokeAtPoint( const Vec2 pt, float32 max )
710   {
711     Stroke* best = NULL;
712     for ( int i=0; i<m_strokes.size(); i++ ) {
713       float32 d = m_strokes[i]->distanceTo( pt );
714       //printf("stroke %d dist %f\n",i,d);
715       if ( d < max ) {
716 	max = d;
717 	best = m_strokes[i];
718       }
719     }
720     return best;
721   }
722 
clear()723   void clear()
724   {
725     reset();
726     while ( m_strokes.size() ) {
727       delete m_strokes[0];
728       m_strokes.erase(0);
729     }
730     if ( m_world ) {
731       //step is required to actually destroy bodies and joints
732       m_world->Step( ITERATION_TIMESTEPf, SOLVER_ITERATIONS );
733     }
734   }
735 
736 
737 
load(unsigned char * buf,int bufsize)738   bool load( unsigned char *buf, int bufsize )
739   {
740     string s( (const char*)buf, bufsize );
741     stringstream in( s, ios::in );
742     return load( in );
743   }
744 
load(const string & file)745   bool load( const string& file )
746   {
747     ifstream in( file.c_str(), ios::in );
748     return load( in );
749   }
750 
load(istream & in)751   bool load( istream& in )
752   {
753     clear();
754     if ( g_bgImage==NULL ) {
755       g_bgImage = new Image("paper.jpg");
756       g_bgImage->scale( SCREEN_WIDTH, SCREEN_HEIGHT );
757     }
758     m_bgImage = g_bgImage;
759     string line;
760     while ( !in.eof() ) {
761       getline( in, line );
762       parseLine( line );
763     }
764     protect();
765     return true;
766   }
767 
parseLine(const string & line)768   bool parseLine( const string& line )
769   {
770     switch( line[0] ) {
771     case 'T': m_title = line.substr(line.find(':')+1);  return true;
772     case 'B': m_bg = line.substr(line.find(':')+1);     return true;
773     case 'A': m_author = line.substr(line.find(':')+1); return true;
774     case 'S': m_strokes.append( new Stroke(line) );     return true;
775     }
776     return false;
777   }
778 
protect(int n=-1)779   void protect( int n=-1 )
780   {
781     m_protect = (n==-1 ? m_strokes.size() : n );
782   }
783 
save(const std::string & file)784   bool save( const std::string& file )
785   {
786     printf("saving to %s\n",file.c_str());
787     ofstream o( file.c_str(), ios::out );
788     if ( o.is_open() ) {
789       o << "Title: "<<m_title<<endl;
790       o << "Author: "<<m_author<<endl;
791       o << "Background: "<<m_bg<<endl;
792       for ( int i=0; i<m_strokes.size(); i++ ) {
793 	o << m_strokes[i]->asString();
794       }
795       o.close();
796       return true;
797     } else {
798       return false;
799     }
800   }
801 
strokes()802   Array<Stroke*>& strokes()
803   {
804     return m_strokes;
805   }
806 
807 private:
808   b2World        *m_world;
809   Array<Stroke*>  m_strokes;
810   string          m_title, m_author, m_bg;
811   Image          *m_bgImage;
812   static Image   *g_bgImage;
813   int             m_protect;
814 };
815 
816 Image *Scene::g_bgImage = NULL;
817 
818 
819 
820 
821 
822 struct DemoEntry {
DemoEntryDemoEntry823   DemoEntry( int _t, int _o, SDL_Event& _e ) : t(_t), o(_o), e(_e) {}
824   int t,o;
825   SDL_Event e;
826 };
827 
828 class DemoLog : public Array<DemoEntry>
829 {
830 public:
asString(int i)831   std::string asString( int i )
832   {
833     if ( i < size() ) {
834       DemoEntry& e = at(i);
835       stringstream s;
836       s << "E: " << e.t << " " << e.o << " ";
837       switch( e.e.type ) {
838       case SDL_KEYDOWN:
839 	s << "K" << e.e.key.keysym.sym;
840 	break;
841       case SDL_KEYUP:
842 	s << "k" << e.e.key.keysym.sym;
843 	break;
844       case SDL_MOUSEBUTTONDOWN:
845 	s << "B" << e.e.button.button;
846 	s << "," << e.e.button.x << "," << e.e.button.y;
847 	break;
848       case SDL_MOUSEBUTTONUP:
849 	s << "b" << e.e.button.button;
850 	s << "," << e.e.button.x << "," << e.e.button.y;
851 	break;
852       case SDL_MOUSEMOTION:
853 	s << "M" << e.e.button.x << "," << e.e.button.y;
854 	break;
855       }
856       return s.str();
857     }
858     return std::string();
859   }
860 
append(int tick,int offset,SDL_Event & ev)861   void append( int tick, int offset, SDL_Event& ev )
862   {
863     Array<DemoEntry>::append( DemoEntry( tick, offset, ev ) );
864   }
865 
appendFromString(const std::string & str)866   void appendFromString( const std::string& str )
867   {
868     const char *s = str.c_str();
869     int t, o, v1, v2, v3;
870     char c;
871     SDL_Event ev = {0};
872     ev.type = 0xff;
873     if ( sscanf(s,"E: %d %d %c%d",&t,&o,&c,&v1)==4 ) { //1 arg
874       switch ( c ) {
875       case 'K': ev.type = SDL_KEYDOWN; break;
876       case 'k': ev.type = SDL_KEYUP; break;
877       }
878       ev.key.keysym.sym = (SDLKey)v1;
879     } else if ( sscanf(s,"E: %d %d %c%d,%d",&t,&o,&c,&v1,&v2)==5 ) { //2 args
880       switch ( c ) {
881       case 'M': ev.type = SDL_MOUSEMOTION; break;
882       }
883       ev.button.x = v1;
884       ev.button.y = v2;
885     } else if ( sscanf(s,"E: %d %d %c%d,%d,%d",&t,&o,&c,&v1,&v2,&v3)==6 ) { //3 args
886       switch ( c ) {
887       case 'B': ev.type = SDL_MOUSEBUTTONDOWN; break;
888       case 'b': ev.type = SDL_MOUSEBUTTONUP; break;
889       }
890       ev.button.button = v1;
891       ev.button.x = v2;
892       ev.button.y = v3;
893     }
894     if ( ev.type != 0xff ) {
895       append( t, o, ev );
896     }
897   }
898 };
899 
900 class DemoRecorder
901 {
902 public:
903 
start()904   void start()
905   {
906     m_running = true;
907     m_log.empty();
908     m_log.capacity(512);
909     m_lastTick = 0;
910     m_lastTickTime = SDL_GetTicks();
911   }
912 
stop()913   void stop()
914   {
915     printf("stop recording: %d events\n",m_log.size());
916     m_running = false;
917   }
918 
tick()919   void tick()
920   {
921     if ( m_running ) {
922       m_lastTick++;
923       m_lastTickTime = SDL_GetTicks();
924     }
925   }
926 
record(SDL_Event & ev)927   void record( SDL_Event& ev )
928   {
929     if ( m_running ) {
930       m_log.append( m_lastTick, SDL_GetTicks()-m_lastTickTime, ev );
931     }
932   }
933 
getLog()934   DemoLog& getLog() { return m_log; }
935 
936 private:
937   bool          m_running;
938   DemoLog       m_log;
939   int 		m_lastTick;
940   int 		m_lastTickTime;
941 };
942 
943 
944 class DemoPlayer
945 {
946 public:
947 
start(const DemoLog * log)948   void start( const DemoLog* log )
949   {
950     m_playing = true;
951     m_log = log;
952     m_index = 0;
953     m_lastTick = 0;
954     m_lastTickTime = SDL_GetTicks();
955     printf("start playback: %d events\n",m_log->size());
956   }
957 
isRunning()958   bool isRunning() { return m_playing; }
959 
stop()960   void stop()
961   {
962     m_playing = false;
963     m_log = NULL;
964   }
965 
tick()966   void tick()
967   {
968     if ( m_playing ) {
969       m_lastTick++;
970       m_lastTickTime = SDL_GetTicks();
971     }
972   }
973 
fetchEvent(SDL_Event & ev)974   bool fetchEvent( SDL_Event& ev )
975   {
976     if ( m_playing
977 	 && m_index < m_log->size()
978 	 && m_log->at(m_index).t <= m_lastTick
979 	 /*&& m_log->at(m_index).o <= SDL_GetTicks()-m_lastTickTime*/ ) {
980       ev = m_log->at(m_index).e;
981       m_index++;
982       return true;
983     }
984     return false;
985   }
986 
987 private:
988   bool           m_playing;
989   const DemoLog* m_log;
990   int            m_index;
991   int  		 m_lastTick;
992   int  		 m_lastTickTime;
993 };
994 
995 
996 class Game : public GameControl
997 {
998   Scene   	    m_scene;
999   Stroke  	   *m_createStroke;
1000   Stroke           *m_moveStroke;
1001   Array<Overlay*>   m_overlays;
1002   Window            m_window;
1003   Overlay          *m_pauseOverlay;
1004   Overlay          *m_editOverlay;
1005   //  DemoOverlay       m_demoOverlay;
1006   DemoRecorder      m_recorder;
1007   DemoPlayer        m_player;
1008   Os               *m_os;
1009 public:
Game(int width,int height)1010   Game( int width, int height )
1011   : m_createStroke(NULL),
1012     m_moveStroke(NULL),
1013     m_window(width,height,"Numpty Physics","NPhysics"),
1014     m_pauseOverlay( NULL ),
1015     m_editOverlay( NULL ),
1016     m_os( Os::get() )
1017     //,m_demoOverlay( *this )
1018   {
1019     configureScreenTransform( m_window.width(), m_window.height() );
1020   }
1021 
1022 
renderScene(Canvas & c,int level)1023   virtual bool renderScene( Canvas& c, int level )
1024   {
1025     Scene scene( true );
1026     int size = m_levels.load( level, levelbuf, sizeof(levelbuf) );
1027     if ( size && scene.load( levelbuf, size ) ) {
1028       scene.draw( c, FULLSCREEN_RECT );
1029       return true;
1030     }
1031     return false;
1032   }
1033 
gotoLevel(int level,bool replay=false)1034   void gotoLevel( int level, bool replay=false )
1035   {
1036     printf("gotoLevel %d\n",level);
1037     if ( level >= 0 && level < m_levels.numLevels() ) {
1038       int size = m_levels.load( level, levelbuf, sizeof(levelbuf) );
1039       if ( size && m_scene.load( levelbuf, size ) ) {
1040 	m_scene.activateAll();
1041 	//m_window.setSubName( file );
1042 	m_refresh = true;
1043 	if ( m_edit ) {
1044 	  m_scene.protect(0);
1045 	}
1046 	m_recorder.stop();
1047 	m_player.stop();
1048 	if ( replay ) {
1049 	  m_player.start( &m_recorder.getLog() );
1050 	} else {
1051 	  m_recorder.start();
1052 	}
1053 	m_level = level;
1054       }
1055     }
1056   }
1057 
1058 
save(const char * file=NULL)1059   bool save( const char *file=NULL )
1060   {
1061     string p;
1062     if ( file ) {
1063       p = file;
1064     } else {
1065       p = Config::userDataDir() + Os::pathSep + "L99_saved.nph";
1066     }
1067     if ( m_scene.save( p ) ) {
1068       m_levels.addPath( p.c_str() );
1069       int l = m_levels.findLevel( p.c_str() );
1070       if ( l >= 0 ) {
1071 	m_level = l;
1072 	m_window.setSubName( p.c_str() );
1073       }
1074       return true;
1075     }
1076     return false;
1077   }
1078 
send()1079   bool send()
1080   {
1081     if ( save( SEND_TEMP_FILE ) ) {
1082       Http h;
1083       if ( h.post( Config::planetRoot().c_str(), "upload", SEND_TEMP_FILE ) ) {
1084 	std::string id = h.getHeader("NP-Upload-Id");
1085 	if ( id.length() > 0 ) {
1086 	  if ( !m_os->openBrowser((Config::planetRoot()+"?level="+id).c_str()) ) {
1087 	    showMessage("Unable to launch browser");
1088 	  }
1089 	} else {
1090 	  showMessage("UploadFailed: unknown error");
1091 	}
1092       } else {
1093 	showMessage(std::string("UploadFailed: ")+h.errorMessage());
1094       }
1095     }
1096     return false;
1097   }
1098 
setTool(int t)1099   void setTool( int t )
1100   {
1101     m_colour = t;
1102   }
1103 
editMode(bool set)1104   void editMode( bool set )
1105   {
1106     m_edit = set;
1107   }
1108 
showMessage(const std::string & msg)1109   void showMessage( const std::string& msg )
1110   {
1111     //todo
1112     printf("showMessage \"%s\"\n",msg.c_str());
1113   }
1114 
showOverlay(Overlay * o)1115   void showOverlay( Overlay* o )
1116   {
1117     printf("show overlay\n");
1118     m_overlays.append( o );
1119     o->onShow();
1120   }
1121 
hideOverlay(Overlay * o)1122   void hideOverlay( Overlay* o )
1123   {
1124     printf("hide overlay\n");
1125     o->onHide();
1126     m_overlays.erase( m_overlays.indexOf(o) );
1127     m_refresh = true;
1128   }
1129 
toggleOverlay(Overlay * o)1130   void toggleOverlay( Overlay* o )
1131   {
1132     if ( m_overlays.indexOf( o ) >= 0 ) {
1133       hideOverlay( o );
1134     } else {
1135       showOverlay( o );
1136     }
1137   }
1138 
isPaused()1139   bool isPaused()
1140   {
1141     return m_pauseOverlay != NULL
1142       && m_overlays.indexOf( m_pauseOverlay ) >= 0;
1143   }
1144 
edit(bool doEdit)1145   void edit( bool doEdit )
1146   {
1147     if ( m_edit != doEdit ) {
1148       m_edit = doEdit;
1149       if ( m_edit ) {
1150 	if ( !m_editOverlay ) {
1151 	  m_editOverlay = createEditOverlay(*this);
1152 	}
1153 	showOverlay( m_editOverlay );
1154 	m_scene.protect(0);
1155       } else {
1156 	hideOverlay( m_editOverlay );
1157 	m_strokeFixed = false;
1158 	m_strokeSleep = false;
1159 	m_strokeDecor = false;
1160 	if ( m_colour < 2 ) m_colour = 2;
1161 	m_scene.protect();
1162       }
1163     }
1164   }
1165 
mousePoint(SDL_Event ev)1166   Vec2 mousePoint( SDL_Event ev )
1167   {
1168     Vec2 pt( ev.button.x, ev.button.y );
1169     worldToScreen.inverseTransform( pt );
1170     return pt;
1171   }
1172 
handleGameEvent(SDL_Event & ev)1173   bool handleGameEvent( SDL_Event &ev )
1174   {
1175     switch( ev.type ) {
1176     case SDL_QUIT:
1177       m_quit = true;
1178       break;
1179     case SDL_KEYDOWN:
1180       switch ( ev.key.keysym.sym ) {
1181       case SDLK_q:
1182 	m_quit = true;
1183 	break;
1184       case SDLK_SPACE:
1185       case SDLK_KP_ENTER:
1186       case SDLK_RETURN:
1187 	if ( !m_pauseOverlay ) {
1188 	  m_pauseOverlay = createIconOverlay( *this, "pause.png", 50, 50 );
1189 	}
1190 	toggleOverlay( m_pauseOverlay );
1191 	break;
1192       case SDLK_s:
1193       case SDLK_F4:
1194 	save();
1195 	break;
1196       case SDLK_e:
1197       case SDLK_F6:
1198 	edit( !m_edit );
1199 	break;
1200       case SDLK_d:
1201 	//toggleOverlay( m_demoOverlay );
1202 	break;
1203       case SDLK_r:
1204       case SDLK_UP:
1205 	gotoLevel( m_level );
1206 	break;
1207       case SDLK_n:
1208       case SDLK_RIGHT:
1209 	gotoLevel( m_level+1 );
1210 	break;
1211       case SDLK_p:
1212       case SDLK_LEFT:
1213 	gotoLevel( m_level-1 );
1214 	break;
1215       case SDLK_v:
1216 	gotoLevel( m_level, true );
1217 	break;
1218       default:
1219 	break;
1220       }
1221       break;
1222     default:
1223       break;
1224     }
1225     return false;
1226   }
1227 
1228 
handleModEvent(SDL_Event & ev)1229   bool handleModEvent( SDL_Event &ev )
1230   {
1231     static int mod=0;
1232     //printf("mod=%d\n",ev.key.keysym.sym,mod);
1233     switch( ev.type ) {
1234     case SDL_KEYDOWN:
1235       //printf("mod key=%x mod=%d\n",ev.key.keysym.sym,mod);
1236       if ( ev.key.keysym.sym == SDLK_F8 ) {
1237 	mod = 1;  //zoom- == middle (delete)
1238 	return true;
1239       } else if ( ev.key.keysym.sym == SDLK_F7 ) {
1240 	mod = 2;  //zoom+ == right (move)
1241 	return true;
1242       }
1243       break;
1244     case SDL_KEYUP:
1245       if ( ev.key.keysym.sym == SDLK_F7
1246 	   || ev.key.keysym.sym == SDLK_F8 ) {
1247 	mod = 0;
1248 	return true;
1249       }
1250       break;
1251     case SDL_MOUSEBUTTONDOWN:
1252     case SDL_MOUSEBUTTONUP:
1253       if ( ev.button.button == SDL_BUTTON_LEFT && mod != 0 ) {
1254 	ev.button.button = ((mod==1) ? SDL_BUTTON_MIDDLE : SDL_BUTTON_RIGHT);
1255       }
1256       break;
1257     }
1258     return false;
1259   }
1260 
handlePlayEvent(SDL_Event & ev)1261   bool handlePlayEvent( SDL_Event &ev )
1262   {
1263     switch( ev.type ) {
1264     case SDL_MOUSEBUTTONDOWN:
1265       if ( ev.button.button == SDL_BUTTON_LEFT
1266 	   && !m_createStroke ) {
1267 	m_createStroke = m_scene.newStroke( Path()&mousePoint(ev) );
1268 	if ( m_createStroke ) {
1269 	  switch ( m_colour ) {
1270 	  case 0: m_createStroke->setAttribute( Stroke::ATTRIB_TOKEN ); break;
1271 	  case 1: m_createStroke->setAttribute( Stroke::ATTRIB_GOAL ); break;
1272 	  default: m_createStroke->setColour( brushColours[m_colour] ); break;
1273 	  }
1274 	  if ( m_strokeFixed ) {
1275 	    m_createStroke->setAttribute( Stroke::ATTRIB_GROUND );
1276 	  }
1277 	  if ( m_strokeSleep ) {
1278 	    m_createStroke->setAttribute( Stroke::ATTRIB_SLEEPING );
1279 	  }
1280 	  if ( m_strokeDecor ) {
1281 	    m_createStroke->setAttribute( Stroke::ATTRIB_DECOR );
1282 	  }
1283 	}
1284       }
1285       break;
1286     case SDL_MOUSEBUTTONUP:
1287       if ( ev.button.button == SDL_BUTTON_LEFT
1288 	   && m_createStroke ) {
1289 	if ( m_createStroke->numPoints() > 1 ) {
1290 	  m_scene.activate( m_createStroke );
1291 	} else {
1292 	  m_scene.deleteStroke( m_createStroke );
1293 	}
1294 	m_createStroke = NULL;
1295       }
1296       break;
1297     case SDL_MOUSEMOTION:
1298       if ( m_createStroke ) {
1299 	m_createStroke->addPoint( mousePoint(ev) );
1300       }
1301       break;
1302     case SDL_KEYDOWN:
1303       if ( ev.key.keysym.sym == SDLK_ESCAPE ) {
1304 	if ( m_createStroke ) {
1305 	  m_scene.deleteStroke( m_createStroke );
1306 	  m_createStroke = NULL;
1307 	} else {
1308 	  m_scene.deleteStroke( m_scene.strokes().at(m_scene.strokes().size()-1) );
1309 	}
1310 	m_refresh = true;
1311       }
1312       break;
1313     default:
1314       break;
1315     }
1316     return false;
1317   }
1318 
handleEditEvent(SDL_Event & ev)1319   bool handleEditEvent( SDL_Event &ev )
1320   {
1321     //allow move/delete in normal play!!
1322     //if ( !m_edit ) return false;
1323 
1324     switch( ev.type ) {
1325     case SDL_MOUSEBUTTONDOWN:
1326       if ( ev.button.button == SDL_BUTTON_RIGHT
1327 	   && !m_moveStroke ) {
1328 	m_moveStroke = m_scene.strokeAtPoint( mousePoint(ev),
1329 					      SELECT_TOLERANCE );
1330       } else if ( ev.button.button == SDL_BUTTON_MIDDLE ) {
1331 	m_scene.deleteStroke( m_scene.strokeAtPoint( mousePoint(ev),
1332 						     SELECT_TOLERANCE ) );
1333 	m_refresh = true;
1334       }
1335       break;
1336     case SDL_MOUSEBUTTONUP:
1337       if ( ev.button.button == SDL_BUTTON_RIGHT
1338 	   && m_moveStroke ) {
1339 	m_moveStroke = NULL;
1340       }
1341       break;
1342     case SDL_MOUSEMOTION:
1343       if ( m_moveStroke ) {
1344 	m_moveStroke->origin( mousePoint(ev) );
1345       }
1346       break;
1347     default:
1348       break;
1349     }
1350     return false;
1351   }
1352 
run()1353   void run()
1354   {
1355     Overlay *completedOverlay = createNextLevelOverlay(*this);
1356 
1357     m_scene.draw( m_window, FULLSCREEN_RECT );
1358     m_window.update( FULLSCREEN_RECT );
1359 
1360     int iterateCounter = 0;
1361     int lastTick = SDL_GetTicks();
1362     bool isComplete = false;
1363 
1364     while ( !m_quit ) {
1365       for ( int i=0; i<m_overlays.size(); i++ ) {
1366 	m_overlays[i]->onTick( lastTick );
1367       }
1368 
1369       //assumes RENDER_RATE <= ITERATION_RATE
1370       while ( iterateCounter < ITERATION_RATE ) {
1371 	if ( !isPaused() ) {
1372 	  m_scene.step();
1373 	  m_recorder.tick();
1374 	  m_player.tick();
1375 	}
1376 
1377 	SDL_Event ev;
1378 	bool more = true;
1379 	while ( more ) {
1380 	  more = SDL_PollEvent(&ev);
1381 	  if ( m_player.isRunning()
1382 	       && ( ev.type==SDL_MOUSEMOTION ||
1383 		    ev.type==SDL_MOUSEBUTTONDOWN ||
1384 		    ev.type==SDL_MOUSEBUTTONUP ) ) {
1385 	    more = false; //discard
1386 	  }
1387 	  if (!more) {
1388 	    more = m_player.fetchEvent(ev);
1389 	  }
1390 	  if ( more ) {
1391 	    bool handled = false;
1392 	    m_recorder.record( ev );
1393 	    for ( int i=m_overlays.size()-1; i>=0 && !handled; i-- ) {
1394 	      handled = m_overlays[i]->handleEvent(ev);
1395 	    }
1396 	    if ( !handled ) {
1397 	      handled = false
1398 		|| handleModEvent(ev)
1399 		|| handleGameEvent(ev)
1400 		|| handleEditEvent(ev)
1401 		|| handlePlayEvent(ev);
1402 	    }
1403 	  }
1404 	}
1405 	iterateCounter += RENDER_RATE;
1406       }
1407       iterateCounter -= ITERATION_RATE;
1408 
1409 
1410       if ( isComplete && m_edit ) {
1411 	hideOverlay( completedOverlay );
1412 	isComplete = false;
1413       }
1414       if ( m_scene.isCompleted() != isComplete && !m_edit ) {
1415 	isComplete = m_scene.isCompleted();
1416 	if ( isComplete ) {
1417 	  m_player.stop();
1418 	  m_recorder.stop();
1419 	  showOverlay( completedOverlay );
1420 	} else {
1421 	  hideOverlay( completedOverlay );
1422 	}
1423       }
1424 
1425       Rect r = m_scene.dirtyArea();
1426       if ( m_refresh  ) {
1427 	r = FULLSCREEN_RECT;
1428       } else {
1429 	for ( int i=0; i<m_overlays.size(); i++ ) {
1430 	  if ( m_overlays[i]->isDirty() ) {
1431 	    r.expand( m_overlays[i]->dirtyArea() );
1432 	  }
1433 	}
1434       }
1435 
1436       if ( !r.isEmpty() ) {
1437 	m_scene.draw( m_window, r );
1438 	if ( m_fade ) {
1439 	  m_window.fade( r );
1440 	}
1441       }
1442       for ( int i=0; i<m_overlays.size(); i++ ) {
1443 	if ( m_overlays[i]->dirtyArea().intersects(r) ) {
1444 	  m_overlays[i]->draw( m_window );
1445 	}
1446       }
1447 
1448       if ( m_refresh ) {
1449 	m_window.update( FULLSCREEN_RECT );
1450 	m_refresh = false;
1451       } else {
1452 	r.br.x++; r.br.y++;
1453 	//uncomment this to show dirty areas
1454 	//m_window.drawRect( r, 0x00ff00, false );
1455 	m_window.update( r );
1456       }
1457 
1458 
1459       if ( m_os ) {
1460 	m_os->poll();
1461 	for ( char *f = m_os->getLaunchFile(); f; f=m_os->getLaunchFile() ) {
1462 	  if ( f ) {
1463 	    m_levels.addPath( f );
1464 	    int l = m_levels.findLevel( f );
1465 	    if ( l >= 0 ) {
1466 	      gotoLevel( l );
1467 	      m_window.raise();
1468 	    }
1469 	  }
1470 	}
1471       }
1472 
1473       int sleepMs = lastTick + RENDER_INTERVAL -  SDL_GetTicks();
1474       if ( sleepMs > 0 ) {
1475 	SDL_Delay( sleepMs );
1476 	//printf("sleep %dms\n",sleepMs);
1477       } else {
1478 	printf("overrun %dms\n",-sleepMs);
1479       }
1480       lastTick = SDL_GetTicks();
1481     }
1482   }
1483 };
1484 
1485 
init(bool useDummyVideo=false)1486 void init( bool useDummyVideo=false )
1487 {
1488   if ( useDummyVideo ) {
1489     putenv((char*)"SDL_VIDEODRIVER=dummy");
1490   } else {
1491     putenv((char*)"SDL_VIDEO_X11_WMCLASS=NPhysics");
1492   }
1493 
1494   if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER) < 0) {
1495     throw "Couldn't initialize SDL";
1496   }
1497 
1498   if ( mkdir( (string(getenv("HOME"))+"/" USER_BASE_PATH).c_str(),
1499 	      0755)==0 ) {
1500     printf("created user dir\n");
1501   } else if ( errno==EEXIST ){
1502     //printf("user dir ok\n");
1503   } else {
1504     printf("failed to create user dir\n");
1505   }
1506 
1507 }
1508 
npmain(int argc,char ** argv)1509 int npmain(int argc, char** argv)
1510 {
1511   try {
1512     bool rotate = false;
1513     bool thumbnailMode = false;
1514     int width=SCREEN_WIDTH, height=SCREEN_HEIGHT;
1515     Array<const char*> files;
1516 
1517     for ( int i=1; i<argc; i++ ) {
1518       if ( strcmp(argv[i],"-bmp")==0 ) {
1519 	thumbnailMode = true;
1520       } else if ( strcmp(argv[i],"-rotate")==0 ) {
1521        rotate = true;
1522       } else if ( strcmp(argv[i],"-geometry")==0 && i<argc-1) {
1523 	int ww, hh;
1524 	if ( sscanf(argv[i+1],"%dx%d",&ww,&hh) ==2 ) {
1525 	  width = ww;
1526 	  height = hh;
1527 	}
1528 	i++;
1529       } else {
1530 	files.append( argv[i] );
1531       }
1532     }
1533 
1534     if ( thumbnailMode ) {
1535       init(true);
1536       configureScreenTransform( width, height );
1537       for ( int i=0; i<files.size(); i++ ) {
1538 	Scene scene( true );
1539 	if ( scene.load( files[i] ) ) {
1540 	  printf("generating bmp %s\n", files[i]);
1541 	  Canvas temp( width, height );
1542 	  scene.draw( temp, FULLSCREEN_RECT );
1543 	  string bmp( files[i] );
1544 	  bmp += ".bmp";
1545 	  temp.writeBMP( bmp.c_str() );
1546 	}
1547       }
1548     } else {
1549       init();
1550       Game game( width, height );
1551 
1552       if ( files.size() > 0 ) {
1553 	for ( int i=0; i<files.size(); i++ ) {
1554 	  game.levels().addPath( files[i] );
1555 	}
1556       } else {
1557 	struct stat st;
1558 	if ( stat("Game.cpp",&st)==0 ) {
1559 	  game.levels().addPath( "data" );
1560 	} else {
1561 	  game.levels().addPath( DEFAULT_LEVEL_PATH );
1562 	}
1563 	game.levels().addPath( Config::userDataDir().c_str() );
1564       }
1565       game.gotoLevel(0);
1566       game.run();
1567     }
1568   } catch ( const char* e ) {
1569     cout <<"*** CAUGHT: "<<e<<endl;
1570   }
1571   return 0;
1572 }
1573 
1574