From a08770c816e46a2d0c2f13e1c4c92076ac130b91 Mon Sep 17 00:00:00 2001 From: humbletim Date: Fri, 9 Feb 2018 02:14:32 -0500 Subject: [PATCH] cleanup --- libraries/fbx/src/OBJWriter.cpp | 6 +- .../graphics-scripting/BufferViewHelpers.cpp | 55 ++ .../graphics-scripting/BufferViewHelpers.h | 9 +- .../ModelScriptingInterface.cpp | 641 ++++-------------- .../ModelScriptingInterface.h | 16 +- .../src/graphics-scripting/ScriptableMesh.cpp | 403 ++++++++++- .../src/graphics-scripting/ScriptableMesh.h | 123 ++-- .../src/graphics-scripting/ScriptableModel.h | 67 +- libraries/render-utils/src/Model.cpp | 58 +- 9 files changed, 722 insertions(+), 656 deletions(-) diff --git a/libraries/fbx/src/OBJWriter.cpp b/libraries/fbx/src/OBJWriter.cpp index 37bced8458..5307f49f36 100644 --- a/libraries/fbx/src/OBJWriter.cpp +++ b/libraries/fbx/src/OBJWriter.cpp @@ -15,6 +15,9 @@ #include "OBJWriter.h" #include "ModelFormatLogging.h" +// FIXME: should this live in shared? (it depends on gpu/) +#include <../graphics-scripting/src/graphics-scripting/BufferViewHelpers.h> + static QString formatFloat(double n) { // limit precision to 6, but don't output trailing zeros. QString s = QString::number(n, 'f', 6); @@ -91,7 +94,8 @@ bool writeOBJToTextStream(QTextStream& out, QList meshes) { const gpu::BufferView& normalsBufferView = mesh->getAttributeBuffer(gpu::Stream::InputSlot::NORMAL); gpu::BufferView::Index numNormals = (gpu::BufferView::Index)normalsBufferView.getNumElements(); for (gpu::BufferView::Index i = 0; i < numNormals; i++) { - glm::vec3 normal = normalsBufferView.get(i); + glm::vec3 normal = glmVecFromVariant(bufferViewElementToVariant(normalsBufferView, i)); + //glm::vec3 normal = normalsBufferView.get(i); out << "vn "; out << formatFloat(normal[0]) << " "; out << formatFloat(normal[1]) << " "; diff --git a/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.cpp b/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.cpp index e865ed0e5a..d83322f360 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.cpp @@ -8,9 +8,15 @@ #include #include +namespace glm { + using hvec2 = glm::tvec2; + using hvec4 = glm::tvec4; +} +//#define DEBUG_BUFFERVIEW_SCRIPTING #ifdef DEBUG_BUFFERVIEW_SCRIPTING #include "DebugNames.h" + QLoggingCategory bufferview_helpers{"hifi.bufferview"}; #endif namespace { @@ -61,6 +67,9 @@ bool bufferViewElementFromVariant(const gpu::BufferView& view, quint32 index, co 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; +#endif if (BYTES_PER_ELEMENT == 1) { switch(vecN) { case 2: setBufferViewElement(view, index, v); return true; @@ -71,16 +80,25 @@ bool bufferViewElementFromVariant(const gpu::BufferView& view, quint32 index, co glm::uint32 unused; packNormalAndTangent(glmVecFromVariant(v), glm::vec3(), rawColor, unused); view.edit(index) = rawColor; + return true; } else if (element == gpu::Element::VEC4F_NORMALIZED_XYZ10W2) { glm::uint32 packedNormal;// = glm::packSnorm3x10_1x2(glm::vec4(glmVecFromVariant(v), 0.0f)); glm::uint32 unused; packNormalAndTangent(glm::vec3(), glmVecFromVariant(v), unused, packedNormal); view.edit(index) = packedNormal; + return true; } setBufferViewElement(view, index, v); return true; } } } else if (BYTES_PER_ELEMENT == 2) { + if (dataType == gpu::HALF) { + switch(vecN) { + case 2: view.edit(index) = glm::packSnorm2x8(glmVecFromVariant(v)); return true; + case 4: view.edit(index) = glm::packSnorm4x8(glmVecFromVariant(v)); return true; + default: return false; + } + } switch(vecN) { case 2: setBufferViewElement(view, index, v); return true; case 3: setBufferViewElement(view, index, v); return true; @@ -112,6 +130,9 @@ QVariant bufferViewElementToVariant(const gpu::BufferView& view, quint32 index, 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)); +#ifdef DEBUG_BUFFERVIEW_SCRIPTING + qCDebug(bufferview_helpers) << "bufferViewElementToVariant" << index << DebugNames::stringFrom(dataType) << BYTES_PER_ELEMENT << vecN; +#endif if (BYTES_PER_ELEMENT == 1) { switch(vecN) { case 2: return getBufferViewElement(view, index, asArray); @@ -129,6 +150,12 @@ QVariant bufferViewElementToVariant(const gpu::BufferView& view, quint32 index, } } } else if (BYTES_PER_ELEMENT == 2) { + if (dataType == gpu::HALF) { + switch(vecN) { + case 2: return glmVecToVariant(glm::vec2(glm::unpackSnorm2x8(view.get(index)))); + case 4: return glmVecToVariant(glm::vec4(glm::unpackSnorm4x8(view.get(index)))); + } + } switch(vecN) { case 2: return getBufferViewElement(view, index, asArray); case 3: return getBufferViewElement(view, index, asArray); @@ -193,3 +220,31 @@ const T glmVecFromVariant(const QVariant& v) { return result; } +template +gpu::BufferView bufferViewFromVector(QVector elements, 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 bufferViewFromVector(QVector elements, gpu::Element elementType) { return bufferViewFromVector(elements, elementType); } +template<> gpu::BufferView bufferViewFromVector(QVector elements, gpu::Element elementType) { return bufferViewFromVector(elements, elementType); } + +gpu::BufferView cloneBufferView(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) { + auto effectiveSize = input._buffer->getSize() / input.getNumElements(); + qDebug() << "resize input" << input.getNumElements() << input._buffer->getSize() << "effectiveSize" << effectiveSize; + auto vsize = input._element.getSize() * numElements; + gpu::Byte *data = new gpu::Byte[vsize]; + memset(data, 0, vsize); + auto buffer = new gpu::Buffer(vsize, (gpu::Byte*)data); + delete[] data; + auto output = gpu::BufferView(buffer, input._element); + qDebug() << "resized output" << output.getNumElements() << output._buffer->getSize(); + return output; +} diff --git a/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h b/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h index 0fe2602f6c..d0d42ca419 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h +++ b/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h @@ -8,11 +8,18 @@ #include -namespace gpu { class BufferView; } +namespace gpu { + class BufferView; + class Element; +} 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); + +gpu::BufferView cloneBufferView(const gpu::BufferView& input); +gpu::BufferView resizedBufferView(const gpu::BufferView& input, quint32 numElements); diff --git a/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp index 68a00bc02c..ab85fb8265 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp @@ -17,27 +17,16 @@ #include "BaseScriptEngine.h" #include "ScriptEngineLogging.h" #include "OBJWriter.h" -#include "OBJReader.h" -//#include "ui/overlays/Base3DOverlay.h" -//#include "EntityTreeRenderer.h" -//#include "avatar/AvatarManager.h" -//#include "RenderableEntityItem.h" -#include #include - #include - #include - #include + #include "BufferViewScripting.h" - #include "ScriptableMesh.h" -using ScriptableMesh = scriptable::ScriptableMesh; - #include "ModelScriptingInterface.moc" namespace { @@ -50,13 +39,56 @@ ModelScriptingInterface::ModelScriptingInterface(QObject* parent) : QObject(pare } } +void ModelScriptingInterface::getMeshes(QUuid uuid, QScriptValue scopeOrCallback, QScriptValue methodOrName) { + auto handler = makeScopedHandlerObject(scopeOrCallback, methodOrName); + Q_ASSERT(handler.engine() == this->engine()); + QPointer engine = dynamic_cast(handler.engine()); + + scriptable::ScriptableModel* meshes{ nullptr }; + bool success = false; + QString error; + + auto appProvider = DependencyManager::get(); + qDebug() << "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 << "..."; + auto scriptableMeshes = provider->getScriptableModel(&success); + qCDebug(model_scripting) << "//fetched meshes from " << providerType << "success:" <objectName().isEmpty()) { + meshes->setObjectName(providerType+"::meshes"); + } + if (meshes->objectID.isNull()) { + meshes->objectID = uuid.toString(); + } + meshes->metadata["provider"] = provider->metadata; + } + } + if (!success) { + error = QString("failed to get meshes from %1 provider for uuid %2").arg(providerType).arg(uuid.toString()); + } + + if (!error.isEmpty()) { + qCWarning(model_scripting) << "ModelScriptingInterface::getMeshes ERROR" << error; + callScopedHandlerObject(handler, engine->makeError(error), QScriptValue::NullValue); + } else { + callScopedHandlerObject(handler, QScriptValue::NullValue, engine->newQObject(meshes, QScriptEngine::ScriptOwnership)); + } +} + QString ModelScriptingInterface::meshToOBJ(const scriptable::ScriptableModel& _in) { - const auto& in = _in.getMeshes(); + const auto& in = _in.getConstMeshes(); qCDebug(model_scripting) << "meshToOBJ" << in.size(); if (in.size()) { QList meshes; - foreach (const auto meshProxy, in) { - qCDebug(model_scripting) << "meshToOBJ" << meshProxy.get(); + foreach (auto meshProxy, in) { + qCDebug(model_scripting) << "meshToOBJ" << meshProxy; if (meshProxy) { meshes.append(getMeshPointer(meshProxy)); } @@ -77,7 +109,7 @@ QScriptValue ModelScriptingInterface::appendMeshes(scriptable::ScriptableModel _ size_t totalColorCount { 0 }; size_t totalNormalCount { 0 }; size_t totalIndexCount { 0 }; - foreach (const scriptable::ScriptableMeshPointer meshProxy, in) { + foreach (auto& meshProxy, in) { scriptable::MeshPointer mesh = getMeshPointer(meshProxy); totalVertexCount += mesh->getNumVertices(); @@ -113,7 +145,7 @@ QScriptValue ModelScriptingInterface::appendMeshes(scriptable::ScriptableModel _ uint32_t indexStartOffset { 0 }; - foreach (const scriptable::ScriptableMeshPointer meshProxy, in) { + foreach (const auto& meshProxy, in) { scriptable::MeshPointer mesh = getMeshPointer(meshProxy); mesh->forEach( [&](glm::vec3 position){ @@ -175,8 +207,7 @@ QScriptValue ModelScriptingInterface::appendMeshes(scriptable::ScriptableModel _ (gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL)); - scriptable::ScriptableMeshPointer resultProxy = scriptable::ScriptableMeshPointer(new ScriptableMesh(nullptr, result)); - return engine()->toScriptValue(result); + return engine()->toScriptValue(scriptable::ScriptableMeshPointer(new scriptable::ScriptableMesh(nullptr, result))); } QScriptValue ModelScriptingInterface::transformMesh(scriptable::ScriptableMeshPointer meshProxy, glm::mat4 transform) { @@ -189,7 +220,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 ScriptableMesh(nullptr, result)); + scriptable::ScriptableMeshPointer resultProxy = scriptable::ScriptableMeshPointer(new scriptable::ScriptableMesh(nullptr, result)); return engine()->toScriptValue(resultProxy); } @@ -201,8 +232,11 @@ QScriptValue ModelScriptingInterface::getVertexCount(scriptable::ScriptableMeshP return (uint32_t)mesh->getNumVertices(); } -QScriptValue ModelScriptingInterface::getVertex(scriptable::ScriptableMeshPointer meshProxy, mesh::uint32 vertexIndex) { +QScriptValue ModelScriptingInterface::getVertex(scriptable::ScriptableMeshPointer meshProxy, quint32 vertexIndex) { auto mesh = getMeshPointer(meshProxy); + if (!mesh) { + return QScriptValue(); + } const gpu::BufferView& vertexBufferView = mesh->getVertexBuffer(); auto numVertices = mesh->getNumVertices(); @@ -266,480 +300,46 @@ QScriptValue ModelScriptingInterface::newMesh(const QVector& vertices - scriptable::ScriptableMeshPointer meshProxy = scriptable::ScriptableMeshPointer(new ScriptableMesh(nullptr, mesh)); + scriptable::ScriptableMeshPointer meshProxy = scriptable::ScriptableMeshPointer(new scriptable::ScriptableMesh(nullptr, mesh)); return engine()->toScriptValue(meshProxy); } -QScriptValue ModelScriptingInterface::mapAttributeValues( - QScriptValue _in, - QScriptValue scopeOrCallback, - QScriptValue methodOrName - ) { - qCInfo(model_scripting) << "mapAttributeValues" << _in.toVariant().typeName() << _in.toVariant().toString() << _in.toQObject(); - auto in = qscriptvalue_cast(_in).getMeshes(); - if (in.size()) { - foreach (scriptable::ScriptableMeshPointer meshProxy, in) { - mapMeshAttributeValues(meshProxy, scopeOrCallback, methodOrName); - } - return thisObject(); - } else if (auto meshProxy = qobject_cast(_in.toQObject())) { - return mapMeshAttributeValues(meshProxy->shared_from_this(), scopeOrCallback, methodOrName); - } else { - context()->throwError("invalid ModelProxy || MeshProxyPointer"); - } - return false; -} - - -QScriptValue ModelScriptingInterface::unrollVertices(scriptable::ScriptableMeshPointer meshProxy, bool recalcNormals) { - auto mesh = getMeshPointer(meshProxy); - qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices" << !!mesh<< !!meshProxy; - if (!mesh) { - return QScriptValue(); - } - - auto positions = mesh->getVertexBuffer(); - auto indices = mesh->getIndexBuffer(); - quint32 numPoints = (quint32)indices.getNumElements(); - auto buffer = new gpu::Buffer(); - buffer->resize(numPoints * sizeof(uint32_t)); - auto newindices = gpu::BufferView(buffer, { gpu::SCALAR, gpu::UINT32, gpu::INDEX }); - qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices numPoints" << numPoints; - auto attributeViews = ScriptableMesh::gatherBufferViews(mesh); - for (const auto& a : attributeViews) { - auto& view = a.second; - auto sz = view._element.getSize(); - auto buffer = new gpu::Buffer(); - buffer->resize(numPoints * sz); - 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(model_scripting) << "ModelScriptingInterface::unrollVertices buffer" << a.first; - qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices source" << view.getNumElements(); - qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices dest" << points.getNumElements(); - qCInfo(model_scripting) << "ModelScriptingInterface::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( - points, i, - bufferViewElementToVariant(view, index, false, hint) - ); - } - if (slot == gpu::Stream::POSITION) { - mesh->setVertexBuffer(points); - } else { - mesh->addAttribute(slot, points); - } - } - mesh->setIndexBuffer(newindices); - if (recalcNormals) { - recalculateNormals(meshProxy); - } - return true; -} - namespace { - template - gpu::BufferView bufferViewFromVector(QVector elements, gpu::Element elementType) { - auto vertexBuffer = std::make_shared( - elements.size() * sizeof(T), - (gpu::Byte*)elements.data() - ); - return { vertexBuffer, 0, vertexBuffer->getSize(),sizeof(T), elementType }; - } - - gpu::BufferView cloneBufferView(const gpu::BufferView& input) { - //qCInfo(model_scripting) << "input" << input.getNumElements() << input._buffer->getSize(); - auto output = gpu::BufferView( - std::make_shared(input._buffer->getSize(), input._buffer->getData()), - input._offset, - input._size, - input._stride, - input._element - ); - //qCInfo(model_scripting) << "after" << output.getNumElements() << output._buffer->getSize(); - return output; - } - - gpu::BufferView resizedBufferView(const gpu::BufferView& input, quint32 numElements) { - auto effectiveSize = input._buffer->getSize() / input.getNumElements(); - qCInfo(model_scripting) << "resize input" << input.getNumElements() << input._buffer->getSize() << "effectiveSize" << effectiveSize; - auto vsize = input._element.getSize() * numElements; - gpu::Byte *data = new gpu::Byte[vsize]; - memset(data, 0, vsize); - auto buffer = new gpu::Buffer(vsize, (gpu::Byte*)data); - delete[] data; - auto output = gpu::BufferView(buffer, input._element); - qCInfo(model_scripting) << "resized output" << output.getNumElements() << output._buffer->getSize(); - return output; - } -} - -bool ModelScriptingInterface::replaceMeshData(scriptable::ScriptableMeshPointer dest, scriptable::ScriptableMeshPointer src, const QVector& attributeNames) { - auto target = getMeshPointer(dest); - auto source = getMeshPointer(src); - if (!target || !source) { - context()->throwError("ModelScriptingInterface::replaceMeshData -- expected dest and src to be valid mesh proxy pointers"); - return false; - } - - QVector attributes = attributeNames.isEmpty() ? src->getAttributeNames() : attributeNames; - - //qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData -- source:" << source->displayName << "target:" << target->displayName << "attributes:" << attributes; - - // remove attributes only found on target mesh, unless user has explicitly specified the relevant attribute names - if (attributeNames.isEmpty()) { - auto attributeViews = ScriptableMesh::gatherBufferViews(target); - for (const auto& a : attributeViews) { - auto slot = ScriptableMesh::ATTRIBUTES[a.first]; - if (!attributes.contains(a.first)) { - //qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData -- pruning target attribute" << a.first << slot; - target->removeAttribute(slot); - } + QScriptValue meshPointerToScriptValue(QScriptEngine* engine, scriptable::ScriptableMeshPointer const &in) { + if (!in) { + return QScriptValue::NullValue; } + return engine->newQObject(in, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects); } - target->setVertexBuffer(cloneBufferView(source->getVertexBuffer())); - target->setIndexBuffer(cloneBufferView(source->getIndexBuffer())); - target->setPartBuffer(cloneBufferView(source->getPartBuffer())); - - for (const auto& a : attributes) { - auto slot = ScriptableMesh::ATTRIBUTES[a]; - if (slot == gpu::Stream::POSITION) { - continue; - } - // auto& before = target->getAttributeBuffer(slot); - auto& input = source->getAttributeBuffer(slot); - if (input.getNumElements() == 0) { - //qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData buffer is empty -- pruning" << a << slot; - target->removeAttribute(slot); - } else { - // if (before.getNumElements() == 0) { - // qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData target buffer is empty -- adding" << a << slot; - // } else { - // qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData target buffer exists -- updating" << a << slot; - // } - target->addAttribute(slot, cloneBufferView(input)); - } - // auto& after = target->getAttributeBuffer(slot); - // qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData" << a << slot << before.getNumElements() << " -> " << after.getNumElements(); - } - - - return true; -} - -bool ModelScriptingInterface::dedupeVertices(scriptable::ScriptableMeshPointer meshProxy, float epsilon) { - auto mesh = getMeshPointer(meshProxy); - if (!mesh) { - return false; - } - auto positions = mesh->getVertexBuffer(); - auto numPositions = positions.getNumElements(); - const auto epsilon2 = epsilon*epsilon; - - QVector uniqueVerts; - uniqueVerts.reserve((int)numPositions); - QMap remapIndices; - - for (quint32 i = 0; i < numPositions; i++) { - const quint32 numUnique = uniqueVerts.size(); - const auto& position = positions.get(i); - bool unique = true; - for (quint32 j = 0; j < numUnique; j++) { - if (glm::length2(uniqueVerts[j] - position) <= epsilon2) { - remapIndices[i] = j; - unique = false; - break; - } - } - if (unique) { - uniqueVerts << position; - remapIndices[i] = numUnique; - } - } - - qCInfo(model_scripting) << "//VERTS before" << numPositions << "after" << uniqueVerts.size(); - - auto indices = mesh->getIndexBuffer(); - auto numIndices = indices.getNumElements(); - auto esize = indices._element.getSize(); - QVector newIndices; - newIndices.reserve((int)numIndices); - for (quint32 i = 0; i < numIndices; i++) { - quint32 index = esize == 4 ? indices.get(i) : indices.get(i); - if (remapIndices.contains(index)) { - //qCInfo(model_scripting) << i << index << "->" << remapIndices[index]; - newIndices << remapIndices[index]; - } else { - qCInfo(model_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 })); - - auto attributeViews = ScriptableMesh::gatherBufferViews(mesh); - quint32 numUniqueVerts = uniqueVerts.size(); - for (const auto& a : attributeViews) { - auto& view = a.second; - auto slot = ScriptableMesh::ATTRIBUTES[a.first]; - if (slot == gpu::Stream::POSITION) { - continue; - } - qCInfo(model_scripting) << "ModelScriptingInterface::dedupeVertices" << a.first << slot << view.getNumElements(); - auto newView = resizedBufferView(view, numUniqueVerts); - qCInfo(model_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( - newView, toVertexIndex, - bufferViewElementToVariant(view, fromVertexIndex, false, "dedupe") - ); - } - mesh->addAttribute(slot, newView); - } - return true; -} - -QScriptValue ModelScriptingInterface::cloneMesh(scriptable::ScriptableMeshPointer meshProxy, bool recalcNormals) { - auto mesh = getMeshPointer(meshProxy); - if (!mesh) { - return QScriptValue::NullValue; - } - graphics::MeshPointer clone(new graphics::Mesh()); - clone->displayName = mesh->displayName + "-clone"; - qCInfo(model_scripting) << "ModelScriptingInterface::cloneMesh" << !!mesh<< !!meshProxy; - 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(model_scripting) << "ModelScriptingInterface::cloneVertices buffer" << a.first << slot; - auto points = cloneBufferView(view); - qCInfo(model_scripting) << "ModelScriptingInterface::cloneVertices source" << view.getNumElements(); - qCInfo(model_scripting) << "ModelScriptingInterface::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)); - if (recalcNormals) { - recalculateNormals(result); - } - return engine()->toScriptValue(result); -} - -bool ModelScriptingInterface::recalculateNormals(scriptable::ScriptableMeshPointer meshProxy) { - qCInfo(model_scripting) << "Recalculating normals" << !!meshProxy; - auto mesh = getMeshPointer(meshProxy); - 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(model_scripting) << 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(model_scripting) << 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(model_scripting) << "no faces for key!?" << key; - } - normal = verts.get(j); - } - if (glm::isnan(normal.x)) { - static int logged = 0; - if (logged++ < 10) { - qCInfo(model_scripting) << "isnan(normal.x)" << j << vec3toVariant(normal); - } - break; - } - normals.edit(j) = glm::normalize(normal); - } - return true; -} - -QScriptValue ModelScriptingInterface::mapMeshAttributeValues( - scriptable::ScriptableMeshPointer meshProxy, QScriptValue scopeOrCallback, QScriptValue methodOrName -) { - auto mesh = getMeshPointer(meshProxy); - if (!mesh) { - return false; - } - auto scopedHandler = makeScopedHandlerObject(scopeOrCallback, methodOrName); - - // input buffers - gpu::BufferView positions = mesh->getVertexBuffer(); - - const auto nPositions = positions.getNumElements(); - - // 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 = js->toScriptValue(meshProxy); - - auto obj = js->newObject(); - auto attributeViews = ScriptableMesh::gatherBufferViews(mesh, { "normal", "color" }); - for (uint32_t i=0; 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; - } - - if (result.isBool() && !result.toBool()) { - // bail without modifying data if user explicitly returns false - continue; - } - if (result.isObject() && !result.strictlyEquals(obj)) { - // user returned a new object (ie: instead of modifying input properties) - obj = result; - } - - for (const auto& a : attributeViews) { - const auto& attribute = obj.property(a.first); - auto& view = a.second; - if (attribute.isValid()) { - bufferViewElementFromScriptValue(attribute, view, i); - } - } - } - return thisObject(); -} - -void ModelScriptingInterface::getMeshes(QUuid uuid, QScriptValue scopeOrCallback, QScriptValue methodOrName) { - auto handler = makeScopedHandlerObject(scopeOrCallback, methodOrName); - Q_ASSERT(handler.engine() == this->engine()); - QPointer engine = dynamic_cast(handler.engine()); - - scriptable::ScriptableModel meshes; - bool success = false; - QString error; - - auto appProvider = DependencyManager::get(); - qDebug() << "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 << "..."; - auto scriptableMeshes = provider->getScriptableModel(&success); - qCDebug(model_scripting) << "//fetched meshes from " << providerType << "success:" <makeError(error), QScriptValue::NullValue); - } else { - callScopedHandlerObject(handler, QScriptValue::NullValue, engine->toScriptValue(meshes)); - } -} - -namespace { - QScriptValue meshToScriptValue(QScriptEngine* engine, scriptable::ScriptableMeshPointer const &in) { - return engine->newQObject(in.get(), QScriptEngine::QtOwnership, - QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects - ); - } - - void meshFromScriptValue(const QScriptValue& value, scriptable::ScriptableMeshPointer &out) { + void meshPointerFromScriptValue(const QScriptValue& value, scriptable::ScriptableMeshPointer &out) { auto obj = value.toQObject(); - //qDebug() << "meshFromScriptValue" << obj; + qDebug() << "meshPointerFromScriptValue" << obj; if (auto tmp = qobject_cast(obj)) { - out = tmp->shared_from_this(); + out = tmp; } // FIXME: Why does above cast not work on Win32!? if (!out) { - auto smp = static_cast(obj); - //qDebug() << "meshFromScriptValue2" << smp; - out = smp->shared_from_this(); + if (auto smp = static_cast(obj)) { + qDebug() << "meshPointerFromScriptValue2" << smp; + out = smp; + } } } - QScriptValue meshesToScriptValue(QScriptEngine* engine, const scriptable::ScriptableModelPointer &in) { - // QScriptValueList result; - QScriptValue result = engine->newArray(); - int i = 0; - foreach(scriptable::ScriptableMeshPointer const meshProxy, in->getMeshes()) { - result.setProperty(i++, meshToScriptValue(engine, meshProxy)); - } - return result; + 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 meshesFromScriptValue(const QScriptValue& value, scriptable::ScriptableModelPointer &out) { + void modelPointerFromScriptValue(const QScriptValue& value, scriptable::ScriptableModelPointer &out) { const auto length = value.property("length").toInt32(); - qCDebug(model_scripting) << "in meshesFromScriptValue, length =" << length; + 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()); @@ -749,79 +349,64 @@ namespace { } } - void modelProxyFromScriptValue(const QScriptValue& object, scriptable::ScriptableModel &meshes) { - auto meshesProperty = object.property("meshes"); - if (meshesProperty.property("length").toInt32() > 0) { - //meshes._meshes = qobject_cast(meshesProperty.toQObject()); - // qDebug() << "modelProxyFromScriptValue" << meshesProperty.property("length").toInt32() << meshesProperty.toVariant().typeName(); - qScriptValueToSequence(meshesProperty, meshes.meshes); - } else if (auto mesh = qobject_cast(object.toQObject())) { - meshes.meshes << mesh->getMeshPointer(); - } else { - qDebug() << "modelProxyFromScriptValue -- unrecognized input" << object.toVariant().toString(); - } + // 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); + // } - meshes.metadata = object.property("metadata").toVariant().toMap(); - } - - QScriptValue modelProxyToScriptValue(QScriptEngine* engine, const scriptable::ScriptableModel &in) { - QScriptValue obj = engine->newObject(); - obj.setProperty("meshes", qScriptValueFromSequence(engine, in.meshes)); - obj.setProperty("metadata", engine->toScriptValue(in.metadata)); - return obj; - } - - 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) { + QScriptValue qVectorUInt32ToScriptValue(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) { + void qVectorUInt32FromScriptValue(const QScriptValue& array, QVector& result) { qScriptValueToSequence(array, result); } } -int meshUint32 = qRegisterMetaType(); +int meshUint32 = qRegisterMetaType(); namespace mesh { int meshUint32 = qRegisterMetaType(); } -int qVectorMeshUint32 = qRegisterMetaType>(); +int qVectorMeshUint32 = qRegisterMetaType>(); void ModelScriptingInterface::registerMetaTypes(QScriptEngine* engine) { qScriptRegisterSequenceMetaType>(engine); - qScriptRegisterSequenceMetaType(engine); - qScriptRegisterSequenceMetaType>(engine); - qScriptRegisterMetaType(engine, modelProxyToScriptValue, modelProxyFromScriptValue); + qScriptRegisterSequenceMetaType>(engine); qScriptRegisterMetaType(engine, qVectorUInt32ToScriptValue, qVectorUInt32FromScriptValue); - qScriptRegisterMetaType(engine, meshToScriptValue, meshFromScriptValue); - qScriptRegisterMetaType(engine, meshesToScriptValue, meshesFromScriptValue); - qScriptRegisterMetaType(engine, meshFaceToScriptValue, meshFaceFromScriptValue); - qScriptRegisterMetaType(engine, qVectorMeshFaceToScriptValue, qVectorMeshFaceFromScriptValue); + qScriptRegisterMetaType(engine, meshPointerToScriptValue, meshPointerFromScriptValue); + qScriptRegisterMetaType(engine, modelPointerToScriptValue, modelPointerFromScriptValue); + + // FIXME: MESHFACES: remove if MeshFace is not needed anywhere + // qScriptRegisterSequenceMetaType(engine); + // qScriptRegisterMetaType(engine, meshFaceToScriptValue, meshFaceFromScriptValue); + // qScriptRegisterMetaType(engine, qVectorMeshFaceToScriptValue, qVectorMeshFaceFromScriptValue); } +MeshPointer ModelScriptingInterface::getMeshPointer(const scriptable::ScriptableMesh& meshProxy) { + return meshProxy._mesh;//getMeshPointer(&meshProxy); +} +MeshPointer ModelScriptingInterface::getMeshPointer(scriptable::ScriptableMesh& meshProxy) { + return getMeshPointer(&meshProxy); +} MeshPointer ModelScriptingInterface::getMeshPointer(scriptable::ScriptableMeshPointer meshProxy) { MeshPointer result; if (!meshProxy) { if (context()){ context()->throwError("expected meshProxy as first parameter"); + } else { + qDebug() << "expected meshProxy as first parameter"; } return result; } @@ -829,6 +414,8 @@ MeshPointer ModelScriptingInterface::getMeshPointer(scriptable::ScriptableMeshPo if (!mesh) { if (context()) { context()->throwError("expected valid meshProxy as first parameter"); + } else { + qDebug() << "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 d10fd28170..eac4df3216 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h @@ -38,30 +38,20 @@ public slots: */ void getMeshes(QUuid uuid, QScriptValue scopeOrCallback, QScriptValue methodOrName = QScriptValue()); - bool dedupeVertices(scriptable::ScriptableMeshPointer meshProxy, float epsilon = 1e-6); - bool recalculateNormals(scriptable::ScriptableMeshPointer meshProxy); - QScriptValue cloneMesh(scriptable::ScriptableMeshPointer meshProxy, bool recalcNormals = true); - QScriptValue unrollVertices(scriptable::ScriptableMeshPointer meshProxy, bool recalcNormals = true); - QScriptValue mapAttributeValues(QScriptValue in, - QScriptValue scopeOrCallback, - QScriptValue methodOrName = QScriptValue()); - QScriptValue mapMeshAttributeValues(scriptable::ScriptableMeshPointer meshProxy, - QScriptValue scopeOrCallback, - QScriptValue methodOrName = QScriptValue()); - QString meshToOBJ(const scriptable::ScriptableModel& in); - bool replaceMeshData(scriptable::ScriptableMeshPointer dest, scriptable::ScriptableMeshPointer source, const QVector& attributeNames = QVector()); QScriptValue appendMeshes(scriptable::ScriptableModel in); QScriptValue transformMesh(scriptable::ScriptableMeshPointer meshProxy, glm::mat4 transform); QScriptValue newMesh(const QVector& vertices, const QVector& normals, const QVector& faces); QScriptValue getVertexCount(scriptable::ScriptableMeshPointer meshProxy); - QScriptValue getVertex(scriptable::ScriptableMeshPointer meshProxy, mesh::uint32 vertexIndex); + QScriptValue getVertex(scriptable::ScriptableMeshPointer meshProxy, quint32 vertexIndex); private: scriptable::MeshPointer getMeshPointer(scriptable::ScriptableMeshPointer meshProxy); + scriptable::MeshPointer getMeshPointer(scriptable::ScriptableMesh& meshProxy); + scriptable::MeshPointer getMeshPointer(const scriptable::ScriptableMesh& meshProxy); }; diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp index 47d91e9e59..1b16a6d263 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp @@ -14,14 +14,20 @@ #include #include #include +#include #include #include #include +#include #include #include "ScriptableMesh.moc" #include +#include +#include + +#include "OBJWriter.h" QLoggingCategory mesh_logging { "hifi.scripting.mesh" }; @@ -41,10 +47,26 @@ QMap ScriptableMesh::ATTRIBUTES{ {"texcoord4", gpu::Stream::TEXCOORD4 }, }; -QVector scriptable::ScriptableModel::getMeshes() const { + +QString scriptable::ScriptableModel::toString() const { + return QString("[ScriptableModel%1%2]") + .arg(objectID.isNull() ? "" : " objectID="+objectID.toString()) + .arg(objectName().isEmpty() ? "" : " name=" +objectName()); +} + +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; + } + return out; +} +QVector scriptable::ScriptableModel::getMeshes() { QVector out; for(auto& mesh : meshes) { - out << scriptable::ScriptableMeshPointer(new ScriptableMesh(std::const_pointer_cast(this->shared_from_this()), mesh)); + scriptable::ScriptableMeshPointer m{new scriptable::ScriptableMesh(this, mesh)}; + out << m; } return out; } @@ -134,15 +156,16 @@ QVariantMap ScriptableMesh::getVertexAttributes(quint32 vertexIndex) const { } bool ScriptableMesh::setVertexAttributes(quint32 vertexIndex, QVariantMap attributes) { - qDebug() << "setVertexAttributes" << vertexIndex << attributes; + //qDebug() << "setVertexAttributes" << vertexIndex << attributes; for (auto& a : 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); } else { - qCDebug(mesh_logging) << "setVertexAttributes" << vertexIndex << name; + //qCDebug(mesh_logging) << "(skipping) setVertexAttributes" << vertexIndex << name; } } return true; @@ -357,3 +380,375 @@ std::map ScriptableMesh::gatherBufferViews(scriptable: } 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) { + auto mesh = getMeshPointer(); + if (!mesh) { + return false; + } + auto scopedHandler = makeScopedHandlerObject(scopeOrCallback, methodOrName); + + // input buffers + gpu::BufferView positions = mesh->getVertexBuffer(); + + const auto nPositions = positions.getNumElements(); + + // 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 obj = js->newObject(); + auto attributeViews = ScriptableMesh::gatherBufferViews(mesh, { "normal", "color" }); + for (uint32_t i=0; 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; + } + + if (result.isBool() && !result.toBool()) { + // bail without modifying data if user explicitly returns false + continue; + } + if (result.isObject() && !result.strictlyEquals(obj)) { + // user returned a new object (ie: instead of modifying input properties) + obj = result; + } + + for (const auto& a : attributeViews) { + const auto& attribute = obj.property(a.first); + auto& view = a.second; + if (attribute.isValid()) { + bufferViewElementFromScriptValue(attribute, view, i); + } + } + } + return thisObject(); +} + +QScriptValue ScriptableMesh::unrollVertices(bool recalcNormals) { + auto meshProxy = this; + auto mesh = getMeshPointer(); + qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices" << !!mesh<< !!meshProxy; + if (!mesh) { + return QScriptValue(); + } + + auto positions = mesh->getVertexBuffer(); + auto indices = mesh->getIndexBuffer(); + quint32 numPoints = (quint32)indices.getNumElements(); + 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); + for (const auto& a : attributeViews) { + auto& view = a.second; + auto sz = view._element.getSize(); + auto buffer = new gpu::Buffer(); + buffer->resize(numPoints * sz); + 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 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( + points, i, + bufferViewElementToVariant(view, index, false, hint) + ); + } + if (slot == gpu::Stream::POSITION) { + mesh->setVertexBuffer(points); + } else { + mesh->addAttribute(slot, points); + } + } + mesh->setIndexBuffer(newindices); + if (recalcNormals) { + recalculateNormals(); + } + return true; +} + +bool ScriptableMesh::replaceMeshData(scriptable::ScriptableMeshPointer 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"); + return false; + } + + QVector attributes = attributeNames.isEmpty() ? src->getAttributeNames() : attributeNames; + + //qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData -- source:" << source->displayName << "target:" << target->displayName << "attributes:" << attributes; + + // remove attributes only found on target mesh, unless user has explicitly specified the relevant attribute names + if (attributeNames.isEmpty()) { + auto attributeViews = ScriptableMesh::gatherBufferViews(target); + for (const auto& a : attributeViews) { + auto slot = ScriptableMesh::ATTRIBUTES[a.first]; + if (!attributes.contains(a.first)) { + //qCInfo(mesh_logging) << "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())); + + for (const auto& a : attributes) { + auto slot = ScriptableMesh::ATTRIBUTES[a]; + if (slot == gpu::Stream::POSITION) { + continue; + } + // 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; + 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)); + } + // auto& after = target->getAttributeBuffer(slot); + // qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData" << a << slot << before.getNumElements() << " -> " << after.getNumElements(); + } + + + return true; +} + +bool ScriptableMesh::dedupeVertices(float epsilon) { + scriptable::ScriptableMeshPointer meshProxy = this; + auto mesh = getMeshPointer(); + if (!mesh) { + return false; + } + auto positions = mesh->getVertexBuffer(); + auto numPositions = positions.getNumElements(); + const auto epsilon2 = epsilon*epsilon; + + QVector uniqueVerts; + uniqueVerts.reserve((int)numPositions); + QMap remapIndices; + + for (quint32 i = 0; i < numPositions; i++) { + const quint32 numUnique = uniqueVerts.size(); + const auto& position = positions.get(i); + bool unique = true; + for (quint32 j = 0; j < numUnique; j++) { + if (glm::length2(uniqueVerts[j] - position) <= epsilon2) { + remapIndices[i] = j; + unique = false; + break; + } + } + if (unique) { + uniqueVerts << position; + remapIndices[i] = numUnique; + } + } + + qCInfo(mesh_logging) << "//VERTS before" << numPositions << "after" << uniqueVerts.size(); + + auto indices = mesh->getIndexBuffer(); + auto numIndices = indices.getNumElements(); + auto esize = indices._element.getSize(); + QVector newIndices; + newIndices.reserve((int)numIndices); + 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]; + newIndices << remapIndices[index]; + } else { + qCInfo(mesh_logging) << i << index << "!remapIndices[index]"; + } + } + + mesh->setIndexBuffer(bufferViewFromVector(newIndices, { gpu::SCALAR, gpu::UINT32, gpu::INDEX })); + mesh->setVertexBuffer(bufferViewFromVector(uniqueVerts, { gpu::VEC3, gpu::FLOAT, gpu::XYZ })); + + auto attributeViews = ScriptableMesh::gatherBufferViews(mesh); + quint32 numUniqueVerts = uniqueVerts.size(); + for (const auto& a : attributeViews) { + auto& view = a.second; + auto slot = ScriptableMesh::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(); + quint32 numElements = (quint32)view.getNumElements(); + for (quint32 i = 0; i < numElements; i++) { + quint32 fromVertexIndex = i; + quint32 toVertexIndex = remapIndices.contains(fromVertexIndex) ? remapIndices[fromVertexIndex] : fromVertexIndex; + bufferViewElementFromVariant( + newView, toVertexIndex, + bufferViewElementToVariant(view, fromVertexIndex, false, "dedupe") + ); + } + mesh->addAttribute(slot, newView); + } + return true; +} + +QScriptValue ScriptableMesh::cloneMesh(bool recalcNormals) { + auto mesh = getMeshPointer(); + if (!mesh) { + return QScriptValue::NullValue; + } + 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)); + if (recalcNormals) { + result->recalculateNormals(); + } + return engine()->toScriptValue(result); +} + +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; +} + +QString ScriptableMesh::toOBJ() { + if (!getMeshPointer()) { + context()->throwError(QString("null mesh")); + } + return writeOBJToString({ getMeshPointer() }); +} + diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h index da11002906..257285fa90 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h @@ -6,12 +6,16 @@ #include #include #include +#include #include #include #include +#include +#include + namespace graphics { class Mesh; } @@ -19,91 +23,112 @@ namespace gpu { class BufferView; } namespace scriptable { - class ScriptableMesh : public QObject, public std::enable_shared_from_this { + class ScriptableMeshPart; + using ScriptableMeshPartPointer = QPointer; + class ScriptableMesh : public QObject, QScriptable { Q_OBJECT public: - ScriptableModelPointer _model; - scriptable::MeshPointer _mesh; - QVariantMap _metadata; - ScriptableMesh() : QObject() {} - 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; } 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(QVector attributeNames READ getAttributeNames) - virtual scriptable::MeshPointer getMeshPointer() const { return _mesh; } - Q_INVOKABLE virtual quint32 getNumParts() const; - Q_INVOKABLE virtual quint32 getNumVertices() const; - Q_INVOKABLE virtual quint32 getNumAttributes() const; - Q_INVOKABLE virtual quint32 getNumIndices() const { return 0; } - Q_INVOKABLE virtual QVector getAttributeNames() const; - Q_INVOKABLE virtual QVariantMap getVertexAttributes(quint32 vertexIndex) const; - Q_INVOKABLE virtual QVariantMap getVertexAttributes(quint32 vertexIndex, QVector attributes) const; - - Q_INVOKABLE virtual QVector getIndices() const; - Q_INVOKABLE virtual QVector findNearbyIndices(const glm::vec3& origin, float epsilon = 1e-6) const; - Q_INVOKABLE virtual QVariantMap getMeshExtents() const; - Q_INVOKABLE virtual bool setVertexAttributes(quint32 vertexIndex, QVariantMap attributes); - Q_INVOKABLE virtual QVariantMap scaleToFit(float unitScale); + static QMap ATTRIBUTES; + static std::map gatherBufferViews(MeshPointer mesh, const QStringList& expandToMatchPositions = QStringList()); - static QMap ATTRIBUTES; - static std::map gatherBufferViews(MeshPointer mesh, const QStringList& expandToMatchPositions = QStringList()); + 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; } - Q_INVOKABLE QVariantList getAttributeValues(const QString& attributeName) const; + scriptable::MeshPointer getMeshPointer() const { return _mesh; } + public slots: + quint32 getNumParts() const; + quint32 getNumVertices() const; + quint32 getNumAttributes() const; + quint32 getNumIndices() const { return 0; } + QVector getAttributeNames() const; - Q_INVOKABLE int _getSlotNumber(const QString& attributeName) const; + QVariantMap getVertexAttributes(quint32 vertexIndex) const; + QVariantMap getVertexAttributes(quint32 vertexIndex, QVector attributes) const; - 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)); - Q_INVOKABLE QVariantMap transform(const glm::mat4& transform); + 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); + + QVariantList getAttributeValues(const QString& attributeName) const; + + int _getSlotNumber(const QString& attributeName) const; + + 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; + + 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: for now this is a part-specific wrapper around ScriptableMesh - class ScriptableMeshPart : public ScriptableMesh { + // TODO: part-specific wrapper for working with raw geometries + class ScriptableMeshPart : public QObject { Q_OBJECT public: - ScriptableMeshPart& operator=(const ScriptableMeshPart& view) { _model=view._model; _mesh=view._mesh; return *this; }; - ScriptableMeshPart(const ScriptableMeshPart& other) : ScriptableMesh(other._model, other._mesh) {} - ScriptableMeshPart() : ScriptableMesh(nullptr, nullptr) {} - ~ScriptableMeshPart() { qDebug() << "~ScriptableMeshPart" << this; } - ScriptableMeshPart(ScriptableMeshPointer mesh) : ScriptableMesh(mesh->_model, mesh->_mesh) {} Q_PROPERTY(QString topology READ getTopology) Q_PROPERTY(quint32 numFaces READ getNumFaces) - scriptable::MeshPointer parentMesh; - int partIndex; - QString getTopology() const { return "triangles"; } - Q_INVOKABLE virtual quint32 getNumFaces() const { return getIndices().size() / 3; } - Q_INVOKABLE virtual QVector getFace(quint32 faceIndex) const { - auto inds = getIndices(); + 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(); } + + public: + scriptable::ScriptableMesh parentMesh; + int partIndex; }; class GraphicsScriptingInterface : public QObject { Q_OBJECT public: GraphicsScriptingInterface(QObject* parent = nullptr) : QObject(parent) {} - GraphicsScriptingInterface(const GraphicsScriptingInterface& other) {} + GraphicsScriptingInterface(const GraphicsScriptingInterface& other) {} public slots: ScriptableMeshPart exportMeshPart(ScriptableMesh mesh, int part) { return {}; } - }; } -Q_DECLARE_METATYPE(scriptable::ScriptableMesh) Q_DECLARE_METATYPE(scriptable::ScriptableMeshPointer) Q_DECLARE_METATYPE(QVector) -Q_DECLARE_METATYPE(scriptable::ScriptableMeshPart) +Q_DECLARE_METATYPE(scriptable::ScriptableMeshPartPointer) Q_DECLARE_METATYPE(scriptable::GraphicsScriptingInterface) -// FIXME: faces were supported in the original Model.* API -- are they still needed/used/useful for anything yet? +// FIXME: MESHFACES: faces were supported in the original Model.* API -- are they still needed/used/useful for anything yet? #include namespace mesh { diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h index e8cf6f1656..4ba5a993b1 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -16,50 +17,53 @@ namespace graphics { namespace gpu { class BufferView; } +class QScriptValue; + namespace scriptable { using Mesh = graphics::Mesh; using MeshPointer = std::shared_ptr; class ScriptableModel; + using ScriptableModelPointer = QPointer; class ScriptableMesh; - class ScriptableMeshPart; - using ScriptableModelPointer = std::shared_ptr; - using ScriptableMeshPointer = std::shared_ptr; - using ScriptableMeshPartPointer = std::shared_ptr; - class ScriptableModel : public QObject, public std::enable_shared_from_this { + using ScriptableMeshPointer = QPointer; + + // abstract container for holding one or more scriptable meshes + class ScriptableModel : public QObject { Q_OBJECT public: - Q_PROPERTY(QVector meshes READ getMeshes) - - Q_INVOKABLE QString toString() { return "[ScriptableModel " + objectName()+"]"; } - 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; - } - } QUuid objectID; QVariantMap metadata; QVector meshes; - // TODO: in future accessors for these could go here - QVariantMap shapes; - QVariantMap materials; - QVariantMap armature; - QVector getMeshes() const; + 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; + + 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; } + } + + // TODO: in future accessors for these could go here + // QVariantMap shapes; + // QVariantMap materials; + // QVariantMap armature; + + QVector getMeshes(); + const QVector getConstMeshes() const; + + // QScriptEngine-specific wrappers + Q_INVOKABLE QScriptValue mapAttributeValues(QScriptValue scopeOrCallback, QScriptValue methodOrName); }; + // mixin class for Avatar/Entity/Overlay Rendering that expose their in-memory graphics::Meshes class ModelProvider { public: QVariantMap metadata; @@ -67,11 +71,12 @@ namespace scriptable { 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) diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index d595136c56..ae5ac5d61c 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -579,38 +579,9 @@ scriptable::ScriptableModel Model::getScriptableModel(bool* ok) { if (!isLoaded()) { qDebug() << "Model::getScriptableModel -- !isLoaded"; - if (ok) { - *ok = false; - } - return result; + return scriptable::ModelProvider::modelUnavailableError(ok); } -// TODO: remove -- this was an earlier approach using renderGeometry instead of FBXGeometry -#if 0 // renderGeometry approach - const Geometry::GeometryMeshes& meshes = renderGeometry->getMeshes(); - Transform offset; - offset.setScale(_scale); - offset.postTranslate(_offset); - glm::mat4 offsetMat = offset.getMatrix(); - - for (std::shared_ptr mesh : meshes) { - if (!mesh) { - continue; - } - qDebug() << "Model::getScriptableModel #" << i++ << mesh->displayName; - auto newmesh = mesh->map( - [=](glm::vec3 position) { - return glm::vec3(offsetMat * glm::vec4(position, 1.0f)); - }, - [=](glm::vec3 color) { return color; }, - [=](glm::vec3 normal) { - return glm::normalize(glm::vec3(offsetMat * glm::vec4(normal, 0.0f))); - }, - [&](uint32_t index) { return index; }); - newmesh->displayName = mesh->displayName; - result << newmesh; - } -#endif const FBXGeometry& geometry = getFBXGeometry(); auto mat4toVariant = [](const glm::mat4& mat4) -> QVariant { QVector floats; @@ -659,6 +630,33 @@ scriptable::ScriptableModel Model::getScriptableModel(bool* ok) { qDebug() << "//Model::getScriptableModel -- #" << result.meshes.size(); result.metadata["submeshes"] = submeshes; return result; + +// TODO: remove -- this was an earlier approach using renderGeometry instead of FBXGeometry +#if 0 // renderGeometry approach + const Geometry::GeometryMeshes& meshes = renderGeometry->getMeshes(); + Transform offset; + offset.setScale(_scale); + offset.postTranslate(_offset); + glm::mat4 offsetMat = offset.getMatrix(); + + for (std::shared_ptr mesh : meshes) { + if (!mesh) { + continue; + } + qDebug() << "Model::getScriptableModel #" << i++ << mesh->displayName; + auto newmesh = mesh->map( + [=](glm::vec3 position) { + return glm::vec3(offsetMat * glm::vec4(position, 1.0f)); + }, + [=](glm::vec3 color) { return color; }, + [=](glm::vec3 normal) { + return glm::normalize(glm::vec3(offsetMat * glm::vec4(normal, 0.0f))); + }, + [&](uint32_t index) { return index; }); + newmesh->displayName = mesh->displayName; + result << newmesh; + } +#endif } void Model::calculateTriangleSets() {