Главная страница
Навигация по странице:

  • 10. Построение графиков функций на дискретных устройствах отображения информации

  • прогинг. Гущин_SDL. Аа нн


    Скачать 1.04 Mb.
    НазваниеАа нн
    Анкорпрогинг
    Дата13.10.2019
    Размер1.04 Mb.
    Формат файлаpdf
    Имя файлаГущин_SDL.pdf
    ТипУчебное пособие
    #89893
    страница6 из 7
    1   2   3   4   5   6   7
    9. Обработка событий средствами библиотеки SDL
    Подсистема обработки событий в библиотеке SDL достаточно сложна, поскольку ее основная задача – объединить потребности разработчиков мультимедийных приложений (особенно игр) в быстром отклике на действия пользователя с возможностями сов-ременных многозадачных ОС по обработке событий от множества независимых источников, причем механизмы такой обработки могут существенно различаться на различных платформах. При этом библиотека SDL должна еще сделать логически идентичную обработку событий на различных платформах существенно более простой и единообразной для программиста, чем непосредственное использование возможностей
    ОС. Для обеспечения поставленных задач библиотека SDL предоставляет ряд механизмов по обработке событий, возникающих «извне» программы, таких как нажатие клавиш клавиатуры, перемещение мыши или нажатие клавиш на ней, изменение размера окна программы или его закрытие средствами ОС и т.д.
    Основной способ обработки событий – использование внут-ренней очереди событий программы. Дополнительными спосо-бами являются непосредственный опрос текущего состояния окру-жения с последующей проверкой состояния отдельных устройств ввода: клавиатуры, мыши, джойстиков и т.д. в сочетании с фильт-рацией отдельных типов событий. Еще один дополнительный способ – установка специальных функций фильтров событий, определяющих, должно ли то или иное конкретное событие обрабатываться программой в общем порядке или же отбра-сываться после обработки в данной функции. Активизация всех способов обработки событий средствами библиотеки SDL происходит при инициализации графической подсистемы
    SDL, т.е. при наличии в аргументе функции SDL_Init флага SDL_INIT_VIDEO.
    Рассмотрим основной способ обработки событий с исполь-зованием внутренней очереди библиотеки SDL. Для получения событий из очереди в порядке их поступления служат функции
    SDL_PollEvent и SDL_WaitEvent: extern DECLSPEC int SDLCALL
    SDL_PollEvent(SDL_Event *event); extern DECLSPEC int SDLCALL
    SDL_WaitEvent(SDL_Event *event);
    Функция SDL_PollEvent вызывает функцию принудительного обновления очереди событий
    (однократный опрос возможных источников событий – функция PumpEvents, не имеющая аргумен- тов и не возвращающая никакого значения), затем проверяет, имеется ли в очереди хотя бы одно событие любого типа, ожидающее обработки. Если очередь пуста, функция SDL_PollEvent возвращает 0. Если очередь не пуста и параметр event не равен NULL, то очередное (первое) событие извлекается из очереди и сохраняется в объединении SDL_Event, на которое указывает параметр event, при этом функция SDL_PollEvent возвращает 1. Если очередь не пуста и параметр event равен NULL, то событие остается в очереди без изменений, а функция SDL_PollEvent также возвращает 1.
    Функция SDL_WaitEvent отличается тем, что при отсутствии событий в очереди не возвращает управление вызвавшей функции, а ожидает наступления хотя бы одного события, для чего перио- дически (с интервалом не менее 10 мс, возможно больше, в зависи-мости от особенностей конкретной операционной системы) вызы-вает функцию SDL_PumpEvents с последующим анализом

    47 состоя-ния очереди. При появлении в очереди события функция завершает работу и возвращает 1.
    При обнаружении ошибок в процессе рабо-ты с очередью событий функция возвращает 0. Параметр event обрабатывается аналогично функции SDL_PollEvent.
    Важной особенностью функций SDL_PollEvent и SDL_WaitEvent является требование использования их исклю-чительно в том же потоке исполнения (для многопоточных ОС), в котором была вызвана функция установки видеорежима, поскольку в них вызывается функция
    SDL_PumpEvents, получаю-щая события и внутренние события видеоподсистемы. Для работы с очередью сообщений не только из данного потока, но и из других потоков в программе (при их наличии) служит функция SDL_PeepEvents, прототип и описание параметров (на английском языке) которой находятся в заголовочном файле SDL_events.h. В нем же находятся прототипы и описания большинства прочих функций работы с сообщениями, а также используемых структур данных. При этом для работы с подсистемой обработки событий (как и с остальными подсистемами) подключение к программе данного заголовочного файла (и других заголовочных файлов с прототипами функций подсистем SDL) не требуется. Все необходимые директивы препроцессора будут выполнены при подключении единственного общего заголовочного файла
    SDL.h.
    Для сохранения каждого полученного сообщения в общем виде и последующего анализа используется объединение типа SDL_Event: typedef union SDL_Event {
    Uint8 type;
    SDL_ActiveEvent active;
    SDL_KeyboardEvent key;
    SDL_MouseMotionEvent motion;
    SDL_MouseButtonEvent button;
    SDL_JoyAxisEvent jaxis;
    SDL_JoyBallEvent jball;
    SDL_JoyHatEvent jhat;
    SDL_JoyButtonEvent jbutton;
    SDL_ResizeEvent resize;
    SDL_ExposeEvent expose;
    SDL_QuitEvent quit;
    SDL_UserEvent user;
    SDL_SysWMEvent syswm;
    } SDL_Event;
    Поле type представляет тип события, заданный целым числом без знака, представленным одним байтом. Для символического представления констант, описывающих разные типы событий, в SDL используется перечисление SDL_EventType. Рассмотрим основные типы событий, обозначая их здесь и далее соответ-ствующими константами из данного перечисления (т.е.
    «событие SDL_KEYDOWN» – событие, при котором поле type объединения SDL_Event имеет значение, равное значению константы SDL_KEYDOWN):
    SDL_ACTIVEEVENT – приложение стало активным (с кото-рым работает пользователь) или перестало быть активным;
    SDL_KEYDOWN – нажата клавиша на клавиатуре;
    SDL_KEYUP – отпущена клавиша на клавиатуре;
    SDL_MOUSEMOTION – перемещена мышь;
    SDL_MOUSEBUTTONDOWN – нажата клавиша мыши;
    SDL_MOUSEBUTTONUP – отпущена клавиша мыши;
    SDL_QUIT – запрос выхода из программы по действию поль-зователя (например, по нажатию мышью системной кнопки закры-тия окна);
    SDL_VIDEORESIZE – пользователь изменил размер окна и требуется изменение видеорежима;
    SDL_VIDEOEXPOSE – необходимо перерисовать экран или окно.
    Для каждого типа события или группы событий в объеди-нении SDL_Event имеется отдельное поле – структура, содержа-щая тип события (это поле type, имеющее тот же тип и совпадаю-щее в памяти с полем type самого объединения), а также, для неко-торых типов событий, поля параметров, требующихся для обра-ботки таких событий. Рассмотрим подробнее данные структуры для некоторых типов событий или их групп.

    48
    При обработке событий SDL_KEYDOWN и SDL_KEYUP используется структура
    SDL_KeyboardEvent следующего вида: typedef struct SDL_KeyboardEvent {
    Uint8 type;
    Uint8 which;
    Uint8 state;
    SDL_keysym keysym;
    } SDL_KeyboardEvent;
    Поле type указывает на тип события – SDL_KEYDOWN или SDL_KEYUP. Поле which содержит индекс устройства клавиа-туры, на которой была нажата или отпущена клавиша. Поле state указывает на состояние клавиши – она нажата (SDL_PRESSED) или отпущена
    (SDL_RELEASED). Поле keysym содержит инфор-мацию о конкретной нажатой клавише и, возможно, о преобра-зовании ее в конкретный символ UNICODE. Оно имеет тип SDL_keysym, объявленный в заголовочном файле SDL_keyboard.h так: typedef struct SDL_keysym {
    Uint8 scancode;
    SDLKey sym;
    SDLMod mod;
    Uint16 unicode;
    } SDL_keysym;
    Поле scancode содержит зависящий от аппаратуры скан-код клавиши и, как правило, не должно использоваться. Если данная платформа не поддерживает скан-коды, его значение равно нулю. Поле sym содержит значение виртуального кода клавиши, одина-ковое на различных платформах для одинаковых клавиш неза-висимо от фактических скан-кодов или способов ввода. Тип SDLKey – это объявленное в заголовочном файле SDL_keysym.h перечисление, содержащие символьные константы для исполь-зуемых в библиотеке SDL виртуальных кодов клавиш (или, кратко,
    виртуальных клавиш). Значения части из них совпадают с кодами символов или строчных букв в кодировке ASCII (в диапазоне от 8 до 127), однако общее число различимых клавиш составляет более 300, включая клавиши «международных клавиатур» или специ-фические функциональные клавиши некоторых типов компьютеров. Поле mod содержит состояние модификаторов на момент нажатия клавиши. Тип SDLMod – это объявленное также в заголовочном файле SDL_keysym.h перечисление, содержащее символьные константы для битовых масок состояния клавиш модификаторов. Значение данного поля является объединением (операцией побитового ИЛИ) значений для всех одновременно установленных модификаторов.
    Поле unicode, если оно отлично от нуля, содержит результат преобразования нажатия клавиши в конкретный двухбайтовый символ UNICODE, но поскольку подобные преобразования являются достаточно ресурсоемкими, то по умолчанию они отключены. Для управления режимом трансляции виртуальных клавиш в символы UNICODE служит объявленная в SDL_keyboard.h функция SDL_EnableUNICODE: extern DECLSPEC int SDLCALL SDL_EnableUNICODE(int enable);
    Если значение параметра enable равно 1, разрешается трансля-ция в UNICODE, если равно 0, трансляция запрещается, если равно –1, состояние трансляции остается без изменений. Функция возвращает предыдущее значение состояния трансляции (0 или 1).
    При обработке события SDL_VIDEORESIZE используется структура SDL_ResizeEvent: typedef struct SDL_ResizeEvent {
    Uint8 type; /**< SDL_VIDEORESIZE */ int w;
    /**< New width */ int h;
    /**< New height */
    } SDL_ResizeEvent;
    Поле type указывает на тип события и должно быть равно SDL_VIDEORESIZE. Поле w указывает новую ширину окна, поле h – новую высоту окна. При обнаружении такого события прог-рамма обязана установить новый видеорежим (размер окна) с соответствующей шириной и

    49 высотой. Событие возникает только в том случае, если при задании видеорежима (создании графи- ческого окна) был установлен флаг «окно изменяемого размера». Если установить точно заданные размеры невозможно, необхо-димо установить ближайшие осмысленные для приложения разме- ры окна (например, для сохранения пропорций изображения).
    Для обработки события SDL_VIDEOEXPOSE используется структура SDL_ExposeEvent, содержащая только поле type, зна-чение которого должно быть равно SDL_VIDEOEXPOSE. При возникновении данного события программа должна перерисовать весь экран или все окно. Если же при этом необходимо только частичное обновление окна, то у соответствующей ему поверх- ности будет установлено значение компонента clip_rect, отличное от размеров самой поверхности.
    При этом если производить вывод за пределы данного прямоугольника, то многие функции
    (напри-мер, SDL_BlitSurface) не изменяют состояние поверхности за его пределами для ускорения работы.
    Для обработки события SDL_QUIT используется структура SDL_QuitEvent, содержащая только поле type, значение которого должно быть равно SDL_QUIT. Это событие возникает, если поль-зователь пытается закрыть программу средствами операционной системы (например, используя специальные сочетания клавиш или системную кнопку закрытия окна). При обработке данного собы-тия можно, например, запросить пользователя, действительно ли он хочет завершить работу с программой и следует ли сохранять результаты работы или просто корректно завершить ее с освобож-дением ресурсов и выгрузкой библиотек.
    В заголовочном файле SDL_events.h описаны и проком-ментированы также структуры для обработки событий от мыши (причем возможно от нескольких), джойстиков и т.д.
    Рассмотрим два примера обработки событий. В обоих приме-рах рассматривается только тот фрагмент функции main, который отвечает за обработку событий и завершение программы, и не рассматривается инициализация видеорежима с созданием окна для вывода графики.
    Первый пример для SDL_WaitEvent демонстрирует отобра-жение в окне некоторого статического изображения функцией draw_picture с единственным параметром SDL_Surface* who_draw, определяющим, на какую поверхность производится вывод. Выход из программы осуществляется при наступлении события SDL_QUIT или нажатии клавиши Esc. Также, при необходимости, производится перерисовка изображения при наступлении события
    SDL_VIDEOEXPOSE.
    /* в соответствующем месте объявляем указатель на поверхность: */
    SDL_Surface *screen;
    SDL_Event event;
    /* инициализация библиотеки и установка видеорежима*/ if (!screen)
    { fprintf(stderr,"SDL mode failed: %s\n",SDL_GetError());
    SDL_Quit(); return 1; /* Выход с одним кодом ошибки */
    } draw_picture(screen);
    /* Принудительное обновление окна программы */
    SDL_Flip(screen);
    /* цикл ожидания событий */ while(SDL_WaitEvent(&event))
    { if(event.type == SDL_QUIT ||
    (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE))
    {
    SDL_Quit(); return 0; /* пусть 0 – нормальное завершение*/
    } if(event.type == SDL_VIDEOEXPOSE) draw_picture(screen);
    } fprintf(stderr,"WaitEvent failed: %s\n",SDL_GetError());
    SDL_Quit(); return 2; /* Выход с другим кодом ошибки */

    50
    Второй пример для SDL_PollEvent – рисование прямоуголь-ника, перемещающегося по экрану вверх-вниз при нажатии на клавиатуре стрелок управления курсором, как отдельных, так и на цифровой клавиатуре. Если же никаких действий не происходит, он сам перемещается слева направо и обратно. Но пределы окна он покинуть не может: при перемещении по вертикали он упирается в край, а при перемещении по горизонтали «ударяется» о край и изменяет направление движения.
    /* в соответствующем месте объявляем указатель на поверхность: */
    SDL_Surface *screen;
    SDL_Event event;
    SDL_Rect r;
    /* сам прямоугольник*/
    SDL_Rect r_new;
    /* новое положение прямоугольника*/
    Sint16 leftright = 1;
    /* слева направо = 1, справа налево =-1 */
    Sint16 max_x, max_y; int nextstep = 1; /* для цикла обработки сообщений */
    /* инициализация библиотеки и установка видеорежима */ if (!screen)
    { fprintf(stderr,"SDL mode failed: %s\n",SDL_GetError());
    SDL_Quit(); return 1; /* Выход с одним кодом ошибки */
    }
    /* В объявленных ранее переменных Sint16 max_x и Sint16 max_y записаны фактическая ширина и высота области, в которой перемещается прямоугольник после установки видеорежима – фрагмент опущен */
    /* Первоначальное рисование по центру экрана синего прямоугольника с шириной 40 и высотой 20 пикселей */ r.x = max_x / 2 - 20; r.y = max_y / 2 - 10; r.w = 40; r.h = 20; r_new = r;
    SDL_FillRect(screen, &r, 0x000000FF); /* ярко-синий */
    /* цикл перерисовки и обработки событий */ while(nextstep)
    { if(SDL_PollEvent(&event))
    { if(event.type == SDL_QUIT ||
    ( event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE)) nextstep = 0; /* Выход */ if(event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_DOWN)
    { /* Вниз*/ r_new.y = (r.y + r.h) < max_y ? r.y +1 : r.y;
    } if(event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_UP)
    { /* Вверх*/ r_new.y = r.y > 0 ? r.y -1 : r.y;
    }
    }
    /* расчет перемещения по горизонтали */ r_new.x = r.x + 1*leftright; if(r_new.x < 0 || r_new.x + r.w > max_x)
    { /* отскок от стенки */ leftright = -leftright;
    }
    /* собственно перерисовка: */
    SDL_LockSurface(screen);
    SDL_FillRect(screen, &r, 0x00000000); /* стерли черным */ r = r_new; /* используем новые координаты */

    51
    SDL_FillRect(screen, &r, 0x000000FF); /* ярко-синий */
    SDL_UnlockSurface(screen);
    SDL_UpdateRect(screen,0,0,max_x,max_y);
    /* Задержка на опрос событий составляет около 10 мс или более, в зависимости от производительности компьютера. При необходимости возможна дополнительная задержка */
    }
    SDL_Quit(); return 0; /* Нормальное завершение */
    Аналогичным образом могут обрабатываться и иные события.
    10. Построение графиков функций на дискретных устройствах отображения информации
    С точки зрения математики, график функции y = f(x), где x,y

    R, есть множество точек (x
    мат.гр.
    ,
    y
    мат.гр.
    ) на плоскости (точек, принадлежащих множеству R
    2
    ), таких, что их координаты удовлетворяют равенствам
    x
    мат.гр.
    = x,
    y
    мат.гр.
    = f(x
    мат.гр.
    ) = y = f(x).
    При традиционном отображении графика функции на бумаге из-за невозможности нарисовать точку бесконечно малого размера происходит, во-первых, замена множества действительных чисел множеством рациональных (исходя из точности применяемых средств измерения), а во-вторых, представление каждой точки множеством точек, которое можно приближенно считать кругом с некоторым конечным диаметром, соответствующим минимальной толщине линии, оставляемой используемыми средствами рисо-вания (для самых распространенных в настоящее время – от 0,1 до
    1 мм). При этом направление осей координат традиционно выбирается для оси OX слева направо, для оси OY – снизу вверх, а расположение точки (0, 0) выбирается исходя из вида функции.
    Расположение точки (1, 1), т.е. выбор единицы измерения или масштаба графика, определяется в зависимости от имеющихся средств измерения и требуемой точности графика. Очевидно, что на некотором листе бумаги фиксированного размера A мм по горизонтали (вдоль оси OX) на B мм по вертикали (вдоль оси OY) при условии, что значению x = 1 соответствует горизонтальная черта длиной MX, а значению y = 1 – вертикальная черта длиной MY мм, может быть полностью отображена лишь такая функция, область определения которой полностью находится внутри отрезка
    [x
    min
    , x
    max
    ], а область значений – внутри отрезка [y
    min
    , y
    max
    ], таких что |x
    max
    ×MX x
    min
    ×MX| ≤ А и
    |y
    max
    ×MY y
    min
    ×MY| ≤ B. При этом MX и MY играют роль, во-первых, масштабных коэффициентов, а во-вторых – коэффициентов преобразования единиц измерения из абстрактных математических
    «единиц» в конкретные единицы измерения длины, в данном случае – в миллиметры. Поэтому коэффициенты MX и MY имеют размерность «единица длины/единицу» или, если считать
    «математическую единицу» безразмерной, то просто «единица длины», в данном случае – миллиметры. В случае если область определения и область значений функций полностью находятся внутри некоторых отрезков конечной длины, то график такой функции путем выбора соответствующих коэффициентов MX и MY всегда можно раз-местить на листе заданного размера A на B. Для максимально полного использования листа при вышеприведенных парамет- рах функции коэффициенты будут вычисляться по формулам
    MX = А/(|x
    max
    – x
    min
    |)и MY = B/(|y
    max
    – y
    min
    |). Разумеется, при этом возможно как искажение пропорций
    (различный масштаб по осям), так и просто существенное искажение вида функции – как правило, из-за слияния соседних точек, связанного с физической невоз-можностью нарисовать точку не иначе как некоторое пятно вполне конечного размера. Если же область определения или область значений функции неограниченна, то физически отобразить можно только некоторый интересующий нас фрагмент графика, обозначив тем или иным способом продолжение изменения аргумента или значения за пределами отображенной части. Как правило, для этого используют либо некоторое продолжение графика после крайних явно обозначенных точек, либо изображение асимптот.
    При построении графика функции на экране компьютера или ином дискретном устройстве отображения дополнительно к вышеприведенным рассуждениям имеются и более жесткие огра- ничения. Во-первых, в силу логической дискретности устройства вывода (независимо от физической реализации), т.е. наличия конечного числа адресуемых по горизонтали и вертикали неза-висимых элементов изображения (пикселей, точек и т.п.), тре-буется отображение как множества аргументов, так и множества значений функции на конечное подмножество целых

    52 чисел (а чаще всего – целых неотрицательных чисел). Во-вторых, во многих случаях программные средства отображения (доступа к соответ-ствующим аппаратным средствам компьютера) используют отли-чающиеся от математических направления осей координат.
    Например, рассмотренная библиотека SDL_draw, как и многие другие библиотеки отображения, предполагает, что верхний левый угол экрана (или окна, или конкретной поверхности отображения) имеет координаты (0, 0), а правый нижний – (width–1, height–1), где width – число доступных пикселей по ширине, а height – по высоте. Таким образом, ось OX направлена традиционно слева направо, а ось OY – сверху вниз. Сочетание этих двух факторов приводит к необходимости использовать явное округление и преобразование типов, а также явное указание экранных координат (x
    0экр
    , y
    0экр
    ) в пикселях для точки (0, 0) на математической плоскости при вычислении экранных координат каждой точки графика. Также меняется и единица измерения коэффициентов
    MX и MY на «пиксели/единица» или «пиксели». Для обеспечения точности все округления следует производить только после того, как из мате-матических координат получено экранное (в пикселях) смещение отображаемой точки относительно положения на экране начала математических координат, и уже это смещение использовать для вычисления собственно экранных координат отображаемой точки. При ранее приведенных ограничениях на область определения и область значения функции, значениях ширины экрана width и высоты height пикселей, с приведенным выше направлением экранных осей координат потребуется вычислить следующие коэффициенты и экранные координаты точки начала координат для того, чтобы график функции полностью поместился на экране:
    MX = width/(|x
    max
    – x
    min
    |) пикселей, MY = height/(|y
    max
    – y
    min
    |) пикселей,
    x
    0экр
    = floor(– x
    min
    ×MX) пикселей, y
    0экр
    = floor(height + y
    min
    ×MY) пикселей.
    Для единообразия результатов использована функция floor(), возвращающая ближайшее целое число, не превосходящее аргумент.
    С использованием рассчитанных коэффициентов экранные координаты (x
    экр
    , y
    экр
    ) каждой точки (x, y) на графике функции можно будет вычислить следующим образом:
    x
    экр
    = x
    0экр
    + floor(x×MX) пикселей, y
    экр
    = y
    0экр
    – floor(y
    min
    ×MY) пикселей.
    Поскольку график отображается на дискретном устройстве вывода, то очевидно, что имеет смысл рассчитывать и отображать некоторое минимальное количество точек, достаточное для вос- приятия полученного изображения как графика. В качестве ниж-ней границы числа точек можно рассмотреть width – «исполь-зование каждого пикселя». Тогда точки рассчитываются от x
    min до
    x
    max с шагом ∆x = (|x
    max
    – x
    min
    |)/width = MX
    -1
    . Однако при этом часто для точек непрерывной функции, x
    экр которых различаются на единицу, координаты y
    экр различаются на значительную величину, что на экране выглядит как разбросанные отдельные точки, не похожие на график.
    Одним из способов устранения данного недостатка является увеличение числа рассчитываемых и отображаемых точек (когда для нескольких близко расположенных рассчитанных точек совпа- дают значения x
    экр
    , а координаты y
    экр различаются незначительно). Например, выбирается шаг
    ∆x = (|x
    max
    – x
    min
    |)/(10×width) или
    ∆x = (|x
    max
    – x
    min
    |)/(100×width) и т.п. Основной недостаток данного способа – существенное увеличение вычислительных затрат, а также пропорциональное увеличение числа относительно медлен-ных в графическом режиме операций ввода-вывода по отображе-нию большого числа соседних (часто совпадающих) точек. При этом некоторые функции, имеющие существенное изменение пове-дения (такие как экспонента, логарифм или гипербола), для обеспе-чения визуального восприятия могут требовать расчета до 100 и более точек между двумя соседними пикселями по горизонтали.
    Альтернативой построению графиков функций по точкам является построение графика приближенной кусочно-линейной функции, представляющего собой ломаную линию, состоящую из отрезков, соединяющих рассчитанные точки исходной функции. При этом построение самих отрезков между точками осуществля-ется имеющимися средствами библиотек рисования изображений. Следует заметить, что, в силу дискретности устройств отображения, даже при поточечном рисовании графика также фактически получается график некоторой приближенной функции, зато про-изводительность второго способа существенно выше. Как правило, между точками, экранные координаты которых различаются по горизонтали на 1 пиксель, строится от 1 до
    10 отрезков, поскольку дальнейшее увеличение их числа практически не будет влиять на восприятие графика.

    53
    Примеры программ построения графиков функций, написанные на языке С с использованием библиотеки SDL, приведены в приложении 3.
    П Р И Л О Ж Е Н И Е 1
    1   2   3   4   5   6   7


    написать администратору сайта