diff options
Diffstat (limited to 'src/runtime/Qt3DSQmlEngine.cpp')
-rw-r--r-- | src/runtime/Qt3DSQmlEngine.cpp | 2690 |
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 ¤tIndex, int &previousIndex, + QString ¤tName, 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 ¤tProps : 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 ¤tIndex, int &previousIndex, + QString ¤tName, 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 ¶metersManager + = 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); +} + +} |