Всем привет, в этой статье пойдёт речь о любопытных экспериментах с С++ и 3D графикой. Будем открывать свою собственную кондитерскую-программиста. Bon Appetit!   Для начала давайте немного пробежимся по структуре программ на С++. Как только мы с Вами создадим проект на С++ в среде VisualStudio, нас встретит вот такой вот код: Давайте немного проясним, что же эти «странные» строчки означают? Для начала разберёмся с #include <iostream> - это инициализация заголовочного файла iostream с классами, функциями и переменными для организации ввода-вывода в языке программирования C++. Он включён в стандартную библиотеку C++. Название образовано от Input/Output Stream («поток ввода-вывода»). В языке C++ и его предшественнике, языке программирования Си, нет встроенной поддержки ввода-вывода, вместо этого используется библиотека функций. iostream управляет вводом-выводом. Библиотека использует объекты cin, cout, cerr и clog для передачи информации и из стандартных потоков ввода, вывода, ошибок без буферизации и ошибок с буферизацией соответственно. Являясь частью стандартной библиотеки C++, эти объекты также являются частью стандартного пространства имён — std. Ну всё, теперь мы разобрались с первой строчкой! Получается, что iostream – библиотека, которая помогает нам вводить и выводить данные, этого нам будет достаточно. Далее идёт объявление пространства имён using namespace std, что же это такое? Интернет нам даёт следующее определение: Пространство имен — это декларативная область, в рамках которой определяются различные идентификаторы (имена типов, функций, переменных, и т. д.). Пространства имен используются для организации кода в виде логических групп и с целью избежания конфликтов имен, которые могут возникнуть, особенно в таких случаях, когда база кода включает несколько библиотек. Все идентификаторы в пределах пространства имен доступны друг другу без уточнения. Идентификаторы за пределами пространства имен могут обращаться к членам с помощью полного имени для каждого идентификатора, например std::cin или std::cout для одного идентификатора using std::string, для всех идентификаторов в пространстве имен using namespace std;. Код в файлах заголовков всегда должен содержать полное имя в пространстве имен. Получается, что, если не вдаваться в подробности и сложную терминологию, пространство имён (using namespace std) – это наш универсальный помощник, который помогает преобразовать длинные и сложные конструкции вроде std::cin >> a в простую конструкцию cin >> a, а для этого всего-то надо прописать в начале программы, после объявления библиотек, одну строчку: using namespace std – нашу палочку-выручалочку, пространство имён std. Теперь перейдём к телу программы, а именно к функции int main() – это самая главная функция в нашей программе. Даже её название переводится с английского на русский как «главная», именно с неё и будет начинаться выполняться код, который мы будем прописывать. (Небольшое замечание: в программе может быть великое множество функций, но всегда присутствует главная функция main, где все эти функции объявляются, это своеобразная “стартовая линия” нашей программы, отправная точка маршрута.) Тип у функции может быть разный, например int или float, char или double и т.д., но все эти типы объединяет одно, они возвращают какое-то значение после своего вызова, за исключением void – оно ничего не возвращает. Вы также могли заметить внутри тела функции (внутри фигурных скобок: { }) строчкуcout << “Hello world!”, здесь мы как раз можем наблюдать яркий пример применения пространства имён std, поэтому вместо std::cout << “Hello world!” мы пишем cout << “Hello world!", это ли не магия? В данном случае команда cout отвечает за вывод в консоль фразы, которая написана в “ “, а именно фразу «Hello world!», что означает «Привет мир!». Важно отметить, что в конце таких команд ставится точка с запятой (;), что означает для компилятора конец команды. Итак, мы разобрались с базовой конструкцией программы, теперь перейдём к чуть более сложным вещам, а именно к типам данных и их размерам. Переменная имеет определенный тип. И этот тип определяет, какие значения может иметь переменная и сколько байт в памяти она будет занимать: void: тип без значения int: представляет целое число. В зависимости от архитектуры процессора может занимать 2 байта (16 бит) или 4 байта (32 бита). Диапазон предельных значений соответственно также может варьироваться от –32768 до 32767 (при 2 байтах) или от −2 147 483 648 до 2 147 483 647 (при 4 байтах). float: представляет вещественное число с плавающей точкой в диапазоне +/- 3.4E-38 до 3.4E+38. В памяти занимает 4 байта (32 бита) double: представляет вещественное число двойной точности с плавающей точкой в диапазоне +/- 1.7E-308 до 1.7E+308. В памяти занимает 8 байт (64 бита) char: представляет один символ. Занимает в памяти 1 байт (8 бит). Может хранить любое значение из диапазона от -128 до 127 и т.д. Совсем забыл, для написания кода, нам ещё понадобится понимания термина «массив». Итак, массив – это область памяти, где могут последовательно храниться несколько значений. Массив можно представить в виде здания, где в каждой квартире живут «данные»:  А также нам понадобится циклы, мы будем использовать цикл for, про остальные циклы, вы можете почитать вот здесь. Итак, цикл for — параметрический цикл (цикл с фиксированным числом повторений). Для организации такого цикла необходимо осуществить три операции: Инициализация - присваивание параметру цикла начального значения; Условие - проверка условия повторения цикла, чаще всего - сравнение величины параметра с некоторым граничным значением; Модификация - изменение значения параметра для следующего прохождения тела цикла. Эти три операции записываются в скобках и разделяются точкой с запятой ;. Как правило, параметром цикла является целочисленная переменная. Инициализация параметра осуществляется только один раз — когда цикл for начинает выполняться. Проверка Условия повторения цикла осуществляется перед каждым возможным выполнением тела цикла. Когда выражение, проверяющее Условие становится ложным (равным нулю), цикл завершается. Модификация параметра осуществляется в конце каждого выполнения тела цикла. Параметр может как увеличиваться, так и уменьшаться. Теперь давайте попробуем вывести что-нибудь, в консольную строку, например символ  @ для этого с комментариями, представлен ниже: И вот, что мы получаем на выходе: Немного видоизменив наши циклы for мы сможем создать круг с помощью символа @: Результат:  Теперь создадим новую переменную, которая хранит соотношение сторон нашей консоли:  А также во вложенном цикле for по j координату по ширине (по Ох) домножим на переменную, в которой хранится соотношение сторон нашей консоли:  На выходе мы получим такой «овал»: Но тут выясняется, что помимо соотношения сторон консоли, у нас есть соотношение сторон каждого символа, для символа @ соотношение будет равно 11 на 24 px (пикселя), что же нам делать? Решение есть! Добавим это значение в наш код: А теперь снова поменяв наш код (вложенный цикл for по j):  И вот, что мы получаем, домножив наше значение на соотношение сторон символа: Вы когда-нибудь слышали, что движение – жизнь? Так давайте оживим нашу фигуру, для этого необходимо дописать ещё один цикл и заключить в него уже два имеющихся цикла. Перепишем код: Ура, мы оживили нашу фигуру: Вам не кажется, что стало как-то скучно? Давайте украсим нашу фигуру! Для этого перепишем наш код:  Получим такую вот картину:    Теперь поменяем градиент ".:!/r(l1Z4H9W8$@" на градиент "  .:!/r00    ", чтобы убрать задний фон, и посмотрим что из этого выйдет: Код на данном этапе:  А теперь перейдём к самому пончику:  Для начала, упростим нашу программу, перенеся сравнение яркости символа в отдельную функцию, которую расположим выше нашей функции main (это необходимо для того, чтобы функцию было видно из функции main), а также немного перепишем сравнение яркости символа, поменяв:  На Таким образом мы ограничиваем значение с обоих сторон: минимальным значением и максимальным. Получаем:  // - однострочный комментарий /* */ - многострочный комментарий Теперь перепишем наши координаты, заменив На  На двумерную структуру по двум координатам, заключив vec2 в структуру:  Подробнее о структурах тут. Также создадим структуру для 3 координат: И допишем внутри цикла:  Теперь добавим библиотеку math.h, которая отвечает за неэлементарную математику в С++: #include <math.h>  И допишем функции для работы с векторами: Теперь нормализуем наш вектор:  С помощью функции norm() и получим: Теперь зададим некоторый цвет, если пересечение произошло, для этого добавим в наши циклы следующие строчки: Приблизим сферу поменяв значение в строке  На А теперь перепишем вновь нашу функцию main:    Снова поменяем градиент, чтобы получить сферу сменим   на  и получим такой результат:  Если же сменим градиент обратно, то и получим:  Код:    И немного поменяв код, дописав к нашему коду функцию:    Мы получили наше любимое блюдо «пончик». Всё готово, и как говорят итальянцы "buon appetito", что по русски "приятного аппетита". Post Scriptum: Данная статья была подготовлена в рамках мастер-класса «Сделай пончик с помощью кода» и является методическим материалом для самостоятельного изучения информации в рамках мастер-класса. Так же хотел бы выразить благодарность источникам, которыми я вдохновлялся и пользовался в процессе написания статьи и подготовки к мастер-классу: сайт metanit, а также youtube-каналом Onigiri.     Robotics and CV Engineer. Kalashnikov ISTU