/******************************************************************* Fichier : Vector.cpp Date : 28/04/98 Version : 1.001 Description : Classe de gestion des vecteurs |*******************************************************************| Bugs: Le troisieme point n'est pas pris en compte pour le calcul du vecteur orthogonal La fonction angle n'est pas validee Le calcul du vecteur orthogonal serait surement + rapide en calculant le produit scalaire du vecteur avec le vecteur forme par le point et son projete orthogonal sur le vecteur Notes: on pourrait utiliser controlfp plutot qu'ajouter .5 lors des conversions flottant vers entier La longueur (nombre de points requis pour trace le vecteur) du vecteur est une valeur signee, afin de permettre la recuperation des points n'appartenant pas au vecteur, mais a la droite dont le vecteur est support. operator [-1], retourne par exemple le point juste avant m_pt1 |*******************************************************************| Historique : 28/04/98 1.001 : Fonction de translation d'un vecteur 20/06/97 1.000 : Première version |*******************************************************************/ #include "scale.h" /*----------------------------------------------------------\ Fonctions locales \----------------------------------------------------------*/ inline long Double2Long(double d) { if (d < 0) { d -= .5; } else { d += .5; } return (long (d)); } /*----------------------------------------------------------\ | CVector | |-----------------------------------------------------------| | DESCRIPTION : | | Constructeurs de l'objet | |-----------------------------------------------------------| | PARAMETRES : | | Si aucun, un vecteur nul est créé | | pt1, pt2 : deux points définissants le support du vecteur| \----------------------------------------------------------*/ CVector::CVector (void) : m_pt1 (0,0), // petite optimisation, sinon le constructeur vide m_pt2 (0,0) // est appelé avant que l'on affecte réellement les points { Update (); } // end of CVector CVector::CVector(const Point &pt1, const Point &pt2) : m_pt1 (pt1), m_pt2 (pt2) { Update (); } // end of CVector CVector::CVector(int x1, int y1, int x2, int y2) : m_pt1 (x1, y1), m_pt2 (x2, y2) { Update (); } // end of CVector /*----------------------------------------------------------\ | = | |-----------------------------------------------------------| | DESCRIPTION : | | Opérateur de copie | |-----------------------------------------------------------| | PARAMETRES : | | Si aucun, un vecteur nul est créé | | pt1, pt2 : deux points définissants le support du vecteur| \----------------------------------------------------------*/ const CVector &CVector::operator = (const CVector &vSource) { m_pt1 = vSource.m_pt1; m_pt2 = vSource.m_pt2; Update(); return (*this); } // fin de = /*----------------------------------------------------------\ | Update | |-----------------------------------------------------------| | DESCRIPTION : | | Calcule les valeurs de tous les membres à partir des | | points extrêmes du vecteur | | A appeler à chaque modification de l'un de ces points. | \----------------------------------------------------------*/ void CVector::Update(void) { // calcul des variations m_lDeltaX = m_pt2.x - m_pt1.x; m_lDeltaY = m_pt2.y - m_pt1.y; // calcul de la norme // cela ne devrait jamais arriver (sur un écran), mais en cas de bug... // la somme des produits peut être > à 2x10e9 et donner un résultat // négatif, ce qui n'arrange pas vraiment la fonction sqrt // le DWORD final bien que théoriquement insuffisant, fonctionne car // on traite des vecteurs sur un écran, on évite simplement un plantage // en cas de bug // MAIS on fausse alors le calcul. Une gestion d'exception adequat serait plus judicieuse !! long test1 = ((m_lDeltaX * m_lDeltaX) + (m_lDeltaY * m_lDeltaY)); //DWORD test2 = DWORD ((m_lDeltaX * m_lDeltaX) + (m_lDeltaY * m_lDeltaY)); //double test3 = sqrt(test1); double test3 = sqrt((double) test1); // MAJ2005 //double test4 = sqrt(test2); //m_dblNorm = sqrt (DWORD ((m_lDeltaX * m_lDeltaX) + (m_lDeltaY * m_lDeltaY))); m_dblNorm = sqrt ((double) long ((m_lDeltaX * m_lDeltaX) + (m_lDeltaY * m_lDeltaY))); m_dblNorm = test3; // détermination de la variation la plus importante m_fHorizontal = (abs (m_lDeltaX) > abs (m_lDeltaY)); // calcul du nombre de points requis pour tracer une droite if (m_fHorizontal) m_dwSize = abs (m_lDeltaX); else m_dwSize = abs (m_lDeltaY); } // fin de Update /*----------------------------------------------------------\ | operator [] | |-----------------------------------------------------------| | DESCRIPTION : | | Retourne les coordonnées d'un point du segement | |-----------------------------------------------------------| | PARAMETRES : | | lIndex : position du point sur le segment | | signé afin de pouvoir obtenir les coordonées | | des points de la droite aussi. | |-----------------------------------------------------------| | RETOUR : | | le point voulu | \----------------------------------------------------------*/ Point CVector::operator [] (long lIndex) const { // pour le vecteur nul il aurait mieux valu ne pas appeler la fonction if (Nul ()) { return Point (0,0); } else if (lIndex) { // le double est essentiel, sinon tout est converti en NON signé!!! return (Point (m_pt1.x + Double2Long (m_lDeltaX * lIndex / double (Length ())), m_pt1.y + Double2Long (m_lDeltaY * lIndex / double (Length ())) ) ); } else return (m_pt1); } // end of operator [] /*----------------------------------------------------------\ | PosFromPoint | |-----------------------------------------------------------| | DESCRIPTION : | | Retourne la position que doit avoir le point s'il fait | | partie de la droite | |-----------------------------------------------------------| | PARAMETRES : | | pt : le point en question | |-----------------------------------------------------------| | RETOURNE : | | la position estimée | \----------------------------------------------------------*/ long CVector::PosFromPoint (const Point &pt) const { // calcul de la position approximative if (Nul ()) { return (0); } else { // que de problèmes je me suis faits pour pas grand-chose !! // en fonction du sens, on obtient directement la position // du point, puisqu'il y a UN point ajouté à chaque fois // sans recouvrement dans le sens de variation // LONG ((pt.x - m_pt1.x) * Length () / double (m_lDeltaX)) = (pt.x - m_pt1.x) * sign(m_lDeltaX) // = m_lDeltaX !! // ça sert parfois de prendre le temps de réfléchir !! if (m_fHorizontal) return (m_lDeltaX > 0 ? pt.x - m_pt1.x : m_pt1.x - pt.x); else return (m_lDeltaY > 0 ? pt.y - m_pt1.y : m_pt1.y - pt.y); } } // fin de PosFromPoint /*----------------------------------------------------------\ | PointInVector | |-----------------------------------------------------------| | DESCRIPTION : | | Détermine si un point donné appartient au segment | |-----------------------------------------------------------| | PARAMETRES : | | pt : le point en question | |-----------------------------------------------------------| | RETOURNE : | | un booléen | \----------------------------------------------------------*/ bool CVector::PointInVector (const Point &pt) const { // calcul de la position approximative // la position en X, doit être la même que la position en Y, logique... // donc un seul calcul suffit if (Nul ()) { return (false); } else { long lPos = PosFromPoint (pt); // on regarde aussi si l'un des deux extrêmes ne fait pas partie du segment // à cause des arrondis // si la position est hors limite, on court-circuite le test, d'autant if ((lPos < 0) || (long (lPos) > Length ())) return (false); else // le point PEUT appartenir au segment return ( (operator [] (lPos ) == pt) || (operator [] (lPos + 1) == pt) || ((lPos > 0) && (operator [] (lPos - 1) == pt)) ); } } // fin de PointInVector /*----------------------------------------------------------\ | PointOnLine | |-----------------------------------------------------------| | DESCRIPTION : | | Détermine si un point donné appartient à la droite dont | | ce vecteur est support | |-----------------------------------------------------------| | PARAMETRES : | | pt : le point en question | |-----------------------------------------------------------| | RETOURNE : | | un booléen | \----------------------------------------------------------*/ bool CVector::PointOnLine (const Point &pt) const { // Comme ces classe de vecteur est basée sur une représentation // pixel, on ne peut pas vérifier que les vecteurs sont colinéaires // (à cause de la précision) if (Nul ()) { return (false); } else { long lPos = PosFromPoint (pt); // seul différence avec PointInVector : on ne vérifie pas les bornes // vis à vis de lPos return ( (operator [] (lPos ) == pt) || (operator [] (lPos + 1) == pt) || ((lPos > 0) && (operator [] (lPos - 1) == pt)) ); } } // fin de PointOnLine /*----------------------------------------------------------\ | Angle | |-----------------------------------------------------------| | DESCRIPTION : | | Retourne l'angle formé par le vecteur et un point formant| | un vecteur avec le point de départ | |-----------------------------------------------------------| | PARAMETRES : | | pt : le point en question | |-----------------------------------------------------------| | RETOURNE : | | l'angle en degrés (sens trigonométrique) | | retourne une valeur entre 0 et 180 | \----------------------------------------------------------*/ double CVector::Angle (const Point &pt) const { // calcul du cosinus de l'angle if (Nul () || (pt == m_pt1)) return (0.0); else { // produit scalaire : v1.v2 = x1.x2 + y1.y2 = ||v1||.||v2||.cos(ß) CVector v (m_pt1, pt); return (acos ((v * *this) / (Norm () * v.Norm ())) * 180. / M_PI); } } // fin de Angle /*----------------------------------------------------------\ | Orthogonal | |-----------------------------------------------------------| | DESCRIPTION : | | Retourne un vecteur orthogonal au segment débutant au | | point donné. | |-----------------------------------------------------------| | PARAMETRES : | | ptStart : point de départ du vecteur orthogonal, devant | | appartenir au vecteur source | | ptDir : point donnant la direction du nouveau vecteur | |-----------------------------------------------------------| | RETOURNE : | | un vecteur orthogonal | \----------------------------------------------------------*/ CVector CVector::Orthogonal( const Point &ptStart, const Point &ptDir ) const { CVector v; // Le point de départ doit appartenir à la droite dont ce vecteur est support // Le point de direction ne doit pas appartenir à la droite dont ce vecteur est support if ( PointOnLine (ptStart) && !PointOnLine (ptDir) ) { // il suffit d'inverser les coordonnées en X et Y pour obtenir // un vecteur orthogonal v.m_pt1 = ptStart; // si le vecteur est du "bon côté", l'angle entre le nouveau vecteur // et le point, ne doit pas excéder 90° v.m_pt2.x = ptStart.x - m_lDeltaY; v.m_pt2.y = ptStart.y + m_lDeltaX; v.Update (); // avant de calculer l'angle if (v.Angle (ptDir) > 90.0) { v.m_pt2.x = ptStart.x + m_lDeltaY; v.m_pt2.y = ptStart.y - m_lDeltaX; v.Update(); assert( v.Angle( ptDir ) < 90.0 ); } } v.Update (); return (v); } // fin de Orthogonal /*----------------------------------------------------------\ | Orthogonal | |-----------------------------------------------------------| | DESCRIPTION : | | Retourne un vecteur orthogonal au segment débutant au | | point donné. | |-----------------------------------------------------------| | PARAMETRES : | | ptStart : point de départ du vecteur orthogonal, devant | | appartenir au vecteur source | | uType : 0/1 pour indiquer le sens du vecteur | |-----------------------------------------------------------| | RETOURNE : | | un vecteur orthogonal | \----------------------------------------------------------*/ CVector CVector::Orthogonal( const Point &ptStart, unsigned int uType ) const { CVector v; // Le point de départ doit appartenir à la droite dont ce vecteur est support if (PointOnLine (ptStart)) { // il suffit d'inverser les coordonnées en X et Y pour obtenir // un vecteur orthogonal v.m_pt1 = ptStart; // NE PAS changer la signification de uType, CEIM::Measure compte dessus if (uType) { v.m_pt2.x = ptStart.x + m_lDeltaY; v.m_pt2.y = ptStart.y - m_lDeltaX; } else { v.m_pt2.x = ptStart.x - m_lDeltaY; v.m_pt2.y = ptStart.y + m_lDeltaX; } } v.Update (); return (v); } // fin de Orthogonal /*----------------------------------------------------------\ | Projected | |-----------------------------------------------------------| | DESCRIPTION : | | Retourne le projeté orthogonal du point sur le vecteur | | Le point retourné est assuré d'appartenir au vecteur | |-----------------------------------------------------------| | PARAMETRES : | | pt : point à projeter | |-----------------------------------------------------------| | RETOURNE : | | le projeté orthogonal | \----------------------------------------------------------*/ Point CVector::Projected (const Point &pt) const { // Soient A et B les 2 points du vecteur, C le point en paramètre, M le projeté // orthogonal de C sur AB. // // 1° le projeté appartenant au vecteur, il doit appartenir au support // (Y - Ya)*m_lDeltaX - (X - Xa)*m_lDeltaY = 0 // 2° par le produit scalaire on retrouve une équation à 2 inconnues // (X - Xc)*m_lDeltaX + (Y - Yc)*m_lDeltaY = 0 // petite optimisation if (m_lDeltaY == 0) return (Point (pt.x, m_pt1.y)); else if (m_lDeltaX == 0) return (Point (m_pt1.x, pt.y)); else { // X(1 + SLOPE²) = Yc.SLOPE + Xa.SLOPE² - Ya.SLOPE + Xc // Y = (X - Xa).SLOPE + Ya double dblSlope = double (m_lDeltaY) / double (m_lDeltaX), dblX, dblY; dblX = dblSlope * (pt.y + m_pt1.x * dblSlope - m_pt1.y) + pt.x; dblX /= (1 + dblSlope * dblSlope); dblY = (dblX - m_pt1.x) * dblSlope + m_pt1.y; // suppression de l'erreur induite par l'arrondi // via l'appel à [] return (operator [] (PosFromPoint (Point (Double2Long (dblX), Double2Long (dblY))))); } } // end of Projected /*----------------------------------------------------------\ | SetLength | |-----------------------------------------------------------| | DESCRIPTION : | | Impose une longueur au segment à partir du premier point | |-----------------------------------------------------------| | PARAMETRES : | | lNewSize : nouvelle taille souhaitée pour le segment | | signé pour autoriser une inversion du vecteur| \----------------------------------------------------------*/ void CVector::SetLength (long lNewSize) { m_pt2 = operator [] (lNewSize); Update (); } // fin de SetLength /*----------------------------------------------------------\ | Mask | |-----------------------------------------------------------| | DESCRIPTION : | | Limite l'étendue du vecteur à l'intérieur du rectangle | |-----------------------------------------------------------| | PARAMETRES : | | rcLimit : rectangle limitant l'étendue du vecteur | \----------------------------------------------------------*/ void CVector::Mask (const Rect &rcLimit) { // les bords droit et inférieur ne sont pas considérés comme // faisant partie du rectangle // ATTENTION les coordonnées sont croissantes vers le bas long lX1, lY1, lX2, lY2; // à chaque problème, on regarde si l'autre point résoud le // problème rencontré, si ce n'est pas le cas, il n'y a rien // à faire lX1 = m_pt1.x - rcLimit.left; lX2 = m_pt2.x - rcLimit.left; if (lX1 < 0) { if (lX2 < 0) { // le 2° point est aussi à gauche du bord gauche //NullVector (); return; } } else if (lX1 >= rcLimit.right) { } lY1 = m_pt1.y - rcLimit.top; lY2 = m_pt2.y - rcLimit.top; } // fin de Mask double CVector::Norm (const CScale *pScale) const { if ( pScale ) { return (pScale->Valid () ? pScale->Distance (m_pt1, m_pt2) : 0); } return 0.0; } void CVector::MoveTo (const Point &ptNewStart) { m_pt2 += ptNewStart - m_pt1; m_pt1 = ptNewStart; // pas la peine d'appeler Update, ce n'est qu'une translation } /* Quelques tests CVector v1 (CPoint(4,6), CPoint (12,3)), v2; LONG l; for (l = 0; l <= v1.Length (); l++) afxDump << v1 [l] << "\n"; afxDump << v1.PointInVector (CPoint (4, 6)) << "," << v1.PointOnLine (CPoint ( 4, 6)) << "\n"; afxDump << v1.PointInVector (CPoint (5, 6)) << "," << v1.PointOnLine (CPoint ( 5, 6)) << "\n"; afxDump << v1.PointInVector (CPoint (11,4)) << "," << v1.PointOnLine (CPoint (11, 4)) << "\n"; afxDump << v1.PointInVector (CPoint (15,2)) << "," << v1.PointOnLine (CPoint (15, 2)) << "\n"; afxDump << v1.PointInVector (CPoint (0, 7)) << "," << v1.PointOnLine (CPoint ( 0, 7)) << "\n"; afxDump << v1.PointInVector (CPoint (-120,300)) << "," << v1.PointOnLine (CPoint (-120,300)) << "\n"; v1.SetLength (v1.Length () * 2); for (;l <= v1.Length (); l++) afxDump << v1 [l] << "\n"; afxDump << v1.PointInVector (CPoint (4, 6)) << "," << v1.PointOnLine (CPoint ( 4, 6)) << "\n"; afxDump << v1.PointInVector (CPoint (5, 6)) << "," << v1.PointOnLine (CPoint ( 5, 6)) << "\n"; afxDump << v1.PointInVector (CPoint (11,4)) << "," << v1.PointOnLine (CPoint (11, 4)) << "\n"; afxDump << v1.PointInVector (CPoint (15,2)) << "," << v1.PointOnLine (CPoint (15, 2)) << "\n"; afxDump << v1.PointInVector (CPoint (0, 7)) << "," << v1.PointOnLine (CPoint ( 0, 7)) << "\n"; afxDump << v1.PointInVector (CPoint (-120,300)) << "," << v1.PointOnLine (CPoint (-120,300)) << "\n"; // vérification du premier sens v2 = v1.Orthogonal (v1.StartPoint (), CPoint (10,7)); for (l = 0; l <= v2.Length (); l++) afxDump << v2 [l] << "\n"; v2 = v1.Orthogonal (v1.StartPoint (), CPoint (2,8)); for (l = 0; l <= v2.Length (); l++) afxDump << v2 [l] << "\n"; // vérification du sens opposé v2 = v1.Orthogonal (v1.StartPoint (), CPoint (9,4)); // aussi près que possible du vecteur source for (l = 0; l <= v2.Length (); l++) afxDump << v2 [l] << "\n"; v2 = v1.Orthogonal (v1.StartPoint (), CPoint (0,6)); for (l = 0; l <= v2.Length (); l++) afxDump << v2 [l] << "\n"; */