#include #include "manager.hpp" #include "states/game.hpp" #include "objects/collision.hpp" #include "objects/object.hpp" /** * Définition des variables et fonctions globales internes * (accessibles uniquement dans ce fichier) */ namespace { // identifiant de la propriété de masse et sa valeur par défaut // (une masse de zéro représente une masse infinie) const unsigned int PROP_MASS = 1; const float DEFAULT_MASS = 0.f; // identifiant de la propriété de charge et sa valeur par défaut const unsigned int PROP_CHARGE = 2; const float DEFAULT_CHARGE = 0.f; // identifiant de la propriété de restitution et sa valeur par défaut // (plus la restitution est forte, plus les objets rebondissent) const unsigned int PROP_RESTITUTION = 3; const float DEFAULT_RESTITUTION = 0.4f; // identifiant du coefficient de frottement statique et sa valeur par défaut // (coefficient proportionnel à la qté d'énergie nécessaire pour mettre // en mouvement l'objet) const unsigned int PROP_STATIC_FRICTION = 4; const float DEFAULT_STATIC_FRICTION = 0.4f; // identifiant du coefficient de frottement dynamique et sa valeur par défaut // (coefficient proportionnel aux pertes d'énergie en mouvement) const unsigned int PROP_DYNAMIC_FRICTION = 5; const float DEFAULT_DYNAMIC_FRICTION = 0.2f; // identifiant de la propriété calque et sa valeur par défaut // (les objets sur deux calques différents n'entrent pas en collision, // et les objets sont dessinés par ordre de calque) const unsigned int PROP_LAYER = 6; const int DEFAULT_LAYER = 0; // coefficient d'attraction. Proportionnel à la quantité d'énergie // fournie par un objet chargé const float ATTRACTION = 500000; // coefficients de correction positionnelle permettant de réduire // la visibilité des erreurs d'arrondi des flottants. Le pourcentage // de correction indique la proportion de correction par rapport à la // vitesse et le seuil indique le minimum de correction appliqué const float CORRECTION_PERCENTAGE = .5f; const float CORRECTION_SLOP = .02f; } Object::Object() : acceleration(0, 0), velocity(0, 0), position(0, 0), selected(false), inv_mass(-1.f), mass(DEFAULT_MASS), charge(DEFAULT_CHARGE), restitution(DEFAULT_RESTITUTION), static_friction(DEFAULT_STATIC_FRICTION), dynamic_friction(DEFAULT_DYNAMIC_FRICTION), layer(DEFAULT_LAYER) {} Object::~Object() {} void Object::init(std::ifstream& file, Object::Ptr object) { // lecture de la position de l'objet float pos_x, pos_y; file.read(reinterpret_cast(&pos_x), 4); file.read(reinterpret_cast(&pos_y), 4); object->setPosition(sf::Vector2f( pos_x * Manager::GRID, pos_y * Manager::GRID )); // lecture des propriétés facultatives char prop_type = -1; while (file.read(&prop_type, 1)) { switch (prop_type) { case PROP_MASS: float mass; file.read(reinterpret_cast(&mass), 4); object->setMass(mass); break; case PROP_CHARGE: float charge; file.read(reinterpret_cast(&charge), 4); object->setCharge(charge); break; case PROP_RESTITUTION: float restitution; file.read(reinterpret_cast(&restitution), 4); object->setRestitution(restitution); break; case PROP_STATIC_FRICTION: float static_friction; file.read(reinterpret_cast(&static_friction), 4); object->setStaticFriction(static_friction); break; case PROP_DYNAMIC_FRICTION: float dynamic_friction; file.read(reinterpret_cast(&dynamic_friction), 4); object->setDynamicFriction(dynamic_friction); break; case PROP_LAYER: char layer; file.read(&layer, 1); object->setLayer((int) layer - 127); break; default: // propriété de type inconnu : on sort return; } } } void Object::save(std::ofstream& file) const { // écriture de la position de l'objet float pos_x = getPosition().x / Manager::GRID; float pos_y = getPosition().y / Manager::GRID; file.write(reinterpret_cast(&pos_x), 4); file.write(reinterpret_cast(&pos_y), 4); // écriture des propriétés facultatives si nécessaire char prop_type; if (mass != DEFAULT_MASS) { prop_type = PROP_MASS; file.write(&prop_type, 1); file.write(reinterpret_cast(&mass), 4); } if (charge != DEFAULT_CHARGE) { prop_type = PROP_CHARGE; file.write(&prop_type, 1); file.write(reinterpret_cast(&charge), 4); } if (restitution != DEFAULT_RESTITUTION) { prop_type = PROP_RESTITUTION; file.write(&prop_type, 1); file.write(reinterpret_cast(&restitution), 4); } if (static_friction != DEFAULT_STATIC_FRICTION) { prop_type = PROP_STATIC_FRICTION; file.write(&prop_type, 1); file.write(reinterpret_cast(&static_friction), 4); } if (dynamic_friction != DEFAULT_DYNAMIC_FRICTION) { prop_type = PROP_DYNAMIC_FRICTION; file.write(&prop_type, 1); file.write(reinterpret_cast(&dynamic_friction), 4); } if (layer != DEFAULT_LAYER) { prop_type = PROP_LAYER; file.write(&prop_type, 1); char write_layer = layer + 127; file.write(&write_layer, 1); } // on termine par un octet nul pour signaler la fin char null_byte = 0; file.write(&null_byte, 1); } sf::Vector2f Object::getForces(const Game& game) const { sf::Vector2f forces(0, 0); const std::vector& objects = game.getObjects(); // force de gravité forces += getMass() * game.getGravity(); // force d'attraction entre objets chargés if (getCharge() != 0) { for (unsigned int j = 0; j < objects.size(); j++) { Object::Ptr attractive = objects[j]; if (attractive.get() == this || attractive->getCharge() == 0) { continue; } // vecteur allant de l'objet attracteur vers l'objet actuel sf::Vector2f attraction(getPosition() - attractive->getPosition()); // la norme de ce vecteur est la distance entre les objets float distance_squared = attraction.x * attraction.x + attraction.y * attraction.y; // éviter la division par zéro if (distance_squared == 0) { continue; } // normalisation du vecteur direction qui porte // la force d'attraction, puis application de la norme attraction /= std::sqrt(distance_squared); attraction *= ATTRACTION * ( (getCharge() * attractive->getCharge()) / distance_squared ); forces += attraction; } } return forces; } void Object::updateVelocity(const Game& game) { acceleration = getForces(game) * getMassInvert(); velocity += acceleration * Manager::FRAME_TIME.asSeconds(); } void Object::updatePosition() { position += velocity * Manager::FRAME_TIME.asSeconds(); } void Object::solveCollision(Game& game, Object::Ptr obj, const sf::Vector2f& normal) { // si les deux objets sont de masse infinie, réinitialisation // des vitesses en tant que collision if (getMassInvert() == 0 && obj->getMassInvert() == 0) { setVelocity(sf::Vector2f(0, 0)); obj->setVelocity(sf::Vector2f(0, 0)); return; } sf::Vector2f rel_velo = obj->getVelocity() - getVelocity(); float dot_normal = rel_velo.x * normal.x + rel_velo.y * normal.y; // en ce point, on est bertins qu'une collision a eu lieu. // on peut donc activer les deux objets activate(game, obj); obj->activate(game, shared_from_this()); last_activator = obj; obj->last_activator = shared_from_this(); // si les directions sont divergentes, pas besoin // de résoudre la collision if (dot_normal > 0) { return; } // on utilise le plus petit coefficient de friction entre les // deux objets comme le coefficient de la collision float restitution = std::min(getRestitution(), obj->getRestitution()); // calcule et applique l'impulsion de résolution de la collision float collision_impulse = -(1.f + restitution) * std::min(dot_normal + .8f, 0.f) / (getMassInvert() + obj->getMassInvert()); setVelocity(getVelocity() - getMassInvert() * collision_impulse * normal); obj->setVelocity(obj->getVelocity() + obj->getMassInvert() * collision_impulse * normal); // application des forces de frottement entre les deux objets // on calcule le vecteur tangent qui porte la force de frottement. // les coefficients de friction utilisés sont les moyennes de ceux des deux objets rel_velo = obj->getVelocity() - getVelocity(); dot_normal = rel_velo.x * normal.x + rel_velo.y * normal.y; sf::Vector2f tangent = rel_velo - dot_normal * normal; float tangent_length = tangent.x * tangent.x + tangent.y * tangent.y; // la tangente est nulle : pas de frottement à générer // on évite ainsi une division par zéro if (tangent_length == 0) { return; } tangent /= std::sqrt(tangent_length); float magnitude = -(rel_velo.x * tangent.x + rel_velo.y * tangent.y) / (getMassInvert() + obj->getMassInvert()); float static_friction = (getStaticFriction() + obj->getStaticFriction()) / 2.f; float dynamic_friction = (getDynamicFriction() + obj->getDynamicFriction()) / 2.f; float friction_impulse; // utilisation de la loi de Coulomb sur les frottements dynamiques/statiques // cf https://fr.wikipedia.org/wiki/Loi_de_Coulomb_(m%C3%A9canique) if (std::abs(magnitude) < collision_impulse * static_friction) { friction_impulse = magnitude; } else { friction_impulse = -collision_impulse * dynamic_friction; } setVelocity(getVelocity() - getMassInvert() * friction_impulse * tangent); obj->setVelocity(obj->getVelocity() + obj->getMassInvert() * friction_impulse * tangent); } void Object::positionalCorrection(Object::Ptr obj, const sf::Vector2f& normal, float depth) { float position_correction = std::max(depth - CORRECTION_SLOP, 0.0f) / (getMassInvert() + obj->getMassInvert()) * CORRECTION_PERCENTAGE; setPosition(getPosition() - getMassInvert() * position_correction * normal); obj->setPosition(obj->getPosition() + obj->getMassInvert() * position_correction * normal); } sf::Vector2f Object::getAcceleration() const { return acceleration; } sf::Vector2f Object::getVelocity() const { return velocity; } void Object::setVelocity(sf::Vector2f set_velocity) { velocity = set_velocity; } sf::Vector2f Object::getPosition() const { return position; } void Object::setPosition(sf::Vector2f set_position) { position = set_position; } bool Object::isSelected() const { return selected; } void Object::setSelected(bool set_selected) { selected = set_selected; } Object::WeakPtr Object::getLastActivator() { return last_activator; } float Object::getMass() const { return mass; } float Object::getMassInvert() const { if (inv_mass >= 0) { return inv_mass; } if (mass == 0) { inv_mass = 0; return inv_mass; } inv_mass = 1 / mass; return inv_mass; } void Object::setMass(float set_mass) { mass = set_mass; inv_mass = -1.f; } float Object::getCharge() const { return charge; } void Object::setCharge(float set_charge) { charge = set_charge; } float Object::getRestitution() const { return restitution; } void Object::setRestitution(float set_restitution) { restitution = set_restitution; } float Object::getStaticFriction() const { return static_friction; } void Object::setStaticFriction(float set_static_friction) { static_friction = set_static_friction; } float Object::getDynamicFriction() const { return dynamic_friction; } void Object::setDynamicFriction(float set_dynamic_friction) { dynamic_friction = set_dynamic_friction; } int Object::getLayer() const { return layer; } void Object::setLayer(int set_layer) { layer = set_layer; } bool ObjectCompare::operator()(Object::Ptr const &t1, Object::Ptr const &t2) const { sf::Vector2f t1_pos = t1->getPosition(); sf::Vector2f t2_pos = t2->getPosition(); return t1_pos.x - t1_pos.y + t1->getLayer() > t2_pos.x - t2_pos.y + t2->getLayer(); }