АК
Александр Кузьминых28 ноября 2017 г. 7:59

Написание пользовательского Qt 3D аспекта - часть 1

Введение

Qt 3D имеет гибкую и расширяемую архитектуру, которая позволяет нам легко добавлять в нее свои новые функциональные возможности, не нарушая при этом существующие свойства. Функциональность Qt 3D разделена на так называемые аспекты, каждый из которых инкапсулирует конкретную предметную область, такую как рендеринг (Render Aspect), ввод (Input Aspect) или анимация (Animation Aspect).

В этой короткой серии статей вы познакомитесь с процессом добавления нового аспекта, который предоставляет типы компонентов и поведение для новой области, не охваченной Qt 3D из коробки. В этом примере мы решили реализовать аспект, который позволяет рассчитывать текущую среднюю частоту кадров. Конечно, это можно было бы добавить в средство визуализации, но это достаточно просто, чтобы быть хорошим примером для наших сегодняшних целей. Полный исходный код примера доступен для загрузки .


Обзор

Приложение, которое мы соберем, выглядит так. Это - очень простая Qt 3D-сцена, и окно, показывающее текущую среднюю частоту кадров.

Существует несколько частей, которые нам нужно учитывать при добавлении нового функционала:

  • Аспект - отвечает за организацию любых заданий, требующих выполнения каждого фрейма, и за управление хранением любых внешних объектов.
  • Компоненты - предоставляются вашей библиотекой или приложением, которые в совокупности придают конечному объекту новое поведение. Компоненты - это основной API, который вам нужно создавать.
  • Узлы (Nodes на рисунке) - как и для компонентов, за исключением того, что подклассы QNode обычно предоставляют вспомогательные данные, требуемые компонентом. Например QAnimationClipLoader является узлом, который предоставляет ключевые данные анимации кадров компоненту QClipAnimator.
  • Бэкэнд-узлы (Backend Nodes на рисунке) - Бэкэнд-копии для любых внешних компонентов или узлов, предоставляемых вашей библиотекой / приложением. Это объекты, которые обычно обрабатываются заданиями, запущенными в пуле потоков, как это продиктовано самим аспектом.
  • Mapper - Пользовательские карты, которые регистрируются с учетом этого аспекта и отвечают за создание, выборку и уничтожение внутренних узлов по требованию. Mapper используется QAbstractAspect и QAspectEngine для синхронизации времени жизни внешних и внутренних объектов.
  • Задания - создаются и планируются аспектом, обрабатывают базовые узлы. Задания могут также отправлять события во внешние узлы и компоненты, если свойства изменяются.
  • Арбитр изменений - отвечает за доставку событий между фронтэнд и бэкэнд объектами. С ним ничего не нужно делать, но имейте в виду его существование.

Добавление аспекта

Написание исходного аспекта действительно тривиально. Просто унаследуйте QAbstractAspect и зарегистрируйте его с помощью QAspectEngine:

customaspect.h

class CustomAspect : public Qt3DCore::QAbstractAspect
{
    Q_OBJECT
public:
    explicit CustomAspect(QObject *parent = nullptr)
        : Qt3DCore::QAbstractAspect(parent)
    {}

protected:
    QVector<Qt3DCore::QAspectJobPtr> jobsToExecute(qint64 time) override
    {
        qDebug() << Q_FUNC_INFO << "Frame time =" << time;
        return {};
    }
};

main.cpp

int main(int argc, char **argv)
{
    QGuiApplication app(argc, argv);
    Qt3DExtras::Quick::Qt3DQuickWindow view;

    view.engine()->aspectEngine()->registerAspect(new CustomAspect);

    view.setSource(QUrl("qrc:/main.qml"));
    view.show();
    return app.exec();
}

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

Компонент FpsMonitor

Мы хотим добавить функционал, который бы выводил информацию о средней частоте кадров, усредненной по заданному числу кадров. Мы можем написать API-интерфейс примерно так:

fpsmonitor.h

class FpsMonitor : public Qt3DCore::QComponent
{
    Q_OBJECT
    Q_PROPERTY(int rollingMeanFrameCount READ rollingMeanFrameCount WRITE setRollingMeanFrameCount NOTIFY rollingMeanFrameCountChanged)
    Q_PROPERTY(float framesPerSecond READ framesPerSecond NOTIFY framesPerSecondChanged)

public:
    explicit FpsMonitor(Qt3DCore::QNode *parent = nullptr);

    float framesPerSecond() const;
    int rollingMeanFrameCount() const;

public slots:
    void setRollingMeanFrameCount(int rollingMeanFrameCount);

signals:
    void framesPerSecondChanged(float framesPerSecond);
    void rollingMeanFrameCountChanged(int rollingMeanFrameCount);

private:
    float m_framesPerSecond;
    int m_rollingMeanFrameCount;
};

Обратите внимание, что мы используем декларативный API на основе свойств, чтобы этот класс можно было легко использовать как из QML, так и из C ++. Свойство rollingMeanFrameCount является обычным свойством чтения и записи, а реализация функций setter и getter полностью стандартная. Это свойство будет использоваться для управления количеством кадров, по которым мы вычисляем скользящую среднюю частоту кадров. Свойство framesPerSecond - это свойство только для чтения, которое позже будет установлено из задания, которое мы будем писать для обработки компонентов FpsMonitor в пуле потоков Qt 3D .

Создание небольшого тестового приложения

Прежде чем мы сможем использовать наш пользовательский компонент из QML, мы должны зарегистрировать тип с помощью системы типов QML. Это простой однострочный код, который мы можем добавить к нашей основной функции:

main.cpp

qmlRegisterType<FpsMonitor>("CustomModule", 1, 0, "FpsMonitor");
rootContext->setContextProperty("_window", &view);

где мы также воспользовались возможностью экспортировать окно в QML-контекст (это нам понадобится в определенный момент).

Как только это будет сделано, мы можем импортировать модуль CustomModule QML и использовать компонент FpsMonitor так же, как любой из встроенных типов.

main.qml

Entity {
    components: [
        FpsMonitor {
            rollingMeanFrameCount: 20
            onFramesPerSecondChanged: {
                var fpsString = parseFloat(Math.round(framesPerSecond * 100) / 100).toFixed(2);
                _window.title = "CustomAspect: FPS = " + fpsString
                        + " (" + rollingMeanFrameCount + " frame average)"
            }
        }
    ]
}

Конечно, на данный момент FpsMonitor не делает ничего, кроме установки значения свойства rollingMeanFrameCount. Как только мы закончим бэкэнд, приведенный выше код обновит заголовок окна, чтобы показать текущую среднюю частоту кадров и количество кадров, используемых для ее расчета.

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

Статья написана: Sean Harmer | Суббота, Ноябрь 25, 2017г.

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Вам это нравится? Поделитесь в социальных сетях!

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
AD

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:50баллов,
  • Очки рейтинга-4
m
  • molni99
  • 26 октября 2024 г. 1:37

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:80баллов,
  • Очки рейтинга4
m
  • molni99
  • 26 октября 2024 г. 1:29

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:20баллов,
  • Очки рейтинга-10
Последние комментарии
ИМ
Игорь Максимов22 ноября 2024 г. 11:51
Django - Урок 017. Кастомизированная страница авторизации на Django Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
Evgenii Legotckoi
Evgenii Legotckoi31 октября 2024 г. 14:37
Django - Урок 064. Как написать расширение для Python Markdown Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZE19 октября 2024 г. 8:19
Читалка fb3-файлов на Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь Максимов5 октября 2024 г. 7:51
Django - Урок 064. Как написать расширение для Python Markdown Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas55 июля 2024 г. 11:02
QML - Урок 016. База данных SQLite и работа с ней в QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
Сейчас обсуждают на форуме
Evgenii Legotckoi
Evgenii Legotckoi24 июня 2024 г. 15:11
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
t
tonypeachey115 ноября 2024 г. 6:04
google domain [url=https://google.com/]domain[/url] domain [http://www.example.com link title]
NSProject
NSProject4 июня 2022 г. 3:49
Всё ещё разбираюсь с кешем. В следствии прочтения данной статьи. Я принял для себя решение сделать кеширование свойств менеджера модели LikeDislike. И так как установка evileg_core для меня не была возможна, ибо он писался…
9
9Anonim25 октября 2024 г. 9:10
Машина тьюринга // Начальное состояние 0 0, ,<,1 // Переход в состояние 1 при пустом символе 0,0,>,0 // Остаемся в состоянии 0, двигаясь вправо при встрече 0 0,1,>…

Следите за нами в социальных сетях