2 * This file is part of NumptyPhysics
3 * Copyright (C) 2008 Tim Edmonds
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.
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.
18 #include <SDL/SDL_image.h>
40 const Rect
FULLSCREEN_RECT( 0, 0, CANVAS_WIDTH
-1, CANVAS_HEIGHT
-1 );
41 const Rect
BOUNDS_RECT( -CANVAS_WIDTH
/4, -CANVAS_HEIGHT
,
42 CANVAS_WIDTH
*5/4, CANVAS_HEIGHT
);
44 const int brush_colours
[] = {
54 0x00fa9a, //lightgreen
58 #define NUM_COLOURS (sizeof(brush_colours)/sizeof(brush_colours[0]))
70 ATTRIB_CLASSBITS
= ATTRIB_TOKEN
| ATTRIB_GOAL
74 struct JointDef
: public b2RevoluteJointDef
76 JointDef( b2Body
* b1
, b2Body
* b2
, const b2Vec2
& pt
)
87 struct BoxDef
: public b2BoxDef
89 void init( const Vec2
& p1
, const Vec2
& p2
, int attr
)
91 b2Vec2 barOrigin
= p1
;
93 bar
*= 1.0f
/PIXELS_PER_METREf
;
94 barOrigin
*= 1.0f
/PIXELS_PER_METREf
;;
95 extents
.Set( bar
.Length()/2.0f
, 0.1f
);
96 localPosition
= 0.5f
*bar
+ barOrigin
;
97 localRotation
= vec2Angle( bar
);
99 if ( attr
& ATTRIB_GROUND
) {
101 } else if ( attr
& ATTRIB_GOAL
) {
103 } else if ( attr
& ATTRIB_TOKEN
) {
114 Stroke( const Path
& path
)
117 m_colour
= COLOUR_BLUE
;
119 m_origin
= m_rawPath
.point(0);
120 m_rawPath
.translate( -m_origin
);
124 Stroke( const string
& str
)
127 m_colour
= brush_colours
[2];
129 m_origin
= Vec2(400,240);
131 const char *s
= str
.c_str();
132 while ( *s
&& *s
!=':' && *s
!='\n' ) {
134 case 't': setAttribute( ATTRIB_TOKEN
); break;
135 case 'g': setAttribute( ATTRIB_GOAL
); break;
136 case 'f': setAttribute( ATTRIB_GROUND
); break;
137 case 's': setAttribute( ATTRIB_SLEEPING
); break;
138 case 'd': setAttribute( ATTRIB_DECOR
); break;
140 if ( *s
>= '0' && *s
<= '9' ) {
141 col
= col
*10 + *s
-'0';
147 if ( col
>= 0 && col
< NUM_COLOURS
) {
148 m_colour
= brush_colours
[col
];
152 while ( sscanf( s
, "%f,%f", &x
, &y
)==2) {
153 m_rawPath
.append( Vec2((int)x
,(int)y
) );
154 while ( *s
&& *s
!=' ' && *s
!='\t' ) s
++;
155 while ( *s
==' ' || *s
=='\t' ) s
++;
158 if ( m_rawPath
.size() < 2 ) {
159 throw "invalid stroke def";
161 m_origin
= m_rawPath
.point(0);
162 m_rawPath
.translate( -m_origin
);
163 setAttribute( ATTRIB_DUMMY
);
166 void reset( b2World
* world
=NULL
)
168 if (m_body
&& world
) {
169 world
->DestroyBody( m_body
);
173 m_drawnBbox
.tl
= m_origin
;
174 m_drawnBbox
.br
= m_origin
;
175 m_jointed
[0] = m_jointed
[1] = false;
176 m_shapePath
= m_rawPath
;
185 if ( hasAttribute(ATTRIB_TOKEN
) ) s
<<'t';
186 if ( hasAttribute(ATTRIB_GOAL
) ) s
<<'g';
187 if ( hasAttribute(ATTRIB_GROUND
) ) s
<<'f';
188 if ( hasAttribute(ATTRIB_SLEEPING
) ) s
<<'s';
189 if ( hasAttribute(ATTRIB_DECOR
) ) s
<<'d';
190 for ( int i
=0; i
<NUM_COLOURS
; i
++ ) {
191 if ( m_colour
==brush_colours
[i
] ) s
<<i
;
195 for ( int i
=0; i
<m_xformedPath
.size(); i
++ ) {
196 const Vec2
& p
= m_xformedPath
.point(i
);
197 s
<<' '<< p
.x
<< ',' << p
.y
;
203 void setAttribute( Stroke::Attribute a
)
206 if ( m_attributes
& ATTRIB_TOKEN
) m_colour
= COLOUR_RED
;
207 else if ( m_attributes
& ATTRIB_GOAL
) m_colour
= COLOUR_YELLOW
;
210 bool hasAttribute( Stroke::Attribute a
)
212 return (m_attributes
&a
) != 0;
214 void setColour( int c
)
219 void createBodies( b2World
& world
)
222 if ( hasAttribute( ATTRIB_DECOR
) ){
223 return; //decorators have no physical embodiment
225 int n
= m_shapePath
.numPoints();
229 for ( int i
=1; i
<n
; i
++ ) {
230 boxDef
[i
].init( m_shapePath
.point(i
-1),
231 m_shapePath
.point(i
),
233 bodyDef
.AddShape( &boxDef
[i
] );
235 bodyDef
.position
= m_origin
;
236 bodyDef
.position
*= 1.0f
/PIXELS_PER_METREf
;
237 bodyDef
.userData
= this;
238 if ( m_attributes
& ATTRIB_SLEEPING
) {
239 bodyDef
.isSleeping
= true;
241 m_body
= world
.CreateBody( &bodyDef
);
246 bool maybeCreateJoint( b2World
& world
, Stroke
* other
)
248 if ( (m_attributes
&ATTRIB_CLASSBITS
)
249 != (other
->m_attributes
&ATTRIB_CLASSBITS
) ) {
250 return false; // can only joint matching classes
251 } else if ( hasAttribute(ATTRIB_GROUND
) ) {
252 return true; // no point jointing grounds
253 } else if ( m_body
&& other
->body() ) {
255 int n
= m_xformedPath
.numPoints();
256 for ( int end
=0; end
<2; end
++ ) {
257 if ( !m_jointed
[end
] ) {
258 const Vec2
& p
= m_xformedPath
.point( end
? n
-1 : 0 );
259 if ( other
->distanceTo( p
) <= JOINT_TOLERANCE
) {
260 //printf("jointed end %d d=%f\n",end,other->distanceTo( p ));
262 pw
*= 1.0f
/PIXELS_PER_METREf
;
263 JointDef
j( m_body
, other
->m_body
, pw
);
264 world
.CreateJoint( &j
);
265 m_jointed
[end
] = true;
271 return m_jointed
[0] && m_jointed
[1];
273 return true; ///nothing to do
276 void draw( Canvas
& canvas
)
278 if ( m_hide
< HIDE_STEPS
) {
280 canvas
.drawPath( m_xformedPath
, canvas
.makeColour(m_colour
), true );
283 m_drawnBbox
= m_xformBbox
;
286 void addPoint( const Vec2
& pp
)
288 Vec2 p
= pp
; p
-= m_origin
;
289 if ( p
== m_rawPath
.point( m_rawPath
.numPoints()-1 ) ) {
291 m_rawPath
.append( p
);
296 void origin( const Vec2
& p
)
301 pw
*= 1.0f
/PIXELS_PER_METREf
;
302 m_body
->SetOriginPosition( pw
, m_body
->GetRotation() );
307 b2Body
* body() { return m_body
; }
309 float distanceTo( const Vec2
& pt
)
311 float best
= 100000.0;
313 for ( int i
=1; i
<m_xformedPath
.numPoints(); i
++ ) {
314 Segment
s( m_xformedPath
.point(i
-1), m_xformedPath
.point(i
) );
315 float d
= s
.distanceTo( pt
);
316 //printf(" d[%d]=%f %d,%d\n",i,d,m_rawPath.point(i-1).x,m_rawPath.point(i-1).y);
337 return !m_drawn
|| transform();
346 // stash the body where no-one will find it
347 m_body
->SetCenterPosition( b2Vec2(0.0f
,CANVAS_HEIGHTf
*2.0f
), 0.0f
);
348 m_body
->SetLinearVelocity( b2Vec2(0.0f
,0.0f
) );
349 m_body
->SetAngularVelocity( 0.0f
);
356 return m_hide
>= HIDE_STEPS
;
361 return m_rawPath
.numPoints();
365 static float vec2Angle( b2Vec2 v
)
367 float a
=atan(v
.y
/v
.x
);
368 return v
.y
>0?a
:a
+b2_pi
;
373 float thresh
= 0.1*SIMPLIFY_THRESHOLDf
;
374 m_rawPath
.simplify( thresh
);
375 m_shapePath
= m_rawPath
;
377 while ( m_shapePath
.numPoints() > MULTI_VERTEX_LIMIT
) {
378 thresh
+= SIMPLIFY_THRESHOLDf
;
379 m_shapePath
.simplify( thresh
);
385 // distinguish between xformed raw and shape path as needed
387 if ( m_hide
< HIDE_STEPS
) {
388 //printf("hide %d\n",m_hide);
389 Vec2 o
= m_xformBbox
.centroid();
391 m_xformedPath
.scale( 0.99 );
393 m_xformBbox
= m_xformedPath
.bbox();
397 } else if ( m_body
) {
398 if ( hasAttribute( ATTRIB_DECOR
) ) {
399 return false; // decor never moves
400 } else if ( hasAttribute( ATTRIB_GROUND
)
401 && m_xformAngle
== m_body
->GetRotation() ) {
402 return false; // ground strokes never move.
403 } else if ( m_xformAngle
!= m_body
->GetRotation()
404 || ! (m_xformPos
== m_body
->GetOriginPosition()) ) {
405 //printf("transform stroke - rot or pos\n");
406 b2Mat22
rot( m_body
->GetRotation() );
407 b2Vec2 orig
= PIXELS_PER_METREf
* m_body
->GetOriginPosition();
408 m_xformedPath
= m_rawPath
;
409 m_xformedPath
.rotate( rot
);
410 m_xformedPath
.translate( Vec2(orig
) );
411 m_xformAngle
= m_body
->GetRotation();
412 m_xformPos
= m_body
->GetOriginPosition();
413 m_xformBbox
= m_xformedPath
.bbox();
414 } else if ( ! (m_xformPos
== m_body
->GetOriginPosition() ) ) {
415 //NOT WORKING printf("transform stroke - pos\n");
416 b2Vec2 move
= m_body
->GetOriginPosition() - m_xformPos
;
417 move
*= PIXELS_PER_METREf
;
418 m_xformedPath
.translate( Vec2(move
) );
419 m_xformPos
= m_body
->GetOriginPosition();
420 m_xformBbox
= m_xformedPath
.bbox();//TODO translate instead of recalc
422 //printf("transform none\n");
426 //printf("transform no body\n");
427 m_xformedPath
= m_rawPath
;
428 m_xformedPath
.translate( m_origin
);
429 m_xformBbox
= m_xformedPath
.bbox();
456 Scene( bool noWorld
=false )
463 worldAABB
.minVertex
.Set(-100.0f
, -100.0f
);
464 worldAABB
.maxVertex
.Set(100.0f
, 100.0f
);
466 b2Vec2
gravity(0.0f
, 10.0f
);
468 m_world
= new b2World(worldAABB
, gravity
, doSleep
);
480 return m_strokes
.size();
483 Stroke
* newStroke( const Path
& p
) {
484 Stroke
*s
= new Stroke(p
);
485 m_strokes
.append( s
);
489 void deleteStroke( Stroke
*s
) {
490 //printf("delete stroke %p\n",s);
492 int i
= m_strokes
.indexOf(s
);
493 if ( i
>= m_protect
) {
495 m_strokes
.erase( m_strokes
.indexOf(s
) );
501 void activate( Stroke
*s
)
503 //printf("activate stroke\n");
504 s
->createBodies( *m_world
);
510 for ( int i
=0; i
< m_strokes
.size(); i
++ ) {
511 m_strokes
[i
]->createBodies( *m_world
);
513 for ( int i
=0; i
< m_strokes
.size(); i
++ ) {
514 createJoints( m_strokes
[i
] );
518 void createJoints( Stroke
*s
)
520 for ( int j
=m_strokes
.size()-1; j
>=0; j
-- ) {
521 if ( s
!= m_strokes
[j
] ) {
522 //printf("try join to %d\n",j);
523 s
->maybeCreateJoint( *m_world
, m_strokes
[j
] );
524 m_strokes
[j
]->maybeCreateJoint( *m_world
, s
);
531 m_world
->Step( ITERATION_TIMESTEPf
, SOLVER_ITERATIONS
);
533 // check for completion
534 for (b2Contact
* c
= m_world
->GetContactList(); c
; c
= c
->GetNext()) {
535 if (c
->GetManifoldCount() > 0) {
536 Stroke
* s1
= (Stroke
*)c
->GetShape1()->GetBody()->GetUserData();
537 Stroke
* s2
= (Stroke
*)c
->GetShape2()->GetBody()->GetUserData();
539 if ( s2
->hasAttribute(Stroke::ATTRIB_TOKEN
) ) {
542 if ( s1
->hasAttribute(Stroke::ATTRIB_TOKEN
)
543 && s2
->hasAttribute(Stroke::ATTRIB_GOAL
) ) {
545 //printf("SUCCESS!! level complete\n");
551 // check for token respawn
552 for ( int i
=0; i
< m_strokes
.size(); i
++ ) {
553 if ( m_strokes
[i
]->hasAttribute( Stroke::ATTRIB_TOKEN
)
554 && !BOUNDS_RECT
.intersects( m_strokes
[i
]->lastDrawnBbox() ) ) {
555 printf("RESPAWN token %d\n",i
);
556 reset( m_strokes
[i
] );
557 activate( m_strokes
[i
] );
564 for ( int i
=0; i
< m_strokes
.size(); i
++ ) {
565 if ( m_strokes
[i
]->hasAttribute( Stroke::ATTRIB_GOAL
)
566 && !m_strokes
[i
]->hidden() ) {
570 //printf("completed!\n");
576 Rect
r(0,0,0,0),temp
;
578 for ( int i
=0; i
<m_strokes
.size(); i
++ ) {
579 if ( m_strokes
[i
]->isDirty() ) {
580 // acumulate new areas to draw
581 temp
= m_strokes
[i
]->bbox();
582 if ( !temp
.isEmpty() ) {
586 r
.expand( m_strokes
[i
]->bbox() );
588 // plus prev areas to erase
589 r
.expand( m_strokes
[i
]->lastDrawnBbox() );
594 if ( !r
.isEmpty() ) {
595 // expand to allow for thick lines
602 void draw( Canvas
& canvas
, const Rect
& area
)
605 canvas
.setBackground( m_bgImage
);
607 canvas
.setBackground( 0 );
609 canvas
.clear( area
);
610 Rect clipArea
= area
;
615 for ( int i
=0; i
<m_strokes
.size(); i
++ ) {
616 if ( area
.intersects( m_strokes
[i
]->bbox() ) ) {
617 m_strokes
[i
]->draw( canvas
);
620 //canvas.drawRect( area, 0xffff0000, false );
623 void reset( Stroke
* s
=NULL
)
625 for ( int i
=0; i
<m_strokes
.size(); i
++ ) {
626 if (s
==NULL
|| s
==m_strokes
[i
]) {
627 m_strokes
[i
]->reset(m_world
);
632 Stroke
* strokeAtPoint( const Vec2 pt
, float max
)
635 for ( int i
=0; i
<m_strokes
.size(); i
++ ) {
636 float d
= m_strokes
[i
]->distanceTo( pt
);
637 //printf("stroke %d dist %f\n",i,d);
649 while ( m_strokes
.size() ) {
654 while ( m_world
->GetBodyList() ) {
655 printf("body left over %p\n",m_world
->GetBodyList());
656 m_world
->DestroyBody( m_world
->GetBodyList() );
658 //step is required to actually destroy bodies and joints
659 m_world
->Step( ITERATION_TIMESTEPf
, SOLVER_ITERATIONS
);
663 bool load( const string
& file
)
666 if ( g_bgImage
==NULL
) {
667 g_bgImage
= new Image("paper.jpg");
669 m_bgImage
= g_bgImage
;
671 ifstream
i( file
.c_str(), ios::in
);
672 while ( i
.is_open() && !i
.eof() ) {
674 //cout << "read: " << line << endl;;
676 case 'T': m_title
= line
.substr(line
.find(':')+1); break;
677 case 'B': m_bg
= line
.substr(line
.find(':')+1); break;
678 case 'A': m_author
= line
.substr(line
.find(':')+1); break;
679 case 'S': m_strokes
.append( new Stroke(line
) ); break;
687 void protect( int n
=-1 )
689 m_protect
= (n
==-1 ? m_strokes
.size() : n
);
692 bool save( const std::string
& file
)
694 printf("saving to %s\n",file
.c_str());
695 ofstream
o( file
.c_str(), ios::out
);
697 o
<< "Title: "<<m_title
<<endl
;
698 o
<< "Author: "<<m_author
<<endl
;
699 o
<< "Background: "<<m_bg
<<endl
;
700 for ( int i
=0; i
<m_strokes
.size(); i
++ ) {
701 o
<< m_strokes
[i
]->asString();
710 Array
<Stroke
*>& strokes()
717 Array
<Stroke
*> m_strokes
;
718 string m_title
, m_author
, m_bg
;
720 static Image
*g_bgImage
;
724 Image
*Scene::g_bgImage
= NULL
;
729 GameParams() : m_quit(false),
734 m_strokeFixed( false ),
735 m_strokeSleep( false ),
736 m_strokeDecor( false ),
740 virtual ~GameParams() {}
741 virtual bool save( char *file
=NULL
) { return false; }
742 virtual bool send() { return false; }
743 virtual void gotoLevel( int l
) {}
760 Overlay( GameParams
& game
, int x
=10, int y
=10 )
767 virtual ~Overlay() {}
771 return Rect(m_x
,m_y
,m_x
+m_canvas
->width()-1,m_y
+m_canvas
->height()-1);
774 virtual void onShow() {}
775 virtual void onHide() {}
776 virtual void onTick( int tick
) {}
778 virtual void draw( Canvas
& screen
)
781 screen
.drawImage( m_canvas
, m_x
, m_y
);
785 virtual bool handleEvent( SDL_Event
& ev
)
788 case SDL_MOUSEBUTTONDOWN
:
789 //printf("overlay click\n");
790 if ( ev
.button
.button
== SDL_BUTTON_LEFT
791 && ev
.button
.x
>= m_x
&& ev
.button
.x
<= m_x
+ m_canvas
->width()
792 && ev
.button
.y
>= m_y
&& ev
.button
.y
<= m_y
+ m_canvas
->height() ) {
793 m_orgx
= ev
.button
.x
;
794 m_orgy
= ev
.button
.y
;
795 m_prevx
= ev
.button
.x
;
796 m_prevy
= ev
.button
.y
;
801 case SDL_MOUSEBUTTONUP
:
802 if ( ev
.button
.button
== SDL_BUTTON_LEFT
) {
805 } else if ( ABS(ev
.button
.x
-m_orgx
) < CLICK_TOLERANCE
806 && ABS(ev
.button
.y
-m_orgy
) < CLICK_TOLERANCE
){
807 onClick( m_orgx
-m_x
, m_orgy
-m_y
);
809 m_buttonDown
= false;
812 case SDL_MOUSEMOTION
:
815 && ( ABS(ev
.button
.x
-m_orgx
) >= CLICK_TOLERANCE
816 || ABS(ev
.button
.y
-m_orgy
) >= CLICK_TOLERANCE
) ) {
820 m_x
+= ev
.button
.x
- m_prevx
;
821 m_y
+= ev
.button
.y
- m_prevy
;
822 m_prevx
= ev
.button
.x
;
823 m_prevy
= ev
.button
.y
;
824 m_game
.m_refresh
= true;
833 virtual bool onClick( int x
, int y
) { return false; }
841 int m_prevx
, m_prevy
;
847 class IconOverlay
: public Overlay
850 IconOverlay(GameParams
& game
, const char* file
, int x
=100,int y
=20)
853 m_canvas
= new Image(file
);
858 class NextLevelOverlay
: public IconOverlay
861 NextLevelOverlay( GameParams
& game
)
862 : IconOverlay(game
,"next.png",CANVAS_WIDTH
/2-200,100),
870 virtual void onShow()
872 m_selectedLevel
= m_game
.m_level
+1;
874 virtual void draw( Canvas
& screen
)
877 IconOverlay::draw( screen
);
879 screen
.drawImage( m_icon
, m_x
+100, m_y
+75 );
882 virtual bool onClick( int x
, int y
)
885 m_game
.gotoLevel( m_selectedLevel
);
886 } else if ( x
> 300 ) {
888 printf("NextLevel++ = %d\n",m_selectedLevel
);
889 } else if ( x
< 100 && m_selectedLevel
> 0 ) {
891 printf("NextLevel-- = %d\n",m_selectedLevel
);
898 if ( m_icon
==NULL
|| m_levelIcon
!= m_selectedLevel
) {
899 printf("new thumbnail required\n");
902 if ( m_selectedLevel
< m_game
.m_levels
.numLevels() ) {
904 if ( scene
.load( m_game
.m_levels
.levelFile(m_selectedLevel
) ) ) {
905 printf("generating thumbnail %s\n",
906 m_game
.m_levels
.levelFile(m_selectedLevel
).c_str());
907 Canvas
temp( CANVAS_WIDTH
, CANVAS_HEIGHT
);
908 scene
.draw( temp
, FULLSCREEN_RECT
);
909 m_icon
= temp
.scale( ICON_SCALE_FACTOR
);
910 printf("generating thumbnail %s done\n",
911 m_game
.m_levels
.levelFile(m_selectedLevel
).c_str());
913 printf("failed to gen scene thumbnail %s\n",
914 m_game
.m_levels
.levelFile(m_selectedLevel
).c_str());
917 m_icon
= new Image("theend.png");
918 m_caption
= "no more levels!";
919 m_selectedLevel
= m_game
.m_levels
.numLevels();
921 m_levelIcon
= m_selectedLevel
;
932 class DemoOverlay
: public IconOverlay
946 DemoOverlay( GameParams
& game
)
947 : IconOverlay( game
, "pause.png" ),
952 virtual void onTick( int tick
)
956 m_game
.save( DEMO_TEMP_FILE
);
957 //todo reset motion: m_game.load( DEMO_TEMP_FILE );
966 while ( m_playidx
< m_log
.size()
967 && m_log
[m_playidx
].t
< tick
-m_t0
) {
968 SDL_PushEvent( &m_log
[m_playidx
++].e
);
973 virtual bool onClick( int x
, int y
)
985 virtual void draw( Canvas
& screen
)
987 IconOverlay::draw( screen
);
995 virtual bool handleEvent( SDL_Event
& ev
)
997 if ( IconOverlay::handleEvent(ev
) ) {
999 } else if ( m_state
== RUNNING
) {
1000 EvEntry e
= { m_tnow
- m_t0
, ev
};
1010 Array
<EvEntry
> m_log
;
1014 class EditOverlay
: public IconOverlay
1016 int m_saving
, m_sending
;
1018 EditOverlay( GameParams
& game
)
1019 : IconOverlay(game
,"edit.png"),
1020 m_saving(0), m_sending(0)
1023 for ( int i
=0; i
<NUM_COLOURS
; i
++ ) {
1024 m_canvas
->drawRect( pos(i
), m_canvas
->makeColour(brush_colours
[i
]), true );
1029 int c
= i
%3, r
= i
/3;
1030 return Rect( c
*28+13, r
*28+33, c
*28+33, r
*28+53 );
1032 int index( int x
, int y
)
1036 if ( r
<0 || c
<0 || c
>2 ) return -1;
1039 void outline( Canvas
& screen
, int i
, int c
)
1041 Rect r
= pos(i
) + Vec2(m_x
,m_y
);
1042 r
.tl
.x
-=2; r
.tl
.y
-=2;
1043 r
.br
.x
+=2; r
.br
.y
+=2;
1044 screen
.drawRect( r
, c
, false );
1046 virtual void draw( Canvas
& screen
)
1048 IconOverlay::draw( screen
);
1049 outline( screen
, m_game
.m_colour
, 0 );
1050 if ( m_game
.m_strokeFixed
) outline( screen
, 12, 0 );
1051 if ( m_game
.m_strokeSleep
) outline( screen
, 13, 0 );
1052 if ( m_game
.m_strokeDecor
) outline( screen
, 14, 0 );
1053 if ( m_sending
) outline( screen
, 16, screen
.makeColour((m_sending
--)<<9) );
1054 if ( m_saving
) outline( screen
, 17, screen
.makeColour((m_saving
--)<<9) );
1056 virtual bool onClick( int x
, int y
)
1060 case -1: return false;
1061 case 12: m_game
.m_strokeFixed
= ! m_game
.m_strokeFixed
; break;
1062 case 13: m_game
.m_strokeSleep
= ! m_game
.m_strokeSleep
; break;
1063 case 14: m_game
.m_strokeDecor
= ! m_game
.m_strokeDecor
; break;
1064 case 16: if ( m_game
.send() ) m_sending
=10; break;
1065 case 17: if ( m_game
.save() ) m_saving
=10; break;
1066 default: if (i
<NUM_COLOURS
) m_game
.m_colour
= i
; break;
1073 class Game
: public GameParams
1076 Stroke
*m_createStroke
;
1077 Stroke
*m_moveStroke
;
1078 Array
<Overlay
*> m_overlays
;
1080 IconOverlay m_pauseOverlay
;
1081 EditOverlay m_editOverlay
;
1086 Game( int argc
, const char** argv
)
1087 : m_createStroke(NULL
),
1089 m_window(800,480,"Numpty Physics","NPhysics"),
1090 m_pauseOverlay( *this, "pause.png",50,50),
1091 m_editOverlay( *this)
1094 FILE *f
= fopen("Game.cpp","rt");
1096 m_levels
.addPath( "." );
1099 m_levels
.addPath( DEFAULT_LEVEL_PATH
);
1101 std::string
u( getenv("HOME") );
1102 m_levels
.addPath( (u
+ "/" USER_LEVEL_PATH
).c_str() );
1104 for ( int i
=1;i
<argc
;i
++ ) {
1105 m_levels
.addPath( argv
[i
] );
1111 void gotoLevel( int l
)
1113 if ( l
>= 0 && l
< m_levels
.numLevels() ) {
1114 printf("loading from %s\n",m_levels
.levelFile(l
).c_str());
1115 m_scene
.load( m_levels
.levelFile(l
).c_str() );
1116 m_scene
.activateAll();
1118 m_window
.setSubName( m_levels
.levelFile(l
).c_str() );
1126 bool save( char *file
=NULL
)
1132 p
= getenv("HOME"); p
+="/"USER_BASE_PATH
"/L99_saved.nph";
1134 if ( m_scene
.save( p
) ) {
1135 m_levels
.addPath( p
.c_str() );
1136 int l
= m_levels
.findLevel( p
.c_str() );
1139 m_window
.setSubName( p
.c_str() );
1148 return save( SEND_TEMP_FILE
)
1150 && m_hildon
.sendFile( "nobody@", SEND_TEMP_FILE
)
1155 void setTool( int t
)
1160 void editMode( bool set
)
1165 void showOverlay( Overlay
& o
)
1167 printf("show overlay\n");
1168 m_overlays
.append( &o
);
1172 void hideOverlay( Overlay
& o
)
1174 printf("hide overlay\n");
1176 m_overlays
.erase( m_overlays
.indexOf(&o
) );
1180 void pause( bool doPause
)
1182 if ( m_pause
!= doPause
) {
1185 showOverlay( m_pauseOverlay
);
1187 hideOverlay( m_pauseOverlay
);
1192 void edit( bool doEdit
)
1194 if ( m_edit
!= doEdit
) {
1197 showOverlay( m_editOverlay
);
1200 hideOverlay( m_editOverlay
);
1201 m_strokeFixed
= false;
1202 m_strokeSleep
= false;
1203 m_strokeDecor
= false;
1204 if ( m_colour
< 2 ) m_colour
= 2;
1210 Vec2
mousePoint( SDL_Event ev
)
1212 return Vec2( ev
.button
.x
, ev
.button
.y
);
1215 bool handleGameEvent( SDL_Event
&ev
)
1222 switch ( ev
.key
.keysym
.sym
) {
1241 gotoLevel( m_level
);
1245 gotoLevel( m_level
+1 );
1249 gotoLevel( m_level
-1 );
1264 bool handleModEvent( SDL_Event
&ev
)
1267 //printf("mod=%d\n",ev.key.keysym.sym,mod);
1270 //printf("mod key=%x mod=%d\n",ev.key.keysym.sym,mod);
1271 if ( ev
.key
.keysym
.sym
== SDLK_F8
) {
1272 mod
= 1; //zoom- == middle (delete)
1274 } else if ( ev
.key
.keysym
.sym
== SDLK_F7
) {
1275 mod
= 2; //zoom+ == right (move)
1280 if ( ev
.key
.keysym
.sym
== SDLK_F7
1281 || ev
.key
.keysym
.sym
== SDLK_F8
) {
1286 case SDL_MOUSEBUTTONDOWN
:
1287 case SDL_MOUSEBUTTONUP
:
1288 if ( ev
.button
.button
== SDL_BUTTON_LEFT
&& mod
!= 0 ) {
1289 ev
.button
.button
= ((mod
==1) ? SDL_BUTTON_MIDDLE
: SDL_BUTTON_RIGHT
);
1296 bool handlePlayEvent( SDL_Event
&ev
)
1299 case SDL_MOUSEBUTTONDOWN
:
1300 if ( ev
.button
.button
== SDL_BUTTON_LEFT
1301 && !m_createStroke
) {
1302 m_createStroke
= m_scene
.newStroke( Path()&mousePoint(ev
) );
1303 if ( m_createStroke
) {
1304 switch ( m_colour
) {
1305 case 0: m_createStroke
->setAttribute( Stroke::ATTRIB_TOKEN
); break;
1306 case 1: m_createStroke
->setAttribute( Stroke::ATTRIB_GOAL
); break;
1307 default: m_createStroke
->setColour( brush_colours
[m_colour
] ); break;
1309 if ( m_strokeFixed
) {
1310 m_createStroke
->setAttribute( Stroke::ATTRIB_GROUND
);
1312 if ( m_strokeSleep
) {
1313 m_createStroke
->setAttribute( Stroke::ATTRIB_SLEEPING
);
1315 if ( m_strokeDecor
) {
1316 m_createStroke
->setAttribute( Stroke::ATTRIB_DECOR
);
1321 case SDL_MOUSEBUTTONUP
:
1322 if ( ev
.button
.button
== SDL_BUTTON_LEFT
1323 && m_createStroke
) {
1324 if ( m_createStroke
->numPoints() > 1 ) {
1325 m_scene
.activate( m_createStroke
);
1327 m_scene
.deleteStroke( m_createStroke
);
1329 m_createStroke
= NULL
;
1332 case SDL_MOUSEMOTION
:
1333 if ( m_createStroke
) {
1334 m_createStroke
->addPoint( mousePoint(ev
) );
1338 if ( ev
.key
.keysym
.sym
== SDLK_ESCAPE
) {
1339 if ( m_createStroke
) {
1340 m_scene
.deleteStroke( m_createStroke
);
1341 m_createStroke
= NULL
;
1343 m_scene
.deleteStroke( m_scene
.strokes().at(m_scene
.strokes().size()-1) );
1354 bool handleEditEvent( SDL_Event
&ev
)
1356 if ( !m_edit
) return false;
1359 case SDL_MOUSEBUTTONDOWN
:
1360 if ( ev
.button
.button
== SDL_BUTTON_RIGHT
1361 && !m_moveStroke
) {
1362 m_moveStroke
= m_scene
.strokeAtPoint( mousePoint(ev
),
1364 } else if ( ev
.button
.button
== SDL_BUTTON_MIDDLE
) {
1365 m_scene
.deleteStroke( m_scene
.strokeAtPoint( mousePoint(ev
),
1366 SELECT_TOLERANCE
) );
1370 case SDL_MOUSEBUTTONUP
:
1371 if ( ev
.button
.button
== SDL_BUTTON_RIGHT
1373 m_moveStroke
= NULL
;
1376 case SDL_MOUSEMOTION
:
1377 if ( m_moveStroke
) {
1378 m_moveStroke
->origin( mousePoint(ev
) );
1389 NextLevelOverlay
completedOverlay(*this);
1391 m_scene
.draw( m_window
, FULLSCREEN_RECT
);
1392 m_window
.update( FULLSCREEN_RECT
);
1394 int iterateCounter
= 0;
1395 int lastTick
= SDL_GetTicks();
1396 bool isComplete
= false;
1399 for ( int i
=0; i
<m_overlays
.size(); i
++ ) {
1400 m_overlays
[i
]->onTick( lastTick
);
1404 while ( SDL_PollEvent(&ev
) ) {
1405 bool handled
= false;
1406 for ( int i
=m_overlays
.size()-1; i
>=0 && !handled
; i
-- ) {
1407 handled
= m_overlays
[i
]->handleEvent(ev
);
1411 || handleModEvent(ev
)
1412 || handleGameEvent(ev
)
1413 || handleEditEvent(ev
)
1414 || handlePlayEvent(ev
);
1418 if ( isComplete
&& m_edit
) {
1419 hideOverlay( completedOverlay
);
1422 if ( m_scene
.isCompleted() != isComplete
&& !m_edit
) {
1423 isComplete
= m_scene
.isCompleted();
1425 showOverlay( completedOverlay
);
1427 hideOverlay( completedOverlay
);
1431 Rect r
= m_scene
.dirtyArea();
1432 if ( m_refresh
|| isComplete
) {
1433 r
= FULLSCREEN_RECT
;
1436 if ( !r
.isEmpty() ) {
1437 m_scene
.draw( m_window
, r
);
1440 for ( int i
=0; i
<m_overlays
.size(); i
++ ) {
1441 m_overlays
[i
]->draw( m_window
);
1442 r
.expand( m_overlays
[i
]->dirtyArea() );
1447 m_window
.update( FULLSCREEN_RECT
);
1451 m_window
.update( r
);
1456 //assumes RENDER_RATE <= ITERATION_RATE
1457 while ( iterateCounter
< ITERATION_RATE
) {
1459 iterateCounter
+= RENDER_RATE
;
1461 iterateCounter
-= ITERATION_RATE
;
1466 for ( char *f
= m_hildon
.getFile(); f
; f
=m_hildon
.getFile() ) {
1468 m_levels
.addPath( f
);
1469 int l
= m_levels
.findLevel( f
);
1477 int sleepMs
= lastTick
+ RENDER_INTERVAL
- SDL_GetTicks();
1478 if ( sleepMs
> 0 ) {
1479 SDL_Delay( sleepMs
);
1480 //printf("sleep %dms\n",sleepMs);
1482 printf("overrun %dms\n",-sleepMs
);
1484 lastTick
= SDL_GetTicks();
1490 int main(int argc
, char** argv
)
1493 putenv("SDL_VIDEO_X11_WMCLASS=NPhysics");
1494 if (SDL_Init(SDL_INIT_VIDEO
|SDL_INIT_TIMER
) < 0) {
1495 throw "Couldn't initialize SDL";
1498 if ( mkdir( (string(getenv("HOME"))+"/"USER_BASE_PATH
).c_str(),
1500 printf("created user dir\n");
1501 } else if ( errno
==EEXIST
){
1502 printf("user dir ok\n");
1504 printf("failed to create user dir\n");
1508 if ( argc
> 2 && strcmp(argv
[1],"-bmp")==0 ) {
1509 for ( int i
=2; i
<argc
; i
++ ) {
1510 Scene
scene( true );
1511 if ( scene
.load( argv
[i
] ) ) {
1512 printf("generating bmp %s\n", argv
[i
]);
1513 Canvas
temp( CANVAS_WIDTH
, CANVAS_HEIGHT
);
1514 scene
.draw( temp
, FULLSCREEN_RECT
);
1515 string
bmp( argv
[i
] );
1517 temp
.writeBMP( bmp
.c_str() );
1521 Game
game( argc
, (const char**)argv
);
1524 } catch ( const char* e
) {
1525 cout
<<"*** CAUGHT: "<<e
<<endl
;