From f824edd04e98d3cbd5da12a4a60f5779f4c5bd7a Mon Sep 17 00:00:00 2001 From: humbletim Date: Mon, 26 Feb 2018 04:58:22 -0500 Subject: [PATCH] * remove Model_temporary_hack * split gpuhelpers and mesh part * fix objwriter * more work on bufferview helpers * cr cleanup --- interface/src/Application.cpp | 31 +- interface/src/ui/overlays/Cube3DOverlay.cpp | 11 + interface/src/ui/overlays/Cube3DOverlay.h | 1 + interface/src/ui/overlays/ModelOverlay.cpp | 10 + interface/src/ui/overlays/ModelOverlay.h | 3 + interface/src/ui/overlays/Sphere3DOverlay.cpp | 12 + interface/src/ui/overlays/Sphere3DOverlay.h | 1 + .../src/avatars-renderer/Avatar.cpp | 2 +- .../src/RenderableModelEntityItem.cpp | 12 +- .../src/RenderableModelEntityItem.h | 1 + libraries/fbx/src/OBJWriter.cpp | 110 +-- libraries/gpu/src/gpu/Stream.cpp | 2 +- libraries/gpu/src/gpu/Stream.h | 4 + .../BufferViewScripting.cpp | 72 -- .../graphics-scripting/BufferViewScripting.h | 11 - .../src/graphics-scripting/Forward.h | 3 +- .../GraphicsScriptingInterface.cpp | 310 ++++++-- .../GraphicsScriptingInterface.h | 15 +- .../GraphicsScriptingUtil.cpp | 111 +++ .../GraphicsScriptingUtil.h | 57 +- .../src/graphics-scripting/ScriptableMesh.cpp | 622 +++++---------- .../src/graphics-scripting/ScriptableMesh.h | 142 +--- .../graphics-scripting/ScriptableMeshPart.cpp | 438 +++++++++++ .../graphics-scripting/ScriptableMeshPart.h | 106 +++ .../graphics-scripting/ScriptableModel.cpp | 18 +- .../src/graphics-scripting/ScriptableModel.h | 29 +- .../src/graphics/BufferViewHelpers.cpp | 733 ++++++++---------- .../graphics/src/graphics/BufferViewHelpers.h | 57 +- .../graphics/src/graphics/GpuHelpers.cpp | 122 +++ libraries/graphics/src/graphics/GpuHelpers.h | 47 ++ libraries/render-utils/src/Model.cpp | 76 +- libraries/render-utils/src/Model.h | 2 +- .../src/Model_temporary_hack.cpp.h | 84 -- 33 files changed, 1938 insertions(+), 1317 deletions(-) delete mode 100644 libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp delete mode 100644 libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.h create mode 100644 libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.cpp create mode 100644 libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h create mode 100644 libraries/graphics/src/graphics/GpuHelpers.cpp create mode 100644 libraries/graphics/src/graphics/GpuHelpers.h delete mode 100644 libraries/render-utils/src/Model_temporary_hack.cpp.h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 69be01fc0c..e8847849dc 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -608,22 +608,25 @@ void messageHandler(QtMsgType type, const QMessageLogContext& context, const QSt class ApplicationMeshProvider : public scriptable::ModelProviderFactory { public: virtual scriptable::ModelProviderPointer lookupModelProvider(const QUuid& uuid) override { - QString error; - - scriptable::ModelProviderPointer provider; - 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; - } else if (auto avatarInterface = getAvatarModelProvider(uuid)) { - provider = avatarInterface; + bool success; + if (auto nestable = DependencyManager::get()->find(uuid, success).lock()) { + auto type = nestable->getNestableType(); +#ifdef SCRIPTABLE_MESH_DEBUG + qCDebug(interfaceapp) << "ApplicationMeshProvider::lookupModelProvider" << uuid << SpatiallyNestable::nestableTypeToString(type); +#endif + switch (type) { + case NestableType::Entity: + return getEntityModelProvider(static_cast(uuid)); + case NestableType::Overlay: + return getOverlayModelProvider(static_cast(uuid)); + case NestableType::Avatar: + return getAvatarModelProvider(uuid); + } } - - return provider; + return nullptr; } +private: scriptable::ModelProviderPointer getEntityModelProvider(EntityItemID entityID) { scriptable::ModelProviderPointer provider; auto entityTreeRenderer = qApp->getEntities(); @@ -649,6 +652,8 @@ public: } else { qCWarning(interfaceapp) << "no renderer for overlay ID" << overlayID.toString(); } + } else { + qCWarning(interfaceapp) << "overlay not found" << overlayID.toString(); } return provider; } diff --git a/interface/src/ui/overlays/Cube3DOverlay.cpp b/interface/src/ui/overlays/Cube3DOverlay.cpp index f13f782482..810c7b0fca 100644 --- a/interface/src/ui/overlays/Cube3DOverlay.cpp +++ b/interface/src/ui/overlays/Cube3DOverlay.cpp @@ -200,3 +200,14 @@ Transform Cube3DOverlay::evalRenderTransform() { transform.setRotation(rotation); return transform; } + +scriptable::ScriptableModelBase Cube3DOverlay::getScriptableModel() { + auto geometryCache = DependencyManager::get(); + auto vertexColor = ColorUtils::toVec3(_color); + scriptable::ScriptableModelBase result; + if (auto mesh = geometryCache->meshFromShape(GeometryCache::Cube, vertexColor)) { + result.objectID = getID(); + result.append(mesh); + } + return result; +} diff --git a/interface/src/ui/overlays/Cube3DOverlay.h b/interface/src/ui/overlays/Cube3DOverlay.h index e7b58ad911..0b1748c00f 100644 --- a/interface/src/ui/overlays/Cube3DOverlay.h +++ b/interface/src/ui/overlays/Cube3DOverlay.h @@ -36,6 +36,7 @@ public: void setProperties(const QVariantMap& properties) override; QVariant getProperty(const QString& property) override; + virtual scriptable::ScriptableModelBase getScriptableModel() override; protected: Transform evalRenderTransform() override; diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index ffcc18032c..6603a44d46 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -665,6 +665,16 @@ void ModelOverlay::processMaterials() { } } +bool ModelOverlay::canReplaceModelMeshPart(int meshIndex, int partIndex) { + // TODO: bounds checking; for now just used to indicate provider generally supports mesh updates + return _model && _model->isLoaded(); +} + +bool ModelOverlay::replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer newModel, int meshIndex, int partIndex) { + return canReplaceModelMeshPart(meshIndex, partIndex) && + _model->replaceScriptableModelMeshPart(newModel, meshIndex, partIndex); +} + scriptable::ScriptableModelBase ModelOverlay::getScriptableModel() { if (!_model || !_model->isLoaded()) { return Base3DOverlay::getScriptableModel(); diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index fa399d40f9..88a1729d68 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -63,6 +63,9 @@ public: void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) override; virtual scriptable::ScriptableModelBase getScriptableModel() override; + virtual bool canReplaceModelMeshPart(int meshIndex, int partIndex) override; + virtual bool replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer model, int meshIndex, int partIndex) override; + protected: Transform evalRenderTransform() override; diff --git a/interface/src/ui/overlays/Sphere3DOverlay.cpp b/interface/src/ui/overlays/Sphere3DOverlay.cpp index 3021aa4404..4743e1ed3a 100644 --- a/interface/src/ui/overlays/Sphere3DOverlay.cpp +++ b/interface/src/ui/overlays/Sphere3DOverlay.cpp @@ -123,3 +123,15 @@ Transform Sphere3DOverlay::evalRenderTransform() { return transform; } + + +scriptable::ScriptableModelBase Sphere3DOverlay::getScriptableModel() { + auto geometryCache = DependencyManager::get(); + auto vertexColor = ColorUtils::toVec3(_color); + scriptable::ScriptableModelBase result; + if (auto mesh = geometryCache->meshFromShape(GeometryCache::Sphere, vertexColor)) { + result.objectID = getID(); + result.append(mesh); + } + return result; +} diff --git a/interface/src/ui/overlays/Sphere3DOverlay.h b/interface/src/ui/overlays/Sphere3DOverlay.h index ebe6dc8d83..9a434e7182 100644 --- a/interface/src/ui/overlays/Sphere3DOverlay.h +++ b/interface/src/ui/overlays/Sphere3DOverlay.h @@ -28,6 +28,7 @@ public: virtual Sphere3DOverlay* createClone() const override; + virtual scriptable::ScriptableModelBase getScriptableModel() override; protected: Transform evalRenderTransform() override; }; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 25ddfba670..f493bb14dd 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -1800,6 +1800,6 @@ scriptable::ScriptableModelBase Avatar::getScriptableModel() { return scriptable::ScriptableModelBase(); } auto result = _skeletonModel->getScriptableModel(); - result.objectID = getSessionUUID(); + result.objectID = getSessionUUID().isNull() ? AVATAR_SELF_ID : getSessionUUID(); return result; } \ No newline at end of file diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index f3c99cde2d..ae7bb26e87 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -961,8 +961,7 @@ bool RenderableModelEntityItem::getMeshes(MeshProxyList& result) { } scriptable::ScriptableModelBase render::entities::ModelEntityRenderer::getScriptableModel() { - ModelPointer model; - withReadLock([&] { model = _model; }); + auto model = resultWithReadLock([this]{ return _model; }); if (!model || !model->isLoaded()) { return scriptable::ScriptableModelBase(); @@ -973,9 +972,14 @@ scriptable::ScriptableModelBase render::entities::ModelEntityRenderer::getScript return result; } +bool render::entities::ModelEntityRenderer::canReplaceModelMeshPart(int meshIndex, int partIndex) { + // TODO: for now this method is just used to indicate that this provider generally supports mesh updates + auto model = resultWithReadLock([this]{ return _model; }); + return model && model->isLoaded(); +} + bool render::entities::ModelEntityRenderer::replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer newModel, int meshIndex, int partIndex) { - ModelPointer model; - withReadLock([&] { model = _model; }); + auto model = resultWithReadLock([this]{ return _model; }); if (!model || !model->isLoaded()) { return false; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 7edaef264d..5d7d84b7bc 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -143,6 +143,7 @@ class ModelEntityRenderer : public TypedEntityRenderer #include -#include "graphics/Geometry.h" -#include "OBJWriter.h" +#include +#include #include "ModelFormatLogging.h" static QString formatFloat(double n) { @@ -46,59 +48,60 @@ bool writeOBJToTextStream(QTextStream& out, QList meshes) { QList meshNormalStartOffset; int currentVertexStartOffset = 0; int currentNormalStartOffset = 0; + int subMeshIndex = 0; + out << "# OBJWriter::writeOBJToTextStream\n"; // write out vertices (and maybe colors) foreach (const MeshPointer& mesh, meshes) { + out << "# vertices::subMeshIndex " << subMeshIndex++ << "\n"; meshVertexStartOffset.append(currentVertexStartOffset); - const gpu::BufferView& vertexBuffer = mesh->getVertexBuffer(); - const gpu::BufferView& colorsBufferView = mesh->getAttributeBuffer(gpu::Stream::COLOR); - gpu::BufferView::Index numColors = (gpu::BufferView::Index)colorsBufferView.getNumElements(); - gpu::BufferView::Index colorIndex = 0; + auto vertices = buffer_helpers::bufferToVector(mesh->getVertexBuffer(), "mesh.vertices"); + auto colors = buffer_helpers::mesh::attributeToVector(mesh, gpu::Stream::COLOR); - int vertexCount = 0; - gpu::BufferView::Iterator vertexItr = vertexBuffer.cbegin(); - while (vertexItr != vertexBuffer.cend()) { - glm::vec3 v = *vertexItr; + gpu::BufferView::Index numColors = colors.size(); + + int i = 0; + for (const auto& v : vertices) { out << "v "; out << formatFloat(v[0]) << " "; out << formatFloat(v[1]) << " "; out << formatFloat(v[2]); - if (colorIndex < numColors) { - glm::vec3 color = colorsBufferView.get(colorIndex); + if (i < numColors) { + const glm::vec3& color = colors[i]; out << " " << formatFloat(color[0]); out << " " << formatFloat(color[1]); out << " " << formatFloat(color[2]); - colorIndex++; } out << "\n"; - vertexItr++; - vertexCount++; + i++; } - currentVertexStartOffset += vertexCount; + currentVertexStartOffset += i; } out << "\n"; // write out normals bool haveNormals = true; + subMeshIndex = 0; foreach (const MeshPointer& mesh, meshes) { + out << "# normals::subMeshIndex " << subMeshIndex++ << "\n"; meshNormalStartOffset.append(currentNormalStartOffset); - 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); + auto normals = buffer_helpers::mesh::attributeToVector(mesh, gpu::Stream::NORMAL); + for (const auto& normal : normals) { out << "vn "; out << formatFloat(normal[0]) << " "; out << formatFloat(normal[1]) << " "; out << formatFloat(normal[2]) << "\n"; } - currentNormalStartOffset += numNormals; + currentNormalStartOffset += normals.size(); } out << "\n"; // write out faces int nth = 0; + subMeshIndex = 0; foreach (const MeshPointer& mesh, meshes) { + out << "# faces::subMeshIndex " << subMeshIndex++ << "\n"; currentVertexStartOffset = meshVertexStartOffset.takeFirst(); currentNormalStartOffset = meshNormalStartOffset.takeFirst(); @@ -106,45 +109,46 @@ bool writeOBJToTextStream(QTextStream& out, QList meshes) { const gpu::BufferView& indexBuffer = mesh->getIndexBuffer(); graphics::Index partCount = (graphics::Index)mesh->getNumParts(); + QString name = (!mesh->displayName.size() ? QString("mesh-%1-part").arg(nth) : QString::fromStdString(mesh->displayName)) + .replace(QRegExp("[^-_a-zA-Z0-9]"), "_"); for (int partIndex = 0; partIndex < partCount; partIndex++) { const graphics::Mesh::Part& part = partBuffer.get(partIndex); - out << "g part-" << nth++ << "\n"; - - // graphics::Mesh::TRIANGLES - // TODO -- handle other formats - gpu::BufferView::Iterator indexItr = indexBuffer.cbegin(); - indexItr += part._startIndex; - - int indexCount = 0; - while (indexItr != indexBuffer.cend() && indexCount < part._numIndices) { - uint32_t index0 = *indexItr; - indexItr++; - indexCount++; - if (indexItr == indexBuffer.cend() || indexCount >= part._numIndices) { - qCDebug(modelformat) << "OBJWriter -- index buffer length isn't multiple of 3"; - break; - } - uint32_t index1 = *indexItr; - indexItr++; - indexCount++; - if (indexItr == indexBuffer.cend() || indexCount >= part._numIndices) { - qCDebug(modelformat) << "OBJWriter -- index buffer length isn't multiple of 3"; - break; - } - uint32_t index2 = *indexItr; - indexItr++; - indexCount++; + out << QString("g %1-%2-%3\n").arg(subMeshIndex, 3, 10, QChar('0')).arg(name).arg(partIndex); + auto indices = buffer_helpers::bufferToVector(mesh->getIndexBuffer(), "mesh.indices"); + auto face = [&](uint32_t i0, uint32_t i1, uint32_t i2) { out << "f "; if (haveNormals) { - out << currentVertexStartOffset + index0 + 1 << "//" << currentVertexStartOffset + index0 + 1 << " "; - out << currentVertexStartOffset + index1 + 1 << "//" << currentVertexStartOffset + index1 + 1 << " "; - out << currentVertexStartOffset + index2 + 1 << "//" << currentVertexStartOffset + index2 + 1 << "\n"; + out << currentVertexStartOffset + indices[i0] + 1 << "//" << currentVertexStartOffset + indices[i0] + 1 << " "; + out << currentVertexStartOffset + indices[i1] + 1 << "//" << currentVertexStartOffset + indices[i1] + 1 << " "; + out << currentVertexStartOffset + indices[i2] + 1 << "//" << currentVertexStartOffset + indices[i2] + 1 << "\n"; } else { - out << currentVertexStartOffset + index0 + 1 << " "; - out << currentVertexStartOffset + index1 + 1 << " "; - out << currentVertexStartOffset + index2 + 1 << "\n"; + out << currentVertexStartOffset + indices[i0] + 1 << " "; + out << currentVertexStartOffset + indices[i1] + 1 << " "; + out << currentVertexStartOffset + indices[i2] + 1 << "\n"; + } + }; + + uint32_t len = part._startIndex + part._numIndices; + qCDebug(modelformat) << "OBJWriter -- part" << partIndex << "topo" << part._topology << "index elements"; + if (part._topology == graphics::Mesh::TRIANGLES && len % 3 != 0) { + qCDebug(modelformat) << "OBJWriter -- index buffer length isn't a multiple of 3" << len; + } + if (part._topology == graphics::Mesh::QUADS && len % 4 != 0) { + qCDebug(modelformat) << "OBJWriter -- index buffer length isn't a multiple of 4" << len; + } + if (len > indexBuffer.getNumElements()) { + qCDebug(modelformat) << "OBJWriter -- len > index size" << len << indexBuffer.getNumElements(); + } + if (part._topology == graphics::Mesh::QUADS) { + for (uint32_t idx = part._startIndex; idx+3 < len; idx += 4) { + face(idx+0, idx+1, idx+3); + face(idx+1, idx+2, idx+3); + } + } else if (part._topology == graphics::Mesh::TRIANGLES) { + for (uint32_t idx = part._startIndex; idx+2 < len; idx += 3) { + face(idx+0, idx+1, idx+2); } } out << "\n"; diff --git a/libraries/gpu/src/gpu/Stream.cpp b/libraries/gpu/src/gpu/Stream.cpp index caa1ecbc06..f83900be42 100644 --- a/libraries/gpu/src/gpu/Stream.cpp +++ b/libraries/gpu/src/gpu/Stream.cpp @@ -19,7 +19,7 @@ using namespace gpu; using ElementArray = std::array; -const ElementArray& getDefaultElements() { +const ElementArray& Stream::getDefaultElements() { static ElementArray defaultElements{{ //POSITION = 0, Element::VEC3F_XYZ, diff --git a/libraries/gpu/src/gpu/Stream.h b/libraries/gpu/src/gpu/Stream.h index 5562980a91..0def1ab201 100644 --- a/libraries/gpu/src/gpu/Stream.h +++ b/libraries/gpu/src/gpu/Stream.h @@ -23,6 +23,8 @@ namespace gpu { +class Element; + // Stream namespace class class Stream { public: @@ -49,6 +51,8 @@ public: typedef uint8 Slot; + static const std::array& getDefaultElements(); + // Frequency describer enum Frequency { PER_VERTEX = 0, diff --git a/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp b/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp deleted file mode 100644 index 9d7a0e1f30..0000000000 --- a/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp +++ /dev/null @@ -1,72 +0,0 @@ -#include "BufferViewScripting.h" - -#include -#include -#include -#include - -#include -#include -#include - -#include - -template -QScriptValue getBufferViewElement(QScriptEngine* js, const gpu::BufferView& view, quint32 index, bool asArray = false) { - return glmVecToScriptValue(js, view.get(index), asArray); -} - -QScriptValue bufferViewElementToScriptValue(QScriptEngine* engine, const gpu::BufferView& view, quint32 index, bool asArray, const char* hint) { - QVariant result = buffer_helpers::toVariant(view, index, asArray, hint); - if (!result.isValid()) { - return QScriptValue::NullValue; - } - return engine->toScriptValue(result); -} - -template -void setBufferViewElement(const gpu::BufferView& view, quint32 index, const QScriptValue& v) { - view.edit(index) = glmVecFromScriptValue(v); -} - -bool bufferViewElementFromScriptValue(const QScriptValue& v, const gpu::BufferView& view, quint32 index) { - return buffer_helpers::fromVariant(view, index, v.toVariant()); -} - -template -QScriptValue glmVecToScriptValue(QScriptEngine *js, const T& v, bool asArray) { - static const auto len = T().length(); - const auto& components = asArray ? buffer_helpers::ZERO123 : buffer_helpers::XYZW; - auto obj = asArray ? js->newArray() : js->newObject(); - for (int i = 0; i < len ; i++) { - const auto key = components[i]; - const auto value = v[i]; - if (value != value) { // NAN -#ifdef DEV_BUILD - qWarning().nospace()<< "vec" << len << "." << key << " converting NAN to javascript NaN.... " << value; -#endif - obj.setProperty(key, js->globalObject().property("NaN")); - } else { - obj.setProperty(key, value); - } - } - return obj; -} - -template -const T glmVecFromScriptValue(const QScriptValue& v) { - static const auto len = T().length(); - const auto& components = v.property("x").isValid() ? buffer_helpers::XYZW : buffer_helpers::ZERO123; - T result; - for (int i = 0; i < len ; i++) { - const auto key = components[i]; - const auto value = v.property(key).toNumber(); -#ifdef DEV_BUILD - if (value != value) { // NAN - qWarning().nospace()<< "vec" << len << "." << key << " NAN received from script.... " << v.toVariant().toString(); - } -#endif - result[i] = value; - } - return result; -} diff --git a/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.h b/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.h deleted file mode 100644 index f2e3fe734e..0000000000 --- a/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include - -namespace gpu { class BufferView; } -class QScriptValue; -class QScriptEngine; -template QScriptValue glmVecToScriptValue(QScriptEngine *js, const T& v, bool asArray = false); -template const T glmVecFromScriptValue(const QScriptValue& v); -QScriptValue bufferViewElementToScriptValue(QScriptEngine* engine, const gpu::BufferView& view, quint32 index, bool asArray = false, const char* hint = ""); -bool bufferViewElementFromScriptValue(const QScriptValue& v, const gpu::BufferView& view, quint32 index); diff --git a/libraries/graphics-scripting/src/graphics-scripting/Forward.h b/libraries/graphics-scripting/src/graphics-scripting/Forward.h index 94a96446a0..2f2f191dce 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/Forward.h +++ b/libraries/graphics-scripting/src/graphics-scripting/Forward.h @@ -74,7 +74,7 @@ namespace scriptable { public: NestableType modelProviderType; virtual scriptable::ScriptableModelBase getScriptableModel() = 0; - + virtual bool canReplaceModelMeshPart(int meshIndex, int partIndex) { return false; } virtual bool replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer model, int meshIndex, int partIndex) { return false; } }; @@ -88,7 +88,6 @@ namespace scriptable { void modelRemovedFromScene(const QUuid& objectID, NestableType nestableType, const ModelPointer& sender); }; - using uint32 = quint32; class ScriptableModel; using ScriptableModelPointer = QPointer; class ScriptableMesh; diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp index 787905520a..22ef346df6 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp @@ -15,13 +15,16 @@ #include "RegisteredMetaTypes.h" #include "ScriptEngineLogging.h" #include "ScriptableMesh.h" +#include "ScriptableMeshPart.h" #include #include #include #include #include #include +#include #include +#include GraphicsScriptingInterface::GraphicsScriptingInterface(QObject* parent) : QObject(parent), QScriptable() { if (auto scriptEngine = qobject_cast(parent)) { @@ -29,21 +32,45 @@ GraphicsScriptingInterface::GraphicsScriptingInterface(QObject* parent) : QObjec } } -bool GraphicsScriptingInterface::updateModelObject(QUuid uuid, const scriptable::ScriptableModelPointer model) { - if (auto provider = getModelProvider(uuid)) { - if (auto base = model->operator scriptable::ScriptableModelBasePointer()) { -#ifdef SCRIPTABLE_MESH_DEBUG - qDebug() << "replaceScriptableModelMeshPart" << model->toString() << -1 << -1; -#endif - return provider->replaceScriptableModelMeshPart(base, -1, -1); - } else { - qDebug() << "replaceScriptableModelMeshPart -- !base" << model << base << -1 << -1; - } +void GraphicsScriptingInterface::jsThrowError(const QString& error) { + if (context()) { + context()->throwError(error); } else { - qDebug() << "replaceScriptableModelMeshPart -- !provider"; + qCWarning(graphics_scripting) << "GraphicsScriptingInterface::jsThrowError (without valid JS context):" << error; + } +} + +bool GraphicsScriptingInterface::canUpdateModel(QUuid uuid, int meshIndex, int partNumber) { + auto provider = getModelProvider(uuid); + return provider && provider->canReplaceModelMeshPart(meshIndex, partNumber); +} + +bool GraphicsScriptingInterface::updateModel(QUuid uuid, const scriptable::ScriptableModelPointer& model) { + if (!model) { + jsThrowError("null model argument"); } - return false; + auto base = model->operator scriptable::ScriptableModelBasePointer(); + if (!base) { + jsThrowError("could not get base model pointer"); + return false; + } + + auto provider = getModelProvider(uuid); + if (!provider) { + jsThrowError("provider unavailable"); + return false; + } + + if (!provider->canReplaceModelMeshPart(-1, -1)) { + jsThrowError("provider does not support updating mesh parts"); + return false; + } + +#ifdef SCRIPTABLE_MESH_DEBUG + qDebug() << "replaceScriptableModelMeshPart" << model->toString() << -1 << -1; +#endif + return provider->replaceScriptableModelMeshPart(base, -1, -1); } scriptable::ModelProviderPointer GraphicsScriptingInterface::getModelProvider(QUuid uuid) { @@ -57,28 +84,26 @@ scriptable::ModelProviderPointer GraphicsScriptingInterface::getModelProvider(QU } else { error = "appProvider unavailable"; } - if (context()) { - context()->throwError(error); - } else { - qCWarning(graphics_scripting) << "GraphicsScriptingInterface::getModelProvider ERROR" << error; - } + jsThrowError(error); return nullptr; } -scriptable::ScriptableModelPointer GraphicsScriptingInterface::newModelObject(QVector meshes) { +scriptable::ScriptableModelPointer GraphicsScriptingInterface::newModel(const scriptable::ScriptableMeshes& meshes) { auto modelWrapper = scriptable::make_scriptowned(); modelWrapper->setObjectName("js::model"); if (meshes.isEmpty()) { - if (context()) { - context()->throwError("expected [meshes] array as first argument"); - } + jsThrowError("expected [meshes] array as first argument"); } else { int i = 0; for (const auto& mesh : meshes) { +#ifdef SCRIPTABLE_MESH_DEBUG + qDebug() << "newModel" << i << meshes.size() << mesh; +#endif if (mesh) { modelWrapper->append(*mesh); - } else if (context()) { - context()->throwError(QString("invalid mesh at index: %1").arg(i)); + } else { + jsThrowError(QString("invalid mesh at index: %1").arg(i)); + break; } i++; } @@ -86,30 +111,37 @@ scriptable::ScriptableModelPointer GraphicsScriptingInterface::newModelObject(QV return modelWrapper; } -scriptable::ScriptableModelPointer GraphicsScriptingInterface::getModelObject(QUuid uuid) { - QString error, providerType = "unknown"; - if (auto provider = getModelProvider(uuid)) { - providerType = SpatiallyNestable::nestableTypeToString(provider->modelProviderType); - auto modelObject = provider->getScriptableModel(); - if (modelObject.objectID == uuid) { - if (modelObject.meshes.size()) { - auto modelWrapper = scriptable::make_scriptowned(modelObject); - modelWrapper->setObjectName(providerType+"::"+uuid.toString()+"::model"); - return modelWrapper; +scriptable::ScriptableModelPointer GraphicsScriptingInterface::getModel(QUuid uuid) { + QString error; + bool success; + QString providerType = "unknown"; + if (auto nestable = DependencyManager::get()->find(uuid, success).lock()) { + providerType = SpatiallyNestable::nestableTypeToString(nestable->getNestableType()); + if (auto provider = getModelProvider(uuid)) { + auto modelObject = provider->getScriptableModel(); + const bool found = !modelObject.objectID.isNull(); + if (found && uuid == AVATAR_SELF_ID) { + // special case override so that scripts can rely on matching intput/output UUIDs + modelObject.objectID = AVATAR_SELF_ID; + } + if (modelObject.objectID == uuid) { + if (modelObject.meshes.size()) { + auto modelWrapper = scriptable::make_scriptowned(modelObject); + modelWrapper->setObjectName(providerType+"::"+uuid.toString()+"::model"); + return modelWrapper; + } else { + error = "no meshes available: " + modelObject.objectID.toString(); + } } else { - error = "no meshes available: " + modelObject.objectID.toString(); + error = QString("objectID mismatch: %1 (result contained %2 meshes)").arg(modelObject.objectID.toString()).arg(modelObject.meshes.size()); } } else { - error = QString("objectID mismatch: %1 (containing %2 meshes)").arg(modelObject.objectID.toString()).arg(modelObject.meshes.size()); + error = "model provider unavailable"; } } else { - error = "provider unavailable"; - } - auto errorMessage = QString("failed to get meshes from %1 provider for uuid %2 (%3)").arg(providerType).arg(uuid.toString()).arg(error); - qCWarning(graphics_scripting) << "GraphicsScriptingInterface::getModelObject ERROR" << errorMessage; - if (context()) { - context()->throwError(errorMessage); + error = "model object not found"; } + jsThrowError(QString("failed to get meshes from %1 provider for uuid %2 (%3)").arg(providerType).arg(uuid.toString()).arg(error)); return nullptr; } @@ -135,6 +167,85 @@ bool GraphicsScriptingInterface::updateMeshPart(scriptable::ScriptableMeshPointe } #endif +scriptable::ScriptableMeshPointer GraphicsScriptingInterface::newMesh(const QVariantMap& ifsMeshData) { + // TODO: this is bare-bones way for now to improvise a new mesh from the scripting side + // in the future we want to support a formal C++ structure data type here instead + QString meshName = ifsMeshData.value("name").toString(); + QString topologyName = ifsMeshData.value("topology").toString(); + QVector indices = buffer_helpers::variantToVector(ifsMeshData.value("indices")); + QVector vertices = buffer_helpers::variantToVector(ifsMeshData.value("positions")); + QVector normals = buffer_helpers::variantToVector(ifsMeshData.value("normals")); + QVector colors = buffer_helpers::variantToVector(ifsMeshData.value("colors")); + QVector texCoords0 = buffer_helpers::variantToVector(ifsMeshData.value("texCoords0")); + + const auto numVertices = vertices.size(); + const auto numIndices = indices.size(); + const auto topology = graphics::Mesh::TRIANGLES; + + // sanity checks + QString error; + if (!topologyName.isEmpty() && topologyName != "triangles") { + error = "expected 'triangles' or undefined for .topology"; + } else if (!numIndices) { + error = QString("expected non-empty [uint32,...] array for .indices (got type=%1)").arg(ifsMeshData.value("indices").typeName()); + } else if (numIndices % 3 != 0) { + error = QString("expected 'triangle faces' for .indices (ie: length to be divisible by 3) length=%1").arg(numIndices); + } else if (!numVertices) { + error = "expected non-empty [glm::vec3(),...] array for .positions"; + } else { + const gpu::uint32 maxVertexIndex = numVertices; + int i = 0; + for (const auto& ind : indices) { + if (ind >= maxVertexIndex) { + error = QString("index out of .indices[%1] index=%2 >= maxVertexIndex=%3").arg(i).arg(ind).arg(maxVertexIndex); + break; + } + i++; + } + } + if (!error.isEmpty()) { + jsThrowError(error); + return nullptr; + } + + if (ifsMeshData.contains("normals") && normals.size() < numVertices) { + qCInfo(graphics_scripting) << "newMesh -- expanding .normals to #" << numVertices; + normals.resize(numVertices); + } + if (ifsMeshData.contains("colors") && colors.size() < numVertices) { + qCInfo(graphics_scripting) << "newMesh -- expanding .colors to #" << numVertices; + colors.resize(numVertices); + } + if (ifsMeshData.contains("texCoords0") && texCoords0.size() < numVertices) { + qCInfo(graphics_scripting) << "newMesh -- expanding .texCoords0 to #" << numVertices; + texCoords0.resize(numVertices); + } + if (ifsMeshData.contains("texCoords1")) { + qCWarning(graphics_scripting) << "newMesh - texCoords1 not yet supported; ignoring"; + } + + graphics::MeshPointer mesh(new graphics::Mesh()); + mesh->modelName = "graphics::newMesh"; + mesh->displayName = meshName.toStdString(); + + // TODO: newFromVector does no conversion -- later we could autodetect if fitting into gpu::INDEX_UINT16 + // and also pack other values (like NORMAL / TEXCOORD0 where relevant) + mesh->setIndexBuffer(buffer_helpers::newFromVector(indices, gpu::Format::INDEX_INT32)); + mesh->setVertexBuffer(buffer_helpers::newFromVector(vertices, gpu::Format::VEC3F_XYZ)); + if (normals.size()) { + mesh->addAttribute(gpu::Stream::NORMAL, buffer_helpers::newFromVector(normals, gpu::Format::VEC3F_XYZ)); + } + if (colors.size()) { + mesh->addAttribute(gpu::Stream::COLOR, buffer_helpers::newFromVector(colors, gpu::Format::VEC3F_XYZ)); + } + if (texCoords0.size()) { + mesh->addAttribute(gpu::Stream::TEXCOORD0, buffer_helpers::newFromVector(texCoords0, gpu::Format::VEC2F_UV)); + } + QVector parts = {{ 0, indices.size(), 0, topology}}; + mesh->setPartBuffer(buffer_helpers::newFromVector(parts, gpu::Element::PART_DRAWCALL)); + return scriptable::make_scriptowned(mesh, nullptr); +} + QString GraphicsScriptingInterface::exportModelToOBJ(const scriptable::ScriptableModel& _in) { const auto& in = _in.getConstMeshes(); if (in.size()) { @@ -148,11 +259,10 @@ QString GraphicsScriptingInterface::exportModelToOBJ(const scriptable::Scriptabl return writeOBJToString(meshes); } } - if (context()) { - context()->throwError(QString("null mesh")); - } + jsThrowError("null mesh"); return QString(); } + void GraphicsScriptingInterface::registerMetaTypes(QScriptEngine* engine) { scriptable::registerMetaTypes(engine); } @@ -166,23 +276,115 @@ MeshPointer GraphicsScriptingInterface::getMeshPointer(scriptable::ScriptableMes MeshPointer GraphicsScriptingInterface::getMeshPointer(scriptable::ScriptableMeshPointer meshProxy) { MeshPointer result; if (!meshProxy) { - if (context()){ - context()->throwError("expected meshProxy as first parameter"); - } else { - qCDebug(graphics_scripting) << "expected meshProxy as first parameter"; - } + jsThrowError("expected meshProxy as first parameter"); return result; } auto mesh = meshProxy->getMeshPointer(); if (!mesh) { - if (context()) { - context()->throwError("expected valid meshProxy as first parameter"); - } else { - qCDebug(graphics_scripting) << "expected valid meshProxy as first parameter"; - } + jsThrowError("expected valid meshProxy as first parameter"); return result; } return mesh; } +namespace { + 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("glm::uint32"), + qRegisterMetaType>(), + qRegisterMetaType>("QVector"), + qRegisterMetaType(), + qRegisterMetaType("ScriptableMeshes"), + qRegisterMetaType("scriptable::ScriptableMeshes"), + qRegisterMetaType>("QVector"), + qRegisterMetaType(), + qRegisterMetaType(), + qRegisterMetaType(), + qRegisterMetaType(), + }; +} + +namespace scriptable { + template int registerQPointerThing(QScriptEngine* engine) { + qScriptRegisterSequenceMetaType>>(engine); + return qScriptRegisterMetaType>( + engine, + [](QScriptEngine* engine, const QPointer& object) -> QScriptValue { + if (!object) { + return QScriptValue::NullValue; + } + return engine->newQObject(object, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::AutoCreateDynamicProperties); + }, + [](const QScriptValue& value, QPointer& out) { + auto obj = value.toQObject(); +#ifdef SCRIPTABLE_MESH_DEBUG + qCInfo(graphics_scripting) << "qpointer_qobject_cast" << obj << value.toString(); +#endif + if (auto tmp = qobject_cast(obj)) { + out = QPointer(tmp); + return; + } +#if 0 + if (auto tmp = static_cast(obj)) { +#ifdef SCRIPTABLE_MESH_DEBUG + qCInfo(graphics_scripting) << "qpointer_qobject_cast -- via static_cast" << obj << tmp << value.toString(); +#endif + out = QPointer(tmp); + return; + } +#endif + out = nullptr; + } + ); + } + + template int registerDebugEnum(QScriptEngine* engine, const DebugEnums& debugEnums) { + static const DebugEnums& poop = debugEnums; + return qScriptRegisterMetaType( + engine, + [](QScriptEngine* engine, const T& topology) -> QScriptValue { + return poop.value(topology); + }, + [](const QScriptValue& value, T& topology) { + topology = poop.key(value.toString()); + } + ); + } + + bool registerMetaTypes(QScriptEngine* engine) { + qScriptRegisterSequenceMetaType>(engine); + + qScriptRegisterMetaType(engine, qVectorUInt32ToScriptValue, qVectorUInt32FromScriptValue); + + registerQPointerThing(engine); + registerQPointerThing(engine); + registerQPointerThing(engine); + qScriptRegisterMetaType>( + engine, + [](QScriptEngine* engine, const QVector& vector) -> QScriptValue { + return qScriptValueFromSequence(engine, vector); + }, + [](const QScriptValue& array, QVector& result) { + qScriptValueToSequence(array, result); + } + ); + + registerDebugEnum(engine, graphics::TOPOLOGIES); + registerDebugEnum(engine, gpu::TYPES); + registerDebugEnum(engine, gpu::SEMANTICS); + registerDebugEnum(engine, gpu::DIMENSIONS); + + return metaTypeIds.size(); + } + +} + #include "GraphicsScriptingInterface.moc" diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h index 9866b5585c..a66e382bc7 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h @@ -34,13 +34,14 @@ public slots: * @function GraphicsScriptingInterface.getModel * @param {UUID} The objectID of the model whose meshes are to be retrieve */ - scriptable::ModelProviderPointer getModelProvider(QUuid uuid); - scriptable::ScriptableModelPointer getModelObject(QUuid uuid); - bool updateModelObject(QUuid uuid, const scriptable::ScriptableModelPointer model); - scriptable::ScriptableModelPointer newModelObject(QVector meshes); + scriptable::ScriptableModelPointer getModel(QUuid uuid); + bool updateModel(QUuid uuid, const scriptable::ScriptableModelPointer& model); + bool canUpdateModel(QUuid uuid, int meshIndex = -1, int partNumber = -1); + scriptable::ScriptableModelPointer newModel(const scriptable::ScriptableMeshes& meshes); + scriptable::ScriptableMeshPointer newMesh(const QVariantMap& ifsMeshData); #ifdef SCRIPTABLE_MESH_TODO - scriptable::ScriptableMeshPartPointer exportMeshPart(scriptable::ScriptableMeshPointer mesh, int part=0) { + scriptable::ScriptableMeshPartPointer exportMeshPart(scriptable::ScriptableMeshPointer mesh, int partNumber = -1) { return scriptable::make_scriptowned(mesh, part); } bool updateMeshPart(scriptable::ScriptableMeshPointer mesh, scriptable::ScriptableMeshPartPointer part); @@ -49,10 +50,14 @@ public slots: QString exportModelToOBJ(const scriptable::ScriptableModel& in); private: + scriptable::ModelProviderPointer getModelProvider(QUuid uuid); + void jsThrowError(const QString& error); scriptable::MeshPointer getMeshPointer(scriptable::ScriptableMeshPointer meshProxy); scriptable::MeshPointer getMeshPointer(scriptable::ScriptableMesh& meshProxy); scriptable::MeshPointer getMeshPointer(const scriptable::ScriptableMesh& meshProxy); }; +Q_DECLARE_METATYPE(scriptable::ModelProviderPointer) + #endif // hifi_GraphicsScriptingInterface_h diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.cpp b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.cpp index aabf83ff66..da582b2d21 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.cpp @@ -1,3 +1,114 @@ +// +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + #include "GraphicsScriptingUtil.h" +#include + +#include +#include +#include + +using buffer_helpers::glmVecToVariant; + Q_LOGGING_CATEGORY(graphics_scripting, "hifi.scripting.graphics") + +namespace scriptable { + +QVariant 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 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 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()) }, + }; +} + +QVariant toVariant(const gpu::Element& element) { + return QVariantMap{ + { "type", gpu::toString(element.getType()) }, + { "semantic", gpu::toString(element.getSemantic()) }, + { "dimension", gpu::toString(element.getDimension()) }, + { "scalarCount", element.getScalarCount() }, + { "byteSize", element.getSize() }, + { "BYTES_PER_ELEMENT", element.getSize() / element.getScalarCount() }, + }; +} + +QScriptValue jsBindCallback(QScriptValue value) { + if (value.isObject() && value.property("callback").isFunction()) { + // value is already a bound callback + return value; + } + auto engine = value.engine(); + auto context = engine ? engine->currentContext() : nullptr; + auto length = context ? context->argumentCount() : 0; + QScriptValue scope = context ? context->thisObject() : QScriptValue::NullValue; + QScriptValue method; +#ifdef SCRIPTABLE_MESH_DEBUG + qCInfo(graphics_scripting) << "jsBindCallback" << engine << length << scope.toQObject() << method.toString(); +#endif + + // find position in the incoming JS Function.arguments array (so we can test for the two-argument case) + for (int i = 0; context && i < length; i++) { + if (context->argument(i).strictlyEquals(value)) { + method = context->argument(i+1); + } + } + if (method.isFunction() || method.isString()) { + // interpret as `API.func(..., scope, function callback(){})` or `API.func(..., scope, "methodName")` + scope = value; + } else { + // interpret as `API.func(..., function callback(){})` + method = value; + } +#ifdef SCRIPTABLE_MESH_DEBUG + qCInfo(graphics_scripting) << "scope:" << scope.toQObject() << "method:" << method.toString(); +#endif + return ::makeScopedHandlerObject(scope, method); +} + +template +T this_qobject_cast(QScriptEngine* engine) { + auto context = engine ? engine->currentContext() : nullptr; + return qscriptvalue_cast(context ? context->thisObject() : QScriptValue::NullValue); +} +QString toDebugString(QObject* tmp) { + QString s; + QTextStream out(&s); + out << tmp; + return s; + // 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())); +} + +} diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h index 9b86ddee82..1ca62277ff 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h @@ -8,46 +8,39 @@ #include #include #include +#include + +class Extents; +class AABox; +namespace gpu { + class Element; +} 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); - } + QVariant toVariant(const Extents& box); + QVariant toVariant(const AABox& box); + QVariant toVariant(const gpu::Element& element); + QVariant toVariant(const glm::mat4& mat4); - // JS => QPointer - template - QPointer qpointer_qobject_cast(const QScriptValue& value) { - auto obj = value.toQObject(); -#ifdef SCRIPTABLE_MESH_DEBUG - qCInfo(graphics_scripting) << "qpointer_qobject_cast" << obj << value.toString(); -#endif - 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())); - } + // helper that automatically resolves Qt-signal-like scoped callbacks + // ... C++ side: `void MyClass::asyncMethod(..., QScriptValue callback)` + // ... JS side: + // * `API.asyncMethod(..., function(){})` + // * `API.asyncMethod(..., scope, function(){})` + // * `API.asyncMethod(..., scope, "methodName")` + QScriptValue jsBindCallback(QScriptValue callback); + + // cast engine->thisObject() => C++ class instance + template T this_qobject_cast(QScriptEngine* engine); + + QString toDebugString(QObject* tmp); + template QString toDebugString(std::shared_ptr tmp); // Helper for creating C++ > ScriptOwned JS instances // (NOTE: this also helps track in the code where we need to update later if switching to // std::shared_ptr's -- something currently non-trivial given mixed JS/C++ object ownership) - template - QPointer make_scriptowned(Rest... rest) { + template inline QPointer make_scriptowned(Rest... rest) { auto instance = QPointer(new T(rest...)); Q_ASSERT(instance && instance->parent()); return instance; diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp index 76741947fd..2cef4fef64 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp @@ -8,6 +8,7 @@ #include "Forward.h" #include "ScriptableMesh.h" +#include "ScriptableMeshPart.h" #include "BufferViewScripting.h" #include "GraphicsScriptingUtil.h" @@ -19,15 +20,11 @@ #include #include #include +#include #include // #define SCRIPTABLE_MESH_DEBUG 1 -scriptable::ScriptableMeshPart::ScriptableMeshPart(scriptable::ScriptableMeshPointer parentMesh, int partIndex) - : QObject(), parentMesh(parentMesh), partIndex(partIndex) { - setObjectName(QString("%1.part[%2]").arg(parentMesh ? parentMesh->objectName() : "").arg(partIndex)); -} - scriptable::ScriptableMesh::ScriptableMesh(const ScriptableMeshBase& other) : ScriptableMeshBase(other), QScriptable() { auto mesh = getMeshPointer(); @@ -41,74 +38,55 @@ scriptable::ScriptableMesh::ScriptableMesh(const ScriptableMeshBase& other) QVector scriptable::ScriptableMesh::getMeshParts() const { QVector out; - for (quint32 i = 0; i < getNumParts(); i++) { + for (glm::uint32 i = 0; i < getNumParts(); i++) { out << scriptable::make_scriptowned(getSelf(), i); } return out; } -quint32 scriptable::ScriptableMesh::getNumIndices() const { +glm::uint32 scriptable::ScriptableMesh::getNumIndices() const { if (auto mesh = getMeshPointer()) { - return (quint32)mesh->getNumIndices(); + return (glm::uint32)mesh->getNumIndices(); } return 0; } -quint32 scriptable::ScriptableMesh::getNumVertices() const { +glm::uint32 scriptable::ScriptableMesh::getNumVertices() const { if (auto mesh = getMeshPointer()) { - return (quint32)mesh->getNumVertices(); + return (glm::uint32)mesh->getNumVertices(); } return 0; } -QVector scriptable::ScriptableMesh::findNearbyIndices(const glm::vec3& origin, float epsilon) const { - QVector result; - if (auto mesh = getMeshPointer()) { - 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); - if (glm::distance(position, origin) <= epsilon) { - result << i; - } +QVector scriptable::ScriptableMesh::findNearbyVertexIndices(const glm::vec3& origin, float epsilon) const { + QVector result; + if (!isValid()) { + return result; + } + const auto epsilon2 = epsilon*epsilon; + buffer_helpers::forEach(buffer_helpers::mesh::getBufferView(getMeshPointer(), gpu::Stream::POSITION), [&](glm::uint32 index, const glm::vec3& position) { + if (glm::length2(position - origin) <= epsilon2) { + result << index; } - } + return true; + }); return result; } -QVector scriptable::ScriptableMesh::getIndices() const { - QVector result; +QVector scriptable::ScriptableMesh::getIndices() const { if (auto mesh = getMeshPointer()) { #ifdef SCRIPTABLE_MESH_DEBUG - qCDebug(graphics_scripting, "getTriangleIndices mesh %p", mesh.get()); + qCDebug(graphics_scripting, "getIndices mesh %p", mesh.get()); #endif - gpu::BufferView indexBufferView = mesh->getIndexBuffer(); - if (quint32 count = (quint32)indexBufferView.getNumElements()) { - result.resize(count); - 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); - } - break; - case gpu::UINT16: - for (quint32 i = 0; i < count; i++) { - result[i] = indexBufferView.get(i); - } - break; - default: - assert(false); - Q_ASSERT(false); - } - } + return buffer_helpers::bufferToVector(mesh->getIndexBuffer()); } - return result; + return QVector(); } -quint32 scriptable::ScriptableMesh::getNumAttributes() const { + +glm::uint32 scriptable::ScriptableMesh::getNumAttributes() const { if (auto mesh = getMeshPointer()) { - return (quint32)mesh->getNumAttributes(); + return (glm::uint32)mesh->getNumAttributes() + 1; } return 0; } @@ -116,7 +94,7 @@ QVector scriptable::ScriptableMesh::getAttributeNames() const { QVector result; if (auto mesh = getMeshPointer()) { for (const auto& a : buffer_helpers::ATTRIBUTES.toStdMap()) { - auto bufferView = buffer_helpers::getBufferView(mesh, a.second); + auto bufferView = buffer_helpers::mesh::getBufferView(mesh, a.second); if (bufferView.getNumElements() > 0) { result << a.first; } @@ -125,22 +103,20 @@ QVector scriptable::ScriptableMesh::getAttributeNames() const { return result; } -QVariantMap scriptable::ScriptableMesh::getVertexAttributes(quint32 vertexIndex) const { - return getVertexAttributes(vertexIndex, getAttributeNames()); +QVariantMap scriptable::ScriptableMesh::getVertexAttributes(glm::uint32 vertexIndex) const { + if (!isValidIndex(vertexIndex)) { + return QVariantMap(); + } + return buffer_helpers::mesh::getVertexAttributes(getMeshPointer(), vertexIndex).toMap(); } -bool scriptable::ScriptableMesh::setVertexAttributes(quint32 vertexIndex, QVariantMap attributes) { - 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; - buffer_helpers::fromVariant(view, vertexIndex, value); - } else { - //qCDebug(graphics_scripting) << "(skipping) setVertexAttributes" << vertexIndex << name; +bool scriptable::ScriptableMesh::setVertexAttributes(glm::uint32 vertexIndex, const QVariantMap& attributes) { + for (const auto& name : attributes.keys()) { + if (!isValidIndex(vertexIndex, name)) { + return false; } } - return true; + return buffer_helpers::mesh::setVertexAttributes(getMeshPointer(), vertexIndex, attributes); } int scriptable::ScriptableMesh::_getSlotNumber(const QString& attributeName) const { @@ -150,112 +126,133 @@ int scriptable::ScriptableMesh::_getSlotNumber(const QString& attributeName) con return -1; } - -QVariantMap scriptable::ScriptableMesh::getMeshExtents() const { - auto mesh = getMeshPointer(); - auto box = mesh ? mesh->evalPartsBound(0, (int)mesh->getNumParts()) : AABox(); - return buffer_helpers::toVariant(box).toMap(); +QVariantMap scriptable::ScriptableMesh::getBufferFormats() const { + QVariantMap result; + for (const auto& a : buffer_helpers::ATTRIBUTES.toStdMap()) { + auto bufferView = buffer_helpers::mesh::getBufferView(getMeshPointer(), a.second); + result[a.first] = QVariantMap{ + { "slot", a.second }, + { "length", (quint32)bufferView.getNumElements() }, + { "byteLength", (quint32)bufferView._size }, + { "offset", (quint32) bufferView._offset }, + { "stride", (quint32)bufferView._stride }, + { "element", scriptable::toVariant(bufferView._element) }, + }; + } + return result; } -quint32 scriptable::ScriptableMesh::getNumParts() const { - if (auto mesh = getMeshPointer()) { - return (quint32)mesh->getNumParts(); +bool scriptable::ScriptableMesh::removeAttribute(const QString& attributeName) { + auto slot = isValid() ? _getSlotNumber(attributeName) : -1; + if (slot < 0) { + return 0; + } + if (slot == gpu::Stream::POSITION) { + context()->throwError("cannot remove .position attribute"); + return false; + } + if (buffer_helpers::mesh::getBufferView(getMeshPointer(), slot).getNumElements()) { + getMeshPointer()->removeAttribute(slot); + return true; + } + return false; +} + +glm::uint32 scriptable::ScriptableMesh::addAttribute(const QString& attributeName, const QVariant& defaultValue) { + auto slot = isValid() ? _getSlotNumber(attributeName) : -1; + if (slot < 0) { + return 0; + } + auto mesh = getMeshPointer(); + auto numVertices = getNumVertices(); + if (!getAttributeNames().contains(attributeName)) { + QVector values; + values.fill(defaultValue, numVertices); + mesh->addAttribute(slot, buffer_helpers::newFromVector(values, gpu::Stream::getDefaultElements()[slot])); + return values.size(); + } else { + auto bufferView = buffer_helpers::mesh::getBufferView(mesh, slot); + auto current = bufferView.getNumElements(); + if (current < numVertices) { + bufferView = buffer_helpers::resized(bufferView, numVertices); + for (glm::uint32 i = current; i < numVertices; i++) { + buffer_helpers::setValue(bufferView, i, defaultValue); + } + return numVertices - current; + } else if (current > numVertices) { + qCDebug(graphics_scripting) << QString("current=%1 > numVertices=%2").arg(current).arg(numVertices); + return 0; + } } return 0; } -QVariantMap scriptable::ScriptableMeshPart::scaleToFit(float unitScale) { - if (auto mesh = getMeshPointer()) { - auto box = mesh->evalPartsBound(0, (int)mesh->getNumParts()); - auto center = box.calcCenter(); - float maxDimension = glm::distance(box.getMaximumPoint(), box.getMinimumPoint()); - return scale(glm::vec3(unitScale / maxDimension), center); +glm::uint32 scriptable::ScriptableMesh::fillAttribute(const QString& attributeName, const QVariant& value) { + auto slot = isValid() ? _getSlotNumber(attributeName) : -1; + if (slot < 0) { + return 0; } - return {}; -} -QVariantMap scriptable::ScriptableMeshPart::translate(const glm::vec3& translation) { - return transform(glm::translate(translation)); -} -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; - return transform(glm::translate(center) * glm::scale(scale)); - } - return {}; -} -QVariantMap scriptable::ScriptableMeshPart::rotateDegrees(const glm::vec3& eulerAngles, const glm::vec3& origin) { - return rotate(glm::quat(glm::radians(eulerAngles)), 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; - return transform(glm::translate(center) * glm::toMat4(rotation)); - } - return {}; -} -QVariantMap scriptable::ScriptableMeshPart::transform(const glm::mat4& transform) { - if (auto mesh = getMeshPointer()) { - 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 {}; + auto mesh = getMeshPointer(); + auto numVertices = getNumVertices(); + QVector values; + values.fill(value, numVertices); + mesh->addAttribute(slot, buffer_helpers::newFromVector(values, gpu::Stream::getDefaultElements()[slot])); + return true; } -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 = 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 << buffer_helpers::toVariant(bufferView, i, asArray, attributeName.toStdString().c_str()); - } - } - } - return result; -} -QVariantMap scriptable::ScriptableMesh::getVertexAttributes(quint32 vertexIndex, QVector names) const { - QVariantMap result; +QVariantMap scriptable::ScriptableMesh::getMeshExtents() const { auto mesh = getMeshPointer(); - if (!mesh || vertexIndex >= getNumVertices()) { + auto box = mesh ? mesh->evalPartsBound(0, (int)mesh->getNumParts()) : AABox(); + return scriptable::toVariant(box).toMap(); +} + +glm::uint32 scriptable::ScriptableMesh::getNumParts() const { + if (auto mesh = getMeshPointer()) { + return (glm::uint32)mesh->getNumParts(); + } + return 0; +} + +QVariantList scriptable::ScriptableMesh::queryVertexAttributes(QVariant selector) const { + QVariantList result; + const auto& attributeName = selector.toString(); + if (!isValidIndex(0, attributeName)) { return result; } - 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 = buffer_helpers::getBufferView(mesh, slot); - if (vertexIndex < bufferView.getNumElements()) { - bool asArray = bufferView._element.getType() != gpu::FLOAT; - result[name] = buffer_helpers::toVariant(bufferView, vertexIndex, asArray, name.toStdString().c_str()); - } + auto slotNum = _getSlotNumber(attributeName); + const auto& bufferView = buffer_helpers::mesh::getBufferView(getMeshPointer(), static_cast(slotNum)); + glm::uint32 numElements = bufferView.getNumElements(); + for (glm::uint32 i = 0; i < numElements; i++) { + result << buffer_helpers::getValue(bufferView, i, qUtf8Printable(attributeName)); } return result; } -quint32 scriptable::ScriptableMesh::mapAttributeValues(QScriptValue _callback) { +QVariant scriptable::ScriptableMesh::getVertexProperty(glm::uint32 vertexIndex, const QString& attributeName) const { + if (!isValidIndex(vertexIndex, attributeName)) { + return QVariant(); + } + auto slotNum = _getSlotNumber(attributeName); + const auto& bufferView = buffer_helpers::mesh::getBufferView(getMeshPointer(), static_cast(slotNum)); + return buffer_helpers::getValue(bufferView, vertexIndex, qUtf8Printable(attributeName)); +} + +bool scriptable::ScriptableMesh::setVertexProperty(glm::uint32 vertexIndex, const QString& attributeName, const QVariant& value) { + if (!isValidIndex(vertexIndex, attributeName)) { + return false; + } + auto slotNum = _getSlotNumber(attributeName); + const auto& bufferView = buffer_helpers::mesh::getBufferView(getMeshPointer(), static_cast(slotNum)); + return buffer_helpers::setValue(bufferView, vertexIndex, value); +} + +glm::uint32 scriptable::ScriptableMesh::forEachVertex(QScriptValue _callback) { auto mesh = getMeshPointer(); if (!mesh) { return 0; } auto scopedHandler = jsBindCallback(_callback); - // 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"); @@ -264,205 +261,104 @@ quint32 scriptable::ScriptableMesh::mapAttributeValues(QScriptValue _callback) { return 0; } auto meshPart = js ? js->toScriptValue(getSelf()) : QScriptValue::NullValue; -#ifdef SCRIPTABLE_MESH_DEBUG - qCInfo(graphics_scripting) << "mapAttributeValues" << mesh.get() << js->currentContext()->thisObject().toQObject(); -#endif - auto obj = js->newObject(); - auto attributeViews = buffer_helpers::gatherBufferViews(mesh, { "normal", "color" }); - 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 }); + int numProcessed = 0; + buffer_helpers::mesh::forEachVertex(mesh, [&](glm::uint32 index, const QVariantMap& values) { + auto result = callback.call(scope, { js->toScriptValue(values), index, meshPart }); if (js->hasUncaughtException()) { js->currentContext()->throwValue(js->uncaughtException()); - return i; + return false; } + numProcessed++; + return true; + }); + return numProcessed; +} + +glm::uint32 scriptable::ScriptableMesh::updateVertexAttributes(QScriptValue _callback) { + auto mesh = getMeshPointer(); + if (!mesh) { + return 0; + } + auto scopedHandler = jsBindCallback(_callback); + + // 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() ? engine() : scopedHandler.engine(); // cache value to avoid resolving each iteration + if (!js) { + return 0; + } + auto meshPart = js ? js->toScriptValue(getSelf()) : QScriptValue::NullValue; + int numProcessed = 0; + auto attributeViews = buffer_helpers::mesh::getAllBufferViews(mesh); + buffer_helpers::mesh::forEachVertex(mesh, [&](glm::uint32 index, const QVariantMap& values) { + auto obj = js->toScriptValue(values); + auto result = callback.call(scope, { obj, index, meshPart }); + if (js->hasUncaughtException()) { + js->currentContext()->throwValue(js->uncaughtException()); + return false; + } if (result.isBool() && !result.toBool()) { // bail without modifying data if user explicitly returns false - continue; + return true; } 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); + buffer_helpers::setValue(a.second, index, attribute.toVariant()); } } + numProcessed++; + return true; + }); + return numProcessed; +} + +// protect against user scripts sending bogus values +bool scriptable::ScriptableMesh::isValidIndex(glm::uint32 vertexIndex, const QString& attributeName) const { + if (!isValid()) { + return false; } - return i; -} - -quint32 scriptable::ScriptableMeshPart::mapAttributeValues(QScriptValue callback) { - return parentMesh ? parentMesh->mapAttributeValues(callback) : 0; -} - -bool scriptable::ScriptableMeshPart::replaceMeshData(scriptable::ScriptableMeshPartPointer src, const QVector& attributeNames) { - auto target = getMeshPointer(); - auto source = src ? src->getMeshPointer() : nullptr; - if (!target || !source) { + const auto last = getNumVertices() - 1; + if (vertexIndex > last) { 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"; + context()->throwError(QString("vertexIndex=%1 out of range (firstVertexIndex=%2, lastVertexIndex=%3)").arg(vertexIndex).arg(0).arg(last)); } return false; } - - QVector attributes = attributeNames.isEmpty() ? src->parentMesh->getAttributeNames() : attributeNames; - - qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData -- " << - "source:" << QString::fromStdString(source->displayName) << - "target:" << QString::fromStdString(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 = buffer_helpers::gatherBufferViews(target); - for (const auto& a : attributeViews) { - auto slot = buffer_helpers::ATTRIBUTES[a.first]; - if (!attributes.contains(a.first)) { -#ifdef SCRIPTABLE_MESH_DEBUG - qCInfo(graphics_scripting) << "ScriptableMesh::replaceMeshData -- pruning target attribute" << a.first << slot; -#endif - target->removeAttribute(slot); + if (!attributeName.isEmpty()) { + auto slotNum = _getSlotNumber(attributeName); + if (slotNum < 0) { + if (context()) { + context()->throwError(QString("invalid attributeName=%1").arg(attributeName)); } + return false; } - } - - 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 = buffer_helpers::ATTRIBUTES[a]; - if (slot == gpu::Stream::POSITION) { - continue; - } -#ifdef SCRIPTABLE_MESH_DEBUG - auto& before = target->getAttributeBuffer(slot); -#endif - auto& input = source->getAttributeBuffer(slot); - if (input.getNumElements() == 0) { -#ifdef SCRIPTABLE_MESH_DEBUG - qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData buffer is empty -- pruning" << a << slot; -#endif - target->removeAttribute(slot); - } else { -#ifdef SCRIPTABLE_MESH_DEBUG - 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; + auto view = buffer_helpers::mesh::getBufferView(getMeshPointer(), static_cast(slotNum)); + if (vertexIndex >= view.getNumElements()) { + if (context()) { + context()->throwError(QString("vertexIndex=%1 out of range (attribute=%2, numElements=%3)").arg(vertexIndex).arg(attributeName).arg(view.getNumElements())); } -#endif - target->addAttribute(slot, buffer_helpers::clone(input)); + return false; } -#ifdef SCRIPTABLE_MESH_DEBUG - auto& after = target->getAttributeBuffer(slot); - qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData" << a << slot << before.getNumElements() << " -> " << after.getNumElements(); -#endif - } - - - return true; -} - -bool scriptable::ScriptableMeshPart::dedupeVertices(float epsilon) { - 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(graphics_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)) { - newIndices << remapIndices[index]; - } else { - qCInfo(graphics_scripting) << i << index << "!remapIndices[index]"; - } - } - - 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 = buffer_helpers::gatherBufferViews(mesh); - quint32 numUniqueVerts = uniqueVerts.size(); - for (const auto& a : attributeViews) { - auto& view = a.second; - auto slot = buffer_helpers::ATTRIBUTES[a.first]; - if (slot == gpu::Stream::POSITION) { - continue; - } - auto newView = buffer_helpers::resize(view, numUniqueVerts); -#ifdef SCRIPTABLE_MESH_DEBUG - qCInfo(graphics_scripting) << "ScriptableMeshPart::dedupeVertices" << a.first << slot << view.getNumElements(); - qCInfo(graphics_scripting) << a.first << "before: #" << view.getNumElements() << "after: #" << newView.getNumElements(); -#endif - quint32 numElements = (quint32)view.getNumElements(); - for (quint32 i = 0; i < numElements; i++) { - quint32 fromVertexIndex = i; - quint32 toVertexIndex = remapIndices.contains(fromVertexIndex) ? remapIndices[fromVertexIndex] : fromVertexIndex; - buffer_helpers::fromVariant( - newView, toVertexIndex, - buffer_helpers::toVariant(view, fromVertexIndex, false, "dedupe") - ); - } - mesh->addAttribute(slot, newView); } return true; } -scriptable::ScriptableMeshPointer scriptable::ScriptableMesh::cloneMesh(bool recalcNormals) { + +scriptable::ScriptableMeshPointer scriptable::ScriptableMesh::cloneMesh() { auto mesh = getMeshPointer(); if (!mesh) { qCInfo(graphics_scripting) << "ScriptableMesh::cloneMesh -- !meshPointer"; return nullptr; } - auto clone = buffer_helpers::cloneMesh(mesh); + auto clone = buffer_helpers::mesh::clone(mesh); - if (recalcNormals) { - buffer_helpers::recalculateNormals(clone); - } auto meshPointer = scriptable::make_scriptowned(provider, model, clone, nullptr); return scriptable::ScriptableMeshPointer(meshPointer); } @@ -505,114 +401,6 @@ scriptable::ScriptableMesh::~ScriptableMesh() { strongMesh.reset(); } -QString scriptable::ScriptableMeshPart::toOBJ() { - if (!getMeshPointer()) { - 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; - } - return engine->newQObject(object, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater); - } - - 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); - } - - 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 value) { - if (value.isObject() && value.property("callback").isFunction()) { - // value is already a bound callback - return value; - } - auto engine = value.engine(); - auto context = engine ? engine->currentContext() : nullptr; - auto length = context ? context->argumentCount() : 0; - QScriptValue scope = context ? context->thisObject() : QScriptValue::NullValue; - QScriptValue method; -#ifdef SCRIPTABLE_MESH_DEBUG - qCInfo(graphics_scripting) << "jsBindCallback" << engine << length << scope.toQObject() << method.toString(); -#endif - - // find position in the incoming JS Function.arguments array (so we can test for the two-argument case) - for (int i = 0; context && i < length; i++) { - if (context->argument(i).strictlyEquals(value)) { - method = context->argument(i+1); - } - } - if (method.isFunction() || method.isString()) { - // interpret as `API.func(..., scope, function callback(){})` or `API.func(..., scope, "methodName")` - scope = value; - } else { - // interpret as `API.func(..., function callback(){})` - method = value; - } -#ifdef SCRIPTABLE_MESH_DEBUG - qCInfo(graphics_scripting) << "scope:" << scope.toQObject() << "method:" << method.toString(); -#endif - return ::makeScopedHandlerObject(scope, method); - } -} #include "ScriptableMesh.moc" + diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h index 48ef6c3a81..16393de8c7 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h @@ -25,18 +25,23 @@ #include "GraphicsScriptingUtil.h" +#include + namespace scriptable { class ScriptableMesh : public ScriptableMeshBase, QScriptable { Q_OBJECT public: - 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(glm::uint32 numParts READ getNumParts) + Q_PROPERTY(glm::uint32 numAttributes READ getNumAttributes) + Q_PROPERTY(glm::uint32 numVertices READ getNumVertices) + Q_PROPERTY(glm::uint32 numIndices READ getNumIndices) Q_PROPERTY(QVector attributeNames READ getAttributeNames) Q_PROPERTY(QVector parts READ getMeshParts) - Q_PROPERTY(bool valid READ hasValidMesh) + Q_PROPERTY(bool valid READ isValid) Q_PROPERTY(bool strong READ hasValidStrongMesh) + Q_PROPERTY(QVariantMap extents READ getMeshExtents) + Q_PROPERTY(QVariantMap bufferFormats READ getBufferFormats) + QVariantMap getBufferFormats() const; operator const ScriptableMeshBase*() const { return (qobject_cast(this)); } @@ -48,116 +53,49 @@ namespace scriptable { ScriptableMesh(const ScriptableMesh& other) : ScriptableMeshBase(other), QScriptable() {}; virtual ~ScriptableMesh(); - Q_INVOKABLE const scriptable::ScriptableModelPointer getParentModel() const { return qobject_cast(model); } - Q_INVOKABLE const scriptable::MeshPointer getOwnedMeshPointer() const { return strongMesh; } + const scriptable::MeshPointer getOwnedMeshPointer() const { return strongMesh; } scriptable::ScriptableMeshPointer getSelf() const { return const_cast(this); } - bool hasValidMesh() const { return !weakMesh.expired(); } + bool isValid() const { return !weakMesh.expired(); } bool hasValidStrongMesh() const { return (bool)strongMesh; } - public slots: - uint32 getNumParts() const; - uint32 getNumVertices() const; - uint32 getNumAttributes() const; - uint32 getNumIndices() const; + glm::uint32 getNumParts() const; + glm::uint32 getNumVertices() const; + glm::uint32 getNumAttributes() const; + glm::uint32 getNumIndices() const; QVector getAttributeNames() const; QVector getMeshParts() 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; QVariantMap getMeshExtents() const; - 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: + // TODO: remove Q_INVOKABLE (curently exposed for debugging ) + Q_INVOKABLE int _getSlotNumber(const QString& attributeName) const; operator bool() const { return !weakMesh.expired(); } public slots: + const scriptable::ScriptableModelPointer getParentModel() const { return qobject_cast(model); } + QVector getIndices() const; + QVector findNearbyVertexIndices(const glm::vec3& origin, float epsilon = 1e-6) const; + + glm::uint32 addAttribute(const QString& attributeName, const QVariant& defaultValue = QVariant()); + glm::uint32 fillAttribute(const QString& attributeName, const QVariant& value); + bool removeAttribute(const QString& attributeName); + + QVariantList queryVertexAttributes(QVariant selector) const; + QVariantMap getVertexAttributes(glm::uint32 vertexIndex) const; + bool setVertexAttributes(glm::uint32 vertexIndex, const QVariantMap& attributeValues); + + QVariant getVertexProperty(glm::uint32 vertexIndex, const QString& attributeName) const; + bool setVertexProperty(glm::uint32 vertexIndex, const QString& attributeName, const QVariant& value); + + scriptable::ScriptableMeshPointer cloneMesh(); + // QScriptEngine-specific wrappers - uint32 mapAttributeValues(QScriptValue callback); + glm::uint32 updateVertexAttributes(QScriptValue callback); + glm::uint32 forEachVertex(QScriptValue callback); + bool isValidIndex(glm::uint32 vertexIndex, const QString& attributeName = QString()) const; }; - // 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) - - ScriptableMeshPart(scriptable::ScriptableMeshPointer parentMesh, int partIndex); - ScriptableMeshPart& operator=(const ScriptableMeshPart& view) { parentMesh=view.parentMesh; return *this; }; - ScriptableMeshPart(const ScriptableMeshPart& other) : QObject(other.parent()), QScriptable(), parentMesh(other.parentMesh), partIndex(other.partIndex) {} - - 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 { - if (parentMesh && faceIndex + 2 < parentMesh->getNumIndices()) { - return parentMesh->getIndices().mid(faceIndex*3, 3); - } - return 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); - - 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 - uint32 mapAttributeValues(QScriptValue callback); - - public: - scriptable::ScriptableMeshPointer parentMesh; - uint32 partIndex; - protected: - int _elementsPerFace{ 3 }; - QString _topology{ "triangles" }; - scriptable::MeshPointer getMeshPointer() const { return parentMesh ? parentMesh->getMeshPointer() : nullptr; } - }; - - // 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::uint32) -Q_DECLARE_METATYPE(QVector) +Q_DECLARE_METATYPE(glm::uint32) +Q_DECLARE_METATYPE(QVector) diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.cpp new file mode 100644 index 0000000000..0d4bb7bdc5 --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.cpp @@ -0,0 +1,438 @@ +// +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "Forward.h" + +#include "ScriptableMeshPart.h" + +#include "BufferViewScripting.h" +#include "GraphicsScriptingUtil.h" +#include "OBJWriter.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QString scriptable::ScriptableMeshPart::toOBJ() { + if (!getMeshPointer()) { + if (context()) { + context()->throwError(QString("null mesh")); + } else { + qCWarning(graphics_scripting) << "null mesh"; + } + return QString(); + } + return writeOBJToString({ getMeshPointer() }); +} + + +bool scriptable::ScriptableMeshPart::isValidIndex(glm::uint32 vertexIndex, const QString& attributeName) const { + return isValid() && parentMesh->isValidIndex(vertexIndex, attributeName); +} + +bool scriptable::ScriptableMeshPart::setVertexAttributes(glm::uint32 vertexIndex, const QVariantMap& attributes) { + if (!isValidIndex(vertexIndex)) { + return false; + } + return buffer_helpers::mesh::setVertexAttributes(getMeshPointer(), vertexIndex, attributes); +} + +QVariantMap scriptable::ScriptableMeshPart::getVertexAttributes(glm::uint32 vertexIndex) const { + if (!isValidIndex(vertexIndex)) { + return QVariantMap(); + } + return parentMesh->getVertexAttributes(vertexIndex); +} + +bool scriptable::ScriptableMeshPart::setVertexProperty(glm::uint32 vertexIndex, const QString& attributeName, const QVariant& value) { + if (!isValidIndex(vertexIndex, attributeName)) { + return false; + } + auto slotNum = parentMesh->_getSlotNumber(attributeName); + const auto& bufferView = buffer_helpers::mesh::getBufferView(getMeshPointer(), static_cast(slotNum)); + return buffer_helpers::setValue(bufferView, vertexIndex, value); +} + +QVariant scriptable::ScriptableMeshPart::getVertexProperty(glm::uint32 vertexIndex, const QString& attributeName) const { + if (!isValidIndex(vertexIndex, attributeName)) { + return false; + } + return parentMesh->getVertexProperty(vertexIndex, attributeName); +} + +QVariantList scriptable::ScriptableMeshPart::queryVertexAttributes(QVariant selector) const { + QVariantList result; + if (!isValid()) { + return result; + } + return parentMesh->queryVertexAttributes(selector); +} + +glm::uint32 scriptable::ScriptableMeshPart::forEachVertex(QScriptValue _callback) { + // TODO: limit to vertices within the part's indexed range? + return isValid() ? parentMesh->forEachVertex(_callback) : 0; +} + +glm::uint32 scriptable::ScriptableMeshPart::updateVertexAttributes(QScriptValue _callback) { + // TODO: limit to vertices within the part's indexed range? + return isValid() ? parentMesh->updateVertexAttributes(_callback) : 0; +} + +bool scriptable::ScriptableMeshPart::replaceMeshPartData(scriptable::ScriptableMeshPartPointer src, const QVector& attributeNames) { + auto target = getMeshPointer(); + auto source = src ? src->getMeshPointer() : nullptr; + if (!target || !source) { + 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->parentMesh->getAttributeNames() : attributeNames; + + qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData -- " << + "source:" << QString::fromStdString(source->displayName) << + "target:" << QString::fromStdString(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 = buffer_helpers::mesh::getAllBufferViews(target); + for (const auto& a : attributeViews) { + auto slot = buffer_helpers::ATTRIBUTES[a.first]; + if (!attributes.contains(a.first)) { +#ifdef SCRIPTABLE_MESH_DEBUG + qCInfo(graphics_scripting) << "ScriptableMesh::replaceMeshData -- pruning target attribute" << a.first << slot; +#endif + target->removeAttribute(slot); + } + } + } + + 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 = buffer_helpers::ATTRIBUTES[a]; + if (slot == gpu::Stream::POSITION) { + continue; + } +#ifdef SCRIPTABLE_MESH_DEBUG + auto& before = target->getAttributeBuffer(slot); +#endif + auto& input = source->getAttributeBuffer(slot); + if (input.getNumElements() == 0) { +#ifdef SCRIPTABLE_MESH_DEBUG + qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData buffer is empty -- pruning" << a << slot; +#endif + target->removeAttribute(slot); + } else { +#ifdef SCRIPTABLE_MESH_DEBUG + 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; + } +#endif + target->addAttribute(slot, buffer_helpers::clone(input)); + } +#ifdef SCRIPTABLE_MESH_DEBUG + auto& after = target->getAttributeBuffer(slot); + qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData" << a << slot << before.getNumElements() << " -> " << after.getNumElements(); +#endif + } + + + return true; +} + +bool scriptable::ScriptableMeshPart::dedupeVertices(float epsilon) { + 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 (glm::uint32 i = 0; i < numPositions; i++) { + const glm::uint32 numUnique = uniqueVerts.size(); + const auto& position = positions.get(i); + bool unique = true; + for (glm::uint32 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(graphics_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 (glm::uint32 i = 0; i < numIndices; i++) { + glm::uint32 index = esize == 4 ? indices.get(i) : indices.get(i); + if (remapIndices.contains(index)) { + newIndices << remapIndices[index]; + } else { + qCInfo(graphics_scripting) << i << index << "!remapIndices[index]"; + } + } + + mesh->setIndexBuffer(buffer_helpers::newFromVector(newIndices, { gpu::SCALAR, gpu::UINT32, gpu::INDEX })); + mesh->setVertexBuffer(buffer_helpers::newFromVector(uniqueVerts, gpu::Element::VEC3F_XYZ)); + + auto attributeViews = buffer_helpers::mesh::getAllBufferViews(mesh); + glm::uint32 numUniqueVerts = uniqueVerts.size(); + for (const auto& a : attributeViews) { + auto& view = a.second; + auto slot = buffer_helpers::ATTRIBUTES[a.first]; + if (slot == gpu::Stream::POSITION) { + continue; + } + auto newView = buffer_helpers::resized(view, numUniqueVerts); +#ifdef SCRIPTABLE_MESH_DEBUG + qCInfo(graphics_scripting) << "ScriptableMeshPart::dedupeVertices" << a.first << slot << view.getNumElements(); + qCInfo(graphics_scripting) << a.first << "before: #" << view.getNumElements() << "after: #" << newView.getNumElements(); +#endif + glm::uint32 numElements = (glm::uint32)view.getNumElements(); + for (glm::uint32 i = 0; i < numElements; i++) { + glm::uint32 fromVertexIndex = i; + glm::uint32 toVertexIndex = remapIndices.contains(fromVertexIndex) ? remapIndices[fromVertexIndex] : fromVertexIndex; + buffer_helpers::setValue(newView, toVertexIndex, buffer_helpers::getValue(view, fromVertexIndex, "dedupe")); + } + mesh->addAttribute(slot, newView); + } + return true; +} + +bool scriptable::ScriptableMeshPart::removeAttribute(const QString& attributeName) { + return isValid() && parentMesh->removeAttribute(attributeName); +} + +glm::uint32 scriptable::ScriptableMeshPart::addAttribute(const QString& attributeName, const QVariant& defaultValue) { + return isValid() ? parentMesh->addAttribute(attributeName, defaultValue): 0; +} + +glm::uint32 scriptable::ScriptableMeshPart::fillAttribute(const QString& attributeName, const QVariant& value) { + return isValid() ? parentMesh->fillAttribute(attributeName, value) : 0; +} + +QVector scriptable::ScriptableMeshPart::findNearbyPartVertexIndices(const glm::vec3& origin, float epsilon) const { + QSet result; + if (!isValid()) { + return result.toList().toVector(); + } + auto mesh = getMeshPointer(); + auto offset = getFirstVertexIndex(); + auto numIndices = getNumIndices(); + auto vertexBuffer = mesh->getVertexBuffer(); + auto indexBuffer = mesh->getIndexBuffer(); + const auto epsilon2 = epsilon*epsilon; + + for (glm::uint32 i = 0; i < numIndices; i++) { + auto vertexIndex = buffer_helpers::getValue(indexBuffer, offset + i); + if (result.contains(vertexIndex)) { + continue; + } + const auto& position = buffer_helpers::getValue(vertexBuffer, vertexIndex); + if (glm::length2(position - origin) <= epsilon2) { + result << vertexIndex; + } + } + return result.toList().toVector(); +} + +scriptable::ScriptableMeshPartPointer scriptable::ScriptableMeshPart::cloneMeshPart() { + if (parentMesh) { + if (auto clone = parentMesh->cloneMesh()) { + return clone->getMeshParts().value(partIndex); + } + } + return nullptr; +} + +QVariantMap scriptable::ScriptableMeshPart::scaleToFit(float unitScale) { + if (auto mesh = getMeshPointer()) { + auto box = mesh->evalPartsBound(0, (int)mesh->getNumParts()); + auto center = box.calcCenter(); + float maxDimension = glm::distance(box.getMaximumPoint(), box.getMinimumPoint()); + return scale(glm::vec3(unitScale / maxDimension), center); + } + return {}; +} +QVariantMap scriptable::ScriptableMeshPart::translate(const glm::vec3& translation) { + return transform(glm::translate(translation)); +} +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; + return transform(glm::translate(center) * glm::scale(scale)); + } + return {}; +} +QVariantMap scriptable::ScriptableMeshPart::rotateDegrees(const glm::vec3& eulerAngles, const glm::vec3& origin) { + return rotate(glm::quat(glm::radians(eulerAngles)), 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; + return transform(glm::translate(center) * glm::toMat4(rotation)); + } + return {}; +} +QVariantMap scriptable::ScriptableMeshPart::transform(const glm::mat4& transform) { + if (auto mesh = getMeshPointer()) { + const auto& pos = buffer_helpers::mesh::getBufferView(mesh, gpu::Stream::POSITION); + const glm::uint32 num = (glm::uint32)pos.getNumElements(); + for (glm::uint32 i = 0; i < num; i++) { + auto& position = pos.edit(i); + position = transform * glm::vec4(position, 1.0f); + } + return parentMesh->getMeshExtents(); + } + return {}; +} + + +scriptable::ScriptableMeshPart::ScriptableMeshPart(scriptable::ScriptableMeshPointer parentMesh, int partIndex) + : QObject(), parentMesh(parentMesh), partIndex(partIndex) { + setObjectName(QString("%1.part[%2]").arg(parentMesh ? parentMesh->objectName() : "").arg(partIndex)); +} + +QVector scriptable::ScriptableMeshPart::getIndices() const { + if (auto mesh = getMeshPointer()) { +#ifdef SCRIPTABLE_MESH_DEBUG + qCDebug(graphics_scripting, "getIndices mesh %p", mesh.get()); +#endif + return buffer_helpers::bufferToVector(mesh->getIndexBuffer()); + } + return QVector(); +} + +bool scriptable::ScriptableMeshPart::setFirstVertexIndex( glm::uint32 vertexIndex) { + if (!isValidIndex(vertexIndex)) { + return false; + } + auto& part = getMeshPointer()->getPartBuffer().edit(partIndex); + part._startIndex = vertexIndex; + return true; +} + +bool scriptable::ScriptableMeshPart::setBaseVertexIndex( glm::uint32 vertexIndex) { + if (!isValidIndex(vertexIndex)) { + return false; + } + auto& part = getMeshPointer()->getPartBuffer().edit(partIndex); + part._baseVertex = vertexIndex; + return true; +} + +bool scriptable::ScriptableMeshPart::setLastVertexIndex( glm::uint32 vertexIndex) { + if (!isValidIndex(vertexIndex) || vertexIndex <= getFirstVertexIndex()) { + return false; + } + auto& part = getMeshPointer()->getPartBuffer().edit(partIndex); + part._numIndices = vertexIndex - part._startIndex; + return true; +} + +bool scriptable::ScriptableMeshPart::setIndices(const QVector& indices) { + if (!isValid()) { + return false; + } + glm::uint32 len = indices.size(); + if (len != getNumVertices()) { + context()->throwError(QString("setIndices: currently new indicies must be assign 1:1 across old indicies (indicies.size()=%1, numIndices=%2)") + .arg(len).arg(getNumIndices())); + } + auto mesh = getMeshPointer(); + auto indexBuffer = mesh->getIndexBuffer(); + + // first loop to validate all indices are valid + for (glm::uint32 i = 0; i < len; i++) { + if (!isValidIndex(indices.at(i))) { + return false; + } + } + const auto first = getFirstVertexIndex(); + // now actually apply them + for (glm::uint32 i = 0; i < len; i++) { + buffer_helpers::setValue(indexBuffer, first + i, indices.at(i)); + } + return true; +} + +const graphics::Mesh::Part& scriptable::ScriptableMeshPart::getMeshPart() const { + static const graphics::Mesh::Part invalidPart; + if (!isValid()) { + return invalidPart; + } + return getMeshPointer()->getPartBuffer().get(partIndex); +} + +bool scriptable::ScriptableMeshPart::setTopology(graphics::Mesh::Topology topology) { + if (!isValid()) { + return false; + } + auto& part = getMeshPointer()->getPartBuffer().edit(partIndex); + if (topology == graphics::Mesh::Topology::POINTS || + topology == graphics::Mesh::Topology::LINES || + topology == graphics::Mesh::Topology::TRIANGLES) { + part._topology = topology; + return true; + } + return false; +} + +glm::uint32 scriptable::ScriptableMeshPart::getTopologyLength() const { + switch(getTopology()) { + case graphics::Mesh::Topology::POINTS: return 1; + case graphics::Mesh::Topology::LINES: return 2; + case graphics::Mesh::Topology::TRIANGLES: return 3; + default: qCDebug(graphics_scripting) << "getTopologyLength -- unrecognized topology" << getTopology(); + } + return 0; +} + +QVector scriptable::ScriptableMeshPart::getFace(glm::uint32 faceIndex) const { + if (faceIndex < getNumFaces()) { + return getIndices().mid(faceIndex * getTopologyLength(), getTopologyLength()); + } + return QVector(); +} + +QVariantMap scriptable::ScriptableMeshPart::getPartExtents() const { + graphics::Box box; + if (auto mesh = getMeshPointer()) { + box = mesh->evalPartBound(partIndex); + } + return scriptable::toVariant(box).toMap(); +} diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h new file mode 100644 index 0000000000..4ef0465ca3 --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h @@ -0,0 +1,106 @@ +// +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once + +#include "ScriptableMesh.h" + +namespace scriptable { + class ScriptableMeshPart : public QObject, QScriptable { + Q_OBJECT + Q_PROPERTY(bool valid READ isValid) + Q_PROPERTY(glm::uint32 partIndex MEMBER partIndex CONSTANT) + Q_PROPERTY(glm::uint32 firstVertexIndex READ getFirstVertexIndex WRITE setFirstVertexIndex) + Q_PROPERTY(glm::uint32 baseVertexIndex READ getBaseVertexIndex WRITE setBaseVertexIndex) + Q_PROPERTY(glm::uint32 lastVertexIndex READ getLastVertexIndex WRITE setLastVertexIndex) + Q_PROPERTY(int numVerticesPerFace READ getTopologyLength) + Q_PROPERTY(graphics::Mesh::Topology topology READ getTopology WRITE setTopology) + + Q_PROPERTY(glm::uint32 numFaces READ getNumFaces) + Q_PROPERTY(glm::uint32 numAttributes READ getNumAttributes) + Q_PROPERTY(glm::uint32 numVertices READ getNumVertices) + Q_PROPERTY(glm::uint32 numIndices READ getNumIndices WRITE setNumIndices) + + Q_PROPERTY(QVariantMap extents READ getPartExtents) + Q_PROPERTY(QVector attributeNames READ getAttributeNames) + Q_PROPERTY(QVariantMap bufferFormats READ getBufferFormats) + + public: + ScriptableMeshPart(scriptable::ScriptableMeshPointer parentMesh, int partIndex); + ScriptableMeshPart& operator=(const ScriptableMeshPart& view) { parentMesh=view.parentMesh; return *this; }; + ScriptableMeshPart(const ScriptableMeshPart& other) : QObject(other.parent()), QScriptable(), parentMesh(other.parentMesh), partIndex(other.partIndex) {} + bool isValid() const { auto mesh = getMeshPointer(); return mesh && partIndex < mesh->getNumParts(); } + + public slots: + QVector getIndices() const; + bool setIndices(const QVector& indices); + QVector findNearbyPartVertexIndices(const glm::vec3& origin, float epsilon = 1e-6) const; + QVariantList queryVertexAttributes(QVariant selector) const; + QVariantMap getVertexAttributes(glm::uint32 vertexIndex) const; + bool setVertexAttributes(glm::uint32 vertexIndex, const QVariantMap& attributeValues); + + QVariant getVertexProperty(glm::uint32 vertexIndex, const QString& attributeName) const; + bool setVertexProperty(glm::uint32 vertexIndex, const QString& attributeName, const QVariant& attributeValues); + + QVector getFace(glm::uint32 faceIndex) const; + + 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); + + glm::uint32 addAttribute(const QString& attributeName, const QVariant& defaultValue = QVariant()); + glm::uint32 fillAttribute(const QString& attributeName, const QVariant& value); + bool removeAttribute(const QString& attributeName); + bool dedupeVertices(float epsilon = 1e-6); + + scriptable::ScriptableMeshPointer getParentMesh() const { return parentMesh; } + + bool replaceMeshPartData(scriptable::ScriptableMeshPartPointer source, const QVector& attributeNames = QVector()); + scriptable::ScriptableMeshPartPointer cloneMeshPart(); + + QString toOBJ(); + + // QScriptEngine-specific wrappers + glm::uint32 updateVertexAttributes(QScriptValue callback); + glm::uint32 forEachVertex(QScriptValue callback); + + bool isValidIndex(glm::uint32 vertexIndex, const QString& attributeName = QString()) const; + public: + scriptable::ScriptableMeshPointer parentMesh; + glm::uint32 partIndex; + + protected: + const graphics::Mesh::Part& getMeshPart() const; + scriptable::MeshPointer getMeshPointer() const { return parentMesh ? parentMesh->getMeshPointer() : nullptr; } + QVariantMap getBufferFormats() { return isValid() ? parentMesh->getBufferFormats() : QVariantMap(); } + glm::uint32 getNumAttributes() const { return isValid() ? parentMesh->getNumAttributes() : 0; } + + bool setTopology(graphics::Mesh::Topology topology); + graphics::Mesh::Topology getTopology() const { return isValid() ? getMeshPart()._topology : graphics::Mesh::Topology(); } + glm::uint32 getTopologyLength() const; + glm::uint32 getNumIndices() const { return isValid() ? getMeshPart()._numIndices : 0; } + bool setNumIndices(glm::uint32 numIndices) { return setLastVertexIndex(getFirstVertexIndex() + numIndices); } + glm::uint32 getNumVertices() const { return isValid() ? parentMesh->getNumVertices() : 0; } + + bool setFirstVertexIndex(glm::uint32 vertexIndex); + glm::uint32 getFirstVertexIndex() const { return isValid() ? getMeshPart()._startIndex : 0; } + bool setLastVertexIndex(glm::uint32 vertexIndex); + glm::uint32 getLastVertexIndex() const { return isValid() ? getFirstVertexIndex() + getNumIndices() - 1 : 0; } + bool setBaseVertexIndex(glm::uint32 vertexIndex); + glm::uint32 getBaseVertexIndex() const { return isValid() ? getMeshPart()._baseVertex : 0; } + + glm::uint32 getNumFaces() const { return getNumIndices() / getTopologyLength(); } + QVector getAttributeNames() const { return isValid() ? parentMesh->getAttributeNames() : QVector(); } + QVariantMap getPartExtents() const; + }; +} + +Q_DECLARE_METATYPE(scriptable::ScriptableMeshPartPointer) +Q_DECLARE_METATYPE(QVector) diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp index 8ceb7de6a2..36322d170d 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp @@ -58,7 +58,7 @@ scriptable::ScriptableModelPointer scriptable::ScriptableModel::cloneModel(const scriptable::ScriptableModelPointer clone = scriptable::ScriptableModelPointer(new scriptable::ScriptableModel(*this)); clone->meshes.clear(); for (const auto &mesh : getConstMeshes()) { - auto cloned = mesh->cloneMesh(options.value("recalculateNormals").toBool()); + auto cloned = mesh->cloneMesh(); if (auto tmp = qobject_cast(cloned)) { clone->meshes << *tmp; tmp->deleteLater(); // schedule our copy for cleanup @@ -70,8 +70,8 @@ scriptable::ScriptableModelPointer scriptable::ScriptableModel::cloneModel(const } -const QVector scriptable::ScriptableModel::getConstMeshes() const { - QVector out; +const scriptable::ScriptableMeshes scriptable::ScriptableModel::getConstMeshes() const { + scriptable::ScriptableMeshes out; for (const auto& mesh : meshes) { const scriptable::ScriptableMesh* m = qobject_cast(&mesh); if (!m) { @@ -85,8 +85,8 @@ const QVector scriptable::ScriptableModel::ge return out; } -QVector scriptable::ScriptableModel::getMeshes() { - QVector out; +scriptable::ScriptableMeshes scriptable::ScriptableModel::getMeshes() { + scriptable::ScriptableMeshes out; for (auto& mesh : meshes) { scriptable::ScriptableMesh* m = qobject_cast(&mesh); if (!m) { @@ -100,9 +100,10 @@ QVector scriptable::ScriptableModel::getMeshe return out; } -quint32 scriptable::ScriptableModel::mapAttributeValues(QScriptValue callback) { - quint32 result = 0; - QVector in = getMeshes(); +#if 0 +glm::uint32 scriptable::ScriptableModel::forEachVertexAttribute(QScriptValue callback) { + glm::uint32 result = 0; + scriptable::ScriptableMeshes in = getMeshes(); if (in.size()) { foreach (scriptable::ScriptableMeshPointer meshProxy, in) { result += meshProxy->mapAttributeValues(callback); @@ -110,5 +111,6 @@ quint32 scriptable::ScriptableModel::mapAttributeValues(QScriptValue callback) { } return result; } +#endif #include "ScriptableModel.moc" diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h index 4ed1cc9554..d821f1224d 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h @@ -13,35 +13,34 @@ class QScriptValue; namespace scriptable { + + using ScriptableMeshes = QVector; class ScriptableModel : public ScriptableModelBase { Q_OBJECT - public: Q_PROPERTY(QUuid objectID MEMBER objectID CONSTANT) - Q_PROPERTY(uint32 numMeshes READ getNumMeshes) - Q_PROPERTY(QVector meshes READ getMeshes) + Q_PROPERTY(glm::uint32 numMeshes READ getNumMeshes) + Q_PROPERTY(ScriptableMeshes meshes READ getMeshes) + public: 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; } - - Q_INVOKABLE scriptable::ScriptableModelPointer cloneModel(const QVariantMap& options = QVariantMap()); - // TODO: in future accessors for these could go here - // QVariantMap shapes; - // QVariantMap materials; - // QVariantMap armature; - - QVector getMeshes(); - const QVector getConstMeshes() const; operator scriptable::ScriptableModelBasePointer() { return QPointer(qobject_cast(this)); } + ScriptableMeshes getMeshes(); + const ScriptableMeshes getConstMeshes() const; + public slots: + scriptable::ScriptableModelPointer cloneModel(const QVariantMap& options = QVariantMap()); + QString toString() const; // QScriptEngine-specific wrappers - Q_INVOKABLE uint32 mapAttributeValues(QScriptValue callback); - Q_INVOKABLE QString toString() const; - Q_INVOKABLE uint32 getNumMeshes() { return meshes.size(); } + //glm::uint32 forEachMeshVertexAttribute(QScriptValue callback); + protected: + glm::uint32 getNumMeshes() { return meshes.size(); } + }; } diff --git a/libraries/graphics/src/graphics/BufferViewHelpers.cpp b/libraries/graphics/src/graphics/BufferViewHelpers.cpp index 29dcbd58e3..46bd39bb45 100644 --- a/libraries/graphics/src/graphics/BufferViewHelpers.cpp +++ b/libraries/graphics/src/graphics/BufferViewHelpers.cpp @@ -15,11 +15,11 @@ #include #include "Geometry.h" +#include "GpuHelpers.h" -#include #include +#include -#include #include #include @@ -32,11 +32,10 @@ namespace { QLoggingCategory bufferhelper_logging{ "hifi.bufferview" }; } - const std::array buffer_helpers::XYZW = { { "x", "y", "z", "w" } }; const std::array buffer_helpers::ZERO123 = { { "0", "1", "2", "3" } }; -gpu::BufferView buffer_helpers::getBufferView(graphics::MeshPointer mesh, gpu::Stream::Slot slot) { +gpu::BufferView buffer_helpers::mesh::getBufferView(const graphics::MeshPointer& mesh, gpu::Stream::Slot slot) { return slot == gpu::Stream::POSITION ? mesh->getVertexBuffer() : mesh->getAttributeBuffer(slot); } @@ -56,21 +55,13 @@ QMap buffer_helpers::ATTRIBUTES{ namespace { - bool boundsCheck(const gpu::BufferView& view, quint32 index) { + bool boundsCheck(const gpu::BufferView& view, glm::uint32 index) { const auto byteLength = view._element.getSize(); return ( index < view.getNumElements() && index * byteLength < (view._size - 1) * byteLength ); } - - template QVariant getBufferViewElement(const gpu::BufferView& view, quint32 index, bool asArray = false) { - return buffer_helpers::glmVecToVariant(view.get(index), asArray); - } - - template void setBufferViewElement(const gpu::BufferView& view, quint32 index, const QVariant& v) { - view.edit(index) = buffer_helpers::glmVecFromVariant(v); - } } void buffer_helpers::packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, glm::uint32& packedNormal, glm::uint32& packedTangent) { @@ -99,127 +90,23 @@ void buffer_helpers::packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, g packedTangent = tangentStruct.pack; } -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; - - if (BYTES_PER_ELEMENT == 1) { - switch(vecN) { - case 2: setBufferViewElement(view, index, v); return true; - case 3: setBufferViewElement(view, index, v); return true; - case 4: { - if (element == gpu::Element::COLOR_RGBA_32) { - glm::uint32 rawColor;// = glm::packUnorm4x8(glm::vec4(glmVecFromVariant(v), 0.0f)); - 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; - case 4: setBufferViewElement(view, index, v); return true; - } - } else if (BYTES_PER_ELEMENT == 4) { - if (dataType == gpu::FLOAT) { - switch(vecN) { - case 2: setBufferViewElement(view, index, v); return true; - case 3: setBufferViewElement(view, index, v); return true; - case 4: setBufferViewElement(view, index, v); return true; - } - } else { - switch(vecN) { - case 2: setBufferViewElement(view, index, v); return true; - case 3: setBufferViewElement(view, index, v); return true; - case 4: setBufferViewElement(view, index, v); return true; +namespace { + template + glm::uint32 forEachGlmVec(const gpu::BufferView& view, std::function func) { + QVector result; + const glm::uint32 num = (glm::uint32)view.getNumElements(); + glm::uint32 i = 0; + for (; i < num; i++) { + if (!func(i, view.get(i))) { + break; } } + return i; } - return false; } -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()); - 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() << "toVariant -- byteOffset out of range " << byteOffset << " < " << maxByteOffset; - qDebug() << "toVariant -- index: " << index << "numElements" << view.getNumElements(); - qDebug() << "toVariant -- vecN: " << vecN << "byteLength" << byteLength << "BYTES_PER_ELEMENT" << BYTES_PER_ELEMENT; - } - Q_ASSERT(byteOffset <= maxByteOffset); - } - if (BYTES_PER_ELEMENT == 1) { - switch(vecN) { - case 2: return getBufferViewElement(view, index, asArray); - case 3: return getBufferViewElement(view, index, asArray); - case 4: { - if (element == gpu::Element::COLOR_RGBA_32) { - auto rawColor = view.get(index); - return glmVecToVariant(glm::vec3(glm::unpackUnorm4x8(rawColor))); - } else if (element == gpu::Element::VEC4F_NORMALIZED_XYZ10W2) { - auto packedNormal = view.get(index); - return glmVecToVariant(glm::vec3(glm::unpackSnorm3x10_1x2(packedNormal))); - } - - return getBufferViewElement(view, index, asArray); - } - } - } 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); - case 4: return getBufferViewElement(view, index, asArray); - } - } else if (BYTES_PER_ELEMENT == 4) { - if (dataType == gpu::FLOAT) { - switch(vecN) { - case 2: return getBufferViewElement(view, index, asArray); - case 3: return getBufferViewElement(view, index, asArray); - case 4: return getBufferViewElement(view, index, asArray); - } - } else { - switch(vecN) { - case 2: return getBufferViewElement(view, index, asArray); - case 3: return getBufferViewElement(view, index, asArray); - case 4: return getBufferViewElement(view, index, asArray); - } - } - } - return QVariant(); +template<> glm::uint32 buffer_helpers::forEach(const gpu::BufferView& view, std::function func) { + return forEachGlmVec(view, func); } template @@ -256,59 +143,63 @@ const T buffer_helpers::glmVecFromVariant(const QVariant& v) { } else { value = list.value(i).toFloat(); } -#ifdef DEBUG_BUFFERVIEW_SCRIPTING +#ifdef DEBUG_BUFFERVIEW_HELPERS if (value != value) { // NAN qWarning().nospace()<< "vec" << len << "." << components[i] << " NAN received from script.... " << v.toString(); } -#endif +#endif result[i] = value; } return result; } +// QVector => BufferView template -gpu::BufferView buffer_helpers::fromVector(const QVector& elements, const gpu::Element& elementType) { +gpu::BufferView buffer_helpers::newFromVector(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 }; } namespace { template - gpu::BufferView _fromVector(const QVector& elements, const gpu::Element& elementType) { + gpu::BufferView bufferViewFromVector(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 struct GpuVec4ToGlm; -template struct GpuScalarToGlm; +template<> gpu::BufferView buffer_helpers::newFromVector(const QVector& elements, const gpu::Element& elementType) { return bufferViewFromVector(elements, elementType); } +template<> gpu::BufferView buffer_helpers::newFromVector(const QVector& elements, const gpu::Element& elementType) { return bufferViewFromVector(elements, elementType); } +template<> gpu::BufferView buffer_helpers::newFromVector( const QVector& elements, const gpu::Element& elementType) { return bufferViewFromVector(elements, elementType); } +template<> gpu::BufferView buffer_helpers::newFromVector(const QVector& elements, const gpu::Element& elementType) { return bufferViewFromVector(elements, elementType); } +template<> gpu::BufferView buffer_helpers::newFromVector(const QVector& elements, const gpu::Element& elementType) { return bufferViewFromVector(elements, elementType); } struct GpuToGlmAdapter { - static float error(const QString& name, const gpu::BufferView& view, quint32 index, const char *hint) { - qDebug() << QString("GpuToGlmAdapter:: unhandled type=%1(element=%2) size=%3(per=%4) vec%5 hint=%6 #%7") + static float error(const QString& name, const gpu::BufferView& view, glm::uint32 index, const char *hint) { + qDebug() << QString("GpuToGlmAdapter:: unhandled type=%1(element=%2) size=%3(location=%4,per=%5) vec%6 hint=%7 #%8 %9 %10") .arg(name) - .arg(view._element.getType()) + .arg(gpu::toString(view._element.getType())) .arg(view._element.getSize()) + .arg(view._element.getLocationSize()) .arg(view._element.getSize() / view._element.getScalarCount()) .arg(view._element.getScalarCount()) .arg(hint) - .arg(view.getNumElements()); + .arg(view.getNumElements()) + .arg(gpu::toString(view._element.getSemantic())) + .arg(gpu::toString(view._element.getDimension())); Q_ASSERT(false); assert(false); return NAN; } }; +#define CHECK_SIZE(T) if (view._element.getSize() != sizeof(T)) { qDebug() << "invalid elementSize" << hint << view._element.getSize() << "expected:" << sizeof(T); break; } + template struct GpuScalarToGlm : GpuToGlmAdapter { - static T get(const gpu::BufferView& view, quint32 index, const char *hint) { switch(view._element.getType()) { + static T get(const gpu::BufferView& view, glm::uint32 index, const char *hint) { +#ifdef DEBUG_BUFFERVIEW_HELPERS + if(!boundsCheck(view, index))return T(error("GpuScalarToGlm::get::out of bounds", view, index, hint)); +#endif + 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); @@ -316,44 +207,101 @@ template struct GpuScalarToGlm : GpuToGlmAdapter { 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))); + case gpu::HALF: return T(glm::unpackHalf1x16(view.get(index))); + case gpu::NUINT8: return T(glm::unpackUnorm1x8(view.get(index))); default: break; - } return T(error("GpuScalarToGlm", view, index, hint)); + } return T(error("GpuScalarToGlm::get", view, index, hint)); + } + static bool set(const gpu::BufferView& view, glm::uint32 index, const T& value, const char *hint) { +#ifdef DEBUG_BUFFERVIEW_HELPERS + if(!boundsCheck(view, index))return T(error("GpuScalarToGlm::set::out of bounds", view, index, hint)); +#endif + switch(view._element.getType()) { + case gpu::UINT32: view.edit(index) = value; return true; + case gpu::UINT16: view.edit(index) = value; return true; + case gpu::UINT8: view.edit(index) = value; return true; + case gpu::INT32: view.edit(index) = value; return true; + case gpu::INT16: view.edit(index) = value; return true; + case gpu::INT8: view.edit(index) = value; return true; + case gpu::FLOAT: view.edit(index) = value; return true; + case gpu::HALF: view.edit(index) = glm::packHalf1x16(value); return true; + case gpu::NUINT8: view.edit(index) = glm::packUnorm1x8(value); return true; + default: break; + } error("GpuScalarToGlm::set", view, index, hint); return false; } }; -template struct GpuVec2ToGlm : GpuToGlmAdapter { 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("GpuVec2ToGlm", view, index, hint)); }}; +template struct GpuVec2ToGlm : GpuToGlmAdapter { static T get(const gpu::BufferView& view, glm::uint32 index, const char *hint) { +#ifdef DEBUG_BUFFERVIEW_HELPERS + if(!boundsCheck(view, index))return T(error("GpuVec2ToGlm::get::out of bounds", view, index, hint)); +#endif + 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: CHECK_SIZE(glm::uint32); return glm::unpackHalf2x16(view.get(index)); + case gpu::NUINT16: CHECK_SIZE(glm::uint32); return glm::unpackUnorm2x16(view.get(index)); + case gpu::NUINT8: CHECK_SIZE(glm::uint16); return glm::unpackUnorm2x8(view.get(index)); + default: break; + } return T(error("GpuVec2ToGlm::get", view, index, hint)); } + static bool set(const gpu::BufferView& view, glm::uint32 index, const T& value, const char *hint) { +#ifdef DEBUG_BUFFERVIEW_HELPERS + if(!boundsCheck(view, index))return T(error("GpuVec2ToGlm::set::out of bounds", view, index, hint)); +#endif + switch(view._element.getType()) { + // TODO: flush out GpuVec2ToGlm::set(value) + case gpu::FLOAT: view.edit(index) = value; return true; + case gpu::HALF: view.edit(index) = glm::packHalf2x16(value); return true; + case gpu::NUINT16: view.edit(index) = glm::packUnorm2x16(value); return true; + case gpu::NUINT8: view.edit(index) = glm::packUnorm2x8(value); return true; + default: break; + } error("GpuVec2ToGlm::set", view, index, hint); return false; + } +}; -template struct GpuVec3ToGlm : GpuToGlmAdapter { 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 GpuVec4ToGlm::get(view, index, hint); - } - default: break; - } return T(error("GpuVec3ToGlm", view, index, hint)); }}; +template struct GpuVec4ToGlm; -template struct GpuVec4ToGlm : GpuToGlmAdapter { static T get(const gpu::BufferView& view, quint32 index, const char *hint) { - assert(view._element.getSize() == sizeof(glm::int32)); +template struct GpuVec3ToGlm : GpuToGlmAdapter { static T get(const gpu::BufferView& view, glm::uint32 index, const char *hint) { +#ifdef DEBUG_BUFFERVIEW_HELPERS + if(!boundsCheck(view, index))return T(error("GpuVec3ToGlm::get::out of bounds", view, index, hint)); +#endif + 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: CHECK_SIZE(glm::uint64); return T(glm::unpackHalf4x16(view.get(index))); + case gpu::NUINT8: CHECK_SIZE(glm::uint32); return T(glm::unpackUnorm4x8(view.get(index))); + case gpu::NINT2_10_10_10: return T(glm::unpackSnorm3x10_1x2(view.get(index))); + default: break; + } return T(error("GpuVec3ToGlm::get", view, index, hint)); } + static bool set(const gpu::BufferView& view, glm::uint32 index, const T& value, const char *hint) { +#ifdef DEBUG_BUFFERVIEW_HELPERS + if(!boundsCheck(view, index))return T(error("GpuVec3ToGlm::set::out of bounds", view, index, hint)); +#endif + switch(view._element.getType()) { + // TODO: flush out GpuVec3ToGlm::set(value) + case gpu::FLOAT: view.edit(index) = value; return true; + case gpu::NUINT8: CHECK_SIZE(glm::uint32); view.edit(index) = glm::packUnorm4x8(glm::fvec4(value,0.0f)); return true; + case gpu::UINT8: view.edit(index) = value; return true; + case gpu::NINT2_10_10_10: view.edit(index) = glm::packSnorm3x10_1x2(glm::fvec4(value,0.0f)); return true; + default: break; + } error("GpuVec3ToGlm::set", view, index, hint); return false; + } +}; + +template struct GpuVec4ToGlm : GpuToGlmAdapter { static T get(const gpu::BufferView& view, glm::uint32 index, const char *hint) { +#ifdef DEBUG_BUFFERVIEW_HELPERS + if(!boundsCheck(view, index))return T(error("GpuVec4ToGlm::get::out of bounds", view, index, hint)); +#endif switch(view._element.getType()) { case gpu::UINT32: return view.get(index); case gpu::UINT16: return view.get(index); @@ -362,8 +310,8 @@ template struct GpuVec4ToGlm : GpuToGlmAdapter { static T get(const 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::NUINT16: CHECK_SIZE(glm::uint64); return glm::unpackUnorm4x16(view.get(index)); + case gpu::NUINT8: CHECK_SIZE(glm::uint32); return glm::unpackUnorm4x8(view.get(index)); case gpu::NUINT2: break; case gpu::NINT32: break; case gpu::NINT16: break; @@ -371,56 +319,221 @@ template struct GpuVec4ToGlm : GpuToGlmAdapter { static T get(const 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::HALF: CHECK_SIZE(glm::uint64); return glm::unpackHalf4x16(view.get(index)); case gpu::NINT2_10_10_10: return glm::unpackSnorm3x10_1x2(view.get(index)); - } return T(error("GpuVec4ToGlm", view, index, hint)); }}; - + } return T(error("GpuVec4ToGlm::get", view, index, hint)); } + static bool set(const gpu::BufferView& view, glm::uint32 index, const T& value, const char *hint) { +#ifdef DEBUG_BUFFERVIEW_HELPERS + if(!boundsCheck(view, index))return T(error("GpuVec4ToGlm::set::out of bounds", view, index, hint)); +#endif + switch(view._element.getType()) { + case gpu::FLOAT: view.edit(index) = value; return true; + case gpu::HALF: CHECK_SIZE(glm::uint64); view.edit(index) = glm::packHalf4x16(value); return true; + case gpu::UINT8: view.edit(index) = value; return true; + case gpu::NINT2_10_10_10: view.edit(index) = glm::packSnorm3x10_1x2(value); return true; + case gpu::NUINT16: CHECK_SIZE(glm::uint64); view.edit(index) = glm::packUnorm4x16(value); return true; + case gpu::NUINT8: CHECK_SIZE(glm::uint32); view.edit(index) = glm::packUnorm4x8(value); return true; + default: break; + } error("GpuVec4ToGlm::set", view, index, hint); return false; + } +}; +#undef CHECK_SIZE template -struct getVec { - static QVector __to_vector__(const gpu::BufferView& view, const char *hint) { +struct GpuValueResolver { + static QVector toVector(const gpu::BufferView& view, const char *hint) { QVector result; - const quint32 count = (quint32)view.getNumElements(); + const glm::uint32 count = (glm::uint32)view.getNumElements(); result.resize(count); - for (quint32 i = 0; i < count; i++) { + for (glm::uint32 i = 0; i < count; i++) { result[i] = FUNC::get(view, i, hint); } return result; } - static T __to_value__(const gpu::BufferView& view, quint32 index, const char *hint) { - assert(boundsCheck(view, index)); + static T toValue(const gpu::BufferView& view, glm::uint32 index, const char *hint) { return FUNC::get(view, index, hint); } }; // BufferView => QVector -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 QVector buffer_helpers::bufferToVector(const gpu::BufferView& view, const char *hint) { return GpuValueResolver,U>::toVector(view, hint); } + +template<> QVector buffer_helpers::bufferToVector(const gpu::BufferView& view, const char *hint) { return GpuValueResolver,int>::toVector(view, hint); } +template<> QVector buffer_helpers::bufferToVector(const gpu::BufferView& view, const char *hint) { return GpuValueResolver,glm::uint16>::toVector(view, hint); } +template<> QVector buffer_helpers::bufferToVector(const gpu::BufferView& view, const char *hint) { return GpuValueResolver,glm::uint32>::toVector(view, hint); } +template<> QVector buffer_helpers::bufferToVector(const gpu::BufferView& view, const char *hint) { return GpuValueResolver,glm::vec2>::toVector(view, hint); } +template<> QVector buffer_helpers::bufferToVector(const gpu::BufferView& view, const char *hint) { return GpuValueResolver,glm::vec3>::toVector(view, hint); } +template<> QVector buffer_helpers::bufferToVector(const gpu::BufferView& view, const char *hint) { return GpuValueResolver,glm::vec4>::toVector(view, hint); } + +// view.get with conversion between types +template<> int buffer_helpers::getValue(const gpu::BufferView& view, glm::uint32 index, const char *hint) { return GpuScalarToGlm::get(view, index, hint); } +template<> glm::uint32 buffer_helpers::getValue(const gpu::BufferView& view, glm::uint32 index, const char *hint) { return GpuScalarToGlm::get(view, index, hint); } +template<> glm::vec2 buffer_helpers::getValue(const gpu::BufferView& view, glm::uint32 index, const char *hint) { return GpuVec2ToGlm::get(view, index, hint); } +template<> glm::vec3 buffer_helpers::getValue(const gpu::BufferView& view, glm::uint32 index, const char *hint) { return GpuVec3ToGlm::get(view, index, hint); } +template<> glm::vec4 buffer_helpers::getValue(const gpu::BufferView& view, glm::uint32 index, const char *hint) { return GpuVec4ToGlm::get(view, index, hint); } + +// bufferView => QVariant +template<> QVariant buffer_helpers::getValue(const gpu::BufferView& view, glm::uint32 index, const char* hint) { + if (!boundsCheck(view, index)) { + qDebug() << "getValue -- out of bounds" << index << hint; + return false; + } + const auto dataType = view._element.getType(); + switch(view._element.getScalarCount()) { + case 1: + if (dataType == gpu::Type::FLOAT) { + return GpuScalarToGlm::get(view, index, hint); + } else { + switch(dataType) { + case gpu::INT8: case gpu::INT16: case gpu::INT32: + case gpu::NINT8: case gpu::NINT16: case gpu::NINT32: + case gpu::NINT2_10_10_10: + // signed + return GpuScalarToGlm::get(view, index, hint); + default: + // unsigned + return GpuScalarToGlm::get(view, index, hint); + } + } + case 2: return glmVecToVariant(GpuVec2ToGlm::get(view, index, hint)); + case 3: return glmVecToVariant(GpuVec3ToGlm::get(view, index, hint)); + case 4: return glmVecToVariant(GpuVec4ToGlm::get(view, index, hint)); + } + return QVariant(); } +glm::uint32 buffer_helpers::mesh::forEachVertex(const graphics::MeshPointer& mesh, std::function func) { + glm::uint32 i = 0; + auto attributeViews = getAllBufferViews(mesh); + auto nPositions = mesh->getNumVertices(); + for (; i < nPositions; i++) { + QVariantMap values; + for (const auto& a : attributeViews) { + values[a.first] = buffer_helpers::getValue(a.second, i, qUtf8Printable(a.first)); + } + if (!func(i, values)) { + break; + } + } + return i; +} -// indexed conversion accessors (like the hypothetical "view.convert(i)") -template <> int buffer_helpers::convert(const gpu::BufferView& view, quint32 index, const char *hint) { - return getVec,int>::__to_value__(view, index, hint); +// view.edit with conversion between types +template<> bool buffer_helpers::setValue(const gpu::BufferView& view, glm::uint32 index, const QVariant& v, const char* hint) { + if (!boundsCheck(view, index)) { + qDebug() << "setValue -- out of bounds" << index << hint; + return false; + } + const auto dataType = view._element.getType(); + + switch(view._element.getScalarCount()) { + case 1: + if (dataType == gpu::Type::FLOAT) { + return GpuScalarToGlm::set(view, index, v.toFloat(), hint); + } else { + switch(dataType) { + case gpu::INT8: case gpu::INT16: case gpu::INT32: + case gpu::NINT8: case gpu::NINT16: case gpu::NINT32: + case gpu::NINT2_10_10_10: + // signed + return GpuScalarToGlm::set(view, index, v.toInt(), hint); + default: + // unsigned + return GpuScalarToGlm::set(view, index, v.toUInt(), hint); + } + } + return false; + case 2: return GpuVec2ToGlm::set(view, index, glmVecFromVariant(v), hint); + case 3: return GpuVec3ToGlm::set(view, index, glmVecFromVariant(v), hint); + case 4: return GpuVec4ToGlm::set(view, index, glmVecFromVariant(v), hint); + } + return false; } -template <> glm::vec2 buffer_helpers::convert(const gpu::BufferView& view, quint32 index, const char *hint) { - return getVec,glm::vec2>::__to_value__(view, index, hint); + +template<> bool buffer_helpers::setValue(const gpu::BufferView& view, glm::uint32 index, const glm::uint32& value, const char* hint) { + return GpuScalarToGlm::set(view, index, value, hint); } -template <> glm::vec3 buffer_helpers::convert(const gpu::BufferView& view, quint32 index, const char *hint) { - return getVec,glm::vec3>::__to_value__(view, index, hint); +template<> bool buffer_helpers::setValue(const gpu::BufferView& view, glm::uint32 index, const glm::uint16& value, const char* hint) { + return GpuScalarToGlm::set(view, index, value, hint); } -template <> glm::vec4 buffer_helpers::convert(const gpu::BufferView& view, quint32 index, const char *hint) { - return getVec,glm::vec4>::__to_value__(view, index, hint); +template<> bool buffer_helpers::setValue(const gpu::BufferView& view, glm::uint32 index, const glm::vec2& value, const char* hint) { + return GpuVec2ToGlm::set(view, index, value, hint); } +template<> bool buffer_helpers::setValue(const gpu::BufferView& view, glm::uint32 index, const glm::vec3& value, const char* hint) { + return GpuVec3ToGlm::set(view, index, value, hint); +} +template<> bool buffer_helpers::setValue(const gpu::BufferView& view, glm::uint32 index, const glm::vec4& value, const char* hint) { + return GpuVec4ToGlm::set(view, index, value, hint); +} + +bool buffer_helpers::mesh::setVertexAttributes(const graphics::MeshPointer& mesh, glm::uint32 index, const QVariantMap& attributes) { + bool ok = true; + for (auto& a : getAllBufferViews(mesh)) { + const auto& name = a.first; + if (attributes.contains(name)) { + const auto& value = attributes.value(name); + if (value.isValid()) { + auto& view = a.second; + buffer_helpers::setValue(view, index, value); + } else { + ok = false; + //qCDebug(graphics_scripting) << "(skipping) setVertexAttributes" << vertexIndex << name; + } + } + } + return ok; +} + +QVariant buffer_helpers::mesh::getVertexAttributes(const graphics::MeshPointer& mesh, glm::uint32 vertexIndex) { + auto attributeViews = getAllBufferViews(mesh); + QVariantMap values; + for (const auto& a : attributeViews) { + values[a.first] = buffer_helpers::getValue(a.second, vertexIndex, qUtf8Printable(a.first)); + } + return values; +} + +// QVariantList => QVector +namespace { + template QVector qVariantListToGlmVector(const QVariantList& list) { + QVector output; + output.resize(list.size()); + int i = 0; + for (const auto& v : list) { + output[i++] = buffer_helpers::glmVecFromVariant(v); + } + return output; + } + template QVector qVariantListToScalarVector(const QVariantList& list) { + QVector output; + output.resize(list.size()); + int i = 0; + for (const auto& v : list) { + output[i++] = v.value(); + } + return output; + } +} + +template QVector buffer_helpers::variantToVector(const QVariant& value) { qDebug() << "variantToVector[class]"; return qVariantListToGlmVector(value.toList()); } +template<> QVector buffer_helpers::variantToVector(const QVariant& value) { return qVariantListToScalarVector(value.toList()); } +template<> QVector buffer_helpers::variantToVector(const QVariant& value) { return qVariantListToScalarVector(value.toList()); } +template<> QVector buffer_helpers::variantToVector(const QVariant& value) { return qVariantListToScalarVector(value.toList()); } +template<> QVector buffer_helpers::variantToVector(const QVariant& value) { return qVariantListToGlmVector(value.toList()); } +template<> QVector buffer_helpers::variantToVector(const QVariant& value) { return qVariantListToGlmVector(value.toList()); } +template<> QVector buffer_helpers::variantToVector(const QVariant& value) { return qVariantListToGlmVector(value.toList()); } + +template<> gpu::BufferView buffer_helpers::newFromVector(const QVector& _elements, const gpu::Element& elementType) { + glm::uint32 numElements = _elements.size(); + auto buffer = new gpu::Buffer(); + buffer->resize(elementType.getSize() * numElements); + auto bufferView = gpu::BufferView(buffer, elementType); + for (glm::uint32 i = 0; i < numElements; i++) { + setValue(bufferView, i, _elements[i]); + } + return bufferView; +} + gpu::BufferView buffer_helpers::clone(const gpu::BufferView& input) { return gpu::BufferView( @@ -429,25 +542,29 @@ gpu::BufferView buffer_helpers::clone(const gpu::BufferView& input) { ); } -// TODO: preserve existing data -gpu::BufferView buffer_helpers::resize(const gpu::BufferView& input, quint32 numElements) { +gpu::BufferView buffer_helpers::resized(const gpu::BufferView& input, glm::uint32 numElements) { +#ifdef DEBUG_BUFFERVIEW_HELPERS auto effectiveSize = input._buffer->getSize() / input.getNumElements(); qCDebug(bufferhelper_logging) << "resize input" << input.getNumElements() << input._buffer->getSize() << "effectiveSize" << effectiveSize; +#endif auto vsize = input._element.getSize() * numElements; std::unique_ptr data{ new gpu::Byte[vsize] }; memset(data.get(), 0, vsize); auto buffer = new gpu::Buffer(vsize, data.get()); + memcpy(data.get(), input._buffer->getData(), std::min(vsize, (glm::uint32)input._buffer->getSize())); auto output = gpu::BufferView(buffer, input._element); +#ifdef DEBUG_BUFFERVIEW_HELPERS qCDebug(bufferhelper_logging) << "resized output" << output.getNumElements() << output._buffer->getSize(); +#endif return output; } -graphics::MeshPointer buffer_helpers::cloneMesh(graphics::MeshPointer mesh) { +graphics::MeshPointer buffer_helpers::mesh::clone(const graphics::MeshPointer& mesh) { auto clone = std::make_shared(); clone->displayName = (QString::fromStdString(mesh->displayName) + "-clone").toStdString(); clone->setIndexBuffer(buffer_helpers::clone(mesh->getIndexBuffer())); clone->setPartBuffer(buffer_helpers::clone(mesh->getPartBuffer())); - auto attributeViews = buffer_helpers::gatherBufferViews(mesh); + auto attributeViews = buffer_helpers::mesh::getAllBufferViews(mesh); for (const auto& a : attributeViews) { auto& view = a.second; auto slot = buffer_helpers::ATTRIBUTES[a.first]; @@ -461,195 +578,17 @@ graphics::MeshPointer buffer_helpers::cloneMesh(graphics::MeshPointer mesh) { return clone; } -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; - 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 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"; - std::unique_ptr data{ new gpu::Byte[vsize] }; - memset(data.get(), 0, vsize); - auto buffer = new gpu::Buffer(vsize, data.get()); - 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()); - 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 buffer_helpers::mesh::getAllBufferViews(const graphics::MeshPointer& mesh) { 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(); -#if DEV_BUILD - auto beforeTotal = view._size; -#endif - if (expandToMatchPositions.contains(name)) { - expandAttributeToMatchPositions(mesh, slot); - } - if (beforeCount > 0) { - auto element = view._element; - QString typeName = QString("%1").arg(element.getType()); - - attributeViews[name] = getBufferView(mesh, slot); - -#if DEV_BUILD - const auto vecN = element.getScalarCount(); - 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 + auto bufferView = getBufferView(mesh, a.second); + if (bufferView.getNumElements()) { + attributeViews[a.first] = bufferView; } } 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 faceNormals; - QMap> vertexToFaces; - 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)) { -#ifdef DEBUG_BUFFERVIEW_SCRIPTING - qCInfo(bufferhelper_logging) << i << i0 << i1 << i2 << glmVecToVariant(face.v0) << glmVecToVariant(face.v1) << glmVecToVariant(face.v2); -#endif - break; - } - vertexToFaces[glm::to_string(glm::dvec3(face.v0)).c_str()] << i; - vertexToFaces[glm::to_string(glm::dvec3(face.v1)).c_str()] << i; - vertexToFaces[glm::to_string(glm::dvec3(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(glm::dvec3(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)) { -#ifdef DEBUG_BUFFERVIEW_SCRIPTING - static int logged = 0; - if (logged++ < 10) { - qCInfo(bufferhelper_logging) << "isnan(normal.x)" << j << glmVecToVariant(normal); - } -#endif - break; - } - buffer_helpers::fromVariant(normals, j, glmVecToVariant(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/src/graphics/BufferViewHelpers.h b/libraries/graphics/src/graphics/BufferViewHelpers.h index 6d4908a2c7..f877341d50 100644 --- a/libraries/graphics/src/graphics/BufferViewHelpers.h +++ b/libraries/graphics/src/graphics/BufferViewHelpers.h @@ -10,10 +10,7 @@ #include #include -namespace gpu { - class BufferView; - class Element; -} +#include "GpuHelpers.h" namespace graphics { class Mesh; @@ -23,33 +20,41 @@ namespace graphics { class Extents; class AABox; -struct buffer_helpers { - template static QVariant glmVecToVariant(const T& v, bool asArray = false); - template static const T glmVecFromVariant(const QVariant& v); +namespace buffer_helpers { + extern QMap ATTRIBUTES; + extern const std::array XYZW; + extern const std::array ZERO123; - 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); + template QVariant glmVecToVariant(const T& v, bool asArray = false); + template const T glmVecFromVariant(const QVariant& v); - 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 = ""); + glm::uint32 forEachVariant(const gpu::BufferView& view, std::function func, const char* hint = ""); + template glm::uint32 forEach(const gpu::BufferView& view, std::function func); - static bool fromVariant(const gpu::BufferView& view, quint32 index, const QVariant& v); + template gpu::BufferView newFromVector(const QVector& elements, const gpu::Element& elementType); + template gpu::BufferView newFromVariantList(const QVariantList& list, const gpu::Element& elementType); - template static gpu::BufferView fromVector(const QVector& elements, const gpu::Element& elementType); + template QVector variantToVector(const QVariant& list); + template QVector bufferToVector(const gpu::BufferView& view, const char *hint = ""); - template static QVector toVector(const gpu::BufferView& view, const char *hint = ""); - template static T convert(const gpu::BufferView& view, quint32 index, const char* hint = ""); + // note: these do value conversions from the underlying buffer type into the template type + template T getValue(const gpu::BufferView& view, glm::uint32 index, const char* hint = ""); + template bool setValue(const gpu::BufferView& view, glm::uint32 index, const T& value, const char* hint = ""); - static gpu::BufferView clone(const gpu::BufferView& input); - static gpu::BufferView resize(const gpu::BufferView& input, quint32 numElements); + gpu::BufferView clone(const gpu::BufferView& input); + gpu::BufferView resized(const gpu::BufferView& input, glm::uint32 numElements); - static void packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, glm::uint32& packedNormal, glm::uint32& packedTangent); + void packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, glm::uint32& packedNormal, glm::uint32& packedTangent); - static const std::array XYZW; - static const std::array ZERO123; -}; + namespace mesh { + glm::uint32 forEachVertex(const graphics::MeshPointer& mesh, std::function func); + bool setVertexAttributes(const graphics::MeshPointer& mesh, glm::uint32 index, const QVariantMap& attributes); + QVariant getVertexAttributes(const graphics::MeshPointer& mesh, glm::uint32 index); + graphics::MeshPointer clone(const graphics::MeshPointer& mesh); + gpu::BufferView getBufferView(const graphics::MeshPointer& mesh, quint8 slot); + std::map getAllBufferViews(const graphics::MeshPointer& mesh); + template QVector attributeToVector(const graphics::MeshPointer& mesh, gpu::Stream::InputSlot slot) { + return bufferToVector(getBufferView(mesh, slot), qUtf8Printable(gpu::toString(slot))); + } + } +} diff --git a/libraries/graphics/src/graphics/GpuHelpers.cpp b/libraries/graphics/src/graphics/GpuHelpers.cpp new file mode 100644 index 0000000000..63393df5e1 --- /dev/null +++ b/libraries/graphics/src/graphics/GpuHelpers.cpp @@ -0,0 +1,122 @@ +// +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +// + +#include "GpuHelpers.h" + +namespace graphics { + DebugEnums TOPOLOGIES{ + { Mesh::Topology::POINTS, "points" }, + { Mesh::Topology::LINES, "lines" }, + { Mesh::Topology::LINE_STRIP, "line_strip" }, + { Mesh::Topology::TRIANGLES, "triangles" }, + { Mesh::Topology::TRIANGLE_STRIP, "triangle_strip" }, + { Mesh::Topology::QUADS, "quads" }, + { Mesh::Topology::QUAD_STRIP, "quad_strip" }, + { Mesh::Topology::NUM_TOPOLOGIES, "num_topologies" }, + }; +} +namespace gpu { + + DebugEnums TYPES{ + { Type::FLOAT, "float" }, + { Type::INT32, "int32" }, + { Type::UINT32, "uint32" }, + { Type::HALF, "half" }, + { Type::INT16, "int16" }, + { Type::UINT16, "uint16" }, + { Type::INT8, "int8" }, + { Type::UINT8, "uint8" }, + { Type::NINT32, "nint32" }, + { Type::NUINT32, "nuint32" }, + { Type::NINT16, "nint16" }, + { Type::NUINT16, "nuint16" }, + { Type::NINT8, "nint8" }, + { Type::NUINT8, "nuint8" }, + { Type::NUINT2, "nuint2" }, + { Type::NINT2_10_10_10, "nint2_10_10_10" }, + { Type::COMPRESSED, "compressed" }, + { Type::NUM_TYPES, "num_types" }, + }; + DebugEnums DIMENSIONS{ + { Dimension::SCALAR, "scalar" }, + { Dimension::VEC2, "vec2" }, + { Dimension::VEC3, "vec3" }, + { Dimension::VEC4, "vec4" }, + { Dimension::MAT2, "mat2" }, + { Dimension::MAT3, "mat3" }, + { Dimension::MAT4, "mat4" }, + { Dimension::TILE4x4, "tile4x4" }, + { Dimension::NUM_DIMENSIONS, "num_dimensions" }, + }; + DebugEnums SEMANTICS{ + { Semantic::RAW, "raw" }, + + { Semantic::RED, "red" }, + { Semantic::RGB, "rgb" }, + { Semantic::RGBA, "rgba" }, + { Semantic::BGRA, "bgra" }, + + { Semantic::XY, "xy" }, + { Semantic::XYZ, "xyz" }, + { Semantic::XYZW, "xyzw" }, + { Semantic::QUAT, "quat" }, + { Semantic::UV, "uv" }, + { Semantic::INDEX, "index" }, + { Semantic::PART, "part" }, + + { Semantic::DEPTH, "depth" }, + { Semantic::STENCIL, "stencil" }, + { Semantic::DEPTH_STENCIL, "depth_stencil" }, + + { Semantic::SRED, "sred" }, + { Semantic::SRGB, "srgb" }, + { Semantic::SRGBA, "srgba" }, + { Semantic::SBGRA, "sbgra" }, + + { Semantic::_FIRST_COMPRESSED, "_first_compressed" }, + + { Semantic::COMPRESSED_BC1_SRGB, "compressed_bc1_srgb" }, + { Semantic::COMPRESSED_BC1_SRGBA, "compressed_bc1_srgba" }, + { Semantic::COMPRESSED_BC3_SRGBA, "compressed_bc3_srgba" }, + { Semantic::COMPRESSED_BC4_RED, "compressed_bc4_red" }, + { Semantic::COMPRESSED_BC5_XY, "compressed_bc5_xy" }, + { Semantic::COMPRESSED_BC6_RGB, "compressed_bc6_rgb" }, + { Semantic::COMPRESSED_BC7_SRGBA, "compressed_bc7_srgba" }, + + { Semantic::_LAST_COMPRESSED, "_last_compressed" }, + + { Semantic::R11G11B10, "r11g11b10" }, + { Semantic::RGB9E5, "rgb9e5" }, + + { Semantic::UNIFORM, "uniform" }, + { Semantic::UNIFORM_BUFFER, "uniform_buffer" }, + { Semantic::RESOURCE_BUFFER, "resource_buffer" }, + { Semantic::SAMPLER, "sampler" }, + { Semantic::SAMPLER_MULTISAMPLE, "sampler_multisample" }, + { Semantic::SAMPLER_SHADOW, "sampler_shadow" }, + + + { Semantic::NUM_SEMANTICS, "num_semantics" }, + }; + DebugEnums SLOTS{ + { Stream::InputSlot::POSITION, "position" }, + { Stream::InputSlot::NORMAL, "normal" }, + { Stream::InputSlot::COLOR, "color" }, + { Stream::InputSlot::TEXCOORD0, "texcoord0" }, + { Stream::InputSlot::TEXCOORD, "texcoord" }, + { Stream::InputSlot::TANGENT, "tangent" }, + { Stream::InputSlot::SKIN_CLUSTER_INDEX, "skin_cluster_index" }, + { Stream::InputSlot::SKIN_CLUSTER_WEIGHT, "skin_cluster_weight" }, + { Stream::InputSlot::TEXCOORD1, "texcoord1" }, + { Stream::InputSlot::TEXCOORD2, "texcoord2" }, + { Stream::InputSlot::TEXCOORD3, "texcoord3" }, + { Stream::InputSlot::TEXCOORD4, "texcoord4" }, + { Stream::InputSlot::NUM_INPUT_SLOTS, "num_input_slots" }, + { Stream::InputSlot::DRAW_CALL_INFO, "draw_call_info" }, + }; +} diff --git a/libraries/graphics/src/graphics/GpuHelpers.h b/libraries/graphics/src/graphics/GpuHelpers.h new file mode 100644 index 0000000000..ceae823f83 --- /dev/null +++ b/libraries/graphics/src/graphics/GpuHelpers.h @@ -0,0 +1,47 @@ +// +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#include +#include +#include +#include "Geometry.h" + +template +using DebugEnums = QMap; + +namespace graphics { + extern DebugEnums TOPOLOGIES; + inline QDebug operator<<(QDebug dbg, Mesh::Topology type) { return dbg << TOPOLOGIES.value(type);} + inline const QString toString(Mesh::Topology v) { return TOPOLOGIES.value(v); } +} + +namespace gpu { + extern DebugEnums TYPES; + extern DebugEnums DIMENSIONS; + extern DebugEnums SEMANTICS; + extern DebugEnums SLOTS; + inline QDebug operator<<(QDebug dbg, gpu::Type type) { return dbg << TYPES.value(type); } + inline QDebug operator<<(QDebug dbg, gpu::Dimension type) { return dbg << DIMENSIONS.value(type); } + inline QDebug operator<<(QDebug dbg, gpu::Semantic type) { return dbg << SEMANTICS.value(type); } + inline QDebug operator<<(QDebug dbg, gpu::Stream::InputSlot type) { return dbg << SLOTS.value(type); } + inline const QString toString(gpu::Type v) { return TYPES.value(v); } + inline const QString toString(gpu::Dimension v) { return DIMENSIONS.value(v); } + inline const QString toString(gpu::Semantic v) { return SEMANTICS.value(v); } + inline const QString toString(gpu::Stream::InputSlot v) { return SLOTS.value(v); } + inline const QString toString(gpu::Element v) { + return QString("[Element semantic=%1 type=%1 dimension=%2]") + .arg(toString(v.getSemantic())) + .arg(toString(v.getType())) + .arg(toString(v.getDimension())); + } +} + +Q_DECLARE_METATYPE(gpu::Type) +Q_DECLARE_METATYPE(gpu::Dimension) +Q_DECLARE_METATYPE(gpu::Semantic) +Q_DECLARE_METATYPE(graphics::Mesh::Topology) diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 6d735497c0..0abeb70254 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -440,7 +440,7 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g const FBXGeometry& geometry = getFBXGeometry(); if (!_triangleSetsValid) { - calculateTriangleSets(); + calculateTriangleSets(geometry); } glm::mat4 meshToModelMatrix = glm::scale(_scale) * glm::translate(_offset); @@ -525,7 +525,7 @@ bool Model::convexHullContains(glm::vec3 point) { QMutexLocker locker(&_mutex); if (!_triangleSetsValid) { - calculateTriangleSets(); + calculateTriangleSets(getFBXGeometry()); } // If we are inside the models box, then consider the submeshes... @@ -587,9 +587,6 @@ MeshProxyList Model::getMeshes() const { return result; } -// FIXME: temporary workaround that updates the whole FBXGeometry (to keep findRayIntersection in sync) -#include "Model_temporary_hack.cpp.h" - bool Model::replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer newModel, int meshIndex, int partIndex) { QMutexLocker lock(&_mutex); @@ -603,18 +600,60 @@ bool Model::replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointe return false; } - auto resource = new MyGeometryResource(_url, _renderGeometry, newModel); - _needsReload = false; - _needsUpdateTextures = false; - _visualGeometryRequestFailed = false; - _needsFixupInScene = true; + const auto& meshes = newModel->meshes; + render::Transaction transaction; + const render::ScenePointer& scene = AbstractViewStateInterface::instance()->getMain3DScene(); - invalidCalculatedMeshBoxes(); - deleteGeometry(); - _renderGeometry.reset(resource); - updateGeometry(); - calculateTriangleSets(); - setRenderItemsNeedUpdate(); + meshIndex = meshIndex >= 0 ? meshIndex : 0; + partIndex = partIndex >= 0 ? partIndex : 0; + + if (meshIndex >= meshes.size()) { + qDebug() << meshIndex << "meshIndex >= newModel.meshes.size()" << meshes.size(); + return false; + } + + auto mesh = meshes[meshIndex].getMeshPointer(); + { + // update visual geometry + render::Transaction transaction; + for (int i = 0; i < (int) _modelMeshRenderItemIDs.size(); i++) { + auto itemID = _modelMeshRenderItemIDs[i]; + auto shape = _modelMeshRenderItemShapes[i]; + // TODO: check to see if .partIndex matches too + if (shape.meshIndex == meshIndex) { + transaction.updateItem(itemID, [=](ModelMeshPartPayload& data) { + data.updateMeshPart(mesh, partIndex); + }); + } + } + scene->enqueueTransaction(transaction); + } + // update triangles for ray picking + { + FBXGeometry geometry; + for (const auto& newMesh : meshes) { + FBXMesh mesh; + mesh._mesh = newMesh.getMeshPointer(); + mesh.vertices = buffer_helpers::mesh::attributeToVector(mesh._mesh, gpu::Stream::POSITION); + int numParts = newMesh.getMeshPointer()->getNumParts(); + for (int partID = 0; partID < numParts; partID++) { + FBXMeshPart part; + part.triangleIndices = buffer_helpers::bufferToVector(mesh._mesh->getIndexBuffer(), "part.triangleIndices"); + mesh.parts << part; + } + { + foreach (const glm::vec3& vertex, mesh.vertices) { + glm::vec3 transformedVertex = glm::vec3(mesh.modelTransform * glm::vec4(vertex, 1.0f)); + geometry.meshExtents.minimum = glm::min(geometry.meshExtents.minimum, transformedVertex); + geometry.meshExtents.maximum = glm::max(geometry.meshExtents.maximum, transformedVertex); + mesh.meshExtents.minimum = glm::min(mesh.meshExtents.minimum, transformedVertex); + mesh.meshExtents.maximum = glm::max(mesh.meshExtents.maximum, transformedVertex); + } + } + geometry.meshes << mesh; + } + calculateTriangleSets(geometry); + } return true; } @@ -638,10 +677,9 @@ scriptable::ScriptableModelBase Model::getScriptableModel() { return result; } -void Model::calculateTriangleSets() { +void Model::calculateTriangleSets(const FBXGeometry& geometry) { PROFILE_RANGE(render, __FUNCTION__); - const FBXGeometry& geometry = getFBXGeometry(); int numberOfMeshes = geometry.meshes.size(); _triangleSetsValid = true; @@ -664,7 +702,7 @@ void Model::calculateTriangleSets() { int totalTriangles = (numberOfQuads * TRIANGLES_PER_QUAD) + numberOfTris; _modelSpaceMeshTriangleSets[i].reserve(totalTriangles); - auto meshTransform = getFBXGeometry().offset * mesh.modelTransform; + auto meshTransform = geometry.offset * mesh.modelTransform; if (part.quadIndices.size() > 0) { int vIndex = 0; diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index bbb323d1e7..f6a9a64e83 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -419,7 +419,7 @@ protected: bool _overrideModelTransform { false }; bool _triangleSetsValid { false }; - void calculateTriangleSets(); + void calculateTriangleSets(const FBXGeometry& geometry); QVector _modelSpaceMeshTriangleSets; // model space triangles for all sub meshes diff --git a/libraries/render-utils/src/Model_temporary_hack.cpp.h b/libraries/render-utils/src/Model_temporary_hack.cpp.h deleted file mode 100644 index cfa6945571..0000000000 --- a/libraries/render-utils/src/Model_temporary_hack.cpp.h +++ /dev/null @@ -1,84 +0,0 @@ -// FIXME: temporary workaround for duplicating the FBXModel when dynamically replacing an underlying mesh part -#include -#include -class MyGeometryResource : public GeometryResource { -public: - shared_ptr fbxGeometry; - MyGeometryResource(const QUrl& url, Geometry::Pointer originalGeometry, scriptable::ScriptableModelBasePointer newModel) : GeometryResource(url) { - fbxGeometry = std::make_shared(); - FBXGeometry& geometry = *fbxGeometry.get(); - const FBXGeometry* original; - shared_ptr tmpGeometry; - if (originalGeometry) { - original = &originalGeometry->getFBXGeometry(); - } else { - tmpGeometry = std::make_shared(); - original = tmpGeometry.get(); - } - geometry.originalURL = original->originalURL; - geometry.bindExtents = original->bindExtents; - - for (const auto &j : original->joints) { - geometry.joints << j; - } - for (const FBXMaterial& material : original->materials) { - _materials.push_back(std::make_shared(material, _textureBaseUrl)); - } - std::shared_ptr meshes = std::make_shared(); - std::shared_ptr parts = std::make_shared(); - int meshID = 0; - if (newModel) { - geometry.meshExtents.reset(); - for (const auto& newMesh : newModel->meshes) { - // qDebug() << "newMesh #" << meshID; - FBXMesh mesh; - if (meshID < original->meshes.size()) { - mesh = original->meshes.at(meshID); // copy - } - mesh._mesh = newMesh.getMeshPointer(); - // duplicate the buffers - mesh.vertices = buffer_helpers::toVector(mesh._mesh->getVertexBuffer(), "mesh.vertices"); - mesh.normals = buffer_helpers::toVector(buffer_helpers::getBufferView(mesh._mesh, gpu::Stream::NORMAL), "mesh.normals"); - mesh.colors = buffer_helpers::toVector(buffer_helpers::getBufferView(mesh._mesh, gpu::Stream::COLOR), "mesh.colors"); - mesh.texCoords = buffer_helpers::toVector(buffer_helpers::getBufferView(mesh._mesh, gpu::Stream::TEXCOORD0), "mesh.texCoords"); - mesh.texCoords1 = buffer_helpers::toVector(buffer_helpers::getBufferView(mesh._mesh, gpu::Stream::TEXCOORD1), "mesh.texCoords1"); - mesh.createMeshTangents(true); - mesh.createBlendShapeTangents(false); - geometry.meshes << mesh; - // Copy mesh pointers - meshes->emplace_back(newMesh.getMeshPointer()); - int partID = 0; - const auto oldParts = mesh.parts; - mesh.parts.clear(); - for (const FBXMeshPart& fbxPart : oldParts) { - FBXMeshPart part; // new copy - part.materialID = fbxPart.materialID; - // Construct local parts - part.triangleIndices = buffer_helpers::toVector(mesh._mesh->getIndexBuffer(), "part.triangleIndices"); - mesh.parts << part; - auto p = std::make_shared(meshID, partID, 0); - parts->push_back(p); - partID++; - } - { - // accumulate local transforms - // compute the mesh extents from the transformed vertices - foreach (const glm::vec3& vertex, mesh.vertices) { - glm::vec3 transformedVertex = glm::vec3(mesh.modelTransform * glm::vec4(vertex, 1.0f)); - geometry.meshExtents.minimum = glm::min(geometry.meshExtents.minimum, transformedVertex); - geometry.meshExtents.maximum = glm::max(geometry.meshExtents.maximum, transformedVertex); - - mesh.meshExtents.minimum = glm::min(mesh.meshExtents.minimum, transformedVertex); - mesh.meshExtents.maximum = glm::max(mesh.meshExtents.maximum, transformedVertex); - } - } - meshID++; - } - } - _meshes = meshes; - _meshParts = parts; - _loaded = true; - _fbxGeometry = fbxGeometry; - }; -}; -