Space Jaguar 3D Action RPG

Давно я не анонсировал новых проектов) Следующий проект над которым я начинаю работу – 3д экшн рпг под названием Space Jaguar (Космический Ягуар) История в sci-fi сеттинге про крутого парня по имени Джаг и его нелегкое приключение в поисках пропавшего отца. Будет 3D графика на движке Flame Steel Engine (или возможно на любом другом популярном), с использованием наработок прошлых проектов (Death Mask, Cube Art Project), комедийный сюжет со множеством отсылок, аркадными боями и боссами. О сроках релиза полной версии говорить не готов, планирую выпускать игру частями.

Репозиторий проекта:
https://gitlab.com/demensdeum/space-jaguar-action-rpg

0

Пишем бэкенд сервер на C++ FCGI

Краткая заметка о том как я писал серверную часть для 3д редактора Cube Art Project, сервер должен сохранять и выводить работы пользователей веб версии, отдавая им короткие URL по кнопке сохранения. Сначала я хотел использовать Swift/PHP/Ruby/JS или какой-то подобный современный язык для бэкэнда, но посмотрев на характеристики моей VPS было принято решение написать сервер на C/C++.
Для начала нужно установить на сервере libfcgi и модуль поддержки fcgi для вашего вебсервера, пример для Ubuntu и Apache:


sudo apt install libfcgi libapache2-mod-fcgid

Далее настраиваем модуль в конфиге:

FcgidMaxProcessesPerClass – максимальное количество процессов на класс, я поставил 1 процесс так как не расчитываю на большую нагрузку.
AddHandler fcgid-script .fcgi – расширение файла с которым должен стартовать модуль fcgi.
Добавляем в конфиг папку из которой будут запускаться cgi приложения:

Далее пишем приложение на C/C++ с поддержкой fcgi, собираем его, и копируем в папку /var/www/html/cgi-bin.
Примеры кода и скрипта сборки:
https://gitlab.com/demensdeum/cube-art-project-server/-/blob/master/src/cubeArtProjectServer.cpp
https://gitlab.com/demensdeum/cube-art-project-server/-/blob/master/src/build.sh
После этого нужно будет перезапустить ваш вебсервер:


systemctl restart apache2

Далее проставьте необходимые права на выполение папки cgi-bin через chmod.
После этого ваша cgi программа должна работать через браузер по ссылке, пример для сервера Cube Art Project:
http://192.243.103.70/cgi-bin/cubeArtProject/cubeArtProjectServer.fcgi
Если что-то не получается, то смотрите логи вебсервера, либо подключайтесь дебаггером к запущенному процессу, процесс отладки не должен отличаться от процесса отладки обычного клиентского приложения.

Источники

https://habr.com/ru/post/154187/
http://chriswu.me/blog/writing-hello-world-in-fcgi-with-c-plus-plus/

Исходный код

https://gitlab.com/demensdeum/cube-art-project-server

0

Cube Art Project

Cube Art Project – кубический 3D редактор.
Вам открывается потрясающая возможность передвигаться по сцене, строить и удалять кубы кнопками WSAD + E, крутите колесо мышки для смены цвета куба. На данный момент поддерживаются только 16 цветов, но в будущем планируется множество улучшений.

Веб версия
https://demensdeum.com/games/CubeArtProjectWEB/

Веб версия с сохранением на стороне сервера
http://cubesss.tk/

Windows
https://demensdeum.com/games/CubeArtProjectReleases/CubeArtProjectWin32.zip

macOS
https://demensdeum.com/games/CubeArtProjectReleases/CubeArtProjectMacOS.zip

Linux (x86-64)
https://demensdeum.com/games/CubeArtProjectReleases/CubeArtProjectLinux86_64.zip

Android
(Концепт, требуется usb-мышь)
https://demensdeum.com/games/CubeArtProjectReleases/CubeArtProject.apk

Исходный код
https://gitlab.com/demensdeum/cube-art-project-bootstrap
https://gitlab.com/demensdeum/cube-art-project-server

Технологии: SDL, Emscripten, MinGW, Glew, GLM, Cpp-JSON

0

Портируем C++ SDL приложение на Android

В данной заметке я опишу свой опыт портирования прототипа 3D редактора Cube Art Project на Android.
Сначала посмотрим на результат, в эмуляторе запущен редактор с 3D курсором куба красного цвета:

Для успешной сборки нужно было сделать следующее:

  1. Установить последний Android SDK и NDK (версию ндк чем свежее тем лучше).
  2. Загрузить исходный код SDL2, взять оттуда шаблон для сборки андроид приложения.
  3. Добавить SDL Image, SDL Mixer к сборке.
  4. Добавить библиотеки моего игрового движка и тулкита, их зависимости (GLM, JSON for Modern C++)
  5. Адаптировать файлы сборок для Gradle.
  6. Адаптировать C++ код для совместимости с Android, изменения коснулись платформозависимых компонентов (OpenGL ES, инициализация графического контекста)
  7. Собрать и проверить проект на эмуляторе.

Шаблон проекта

Загружаем исходники SDL, SDL Image, SDL Mixer:
https://www.libsdl.org/download-2.0.php
В папке docs есть подробная инструкция по работе с шаблоном андроид проекта; скопируем директорию android-project в отдельную папку, сделаем симлинк или скопируем папку SDL в android-project/app/jni.
Подставляем правильный идентификатор для флага avd, запускаем эмулятор андроида из директории Sdk:


cd ~/Android/Sdk/emulator
./emulator -avd Pixel_2_API_24

Указываем пути в скрипте, собираем проект:


rm -rf app/build || true
export ANDROID_HOME=/home/demensdeum/Android/Sdk/
export ANDROID_NDK_HOME=/home/demensdeum/Android/android-ndk-r21-beta2/
./gradlew clean build
./gradlew installDebug

Должен собраться шаблон проекта SDL с кодом на Си из файла


android-sdl-test-app/cube-art-project-android/app/jni/src/YourSourceHere.c

Зависимости

Загружаем исходный код в архивах для SDL_image, SDL_mixer:
https://www.libsdl.org/projects/SDL_image/
https://www.libsdl.org/projects/SDL_mixer/

Загружаем зависимости вашего проекта, для примера мои shared библиотеки:
https://gitlab.com/demensdeum/FlameSteelCore/
https://gitlab.com/demensdeum/FlameSteelCommonTraits
https://gitlab.com/demensdeum/FlameSteelBattleHorn
https://gitlab.com/demensdeum/FlameSteelEngineGameToolkit/
https://gitlab.com/demensdeum/FlameSteelEngineGameToolkitFSGL
https://gitlab.com/demensdeum/FSGL
https://gitlab.com/demensdeum/cube-art-project

Все это выгружаем в app/jni, каждый “модуль” в отдельную папку, например app/jni/FSGL. Далее у вас есть вариант найти рабочие генераторы для файлов Application.mk и Android.mk, я не нашел, однако возможно есть простое решение на основе CMake. Переходим по ссылкам и начинаем знакомиться с форматом файлов сборки для Android NDK:
https://developer.android.com/ndk/guides/application_mk
https://developer.android.com/ndk/guides/android_mk

Также следует прочитать про разные APP_STL реализации в NDK:
https://developer.android.com/ndk/guides/cpp-support.html

После ознакомления создаем для каждого “модуля” файл Android.mk, далее пример файл сборки shared библиотеки Cube-Art-Project:


LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

APP_STL := c++_static
APP_CPPFLAGS := -fexceptions
LOCAL_MODULE := CubeArtProject
LOCAL_C_INCLUDES := $(LOCAL_PATH)/src $(LOCAL_PATH)/../include $(LOCAL_PATH)/../include/FlameSteelCommonTraits/src/FlameSteelCommonTraits
LOCAL_EXPORT_C_INCLUDES = $(LOCAL_PATH)/src/

define walk
$(wildcard $(1)) $(foreach e, $(wildcard $(1)/*), $(call walk, $(e)))
endef

ALLFILES = $(call walk, $(LOCAL_PATH)/src)
FILE_LIST := $(filter %.cpp, $(ALLFILES))
$(info CubeArtProject source code files list)
$(info $(FILE_LIST))
LOCAL_SRC_FILES := $(FILE_LIST:$(LOCAL_PATH)/%=%)

LOCAL_SHARED_LIBRARIES += FlameSteelCore
LOCAL_SHARED_LIBRARIES += FlameSteelBattleHorn
LOCAL_SHARED_LIBRARIES += FlameSteelCommonTraits
LOCAL_SHARED_LIBRARIES += FlameSteelEngineGameToolkit
LOCAL_SHARED_LIBRARIES += FlameSteelEngineGameToolkitFSGL
LOCAL_SHARED_LIBRARIES += FSGL
LOCAL_SHARED_LIBRARIES += SDL2
LOCAL_SHARED_LIBRARIES += SDL2_image

LOCAL_LDFLAGS := -static-libstdc++
include $(BUILD_SHARED_LIBRARY)

Любой опытный CMake пользователь поймет этот конфиг с первых строк, форматы очень похожи, в Android.mk отсутствует GLOB_RECURSIVE, поэтому приходится рекурсивно искать исходные файлы с помощью функции walk.

Меняем Application.mk, Android.mk со-но для сборки C++ а не Си кода:


APP_ABI := armeabi-v7a arm64-v8a x86 x86_64
APP_PLATFORM=android-16
APP_STL := c++_static
APP_CPPFLAGS := -fexceptions

Переименовываем YourSourceHere.c -> YourSourceHere.cpp, grep’аем по вхождениям, меняем путь в сборке, например:


app/jni/src/Android.mk:LOCAL_SRC_FILES := YourSourceHere.cpp

Далее попробуйте собрать проект, если вы увидете ошибки от компилятора об отсутствии хидеров, то проверьте корректность путей в Android.mk; если ошибки будут от линковщика вида “undefined reference”, то проверьте корректность указания файлов исходного кода в сборках, оттрейсить списки можно через указание $(info $(FILE_LIST)) в Android.mk файле. Не забудьте о двойном механизме линковки, с помощью модулей в ключе LOCAL_SHARED_LIBRARIES и корректной линковке через LD, например для FSGL:


LOCAL_LDLIBS := -lEGL -lGLESv2

Адаптация и запуск

Пришлось поменять некоторые вещи, например убрать GLEW из сборок для iOS и Android, переименовать часть вызовов OpenGL, добавив постфикс EOS (glGenVertexArrays -> glGenVertexArraysOES), включать макрос отсутствующих модерновых функций дебага, вишенка на торте это неявный инклуд GLES2 хидеров с указанием макроса GL_GLEXT_PROTOTYPES 1:


#define GL_GLEXT_PROTOTYPES 1
#include "SDL_opengles2.h"

Также наблюдал черный экран на первых запусках с ошибкой вида “E/libEGL: validate_display:255 error 3008 (EGL_BAD_DISPLAY)”, поменял инициализацию окна SDL, профайла OpenGL и все заработало:


SDL_DisplayMode mode;
SDL_GetDisplayMode(0,0,&mode);
int width = mode.w;
int height = mode.h;

window = SDL_CreateWindow(
            title,
            0,
            0,
            width,
            height,
            SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN | SDL_WINDOW_RESIZABLE
        );

SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES );

На эмуляторе приложение по умолчанию устанавливается с иконкой SDL и именем “Game”.

Мне осталось исследовать возможность автоматической генерации файлов сборки на основе CMake, либо же мигрировать сборки для всех платформ на Gradle; однако CMake остается выбором дефакто для текущей разработки на C++.

Исходный код

https://gitlab.com/demensdeum/android-sdl-test-app
https://gitlab.com/demensdeum/android-sdl-test-app/tree/master/cube-art-project-android

Источники

https://developer.android.com/ndk/guides/cpp-support.html
https://developer.android.com/ndk/guides/application_mk
https://developer.android.com/ndk/guides/android_mk
https://lazyfoo.net/tutorials/SDL/52_hello_mobile/android_windows/index.php
https://medium.com/androiddevelopers/getting-started-with-c-and-android-native-activities-2213b402ffff

0

LazyFoo Productions вебсайт

Пожалуй стоит упомянуть вебсайт с которого почти всегда начинаются мои проекты и новые разработки, это вебсайт LazyFoo Productions, на котором вы можете найти ответы на достаточно хардкорные тематики: примеры использования сложных API, узнать как объединять казалось бы несовместимые системы (Android/C++) с подробным объяснением принципов работы, рабочими примерами кода.

https://lazyfoo.net/

0

Перевернутый мир

Для разработки нового проекта Cube Art Project взял на вооружение методологию разработки Test Driven Development. В данном подходе сначала реализуется тест для определенного функционала приложения, а затем уже реализуется конкретный функционал. Большим плюсом в данном подходе я считаю реализацию финальных интерфейсов, максимально непосвященных в детали реализации, до начала разработки функционала. При таком подходе тест диктует дальнейшую реализацию, добавляются все преимущества контрактного программирования, когда интерфейсы являются контрактами для конкретной реализации.
Cube Art Project – 3D редактор в котором пользователь строит фигуры из кубов, не так давно этот жанр был очень популярен. Так как это графическое приложение, то я решил добавить тесты с валидацией скриншотов.
Для валидирования скриншотов нужно получить их из OpenGL контекста, делается это с помощью функции glReadPixels. Описание аргументов функции простейшие – начальная позиция, ширина, высота, формат (RGB/RGBA/проч.), указатель на выходной буфер, любому работавшему с SDL или имеющему опыт с буферами данных в Си будет просто подставить нужные аргументы. Однако считаю необходимым описать интересную особенность выходного буфера glReadPixels, пиксели в нем хранятся снизу вверх, а в SDL_Surface все базовые операции происходят сверху вниз.
То есть загрузив референсный скриншот из png файла, я не смог сравнить два буфера в лоб, так как один из них был перевернутым.
Чтобы перевернуть выходной буфер из OpenGL вам нужно заполнить его отнимая высоту скриншота для координаты Y. Однако стоить учесть что есть шансы выйти за пределы буфера, если не отнять единицу во время заполнения, что приведет к memory corruption.
Так как я повсеместно стараюсь использовать ООП парадигму “программирования интерфейсами”, вместо прямого Си-подобного доступа к памяти по указателю, то при попытке записать данные за пределами буфера объект мне об этом сообщил благодаря валидации границ в методе.
Итоговый код метода получения скриншота в стиле сверху-вниз:


    auto width = params->width;
    auto height = params->height;

    auto colorComponentsCount = 3;
    GLubyte *bytes = (GLubyte *)malloc(colorComponentsCount * width * height);
    glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, bytes);

    auto screenshot = make_shared(width, height);

    for (auto y = 0; y < height; y++) {
        for (auto x = 0; x < width; x++) {
            auto byteX = x * colorComponentsCount;
            auto byteIndex = byteX + (y * (width * colorComponentsCount));
            auto redColorByte = bytes[byteIndex];
            auto greenColorByte = bytes[byteIndex + 1];
            auto blueColorByte = bytes[byteIndex + 2];
            auto color = make_shared(redColorByte, greenColorByte, blueColorByte, 255);
            screenshot->setColorAtXY(color, x, height - y - 1);
        }
    }

    free(bytes);

Источники

https://community.khronos.org/t/glreadpixels-fliped-image/26561
https://stackoverflow.com/questions/8346115/why-are-bmps-stored-upside-down

Исходный код

https://gitlab.com/demensdeum/cube-art-project-bootstrap

0

Longest Common Substring

В данной заметке я опишу алгоритм решения задачи наибольшей общей подстроки. Допустим мы пытаемся расшифровать зашифрованные бинарные данные, для начала попробуем найти общие паттерны с помощью поиска наибольшей подстроки.
Входная строка для примера:
adasDATAHEADER??jpjjwerthhkjbcvkDATAHEADER??kkasdf
Мы ищем повторяющуюся дважды строку:
DATAHEADER??

Префиксы

Для начала напишем метод для сравнения префиксов двух строк, пусть возвращает результирующую строку в которой символы левого префикса равны символам правого префикса.
Например для строк:


        val lhs = "asdfWUKI"
        val rhs = "asdfIKUW"

Результирующая строка – asdf
Пример на Kotlin:


fun longestPrefix(lhs: String, rhs: String): String {
        val maximalLength = min(lhs.length-1, rhs.length -1)
        for (i in 0..maximalLength) {
            val xChar = lhs.take(i)
            val yChar = rhs.take(i)
                if (xChar != yChar) {
                    return lhs.substring(0, i-1)
                }
        }
        return lhs.substring(0,maximalLength)
}

Brute Force

Когда не получается по хорошему, стоит прибегнуть к грубой силе. Используя метод longestPrefix пройдем по строке двумя циклами, первый берет строку от i до конца, второй от i + 1 до конца, передает их в поиск наибольшего префикса. Временная сложность данного алгоритма примерно равна O(n^2) ~ O(n*^3).
Пример на Kotlin:


fun searchLongestRepeatedSubstring(searchString: String): String {
        var longestRepeatedSubstring = ""
        for (x in 0..searchString.length-1) {
            val lhs = searchString.substring(x)
            for (y in x+1..searchString.length-1) {
                val rhs = searchString.substring(y)
                val longestPrefix = longestPrefix(lhs, rhs)
                if (longestRepeatedSubstring.length < longestPrefix.length) {
                    longestRepeatedSubstring = longestPrefix
                }
            }
        }
        return longestRepeatedSubstring
}

Суффиксный массив

Для более элегантного решения нам потребуется инструмент - структура данных под названием “Суффиксный массив”. Данная структура данных представляет из себя массив из подстрок заполняемых в цикле, где каждая подстрока начинается со следующего символа строки до конца.
Например для строки:


adasDATAHEADER??

Суффиксный массив выглядит так:


adasDATAHEADER??
dasDATAHEADER??
asDATAHEADER??
sDATAHEADER??
DATAHEADER??
ATAHEADER??
TAHEADER??
AHEADER??
HEADER??
EADER??
ADER??
DER??
ER??
R??
??
?

Решаем сортировкой

Отсортируем суффиксный массив, затем пройдем по всем элементам циклом где в левой руке (lhs) текущий элемент, в правой (rhs) следующий и вычислим самый длинный префикс с помощью метода longestPrefix.
Пример на Kotlin:


fun searchLongestRepeatedSubstring(searchString: String): String {
    val suffixTree = suffixArray(searchString)
    val sortedSuffixTree = suffixTree.sorted()

    var longestRepeatedSubstring = ""
    for (i in 0..sortedSuffixTree.count() - 2) {
        val lhs = sortedSuffixTree[i]
        val rhs = sortedSuffixTree[i+1]
        val longestPrefix = longestPrefix(lhs, rhs)
        if (longestRepeatedSubstring.length < longestPrefix.length) {
            longestRepeatedSubstring = longestPrefix
        }
    }
    return longestRepeatedSubstring
}

Временная сложность алгоритма O(N log N), что гораздо лучше решения в лоб.

Источники

https://en.wikipedia.org/wiki/Longest_common_substring_problem

Исходный код

https://gitlab.com/demensdeum/algorithms

0