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.
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) ) {
35 m_rot
.Set( rotation
);
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();
46 Transform
worldToScreen( 0.5f
, M_PI
/2, Vec2(240,0) );
48 void configureScreenTransform( int w
, int 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) );
58 if ( h
> w
) { //portrait
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
);
68 worldToScreen
.set( scaleh
, rot
, tr
);
79 struct JointDef
: public b2RevoluteJointDef
81 JointDef( b2Body
* b1
, b2Body
* b2
, const b2Vec2
& pt
)
83 Initialize( b1
, b2
, pt
);
84 maxMotorTorque
= 10.0f
;
90 struct BoxDef
: public b2PolygonDef
92 void init( const Vec2
& p1
, const Vec2
& p2
, int attr
)
94 b2Vec2 barOrigin
= 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 ));
103 if ( attr
& ATTRIB_GROUND
) {
105 } else if ( attr
& ATTRIB_GOAL
) {
107 } else if ( attr
& ATTRIB_TOKEN
) {
118 Stroke( const Path
& path
, int attributes
)
121 m_colour
= brushColours
[DEFAULT_BRUSH
];
122 m_attributes
= attributes
;
123 m_origin
= m_rawPath
.point(0);
124 m_rawPath
.translate( -m_origin
);
129 Stroke( const std::string
& str
)
133 m_colour
= brushColours
[DEFAULT_BRUSH
];
135 m_origin
= Vec2(400,240);
137 const char *s
= str
.c_str();
138 while ( *s
&& *s
!=':' && *s
!='\n' ) {
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;
148 if ( *s
>= '0' && *s
<= '9' ) {
149 col
= col
*10 + *s
-'0';
155 if ( col
>= 0 && col
< NUM_BRUSHES
) {
156 m_colour
= brushColours
[col
];
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
);
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
;
184 std::string
asString()
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
;
198 for ( int i
=0; i
<m_xformedPath
.size(); i
++ ) {
199 const Vec2
& p
= m_xformedPath
.point(i
);
200 s
<<' '<< p
.x
<< ',' << p
.y
;
206 void setAttribute( Attribute 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
)
223 void clearAttribute( Attribute a
)
228 bool hasAttribute( Attribute a
)
230 return (m_attributes
&a
) != 0;
232 void setColour( int c
)
237 void createBodies( b2World
& world
)
240 if ( hasAttribute( ATTRIB_DECOR
) ){
241 return; //decorators have no physical embodiment
243 int n
= m_shapePath
.numPoints();
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
++ ) {
255 boxDef
.init( m_shapePath
.point(i
-1),
256 m_shapePath
.point(i
),
258 m_body
->CreateShape( &boxDef
);
260 m_body
->SetMassFromShapes();
274 void setDefaultMass()
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() ) {
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 ));
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;
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);
316 canvas
.drawPath( m_screenPath
, canvas
.makeColour(m_colour
), thick
);
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");
338 if ( p
== m_rawPath
.point( m_rawPath
.numPoints()-1 ) ) {
340 m_rawPath
.append( p
);
345 void origin( const Vec2
& p
)
350 pw
*= 1.0f
/PIXELS_PER_METREf
;
351 m_body
->SetXForm( pw
, m_body
->GetAngle() );
356 b2Body
* body() { return m_body
; }
358 float32
distanceTo( const Vec2
& pt
)
360 float32 best
= 100000.0;
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);
386 return m_xformedPath
.bbox();
391 return (!m_drawn
|| transform()) && !hasAttribute(ATTRIB_DELETED
);
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
);
410 return m_hide
>= HIDE_STEPS
;
415 return m_rawPath
.numPoints();
419 static float32
vec2Angle( b2Vec2 v
)
421 return b2Atan2(v
.y
, v
.x
);
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
);
438 // distinguish between xformed raw and shape path as needed
440 if ( m_hide
< HIDE_STEPS
) {
441 //printf("hide %d\n",m_hide);
442 Vec2 o
= m_screenBbox
.centroid();
444 m_screenPath
.scale( 0.99 );
446 m_screenBbox
= m_screenPath
.bbox();
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();
469 //printf("transform none\n");
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();
491 float32 m_xformAngle
;
501 Scene::Scene( bool noWorld
)
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
);
513 m_world
= new b2World(worldAABB
, gravity
, doSleep
);
514 m_world
->SetContactListener( this );
527 Stroke
* Scene::newStroke( const Path
& p
, int colour
, int attribs
) {
528 Stroke
*s
= new Stroke(p
, attribs
);
530 case 0: s
->setAttribute( ATTRIB_TOKEN
); break;
531 case 1: s
->setAttribute( ATTRIB_GOAL
); break;
532 default: s
->setColour( brushColours
[colour
] ); break;
535 m_strokes
.append( s
);
539 bool Scene::deleteStroke( Stroke
*s
) {
541 int i
= m_strokes
.indexOf(s
);
542 if ( i
>= m_protect
) {
544 m_strokes
.erase( m_strokes
.indexOf(s
) );
552 void Scene::extendStroke( Stroke
* s
, const Vec2
& pt
)
559 void Scene::moveStroke( Stroke
* s
, const Vec2
& origin
)
562 int i
= m_strokes
.indexOf(s
);
563 if ( i
>= m_protect
) {
569 void Scene::setNoMass(Stroke
* s
)
574 void Scene::setDefaultMass(Stroke
* s
)
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
);
594 void Scene::activateAll()
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
);
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();
652 if ( s2
->hasAttribute(ATTRIB_TOKEN
) ) {
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)) {
672 else if (m_num_goal
> 1)
674 int left
[m_num_player
];
675 for(int n
=0; n
<m_num_player
; n
++) {
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
++) {
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
;
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() ) {
714 r
.expand( m_strokes
[i
]->screenBbox() );
716 // plus prev areas to erase
717 r
.expand( m_strokes
[i
]->lastDrawnBbox() );
722 if ( !r
.isEmpty() ) {
723 // expand to allow for thick lines
730 void Scene::draw( Canvas
& canvas
, const Rect
& area
)
733 canvas
.setBackground( m_bgImage
);
735 canvas
.setBackground( 0 );
737 canvas
.clear( area
);
738 Rect clipArea
= area
;
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
)
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);
777 while ( m_strokes
.size() ) {
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
)
790 if ( sscanf( s
.c_str(), "%f,%f", &x
, &y
)==2) {
793 g
*= PIXELS_PER_METREf
/GRAVITY_FUDGEf
;
794 m_world
->SetGravity( g
);
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
);
808 bool Scene::load( const std::string
& file
)
810 std::ifstream
in( file
.c_str(), std::ios::in
);
814 bool Scene::load( std::istream
& in
)
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
;
823 while ( !in
.eof() ) {
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
) {
840 s
= s
.substr(start
, end
-start
+1);
844 bool Scene::parseLine( const std::string
& line
)
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) {
859 Stroke
*s
= new Stroke(line
);
863 case 'G': setGravity(line
.substr(line
.find(':')+1));return true;
865 } catch ( const char* e
) {
866 printf("Stroke error: %s\n",e
);
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
);
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();
895 Image
*Scene::g_bgImage
= NULL
;