OLD | NEW |
| (Empty) |
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "physics_layer.h" | |
6 #include "app_delegate.h" | |
7 #include "gameplay_scene.h" | |
8 | |
9 #include "physics_nodes/CCPhysicsSprite.h" | |
10 #include "CCLuaEngine.h" | |
11 | |
12 extern "C" { | |
13 #include "lua.h" | |
14 #include "tolua++.h" | |
15 #include "lualib.h" | |
16 #include "lauxlib.h" | |
17 #include "tolua_fix.h" | |
18 } | |
19 | |
20 // Pixels-to-meters ratio for converting screen coordinates | |
21 // to Box2D "meters". | |
22 #define PTM_RATIO 32 | |
23 #define SCREEN_TO_WORLD(n) ((n) / PTM_RATIO) | |
24 #define WORLD_TO_SCREEN(n) ((n) * PTM_RATIO) | |
25 #define VELOCITY_ITERATIONS 8 | |
26 #define POS_ITERATIONS 1 | |
27 | |
28 #define MAX_SPRITES 100 | |
29 | |
30 #define DEFAULT_DENSITY 1.0f | |
31 #define DEFAULT_FRICTION 0.2f | |
32 #define DEFAULT_RESTITUTION 0.1f | |
33 | |
34 | |
35 USING_NS_CC_EXT; | |
36 | |
37 PhysicsLayer* PhysicsLayer::create(int level_number) | |
38 { | |
39 PhysicsLayer* layer = new PhysicsLayer(level_number); | |
40 if (!layer) | |
41 return NULL; | |
42 layer->init(); | |
43 return layer; | |
44 } | |
45 | |
46 bool PhysicsLayer::init() { | |
47 if (!CCLayerColor::initWithColor(ccc4(0,0x8F,0xD8,0xD8))) | |
48 return false; | |
49 | |
50 setTouchEnabled(true); | |
51 | |
52 InitPhysics(); | |
53 | |
54 // Load level from lua file. For now we simple load level 1. | |
55 LoadLua(); | |
56 | |
57 // Calculate brush size | |
58 CCSpriteBatchNode* brush_batch = (CCSpriteBatchNode*)getChildByTag(TAG_BRUSH); | |
59 assert(brush_batch); | |
60 brush_ = CCSprite::createWithTexture(brush_batch->getTexture()); | |
61 brush_->retain(); | |
62 CCSize brush_size = brush_->getContentSize(); | |
63 brush_radius_ = MAX(brush_size.height/2, brush_size.width/2); | |
64 | |
65 // Schedule physics updates each frame | |
66 schedule(schedule_selector(PhysicsLayer::UpdateWorld)); | |
67 return true; | |
68 } | |
69 | |
70 PhysicsLayer::PhysicsLayer(int level_number) : | |
71 level_number_(level_number), | |
72 goal_reached_(false), | |
73 current_touch_id_(-1), | |
74 render_target_(NULL), | |
75 #ifdef COCOS2D_DEBUG | |
76 debug_enabled_(false), | |
77 #endif | |
78 box2d_density_(DEFAULT_DENSITY), | |
79 box2d_restitution_(DEFAULT_RESTITUTION), | |
80 box2d_friction_(DEFAULT_FRICTION) { | |
81 memset(stars_collected_, 0, sizeof(stars_collected_)); | |
82 } | |
83 | |
84 PhysicsLayer::~PhysicsLayer() { | |
85 brush_->release(); | |
86 delete box2d_world_; | |
87 #ifdef COCOS2D_DEBUG | |
88 delete box2d_debug_draw_; | |
89 #endif | |
90 } | |
91 | |
92 void PhysicsLayer::registerWithTouchDispatcher() { | |
93 CCDirector* director = CCDirector::sharedDirector(); | |
94 director->getTouchDispatcher()->addTargetedDelegate(this, 0, true); | |
95 } | |
96 | |
97 void PhysicsLayer::CreateRenderTarget() { | |
98 // create render target for shape drawing | |
99 assert(!render_target_); | |
100 CCSize win_size = CCDirector::sharedDirector()->getWinSize(); | |
101 render_target_ = CCRenderTexture::create(win_size.width, | |
102 win_size.height, | |
103 kCCTexture2DPixelFormat_RGBA8888); | |
104 render_target_->setPosition(ccp(win_size.width / 2, win_size.height / 2)); | |
105 addChild(render_target_); | |
106 } | |
107 | |
108 bool PhysicsLayer::LoadLua() { | |
109 CCScriptEngineManager* manager = CCScriptEngineManager::sharedManager(); | |
110 CCLuaEngine* engine = (CCLuaEngine*)manager->getScriptEngine(); | |
111 assert(engine); | |
112 lua_stack_ = engine->getLuaStack(); | |
113 assert(lua_stack_); | |
114 | |
115 lua_stack_->pushString("sample_game/game.lua"); | |
116 lua_stack_->pushCCObject(this, "PhysicsLayer"); | |
117 lua_stack_->pushInt(level_number_); | |
118 | |
119 // Call 'main' with three arguments pushed above | |
120 int rtn = lua_stack_->executeFunctionByName("main", 3); | |
121 if (rtn != 1) | |
122 return false; | |
123 | |
124 return true; | |
125 } | |
126 | |
127 bool PhysicsLayer::InitPhysics() { | |
128 b2Vec2 gravity(0.0f, -9.8f); | |
129 box2d_world_ = new b2World(gravity); | |
130 box2d_world_->SetAllowSleeping(true); | |
131 box2d_world_->SetContinuousPhysics(true); | |
132 | |
133 // Find visible rect, and convert to box2d space. | |
134 CCPoint origin = CCDirector::sharedDirector()->getVisibleOrigin(); | |
135 CCSize visible_size = CCDirector::sharedDirector()->getVisibleSize(); | |
136 float world_width = SCREEN_TO_WORLD(visible_size.width); | |
137 b2Vec2 world_origin(SCREEN_TO_WORLD(origin.x), SCREEN_TO_WORLD(origin.y)); | |
138 | |
139 // create the ground | |
140 b2BodyDef ground_def; | |
141 ground_def.position = world_origin; | |
142 b2Body* ground_body = box2d_world_->CreateBody(&ground_def); | |
143 | |
144 CCLog("origin: %.fx%.f", origin.x, origin.y); | |
145 CCLog("size: %.fx%f", visible_size.width, visible_size.height); | |
146 | |
147 // Define the ground box shape. | |
148 b2EdgeShape ground_box; | |
149 ground_box.Set(b2Vec2(0, 0), b2Vec2(world_width, 0)); | |
150 ground_body->CreateFixture(&ground_box, 0); | |
151 ground_body->SetUserData((void*)TAG_GROUND); | |
152 | |
153 box2d_world_->SetContactListener(this); | |
154 | |
155 #ifdef COCOS2D_DEBUG | |
156 box2d_debug_draw_ = new GLESDebugDraw(PTM_RATIO); | |
157 box2d_world_->SetDebugDraw(box2d_debug_draw_); | |
158 | |
159 uint32 flags = 0; | |
160 flags += b2Draw::e_shapeBit; | |
161 flags += b2Draw::e_jointBit; | |
162 flags += b2Draw::e_centerOfMassBit; | |
163 //flags += b2Draw::e_aabbBit; | |
164 //flags += b2Draw::e_pairBit; | |
165 box2d_debug_draw_->SetFlags(flags); | |
166 #endif | |
167 return true; | |
168 } | |
169 | |
170 void PhysicsLayer::ToggleDebug() { | |
171 debug_enabled_ = !debug_enabled_; | |
172 | |
173 // Set visibility of all children based on debug_enabled_ | |
174 CCArray* children = getChildren(); | |
175 if (!children) | |
176 return; | |
177 for (uint i = 0; i < children->count(); i++) | |
178 { | |
179 CCNode* child = static_cast<CCNode*>(children->objectAtIndex(i)); | |
180 if (child == render_target_) | |
181 continue; | |
182 child->setVisible(!debug_enabled_); | |
183 } | |
184 } | |
185 | |
186 CCRect CalcBoundingBox(CCSprite* sprite) { | |
187 CCSize size = sprite->getContentSize(); | |
188 CCPoint pos = sprite->getPosition(); | |
189 return CCRectMake(pos.x - size.width, pos.y - size.height, | |
190 size.width, size.height/2); | |
191 } | |
192 | |
193 void PhysicsLayer::UpdateWorld(float dt) { | |
194 // update physics | |
195 box2d_world_->Step(dt, VELOCITY_ITERATIONS, POS_ITERATIONS); | |
196 } | |
197 | |
198 void PhysicsLayer::LuaNotifyContact(b2Contact* contact, | |
199 const char* function_name) { | |
200 // Return early if lua didn't define the function_name | |
201 lua_State* state = lua_stack_->getLuaState(); | |
202 lua_getglobal(state, function_name); | |
203 bool is_func = lua_isfunction(state, -1); | |
204 lua_pop(state, 1); | |
205 | |
206 if (!is_func) | |
207 return; | |
208 | |
209 // Only send to lua collitions between body's that | |
210 // have been tagged. | |
211 b2Body* body1 = contact->GetFixtureA()->GetBody(); | |
212 b2Body* body2 = contact->GetFixtureB()->GetBody(); | |
213 int tag1 = (int)body1->GetUserData(); | |
214 int tag2 = (int)body2->GetUserData(); | |
215 if (!tag1 || !tag2) | |
216 return; | |
217 | |
218 // Call 'ContactBegan' lua function passing in 'this' | |
219 // as well as the tags of the two bodies that collided | |
220 lua_stack_->pushCCObject(this, "PhysicsLayer"); | |
221 lua_stack_->pushInt(tag1); | |
222 lua_stack_->pushInt(tag2); | |
223 lua_stack_->executeFunctionByName(function_name, 3); | |
224 } | |
225 | |
226 void PhysicsLayer::BeginContact(b2Contact* contact) { | |
227 LuaNotifyContact(contact, "BeginContact"); | |
228 } | |
229 | |
230 void PhysicsLayer::EndContact(b2Contact* contact) { | |
231 LuaNotifyContact(contact, "EndContact"); | |
232 } | |
233 | |
234 void PhysicsLayer::LevelComplete() { | |
235 // fade out the goal and trigger gameover callback when its | |
236 // done | |
237 CCPhysicsSprite* goal = (CCPhysicsSprite*)getChildByTag(TAG_GOAL); | |
238 CCActionInterval* fadeout = CCFadeOut::create(0.5f); | |
239 CCFiniteTimeAction* fadeout_done = CCCallFuncN::create(this, | |
240 callfuncN_selector(PhysicsLayer::LevelCompleteDone)); | |
241 CCSequence* seq = CCSequence::create(fadeout, fadeout_done, NULL); | |
242 goal->runAction(seq); | |
243 } | |
244 | |
245 void PhysicsLayer::LevelCompleteDone(CCNode* sender) { | |
246 unschedule(schedule_selector(PhysicsLayer::UpdateWorld)); | |
247 setTouchEnabled(false); | |
248 GameplayScene* scene = static_cast<GameplayScene*>(getParent()); | |
249 scene->GameOver(true); | |
250 } | |
251 | |
252 void PhysicsLayer::DrawPoint(CCPoint& location) { | |
253 ClampBrushLocation(location); | |
254 render_target_->begin(); | |
255 brush_->setVisible(true); | |
256 brush_->setPosition(ccp(location.x, location.y)); | |
257 brush_->visit(); | |
258 brush_->setVisible(false); | |
259 render_target_->end(); | |
260 points_being_drawn_.push_back(location); | |
261 } | |
262 | |
263 void PhysicsLayer::draw() { | |
264 CCLayerColor::draw(); | |
265 | |
266 #ifdef COCOS2D_DEBUG | |
267 if (debug_enabled_) { | |
268 ccGLEnableVertexAttribs(kCCVertexAttribFlag_Position); | |
269 kmGLPushMatrix(); | |
270 box2d_world_->DrawDebugData(); | |
271 kmGLPopMatrix(); | |
272 } | |
273 #endif | |
274 } | |
275 | |
276 void PhysicsLayer::ClampBrushLocation(CCPoint& point) { | |
277 CCPoint origin = CCDirector::sharedDirector()->getVisibleOrigin(); | |
278 CCSize visible_size = CCDirector::sharedDirector()->getVisibleSize(); | |
279 | |
280 float min_x = origin.x + brush_radius_; | |
281 float min_y = origin.y + brush_radius_; | |
282 if (point.x < min_x) point.x = min_x; | |
283 if (point.y < min_y) point.y = min_y; | |
284 | |
285 float max_x = origin.x + visible_size.width - brush_radius_; | |
286 float max_y = origin.y + visible_size.height - brush_radius_; | |
287 if (point.x > max_x) point.x = max_x; | |
288 if (point.y > max_y) point.y = max_y; | |
289 } | |
290 | |
291 void PhysicsLayer::DrawLine(CCPoint& start, CCPoint& end) { | |
292 ClampBrushLocation(start); | |
293 ClampBrushLocation(end); | |
294 | |
295 // calculate distance moved | |
296 float distance = ccpDistance(start, end); | |
297 | |
298 // draw the brush sprite into render texture at every point between the old | |
299 // and new cursor positions | |
300 render_target_->begin(); | |
301 for (int i = 0; i < int(distance + 0.5); i++) { | |
302 float difx = end.x - start.x; | |
303 float dify = end.y - start.y; | |
304 float delta = (float)i / distance; | |
305 brush_->setVisible(true); | |
306 brush_->setPosition( | |
307 ccp(start.x + (difx * delta), start.y + (dify * delta))); | |
308 | |
309 brush_->visit(); | |
310 brush_->setVisible(false); | |
311 } | |
312 render_target_->end(); | |
313 points_being_drawn_.push_back(end); | |
314 } | |
315 | |
316 bool PhysicsLayer::ccTouchBegan(CCTouch* touch, CCEvent* event) { | |
317 if (current_touch_id_ != -1) | |
318 return false; | |
319 | |
320 current_touch_id_ = touch->getID(); | |
321 | |
322 if (!render_target_) | |
323 CreateRenderTarget(); | |
324 | |
325 points_being_drawn_.clear(); | |
326 CCPoint location = touch->getLocation(); | |
327 DrawPoint(location); | |
328 return true; | |
329 } | |
330 | |
331 void PhysicsLayer::ccTouchMoved(CCTouch* touch, CCEvent* event) { | |
332 assert(touch->getID() == current_touch_id_); | |
333 CCPoint end = touch->getLocation(); | |
334 CCPoint start = touch->getPreviousLocation(); | |
335 DrawLine(start, end); | |
336 } | |
337 | |
338 void PhysicsLayer::ccTouchEnded(CCTouch* touch, CCEvent* event) { | |
339 assert(touch->getID() == current_touch_id_); | |
340 b2Body* body = CreatePhysicsBody(); | |
341 CCSprite* sprite = CreatePhysicsSprite(body); | |
342 addChild(sprite); | |
343 if (debug_enabled_) | |
344 sprite->setVisible(false); | |
345 | |
346 // release render target (it will get recreated on next touch). | |
347 removeChild(render_target_, true); | |
348 render_target_ = NULL; | |
349 current_touch_id_ = -1; | |
350 } | |
351 | |
352 CCRect CalcBodyBounds(b2Body* body) { | |
353 CCSize s = CCDirector::sharedDirector()->getWinSize(); | |
354 | |
355 float minX = FLT_MAX; | |
356 float maxX = 0; | |
357 float minY = FLT_MAX; | |
358 float maxY = 0; | |
359 | |
360 const b2Transform& xform = body->GetTransform(); | |
361 for (b2Fixture* f = body->GetFixtureList(); f; f = f->GetNext()) { | |
362 b2Shape* shape = f->GetShape(); | |
363 if (shape->GetType() == b2Shape::e_circle) { | |
364 b2CircleShape* c = static_cast<b2CircleShape*>(shape); | |
365 b2Vec2 center = b2Mul(xform, c->m_p); | |
366 if (center.x - c->m_radius < minX) | |
367 minX = center.x - c->m_radius; | |
368 if (center.x + c->m_radius > maxX) | |
369 maxX = center.x + c->m_radius; | |
370 if (center.y - c->m_radius < minY) | |
371 minY = center.y - c->m_radius; | |
372 if (center.y + c->m_radius > maxY) | |
373 maxY = center.y + c->m_radius; | |
374 } else { | |
375 b2PolygonShape* poly = static_cast<b2PolygonShape*>(shape); | |
376 int32 vertex_count = poly->m_vertexCount; | |
377 | |
378 for (int i = 0; i < vertex_count; ++i) | |
379 { | |
380 b2Vec2 vertex = b2Mul(xform, poly->m_vertices[i]); | |
381 if (vertex.x < minX) | |
382 minX = vertex.x; | |
383 if (vertex.x > maxX) | |
384 maxX = vertex.x; | |
385 if (vertex.y < minY) | |
386 minY = vertex.y; | |
387 if (vertex.y > maxY) | |
388 maxY = vertex.y; | |
389 } | |
390 } | |
391 } | |
392 | |
393 maxX = WORLD_TO_SCREEN(maxX); | |
394 minX = WORLD_TO_SCREEN(minX); | |
395 maxY = WORLD_TO_SCREEN(maxY); | |
396 minY = WORLD_TO_SCREEN(minY); | |
397 | |
398 float width = maxX - minX; | |
399 float height = maxY - minY; | |
400 float remY = s.height - maxY; | |
401 return CCRectMake(minX, remY, width, height); | |
402 } | |
403 | |
404 CCSprite* PhysicsLayer::CreatePhysicsSprite(b2Body* body) { | |
405 CCPhysicsSprite *sprite; | |
406 | |
407 // create a new texture based on the current contents of the | |
408 // render target | |
409 CCImage* image = render_target_->newCCImage(); | |
410 CCTexture2D* tex = new CCTexture2D(); | |
411 tex->initWithImage(image); | |
412 tex->autorelease(); | |
413 delete image; | |
414 | |
415 // Find the bounds of the physics body wihh the target texture | |
416 CCRect sprite_rect = CalcBodyBounds(body); | |
417 sprite_rect.origin.x -= brush_radius_; | |
418 sprite_rect.origin.y -= brush_radius_; | |
419 sprite_rect.size.width += brush_radius_; | |
420 sprite_rect.size.height += brush_radius_; | |
421 | |
422 CCSize s = CCDirector::sharedDirector()->getWinSize(); | |
423 CCPoint body_pos = ccp(WORLD_TO_SCREEN(body->GetPosition().x), | |
424 WORLD_TO_SCREEN(body->GetPosition().y)); | |
425 | |
426 | |
427 // Create a new sprite based on the texture | |
428 sprite = CCPhysicsSprite::createWithTexture(tex, sprite_rect); | |
429 sprite->setB2Body(body); | |
430 sprite->setPTMRatio(PTM_RATIO); | |
431 | |
432 // Set the anchor point of the sprite | |
433 float anchorX = body_pos.x - sprite_rect.origin.x; | |
434 float anchorY = body_pos.y + sprite_rect.origin.y + sprite_rect.size.height; | |
435 anchorY -= s.height; | |
436 | |
437 // anchor point goes from 0.0 to 1.0 with in bounds of the sprite itself. | |
438 sprite->setAnchorPoint(ccp(anchorX / sprite_rect.size.width, | |
439 anchorY / sprite_rect.size.height)); | |
440 return sprite; | |
441 } | |
442 | |
443 b2Body* PhysicsLayer::CreatePhysicsBody() { | |
444 assert(points_being_drawn_.size()); | |
445 CCPoint start_point = points_being_drawn_.front(); | |
446 | |
447 assert(points_being_drawn_.size()); | |
448 CCLog("new body from %d points", points_being_drawn_.size()); | |
449 // create initial body | |
450 b2BodyDef def; | |
451 def.type = b2_dynamicBody; | |
452 def.position.Set(SCREEN_TO_WORLD(start_point.x), | |
453 SCREEN_TO_WORLD(start_point.y)); | |
454 b2Body* body = box2d_world_->CreateBody(&def); | |
455 | |
456 const int min_box_length = brush_radius_; | |
457 | |
458 // Create an initial box the size of the brush | |
459 AddSphereToBody(body, &start_point); | |
460 AddSphereToBody(body, &points_being_drawn_.back()); | |
461 | |
462 // Add boxes to body for every point that was drawn by the | |
463 // user. | |
464 // initialise endpoint to be the second item in the list | |
465 // and iterate until it points to the final element. | |
466 PointList::iterator iter = points_being_drawn_.begin(); | |
467 ++iter; | |
468 for (; iter != points_being_drawn_.end(); iter++) { | |
469 CCPoint end_point = *iter; | |
470 float distance = ccpDistance(start_point, end_point); | |
471 // if the distance between points it too small then | |
472 // skip the current point | |
473 if (distance < min_box_length) { | |
474 if (iter != points_being_drawn_.end() - 1) | |
475 continue; | |
476 } | |
477 AddLineToBody(body, start_point, end_point); | |
478 start_point = *iter; | |
479 } | |
480 | |
481 points_being_drawn_.clear(); | |
482 return body; | |
483 } | |
484 | |
485 void PhysicsLayer::AddShapeToBody(b2Body *body, b2Shape* shape) { | |
486 b2FixtureDef shape_def; | |
487 shape_def.shape = shape; | |
488 shape_def.density = box2d_density_; | |
489 shape_def.friction = box2d_friction_; | |
490 shape_def.restitution = box2d_restitution_; | |
491 body->CreateFixture(&shape_def); | |
492 } | |
493 | |
494 void PhysicsLayer::AddSphereToBody(b2Body *body, CCPoint* location) { | |
495 b2CircleShape shape; | |
496 shape.m_radius = SCREEN_TO_WORLD(brush_radius_); | |
497 shape.m_p.x = SCREEN_TO_WORLD(location->x) - body->GetPosition().x; | |
498 shape.m_p.y = SCREEN_TO_WORLD(location->y) - body->GetPosition().y; | |
499 AddShapeToBody(body, &shape); | |
500 } | |
501 | |
502 void PhysicsLayer::AddLineToBody(b2Body *body, CCPoint start, CCPoint end) { | |
503 float distance = ccpDistance(start, end); | |
504 | |
505 float sx = start.x; | |
506 float sy = start.y; | |
507 float ex = end.x; | |
508 float ey = end.y; | |
509 float dist_x = sx - ex; | |
510 float dist_y = sy - ey; | |
511 float angle = atan2(dist_y, dist_x); | |
512 | |
513 float posx = SCREEN_TO_WORLD((sx+ex)/2) - body->GetPosition().x; | |
514 float posy = SCREEN_TO_WORLD((sy+ey)/2) - body->GetPosition().y; | |
515 | |
516 float width = SCREEN_TO_WORLD(abs(distance)); | |
517 float height = SCREEN_TO_WORLD(brush_->boundingBox().size.height); | |
518 | |
519 b2PolygonShape shape; | |
520 shape.SetAsBox(width / 2, height / 2, b2Vec2(posx, posy), angle); | |
521 AddShapeToBody(body, &shape); | |
522 } | |
OLD | NEW |