Top.Mail.Ru

Qt и QML. mtPaint3D. Создаем утилиту для 3D-рисования. День 8.

Предпоследний, восьмой день, из отведенных девяти на разработку аналога Paint3D. В целом, времени с запасом, можно не спешить 😉 Среди добавленных вчера фигур не хватает одной из наиболее интересных, а именно кривой Безье. Вот восьмой день и надо ознаменовать решением этого вопроса!

Мне в данной утилите требуется на данный момент кривая 2-го порядка, то есть квадратичная кривая Безье, построенная соответственно по 3-м опорным точкам:

Кривая Безье 2-го порядка.

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

Так вот, на практике мы будем находить координаты N точек кривой. Это будет первичными данными, на основе которых в дальнейшем получим 3D-объект на сцене. Но не забегаем вперед. Итак, по сути входные данные представляют из себя следующее:

  • координаты опорных точек - basePointsCoordinates:
QVector<QVector3D> basePointsCoordinates;
  • количество промежуточных точек кривой, которые мы будем рассчитывать - curvePointsNum:
constexpr quint32 curvePointsNum = 10;
  • количество опорных точек - pointsNum:
constexpr quint32 pointsNum = 3;

Выходными данными этой локальной задачи будут координаты промежуточных точек - curvePointsCoordinates:

QVector<QVector3D> curvePointsCoordinates;

Графически смысл такой. Для примера я тут привел 10 точек, чтобы было проще нарисовать ) При этом крайние точки совпадают с опорными.

Построение кривой Безье.

Резюмируем - финальную задачу по отрисовке кривых Безье мы разбили на несколько локальных, первая из которых заключается в определении curvePointsCoordinates из перечисленных выше входных данных 👍

Переходим к осуществлению... "Базовый" алгоритм можно реализовать так:

void Curve::computeCurvePointsCoordinates()
{
    qreal tStep = static_cast<qreal>(1) / (curvePointsNum - 1);
    qreal t = 0;

    for(quint32 j = 0; j < curvePointsNum; j++)
    {
        QVector3D newPoint(0, 0, 0);

        for (quint32 i = 0; i < pointsNum; i++)
        {
            newPoint.setX(computeCoordinate(newPoint.x(), basePointsCoordinates.at(i).x(), i, t));
            newPoint.setY(computeCoordinate(newPoint.y(), basePointsCoordinates.at(i).y(), i, t));
            newPoint.setZ(computeCoordinate(newPoint.z(), basePointsCoordinates.at(i).z(), i, t));
        }

        curvePointsCoordinates[j] = newPoint;
        t += tStep;
    }

Где также используются вспомогательные функции, по мере появления:

qreal Curve::computeCoordinate(qreal currentCoordinate, qreal baseCoordinate, quint32 baseIndex, qreal t)
{
    currentCoordinate += computeBinomial((pointsNum - 1), baseIndex) * qPow(t, baseIndex) *
                                         qPow((1 - t), (pointsNum - 1 - baseIndex)) * baseCoordinate;

    return currentCoordinate;
}



quint32 Curve::computeFactorial(quint32 arg)
{
    if (arg <= 1)
    {
        return 1;
    }
    else
    {
        arg = arg * computeFactorial(arg - 1);
    }

    return arg;
}



qreal Curve::computeBinomial(qreal arg1, qreal arg2)
{
    qreal res;
    res = computeFactorial(arg1) / (computeFactorial(arg2) * computeFactorial(arg1 - arg2));
    return res;
}

На условном входе: basePointsCoordinates, curvePointsNum и pointsNum.

На выходе: curvePointsCoordinates.

Но данный алгоритм крайне печален с точки зрения производительности, причем это именно тот случай, когда можно упростить без потери качества. И получаем второй вариант, который и оставим в качестве основного:

void Curve::computeCurvePointsCoordinates()
{
    qreal tStep = static_cast<qreal>(1) / (curvePointsNum - 1);
    qreal t = 0;

    for(quint32 j = 0; j < curvePointsNum; j++)
    {
        QVector3D newPoint(0, 0, 0);

        newPoint = (qPow(1 - t, 2) * basePointsCoordinates.at(0) +
                    2 * (1 - t) * t * basePointsCoordinates.at(1) +
                    qPow(t, 2) * basePointsCoordinates.at(2));

        curvePointsCoordinates[j] = newPoint;
        t += tStep;
    }
}

Все, точки у нас есть, осталось произвести их графическое отображение. Зная возможности QML, которые нам предоставляются для базовых 3D-объектов, можем прикинуть, какие есть варианты для отображения кривой на сцене. И основных варианта два:

  • представить кривую как набор близко расположенных друг к другу точек
  • представить кривую как набор цилиндров небольшого диаметра
Варианты построения.

На изображениях я специально параметры "промежуточных" фигур сделал такими, чтобы была наглядно видна структура. В реальности мы сделаем так, чтобы кривая выглядела как единый объект. И остановимся на втором варианте, так как он намного более оптимален и удобен.

Соответственно, кривая будет составлена из (curvePointsNum - 1) цилиндров:

Кривая Безье второго порядка.

Логика прежняя, просто теперь один 3D-объект состоит из ряда Entity, механизм от этого не меняется. Таким образом, в конструкторе объекта Curve создаем все необходимые нам сущности:

Curve::Curve(Qt3DCore::QEntity *parentEntity, quint32 objectId, const QVector3D &coordinate) :
              PaintObject(parentEntity, objectId),
              activePointIndex(0)
{
    type = PaintObject::Type::Curve;

    Qt3DExtras::QDiffuseSpecularMaterial *defaultMaterial = new Qt3DExtras::QDiffuseSpecularMaterial();
    defaultMaterial->setSpecular(QColor(Qt::white));
    defaultMaterial->setShininess(15);

    Qt3DExtras::QDiffuseSpecularMaterial *pointsMaterial = new Qt3DExtras::QDiffuseSpecularMaterial();
    pointsMaterial->setSpecular(QColor(Qt::white));
    pointsMaterial->setShininess(15);
    pointsMaterial->setAmbient(QColor("#218e59"));

    for (quint32 i = 0; i < curvePointsNum; i++)
    {
        curvePointsCoordinates.append(QVector3D(coordinate.x(),
                                                coordinate.y(),
                                                constants::basePlaneZExtent / 2 + pointSphereRadius));
    }

    for (quint32 i = 0; i < pointsNum; i++)
    {
        basePointsCoordinates.append(QVector3D(coordinate.x(),
                                               coordinate.y(),
                                               constants::basePlaneZExtent / 2 + pointSphereRadius));

        pointEntities.append(new Qt3DCore::QEntity(entity.data()));

        Qt3DExtras::QSphereMesh *mesh = new Qt3DExtras::QSphereMesh();
        Qt3DCore::QTransform *transform = new Qt3DCore::QTransform();

        mesh->setRadius(pointSphereRadius);

        transform->setTranslation(QVector3D(coordinate.x(),
                                            coordinate.y(),
                                            constants::basePlaneZExtent / 2 + pointSphereRadius));

        pointEntities.at(i)->addComponent(mesh);
        pointEntities.at(i)->addComponent(transform);
        pointEntities.at(i)->addComponent(pointsMaterial);
    }

    for (quint32 i = 0; i < segmentsNum; i++)
    {
        segmentEntities.append(new Qt3DCore::QEntity(entity.data()));

        Qt3DExtras::QCylinderMesh *mesh = new Qt3DExtras::QCylinderMesh();
        Qt3DCore::QTransform *transform = new Qt3DCore::QTransform();

        mesh->setRadius(segmentCylinderRadius);
        mesh->setRings(100);
        mesh->setSlices(100);
        mesh->setLength(0.75);

        transform->setTranslation(QVector3D(coordinate.x(),
                                            coordinate.y(),
                                            constants::basePlaneZExtent / 2 + pointSphereRadius));

        segmentEntities.at(i)->addComponent(mesh);
        segmentEntities.at(i)->addComponent(transform);
        segmentEntities.at(i)->addComponent(defaultMaterial);
    }

    initialCoordinate = coordinate;
}

И при перемещении указателя:

void Curve::onMouseMoved(const QVector3D &coordinate)
{
    basePointsCoordinates[endPointIndex] = coordinate;
    basePointsCoordinates[endPointIndex].setZ(basePointsCoordinates.at(endPointIndex).z() + pointSphereRadius);

    basePointsCoordinates[middlePointIndex] = (coordinate + initialCoordinate) / 2;
    basePointsCoordinates[middlePointIndex].setZ(basePointsCoordinates.at(middlePointIndex).z() + pointSphereRadius);

    computeCurvePointsCoordinates();
    updateEntities();
}

Построение кривой сводится к двум этапам:

  • математический этап, рассмотренный в начале статьи, заключается в расчете координат промежуточных точек кривой - computeCurvePointsCoordinates()
  • графический этап представляет из себя обновление отображаемых примитивов, из которых складывается 3D-кривая - updateEntities()
void Curve::updateEntities()
{
    for (int i = 0; i < pointEntities.size(); i++)
    {
        Qt3DCore::QTransform *currentTransform = getEntityTransform(pointEntities.at(i));
        currentTransform->setTranslation(QVector3D(basePointsCoordinates.at(i).x(),
                                                   basePointsCoordinates.at(i).y(),
                                                   basePointsCoordinates.at(i).z()));
    }

    for (int i = 0; i < segmentEntities.size(); i++)
    {
        QVector3D neighborPointsVector = curvePointsCoordinates.at(i + 1) - curvePointsCoordinates.at(i);

        Qt3DCore::QTransform *currentTransform = getEntityTransform(segmentEntities.at(i));
        QVector3D middlePoint = (curvePointsCoordinates.at(i) + curvePointsCoordinates.at(i + 1)) / 2;
        currentTransform->setTranslation(middlePoint);
        currentTransform->setRotation(QQuaternion::rotationTo(QVector3D(0, 1, 0), neighborPointsVector));

        Qt3DExtras::QCylinderMesh *currentMesh = getSegmentEntityMesh(segmentEntities.at(i));
        currentMesh->setLength(neighborPointsVector.length());
    }
}

Полный код и ссылка на проект будут в конце статьи. А текущий результат выглядит так:

Кривая на QML.

Соответственно, для кривых Безье, как и ожидалось, методы PaintObject'а будут переопределены. В частности, move() свое базовое предназначение имеет в полном перемещении объекта, здесь же перемещаться будет лишь одна из опорных точек. Вслед за этим будет осуществляться "двухэтапная" перестройка всей кривой:

void Curve::move(const QVector3D &position)
{
    basePointsCoordinates[activePointIndex] = position;

    computeCurvePointsCoordinates();
    updateEntities();
}

Аналогично, при изменении цвета будет изменяться цвет промежуточных цилиндров, опорные точки я сделал фиксированными (с точки зрения их цвета). Никаких нужных параметров кривой для изменения я пока не придумал, поэтому панель CommonObjectPanel смысла для кривых Безье в себе не несет. Функционала, который дает произвольное перемещение любой из трех точек в пространстве, мне достаточно, он, в общем-то, и был необходим изначально. Отрывки кода для вышеперечисленного, пожалуй, вставлять не буду, все равно удобнее посмотреть полностью 👇

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

Так же как мы делали для новых 3D-объектов в предыдущей части производим добавление кнопки на панель инструментов и сопутствующие изменения. И получаем:

P. S. Для кривой стали неактуальными кнопки для вращения объекта, поэтому к TransformPanel я добавил свойство rotationEnabled, которое позволяет не отображать эти режимы. Таким образом, для кривой мы имеем там только три кнопки для перемещения по осям.

На этом заканчиваем 8-й день! ) Все проходит четко по плану, остался один день, и осталось доработать дизайн, изначально и отложенный на финальные этапы 👌

И традиционно - код и ссылка:

#include "curve.h"



constexpr quint32 pointsNum = 3;
constexpr quint32 curvePointsNum = 40;
constexpr quint32 segmentsNum = curvePointsNum - 1;
constexpr qreal pointSphereRadius = 0.16;
constexpr qreal segmentCylinderRadius = 0.05;

constexpr quint32 middlePointIndex = 1;
constexpr quint32 endPointIndex = 2;



Curve::Curve(Qt3DCore::QEntity *parentEntity, quint32 objectId, const QVector3D &coordinate) :
              PaintObject(parentEntity, objectId),
              activePointIndex(0)
{
    type = PaintObject::Type::Curve;

    Qt3DExtras::QDiffuseSpecularMaterial *defaultMaterial = new Qt3DExtras::QDiffuseSpecularMaterial();
    defaultMaterial->setSpecular(QColor(Qt::white));
    defaultMaterial->setShininess(15);

    Qt3DExtras::QDiffuseSpecularMaterial *pointsMaterial = new Qt3DExtras::QDiffuseSpecularMaterial();
    pointsMaterial->setSpecular(QColor(Qt::white));
    pointsMaterial->setShininess(15);
    pointsMaterial->setAmbient(QColor("#218e59"));

    for (quint32 i = 0; i < curvePointsNum; i++)
    {
        curvePointsCoordinates.append(QVector3D(coordinate.x(),
                                                coordinate.y(),
                                                constants::basePlaneZExtent / 2 + pointSphereRadius));
    }

    for (quint32 i = 0; i < pointsNum; i++)
    {
        basePointsCoordinates.append(QVector3D(coordinate.x(),
                                               coordinate.y(),
                                               constants::basePlaneZExtent / 2 + pointSphereRadius));

        pointEntities.append(new Qt3DCore::QEntity(entity.data()));

        Qt3DExtras::QSphereMesh *mesh = new Qt3DExtras::QSphereMesh();
        Qt3DCore::QTransform *transform = new Qt3DCore::QTransform();

        mesh->setRadius(pointSphereRadius);

        transform->setTranslation(QVector3D(coordinate.x(),
                                            coordinate.y(),
                                            constants::basePlaneZExtent / 2 + pointSphereRadius));

        pointEntities.at(i)->addComponent(mesh);
        pointEntities.at(i)->addComponent(transform);
        pointEntities.at(i)->addComponent(pointsMaterial);
    }

    for (quint32 i = 0; i < segmentsNum; i++)
    {
        segmentEntities.append(new Qt3DCore::QEntity(entity.data()));

        Qt3DExtras::QCylinderMesh *mesh = new Qt3DExtras::QCylinderMesh();
        Qt3DCore::QTransform *transform = new Qt3DCore::QTransform();

        mesh->setRadius(segmentCylinderRadius);
        mesh->setRings(100);
        mesh->setSlices(100);
        mesh->setLength(0.75);

        transform->setTranslation(QVector3D(coordinate.x(),
                                            coordinate.y(),
                                            constants::basePlaneZExtent / 2 + pointSphereRadius));

        segmentEntities.at(i)->addComponent(mesh);
        segmentEntities.at(i)->addComponent(transform);
        segmentEntities.at(i)->addComponent(defaultMaterial);
    }

    initialCoordinate = coordinate;
}



void Curve::addMaterial()
{

}



void Curve::onMouseMoved(const QVector3D &coordinate)
{
    basePointsCoordinates[endPointIndex] = coordinate;
    basePointsCoordinates[endPointIndex].setZ(basePointsCoordinates.at(endPointIndex).z() + pointSphereRadius);

    basePointsCoordinates[middlePointIndex] = (coordinate + initialCoordinate) / 2;
    basePointsCoordinates[middlePointIndex].setZ(basePointsCoordinates.at(middlePointIndex).z() + pointSphereRadius);

    computeCurvePointsCoordinates();
    updateEntities();
}



QVector<qreal> Curve::getParameters()
{
    QVector<qreal> dummy;
    return dummy;
}



void Curve::update(const QVector<qreal> &parameters)
{
    Q_UNUSED(parameters);
}



Qt3DCore::QTransform* Curve::getEntityTransform(Qt3DCore::QEntity *targetEntity)
{
    return targetEntity->componentsOfType<Qt3DCore::QTransform>().at(0);
}



Qt3DExtras::QDiffuseSpecularMaterial* Curve::getEntityMaterial(Qt3DCore::QEntity *targetEntity)
{
    return targetEntity->componentsOfType<Qt3DExtras::QDiffuseSpecularMaterial>().at(0);
}



Qt3DExtras::QCylinderMesh* Curve::getSegmentEntityMesh(Qt3DCore::QEntity *targetEntity)
{
    return targetEntity->componentsOfType<Qt3DExtras::QCylinderMesh>().at(0);
}



Qt3DCore::QTransform* Curve::getTransform()
{
    Qt3DCore::QTransform *currentTransform = getEntityTransform(pointEntities.at(activePointIndex));
    return currentTransform;
}



QString Curve::getColor()
{
    QString color;

    Qt3DExtras::QDiffuseSpecularMaterial *currentMaterial = getEntityMaterial(segmentEntities.at(0));
    color = currentMaterial->ambient().name();

    return color;
}



void Curve::setColor(const QString &color)
{
    for (int i = 0; i < segmentEntities.size(); i++)
    {
        Qt3DExtras::QDiffuseSpecularMaterial *currentMaterial = getEntityMaterial(segmentEntities.at(i));
        currentMaterial->setAmbient(QColor(color));
    }
}



void Curve::updateEntities()
{
    for (int i = 0; i < pointEntities.size(); i++)
    {
        Qt3DCore::QTransform *currentTransform = getEntityTransform(pointEntities.at(i));
        currentTransform->setTranslation(QVector3D(basePointsCoordinates.at(i).x(),
                                                   basePointsCoordinates.at(i).y(),
                                                   basePointsCoordinates.at(i).z()));
    }

    for (int i = 0; i < segmentEntities.size(); i++)
    {
        QVector3D neighborPointsVector = curvePointsCoordinates.at(i + 1) - curvePointsCoordinates.at(i);

        Qt3DCore::QTransform *currentTransform = getEntityTransform(segmentEntities.at(i));
        QVector3D middlePoint = (curvePointsCoordinates.at(i) + curvePointsCoordinates.at(i + 1)) / 2;
        currentTransform->setTranslation(middlePoint);
        currentTransform->setRotation(QQuaternion::rotationTo(QVector3D(0, 1, 0), neighborPointsVector));

        Qt3DExtras::QCylinderMesh *currentMesh = getSegmentEntityMesh(segmentEntities.at(i));
        currentMesh->setLength(neighborPointsVector.length());
    }
}



void Curve::onClicked(Qt3DRender::QPickEvent *event)
{
    int minIndex = 0;
    qreal minDistance = 0;

    for (int i = 0; i < pointEntities.size(); i++)
    {
        Qt3DCore::QTransform *currentTransform = getEntityTransform(pointEntities.at(i));
        QVector3D point = currentTransform->translation();
        qreal dist = point.distanceToPoint(event->worldIntersection());

        if (i == 0)
        {
            minDistance = dist;
        }
        else
        {
            if (dist < minDistance)
            {
                minIndex = i;
                minDistance = dist;
            }
        }
    }

    activePointIndex = minIndex;
    emit picked(id);
}



void Curve::move(const QVector3D &position)
{
    basePointsCoordinates[activePointIndex] = position;

    computeCurvePointsCoordinates();
    updateEntities();
}



quint32 Curve::computeFactorial(quint32 arg)
{
    if (arg <= 1)
    {
        return 1;
    }
    else
    {
        arg = arg * computeFactorial(arg - 1);
    }

    return arg;
}



qreal Curve::computeBinomial(qreal arg1, qreal arg2)
{
    qreal res;
    res = computeFactorial(arg1) / (computeFactorial(arg2) * computeFactorial(arg1 - arg2));
    return res;
}



qreal Curve::computeCoordinate(qreal currentCoordinate, qreal baseCoordinate, quint32 baseIndex, qreal t)
{
    currentCoordinate += computeBinomial((pointsNum - 1), baseIndex) * qPow(t, baseIndex) *
                                         qPow((1 - t), (pointsNum - 1 - baseIndex)) * baseCoordinate;

    return currentCoordinate;
}



void Curve::computeCurvePointsCoordinates()
{
    qreal tStep = static_cast<qreal>(1) / (curvePointsNum - 1);
    qreal t = 0;

    for(quint32 j = 0; j < curvePointsNum; j++)
    {
        QVector3D newPoint(0, 0, 0);

//        for (quint32 i = 0; i < pointsNum; i++)
//        {
//            newPoint.setX(computeCoordinate(newPoint.x(), basePointsCoordinates.at(i).x(), i, t));
//            newPoint.setY(computeCoordinate(newPoint.y(), basePointsCoordinates.at(i).y(), i, t));
//            newPoint.setZ(computeCoordinate(newPoint.z(), basePointsCoordinates.at(i).z(), i, t));
//        }

        newPoint = (qPow(1 - t, 2) * basePointsCoordinates.at(0) +
                    2 * (1 - t) * t * basePointsCoordinates.at(1) +
                    qPow(t, 2) * basePointsCoordinates.at(2));

        curvePointsCoordinates[j] = newPoint;
        t += tStep;
    }
}
#ifndef CURVE_H
#define CURVE_H



#include <QObject>
#include "paintobject.h"



class Curve : public PaintObject
{
    Q_OBJECT
public:
    Curve(Qt3DCore::QEntity *parentEntity, quint32 objectId, const QVector3D &coordinate = QVector3D(0, 0, 0));
    void onMouseMoved(const QVector3D &coordinate);
    void update(const QVector<qreal> &parameters);
    QVector<qreal> getParameters();

    Qt3DCore::QTransform* getTransform();
    void move(const QVector3D &position);
    void setColor(const QString &color);
    QString getColor();

private slots:
    void onClicked(Qt3DRender::QPickEvent *event);

private:
    void computeCurvePointsCoordinates();
    qreal computeBinomial(qreal arg1, qreal arg2);
    quint32 computeFactorial(quint32 arg);
    qreal computeCoordinate(qreal currentCoordinate, qreal baseCoordinate, quint32 baseIndex, qreal t);

    void updateEntities();
    void addMaterial();

    Qt3DCore::QTransform* getEntityTransform(Qt3DCore::QEntity *targetEntity);
    Qt3DExtras::QDiffuseSpecularMaterial* getEntityMaterial(Qt3DCore::QEntity *targetEntity);
    Qt3DExtras::QCylinderMesh* getSegmentEntityMesh(Qt3DCore::QEntity *targetEntity);

    QVector<QVector3D> curvePointsCoordinates;
    QVector<QVector3D> basePointsCoordinates;
    QVector<Qt3DCore::QEntity*> pointEntities;
    QVector<Qt3DCore::QEntity*> segmentEntities;
    quint32 activePointIndex;

};



#endif // CURVE_H
#include <QDebug>
#include "paintentity.h"
#include "objects/cuboid.h"
#include "objects/curve.h"
#include "objects/cone.h"
#include "objects/sphere.h"
#include "objects/cylinder.h"
#include "objects/torus.h"
#include "objects/extrudedtext.h"



PaintEntity::PaintEntity(QNode *parent) : Qt3DCore::QEntity(parent),
    activeTool(ToolType::Cuboid),
    activePaintObject(nullptr),
    activeColor("#b22222")
{
}



void PaintEntity::paintObjectPicked(quint32 pickedId)
{
    if (activeTool == PaintEntity::ToolType::Select)
    {
        paintObjects.at(pickedId)->startSelectAnimation();
        activePaintObject = paintObjects.at(pickedId).data();
        emit activePaintObjectChanged();
    }
    else
    {
        if (activeTool == PaintEntity::ToolType::Delete)
        {
            paintObjects.at(pickedId)->startDeleteAnimation();
        }
    }
}



void PaintEntity::removePaintObject(quint32 objectId)
{
    if (activePaintObject != nullptr)
    {
        if (objectId == activePaintObject->getId())
        {
            activePaintObject = nullptr;
            emit activePaintObjectChanged();
        }
    }

    paintObjects.remove(objectId);
    updatePaintObjectIds(objectId);
}



void PaintEntity::updatePaintObjectIds(int deletedIndex)
{
    for (int i = deletedIndex; i < paintObjects.size(); i++)
    {
        paintObjects.at(i)->setId(i);
    }
}



void PaintEntity::onBasePlaneMousePressed(const QVector3D &coordinate)
{
    switch (activeTool)
    {
        case PaintEntity::ToolType::Cuboid:
            paintObjects.append(QSharedPointer<PaintObject>(new Cuboid(this, paintObjects.size(), coordinate)));
            break;

        case PaintEntity::ToolType::Sphere:
            paintObjects.append(QSharedPointer<PaintObject>(new Sphere(this, paintObjects.size(), coordinate)));
            break;

        case PaintEntity::ToolType::Cone:
            paintObjects.append(QSharedPointer<PaintObject>(new Cone(this, paintObjects.size(), coordinate)));
            break;

        case PaintEntity::ToolType::Cylinder:
            paintObjects.append(QSharedPointer<PaintObject>(new Cylinder(this, paintObjects.size(), coordinate)));
            break;

        case PaintEntity::ToolType::Torus:
            paintObjects.append(QSharedPointer<PaintObject>(new Torus(this, paintObjects.size(), coordinate)));
            break;

        case PaintEntity::ToolType::ExtrudedText:
            paintObjects.append(QSharedPointer<PaintObject>(new ExtrudedText(this, paintObjects.size(), coordinate)));
            break;

        case PaintEntity::ToolType::Curve:
            paintObjects.append(QSharedPointer<PaintObject>(new Curve(this, paintObjects.size(), coordinate)));
            break;

        default:
            // This branch should never be executed
            break;
    }

    int currentIndex = paintObjects.size() - 1;
    paintObjects.at(currentIndex)->setColor(activeColor);
    QObject::connect(paintObjects.at(currentIndex).data(), &PaintObject::picked,
                     this, &PaintEntity::paintObjectPicked);
    QObject::connect(paintObjects.at(currentIndex).data(), &PaintObject::readyForDeletion,
                     this, &PaintEntity::removePaintObject);
}



void PaintEntity::onBasePlaneMouseMoved(const QVector3D &coordinate)
{
    paintObjects.at(paintObjects.size() - 1)->onMouseMoved(coordinate);
}



void PaintEntity::setActiveColor(const QString &color)
{
    activeColor = color;

    if (activeTool == PaintEntity::ToolType::Select)
    {
        if (activePaintObject != nullptr)
        {
            activePaintObject->setColor(activeColor);
        }
    }
}



PaintEntity::ToolType PaintEntity::getActiveTool()
{
    return activeTool;
}



void PaintEntity::setActiveTool(ToolType tool)
{
    activeTool = tool;
    bool pickerState = false;

    if ((activeTool == PaintEntity::ToolType::Select) ||
        (activeTool == PaintEntity::ToolType::Delete))
    {
        pickerState = true;
    }

    for (int i = 0; i < paintObjects.size(); i++)
    {
        paintObjects.at(i)->setPickerEnabled(pickerState);
    }
}



PaintObject* PaintEntity::getPaintObject()
{
    return activePaintObject;
}
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import Qt3D.Core 2.15
import QtQuick3D 1.15
import QtQuick.Scene3D 2.15
import Qt3D.Render 2.15
import Qt3D.Input 2.15
import Qt3D.Extras 2.15
import QtQuick3D.Materials 1.15
import "../qml/objects"

import microtechnics.paintEntity 1.0
import microtechnics.paintObject 1.0



ApplicationWindow {
    id: window
    width: Screen.width * 3 / 4
    height: Screen.height * 3 / 4
    visible: true
    title: qsTr("mtPaint3D")
    color: "#505050"

    onClosing: {
        paintEntity.destroy();
    }

    GridLayout {
        id: mainLayout
        rows: 12
        columns: 6
        flow: GridLayout.TopToBottom
        anchors.fill: parent

        component GridRectangle : Rectangle {
            Layout.fillHeight: true
            Layout.fillWidth: true
            Layout.preferredWidth: Layout.columnSpan
            Layout.preferredHeight: Layout.rowSpan

            color: "#808080"
            border.color: Qt.darker(color)
            border.width: 1
        }

        GridRectangle {
            id: objectBlock

            Layout.row: 0
            Layout.column: 0
            Layout.rowSpan: 12
            Layout.columnSpan: 1

            ScrollView {
                clip: true
                anchors.fill: parent
                contentHeight: toolPanel.height + colorPanel.height + colorPanel.anchors.topMargin +
                               transformPanel.height + transformPanel.anchors.topMargin + objectsPanels.childrenRect.height +
                               extrudedTextPanel.anchors.topMargin

                ToolPanel {
                    id: toolPanel
                    anchors.left: parent.left
                    anchors.right: parent.right
                    anchors.top: parent.top

                    onSelectedToolChanged: {
                        if ((toolPanel.selectedTool === PaintEntity.ToolType.Select) ||
                            (toolPanel.selectedTool === PaintEntity.ToolType.Delete)) {
                            baseCuboidPicker.paintEnabled = false;
                        } else {
                            baseCuboidPicker.paintEnabled = true;
                        }

                        paintEntity.activeTool = toolPanel.selectedTool;
                    }
                }

                ColorPanel {
                    id: colorPanel
                    anchors.left: parent.left
                    anchors.right: parent.right
                    anchors.top: toolPanel.bottom
                    anchors.topMargin: 10
                    visible: true

                    onSelectedColorChanged: {
                        paintEntity.setActiveColor(selectedColor);
                    }
                }

                TransformPanel {
                    id: transformPanel
                    anchors.left: parent.left
                    anchors.right: parent.right
                    anchors.top: colorPanel.bottom
                    anchors.topMargin: 10

                    visible: false

                    positionLimits: Qt.vector3d(baseCuboid.xExtent, baseCuboid.yExtent, 20)

                    onAngleChanged: {
                        if (paintEntity.activePaintObject !== null) {
                            paintEntity.activePaintObject.rotate(angle);
                        }
                    }

                    onPositionChanged: {
                        if (paintEntity.activePaintObject !== null) {
                            paintEntity.activePaintObject.move(position);
                        }
                    }
                }

                Item {
                    id: objectsPanels
                    anchors.left: parent.left
                    anchors.right: parent.right
                    anchors.top: transformPanel.bottom

                    CommonObjectPanel {
                        id: commonObjectPanel
                        anchors.left: parent.left
                        anchors.right: parent.right
                        visible: false

                        onParametersChanged: {
                            if (paintEntity.activePaintObject !== null) {
                                paintEntity.activePaintObject.update(parameters);
                            }
                        }
                    }

                    ExtrudedTextPanel {
                        id: extrudedTextPanel
                        anchors.left: parent.left
                        anchors.right: parent.right
                        anchors.top: commonObjectPanel.bottom
                        anchors.topMargin: 20
                        visible: false

                        onTextInputChanged: {
                            if (paintEntity.activePaintObject !== null) {
                                paintEntity.activePaintObject.updateText(textInput);
                            }
                        }
                    }
                }
            }
        }

        GridRectangle {
            id: viewBlock

            Layout.row: 0
            Layout.column: 1
            Layout.rowSpan: 1
            Layout.columnSpan: 5

            Row {
                id: viewButtonsRow
                spacing: 2
                anchors.horizontalCenter: parent.horizontalCenter
                anchors.verticalCenter: parent.verticalCenter

                Button {
                    id: topViewButton
                    text: qsTr("Top View")

                    onClicked:
                        ParallelAnimation {
                            PropertyAnimation { target: camera; property: "position"; to: Qt.vector3d(0.0, 0.0, 35.0); duration: 675; }
                            PropertyAnimation { target: camera; property: "upVector"; to: Qt.vector3d(0.0, 1.0, 0.0); duration: 675; }
                    }
                }

                Button {
                    id: frontViewButton
                    text: qsTr("Front View")

                    onClicked:
                        ParallelAnimation {
                            PropertyAnimation { target: camera; property: "position"; to: Qt.vector3d(0.0, -25.0, 25.0); duration: 675; }
                            PropertyAnimation { target: camera; property: "upVector"; to: Qt.vector3d(0.0, 0.0, 1.0); duration: 675; }
                    }
                }
            }
        }

        GridRectangle {
            id: paintBlock

            Layout.row: 1
            Layout.column: 1
            Layout.rowSpan: 11
            Layout.columnSpan: 5

            Scene3D {
                id: scene3D
                anchors.fill: parent
                focus: true
                aspects: ["input", "logic"]

                Entity {
                    id: rootEntity

                    Entity {
                        id: lightEntity
                        components: [
                          PointLight {
                              color: "#ffffa7"
                              intensity: 0.5
                              enabled: true
                          },
                          Transform {
                              translation: Qt.vector3d(10, 0, 10)
                          }
                        ]
                    }

                    Camera {
                        id: camera

                        projectionType: CameraLens.PerspectiveProjection
                        fieldOfView: 45
                        nearPlane : 0.1
                        farPlane : 100
                        aspectRatio: 1
                        position: Qt.vector3d(0.0, -25.0, 25.0)
                        upVector: Qt.vector3d(0.0, 0.0, 1.0)
                        viewCenter: Qt.vector3d(0.0, 0.0, 0.0)
                    }

                    PaintCameraController {
                        camera: camera
                        lookSpeed: 35
                    }

                    RenderSettings {
                        id: renderSettings
                        activeFrameGraph: ForwardRenderer {
                            camera: camera
                            clearColor: "transparent"
                        }
                        pickingSettings.pickMethod: PickingSettings.TrianglePicking
                    }

                    InputSettings { id: inputSettings }

                    Entity {
                        id: baseCuboidEntity

                        CuboidMesh {
                            id: baseCuboid

                            xExtent: 35
                            yExtent: 25
                            zExtent: basePlaneZExtent
                        }

                        Transform {
                            id: baseCuboidTransform
                        }

                        DiffuseSpecularMaterial {
                            id: baseCuboidMaterial
                            ambient: "#ffffff"
                            shininess: 100
                        }

                        ObjectPicker {
                            id: baseCuboidPicker
                            hoverEnabled: true
                            dragEnabled: true
                            enabled: true
                            property bool isAddingPaintObject: false
                            property bool paintEnabled: true

                            onPressed: {
                                if (paintEnabled === true) {
                                    if (pick.buttons === Qt.LeftButton) {
                                        isAddingPaintObject = true;
                                        paintEntity.onBasePlaneMousePressed(pick.worldIntersection);
                                    }
                                }
                            }

                            onMoved: {
                                var hoverCoordinate = Qt.vector3d(pick.worldIntersection.x, pick.worldIntersection.y, basePlaneHoverTransform.translation.z);
                                basePlaneHoverTransform.translation = hoverCoordinate;

                                if (paintEnabled === true) {
                                    if (isAddingPaintObject === true) {
                                        paintEntity.onBasePlaneMouseMoved(pick.worldIntersection);
                                    }
                                }
                            }

                            onReleased: {
                                isAddingPaintObject = false;
                            }
                        }

                        components: [
                            baseCuboid,
                            baseCuboidMaterial,
                            baseCuboidTransform,
                            baseCuboidPicker
                        ]
                    }

                    Entity {
                        id: basePlaneHoverEntity
                        enabled: true
                        property var zCoordinate: 0.01

                        PlaneMesh {
                            id: basePlaneHoverMesh
                            width: 0.3
                            height: 0.3
                        }

                        Transform {
                            id: basePlaneHoverTransform
                            translation: Qt.vector3d(0, 0, baseCuboid.zExtent + basePlaneHoverEntity.zCoordinate)
                            rotationX: 90
                        }

                        GoochMaterial {
                            id: basePlaneHoverMaterial
                            alpha: 1
                            cool: "#0000b0"
                        }

                        components: [
                            basePlaneHoverMesh,
                            basePlaneHoverMaterial,
                            basePlaneHoverTransform
                        ]
                    }

                    PaintEntity {
                        id: paintEntity

                        function hideObjectControls() {
                            for (var i = 0; i < objectsPanels.visibleChildren.length; i++) {
                                objectsPanels.visibleChildren[i].visible = false;
                            }

                            transformPanel.visible = false;
                            extrudedTextPanel.visible = false;
                            commonObjectPanel.visible = false;
                        }

                        onActivePaintObjectChanged: {
                            hideObjectControls();

                            if (activePaintObject !== null) {
                                colorPanel.setCurrentItem(activePaintObject.color);

                                if (activePaintObject.type === PaintObject.Curve) {
                                    transformPanel.rotationEnabled = false;
                                } else {
                                    transformPanel.rotationEnabled = true;
                                }

                                transformPanel.initialPosition = activePaintObject.position;
                                transformPanel.reset();
                                transformPanel.visible = true;

                                switch(activePaintObject.type) {
                                    case PaintObject.Type.Cuboid:
                                        commonObjectPanel.controlsModel = [
                                                {text: qsTr("X extent"), sliderMin: 0.1, sliderMax: baseCuboid.xExtent, sliderCurrent: activePaintObject.parameters[0]},
                                                {text: qsTr("Y extent"), sliderMin: 0.1, sliderMax: baseCuboid.yExtent, sliderCurrent: activePaintObject.parameters[1]},
                                                {text: qsTr("Z extent"), sliderMin: 0.1, sliderMax: 10, sliderCurrent: activePaintObject.parameters[2]}
                                            ];
                                        break;

                                    case PaintObject.Type.Cone:
                                        commonObjectPanel.controlsModel = [
                                                {text: qsTr("Length"), sliderMin: 0.1, sliderMax: 10, sliderCurrent: activePaintObject.parameters[0]},
                                                {text: qsTr("Bottom radius"), sliderMin: 0.1, sliderMax: 10, sliderCurrent: activePaintObject.parameters[1]},
                                                {text: qsTr("Top radius"), sliderMin: 0.1, sliderMax: 8, sliderCurrent: activePaintObject.parameters[2]}
                                            ];

                                        break;

                                    case PaintObject.Type.Sphere:
                                        commonObjectPanel.controlsModel = [
                                                {text: qsTr("Radius"), sliderMin: 0.1, sliderMax: 10, sliderCurrent: activePaintObject.parameters[0]}
                                            ];

                                        break;

                                    case PaintObject.Type.Cylinder:
                                        commonObjectPanel.controlsModel = [
                                                {text: qsTr("Length"), sliderMin: 0.1, sliderMax: 10, sliderCurrent: activePaintObject.parameters[0]},
                                                {text: qsTr("Radius"), sliderMin: 0.1, sliderMax: 10, sliderCurrent: activePaintObject.parameters[1]}
                                            ];

                                        break;

                                    case PaintObject.Type.Torus:
                                        commonObjectPanel.controlsModel = [
                                                {text: qsTr("Minor radius"), sliderMin: 0.1, sliderMax: 1, sliderCurrent: activePaintObject.parameters[0]},
                                                {text: qsTr("Radius"), sliderMin: 0.1, sliderMax: 10, sliderCurrent: activePaintObject.parameters[1]}
                                            ];

                                        break;

                                    case PaintObject.Type.ExtrudedText:
                                        commonObjectPanel.controlsModel = [
                                                {text: qsTr("Scale"), sliderMin: 0.1, sliderMax: 5, sliderCurrent: activePaintObject.parameters[0]}
                                            ];

                                        extrudedTextPanel.initialText = activePaintObject.text;
                                        extrudedTextPanel.visible = true;
                                        break;

                                    default:
                                        break;
                                }

                                if (activePaintObject.type !== PaintObject.Type.Curve) {
                                    commonObjectPanel.visible = true;
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

Исходный код и проект: mtPaint3D_day_8

Подписаться
Уведомление о
guest
0 комментариев
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x