23
JTP lato 2019/2020 Wykład 3 1/23 Figury geometryczne Nieco wyprzedzając możliwości wyświetlania graficznego zajmiemy się dzisiaj przygotowaniem infrastruktury do wyświetlania „mapy” – zestawu figur, który ma być w całości wyświetlony i widoczny w oknie. Podstawowe pytanie, które trzeba zadać sobie na początku projektowania rozwiązania brzmi: Czy można całe zadanie rozwiązać w jednym przebiegu? (tzn. czy czytając dane będę w stanie od razu wyświetlać figury w oknie?) Natychmiastowa odpowiedź brzmi: NIE Potrzebuję wyznaczyć skalę, a to mogę zrobić dopiero po odczytaniu współrzędnych wszystkich figur.

Figury geometryczne - Warsaw University of Technologyrkz/GI/JTP2/jtp2_3.pdf · 2020. 6. 5. · Figury geometryczne Nieco wyprzedzając możliwości wyświetlania graficznego zajmiemy

  • Upload
    others

  • View
    5

  • Download
    0

Embed Size (px)

Citation preview

  • JTP lato 2019/2020 Wykład 3 1/23

    Figury geometryczne

    Nieco wyprzedzając możliwości wyświetlania graficznego zajmiemy się dzisiaj przygotowaniem infrastruktury do wyświetlania „mapy” – zestawu figur, który ma być w całości wyświetlony i widoczny w oknie.

    Podstawowe pytanie, które trzeba zadać sobie na początku projektowania rozwiązania brzmi:

    Czy można całe zadanie rozwiązać w jednym przebiegu?

    (tzn. czy czytając dane będę w stanie od razu wyświetlać figury w oknie?)

    Natychmiastowa odpowiedź brzmi: NIE

    Potrzebuję wyznaczyć skalę, a to mogę zrobić dopiero po odczytaniu współrzędnych wszystkich figur.

  • JTP lato 2019/2020 Wykład 3 2/23

    Podział zadania na części

    Jeśli nie da się wykonać całego zadania w jednym przebiegu, warto zastanowić się nad tym, jak podzielić zadanie na mniejsze części.

    W tym przypadku podział jest w pewnym sensie narzucony przez zadanie:

    1. Odczytać opis figur z pliku.

    2. Wyznaczyć skalę i przesunięcie („po drodze” musi pojawić się okno, bo od jego wymiarów zależy skala).

    3. Na podstawie rodzaju figury, jej współrzędnych odczytanych z pliku oraz wartości skali i przesunięcia utworzyć figury geometryczne i powiązać z oknem.

  • JTP lato 2019/2020 Wykład 3 3/23

    Odczyt figur z pliku

    Rzut oka na postać pliku wejściowego pozwala stwierdzić, że największą jego część stanowią współrzędne punktów (ale współrzędne zmiennopozycyjne!)

    Line((-1,-1)(-2,-2)(12,-2)) Poly((1.3, 0.7) (2, 1.4) (1.3, 2.1) (0.6, 1.4)) Rect ( (-3, -3) (3, 3) ) Circ ((0.0, 0.0) (1.0, 0.0)) Elip ( (0.0, 0.0) (2.0, 0.0) (0.0, 1.0)

    Jako że będę potrzebował współrzędne punktów przechowywać (i wykonywać na nich parę dodatkowych operacji) warto zdefiniować klasę FPoint reprezentującą współrzędne punktu.

  • JTP lato 2019/2020 Wykład 3 4/23

    Otoczenie

    Ponieważ będę testował od razu pisany kod, to warto poświęcić parę minut na przygotowanie infrastruktury – szczególnie przechwytywania wyjątków. Punkt wyjścia jest taki: int main() { try { // testowanie } catch (exception& ex) { cout

  • JTP lato 2019/2020 Wykład 3 5/23

    FPoint – start

    Pierwsza wersja FPoint, którą na razie zamieszczę w tym samym pliku, co funkcja main jest następująca: class FPoint { public: float x, y; FPoint(float cx = 0.0f, float cy = 0.0f) : x(cx), y(cy) {} friend ostream& operator

  • JTP lato 2019/2020 Wykład 3 6/23

    FPoint – odczyt

    O ile wyprowadzanie na strumień wyjściowy przyda mi się przy testowaniu, to wprowadzenie ze strumienia jest niezbędne, żeby zrobić zadanie. Ponieważ tego rodzaju funkcję piszemy n-ty raz, to podaję jej kod bez komentarza: friend istream& operator>>(istream& is, FPoint& p) { char l, m, r; is >> l >> p.x >> m >> p.y >> r; if (l != '(' || m != ',' || r != ')') throw runtime_error("Invalid point."); return is; }

    Do sygnalizacji wyjątku wybrałem klasę runtime_error (nie do końca pasuje logicznie, ale może być).

  • JTP lato 2019/2020 Wykład 3 7/23

    Odczyt FPoint – test

    O wiele ciekawsze od funkcji >> jest jej testowanie. Znowu wykorzystamy do tego strumienie łańcuchowe stringstream – można je zgrabnie zainicjować łańcuchem znaków, a następnie korzystać ze zmiennej, jak ze strumienia. stringstream ss("( 1.5 , 3.75) (2.5 ; 2.5) "); FPoint p; while (ss >> p) cout

  • JTP lato 2019/2020 Wykład 3 8/23

    Odczyt FPoint – co jest nie tak?

    Po poprawieniu drugiego zestawu współrzędnych działanie dalej kończy się wyjątkiem. Dlaczego? Problem wynika stąd, że po odczycie ostatniego z zestawów, ss nie jest jeszcze „wyczerpany”, tzn. wartość funkcji eof() jest false (a eof jest związane z operatorem konwersji ss na bool, używanym w warunku while).

    stringstream ss("( 1.5 , 3.75) (2.5 , 2.5) "); FPoint p; while (ss >> p) cout

  • JTP lato 2019/2020 Wykład 3 9/23

    Odczyt wielu punktów

    Szczęśliwie lista punktów, którą mamy daną dla każdej figury, jest zamknięta dodatkową parą nawiasów:

    (( 1.5 , 3.75) (2.5 , 2.5) …i może wiele więcej…)

    To pozwala nam nie martwić się niedogodnością zasygnalizowaną na poprzedniej stronie, ale ma to swoją cenę. Tą ceną jest konieczność „wyjrzenia w przód” przy analizie listy punktów.

    Pobieramy znak ze strumienia wejściowego i jeśli jest to nawias zamykający, wiemy że lista skończyła się. Jeśli jest to nawias otwierający mamy niespodziewany problem, bo właśnie zabraliśmy jeden znak z najpewniej zupełnie poprawnego punktu.

    Z pomocą przyjdzie nam funkcja unget(), która pozwala „odłożyć” ostatnio odczytany znak do strumienia wejściowego.

  • JTP lato 2019/2020 Wykład 3 10/23

    Odczyt wielu punktów

    vector get_points(istream& is) { char ch; is >> ch; // lista zawsze zaczyna się od '(' if (ch != '(') throw runtime_error("Invalid figure."); vector vp; do { FPoint p; is >> p; // może wygenerować wyjątek vp.push_back(p); // jak nie, to mamy zdrowy punkt is >> ch; // sprawdzamy, co czai się w strumieniu if (ch == '(') is.unget(); // odkładamy '(' żeby punkt był cały } while (ch != ')'); return vp; }

  • JTP lato 2019/2020 Wykład 3 11/23

    get_points()

    Wybór klasy vector na kontener dla punktów jest dość oczywisty – dokładamy nowe punkty tylko na końcu i nie znamy z góry ich liczby. Mimo pozorów, sprawdzanie poprawności danych jest bardzo pobieżne. Polegamy w zasadzie na operatorze >> czytającym FPoint (tzn. jeśli coś będzie nie tak z danymi, to liczymy na to, że w tej funkcji zostanie wygenerowany wyjątek). W tym zadaniu diagnostyka, czyli precyzyjne podanie użytkownikowi, co jest nie tak z danymi wejściowymi, nie jest potrzebna, więc możemy pozostać z tym uproszczonym rozwiązaniem. To jest moment, na przeprowadzenie testów get_points()!

  • JTP lato 2019/2020 Wykład 3 12/23

    Odczyt identyfikatora figury

    Zanim przejdziemy do implementacji odczytu identyfikatora, krótkie przypomnienie dotyczące działania operatora >> dla typu string i char.

    vector test{ "Line(...punkty...)", "line (", "L i n e (" };

    for (auto& src : test) { stringstream ts(src); string ch; ts >> ch; cout

  • JTP lato 2019/2020 Wykład 3 13/23

    Odczyt identyfikatora figury

    Rezygnacja z typu string zostawia nam do wyboru tylko char – to oznacza czytanie w pętli while: vector test{ "Line(..", "line (", "L i n e (" }; for (auto& src : test) { stringstream ts(src); char ch; while (ts >> ch) cout dla char pomija białe znaki (nie tylko spacje, ale i tabulacje, nowe linie, itd.). Dla zaoszczędzenia czasu zostajemy przy >> dla char postanawiając przy najbliższej okazji usprawnić funkcję.

    Line( line(

    Line(

  • JTP lato 2019/2020 Wykład 3 14/23

    get_id()

    string get_id(istream& is) { string retv; char ch; while (is >> ch) { if (isalpha(ch)) // litery dodajemy do identyfikatora retv += ch; else if (retv.empty()) // pusty identyfikator throw runtime_error("Indentifier not found."); else { is.unget(); // nie wiemy co, ale odkładamy return retv; } } return retv; }

    Jakie są możliwe zakończenia działania tej funkcji (poza wyjątkiem)?

  • JTP lato 2019/2020 Wykład 3 15/23

    Składamy figurę

    get_id() nie jest idealna, ale na teraz wystarczy. Warto byłoby teraz złożyć wszystkie elementy w jedną figurę. Oznacza to oczywiście stworzenie odpowiedniej klasy, w której na początek pomieścimy identyfikator i punkty oraz operatory wejścia/wyjścia.

    class figure { string id; vector fdef; public: friend istream& operator>>(istream& is, figure& f); friend ostream& operator

  • JTP lato 2019/2020 Wykład 3 16/23

    figure – wejście

    W operatorze wprowadzania ze strumienia wejściowego skorzystamy z tego, że get_id() w jednym przypadku zwraca pusty identyfikator (w jakiej sytuacji?)

    friend istream& operator>>(istream& is, figure& f) { f.id = get_id(is); if (f.id.length() == 0) return is; f.fdef = get_points(is); return is; }

    Także tutaj liczymy na to, że wystąpienie niezgodności danych z formatem zostanie wyłapane przy czytaniu FPoint lub sprawdzaniu nawiasów.

  • JTP lato 2019/2020 Wykład 3 17/23

    figure – wyjście

    Operator wyjściowy jest boleśnie oczywisty:

    friend ostream& operator

  • JTP lato 2019/2020 Wykład 3 18/23

    figure – test wejścia – wyjścia

    ifstream ifs("mapa_test01.txt"); if (!ifs.good())// dobrze sprawdzić przed czytaniem throw runtime_error("Cannot find input file."); figure fig; while (ifs >> fig) cout

  • JTP lato 2019/2020 Wykład 3 19/23

    Prostokąt otaczający

    Wygląda na to, że można już myśleć nieśmiało o przejściu do wyświetlania figur – pierwszym krokiem w tym kierunku będzie wyznaczenie skali i przesunięcia figur.

    W klasie figure dobrze byłoby mieć funkcję bbox (od bounding box – prostokąt otaczający), której wartością byłyby współrzędne przeciwległych wierzchołków minimalnego prostokąta zawierającego wszystkie punkty figury.

    Żeby nie definiować nowej klasy skorzystam z szablonu pary elementów: pair, w której pierwszy będzie reprezentować minimalne współrzędne, a drugi – maksymalne.

    Jeśli mam mieć minimalne i maksymalne współrzędne punktów, to rozsądnie będzie zacząć od zdefiniowania stosownych funkcji dla FPoint.

  • JTP lato 2019/2020 Wykład 3 20/23

    FPoint – minimum i maksimum

    Wynikiem każdej z funkcji ma być nowy punkt, zatem umieszczę je poza klasą FPoint:

    FPoint min(const FPoint& lf, const FPoint& rt) { return FPoint(min(lf.x, rt.x), min(lf.y, rt.y)); } FPoint max(const FPoint& lf, const FPoint& rt) { return FPoint(max(lf.x, rt.x), max(lf.y, rt.y)); }

    Nieuchronny test: FPoint p1(1.5, 3.75), p2(2.5, 2.5); cout

  • JTP lato 2019/2020 Wykład 3 21/23

    Prostokąt otaczający w klasie figure

    Z funkcjami min i max implementacja bbox jest krótka: pair bbox() const { FPoint pmin = fdef[0], pmax = fdef[0]; for (int i = 1; i < (int)fdef.size(); ++i) { pmin = min(pmin, fdef[i]); pmax = max(pmax, fdef[i]); } return make_pair(pmin, pmax); }

    Wszystkie obliczenia są prowadzone na obiektach FPoint, dopiero na sam koniec korzystam z funkcji szablonowej make_pair, żeby utworzyć parę zwracaną przez funkcję.

  • JTP lato 2019/2020 Wykład 3 22/23

    bbox() – test

    Czasu starczy tylko na bardzo pobieżny test – po wyświetleniu figury (tzn. tego co przeczytałem z pliku) wywołam bbox i wyświetlę jego wynik. while (ifs >> fig) { cout

  • JTP lato 2019/2020 Wykład 3 23/23

    Klasa figure – co z nią nie tak?

    Niedostatki funkcji bbox, to tylko jedno (i nie największe) niedomaganie klasy figure.

    Listę można zamknąć w 3 punktach:

    1. Nie sprawdzamy w ogóle, czy odczytaną figurę znamy!

    2. Nie sprawdzamy, czy liczba odczytanych punktów zgadza się z typem figury.

    3. Mamy niedomaganie funkcji bbox – dla okręgów (i już widać, że dla elips).

    Narzucające się „szybkie” rozwiązanie, to wstawienie w odpowiednie miejsca instrukcji if (nie switch, bo identyfikatory są typu string). Już mamy przynajmniej dwa miejsca na sieć if-ów, a to nie koniec.

    Wygląda na to, że figure potrzebuje przebudowy…