summaryrefslogtreecommitdiffstats
path: root/src/runtime/Qt3DSQmlEngine.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/runtime/Qt3DSQmlEngine.cpp')
-rw-r--r--src/runtime/Qt3DSQmlEngine.cpp2690
1 files changed, 2690 insertions, 0 deletions
diff --git a/src/runtime/Qt3DSQmlEngine.cpp b/src/runtime/Qt3DSQmlEngine.cpp
new file mode 100644
index 0000000..ac958ea
--- /dev/null
+++ b/src/runtime/Qt3DSQmlEngine.cpp
@@ -0,0 +1,2690 @@
+/****************************************************************************
+**
+** Copyright (C) 1993-2009 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "RuntimePrefix.h"
+
+//==============================================================================
+// Includes
+//==============================================================================
+#include "Qt3DSQmlEngine.h"
+#include "Qt3DSPresentation.h"
+#include "Qt3DSInputEngine.h"
+#include "Qt3DSSceneManager.h"
+#include "Qt3DSVector3.h"
+#include "Qt3DSColor.h"
+#include "Qt3DSAttributeHashes.h"
+#include "Qt3DSFileStream.h"
+#include "Qt3DSDataLogger.h"
+#include "render/Qt3DSRenderBaseTypes.h"
+#include "foundation/FileTools.h"
+#include "foundation/Qt3DSSystem.h"
+#include "foundation/Qt3DSBroadcastingAllocator.h"
+#include "Qt3DSRenderInputStreamFactory.h"
+#include "Qt3DSSlideSystem.h"
+#include "Qt3DSRenderModel.h"
+
+#include "EASTL/vector.h"
+#include "EASTL/list.h"
+#include <sys/stat.h>
+#include "Qt3DSCommandEventTypes.h"
+#include "Qt3DSApplication.h"
+#include "Qt3DSRuntimeFactory.h"
+#include "Qt3DSRenderContextCore.h"
+#include "Qt3DSRenderInputStreamFactory.h"
+#include "foundation/Qt3DSPerfTimer.h"
+#include "Qt3DSRenderThreadPool.h"
+#include "Qt3DSRenderImageBatchLoader.h"
+#include "foundation/Qt3DSAtomic.h"
+#include "Qt3DSAudioPlayer.h"
+#include "Qt3DSActivationManager.h"
+#include "Qt3DSParametersSystem.h"
+#include "Qt3DSQmlElementHelper.h"
+#include "q3dsqmlscript.h"
+#include "Qt3DSRenderRuntimeBindingImpl.h"
+#include "Qt3DSRenderBufferManager.h"
+#include "Qt3DSImportMesh.h"
+#include "Qt3DSRenderer.h"
+#include "q3dsmaterialdefinitionparser.h"
+#include "Qt3DSRenderCustomMaterialSystem.h"
+#include "Qt3DSRenderDynamicObjectSystem.h"
+#include "Qt3DSRenderMaterialHelpers.h"
+#include "Qt3DSRenderUIPLoader.h"
+#include "Qt3DSDMMetaData.h"
+#include "Qt3DSRenderUIPSharedTranslation.h"
+
+#include <QtQml/qqmlengine.h>
+#include <QtQml/qqmlcontext.h>
+#include <QtQml/qqmlcomponent.h>
+#include <QtQml/qjsengine.h>
+#include <QtCore/qnumeric.h>
+#include <QtCore/qfileinfo.h>
+
+//==============================================================================
+// Namespace
+//==============================================================================
+namespace Q3DStudio {
+
+#if defined(_DEBUG) || (_PROFILE)
+#define Q3DStudio_LOG_EVENT(S) // something
+#else
+#define Q3DStudio_LOG_EVENT(S)
+#endif
+
+using qt3ds::runtime::IApplication;
+using namespace qt3ds;
+
+namespace __SQmlEngineImpl_Basic_Structs__ {
+ struct QmlScopedLock
+ {
+ qt3ds::foundation::Mutex *m_Mutex;
+
+ QmlScopedLock(qt3ds::foundation::Mutex *mtx)
+ : m_Mutex(mtx)
+ {
+ if (m_Mutex)
+ m_Mutex->lock();
+ }
+
+ ~QmlScopedLock()
+ {
+ if (m_Mutex)
+ m_Mutex->unlock();
+ }
+ };
+}
+
+using namespace __SQmlEngineImpl_Basic_Structs__;
+#define QML_ENGINE_MULTITHREAD_PROTECT_METHOD QmlScopedLock __locker(m_MultithreadedMutex);
+
+//==============================================================================
+// Callback handling
+/**
+* Constructor
+*/
+CScriptCallbacks::CScriptCallbacks()
+ : m_CallbackList(0, 0, "CallbackList")
+{
+}
+
+/**
+* Destructor
+*/
+CScriptCallbacks::~CScriptCallbacks()
+{
+ FOR_ARRAY(SFrameCallbackEntry *, theEntry, m_CallbackList)
+ Q3DStudio_delete(*theEntry, SFrameCallbackEntry);
+
+ m_CallbackList.Clear();
+}
+
+bool CScriptCallbacks::RegisterCallback(Q3DStudio::UINT32 callbackType,
+ const TScriptCallback inCallback, void *inUserData)
+{
+ // currently we only support these two types of callbacks
+ if (callbackType != SCRIPT_ON_INITIALIZE && callbackType != SCRIPT_ON_UPDATE) {
+ return false;
+ }
+
+ SFrameCallbackEntry *theFrameCallbackEntry = NULL;
+
+ try {
+ // note we don't care if someone registers the same function twice.
+ theFrameCallbackEntry = Q3DStudio_new(SFrameCallbackEntry) SFrameCallbackEntry();
+
+ if (!theFrameCallbackEntry) {
+ throw "failed to allocated SFrameCallbackEntry";
+ }
+
+ SScriptCallbackEntry *theScriptCallbackEntry =
+ Q3DStudio_new(SScriptCallbackEntry) SScriptCallbackEntry();
+ if (!theScriptCallbackEntry) {
+ throw "failed to allocated SScriptCallbackEntry";
+ }
+
+ theScriptCallbackEntry->m_CallbackType = callbackType;
+ theScriptCallbackEntry->m_Function = inCallback;
+ theScriptCallbackEntry->m_UserData = inUserData;
+ theScriptCallbackEntry->m_Processed = false;
+
+ theFrameCallbackEntry->m_Callbacks.Push(theScriptCallbackEntry);
+
+ m_CallbackList.Push(theFrameCallbackEntry);
+
+ } catch (const char *) {
+ if (theFrameCallbackEntry)
+ Q3DStudio_delete(theFrameCallbackEntry, SFrameCallbackEntry);
+
+ return false;
+ }
+
+ return true;
+}
+
+void CScriptCallbacks::UnregisterCallback(Q3DStudio::UINT32, const TScriptCallback)
+{
+ // not used so far
+}
+
+void CScriptCallbacks::ProcessCallbacks()
+{
+ // Call onInitialize function
+ FOR_ARRAY(SFrameCallbackEntry *, theFrameEntry, m_CallbackList)
+ {
+ FOR_ARRAY(SScriptCallbackEntry *, theEntry, (*theFrameEntry)->m_Callbacks)
+ {
+ if (((*theEntry)->m_CallbackType == SCRIPT_ON_INITIALIZE) && !(*theEntry)->m_Processed
+ && (*theEntry)->m_Function) {
+ (*theEntry)->m_Function((*theEntry)->m_UserData);
+ (*theEntry)->m_Processed = true;
+ }
+ }
+ }
+
+ // Call onUpdate functions
+ FOR_ARRAY(SFrameCallbackEntry *, theFrameEntry, m_CallbackList)
+ {
+ FOR_ARRAY(SScriptCallbackEntry *, theEntry, (*theFrameEntry)->m_Callbacks)
+ {
+ // int type = (*theEntry)->m_CallbackType;
+ if ((*theEntry)->m_CallbackType == SCRIPT_ON_UPDATE && (*theEntry)->m_Function) {
+ (*theEntry)->m_Function((*theEntry)->m_UserData);
+ }
+ }
+ }
+}
+
+//==============================================================================
+//* End of helper class handling element operations
+//==============================================================================
+
+//==============================================================================
+/**
+* Helper class handling command operations
+*/
+class CQmlCommandHelper
+{
+ //==============================================================================
+ // Methods
+ //==============================================================================
+private: // Constructors
+ CQmlCommandHelper();
+ virtual ~CQmlCommandHelper();
+
+public: // static Functions
+ static bool SetupGotoSlideCommand(TElement &inElement, const char *slideName,
+ const SScriptEngineGotoSlideArgs &inArgs);
+ static bool SetupGotoSlideCommand(TElement &inElement, Q3DStudio::INT32 inSlideIndex,
+ const SScriptEngineGotoSlideArgs &inArgs);
+
+protected: // Static Hidden Helpers
+ // static TElement* FireCommand(TEventCommandHash inCommand);
+
+public: // Static Helpers
+ static TElement *GetComponentParent(TElement *inParent);
+};
+
+//==============================================================================
+/**
+* Constructor
+*/
+CQmlCommandHelper::CQmlCommandHelper()
+{
+}
+
+//==============================================================================
+/**
+* Destructor
+*/
+CQmlCommandHelper::~CQmlCommandHelper()
+{
+}
+
+static Option<Q3DStudio::UINT8> FindSlide(TElement &inElement, const char *slideName)
+{
+ IPresentation *thePresentation = inElement.GetBelongedPresentation();
+
+ int theSlideHashName = thePresentation->GetApplication().HashString(slideName);
+ TComponent *theComponent = thePresentation->GetComponentManager().GetComponent(&inElement);
+ UINT8 theSlideIndex =
+ thePresentation->GetSlideSystem().FindSlide(*theComponent, theSlideHashName);
+ if (theSlideIndex >= theComponent->GetSlideCount()) {
+ qt3ds::foundation::CRegisteredString elemPath = thePresentation->GetElementPath(inElement);
+ if (elemPath.IsValid()) {
+ qCCritical(qt3ds::INVALID_OPERATION)
+ << "QMLCommandHelper: goToSlide: Unable to find slide "
+ << slideName <<"on element " << elemPath.c_str();
+ } else {
+ qCCritical(qt3ds::INVALID_OPERATION)
+ << "QMLCommandHelper: goToSlide: Unable to find slide "
+ << slideName;
+ }
+ return Empty();
+ }
+
+ return theSlideIndex;
+}
+
+TElement *CQmlCommandHelper::GetComponentParent(TElement *inElement)
+{
+ Q3DStudio_ASSERT(inElement);
+ return &inElement->GetComponentParent();
+}
+
+bool CQmlCommandHelper::SetupGotoSlideCommand(TElement &inElement, Q3DStudio::INT32 inSlideIndex,
+ const SScriptEngineGotoSlideArgs &inArgs)
+{
+ IPresentation *thePresentation = inElement.GetBelongedPresentation();
+ TElement *theTarget = GetComponentParent(&inElement);
+ SComponentGotoSlideData theSlideData(inSlideIndex);
+ theSlideData.m_Mode = inArgs.m_Mode;
+ theSlideData.m_Paused = inArgs.m_Paused;
+ theSlideData.m_Rate = inArgs.m_Rate;
+ theSlideData.m_Reverse = inArgs.m_Reverse;
+ theSlideData.m_StartTime = inArgs.m_StartTime;
+ // Resolve playthroughto if it has a valid value.
+ if (!isTrivial(inArgs.m_PlaythroughTo)) {
+ if (AreEqual(inArgs.m_PlaythroughTo, "next"))
+ theSlideData.m_PlaythroughTo = -1;
+ else if (AreEqual(inArgs.m_PlaythroughTo, "previous"))
+ theSlideData.m_PlaythroughTo = -2;
+ else {
+ // Find the slide if possible. If not, then just error leave things as they are.
+
+ Option<UINT8> theSlideIndex = FindSlide(inElement, inArgs.m_PlaythroughTo);
+ if (theSlideIndex.hasValue())
+ theSlideData.m_PlaythroughTo = *theSlideIndex;
+ }
+ }
+ thePresentation->GetComponentManager().SetupComponentGotoSlideCommand(theTarget, theSlideData);
+ UVariant theArg1;
+ UVariant theArg2;
+ qt3ds::intrinsics::memZero(&theArg1, sizeof(UVariant));
+ qt3ds::intrinsics::memZero(&theArg2, sizeof(UVariant));
+ thePresentation->FireCommand(COMMAND_GOTOSLIDE, theTarget, &theArg1, &theArg2);
+ return true;
+}
+
+bool CQmlCommandHelper::SetupGotoSlideCommand(TElement &inElement, const char *slideName,
+ const SScriptEngineGotoSlideArgs &inArgs)
+{
+ Option<UINT8> theSlideIndex = FindSlide(inElement, slideName);
+ if (theSlideIndex.hasValue())
+ return SetupGotoSlideCommand(inElement, *theSlideIndex, inArgs);
+
+ return false;
+}
+
+//==============================================================================
+//* End of helper class handling command operations
+//==============================================================================
+
+struct SEmitSignalData : public NVRefCounted
+{
+ NVAllocatorCallback &m_Alloc;
+ volatile QT3DSI32 mRefCount;
+ INT32 m_SignalNameHandler; ///< The name of the signal to emit
+ TElement *m_TargetElement; ///< The source element where the event is going to occur
+
+ SEmitSignalData(NVAllocatorCallback &alloc, INT32 inSignalName, TElement *inElement)
+ : m_Alloc(alloc)
+ , mRefCount(0)
+ , m_SignalNameHandler(inSignalName)
+ , m_TargetElement(inElement)
+ {
+ }
+
+ QT3DS_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(m_Alloc)
+};
+
+class CQmlEngineImpl : public CQmlEngine
+{
+ using TEmitSignalPtr = NVScopedRefCounted<SEmitSignalData>;
+ using TEmitSignalQueue = eastl::list<TEmitSignalPtr, ForwardingAllocator>;
+ using TPropertyDescAndValueList = eastl::vector<qt3ds::runtime::element::TPropertyDescAndValue>;
+ using TPropertyDesc = qt3ds::runtime::element::SPropertyDesc;
+
+ static const int MAX_ACTION_QUEUE_SIZE = 100;
+
+private:
+ qt3ds::NVFoundationBase &m_Foundation;
+ IApplication *m_Application;
+ IApplication *m_ApplicationCore;
+ qt3ds::foundation::Mutex m_PreloadMutex;
+ qt3ds::foundation::Mutex *m_MultithreadedMutex;
+
+ TEmitSignalQueue m_EmitSignalDataList;
+
+ QT3DSI32 mRefCount;
+
+ CScriptCallbacks m_ScriptCallbacks;
+
+ QQmlEngine m_engine;
+ QMap<QString, QQmlComponent *> m_components;
+ QVector<Q3DSQmlScript *> m_scripts;
+
+public:
+ CQmlEngineImpl(NVFoundationBase &fnd, ITimeProvider &inTimeProvider);
+
+ QT3DS_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(m_Foundation.getAllocator())
+
+ // functions from IScriptBridge
+ void EnableMultithreadedAccess() override;
+ void DisableMultithreadedAccess() override;
+
+ void SetApplicationCore(qt3ds::runtime::IApplication &inApplication) override;
+ void SetApplication(qt3ds::runtime::IApplication &inApplication) override;
+ qt3ds::runtime::IApplication *GetApplication() override;
+ void Initialize() override;
+
+ void LoadScript(IPresentation *presentation, TElement *element, const CHAR *path) override;
+ Q3DStudio::INT32 InitializeApplicationBehavior(const char *) override
+ {
+ return -1;
+ }
+
+ void ProcessFrameCallbacks(IPresentation *) override;
+ void ExecuteApplicationScriptFunction(Q3DStudio::INT32, const char *) override {}
+ void CallFunction(const char *, const char *,
+ CScriptEngineCallFunctionArgRetriever &) override {}
+
+ void ProcessSignal(IPresentation *inPresentation,
+ const SEventCommand &inCommand) override;
+ void ProcessCustomActions(IPresentation *inPresentation,
+ const SEventCommand &inCommand) override;
+ void ProcessCustomCallback(IPresentation *, const SEventCommand &) override {}
+
+ void SetTableForElement(TElement &, IScriptTableProvider &) override {}
+ void SetAttribute(TElement *target, const char *attName, const char *value) override;
+ void SetAttribute(const char *element, const char *attName, const char *value) override;
+ bool GetAttribute(TElement *target, const char *attName, char *value) override;
+ bool GetAttribute(const char *element, const char *attName, char *value) override;
+ void FireEvent(const char *element, const char *evtName) override;
+ void SetDataInputValue(const QString &name, const QVariant &value,
+ qt3ds::runtime::DataInputValueRole valueRole) override;
+ void createElements(const QString &parentElementPath, const QString &slideName,
+ const QVector<QHash<QString, QVariant>> &properties,
+ qt3ds::render::IQt3DSRenderer *renderer) override;
+ void deleteElements(const QStringList &elementPaths,
+ qt3ds::render::IQt3DSRenderer *renderer) override;
+ void createMaterials(const QString &subPresId, const QStringList &materialDefinitions,
+ qt3ds::render::ICustomMaterialSystem *customMaterialSystem,
+ IDynamicObjectSystem *dynamicObjectSystem,
+ qt3ds::render::IQt3DSRenderer *renderer) override;
+ void deleteMaterials(const QStringList &materialNames,
+ qt3ds::render::IQt3DSRenderer *renderer) override;
+ void createMesh(const QString &name, qt3dsimp::Mesh *mesh,
+ qt3ds::render::IBufferManager *bufferManager) override;
+ void deleteMeshes(const QStringList &meshNames,
+ qt3ds::render::IBufferManager *bufferManager) override;
+
+ void GotoSlide(const char *component, const char *slideName,
+ const SScriptEngineGotoSlideArgs &inArgs) override;
+ void GotoSlideRelative(const char *, bool, bool, const SScriptEngineGotoSlideArgs &) override;
+
+ void SetPresentationAttribute(const char *, const char *, const char *) override;
+
+ // No need to implement here, as sound playing is done in Qt3DSViewerApp
+ bool PlaySoundFile(const char *) override { return false; }
+
+ void EnableDebugging(qt3ds::state::debugger::IMultiProtocolSocket &) override {}
+ void EnableProfiling() override {}
+ void StepGC() override {}
+
+ // functions from CQMLEngine
+ bool PeekSignal(TElement *&outElement, char *&outName) override;
+ void GotoSlideIndex(const char *component, const Q3DStudio::INT32 slideIndex,
+ const SScriptEngineGotoSlideArgs &inArgs) override;
+ bool GetSlideInfo(const char *elementPath, int &currentIndex, int &previousIndex,
+ QString &currentName, QString &previousName) override;
+ void GotoTime(const char *component, const Q3DStudio::FLOAT time) override;
+ bool RegisterCallback(Q3DStudio::UINT32 callbackType, const TScriptCallback inCallback,
+ void *inUserData) override;
+ void Shutdown(qt3ds::NVFoundationBase &inFoundation) override;
+
+private:
+ TElement *createMaterialContainer(TElement *parent, CPresentation *presentation);
+ void createComponent(QQmlComponent *component, TElement *element);
+ TElement *getTarget(const char *component);
+ void listAllElements(TElement *root, QList<TElement *> &elements);
+ void initializeDataInputsInPresentation(CPresentation &presentation, bool isPrimary,
+ QList<TElement *> inElements = QList<TElement *>());
+ void initializeDataOutputsInPresentation(CPresentation &presentation, bool isPrimary);
+ // Splits down vector attributes to components as Runtime does not really
+ // handle vectors at this level anymore
+ bool getAttributeVector3(QVector<QByteArray> &outAttVec, const QByteArray &attName,
+ TElement *elem);
+ bool getAttributeVector2(QVector<QByteArray> &outAttVec, const QByteArray &attName,
+ TElement *elem);
+ // build and evaluate Evaluator datainput type expression
+ QJSValue buildJSFunc(const QString &userFunc);
+ // pass controller name for error reporting purposes
+ QVariant callJSFunc(const QString &controllerName, qt3ds::runtime::DataInputDef &diDef,
+ const QVariant::Type type);
+ // matches all numeric datatypes so we do not get datatype mismatch when JS
+ // decides to change result datatype (f.ex from double to int when result
+ // fractional part for specific input values happens to be exactly zero)
+ bool isMatchingDatatype(QVariant::Type resultType, QVariant::Type propertyType);
+ // find out which datainputs are used in the expression
+ QVector<QString> resolveDependentDatainputs(const QString &expression,
+ const QString &controllerName);
+
+ // Methods to add element attributes to list for element creation
+ void addStringAttribute(qt3ds::foundation::IStringTable &strTable,
+ TPropertyDescAndValueList &list,
+ const QString &inAttName, const QString &inValue);
+ void addIntAttribute(qt3ds::foundation::IStringTable &strTable,
+ TPropertyDescAndValueList &list,
+ const QString &inAttName, int inValue);
+ void addBoolAttribute(qt3ds::foundation::IStringTable &strTable,
+ TPropertyDescAndValueList &list,
+ const QString &inAttName, bool inValue);
+ void addFloatAttribute(qt3ds::foundation::IStringTable &strTable,
+ TPropertyDescAndValueList &list,
+ const QString &inAttName, float inValue);
+ void addFloat2Attribute(qt3ds::foundation::IStringTable &strTable,
+ TPropertyDescAndValueList &list,
+ const QStringList &inAttNames, const QVector2D &inValue);
+ void addFloat3Attribute(qt3ds::foundation::IStringTable &strTable,
+ TPropertyDescAndValueList &list,
+ const QStringList &inAttNames, const QVector3D &inValue);
+ void addFloat4Attribute(qt3ds::foundation::IStringTable &strTable,
+ TPropertyDescAndValueList &list,
+ const QStringList &inAttNames, const QVector4D &inValue);
+ void addElementRefAttribute(qt3ds::foundation::IStringTable &strTable,
+ TPropertyDescAndValueList &list,
+ const QString &inAttName, TElement *element);
+ template <typename TDataType>
+ void setDynamicObjectProperty(qt3ds::render::SDynamicObject &material,
+ const qt3ds::render::dynamic::SPropertyDefinition &propDesc,
+ const TDataType &propValue);
+ QVector2D parseFloat2Property(const QString &propValue);
+ QVector3D parseFloat3Property(const QString &propValue);
+ QVector4D parseFloat4Property(const QString &propValue);
+
+ void notifyElementCreation(const QStringList &elementNames, const QString &error);
+ void notifyMaterialCreation(const QStringList &materialNames, const QString &error);
+ void deleteElements(const QVector<TElement *> &elements,
+ qt3ds::render::IQt3DSRenderer *renderer);
+};
+
+CQmlEngineImpl::CQmlEngineImpl(NVFoundationBase &fnd, ITimeProvider &)
+ : m_Foundation(fnd)
+ , m_Application(NULL)
+ , m_ApplicationCore(NULL)
+ , m_PreloadMutex(fnd.getAllocator())
+ , m_MultithreadedMutex(NULL)
+ , m_EmitSignalDataList(
+ ForwardingAllocator(fnd.getAllocator(), "CQmlEngineImpl::m_EmitSignalDataList"))
+ , mRefCount(0)
+{
+ qmlRegisterType<Q3DSQmlBehavior>("QtStudio3D.Behavior", 1, 0, "Behavior");
+ qmlRegisterType<Q3DSQmlBehavior, 1>("QtStudio3D.Behavior", 1, 1, "Behavior");
+}
+
+void CQmlEngineImpl::Shutdown(qt3ds::NVFoundationBase &inFoundation)
+{
+}
+
+void CQmlEngineImpl::EnableMultithreadedAccess()
+{
+ m_MultithreadedMutex = &m_PreloadMutex;
+}
+
+void CQmlEngineImpl::DisableMultithreadedAccess()
+{
+ m_MultithreadedMutex = NULL;
+}
+
+void CQmlEngineImpl::SetApplicationCore(runtime::IApplication &inApplication)
+{
+ QML_ENGINE_MULTITHREAD_PROTECT_METHOD;
+ m_ApplicationCore = &inApplication;
+}
+
+void CQmlEngineImpl::SetApplication(qt3ds::runtime::IApplication &inApplication)
+{
+ QML_ENGINE_MULTITHREAD_PROTECT_METHOD;
+ m_Application = &inApplication;
+}
+
+qt3ds::runtime::IApplication *CQmlEngineImpl::GetApplication()
+{
+ QML_ENGINE_MULTITHREAD_PROTECT_METHOD;
+ return m_Application;
+}
+
+void CQmlEngineImpl::Initialize()
+{
+ // Gather data input controlled properties
+ QList<CPresentation *> presentations = m_Application->GetPresentationList();
+
+ for (int i = 0; i < presentations.size(); ++i) {
+ initializeDataInputsInPresentation(*presentations[i], i == 0);
+ initializeDataOutputsInPresentation(*presentations[i], i == 0);
+ }
+}
+
+void CQmlEngineImpl::SetAttribute(TElement *target, const char *attName, const char *value)
+{
+ QML_ENGINE_MULTITHREAD_PROTECT_METHOD;
+ if (target) {
+ bool success = CQmlElementHelper::SetAttribute(target, attName, value, false);
+ if (!success) {
+ qCCritical(qt3ds::INVALID_OPERATION)
+ << "CQmlEngineImpl::SetAttribute: "
+ << "failed to set attribute on element"
+ << target << ":" << attName << ":" << value;
+ }
+ }
+}
+
+void CQmlEngineImpl::SetAttribute(const char *element, const char *attName, const char *value)
+{
+ QML_ENGINE_MULTITHREAD_PROTECT_METHOD;
+
+ TElement *theTarget = getTarget(element);
+ if (theTarget) {
+ bool success = CQmlElementHelper::SetAttribute(theTarget, attName, value, false);
+ if (!success) {
+ qCCritical(qt3ds::INVALID_OPERATION)
+ << "CQmlEngineImpl::SetAttribute: "
+ << "failed to set attribute on element"
+ << element << ":" << attName << ":" << value;
+ }
+ }
+}
+
+bool CQmlEngineImpl::GetAttribute(TElement *target, const char *attName, char *value)
+{
+ QML_ENGINE_MULTITHREAD_PROTECT_METHOD;
+
+ if (target) {
+ bool success = CQmlElementHelper::GetAttribute(target, attName, value);
+ if (!success) {
+ qCCritical(qt3ds::INVALID_OPERATION)
+ << "CQmlEngineImpl::GetAttribute: "
+ << "failed to get attribute on element"
+ << target << ":" << attName << ":" << value;
+ }
+ return success;
+ }
+
+ return false;
+}
+
+bool CQmlEngineImpl::GetAttribute(const char *element, const char *attName, char *value)
+{
+ QML_ENGINE_MULTITHREAD_PROTECT_METHOD;
+
+ TElement *theTarget = getTarget(element);
+ if (theTarget) {
+ bool success = CQmlElementHelper::GetAttribute(theTarget, attName, value);
+ if (!success) {
+ qCCritical(qt3ds::INVALID_OPERATION)
+ << "CQmlEngineImpl::GetAttribute: "
+ << "failed to get attribute on element"
+ << element << ":" << attName << ":" << value;
+ }
+ return success;
+ }
+
+ return false;
+}
+
+void CQmlEngineImpl::LoadScript(IPresentation *presentation, TElement *element, const CHAR *path)
+{
+ QString presPath = QFileInfo(presentation->GetFilePath()).absolutePath();
+
+ QString sourcePath(presPath + QLatin1Char('/') + path);
+ sourcePath.replace(QLatin1Char('\\'), QLatin1Char('/'));
+
+ TElement *parent = element->GetParent();
+ if (!parent)
+ return;
+
+ if (!m_components.contains(sourcePath)) {
+ m_components[sourcePath] = new QQmlComponent(&m_engine, QUrl::fromLocalFile(sourcePath),
+ &m_engine);
+ }
+
+ QQmlComponent *component = m_components[sourcePath];
+ if (component->status() == QQmlComponent::Ready) {
+ createComponent(component, element);
+ } else if (component->status() == QQmlComponent::Error) {
+ qWarning() << "Error while loading script"
+ << component->url().toString()
+ << ":" << component->errorString();
+ } else {
+ QObject::connect(component, &QQmlComponent::statusChanged,
+ [this, component, element] (QQmlComponent::Status status) {
+ if (status == QQmlComponent::Ready) {
+ createComponent(component, element);
+ } else {
+ qWarning() << "Error while loading script"
+ << component->url().toString()
+ << ":" << component->errorString();
+ }
+ }
+ );
+ }
+}
+
+void CQmlEngineImpl::FireEvent(const char *element, const char *evtName)
+{
+ TElement *theElement = getTarget(element);
+ if (theElement && theElement->GetActive() == true) {
+ IPresentation *thePresentation = theElement->GetBelongedPresentation();
+ thePresentation->FireEvent(CHash::HashEventCommand(evtName), theElement);
+ } else {
+ qCCritical(qt3ds::INVALID_OPERATION)
+ << "CQmlEngineImpl::FireEvent: "
+ << "failed to find element: "
+ << element << " " << evtName;
+ }
+}
+
+void CQmlEngineImpl::SetDataInputValue(
+ const QString &name, const QVariant &value,
+ qt3ds::runtime::DataInputValueRole valueRole = qt3ds::runtime::DataInputValueRole::Value)
+{
+ qt3ds::runtime::DataInputMap &diMap = m_Application->dataInputMap();
+ if (diMap.contains(name)) {
+ qt3ds::runtime::DataInputDef &diDef = diMap[name];
+ switch (valueRole) {
+ case qt3ds::runtime::DataInputValueRole::Value: { // switch (valueRole)
+ diDef.value = value;
+ const QVector<qt3ds::runtime::DataInOutAttribute> &ctrlAtt
+ = diDef.controlledAttributes;
+ for (const qt3ds::runtime::DataInOutAttribute &ctrlElem : ctrlAtt) {
+ switch (ctrlElem.propertyType) {
+ case ATTRIBUTETYPE_DATAINPUT_TIMELINE: {
+ // Quietly ignore other than number type data inputs when adjusting timeline
+ if (diDef.type == qt3ds::runtime::DataInOutTypeRangedNumber) {
+ TTimeUnit endTime = 0;
+ TElement *element = getTarget(ctrlElem.elementPath.constData());
+ TComponent *component = static_cast<TComponent *>(element);
+ endTime = component->GetTimePolicy().GetLoopingDuration();
+
+ // Normalize the value to dataInput range
+ qreal newTime = qreal(endTime) * (qreal(value.toFloat() - diDef.min)
+ / qreal(diDef.max - diDef.min));
+ GotoTime(ctrlElem.elementPath.constData(), float(newTime / 1000.0));
+ }
+ break;
+ }
+ case ATTRIBUTETYPE_DATAINPUT_SLIDE: {
+ // Quietly ignore other than string type when adjusting slide
+ if (diDef.type == qt3ds::runtime::DataInOutTypeString) {
+ const QByteArray valueStr = value.toString().toUtf8();
+ GotoSlide(ctrlElem.elementPath.constData(), valueStr.constData(),
+ SScriptEngineGotoSlideArgs());
+ }
+ break;
+ }
+ // Silently ignore invalid incoming type if it does not
+ // match with the datainput type except with type Variant, for which
+ // the incoming value is cast to target property type without checking.
+ // Caveat emptor.
+
+ // For Evaluator, typecheck the JS evaluation result to see if it
+ // matches with the target property.
+
+ // Handle ranged number similarly to generic float
+ // if it is bound to properties other
+ // than timeline animation i.e. disregard range min and max
+ case ATTRIBUTETYPE_FLOAT: {
+ float valueFloat;
+ if (diDef.type == qt3ds::runtime::DataInOutTypeFloat
+ || diDef.type == qt3ds::runtime::DataInOutTypeRangedNumber
+ || diDef.type == qt3ds::runtime::DataInOutTypeVariant) {
+ valueFloat = value.toFloat();
+ } else if (diDef.type == qt3ds::runtime::DataInOutTypeEvaluator) {
+ valueFloat = callJSFunc(name, diDef, QVariant::Type::Double).toFloat();
+ } else {
+ qWarning() << __FUNCTION__ << "Property type "
+ << ctrlElem.propertyType
+ << " not matching with Datainput " << name
+ << " data type "
+ << diDef.type;
+ break;
+ }
+
+ SetAttribute(ctrlElem.elementPath.constData(),
+ ctrlElem.attributeName.first().constData(),
+ reinterpret_cast<const char *>(&valueFloat));
+ break;
+ }
+ case ATTRIBUTETYPE_FLOAT4: {
+ QVector4D valueVec;
+ if (diDef.type == qt3ds::runtime::DataInOutTypeVector4) {
+ valueVec = value.value<QVector4D>();
+ } else {
+ qWarning() << __FUNCTION__ << "Property type "
+ << ctrlElem.propertyType
+ << " not matching with Datainput " << name
+ << " data type "
+ << diDef.type;
+ break;
+ }
+ // Set the values of vector attribute components separately
+ for (int i = 0; i < 4; i++) {
+ const float val = valueVec[i];
+ SetAttribute(ctrlElem.elementPath.constData(),
+ ctrlElem.attributeName[i].constData(),
+ reinterpret_cast<const char *>(&val));
+ }
+ break;
+ }
+ case ATTRIBUTETYPE_FLOAT3: {
+ QVector3D valueVec;
+ if (diDef.type == qt3ds::runtime::DataInOutTypeVector3
+ || diDef.type == qt3ds::runtime::DataInOutTypeVariant) {
+ valueVec = value.value<QVector3D>();
+ } else if (diDef.type == qt3ds::runtime::DataInOutTypeEvaluator) {
+ const QVariant res = callJSFunc(name, diDef, QVariant::Type::Vector3D);
+ valueVec = res.value<QVector3D>();
+ } else {
+ qWarning() << __FUNCTION__ << "Property type "
+ << ctrlElem.propertyType
+ << " not matching with Datainput " << name
+ << " data type "
+ << diDef.type;
+ break;
+ }
+ // Set the values of vector attribute components separately
+ for (int i = 0; i < 3; i++) {
+ const float val = valueVec[i];
+ SetAttribute(ctrlElem.elementPath.constData(),
+ ctrlElem.attributeName[i].constData(),
+ reinterpret_cast<const char *>(&val));
+ }
+ break;
+ }
+ case ATTRIBUTETYPE_FLOAT2:
+ {
+ QVector2D valueVec;
+ if (diDef.type == qt3ds::runtime::DataInOutTypeVector2
+ || diDef.type == qt3ds::runtime::DataInOutTypeVariant) {
+ valueVec = value.value<QVector2D>();
+ } else if (diDef.type == qt3ds::runtime::DataInOutTypeEvaluator) {
+ const QVariant res = callJSFunc(name, diDef, QVariant::Type::Vector2D);
+ valueVec = res.value<QVector2D>();
+ } else {
+ qWarning() << __FUNCTION__ << "Property type "
+ << ctrlElem.propertyType
+ << " not matching with Datainput " << name
+ << " data type "
+ << diDef.type;
+ break;
+ }
+ // Set the values of vector attribute components separately
+ for (int i = 0; i < 2; i++) {
+ const float val = valueVec[i];
+ SetAttribute(ctrlElem.elementPath.constData(),
+ ctrlElem.attributeName[i].constData(),
+ reinterpret_cast<const char *>(&val));
+ }
+ break;
+ }
+ case ATTRIBUTETYPE_BOOL: {
+ uint valueBool; // SetAttribute requires at least 32-bit variable
+ if (diDef.type == qt3ds::runtime::DataInOutTypeBoolean
+ || diDef.type == qt3ds::runtime::DataInOutTypeVariant) {
+ valueBool = value.toBool();
+ } else if (diDef.type == qt3ds::runtime::DataInOutTypeEvaluator) {
+ valueBool = callJSFunc(name, diDef, QVariant::Type::Bool).toBool();
+ } else {
+ qWarning() << __FUNCTION__ << "Property type "
+ << ctrlElem.propertyType
+ << " not matching with Datainput " << name
+ << " data type "
+ << diDef.type;
+ break;
+ }
+
+ SetAttribute(ctrlElem.elementPath.constData(),
+ ctrlElem.attributeName.first().constData(),
+ reinterpret_cast<const char *>(&valueBool));
+ break;
+ }
+ case ATTRIBUTETYPE_STRING: {
+ QByteArray valueStr;
+ // Allow scalar number types also as inputs to string attribute
+ if (diDef.type == qt3ds::runtime::DataInOutTypeString
+ || diDef.type == qt3ds::runtime::DataInOutTypeRangedNumber
+ || diDef.type == qt3ds::runtime::DataInOutTypeFloat
+ || diDef.type == qt3ds::runtime::DataInOutTypeVariant) {
+ valueStr = value.toString().toUtf8();
+ } else if (diDef.type == qt3ds::runtime::DataInOutTypeEvaluator) {
+ valueStr = callJSFunc(name, diDef, QVariant::Type::String)
+ .toString().toUtf8();
+ } else {
+ qWarning() << __FUNCTION__ << "Property type "
+ << ctrlElem.propertyType
+ << " not matching with Datainput " << name
+ << " data type "
+ << diDef.type;
+ break;
+ }
+
+ SetAttribute(ctrlElem.elementPath.constData(),
+ ctrlElem.attributeName.first().constData(),
+ valueStr.constData());
+ break;
+ }
+ default:
+ QT3DS_ALWAYS_ASSERT_MESSAGE("Unexpected data input type");
+ break;
+ }
+ }
+
+ // Trigger re-evaluation of Evaluator datainputs that use this datainput
+ // as source data. Do this by calling setDataInputValue for evaluator
+ // with the current set value of the Evaluator (_not_ the evaluator result)
+ for (auto dependent : diDef.dependents) {
+ // Dependent list also contains the name of this datainput if
+ // the value of this datainput is used as source data. In this case
+ // obviously do not cause infinite recursion.
+ if (dependent != name)
+ SetDataInputValue(dependent, diMap[dependent].value);
+ }
+ break;
+ }
+ case qt3ds::runtime::DataInputValueRole::Max: { // switch (valueRole)
+ diDef.max = value.toFloat();
+ break;
+ }
+ case qt3ds::runtime::DataInputValueRole::Min: { // switch (valueRole)
+ diDef.min = value.toFloat();
+ break;
+ }
+ }
+ }
+}
+
+static int _idCounter = 0;
+
+void CQmlEngineImpl::createElements(const QString &parentElementPath, const QString &slideName,
+ const QVector<QHash<QString, QVariant>> &properties,
+ qt3ds::render::IQt3DSRenderer *renderer)
+{
+ using namespace qt3ds::render;
+
+ int elementIndex = -1;
+ QString error;
+ CPresentation *presentation = nullptr;
+ QStringList elementPaths;
+ elementPaths.reserve(properties.size());
+ QVector<QHash<QString, QVariant>> theProperties = properties;
+ const QString namePropName = QStringLiteral("name");
+
+ for (int i = 0; i < theProperties.size(); ++i) {
+ auto &props = theProperties[i];
+ QString newElementName = props.value(namePropName).toString();
+ if (newElementName.isEmpty()) {
+ // The id number on generated name will match generated graph object identifiers
+ newElementName = QStringLiteral("NewElement_%1").arg(_idCounter + i + 1);
+ props.insert(namePropName, newElementName);
+ }
+ elementPaths << parentElementPath + QLatin1Char('.') + newElementName;
+ }
+ QVector<TElement *> createdElements;
+ TElement *parentElement = nullptr;
+
+ auto handleError = [&]() {
+ if (!error.isEmpty())
+ deleteElements(createdElements, renderer);
+ notifyElementCreation(elementPaths, error);
+ };
+
+ // Resolve parent element
+ QByteArray theParentPath = parentElementPath.toUtf8();
+ parentElement = getTarget(theParentPath.constData());
+
+ if (!parentElement) {
+ error = QObject::tr("Invalid parent element: '%1'").arg(parentElementPath);
+ handleError();
+ return;
+ }
+
+ auto parentTranslator = static_cast<Qt3DSTranslator *>(parentElement->GetAssociation());
+
+ if (!parentTranslator || !GraphObjectTypes::IsNodeType(
+ parentTranslator->GetUIPType())) {
+ error = QObject::tr("Parent element is not a valid node: '%1'").arg(parentElementPath);
+ handleError();
+ return;
+ }
+
+ TElement &component = parentElement->GetComponentParent();
+ auto &parentObject = static_cast<SNode &>(parentTranslator->RenderObject());
+ presentation = static_cast<CPresentation *>(parentElement->GetBelongedPresentation());
+
+ // 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) {
+ error = QObject::tr("Invalid slide name for time context: '%1'").arg(slideName);
+ handleError();
+ return;
+ }
+
+ auto &strTable = presentation->GetStringTable();
+
+ const QString sourcePathPropName = QStringLiteral("sourcepath");
+ const QString startTimePropName = QStringLiteral("starttime");
+ const QString endTimePropName = QStringLiteral("endtime");
+ const QString eyeBallPropName = QStringLiteral("eyeball");
+ const QString typePropName = QStringLiteral("type");
+
+ Q3DStudio::UVariant attValue;
+ parentElement->GetAttribute(Q3DStudio::ATTRIBUTE_STARTTIME, attValue);
+ const int parentStartTime = int(attValue.m_INT32);
+ parentElement->GetAttribute(Q3DStudio::ATTRIBUTE_ENDTIME, attValue);
+ const int parentEndTime = int(attValue.m_INT32);
+
+ for (const auto &currentProps : qAsConst(theProperties)) {
+ ++_idCounter;
+ ++elementIndex;
+
+ // Remove properties requiring custom handling
+ QHash<QString, QVariant> fixedProps = currentProps;
+ QString newElementName = fixedProps.take(namePropName).toString();
+ QByteArray newElementNameBa = newElementName.toUtf8();
+
+ QString refMatName = fixedProps.take(QStringLiteral("material")).toString();
+ int colonIndex = refMatName.indexOf(QLatin1Char(':'));
+ if (colonIndex != -1)
+ refMatName = refMatName.mid(colonIndex + 1);
+ if (refMatName.startsWith(QLatin1Char('#'))) // Absolute reference
+ refMatName = refMatName.mid(1);
+ else if (!refMatName.isEmpty() && !refMatName.contains(QLatin1Char('/')))
+ refMatName = QStringLiteral("/") + refMatName;
+
+ // Make sure the name is not duplicate
+ TElement *existingChild
+ = parentElement->FindChild(CHash::HashString(newElementNameBa.constData()));
+ if (existingChild) {
+ error = QObject::tr("Element already exists: '%1'").arg(elementPaths[elementIndex]);
+ handleError();
+ return;
+ }
+
+ const CRegisteredString regName = strTable.RegisterStr(newElementNameBa);
+ TPropertyDescAndValueList elementProperties;
+ CRegisteredString metaType;
+ const CRegisteredString elementSubType;
+ GraphObjectTypes::Enum objectType = GraphObjectTypes::Unknown;
+
+ QString typeStr = fixedProps.take(typePropName).toString();
+ if (typeStr.isEmpty() || typeStr.compare(QLatin1String("model"),
+ Qt::CaseInsensitive) == 0) {
+ metaType = strTable.RegisterStr("Model");
+ objectType = GraphObjectTypes::Model;
+ } else if (typeStr.compare(QLatin1String("group"), Qt::CaseInsensitive) == 0) {
+ metaType = strTable.RegisterStr("Group");
+ objectType = GraphObjectTypes::Node;
+ }
+
+ // Set default values for missing mandatory properties
+ bool eyeBall = true;
+ fixedProps.value(eyeBallPropName, true).toBool();
+ if (objectType == GraphObjectTypes::Model && !fixedProps.contains(sourcePathPropName)) {
+ addStringAttribute(strTable, elementProperties, sourcePathPropName,
+ QStringLiteral("#Cube"));
+ }
+ if (!fixedProps.contains(startTimePropName))
+ addIntAttribute(strTable, elementProperties, startTimePropName, parentStartTime);
+ if (!fixedProps.contains(endTimePropName))
+ addIntAttribute(strTable, elementProperties, endTimePropName, parentEndTime);
+ if (!fixedProps.contains(eyeBallPropName))
+ addBoolAttribute(strTable, elementProperties, eyeBallPropName, true);
+ else
+ eyeBall = fixedProps.value(eyeBallPropName).toBool();
+
+ QHashIterator<QString, QVariant> it(fixedProps);
+ while (it.hasNext()) {
+ it.next();
+ switch (it.value().type()) {
+ case QVariant::Double:
+ addFloatAttribute(strTable, elementProperties, it.key(), it.value().toFloat());
+ break;
+ case QVariant::Bool:
+ addBoolAttribute(strTable, elementProperties, it.key(), it.value().toBool());
+ break;
+ case QVariant::Int:
+ addIntAttribute(strTable, elementProperties, it.key(), it.value().toInt());
+ break;
+ case QVariant::String:
+ addStringAttribute(strTable, 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(strTable, elementProperties, atts, vec);
+ break;
+ }
+ default:
+ error = QObject::tr("Unsupported property type for: '%1'").arg(it.key());
+ handleError();
+ return;
+ }
+ }
+
+ // Create new element
+ QString localElementPath = elementPaths[elementIndex];
+ colonIndex = localElementPath.indexOf(QLatin1Char(':'));
+ if (colonIndex != -1)
+ localElementPath = localElementPath.mid(colonIndex + 1);
+ TElement &newElem = m_Application->GetElementAllocator().CreateElement(
+ regName, metaType, elementSubType,
+ toConstDataRef(elementProperties.data(), QT3DSU32(elementProperties.size())),
+ presentation, parentElement, false);
+ newElem.m_Path = strTable.RegisterStr(localElementPath);
+
+ // Insert the new element into the correct slide
+ if (!slideSystem.addSlideElement(component, slideIndex, newElem, eyeBall)) {
+ // Delete created element if adding to slide failed
+ m_Application->GetElementAllocator().ReleaseElement(newElem, true);
+ error = QObject::tr("Failed to add the new element to a slide");
+ handleError();
+ return;
+ }
+
+ SGraphObject *referencedMaterial = nullptr;
+ if (objectType == GraphObjectTypes::Model) {
+ // 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 = localElementPath + QLatin1String(".refmat");
+ newMatElem.m_Path = strTable.RegisterStr(matElemPath);
+
+ if (!slideSystem.addSlideElement(component, slideIndex, newMatElem, eyeBall)) {
+ // Delete created element and material element if adding to slide failed
+ m_Application->GetElementAllocator().ReleaseElement(newElem, true);
+ error = QObject::tr("Failed to add the new material element to a slide");
+ handleError();
+ return;
+ }
+ // First check if we can resolve the referenced material before creating any 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;
+ 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<Qt3DSTranslator *>(
+ nextChild->GetAssociation());
+ referencedMaterial = static_cast<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<Qt3DSTranslator *>(firstChild->GetAssociation());
+ referencedMaterial = static_cast<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.
+ m_Application->GetElementAllocator().ReleaseElement(newElem, true);
+ error = QObject::tr("Unable to resolve a fallback material");
+ handleError();
+ return;
+ }
+ }
+ }
+
+ // Create model SGraphObject
+ NVAllocatorCallback &allocator = presentation->GetScene()->allocator();
+ SModel *newObject = QT3DS_NEW(allocator, SModel)();
+ newObject->m_Id = strTable.RegisterStr((QByteArrayLiteral("__newObj_")
+ + QByteArray::number(_idCounter)).constData());
+ parentObject.AddChild(*newObject);
+
+ Qt3DSTranslator::CreateTranslatorForElement(newElem, *newObject, allocator);
+
+ if (referencedMaterial) {
+ // Create material SGraphObject
+ SReferencedMaterial *newMaterial = QT3DS_NEW(allocator, SReferencedMaterial)();
+ newMaterial->m_Id = strTable.RegisterStr(
+ (QByteArrayLiteral("__newMat_")
+ + 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);
+
+ createdElements << &newElem;
+ }
+
+ bool isPrimary = presentation == m_Application->GetPrimaryPresentation() ? true : false;
+ initializeDataInputsInPresentation(*presentation, isPrimary, createdElements.toList());
+
+ renderer->ChildrenUpdated(parentObject);
+
+ handleError();
+}
+
+// Only supports deleting element types that can be added via createElement.
+void CQmlEngineImpl::deleteElements(const QStringList &elementPaths,
+ qt3ds::render::IQt3DSRenderer *renderer)
+{
+ QVector<TElement *> elements;
+ // Convert the path list to set for quicker lookups
+ QSet<QString> pathSet = elementPaths.toSet();
+
+ for (auto &elementPath : elementPaths) {
+ // Check that parent is not already included in the deleted elements
+ int idx = elementPath.lastIndexOf(QLatin1Char('.'));
+ bool parentFound = false;
+ while (idx != -1) {
+ QString parentPath = elementPath.left(idx);
+ if (pathSet.contains(parentPath)) {
+ parentFound = true;
+ break;
+ }
+ idx = parentPath.lastIndexOf(QLatin1Char('.'));
+ }
+ if (!parentFound) {
+ // Resolve element
+ QByteArray thePath = elementPath.toUtf8();
+ TElement *element = getTarget(thePath.constData());
+ if (element)
+ elements << element;
+ }
+ }
+ deleteElements(elements, renderer);
+}
+
+TElement *CQmlEngineImpl::createMaterialContainer(TElement *parent, CPresentation *presentation)
+{
+ TPropertyDescAndValueList prop;
+ auto &strTable = presentation->GetStringTable();
+ const auto matName = strTable.RegisterStr(QStringLiteral("__Container"));
+ const auto matType = strTable.RegisterStr(QStringLiteral("Material"));
+ const auto matClass = CRegisteredString();
+ TElement &element = m_Application->GetElementAllocator().CreateElement(
+ matName, matType, matClass,
+ toConstDataRef(prop.data(), prop.size()),
+ presentation, parent, false);
+ return &element;
+}
+
+/**
+ Creates material into material container of the specified subpresentation.
+ The materialDefinition parameter can contain a .materialdef file path or
+ the entire material definition in the .materialdef format.
+*/
+void CQmlEngineImpl::createMaterials(const QString &subPresId,
+ const QStringList &materialDefinitions,
+ ICustomMaterialSystem *customMaterialSystem,
+ IDynamicObjectSystem *dynamicObjectSystem,
+ qt3ds::render::IQt3DSRenderer *renderer)
+{
+ CPresentation *presentation = nullptr;
+ QString error;
+ QString presPath;
+ QString projPath;
+ struct MaterialInfo {
+ QString materialDefinition;
+ QString materialName;
+ QMap<QString, QString> materialProps;
+ QMap<QString, QMap<QString, QString>> textureProps;
+ };
+ QVector<MaterialInfo *> materialInfos;
+ QVector<TElement *> createdElements;
+
+ auto handleError = [&]() {
+ if (!error.isEmpty())
+ deleteElements(createdElements, renderer);
+ QStringList materialNames;
+ QString prefix;
+ if (!subPresId.isEmpty())
+ prefix = subPresId + QLatin1Char(':');
+ for (auto &materialInfo : materialInfos)
+ materialNames << prefix + materialInfo->materialName;
+ qDeleteAll(materialInfos);
+ notifyMaterialCreation(materialNames, error);
+ };
+
+ auto getMaterialInfos = [&]() {
+ for (auto &materialDefinition : materialDefinitions) {
+ MaterialInfo *info = new MaterialInfo;
+ info->materialDefinition = materialDefinition;
+ Q3DSMaterialDefinitionParser::getMaterialInfo(materialDefinition, projPath, presPath,
+ info->materialName, info->materialProps,
+ info->textureProps);
+ materialInfos << info;
+ }
+ };
+
+ QByteArray theSubPresId = subPresId.toUtf8();
+ if (theSubPresId.isEmpty())
+ presentation = m_Application->GetPrimaryPresentation();
+ else
+ presentation = m_Application->GetPresentationById(theSubPresId.constData());
+
+ if (!presentation) {
+ error = QObject::tr("Invalid presentation ID: '%1'").arg(subPresId);
+ presentation = m_Application->GetPrimaryPresentation();
+ presPath = QFileInfo(presentation->GetFilePath()).absolutePath();
+ projPath = presentation->getProjectPath();
+ getMaterialInfos();
+ handleError();
+ return;
+ }
+
+ presPath = QFileInfo(presentation->GetFilePath()).absolutePath();
+ projPath = presentation->getProjectPath();
+ getMaterialInfos();
+
+ // Find material container
+ auto &strTable = presentation->GetStringTable();
+ NVAllocatorCallback &allocator = presentation->GetScene()->allocator();
+ TElement *rootElement = presentation->GetRoot();
+ const auto containerName = strTable.RegisterStr("__Container");
+ TElement *container = rootElement->FindChild(CHash::HashString(containerName.c_str()));
+ if (!container)
+ container = createMaterialContainer(rootElement, presentation);
+
+ for (auto &materialInfo : materialInfos) {
+ if (materialInfo->materialName.isEmpty() || materialInfo->materialProps.isEmpty()) {
+ error = QObject::tr("Invalid material definition: '%1'")
+ .arg(materialInfo->materialDefinition);
+ handleError();
+ return;
+ }
+
+ // We don't care about the path parameter
+ const QString pathStr = QStringLiteral("path");
+ if (materialInfo->materialProps.contains(pathStr))
+ materialInfo->materialProps.remove(pathStr);
+
+ // Check that the material doesn't already exist in container
+ TElement *firstChild = nullptr;
+ TElement *nextChild = container->GetChild();
+ firstChild = nextChild;
+ while (nextChild) {
+ QString childName = QString::fromUtf8(nextChild->m_Name);
+ if (childName == materialInfo->materialName) {
+ error = QObject::tr("Material already exists in material container");
+ handleError();
+ return;
+ }
+ nextChild = nextChild->GetSibling();
+ }
+
+ // Create material element in the container based on the material definition
+ auto &metaData = m_Application->GetMetaData();
+ const auto matName = strTable.RegisterStr(materialInfo->materialName);
+ const bool isCustomMaterial = (materialInfo->materialProps.value(QStringLiteral("type"))
+ == QLatin1String("CustomMaterial"));
+ CRegisteredString matType;
+ CRegisteredString matClass;
+ QHash<QString, qt3ds::render::dynamic::SPropertyDefinition> dynPropDefs;
+
+ if (isCustomMaterial) {
+ CRegisteredString sourcePath
+ = strTable.RegisterStr(materialInfo->materialProps.value(
+ QStringLiteral("sourcepath")).toUtf8());
+ matType = strTable.RegisterStr("CustomMaterial");
+ matClass = sourcePath; // Create just one class per shader
+ if (sourcePath.IsValid()) {
+ Option<qt3dsdm::SMetaDataCustomMaterial> matMetaData =
+ metaData.GetMaterialMetaDataBySourcePath(sourcePath.c_str());
+ if (!matMetaData.hasValue()) {
+ metaData.LoadMaterialXMLFile(matType.c_str(), matClass.c_str(), matName.c_str(),
+ sourcePath.c_str());
+ matMetaData = metaData.GetMaterialMetaDataBySourcePath(sourcePath.c_str());
+ }
+ if (matMetaData.hasValue()) {
+ qt3ds::render::IUIPLoader::CreateMaterialClassFromMetaMaterial(
+ matClass, m_Foundation, *customMaterialSystem, matMetaData,
+ strTable);
+ NVConstDataRef<qt3ds::render::dynamic::SPropertyDefinition> customProperties;
+ customProperties = dynamicObjectSystem->GetProperties(matClass);
+ for (QT3DSU32 i = 0, end = customProperties.size(); i < end; ++i) {
+ QString propName = QString::fromUtf8(customProperties[i].m_Name.c_str());
+ if (materialInfo->materialProps.contains(propName))
+ dynPropDefs.insert(propName, customProperties[i]);
+ }
+ } else {
+ error = QObject::tr("Could not resolve properties for CustomMaterial");
+ handleError();
+ return;
+ }
+ } else {
+ error = QObject::tr("Missing sourcepath in definition of CustomMaterial");
+ handleError();
+ return;
+ }
+ } else {
+ matType = strTable.RegisterStr("Material");
+ }
+
+ auto createElementPropsFromDefProps = [&](const QMap<QString, QString> &defProps,
+ TPropertyDescAndValueList &elementProps,
+ const CRegisteredString &elementType) {
+ QMapIterator<QString, QString> propIter(defProps);
+ while (propIter.hasNext()) {
+ propIter.next();
+ if (dynPropDefs.contains(propIter.key()))
+ continue; // Dynamic properties are added directly to graph objects later
+
+ auto propName = strTable.RegisterStr(propIter.key());
+ ERuntimeDataModelDataType dataType;
+ ERuntimeAdditionalMetaDataType additionalType;
+ dataType = metaData.GetPropertyType(elementType, propName);
+ additionalType = metaData.GetAdditionalType(elementType, propName);
+
+ switch (dataType) {
+ case ERuntimeDataModelDataTypeLong: {
+ addIntAttribute(strTable, elementProps, propIter.key(),
+ propIter.value().toInt());
+ break;
+ }
+ case ERuntimeDataModelDataTypeFloat: {
+ addFloatAttribute(strTable, elementProps, propIter.key(),
+ propIter.value().toFloat());
+ break;
+ }
+ case ERuntimeDataModelDataTypeFloat2: {
+ QVector2D vec = parseFloat2Property(propIter.value());
+ QStringList atts;
+ atts << (propIter.key() + QLatin1String(".x"))
+ << (propIter.key() + QLatin1String(".y"));
+ addFloat2Attribute(strTable, elementProps, atts, vec);
+ break;
+ }
+ case ERuntimeDataModelDataTypeFloat3: {
+ QVector3D vec = parseFloat3Property(propIter.value());
+ if (additionalType == ERuntimeAdditionalMetaDataTypeRotation) {
+ vec.setX(qDegreesToRadians(vec.x()));
+ vec.setY(qDegreesToRadians(vec.y()));
+ vec.setZ(qDegreesToRadians(vec.z()));
+ }
+ QStringList atts;
+ atts << (propIter.key() + QLatin1String(".x"))
+ << (propIter.key() + QLatin1String(".y"))
+ << (propIter.key() + QLatin1String(".z"));
+ addFloat3Attribute(strTable, elementProps, atts, vec);
+ break;
+ }
+ case ERuntimeDataModelDataTypeFloat4: {
+ QVector4D vec = parseFloat4Property(propIter.value());
+ QStringList atts;
+ if (additionalType == ERuntimeAdditionalMetaDataTypeColor) {
+ atts << (propIter.key() + QLatin1String(".r"))
+ << (propIter.key() + QLatin1String(".g"))
+ << (propIter.key() + QLatin1String(".b"))
+ << (propIter.key() + QLatin1String(".a"));
+ } else {
+ atts << (propIter.key() + QLatin1String(".x"))
+ << (propIter.key() + QLatin1String(".y"))
+ << (propIter.key() + QLatin1String(".z"))
+ << (propIter.key() + QLatin1String(".w"));
+ }
+ addFloat4Attribute(strTable, elementProps, atts, vec);
+ break;
+ }
+ case ERuntimeDataModelDataTypeBool: {
+ bool boolValue = propIter.value().compare(QLatin1String("true"),
+ Qt::CaseInsensitive) == 0;
+ addBoolAttribute(strTable, elementProps, propIter.key(), boolValue);
+ break;
+ }
+ case ERuntimeDataModelDataTypeStringRef:
+ case ERuntimeDataModelDataTypeString: {
+ addStringAttribute(strTable, elementProps, propIter.key(), propIter.value());
+ break;
+ }
+ case ERuntimeDataModelDataTypeLong4: {
+ if (additionalType == ERuntimeAdditionalMetaDataTypeImage) {
+ // Insert placeholder for now, will be patched later
+ addElementRefAttribute(strTable, elementProps, propIter.key(), nullptr);
+ }
+ break;
+ }
+ default:
+ error = QObject::tr("Unsupported material property type for property: '%1'")
+ .arg(propIter.key());
+ return;
+ }
+ }
+ };
+
+ TPropertyDescAndValueList elementProps;
+ createElementPropsFromDefProps(materialInfo->materialProps, elementProps, matType);
+ if (!error.isEmpty()) {
+ handleError();
+ return;
+ }
+
+ TElement &newMatElem = m_Application->GetElementAllocator().CreateElement(
+ matName, matType, matClass,
+ toConstDataRef(elementProps.data(), QT3DSU32(elementProps.size())),
+ presentation, container, false);
+ newMatElem.SetActive(true);
+
+ // Create image elements
+ CRegisteredString imageType = strTable.RegisterStr("Image");
+ QMapIterator<QString, QMap<QString, QString>> texIter(materialInfo->textureProps);
+ QHash<QString, TElement *> imageElementMap;
+ while (texIter.hasNext()) {
+ texIter.next();
+ elementProps.clear();
+ createElementPropsFromDefProps(texIter.value(), elementProps, imageType);
+ if (!error.isEmpty()) {
+ m_Application->GetElementAllocator().ReleaseElement(newMatElem, true);
+ handleError();
+ return;
+ }
+ CRegisteredString imageName = strTable.RegisterStr(texIter.key());
+
+ TElement &newImageElem = m_Application->GetElementAllocator().CreateElement(
+ imageName, imageType, CRegisteredString(),
+ toConstDataRef(elementProps.data(), QT3DSU32(elementProps.size())),
+ presentation, &newMatElem, false);
+ imageElementMap.insert(texIter.key(), &newImageElem);
+ newImageElem.SetActive(true);
+ }
+
+ // Create render object for the material
+ qt3ds::render::SGraphObject *newMaterial = nullptr;
+ CRegisteredString newMatId = strTable.RegisterStr(
+ (QByteArrayLiteral("__newMat_") + QByteArray::number(++_idCounter))
+ .constData());
+ if (isCustomMaterial) {
+ newMaterial = customMaterialSystem->CreateCustomMaterial(matClass, allocator);
+ newMaterial->m_Id = newMatId;
+ auto dynObj = static_cast<qt3ds::render::SDynamicObject *>(newMaterial);
+
+ QHashIterator<QString, qt3ds::render::dynamic::SPropertyDefinition>
+ dynPropIter(dynPropDefs);
+ while (dynPropIter.hasNext()) {
+ dynPropIter.next();
+ QByteArray propValStr = materialInfo->materialProps.value(
+ dynPropIter.key()).toUtf8();
+ const auto propDesc = dynPropIter.value();
+ switch (propDesc.m_DataType) {
+ case qt3ds::render::NVRenderShaderDataTypes::QT3DSRenderBool: {
+ bool boolValue = propValStr.compare(QByteArrayLiteral("true"),
+ Qt::CaseInsensitive) == 0;
+ setDynamicObjectProperty(*dynObj, propDesc, boolValue);
+ break;
+ }
+ case qt3ds::render::NVRenderShaderDataTypes::QT3DSF32:
+ setDynamicObjectProperty(*dynObj, propDesc, propValStr.toFloat());
+ break;
+ case qt3ds::render::NVRenderShaderDataTypes::QT3DSI32:
+ if (!propDesc.m_IsEnumProperty) {
+ setDynamicObjectProperty(*dynObj, propDesc, propValStr.toInt());
+ } else {
+ const NVConstDataRef<CRegisteredString> &enumNames
+ = propDesc.m_EnumValueNames;
+ for (QT3DSU32 i = 0, end = enumNames.size(); i < end; ++i) {
+ if (propValStr.compare(enumNames[i].c_str()) == 0) {
+ setDynamicObjectProperty(*dynObj, propDesc, i);
+ break;
+ }
+ }
+ }
+ break;
+ case qt3ds::render::NVRenderShaderDataTypes::QT3DSVec2:
+ setDynamicObjectProperty(*dynObj, propDesc, parseFloat2Property(propValStr));
+ break;
+ case qt3ds::render::NVRenderShaderDataTypes::QT3DSVec3:
+ setDynamicObjectProperty(*dynObj, propDesc, parseFloat3Property(propValStr));
+ break;
+ case qt3ds::render::NVRenderShaderDataTypes::QT3DSVec4:
+ setDynamicObjectProperty(*dynObj, propDesc, parseFloat4Property(propValStr));
+ break;
+ case qt3ds::render::NVRenderShaderDataTypes::NVRenderTexture2DPtr:
+ case qt3ds::render::NVRenderShaderDataTypes::NVRenderImage2DPtr: {
+ CRegisteredString regStr;
+ regStr = strTable.RegisterStr(propValStr);
+ setDynamicObjectProperty(*dynObj, propDesc, regStr);
+ break;
+ }
+ default:
+ error = QObject::tr("Unsupported custom material property type for '%1'")
+ .arg(dynPropIter.key());
+ m_Application->GetElementAllocator().ReleaseElement(newMatElem, true);
+ handleError();
+ return;
+ }
+ }
+ } else {
+ newMaterial = QT3DS_NEW(allocator, qt3ds::render::SDefaultMaterial)();
+ newMaterial->m_Id = newMatId;
+
+ // Update element refs for image elements and create graph objects & translators
+ QHashIterator<QString, TElement *> imageIter(imageElementMap);
+ while (imageIter.hasNext()) {
+ imageIter.next();
+ TElement *imageElem = imageIter.value();
+ UVariant *propValue = newMatElem.FindPropertyValue(imageElem->m_Name);
+ if (propValue) {
+ propValue->m_ElementHandle = imageIter.value()->GetHandle();
+
+ qt3ds::render::SImage *newImageObj
+ = QT3DS_NEW(allocator, qt3ds::render::SImage)();
+ newImageObj->m_Id = strTable.RegisterStr(
+ (QByteArrayLiteral("__newImage_")
+ + QByteArray::number(++_idCounter)).constData());
+ qt3ds::render::Qt3DSTranslator::CreateTranslatorForElement(
+ *imageElem, *newImageObj, allocator);
+ }
+ }
+ createdElements << &newMatElem;
+ }
+
+ qt3ds::render::Qt3DSTranslator::CreateTranslatorForElement(newMatElem, *newMaterial,
+ allocator);
+ }
+
+ handleError();
+}
+
+void CQmlEngineImpl::deleteMaterials(const QStringList &materialNames, IQt3DSRenderer *renderer)
+{
+ // Material class (i.e. the shader) is not deleted as those can be shared between materials,
+ // so we just delete the material elements from the container and the related render objects
+
+ // Sort materials to presentations
+ QMultiHash<QString, QString> presMaterialMap;
+ QSet<QString> presIds;
+ for (const auto &matName : materialNames) {
+ QString presId;
+ QString localName = matName;
+ int index = matName.indexOf(QLatin1Char(':'));
+ if (index != -1) {
+ presId = matName.left(index);
+ localName = matName.mid(index + 1);
+ }
+ presMaterialMap.insert(presId, localName);
+ presIds.insert(presId);
+ }
+ for (const auto &presId : qAsConst(presIds)) {
+ QByteArray theId = presId.toUtf8();
+ CPresentation *presentation = nullptr;
+ if (presId.isEmpty())
+ presentation = m_Application->GetPrimaryPresentation();
+ else
+ presentation = m_Application->GetPresentationById(theId.constData());
+
+ if (presentation) {
+ // Find material container
+ auto &strTable = presentation->GetStringTable();
+ TElement *rootElement = presentation->GetRoot();
+ const auto containerName = strTable.RegisterStr("__Container");
+ TElement *container = rootElement->FindChild(CHash::HashString(containerName.c_str()));
+ Q_ASSERT_X(container, __FUNCTION__,
+ QStringLiteral("No material container found for presentation: '%1'")
+ .arg(presId).toUtf8());
+
+ QVector<TElement *> elementsToDelete;
+ const QList<QString> matNames = presMaterialMap.values(presId);
+ for (const auto &materialName : matNames) {
+ TElement *firstChild = nullptr;
+ TElement *nextChild = container->GetChild();
+ firstChild = nextChild;
+ bool added = false;
+ while (nextChild) {
+ QString childName = QString::fromUtf8(nextChild->m_Name);
+ if (childName == materialName) {
+ elementsToDelete << nextChild;
+ added = true;
+ break;
+ }
+ nextChild = nextChild->GetSibling();
+ }
+ if (!added) {
+ if (presId.isEmpty()) {
+ qWarning() << __FUNCTION__
+ << QStringLiteral("Could not find material '%1'")
+ .arg(materialName);
+ } else {
+ qWarning() << __FUNCTION__
+ << QStringLiteral("Could not find material '%1' in '%2'")
+ .arg(materialName).arg(presId);
+ }
+ }
+ }
+ deleteElements(elementsToDelete, renderer);
+ } else {
+ qWarning() << __FUNCTION__ << "Warning: Presentation ID could not be resolved:"
+ << presId;
+ }
+ }
+}
+
+void CQmlEngineImpl::createMesh(const QString &name, qt3dsimp::Mesh *mesh,
+ qt3ds::render::IBufferManager *bufferManager)
+{
+ // Add the custom meshes to buffer manager.
+ bufferManager->loadCustomMesh(name, mesh);
+}
+
+void CQmlEngineImpl::deleteMeshes(const QStringList &meshNames,
+ qt3ds::render::IBufferManager *bufferManager)
+{
+ for (const auto &meshName : meshNames) {
+ if (!meshName.isEmpty()) {
+ CRegisteredString regName = bufferManager->GetStringTable().RegisterStr(meshName);
+ bufferManager->InvalidateBuffer(regName);
+ }
+ }
+}
+
+void CQmlEngineImpl::GotoSlide(const char *component, const char *slideName,
+ const SScriptEngineGotoSlideArgs &inArgs)
+{
+ TElement *theTarget = getTarget(component);
+ if (theTarget) {
+ CQmlCommandHelper::SetupGotoSlideCommand(*theTarget, slideName, inArgs);
+ } else {
+ qCWarning(qt3ds::INVALID_OPERATION)
+ << "CQmlEngineImpl::GotoSlide: Unable to find component " << component;
+ }
+}
+
+void CQmlEngineImpl::GotoSlideRelative(const char *component, bool inNextSlide, bool inWrap,
+ const SScriptEngineGotoSlideArgs &inArgs)
+{
+ TElement *theTarget = getTarget(component);
+ if (theTarget) {
+ theTarget = &theTarget->GetComponentParent();
+ if (theTarget && theTarget->GetActive()) {
+ TComponent *theComponent = static_cast<TComponent *>(theTarget);
+ Q3DStudio::INT32 theSlide = theComponent->GetCurrentSlide();
+ Q3DStudio::INT32 theSlideCount = theComponent->GetSlideCount();
+ theSlide = inNextSlide ? theSlide + 1 : theSlide - 1;
+ if (theSlide < 1) {
+ if (inWrap)
+ theSlide = theSlideCount - 1;
+ else
+ theSlide = 1;
+ } else if (theSlide == theSlideCount) {
+ if (inWrap)
+ theSlide = 1;
+ else
+ theSlide = theSlideCount - 1;
+ }
+ if (theSlide != theComponent->GetCurrentSlide()) {
+ CQmlCommandHelper::SetupGotoSlideCommand(*theTarget, theSlide, inArgs);
+ }
+ } else {
+ qCCritical(qt3ds::INVALID_OPERATION)
+ << "QmlEngine::GotoSlideRelative: Component is not active: " << component;
+ }
+ } else {
+ qCCritical(qt3ds::INVALID_OPERATION)
+ << "QmlEngine::GotoSlideRelative: failed to find component: " << component;
+ }
+}
+
+void CQmlEngineImpl::SetPresentationAttribute(const char *presId, const char *, const char *value)
+{
+ if (isTrivial(presId))
+ return;
+ if (presId[0] == '#')
+ ++presId;
+ CPresentation *thePresentation = m_Application->GetPresentationById(presId);
+ if (thePresentation) {
+ bool active = AreEqualCaseless(nonNull(value), "True");
+ thePresentation->SetActive(active);
+ }
+}
+
+void CQmlEngineImpl::GotoSlideIndex(const char *component, const Q3DStudio::INT32 slideIndex,
+ const SScriptEngineGotoSlideArgs &inArgs)
+{
+ TElement *theTarget = getTarget(component);
+ if (theTarget) {
+ CQmlCommandHelper::SetupGotoSlideCommand(*theTarget, slideIndex, inArgs);
+ } else {
+ qCWarning(qt3ds::INVALID_OPERATION)
+ << "CQmlEngineImpl::GotoSlide: Unable to find component " << component;
+ }
+}
+
+bool CQmlEngineImpl::GetSlideInfo(const char *element, int &currentIndex, int &previousIndex,
+ QString &currentName, QString &previousName)
+{
+ QML_ENGINE_MULTITHREAD_PROTECT_METHOD;
+
+ TElement *theTarget = getTarget(element);
+ if (theTarget && theTarget->IsComponent()) {
+ IPresentation *thePresentation = theTarget->GetBelongedPresentation();
+ TComponent *theComponent = thePresentation->GetComponentManager().GetComponent(theTarget);
+ currentIndex = int(theComponent->GetCurrentSlide());
+ previousIndex = int(theComponent->GetPreviousSlide());
+ if (currentIndex > 0) {
+ currentName = QString::fromLocal8Bit(
+ thePresentation->GetSlideSystem().GetSlideName(
+ qt3ds::runtime::SSlideKey(*theComponent, QT3DSU32(currentIndex))));
+ } else {
+ currentName.clear();
+ }
+ if (previousIndex > 0) {
+ previousName = QString::fromLocal8Bit(
+ thePresentation->GetSlideSystem().GetSlideName(
+ qt3ds::runtime::SSlideKey(*theComponent, QT3DSU32(previousIndex))));
+ } else {
+ previousName.clear();
+ }
+ return true;
+ } else {
+ qCCritical(qt3ds::INVALID_OPERATION)
+ << "CQmlEngineImpl::GetSlideInfo: Supplied element is not a component " << element;
+ }
+ return false;
+}
+
+void CQmlEngineImpl::GotoTime(const char *component, const Q3DStudio::FLOAT time)
+{
+ TElement *theTarget = getTarget(component);
+ if (theTarget && theTarget->GetActive()) {
+ UVariant theArg1;
+ UVariant theArg2;
+
+ IPresentation *thePresentation = theTarget->GetBelongedPresentation();
+
+ theArg1.m_INT32 = static_cast<INT32>(time * 1000);
+
+ TElement *theParentTarget = &theTarget->GetComponentParent();
+
+ thePresentation->FireCommand(COMMAND_GOTOTIME, theParentTarget, &theArg1, &theArg2);
+ }
+}
+
+bool CQmlEngineImpl::RegisterCallback(Q3DStudio::UINT32 callbackType,
+ const TScriptCallback inCallback, void *inUserData)
+{
+ QML_ENGINE_MULTITHREAD_PROTECT_METHOD;
+ return m_ScriptCallbacks.RegisterCallback(callbackType, inCallback, inUserData);
+}
+
+void CQmlEngineImpl::ProcessFrameCallbacks(IPresentation *)
+{
+ QML_ENGINE_MULTITHREAD_PROTECT_METHOD;
+ m_ScriptCallbacks.ProcessCallbacks();
+
+ for (Q3DSQmlScript *script : m_scripts)
+ script->update();
+}
+
+void CQmlEngineImpl::ProcessSignal(IPresentation *inPresentation,
+ const SEventCommand &inCommand)
+{
+ using qt3ds::runtime::TIdValuePair;
+ QML_ENGINE_MULTITHREAD_PROTECT_METHOD;
+ TElement *theElement = inCommand.m_Target; // the element that is a behavior
+
+ CPresentation *thePresentation = (CPresentation *)inPresentation;
+ // IParametersSystem& theParametersManager = thePresentation->GetParametersSystem();
+ qt3ds::foundation::IStringTable &theStrTable(thePresentation->GetStringTable());
+
+ CRegisteredString theSignal = theStrTable.HandleToStr(inCommand.m_Arg1.m_INT32);
+
+ if (!theSignal.IsValid() || m_EmitSignalDataList.size() > CQmlEngineImpl::MAX_ACTION_QUEUE_SIZE)
+ return;
+
+ TEmitSignalPtr theTemp = QT3DS_NEW(m_Foundation.getAllocator(), SEmitSignalData)(
+ m_Foundation.getAllocator(), inCommand.m_Arg1.m_INT32, theElement);
+ m_EmitSignalDataList.push_back(theTemp);
+}
+
+
+//==============================================================================
+/**
+* Handle custom actions
+* @param inCommand carrier of command and parameter information
+*/
+void CQmlEngineImpl::ProcessCustomActions(IPresentation *presentation,
+ const SEventCommand &command)
+{
+ TElement *element = command.m_Target;
+ for (Q3DSQmlScript *script : m_scripts) {
+ if (script->hasBehavior(element)) {
+ IParametersSystem &parametersManager
+ = static_cast<CPresentation *>(presentation)->GetParametersSystem();
+ INT32 groupId = command.m_Arg1.m_INT32;
+ UINT32 numParams = parametersManager.GetNumParameters(groupId);
+ if (numParams == 0) {
+ QT3DS_ASSERT(false);
+ return;
+ }
+
+ qt3ds::runtime::TIdValuePair tempData = parametersManager.GetParameter(groupId, 0);
+ if (tempData.first != CHash::HashString("string")) {
+ QT3DS_ASSERT(false);
+ return;
+ }
+
+ CRegisteredString functionName = presentation->GetStringTable().HandleToStr(
+ tempData.second.m_StringHandle);
+ script->call(functionName.c_str());
+ return;
+ }
+ }
+}
+
+bool CQmlEngineImpl::PeekSignal(TElement *&outElement, char *&outName)
+{
+ if (m_EmitSignalDataList.empty())
+ return false;
+
+ NVScopedRefCounted<SEmitSignalData> theAction = m_EmitSignalDataList.front();
+ m_EmitSignalDataList.pop_front();
+
+ outElement = theAction->m_TargetElement;
+ if (outElement) {
+ IPresentation *thePresentation = outElement->GetBelongedPresentation();
+ qt3ds::foundation::IStringTable &theStrTable(thePresentation->GetStringTable());
+ CRegisteredString theSignal = theStrTable.HandleToStr(theAction->m_SignalNameHandler);
+ outName = (char *)theSignal.c_str();
+ } else {
+ qCWarning(qt3ds::INVALID_OPERATION)
+ << "CQmlEngineImpl::PeekCustomAction: Unable to find element: EmitSignal queue error";
+ outName = NULL;
+ }
+
+ return true;
+}
+
+void CQmlEngineImpl::createComponent(QQmlComponent *component, TElement *element)
+{
+ TElement *parent = element->GetParent();
+ if (!parent)
+ return;
+
+ QQmlContext *context = new QQmlContext(&m_engine, &m_engine);
+ QObject *obj = component->beginCreate(context);
+ if (!obj) {
+ context->deleteLater();
+ return;
+ }
+ Q3DSQmlBehavior *behaviorObject = qobject_cast<Q3DSQmlBehavior *>(obj);
+ if (behaviorObject == nullptr) {
+ obj->deleteLater();
+ context->deleteLater();
+ return;
+ }
+
+ auto script = new Q3DSQmlScript(*this, *behaviorObject, *element, *parent);
+ component->completeCreate();
+ behaviorObject->setScript(script);
+
+ script->setParent(component);
+ obj->setParent(script);
+ m_scripts.push_back(script);
+}
+
+TElement *CQmlEngineImpl::getTarget(const char *component) {
+ TElement *target = NULL;
+ QStringList split = QString(component).split(":");
+ if (split.size() > 1) {
+ target = CQmlElementHelper::GetElement(
+ *m_Application,
+ m_Application->GetPresentationById(split.at(0).toStdString().c_str()),
+ split.at(1).toStdString().c_str(), NULL);
+ } else {
+ target = CQmlElementHelper::GetElement(
+ *m_Application,
+ m_Application->GetPrimaryPresentation(),
+ split.at(0).toStdString().c_str(), NULL);
+ }
+ return target;
+}
+
+void CQmlEngineImpl::listAllElements(TElement *root, QList<TElement *> &elements)
+{
+ elements.append(root);
+ TElement *nextChild = root->GetChild();
+ while (nextChild) {
+ listAllElements(nextChild, elements);
+ nextChild = nextChild->GetSibling();
+ }
+}
+
+// Initializes datainput bindings in the presentation starting by default from the root element.
+// If inElements is specified, only parses the specified elements.
+void CQmlEngineImpl::initializeDataInputsInPresentation(CPresentation &presentation,
+ bool isPrimary,
+ QList<TElement *> inElements)
+{
+ QList<TElement *> elements;
+ if (!inElements.empty()) {
+ elements = inElements;
+ } else {
+ TElement *parent = presentation.GetRoot();
+ listAllElements(parent, elements);
+ }
+ qt3ds::runtime::DataInputMap &diMap = m_Application->dataInputMap();
+
+// #TODO: Remove below once QT3DS-3510 has been implemented in the editor
+ qt3ds::runtime::DataOutputMap &doMap = m_Application->dataOutputMap();
+// #TODO: Remove above once QT3DS-3510 has been implemented in the editor
+
+ qt3ds::foundation::IStringTable &strTable(presentation.GetStringTable());
+ QHash<CRegisteredString, qt3ds::runtime::DataOutputDef> elementPathToDataOutputDefMap;
+ for (TElement *element : qAsConst(elements)) {
+ Option<QT3DSU32> ctrlIndex = element->FindPropertyIndex(ATTRIBUTE_CONTROLLEDPROPERTY);
+ if (ctrlIndex.hasValue()) {
+ Option<qt3ds::runtime::element::TPropertyDescAndValuePtr> propertyInfo =
+ element->GetPropertyByIndex(*ctrlIndex);
+ UVariant *valuePtr = propertyInfo->second;
+ QString valueStr =
+ QString::fromUtf8(strTable.HandleToStr(valuePtr->m_StringHandle));
+ if (!valueStr.isEmpty()) {
+ QStringList splitValues = valueStr.split(QChar(' '));
+ for (int i = 1; i < splitValues.size(); i += 2) {
+ QString controllerName = splitValues[i - 1];
+ // remove datainput name prefix "$"
+ controllerName.remove(0, 1);
+ if (diMap.contains(controllerName)) {
+ qt3ds::runtime::DataInOutAttribute ctrlElem;
+ if (!isPrimary) {
+ // Prepend presentation id to element path
+ ctrlElem.elementPath = presentation.GetName();
+ ctrlElem.elementPath.append(QByteArrayLiteral(":"));
+ }
+ ctrlElem.attributeName.append(splitValues[i].toUtf8());
+ if (ctrlElem.attributeName.first() == QByteArrayLiteral("@timeline")) {
+ ctrlElem.propertyType = ATTRIBUTETYPE_DATAINPUT_TIMELINE;
+ TElement *component = &element->GetComponentParent();
+ ctrlElem.elementPath.append(component->m_Path);
+ } else if (ctrlElem.attributeName.first() == QByteArrayLiteral("@slide")) {
+ ctrlElem.propertyType = ATTRIBUTETYPE_DATAINPUT_SLIDE;
+ TElement *component = &element->GetComponentParent();
+ ctrlElem.elementPath.append(component->m_Path);
+ } else if (diMap[controllerName].type
+ == qt3ds::runtime::DataInOutTypeVector3) {
+ // special handling for vector datatype to handle
+ // expansion from <propertyname> to <propertyname>.x .y .z
+ QVector<QByteArray> attVec;
+ bool success = getAttributeVector3(
+ attVec, ctrlElem.attributeName.first().constData(),
+ element);
+ if (!attVec.empty() && success) {
+ ctrlElem.attributeName = attVec;
+ ctrlElem.elementPath.append(element->m_Path);
+ ctrlElem.propertyType = ATTRIBUTETYPE_FLOAT3;
+ } else {
+ qWarning() << __FUNCTION__ << "Property "
+ << ctrlElem.attributeName.first()
+ << " was not expanded to vector";
+ ctrlElem.propertyType = ATTRIBUTETYPE_NONE;
+ }
+ } else if (diMap[controllerName].type
+ == qt3ds::runtime::DataInOutTypeVector2) {
+ // special handling for vector datatype to handle
+ // expansion from <propertyname> to <propertyname>.x .y
+ QVector<QByteArray> attVec;
+ bool success = getAttributeVector2(
+ attVec, ctrlElem.attributeName.first().constData(),
+ element);
+ if (!attVec.empty() && success) {
+ ctrlElem.attributeName = attVec;
+ ctrlElem.elementPath.append(element->m_Path);
+ ctrlElem.propertyType = ATTRIBUTETYPE_FLOAT2;
+ } else {
+ qWarning() << __FUNCTION__ << "Property "
+ << ctrlElem.attributeName.first()
+ << " was not expanded to vector";
+ ctrlElem.propertyType = ATTRIBUTETYPE_NONE;
+ }
+ } else if (diMap[controllerName].type
+ == qt3ds::runtime::DataInOutTypeEvaluator) {
+ diMap[controllerName].evalFunc
+ = buildJSFunc(diMap[controllerName].evaluator);
+ auto referencedDIs = resolveDependentDatainputs(
+ diMap[controllerName].evaluator, controllerName);
+ // add this evaluator datainput to the dependent list
+ // for those datainputs that are used in the expression
+ // for this evaluator
+ for (auto ref : referencedDIs)
+ diMap[ref].dependents.append(controllerName);
+
+ ctrlElem.elementPath.append(element->m_Path);
+ TStringHash attHash = CHash::HashAttribute(
+ ctrlElem.attributeName.first().constData());
+ Option<qt3ds::runtime::element::TPropertyDescAndValuePtr> attInfo
+ = element->FindProperty(attHash);
+ if (attInfo.hasValue())
+ ctrlElem.propertyType = attInfo->first.m_Type;
+ } else {
+ // all other scalar datatypes
+ ctrlElem.elementPath.append(element->m_Path);
+ TStringHash attHash = CHash::HashAttribute(
+ ctrlElem.attributeName.first().constData());
+ Option<qt3ds::runtime::element::TPropertyDescAndValuePtr> attInfo
+ = element->FindProperty(attHash);
+ if (attInfo.hasValue()) {
+ ctrlElem.propertyType = attInfo->first.m_Type;
+ } else {
+ ctrlElem.propertyType = ATTRIBUTETYPE_NONE;
+ qWarning() << __FUNCTION__ << "Property "
+ << ctrlElem.attributeName.first() << " not existing!";
+ }
+ }
+ qt3ds::runtime::DataInputDef &diDef = diMap[controllerName];
+ diDef.controlledAttributes.append(ctrlElem);
+
+// #TODO: Remove below once QT3DS-3510 has been implemented in the editor
+ // Note, in this temp implementation only the LAST of multiple attributes
+ // will be notified from the object under the DataInput name..
+ qt3ds::runtime::DataInputDef inDef = diMap[controllerName];
+ qt3ds::runtime::DataOutputDef &doDef = doMap[controllerName];
+ doDef.observedAttribute = ctrlElem;
+ doDef.type = inDef.type;
+ doDef.min = inDef.min;
+ doDef.max = inDef.max;
+
+ if (ctrlElem.propertyType == ATTRIBUTETYPE_DATAINPUT_TIMELINE) {
+ // Find the TElement for the @timeline attrib
+ TElement *target = nullptr;
+ QStringList split
+ = QString(ctrlElem.elementPath).split(QLatin1Char(':'));
+ if (split.size() > 1) {
+ target = CQmlElementHelper::GetElement(
+ *m_Application,
+ m_Application->GetPresentationById(
+ split.at(0).toStdString().c_str()),
+ split.at(1).toStdString().c_str(), nullptr);
+ } else {
+ target = CQmlElementHelper::GetElement(
+ *m_Application,
+ m_Application->GetPrimaryPresentation(),
+ split.at(0).toStdString().c_str(), nullptr);
+ }
+
+ doDef.timelineComponent = static_cast<TComponent *>(target);
+ } else if (ctrlElem.propertyType == ATTRIBUTETYPE_DATAINPUT_SLIDE) {
+ // Slide notifications are already done with separate signal
+ // No need to process
+ } else {
+ // Other than slide or timeline attributes are handled by CPresentation
+ CRegisteredString rString = strTable.RegisterStr(ctrlElem.elementPath);
+ elementPathToDataOutputDefMap.insertMulti(rString, doDef);
+ }
+// #TODO: Remove above once QT3DS-3510 has been implemented in the editor
+ }
+ }
+ }
+ }
+ }
+
+// #TODO: Remove below once QT3DS-3510 has been implemented in the editor
+ presentation.AddToDataOutputMap(elementPathToDataOutputDefMap);
+// #TODO: Remove above once QT3DS-3510 has been implemented in the editor
+}
+
+void CQmlEngineImpl::initializeDataOutputsInPresentation(CPresentation &presentation,
+ bool isPrimary)
+{
+ TElement *parent = presentation.GetRoot();
+ QList<TElement *> elements;
+ listAllElements(parent, elements);
+ qt3ds::runtime::DataOutputMap &doMap = m_Application->dataOutputMap();
+
+ qt3ds::foundation::IStringTable &strTable(presentation.GetStringTable());
+ QHash<CRegisteredString, qt3ds::runtime::DataOutputDef> elementPathToDataOutputDefMap;
+ for (TElement *element : qAsConst(elements)) {
+ Option<QT3DSU32> ctrlIndex = element->FindPropertyIndex(ATTRIBUTE_OBSERVEDPROPERTY);
+ if (ctrlIndex.hasValue()) {
+ Option<qt3ds::runtime::element::TPropertyDescAndValuePtr> propertyInfo =
+ element->GetPropertyByIndex(*ctrlIndex);
+ UVariant *valuePtr = propertyInfo->second;
+ QString valueStr =
+ QString::fromUtf8(strTable.HandleToStr(valuePtr->m_StringHandle));
+
+ if (!valueStr.isEmpty()) {
+ QStringList splitValues = valueStr.split(QLatin1Char(' '));
+
+ // Single value pair expected for DataOutputs
+ QString observerName = splitValues[0];
+ QString observedAttribute = splitValues[1];
+ // Remove DataOutput name prefix "$"
+ observerName.remove(0, 1);
+ qt3ds::runtime::DataOutputDef &doDef = doMap[observerName];
+
+ if (doMap.contains(observerName)) {
+ qt3ds::runtime::DataInOutAttribute obsElem;
+ if (!isPrimary) {
+ // Prepend presentation id to element path
+ obsElem.elementPath = presentation.GetName();
+ obsElem.elementPath.append(QByteArrayLiteral(":"));
+ }
+
+ obsElem.attributeName.append(observedAttribute.toUtf8());
+
+ if (obsElem.attributeName.first() == QByteArrayLiteral("@timeline")) {
+ // Timeline requires special additional handling
+ obsElem.propertyType = ATTRIBUTETYPE_DATAINPUT_TIMELINE;
+ TElement *component = &element->GetComponentParent();
+ obsElem.elementPath.append(component->m_Path);
+
+ // Find the TElement for the @timeline attrib
+ TElement *target = nullptr;
+ QStringList split = QString(obsElem.elementPath).split(QLatin1Char(':'));
+ if (split.size() > 1) {
+ target = CQmlElementHelper::GetElement(
+ *m_Application,
+ m_Application->GetPresentationById(
+ split.at(0).toStdString().c_str()),
+ split.at(1).toStdString().c_str(), nullptr);
+ } else {
+ target = CQmlElementHelper::GetElement(
+ *m_Application,
+ m_Application->GetPrimaryPresentation(),
+ split.at(0).toStdString().c_str(), nullptr);
+ }
+ doDef.timelineComponent = static_cast<TComponent *>(target);
+ } else if (obsElem.attributeName.first() == QByteArrayLiteral("@slide")) {
+ // Slides are ignored if set as we have separate signal in the API for
+ // slide transitions
+ } else {
+ // Every other type is handled by CPresentation
+ obsElem.elementPath.append(element->m_Path);
+ TStringHash attHash = CHash::HashAttribute(
+ obsElem.attributeName.first().constData());
+ Option<qt3ds::runtime::element::TPropertyDescAndValuePtr> attInfo
+ = element->FindProperty(attHash);
+ if (attInfo.hasValue()) {
+ obsElem.propertyType = attInfo->first.m_Type;
+ } else {
+ obsElem.propertyType = ATTRIBUTETYPE_NONE;
+ qWarning() << __FUNCTION__ << "Property"
+ << obsElem.attributeName.first() << "not existing!";
+ }
+
+ doDef.observedAttribute = obsElem;
+ CRegisteredString rString = strTable.RegisterStr(obsElem.elementPath);
+ elementPathToDataOutputDefMap.insertMulti(rString, doDef);
+ }
+ }
+ }
+ }
+ }
+
+ // Inform the presentation of the ready data output defs
+ presentation.AddToDataOutputMap(elementPathToDataOutputDefMap);
+}
+
+// Bit clumsy way of getting from "position" to "position .x .y .z" and enabling datainput
+// support for vectorized types. UIP parser has already thrown away all vector
+// type attributes and at this point we are operating with scalar components only.
+// We check if this element has a property attName.x or attName.r to find out it
+// we should expand property attName to XYZ or RGB vector
+bool CQmlEngineImpl::getAttributeVector3(QVector<QByteArray> &outAttVec,
+ const QByteArray &attName,
+ TElement *elem)
+{
+ auto hashName = Q3DStudio::CHash::HashAttribute(attName + ".x");
+
+ if (!elem->FindProperty(hashName).isEmpty()) {
+ outAttVec.append(attName + ".x");
+ outAttVec.append(attName + ".y");
+ outAttVec.append(attName + ".z");
+ return true;
+ }
+ hashName = Q3DStudio::CHash::HashAttribute(attName + ".r");
+ if (!elem->FindProperty(hashName).isEmpty()) {
+ outAttVec.append(attName + ".r");
+ outAttVec.append(attName + ".g");
+ outAttVec.append(attName + ".b");
+ return true;
+ }
+ return false;
+}
+
+bool CQmlEngineImpl::getAttributeVector2(QVector<QByteArray> &outAttVec,
+ const QByteArray &attName,
+ TElement *elem)
+{
+ auto hashName = Q3DStudio::CHash::HashAttribute(attName + ".x");
+
+ if (!elem->FindProperty(hashName).isEmpty()) {
+ outAttVec.append(attName + ".x");
+ outAttVec.append(attName + ".y");
+ return true;
+ }
+
+ hashName = Q3DStudio::CHash::HashAttribute(attName + ".u");
+ if (!elem->FindProperty(hashName).isEmpty()) {
+ outAttVec.append(attName + ".u");
+ outAttVec.append(attName + ".v");
+ return true;
+ }
+ return false;
+}
+
+QJSValue CQmlEngineImpl::buildJSFunc(const QString &userFunc)
+{
+ auto res = this->m_engine.evaluate(userFunc);
+ if (res.isError()) {
+ qWarning() << __FUNCTION__
+ << "Uncaught exception during datainput evaluation. Evaluator function" << userFunc;
+ }
+ return res;
+}
+
+QVariant CQmlEngineImpl::callJSFunc(const QString &controllerName,
+ qt3ds::runtime::DataInputDef &diDef,
+ const QVariant::Type type)
+{
+ qt3ds::runtime::DataInputMap &diMap = m_Application->dataInputMap();
+ QJSValueList args;
+ QVector<QString> sourceDIs = resolveDependentDatainputs(diDef.evaluator, controllerName);
+
+ // get the most recent set values for datainput sources (arguments) in the expression
+ for (auto diVal : sourceDIs)
+ args << this->m_engine.toScriptValue(diMap[diVal].value);
+
+ if (diDef.evalFunc.isCallable()) {
+ QJSValue res = diDef.evalFunc.call(args);
+ if (res.isError()) {
+ qWarning() << __FUNCTION__ << "Error during datainput" << controllerName
+ << "evaluator call:" << res.toString() << "\nEvaluator function"
+ << diDef.evaluator;
+ return QVariant::Invalid;
+ }
+
+ QVariant ret = res.toVariant();
+ if (ret.isValid() && isMatchingDatatype(ret.type(), type)) {
+ // further check if the result is valid number
+ if (ret.type() == QVariant::Double && qIsNaN(res.toNumber())) {
+ qWarning() << __FUNCTION__ << "Datainput" << controllerName << "evaluator"
+ << "result not a number (NaN)."
+ << "\nEvaluator function" << diDef.evaluator;
+ return QVariant::Invalid;
+ } else {
+ return ret;
+ }
+ } else {
+ qWarning() << __FUNCTION__ << "Datainput" << controllerName << "evaluator"
+ << "result not valid or matching with target attribute type. Result type"
+ << QVariant::typeToName(ret.type()) << " target attribute type "
+ << QVariant::typeToName(type) << "\nEvaluator function" << diDef.evaluator;
+ }
+ } else {
+ qWarning() << __FUNCTION__ << "Datainput" << controllerName << "evaluator"
+ << diDef.evaluator << " not valid callable";
+ }
+ return QVariant::Invalid;
+}
+
+bool CQmlEngineImpl::isMatchingDatatype(QVariant::Type resultType, QVariant::Type propertyType)
+{
+ if (resultType == propertyType)
+ return true;
+ // Allow binding from numeric datainput to string target
+ if ((resultType == QVariant::Double || resultType == QVariant::Int
+ || resultType == QVariant::LongLong)
+ && (propertyType == QVariant::Double || propertyType == QVariant::Int
+ || propertyType == QVariant::LongLong || propertyType == QVariant::String)) {
+ return true;
+ }
+ return false;
+}
+
+QVector<QString> CQmlEngineImpl::resolveDependentDatainputs(const QString &expression,
+ const QString &controllerName)
+{
+ QVector<QString> ret;
+ qt3ds::runtime::DataInputMap &diMap = m_Application->dataInputMap();
+ if (!expression.contains("function", Qt::CaseInsensitive)) {
+ qWarning() << __FUNCTION__ << "Function keyword not found in datainput"
+ << controllerName << "evaluator";
+ return QVector<QString>();
+ }
+
+ int argListStart = expression.indexOf("function(") + 9;
+ int argListStop = expression.indexOf(')', argListStart);
+ QString argstr = expression.mid(argListStart , argListStop - argListStart);
+ QStringList args = argstr.split(',');
+
+ for (auto di : args) {
+ auto diTrim = di.trimmed();
+ if (diMap.contains(diTrim)) {
+ if (diMap[diTrim].type == qt3ds::runtime::DataInOutTypeEvaluator
+ && diTrim != controllerName) {
+ qWarning() << __FUNCTION__ << "Invalid evaluator function in" << controllerName
+ << ". Another evaluator is used as source data.";
+ } else {
+ ret.append(diTrim);
+ }
+ } else {
+ qWarning() << __FUNCTION__ << "Evaluator in" << controllerName << "evaluator"
+ << "is using unknown datainput" << diTrim << " as input argument name";
+ }
+ }
+ return ret;
+}
+
+void CQmlEngineImpl::addStringAttribute(IStringTable &strTable,
+ CQmlEngineImpl::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));
+}
+
+void CQmlEngineImpl::addIntAttribute(IStringTable &strTable,
+ CQmlEngineImpl::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));
+}
+
+void CQmlEngineImpl::addBoolAttribute(IStringTable &strTable,
+ CQmlEngineImpl::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));
+}
+
+void CQmlEngineImpl::addFloatAttribute(IStringTable &strTable,
+ CQmlEngineImpl::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));
+}
+
+void CQmlEngineImpl::addFloat2Attribute(IStringTable &strTable,
+ CQmlEngineImpl::TPropertyDescAndValueList &list,
+ const QStringList &inAttNames, const QVector2D &inValue)
+{
+ for (int i = 0; i < 2; ++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));
+ }
+}
+
+void CQmlEngineImpl::addFloat3Attribute(IStringTable &strTable,
+ CQmlEngineImpl::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));
+ }
+}
+
+void CQmlEngineImpl::addFloat4Attribute(IStringTable &strTable,
+ CQmlEngineImpl::TPropertyDescAndValueList &list,
+ const QStringList &inAttNames, const QVector4D &inValue)
+{
+ for (int i = 0; i < 4; ++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));
+ }
+}
+
+void CQmlEngineImpl::addElementRefAttribute(IStringTable &strTable,
+ CQmlEngineImpl::TPropertyDescAndValueList &list,
+ const QString &inAttName, TElement *element)
+{
+ UVariant theValue;
+ if (element) {
+ theValue.m_ElementHandle = element->GetHandle();
+ QT3DS_ASSERT(theValue.m_ElementHandle);
+ } else {
+ theValue.m_ElementHandle = 0;
+ }
+ const CRegisteredString attStr = strTable.RegisterStr(inAttName);
+ list.push_back(eastl::make_pair(TPropertyDesc(attStr, ATTRIBUTETYPE_ELEMENTREF), theValue));
+}
+
+QVector2D CQmlEngineImpl::parseFloat2Property(const QString &propValue)
+{
+ QVector<QStringRef> values = propValue.splitRef(QLatin1Char(' '));
+ QVector2D retVal;
+ for (int i = 0; i < values.size() && i < 2; ++i)
+ retVal[i] = values[i].toFloat();
+ return retVal;
+}
+
+QVector3D CQmlEngineImpl::parseFloat3Property(const QString &propValue)
+{
+ QVector<QStringRef> values = propValue.splitRef(QLatin1Char(' '));
+ QVector3D retVal;
+ for (int i = 0; i < values.size() && i < 3; ++i)
+ retVal[i] = values[i].toFloat();
+ return retVal;
+}
+
+QVector4D CQmlEngineImpl::parseFloat4Property(const QString &propValue)
+{
+ QVector<QStringRef> values = propValue.splitRef(QLatin1Char(' '));
+ QVector4D retVal;
+ for (int i = 0; i < values.size() && i < 4; ++i)
+ retVal[i] = values[i].toFloat();
+ return retVal;
+}
+
+void CQmlEngineImpl::notifyElementCreation(const QStringList &elementNames, const QString &error)
+{
+ // Notify presentation asynchronously to give renderer time to initialize the elements properly
+ if (!error.isEmpty()) {
+ qWarning() << "Warning: Element creation failed:" << error;
+ QT3DS_ASSERT(false);
+ }
+ QTimer::singleShot(0, [this, elementNames, error]() {
+ m_Application->GetPrimaryPresentation()->signalProxy()
+ ->SigElementsCreated(elementNames, error);
+ });
+}
+
+void CQmlEngineImpl::notifyMaterialCreation(const QStringList &materialNames, const QString &error)
+{
+ // Notify presentation asynchronously to give renderer time to initialize the materials properly
+ if (!error.isEmpty()) {
+ qWarning() << "Warning: Material creation failed:" << materialNames << error;
+ QT3DS_ASSERT(false);
+ }
+ QTimer::singleShot(0, [this, materialNames, error]() {
+ m_Application->GetPrimaryPresentation()->signalProxy()
+ ->SigMaterialsCreated(materialNames, error);
+ });
+}
+
+void CQmlEngineImpl::deleteElements(const QVector<TElement *> &elements,
+ IQt3DSRenderer *renderer)
+{
+ TElement *lastParent = nullptr;
+ IPresentation *presentation = nullptr;
+ TElement *component = nullptr;
+ ISlideSystem *slideSystem = nullptr;
+ QSet<qt3ds::render::SNode *> parentNodes;
+ for (auto element : elements) {
+ TElement *parentElement = element->GetParent();
+ if (parentElement != lastParent) {
+ lastParent = parentElement;
+ presentation = element->GetBelongedPresentation();
+ component = &element->GetComponentParent();
+ slideSystem = &presentation->GetSlideSystem();
+ }
+ // Remove element recursively from slide system
+ slideSystem->removeElement(*component, *element);
+
+ Q_ASSERT(parentElement);
+
+ NVAllocatorCallback &allocator = presentation->GetScene()->allocator();
+
+ // Recursive deleter for translators and graph objects
+ std::function<void(TElement *)> deleteRenderObjects;
+ deleteRenderObjects = [&](TElement *elem) {
+ TElement *child = elem->m_Child;
+ while (child) {
+ TElement *sibling = child->m_Sibling;
+ deleteRenderObjects(child);
+ child = sibling;
+ }
+
+ auto translator = static_cast<qt3ds::render::Qt3DSTranslator *>(elem->GetAssociation());
+ if (translator) {
+ qt3ds::render::GraphObjectTypes::Enum type = translator->GetUIPType();
+ if (type == qt3ds::render::GraphObjectTypes::Model) {
+ auto model = static_cast<qt3ds::render::SModel *>(&translator->RenderObject());
+ // Delete material
+ if (model->m_FirstMaterial) {
+ auto material = static_cast<qt3ds::render::SReferencedMaterial *>(
+ model->m_FirstMaterial);
+ QT3DS_FREE(allocator, material);
+ }
+ }
+ QT3DS_FREE(allocator, &translator->RenderObject());
+ QT3DS_FREE(allocator, translator);
+ }
+ };
+
+ qt3ds::render::SNode *node = nullptr;
+ qt3ds::render::SNode *parentNode = nullptr;
+ auto translator = static_cast<qt3ds::render::Qt3DSTranslator *>(element->GetAssociation());
+
+ if (translator) {
+ if (qt3ds::render::GraphObjectTypes::IsNodeType(translator->GetUIPType())) {
+ node = &static_cast<qt3ds::render::SNode &>(translator->RenderObject());
+ auto parentTranslator = static_cast<qt3ds::render::Qt3DSTranslator *>(
+ parentElement->GetAssociation());
+ if (parentTranslator) {
+ parentNode = &static_cast<qt3ds::render::SNode &>(
+ parentTranslator->RenderObject());
+ parentNode->RemoveChild(*node);
+ parentNodes.insert(parentNode);
+ }
+ }
+ // Release child element graph objects/translators
+ deleteRenderObjects(element);
+ }
+
+ // Remove element recursively
+ m_Application->GetElementAllocator().ReleaseElement(*element, true);
+ }
+ for (auto parentNode : qAsConst(parentNodes))
+ renderer->ChildrenUpdated(*parentNode);
+}
+
+template<typename TDataType>
+void CQmlEngineImpl::setDynamicObjectProperty(qt3ds::render::SDynamicObject &material,
+ const dynamic::SPropertyDefinition &propDesc,
+ const TDataType &propValue)
+{
+ memCopy(material.GetDataSectionBegin() + propDesc.m_Offset, &propValue, sizeof(TDataType));
+}
+
+/**
+* @brief Create QML engine
+*
+* @param[in] inFoundation Pointer to foundation
+* @param[in] inTimeProvider Pointer to time provider
+*
+* @return no return
+*/
+CQmlEngine *CQmlEngine::Create(qt3ds::NVFoundationBase &inFoundation, ITimeProvider &inTimeProvider)
+{
+ return QT3DS_NEW(inFoundation.getAllocator(), CQmlEngineImpl)(inFoundation, inTimeProvider);
+}
+
+}