summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMiikka Heikkinen <miikka.heikkinen@qt.io>2019-04-02 16:12:08 +0300
committerMiikka Heikkinen <miikka.heikkinen@qt.io>2019-05-07 08:47:05 +0000
commit988f62c763eef90e4dafe9ecf6bb05629279278e (patch)
treee5ea6c42f5fba693ca9bd419f71ac69d7e34ca9a
parentfe0a5a474392de0036019f0083a4401863508a83 (diff)
Add dynamic object creation to presentation C++ API
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ää <tomi.korpipaa@qt.io>
-rw-r--r--src/Runtime/Source/engine/Qt3DSRenderRuntimeBinding.cpp1
-rw-r--r--src/Runtime/Source/engine/Qt3DSRuntimeView.cpp35
-rw-r--r--src/Runtime/Source/engine/Qt3DSRuntimeView.h9
-rw-r--r--src/Runtime/Source/foundation/StringTable.h2
-rw-r--r--src/Runtime/Source/runtime/Qt3DSIScene.h20
-rw-r--r--src/Runtime/Source/runtime/Qt3DSIScriptBridge.h18
-rw-r--r--src/Runtime/Source/runtime/Qt3DSQmlEngine.cpp307
-rw-r--r--src/Runtime/Source/runtime/Qt3DSSlideSystem.cpp156
-rw-r--r--src/Runtime/Source/runtime/Qt3DSSlideSystem.h2
-rw-r--r--src/Runtime/Source/viewer/Qt3DSViewerApp.cpp9
-rw-r--r--src/Runtime/Source/viewer/Qt3DSViewerApp.h3
-rw-r--r--src/Viewer/qmlviewer/Qt3DSRenderer.cpp17
-rw-r--r--src/Viewer/qmlviewer/Qt3DSView.cpp2
-rw-r--r--src/Viewer/studio3d/q3dscommandqueue.cpp42
-rw-r--r--src/Viewer/studio3d/q3dscommandqueue_p.h13
-rw-r--r--src/Viewer/studio3d/q3dspresentation.cpp20
-rw-r--r--src/Viewer/studio3d/q3dspresentation.h2
-rw-r--r--tests/auto/viewer/simple_cube_animation/simple_cube_animation.uia6
-rw-r--r--tests/auto/viewer/simple_cube_animation/simple_cube_animation.uip50
-rw-r--r--tests/auto/viewer/tst_qt3dsviewer.cpp163
-rw-r--r--tests/auto/viewer/tst_qt3dsviewer.h2
-rw-r--r--tests/auto/viewer/tst_qt3dsviewer.qml6
-rw-r--r--tests/auto/viewer/viewer.qrc6
-rw-r--r--tests/scenes/simple_cube_animation/materials/Basic Green.materialdef25
-rw-r--r--tests/scenes/simple_cube_animation/materials/Basic Red.materialdef25
-rw-r--r--tests/scenes/simple_cube_animation/presentations/simple_cube_animation.uip54
-rw-r--r--tests/scenes/simple_cube_animation/simple_cube_animation.uia15
27 files changed, 836 insertions, 174 deletions
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<QString, QVariant> &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<Q3DStudio::CQmlEngine &>(m_RuntimeFactoryCore->GetScriptEngineQml());
- theBridgeEngine.SetDataInputValue(name, value, property);
+ if (m_Application) {
+ Q3DStudio::CQmlEngine &theBridgeEngine
+ = static_cast<Q3DStudio::CQmlEngine &>(m_RuntimeFactoryCore->GetScriptEngineQml());
+ theBridgeEngine.SetDataInputValue(name, value, property);
+ }
}
QList<QString> CRuntimeView::dataInputs() const
@@ -594,12 +600,29 @@ QList<QString> 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<QString, QVariant> &properties)
+{
+ if (m_Application) {
+ Q3DStudio::CQmlEngine &theBridgeEngine
+ = static_cast<Q3DStudio::CQmlEngine &>(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 <QtCore/qobject.h>
#include <QtGui/qsurfaceformat.h>
-//==============================================================================
-// 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<QString> 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<QString, QVariant> &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 <QtCore/qstring.h>
+
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 <QtCore/qvariant.h>
+
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<QString, QVariant> &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 <QtQml/qqmlengine.h>
#include <QtQml/qqmlcontext.h>
@@ -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<QString, QVariant> &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<qt3ds::runtime::element::TPropertyDescAndValue>;
+using TPropertyDesc = qt3ds::runtime::element::SPropertyDesc;
+
+void CQmlEngineImpl::createElement(const QString &parentElementPath, const QString &slideName,
+ const QHash<QString, QVariant> &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<qt3ds::render::Qt3DSTranslator *>(
+ 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<qt3ds::render::SNode &>(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<TComponent &>(component).GetCurrentSlide();
+ if (slideIndex == 0xff) {
+ qWarning() << __FUNCTION__ << "Invalid slide name for time context:" << slideName;
+ return;
+ }
+
+ // Remove properties requiring custom handling
+ QHash<QString, QVariant> 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<QString, QVariant> 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<QVector3D>();
+ 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<qt3ds::render::Qt3DSTranslator *>(
+ nextChild->GetAssociation());
+ referencedMaterial = static_cast<qt3ds::render::SGraphObject *>(
+ &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<qt3ds::render::Qt3DSTranslator *>(firstChild->GetAssociation());
+ referencedMaterial = static_cast<qt3ds::render::SGraphObject *>(&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<SSlideAttributeNode, SSlideAttribute,
@@ -83,18 +77,15 @@ typedef IndexableLinkedList<SSlideAttributeNode, SSlideAttribute,
struct SSlideElement
{
- QT3DSU32 m_ElementHandle;
+ QT3DSU32 m_ElementHandle = 0;
QT3DSU32 m_AttributeCount : 31;
QT3DSU32 m_Active : 1;
- SSlideElement *m_NextElement;
- SSlideAttributeNode *m_AttributeNodes;
+ SSlideElement *m_NextElement = nullptr;
+ SSlideAttributeNode *m_AttributeNodes = nullptr;
SSlideElement()
- : m_ElementHandle(0)
- , m_AttributeCount(0)
+ : m_AttributeCount(0)
, m_Active(false)
- , m_NextElement(nullptr)
- , m_AttributeNodes(nullptr)
{
}
};
@@ -105,11 +96,7 @@ struct SSlideAnimActionNode
AnimActionCount = 8,
};
SSlideAnimAction m_Data[AnimActionCount];
- SSlideAnimActionNode *m_NextNode;
- SSlideAnimActionNode()
- : m_NextNode(nullptr)
- {
- }
+ SSlideAnimActionNode *m_NextNode = nullptr;
};
typedef IndexableLinkedList<SSlideAnimActionNode, SSlideAnimAction,
@@ -118,32 +105,25 @@ typedef IndexableLinkedList<SSlideAnimActionNode, SSlideAnimAction,
struct SSlide
{
- SSlide *m_NextSlide;
+ SSlide *m_NextSlide = nullptr;
CRegisteredString m_Name;
QT3DSU32 m_PlayMode : 3;
QT3DSU32 m_PlayThroughTo : 8; // OXFF means no playthrough
QT3DSU32 m_Paused : 1;
- QT3DSU32 m_StartTime;
- QT3DSU32 m_EndTime;
- QT3DSU32 m_AnimActionCount;
- SSlideElement *m_FirstElement;
- SSlideAnimActionNode *m_FirstAnimActionNode;
+ QT3DSU32 m_StartTime = 0;
+ QT3DSU32 m_EndTime = 0;
+ QT3DSU32 m_AnimActionCount = 0;
+ SSlideElement *m_FirstElement = nullptr;
+ SSlideElement *m_lastElement = nullptr;
+ SSlideAnimActionNode *m_FirstAnimActionNode = nullptr;
+ bool m_activeSlide = false;
+ bool m_unloadSlide = false;
QVector<QString> 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<QT3DSU8> 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<TComponentSlideHash::iterator, bool> inserter =
- m_Slides.insert(eastl::make_pair(&inComponent, (SSlide *)nullptr));
+ m_Slides.insert(eastl::make_pair(&inComponent, static_cast<SSlide *>(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<SComponent &>(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<QT3DSU32> 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<QString, QVariant> &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<QString, QVariant> &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<Q3DSDataInput::ValueRole>(cmd.m_intValues[0]));
break;
+ case CommandType_CreateElement: {
+ m_runtime->createElement(cmd.m_elementPath, cmd.m_stringValue,
+ *static_cast<QHash<QString, QVariant> *>(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<QHash<QString, QVariant> *>(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<QHash<QString, QVariant> *>(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<QString, QVariant> &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<QString, QVariant> *theProperties = new QHash<QString, QVariant>(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<QString, QVariant> &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 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<application xmlns="http://qt.io/qt3dstudio/uia">
- <assets initial="simple_cube_animation">
- <presentation id="simple_cube_animation" src="simple_cube_animation.uip"/>
- </assets>
-</application>
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 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<UIP version="3" >
- <Project >
- <ProjectSettings author="" company="" presentationWidth="640" presentationHeight="480" maintainAspect="False" />
- <Graph >
- <Scene id="Scene" >
- <Layer id="Layer" >
- <Camera id="Camera" />
- <Light id="Light" />
- <Model id="Cube" >
- <Material id="Material" />
- </Model>
- <Model id="Cube_001" >
- <Material id="Material_001" />
- </Model>
- </Layer>
- </Scene>
- </Graph>
- <Logic >
- <State name="Master Slide" component="#Scene" >
- <Add ref="#Layer" />
- <Add ref="#Camera" >
- <AnimationTrack property="rotation.x" type="EaseInOut" />
- <AnimationTrack property="rotation.y" type="EaseInOut" />
- <AnimationTrack property="rotation.z" type="EaseInOut" />
- </Add>
- <Add ref="#Light" lightdiffuse="0 1 0" lightspecular="1 1 1" >
- <AnimationTrack property="rotation.x" type="EaseInOut" />
- <AnimationTrack property="rotation.y" type="EaseInOut" />
- <AnimationTrack property="rotation.z" type="EaseInOut" />
- </Add>
- <State id="Scene-Slide1" name="Slide1" playmode="Play Through To..." playthroughto="#Scene-Slide2" >
- <Add ref="#Cube" name="Cube" scale="1 1 1" sourcepath="#Cube" >
- <AnimationTrack property="rotation.x" type="EaseInOut" >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</AnimationTrack>
- <AnimationTrack property="rotation.y" type="EaseInOut" >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</AnimationTrack>
- <AnimationTrack property="rotation.z" type="EaseInOut" >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</AnimationTrack>
- <AnimationTrack property="scale.x" type="EaseInOut" dynamic="True" >0 1 100 100 3.958 2 100 100 4.902 2 100 100 5.981 2 100 100 10 1 100 100</AnimationTrack>
- <AnimationTrack property="scale.y" type="EaseInOut" dynamic="True" >0 1 100 100 3.958 1 100 100 4.902 2 100 100 5.981 2 100 100 10 1 100 100</AnimationTrack>
- <AnimationTrack property="scale.z" type="EaseInOut" dynamic="True" >0 1 100 100 3.958 1 100 100 4.902 1 100 100 5.981 2 100 100 10 1 100 100</AnimationTrack>
- </Add>
- <Add ref="#Material" />
- </State>
- <State id="Scene-Slide2" name="Slide2" initialplaystate="Play" playmode="Stop at end" playthroughto="Previous" >
- <Add ref="#Cube_001" name="Cube" scale="100 100 2" sourcepath="#Cube" />
- <Add ref="#Material_001" />
- </State>
- </State>
- </Logic>
- </Project>
-</UIP>
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 <QtQuick/QQuickItem>
#include <QtGui/QSurfaceFormat>
+#include <QtStudio3D/Q3DSPresentation>
+#include <QtStudio3D/Q3DSElement>
+#include <QtStudio3D/Q3DSViewerSettings>
+#include <QtCore/QRandomGenerator>
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<Q3DSPresentation *>(m_studio3DItem->children().at(0));
+
+ const auto children = m_studio3DItem->children();
+ for (auto &child : children) {
+ if (!m_presentation)
+ m_presentation = qobject_cast<Q3DSPresentation *>(child);
+ if (!m_settings)
+ m_settings = qobject_cast<Q3DSViewerSettings *>(child);
+ }
+
QVERIFY(m_presentation);
- m_settings = static_cast<Q3DSViewerSettings *>(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<QString, QVariant> 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>(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>(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>(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>(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>(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>(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 @@
<RCC>
<qresource prefix="/">
- <file>simple_cube_animation/simple_cube_animation.uia</file>
- <file>simple_cube_animation/simple_cube_animation.uip</file>
+ <file>../../scenes/simple_cube_animation/simple_cube_animation.uia</file>
+ <file>../../scenes/simple_cube_animation/presentations/simple_cube_animation.uip</file>
+ <file>../../scenes/simple_cube_animation/materials/Basic Green.materialdef</file>
+ <file>../../scenes/simple_cube_animation/materials/Basic Red.materialdef</file>
<file>tst_qt3dsviewer.qml</file>
</qresource>
</RCC>
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 @@
+<MaterialData version="1.0">
+ <Property name="shaderlighting">Pixel</Property>
+ <Property name="blendmode">Normal</Property>
+ <Property name="diffuse">0 1 0.0313726</Property>
+ <Property name="specularamount">0</Property>
+ <Property name="specularroughness">0</Property>
+ <Property name="opacity">100</Property>
+ <Property name="emissivecolor">1 1 1</Property>
+ <Property name="emissivepower">0</Property>
+ <Property name="bumpamount">0.5</Property>
+ <Property name="displaceamount">20</Property>
+ <Property name="translucentfalloff">1</Property>
+ <Property name="diffuselightwrap">0</Property>
+ <Property name="specularmodel">Default</Property>
+ <Property name="speculartint">1 1 1</Property>
+ <Property name="ior">1.5</Property>
+ <Property name="fresnelPower">0</Property>
+ <Property name="vertexcolors">False</Property>
+ <Property name="sourcepath"></Property>
+ <Property name="importid"></Property>
+ <Property name="importfile"></Property>
+ <Property name="type">Material</Property>
+ <Property name="name"><![CDATA[materials/Basic Green]]></Property>
+ <Property name="path"><![CDATA[C:/dev/qt/NDD/qt3ds2/tests/scenes/simple_cube_animation/materials/Basic Green.materialdef]]></Property>
+</MaterialData> \ 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 @@
+<MaterialData version="1.0">
+ <Property name="shaderlighting">Pixel</Property>
+ <Property name="blendmode">Normal</Property>
+ <Property name="diffuse">1 0.0196078 0.0352941</Property>
+ <Property name="specularamount">0</Property>
+ <Property name="specularroughness">0</Property>
+ <Property name="opacity">100</Property>
+ <Property name="emissivecolor">1 1 1</Property>
+ <Property name="emissivepower">0</Property>
+ <Property name="bumpamount">0.5</Property>
+ <Property name="displaceamount">20</Property>
+ <Property name="translucentfalloff">1</Property>
+ <Property name="diffuselightwrap">0</Property>
+ <Property name="specularmodel">Default</Property>
+ <Property name="speculartint">1 1 1</Property>
+ <Property name="ior">1.5</Property>
+ <Property name="fresnelPower">0</Property>
+ <Property name="vertexcolors">False</Property>
+ <Property name="sourcepath"></Property>
+ <Property name="importid"></Property>
+ <Property name="importfile"></Property>
+ <Property name="type">Material</Property>
+ <Property name="name"><![CDATA[materials/Basic Red]]></Property>
+ <Property name="path"><![CDATA[C:/dev/qt/NDD/qt3ds2/tests/scenes/simple_cube_animation/materials/Basic Red.materialdef]]></Property>
+</MaterialData> \ 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 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<UIP version="5" >
+ <Project >
+ <ProjectSettings author="" company="" presentationWidth="600" presentationHeight="600" maintainAspect="False" preferKtx="False" >
+ <CustomColors count="16" >#7391ff #ffffff #ffffff #ffffff #ffffff #ffffff #ffffff #ffffff #ffffff #ffffff #ffffff #ffffff #ffffff #ffffff #ffffff #ffffff</CustomColors>
+ </ProjectSettings>
+ <Graph >
+ <Scene id="Scene" >
+ <Layer id="Layer" variants="" >
+ <Camera id="Camera" />
+ <Light id="Light" />
+ <Model id="Cube" >
+ <ReferencedMaterial id="Basic Green" />
+ </Model>
+ <Model id="Cube2" >
+ <ReferencedMaterial id="Basic Red" />
+ </Model>
+ </Layer>
+ <Material id="__Container" >
+ <Material id="materials/Basic Green" />
+ <Material id="materials/Basic Red" />
+ </Material>
+ </Scene>
+ </Graph>
+ <Logic >
+ <State name="Master Slide" component="#Scene" >
+ <Add ref="#Layer" background="SolidColor" backgroundcolor="0.584314 0.0941176 0.34902" />
+ <Add ref="#Camera" />
+ <Add ref="#Light" />
+ <Add ref="#__Container" name="__Container" />
+ <Add ref="#materials/Basic Green" name="materials/Basic Green" diffuse="0 1 0.0313726" importid="" />
+ <Add ref="#materials/Basic Red" name="materials/Basic Red" diffuse="1 0.0196078 0.0352941" importid="" />
+ <State id="Scene-Slide1" name="Slide1" playmode="Play Through To..." >
+ <Set ref="#Layer" endtime="5000" />
+ <Set ref="#Camera" endtime="5000" />
+ <Set ref="#Light" endtime="5000" />
+ <Add ref="#Cube" name="Cube" endtime="5000" position="-408.071 -47.2654 600" sourcepath="#Cube" >
+ <AnimationTrack property="rotation.x" type="EaseInOut" >0 0 100 100 1 0 100 100 4 0 100 100 5 90 100 100 7 90 100 100 10 0 100 100</AnimationTrack>
+ <AnimationTrack property="rotation.y" type="EaseInOut" >0 0 100 100 1 0 100 100 4 90 100 100 5 0 100 100 7 0 100 100 10 0 100 100</AnimationTrack>
+ <AnimationTrack property="rotation.z" type="EaseInOut" >0 0 100 100 1 -90 100 100 4 -90 100 100 5 -90 100 100 7 0 100 100 10 0 100 100</AnimationTrack>
+ <AnimationTrack property="scale.x" type="EaseInOut" >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</AnimationTrack>
+ <AnimationTrack property="scale.y" type="EaseInOut" >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</AnimationTrack>
+ <AnimationTrack property="scale.z" type="EaseInOut" >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</AnimationTrack>
+ </Add>
+ <Add ref="#Basic Green" name="Basic Green" endtime="5000" referencedmaterial="#materials/Basic Green" sourcepath="../materials/Basic Green.materialdef" />
+ </State>
+ <State id="Scene-Slide2" name="Slide2" playmode="Play Through To..." playthroughto="Previous" >
+ <Add ref="#Cube2" name="Cube2" position="-401.836 -18.4752 600" scale="3 3 3" sourcepath="#Cube" />
+ <Add ref="#Basic Red" name="Basic Red" referencedmaterial="#materials/Basic Red" sourcepath="../materials/Basic Red.materialdef" />
+ </State>
+ </State>
+ </Logic>
+ </Project>
+</UIP>
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 @@
+<?xml version='1.0' encoding='utf-8'?>
+<application xmlns="http://qt.io/qt3dstudio/uia">
+ <assets initial="simple_cube_animation">
+ <presentation id="simple_cube_animation" src="presentations/simple_cube_animation.uip"/>
+ </assets>
+ <statemachine ref="#logic">
+ <visual-states>
+ <state ref="Initial">
+ <enter>
+ <goto-slide rel="next" element="main:Scene"/>
+ </enter>
+ </state>
+ </visual-states>
+ </statemachine>
+</application>