From 988f62c763eef90e4dafe9ecf6bb05629279278e Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Tue, 2 Apr 2019 16:12:08 +0300 Subject: Add dynamic object creation to presentation C++ API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A new model element can be dynamically created into the scene. The slide and properties of the element can be specified at creation time. A material element is automatically created for each added model. The material is specified with a custom "material" property in the list of creation properties. Task-number: QT3DS-3209 Change-Id: I52b0e929023092110820405473a9399b67a305cd Reviewed-by: Tomi Korpipää --- .../Source/engine/Qt3DSRenderRuntimeBinding.cpp | 1 + src/Runtime/Source/engine/Qt3DSRuntimeView.cpp | 35 ++- src/Runtime/Source/engine/Qt3DSRuntimeView.h | 9 +- src/Runtime/Source/foundation/StringTable.h | 2 + src/Runtime/Source/runtime/Qt3DSIScene.h | 20 +- src/Runtime/Source/runtime/Qt3DSIScriptBridge.h | 18 +- src/Runtime/Source/runtime/Qt3DSQmlEngine.cpp | 307 +++++++++++++++++++++ src/Runtime/Source/runtime/Qt3DSSlideSystem.cpp | 156 +++++++---- src/Runtime/Source/runtime/Qt3DSSlideSystem.h | 2 + src/Runtime/Source/viewer/Qt3DSViewerApp.cpp | 9 + src/Runtime/Source/viewer/Qt3DSViewerApp.h | 3 + src/Viewer/qmlviewer/Qt3DSRenderer.cpp | 17 +- src/Viewer/qmlviewer/Qt3DSView.cpp | 2 +- src/Viewer/studio3d/q3dscommandqueue.cpp | 42 ++- src/Viewer/studio3d/q3dscommandqueue_p.h | 13 +- src/Viewer/studio3d/q3dspresentation.cpp | 20 ++ src/Viewer/studio3d/q3dspresentation.h | 2 + .../simple_cube_animation.uia | 6 - .../simple_cube_animation.uip | 50 ---- tests/auto/viewer/tst_qt3dsviewer.cpp | 163 ++++++++++- tests/auto/viewer/tst_qt3dsviewer.h | 2 +- tests/auto/viewer/tst_qt3dsviewer.qml | 6 +- tests/auto/viewer/viewer.qrc | 6 +- .../materials/Basic Green.materialdef | 25 ++ .../materials/Basic Red.materialdef | 25 ++ .../presentations/simple_cube_animation.uip | 54 ++++ .../simple_cube_animation.uia | 15 + 27 files changed, 836 insertions(+), 174 deletions(-) delete mode 100644 tests/auto/viewer/simple_cube_animation/simple_cube_animation.uia delete mode 100644 tests/auto/viewer/simple_cube_animation/simple_cube_animation.uip create mode 100644 tests/scenes/simple_cube_animation/materials/Basic Green.materialdef create mode 100644 tests/scenes/simple_cube_animation/materials/Basic Red.materialdef create mode 100644 tests/scenes/simple_cube_animation/presentations/simple_cube_animation.uip create mode 100644 tests/scenes/simple_cube_animation/simple_cube_animation.uia diff --git a/src/Runtime/Source/engine/Qt3DSRenderRuntimeBinding.cpp b/src/Runtime/Source/engine/Qt3DSRenderRuntimeBinding.cpp index 8ba76fd1..f737ec4a 100644 --- a/src/Runtime/Source/engine/Qt3DSRenderRuntimeBinding.cpp +++ b/src/Runtime/Source/engine/Qt3DSRenderRuntimeBinding.cpp @@ -197,6 +197,7 @@ struct Qt3DSRenderScene : public Q3DStudio::IScene } } + qt3ds::NVAllocatorCallback &allocator() override { return m_LoadData->m_AutoAllocator; } Q3DStudio::IPresentation &GetPresentation() override { return *m_RuntimePresentation; } // Update really just adds objects to the dirty set diff --git a/src/Runtime/Source/engine/Qt3DSRuntimeView.cpp b/src/Runtime/Source/engine/Qt3DSRuntimeView.cpp index b582195b..abff8f09 100644 --- a/src/Runtime/Source/engine/Qt3DSRuntimeView.cpp +++ b/src/Runtime/Source/engine/Qt3DSRuntimeView.cpp @@ -46,6 +46,7 @@ #include "Qt3DSRenderContextCore.h" #include "Qt3DSRenderer.h" #include "Qt3DSRenderBufferManager.h" +#include "Qt3DSRenderRuntimeBindingImpl.h" #include "Qt3DSDLLManager.h" #include "foundation/Qt3DSSimpleTypes.h" @@ -214,7 +215,10 @@ public: float dataInputMax(const QString &name) const override; float dataInputMin(const QString &name) const override; - void SetAttribute(const char *elementPath, const char *attributeName, const char *value) override; + void createElement(const QString &parentElementPath, const QString &slideName, + const QHash &properties) override; + void SetAttribute(const char *elementPath, const char *attributeName, + const char *value) override; bool GetAttribute(const char *elementPath, const char *attributeName, void *value) override; void FireEvent(const char *element, const char *evtName) override; bool PeekCustomAction(char *&outElementPath, char *&outActionName) override; @@ -579,9 +583,11 @@ void CRuntimeView::SetDataInputValue( const QString &name, const QVariant &value, Q3DSDataInput::ValueRole property = Q3DSDataInput::ValueRole::Value) { - Q3DStudio::CQmlEngine &theBridgeEngine - = static_cast(m_RuntimeFactoryCore->GetScriptEngineQml()); - theBridgeEngine.SetDataInputValue(name, value, property); + if (m_Application) { + Q3DStudio::CQmlEngine &theBridgeEngine + = static_cast(m_RuntimeFactoryCore->GetScriptEngineQml()); + theBridgeEngine.SetDataInputValue(name, value, property); + } } QList CRuntimeView::dataInputs() const @@ -594,12 +600,29 @@ QList CRuntimeView::dataInputs() const float CRuntimeView::dataInputMax(const QString &name) const { - return m_Application->dataInputMax(name); + if (m_Application) + return m_Application->dataInputMax(name); + + return 0; } float CRuntimeView::dataInputMin(const QString &name) const { - return m_Application->dataInputMin(name); + if (m_Application) + return m_Application->dataInputMin(name); + + return 0; +} + +void CRuntimeView::createElement(const QString &parentElementPath, const QString &slideName, + const QHash &properties) +{ + if (m_Application) { + Q3DStudio::CQmlEngine &theBridgeEngine + = static_cast(m_RuntimeFactoryCore->GetScriptEngineQml()); + theBridgeEngine.createElement(parentElementPath, slideName, properties, + &m_RuntimeFactory->GetQt3DSRenderContext().GetRenderer()); + } } void CRuntimeView::SetAttribute(const char *elementPath, const char *attributeName, const char *value) diff --git a/src/Runtime/Source/engine/Qt3DSRuntimeView.h b/src/Runtime/Source/engine/Qt3DSRuntimeView.h index 7b9a5945..3626fd52 100644 --- a/src/Runtime/Source/engine/Qt3DSRuntimeView.h +++ b/src/Runtime/Source/engine/Qt3DSRuntimeView.h @@ -46,10 +46,6 @@ #include #include -//============================================================================== -// Namespace -//============================================================================== - typedef void (*qml_Function)(void *inUserData); class QRuntimeViewSignalProxy : public QObject @@ -73,9 +69,6 @@ class NVRenderContext; namespace Q3DStudio { -//============================================================================== -// Forwards -//============================================================================== class CTegraInputEngine; class CTegraRenderEngine; class IScene; @@ -196,6 +189,8 @@ public: virtual QList dataInputs() const = 0; virtual float dataInputMax(const QString &name) const = 0; virtual float dataInputMin(const QString &name) const = 0; + virtual void createElement(const QString &parentElementPath, const QString &slideName, + const QHash &properties) = 0; virtual void SetAttribute(const char *elementPath, const char *attributeName, const char *value) = 0; virtual bool GetAttribute(const char *elementPath, const char *attributeName, void *value) = 0; diff --git a/src/Runtime/Source/foundation/StringTable.h b/src/Runtime/Source/foundation/StringTable.h index c7c62199..123992cc 100644 --- a/src/Runtime/Source/foundation/StringTable.h +++ b/src/Runtime/Source/foundation/StringTable.h @@ -49,6 +49,8 @@ #include "foundation/Qt3DSDataRef.h" #include "foundation/Qt3DSOption.h" +#include + namespace qt3ds { namespace foundation { typedef char8_t Qt3DSBChar; diff --git a/src/Runtime/Source/runtime/Qt3DSIScene.h b/src/Runtime/Source/runtime/Qt3DSIScene.h index 3f80f85f..83347e11 100644 --- a/src/Runtime/Source/runtime/Qt3DSIScene.h +++ b/src/Runtime/Source/runtime/Qt3DSIScene.h @@ -38,21 +38,15 @@ namespace qt3ds { namespace render { class IImageLoadListener; } + class NVAllocatorCallback; } -//============================================================================== -// Namespace -//============================================================================== namespace Q3DStudio { -//============================================================================== -// Forwards -//============================================================================== class IPresentation; class RuntimeMatrix; struct SPickFrame; -//============================================================================== /** * @interface IScene * @@ -110,9 +104,6 @@ struct SCameraRect class IScene { - //============================================================================== - // Methods - //============================================================================== protected: virtual ~IScene() {} @@ -122,8 +113,6 @@ public: // Base Interface virtual void SetUserData(void *inUserData) = 0; virtual void *GetUserData() = 0; - // virtual void Clone( TElementList& inElements, TElementList& inElementClones, TElement* - // inNewParent = NULL ) = 0; virtual void CalculateGlobalTransform(TElement *inElement, RuntimeMatrix &outTransform) = 0; virtual void SetLocalTransformMatrix(TElement *inElement, const RuntimeMatrix &inTransform) = 0; // Get bounding box in global space @@ -147,8 +136,7 @@ public: // Base Interface virtual STextSizes GetPresentationDesignDimensions() = 0; // If the rect's right - left == 0.0, this method failed. Possibly because the layer is just - // direct-rendering - // a sub-presentation. + // direct-rendering a sub-presentation. virtual SCameraRect GetCameraBounds(TElement &inElement) = 0; virtual void PositionToScreen(TElement &inElement, qt3ds::QT3DSVec3 &inPos, @@ -161,13 +149,15 @@ public: // Base Interface virtual Q3DStudio::INT32 LoadImageBatch(qt3ds::foundation::CRegisteredString *inFullPaths, INT32 inNumPaths, qt3ds::foundation::CRegisteredString inDefaultImage, - qt3ds::render::IImageLoadListener *inLoadCallback = NULL) = 0; + qt3ds::render::IImageLoadListener *inLoadCallback = nullptr) = 0; virtual SMousePosition WindowToPresentation(const SMousePosition &inWindowPos) = 0; virtual void RegisterOffscreenRenderer(const char *inKey) = 0; virtual void Release() = 0; + + virtual qt3ds::NVAllocatorCallback &allocator() = 0; }; } // namespace Q3DStudio diff --git a/src/Runtime/Source/runtime/Qt3DSIScriptBridge.h b/src/Runtime/Source/runtime/Qt3DSIScriptBridge.h index 4be6fe16..3b26abd2 100644 --- a/src/Runtime/Source/runtime/Qt3DSIScriptBridge.h +++ b/src/Runtime/Source/runtime/Qt3DSIScriptBridge.h @@ -35,9 +35,8 @@ #include "foundation/Qt3DSRefCounted.h" #include "q3dsdatainput.h" -//============================================================================== -// Namespace -//============================================================================== +#include + namespace qt3ds { namespace runtime { class IApplication; @@ -53,6 +52,7 @@ namespace state { namespace qt3ds { namespace render { class IThreadPool; + class IQt3DSRenderer; } } @@ -60,13 +60,9 @@ struct script_State; namespace Q3DStudio { -//============================================================================== -// Forwards -//============================================================================== struct SEventCommand; class IPresentation; -//============================================================================== /** * @interface IScriptBridge * @brief Callback and load interface for a script engine. @@ -116,10 +112,7 @@ protected: class IScriptBridge : public qt3ds::foundation::NVRefCounted { - //============================================================================== - // Methods - //============================================================================== -public: // Construction +public: virtual ~IScriptBridge() {} public: // thread @@ -165,6 +158,9 @@ public: // Elements virtual void SetDataInputValue( const QString &name, const QVariant &value, Q3DSDataInput::ValueRole property = Q3DSDataInput::ValueRole::Value) = 0; + virtual void createElement(const QString &parentElementPath, const QString &slideName, + const QHash &properties, + qt3ds::render::IQt3DSRenderer *renderer) = 0; public: // Components virtual void GotoSlide(const char *component, const char *slideName, diff --git a/src/Runtime/Source/runtime/Qt3DSQmlEngine.cpp b/src/Runtime/Source/runtime/Qt3DSQmlEngine.cpp index 0898df70..23a097a7 100644 --- a/src/Runtime/Source/runtime/Qt3DSQmlEngine.cpp +++ b/src/Runtime/Source/runtime/Qt3DSQmlEngine.cpp @@ -48,6 +48,7 @@ #include "foundation/Qt3DSBroadcastingAllocator.h" #include "Qt3DSRenderInputStreamFactory.h" #include "Qt3DSSlideSystem.h" +#include "Qt3DSRenderModel.h" #include "EASTL/vector.h" #include "EASTL/list.h" @@ -66,6 +67,9 @@ #include "Qt3DSParametersSystem.h" #include "Qt3DSQmlElementHelper.h" #include "q3dsqmlscript.h" +#include "Qt3DSRenderRuntimeBindingImpl.h" +#include "Qt3DSRenderBufferManager.h" // TODO: Needed for adding meshes dynamically (QT3DS-3378) +#include "Qt3DSRenderer.h" #include #include @@ -413,6 +417,11 @@ public: void FireEvent(const char *element, const char *evtName) override; void SetDataInputValue(const QString &name, const QVariant &value, Q3DSDataInput::ValueRole valueRole) override; + void createElement(const QString &parentElementPath, const QString &slideName, + const QHash &properties, + qt3ds::render::IQt3DSRenderer *renderer) override; + //void createMaterial() override; // TODO (QT3DS-3377) + //void createMesh() override; // TODO (QT3DS-3378) void GotoSlide(const char *component, const char *slideName, const SScriptEngineGotoSlideArgs &inArgs) override; @@ -817,6 +826,304 @@ void CQmlEngineImpl::SetDataInputValue( } } +using TPropertyDescAndValueList = eastl::vector; +using TPropertyDesc = qt3ds::runtime::element::SPropertyDesc; + +void CQmlEngineImpl::createElement(const QString &parentElementPath, const QString &slideName, + const QHash &properties, + qt3ds::render::IQt3DSRenderer *renderer) +{ + // Resolve parent element + QByteArray theParentPath = parentElementPath.toUtf8(); + TElement *parentElement = getTarget(theParentPath.constData()); + + if (!parentElement) { + qWarning() << __FUNCTION__ << "Invalid parent element:" << parentElementPath; + return; + } + + auto parentTranslator = static_cast( + parentElement->GetAssociation()); + + if (!parentTranslator || !qt3ds::render::GraphObjectTypes::IsNodeType( + parentTranslator->GetUIPType())) { + qWarning() << __FUNCTION__ << "Parent element is not a valid node"; + return; + } + + TElement &component = parentElement->GetComponentParent(); + auto &parentObject = static_cast(parentTranslator->RenderObject()); + + IPresentation *presentation = parentElement->GetBelongedPresentation(); + + static int idCounter = 0; + ++idCounter; + + // Resolve slide + QByteArray theSlideName = slideName.toUtf8(); + ISlideSystem &slideSystem = presentation->GetSlideSystem(); + int slideIndex = slideSystem.FindSlide(component, theSlideName.constData()); + int currentSlide = static_cast(component).GetCurrentSlide(); + if (slideIndex == 0xff) { + qWarning() << __FUNCTION__ << "Invalid slide name for time context:" << slideName; + return; + } + + // Remove properties requiring custom handling + QHash theProperties = properties; + QString newElementName = theProperties.take(QStringLiteral("name")).toString(); + QString refMatName = theProperties.take(QStringLiteral("material")).toString(); + if (refMatName.startsWith(QLatin1Char('#'))) // Absolute reference + refMatName = refMatName.mid(1); + else if (!refMatName.isEmpty() && !refMatName.contains(QLatin1Char('/'))) + refMatName = QStringLiteral("/") + refMatName; + + if (newElementName.isEmpty()) + newElementName = QStringLiteral("NewElement_%1").arg(idCounter); + QByteArray newElementNameBa = newElementName.toUtf8(); + + // Make sure the name is not duplicate + TElement *existingChild + = parentElement->FindChild(CHash::HashString(newElementNameBa.constData())); + if (existingChild) { + qWarning() << __FUNCTION__ + << "The specified parent" << parentElementPath + << "already has a child with the same name:" << newElementName; + return; + } + + auto &strTable = presentation->GetStringTable(); + const CRegisteredString regName = strTable.RegisterStr(newElementNameBa); + // TODO: Support also some non-model element types, like group and text elements (QT3DS-3381) + const CRegisteredString elementType = strTable.RegisterStr("Model"); + const CRegisteredString elementSubType; + TPropertyDescAndValueList elementProperties; + + // Set properties + auto addStringAttribute = [&strTable](TPropertyDescAndValueList &list, + const QString &inAttName, const QString &inValue) { + QByteArray valueBa = inValue.toUtf8(); + qt3ds::foundation::CStringHandle strHandle = strTable.GetHandle(valueBa.constData()); + UVariant theValue; + theValue.m_StringHandle = strHandle.handle(); + const CRegisteredString attStr = strTable.RegisterStr(inAttName); + list.push_back( + eastl::make_pair(TPropertyDesc(attStr, ATTRIBUTETYPE_STRING), theValue)); + }; + auto addIntAttribute = [&strTable](TPropertyDescAndValueList &list, const QString &inAttName, + int inValue) { + UVariant theValue; + theValue.m_INT32 = inValue; + const CRegisteredString attStr = strTable.RegisterStr(inAttName); + list.push_back( + eastl::make_pair(TPropertyDesc(attStr, ATTRIBUTETYPE_INT32), theValue)); + }; + auto addBoolAttribute = [&strTable](TPropertyDescAndValueList &list, + const QString &inAttName, bool inValue) { + UVariant theValue; + theValue.m_INT32 = int(inValue); + const CRegisteredString attStr = strTable.RegisterStr(inAttName); + list.push_back( + eastl::make_pair(TPropertyDesc(attStr, ATTRIBUTETYPE_BOOL), theValue)); + }; + auto addFloatAttribute = [&strTable](TPropertyDescAndValueList &list, const QString &inAttName, + float inValue) { + UVariant theValue; + theValue.m_FLOAT = inValue; + const CRegisteredString attStr = strTable.RegisterStr(inAttName); + list.push_back( + eastl::make_pair(TPropertyDesc(attStr, ATTRIBUTETYPE_FLOAT), theValue)); + }; + auto addFloat3Attribute = [&strTable](TPropertyDescAndValueList &list, + const QStringList &inAttNames, const QVector3D &inValue) { + for (int i = 0; i < 3; ++i) { + UVariant theValue; + theValue.m_FLOAT = inValue[i]; + const CRegisteredString attStr = strTable.RegisterStr(inAttNames.at(i)); + list.push_back( + eastl::make_pair(TPropertyDesc(attStr, ATTRIBUTETYPE_FLOAT), theValue)); + } + }; + + // Set default values for missing mandatory properties + const QString sourcePathPropName = QStringLiteral("sourcepath"); + const QString startTimePropName = QStringLiteral("starttime"); + const QString endTimePropName = QStringLiteral("endtime"); + const QString eyeBallPropName = QStringLiteral("eyeball"); + Q3DStudio::UVariant attValue; + bool eyeBall = true; + theProperties.value(QStringLiteral("eyeball"), true).toBool(); + if (!theProperties.contains(sourcePathPropName)) + addStringAttribute(elementProperties, sourcePathPropName, QStringLiteral("#Cube")); + if (!theProperties.contains(startTimePropName)) { + parentElement->GetAttribute(Q3DStudio::ATTRIBUTE_STARTTIME, attValue); + addIntAttribute(elementProperties, startTimePropName, int(attValue.m_INT32)); + } + if (!theProperties.contains(endTimePropName)) { + parentElement->GetAttribute(Q3DStudio::ATTRIBUTE_ENDTIME, attValue); + addIntAttribute(elementProperties, endTimePropName, int(attValue.m_INT32)); + } + if (!theProperties.contains(eyeBallPropName)) + addBoolAttribute(elementProperties, eyeBallPropName, true); + else + eyeBall = theProperties.value(QStringLiteral("eyeball")).toBool(); + + QHashIterator it(theProperties); + while (it.hasNext()) { + it.next(); + switch (it.value().type()) { + case QVariant::Double: + addFloatAttribute(elementProperties, it.key(), it.value().toFloat()); + break; + case QVariant::Bool: + addBoolAttribute(elementProperties, it.key(), it.value().toBool()); + break; + case QVariant::Int: + addIntAttribute(elementProperties, it.key(), it.value().toInt()); + break; + case QVariant::String: + addStringAttribute(elementProperties, it.key(), it.value().toString()); + break; + case QVariant::Vector3D: { + QVector3D vec = it.value().value(); + if (it.key() == QLatin1String("rotation")) { + vec.setX(qDegreesToRadians(vec.x())); + vec.setY(qDegreesToRadians(vec.y())); + vec.setZ(qDegreesToRadians(vec.z())); + } + // TODO: Need to support also colors if non-model elements what need colors are + // TODO: supported (QT3DS-3381) + QStringList atts; + atts << (it.key() + QLatin1String(".x")) + << (it.key() + QLatin1String(".y")) + << (it.key() + QLatin1String(".z")); + addFloat3Attribute(elementProperties, atts, vec); + break; + } + default: + qWarning() << __FUNCTION__ << "Unsupported property type for" << it.key(); + break; + } + } + + // Create new element + TElement &newElem = m_Application->GetElementAllocator().CreateElement( + regName, elementType, elementSubType, + toConstDataRef(elementProperties.data(), QT3DSU32(elementProperties.size())), + presentation, parentElement, false); + + QString elementPath = parentElementPath + QLatin1Char('.') + newElementName; + newElem.m_Path = strTable.RegisterStr(elementPath); + + // Insert the new element into the correct slide + if (!slideSystem.addSlideElement(component, slideIndex, newElem, eyeBall)) { + qWarning() << __FUNCTION__ << "Failed to add the new element to a slide"; + // Delete created element if adding to slide failed + m_Application->GetElementAllocator().ReleaseElement(newElem, true); + return; + } + + // Create material element + const CRegisteredString matName = strTable.RegisterStr("refmat"); + const CRegisteredString matType = strTable.RegisterStr("ReferencedMaterial"); + TPropertyDescAndValueList matProperties; + TElement &newMatElem = m_Application->GetElementAllocator().CreateElement( + matName, matType, elementSubType, + toConstDataRef(matProperties.data(), QT3DSU32(matProperties.size())), + presentation, &newElem, false); + + QString matElemPath = elementPath + QLatin1String(".refmat"); + newMatElem.m_Path = strTable.RegisterStr(matElemPath); + + if (!slideSystem.addSlideElement(component, slideIndex, newMatElem, eyeBall)) { + qWarning() << __FUNCTION__ << "Failed to add the new material element to a slide"; + // Delete created element and material element if adding to slide failed + m_Application->GetElementAllocator().ReleaseElement(newElem, true); + return; + } + + + // First check if we can resolve the referenced material before creating any graph objects + // Find a match in material container + // If the specified material is not available in original presentation, or was not specified, + // use the first material found as placeholder + TElement *rootElement = presentation->GetRoot(); + TElement *container = rootElement->FindChild(CHash::HashString("__Container")); + TElement *firstChild = nullptr; + SGraphObject *referencedMaterial = nullptr; + if (container) { + TElement *nextChild = container->GetChild(); + firstChild = nextChild; + while (nextChild) { + QString childName = QString::fromUtf8(nextChild->m_Name); + if (childName.endsWith(refMatName)) { + auto tr = static_cast( + nextChild->GetAssociation()); + referencedMaterial = static_cast( + &tr->RenderObject()); + break; + } + nextChild = nextChild->GetSibling(); + } + } + + if (!referencedMaterial) { + // Empty material is assumed to be deliberate, so don't warn in that case + if (!refMatName.isEmpty()) { + qWarning() << __FUNCTION__ << "Requested material" << refMatName + << "was not found. Trying to find a fallback material."; + } + if (firstChild) { + auto tr = static_cast(firstChild->GetAssociation()); + referencedMaterial = static_cast(&tr->RenderObject()); + } + if (!referencedMaterial) { + // We could create default material into the container in case there is no materials + // in there, but it is unlikely that such a presentation would be used in practice. + qWarning() << __FUNCTION__ << "Unable to resolve a fallback material"; + m_Application->GetElementAllocator().ReleaseElement(newElem, true); + return; + } + } + + // Create new SGraphObject (SNode) + NVAllocatorCallback &allocator = presentation->GetScene()->allocator(); + qt3ds::render::SModel *newObject = QT3DS_NEW(allocator, qt3ds::render::SModel)(); + newObject->m_Id = strTable.RegisterStr((QByteArrayLiteral("_newObject_") + + QByteArray::number(idCounter)).constData()); + parentObject.AddChild(*newObject); + + qt3ds::render::Qt3DSTranslator::CreateTranslatorForElement(newElem, *newObject, allocator); + + // Create material SGraphObject + qt3ds::render::SReferencedMaterial *newMaterial + = QT3DS_NEW(allocator, qt3ds::render::SReferencedMaterial)(); + newMaterial->m_Id = strTable.RegisterStr((QByteArrayLiteral("_newMaterial_") + + QByteArray::number(idCounter)).constData()); + newMaterial->m_ReferencedMaterial = referencedMaterial; + + newObject->AddMaterial(*newMaterial); + + // Determine if element should be active based on start/end times + TTimeUnit startTime = 0; + TTimeUnit stopTime = 0; + if (newElem.GetAttribute(Q3DStudio::ATTRIBUTE_STARTTIME, attValue)) + startTime = TTimeUnit(attValue.m_INT32); + if (newElem.GetAttribute(Q3DStudio::ATTRIBUTE_ENDTIME, attValue)) + stopTime = TTimeUnit(attValue.m_INT32); + TTimeUnit localTime = newElem.GetActivityZone().GetItemLocalTime(newElem); + + bool isActiveRightNow = eyeBall && localTime >= startTime && localTime <= stopTime + && currentSlide == slideIndex; + + newElem.SetActive(isActiveRightNow); + newObject->m_Flags.SetActive(isActiveRightNow); + if (eyeBall) + newElem.GetActivityZone().UpdateItemInfo(newElem); + + renderer->ChildrenUpdated(parentObject); +} + void CQmlEngineImpl::GotoSlide(const char *component, const char *slideName, const SScriptEngineGotoSlideArgs &inArgs) { diff --git a/src/Runtime/Source/runtime/Qt3DSSlideSystem.cpp b/src/Runtime/Source/runtime/Qt3DSSlideSystem.cpp index 524422f4..3e2f95c2 100644 --- a/src/Runtime/Source/runtime/Qt3DSSlideSystem.cpp +++ b/src/Runtime/Source/runtime/Qt3DSSlideSystem.cpp @@ -48,10 +48,9 @@ using namespace qt3ds::runtime::element; namespace { struct SSlideAttribute { - QT3DSU32 m_Index; + QT3DSU32 m_Index = 0; Q3DStudio::UVariant m_Value; SSlideAttribute() - : m_Index(0) { m_Value.m_INT32 = 0; } @@ -69,12 +68,7 @@ struct SSlideAttributeNode }; SSlideAttribute m_Data[AttributeCount]; - SSlideAttributeNode *m_NextNode; - - SSlideAttributeNode() - : m_NextNode(nullptr) - { - } + SSlideAttributeNode *m_NextNode = nullptr; }; typedef IndexableLinkedList m_sourcePaths; - bool m_activeSlide; - bool m_unloadSlide; SSlide() - : m_NextSlide(nullptr) - , m_PlayMode(PlayMode::StopAtEnd) + : m_PlayMode(PlayMode::StopAtEnd) , m_PlayThroughTo(0xFF) , m_Paused(false) - , m_StartTime(0) - , m_EndTime(0) - , m_AnimActionCount(0) - , m_FirstElement(nullptr) - , m_FirstAnimActionNode(nullptr) - , m_activeSlide(false) - , m_unloadSlide(false) { } @@ -172,12 +152,12 @@ struct SSlideSystem : public ISlideSystem IElementAllocator &m_ElementSystem; TAttributeNodePool m_AttributeNodePool; TSlideAnimActionPool m_AnimActionPool; - TSlideElementPool m_SlideElements; TSlidePool m_SlidePool; + TSlideElementPool m_SlideElements; TComponentSlideHash m_Slides; - SSlide *m_CurrentSlide; - SSlideElement *m_CurrentSlideElement; + SSlide *m_CurrentSlide = nullptr; + SSlideElement *m_CurrentSlideElement = nullptr; NVDataRef m_LoadData; @@ -192,8 +172,6 @@ struct SSlideSystem : public ISlideSystem , m_SlidePool(ForwardingAllocator(inFnd.getAllocator(), "m_SlidePool")) , m_SlideElements(ForwardingAllocator(inFnd.getAllocator(), "m_SlideElements")) , m_Slides(inFnd.getAllocator(), "m_Slides") - , m_CurrentSlide(nullptr) - , m_CurrentSlideElement(nullptr) , m_RefCount(0) { } @@ -214,14 +192,14 @@ struct SSlideSystem : public ISlideSystem QT3DSU32 inMinTime, QT3DSU32 inMaxTime) override { eastl::pair inserter = - m_Slides.insert(eastl::make_pair(&inComponent, (SSlide *)nullptr)); + m_Slides.insert(eastl::make_pair(&inComponent, static_cast(nullptr))); SSlide *newSlide = m_SlidePool.construct(__FILE__, __LINE__); QT3DSU32 slideIndex = 0; - if (inserter.first->second == nullptr) { + if (!inserter.first->second) { inserter.first->second = newSlide; } else { SSlide *theSlide = nullptr; - for (theSlide = inserter.first->second; theSlide->m_NextSlide != nullptr; + for (theSlide = inserter.first->second; theSlide->m_NextSlide; theSlide = theSlide->m_NextSlide) { ++slideIndex; } @@ -254,32 +232,91 @@ struct SSlideSystem : public ISlideSystem void SetSlideMaxTime(QT3DSU32 inMaxTime) override { - if (m_CurrentSlide != nullptr) + if (m_CurrentSlide) m_CurrentSlide->m_EndTime = inMaxTime; } void AddSlideElement(element::SElement &inElement, bool inActive) override { - if (m_CurrentSlide != nullptr) { + if (m_CurrentSlide) { SSlideElement *lastSlideElement = m_CurrentSlideElement; m_CurrentSlideElement = m_SlideElements.construct(__FILE__, __LINE__); m_CurrentSlideElement->m_Active = inActive; m_CurrentSlideElement->m_ElementHandle = inElement.GetHandle(); - if (lastSlideElement == nullptr) { - QT3DS_ASSERT(m_CurrentSlide->m_FirstElement == nullptr); + if (!lastSlideElement) { + QT3DS_ASSERT(!m_CurrentSlide->m_FirstElement); m_CurrentSlide->m_FirstElement = m_CurrentSlideElement; } else { lastSlideElement->m_NextElement = m_CurrentSlideElement; } - m_CurrentSlideElement->m_Active = inActive; + m_CurrentSlide->m_lastElement = m_CurrentSlideElement; } else { QT3DS_ASSERT(false); } } - void AddSlideAttribute(Q3DStudio::SAttributeKey inKey, Q3DStudio::UVariant inValue) override + + + // The parent element must be found from target slide. + // Elements cannot be added to the master slide. + bool addSlideElement(element::SElement &inComponent, int slideIndex, + element::SElement &inElement, bool eyeBall) override { - if (m_CurrentSlideElement != nullptr) { + // Note: Slide 0 contains all loaded items in the graph with m_active set to false. + // Other slides contain all master slide items and slide specific items with m_active + // set to eyeball. We shouldn't need to care about slide 0 here as it won't be + // executed/rolled back after initial loading of the scene. + + Q_ASSERT(slideIndex > 0); + TComponentSlideHash::const_iterator theFindResult = m_Slides.find(&inComponent); + element::SElement *parentElement = inElement.GetParent(); + if (theFindResult == m_Slides.end()) { + qWarning() << __FUNCTION__ << "Could not find slides for component"; + return false; + } + if (!parentElement) { + qWarning() << __FUNCTION__ << "Element has no parent"; + return false; + } + + int parentFound = false; + SSlide *slide = theFindResult->second; + SSlide *targetSlide = nullptr; + for (int idx = 0; slide && !targetSlide; slide = slide->m_NextSlide, ++idx) { + if (slideIndex == idx) { + targetSlide = slide; + SSlideElement *slideElement = slide->m_FirstElement; + while (slideElement) { + if (slideElement->m_ElementHandle == parentElement->m_Handle) { + parentFound = true; + break; + } + slideElement = slideElement->m_NextElement; + } + } + } + + if (!parentFound) { + qWarning() << __FUNCTION__ << "Parent element could not be found from target slide"; + return false; + } + + m_CurrentSlide = targetSlide; + m_CurrentSlideElement = m_CurrentSlide->m_lastElement; + + // Explicit active state is based solely on the slide and eyeball + int activeSlideIndex = static_cast(inComponent).GetCurrentSlide(); + bool isActive = activeSlideIndex == slideIndex && eyeBall; + inElement.Flags().SetExplicitActive(isActive); + + AddSlideElement(inElement, eyeBall); + + return true; + } + + void AddSlideAttribute(Q3DStudio::SAttributeKey inKey, Q3DStudio::UVariant inValue) override + { + if (m_CurrentSlideElement) { SElement *theElement = m_ElementSystem.FindElementByHandle(m_CurrentSlideElement->m_ElementHandle); Option theIdx = theElement->FindPropertyIndex(inKey.m_Hash); @@ -297,12 +334,12 @@ struct SSlideSystem : public ISlideSystem SSlideAnimAction *AddSlideAnimAction(bool inAnimation, QT3DSI32 inId, bool inActive) override { - if (m_CurrentSlide != nullptr) { + if (m_CurrentSlide) { SSlideAnimAction &theAnimAction = TSlideAnimActionNodeList::Create( m_CurrentSlide->m_FirstAnimActionNode, m_CurrentSlide->m_AnimActionCount, m_AnimActionPool); - theAnimAction = SSlideAnimAction((QT3DSI32)inId, inActive, inAnimation); - &theAnimAction; + theAnimAction = SSlideAnimAction(QT3DSI32(inId), inActive, inAnimation); + return &theAnimAction; } return nullptr; @@ -451,17 +488,16 @@ struct SSlideSystem : public ISlideSystem void InitializeDynamicKeys(SSlideKey inKey, IAnimationSystem &inAnimationSystem) const override { const SSlide *theSlide = FindSlide(inKey); - if (theSlide != nullptr) { + if (theSlide) { IterateSlideAnimActions(*theSlide, SDynamicKeyOperator(inAnimationSystem)); } } void ExecuteSlide(SSlideKey inKey, IAnimationSystem &inAnimationSystem, - ILogicSystem &inLogicManager) override + ILogicSystem &inLogicManager) override { SSlide *theSlide = FindSlide(inKey); - - if (theSlide == nullptr) { + if (!theSlide) { QT3DS_ASSERT(false); return; } @@ -503,7 +539,7 @@ struct SSlideSystem : public ISlideSystem { SSlide *theSlide = FindSlide(inKey); - if (theSlide == nullptr) { + if (!theSlide) { QT3DS_ASSERT(false); return; } diff --git a/src/Runtime/Source/runtime/Qt3DSSlideSystem.h b/src/Runtime/Source/runtime/Qt3DSSlideSystem.h index becc057f..3aa2a011 100644 --- a/src/Runtime/Source/runtime/Qt3DSSlideSystem.h +++ b/src/Runtime/Source/runtime/Qt3DSSlideSystem.h @@ -126,6 +126,8 @@ namespace runtime { virtual void SetSlideMaxTime(QT3DSU32 inMaxTime) = 0; virtual void AddSlideElement(element::SElement &inElement, bool inActive) = 0; + virtual bool addSlideElement(element::SElement &inComponent, int slideIndex, + element::SElement &inElement, bool eyeBall) = 0; virtual void AddSlideAttribute(Q3DStudio::SAttributeKey inKey, Q3DStudio::UVariant inValue) = 0; virtual SSlideAnimAction *AddSlideAnimAction(bool inAnimation, QT3DSI32 inIndex, diff --git a/src/Runtime/Source/viewer/Qt3DSViewerApp.cpp b/src/Runtime/Source/viewer/Qt3DSViewerApp.cpp index c1cdd56e..005eedcd 100644 --- a/src/Runtime/Source/viewer/Qt3DSViewerApp.cpp +++ b/src/Runtime/Source/viewer/Qt3DSViewerApp.cpp @@ -794,6 +794,15 @@ float Q3DSViewerApp::dataInputMin(const QString &name) const return m_Impl.m_view->dataInputMin(name); } +void Q3DSViewerApp::createElement(const QString &parentElementPath, const QString &slideName, + const QHash &properties) +{ + if (!m_Impl.m_view) + return; + + m_Impl.m_view->createElement(parentElementPath, slideName, properties); +} + Q3DSViewerApp &Q3DSViewerApp::Create(void *glContext, Q3DStudio::IAudioPlayer *inAudioPlayer, QElapsedTimer *startupTimer) { diff --git a/src/Runtime/Source/viewer/Qt3DSViewerApp.h b/src/Runtime/Source/viewer/Qt3DSViewerApp.h index ce7c63bf..a4bdc2e3 100644 --- a/src/Runtime/Source/viewer/Qt3DSViewerApp.h +++ b/src/Runtime/Source/viewer/Qt3DSViewerApp.h @@ -359,6 +359,9 @@ public: float dataInputMax(const QString &name) const; float dataInputMin(const QString &name) const; + void createElement(const QString &parentElementPath, const QString &slideName, + const QHash &properties); + QString error(); void setPresentationId(const QString &id); diff --git a/src/Viewer/qmlviewer/Qt3DSRenderer.cpp b/src/Viewer/qmlviewer/Qt3DSRenderer.cpp index 9b7d6e0b..35b3da71 100644 --- a/src/Viewer/qmlviewer/Qt3DSRenderer.cpp +++ b/src/Viewer/qmlviewer/Qt3DSRenderer.cpp @@ -138,7 +138,7 @@ void Q3DSRenderer::render() m_initialized = initializeRuntime(this->framebufferObject()); m_initializationFailure = !m_initialized; if (m_initializationFailure) - m_commands.clear(); + m_commands.clear(true); } // Don't render if the plugin is hidden; however, if hidden, but sure @@ -249,7 +249,7 @@ void Q3DSRenderer::onUpdateHandler(void *userData) void Q3DSRenderer::processCommands() { if (!m_runtime) { - m_commands.clear(); + m_commands.clear(true); return; } @@ -279,7 +279,7 @@ void Q3DSRenderer::processCommands() // Send scene graph changes over to Q3DS for (int i = 0; i < m_commands.size(); i++) { - const ElementCommand &cmd = m_commands.commandAt(i); + const ElementCommand &cmd = m_commands.constCommandAt(i); switch (cmd.m_commandType) { case CommandType_SetAttribute: m_presentation->setAttribute(cmd.m_elementPath, cmd.m_stringValue, cmd.m_variantValue); @@ -328,6 +328,15 @@ void Q3DSRenderer::processCommands() m_runtime->SetDataInputValue(cmd.m_stringValue, cmd.m_variantValue, static_cast(cmd.m_intValues[0])); break; + case CommandType_CreateElement: { + m_runtime->createElement(cmd.m_elementPath, cmd.m_stringValue, + *static_cast *>(cmd.m_data)); + // Runtime makes copy of the data in its own format, so we can delete it now + auto &command = m_commands.commandAt(i); + delete reinterpret_cast *>(command.m_data); + command.m_data = nullptr; + break; + } case CommandType_RequestSlideInfo: { int current = 0; int previous = 0; @@ -368,7 +377,7 @@ void Q3DSRenderer::processCommands() } } - m_commands.clear(); + m_commands.clear(false); } QT_END_NAMESPACE diff --git a/src/Viewer/qmlviewer/Qt3DSView.cpp b/src/Viewer/qmlviewer/Qt3DSView.cpp index e7b90056..f5abb3e1 100644 --- a/src/Viewer/qmlviewer/Qt3DSView.cpp +++ b/src/Viewer/qmlviewer/Qt3DSView.cpp @@ -252,7 +252,7 @@ void Q3DSView::getCommands(bool emitInitialize, CommandQueue &renderQueue) m_emitRunningChange = true; renderQueue.copyCommands(m_pendingCommands); - m_pendingCommands.clear(); + m_pendingCommands.clear(false); } void Q3DSView::mousePressEvent(QMouseEvent *event) diff --git a/src/Viewer/studio3d/q3dscommandqueue.cpp b/src/Viewer/studio3d/q3dscommandqueue.cpp index 2bc540af..71220410 100644 --- a/src/Viewer/studio3d/q3dscommandqueue.cpp +++ b/src/Viewer/studio3d/q3dscommandqueue.cpp @@ -28,6 +28,7 @@ ****************************************************************************/ #include "q3dscommandqueue_p.h" +#include "q3dspresentation.h" ElementCommand::ElementCommand() : m_commandType(CommandType_Invalid) @@ -146,6 +147,19 @@ ElementCommand &CommandQueue::queueCommand(const QString &elementPath, return cmd; } +ElementCommand &CommandQueue::queueCommand(const QString &elementPath, CommandType commandType, + const QString &stringValue, void *commandData) +{ + ElementCommand &cmd = nextFreeCommand(); + + cmd.m_commandType = commandType; + cmd.m_elementPath = elementPath; + cmd.m_stringValue = stringValue; + cmd.m_data = commandData; + + return cmd; +} + ElementCommand &CommandQueue::queueRequest(const QString &elementPath, CommandType commandType) { ElementCommand &cmd = nextFreeCommand(); @@ -156,7 +170,7 @@ ElementCommand &CommandQueue::queueRequest(const QString &elementPath, CommandTy return cmd; } -void CommandQueue::copyCommands(const CommandQueue &fromQueue) +void CommandQueue::copyCommands(CommandQueue &fromQueue) { m_visibleChanged = m_visibleChanged || fromQueue.m_visibleChanged; m_scaleModeChanged = m_scaleModeChanged || fromQueue.m_scaleModeChanged; @@ -191,7 +205,7 @@ void CommandQueue::copyCommands(const CommandQueue &fromQueue) // Pending queue may be synchronized multiple times between queue processing, so let's append // to the existing queue rather than clearing it. for (int i = 0; i < fromQueue.m_size; i++) { - const ElementCommand &source = fromQueue.commandAt(i); + const ElementCommand &source = fromQueue.constCommandAt(i); switch (source.m_commandType) { case CommandType_SetDataInputValue: queueCommand(source.m_elementPath, source.m_commandType, source.m_stringValue, @@ -225,6 +239,11 @@ void CommandQueue::copyCommands(const CommandQueue &fromQueue) source.m_intValues[0], source.m_intValues[1], source.m_intValues[2], source.m_intValues[3]); break; + case CommandType_CreateElement: + queueCommand(source.m_elementPath, source.m_commandType, source.m_stringValue, + source.m_data); + fromQueue.commandAt(i).m_data = nullptr; // This queue takes ownership of data + break; case CommandType_RequestSlideInfo: case CommandType_UnloadSlide: case CommandType_PreloadSlide: @@ -241,7 +260,7 @@ void CommandQueue::copyCommands(const CommandQueue &fromQueue) } // Clears changed states and empties the queue -void CommandQueue::clear() +void CommandQueue::clear(bool deleteCommandData) { m_visibleChanged = false; m_scaleModeChanged = false; @@ -253,6 +272,23 @@ void CommandQueue::clear() m_globalAnimationTimeChanged = false; m_delayedLoadingChanged = false; + if (deleteCommandData) { + for (int i = 0; i < m_size; ++i) { + ElementCommand &cmd = m_elementCommands[i]; + if (cmd.m_data) { + switch (cmd.m_commandType) { + case CommandType_CreateElement: + delete static_cast *>(cmd.m_data); + break; + default: + Q_ASSERT(false); // Should never come here + break; + } + cmd.m_data = nullptr; + } + } + } + // We do not clear the actual queued commands, those will be reused the next frame // To avoid a lot of unnecessary reallocations. m_size = 0; diff --git a/src/Viewer/studio3d/q3dscommandqueue_p.h b/src/Viewer/studio3d/q3dscommandqueue_p.h index 27612bfb..533f61ed 100644 --- a/src/Viewer/studio3d/q3dscommandqueue_p.h +++ b/src/Viewer/studio3d/q3dscommandqueue_p.h @@ -68,6 +68,9 @@ enum CommandType { CommandType_KeyRelease, CommandType_SetGlobalAnimationTime, CommandType_SetDataInputValue, + CommandType_CreateElement, + + // Requests CommandType_RequestSlideInfo, CommandType_RequestDataInputs, CommandType_PreloadSlide, @@ -83,6 +86,7 @@ public: QString m_elementPath; QString m_stringValue; QVariant m_variantValue; + void *m_data = nullptr; // Data is owned by the queue and is deleted once command is handled union { bool m_boolValue; float m_floatValue; @@ -114,9 +118,11 @@ public: ElementCommand &queueCommand(const QString &elementPath, CommandType commandType, int value0, int value1 = 0, int value2 = 0, int value3 = 0); + ElementCommand &queueCommand(const QString &elementPath, CommandType commandType, + const QString &stringValue, void *commandData); ElementCommand &queueRequest(const QString &elementPath, CommandType commandType); - void copyCommands(const CommandQueue &fromQueue); + void copyCommands(CommandQueue &fromQueue); bool m_visibleChanged; bool m_scaleModeChanged; @@ -138,9 +144,10 @@ public: qint64 m_globalAnimationTime; bool m_delayedLoading; - void clear(); + void clear(bool deleteCommandData); int size() const { return m_size; } - const ElementCommand &commandAt(int index) const { return m_elementCommands.at(index); } + const ElementCommand &constCommandAt(int index) const { return m_elementCommands.at(index); } + ElementCommand &commandAt(int index) { return m_elementCommands[index]; } private: ElementCommand &nextFreeCommand(); diff --git a/src/Viewer/studio3d/q3dspresentation.cpp b/src/Viewer/studio3d/q3dspresentation.cpp index f4235456..362c2803 100644 --- a/src/Viewer/studio3d/q3dspresentation.cpp +++ b/src/Viewer/studio3d/q3dspresentation.cpp @@ -274,6 +274,26 @@ void Q3DSPresentation::setDataInputValue(const QString &name, const QVariant &va } } +/** + Adds a new child element for the element specified by parentElementPath to the slide specified with + slideName. Only model element creation is currently supported. + A referenced material element is also created for the new model element. The source material name + can be specified with custom "material" property in the properties hash. + The source material must exist in the material container of the presentation. +*/ +void Q3DSPresentation::createElement(const QString &parentElementPath, const QString &slideName, + const QHash &properties) +{ + if (d_ptr->m_viewerApp) { + d_ptr->m_viewerApp->createElement(parentElementPath, slideName, properties); + } else if (d_ptr->m_commandQueue) { + // We need to copy the properties map as queue takes ownership of it + QHash *theProperties = new QHash(properties); + d_ptr->m_commandQueue->queueCommand(parentElementPath, CommandType_CreateElement, + slideName, theProperties); + } +} + void Q3DSPresentation::mousePressEvent(QMouseEvent *e) { if (d_ptr->m_viewerApp) { diff --git a/src/Viewer/studio3d/q3dspresentation.h b/src/Viewer/studio3d/q3dspresentation.h index 34809613..97f7b6fc 100644 --- a/src/Viewer/studio3d/q3dspresentation.h +++ b/src/Viewer/studio3d/q3dspresentation.h @@ -83,6 +83,8 @@ public: void keyPressEvent(QKeyEvent *e); void keyReleaseEvent(QKeyEvent *e); + void createElement(const QString &parentElementPath, const QString &slideName, + const QHash &properties); public Q_SLOTS: void setSource(const QUrl &source); void setVariantList(const QStringList &variantList); diff --git a/tests/auto/viewer/simple_cube_animation/simple_cube_animation.uia b/tests/auto/viewer/simple_cube_animation/simple_cube_animation.uia deleted file mode 100644 index b3dffbd2..00000000 --- a/tests/auto/viewer/simple_cube_animation/simple_cube_animation.uia +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/tests/auto/viewer/simple_cube_animation/simple_cube_animation.uip b/tests/auto/viewer/simple_cube_animation/simple_cube_animation.uip deleted file mode 100644 index 22584127..00000000 --- a/tests/auto/viewer/simple_cube_animation/simple_cube_animation.uip +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0 0 100 100 0.963 45 100 100 1.926 45 100 100 2.944 45 100 100 6.942 0 100 100 8.022 0 100 100 8.98 0 100 100 - 0 0 100 100 0.963 0 100 100 1.926 45 100 100 2.944 45 100 100 6.942 45 100 100 8.022 0 100 100 8.98 0 100 100 - 0 0 100 100 0.963 0 100 100 1.926 0 100 100 2.944 45 100 100 6.942 45 100 100 8.022 45 100 100 8.98 0 100 100 - 0 1 100 100 3.958 2 100 100 4.902 2 100 100 5.981 2 100 100 10 1 100 100 - 0 1 100 100 3.958 1 100 100 4.902 2 100 100 5.981 2 100 100 10 1 100 100 - 0 1 100 100 3.958 1 100 100 4.902 1 100 100 5.981 2 100 100 10 1 100 100 - - - - - - - - - - - diff --git a/tests/auto/viewer/tst_qt3dsviewer.cpp b/tests/auto/viewer/tst_qt3dsviewer.cpp index c6692c3d..90c89b28 100644 --- a/tests/auto/viewer/tst_qt3dsviewer.cpp +++ b/tests/auto/viewer/tst_qt3dsviewer.cpp @@ -30,6 +30,10 @@ #include #include +#include +#include +#include +#include void messageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) @@ -75,10 +79,20 @@ void tst_qt3dsviewer::init() m_viewer.setSource(QUrl("qrc:/tst_qt3dsviewer.qml")); m_studio3DItem = m_viewer.rootObject(); + m_presentation = nullptr; + m_settings = nullptr; + QVERIFY(m_studio3DItem); - m_presentation = static_cast(m_studio3DItem->children().at(0)); + + const auto children = m_studio3DItem->children(); + for (auto &child : children) { + if (!m_presentation) + m_presentation = qobject_cast(child); + if (!m_settings) + m_settings = qobject_cast(child); + } + QVERIFY(m_presentation); - m_settings = static_cast(m_studio3DItem->children().at(1)); QVERIFY(m_settings); } @@ -149,4 +163,149 @@ void tst_qt3dsviewer::testSettings() QVERIFY(m_settings->matteColor() == QColor("#0000ff")); } +void tst_qt3dsviewer::testCreateElement() +{ + // Currently this method is very bare bones on actual testing. + // It can be used to visually check if items are getting created. + m_viewer.show(); + + m_settings->setShowRenderStats(true); + m_settings->setScaleMode(Q3DSViewerSettings::ScaleModeFill); + + QSignalSpy spyExited(m_presentation, + SIGNAL(slideExited(const QString &, unsigned int, const QString &))); + + int animValue = 0; + + QHash data; + data.insert(QStringLiteral("name"), QStringLiteral("New Cylinder")); + data.insert(QStringLiteral("sourcepath"), QStringLiteral("#Cylinder")); + data.insert(QStringLiteral("material"), QString()); + data.insert(QStringLiteral("starttime"), 0); + data.insert(QStringLiteral("endtime"), 4500); + data.insert(QStringLiteral("position"), + QVariant::fromValue(QVector3D(200, 300, 200))); + data.insert(QStringLiteral("opacity"), 20.0); + + m_presentation->createElement(QStringLiteral("Scene.Layer"), QStringLiteral("Slide1"), data); + + // Elements can be registered before they are created + Q3DSElement newCylinder(m_presentation, QStringLiteral("Scene.Layer.New Cylinder")); + Q3DSElement newCylinder2(m_presentation, + QStringLiteral("Scene.Layer.New Cylinder.New Cylinder 2")); + Q3DSElement newSphere(m_presentation, QStringLiteral("Scene.Layer.Cube2.New Sphere")); + + QTimer animationTimer; + animationTimer.setInterval(10); + int animDir = 1; + QObject::connect(&animationTimer, &QTimer::timeout, [&]() { + if (qAbs(animValue) > 100) + animDir = -animDir; + animValue += animDir; + newCylinder.setAttribute(QStringLiteral("rotation.x"), animValue * 4); + newCylinder2.setAttribute(QStringLiteral("position.y"), animValue * 3); + newSphere.setAttribute(QStringLiteral("position.x"), 50 + animValue * 2); + }); + + // Create objects to slides 1 & 2 while slide 1 is executing + QTimer::singleShot(1000, [&]() { + data.clear(); + data.insert(QStringLiteral("name"), QStringLiteral("New Cylinder 2")); + data.insert(QStringLiteral("sourcepath"), QStringLiteral("#Cylinder")); + data.insert(QStringLiteral("material"), QStringLiteral("Basic Red")); + data.insert(QStringLiteral("starttime"), 500); + data.insert(QStringLiteral("endtime"), 5000); + data.insert(QStringLiteral("position"), + QVariant::fromValue(QVector3D(50, animValue, 50))); + + m_presentation->createElement(QStringLiteral("Scene.Layer.New Cylinder"), + QStringLiteral("Slide1"), data); + + data.clear(); + data.insert(QStringLiteral("name"), QStringLiteral("New Sphere")); + data.insert(QStringLiteral("sourcepath"), QStringLiteral("#Sphere")); + data.insert(QStringLiteral("material"), QStringLiteral("Basic Green")); + data.insert(QStringLiteral("starttime"), 1000); + data.insert(QStringLiteral("endtime"), 4000); + data.insert(QStringLiteral("position"), + QVariant::fromValue(QVector3D(animValue, 75, 0))); + + m_presentation->createElement(QStringLiteral("Scene.Layer.Cube2"), + QStringLiteral("Slide2"), data); + + animationTimer.start(); + }); + + // Switch to slide 2 + QVERIFY(spyExited.wait(20000)); + + // Create objects to slides 1 and 2 while slide 2 is executing + QTimer::singleShot(2000, [&]() { + data.clear(); + data.insert(QStringLiteral("name"), QStringLiteral("New Cylinder 3")); + data.insert(QStringLiteral("sourcepath"), QStringLiteral("#Cylinder")); + data.insert(QStringLiteral("material"), QStringLiteral("Basic Green")); + data.insert(QStringLiteral("starttime"), 0); + data.insert(QStringLiteral("endtime"), 3000); + data.insert(QStringLiteral("position"), + QVariant::fromValue(QVector3D(-100, -100, 0))); + + m_presentation->createElement(QStringLiteral("Scene.Layer"), QStringLiteral("Slide1"), + data); + + data.clear(); + data.insert(QStringLiteral("name"), QStringLiteral("New Sphere 2")); + data.insert(QStringLiteral("sourcepath"), QStringLiteral("#Sphere")); + data.insert(QStringLiteral("material"), QStringLiteral("Basic Green")); + data.insert(QStringLiteral("starttime"), 0); + data.insert(QStringLiteral("endtime"), 5000); + data.insert(QStringLiteral("position"), + QVariant::fromValue(QVector3D(-100, 100, 0))); + + m_presentation->createElement(QStringLiteral("Scene.Layer"), QStringLiteral("Slide2"), + data); + }); + + // Switch to slide 1 + QVERIFY(spyExited.wait(20000)); + + QTimer createTimer; + createTimer.setInterval(0); + static int elemCounter = 0; + QRandomGenerator rnd; + + QObject::connect(&createTimer, &QTimer::timeout, [&]() { + // Create a bunch of cubes to slide 2 in small batches to avoid slowdown + for (int i = 0; i < 5; ++i) { + ++elemCounter; + data.clear(); + data.insert(QStringLiteral("name"), QStringLiteral("MassCube_%1").arg(elemCounter)); + data.insert(QStringLiteral("sourcepath"), + elemCounter % 2 ? QStringLiteral("#Cube") : QStringLiteral("#Cone")); + data.insert(QStringLiteral("material"), + elemCounter % 2 ? QStringLiteral("Basic Green") + : QStringLiteral("Basic Red")); + data.insert(QStringLiteral("position"), + QVariant::fromValue(QVector3D(rnd.bounded(-600, 600), + rnd.bounded(-600, 600), + rnd.bounded(800, 1200)))); + + m_presentation->createElement(QStringLiteral("Scene.Layer"), QStringLiteral("Slide2"), + data); + } + if (elemCounter >= 1000) { + qDebug() << "Extra cubes created:" << elemCounter; + createTimer.stop(); + } + }); + qDebug() << "Start creating extra cubes!"; + createTimer.start(); + + // Switch to slide 2 + QVERIFY(spyExited.wait(20000)); + + // Switch to slide 1 + QVERIFY(spyExited.wait(20000)); +} + QTEST_MAIN(tst_qt3dsviewer) diff --git a/tests/auto/viewer/tst_qt3dsviewer.h b/tests/auto/viewer/tst_qt3dsviewer.h index 1fe60bb5..f50bf935 100644 --- a/tests/auto/viewer/tst_qt3dsviewer.h +++ b/tests/auto/viewer/tst_qt3dsviewer.h @@ -55,13 +55,13 @@ private Q_SLOTS: void testSlides(); void testFrameUpdates(); void testSettings(); + void testCreateElement(); private: QQuickView m_viewer; QObject *m_studio3DItem = nullptr; Q3DSPresentation *m_presentation = nullptr; Q3DSViewerSettings *m_settings = nullptr; - }; #endif // TST_QT3DSVIEWER diff --git a/tests/auto/viewer/tst_qt3dsviewer.qml b/tests/auto/viewer/tst_qt3dsviewer.qml index 794928f6..2368f039 100644 --- a/tests/auto/viewer/tst_qt3dsviewer.qml +++ b/tests/auto/viewer/tst_qt3dsviewer.qml @@ -52,10 +52,10 @@ import QtStudio3D.OpenGL 2.4 Studio3D { id: studio3D - width: 640 - height: 480 + width: 800 + height: 800 Presentation { - source: "qrc:/simple_cube_animation/simple_cube_animation.uia" + source: "qrc:/../../scenes/simple_cube_animation/simple_cube_animation.uia" } ViewerSettings { } diff --git a/tests/auto/viewer/viewer.qrc b/tests/auto/viewer/viewer.qrc index 2f38ba14..d85ee786 100644 --- a/tests/auto/viewer/viewer.qrc +++ b/tests/auto/viewer/viewer.qrc @@ -1,7 +1,9 @@ - simple_cube_animation/simple_cube_animation.uia - simple_cube_animation/simple_cube_animation.uip + ../../scenes/simple_cube_animation/simple_cube_animation.uia + ../../scenes/simple_cube_animation/presentations/simple_cube_animation.uip + ../../scenes/simple_cube_animation/materials/Basic Green.materialdef + ../../scenes/simple_cube_animation/materials/Basic Red.materialdef tst_qt3dsviewer.qml diff --git a/tests/scenes/simple_cube_animation/materials/Basic Green.materialdef b/tests/scenes/simple_cube_animation/materials/Basic Green.materialdef new file mode 100644 index 00000000..5dbd3d6a --- /dev/null +++ b/tests/scenes/simple_cube_animation/materials/Basic Green.materialdef @@ -0,0 +1,25 @@ + + Pixel + Normal + 0 1 0.0313726 + 0 + 0 + 100 + 1 1 1 + 0 + 0.5 + 20 + 1 + 0 + Default + 1 1 1 + 1.5 + 0 + False + + + + Material + + + \ No newline at end of file diff --git a/tests/scenes/simple_cube_animation/materials/Basic Red.materialdef b/tests/scenes/simple_cube_animation/materials/Basic Red.materialdef new file mode 100644 index 00000000..b5b43717 --- /dev/null +++ b/tests/scenes/simple_cube_animation/materials/Basic Red.materialdef @@ -0,0 +1,25 @@ + + Pixel + Normal + 1 0.0196078 0.0352941 + 0 + 0 + 100 + 1 1 1 + 0 + 0.5 + 20 + 1 + 0 + Default + 1 1 1 + 1.5 + 0 + False + + + + Material + + + \ No newline at end of file diff --git a/tests/scenes/simple_cube_animation/presentations/simple_cube_animation.uip b/tests/scenes/simple_cube_animation/presentations/simple_cube_animation.uip new file mode 100644 index 00000000..7cabcbf0 --- /dev/null +++ b/tests/scenes/simple_cube_animation/presentations/simple_cube_animation.uip @@ -0,0 +1,54 @@ + + + + + #7391ff #ffffff #ffffff #ffffff #ffffff #ffffff #ffffff #ffffff #ffffff #ffffff #ffffff #ffffff #ffffff #ffffff #ffffff #ffffff + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 0 100 100 1 0 100 100 4 0 100 100 5 90 100 100 7 90 100 100 10 0 100 100 + 0 0 100 100 1 0 100 100 4 90 100 100 5 0 100 100 7 0 100 100 10 0 100 100 + 0 0 100 100 1 -90 100 100 4 -90 100 100 5 -90 100 100 7 0 100 100 10 0 100 100 + 0 1 100 100 2 1.5 100 100 3 1.5 100 100 6 2 100 100 7 1 100 100 8 1 100 100 9 1 100 100 + 0 1 100 100 2 1.5 100 100 3 1.5 100 100 6 2 100 100 7 2 100 100 8 1 100 100 9 1 100 100 + 0 1 100 100 2 1 100 100 3 1.5 100 100 6 2 100 100 7 2 100 100 8 2 100 100 9 1 100 100 + + + + + + + + + + + diff --git a/tests/scenes/simple_cube_animation/simple_cube_animation.uia b/tests/scenes/simple_cube_animation/simple_cube_animation.uia new file mode 100644 index 00000000..46dbf70e --- /dev/null +++ b/tests/scenes/simple_cube_animation/simple_cube_animation.uia @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + -- cgit v1.2.3