/** Game Develop - Physic Automatism Extension Copyright (c) 2010-2012 Florian Rival (Florian.Rival@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #include "PhysicsAutomatism.h" #include "Box2D/Box2D.h" #include "Triangulation/triangulate.h" #include "GDL/RuntimeScene.h" #include "GDL/tinyxml/tinyxml.h" #include "GDL/XmlMacros.h" #include "PhysicsAutomatismEditor.h" #include "GDL/CommonTools.h" #include #undef GetObject PhysicsAutomatism::PhysicsAutomatism(std::string automatismTypeName) : Automatism(automatismTypeName), shapeType(Box), dynamic(true), fixedRotation(false), isBullet(false), massDensity(1), averageFriction(0.8), averageRestitution(0), linearDamping(0.1), angularDamping(0.1), body(NULL) { polygonHeight = 200; polygonWidth = 200; automaticResizing = false; polygonPositioning = OnCenter; polygonScaleX = 1; polygonScaleY = 1; } PhysicsAutomatism::~PhysicsAutomatism() { if ( runtimeScenesPhysicsDatas != boost::shared_ptr() && body) runtimeScenesPhysicsDatas->world->DestroyBody(body); } #if defined(GD_IDE_ONLY) void PhysicsAutomatism::EditAutomatism( wxWindow* parent, Game & game_, Scene * scene, MainEditorCommand & mainEditorCommand_ ) { PhysicsAutomatismEditor editor(parent, game_, scene, *this, mainEditorCommand_); editor.ShowModal(); } #endif /** * Called at each frame before events : * Simulate the world if necessary and update body positions. */ void PhysicsAutomatism::DoStepPreEvents(RuntimeScene & scene) { if ( !body ) CreateBody(scene); if ( !runtimeScenesPhysicsDatas->stepped ) //Simulate the world, once at each frame { runtimeScenesPhysicsDatas->StepWorld(static_cast(scene.GetElapsedTime())/1000.0, 6, 10); runtimeScenesPhysicsDatas->stepped = true; } //Update object position according to Box2D body b2Vec2 position = body->GetPosition(); object->SetX(position.x*runtimeScenesPhysicsDatas->GetScaleX()-object->GetWidth()/2+object->GetX()-object->GetDrawableX()); object->SetY(-position.y*runtimeScenesPhysicsDatas->GetScaleY()-object->GetHeight()/2+object->GetY()-object->GetDrawableY()); //Y axis is inverted object->SetAngle(-body->GetAngle()*180.0f/b2_pi); //Angles are inverted objectOldX = object->GetX(); objectOldY = object->GetY(); objectOldAngle = object->GetAngle(); }; /** * Called at each frame after events : * Update Box2D body if necessary */ void PhysicsAutomatism::DoStepPostEvents(RuntimeScene & scene) { if ( !body ) CreateBody(scene); if ( objectOldWidth != object->GetWidth() || objectOldHeight != object->GetHeight() ) { runtimeScenesPhysicsDatas->world->DestroyBody(body); CreateBody(scene); } runtimeScenesPhysicsDatas->stepped = false; //Prepare for a new simulation if ( objectOldX == object->GetX() && objectOldY == object->GetY() && objectOldAngle == object->GetAngle()) return; b2Vec2 oldPos; oldPos.x = (object->GetDrawableX()+object->GetWidth()/2)*runtimeScenesPhysicsDatas->GetInvScaleX(); oldPos.y = -(object->GetDrawableY()+object->GetHeight()/2)*runtimeScenesPhysicsDatas->GetInvScaleY(); //Y axis is inverted body->SetTransform(oldPos, -object->GetAngle()*b2_pi/180.0f); //Angles are inverted body->SetAwake(true); } /** * Prepare Box2D body, and set up also runtimeScenePhysicsDatasPtr. */ void PhysicsAutomatism::CreateBody(const RuntimeScene & scene) { if ( runtimeScenesPhysicsDatas == boost::shared_ptr() ) runtimeScenesPhysicsDatas = boost::static_pointer_cast(scene.GetAutomatismSharedDatas(name)); //Create body from object b2BodyDef bodyDef; bodyDef.type = dynamic ? b2_dynamicBody : b2_staticBody; bodyDef.position.Set((object->GetDrawableX()+object->GetWidth()/2)*runtimeScenesPhysicsDatas->GetInvScaleX(), -(object->GetDrawableY()+object->GetHeight()/2)*runtimeScenesPhysicsDatas->GetInvScaleY()); bodyDef.angle = -object->GetAngle()*b2_pi/180.0f; //Angles are inverted bodyDef.angularDamping = angularDamping > 0.0f ? angularDamping : 0.0f; bodyDef.linearDamping = linearDamping > 0.0f ? linearDamping : 0.0f; bodyDef.bullet = isBullet; bodyDef.fixedRotation = fixedRotation; body = runtimeScenesPhysicsDatas->world->CreateBody(&bodyDef); body->SetUserData(this); //Setup body if ( shapeType == Circle) { b2FixtureDef fixtureDef; b2CircleShape circle; circle.m_radius = (object->GetWidth()*runtimeScenesPhysicsDatas->GetInvScaleX()+ object->GetHeight()*runtimeScenesPhysicsDatas->GetInvScaleY())/4; //Radius is based on the average of height and width if ( circle.m_radius <= 0 ) circle.m_radius = 1; fixtureDef.shape = &circle; fixtureDef.density = massDensity; fixtureDef.friction = averageFriction; fixtureDef.restitution = averageRestitution; body->CreateFixture(&fixtureDef); } else if( shapeType == CustomPolygon && polygonCoords.size() > 2) { //Make a polygon triangulation to make possible to use a concave polygon and more than 8 edged polygons std::vector resultOfTriangulation; Triangulate::Process(polygonCoords, resultOfTriangulation); //Iterate over all triangles for(unsigned int i = 0; i < resultOfTriangulation.size() / 3; i++) { b2FixtureDef fixtureDef; b2PolygonShape dynamicBox; //Create vertices b2Vec2 vertices[3]; unsigned int b = 0; for(int a = 2; a >= 0; a--) //Box2D use another direction for vertices { if(polygonPositioning == OnOrigin) { vertices[b].Set((resultOfTriangulation.at(i*3 + a).x * GetPolygonScaleX() - object->GetWidth()/2 - (object->GetDrawableX() - object->GetX())) * runtimeScenesPhysicsDatas->GetInvScaleX(), (((object->GetHeight() - (resultOfTriangulation.at(i*3 + a).y * GetPolygonScaleY())) - object->GetHeight()/2 + (object->GetDrawableY() - object->GetY())) * runtimeScenesPhysicsDatas->GetInvScaleY())); } /*else if(polygonPositioning == OnTopLeftCorner) { vertices[b].Set((resultOfTriangulation.at(i*3 + a).x * GetPolygonScaleX() - object->GetWidth()/2 ) * runtimeScenesPhysicsDatas->GetInvScaleX(), (((object->GetHeight() - (resultOfTriangulation.at(i*3 + a).y * GetPolygonScaleY())) - object->GetHeight()/2) * runtimeScenesPhysicsDatas->GetInvScaleY())); }*/ else if(polygonPositioning == OnCenter) { vertices[b].Set((resultOfTriangulation.at(i*3 + a).x * GetPolygonScaleX()) * runtimeScenesPhysicsDatas->GetInvScaleX(), (((object->GetHeight() - (resultOfTriangulation.at(i*3 + a).y * GetPolygonScaleY())) - object->GetHeight()) * runtimeScenesPhysicsDatas->GetInvScaleY())); } b++; } dynamicBox.Set(vertices, 3); fixtureDef.shape = &dynamicBox; fixtureDef.density = massDensity; fixtureDef.friction = averageFriction; fixtureDef.restitution = averageRestitution; body->CreateFixture(&fixtureDef); } } else { b2FixtureDef fixtureDef; b2PolygonShape dynamicBox; dynamicBox.SetAsBox((object->GetWidth() > 0 ? object->GetWidth() : 1.0f)*runtimeScenesPhysicsDatas->GetInvScaleX()/2, (object->GetHeight() > 0 ? object->GetHeight() : 1.0f)*runtimeScenesPhysicsDatas->GetInvScaleY()/2); fixtureDef.shape = &dynamicBox; fixtureDef.density = massDensity; fixtureDef.friction = averageFriction; fixtureDef.restitution = averageRestitution; body->CreateFixture(&fixtureDef); } objectOldWidth = object->GetWidth(); objectOldHeight = object->GetHeight(); } void PhysicsAutomatism::OnDeActivate() { if ( runtimeScenesPhysicsDatas && body ) { runtimeScenesPhysicsDatas->world->DestroyBody(body); body = NULL; //Of course: body can ( and will ) be reused: Make sure we nullify the pointer as the body was destroyed. } } /** * Set a body to be static */ void PhysicsAutomatism::SetStatic(RuntimeScene & scene ) { dynamic = false; if ( !body ) CreateBody(scene); body->SetType(b2_staticBody); } /** * Set a body to be dynamic */ void PhysicsAutomatism::SetDynamic(RuntimeScene & scene ) { dynamic = true; if ( !body ) CreateBody(scene); body->SetType(b2_dynamicBody); } /** * Set rotation to be fixed */ void PhysicsAutomatism::SetFixedRotation(RuntimeScene & scene ) { fixedRotation = true; if ( !body ) CreateBody(scene); body->SetFixedRotation(true); } /** * Set rotation to be free */ void PhysicsAutomatism::SetFreeRotation(RuntimeScene & scene ) { fixedRotation = false; if ( !body ) CreateBody(scene); body->SetFixedRotation(false); } /** * Consider object as bullet, for better collision handling */ void PhysicsAutomatism::SetAsBullet(RuntimeScene & scene ) { isBullet = true; if ( !body ) CreateBody(scene); body->SetBullet(true); } /** * Don't consider object as bullet, for faster collision handling */ void PhysicsAutomatism::DontSetAsBullet(RuntimeScene & scene ) { isBullet = false; if ( !body ) CreateBody(scene); body->SetBullet(false); } /** * Apply a force */ void PhysicsAutomatism::ApplyForce(double xCoordinate, double yCoordinate, RuntimeScene & scene ) { if ( !body ) CreateBody(scene); body->ApplyForce(b2Vec2(xCoordinate, -yCoordinate),body->GetPosition()); } /** * Apply a force */ void PhysicsAutomatism::ApplyForceUsingPolarCoordinates( float angle, float length, RuntimeScene & scene ) { if ( !body ) CreateBody(scene); body->ApplyForce(b2Vec2(cos(angle)*length,-sin(angle)*length), body->GetPosition()); } /** * Apply a force */ void PhysicsAutomatism::ApplyForceTowardPosition(float xPosition, float yPosition, float length, RuntimeScene & scene ) { if ( !body ) CreateBody(scene); float angle = atan2(yPosition*runtimeScenesPhysicsDatas->GetInvScaleY()+body->GetPosition().y, xPosition*runtimeScenesPhysicsDatas->GetInvScaleX()-body->GetPosition().x); body->ApplyForce(b2Vec2(cos(angle)*length, -sin(angle)*length), body->GetPosition()); } /** * Apply a torque */ void PhysicsAutomatism::ApplyTorque( double torque, RuntimeScene & scene ) { if ( !body ) CreateBody(scene); body->ApplyTorque(torque); } /** * Change linear velocity */ void PhysicsAutomatism::SetLinearVelocity( double xVelocity, double yVelocity, RuntimeScene & scene ) { if ( !body ) CreateBody(scene); body->SetLinearVelocity(b2Vec2(xVelocity,-yVelocity)); } /** * Change angular velocity */ void PhysicsAutomatism::SetAngularVelocity( double angularVelocity, RuntimeScene & scene ) { if ( !body ) CreateBody(scene); body->SetAngularVelocity(angularVelocity); } /** * Change linear damping */ void PhysicsAutomatism::SetLinearDamping( float linearDamping_ , RuntimeScene & scene ) { if ( !body ) CreateBody(scene); body->SetLinearDamping(linearDamping_); } /** * Change angular damping */ void PhysicsAutomatism::SetAngularDamping( float angularDamping_ , RuntimeScene & scene ) { if ( !body ) CreateBody(scene); body->SetAngularDamping(angularDamping_); } /** * Add an hinge between two objects */ void PhysicsAutomatism::AddRevoluteJointBetweenObjects( const std::string & , Object * object, RuntimeScene & scene ) { if ( !body ) CreateBody(scene); if ( object == NULL || !object->HasAutomatism(name) ) return; b2Body * otherBody = static_cast(object->GetAutomatismRawPointer(name))->GetBox2DBody(scene); if ( body == otherBody ) return; b2RevoluteJointDef jointDef; jointDef.Initialize(otherBody, body, otherBody->GetWorldCenter()); runtimeScenesPhysicsDatas->world->CreateJoint(&jointDef); } /** * Add an hinge to an object */ void PhysicsAutomatism::AddRevoluteJoint( float xPosition, float yPosition, RuntimeScene & scene) { if ( !body ) CreateBody(scene); b2RevoluteJointDef jointDef; jointDef.Initialize(body, runtimeScenesPhysicsDatas->staticBody, b2Vec2( xPosition*runtimeScenesPhysicsDatas->GetInvScaleX(), -yPosition*runtimeScenesPhysicsDatas->GetInvScaleY())); runtimeScenesPhysicsDatas->world->CreateJoint(&jointDef); } /** * Change gravity */ void PhysicsAutomatism::SetGravity( float xGravity, float yGravity, RuntimeScene & scene) { if ( !body ) CreateBody(scene); runtimeScenesPhysicsDatas->world->SetGravity(b2Vec2( xGravity*runtimeScenesPhysicsDatas->GetInvScaleX(), -yGravity*runtimeScenesPhysicsDatas->GetInvScaleY())); } /** * Add a gear joint between two objects */ void PhysicsAutomatism::AddGearJointBetweenObjects( const std::string & , Object * object, RuntimeScene & scene ) { if ( !body ) CreateBody(scene); if ( object == NULL || !object->HasAutomatism(name) ) return; b2Body * otherBody = boost::static_pointer_cast(object->GetAutomatism(name))->GetBox2DBody(scene); if ( body == otherBody ) return; //Gear joint need a revolute joint to the ground for the two objects b2RevoluteJointDef jointDef1; jointDef1.Initialize(runtimeScenesPhysicsDatas->staticBody, body, body->GetWorldCenter()); b2RevoluteJointDef jointDef2; jointDef2.Initialize(runtimeScenesPhysicsDatas->staticBody, otherBody, otherBody->GetWorldCenter()); b2GearJointDef jointDef; jointDef.bodyA = body; jointDef.bodyB = otherBody; jointDef.joint1 = runtimeScenesPhysicsDatas->world->CreateJoint(&jointDef1); jointDef.joint2 = runtimeScenesPhysicsDatas->world->CreateJoint(&jointDef2); jointDef.ratio = 2.0f * b2_pi / 1.0f; //TODO : Ratio parameter ? runtimeScenesPhysicsDatas->world->CreateJoint(&jointDef); } void PhysicsAutomatism::SetLinearVelocityX( double xVelocity, RuntimeScene & scene ) { if ( !body ) CreateBody(scene); body->SetLinearVelocity(b2Vec2(xVelocity, body->GetLinearVelocity().y)); } void PhysicsAutomatism::SetLinearVelocityY( double yVelocity, RuntimeScene & scene ) { if ( !body ) CreateBody(scene); body->SetLinearVelocity(b2Vec2(body->GetLinearVelocity().x, -yVelocity)); } float PhysicsAutomatism::GetLinearVelocityX( RuntimeScene & scene ) { if ( !body ) CreateBody(scene); return body->GetLinearVelocity().x; } float PhysicsAutomatism::GetLinearVelocityY( RuntimeScene & scene ) { if ( !body ) CreateBody(scene); return -body->GetLinearVelocity().y; } double PhysicsAutomatism::GetAngularVelocity( const RuntimeScene & scene ) { if ( !body ) CreateBody(scene); return body->GetAngularVelocity(); } double PhysicsAutomatism::GetLinearDamping( const RuntimeScene & scene) { if ( !body ) CreateBody(scene); return body->GetLinearDamping(); } double PhysicsAutomatism::GetAngularDamping( const RuntimeScene & scene) { if ( !body ) CreateBody(scene); return body->GetAngularDamping(); } /** * Test if there is a contact with another object */ bool PhysicsAutomatism::CollisionWith( const std::string & , std::map *> otherObjectsLists, RuntimeScene & scene) { if ( !body ) CreateBody(scene); //Getting a list of all objects which are tested vector objects; for (std::map *>::const_iterator it = otherObjectsLists.begin();it!=otherObjectsLists.end();++it) { if ( it->second != NULL ) { objects.reserve(objects.size()+it->second->size()); std::copy(it->second->begin(), it->second->end(), std::back_inserter(objects)); } } //Test if an object of the list is in collision with our object. std::vector::const_iterator obj_end = objects.end(); for (std::vector::iterator obj = objects.begin(); obj != obj_end; ++obj ) { set::const_iterator it = currentContacts.begin(); set::const_iterator end = currentContacts.end(); for (;it != end;++it) { if ( (*it)->GetObject()->GetName() == (*obj)->GetName() ) return true; } } return false; } bool PhysicsAutomatism::IsStatic() { return !dynamic; } bool PhysicsAutomatism::IsDynamic() { return dynamic; } void PhysicsAutomatism::SetPolygonCoords(const std::vector &vec) { polygonCoords = vec; } const std::vector& PhysicsAutomatism::GetPolygonCoords() const { return polygonCoords; } bool PhysicsAutomatism::HasAutomaticResizing() const { return automaticResizing; } void PhysicsAutomatism::SetAutomaticResizing(bool b) { automaticResizing = b; } float PhysicsAutomatism::GetPolygonScaleX() const { if(automaticResizing) return object->GetWidth() / polygonWidth; else return polygonScaleX; } void PhysicsAutomatism::SetPolygonScaleX(float scX, RuntimeScene &scene) { polygonScaleX = scX; runtimeScenesPhysicsDatas->world->DestroyBody(body); CreateBody(scene); } float PhysicsAutomatism::GetPolygonScaleY() const { if(automaticResizing) return object->GetHeight() / polygonHeight; else return polygonScaleY; } void PhysicsAutomatism::SetPolygonScaleY(float scY, RuntimeScene &scene) { polygonScaleY = scY; runtimeScenesPhysicsDatas->world->DestroyBody(body); CreateBody(scene); } #if defined(GD_IDE_ONLY) void PhysicsAutomatism::SaveToXml(TiXmlElement * elem) const { GD_CURRENT_ELEMENT_SAVE_ATTRIBUTE_BOOL("dynamic", dynamic); GD_CURRENT_ELEMENT_SAVE_ATTRIBUTE_BOOL("fixedRotation", fixedRotation); GD_CURRENT_ELEMENT_SAVE_ATTRIBUTE_BOOL("isBullet", isBullet); GD_CURRENT_ELEMENT_SAVE_ATTRIBUTE_FLOAT("massDensity", massDensity); GD_CURRENT_ELEMENT_SAVE_ATTRIBUTE_FLOAT("averageFriction", averageFriction); GD_CURRENT_ELEMENT_SAVE_ATTRIBUTE_FLOAT("linearDamping", linearDamping); GD_CURRENT_ELEMENT_SAVE_ATTRIBUTE_FLOAT("angularDamping", angularDamping); if ( shapeType == Circle) GD_CURRENT_ELEMENT_SAVE_ATTRIBUTE("shapeType", "Circle") else if( shapeType == CustomPolygon ) GD_CURRENT_ELEMENT_SAVE_ATTRIBUTE("shapeType", "CustomPolygon") else GD_CURRENT_ELEMENT_SAVE_ATTRIBUTE("shapeType", "Box") if ( polygonPositioning == OnOrigin ) GD_CURRENT_ELEMENT_SAVE_ATTRIBUTE("positioning", "OnOrigin") /*else if ( polygonPositioning == OnTopLeftCorner ) GD_CURRENT_ELEMENT_SAVE_ATTRIBUTE("positioning", "OnTopLeftCorner")*/ else GD_CURRENT_ELEMENT_SAVE_ATTRIBUTE("positioning", "OnCenter") GD_CURRENT_ELEMENT_SAVE_ATTRIBUTE_BOOL("autoResizing", automaticResizing); GD_CURRENT_ELEMENT_SAVE_ATTRIBUTE_FLOAT("polygonWidth", polygonWidth); GD_CURRENT_ELEMENT_SAVE_ATTRIBUTE_FLOAT("polygonHeight", polygonHeight); GD_CURRENT_ELEMENT_SAVE_ATTRIBUTE("coordsList", PhysicsAutomatism::GetStringFromCoordsVector(GetPolygonCoords(), '/', ';').c_str()); GD_CURRENT_ELEMENT_SAVE_ATTRIBUTE_FLOAT("averageRestitution", averageRestitution); } #endif void PhysicsAutomatism::LoadFromXml(const TiXmlElement * elem) { GD_CURRENT_ELEMENT_LOAD_ATTRIBUTE_BOOL("dynamic", dynamic); GD_CURRENT_ELEMENT_LOAD_ATTRIBUTE_BOOL("fixedRotation", fixedRotation); GD_CURRENT_ELEMENT_LOAD_ATTRIBUTE_BOOL("isBullet", isBullet); GD_CURRENT_ELEMENT_LOAD_ATTRIBUTE_FLOAT("massDensity", massDensity); GD_CURRENT_ELEMENT_LOAD_ATTRIBUTE_FLOAT("averageFriction", averageFriction); GD_CURRENT_ELEMENT_LOAD_ATTRIBUTE_FLOAT("linearDamping", linearDamping); GD_CURRENT_ELEMENT_LOAD_ATTRIBUTE_FLOAT("angularDamping", angularDamping); std::string shape; GD_CURRENT_ELEMENT_LOAD_ATTRIBUTE_STRING("shapeType", shape); if ( shape == "Circle" ) shapeType = Circle; else if (shape == "CustomPolygon") shapeType = CustomPolygon; else shapeType = Box; std::string posi = "OnOrigin"; GD_CURRENT_ELEMENT_LOAD_ATTRIBUTE_STRING("positioning", posi); if(posi == "OnOrigin") polygonPositioning = OnOrigin; /*else if (posi == "OnTopLeftCorner") polygonPositioning = OnTopLeftCorner;*/ else polygonPositioning = OnCenter; automaticResizing = false; GD_CURRENT_ELEMENT_LOAD_ATTRIBUTE_BOOL("autoResizing", automaticResizing); GD_CURRENT_ELEMENT_LOAD_ATTRIBUTE_FLOAT("polygonWidth", polygonWidth); GD_CURRENT_ELEMENT_LOAD_ATTRIBUTE_FLOAT("polygonHeight", polygonHeight); std::string coordsStr; GD_CURRENT_ELEMENT_LOAD_ATTRIBUTE_STRING("coordsList", coordsStr); SetPolygonCoords(PhysicsAutomatism::GetCoordsVectorFromString(coordsStr, '/', ';')); GD_CURRENT_ELEMENT_LOAD_ATTRIBUTE_FLOAT("averageRestitution", averageRestitution); } std::string PhysicsAutomatism::GetStringFromCoordsVector(const std::vector &vec, char coordsSep, char composantSep) { std::string coordsStr; for (unsigned int a = 0; a < vec.size(); a++) { coordsStr += ToString(vec.at(a).x) + composantSep + ToString(vec.at(a).y); if(a != vec.size() - 1) coordsStr += coordsSep; } return coordsStr; } std::vector PhysicsAutomatism::GetCoordsVectorFromString(const std::string &str, char coordsSep, char composantSep) { std::vector coordsVec; std::vector coordsDecomposed = SplitString(str, coordsSep); for(unsigned int a = 0; a < coordsDecomposed.size(); a++) { std::vector coordXY = SplitString(coordsDecomposed.at(a), composantSep); if(coordXY.size() != 2) continue; sf::Vector2f newCoord(ToFloat(coordXY.at(0)), ToFloat(coordXY.at(1))); coordsVec.push_back(newCoord); } return coordsVec; }