Table of Contents

Partie VI – Objets graphiques

TD1

A effectuer en dehors du cours

Cette partie vise à définir des classes pour des objets graphiques. La plupart des bibliothèques d’interfaces graphiques sont écrites dans des langages orientés objets, qui sont particulièrement adaptés pour. Dans cette partie, nous définissons les classes de points, de segment et de droite dans le plan.

Question n°1

En vous inspirant du code fourni, écrivez, compilez et mettez au point en C++

Correction classe Point

Correction classe Point

La classe Point définit deux champs m_x et m_y qui sont déclarés comme étant privés, ie. uniquement visible des méthodes définies dans la classe. Il est habituel de préfixer les champs membres par un indicateur qui indique que ces champs appartiennent à la classe. Il existe plusieurs notations :

  • on préfixe le champ par m_, donc x devient m_x,
  • on préfixe le champ par _, voire , donc x devient

Dans le corrigé, nous avons adopter la première notation, cependant, ce n'est qu'indicatif.

Dans le cas présent, nous n'implantons pas de constructeur par défaut, nous considérons qu'il est nécessaire de fournir une abcisse et une ordonnée pour pouvoir initialiser une objet de type Point. Ceci est un choix d'implantation et d'autres implantations peuvent considérer que l'initialisation d'un objet de type Point par défaut consiste à initialiser les champs m_x et m_y avec la valeur 0.

Nous implantons de manière systématique le constructeur de recopie. Ceci est un choix. En pratique, nous aurions pu ne pas implanter le constructeur de recopie mais il est préférable de savoir ce que ce constructeur fait effectivement.

Nous implantons enfin un constructeur spécialisé qui construit un object Point à partir d'une valeur abscisse et d'une valeur ordonnée.

Comme les champs sont privés, nous définissons deux méthodes d'accès en lecture (getter methods) qui retourne les valeurs des champs m_x et m_y. Ceci est un moyen de garantir que les valeurs des champs ne seront jamais modifiées.

class Point
{
private:
    int m_x;
    int m_y;
 
public:
    Point(int x, int y): m_x(x), m_y(y) 
    {}
    Point(const Poinst& anotherPoint): 
        m_x(anotherPoint.m_x), 
        m_y(anotherPoint.m_y) 
    {}
 
    int getX() const { return m_X; }
    int getY() const { return m_Y; }
};
 

Correction classe Segment

Correction classe Segment

La classe Segment constituée de deux extrémités sous la forme de Point. Ces deux points sont définis par les champs m_start et m_end.

Nous définissons le constructeur de recopie ainsi que deux constructeurs spécialisés construisant un segment soit à partir des points extrémaux, soit au contraire à partir des abscisses et ordonnés des points extrémaux.

une l’intersection ainsi que le point d’intersection faux, dans le cas contraire.

class Segment
{
private:
    Point m_start;
    Point m_end;
 
public:
    Segment(const Point& theStart, const Point& theEnd): 
        m_start(theStart), m_end(theEnd)
    {}   
    Segment(int startPointX, int startPointY, 
        int endPointX, int endPointY): 
        m_start(startPointX, startPointY), 
        m_end(endPointX, endPointY)
    {}   
    Segment(const Segment& anotherSegment): 
        m_start(anotherSegment.m_start), 
        m_end(anotherSegment.m_end)
    {}   
    bool contains(Point aPoint)
    {
        if(m_start.getX() == m_end.getX())
        {
            if(aPoint.getX() != m_start.getX())
                return false;
 
            if(m_start.getY() <= m_end.getY())
            {
                if(aPoint.getY() < m_start.getY() 
                    || aPoint.getY() > m_end.getY())
                    return false;
            }
            else
            {
                if(aPoint.getY() > m_start.getY() 
                    || aPoint.getY() < m_end.getY())
                    return false;
            }
            return true;            
        }
        double ratio = 
            (double)(m_start.getY() - m_end.getY() 
            / (double)(m_start.getX() - m_end.getX());
        int pointY = 
            (int)floor(ratio * (aPoint.getX() - m_end.getX())
            + m_end.getY();
        return pointY == aPoint.getY();
    }
    bool intersectsWith(
      const Segment& anotherSegment, 
      Point& theIntersectingPoint)
    {
        Droite line(m_start, m_end);
        Droite anotherLine(anotherSegment.m_start, anotherSegment.m_end);
 
        double intersectionX, intersectionY;
        if(!line.intersectsWith(
          anotherLine, 
          intersectionX, intersectionY))
          return false;
 
        theIntersectingPoint = 
          Point(
              (int)round(intersectionX), 
              (int)round(intersectionY));
        return contains(theIntersectingPoint ) 
            || anotherSegment.contains(theIntersectingPoint);   
    }
};

Correction classe Droite

Correction classe Droite

class Droite
{
private:
    Point m_point;
    double m_angle;
 
public:
    Droite(double anAngle, const Point& aPoint): 
        m_point(aPoint), 
        m_angle(anAngle)
    {}
    Droite(const Point& theStart, const Point& theEnd): 
        m_point(theStart), 
        m_angle(atan2(
            (double)(theEnd.getY() - theStart.getY())
            (double)(theEnd.getX() - theStart.getX())))
    {} 
    Droite(const Line& anotherLine):
        m_point(anotherLine.m_point),
        m_angle(anotherLine.m_angle)
    {}
    bool intersectsWith(const Line& anotherLine)
    {
        double intersectionX;
        double intersectionY;
        return intersectsWith(anotherLine,
            intersectionX, intersectionY);
    }
    bool intersectsWith(
        const Line& anotherLine,
        double& theIntersectionX,
        double& theIntersectionY)
    {
        if(m_point.getX()
            == anotherLine.m_point.getX() && 
            m_point.getY() 
            == anotherLine.m_point.getY())
            return true;
        if(m_angle == anotherLine.m_angle)
        {
            double angle = atan2(
                anotherLine.m_point.getY() - m_point.getY(),
                anotherLine.m_point.getX() - m_point.getX());
            if(angle != m_angle)
                return false;                          
        }
        return true;
    }
}
 

Question n° 2

Ajouter à toutes ces classes des méthodes d’affichage pour le déboguage que l’on appellera print.

Correction

Correction

class Point
{
    // Ajouter la méthode suivante après les autres
    // méthodes.
public:
    void print() const
    {
        std::cout << "(" << m_x << ", " << m_y << ")"; 
    }
};
 
class Segment
{
    // Ajouter la méthode suivante après les autres
    // méthodes.
public:
    void print() const
    {
        std::cout << "[";
        m_start.print();
        std::cout << "-";
        m_end.print();
        std::cout << "]"; 
    }
};
 
class Droite
{
    // Ajouter la méthode suivante après les autres
    // méthodes.
public:
    void print() const
    {
        std::cout << "<";
        m_point.print();
        std::cout << ", " m_angle << ">"; 
    }
};

Question n° 3

A la classe Droite

Correction

Correction

Cette version correspond à des méthodes qui crée un nouvel objet qui est une copie de l'objet courant et ne modifie par l'objet courant.

...
 
const double pi = 3.14;
 
...
 
class Droite
{
    // Code précédent de la classe.
    ...     
 
public:
    Droite rotate(double anAngle) const
    {
        return Droite(m_Point, m_angle + anAngle);
    }
    Droite rotate90(double anAngle) const
    {
        return rotate(m_Point, m_angle + 3.14/4);
    }
    Droite move(int x, int y) const
    {
        return Droite(
            Point(m_Point.getX() + x,
                  m_Point.GetY() + y),
            m_angle);
    }
    ...
};

Une autre possibilité consiste à modifier l'objet courant. C'est ce que fait le code suivant :

...
 
const double pi = 3.14;
 
...
 
class Droite
{
    ... // Code précédent de la classe.
 
 
public:
    Droite& rotate(double anAngle)
    {
        m_angle += anAngle;
        return *this;
    }
    Droite& rotate90(double anAngle)
    {
        return Turn(m_Point, m_angle + pi/4);
    }
    Droite& move(int x, int y)
    {
        mPoint =
            Point(m_Point.getX() + x,
                  m_Point.GetY() + y);
        return *this;
    }
};

Typiquement, nous retournons une référence à l'objet que nous venons de modifier. Ceci permet d'enchaîner les transformations comme suit :

    Droite droite(Point(0,0), pi/8);
 
    droite.Move(2,3).rotate(-pi/8);
        // Nous effectuons une droite et puis nous effectuons 
        // une rotation.

Question n° 4

Tester votre classe sur le code suivant :

#include <iostream>
 
int main() {
   Droite d1(Point(0,0), Point(4, 2));
   Droite d2(d1);
   d1.print(); 
   std::cout << std::endl;
   d2.print();
   std::cout << std::endl;
   d1.rotate90();
   d1.print(); 
   std::cout << std::endl;
   d2.print();
   return 0;
}

correction

correction

Il suffit d'exécuter le code pour les classes réalisées et vérifier que les résultas sont conformes aux résultats attendus.