• Как нарисовать такую звезду openGL?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Фигура состоит из 10 вершин, соединенных 10 ребрами.
    На первом шаге тебе надо равномерно распределить по единичной окружности 10 вершин. Сделать это нужно в специально отведенном массиве вершин, т.к. индексированные примитивы в этом старом режиме вывести нельзя.
    На втором этапе переключаем контекст на вывод замкнутой линии (GL_LINE_LOOP) и выводим все 10 точек из массива вершин, но со смещением на 3 точки и по модулю 10.

    Псевдокод:
    void display()
    {
       glClear( GL_COLOR_BUFFER_BIT );
       glBegin( GL_LINE_LOOP );
    
       for( size_t index = 0; index < vertices.size(); ++index )
       {
          const Vertex& vertex = vertices[ ( index * 3 ) % vertices.size() ];
          glVertex2i( vertex.x, vertex.y );
       }
    
       glEnd();
       glFlush(); 
    }
    Ответ написан
  • Как сделать "переопределение" функции в c++?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Твой вопрос можно решить таким способом.
    #include <iostream>
    #include <functional>
    
    class Enemy final
    {
    public:
    	std::function<void()> update = []() {};
    };
    
    int main( int argc, char* argv[] )
    {
    	Enemy enemy;
    	enemy.update = []() { std::cout << "Hello"; };
    
    	enemy.update();
    	return 0;
    }
    Ответ написан
  • OpenGL и C++. Как лучше отсортировать полигоны?

    @MarkusD
    все время мелю чепуху :)
    Честно говоря, странно видеть в заголовке и тегах вопрос об OpenGL, а в теле вопроса - про коллизии и, возможно, физику.

    Видимое пространство и коллизионное пространство - это два абсолютно разных, но одновременно существующих пространства. Вершины видимого пространства являются крайне избыточными для работы в пространстве коллизий, самих вершин для качественной передачи визуальной информации требуется многократно больше, чем необходимо для точной симуляции коллизий. Для качественной симуляции коллизий вершины должны обладать совсем другими атрибутами, которые точно так же будут излишне перегружать размер вершины для пространства визуализации. И OpenGL к теме симуляции коллизий не имеет никакого отношения.

    Тебе надо иметь два набора вершин. Один - набор вершин для визуализации объекта. Второй - набор вершин для симуляции его физики, не только коллизий. И таких наборов может быть много. Для симуляции повреждений и деформаций, для скиннинга, для хитбоксов, для постройки маршрутов перемещений, для звуков...
    BSP подходят больше для отсечения видимой геометрии. В наши дни этот подход не оправдывает своих затрат, его не используют. Для эффективной симуляции коллизий лучше подходят QTree, OTree или KD-списки.

    Почитать начать можно отсюда: Study path for game programmer. Твой раздел: 9. Game Physics and Animation.
    Далее, тебе сюда: https://gamedev.ru/code/articles/?physics
    Да, геймдев се еще жив, хоть уже и не принадлежит Сереге Ваткину.
    Следом лучше пойти на хабр: [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11].
    У Фабьена Сангларда есть набор статей о внутреннем устройстве DOOM 3 и Quake 3, там есть материал и про столкновения.

    На этом этапе у тебя уже должно сформироваться представление о предметной области симуляции коллизий.
    Ответ написан
  • Как std::initializer_list определяет количество элементов в {списке}?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    В общем смысле, инициализация может быть выполнена тремя основными способами. std::initializer_list участвует в двух из трех.

    Foo bar = {...};
    Foo bar{..};


    В обоих случаях выполняется List Initialization, в первом - copy list-initialization, во втором - direct list-initialiaztion.

    Тут важно отметить что принятие решения о использовании std::initializer_list выполняются только на этапе трансляции. В обоих случаях сперва транслятор попробует придумать std::initializer_list. Если у аргументов типы разные (а приведение типов при такой записи не делается), то попробовать создать std::initializer_list у транслятора не получится. Но если получилось, то транслятор уже итак знает число аргументов, переданных в конструктор.

    Образно выражаясь, транслятор прямо перед вызовом конструктора объекта оформляет короткую область видимости, в рамках которой оформляется локальный массив неизменной длины. В этот локальный массив по своему значению складываются аргументы конструктора, далее этот массив обрамляется в std::initializer_list, с которым конструктор и вызывается.
    Сразу по завершении конструктора локальная область видимости закрывается и память массива аргументов конструктора освобождается. Поэтому std::initializer_list нельзя копировать, перемещать, сохранять в состоянии конструируемого объекта. std::initializer_list не владеет отображаемой памятью, он только дает к ней доступ.
    Ответ написан
  • Game-dev путь. Что мне делать?

    @MarkusD
    все время мелю чепуху :)
    Вот держи: Game developer roadmap и Study path for game programmer.
    Этих двух ссылок тебе лет на 15 усерднейшего запоя хватит. :)
    Подробнее, чем там, больше негде.
    Ответ написан
  • Проектирование и архитектура приложений?

    @MarkusD
    все время мелю чепуху :)
    Самое первое и самое главное: клиентский код должен оставаться чистым, состоять как можно больше из кода реализации алгоритма и как можно меньше из кода обслуживающего эту реализацию.
    Клиентский код ничего не должен знать о том, как ты хочешь, например, оповещать пользователя об исключительный ситуациях. Ему просто незачем это знать, ему важно просто работать.

    Поэтому, from classes import MyTelegramm - думаю, не стоит. Мне там и try-catch тоже не нравится. Пусть пользователь блоки try-catch пишет только для себя и только тогда, когда ему это надо. Когда он точно может словить исключение и выкрутиться из ситуации. Про такие исключения тебе, по-хорошему, и знать-то не стоит, т.к. они лишь меняют ветвь исполнения, но не обрывают его.
    Клиентский код стоит выполнять в контролируемой среде, внутри специального защитного блока try-catch, в котором ты уже будешь ловить все доступные тебе исключения и производить их диспетчеризацию.

    Скажем, doit все действия задачи выполняет внутри своего защитного контура. При этом, таких защитных контуров у doit несколько, включая и самый базовый, отвечающий за оповещение о критических сбоях работы. Что мне в doit малость не нравится, так это что обработка исключения делается по месту вместо того, чтобы организовать некоторый диспетчер, в котором это исключение уже и обрабатывать.

    Свой защитный контур я бы связал с диспетчером (Dispatcher) исключений. Если контуров делать несколько, то и диспетчеров я тоже сделал бы несколько, разместив все диспетчеры в локаторе (Service locator).

    Диспетчер начал бы свою работу с того, что завернул бы исключение в конверт (Envelope, [2], [3]). Конверт просто легче читать, чем исключение непредсказуемого типа. Заворачивание я бы сделал с помощью абстрактной фабрики (Abstract factory) конвертов, в которой исключение каждого типа и от каждого источника было бы завернуто в правильный конверт согласно конфигурации фабрики.

    Получив конверт, диспетчер посетил(Visitor) бы с этим конвертом почтовый ящик каждого адресата. Почтовый ящик адресата можно снабдить стратегией (Strategy) приема конвертов, согласно которой почтовый ящик или перепишет конверт к себе, или оставит без внимания, в зависимости от написанных на конверте данных. Управлять почтовыми ящиками может локальный реестр (Registry) диспетчера, в котором так же может быть реализована и функция обхода (Visit) для посещения почтовых ящиков.

    Почтовый ящик обслуживает адресата. Адресат может подписаться (Observer) на поступление письма, а может приходить к ящику за новыми письмами по расписанию и доставать только первые несколько писем, а не все. Как выгребать почтовый ящик - вопрос конфигурации адресата.
    Когда адресат забирает письма, он раздает их в конечные точки, в роли которых уже и будут выступать все эти MyTelegramm или MyLog, но строго сконфигурированные и принимающие уже только копию письма.

    Весь этот ворох шаблонов проектирования тянет на целый отдельный слой с набором подсистем, в каждой из которых будет твориться своя незабываемая чертовщина:
    - Подсистема защитного контура и только диспетчера исключений (вместе с локатором, если он нужен).
    - Подсистема обертывания исключения в конверт, фабрики конвертов и конфигуратора этой фабрики.
    - Подсистема стратегии поведения клиента и, возможно, конфигуратора стратегии для конкретного клиента.
    - Подсистема клиентов конверта, реестра клиентов, фабрики клиентов и конфигура этой фабрики.

    Ну и, конечно же, любую часть всего этого безобразия можно отбросить и написать проще. :)
    На начальных этапах можно заглушить стратегии и свести конфигураторы к виду линейной функции.
    MVP тут, на мой взгляд, будет выглядеть как цельная система с гарантированной доставкой конверта с любым исключением во все созданные конечные точки, проводя их от защитного контура, через диспетчер и до адресата через почтовый ящик.
    Ответ написан
  • Разница между enum ID и var?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    На самом деле твой вопрос звучит так: "Зачем нужно обязательно указывать знак ; после определения составного типа?"

    Попробуй определить enum без завершения знаком ;. При попытке трансляции тебе скажут что дальше знака } требуется указание имени объявляемой переменной. Или еще какие-нибудь другие ошибки. Например, что определение типа не допускается там, где ожидается тип результата функции.

    Дело в том, что все следующие записи по своей сути эквивалентны.
    enum { A, B } variable; - объявление переменной типа анонимного перечисления.
    enum Foo { A, B } variable; - объявление переменной типа именованного перечисления.
    enum Foo { A, B }; Foo variable; - Определение типа перечисления, а затем сразу объявление переменной этого типа.
    enum Foo; Foo variable; - Предварительное объявление типа перечисления, а затем сразу объявление переменной этого типа.
    В конце определения любого составного типа допускается объявление переменной или константы этого типа. При этом тип может быть и анонимным, т.е. без имени. Имя типа нужно для указания при объявлении переменной. Таким образом, определить переменную или константу анонимного типа получится только в месте определения самого типа.
    А если мы хотим определить только тип, мы просто ничего не пишем между } и следующим за ним ;.

    В общем смысле, если речь не идет об анонимном типе, объявление переменной в месте определения типа считается плохим тоном. Это резко снижает структурность кода и заставляет уделять чрезмерное внимание простому объявлению переменной.

    В msdn написан общий синтаксис с учетом особенностей расширения C++/CX.
    Там же приведено и описание var:
    var
    (Optional) The name of a variable of the enumeration type.


    А особенностью расширения C++/CX является указание уровня публичности типа:
    access
    The accessibility of the enumeration, which can be public or private.
    Ответ написан
  • Не работает кнопка cocos2d-x?

    @MarkusD
    все время мелю чепуху :)
    На поведение кнопки может влиять очень много условий. В твоем коде я не увидел ни обращения к setTouchEnabled, ни к setContentSize.

    Тебе надо трассировать код копки. Событие нажатия может до нее доходить, но фильтроваться ее состоянием. А может - и не доходить и фильтроваться где-то раньше. Например - у твоего layer, т.к. его состояние тоже может влиять на поведение кнопки.
    Родной UI в кокосе - это довольно больная тема, в которой без трассировки кода разобраться будет очень тяжело и наверняка сказать практически ничего невозможно.
    Ответ написан
  • Что выбрать для освоения DirectX - UWP или Win32 API?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Microsoft UWP является надстройкой для создания универсальных приложений, которые, в теории, должны легко переноситься между различными аппаратными слоями платформы Windows. UWP поддерживается только в Win10, реализована на базе расширения C++/CX стандарта C++17 и выполна преимущественно в объектном стиле.

    Windows API является низкоуровневым слоем взаимодействия между приложением и операционной системой. Win32 API поддерживается всеми версиями Windows, начиная с Windows 95. Однако, разные версии Windows поддерживают разные наборы функций. Поэтому уровень поддержки той или иной функции из набора API всегда следует уточнять в документации. Реализован Win32 API на языке C стандарта C99 и выполнен в процедурном стиле.

    Данная информация создает ряд допущений и ограничений для использования как UWP, так и WinAPI. Исходя из требований проекта и опираясь как на допущения, так и на ограничения, можно вывести однозначное решение о выборе только одной из этих двух технологий.

    C++/CX является расширением стандарта и не поддерживается другими платформами, а так же всеми компиляторами кроме компилятора Microsoft. При этом, существуют такие условия, когда отказаться от использования C++/CX и UWP невозможно. В иных ситуациях следует принимать к сведению особый синтаксис расширения и объектную организацию. Скажем, я бы не стал полностью весь проект делать на базе C++/CX.

    WinAPI, с другой стороны, предоставляет довольно низкоуровневый доступ, что немного осложняет реализацию требуемого функционала. Ряд абстракций, по умолчанию доступных в UWP, или потребуется сделать самому, или сделать вообще не получится без прямой низкоуровневой работы с драйверами.

    В любом случае, выбрав UWP или WinAPI, дальше тебе все равно лучше работать исключительно со стандартным C++ и стандартной реализацией DirctX для C++. От C++/CX стоит избавляться на как можно ранних слоях абстракции палатформозависимого кода. От типов и абстракций WInAPI, равно как и от наследия C99, тоже лучше избавляться как можно раньше и переходить на работу исключительно со стандартным C++.
    Ответ написан
  • Как лучше реализовать локализацию?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    s = loc::get("key");    // ambiguous overload for op=

    неоднозначность у тебя появляется потому что оба конструктора std::wstring (конструктор копирования и конструктор преобразования из const wchar_t*) являются неявными.
    Операторы преобразования TaggedCWstr так же являются неявными.

    Эту неоднозначность нужно исключить. Замени, например, оба оператора преобразования на оператор преобразования в std::wstring_view.

    Я бы не стал делать неявный оператор преобразования в std::wstring и этим позволять бесконтрольно обращаться к динамической памяти без явного понимания этого процесса. Лучше для получения std::wstring сделать operator *, а еще лучше - вообще не ломать семантику операторов и сделать метод с говорящим именем.
    Ответ написан
  • Как написать свой 3D рэндер?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    На хабре очень много статей на эту тему.
    Например, есть вот такой цикл статей.
    Еще есть вот такая небольшая подборка материала.
    Еще можно найти такое руководство.
    Все материалы доступны для изучения.
    Ответ написан
  • Публичный доступ + const модификатор или приватный доступ + геттер?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    В математике есть понятие инварианта.
    Этот термин перешел и в программирование, претерпев лишь незначительные изменения своего смысла.
    Коротко, инвариант - это некоторое консистентное состояние экземпляра, консистентность которого сохраняется в процессе жизни экземпляра.

    Инвариант объекта гарантируется публичным интерфейсом типа этого объекта. Стало быть, любой публичный доступ к состоянию объекта не должен нарушать инвариант.
    В C++ Core Guidelines присутствует очень хороший совет на эту тему.

    Из этого всего следует что указывать поля публичными тебе практически никогда не надо. Если инварианта нет и поля незачем защищать от внешнего изменения, то тебе стоит оформлять тип как структуру, публичный доступ в которой является доступом по умолчанию. Класс тебе нужен для того, чтобы явно обозначить инвариант своего типа и этим ограничить набор возможных состояний его экземпляров. Наличие инварианта исключает прямой публичный доступ к состоянию экземпляра, т.е. все поля, которые относятся к состоянию экземпляра, нужно пометить как закрытые (protected или private). А для работы с состоянием экземпляра нужно сформировать публичный интерфейс, обеспечивающий сохранение инварианта. Это решается через публичные аксессоры.
    Ответ написан
  • Какую технологию использует Steam для создания игроками серверов?

    @MarkusD
    все время мелю чепуху :)
    Steamworks API предлагает специальный слой абстракции сети - Steam Networking.
    В рамках этого слоя соединение устанавливается между конкретными пользователями по их Steam идентификаторам. Тип соединения между пользователями зависит от конкретных настроек сети. При соединении в открытой сети или в присутствии несимметричного NAT соединение между хостами пользователей будет установлено напрямую. В случае присутствия симметричного NAT соединение будет установлено через специальный Relay-сервер. Steam берет на себя всю рутину с определением типа сети, выбором типа соединения и с его поддержкой во время сеанса игры.

    Сама техника определения соединения и установки подключения является широко известной и общепринятой. Это - NAT Punchtrough. Описывать технологию в ответе бессмысленно, т.к. в сети по этому названию есть очень много документов [1][2][3][4].
    Для определения типа сети у Steam есть набор региональных STUN-серверов, а так же набор Relay-серверов, выступающих посредниками при соединении в присутствии симметричного NAT.

    Примерно в прошлом году Valve начали выделять Steam Networking в отдельный продукт с открытым кодом. Продукт явно будет лишен поддержки серверов Steam, однако его открытый код уже сейчас содержит достаточно информации об организации сети подобного уровня.
    Ответ написан
  • Как правильно передавать массив как параметр?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Тебе в принципе не стоит использовать приведенную тобой форму записи аргументов.
    Дело в том, что тип твоей void func(int arrParamName[10]) будет void(int*).
    Ты не сможешь руководствоваться указанной размерностью (10), т.к. в твою функцию можно передать и массив длины 5. И даже длины 1. Передавать в твою функцию можно массивы любой длины, а еще - nullptr.

    Согласно стандарту тип массива ссылок является недопустимым, поэтому твоя void func(int& arrParamName[10]) является синтаксически неверной записью.

    Теперь о том, как все таки стоит делать. В стандартной библиотеке для нас определен тип std::array.
    Вот именно им и стоит пользоваться. Его можно передать и по ссылке, и по константной ссылке, и переместить тоже можно. И длина у него есть, и итераторами пользоваться тоже можно. И в алгоритмы он прекрасно передается.
    Ответ написан
  • Как работают binary Assignment Operators?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Подробное описание и примеры в документации языка.
    Операции &=, |= и ^= являются побитовыми, а не логическими. В твоих цитатах приведены ошибки описания.

    Как работают побитовые операции.
    5d788934b7918228570392.jpeg

    andB & andA == 0b1000101011 & 0b0000000011 == 0b11 == 3


    Побитовый сдвиг влево производит сдвиг бинарных разрядов в сторону старшего разряда.
    0b00000011 << 1 == 0b00000110
    0b00000011 << 4 == 0b00110000
    0b11000000 << 2 == 0b0000001100000000 // or 0b00000000 for `uint8_t` type.


    Побитовый сдвиг вправо - соответственно, производит сдвиг в сторону младшего разряда.
    0b00110000 >> 1 == 0b00011000
    0b00110000 >> 4 == 0b00000011
    0b00001100 >> 3 == 0b00000001
    Ответ написан
  • Как начинать в геймдеве?

    @MarkusD
    все время мелю чепуху :)
    Когда ты молодой и еще только учишься в школе, начинать в геймдеве проще простого!
    Держи дорожную карту: A Study Path for Game Programmer.

    Изучай на здоровье! :)
    Ответ написан
  • Когда удаляются умные указатели?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    std::auto_ptrявляется устаревшим и удален из стандартной библиотеки начиная со стандарта C++17.
    Вместо него стоит пользоваться std::unique_ptr.

    std::auto_ptr мог следить только за памятью одного экземпляра. Передать туда память массива можно, но деструктор будет вызван только у первого экземпляра массива. В общем смысле это означает утечку памяти.
    std::unique_ptr, наоборот, способен контролировать память как единичного экземпляра, так и массива экземпляров. Еще в отличии от своего устаревшего товарища, std::unique_ptr способен спокойно передавать свое состояние, не создавая возможность двойного освобождения памяти. В дополнение, std::unique_ptr еще способен пользоваться нестандартными деструкторами, что очень кстати при работе, например, с COM-объектами или нестандартной схемой аллокации памяти.

    Любой умный указатель является обычным объектом своего типа. Все объекты всех типов имеют свое время жизни, согласно условиям своего конструирования.
    Умные указатели разрушаются тогда, когда завершается их время жизни. Для умных указателей в глобальном пространстве время жизни завершается сразу после выхода из main.

    В языке нет термина "обычный массив", этот вопрос непонятен.
    Ответ написан
  • Как конвертировать массив типа char в int?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Ошибок в этом коде довольно много.

    Начнем с void swap(T& first, T& second).
    Она просто не нужна т.к. есть std::swap.

    template <typename T1>
    void out_array(const T1* array, size_t arraySize)

    Параметр arraySize тоже не нужен. Ты работаешь с сырыми массивами фиксированной длины и можешь дать своему шаблону функции понимание длины такого массива. Делается это так.
    template < typename TElementType, size_t LENGTH >
    void out_array( const TElementType array[ LENGTH ] )


    Далее.
    char array2[n] = { '2','1','4','5','3','-3','-1','-2','-4','-5' };

    В этом месте сразу две ошибки. Первая - это инициализация символьными литералами. '2' не будет эквивалентно 2. С этого момента сортировка будет сортировать по коду символьной таблицы, а не по значению числа. Это ошибка, т.к. это явно не то поведение, которое ты ждешь.
    Вторая ошибка - это '-3' и.т.д. Тип у такой конструкции будет int, а не char. Компилятор тебе именно об этом и пытается сказать.

    int array3[n] = atoi(array2);
    Эту конструкцию сложно понять. Сказать можно одно - так массив фиксированной длины инициализировать не получится.
    Если этой конструкцией ты хотел описать инициализацию массива, то это можно попробовать сделать вот так:
    int array3[n];
    std::transform(
        std::begin( array2 ), std::end( array2 ), std::begin( array3 ),
        []( const char item ) -> int { return item; } 
    );

    Но это тоже скользкое решение. В твоем случае все будет нормально, в общем случае это плохое решение. Изучи документацию на функцию std::transform.
    Ответ написан
  • Почему возникает эта ошибка?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Согласно 14му стандарту, у тебя есть два способа инициализации поля по месту объявления:
    Способ первый, принятый начиная с C++11:
    Node root = {T{}, nullptr, nullptr};
    Способ второй, принятый начиная с C++14:
    Node root{T{}, nullptr, nullptr};

    Твой текущий код инициализации поля синтаксически неверен.
    Ответ написан
  • Как настроить локальное хранилище отладочных символов в Visual Studio?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    В дереве настроек студии есть раздел "Debugging", там есть подраздел "Symbols". Среди настроек этого подраздела есть поле "Cache symbols in this directory" для указания пути, куда, собственно, и стоит сохранять загруженные отладочные символы.
    dbg-options-symbols.png?view=vs-2019
    Ответ написан