07. Множества точек 2
Пример готового проекта, который должен быть получен
в конце второй части, можно посмотреть здесь.
Fork
репозитория делать запрещается.
Панель добавления
Чтобы создать несколько выпадающих блоков, нужно добавить команду
if (!ImGui::CollapsingHeader("Background"))
return;
в метод ShowBackgroundSetting()
:
// рисование параметров цвета
void ShowBackgroundSetting() {
// если не раскрыт список `Background`
if (!ImGui::CollapsingHeader("Background"))
// заканчиваем выполнение
return;
// Инструмент выбора цвета
if (ImGui::ColorEdit3("Background color", color)) {
// код вызывается при изменении значения
// задаём цвет фона
setColor(color);
}
// конец рисование окна
}
Если нажать на треугольник, помеченный красным
То появится созданная нами панель управления цветом фона:
Теперь создадим новую функцию, рисующий блок добавляющий точку вручную.
Для начала создадим две кнопки друг за другом:
// ручное добавление элементов
void ShowAddElem() {
// если не раскрыта панель `Add Elem`
if (!ImGui::CollapsingHeader("Add Elem"))
// заканчиваем выполнение
return;
// Инструмент выбора цвета
if (ImGui::DragInt2("Coords", lastAddPosBuf)) {
// никаких действий не требуется, достаточно
// тех изменений буфера, которые imGui выполняет
// автоматически
}
// если нажата кнопка `Set 1`
if (ImGui::Button("Set 1"))
// добавляем то добавляем в список точку, принадлежащую первому множеству
points.emplace_back(Point(sf::Vector2i(lastAddPosBuf[0], lastAddPosBuf[1]), SET_1));
// если нажата кнопка `Set 2`
if (ImGui::Button("Set 2"))
// добавляем то добавляем в список точку, принадлежащую второму множеству
points.emplace_back(Point(sf::Vector2i(lastAddPosBuf[0], lastAddPosBuf[1]), SET_2));
}
И вызовем нашу функцию из главного цикла:
...
// создаём окно управления
ImGui::Begin("Control");
// рисование параметров цвета
ShowBackgroundSetting();
// ручное добавление элементов
ShowAddElem();
// конец рисования окна
ImGui::End();
...
Запустим программу:
Если окно у вас получилось слишком сжатым, потяните, за его край.
У вас должно теперь заработать добавление через кнопку.
Однако будет красивее, если кнопки множеств будут на одной горизонтали, а не вертикали. Для этого нужно сгруппировать их:
// ручное добавление элементов
void ShowAddElem() {
// если не раскрыта панель `Add Elem`
if (!ImGui::CollapsingHeader("Add Elem"))
// заканчиваем выполнение
return;
// Инструмент выбора цвета
if (ImGui::DragInt2("Coords", lastAddPosBuf)) {
// никаких действий не требуется, достаточно
// тех изменений буфера, которые imGui выполняет
// автоматически
}
// фиксируем id равный 0 для первого элемента
ImGui::PushID(0);
// если нажата кнопка `Set 1`
if (ImGui::Button("Set 1"))
// добавляем то добавляем в список точку, принадлежащую первому множеству
points.emplace_back(Point(sf::Vector2i(lastAddPosBuf[0], lastAddPosBuf[1]), SET_1));
// восстанавливаем буфер id
ImGui::PopID();
// говорим imGui, что следующий элемент нужно рисовать на той же линии
ImGui::SameLine();
// задаём id, равный одному
ImGui::PushID(1);
// если нажата кнопка `Set 2`
if (ImGui::Button("Set 2"))
// добавляем то добавляем в список точку, принадлежащую второму множеству
points.emplace_back(Point(sf::Vector2i(lastAddPosBuf[0], lastAddPosBuf[1]), SET_2));
// восстанавливаем буфер id
ImGui::PopID();
}
Теперь интерфейс добавления будет выглядеть примерно так:
Если потянуть за блок регулирования x
или y
координаты, то можно
получить отрицательные числа или числа, выходящие за видимую границу экрана.
Поэтому нужно преобразовать объявление элемента управления таким образом:
// Инструмент выбора цвета
if (ImGui::DragInt2("Coords", lastAddPosBuf, 0.5f, 0, std::min(WINDOW_SIZE_X, WINDOW_SIZE_Y))) {
// никаких действий не требуется, достаточно
// тех изменений буфера, которые imGui выполняет
// автоматически
}
Третьим аргументом добавилось значение скорости прокрутки, четвёртым - минимальное значение, пятым - максимальное.
Теперь значение будет ограничено видимой областью экрана:
Если сейчас кликнуть мышью, то значения на панели добавления не изменятся. Чтобы связать клик мышью с ними, добавьте сохранение координат клика в буфер, хранящий координаты последнего добавления:
// если событие - это клик мышью
if (event.type == sf::Event::MouseButtonPressed) {
// если мышь не обрабатывается элементами imGui
if (!ImGui::GetIO().WantCaptureMouse) {
// меняем координаты последней добавленной точки
lastAddPosBuf[0] = event.mouseButton.x;
lastAddPosBuf[1] = event.mouseButton.y;
// если левая кнопка мыши
if (event.mouseButton.button == sf::Mouse::Button::Left)
points.emplace_back(sf::Vector2i(event.mouseButton.x, event.mouseButton.y), SET_1);
else
points.emplace_back(sf::Vector2i(event.mouseButton.x, event.mouseButton.y), SET_2);
}
}
Если теперь запустить приложение и кликнуть по экрану, то и значения на панели ручного добавления изменятся:
Удалите теперь ручное добавление точек до старта главного цикла:
// задаём цвет фона
setColor(color);
points.push_back(Point(sf::Vector2i(100, 600), SET_1));
points.push_back(Point(sf::Vector2i(100, 700), SET_1));
points.push_back(Point(sf::Vector2i(200, 500), SET_2));
points.push_back(Point(sf::Vector2i(200, 700), SET_2));
// переменная таймера
sf::Clock deltaClock;
Теперь этот участок кода должен выглядеть так:
// задаём цвет фона
setColor(color);
// переменная таймера
sf::Clock deltaClock;
Теперь точки не добавляются по умолчанию, при этом ручное добавление и добавление мышью работают:
Создайте коммит manual point add works
и отправьте его на сервер.
Случайные точки
Самый простой способ порождения случайных точек - это статическая функция у структуры:
// Точка
struct Point {
// положение
sf::Vector2i pos;
// номер множества
int setNum;
// конструктор
Point(const sf::Vector2i &pos, int setNum) : pos(pos), setNum(setNum) {
}
// получить случайную точку
static Point randomPoint() {
return Point(sf::Vector2i(
rand() % WINDOW_SIZE_X,
rand() % WINDOW_SIZE_Y),
rand() % 2
);
}
};
Теперь случайную точку можно получить в любом месте такой командой:
Point p = Point::randomPoint();
Теперь напишем функцию добавления заданного числа случайных точек
// добавить заданное кол-во случайных точек
void randomize(int cnt) {
for (int i = 0; i < cnt; i++) {
points.emplace_back(Point::randomPoint());
}
}
Для указания количества случайных точек, которое нужно добавить нам понадобится буфер для хранения последней заданной величины:
// буфер кол-ва случайных точек
int lastRandoCntBuf[1] = {10};
Теперь можно прописать саму панель добавления случайных точек
// панель добавления случайных точек
void ShowRandomize() {
// если не раскрыта панель `Randomize`
if (!ImGui::CollapsingHeader("Randomize"))
// заканчиваем выполнение
return;
// первый элемент в строке
ImGui::PushID(0);
// Инструмент выбора кол-ва
if (ImGui::DragInt("Count", lastRandoCntBuf, 0.1, 0, 100)) {
}
// восстанавливаем буффер id
ImGui::PopID();
// следующий элемент будет на той же строчке
ImGui::SameLine();
// второй элемент
ImGui::PushID(1);
// создаём кнопку добавления
if (ImGui::Button("Add"))
// по клику добавляем заданное число случайных точек
randomize(lastRandoCntBuf[0]);
ImGui::PopID();
}
И вызвать её внутри главного цикла:
...
// создаём окно управления
ImGui::Begin("Control");
// рисование параметров цвета
ShowBackgroundSetting();
// ручное добавление элементов
ShowAddElem();
// добавление случайных точек
ShowRandomize();
// конец рисования окна
ImGui::End();
...
Случайное добавление должно заработать:
Создайте новый коммит random add works
и отправьте его на сервер
Работа с файлами
Чтобы работать с файлами, добавим сначала две константы путей до файла ввода и вывода:
// путь к файлу вывода
static const char OUTPUT_PATH[255] = "D:/Programming/Files/out.txt";
// путь к файлу ввода
static const char INPUT_PATH[255] = "D:/Programming/Files/in.txt";
Все папки должны быть созданы. Иначе программа просто не создаст файл. Вам нужно указать пути до доступной вам папки. Лучше всего создайте где-нибудь новую.
Чтобы работать с файлами, подключим библиотеку <fstream>
:
#include <fstream>
Теперь нам нужно написать функции чтения точек из файла и записи в него:
// загрузка из файла
void loadFromFile() {
// открываем поток данных для чтения из файла
std::ifstream input(INPUT_PATH);
// очищаем массив точек
points.clear();
// пока не достигнут конец файла
while (!input.eof()) {
int x, y, s;
input >> x; // читаем x координату
input >> y; // читаем y координату
input >> s; // читаем номер множества
// добавляем в динамический массив точку на основе прочитанных данных
points.emplace_back(Point(sf::Vector2i(x, y), s));
}
// закрываем файл
input.close();
}
// запись в файл
void saveToFile() {
// открываем поток данных для записи в файл
std::ofstream output(OUTPUT_PATH);
// перебираем точки
for (auto point: points) {
// выводим через пробел построчно: x-координату, y-координату и номер множества
output << point.pos.x << " " << point.pos.y << " " << point.setNum << std::endl;
}
// закрываем
output.close();
}
Теперь напишем функцию, создающую панель работы с файлами:
// работа с файлами
void ShowFiles() {
// если не раскрыта панель `Files`
if (!ImGui::CollapsingHeader("Files"))
// заканчиваем выполнение
return;
// первый элемент в линии
ImGui::PushID(0);
// создаём кнопку загрузки
if (ImGui::Button("Load")) {
// загружаем данные из файла
loadFromFile();
}
// восстанавливаем буфер id
ImGui::PopID();
// следующий элемент будет на той же строчке
ImGui::SameLine();
// второй элемент
ImGui::PushID(1);
// создаём кнопку сохранения
if (ImGui::Button("Save")) {
// сохраняем задачу в файл
saveToFile();
}
// восстанавливаем буфер id
ImGui::PopID();
}
и вызовем её в главном цикле:
...
ImGui::Begin("Control");
// рисование параметров цвета
ShowBackgroundSetting();
// ручное добавление элементов
ShowAddElem();
// добавление случайных точек
ShowRandomize();
// работа с файлами
ShowFiles();
// конец рисования окна
ImGui::End();
...
Запустите программу и создайте 15 случайных точек, а потом нажмите на кнопку сохранения:
По указанному вами пути появится соответствующий файл
Если его открыть, то в нём будет список сгенерированных случайных точек из приложения
Теперь скопируйте этот файл
и поменяйте его название на то, которое вы указали в пути файла ввода. У меня - это in.txt
Теперь заново запустите программу и нажмите на кнопку загрузки. Должны появиться те же точки, что и при прошлом запуске программы
Создайте новый коммит files ready
и отправьте его на сервер.
Решение задачи
Для решения задачи и её отображения будем использовать следующий подход:
если точки совпадают по координатам, то просто меняем их множества на SET_CROSSED
,
потом проходим второй раз по массиву точек и все множества не SET_CROSSED
меняем
на SET_SINGLE
:
// решение задачи
void solve() {
// у совпадающих по координатам точек меняем множество на SET_CROSSED
for (int i = 0; i < points.size(); i++)
for (int j = i + 1; j < points.size(); j++)
if (points[i].pos == points[j].pos)
points[i].setNum = points[j].setNum = SET_CROSSED;
// у всех точек, у которых множество не SET_CROSSED, задаём множество SET_SINGLE
for (auto &point: points)
if (point.setNum != SET_CROSSED)
point.setNum = SET_SINGLE;
}
Теперь создадим функцию рисования панели решения. В ней будет две кнопки: решить задачу и очистить
// решение задачи
void ShowSolve() {
// если не раскрыта панель `Solve`
if (!ImGui::CollapsingHeader("Solve"))
return;
// первый элемент в линии
ImGui::PushID(0);
// создаём кнопку решения
if (ImGui::Button("Solve")) {
solve();
}
// восстанавливаем буфер id
ImGui::PopID();
// следующий элемент будет на той же строчке
ImGui::SameLine();
// второй элемент
ImGui::PushID(1);
// создаём кнопку очистки
if (ImGui::Button("Clear")) {
// удаляем все точки
points.clear();
}
// восстанавливаем буфер id
ImGui::PopID();
}
и вызовем её в главном цикле:
// создаём окно управления
ImGui::Begin("Control");
// рисование параметров цвета
ShowBackgroundSetting();
// ручное добавление элементов
ShowAddElem();
// добавление случайных точек
ShowRandomize();
// работа с файлами
ShowFiles();
// решение задачи
ShowSolve();
// конец рисования окна
ImGui::End();
Теперь нам осталось только добавить ещё два цвета для множеств из решения задачи:
// рисование задачи на невидимом окне во всё окно приложения
void RenderTask() {
// задаём левый верхний край невидимого окна
ImGui::SetNextWindowPos(ImVec2(0, 0));
// задаём правый нижний край невидимого окна
ImGui::SetNextWindowSize(ImVec2(WINDOW_SIZE_X, WINDOW_SIZE_Y));
// создаём невидимое окно
ImGui::Begin("Overlay", nullptr,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoBackground);
// получаем список примитивов, которые будут нарисованы
auto pDrawList = ImGui::GetWindowDrawList();
// перебираем точки из динамического массива точек
for (auto point: points) {
ImColor clr;
// Устанавливаем цвет по номеру множества
switch (point.setNum) {
case SET_1:
clr = ImColor(200, 100, 150);
break;
case SET_2:
clr = ImColor(100, 200, 150);
break;
case SET_CROSSED:
clr = ImColor(100, 150, 200);
break;
case SET_SINGLE:
clr = ImColor(150, 200, 100);
break;
}
// добавляем в список рисования круг
pDrawList->AddCircleFilled(
sf::Vector2i(point.pos.x, point.pos.y),
3,
clr,
20
);
}
// заканчиваем рисование окна
ImGui::End();
}
Запустите программу, сгенерируйте случайные точки, потом несколько раз кликните сначала одной кнопкой мыши, потом - другой. Это позволит создать точки с одними и теми же координатами:
После этого нажмите на кнопку Solve
:
Точки должны окраситься в два новых цвета: множество пересечения и множество разности.
Если теперь нажать на Clear
, то все точки удалятся:
Создайте новый коммит solve and clear work
и отправьте его на сервер
Панель Help
Нам осталось создать панель помощи. Для этого напишем метод порождения строк подсказки:
// помощь
void ShowHelp() {
if (!ImGui::CollapsingHeader("Help"))
return;
// первый заголовок
ImGui::Text("ABOUT THIS DEMO:");
// первый элемент списка
ImGui::BulletText("Author Ivanov Ivan 10-1");
// второй элемент списка
ImGui::BulletText("Powered by SFML+ImGui");
// разделитель
ImGui::Separator();
// второй заголовок
ImGui::Text("TASK:");
// первый элемент списка(многострочный)
ImGui::BulletText("Two sets of points are given\n"
"in an integer two-dimensional space.\n"
"It is required to build intersections and\n"
"the difference between these sets.");
// разделитель
ImGui::Separator();
}
Добавляем вызов созданной нами функции в главный цикл:
// создаём окно управления
ImGui::Begin("Control");
// рисование параметров цвета
ShowBackgroundSetting();
// ручное добавление элементов
ShowAddElem();
// добавление случайных точек
ShowRandomize();
// работа с файлами
ShowFiles();
// решение задачи
ShowSolve();
// помощь
ShowHelp();
// конец рисования окна
ImGui::End();
Если теперь запустить приложение, получим:
Вам нужно поменять данные автора на свои фамилию, имя и класс.
Создайте новый коммит complete
и отправьте его на сервер.
Пример готового проекта можно посмотреть здесь.
Fork
репозитория делать запрещается.
Задание
Вам необходимо создать свой репозиторий, в котором добавлены следующие коммиты (для каждого сначала указывается название, потом требования):
manual point add works
- приложение, в которое добавлено ручное добавление точек с помощью соответствующей панелиrandom add works
- приложение, в которое добавлено ручное добавление случайных точек с помощью соответствующей панелиfiles ready
- приложение, в которое добавлена панель для работы с файламиsolve and clear work
- приложение, в которое добавлена панель для решения задачи и очистки данныхcomplete
- приложение, в которое добавлена панельHelp
Ссылку на github-репозиторий необходимо отправить в поле ввода задания на сайте mdl.