diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index f24969ce60..bdef2f456b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -600,7 +600,9 @@ public: QString error; scriptable::ModelProviderPointer provider; - if (auto entityInterface = getEntityModelProvider(static_cast(uuid))) { + if (uuid.isNull()) { + provider = nullptr; + } else if (auto entityInterface = getEntityModelProvider(static_cast(uuid))) { provider = entityInterface; } else if (auto overlayInterface = getOverlayModelProvider(static_cast(uuid))) { provider = overlayInterface; diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index 0c8bc5aacb..6ccad338c9 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -13,7 +13,7 @@ #include #include -#include +#include #include "Overlay.h" namespace model { class Mesh; } @@ -37,7 +37,7 @@ public: virtual bool is3D() const override { return true; } virtual uint32_t fetchMetaSubItems(render::ItemIDs& subItems) const override { subItems.push_back(getRenderItemID()); return (uint32_t) subItems.size(); } - virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override { return scriptable::ModelProvider::modelUnavailableError(ok); } + virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) override { return scriptable::ModelProvider::modelUnavailableError(ok); } // TODO: consider implementing registration points in this class glm::vec3 getCenter() const { return getWorldPosition(); } diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index 5a80ca1abf..e007591ce0 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -630,7 +630,7 @@ uint32_t ModelOverlay::fetchMetaSubItems(render::ItemIDs& subItems) const { return 0; } -scriptable::ScriptableModel ModelOverlay::getScriptableModel(bool* ok) { +scriptable::ScriptableModelBase ModelOverlay::getScriptableModel(bool* ok) { if (!_model || !_model->isLoaded()) { return Base3DOverlay::getScriptableModel(ok); } diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index 32d9a08c70..8dc386c733 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -59,7 +59,7 @@ public: void setDrawInFront(bool drawInFront) override; void setDrawHUDLayer(bool drawHUDLayer) override; - virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override; + virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) override; protected: Transform evalRenderTransform() override; diff --git a/interface/src/ui/overlays/Shape3DOverlay.cpp b/interface/src/ui/overlays/Shape3DOverlay.cpp index 8bb3d16888..54423feef6 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.cpp +++ b/interface/src/ui/overlays/Shape3DOverlay.cpp @@ -180,15 +180,15 @@ Transform Shape3DOverlay::evalRenderTransform() { return transform; } -scriptable::ScriptableModel Shape3DOverlay::getScriptableModel(bool* ok) { +scriptable::ScriptableModelBase Shape3DOverlay::getScriptableModel(bool* ok) { auto geometryCache = DependencyManager::get(); auto vertexColor = ColorUtils::toVec3(_color); - scriptable::ScriptableModel result; + scriptable::ScriptableModelBase result; result.metadata = { { "origin", "Shape3DOverlay::"+shapeStrings[_shape] }, { "overlayID", getID() }, }; - result.meshes << geometryCache->meshFromShape(_shape, vertexColor); + result.append(geometryCache->meshFromShape(_shape, vertexColor), {{ "shape", shapeStrings[_shape] }}); if (ok) { *ok = true; } diff --git a/interface/src/ui/overlays/Shape3DOverlay.h b/interface/src/ui/overlays/Shape3DOverlay.h index 34f82af278..f5246d95ac 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.h +++ b/interface/src/ui/overlays/Shape3DOverlay.h @@ -37,7 +37,7 @@ public: void setProperties(const QVariantMap& properties) override; QVariant getProperty(const QString& property) override; - virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override; + virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) override; protected: Transform evalRenderTransform() override; diff --git a/libraries/entities-renderer/CMakeLists.txt b/libraries/entities-renderer/CMakeLists.txt index 27ea04f642..3aa561f927 100644 --- a/libraries/entities-renderer/CMakeLists.txt +++ b/libraries/entities-renderer/CMakeLists.txt @@ -13,7 +13,7 @@ include_hifi_library_headers(fbx) include_hifi_library_headers(entities) include_hifi_library_headers(avatars) include_hifi_library_headers(controllers) -include_hifi_library_headers(graphics-scripting) # for ScriptableModel.h +include_hifi_library_headers(graphics-scripting) # for Forward.h target_bullet() target_polyvox() diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index f07b67fbd0..74759f4fe4 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -17,7 +17,7 @@ #include #include "AbstractViewStateInterface.h" #include "EntitiesRendererLogging.h" -#include +#include class EntityTreeRenderer; @@ -55,7 +55,7 @@ public: const uint64_t& getUpdateTime() const { return _updateTime; } - virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override { return scriptable::ModelProvider::modelUnavailableError(ok); } + virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) override { return scriptable::ModelProvider::modelUnavailableError(ok); } protected: virtual bool needsRenderUpdateFromEntity() const final { return needsRenderUpdateFromEntity(_entity); } virtual void onAddToScene(const EntityItemPointer& entity); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 7b022fefac..3d6714a400 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -950,12 +950,9 @@ QStringList RenderableModelEntityItem::getJointNames() const { return result; } - -scriptable::ScriptableModel render::entities::ModelEntityRenderer::getScriptableModel(bool* ok) { +scriptable::ScriptableModelBase render::entities::ModelEntityRenderer::getScriptableModel(bool* ok) { ModelPointer model; - withReadLock([&] { - model = _model; - }); + withReadLock([&] { model = _model; }); if (!model || !model->isLoaded()) { return scriptable::ModelProvider::modelUnavailableError(ok); @@ -964,6 +961,18 @@ scriptable::ScriptableModel render::entities::ModelEntityRenderer::getScriptable return _model->getScriptableModel(ok); } +bool render::entities::ModelEntityRenderer::replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer newModel, int meshIndex, int partIndex) { + qCDebug(entitiesrenderer) << "REPLACING RenderableModelEntityItem" << newModel->objectName(); + ModelPointer model; + withReadLock([&] { model = _model; }); + + if (!model || !model->isLoaded()) { + return false; + } + + return _model->replaceScriptableModelMeshPart(newModel, meshIndex, partIndex); +} + void RenderableModelEntityItem::simulateRelayedJoints() { ModelPointer model = getModel(); if (model && model->isLoaded()) { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 3e952cb9a7..ffb83d3609 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -140,7 +140,8 @@ class ModelEntityRenderer : public TypedEntityRenderer #include +#include + +#include +#include + +#include #include +#include namespace glm { using hvec2 = glm::tvec2; using hvec4 = glm::tvec4; } //#define DEBUG_BUFFERVIEW_SCRIPTING -#ifdef DEBUG_BUFFERVIEW_SCRIPTING +//#ifdef DEBUG_BUFFERVIEW_SCRIPTING #include "DebugNames.h" - QLoggingCategory bufferview_helpers{"hifi.bufferview"}; -#endif +//#endif namespace { + QLoggingCategory bufferhelper_logging{"hifi.bufferview"}; const std::array XYZW = {{ "x", "y", "z", "w" }}; const std::array ZERO123 = {{ "0", "1", "2", "3" }}; } +gpu::BufferView buffer_helpers::getBufferView(graphics::MeshPointer mesh, gpu::Stream::Slot slot) { + return slot == gpu::Stream::POSITION ? mesh->getVertexBuffer() : mesh->getAttributeBuffer(slot); +} +QMap buffer_helpers::ATTRIBUTES{ + {"position", gpu::Stream::POSITION }, + {"normal", gpu::Stream::NORMAL }, + {"color", gpu::Stream::COLOR }, + {"tangent", gpu::Stream::TEXCOORD0 }, + {"skin_cluster_index", gpu::Stream::SKIN_CLUSTER_INDEX }, + {"skin_cluster_weight", gpu::Stream::SKIN_CLUSTER_WEIGHT }, + {"texcoord0", gpu::Stream::TEXCOORD0 }, + {"texcoord1", gpu::Stream::TEXCOORD1 }, + {"texcoord2", gpu::Stream::TEXCOORD2 }, + {"texcoord3", gpu::Stream::TEXCOORD3 }, + {"texcoord4", gpu::Stream::TEXCOORD4 }, +}; + + template QVariant getBufferViewElement(const gpu::BufferView& view, quint32 index, bool asArray = false) { return glmVecToVariant(view.get(index), asArray); @@ -61,14 +86,14 @@ static void packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, glm::uint3 packedTangent = tangentStruct.pack; } -bool bufferViewElementFromVariant(const gpu::BufferView& view, quint32 index, const QVariant& v) { +bool buffer_helpers::fromVariant(const gpu::BufferView& view, quint32 index, const QVariant& v) { const auto& element = view._element; const auto vecN = element.getScalarCount(); const auto dataType = element.getType(); const auto byteLength = element.getSize(); const auto BYTES_PER_ELEMENT = byteLength / vecN; #ifdef DEBUG_BUFFERVIEW_SCRIPTING - qCDebug(bufferview_helpers) << "bufferViewElementFromVariant" << index << DebugNames::stringFrom(dataType) << BYTES_PER_ELEMENT << vecN; + qCDebug(bufferhelper_logging) << "bufferViewElementFromVariant" << index << DebugNames::stringFrom(dataType) << BYTES_PER_ELEMENT << vecN; #endif if (BYTES_PER_ELEMENT == 1) { switch(vecN) { @@ -122,16 +147,34 @@ bool bufferViewElementFromVariant(const gpu::BufferView& view, quint32 index, co return false; } -QVariant bufferViewElementToVariant(const gpu::BufferView& view, quint32 index, bool asArray, const char* hint) { +bool boundsCheck(const gpu::BufferView& view, quint32 index) { + const auto byteLength = view._element.getSize(); + return ( + index < view.getNumElements() && + index * byteLength < (view._size - 1) * byteLength + ); +} + +QVariant buffer_helpers::toVariant(const gpu::BufferView& view, quint32 index, bool asArray, const char* hint) { const auto& element = view._element; const auto vecN = element.getScalarCount(); const auto dataType = element.getType(); const auto byteLength = element.getSize(); const auto BYTES_PER_ELEMENT = byteLength / vecN; Q_ASSERT(index < view.getNumElements()); - Q_ASSERT(index * vecN * BYTES_PER_ELEMENT < (view._size - vecN * BYTES_PER_ELEMENT)); + if (!boundsCheck(view, index)) { + // sanity checks + auto byteOffset = index * vecN * BYTES_PER_ELEMENT; + auto maxByteOffset = (view._size - 1) * vecN * BYTES_PER_ELEMENT; + if (byteOffset > maxByteOffset) { + qDebug() << "bufferViewElementToVariant -- byteOffset out of range " << byteOffset << " < " << maxByteOffset << DebugNames::stringFrom(dataType); + qDebug() << "bufferViewElementToVariant -- index: " << index << "numElements" << view.getNumElements(); + qDebug() << "bufferViewElementToVariant -- vecN: " << vecN << "byteLength" << byteLength << "BYTES_PER_ELEMENT" << BYTES_PER_ELEMENT; + } + Q_ASSERT(byteOffset <= maxByteOffset); + } #ifdef DEBUG_BUFFERVIEW_SCRIPTING - qCDebug(bufferview_helpers) << "bufferViewElementToVariant" << index << DebugNames::stringFrom(dataType) << BYTES_PER_ELEMENT << vecN; + qCDebug(bufferhelper_logging) << "bufferViewElementToVariant" << index << DebugNames::stringFrom(dataType) << BYTES_PER_ELEMENT << vecN; #endif if (BYTES_PER_ELEMENT == 1) { switch(vecN) { @@ -221,22 +264,137 @@ const T glmVecFromVariant(const QVariant& v) { } template -gpu::BufferView bufferViewFromVector(QVector elements, gpu::Element elementType) { +gpu::BufferView buffer_helpers::fromVector(const QVector& elements, const gpu::Element& elementType) { auto vertexBuffer = std::make_shared(elements.size() * sizeof(T), (gpu::Byte*)elements.data()); return { vertexBuffer, 0, vertexBuffer->getSize(),sizeof(T), elementType }; } +template<> gpu::BufferView buffer_helpers::fromVector(const QVector& elements, const gpu::Element& elementType) { return fromVector(elements, elementType); } +template<> gpu::BufferView buffer_helpers::fromVector(const QVector& elements, const gpu::Element& elementType) { return fromVector(elements, elementType); } -template<> gpu::BufferView bufferViewFromVector(QVector elements, gpu::Element elementType) { return bufferViewFromVector(elements, elementType); } -template<> gpu::BufferView bufferViewFromVector(QVector elements, gpu::Element elementType) { return bufferViewFromVector(elements, elementType); } +template struct getVec4;// { static T get(const gpu::BufferView& view, quint32 index, const char *hint); }; +template struct getScalar;// { static T get(const gpu::BufferView& view, quint32 index, const char *hint); }; -gpu::BufferView cloneBufferView(const gpu::BufferView& input) { +struct gotter { + static float error(const QString& name, const gpu::BufferView& view, quint32 index, const char *hint) { + qDebug() << QString("gotter:: unhandled type=%1(element=%2(%3)) size=%4(per=%5) vec%6 hint=%7 #%8") + .arg(name) + .arg(DebugNames::stringFrom(view._element.getType())) + .arg(view._element.getType()) + .arg(view._element.getSize()) + .arg(view._element.getSize() / view._element.getScalarCount()) + .arg(view._element.getScalarCount()) + .arg(hint) + .arg(view.getNumElements()); + Q_ASSERT(false); + assert(false); + return NAN; + } +}; +template struct getScalar : gotter { + static T get(const gpu::BufferView& view, quint32 index, const char *hint) { switch(view._element.getType()) { + case gpu::UINT32: return view.get(index); + case gpu::UINT16: return view.get(index); + case gpu::UINT8: return view.get(index); + case gpu::INT32: return view.get(index); + case gpu::INT16: return view.get(index); + case gpu::INT8: return view.get(index); + case gpu::FLOAT: return view.get(index); + case gpu::HALF: return T(glm::unpackSnorm1x8(view.get(index))); + default: break; + } return T(error("getScalar", view, index, hint)); + } +}; + +template struct getVec2 : gotter { static T get(const gpu::BufferView& view, quint32 index, const char *hint) { switch(view._element.getType()) { + case gpu::UINT32: return view.get(index); + case gpu::UINT16: return view.get(index); + case gpu::UINT8: return view.get(index); + case gpu::INT32: return view.get(index); + case gpu::INT16: return view.get(index); + case gpu::INT8: return view.get(index); + case gpu::FLOAT: return view.get(index); + case gpu::HALF: return glm::unpackSnorm2x8(view.get(index)); + default: break; + } return T(error("getVec2", view, index, hint)); }}; + + +template struct getVec3 : gotter { static T get(const gpu::BufferView& view, quint32 index, const char *hint) { switch(view._element.getType()) { + case gpu::UINT32: return view.get(index); + case gpu::UINT16: return view.get(index); + case gpu::UINT8: return view.get(index); + case gpu::INT32: return view.get(index); + case gpu::INT16: return view.get(index); + case gpu::INT8: return view.get(index); + case gpu::FLOAT: return view.get(index); + case gpu::HALF: + case gpu::NUINT8: + case gpu::NINT2_10_10_10: + if (view._element.getSize() == sizeof(glm::int32)) { + return getVec4::get(view, index, hint); + } + default: break; + } return T(error("getVec3", view, index, hint)); }}; + +template struct getVec4 : gotter { static T get(const gpu::BufferView& view, quint32 index, const char *hint) { + assert(view._element.getSize() == sizeof(glm::int32)); + switch(view._element.getType()) { + case gpu::UINT32: return view.get(index); + case gpu::UINT16: return view.get(index); + case gpu::UINT8: return view.get(index); + case gpu::INT32: return view.get(index); + case gpu::INT16: return view.get(index); + case gpu::INT8: return view.get(index); + case gpu::NUINT32: break; + case gpu::NUINT16: break; + case gpu::NUINT8: return glm::unpackUnorm4x8(view.get(index)); + case gpu::NUINT2: break; + case gpu::NINT32: break; + case gpu::NINT16: break; + case gpu::NINT8: break; + case gpu::COMPRESSED: break; + case gpu::NUM_TYPES: break; + case gpu::FLOAT: return view.get(index); + case gpu::HALF: return glm::unpackSnorm4x8(view.get(index)); + case gpu::NINT2_10_10_10: return glm::unpackSnorm3x10_1x2(view.get(index)); + } return T(error("getVec4", view, index, hint)); }}; + + +template +struct getVec { + static QVector __to_vector__(const gpu::BufferView& view, const char *hint) { + QVector result; + const quint32 count = (quint32)view.getNumElements(); + result.resize(count); + for (quint32 i = 0; i < count; i++) { + result[i] = FUNC::get(view, i, hint); + } + return result; + } + static T __to_scalar__(const gpu::BufferView& view, quint32 index, const char *hint) { + assert(boundsCheck(view, index)); + return FUNC::get(view, index, hint); + } +}; + +template <> QVector buffer_helpers::toVector(const gpu::BufferView& view, const char *hint) { return getVec,int>::__to_vector__(view, hint); } +template <> QVector buffer_helpers::toVector(const gpu::BufferView& view, const char *hint) { return getVec,glm::vec2>::__to_vector__(view, hint); } +template <> QVector buffer_helpers::toVector(const gpu::BufferView& view, const char *hint) { return getVec,glm::vec3>::__to_vector__(view, hint); } +template <> QVector buffer_helpers::toVector(const gpu::BufferView& view, const char *hint) { return getVec,glm::vec4>::__to_vector__(view, hint); } + + +template <> int buffer_helpers::convert(const gpu::BufferView& view, quint32 index, const char *hint) { return getVec,int>::__to_scalar__(view, index, hint); } +template <> glm::vec2 buffer_helpers::convert(const gpu::BufferView& view, quint32 index, const char *hint) { return getVec,glm::vec2>::__to_scalar__(view, index, hint); } +template <> glm::vec3 buffer_helpers::convert(const gpu::BufferView& view, quint32 index, const char *hint) { return getVec,glm::vec3>::__to_scalar__(view, index, hint); } +template <> glm::vec4 buffer_helpers::convert(const gpu::BufferView& view, quint32 index, const char *hint) { return getVec,glm::vec4>::__to_scalar__(view, index, hint); } + +gpu::BufferView buffer_helpers::clone(const gpu::BufferView& input) { return gpu::BufferView( std::make_shared(input._buffer->getSize(), input._buffer->getData()), input._offset, input._size, input._stride, input._element ); } -gpu::BufferView resizedBufferView(const gpu::BufferView& input, quint32 numElements) { +gpu::BufferView buffer_helpers::resize(const gpu::BufferView& input, quint32 numElements) { auto effectiveSize = input._buffer->getSize() / input.getNumElements(); qDebug() << "resize input" << input.getNumElements() << input._buffer->getSize() << "effectiveSize" << effectiveSize; auto vsize = input._element.getSize() * numElements; @@ -248,3 +406,235 @@ gpu::BufferView resizedBufferView(const gpu::BufferView& input, quint32 numEleme qDebug() << "resized output" << output.getNumElements() << output._buffer->getSize(); return output; } + +graphics::MeshPointer buffer_helpers::cloneMesh(graphics::MeshPointer mesh) { + auto clone = std::make_shared(); + //[](graphics::Mesh* blah) { + //qCDebug(bufferhelper_logging) << "--- DELETING MESH POINTER" << blah; + // delete blah; + //}); + clone->displayName = (QString::fromStdString(mesh->displayName) + "-clone").toStdString(); + //qCInfo(bufferhelper_logging) << "+++ ALLOCATED MESH POINTER ScriptableMesh::cloneMesh" << clone->displayName << clone.get() << !!mesh; + clone->setIndexBuffer(buffer_helpers::clone(mesh->getIndexBuffer())); + clone->setPartBuffer(buffer_helpers::clone(mesh->getPartBuffer())); + auto attributeViews = buffer_helpers::gatherBufferViews(mesh); + for (const auto& a : attributeViews) { + auto& view = a.second; + auto slot = buffer_helpers::ATTRIBUTES[a.first]; + auto points = buffer_helpers::clone(view); + if (slot == gpu::Stream::POSITION) { + clone->setVertexBuffer(points); + } else { + clone->addAttribute(slot, points); + } + } + return clone; +} + + +/// --- buffer view <-> variant helpers + +namespace { + // expand the corresponding attribute buffer (creating it if needed) so that it matches POSITIONS size and specified element type + gpu::BufferView _expandedAttributeBuffer(const graphics::MeshPointer mesh, gpu::Stream::Slot slot) { + gpu::BufferView bufferView = buffer_helpers::getBufferView(mesh, slot); + const auto& elementType = bufferView._element; + //auto vecN = element.getScalarCount(); + //auto type = element.getType(); + //gpu::Element elementType = getVecNElement(type, vecN); + + gpu::Size elementSize = elementType.getSize(); + auto nPositions = mesh->getNumVertices(); + auto vsize = nPositions * elementSize; + auto diffTypes = (elementType.getType() != bufferView._element.getType() || + elementType.getSize() > bufferView._element.getSize() || + elementType.getScalarCount() > bufferView._element.getScalarCount() || + vsize > bufferView._size + ); + QString hint = QString("%1").arg(slot); +#ifdef DEBUG_BUFFERVIEW_SCRIPTING + hint = DebugNames::stringFrom(slot); +#endif +#ifdef DEV_BUILD + auto beforeCount = bufferView.getNumElements(); + auto beforeTotal = bufferView._size; +#endif + if (bufferView.getNumElements() < nPositions || diffTypes) { + if (!bufferView._buffer || bufferView.getNumElements() == 0) { + qCInfo(bufferhelper_logging).nospace() << "ScriptableMesh -- adding missing mesh attribute '" << hint << "' for BufferView"; + gpu::Byte *data = new gpu::Byte[vsize]; + memset(data, 0, vsize); + auto buffer = new gpu::Buffer(vsize, (gpu::Byte*)data); + delete[] data; + bufferView = gpu::BufferView(buffer, elementType); + mesh->addAttribute(slot, bufferView); + } else { + qCInfo(bufferhelper_logging) << "ScriptableMesh -- resizing Buffer current:" << hint << bufferView._buffer->getSize() << "wanted:" << vsize; + bufferView._element = elementType; + bufferView._buffer->resize(vsize); + bufferView._size = bufferView._buffer->getSize(); + } + } +#ifdef DEV_BUILD + auto afterCount = bufferView.getNumElements(); + auto afterTotal = bufferView._size; + if (beforeTotal != afterTotal || beforeCount != afterCount) { + QString typeName = QString("%1").arg(bufferView._element.getType()); +#ifdef DEBUG_BUFFERVIEW_SCRIPTING + typeName = DebugNames::stringFrom(bufferView._element.getType()); +#endif + qCDebug(bufferhelper_logging, "NOTE:: _expandedAttributeBuffer.%s vec%d %s (before count=%lu bytes=%lu // after count=%lu bytes=%lu)", + hint.toStdString().c_str(), bufferView._element.getScalarCount(), + typeName.toStdString().c_str(), beforeCount, beforeTotal, afterCount, afterTotal); + } +#endif + return bufferView; + } + + gpu::BufferView expandAttributeToMatchPositions(graphics::MeshPointer mesh, gpu::Stream::Slot slot) { + if (slot == gpu::Stream::POSITION) { + return buffer_helpers::getBufferView(mesh, slot); + } + return _expandedAttributeBuffer(mesh, slot); + } +} + +std::map buffer_helpers::gatherBufferViews(graphics::MeshPointer mesh, const QStringList& expandToMatchPositions) { + std::map attributeViews; + if (!mesh) { + return attributeViews; + } + for (const auto& a : buffer_helpers::ATTRIBUTES.toStdMap()) { + auto name = a.first; + auto slot = a.second; + auto view = getBufferView(mesh, slot); + auto beforeCount = view.getNumElements(); + auto beforeTotal = view._size; + if (expandToMatchPositions.contains(name)) { + expandAttributeToMatchPositions(mesh, slot); + } + if (beforeCount > 0) { + auto element = view._element; + auto vecN = element.getScalarCount(); + //auto type = element.getType(); + QString typeName = QString("%1").arg(element.getType()); +#ifdef DEBUG_BUFFERVIEW_SCRIPTING + typeName = DebugNames::stringFrom(element.getType()); +#endif + + attributeViews[name] = getBufferView(mesh, slot); + +#if DEV_BUILD + auto afterTotal = attributeViews[name]._size; + auto afterCount = attributeViews[name].getNumElements(); + if (beforeTotal != afterTotal || beforeCount != afterCount) { + qCDebug(bufferhelper_logging, "NOTE:: gatherBufferViews.%s vec%d %s (before count=%lu bytes=%lu // after count=%lu bytes=%lu)", + name.toStdString().c_str(), vecN, typeName.toStdString().c_str(), beforeCount, beforeTotal, afterCount, afterTotal); + } +#endif + } + } + return attributeViews; +} + + +bool buffer_helpers::recalculateNormals(graphics::MeshPointer mesh) { + qCInfo(bufferhelper_logging) << "Recalculating normals" << !!mesh; + if (!mesh) { + return false; + } + buffer_helpers::gatherBufferViews(mesh, { "normal", "color" }); // ensures #normals >= #positions + auto normals = mesh->getAttributeBuffer(gpu::Stream::NORMAL); + auto verts = mesh->getVertexBuffer(); + auto indices = mesh->getIndexBuffer(); + auto esize = indices._element.getSize(); + auto numPoints = indices.getNumElements(); + const auto TRIANGLE = 3; + quint32 numFaces = (quint32)numPoints / TRIANGLE; + //QVector faces; + QVector faceNormals; + QMap> vertexToFaces; + //faces.resize(numFaces); + faceNormals.resize(numFaces); + auto numNormals = normals.getNumElements(); + qCInfo(bufferhelper_logging) << QString("numFaces: %1, numNormals: %2, numPoints: %3").arg(numFaces).arg(numNormals).arg(numPoints); + if (normals.getNumElements() != verts.getNumElements()) { + return false; + } + for (quint32 i = 0; i < numFaces; i++) { + quint32 I = TRIANGLE * i; + quint32 i0 = esize == 4 ? indices.get(I+0) : indices.get(I+0); + quint32 i1 = esize == 4 ? indices.get(I+1) : indices.get(I+1); + quint32 i2 = esize == 4 ? indices.get(I+2) : indices.get(I+2); + + Triangle face = { + verts.get(i1), + verts.get(i2), + verts.get(i0) + }; + faceNormals[i] = face.getNormal(); + if (glm::isnan(faceNormals[i].x)) { + qCInfo(bufferhelper_logging) << i << i0 << i1 << i2 << glmVecToVariant(face.v0) << glmVecToVariant(face.v1) << glmVecToVariant(face.v2); + break; + } + vertexToFaces[glm::to_string(face.v0).c_str()] << i; + vertexToFaces[glm::to_string(face.v1).c_str()] << i; + vertexToFaces[glm::to_string(face.v2).c_str()] << i; + } + for (quint32 j = 0; j < numNormals; j++) { + //auto v = verts.get(j); + glm::vec3 normal { 0.0f, 0.0f, 0.0f }; + QString key { glm::to_string(verts.get(j)).c_str() }; + const auto& faces = vertexToFaces.value(key); + if (faces.size()) { + for (const auto i : faces) { + normal += faceNormals[i]; + } + normal *= 1.0f / (float)faces.size(); + } else { + static int logged = 0; + if (logged++ < 10) { + qCInfo(bufferhelper_logging) << "no faces for key!?" << key; + } + normal = verts.get(j); + } + if (glm::isnan(normal.x)) { + static int logged = 0; + if (logged++ < 10) { + qCInfo(bufferhelper_logging) << "isnan(normal.x)" << j << glmVecToVariant(normal); + } + break; + } + normals.edit(j) = glm::normalize(normal); + } + return true; +} + +QVariant buffer_helpers::toVariant(const glm::mat4& mat4) { + QVector floats; + floats.resize(16); + memcpy(floats.data(), &mat4, sizeof(glm::mat4)); + QVariant v; + v.setValue>(floats); + return v; +}; + +QVariant buffer_helpers::toVariant(const Extents& box) { + return QVariantMap{ + { "center", glmVecToVariant(box.minimum + (box.size() / 2.0f)) }, + { "minimum", glmVecToVariant(box.minimum) }, + { "maximum", glmVecToVariant(box.maximum) }, + { "dimensions", glmVecToVariant(box.size()) }, + }; +} + +QVariant buffer_helpers::toVariant(const AABox& box) { + return QVariantMap{ + { "brn", glmVecToVariant(box.getCorner()) }, + { "tfl", glmVecToVariant(box.calcTopFarLeft()) }, + { "center", glmVecToVariant(box.calcCenter()) }, + { "minimum", glmVecToVariant(box.getMinimumPoint()) }, + { "maximum", glmVecToVariant(box.getMaximumPoint()) }, + { "dimensions", glmVecToVariant(box.getDimensions()) }, + }; +} diff --git a/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h b/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h index d0d42ca419..d963fd4b22 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h +++ b/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h @@ -7,6 +7,8 @@ #pragma once #include +#include +#include namespace gpu { class BufferView; @@ -15,11 +17,34 @@ namespace gpu { template QVariant glmVecToVariant(const T& v, bool asArray = false); template const T glmVecFromVariant(const QVariant& v); -QVariant bufferViewElementToVariant(const gpu::BufferView& view, quint32 index, bool asArray = false, const char* hint = ""); -bool bufferViewElementFromVariant(const gpu::BufferView& view, quint32 index, const QVariant& v); -template gpu::BufferView bufferViewFromVector(QVector elements, gpu::Element elementType); +namespace graphics { + class Mesh; + using MeshPointer = std::shared_ptr; +} -gpu::BufferView cloneBufferView(const gpu::BufferView& input); -gpu::BufferView resizedBufferView(const gpu::BufferView& input, quint32 numElements); +class Extents; +class AABox; +struct buffer_helpers { + static graphics::MeshPointer cloneMesh(graphics::MeshPointer mesh); + static QMap ATTRIBUTES; + static std::map gatherBufferViews(graphics::MeshPointer mesh, const QStringList& expandToMatchPositions = QStringList()); + static bool recalculateNormals(graphics::MeshPointer meshProxy); + static gpu::BufferView getBufferView(graphics::MeshPointer mesh, quint8 slot); + + static QVariant toVariant(const Extents& box); + static QVariant toVariant(const AABox& box); + static QVariant toVariant(const glm::mat4& mat4); + static QVariant toVariant(const gpu::BufferView& view, quint32 index, bool asArray = false, const char* hint = ""); + + static bool fromVariant(const gpu::BufferView& view, quint32 index, const QVariant& v); + + template static gpu::BufferView fromVector(const QVector& elements, const gpu::Element& elementType); + + template static QVector toVector(const gpu::BufferView& view, const char *hint = ""); + template static T convert(const gpu::BufferView& view, quint32 index, const char* hint = ""); + + static gpu::BufferView clone(const gpu::BufferView& input); + static gpu::BufferView resize(const gpu::BufferView& input, quint32 numElements); +}; diff --git a/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp b/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp index 367c0589e9..ab6f2c92be 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp @@ -26,7 +26,7 @@ QScriptValue getBufferViewElement(QScriptEngine* js, const gpu::BufferView& view } QScriptValue bufferViewElementToScriptValue(QScriptEngine* engine, const gpu::BufferView& view, quint32 index, bool asArray, const char* hint) { - QVariant result = bufferViewElementToVariant(view, index, asArray, hint); + QVariant result = buffer_helpers::toVariant(view, index, asArray, hint); if (!result.isValid()) { return QScriptValue::NullValue; } @@ -39,7 +39,7 @@ void setBufferViewElement(const gpu::BufferView& view, quint32 index, const QScr } bool bufferViewElementFromScriptValue(const QScriptValue& v, const gpu::BufferView& view, quint32 index) { - return bufferViewElementFromVariant(view, index, v.toVariant()); + return buffer_helpers::fromVariant(view, index, v.toVariant()); } // diff --git a/libraries/graphics-scripting/src/graphics-scripting/Forward.h b/libraries/graphics-scripting/src/graphics-scripting/Forward.h new file mode 100644 index 0000000000..15973b5852 --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/Forward.h @@ -0,0 +1,107 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +namespace graphics { + class Mesh; +} +namespace gpu { + class BufferView; +} +class QScriptEngine; + +namespace scriptable { + using Mesh = graphics::Mesh; + using MeshPointer = std::shared_ptr; + using WeakMeshPointer = std::weak_ptr; + + class ScriptableModelBase; + using ScriptableModelBasePointer = QPointer; + + class ModelProvider; + using ModelProviderPointer = std::shared_ptr; + using WeakModelProviderPointer = std::weak_ptr; + + class ScriptableMeshBase : public QObject { + Q_OBJECT + public: + WeakModelProviderPointer provider; + ScriptableModelBasePointer model; + WeakMeshPointer mesh; + MeshPointer ownedMesh; + QVariantMap metadata; + ScriptableMeshBase(WeakModelProviderPointer provider, ScriptableModelBasePointer model, WeakMeshPointer mesh, const QVariantMap& metadata); + ScriptableMeshBase(WeakMeshPointer mesh = WeakMeshPointer()); + ScriptableMeshBase(MeshPointer mesh, const QVariantMap& metadata); + ScriptableMeshBase(const ScriptableMeshBase& other) { *this = other; } + ScriptableMeshBase& operator=(const ScriptableMeshBase& view); + virtual ~ScriptableMeshBase(); + Q_INVOKABLE const scriptable::MeshPointer getMeshPointer() const { return mesh.lock(); } + Q_INVOKABLE const scriptable::ModelProviderPointer getModelProviderPointer() const { return provider.lock(); } + Q_INVOKABLE const scriptable::ScriptableModelBasePointer getModelBasePointer() const { return model; } + }; + + // abstract container for holding one or more references to mesh pointers + class ScriptableModelBase : public QObject { + Q_OBJECT + public: + WeakModelProviderPointer provider; + QUuid objectID; // spatially nestable ID + QVariantMap metadata; + QVector meshes; + + ScriptableModelBase(QObject* parent = nullptr) : QObject(parent) {} + ScriptableModelBase(const ScriptableModelBase& other) { *this = other; } + ScriptableModelBase& operator=(const ScriptableModelBase& other) { + provider = other.provider; + objectID = other.objectID; + metadata = other.metadata; + for (auto& mesh : other.meshes) { + append(mesh); + } + return *this; + } + virtual ~ScriptableModelBase(); + + void mixin(const QVariantMap& other); + void append(const ScriptableModelBase& other, const QVariantMap& modelMetadata = QVariantMap()); + void append(scriptable::WeakMeshPointer mesh, const QVariantMap& metadata = QVariantMap()); + void append(const ScriptableMeshBase& mesh, const QVariantMap& metadata = QVariantMap()); + // TODO: in future containers for these could go here + // QVariantMap shapes; + // QVariantMap materials; + // QVariantMap armature; + }; + + // mixin class for Avatar/Entity/Overlay Rendering that expose their in-memory graphics::Meshes + class ModelProvider { + public: + QVariantMap metadata{ { "providerType", "unknown" } }; + static scriptable::ScriptableModelBase modelUnavailableError(bool* ok) { if (ok) { *ok = false; } return {}; } + virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) = 0; + + virtual bool replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer model, int meshIndex, int partIndex) { return false; } + }; + + // mixin class for resolving UUIDs into a corresponding ModelProvider + class ModelProviderFactory : public Dependency { + public: + virtual scriptable::ModelProviderPointer lookupModelProvider(QUuid uuid) = 0; + }; + + using uint32 = quint32; + class ScriptableModel; + using ScriptableModelPointer = QPointer; + class ScriptableMesh; + using ScriptableMeshPointer = QPointer; + class ScriptableMeshPart; + using ScriptableMeshPartPointer = QPointer; + bool registerMetaTypes(QScriptEngine* engine); +} diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.cpp b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.cpp new file mode 100644 index 0000000000..aabf83ff66 --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.cpp @@ -0,0 +1,3 @@ +#include "GraphicsScriptingUtil.h" + +Q_LOGGING_CATEGORY(graphics_scripting, "hifi.scripting.graphics") diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h new file mode 100644 index 0000000000..a536fc413c --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h @@ -0,0 +1,85 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +Q_DECLARE_LOGGING_CATEGORY(graphics_scripting) + +namespace scriptable { + // derive current context's C++ QObject (based on current JS "this" value) + template T this_qobject_cast(QScriptEngine* engine) { + auto context = engine ? engine->currentContext() : nullptr; + return qscriptvalue_cast(context ? context->thisObject() : QScriptValue::NullValue); + } + // JS => QPointer + template QPointer qpointer_qobject_cast(const QScriptValue& value) { + auto obj = value.toQObject(); + qCInfo(graphics_scripting) << "qpointer_qobject_cast" << obj << value.toString(); + if (auto tmp = qobject_cast(obj)) { + return QPointer(tmp); + } + if (auto tmp = static_cast(obj)) { + return QPointer(tmp); + } + return nullptr; + } + inline QString toDebugString(QObject* tmp) { + return QString("%0 (0x%1%2)") + .arg(tmp ? tmp->metaObject()->className() : "QObject") + .arg(qulonglong(tmp), 16, 16, QChar('0')) + .arg(tmp && tmp->objectName().size() ? " name=" + tmp->objectName() : ""); + } + template QString toDebugString(std::shared_ptr tmp) { + return toDebugString(qobject_cast(tmp.get())); + } + + // C++ > QtOwned instance + template std::shared_ptr make_qtowned(Rest... rest) { + T* tmp = new T(rest...); + qCInfo(graphics_scripting) << "scriptable::make_qtowned" << toDebugString(tmp); + QString debug = toDebugString(tmp); + if (tmp) { + tmp->metadata["__ownership__"] = QScriptEngine::QtOwnership; + QObject::connect(tmp, &QObject::destroyed, [=]() { qCInfo(graphics_scripting) << "-------- ~scriptable::make_qtowned" << debug; }); + auto ptr = std::shared_ptr(tmp, [debug](T* tmp) { + //qDebug() << "~std::shared_ptr" << debug; + delete tmp; + }); + return ptr; + } else { + return std::shared_ptr(tmp); + } + } + // C++ > ScriptOwned JS instance + template QPointer make_scriptowned(Rest... rest) { + T* tmp = new T(rest...); + qCInfo(graphics_scripting) << "scriptable::make_scriptowned" << toDebugString(tmp); + if (tmp) { + tmp->metadata["__ownership__"] = QScriptEngine::ScriptOwnership; + //auto blah = (DeleterFunction)[](void* delme) { }; + return add_scriptowned_destructor(tmp); + } else { + return QPointer(tmp); + } + } + // C++ > ScriptOwned JS instance + template QPointer add_scriptowned_destructor(T* tmp) { + QString debug = toDebugString(tmp); + if (tmp) { + QObject::connect(tmp, &QObject::destroyed, [=]() { + qCInfo(graphics_scripting) << "-------- ~scriptable::make_scriptowned" << debug;// << !!customDeleter; + //if (customDeleter) { + // customDeleter(tmp); + //} + }); + } else { + qCInfo(graphics_scripting) << "add_scriptowned_destructor -- not connecting to null value" << debug; + } + return QPointer(tmp); + } +} diff --git a/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp index ab85fb8265..ab9403a8ed 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp @@ -26,12 +26,11 @@ #include "BufferViewScripting.h" #include "ScriptableMesh.h" +#include "GraphicsScriptingUtil.h" #include "ModelScriptingInterface.moc" -namespace { - QLoggingCategory model_scripting { "hifi.model.scripting" }; -} +#include "RegisteredMetaTypes.h" ModelScriptingInterface::ModelScriptingInterface(QObject* parent) : QObject(parent) { if (auto scriptEngine = qobject_cast(parent)) { @@ -39,8 +38,51 @@ ModelScriptingInterface::ModelScriptingInterface(QObject* parent) : QObject(pare } } -void ModelScriptingInterface::getMeshes(QUuid uuid, QScriptValue scopeOrCallback, QScriptValue methodOrName) { - auto handler = makeScopedHandlerObject(scopeOrCallback, methodOrName); +bool ModelScriptingInterface::updateMeshes(QUuid uuid, const scriptable::ScriptableMeshPointer mesh, int meshIndex, int partIndex) { + auto model = scriptable::make_qtowned(); + if (mesh) { + model->append(*mesh); + } + return updateMeshes(uuid, model.get()); +} + +bool ModelScriptingInterface::updateMeshes(QUuid uuid, const scriptable::ScriptableModelPointer model) { + auto appProvider = DependencyManager::get(); + qCDebug(graphics_scripting) << "appProvider" << appProvider.data(); + scriptable::ModelProviderPointer provider = appProvider ? appProvider->lookupModelProvider(uuid) : nullptr; + QString providerType = provider ? provider->metadata.value("providerType").toString() : QString(); + if (providerType.isEmpty()) { + providerType = "unknown"; + } + bool success = false; + if (provider) { + qCDebug(graphics_scripting) << "fetching meshes from " << providerType << "..."; + auto scriptableMeshes = provider->getScriptableModel(&success); + qCDebug(graphics_scripting) << "//fetched meshes from " << providerType << "success:" <operator scriptable::ScriptableModelBasePointer(); + qCDebug(graphics_scripting) << "as base" << base; + if (base) { + //auto meshes = model->getConstMeshes(); + success = provider->replaceScriptableModelMeshPart(base, -1, -1); + + // for (uint32_t m = 0; success && m < meshes.size(); m++) { + // const auto& mesh = meshes.at(m); + // for (int p = 0; success && p < mesh->getNumParts(); p++) { + // qCDebug(graphics_scripting) << "provider->replaceScriptableModelMeshPart" << "meshIndex" << m << "partIndex" << p; + // success = provider->replaceScriptableModelMeshPart(base, m, p); + // //if (!success) { + // qCDebug(graphics_scripting) << "//provider->replaceScriptableModelMeshPart" << "meshIndex" << m << "partIndex" << p << success; + // } + // } + } + } + } + return success; +} + +void ModelScriptingInterface::getMeshes(QUuid uuid, QScriptValue callback) { + auto handler = scriptable::jsBindCallback(callback); Q_ASSERT(handler.engine() == this->engine()); QPointer engine = dynamic_cast(handler.engine()); @@ -49,18 +91,23 @@ void ModelScriptingInterface::getMeshes(QUuid uuid, QScriptValue scopeOrCallback QString error; auto appProvider = DependencyManager::get(); - qDebug() << "appProvider" << appProvider.data(); + qCDebug(graphics_scripting) << "appProvider" << appProvider.data(); scriptable::ModelProviderPointer provider = appProvider ? appProvider->lookupModelProvider(uuid) : nullptr; QString providerType = provider ? provider->metadata.value("providerType").toString() : QString(); if (providerType.isEmpty()) { providerType = "unknown"; } if (provider) { - qCDebug(model_scripting) << "fetching meshes from " << providerType << "..."; + qCDebug(graphics_scripting) << "fetching meshes from " << providerType << "..."; auto scriptableMeshes = provider->getScriptableModel(&success); - qCDebug(model_scripting) << "//fetched meshes from " << providerType << "success:" <(scriptableMeshes); + QString debugString = scriptable::toDebugString(meshes); + QObject::connect(meshes, &QObject::destroyed, this, [=]() { + qCDebug(graphics_scripting) << "///fetched meshes" << debugString; + }); + if (meshes->objectName().isEmpty()) { meshes->setObjectName(providerType+"::meshes"); } @@ -75,20 +122,20 @@ void ModelScriptingInterface::getMeshes(QUuid uuid, QScriptValue scopeOrCallback } if (!error.isEmpty()) { - qCWarning(model_scripting) << "ModelScriptingInterface::getMeshes ERROR" << error; + qCWarning(graphics_scripting) << "ModelScriptingInterface::getMeshes ERROR" << error; callScopedHandlerObject(handler, engine->makeError(error), QScriptValue::NullValue); } else { - callScopedHandlerObject(handler, QScriptValue::NullValue, engine->newQObject(meshes, QScriptEngine::ScriptOwnership)); + callScopedHandlerObject(handler, QScriptValue::NullValue, engine->toScriptValue(meshes)); } } QString ModelScriptingInterface::meshToOBJ(const scriptable::ScriptableModel& _in) { const auto& in = _in.getConstMeshes(); - qCDebug(model_scripting) << "meshToOBJ" << in.size(); + qCDebug(graphics_scripting) << "meshToOBJ" << in.size(); if (in.size()) { QList meshes; foreach (auto meshProxy, in) { - qCDebug(model_scripting) << "meshToOBJ" << meshProxy; + qCDebug(graphics_scripting) << "meshToOBJ" << meshProxy; if (meshProxy) { meshes.append(getMeshPointer(meshProxy)); } @@ -207,7 +254,7 @@ QScriptValue ModelScriptingInterface::appendMeshes(scriptable::ScriptableModel _ (gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL)); - return engine()->toScriptValue(scriptable::ScriptableMeshPointer(new scriptable::ScriptableMesh(nullptr, result))); + return engine()->toScriptValue(scriptable::make_scriptowned(result)); } QScriptValue ModelScriptingInterface::transformMesh(scriptable::ScriptableMeshPointer meshProxy, glm::mat4 transform) { @@ -220,8 +267,7 @@ QScriptValue ModelScriptingInterface::transformMesh(scriptable::ScriptableMeshPo [&](glm::vec3 color){ return color; }, [&](glm::vec3 normal){ return glm::vec3(transform * glm::vec4(normal, 0.0f)); }, [&](uint32_t index){ return index; }); - scriptable::ScriptableMeshPointer resultProxy = scriptable::ScriptableMeshPointer(new scriptable::ScriptableMesh(nullptr, result)); - return engine()->toScriptValue(resultProxy); + return engine()->toScriptValue(scriptable::make_scriptowned(result)); } QScriptValue ModelScriptingInterface::getVertexCount(scriptable::ScriptableMeshPointer meshProxy) { @@ -270,7 +316,7 @@ QScriptValue ModelScriptingInterface::newMesh(const QVector& vertices sizeof(glm::vec3), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); mesh->addAttribute(gpu::Stream::NORMAL, normalBufferView); } else { - qCWarning(model_scripting, "ModelScriptingInterface::newMesh normals must be same length as vertices"); + qCWarning(graphics_scripting, "ModelScriptingInterface::newMesh normals must be same length as vertices"); } // indices (faces) @@ -300,54 +346,10 @@ QScriptValue ModelScriptingInterface::newMesh(const QVector& vertices - scriptable::ScriptableMeshPointer meshProxy = scriptable::ScriptableMeshPointer(new scriptable::ScriptableMesh(nullptr, mesh)); - return engine()->toScriptValue(meshProxy); + return engine()->toScriptValue(scriptable::make_scriptowned(mesh)); } namespace { - QScriptValue meshPointerToScriptValue(QScriptEngine* engine, scriptable::ScriptableMeshPointer const &in) { - if (!in) { - return QScriptValue::NullValue; - } - return engine->newQObject(in, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects); - } - - void meshPointerFromScriptValue(const QScriptValue& value, scriptable::ScriptableMeshPointer &out) { - auto obj = value.toQObject(); - qDebug() << "meshPointerFromScriptValue" << obj; - if (auto tmp = qobject_cast(obj)) { - out = tmp; - } - // FIXME: Why does above cast not work on Win32!? - if (!out) { - if (auto smp = static_cast(obj)) { - qDebug() << "meshPointerFromScriptValue2" << smp; - out = smp; - } - } - } - - QScriptValue modelPointerToScriptValue(QScriptEngine* engine, const scriptable::ScriptableModelPointer &in) { - return engine->newQObject(in, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects); - // QScriptValue result = engine->newArray(); - // int i = 0; - // foreach(auto& mesh, in->getMeshes()) { - // result.setProperty(i++, meshPointerToScriptValue(engine, mesh)); - // } - // return result; - } - - void modelPointerFromScriptValue(const QScriptValue& value, scriptable::ScriptableModelPointer &out) { - const auto length = value.property("length").toInt32(); - qCDebug(model_scripting) << "in modelPointerFromScriptValue, length =" << length; - for (int i = 0; i < length; i++) { - if (const auto meshProxy = qobject_cast(value.property(i).toQObject())) { - out->meshes.append(meshProxy->getMeshPointer()); - } else { - qCDebug(model_scripting) << "null meshProxy" << i; - } - } - } // FIXME: MESHFACES: // QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const mesh::MeshFace &meshFace) { @@ -365,29 +367,11 @@ namespace { // qScriptValueToSequence(array, result); // } - QScriptValue qVectorUInt32ToScriptValue(QScriptEngine* engine, const QVector& vector) { - return qScriptValueFromSequence(engine, vector); - } - - void qVectorUInt32FromScriptValue(const QScriptValue& array, QVector& result) { - qScriptValueToSequence(array, result); - } } -int meshUint32 = qRegisterMetaType(); -namespace mesh { - int meshUint32 = qRegisterMetaType(); -} -int qVectorMeshUint32 = qRegisterMetaType>(); void ModelScriptingInterface::registerMetaTypes(QScriptEngine* engine) { - qScriptRegisterSequenceMetaType>(engine); - qScriptRegisterSequenceMetaType>(engine); - - qScriptRegisterMetaType(engine, qVectorUInt32ToScriptValue, qVectorUInt32FromScriptValue); - qScriptRegisterMetaType(engine, meshPointerToScriptValue, meshPointerFromScriptValue); - qScriptRegisterMetaType(engine, modelPointerToScriptValue, modelPointerFromScriptValue); - + scriptable::registerMetaTypes(engine); // FIXME: MESHFACES: remove if MeshFace is not needed anywhere // qScriptRegisterSequenceMetaType(engine); // qScriptRegisterMetaType(engine, meshFaceToScriptValue, meshFaceFromScriptValue); @@ -395,7 +379,7 @@ void ModelScriptingInterface::registerMetaTypes(QScriptEngine* engine) { } MeshPointer ModelScriptingInterface::getMeshPointer(const scriptable::ScriptableMesh& meshProxy) { - return meshProxy._mesh;//getMeshPointer(&meshProxy); + return meshProxy.getMeshPointer(); } MeshPointer ModelScriptingInterface::getMeshPointer(scriptable::ScriptableMesh& meshProxy) { return getMeshPointer(&meshProxy); @@ -406,7 +390,7 @@ MeshPointer ModelScriptingInterface::getMeshPointer(scriptable::ScriptableMeshPo if (context()){ context()->throwError("expected meshProxy as first parameter"); } else { - qDebug() << "expected meshProxy as first parameter"; + qCDebug(graphics_scripting) << "expected meshProxy as first parameter"; } return result; } @@ -415,7 +399,7 @@ MeshPointer ModelScriptingInterface::getMeshPointer(scriptable::ScriptableMeshPo if (context()) { context()->throwError("expected valid meshProxy as first parameter"); } else { - qDebug() << "expected valid meshProxy as first parameter"; + qCDebug(graphics_scripting) << "expected valid meshProxy as first parameter"; } return result; } diff --git a/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h index eac4df3216..fa7b885014 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h @@ -15,19 +15,17 @@ #include #include -#include - #include #include #include "ScriptableMesh.h" #include + class ModelScriptingInterface : public QObject, public QScriptable, public Dependency { Q_OBJECT public: ModelScriptingInterface(QObject* parent = nullptr); - static void registerMetaTypes(QScriptEngine* engine); public slots: /**jsdoc @@ -36,7 +34,9 @@ public slots: * @function ModelScriptingInterface.getMeshes * @param {EntityID} entityID The ID of the entity whose meshes are to be retrieve */ - void getMeshes(QUuid uuid, QScriptValue scopeOrCallback, QScriptValue methodOrName = QScriptValue()); + void getMeshes(QUuid uuid, QScriptValue callback); + bool updateMeshes(QUuid uuid, const scriptable::ScriptableModelPointer model); + bool updateMeshes(QUuid uuid, const scriptable::ScriptableMeshPointer mesh, int meshIndex=0, int partIndex=0); QString meshToOBJ(const scriptable::ScriptableModel& in); @@ -48,6 +48,8 @@ public slots: QScriptValue getVertexCount(scriptable::ScriptableMeshPointer meshProxy); QScriptValue getVertex(scriptable::ScriptableMeshPointer meshProxy, quint32 vertexIndex); + static void registerMetaTypes(QScriptEngine* engine); + private: scriptable::MeshPointer getMeshPointer(scriptable::ScriptableMeshPointer meshProxy); scriptable::MeshPointer getMeshPointer(scriptable::ScriptableMesh& meshProxy); diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp index 1b16a6d263..b83b901acd 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp @@ -9,17 +9,16 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "GraphicsScriptingUtil.h" #include "ScriptableMesh.h" #include #include #include -#include #include #include #include #include -#include #include "ScriptableMesh.moc" @@ -29,49 +28,45 @@ #include "OBJWriter.h" -QLoggingCategory mesh_logging { "hifi.scripting.mesh" }; - -// FIXME: unroll/resolve before PR -using namespace scriptable; -QMap ScriptableMesh::ATTRIBUTES{ - {"position", gpu::Stream::POSITION }, - {"normal", gpu::Stream::NORMAL }, - {"color", gpu::Stream::COLOR }, - {"tangent", gpu::Stream::TEXCOORD0 }, - {"skin_cluster_index", gpu::Stream::SKIN_CLUSTER_INDEX }, - {"skin_cluster_weight", gpu::Stream::SKIN_CLUSTER_WEIGHT }, - {"texcoord0", gpu::Stream::TEXCOORD0 }, - {"texcoord1", gpu::Stream::TEXCOORD1 }, - {"texcoord2", gpu::Stream::TEXCOORD2 }, - {"texcoord3", gpu::Stream::TEXCOORD3 }, - {"texcoord4", gpu::Stream::TEXCOORD4 }, -}; - - -QString scriptable::ScriptableModel::toString() const { - return QString("[ScriptableModel%1%2]") - .arg(objectID.isNull() ? "" : " objectID="+objectID.toString()) - .arg(objectName().isEmpty() ? "" : " name=" +objectName()); +namespace scriptable { + // QScriptValue jsBindCallback(QScriptValue callback); + // template QPointer qpointer_qobject_cast(const QScriptValue& value); + // template T this_qobject_cast(QScriptEngine* engine); + // template QPointer make_scriptowned(Rest... rest); } -const QVector scriptable::ScriptableModel::getConstMeshes() const { - QVector out; - for(const auto& mesh : meshes) { - const scriptable::ScriptableMeshPointer m = scriptable::ScriptableMeshPointer(new scriptable::ScriptableMesh(const_cast(this), mesh)); - out << m; +scriptable::ScriptableMeshPart::ScriptableMeshPart(scriptable::ScriptableMeshPointer parentMesh, int partIndex) + : parentMesh(parentMesh), partIndex(partIndex) { + setObjectName(QString("%1.part[%2]").arg(parentMesh ? parentMesh->objectName() : "").arg(partIndex)); +} + +scriptable::ScriptableMesh::ScriptableMesh(const ScriptableMeshBase& other) + : ScriptableMeshBase(other) { + auto mesh = getMeshPointer(); + QString name = mesh ? QString::fromStdString(mesh->modelName) : ""; + if (name.isEmpty()) { + name = mesh ? QString::fromStdString(mesh->displayName) : ""; } - return out; + auto parentModel = getParentModel(); + setObjectName(QString("%1#%2").arg(parentModel ? parentModel->objectName() : "").arg(name)); } -QVector scriptable::ScriptableModel::getMeshes() { - QVector out; - for(auto& mesh : meshes) { - scriptable::ScriptableMeshPointer m{new scriptable::ScriptableMesh(this, mesh)}; - out << m; + +QVector scriptable::ScriptableMesh::getMeshParts() const { + QVector out; + for (quint32 i = 0; i < getNumParts(); i++) { + out << scriptable::make_scriptowned(getSelf(), i); } return out; } -quint32 ScriptableMesh::getNumVertices() const { +quint32 scriptable::ScriptableMesh::getNumIndices() const { + if (auto mesh = getMeshPointer()) { + return (quint32)mesh->getNumIndices(); + } + return 0; +} + +quint32 scriptable::ScriptableMesh::getNumVertices() const { if (auto mesh = getMeshPointer()) { return (quint32)mesh->getNumVertices(); } @@ -87,16 +82,10 @@ quint32 ScriptableMesh::getNumVertices() const { // return glm::vec3(NAN); // } -namespace { - gpu::BufferView getBufferView(scriptable::MeshPointer mesh, gpu::Stream::Slot slot) { - return slot == gpu::Stream::POSITION ? mesh->getVertexBuffer() : mesh->getAttributeBuffer(slot); - } -} - -QVector ScriptableMesh::findNearbyIndices(const glm::vec3& origin, float epsilon) const { +QVector scriptable::ScriptableMesh::findNearbyIndices(const glm::vec3& origin, float epsilon) const { QVector result; if (auto mesh = getMeshPointer()) { - const auto& pos = getBufferView(mesh, gpu::Stream::POSITION); + const auto& pos = buffer_helpers::getBufferView(mesh, gpu::Stream::POSITION); const uint32_t num = (uint32_t)pos.getNumElements(); for (uint32_t i = 0; i < num; i++) { const auto& position = pos.get(i); @@ -108,40 +97,45 @@ QVector ScriptableMesh::findNearbyIndices(const glm::vec3& origin, floa return result; } -QVector ScriptableMesh::getIndices() const { +QVector scriptable::ScriptableMesh::getIndices() const { QVector result; if (auto mesh = getMeshPointer()) { - qCDebug(mesh_logging, "getTriangleIndices mesh %p", mesh.get()); + qCDebug(graphics_scripting, "getTriangleIndices mesh %p", mesh.get()); gpu::BufferView indexBufferView = mesh->getIndexBuffer(); if (quint32 count = (quint32)indexBufferView.getNumElements()) { result.resize(count); - auto buffer = indexBufferView._buffer; - if (indexBufferView._element.getSize() == 4) { + switch(indexBufferView._element.getType()) { + case gpu::UINT32: // memcpy(result.data(), buffer->getData(), result.size()*sizeof(quint32)); for (quint32 i = 0; i < count; i++) { result[i] = indexBufferView.get(i); } - } else { + break; + case gpu::UINT16: for (quint32 i = 0; i < count; i++) { result[i] = indexBufferView.get(i); } + break; + default: + assert(false); + Q_ASSERT(false); } } } return result; } -quint32 ScriptableMesh::getNumAttributes() const { +quint32 scriptable::ScriptableMesh::getNumAttributes() const { if (auto mesh = getMeshPointer()) { return (quint32)mesh->getNumAttributes(); } return 0; } -QVector ScriptableMesh::getAttributeNames() const { +QVector scriptable::ScriptableMesh::getAttributeNames() const { QVector result; if (auto mesh = getMeshPointer()) { - for (const auto& a : ATTRIBUTES.toStdMap()) { - auto bufferView = getBufferView(mesh, a.second); + for (const auto& a : buffer_helpers::ATTRIBUTES.toStdMap()) { + auto bufferView = buffer_helpers::getBufferView(mesh, a.second); if (bufferView.getNumElements() > 0) { result << a.first; } @@ -151,55 +145,49 @@ QVector ScriptableMesh::getAttributeNames() const { } // override -QVariantMap ScriptableMesh::getVertexAttributes(quint32 vertexIndex) const { +QVariantMap scriptable::ScriptableMesh::getVertexAttributes(quint32 vertexIndex) const { return getVertexAttributes(vertexIndex, getAttributeNames()); } -bool ScriptableMesh::setVertexAttributes(quint32 vertexIndex, QVariantMap attributes) { - //qDebug() << "setVertexAttributes" << vertexIndex << attributes; - for (auto& a : gatherBufferViews(getMeshPointer())) { +bool scriptable::ScriptableMesh::setVertexAttributes(quint32 vertexIndex, QVariantMap attributes) { + //qCInfo(graphics_scripting) << "setVertexAttributes" << vertexIndex << attributes; + metadata["last-modified"] = QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate); + for (auto& a : buffer_helpers::gatherBufferViews(getMeshPointer())) { const auto& name = a.first; const auto& value = attributes.value(name); if (value.isValid()) { auto& view = a.second; - //qCDebug(mesh_logging) << "setVertexAttributes" << vertexIndex << name; - bufferViewElementFromVariant(view, vertexIndex, value); + //qCDebug(graphics_scripting) << "setVertexAttributes" << vertexIndex << name; + buffer_helpers::fromVariant(view, vertexIndex, value); } else { - //qCDebug(mesh_logging) << "(skipping) setVertexAttributes" << vertexIndex << name; + //qCDebug(graphics_scripting) << "(skipping) setVertexAttributes" << vertexIndex << name; } } return true; } -int ScriptableMesh::_getSlotNumber(const QString& attributeName) const { +int scriptable::ScriptableMesh::_getSlotNumber(const QString& attributeName) const { if (auto mesh = getMeshPointer()) { - return ATTRIBUTES.value(attributeName, -1); + return buffer_helpers::ATTRIBUTES.value(attributeName, -1); } return -1; } -QVariantMap ScriptableMesh::getMeshExtents() const { +QVariantMap scriptable::ScriptableMesh::getMeshExtents() const { auto mesh = getMeshPointer(); auto box = mesh ? mesh->evalPartsBound(0, (int)mesh->getNumParts()) : AABox(); - return { - { "brn", glmVecToVariant(box.getCorner()) }, - { "tfl", glmVecToVariant(box.calcTopFarLeft()) }, - { "center", glmVecToVariant(box.calcCenter()) }, - { "min", glmVecToVariant(box.getMinimumPoint()) }, - { "max", glmVecToVariant(box.getMaximumPoint()) }, - { "dimensions", glmVecToVariant(box.getDimensions()) }, - }; + return buffer_helpers::toVariant(box).toMap(); } -quint32 ScriptableMesh::getNumParts() const { +quint32 scriptable::ScriptableMesh::getNumParts() const { if (auto mesh = getMeshPointer()) { return (quint32)mesh->getNumParts(); } return 0; } -QVariantMap ScriptableMesh::scaleToFit(float unitScale) { +QVariantMap scriptable::ScriptableMeshPart::scaleToFit(float unitScale) { if (auto mesh = getMeshPointer()) { auto box = mesh->evalPartsBound(0, (int)mesh->getNumParts()); auto center = box.calcCenter(); @@ -208,10 +196,10 @@ QVariantMap ScriptableMesh::scaleToFit(float unitScale) { } return {}; } -QVariantMap ScriptableMesh::translate(const glm::vec3& translation) { +QVariantMap scriptable::ScriptableMeshPart::translate(const glm::vec3& translation) { return transform(glm::translate(translation)); } -QVariantMap ScriptableMesh::scale(const glm::vec3& scale, const glm::vec3& origin) { +QVariantMap scriptable::ScriptableMeshPart::scale(const glm::vec3& scale, const glm::vec3& origin) { if (auto mesh = getMeshPointer()) { auto box = mesh->evalPartsBound(0, (int)mesh->getNumParts()); glm::vec3 center = glm::isnan(origin.x) ? box.calcCenter() : origin; @@ -219,10 +207,10 @@ QVariantMap ScriptableMesh::scale(const glm::vec3& scale, const glm::vec3& origi } return {}; } -QVariantMap ScriptableMesh::rotateDegrees(const glm::vec3& eulerAngles, const glm::vec3& origin) { +QVariantMap scriptable::ScriptableMeshPart::rotateDegrees(const glm::vec3& eulerAngles, const glm::vec3& origin) { return rotate(glm::quat(glm::radians(eulerAngles)), origin); } -QVariantMap ScriptableMesh::rotate(const glm::quat& rotation, const glm::vec3& origin) { +QVariantMap scriptable::ScriptableMeshPart::rotate(const glm::quat& rotation, const glm::vec3& origin) { if (auto mesh = getMeshPointer()) { auto box = mesh->evalPartsBound(0, (int)mesh->getNumParts()); glm::vec3 center = glm::isnan(origin.x) ? box.calcCenter() : origin; @@ -230,184 +218,61 @@ QVariantMap ScriptableMesh::rotate(const glm::quat& rotation, const glm::vec3& o } return {}; } -QVariantMap ScriptableMesh::transform(const glm::mat4& transform) { +QVariantMap scriptable::ScriptableMeshPart::transform(const glm::mat4& transform) { if (auto mesh = getMeshPointer()) { - const auto& pos = getBufferView(mesh, gpu::Stream::POSITION); + const auto& pos = buffer_helpers::getBufferView(mesh, gpu::Stream::POSITION); const uint32_t num = (uint32_t)pos.getNumElements(); for (uint32_t i = 0; i < num; i++) { auto& position = pos.edit(i); position = transform * glm::vec4(position, 1.0f); } + return parentMesh->getMeshExtents(); } - return getMeshExtents(); + return {}; } -QVariantList ScriptableMesh::getAttributeValues(const QString& attributeName) const { +QVariantList scriptable::ScriptableMesh::getAttributeValues(const QString& attributeName) const { QVariantList result; auto slotNum = _getSlotNumber(attributeName); if (slotNum >= 0) { auto slot = (gpu::Stream::Slot)slotNum; - const auto& bufferView = getBufferView(getMeshPointer(), slot); + const auto& bufferView = buffer_helpers::getBufferView(getMeshPointer(), slot); if (auto len = bufferView.getNumElements()) { bool asArray = bufferView._element.getType() != gpu::FLOAT; for (quint32 i = 0; i < len; i++) { - result << bufferViewElementToVariant(bufferView, i, asArray, attributeName.toStdString().c_str()); + result << buffer_helpers::toVariant(bufferView, i, asArray, attributeName.toStdString().c_str()); } } } return result; } -QVariantMap ScriptableMesh::getVertexAttributes(quint32 vertexIndex, QVector names) const { +QVariantMap scriptable::ScriptableMesh::getVertexAttributes(quint32 vertexIndex, QVector names) const { QVariantMap result; auto mesh = getMeshPointer(); if (!mesh || vertexIndex >= getNumVertices()) { return result; } - for (const auto& a : ATTRIBUTES.toStdMap()) { + for (const auto& a : buffer_helpers::ATTRIBUTES.toStdMap()) { auto name = a.first; if (!names.contains(name)) { continue; } auto slot = a.second; - const gpu::BufferView& bufferView = getBufferView(mesh, slot); + const gpu::BufferView& bufferView = buffer_helpers::getBufferView(mesh, slot); if (vertexIndex < bufferView.getNumElements()) { bool asArray = bufferView._element.getType() != gpu::FLOAT; - result[name] = bufferViewElementToVariant(bufferView, vertexIndex, asArray, name.toStdString().c_str()); + result[name] = buffer_helpers::toVariant(bufferView, vertexIndex, asArray, name.toStdString().c_str()); } } return result; } -/// --- buffer view <-> variant helpers - -namespace { - // expand the corresponding attribute buffer (creating it if needed) so that it matches POSITIONS size and specified element type - gpu::BufferView _expandedAttributeBuffer(const scriptable::MeshPointer mesh, gpu::Stream::Slot slot, const gpu::Element& elementType) { - gpu::Size elementSize = elementType.getSize(); - gpu::BufferView bufferView = getBufferView(mesh, slot); - auto nPositions = mesh->getNumVertices(); - auto vsize = nPositions * elementSize; - auto diffTypes = (elementType.getType() != bufferView._element.getType() || - elementType.getSize() > bufferView._element.getSize() || - elementType.getScalarCount() > bufferView._element.getScalarCount() || - vsize > bufferView._size - ); - auto hint = DebugNames::stringFrom(slot); - -#ifdef DEV_BUILD - auto beforeCount = bufferView.getNumElements(); - auto beforeTotal = bufferView._size; -#endif - if (bufferView.getNumElements() < nPositions || diffTypes) { - if (!bufferView._buffer || bufferView.getNumElements() == 0) { - qCInfo(mesh_logging).nospace() << "ScriptableMesh -- adding missing mesh attribute '" << hint << "' for BufferView"; - gpu::Byte *data = new gpu::Byte[vsize]; - memset(data, 0, vsize); - auto buffer = new gpu::Buffer(vsize, (gpu::Byte*)data); - delete[] data; - bufferView = gpu::BufferView(buffer, elementType); - mesh->addAttribute(slot, bufferView); - } else { - qCInfo(mesh_logging) << "ScriptableMesh -- resizing Buffer current:" << hint << bufferView._buffer->getSize() << "wanted:" << vsize; - bufferView._element = elementType; - bufferView._buffer->resize(vsize); - bufferView._size = bufferView._buffer->getSize(); - } - } -#ifdef DEV_BUILD - auto afterCount = bufferView.getNumElements(); - auto afterTotal = bufferView._size; - if (beforeTotal != afterTotal || beforeCount != afterCount) { - auto typeName = DebugNames::stringFrom(bufferView._element.getType()); - qCDebug(mesh_logging, "NOTE:: _expandedAttributeBuffer.%s vec%d %s (before count=%lu bytes=%lu // after count=%lu bytes=%lu)", - hint.toStdString().c_str(), bufferView._element.getScalarCount(), - typeName.toStdString().c_str(), beforeCount, beforeTotal, afterCount, afterTotal); - } -#endif - return bufferView; - } - const gpu::Element UNUSED{ gpu::SCALAR, gpu::UINT8, gpu::RAW }; - - gpu::Element getVecNElement(gpu::Type T, int N) { - switch(N) { - case 2: return { gpu::VEC2, T, gpu::XY }; - case 3: return { gpu::VEC3, T, gpu::XYZ }; - case 4: return { gpu::VEC4, T, gpu::XYZW }; - } - Q_ASSERT(false); - return UNUSED; - } - - gpu::BufferView expandAttributeToMatchPositions(scriptable::MeshPointer mesh, gpu::Stream::Slot slot) { - if (slot == gpu::Stream::POSITION) { - return getBufferView(mesh, slot); - } - return _expandedAttributeBuffer(mesh, slot, getVecNElement(gpu::FLOAT, 3)); - } -} - -std::map ScriptableMesh::gatherBufferViews(scriptable::MeshPointer mesh, const QStringList& expandToMatchPositions) { - std::map attributeViews; - if (!mesh) { - return attributeViews; - } - for (const auto& a : ScriptableMesh::ATTRIBUTES.toStdMap()) { - auto name = a.first; - auto slot = a.second; - if (expandToMatchPositions.contains(name)) { - expandAttributeToMatchPositions(mesh, slot); - } - auto view = getBufferView(mesh, slot); - auto beforeCount = view.getNumElements(); - if (beforeCount > 0) { - auto element = view._element; - auto vecN = element.getScalarCount(); - auto type = element.getType(); - QString typeName = DebugNames::stringFrom(element.getType()); - auto beforeTotal = view._size; - - attributeViews[name] = _expandedAttributeBuffer(mesh, slot, getVecNElement(type, vecN)); - -#if DEV_BUILD - auto afterTotal = attributeViews[name]._size; - auto afterCount = attributeViews[name].getNumElements(); - if (beforeTotal != afterTotal || beforeCount != afterCount) { - qCDebug(mesh_logging, "NOTE:: gatherBufferViews.%s vec%d %s (before count=%lu bytes=%lu // after count=%lu bytes=%lu)", - name.toStdString().c_str(), vecN, typeName.toStdString().c_str(), beforeCount, beforeTotal, afterCount, afterTotal); - } -#endif - } - } - return attributeViews; -} - -QScriptValue ScriptableModel::mapAttributeValues(QScriptValue scopeOrCallback, QScriptValue methodOrName) { - auto context = scopeOrCallback.engine()->currentContext(); - auto _in = context->thisObject(); - qCInfo(mesh_logging) << "mapAttributeValues" << _in.toVariant().typeName() << _in.toVariant().toString() << _in.toQObject(); - auto model = qscriptvalue_cast(_in); - QVector in = model.getMeshes(); - if (in.size()) { - foreach (scriptable::ScriptableMeshPointer meshProxy, in) { - meshProxy->mapAttributeValues(scopeOrCallback, methodOrName); - } - return _in; - } else if (auto meshProxy = qobject_cast(_in.toQObject())) { - return meshProxy->mapAttributeValues(scopeOrCallback, methodOrName); - } else { - context->throwError("invalid ModelProxy || MeshProxyPointer"); - } - return false; -} - - - -QScriptValue ScriptableMesh::mapAttributeValues(QScriptValue scopeOrCallback, QScriptValue methodOrName) { +quint32 scriptable::ScriptableMesh::mapAttributeValues(QScriptValue _callback) { auto mesh = getMeshPointer(); if (!mesh) { - return false; + return 0; } - auto scopedHandler = makeScopedHandlerObject(scopeOrCallback, methodOrName); + auto scopedHandler = jsBindCallback(_callback); // input buffers gpu::BufferView positions = mesh->getVertexBuffer(); @@ -417,20 +282,25 @@ QScriptValue ScriptableMesh::mapAttributeValues(QScriptValue scopeOrCallback, QS // destructure so we can still invoke callback scoped, but with a custom signature (obj, i, jsMesh) auto scope = scopedHandler.property("scope"); auto callback = scopedHandler.property("callback"); - auto js = engine(); // cache value to avoid resolving each iteration - auto meshPart = thisObject();//js->toScriptValue(meshProxy); - + auto js = engine() ? engine() : scopedHandler.engine(); // cache value to avoid resolving each iteration + if (!js) { + return 0; + } + auto meshPart = js ? js->toScriptValue(getSelf()) : QScriptValue::NullValue; + qCInfo(graphics_scripting) << "mapAttributeValues" << mesh.get() << js->currentContext()->thisObject().toQObject(); auto obj = js->newObject(); - auto attributeViews = ScriptableMesh::gatherBufferViews(mesh, { "normal", "color" }); - for (uint32_t i=0; i < nPositions; i++) { + auto attributeViews = buffer_helpers::gatherBufferViews(mesh, { "normal", "color" }); + metadata["last-modified"] = QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate); + uint32_t i = 0; + for (; i < nPositions; i++) { for (const auto& a : attributeViews) { bool asArray = a.second._element.getType() != gpu::FLOAT; obj.setProperty(a.first, bufferViewElementToScriptValue(js, a.second, i, asArray, a.first.toStdString().c_str())); } auto result = callback.call(scope, { obj, i, meshPart }); if (js->hasUncaughtException()) { - context()->throwValue(js->uncaughtException()); - return false; + js->currentContext()->throwValue(js->uncaughtException()); + return i; } if (result.isBool() && !result.toBool()) { @@ -450,15 +320,19 @@ QScriptValue ScriptableMesh::mapAttributeValues(QScriptValue scopeOrCallback, QS } } } - return thisObject(); + return i; } -QScriptValue ScriptableMesh::unrollVertices(bool recalcNormals) { +quint32 scriptable::ScriptableMeshPart::mapAttributeValues(QScriptValue callback) { + return parentMesh ? parentMesh->mapAttributeValues(callback) : 0; +} + +bool scriptable::ScriptableMeshPart::unrollVertices(bool recalcNormals) { auto meshProxy = this; auto mesh = getMeshPointer(); - qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices" << !!mesh<< !!meshProxy; + qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices" << !!mesh<< !!meshProxy; if (!mesh) { - return QScriptValue(); + return false; } auto positions = mesh->getVertexBuffer(); @@ -467,8 +341,9 @@ QScriptValue ScriptableMesh::unrollVertices(bool recalcNormals) { auto buffer = new gpu::Buffer(); buffer->resize(numPoints * sizeof(uint32_t)); auto newindices = gpu::BufferView(buffer, { gpu::SCALAR, gpu::UINT32, gpu::INDEX }); - qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices numPoints" << numPoints; - auto attributeViews = ScriptableMesh::gatherBufferViews(mesh); + metadata["last-modified"] = QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate); + qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices numPoints" << numPoints; + auto attributeViews = buffer_helpers::gatherBufferViews(mesh); for (const auto& a : attributeViews) { auto& view = a.second; auto sz = view._element.getSize(); @@ -477,19 +352,21 @@ QScriptValue ScriptableMesh::unrollVertices(bool recalcNormals) { auto points = gpu::BufferView(buffer, view._element); auto src = (uint8_t*)view._buffer->getData(); auto dest = (uint8_t*)points._buffer->getData(); - auto slot = ScriptableMesh::ATTRIBUTES[a.first]; - qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices buffer" << a.first; - qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices source" << view.getNumElements(); - qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices dest" << points.getNumElements(); - qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices sz" << sz << src << dest << slot; + auto slot = buffer_helpers::ATTRIBUTES[a.first]; + if (0) { + qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices buffer" << a.first; + qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices source" << view.getNumElements(); + qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices dest" << points.getNumElements(); + qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices sz" << sz << src << dest << slot; + } auto esize = indices._element.getSize(); const char* hint= a.first.toStdString().c_str(); for(quint32 i = 0; i < numPoints; i++) { quint32 index = esize == 4 ? indices.get(i) : indices.get(i); newindices.edit(i) = i; - bufferViewElementFromVariant( + buffer_helpers::fromVariant( points, i, - bufferViewElementToVariant(view, index, false, hint) + buffer_helpers::toVariant(view, index, false, hint) ); } if (slot == gpu::Stream::POSITION) { @@ -505,62 +382,70 @@ QScriptValue ScriptableMesh::unrollVertices(bool recalcNormals) { return true; } -bool ScriptableMesh::replaceMeshData(scriptable::ScriptableMeshPointer src, const QVector& attributeNames) { +bool scriptable::ScriptableMeshPart::replaceMeshData(scriptable::ScriptableMeshPartPointer src, const QVector& attributeNames) { auto target = getMeshPointer(); auto source = src ? src->getMeshPointer() : nullptr; if (!target || !source) { - context()->throwError("ScriptableMesh::replaceMeshData -- expected dest and src to be valid mesh proxy pointers"); + if (context()) { + context()->throwError("ScriptableMeshPart::replaceMeshData -- expected dest and src to be valid mesh proxy pointers"); + } else { + qCWarning(graphics_scripting) << "ScriptableMeshPart::replaceMeshData -- expected dest and src to be valid mesh proxy pointers"; + } return false; } - QVector attributes = attributeNames.isEmpty() ? src->getAttributeNames() : attributeNames; + QVector attributes = attributeNames.isEmpty() ? src->parentMesh->getAttributeNames() : attributeNames; - //qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData -- source:" << source->displayName << "target:" << target->displayName << "attributes:" << attributes; + qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData -- " << + "source:" << QString::fromStdString(source->displayName) << + "target:" << QString::fromStdString(target->displayName) << + "attributes:" << attributes; + + metadata["last-modified"] = QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate); // remove attributes only found on target mesh, unless user has explicitly specified the relevant attribute names if (attributeNames.isEmpty()) { - auto attributeViews = ScriptableMesh::gatherBufferViews(target); + auto attributeViews = buffer_helpers::gatherBufferViews(target); for (const auto& a : attributeViews) { - auto slot = ScriptableMesh::ATTRIBUTES[a.first]; + auto slot = buffer_helpers::ATTRIBUTES[a.first]; if (!attributes.contains(a.first)) { - //qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData -- pruning target attribute" << a.first << slot; + qCInfo(graphics_scripting) << "ScriptableMesh::replaceMeshData -- pruning target attribute" << a.first << slot; target->removeAttribute(slot); } } } - target->setVertexBuffer(cloneBufferView(source->getVertexBuffer())); - target->setIndexBuffer(cloneBufferView(source->getIndexBuffer())); - target->setPartBuffer(cloneBufferView(source->getPartBuffer())); + target->setVertexBuffer(buffer_helpers::clone(source->getVertexBuffer())); + target->setIndexBuffer(buffer_helpers::clone(source->getIndexBuffer())); + target->setPartBuffer(buffer_helpers::clone(source->getPartBuffer())); for (const auto& a : attributes) { - auto slot = ScriptableMesh::ATTRIBUTES[a]; + auto slot = buffer_helpers::ATTRIBUTES[a]; if (slot == gpu::Stream::POSITION) { continue; } - // auto& before = target->getAttributeBuffer(slot); + auto& before = target->getAttributeBuffer(slot); auto& input = source->getAttributeBuffer(slot); if (input.getNumElements() == 0) { - //qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData buffer is empty -- pruning" << a << slot; + qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData buffer is empty -- pruning" << a << slot; target->removeAttribute(slot); } else { - // if (before.getNumElements() == 0) { - // qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData target buffer is empty -- adding" << a << slot; - // } else { - // qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData target buffer exists -- updating" << a << slot; - // } - target->addAttribute(slot, cloneBufferView(input)); + if (before.getNumElements() == 0) { + qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData target buffer is empty -- adding" << a << slot; + } else { + qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData target buffer exists -- updating" << a << slot; + } + target->addAttribute(slot, buffer_helpers::clone(input)); } - // auto& after = target->getAttributeBuffer(slot); - // qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData" << a << slot << before.getNumElements() << " -> " << after.getNumElements(); + auto& after = target->getAttributeBuffer(slot); + qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData" << a << slot << before.getNumElements() << " -> " << after.getNumElements(); } return true; } -bool ScriptableMesh::dedupeVertices(float epsilon) { - scriptable::ScriptableMeshPointer meshProxy = this; +bool scriptable::ScriptableMeshPart::dedupeVertices(float epsilon) { auto mesh = getMeshPointer(); if (!mesh) { return false; @@ -573,6 +458,7 @@ bool ScriptableMesh::dedupeVertices(float epsilon) { uniqueVerts.reserve((int)numPositions); QMap remapIndices; + metadata["last-modified"] = QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate); for (quint32 i = 0; i < numPositions; i++) { const quint32 numUnique = uniqueVerts.size(); const auto& position = positions.get(i); @@ -590,7 +476,7 @@ bool ScriptableMesh::dedupeVertices(float epsilon) { } } - qCInfo(mesh_logging) << "//VERTS before" << numPositions << "after" << uniqueVerts.size(); + qCInfo(graphics_scripting) << "//VERTS before" << numPositions << "after" << uniqueVerts.size(); auto indices = mesh->getIndexBuffer(); auto numIndices = indices.getNumElements(); @@ -600,34 +486,34 @@ bool ScriptableMesh::dedupeVertices(float epsilon) { for (quint32 i = 0; i < numIndices; i++) { quint32 index = esize == 4 ? indices.get(i) : indices.get(i); if (remapIndices.contains(index)) { - //qCInfo(mesh_logging) << i << index << "->" << remapIndices[index]; + //qCInfo(graphics_scripting) << i << index << "->" << remapIndices[index]; newIndices << remapIndices[index]; } else { - qCInfo(mesh_logging) << i << index << "!remapIndices[index]"; + qCInfo(graphics_scripting) << i << index << "!remapIndices[index]"; } } - mesh->setIndexBuffer(bufferViewFromVector(newIndices, { gpu::SCALAR, gpu::UINT32, gpu::INDEX })); - mesh->setVertexBuffer(bufferViewFromVector(uniqueVerts, { gpu::VEC3, gpu::FLOAT, gpu::XYZ })); + mesh->setIndexBuffer(buffer_helpers::fromVector(newIndices, { gpu::SCALAR, gpu::UINT32, gpu::INDEX })); + mesh->setVertexBuffer(buffer_helpers::fromVector(uniqueVerts, { gpu::VEC3, gpu::FLOAT, gpu::XYZ })); - auto attributeViews = ScriptableMesh::gatherBufferViews(mesh); + auto attributeViews = buffer_helpers::gatherBufferViews(mesh); quint32 numUniqueVerts = uniqueVerts.size(); for (const auto& a : attributeViews) { auto& view = a.second; - auto slot = ScriptableMesh::ATTRIBUTES[a.first]; + auto slot = buffer_helpers::ATTRIBUTES[a.first]; if (slot == gpu::Stream::POSITION) { continue; } - qCInfo(mesh_logging) << "ScriptableMesh::dedupeVertices" << a.first << slot << view.getNumElements(); - auto newView = resizedBufferView(view, numUniqueVerts); - qCInfo(mesh_logging) << a.first << "before: #" << view.getNumElements() << "after: #" << newView.getNumElements(); + qCInfo(graphics_scripting) << "ScriptableMeshPart::dedupeVertices" << a.first << slot << view.getNumElements(); + auto newView = buffer_helpers::resize(view, numUniqueVerts); + qCInfo(graphics_scripting) << a.first << "before: #" << view.getNumElements() << "after: #" << newView.getNumElements(); quint32 numElements = (quint32)view.getNumElements(); for (quint32 i = 0; i < numElements; i++) { quint32 fromVertexIndex = i; quint32 toVertexIndex = remapIndices.contains(fromVertexIndex) ? remapIndices[fromVertexIndex] : fromVertexIndex; - bufferViewElementFromVariant( + buffer_helpers::fromVariant( newView, toVertexIndex, - bufferViewElementToVariant(view, fromVertexIndex, false, "dedupe") + buffer_helpers::toVariant(view, fromVertexIndex, false, "dedupe") ); } mesh->addAttribute(slot, newView); @@ -635,120 +521,202 @@ bool ScriptableMesh::dedupeVertices(float epsilon) { return true; } -QScriptValue ScriptableMesh::cloneMesh(bool recalcNormals) { +scriptable::ScriptableMeshPointer scriptable::ScriptableMesh::cloneMesh(bool recalcNormals) { auto mesh = getMeshPointer(); if (!mesh) { - return QScriptValue::NullValue; + qCInfo(graphics_scripting) << "ScriptableMesh::cloneMesh -- !meshPointer"; + return nullptr; } - graphics::MeshPointer clone(new graphics::Mesh()); - clone->displayName = mesh->displayName + "-clone"; - qCInfo(mesh_logging) << "ScriptableMesh::cloneMesh" << !!mesh; - if (!mesh) { - return QScriptValue::NullValue; - } - - clone->setIndexBuffer(cloneBufferView(mesh->getIndexBuffer())); - clone->setPartBuffer(cloneBufferView(mesh->getPartBuffer())); - auto attributeViews = ScriptableMesh::gatherBufferViews(mesh); - for (const auto& a : attributeViews) { - auto& view = a.second; - auto slot = ScriptableMesh::ATTRIBUTES[a.first]; - qCInfo(mesh_logging) << "ScriptableMesh::cloneVertices buffer" << a.first << slot; - auto points = cloneBufferView(view); - qCInfo(mesh_logging) << "ScriptableMesh::cloneVertices source" << view.getNumElements(); - qCInfo(mesh_logging) << "ScriptableMesh::cloneVertices dest" << points.getNumElements(); - if (slot == gpu::Stream::POSITION) { - clone->setVertexBuffer(points); - } else { - clone->addAttribute(slot, points); - } - } - - auto result = scriptable::ScriptableMeshPointer(new ScriptableMesh(nullptr, clone)); + qCInfo(graphics_scripting) << "ScriptableMesh::cloneMesh..."; + auto clone = buffer_helpers::cloneMesh(mesh); + + qCInfo(graphics_scripting) << "ScriptableMesh::cloneMesh..."; if (recalcNormals) { - result->recalculateNormals(); + buffer_helpers::recalculateNormals(clone); } - return engine()->toScriptValue(result); + qCDebug(graphics_scripting) << clone.get();// << metadata; + auto meshPointer = scriptable::make_scriptowned(provider, model, clone, metadata); + clone.reset(); // free local reference + qCInfo(graphics_scripting) << "========= ScriptableMesh::cloneMesh..." << meshPointer << meshPointer->ownedMesh.use_count(); + //scriptable::MeshPointer* ppMesh = new scriptable::MeshPointer(); + //*ppMesh = clone; + + if (meshPointer) { + scriptable::WeakMeshPointer delme = meshPointer->mesh; + QString debugString = scriptable::toDebugString(meshPointer); + QObject::connect(meshPointer, &QObject::destroyed, meshPointer, [=]() { + qCWarning(graphics_scripting) << "*************** cloneMesh/Destroy"; + qCWarning(graphics_scripting) << "*************** " << debugString << delme.lock().get(); + if (!delme.expired()) { + qCWarning(graphics_scripting) << "cloneMesh -- potential memory leak..." << debugString << delme.lock().get(); + } + }); + } + + meshPointer->metadata["last-modified"] = QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate); + return scriptable::ScriptableMeshPointer(meshPointer); } -bool ScriptableMesh::recalculateNormals() { - scriptable::ScriptableMeshPointer meshProxy = this; - qCInfo(mesh_logging) << "Recalculating normals" << !!meshProxy; - auto mesh = getMeshPointer(); - if (!mesh) { - return false; - } - ScriptableMesh::gatherBufferViews(mesh, { "normal", "color" }); // ensures #normals >= #positions - auto normals = mesh->getAttributeBuffer(gpu::Stream::NORMAL); - auto verts = mesh->getVertexBuffer(); - auto indices = mesh->getIndexBuffer(); - auto esize = indices._element.getSize(); - auto numPoints = indices.getNumElements(); - const auto TRIANGLE = 3; - quint32 numFaces = (quint32)numPoints / TRIANGLE; - //QVector faces; - QVector faceNormals; - QMap> vertexToFaces; - //faces.resize(numFaces); - faceNormals.resize(numFaces); - auto numNormals = normals.getNumElements(); - qCInfo(mesh_logging) << QString("numFaces: %1, numNormals: %2, numPoints: %3").arg(numFaces).arg(numNormals).arg(numPoints); - if (normals.getNumElements() != verts.getNumElements()) { - return false; - } - for (quint32 i = 0; i < numFaces; i++) { - quint32 I = TRIANGLE * i; - quint32 i0 = esize == 4 ? indices.get(I+0) : indices.get(I+0); - quint32 i1 = esize == 4 ? indices.get(I+1) : indices.get(I+1); - quint32 i2 = esize == 4 ? indices.get(I+2) : indices.get(I+2); - - Triangle face = { - verts.get(i1), - verts.get(i2), - verts.get(i0) - }; - faceNormals[i] = face.getNormal(); - if (glm::isnan(faceNormals[i].x)) { - qCInfo(mesh_logging) << i << i0 << i1 << i2 << vec3toVariant(face.v0) << vec3toVariant(face.v1) << vec3toVariant(face.v2); - break; - } - vertexToFaces[glm::to_string(face.v0).c_str()] << i; - vertexToFaces[glm::to_string(face.v1).c_str()] << i; - vertexToFaces[glm::to_string(face.v2).c_str()] << i; - } - for (quint32 j = 0; j < numNormals; j++) { - //auto v = verts.get(j); - glm::vec3 normal { 0.0f, 0.0f, 0.0f }; - QString key { glm::to_string(verts.get(j)).c_str() }; - const auto& faces = vertexToFaces.value(key); - if (faces.size()) { - for (const auto i : faces) { - normal += faceNormals[i]; - } - normal *= 1.0f / (float)faces.size(); - } else { - static int logged = 0; - if (logged++ < 10) { - qCInfo(mesh_logging) << "no faces for key!?" << key; - } - normal = verts.get(j); - } - if (glm::isnan(normal.x)) { - static int logged = 0; - if (logged++ < 10) { - qCInfo(mesh_logging) << "isnan(normal.x)" << j << vec3toVariant(normal); - } - break; - } - normals.edit(j) = glm::normalize(normal); - } - return true; +scriptable::ScriptableMeshBase::ScriptableMeshBase(scriptable::WeakModelProviderPointer provider, scriptable::ScriptableModelBasePointer model, scriptable::WeakMeshPointer mesh, const QVariantMap& metadata) + : provider(provider), model(model), mesh(mesh), metadata(metadata) {} +scriptable::ScriptableMeshBase::ScriptableMeshBase(scriptable::WeakMeshPointer mesh) : scriptable::ScriptableMeshBase(scriptable::WeakModelProviderPointer(), nullptr, mesh, QVariantMap()) { } +scriptable::ScriptableMeshBase::ScriptableMeshBase(scriptable::MeshPointer mesh, const QVariantMap& metadata) + : ScriptableMeshBase(WeakModelProviderPointer(), nullptr, mesh, metadata) { + ownedMesh = mesh; +} +//scriptable::ScriptableMeshBase::ScriptableMeshBase(const scriptable::ScriptableMeshBase& other) { *this = other; } +scriptable::ScriptableMeshBase& scriptable::ScriptableMeshBase::operator=(const scriptable::ScriptableMeshBase& view) { + provider = view.provider; + model = view.model; + mesh = view.mesh; + ownedMesh = view.ownedMesh; + metadata = view.metadata; + return *this; +} + scriptable::ScriptableMeshBase::~ScriptableMeshBase() { + ownedMesh.reset(); + qCInfo(graphics_scripting) << "//~ScriptableMeshBase" << this << "ownedMesh:" << ownedMesh.use_count() << "mesh:" << mesh.use_count(); } -QString ScriptableMesh::toOBJ() { +scriptable::ScriptableMesh::~ScriptableMesh() { + ownedMesh.reset(); + qCInfo(graphics_scripting) << "//~ScriptableMesh" << this << "ownedMesh:" << ownedMesh.use_count() << "mesh:" << mesh.use_count(); +} + +QString scriptable::ScriptableMeshPart::toOBJ() { if (!getMeshPointer()) { - context()->throwError(QString("null mesh")); + if (context()) { + context()->throwError(QString("null mesh")); + } else { + qCWarning(graphics_scripting) << "null mesh"; + } + return QString(); } return writeOBJToString({ getMeshPointer() }); } +namespace { + template + QScriptValue qObjectToScriptValue(QScriptEngine* engine, const T& object) { + if (!object) { + return QScriptValue::NullValue; + } + auto ownership = object->metadata.value("__ownership__"); + return engine->newQObject( + object, + ownership.isValid() ? static_cast(ownership.toInt()) : QScriptEngine::QtOwnership + //, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects + ); + } + + QScriptValue meshPointerToScriptValue(QScriptEngine* engine, const scriptable::ScriptableMeshPointer& in) { + return qObjectToScriptValue(engine, in); + } + QScriptValue meshPartPointerToScriptValue(QScriptEngine* engine, const scriptable::ScriptableMeshPartPointer& in) { + return qObjectToScriptValue(engine, in); + } + QScriptValue modelPointerToScriptValue(QScriptEngine* engine, const scriptable::ScriptableModelPointer& in) { + return qObjectToScriptValue(engine, in); + } + + void meshPointerFromScriptValue(const QScriptValue& value, scriptable::ScriptableMeshPointer &out) { + out = scriptable::qpointer_qobject_cast(value); + } + void modelPointerFromScriptValue(const QScriptValue& value, scriptable::ScriptableModelPointer &out) { + out = scriptable::qpointer_qobject_cast(value); + } + void meshPartPointerFromScriptValue(const QScriptValue& value, scriptable::ScriptableMeshPartPointer &out) { + out = scriptable::qpointer_qobject_cast(value); + } + + // FIXME: MESHFACES: + // QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const mesh::MeshFace &meshFace) { + // QScriptValue obj = engine->newObject(); + // obj.setProperty("vertices", qVectorIntToScriptValue(engine, meshFace.vertexIndices)); + // return obj; + // } + // void meshFaceFromScriptValue(const QScriptValue &object, mesh::MeshFace& meshFaceResult) { + // qScriptValueToSequence(object.property("vertices"), meshFaceResult.vertexIndices); + // } + // QScriptValue qVectorMeshFaceToScriptValue(QScriptEngine* engine, const QVector& vector) { + // return qScriptValueFromSequence(engine, vector); + // } + // void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector& result) { + // qScriptValueToSequence(array, result); + // } + + QScriptValue qVectorUInt32ToScriptValue(QScriptEngine* engine, const QVector& vector) { + return qScriptValueFromSequence(engine, vector); + } + + void qVectorUInt32FromScriptValue(const QScriptValue& array, QVector& result) { + qScriptValueToSequence(array, result); + } + + QVector metaTypeIds{ + qRegisterMetaType("uint32"), + qRegisterMetaType("scriptable::uint32"), + qRegisterMetaType>(), + qRegisterMetaType>("QVector"), + qRegisterMetaType(), + qRegisterMetaType(), + qRegisterMetaType(), + }; +} + +namespace scriptable { + bool registerMetaTypes(QScriptEngine* engine) { + qScriptRegisterSequenceMetaType>(engine); + qScriptRegisterSequenceMetaType>(engine); + qScriptRegisterSequenceMetaType>(engine); + + qScriptRegisterMetaType(engine, qVectorUInt32ToScriptValue, qVectorUInt32FromScriptValue); + qScriptRegisterMetaType(engine, modelPointerToScriptValue, modelPointerFromScriptValue); + qScriptRegisterMetaType(engine, meshPointerToScriptValue, meshPointerFromScriptValue); + qScriptRegisterMetaType(engine, meshPartPointerToScriptValue, meshPartPointerFromScriptValue); + + return metaTypeIds.size(); + } + // callback helper that lets C++ method signatures remain simple (ie: taking a single callback argument) while + // still supporting extended Qt signal-like (scope, "methodName") and (scope, function(){}) "this" binding conventions + QScriptValue jsBindCallback(QScriptValue callback) { + if (callback.isObject() && callback.property("callback").isFunction()) { + return callback; + } + auto engine = callback.engine(); + auto context = engine ? engine->currentContext() : nullptr; + auto length = context ? context->argumentCount() : 0; + QScriptValue scope = context ? context->thisObject() : QScriptValue::NullValue; + QScriptValue method; + qCInfo(graphics_scripting) << "jsBindCallback" << engine << length << scope.toQObject() << method.toString(); + int i = 0; + for (; context && i < length; i++) { + if (context->argument(i).strictlyEquals(callback)) { + method = context->argument(i+1); + } + } + if (method.isFunction() || method.isString()) { + scope = callback; + } else { + method = callback; + } + qCInfo(graphics_scripting) << "scope:" << scope.toQObject() << "method:" << method.toString(); + return ::makeScopedHandlerObject(scope, method); + } +} + +bool scriptable::GraphicsScriptingInterface::updateMeshPart(ScriptableMeshPointer mesh, ScriptableMeshPartPointer part) { + Q_ASSERT(mesh); + Q_ASSERT(part); + Q_ASSERT(part->parentMesh); + auto tmp = exportMeshPart(mesh, part->partIndex); + if (part->parentMesh == mesh) { + qCInfo(graphics_scripting) << "updateMeshPart -- update via clone" << mesh << part; + tmp->replaceMeshData(part->cloneMeshPart()); + return false; + } else { + qCInfo(graphics_scripting) << "updateMeshPart -- update via inplace" << mesh << part; + tmp->replaceMeshData(part); + return true; + } +} diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h index 257285fa90..c655167c2b 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h @@ -11,142 +11,188 @@ #include +//#include #include +#include #include #include -namespace graphics { - class Mesh; -} -namespace gpu { - class BufferView; -} namespace scriptable { - class ScriptableMeshPart; - using ScriptableMeshPartPointer = QPointer; - class ScriptableMesh : public QObject, QScriptable { + + QScriptValue jsBindCallback(QScriptValue callback); + class ScriptableMesh : public ScriptableMeshBase, QScriptable { Q_OBJECT public: - Q_PROPERTY(quint32 numParts READ getNumParts) - Q_PROPERTY(quint32 numAttributes READ getNumAttributes) - Q_PROPERTY(quint32 numVertices READ getNumVertices) - Q_PROPERTY(quint32 numIndices READ getNumIndices) - Q_PROPERTY(QVariantMap metadata MEMBER _metadata) + Q_PROPERTY(uint32 numParts READ getNumParts) + Q_PROPERTY(uint32 numAttributes READ getNumAttributes) + Q_PROPERTY(uint32 numVertices READ getNumVertices) + Q_PROPERTY(uint32 numIndices READ getNumIndices) + Q_PROPERTY(QVariantMap metadata MEMBER metadata) Q_PROPERTY(QVector attributeNames READ getAttributeNames) + Q_PROPERTY(QVector parts READ getMeshParts) + Q_PROPERTY(bool valid READ hasValidMesh) + bool hasValidMesh() const { return (bool)getMeshPointer(); } + Q_PROPERTY(bool validOwned READ hasValidOwnedMesh) + bool hasValidOwnedMesh() const { return (bool)getOwnedMeshPointer(); } - static QMap ATTRIBUTES; - static std::map gatherBufferViews(MeshPointer mesh, const QStringList& expandToMatchPositions = QStringList()); + operator const ScriptableMeshBase*() const { return (qobject_cast(this)); } + ScriptableMesh(scriptable::MeshPointer mesh) : ScriptableMeshBase(mesh) { ownedMesh = mesh; } + ScriptableMesh(WeakModelProviderPointer provider, ScriptableModelBasePointer model, MeshPointer mesh, const QVariantMap& metadata) + : ScriptableMeshBase(provider, model, mesh, metadata) { ownedMesh = mesh; } + //ScriptableMesh& operator=(const ScriptableMesh& other) { model=other.model; mesh=other.mesh; metadata=other.metadata; return *this; }; + //ScriptableMesh() : QObject(), model(nullptr) {} + //ScriptableMesh(const ScriptableMesh& other) : QObject(), model(other.model), mesh(other.mesh), metadata(other.metadata) {} + ScriptableMesh(const ScriptableMeshBase& other); + ScriptableMesh(const ScriptableMesh& other) : ScriptableMeshBase(other) {}; + virtual ~ScriptableMesh(); - ScriptableMesh& operator=(const ScriptableMesh& other) { _model=other._model; _mesh=other._mesh; _metadata=other._metadata; return *this; }; - ScriptableMesh() : QObject(), _model(nullptr) {} - ScriptableMesh(ScriptableModelPointer parent, scriptable::MeshPointer mesh) : QObject(), _model(parent), _mesh(mesh) {} - ScriptableMesh(const ScriptableMesh& other) : QObject(), _model(other._model), _mesh(other._mesh), _metadata(other._metadata) {} - ~ScriptableMesh() { qDebug() << "~ScriptableMesh" << this; } - - scriptable::MeshPointer getMeshPointer() const { return _mesh; } + Q_INVOKABLE const scriptable::ScriptableModelPointer getParentModel() const { return qobject_cast(model); } + Q_INVOKABLE const scriptable::MeshPointer getOwnedMeshPointer() const { return ownedMesh; } + scriptable::ScriptableMeshPointer getSelf() const { return const_cast(this); } public slots: - quint32 getNumParts() const; - quint32 getNumVertices() const; - quint32 getNumAttributes() const; - quint32 getNumIndices() const { return 0; } + uint32 getNumParts() const; + uint32 getNumVertices() const; + uint32 getNumAttributes() const; + uint32 getNumIndices() const; QVector getAttributeNames() const; + QVector getMeshParts() const; - QVariantMap getVertexAttributes(quint32 vertexIndex) const; - QVariantMap getVertexAttributes(quint32 vertexIndex, QVector attributes) const; + QVariantMap getVertexAttributes(uint32 vertexIndex) const; + QVariantMap getVertexAttributes(uint32 vertexIndex, QVector attributes) const; - QVector getIndices() const; - QVector findNearbyIndices(const glm::vec3& origin, float epsilon = 1e-6) const; + QVector getIndices() const; + QVector findNearbyIndices(const glm::vec3& origin, float epsilon = 1e-6) const; QVariantMap getMeshExtents() const; - bool setVertexAttributes(quint32 vertexIndex, QVariantMap attributes); - QVariantMap scaleToFit(float unitScale); + bool setVertexAttributes(uint32 vertexIndex, QVariantMap attributes); QVariantList getAttributeValues(const QString& attributeName) const; int _getSlotNumber(const QString& attributeName) const; + scriptable::ScriptableMeshPointer cloneMesh(bool recalcNormals = false); + public: + operator bool() const { return !mesh.expired(); } + + public slots: + // QScriptEngine-specific wrappers + uint32 mapAttributeValues(QScriptValue callback); + }; + + // TODO: part-specific wrapper for working with raw geometries + class ScriptableMeshPart : public QObject, QScriptable { + Q_OBJECT + public: + Q_PROPERTY(uint32 partIndex MEMBER partIndex CONSTANT) + Q_PROPERTY(int numElementsPerFace MEMBER _elementsPerFace CONSTANT) + Q_PROPERTY(QString topology MEMBER _topology CONSTANT) + + Q_PROPERTY(uint32 numFaces READ getNumFaces) + Q_PROPERTY(uint32 numAttributes READ getNumAttributes) + Q_PROPERTY(uint32 numVertices READ getNumVertices) + Q_PROPERTY(uint32 numIndices READ getNumIndices) + Q_PROPERTY(QVector attributeNames READ getAttributeNames) + + Q_PROPERTY(QVariantMap metadata MEMBER metadata) + + //Q_PROPERTY(scriptable::ScriptableMeshPointer parentMesh MEMBER parentMesh CONSTANT HIDE) + + ScriptableMeshPart(scriptable::ScriptableMeshPointer parentMesh, int partIndex); + ScriptableMeshPart& operator=(const ScriptableMeshPart& view) { parentMesh=view.parentMesh; return *this; }; + ScriptableMeshPart(const ScriptableMeshPart& other) : parentMesh(other.parentMesh), partIndex(other.partIndex) {} + ~ScriptableMeshPart() { qDebug() << "~ScriptableMeshPart" << this; } + + public slots: + scriptable::ScriptableMeshPointer getParentMesh() const { return parentMesh; } + uint32 getNumAttributes() const { return parentMesh ? parentMesh->getNumAttributes() : 0; } + uint32 getNumVertices() const { return parentMesh ? parentMesh->getNumVertices() : 0; } + uint32 getNumIndices() const { return parentMesh ? parentMesh->getNumIndices() : 0; } + uint32 getNumFaces() const { return parentMesh ? parentMesh->getNumIndices() / _elementsPerFace : 0; } + QVector getAttributeNames() const { return parentMesh ? parentMesh->getAttributeNames() : QVector(); } + QVector getFace(uint32 faceIndex) const { + auto inds = parentMesh ? parentMesh->getIndices() : QVector(); + return faceIndex+2 < (uint32)inds.size() ? inds.mid(faceIndex*3, 3) : QVector(); + } + QVariantMap scaleToFit(float unitScale); QVariantMap translate(const glm::vec3& translation); QVariantMap scale(const glm::vec3& scale, const glm::vec3& origin = glm::vec3(NAN)); QVariantMap rotateDegrees(const glm::vec3& eulerAngles, const glm::vec3& origin = glm::vec3(NAN)); QVariantMap rotate(const glm::quat& rotation, const glm::vec3& origin = glm::vec3(NAN)); QVariantMap transform(const glm::mat4& transform); - public: - operator bool() const { return _mesh != nullptr; } - ScriptableModelPointer _model; - scriptable::MeshPointer _mesh; - QVariantMap _metadata; + bool unrollVertices(bool recalcNormals = false); + bool dedupeVertices(float epsilon = 1e-6); + bool recalculateNormals() { return buffer_helpers::recalculateNormals(getMeshPointer()); } + + bool replaceMeshData(scriptable::ScriptableMeshPartPointer source, const QVector& attributeNames = QVector()); + scriptable::ScriptableMeshPartPointer cloneMeshPart(bool recalcNormals = false) { + if (parentMesh) { + if (auto clone = parentMesh->cloneMesh(recalcNormals)) { + return clone->getMeshParts().value(partIndex); + } + } + return nullptr; + } + QString toOBJ(); public slots: // QScriptEngine-specific wrappers - QScriptValue mapAttributeValues(QScriptValue scopeOrCallback, QScriptValue methodOrName = QScriptValue()); - bool dedupeVertices(float epsilon = 1e-6); - bool recalculateNormals(); - QScriptValue cloneMesh(bool recalcNormals = true); - QScriptValue unrollVertices(bool recalcNormals = true); - bool replaceMeshData(scriptable::ScriptableMeshPointer source, const QVector& attributeNames = QVector()); - QString toOBJ(); - }; - - // TODO: part-specific wrapper for working with raw geometries - class ScriptableMeshPart : public QObject { - Q_OBJECT - public: - Q_PROPERTY(QString topology READ getTopology) - Q_PROPERTY(quint32 numFaces READ getNumFaces) - - ScriptableMeshPart& operator=(const ScriptableMeshPart& view) { parentMesh=view.parentMesh; return *this; }; - ScriptableMeshPart(const ScriptableMeshPart& other) : parentMesh(other.parentMesh) {} - ScriptableMeshPart() {} - ~ScriptableMeshPart() { qDebug() << "~ScriptableMeshPart" << this; } - - public slots: - QString getTopology() const { return "triangles"; } - quint32 getNumFaces() const { return parentMesh.getIndices().size() / 3; } - QVector getFace(quint32 faceIndex) const { - auto inds = parentMesh.getIndices(); - return faceIndex+2 < (quint32)inds.size() ? inds.mid(faceIndex*3, 3) : QVector(); - } + uint32 mapAttributeValues(QScriptValue callback); public: - scriptable::ScriptableMesh parentMesh; - int partIndex; + scriptable::ScriptableMeshPointer parentMesh; + uint32 partIndex; + QVariantMap metadata; + protected: + int _elementsPerFace{ 3 }; + QString _topology{ "triangles" }; + scriptable::MeshPointer getMeshPointer() const { return parentMesh ? parentMesh->getMeshPointer() : nullptr; } }; - class GraphicsScriptingInterface : public QObject { + class GraphicsScriptingInterface : public QObject, QScriptable { Q_OBJECT public: GraphicsScriptingInterface(QObject* parent = nullptr) : QObject(parent) {} GraphicsScriptingInterface(const GraphicsScriptingInterface& other) {} - public slots: - ScriptableMeshPart exportMeshPart(ScriptableMesh mesh, int part) { return {}; } + public slots: + ScriptableMeshPartPointer exportMeshPart(ScriptableMeshPointer mesh, int part=0) { + return ScriptableMeshPartPointer(new ScriptableMeshPart(mesh, part)); + } + bool updateMeshPart(ScriptableMeshPointer mesh, ScriptableMeshPartPointer part); }; + + // callback helper that lets C++ method signatures remain simple (ie: taking a single callback argument) while + // still supporting extended Qt signal-like (scope, "methodName") and (scope, function(){}) "this" binding conventions + QScriptValue jsBindCallback(QScriptValue callback); + + // derive a corresponding C++ class instance from the current script engine's thisObject + template T this_qobject_cast(QScriptEngine* engine); } Q_DECLARE_METATYPE(scriptable::ScriptableMeshPointer) Q_DECLARE_METATYPE(QVector) Q_DECLARE_METATYPE(scriptable::ScriptableMeshPartPointer) +Q_DECLARE_METATYPE(QVector) Q_DECLARE_METATYPE(scriptable::GraphicsScriptingInterface) // FIXME: MESHFACES: faces were supported in the original Model.* API -- are they still needed/used/useful for anything yet? #include namespace mesh { - using uint32 = quint32; class MeshFace; using MeshFaces = QVector; class MeshFace { public: MeshFace() {} - MeshFace(QVector vertexIndices) : vertexIndices(vertexIndices) {} + MeshFace(QVector vertexIndices) : vertexIndices(vertexIndices) {} ~MeshFace() {} - QVector vertexIndices; + QVector vertexIndices; // TODO -- material... }; }; Q_DECLARE_METATYPE(mesh::MeshFace) Q_DECLARE_METATYPE(QVector) -Q_DECLARE_METATYPE(mesh::uint32) -Q_DECLARE_METATYPE(QVector) +Q_DECLARE_METATYPE(scriptable::uint32) +Q_DECLARE_METATYPE(QVector) diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h index 4ba5a993b1..97a73ddd61 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h @@ -1,56 +1,24 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include +#include "Forward.h" -#include - -namespace graphics { - class Mesh; -} -namespace gpu { - class BufferView; -} class QScriptValue; - namespace scriptable { - using Mesh = graphics::Mesh; - using MeshPointer = std::shared_ptr; - - class ScriptableModel; - using ScriptableModelPointer = QPointer; - class ScriptableMesh; - using ScriptableMeshPointer = QPointer; - - // abstract container for holding one or more scriptable meshes - class ScriptableModel : public QObject { + class ScriptableModel : public ScriptableModelBase { Q_OBJECT public: - QUuid objectID; - QVariantMap metadata; - QVector meshes; - - Q_PROPERTY(QVector meshes READ getMeshes) Q_PROPERTY(QUuid objectID MEMBER objectID CONSTANT) Q_PROPERTY(QVariantMap metadata MEMBER metadata CONSTANT) - Q_INVOKABLE QString toString() const; + Q_PROPERTY(uint32 numMeshes READ getNumMeshes) + Q_PROPERTY(QVector meshes READ getMeshes) - ScriptableModel(QObject* parent = nullptr) : QObject(parent) {} - ScriptableModel(const ScriptableModel& other) : objectID(other.objectID), metadata(other.metadata), meshes(other.meshes) {} - ScriptableModel& operator=(const ScriptableModel& view) { objectID = view.objectID; metadata = view.metadata; meshes = view.meshes; return *this; } - ~ScriptableModel() { qDebug() << "~ScriptableModel" << this; } - - void mixin(const ScriptableModel& other) { - for (const auto& key : other.metadata.keys()) { metadata[key] = other.metadata[key]; } - for (const auto& mesh : other.meshes) { meshes << mesh; } - } + ScriptableModel(QObject* parent = nullptr) : ScriptableModelBase(parent) {} + ScriptableModel(const ScriptableModel& other) : ScriptableModelBase(other) {} + ScriptableModel(const ScriptableModelBase& other) : ScriptableModelBase(other) {} + ScriptableModel& operator=(const ScriptableModelBase& view) { ScriptableModelBase::operator=(view); return *this; } + virtual ~ScriptableModel() { qDebug() << "~ScriptableModel" << this; } + Q_INVOKABLE scriptable::ScriptableModelPointer cloneModel(const QVariantMap& options = QVariantMap()); // TODO: in future accessors for these could go here // QVariantMap shapes; // QVariantMap materials; @@ -58,28 +26,23 @@ namespace scriptable { QVector getMeshes(); const QVector getConstMeshes() const; + operator scriptable::ScriptableModelBasePointer() { + QPointer p; + p = qobject_cast(this); + return p; + } + // QScriptEngine-specific wrappers - Q_INVOKABLE QScriptValue mapAttributeValues(QScriptValue scopeOrCallback, QScriptValue methodOrName); + Q_INVOKABLE uint32 mapAttributeValues(QScriptValue callback); + Q_INVOKABLE QString toString() const; + Q_INVOKABLE uint32 getNumMeshes() { return meshes.size(); } }; - // mixin class for Avatar/Entity/Overlay Rendering that expose their in-memory graphics::Meshes - class ModelProvider { - public: - QVariantMap metadata; - static scriptable::ScriptableModel modelUnavailableError(bool* ok) { if (ok) { *ok = false; } return {}; } - virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) = 0; - }; - using ModelProviderPointer = std::shared_ptr; - - // mixin class for Application to resolve UUIDs into a corresponding ModelProvider - class ModelProviderFactory : public Dependency { - public: - virtual scriptable::ModelProviderPointer lookupModelProvider(QUuid uuid) = 0; - }; } Q_DECLARE_METATYPE(scriptable::MeshPointer) -Q_DECLARE_METATYPE(scriptable::ScriptableModel) +Q_DECLARE_METATYPE(scriptable::WeakMeshPointer) Q_DECLARE_METATYPE(scriptable::ScriptableModelPointer) - +Q_DECLARE_METATYPE(scriptable::ScriptableModelBase) +Q_DECLARE_METATYPE(scriptable::ScriptableModelBasePointer)