From 06afaa7470f90a6b152c474958315d4c7e80ddd5 Mon Sep 17 00:00:00 2001 From: humbletim Date: Thu, 21 Dec 2017 15:13:57 -0500 Subject: [PATCH 01/49] BufferView <-> QVariant/QScriptValue conversion update MeshProxy/SimpleMeshProxy and ScriptableModel ModelScriptingInterface / scriptable::ModelProvider integration update to RC-63 initial graphics-scripting refactoring graphics-scripting baseline commit wip commit Geometry -> MeshPart remove SimpleMeshProxy collapse graphics-utils -> graphics-scripting scriptable::Model => scriptable::ScriptableModel --- interface/CMakeLists.txt | 2 +- interface/src/Application.cpp | 68 +- interface/src/Application.h | 1 - interface/src/ui/overlays/Base3DOverlay.h | 6 +- interface/src/ui/overlays/ModelOverlay.cpp | 7 + interface/src/ui/overlays/ModelOverlay.h | 1 + interface/src/ui/overlays/Shape3DOverlay.cpp | 15 + interface/src/ui/overlays/Shape3DOverlay.h | 1 + libraries/avatars-renderer/CMakeLists.txt | 1 + .../src/avatars-renderer/Avatar.cpp | 28 + .../src/avatars-renderer/Avatar.h | 5 +- libraries/entities-renderer/CMakeLists.txt | 1 + .../src/RenderableEntityItem.h | 6 +- .../src/RenderableModelEntityItem.cpp | 15 +- .../src/RenderableModelEntityItem.h | 2 +- .../src/RenderablePolyLineEntityItem.cpp | 6 +- .../src/RenderablePolyLineEntityItem.h | 1 + .../src/RenderablePolyVoxEntityItem.cpp | 54 +- .../src/RenderablePolyVoxEntityItem.h | 7 +- .../src/RenderableShapeEntityItem.cpp | 21 + .../src/RenderableShapeEntityItem.h | 2 + libraries/entities/src/EntityItem.h | 4 - .../entities/src/EntityScriptingInterface.cpp | 24 - .../entities/src/EntityScriptingInterface.h | 4 - libraries/fbx/src/FBXReader.cpp | 16 +- libraries/fbx/src/OBJWriter.cpp | 78 +- libraries/graphics-scripting/CMakeLists.txt | 5 + .../graphics-scripting/BufferViewHelpers.cpp | 195 ++++ .../graphics-scripting/BufferViewHelpers.h | 18 + .../BufferViewScripting.cpp | 83 ++ .../graphics-scripting/BufferViewScripting.h | 11 + .../src/graphics-scripting/DebugNames.h | 72 ++ .../ModelScriptingInterface.cpp | 836 ++++++++++++++++++ .../ModelScriptingInterface.h | 68 ++ .../src/graphics-scripting/ScriptableMesh.cpp | 359 ++++++++ .../src/graphics-scripting/ScriptableMesh.h | 127 +++ .../src/graphics-scripting/ScriptableModel.h | 80 ++ libraries/graphics/src/graphics/Geometry.cpp | 6 + libraries/graphics/src/graphics/Geometry.h | 1 + .../src/model-networking/SimpleMeshProxy.cpp | 27 - .../src/model-networking/SimpleMeshProxy.h | 36 - libraries/render-utils/CMakeLists.txt | 1 + libraries/render-utils/src/GeometryCache.cpp | 45 + libraries/render-utils/src/GeometryCache.h | 1 + libraries/render-utils/src/Model.cpp | 87 +- libraries/render-utils/src/Model.h | 5 +- .../src/ModelScriptingInterface.cpp | 251 ------ .../src/ModelScriptingInterface.h | 39 - libraries/script-engine/src/ScriptEngine.cpp | 6 - libraries/shared/src/RegisteredMetaTypes.cpp | 65 -- libraries/shared/src/RegisteredMetaTypes.h | 42 - 51 files changed, 2242 insertions(+), 600 deletions(-) create mode 100644 libraries/graphics-scripting/CMakeLists.txt create mode 100644 libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.cpp create mode 100644 libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h create mode 100644 libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp create mode 100644 libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.h create mode 100644 libraries/graphics-scripting/src/graphics-scripting/DebugNames.h create mode 100644 libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp create mode 100644 libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h create mode 100644 libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp create mode 100644 libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h create mode 100644 libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h delete mode 100644 libraries/model-networking/src/model-networking/SimpleMeshProxy.cpp delete mode 100644 libraries/model-networking/src/model-networking/SimpleMeshProxy.h delete mode 100644 libraries/script-engine/src/ModelScriptingInterface.cpp delete mode 100644 libraries/script-engine/src/ModelScriptingInterface.h diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index ee2997e216..081eeae02e 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -191,7 +191,7 @@ endif() # link required hifi libraries link_hifi_libraries( - shared octree ktx gpu gl procedural graphics render + shared octree ktx gpu gl procedural graphics graphics-scripting render pointers recording fbx networking model-networking entities avatars trackers audio audio-client animation script-engine physics diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 99bd4d5758..f24969ce60 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -166,6 +166,7 @@ #include "scripting/AccountServicesScriptingInterface.h" #include "scripting/HMDScriptingInterface.h" #include "scripting/MenuScriptingInterface.h" +#include "graphics-scripting/ModelScriptingInterface.h" #include "scripting/SettingsScriptingInterface.h" #include "scripting/WindowScriptingInterface.h" #include "scripting/ControllerScriptingInterface.h" @@ -198,7 +199,6 @@ #include #include #include -#include #include #include @@ -593,6 +593,66 @@ void messageHandler(QtMsgType type, const QMessageLogContext& context, const QSt } } + +class ApplicationMeshProvider : public scriptable::ModelProviderFactory { +public: + virtual scriptable::ModelProviderPointer lookupModelProvider(QUuid uuid) { + QString error; + + scriptable::ModelProviderPointer provider; + 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; + } + + return provider; + } + + scriptable::ModelProviderPointer getEntityModelProvider(EntityItemID entityID) { + scriptable::ModelProviderPointer provider; + auto entityTreeRenderer = qApp->getEntities(); + auto entityTree = entityTreeRenderer->getTree(); + if (auto entity = entityTree->findEntityByID(entityID)) { + if (auto renderer = entityTreeRenderer->renderableForEntityId(entityID)) { + provider = std::dynamic_pointer_cast(renderer); + provider->metadata["providerType"] = "entity"; + } else { + qCWarning(interfaceapp) << "no renderer for entity ID" << entityID.toString(); + } + } + return provider; + } + + scriptable::ModelProviderPointer getOverlayModelProvider(OverlayID overlayID) { + scriptable::ModelProviderPointer provider; + auto &overlays = qApp->getOverlays(); + if (auto overlay = overlays.getOverlay(overlayID)) { + if (auto base3d = std::dynamic_pointer_cast(overlay)) { + provider = std::dynamic_pointer_cast(base3d); + provider->metadata["providerType"] = "overlay"; + } else { + qCWarning(interfaceapp) << "no renderer for overlay ID" << overlayID.toString(); + } + } + return provider; + } + + scriptable::ModelProviderPointer getAvatarModelProvider(QUuid sessionUUID) { + scriptable::ModelProviderPointer provider; + auto avatarManager = DependencyManager::get(); + if (auto avatar = avatarManager->getAvatarBySessionID(sessionUUID)) { + if (avatar->getSessionUUID() == sessionUUID) { + provider = std::dynamic_pointer_cast(avatar); + provider->metadata["providerType"] = "avatar"; + } + } + return provider; + } +}; + static const QString STATE_IN_HMD = "InHMD"; static const QString STATE_CAMERA_FULL_SCREEN_MIRROR = "CameraFSM"; static const QString STATE_CAMERA_FIRST_PERSON = "CameraFirstPerson"; @@ -737,6 +797,9 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(true); + DependencyManager::set(); + DependencyManager::registerInheritance(); + DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -5915,6 +5978,9 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe scriptEngine->registerGlobalObject("Scene", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Render", _renderEngine->getConfiguration().get()); + ModelScriptingInterface::registerMetaTypes(scriptEngine.data()); + scriptEngine->registerGlobalObject("Model", DependencyManager::get().data()); + scriptEngine->registerGlobalObject("ScriptDiscoveryService", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Reticle", getApplicationCompositor().getReticleInterface()); diff --git a/interface/src/Application.h b/interface/src/Application.h index ddb8ce11e5..ae07ebd9dd 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -72,7 +72,6 @@ #include #include -#include #include "FrameTimingsScriptingInterface.h" #include "Sound.h" diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index df0f3c4728..0c8bc5aacb 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -13,10 +13,11 @@ #include #include - +#include #include "Overlay.h" -class Base3DOverlay : public Overlay, public SpatiallyNestable { +namespace model { class Mesh; } +class Base3DOverlay : public Overlay, public SpatiallyNestable, public scriptable::ModelProvider { Q_OBJECT using Parent = Overlay; @@ -36,6 +37,7 @@ public: virtual bool is3D() const override { return true; } virtual uint32_t fetchMetaSubItems(render::ItemIDs& subItems) const override { subItems.push_back(getRenderItemID()); return (uint32_t) subItems.size(); } + virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override { return scriptable::ModelProvider::modelUnavailableError(ok); } // TODO: consider implementing registration points in this class glm::vec3 getCenter() const { return getWorldPosition(); } diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index 310dbf78d8..5a80ca1abf 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -629,3 +629,10 @@ uint32_t ModelOverlay::fetchMetaSubItems(render::ItemIDs& subItems) const { } return 0; } + +scriptable::ScriptableModel ModelOverlay::getScriptableModel(bool* ok) { + if (!_model || !_model->isLoaded()) { + return Base3DOverlay::getScriptableModel(ok); + } + return _model->getScriptableModel(ok); +} diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index 60ba90e568..32d9a08c70 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -59,6 +59,7 @@ public: void setDrawInFront(bool drawInFront) override; void setDrawHUDLayer(bool drawHUDLayer) override; + virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override; protected: Transform evalRenderTransform() override; diff --git a/interface/src/ui/overlays/Shape3DOverlay.cpp b/interface/src/ui/overlays/Shape3DOverlay.cpp index 97342a80ab..8bb3d16888 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.cpp +++ b/interface/src/ui/overlays/Shape3DOverlay.cpp @@ -179,3 +179,18 @@ Transform Shape3DOverlay::evalRenderTransform() { transform.setRotation(rotation); return transform; } + +scriptable::ScriptableModel Shape3DOverlay::getScriptableModel(bool* ok) { + auto geometryCache = DependencyManager::get(); + auto vertexColor = ColorUtils::toVec3(_color); + scriptable::ScriptableModel result; + result.metadata = { + { "origin", "Shape3DOverlay::"+shapeStrings[_shape] }, + { "overlayID", getID() }, + }; + result.meshes << geometryCache->meshFromShape(_shape, vertexColor); + if (ok) { + *ok = true; + } + return result; +} diff --git a/interface/src/ui/overlays/Shape3DOverlay.h b/interface/src/ui/overlays/Shape3DOverlay.h index 7fc95ec981..34f82af278 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.h +++ b/interface/src/ui/overlays/Shape3DOverlay.h @@ -37,6 +37,7 @@ public: void setProperties(const QVariantMap& properties) override; QVariant getProperty(const QString& property) override; + virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override; protected: Transform evalRenderTransform() override; diff --git a/libraries/avatars-renderer/CMakeLists.txt b/libraries/avatars-renderer/CMakeLists.txt index 53edc692f2..f7e951500b 100644 --- a/libraries/avatars-renderer/CMakeLists.txt +++ b/libraries/avatars-renderer/CMakeLists.txt @@ -13,5 +13,6 @@ include_hifi_library_headers(entities-renderer) include_hifi_library_headers(audio) include_hifi_library_headers(entities) include_hifi_library_headers(octree) +include_hifi_library_headers(graphics-scripting) # for ScriptableModel.h target_bullet() diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 500a24763d..8e22f355e4 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -35,6 +35,8 @@ #include "ModelEntityItem.h" #include "RenderableModelEntityItem.h" +#include + #include "Logging.h" using namespace std; @@ -1760,3 +1762,29 @@ float Avatar::getUnscaledEyeHeightFromSkeleton() const { return DEFAULT_AVATAR_EYE_HEIGHT; } } + +scriptable::ScriptableModel Avatar::getScriptableModel(bool* ok) { + qDebug() << "Avatar::getScriptableModel" ; + if (!_skeletonModel || !_skeletonModel->isLoaded()) { + return scriptable::ModelProvider::modelUnavailableError(ok); + } + scriptable::ScriptableModel result; + result.metadata = { + { "avatarID", getSessionUUID().toString() }, + { "url", _skeletonModelURL.toString() }, + { "origin", "Avatar/avatar::" + _displayName }, + { "textures", _skeletonModel->getTextures() }, + }; + result.mixin(_skeletonModel->getScriptableModel(ok)); + + // FIXME: for now access to attachment models are merged with the main avatar model + for (auto& attachmentModel : _attachmentModels) { + if (attachmentModel->isLoaded()) { + result.mixin(attachmentModel->getScriptableModel(ok)); + } + } + if (ok) { + *ok = true; + } + return result; +} diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index c2b404a925..5cfc399b65 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -20,6 +20,7 @@ #include #include #include +#include #include @@ -53,7 +54,7 @@ class Texture; using AvatarPhysicsCallback = std::function; -class Avatar : public AvatarData { +class Avatar : public AvatarData, public scriptable::ModelProvider { Q_OBJECT /**jsdoc @@ -272,6 +273,8 @@ public: virtual void setAvatarEntityDataChanged(bool value) override; + + virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override; public slots: // FIXME - these should be migrated to use Pose data instead diff --git a/libraries/entities-renderer/CMakeLists.txt b/libraries/entities-renderer/CMakeLists.txt index ea75367e1e..27ea04f642 100644 --- a/libraries/entities-renderer/CMakeLists.txt +++ b/libraries/entities-renderer/CMakeLists.txt @@ -13,6 +13,7 @@ include_hifi_library_headers(fbx) include_hifi_library_headers(entities) include_hifi_library_headers(avatars) include_hifi_library_headers(controllers) +include_hifi_library_headers(graphics-scripting) # for ScriptableModel.h target_bullet() target_polyvox() diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index 8eb82e2c6e..f07b67fbd0 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -17,13 +17,14 @@ #include #include "AbstractViewStateInterface.h" #include "EntitiesRendererLogging.h" +#include class EntityTreeRenderer; namespace render { namespace entities { // Base class for all renderable entities -class EntityRenderer : public QObject, public std::enable_shared_from_this, public PayloadProxyInterface, protected ReadWriteLockable { +class EntityRenderer : public QObject, public std::enable_shared_from_this, public PayloadProxyInterface, protected ReadWriteLockable, public scriptable::ModelProvider { Q_OBJECT using Pointer = std::shared_ptr; @@ -37,7 +38,7 @@ public: virtual bool wantsKeyboardFocus() const { return false; } virtual void setProxyWindow(QWindow* proxyWindow) {} virtual QObject* getEventHandler() { return nullptr; } - const EntityItemPointer& getEntity() { return _entity; } + const EntityItemPointer& getEntity() const { return _entity; } const ItemID& getRenderItemID() const { return _renderItemID; } const SharedSoundPointer& getCollisionSound() { return _collisionSound; } @@ -54,6 +55,7 @@ public: const uint64_t& getUpdateTime() const { return _updateTime; } + virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override { return scriptable::ModelProvider::modelUnavailableError(ok); } protected: virtual bool needsRenderUpdateFromEntity() const final { return needsRenderUpdateFromEntity(_entity); } virtual void onAddToScene(const EntityItemPointer& entity); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 9fcb7640ef..7b022fefac 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -950,13 +950,18 @@ QStringList RenderableModelEntityItem::getJointNames() const { return result; } -bool RenderableModelEntityItem::getMeshes(MeshProxyList& result) { - auto model = getModel(); + +scriptable::ScriptableModel render::entities::ModelEntityRenderer::getScriptableModel(bool* ok) { + ModelPointer model; + withReadLock([&] { + model = _model; + }); + if (!model || !model->isLoaded()) { - return false; + return scriptable::ModelProvider::modelUnavailableError(ok); } - BLOCKING_INVOKE_METHOD(model.get(), "getMeshes", Q_RETURN_ARG(MeshProxyList, result)); - return !result.isEmpty(); + + return _model->getScriptableModel(ok); } void RenderableModelEntityItem::simulateRelayedJoints() { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 33fc9910a0..3e952cb9a7 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -111,7 +111,6 @@ public: virtual int getJointIndex(const QString& name) const override; virtual QStringList getJointNames() const override; - bool getMeshes(MeshProxyList& result) override; const void* getCollisionMeshKey() const { return _collisionMeshKey; } signals: @@ -141,6 +140,7 @@ class ModelEntityRenderer : public TypedEntityRenderer PolyLineEntityRenderer::updateVertic return vertices; } +scriptable::ScriptableModel PolyLineEntityRenderer::getScriptableModel(bool *ok) { + // TODO: adapt polyline into a triangles mesh... + return EntityRenderer::getScriptableModel(ok); +} void PolyLineEntityRenderer::doRender(RenderArgs* args) { if (_empty) { @@ -319,4 +323,4 @@ void PolyLineEntityRenderer::doRender(RenderArgs* args) { #endif batch.draw(gpu::TRIANGLE_STRIP, _numVertices, 0); -} \ No newline at end of file +} diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h index 1e27ac9ae7..3bb8901178 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h @@ -25,6 +25,7 @@ class PolyLineEntityRenderer : public TypedEntityRenderer { public: PolyLineEntityRenderer(const EntityItemPointer& entity); + virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override; protected: virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override; virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index ade3790df6..fd923c40b0 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -20,8 +20,6 @@ #include #include -#include -#include #include #include #include @@ -1416,36 +1414,36 @@ void RenderablePolyVoxEntityItem::bonkNeighbors() { } } -bool RenderablePolyVoxEntityItem::getMeshes(MeshProxyList& result) { - if (!updateDependents()) { - return false; +scriptable::ScriptableModel RenderablePolyVoxEntityItem::getScriptableModel(bool * ok) { + if (!updateDependents() || !_mesh) { + return scriptable::ModelProvider::modelUnavailableError(ok); } bool success = false; - if (_mesh) { - MeshProxy* meshProxy = nullptr; - glm::mat4 transform = voxelToLocalMatrix(); - withReadLock([&] { - gpu::BufferView::Index numVertices = (gpu::BufferView::Index)_mesh->getNumVertices(); - if (!_meshReady) { - // we aren't ready to return a mesh. the caller will have to try again later. - success = false; - } else if (numVertices == 0) { - // we are ready, but there are no triangles in the mesh. - success = true; - } else { - success = true; - // the mesh will be in voxel-space. transform it into object-space - meshProxy = new SimpleMeshProxy( - _mesh->map([=](glm::vec3 position) { return glm::vec3(transform * glm::vec4(position, 1.0f)); }, - [=](glm::vec3 color) { return color; }, - [=](glm::vec3 normal) { return glm::normalize(glm::vec3(transform * glm::vec4(normal, 0.0f))); }, - [&](uint32_t index) { return index; })); - result << meshProxy; - } - }); + glm::mat4 transform = voxelToLocalMatrix(); + scriptable::ScriptableModel result; + withReadLock([&] { + gpu::BufferView::Index numVertices = (gpu::BufferView::Index)_mesh->getNumVertices(); + if (!_meshReady) { + // we aren't ready to return a mesh. the caller will have to try again later. + success = false; + } else if (numVertices == 0) { + // we are ready, but there are no triangles in the mesh. + success = true; + } else { + success = true; + // the mesh will be in voxel-space. transform it into object-space + result.meshes << + _mesh->map([=](glm::vec3 position){ return glm::vec3(transform * glm::vec4(position, 1.0f)); }, + [=](glm::vec3 color){ return color; }, + [=](glm::vec3 normal){ return glm::normalize(glm::vec3(transform * glm::vec4(normal, 0.0f))); }, + [&](uint32_t index){ return index; }); + } + }); + if (ok) { + *ok = success; } - return success; + return result; } using namespace render; diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index db0f0b729a..55b9be23d8 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -32,7 +32,7 @@ namespace render { namespace entities { class PolyVoxEntityRenderer; } } -class RenderablePolyVoxEntityItem : public PolyVoxEntityItem { +class RenderablePolyVoxEntityItem : public PolyVoxEntityItem, public scriptable::ModelProvider { friend class render::entities::PolyVoxEntityRenderer; public: @@ -113,7 +113,7 @@ public: void setVolDataDirty() { withWriteLock([&] { _volDataDirty = true; _meshReady = false; }); } - bool getMeshes(MeshProxyList& result) override; + virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override; private: bool updateOnCount(const ivec3& v, uint8_t toValue); @@ -163,6 +163,9 @@ class PolyVoxEntityRenderer : public TypedEntityRenderer()->getScriptableModel(ok); + } protected: virtual ItemKey getKey() override { return ItemKey::Builder::opaqueShape(); } diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index cdee2c5ec9..746102681c 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -156,3 +156,24 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { const auto triCount = geometryCache->getShapeTriangleCount(geometryShape); args->_details._trianglesRendered += (int)triCount; } + +scriptable::ScriptableModel ShapeEntityRenderer::getScriptableModel(bool* ok) { + scriptable::ScriptableModel result; + result.metadata = { + { "entityID", getEntity()->getID().toString() }, + { "shape", entity::stringFromShape(_shape) }, + { "userData", getEntity()->getUserData() }, + }; + auto geometryCache = DependencyManager::get(); + auto geometryShape = geometryCache->getShapeForEntityShape(_shape); + auto vertexColor = glm::vec3(_color); + auto success = false; + if (auto mesh = geometryCache->meshFromShape(geometryShape, vertexColor)) { + result.meshes << mesh; + success = true; + } + if (ok) { + *ok = success; + } + return result; +} diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.h b/libraries/entities-renderer/src/RenderableShapeEntityItem.h index 433cb41ad2..6ada7e7317 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.h +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.h @@ -22,6 +22,8 @@ class ShapeEntityRenderer : public TypedEntityRenderer { public: ShapeEntityRenderer(const EntityItemPointer& entity); + virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override; + private: virtual bool needsRenderUpdate() const override; virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override; diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 4c398b8a29..5c9324fc8a 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -57,8 +57,6 @@ using EntityTreeElementExtraEncodeDataPointer = std::shared_ptr(_entityTree->findEntityByEntityItemID(entityID)); - if (!entity) { - qCDebug(entities) << "EntityScriptingInterface::getMeshes no entity with ID" << entityID; - QScriptValueList args { callback.engine()->undefinedValue(), false }; - callback.call(QScriptValue(), args); - return; - } - - MeshProxyList result; - bool success = entity->getMeshes(result); - - if (success) { - QScriptValue resultAsScriptValue = meshesToScriptValue(callback.engine(), result); - QScriptValueList args { resultAsScriptValue, true }; - callback.call(QScriptValue(), args); - } else { - QScriptValueList args { callback.engine()->undefinedValue(), false }; - callback.call(QScriptValue(), args); - } -} - glm::mat4 EntityScriptingInterface::getEntityTransform(const QUuid& entityID) { glm::mat4 result; if (_entityTree) { diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 4c4e2ffbfd..da201f93eb 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -37,7 +37,6 @@ #include "BaseScriptEngine.h" class EntityTree; -class MeshProxy; // helper factory to compose standardized, async metadata queries for "magic" Entity properties // like .script and .serverScripts. This is used for automated testing of core scripting features @@ -401,9 +400,6 @@ public slots: Q_INVOKABLE bool AABoxIntersectsCapsule(const glm::vec3& low, const glm::vec3& dimensions, const glm::vec3& start, const glm::vec3& end, float radius); - // FIXME move to a renderable entity interface - Q_INVOKABLE void getMeshes(QUuid entityID, QScriptValue callback); - /**jsdoc * Returns object to world transform, excluding scale * diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 14462e0558..50abe7928f 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1967,19 +1967,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } } } - { - int i = 0; - for (const auto& mesh : geometry.meshes) { - auto name = geometry.getModelNameOfMesh(i++); - if (!name.isEmpty()) { - if (mesh._mesh) { - mesh._mesh->displayName += "#" + name; - } else { - qDebug() << "modelName but no mesh._mesh" << name; - } - } - } - } + return geometryPtr; } @@ -1995,7 +1983,7 @@ FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QStri reader._loadLightmaps = loadLightmaps; reader._lightmapLevel = lightmapLevel; - qCDebug(modelformat) << "Reading FBX: " << url; + qDebug() << "Reading FBX: " << url; return reader.extractFBXGeometry(mapping, url); } diff --git a/libraries/fbx/src/OBJWriter.cpp b/libraries/fbx/src/OBJWriter.cpp index 4441ae6649..37bced8458 100644 --- a/libraries/fbx/src/OBJWriter.cpp +++ b/libraries/fbx/src/OBJWriter.cpp @@ -46,9 +46,12 @@ 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(); @@ -81,7 +84,9 @@ bool writeOBJToTextStream(QTextStream& out, QList meshes) { // 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(); @@ -98,7 +103,9 @@ bool writeOBJToTextStream(QTextStream& out, QList meshes) { // 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,35 +113,25 @@ 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(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"; + out << QString("g %1-%2-%3\n").arg(subMeshIndex, 3, 10, QChar('0')).arg(name).arg(partIndex); - // 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; + const bool shorts = indexBuffer._element == gpu::Element::INDEX_UINT16; + auto face = [&](uint32_t i0, uint32_t i1, uint32_t i2) { + uint32_t index0, index1, index2; + if (shorts) { + index0 = indexBuffer.get(i0); + index1 = indexBuffer.get(i1); + index2 = indexBuffer.get(i2); + } else { + index0 = indexBuffer.get(i0); + index1 = indexBuffer.get(i1); + index2 = indexBuffer.get(i2); } - 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 << "f "; if (haveNormals) { @@ -146,6 +143,39 @@ bool writeOBJToTextStream(QTextStream& out, QList meshes) { out << currentVertexStartOffset + index1 + 1 << " "; out << currentVertexStartOffset + index2 + 1 << "\n"; } + }; + + // graphics::Mesh::TRIANGLES / graphics::Mesh::QUADS + // TODO -- handle other formats + uint32_t len = part._startIndex + part._numIndices; + auto stringFromTopology = [&](graphics::Mesh::Topology topo) -> QString { + return topo == graphics::Mesh::Topology::QUADS ? "QUADS" : + topo == graphics::Mesh::Topology::QUAD_STRIP ? "QUAD_STRIP" : + topo == graphics::Mesh::Topology::TRIANGLES ? "TRIANGLES" : + topo == graphics::Mesh::Topology::TRIANGLE_STRIP ? "TRIANGLE_STRIP" : + topo == graphics::Mesh::Topology::QUAD_STRIP ? "QUAD_STRIP" : + QString("topo:%1").arg((int)topo); + }; + + qCDebug(modelformat) << "OBJWriter -- part" << partIndex << "topo" << stringFromTopology(part._topology) << "index elements" << (shorts ? "uint16_t" : "uint32_t"); + 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/graphics-scripting/CMakeLists.txt b/libraries/graphics-scripting/CMakeLists.txt new file mode 100644 index 0000000000..e7fa3de155 --- /dev/null +++ b/libraries/graphics-scripting/CMakeLists.txt @@ -0,0 +1,5 @@ +set(TARGET_NAME graphics-scripting) +setup_hifi_library() +link_hifi_libraries(shared networking graphics fbx model-networking script-engine) +include_hifi_library_headers(gpu) +include_hifi_library_headers(graphics-scripting) diff --git a/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.cpp b/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.cpp new file mode 100644 index 0000000000..e865ed0e5a --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.cpp @@ -0,0 +1,195 @@ +#include "./graphics-scripting/BufferViewHelpers.h" + +#include +#include + +#include +#include +#include + +#include + +#ifdef DEBUG_BUFFERVIEW_SCRIPTING + #include "DebugNames.h" +#endif + +namespace { + const std::array XYZW = {{ "x", "y", "z", "w" }}; + const std::array ZERO123 = {{ "0", "1", "2", "3" }}; +} + +template +QVariant getBufferViewElement(const gpu::BufferView& view, quint32 index, bool asArray = false) { + return glmVecToVariant(view.get(index), asArray); +} + +template +void setBufferViewElement(const gpu::BufferView& view, quint32 index, const QVariant& v) { + view.edit(index) = glmVecFromVariant(v); +} + +//FIXME copied from Model.cpp +static void packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, glm::uint32& packedNormal, glm::uint32& packedTangent) { + auto absNormal = glm::abs(normal); + auto absTangent = glm::abs(tangent); + normal /= glm::max(1e-6f, glm::max(glm::max(absNormal.x, absNormal.y), absNormal.z)); + tangent /= glm::max(1e-6f, glm::max(glm::max(absTangent.x, absTangent.y), absTangent.z)); + normal = glm::clamp(normal, -1.0f, 1.0f); + tangent = glm::clamp(tangent, -1.0f, 1.0f); + normal *= 511.0f; + tangent *= 511.0f; + normal = glm::round(normal); + tangent = glm::round(tangent); + + glm::detail::i10i10i10i2 normalStruct; + glm::detail::i10i10i10i2 tangentStruct; + normalStruct.data.x = int(normal.x); + normalStruct.data.y = int(normal.y); + normalStruct.data.z = int(normal.z); + normalStruct.data.w = 0; + tangentStruct.data.x = int(tangent.x); + tangentStruct.data.y = int(tangent.y); + tangentStruct.data.z = int(tangent.z); + tangentStruct.data.w = 0; + packedNormal = normalStruct.pack; + packedTangent = tangentStruct.pack; +} + +bool bufferViewElementFromVariant(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; + } 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; + } + setBufferViewElement(view, index, v); return true; + } + } + } else if (BYTES_PER_ELEMENT == 2) { + 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; + } + } + } + return false; +} + +QVariant bufferViewElementToVariant(const gpu::BufferView& view, quint32 index, bool asArray, const char* hint) { + const auto& element = view._element; + const auto vecN = element.getScalarCount(); + const auto dataType = element.getType(); + const auto byteLength = element.getSize(); + const auto BYTES_PER_ELEMENT = byteLength / vecN; + Q_ASSERT(index < view.getNumElements()); + Q_ASSERT(index * vecN * BYTES_PER_ELEMENT < (view._size - vecN * BYTES_PER_ELEMENT)); + if (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) { + 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 +QVariant glmVecToVariant(const T& v, bool asArray /*= false*/) { + static const auto len = T().length(); + if (asArray) { + QVariantList list; + for (int i = 0; i < len ; i++) { + list << v[i]; + } + return list; + } else { + QVariantMap obj; + for (int i = 0; i < len ; i++) { + obj[XYZW[i]] = v[i]; + } + return obj; + } +} +template +const T glmVecFromVariant(const QVariant& v) { + auto isMap = v.type() == (QVariant::Type)QMetaType::QVariantMap; + static const auto len = T().length(); + const auto& components = isMap ? XYZW : ZERO123; + T result; + QVariantMap map; + QVariantList list; + if (isMap) map = v.toMap(); else list = v.toList(); + for (int i = 0; i < len ; i++) { + float value; + if (isMap) { + value = map.value(components[i]).toFloat(); + } else { + value = list.value(i).toFloat(); + } + if (value != value) { // NAN + qWarning().nospace()<< "vec" << len << "." << components[i] << " NAN received from script.... " << v.toString(); + } + result[i] = value; + } + return result; +} + diff --git a/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h b/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h new file mode 100644 index 0000000000..0fe2602f6c --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h @@ -0,0 +1,18 @@ +// +// 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 + +namespace gpu { class BufferView; } + +template QVariant glmVecToVariant(const T& v, bool asArray = false); +template const T glmVecFromVariant(const QVariant& v); +QVariant bufferViewElementToVariant(const gpu::BufferView& view, quint32 index, bool asArray = false, const char* hint = ""); +bool bufferViewElementFromVariant(const gpu::BufferView& view, quint32 index, const QVariant& v); + + diff --git a/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp b/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp new file mode 100644 index 0000000000..367c0589e9 --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp @@ -0,0 +1,83 @@ +#include "BufferViewScripting.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#ifdef DEBUG_BUFFERVIEW_SCRIPTING + #include +#endif + +namespace { + const std::array XYZW = {{ "x", "y", "z", "w" }}; + const std::array ZERO123 = {{ "0", "1", "2", "3" }}; +} + +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 = bufferViewElementToVariant(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 bufferViewElementFromVariant(view, index, v.toVariant()); +} + +// + +template +QScriptValue glmVecToScriptValue(QScriptEngine *js, const T& v, bool asArray) { + static const auto len = T().length(); + const auto& components = asArray ? ZERO123 : 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() ? XYZW : 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 new file mode 100644 index 0000000000..f2e3fe734e --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.h @@ -0,0 +1,11 @@ +#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/DebugNames.h b/libraries/graphics-scripting/src/graphics-scripting/DebugNames.h new file mode 100644 index 0000000000..e5edf1c9d8 --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/DebugNames.h @@ -0,0 +1,72 @@ +#pragma once +#include +#include +#include +#include +#include +//#include + +Q_DECLARE_METATYPE(gpu::Type); +#ifdef QT_MOC_RUN +class DebugNames { + Q_OBJECT +public: +#else + namespace DebugNames { + Q_NAMESPACE + #endif + +enum Type : uint8_t { + + FLOAT = 0, + INT32, + UINT32, + HALF, + INT16, + UINT16, + INT8, + UINT8, + + NINT32, + NUINT32, + NINT16, + NUINT16, + NINT8, + NUINT8, + + COMPRESSED, + + NUM_TYPES, + + BOOL = UINT8, + NORMALIZED_START = NINT32, +}; + + Q_ENUM_NS(Type) + enum InputSlot { + POSITION = 0, + NORMAL = 1, + COLOR = 2, + TEXCOORD0 = 3, + TEXCOORD = TEXCOORD0, + TANGENT = 4, + SKIN_CLUSTER_INDEX = 5, + SKIN_CLUSTER_WEIGHT = 6, + TEXCOORD1 = 7, + TEXCOORD2 = 8, + TEXCOORD3 = 9, + TEXCOORD4 = 10, + + NUM_INPUT_SLOTS, + + DRAW_CALL_INFO = 15, // Reserve last input slot for draw call infos + }; + + Q_ENUM_NS(InputSlot) + inline QString stringFrom(Type t) { return QVariant::fromValue(t).toString(); } + inline QString stringFrom(InputSlot t) { return QVariant::fromValue(t).toString(); } + inline QString stringFrom(gpu::Type t) { return stringFrom((Type)t); } + inline QString stringFrom(gpu::Stream::Slot t) { return stringFrom((InputSlot)t); } + + extern const QMetaObject staticMetaObject; + }; diff --git a/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp new file mode 100644 index 0000000000..68a00bc02c --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp @@ -0,0 +1,836 @@ +// +// ModelScriptingInterface.cpp +// libraries/script-engine/src +// +// Created by Seth Alves on 2017-1-27. +// Copyright 2017 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 "ModelScriptingInterface.h" +#include +#include +#include +#include +#include "BaseScriptEngine.h" +#include "ScriptEngineLogging.h" +#include "OBJWriter.h" +#include "OBJReader.h" +//#include "ui/overlays/Base3DOverlay.h" +//#include "EntityTreeRenderer.h" +//#include "avatar/AvatarManager.h" +//#include "RenderableEntityItem.h" + +#include +#include + +#include + + +#include + +#include +#include "BufferViewScripting.h" + +#include "ScriptableMesh.h" + +using ScriptableMesh = scriptable::ScriptableMesh; + +#include "ModelScriptingInterface.moc" + +namespace { + QLoggingCategory model_scripting { "hifi.model.scripting" }; +} + +ModelScriptingInterface::ModelScriptingInterface(QObject* parent) : QObject(parent) { + if (auto scriptEngine = qobject_cast(parent)) { + this->registerMetaTypes(scriptEngine); + } +} + +QString ModelScriptingInterface::meshToOBJ(const scriptable::ScriptableModel& _in) { + const auto& in = _in.getMeshes(); + qCDebug(model_scripting) << "meshToOBJ" << in.size(); + if (in.size()) { + QList meshes; + foreach (const auto meshProxy, in) { + qCDebug(model_scripting) << "meshToOBJ" << meshProxy.get(); + if (meshProxy) { + meshes.append(getMeshPointer(meshProxy)); + } + } + if (meshes.size()) { + return writeOBJToString(meshes); + } + } + context()->throwError(QString("null mesh")); + return QString(); +} + +QScriptValue ModelScriptingInterface::appendMeshes(scriptable::ScriptableModel _in) { + const auto& in = _in.getMeshes(); + + // figure out the size of the resulting mesh + size_t totalVertexCount { 0 }; + size_t totalColorCount { 0 }; + size_t totalNormalCount { 0 }; + size_t totalIndexCount { 0 }; + foreach (const scriptable::ScriptableMeshPointer meshProxy, in) { + scriptable::MeshPointer mesh = getMeshPointer(meshProxy); + totalVertexCount += mesh->getNumVertices(); + + int attributeTypeColor = gpu::Stream::InputSlot::COLOR; // libraries/gpu/src/gpu/Stream.h + const gpu::BufferView& colorsBufferView = mesh->getAttributeBuffer(attributeTypeColor); + gpu::BufferView::Index numColors = (gpu::BufferView::Index)colorsBufferView.getNumElements(); + totalColorCount += numColors; + + int attributeTypeNormal = gpu::Stream::InputSlot::NORMAL; // libraries/gpu/src/gpu/Stream.h + const gpu::BufferView& normalsBufferView = mesh->getAttributeBuffer(attributeTypeNormal); + gpu::BufferView::Index numNormals = (gpu::BufferView::Index)normalsBufferView.getNumElements(); + totalNormalCount += numNormals; + + totalIndexCount += mesh->getNumIndices(); + } + + // alloc the resulting mesh + gpu::Resource::Size combinedVertexSize = totalVertexCount * sizeof(glm::vec3); + unsigned char* combinedVertexData = new unsigned char[combinedVertexSize]; + unsigned char* combinedVertexDataCursor = combinedVertexData; + + gpu::Resource::Size combinedColorSize = totalColorCount * sizeof(glm::vec3); + unsigned char* combinedColorData = new unsigned char[combinedColorSize]; + unsigned char* combinedColorDataCursor = combinedColorData; + + gpu::Resource::Size combinedNormalSize = totalNormalCount * sizeof(glm::vec3); + unsigned char* combinedNormalData = new unsigned char[combinedNormalSize]; + unsigned char* combinedNormalDataCursor = combinedNormalData; + + gpu::Resource::Size combinedIndexSize = totalIndexCount * sizeof(uint32_t); + unsigned char* combinedIndexData = new unsigned char[combinedIndexSize]; + unsigned char* combinedIndexDataCursor = combinedIndexData; + + uint32_t indexStartOffset { 0 }; + + foreach (const scriptable::ScriptableMeshPointer meshProxy, in) { + scriptable::MeshPointer mesh = getMeshPointer(meshProxy); + mesh->forEach( + [&](glm::vec3 position){ + memcpy(combinedVertexDataCursor, &position, sizeof(position)); + combinedVertexDataCursor += sizeof(position); + }, + [&](glm::vec3 color){ + memcpy(combinedColorDataCursor, &color, sizeof(color)); + combinedColorDataCursor += sizeof(color); + }, + [&](glm::vec3 normal){ + memcpy(combinedNormalDataCursor, &normal, sizeof(normal)); + combinedNormalDataCursor += sizeof(normal); + }, + [&](uint32_t index){ + index += indexStartOffset; + memcpy(combinedIndexDataCursor, &index, sizeof(index)); + combinedIndexDataCursor += sizeof(index); + }); + + gpu::BufferView::Index numVertices = (gpu::BufferView::Index)mesh->getNumVertices(); + indexStartOffset += numVertices; + } + + graphics::MeshPointer result(new graphics::Mesh()); + + gpu::Element vertexElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); + gpu::Buffer* combinedVertexBuffer = new gpu::Buffer(combinedVertexSize, combinedVertexData); + gpu::BufferPointer combinedVertexBufferPointer(combinedVertexBuffer); + gpu::BufferView combinedVertexBufferView(combinedVertexBufferPointer, vertexElement); + result->setVertexBuffer(combinedVertexBufferView); + + int attributeTypeColor = gpu::Stream::InputSlot::COLOR; // libraries/gpu/src/gpu/Stream.h + gpu::Element colorElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); + gpu::Buffer* combinedColorsBuffer = new gpu::Buffer(combinedColorSize, combinedColorData); + gpu::BufferPointer combinedColorsBufferPointer(combinedColorsBuffer); + gpu::BufferView combinedColorsBufferView(combinedColorsBufferPointer, colorElement); + result->addAttribute(attributeTypeColor, combinedColorsBufferView); + + int attributeTypeNormal = gpu::Stream::InputSlot::NORMAL; // libraries/gpu/src/gpu/Stream.h + gpu::Element normalElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); + gpu::Buffer* combinedNormalsBuffer = new gpu::Buffer(combinedNormalSize, combinedNormalData); + gpu::BufferPointer combinedNormalsBufferPointer(combinedNormalsBuffer); + gpu::BufferView combinedNormalsBufferView(combinedNormalsBufferPointer, normalElement); + result->addAttribute(attributeTypeNormal, combinedNormalsBufferView); + + gpu::Element indexElement = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::RAW); + gpu::Buffer* combinedIndexesBuffer = new gpu::Buffer(combinedIndexSize, combinedIndexData); + gpu::BufferPointer combinedIndexesBufferPointer(combinedIndexesBuffer); + gpu::BufferView combinedIndexesBufferView(combinedIndexesBufferPointer, indexElement); + result->setIndexBuffer(combinedIndexesBufferView); + + std::vector parts; + parts.emplace_back(graphics::Mesh::Part((graphics::Index)0, // startIndex + (graphics::Index)result->getNumIndices(), // numIndices + (graphics::Index)0, // baseVertex + graphics::Mesh::TRIANGLES)); // topology + result->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(graphics::Mesh::Part), + (gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL)); + + + scriptable::ScriptableMeshPointer resultProxy = scriptable::ScriptableMeshPointer(new ScriptableMesh(nullptr, result)); + return engine()->toScriptValue(result); +} + +QScriptValue ModelScriptingInterface::transformMesh(scriptable::ScriptableMeshPointer meshProxy, glm::mat4 transform) { + auto mesh = getMeshPointer(meshProxy); + if (!mesh) { + return false; + } + + graphics::MeshPointer result = mesh->map([&](glm::vec3 position){ return glm::vec3(transform * glm::vec4(position, 1.0f)); }, + [&](glm::vec3 color){ return color; }, + [&](glm::vec3 normal){ return glm::vec3(transform * glm::vec4(normal, 0.0f)); }, + [&](uint32_t index){ return index; }); + scriptable::ScriptableMeshPointer resultProxy = scriptable::ScriptableMeshPointer(new ScriptableMesh(nullptr, result)); + return engine()->toScriptValue(resultProxy); +} + +QScriptValue ModelScriptingInterface::getVertexCount(scriptable::ScriptableMeshPointer meshProxy) { + auto mesh = getMeshPointer(meshProxy); + if (!mesh) { + return -1; + } + return (uint32_t)mesh->getNumVertices(); +} + +QScriptValue ModelScriptingInterface::getVertex(scriptable::ScriptableMeshPointer meshProxy, mesh::uint32 vertexIndex) { + auto mesh = getMeshPointer(meshProxy); + + const gpu::BufferView& vertexBufferView = mesh->getVertexBuffer(); + auto numVertices = mesh->getNumVertices(); + + if (vertexIndex >= numVertices) { + context()->throwError(QString("invalid index: %1 [0,%2)").arg(vertexIndex).arg(numVertices)); + return QScriptValue::NullValue; + } + + glm::vec3 pos = vertexBufferView.get(vertexIndex); + return engine()->toScriptValue(pos); +} + +QScriptValue ModelScriptingInterface::newMesh(const QVector& vertices, + const QVector& normals, + const QVector& faces) { + graphics::MeshPointer mesh(new graphics::Mesh()); + + // vertices + auto vertexBuffer = std::make_shared(vertices.size() * sizeof(glm::vec3), (gpu::Byte*)vertices.data()); + auto vertexBufferPtr = gpu::BufferPointer(vertexBuffer); + gpu::BufferView vertexBufferView(vertexBufferPtr, 0, vertexBufferPtr->getSize(), + sizeof(glm::vec3), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); + mesh->setVertexBuffer(vertexBufferView); + + if (vertices.size() == normals.size()) { + // normals + auto normalBuffer = std::make_shared(normals.size() * sizeof(glm::vec3), (gpu::Byte*)normals.data()); + auto normalBufferPtr = gpu::BufferPointer(normalBuffer); + gpu::BufferView normalBufferView(normalBufferPtr, 0, normalBufferPtr->getSize(), + sizeof(glm::vec3), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); + mesh->addAttribute(gpu::Stream::NORMAL, normalBufferView); + } else { + qCWarning(model_scripting, "ModelScriptingInterface::newMesh normals must be same length as vertices"); + } + + // indices (faces) + int VERTICES_PER_TRIANGLE = 3; + int indexBufferSize = faces.size() * sizeof(uint32_t) * VERTICES_PER_TRIANGLE; + unsigned char* indexData = new unsigned char[indexBufferSize]; + unsigned char* indexDataCursor = indexData; + foreach(const mesh::MeshFace& meshFace, faces) { + for (int i = 0; i < VERTICES_PER_TRIANGLE; i++) { + memcpy(indexDataCursor, &meshFace.vertexIndices[i], sizeof(uint32_t)); + indexDataCursor += sizeof(uint32_t); + } + } + auto indexBuffer = std::make_shared(indexBufferSize, (gpu::Byte*)indexData); + auto indexBufferPtr = gpu::BufferPointer(indexBuffer); + gpu::BufferView indexBufferView(indexBufferPtr, gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::RAW)); + mesh->setIndexBuffer(indexBufferView); + + // parts + std::vector parts; + parts.emplace_back(graphics::Mesh::Part((graphics::Index)0, // startIndex + (graphics::Index)faces.size() * 3, // numIndices + (graphics::Index)0, // baseVertex + graphics::Mesh::TRIANGLES)); // topology + mesh->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(graphics::Mesh::Part), + (gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL)); + + + + scriptable::ScriptableMeshPointer meshProxy = scriptable::ScriptableMeshPointer(new ScriptableMesh(nullptr, mesh)); + return engine()->toScriptValue(meshProxy); +} + +QScriptValue ModelScriptingInterface::mapAttributeValues( + QScriptValue _in, + QScriptValue scopeOrCallback, + QScriptValue methodOrName + ) { + qCInfo(model_scripting) << "mapAttributeValues" << _in.toVariant().typeName() << _in.toVariant().toString() << _in.toQObject(); + auto in = qscriptvalue_cast(_in).getMeshes(); + if (in.size()) { + foreach (scriptable::ScriptableMeshPointer meshProxy, in) { + mapMeshAttributeValues(meshProxy, scopeOrCallback, methodOrName); + } + return thisObject(); + } else if (auto meshProxy = qobject_cast(_in.toQObject())) { + return mapMeshAttributeValues(meshProxy->shared_from_this(), scopeOrCallback, methodOrName); + } else { + context()->throwError("invalid ModelProxy || MeshProxyPointer"); + } + return false; +} + + +QScriptValue ModelScriptingInterface::unrollVertices(scriptable::ScriptableMeshPointer meshProxy, bool recalcNormals) { + auto mesh = getMeshPointer(meshProxy); + qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices" << !!mesh<< !!meshProxy; + if (!mesh) { + return QScriptValue(); + } + + auto positions = mesh->getVertexBuffer(); + auto indices = mesh->getIndexBuffer(); + quint32 numPoints = (quint32)indices.getNumElements(); + auto buffer = new gpu::Buffer(); + buffer->resize(numPoints * sizeof(uint32_t)); + auto newindices = gpu::BufferView(buffer, { gpu::SCALAR, gpu::UINT32, gpu::INDEX }); + qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices numPoints" << numPoints; + auto attributeViews = ScriptableMesh::gatherBufferViews(mesh); + for (const auto& a : attributeViews) { + auto& view = a.second; + auto sz = view._element.getSize(); + auto buffer = new gpu::Buffer(); + buffer->resize(numPoints * sz); + auto points = gpu::BufferView(buffer, view._element); + auto src = (uint8_t*)view._buffer->getData(); + auto dest = (uint8_t*)points._buffer->getData(); + auto slot = ScriptableMesh::ATTRIBUTES[a.first]; + qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices buffer" << a.first; + qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices source" << view.getNumElements(); + qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices dest" << points.getNumElements(); + qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices sz" << sz << src << dest << slot; + auto esize = indices._element.getSize(); + const char* hint= a.first.toStdString().c_str(); + for(quint32 i = 0; i < numPoints; i++) { + quint32 index = esize == 4 ? indices.get(i) : indices.get(i); + newindices.edit(i) = i; + bufferViewElementFromVariant( + points, i, + bufferViewElementToVariant(view, index, false, hint) + ); + } + if (slot == gpu::Stream::POSITION) { + mesh->setVertexBuffer(points); + } else { + mesh->addAttribute(slot, points); + } + } + mesh->setIndexBuffer(newindices); + if (recalcNormals) { + recalculateNormals(meshProxy); + } + return true; +} + +namespace { + template + gpu::BufferView bufferViewFromVector(QVector elements, gpu::Element elementType) { + auto vertexBuffer = std::make_shared( + elements.size() * sizeof(T), + (gpu::Byte*)elements.data() + ); + return { vertexBuffer, 0, vertexBuffer->getSize(),sizeof(T), elementType }; + } + + gpu::BufferView cloneBufferView(const gpu::BufferView& input) { + //qCInfo(model_scripting) << "input" << input.getNumElements() << input._buffer->getSize(); + auto output = gpu::BufferView( + std::make_shared(input._buffer->getSize(), input._buffer->getData()), + input._offset, + input._size, + input._stride, + input._element + ); + //qCInfo(model_scripting) << "after" << output.getNumElements() << output._buffer->getSize(); + return output; + } + + gpu::BufferView resizedBufferView(const gpu::BufferView& input, quint32 numElements) { + auto effectiveSize = input._buffer->getSize() / input.getNumElements(); + qCInfo(model_scripting) << "resize input" << input.getNumElements() << input._buffer->getSize() << "effectiveSize" << effectiveSize; + auto vsize = input._element.getSize() * numElements; + gpu::Byte *data = new gpu::Byte[vsize]; + memset(data, 0, vsize); + auto buffer = new gpu::Buffer(vsize, (gpu::Byte*)data); + delete[] data; + auto output = gpu::BufferView(buffer, input._element); + qCInfo(model_scripting) << "resized output" << output.getNumElements() << output._buffer->getSize(); + return output; + } +} + +bool ModelScriptingInterface::replaceMeshData(scriptable::ScriptableMeshPointer dest, scriptable::ScriptableMeshPointer src, const QVector& attributeNames) { + auto target = getMeshPointer(dest); + auto source = getMeshPointer(src); + if (!target || !source) { + context()->throwError("ModelScriptingInterface::replaceMeshData -- expected dest and src to be valid mesh proxy pointers"); + return false; + } + + QVector attributes = attributeNames.isEmpty() ? src->getAttributeNames() : attributeNames; + + //qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData -- source:" << source->displayName << "target:" << target->displayName << "attributes:" << attributes; + + // remove attributes only found on target mesh, unless user has explicitly specified the relevant attribute names + if (attributeNames.isEmpty()) { + auto attributeViews = ScriptableMesh::gatherBufferViews(target); + for (const auto& a : attributeViews) { + auto slot = ScriptableMesh::ATTRIBUTES[a.first]; + if (!attributes.contains(a.first)) { + //qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData -- pruning target attribute" << a.first << slot; + target->removeAttribute(slot); + } + } + } + + target->setVertexBuffer(cloneBufferView(source->getVertexBuffer())); + target->setIndexBuffer(cloneBufferView(source->getIndexBuffer())); + target->setPartBuffer(cloneBufferView(source->getPartBuffer())); + + for (const auto& a : attributes) { + auto slot = ScriptableMesh::ATTRIBUTES[a]; + if (slot == gpu::Stream::POSITION) { + continue; + } + // auto& before = target->getAttributeBuffer(slot); + auto& input = source->getAttributeBuffer(slot); + if (input.getNumElements() == 0) { + //qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData buffer is empty -- pruning" << a << slot; + target->removeAttribute(slot); + } else { + // if (before.getNumElements() == 0) { + // qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData target buffer is empty -- adding" << a << slot; + // } else { + // qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData target buffer exists -- updating" << a << slot; + // } + target->addAttribute(slot, cloneBufferView(input)); + } + // auto& after = target->getAttributeBuffer(slot); + // qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData" << a << slot << before.getNumElements() << " -> " << after.getNumElements(); + } + + + return true; +} + +bool ModelScriptingInterface::dedupeVertices(scriptable::ScriptableMeshPointer meshProxy, float epsilon) { + auto mesh = getMeshPointer(meshProxy); + if (!mesh) { + return false; + } + auto positions = mesh->getVertexBuffer(); + auto numPositions = positions.getNumElements(); + const auto epsilon2 = epsilon*epsilon; + + QVector uniqueVerts; + uniqueVerts.reserve((int)numPositions); + QMap remapIndices; + + for (quint32 i = 0; i < numPositions; i++) { + const quint32 numUnique = uniqueVerts.size(); + const auto& position = positions.get(i); + bool unique = true; + for (quint32 j = 0; j < numUnique; j++) { + if (glm::length2(uniqueVerts[j] - position) <= epsilon2) { + remapIndices[i] = j; + unique = false; + break; + } + } + if (unique) { + uniqueVerts << position; + remapIndices[i] = numUnique; + } + } + + qCInfo(model_scripting) << "//VERTS before" << numPositions << "after" << uniqueVerts.size(); + + auto indices = mesh->getIndexBuffer(); + auto numIndices = indices.getNumElements(); + auto esize = indices._element.getSize(); + QVector newIndices; + newIndices.reserve((int)numIndices); + for (quint32 i = 0; i < numIndices; i++) { + quint32 index = esize == 4 ? indices.get(i) : indices.get(i); + if (remapIndices.contains(index)) { + //qCInfo(model_scripting) << i << index << "->" << remapIndices[index]; + newIndices << remapIndices[index]; + } else { + qCInfo(model_scripting) << i << index << "!remapIndices[index]"; + } + } + + mesh->setIndexBuffer(bufferViewFromVector(newIndices, { gpu::SCALAR, gpu::UINT32, gpu::INDEX })); + mesh->setVertexBuffer(bufferViewFromVector(uniqueVerts, { gpu::VEC3, gpu::FLOAT, gpu::XYZ })); + + auto attributeViews = ScriptableMesh::gatherBufferViews(mesh); + quint32 numUniqueVerts = uniqueVerts.size(); + for (const auto& a : attributeViews) { + auto& view = a.second; + auto slot = ScriptableMesh::ATTRIBUTES[a.first]; + if (slot == gpu::Stream::POSITION) { + continue; + } + qCInfo(model_scripting) << "ModelScriptingInterface::dedupeVertices" << a.first << slot << view.getNumElements(); + auto newView = resizedBufferView(view, numUniqueVerts); + qCInfo(model_scripting) << a.first << "before: #" << view.getNumElements() << "after: #" << newView.getNumElements(); + quint32 numElements = (quint32)view.getNumElements(); + for (quint32 i = 0; i < numElements; i++) { + quint32 fromVertexIndex = i; + quint32 toVertexIndex = remapIndices.contains(fromVertexIndex) ? remapIndices[fromVertexIndex] : fromVertexIndex; + bufferViewElementFromVariant( + newView, toVertexIndex, + bufferViewElementToVariant(view, fromVertexIndex, false, "dedupe") + ); + } + mesh->addAttribute(slot, newView); + } + return true; +} + +QScriptValue ModelScriptingInterface::cloneMesh(scriptable::ScriptableMeshPointer meshProxy, bool recalcNormals) { + auto mesh = getMeshPointer(meshProxy); + if (!mesh) { + return QScriptValue::NullValue; + } + graphics::MeshPointer clone(new graphics::Mesh()); + clone->displayName = mesh->displayName + "-clone"; + qCInfo(model_scripting) << "ModelScriptingInterface::cloneMesh" << !!mesh<< !!meshProxy; + if (!mesh) { + return QScriptValue::NullValue; + } + + clone->setIndexBuffer(cloneBufferView(mesh->getIndexBuffer())); + clone->setPartBuffer(cloneBufferView(mesh->getPartBuffer())); + auto attributeViews = ScriptableMesh::gatherBufferViews(mesh); + for (const auto& a : attributeViews) { + auto& view = a.second; + auto slot = ScriptableMesh::ATTRIBUTES[a.first]; + qCInfo(model_scripting) << "ModelScriptingInterface::cloneVertices buffer" << a.first << slot; + auto points = cloneBufferView(view); + qCInfo(model_scripting) << "ModelScriptingInterface::cloneVertices source" << view.getNumElements(); + qCInfo(model_scripting) << "ModelScriptingInterface::cloneVertices dest" << points.getNumElements(); + if (slot == gpu::Stream::POSITION) { + clone->setVertexBuffer(points); + } else { + clone->addAttribute(slot, points); + } + } + + auto result = scriptable::ScriptableMeshPointer(new ScriptableMesh(nullptr, clone)); + if (recalcNormals) { + recalculateNormals(result); + } + return engine()->toScriptValue(result); +} + +bool ModelScriptingInterface::recalculateNormals(scriptable::ScriptableMeshPointer meshProxy) { + qCInfo(model_scripting) << "Recalculating normals" << !!meshProxy; + auto mesh = getMeshPointer(meshProxy); + if (!mesh) { + return false; + } + ScriptableMesh::gatherBufferViews(mesh, { "normal", "color" }); // ensures #normals >= #positions + auto normals = mesh->getAttributeBuffer(gpu::Stream::NORMAL); + auto verts = mesh->getVertexBuffer(); + auto indices = mesh->getIndexBuffer(); + auto esize = indices._element.getSize(); + auto numPoints = indices.getNumElements(); + const auto TRIANGLE = 3; + quint32 numFaces = (quint32)numPoints / TRIANGLE; + //QVector faces; + QVector faceNormals; + QMap> vertexToFaces; + //faces.resize(numFaces); + faceNormals.resize(numFaces); + auto numNormals = normals.getNumElements(); + qCInfo(model_scripting) << QString("numFaces: %1, numNormals: %2, numPoints: %3").arg(numFaces).arg(numNormals).arg(numPoints); + if (normals.getNumElements() != verts.getNumElements()) { + return false; + } + for (quint32 i = 0; i < numFaces; i++) { + quint32 I = TRIANGLE * i; + quint32 i0 = esize == 4 ? indices.get(I+0) : indices.get(I+0); + quint32 i1 = esize == 4 ? indices.get(I+1) : indices.get(I+1); + quint32 i2 = esize == 4 ? indices.get(I+2) : indices.get(I+2); + + Triangle face = { + verts.get(i1), + verts.get(i2), + verts.get(i0) + }; + faceNormals[i] = face.getNormal(); + if (glm::isnan(faceNormals[i].x)) { + qCInfo(model_scripting) << i << i0 << i1 << i2 << vec3toVariant(face.v0) << vec3toVariant(face.v1) << vec3toVariant(face.v2); + break; + } + vertexToFaces[glm::to_string(face.v0).c_str()] << i; + vertexToFaces[glm::to_string(face.v1).c_str()] << i; + vertexToFaces[glm::to_string(face.v2).c_str()] << i; + } + for (quint32 j = 0; j < numNormals; j++) { + //auto v = verts.get(j); + glm::vec3 normal { 0.0f, 0.0f, 0.0f }; + QString key { glm::to_string(verts.get(j)).c_str() }; + const auto& faces = vertexToFaces.value(key); + if (faces.size()) { + for (const auto i : faces) { + normal += faceNormals[i]; + } + normal *= 1.0f / (float)faces.size(); + } else { + static int logged = 0; + if (logged++ < 10) { + qCInfo(model_scripting) << "no faces for key!?" << key; + } + normal = verts.get(j); + } + if (glm::isnan(normal.x)) { + static int logged = 0; + if (logged++ < 10) { + qCInfo(model_scripting) << "isnan(normal.x)" << j << vec3toVariant(normal); + } + break; + } + normals.edit(j) = glm::normalize(normal); + } + return true; +} + +QScriptValue ModelScriptingInterface::mapMeshAttributeValues( + scriptable::ScriptableMeshPointer meshProxy, QScriptValue scopeOrCallback, QScriptValue methodOrName +) { + auto mesh = getMeshPointer(meshProxy); + if (!mesh) { + return false; + } + auto scopedHandler = makeScopedHandlerObject(scopeOrCallback, methodOrName); + + // input buffers + gpu::BufferView positions = mesh->getVertexBuffer(); + + const auto nPositions = positions.getNumElements(); + + // destructure so we can still invoke callback scoped, but with a custom signature (obj, i, jsMesh) + auto scope = scopedHandler.property("scope"); + auto callback = scopedHandler.property("callback"); + auto js = engine(); // cache value to avoid resolving each iteration + auto meshPart = js->toScriptValue(meshProxy); + + auto obj = js->newObject(); + auto attributeViews = ScriptableMesh::gatherBufferViews(mesh, { "normal", "color" }); + for (uint32_t i=0; i < nPositions; i++) { + for (const auto& a : attributeViews) { + bool asArray = a.second._element.getType() != gpu::FLOAT; + obj.setProperty(a.first, bufferViewElementToScriptValue(js, a.second, i, asArray, a.first.toStdString().c_str())); + } + auto result = callback.call(scope, { obj, i, meshPart }); + if (js->hasUncaughtException()) { + context()->throwValue(js->uncaughtException()); + return false; + } + + if (result.isBool() && !result.toBool()) { + // bail without modifying data if user explicitly returns false + continue; + } + if (result.isObject() && !result.strictlyEquals(obj)) { + // user returned a new object (ie: instead of modifying input properties) + obj = result; + } + + for (const auto& a : attributeViews) { + const auto& attribute = obj.property(a.first); + auto& view = a.second; + if (attribute.isValid()) { + bufferViewElementFromScriptValue(attribute, view, i); + } + } + } + return thisObject(); +} + +void ModelScriptingInterface::getMeshes(QUuid uuid, QScriptValue scopeOrCallback, QScriptValue methodOrName) { + auto handler = makeScopedHandlerObject(scopeOrCallback, methodOrName); + Q_ASSERT(handler.engine() == this->engine()); + QPointer engine = dynamic_cast(handler.engine()); + + scriptable::ScriptableModel meshes; + bool success = false; + QString error; + + auto appProvider = DependencyManager::get(); + qDebug() << "appProvider" << appProvider.data(); + scriptable::ModelProviderPointer provider = appProvider ? appProvider->lookupModelProvider(uuid) : nullptr; + QString providerType = provider ? provider->metadata.value("providerType").toString() : QString(); + if (providerType.isEmpty()) { + providerType = "unknown"; + } + if (provider) { + qCDebug(model_scripting) << "fetching meshes from " << providerType << "..."; + auto scriptableMeshes = provider->getScriptableModel(&success); + qCDebug(model_scripting) << "//fetched meshes from " << providerType << "success:" <makeError(error), QScriptValue::NullValue); + } else { + callScopedHandlerObject(handler, QScriptValue::NullValue, engine->toScriptValue(meshes)); + } +} + +namespace { + QScriptValue meshToScriptValue(QScriptEngine* engine, scriptable::ScriptableMeshPointer const &in) { + return engine->newQObject(in.get(), QScriptEngine::QtOwnership, + QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects + ); + } + + void meshFromScriptValue(const QScriptValue& value, scriptable::ScriptableMeshPointer &out) { + auto obj = value.toQObject(); + //qDebug() << "meshFromScriptValue" << obj; + if (auto tmp = qobject_cast(obj)) { + out = tmp->shared_from_this(); + } + // FIXME: Why does above cast not work on Win32!? + if (!out) { + auto smp = static_cast(obj); + //qDebug() << "meshFromScriptValue2" << smp; + out = smp->shared_from_this(); + } + } + + QScriptValue meshesToScriptValue(QScriptEngine* engine, const scriptable::ScriptableModelPointer &in) { + // QScriptValueList result; + QScriptValue result = engine->newArray(); + int i = 0; + foreach(scriptable::ScriptableMeshPointer const meshProxy, in->getMeshes()) { + result.setProperty(i++, meshToScriptValue(engine, meshProxy)); + } + return result; + } + + void meshesFromScriptValue(const QScriptValue& value, scriptable::ScriptableModelPointer &out) { + const auto length = value.property("length").toInt32(); + qCDebug(model_scripting) << "in meshesFromScriptValue, length =" << length; + for (int i = 0; i < length; i++) { + if (const auto meshProxy = qobject_cast(value.property(i).toQObject())) { + out->meshes.append(meshProxy->getMeshPointer()); + } else { + qCDebug(model_scripting) << "null meshProxy" << i; + } + } + } + + void modelProxyFromScriptValue(const QScriptValue& object, scriptable::ScriptableModel &meshes) { + auto meshesProperty = object.property("meshes"); + if (meshesProperty.property("length").toInt32() > 0) { + //meshes._meshes = qobject_cast(meshesProperty.toQObject()); + // qDebug() << "modelProxyFromScriptValue" << meshesProperty.property("length").toInt32() << meshesProperty.toVariant().typeName(); + qScriptValueToSequence(meshesProperty, meshes.meshes); + } else if (auto mesh = qobject_cast(object.toQObject())) { + meshes.meshes << mesh->getMeshPointer(); + } else { + qDebug() << "modelProxyFromScriptValue -- unrecognized input" << object.toVariant().toString(); + } + + meshes.metadata = object.property("metadata").toVariant().toMap(); + } + + QScriptValue modelProxyToScriptValue(QScriptEngine* engine, const scriptable::ScriptableModel &in) { + QScriptValue obj = engine->newObject(); + obj.setProperty("meshes", qScriptValueFromSequence(engine, in.meshes)); + obj.setProperty("metadata", engine->toScriptValue(in.metadata)); + return obj; + } + + QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const mesh::MeshFace &meshFace) { + QScriptValue obj = engine->newObject(); + obj.setProperty("vertices", qVectorIntToScriptValue(engine, meshFace.vertexIndices)); + return obj; + } + + void meshFaceFromScriptValue(const QScriptValue &object, mesh::MeshFace& meshFaceResult) { + qScriptValueToSequence(object.property("vertices"), meshFaceResult.vertexIndices); + } + + QScriptValue qVectorMeshFaceToScriptValue(QScriptEngine* engine, const QVector& vector) { + return qScriptValueFromSequence(engine, vector); + } + + void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector& result) { + qScriptValueToSequence(array, result); + } + + QScriptValue qVectorUInt32ToScriptValue(QScriptEngine* engine, const QVector& vector) { + return qScriptValueFromSequence(engine, vector); + } + + void qVectorUInt32FromScriptValue(const QScriptValue& array, QVector& result) { + qScriptValueToSequence(array, result); + } +} + +int meshUint32 = qRegisterMetaType(); +namespace mesh { + int meshUint32 = qRegisterMetaType(); +} +int qVectorMeshUint32 = qRegisterMetaType>(); + +void ModelScriptingInterface::registerMetaTypes(QScriptEngine* engine) { + qScriptRegisterSequenceMetaType>(engine); + qScriptRegisterSequenceMetaType(engine); + qScriptRegisterSequenceMetaType>(engine); + qScriptRegisterMetaType(engine, modelProxyToScriptValue, modelProxyFromScriptValue); + + qScriptRegisterMetaType(engine, qVectorUInt32ToScriptValue, qVectorUInt32FromScriptValue); + qScriptRegisterMetaType(engine, meshToScriptValue, meshFromScriptValue); + qScriptRegisterMetaType(engine, meshesToScriptValue, meshesFromScriptValue); + qScriptRegisterMetaType(engine, meshFaceToScriptValue, meshFaceFromScriptValue); + qScriptRegisterMetaType(engine, qVectorMeshFaceToScriptValue, qVectorMeshFaceFromScriptValue); +} + +MeshPointer ModelScriptingInterface::getMeshPointer(scriptable::ScriptableMeshPointer meshProxy) { + MeshPointer result; + if (!meshProxy) { + if (context()){ + context()->throwError("expected meshProxy as first parameter"); + } + return result; + } + auto mesh = meshProxy->getMeshPointer(); + if (!mesh) { + if (context()) { + context()->throwError("expected valid meshProxy as first parameter"); + } + return result; + } + return mesh; +} diff --git a/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h new file mode 100644 index 0000000000..d10fd28170 --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h @@ -0,0 +1,68 @@ +// +// ModelScriptingInterface.h +// libraries/script-engine/src +// +// Created by Seth Alves on 2017-1-27. +// Copyright 2017 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 +// + +#ifndef hifi_ModelScriptingInterface_h +#define hifi_ModelScriptingInterface_h + +#include +#include + +#include + +#include +#include + +#include "ScriptableMesh.h" +#include +class ModelScriptingInterface : public QObject, public QScriptable, public Dependency { + Q_OBJECT + +public: + ModelScriptingInterface(QObject* parent = nullptr); + static void registerMetaTypes(QScriptEngine* engine); + +public slots: + /**jsdoc + * Returns the meshes associated with a UUID (entityID, overlayID, or avatarID) + * + * @function ModelScriptingInterface.getMeshes + * @param {EntityID} entityID The ID of the entity whose meshes are to be retrieve + */ + void getMeshes(QUuid uuid, QScriptValue scopeOrCallback, QScriptValue methodOrName = QScriptValue()); + + bool dedupeVertices(scriptable::ScriptableMeshPointer meshProxy, float epsilon = 1e-6); + bool recalculateNormals(scriptable::ScriptableMeshPointer meshProxy); + QScriptValue cloneMesh(scriptable::ScriptableMeshPointer meshProxy, bool recalcNormals = true); + QScriptValue unrollVertices(scriptable::ScriptableMeshPointer meshProxy, bool recalcNormals = true); + QScriptValue mapAttributeValues(QScriptValue in, + QScriptValue scopeOrCallback, + QScriptValue methodOrName = QScriptValue()); + QScriptValue mapMeshAttributeValues(scriptable::ScriptableMeshPointer meshProxy, + QScriptValue scopeOrCallback, + QScriptValue methodOrName = QScriptValue()); + + QString meshToOBJ(const scriptable::ScriptableModel& in); + + bool replaceMeshData(scriptable::ScriptableMeshPointer dest, scriptable::ScriptableMeshPointer source, const QVector& attributeNames = QVector()); + QScriptValue appendMeshes(scriptable::ScriptableModel in); + QScriptValue transformMesh(scriptable::ScriptableMeshPointer meshProxy, glm::mat4 transform); + QScriptValue newMesh(const QVector& vertices, + const QVector& normals, + const QVector& faces); + QScriptValue getVertexCount(scriptable::ScriptableMeshPointer meshProxy); + QScriptValue getVertex(scriptable::ScriptableMeshPointer meshProxy, mesh::uint32 vertexIndex); + +private: + scriptable::MeshPointer getMeshPointer(scriptable::ScriptableMeshPointer meshProxy); + +}; + +#endif // hifi_ModelScriptingInterface_h diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp new file mode 100644 index 0000000000..47d91e9e59 --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp @@ -0,0 +1,359 @@ +// +// SimpleMeshProxy.cpp +// libraries/model-networking/src/model-networking/ +// +// Created by Seth Alves on 2017-3-22. +// Copyright 2017 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 "ScriptableMesh.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "ScriptableMesh.moc" + +#include + +QLoggingCategory mesh_logging { "hifi.scripting.mesh" }; + +// FIXME: unroll/resolve before PR +using namespace scriptable; +QMap ScriptableMesh::ATTRIBUTES{ + {"position", gpu::Stream::POSITION }, + {"normal", gpu::Stream::NORMAL }, + {"color", gpu::Stream::COLOR }, + {"tangent", gpu::Stream::TEXCOORD0 }, + {"skin_cluster_index", gpu::Stream::SKIN_CLUSTER_INDEX }, + {"skin_cluster_weight", gpu::Stream::SKIN_CLUSTER_WEIGHT }, + {"texcoord0", gpu::Stream::TEXCOORD0 }, + {"texcoord1", gpu::Stream::TEXCOORD1 }, + {"texcoord2", gpu::Stream::TEXCOORD2 }, + {"texcoord3", gpu::Stream::TEXCOORD3 }, + {"texcoord4", gpu::Stream::TEXCOORD4 }, +}; + +QVector scriptable::ScriptableModel::getMeshes() const { + QVector out; + for(auto& mesh : meshes) { + out << scriptable::ScriptableMeshPointer(new ScriptableMesh(std::const_pointer_cast(this->shared_from_this()), mesh)); + } + return out; +} + +quint32 ScriptableMesh::getNumVertices() const { + if (auto mesh = getMeshPointer()) { + return (quint32)mesh->getNumVertices(); + } + return 0; +} + +// glm::vec3 ScriptableMesh::getPos3(quint32 index) const { +// if (auto mesh = getMeshPointer()) { +// if (index < getNumVertices()) { +// return mesh->getPos3(index); +// } +// } +// return glm::vec3(NAN); +// } + +namespace { + gpu::BufferView getBufferView(scriptable::MeshPointer mesh, gpu::Stream::Slot slot) { + return slot == gpu::Stream::POSITION ? mesh->getVertexBuffer() : mesh->getAttributeBuffer(slot); + } +} + +QVector ScriptableMesh::findNearbyIndices(const glm::vec3& origin, float epsilon) const { + QVector result; + if (auto mesh = getMeshPointer()) { + const auto& pos = 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; + } + } + } + return result; +} + +QVector ScriptableMesh::getIndices() const { + QVector result; + if (auto mesh = getMeshPointer()) { + qCDebug(mesh_logging, "getTriangleIndices mesh %p", mesh.get()); + gpu::BufferView indexBufferView = mesh->getIndexBuffer(); + if (quint32 count = (quint32)indexBufferView.getNumElements()) { + result.resize(count); + auto buffer = indexBufferView._buffer; + if (indexBufferView._element.getSize() == 4) { + // memcpy(result.data(), buffer->getData(), result.size()*sizeof(quint32)); + for (quint32 i = 0; i < count; i++) { + result[i] = indexBufferView.get(i); + } + } else { + for (quint32 i = 0; i < count; i++) { + result[i] = indexBufferView.get(i); + } + } + } + } + return result; +} + +quint32 ScriptableMesh::getNumAttributes() const { + if (auto mesh = getMeshPointer()) { + return (quint32)mesh->getNumAttributes(); + } + return 0; +} +QVector ScriptableMesh::getAttributeNames() const { + QVector result; + if (auto mesh = getMeshPointer()) { + for (const auto& a : ATTRIBUTES.toStdMap()) { + auto bufferView = getBufferView(mesh, a.second); + if (bufferView.getNumElements() > 0) { + result << a.first; + } + } + } + return result; +} + +// override +QVariantMap ScriptableMesh::getVertexAttributes(quint32 vertexIndex) const { + return getVertexAttributes(vertexIndex, getAttributeNames()); +} + +bool ScriptableMesh::setVertexAttributes(quint32 vertexIndex, QVariantMap attributes) { + qDebug() << "setVertexAttributes" << vertexIndex << attributes; + for (auto& a : gatherBufferViews(getMeshPointer())) { + const auto& name = a.first; + const auto& value = attributes.value(name); + if (value.isValid()) { + auto& view = a.second; + bufferViewElementFromVariant(view, vertexIndex, value); + } else { + qCDebug(mesh_logging) << "setVertexAttributes" << vertexIndex << name; + } + } + return true; +} + +int ScriptableMesh::_getSlotNumber(const QString& attributeName) const { + if (auto mesh = getMeshPointer()) { + return ATTRIBUTES.value(attributeName, -1); + } + return -1; +} + + +QVariantMap ScriptableMesh::getMeshExtents() const { + auto mesh = getMeshPointer(); + auto box = mesh ? mesh->evalPartsBound(0, (int)mesh->getNumParts()) : AABox(); + return { + { "brn", glmVecToVariant(box.getCorner()) }, + { "tfl", glmVecToVariant(box.calcTopFarLeft()) }, + { "center", glmVecToVariant(box.calcCenter()) }, + { "min", glmVecToVariant(box.getMinimumPoint()) }, + { "max", glmVecToVariant(box.getMaximumPoint()) }, + { "dimensions", glmVecToVariant(box.getDimensions()) }, + }; +} + +quint32 ScriptableMesh::getNumParts() const { + if (auto mesh = getMeshPointer()) { + return (quint32)mesh->getNumParts(); + } + return 0; +} + +QVariantMap ScriptableMesh::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 ScriptableMesh::translate(const glm::vec3& translation) { + return transform(glm::translate(translation)); +} +QVariantMap ScriptableMesh::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 ScriptableMesh::rotateDegrees(const glm::vec3& eulerAngles, const glm::vec3& origin) { + return rotate(glm::quat(glm::radians(eulerAngles)), origin); +} +QVariantMap ScriptableMesh::rotate(const glm::quat& rotation, const glm::vec3& origin) { + 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 ScriptableMesh::transform(const glm::mat4& transform) { + if (auto mesh = getMeshPointer()) { + const auto& pos = 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 getMeshExtents(); +} + +QVariantList ScriptableMesh::getAttributeValues(const QString& attributeName) const { + QVariantList result; + auto slotNum = _getSlotNumber(attributeName); + if (slotNum >= 0) { + auto slot = (gpu::Stream::Slot)slotNum; + const auto& bufferView = getBufferView(getMeshPointer(), slot); + if (auto len = bufferView.getNumElements()) { + bool asArray = bufferView._element.getType() != gpu::FLOAT; + for (quint32 i = 0; i < len; i++) { + result << bufferViewElementToVariant(bufferView, i, asArray, attributeName.toStdString().c_str()); + } + } + } + return result; +} +QVariantMap ScriptableMesh::getVertexAttributes(quint32 vertexIndex, QVector names) const { + QVariantMap result; + auto mesh = getMeshPointer(); + if (!mesh || vertexIndex >= getNumVertices()) { + return result; + } + for (const auto& a : ATTRIBUTES.toStdMap()) { + auto name = a.first; + if (!names.contains(name)) { + continue; + } + auto slot = a.second; + const gpu::BufferView& bufferView = getBufferView(mesh, slot); + if (vertexIndex < bufferView.getNumElements()) { + bool asArray = bufferView._element.getType() != gpu::FLOAT; + result[name] = bufferViewElementToVariant(bufferView, vertexIndex, asArray, name.toStdString().c_str()); + } + } + return result; +} + +/// --- buffer view <-> variant helpers + +namespace { + // expand the corresponding attribute buffer (creating it if needed) so that it matches POSITIONS size and specified element type + gpu::BufferView _expandedAttributeBuffer(const scriptable::MeshPointer mesh, gpu::Stream::Slot slot, const gpu::Element& elementType) { + gpu::Size elementSize = elementType.getSize(); + gpu::BufferView bufferView = getBufferView(mesh, slot); + auto nPositions = mesh->getNumVertices(); + auto vsize = nPositions * elementSize; + auto diffTypes = (elementType.getType() != bufferView._element.getType() || + elementType.getSize() > bufferView._element.getSize() || + elementType.getScalarCount() > bufferView._element.getScalarCount() || + vsize > bufferView._size + ); + auto hint = DebugNames::stringFrom(slot); + +#ifdef DEV_BUILD + auto beforeCount = bufferView.getNumElements(); + auto beforeTotal = bufferView._size; +#endif + if (bufferView.getNumElements() < nPositions || diffTypes) { + if (!bufferView._buffer || bufferView.getNumElements() == 0) { + qCInfo(mesh_logging).nospace() << "ScriptableMesh -- adding missing mesh attribute '" << hint << "' for BufferView"; + gpu::Byte *data = new gpu::Byte[vsize]; + memset(data, 0, vsize); + auto buffer = new gpu::Buffer(vsize, (gpu::Byte*)data); + delete[] data; + bufferView = gpu::BufferView(buffer, elementType); + mesh->addAttribute(slot, bufferView); + } else { + qCInfo(mesh_logging) << "ScriptableMesh -- resizing Buffer current:" << hint << bufferView._buffer->getSize() << "wanted:" << vsize; + bufferView._element = elementType; + bufferView._buffer->resize(vsize); + bufferView._size = bufferView._buffer->getSize(); + } + } +#ifdef DEV_BUILD + auto afterCount = bufferView.getNumElements(); + auto afterTotal = bufferView._size; + if (beforeTotal != afterTotal || beforeCount != afterCount) { + auto typeName = DebugNames::stringFrom(bufferView._element.getType()); + qCDebug(mesh_logging, "NOTE:: _expandedAttributeBuffer.%s vec%d %s (before count=%lu bytes=%lu // after count=%lu bytes=%lu)", + hint.toStdString().c_str(), bufferView._element.getScalarCount(), + typeName.toStdString().c_str(), beforeCount, beforeTotal, afterCount, afterTotal); + } +#endif + return bufferView; + } + const gpu::Element UNUSED{ gpu::SCALAR, gpu::UINT8, gpu::RAW }; + + gpu::Element getVecNElement(gpu::Type T, int N) { + switch(N) { + case 2: return { gpu::VEC2, T, gpu::XY }; + case 3: return { gpu::VEC3, T, gpu::XYZ }; + case 4: return { gpu::VEC4, T, gpu::XYZW }; + } + Q_ASSERT(false); + return UNUSED; + } + + gpu::BufferView expandAttributeToMatchPositions(scriptable::MeshPointer mesh, gpu::Stream::Slot slot) { + if (slot == gpu::Stream::POSITION) { + return getBufferView(mesh, slot); + } + return _expandedAttributeBuffer(mesh, slot, getVecNElement(gpu::FLOAT, 3)); + } +} + +std::map ScriptableMesh::gatherBufferViews(scriptable::MeshPointer mesh, const QStringList& expandToMatchPositions) { + std::map attributeViews; + if (!mesh) { + return attributeViews; + } + for (const auto& a : ScriptableMesh::ATTRIBUTES.toStdMap()) { + auto name = a.first; + auto slot = a.second; + if (expandToMatchPositions.contains(name)) { + expandAttributeToMatchPositions(mesh, slot); + } + auto view = getBufferView(mesh, slot); + auto beforeCount = view.getNumElements(); + if (beforeCount > 0) { + auto element = view._element; + auto vecN = element.getScalarCount(); + auto type = element.getType(); + QString typeName = DebugNames::stringFrom(element.getType()); + auto beforeTotal = view._size; + + attributeViews[name] = _expandedAttributeBuffer(mesh, slot, getVecNElement(type, vecN)); + +#if DEV_BUILD + auto afterTotal = attributeViews[name]._size; + auto afterCount = attributeViews[name].getNumElements(); + if (beforeTotal != afterTotal || beforeCount != afterCount) { + qCDebug(mesh_logging, "NOTE:: gatherBufferViews.%s vec%d %s (before count=%lu bytes=%lu // after count=%lu bytes=%lu)", + name.toStdString().c_str(), vecN, typeName.toStdString().c_str(), beforeCount, beforeTotal, afterCount, afterTotal); + } +#endif + } + } + return attributeViews; +} diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h new file mode 100644 index 0000000000..da11002906 --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h @@ -0,0 +1,127 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +namespace graphics { + class Mesh; +} +namespace gpu { + class BufferView; +} +namespace scriptable { + class ScriptableMesh : public QObject, public std::enable_shared_from_this { + Q_OBJECT + public: + ScriptableModelPointer _model; + scriptable::MeshPointer _mesh; + QVariantMap _metadata; + ScriptableMesh() : QObject() {} + ScriptableMesh(ScriptableModelPointer parent, scriptable::MeshPointer mesh) : QObject(), _model(parent), _mesh(mesh) {} + ScriptableMesh(const ScriptableMesh& other) : QObject(), _model(other._model), _mesh(other._mesh), _metadata(other._metadata) {} + ~ScriptableMesh() { qDebug() << "~ScriptableMesh" << this; } + Q_PROPERTY(quint32 numParts READ getNumParts) + Q_PROPERTY(quint32 numAttributes READ getNumAttributes) + Q_PROPERTY(quint32 numVertices READ getNumVertices) + Q_PROPERTY(quint32 numIndices READ getNumIndices) + Q_PROPERTY(QVector attributeNames READ getAttributeNames) + + virtual scriptable::MeshPointer getMeshPointer() const { return _mesh; } + Q_INVOKABLE virtual quint32 getNumParts() const; + Q_INVOKABLE virtual quint32 getNumVertices() const; + Q_INVOKABLE virtual quint32 getNumAttributes() const; + Q_INVOKABLE virtual quint32 getNumIndices() const { return 0; } + Q_INVOKABLE virtual QVector getAttributeNames() const; + Q_INVOKABLE virtual QVariantMap getVertexAttributes(quint32 vertexIndex) const; + Q_INVOKABLE virtual QVariantMap getVertexAttributes(quint32 vertexIndex, QVector attributes) const; + + Q_INVOKABLE virtual QVector getIndices() const; + Q_INVOKABLE virtual QVector findNearbyIndices(const glm::vec3& origin, float epsilon = 1e-6) const; + Q_INVOKABLE virtual QVariantMap getMeshExtents() const; + Q_INVOKABLE virtual bool setVertexAttributes(quint32 vertexIndex, QVariantMap attributes); + Q_INVOKABLE virtual QVariantMap scaleToFit(float unitScale); + + static QMap ATTRIBUTES; + static std::map gatherBufferViews(MeshPointer mesh, const QStringList& expandToMatchPositions = QStringList()); + + Q_INVOKABLE QVariantList getAttributeValues(const QString& attributeName) const; + + Q_INVOKABLE int _getSlotNumber(const QString& attributeName) const; + + QVariantMap translate(const glm::vec3& translation); + QVariantMap scale(const glm::vec3& scale, const glm::vec3& origin = glm::vec3(NAN)); + QVariantMap rotateDegrees(const glm::vec3& eulerAngles, const glm::vec3& origin = glm::vec3(NAN)); + QVariantMap rotate(const glm::quat& rotation, const glm::vec3& origin = glm::vec3(NAN)); + Q_INVOKABLE QVariantMap transform(const glm::mat4& transform); + }; + + // TODO: for now this is a part-specific wrapper around ScriptableMesh + class ScriptableMeshPart : public ScriptableMesh { + Q_OBJECT + public: + ScriptableMeshPart& operator=(const ScriptableMeshPart& view) { _model=view._model; _mesh=view._mesh; return *this; }; + ScriptableMeshPart(const ScriptableMeshPart& other) : ScriptableMesh(other._model, other._mesh) {} + ScriptableMeshPart() : ScriptableMesh(nullptr, nullptr) {} + ~ScriptableMeshPart() { qDebug() << "~ScriptableMeshPart" << this; } + ScriptableMeshPart(ScriptableMeshPointer mesh) : ScriptableMesh(mesh->_model, mesh->_mesh) {} + Q_PROPERTY(QString topology READ getTopology) + Q_PROPERTY(quint32 numFaces READ getNumFaces) + + scriptable::MeshPointer parentMesh; + int partIndex; + QString getTopology() const { return "triangles"; } + Q_INVOKABLE virtual quint32 getNumFaces() const { return getIndices().size() / 3; } + Q_INVOKABLE virtual QVector getFace(quint32 faceIndex) const { + auto inds = getIndices(); + return faceIndex+2 < (quint32)inds.size() ? inds.mid(faceIndex*3, 3) : QVector(); + } + }; + + class GraphicsScriptingInterface : public QObject { + Q_OBJECT + public: + GraphicsScriptingInterface(QObject* parent = nullptr) : QObject(parent) {} + GraphicsScriptingInterface(const GraphicsScriptingInterface& other) {} + public slots: + ScriptableMeshPart exportMeshPart(ScriptableMesh mesh, int part) { return {}; } + + }; +} + +Q_DECLARE_METATYPE(scriptable::ScriptableMesh) +Q_DECLARE_METATYPE(scriptable::ScriptableMeshPointer) +Q_DECLARE_METATYPE(QVector) +Q_DECLARE_METATYPE(scriptable::ScriptableMeshPart) +Q_DECLARE_METATYPE(scriptable::GraphicsScriptingInterface) + +// FIXME: faces were supported in the original Model.* API -- are they still needed/used/useful for anything yet? +#include + +namespace mesh { + using uint32 = quint32; + class MeshFace; + using MeshFaces = QVector; + class MeshFace { + public: + MeshFace() {} + MeshFace(QVector vertexIndices) : vertexIndices(vertexIndices) {} + ~MeshFace() {} + + QVector vertexIndices; + // TODO -- material... + }; +}; + +Q_DECLARE_METATYPE(mesh::MeshFace) +Q_DECLARE_METATYPE(QVector) +Q_DECLARE_METATYPE(mesh::uint32) +Q_DECLARE_METATYPE(QVector) diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h new file mode 100644 index 0000000000..e8cf6f1656 --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace graphics { + class Mesh; +} +namespace gpu { + class BufferView; +} +namespace scriptable { + using Mesh = graphics::Mesh; + using MeshPointer = std::shared_ptr; + + class ScriptableModel; + class ScriptableMesh; + class ScriptableMeshPart; + using ScriptableModelPointer = std::shared_ptr; + using ScriptableMeshPointer = std::shared_ptr; + using ScriptableMeshPartPointer = std::shared_ptr; + class ScriptableModel : public QObject, public std::enable_shared_from_this { + Q_OBJECT + public: + Q_PROPERTY(QVector meshes READ getMeshes) + + Q_INVOKABLE QString toString() { return "[ScriptableModel " + objectName()+"]"; } + ScriptableModel(QObject* parent = nullptr) : QObject(parent) {} + ScriptableModel(const ScriptableModel& other) : objectID(other.objectID), metadata(other.metadata), meshes(other.meshes) {} + ScriptableModel& operator=(const ScriptableModel& view) { + objectID = view.objectID; + metadata = view.metadata; + meshes = view.meshes; + return *this; + } + ~ScriptableModel() { qDebug() << "~ScriptableModel" << this; } + void mixin(const ScriptableModel& other) { + for (const auto& key : other.metadata.keys()) { + metadata[key] = other.metadata[key]; + } + for(const auto&mesh : other.meshes) { + meshes << mesh; + } + } + QUuid objectID; + QVariantMap metadata; + QVector meshes; + // TODO: in future accessors for these could go here + QVariantMap shapes; + QVariantMap materials; + QVariantMap armature; + + QVector getMeshes() const; + }; + + class ModelProvider { + public: + QVariantMap metadata; + static scriptable::ScriptableModel modelUnavailableError(bool* ok) { if (ok) { *ok = false; } return {}; } + virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) = 0; + }; + using ModelProviderPointer = std::shared_ptr; + class ModelProviderFactory : public Dependency { + public: + virtual scriptable::ModelProviderPointer lookupModelProvider(QUuid uuid) = 0; + }; + +} + +Q_DECLARE_METATYPE(scriptable::MeshPointer) +Q_DECLARE_METATYPE(scriptable::ScriptableModel) +Q_DECLARE_METATYPE(scriptable::ScriptableModelPointer) + diff --git a/libraries/graphics/src/graphics/Geometry.cpp b/libraries/graphics/src/graphics/Geometry.cpp index ba5afcbc62..d43c773249 100755 --- a/libraries/graphics/src/graphics/Geometry.cpp +++ b/libraries/graphics/src/graphics/Geometry.cpp @@ -42,6 +42,11 @@ void Mesh::addAttribute(Slot slot, const BufferView& buffer) { evalVertexFormat(); } +void Mesh::removeAttribute(Slot slot) { + _attributeBuffers.erase(slot); + evalVertexFormat(); +} + const BufferView Mesh::getAttributeBuffer(int attrib) const { auto attribBuffer = _attributeBuffers.find(attrib); if (attribBuffer != _attributeBuffers.end()) { @@ -224,6 +229,7 @@ graphics::MeshPointer Mesh::map(std::function vertexFunc, } graphics::MeshPointer result(new graphics::Mesh()); + result->displayName = displayName; gpu::Element vertexElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); gpu::Buffer* resultVertexBuffer = new gpu::Buffer(vertexSize, resultVertexData.get()); diff --git a/libraries/graphics/src/graphics/Geometry.h b/libraries/graphics/src/graphics/Geometry.h index 642aa9e38d..23ebec2965 100755 --- a/libraries/graphics/src/graphics/Geometry.h +++ b/libraries/graphics/src/graphics/Geometry.h @@ -56,6 +56,7 @@ public: // Attribute Buffers size_t getNumAttributes() const { return _attributeBuffers.size(); } void addAttribute(Slot slot, const BufferView& buffer); + void removeAttribute(Slot slot); const BufferView getAttributeBuffer(int attrib) const; // Stream format diff --git a/libraries/model-networking/src/model-networking/SimpleMeshProxy.cpp b/libraries/model-networking/src/model-networking/SimpleMeshProxy.cpp deleted file mode 100644 index 741478789e..0000000000 --- a/libraries/model-networking/src/model-networking/SimpleMeshProxy.cpp +++ /dev/null @@ -1,27 +0,0 @@ -// -// SimpleMeshProxy.cpp -// libraries/model-networking/src/model-networking/ -// -// Created by Seth Alves on 2017-3-22. -// Copyright 2017 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 "SimpleMeshProxy.h" - -#include - -MeshPointer SimpleMeshProxy::getMeshPointer() const { - return _mesh; -} - -int SimpleMeshProxy::getNumVertices() const { - return (int)_mesh->getNumVertices(); -} - -glm::vec3 SimpleMeshProxy::getPos3(int index) const { - return _mesh->getPos3(index); -} - diff --git a/libraries/model-networking/src/model-networking/SimpleMeshProxy.h b/libraries/model-networking/src/model-networking/SimpleMeshProxy.h deleted file mode 100644 index 24c3fca27e..0000000000 --- a/libraries/model-networking/src/model-networking/SimpleMeshProxy.h +++ /dev/null @@ -1,36 +0,0 @@ -// -// SimpleMeshProxy.h -// libraries/model-networking/src/model-networking/ -// -// Created by Seth Alves on 2017-1-27. -// Copyright 2017 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 -// - -#ifndef hifi_SimpleMeshProxy_h -#define hifi_SimpleMeshProxy_h - -#include -#include -#include - -#include - -class SimpleMeshProxy : public MeshProxy { -public: - SimpleMeshProxy(const MeshPointer& mesh) : _mesh(mesh) { } - - MeshPointer getMeshPointer() const override; - - int getNumVertices() const override; - - glm::vec3 getPos3(int index) const override; - - -protected: - const MeshPointer _mesh; -}; - -#endif // hifi_SimpleMeshProxy_h diff --git a/libraries/render-utils/CMakeLists.txt b/libraries/render-utils/CMakeLists.txt index 6be3057c93..55762e38fd 100644 --- a/libraries/render-utils/CMakeLists.txt +++ b/libraries/render-utils/CMakeLists.txt @@ -7,6 +7,7 @@ link_hifi_libraries(shared ktx gpu graphics model-networking render animation fb include_hifi_library_headers(networking) include_hifi_library_headers(octree) include_hifi_library_headers(audio) +include_hifi_library_headers(graphics-scripting) # for ScriptableModel.h if (NOT ANDROID) target_nsight() diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 23473e74f2..6aa42cf6df 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -2407,3 +2407,48 @@ void GeometryCache::renderWireCubeInstance(RenderArgs* args, gpu::Batch& batch, assert(pipeline != nullptr); renderInstances(args, batch, color, true, pipeline, GeometryCache::Cube); } + +graphics::MeshPointer GeometryCache::meshFromShape(Shape geometryShape, glm::vec3 color) { + auto shapeData = getShapeData(geometryShape); + + qDebug() << "GeometryCache::getMeshProxyListFromShape" << shapeData << stringFromShape(geometryShape); + + auto cloneBufferView = [](const gpu::BufferView& in) -> gpu::BufferView { + auto buffer = std::make_shared(*in._buffer); // copy + // FIXME: gpu::BufferView seems to have a bug where constructing a new instance from an existing one + // results in over-multiplied buffer/view sizes -- hence constructing manually here from each input prop + auto out = gpu::BufferView(buffer, in._offset, in._size, in._stride, in._element); + Q_ASSERT(out.getNumElements() == in.getNumElements()); + Q_ASSERT(out._size == in._size); + Q_ASSERT(out._buffer->getSize() == in._buffer->getSize()); + return out; + }; + + auto positionsBufferView = cloneBufferView(shapeData->_positionView); + auto normalsBufferView = cloneBufferView(shapeData->_normalView); + auto indexBufferView = cloneBufferView(shapeData->_indicesView); + + gpu::BufferView::Size numVertices = positionsBufferView.getNumElements(); + Q_ASSERT(numVertices == normalsBufferView.getNumElements()); + + // apply input color across all vertices + auto colorsBufferView = cloneBufferView(shapeData->_normalView); + for (gpu::BufferView::Size i = 0; i < numVertices; i++) { + colorsBufferView.edit((gpu::BufferView::Index)i) = color; + } + + graphics::MeshPointer mesh(new graphics::Mesh()); + mesh->setVertexBuffer(positionsBufferView); + mesh->setIndexBuffer(indexBufferView); + mesh->addAttribute(gpu::Stream::NORMAL, normalsBufferView); + mesh->addAttribute(gpu::Stream::COLOR, colorsBufferView); + + const auto startIndex = 0, baseVertex = 0; + graphics::Mesh::Part part(startIndex, (graphics::Index)indexBufferView.getNumElements(), baseVertex, graphics::Mesh::TRIANGLES); + auto partBuffer = new gpu::Buffer(sizeof(graphics::Mesh::Part), (gpu::Byte*)&part); + mesh->setPartBuffer(gpu::BufferView(partBuffer, gpu::Element::PART_DRAWCALL)); + + mesh->displayName = QString("GeometryCache/shape::%1").arg(GeometryCache::stringFromShape(geometryShape)); + + return mesh; +} diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index 63af30bb79..998043b80e 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -375,6 +375,7 @@ public: /// otherwise nullptr in the event of an error. const ShapeData * getShapeData(Shape shape) const; + graphics::MeshPointer meshFromShape(Shape geometryShape, glm::vec3 color); private: GeometryCache(); diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index b0763c0fb3..d595136c56 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -26,7 +26,7 @@ #include #include -#include +#include #include #include @@ -573,15 +573,21 @@ bool Model::convexHullContains(glm::vec3 point) { return false; } -MeshProxyList Model::getMeshes() const { - MeshProxyList result; +scriptable::ScriptableModel Model::getScriptableModel(bool* ok) { + scriptable::ScriptableModel result; const Geometry::Pointer& renderGeometry = getGeometry(); - const Geometry::GeometryMeshes& meshes = renderGeometry->getMeshes(); if (!isLoaded()) { + qDebug() << "Model::getScriptableModel -- !isLoaded"; + if (ok) { + *ok = false; + } return result; } +// TODO: remove -- this was an earlier approach using renderGeometry instead of FBXGeometry +#if 0 // renderGeometry approach + const Geometry::GeometryMeshes& meshes = renderGeometry->getMeshes(); Transform offset; offset.setScale(_scale); offset.postTranslate(_offset); @@ -591,20 +597,67 @@ MeshProxyList Model::getMeshes() const { if (!mesh) { continue; } - - MeshProxy* meshProxy = new SimpleMeshProxy( - mesh->map( - [=](glm::vec3 position) { - return glm::vec3(offsetMat * glm::vec4(position, 1.0f)); - }, - [=](glm::vec3 color) { return color; }, - [=](glm::vec3 normal) { - return glm::normalize(glm::vec3(offsetMat * glm::vec4(normal, 0.0f))); - }, - [&](uint32_t index) { return index; })); - result << meshProxy; + qDebug() << "Model::getScriptableModel #" << i++ << mesh->displayName; + auto newmesh = mesh->map( + [=](glm::vec3 position) { + return glm::vec3(offsetMat * glm::vec4(position, 1.0f)); + }, + [=](glm::vec3 color) { return color; }, + [=](glm::vec3 normal) { + return glm::normalize(glm::vec3(offsetMat * glm::vec4(normal, 0.0f))); + }, + [&](uint32_t index) { return index; }); + newmesh->displayName = mesh->displayName; + result << newmesh; } - +#endif + const FBXGeometry& geometry = getFBXGeometry(); + auto mat4toVariant = [](const glm::mat4& mat4) -> QVariant { + QVector floats; + floats.resize(16); + memcpy(floats.data(), &mat4, sizeof(glm::mat4)); + QVariant v; + v.setValue>(floats); + return v; + }; + result.metadata = { + { "url", _url.toString() }, + { "textures", renderGeometry->getTextures() }, + { "offset", vec3toVariant(_offset) }, + { "scale", vec3toVariant(_scale) }, + { "rotation", quatToVariant(_rotation) }, + { "translation", vec3toVariant(_translation) }, + { "meshToModel", mat4toVariant(glm::scale(_scale) * glm::translate(_offset)) }, + { "meshToWorld", mat4toVariant(createMatFromQuatAndPos(_rotation, _translation) * (glm::scale(_scale) * glm::translate(_offset))) }, + { "geometryOffset", mat4toVariant(geometry.offset) }, + }; + QVariantList submeshes; + int numberOfMeshes = geometry.meshes.size(); + for (int i = 0; i < numberOfMeshes; i++) { + const FBXMesh& fbxMesh = geometry.meshes.at(i); + auto mesh = fbxMesh._mesh; + if (!mesh) { + continue; + } + result.meshes << std::const_pointer_cast(mesh); + auto extraInfo = geometry.getModelNameOfMesh(i); + qDebug() << "Model::getScriptableModel #" << i << QString(mesh->displayName) << extraInfo; + submeshes << QVariantMap{ + { "index", i }, + { "meshIndex", fbxMesh.meshIndex }, + { "modelName", extraInfo }, + { "transform", mat4toVariant(fbxMesh.modelTransform) }, + { "extents", QVariantMap({ + { "minimum", vec3toVariant(fbxMesh.meshExtents.minimum) }, + { "maximum", vec3toVariant(fbxMesh.meshExtents.maximum) }, + })}, + }; + } + if (ok) { + *ok = true; + } + qDebug() << "//Model::getScriptableModel -- #" << result.meshes.size(); + result.metadata["submeshes"] = submeshes; return result; } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 027d52ecfd..4fd00c9f9a 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -64,7 +65,7 @@ using ModelWeakPointer = std::weak_ptr; /// A generic 3D model displaying geometry loaded from a URL. -class Model : public QObject, public std::enable_shared_from_this { +class Model : public QObject, public std::enable_shared_from_this, public scriptable::ModelProvider { Q_OBJECT public: @@ -313,7 +314,7 @@ public: int getResourceDownloadAttempts() { return _renderWatcher.getResourceDownloadAttempts(); } int getResourceDownloadAttemptsRemaining() { return _renderWatcher.getResourceDownloadAttemptsRemaining(); } - Q_INVOKABLE MeshProxyList getMeshes() const; + Q_INVOKABLE virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override; void scaleToFit(); diff --git a/libraries/script-engine/src/ModelScriptingInterface.cpp b/libraries/script-engine/src/ModelScriptingInterface.cpp deleted file mode 100644 index c693083ebf..0000000000 --- a/libraries/script-engine/src/ModelScriptingInterface.cpp +++ /dev/null @@ -1,251 +0,0 @@ -// -// ModelScriptingInterface.cpp -// libraries/script-engine/src -// -// Created by Seth Alves on 2017-1-27. -// Copyright 2017 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 "ModelScriptingInterface.h" -#include -#include -#include -#include -#include "ScriptEngine.h" -#include "ScriptEngineLogging.h" -#include "OBJWriter.h" - -ModelScriptingInterface::ModelScriptingInterface(QObject* parent) : QObject(parent) { - _modelScriptEngine = qobject_cast(parent); - - qScriptRegisterSequenceMetaType>(_modelScriptEngine); - qScriptRegisterMetaType(_modelScriptEngine, meshFaceToScriptValue, meshFaceFromScriptValue); - qScriptRegisterMetaType(_modelScriptEngine, qVectorMeshFaceToScriptValue, qVectorMeshFaceFromScriptValue); -} - -QString ModelScriptingInterface::meshToOBJ(MeshProxyList in) { - QList meshes; - foreach (const MeshProxy* meshProxy, in) { - meshes.append(meshProxy->getMeshPointer()); - } - - return writeOBJToString(meshes); -} - -QScriptValue ModelScriptingInterface::appendMeshes(MeshProxyList in) { - // figure out the size of the resulting mesh - size_t totalVertexCount { 0 }; - size_t totalColorCount { 0 }; - size_t totalNormalCount { 0 }; - size_t totalIndexCount { 0 }; - foreach (const MeshProxy* meshProxy, in) { - MeshPointer mesh = meshProxy->getMeshPointer(); - totalVertexCount += mesh->getNumVertices(); - - int attributeTypeColor = gpu::Stream::InputSlot::COLOR; // libraries/gpu/src/gpu/Stream.h - const gpu::BufferView& colorsBufferView = mesh->getAttributeBuffer(attributeTypeColor); - gpu::BufferView::Index numColors = (gpu::BufferView::Index)colorsBufferView.getNumElements(); - totalColorCount += numColors; - - int attributeTypeNormal = gpu::Stream::InputSlot::NORMAL; // libraries/gpu/src/gpu/Stream.h - const gpu::BufferView& normalsBufferView = mesh->getAttributeBuffer(attributeTypeNormal); - gpu::BufferView::Index numNormals = (gpu::BufferView::Index)normalsBufferView.getNumElements(); - totalNormalCount += numNormals; - - totalIndexCount += mesh->getNumIndices(); - } - - // alloc the resulting mesh - gpu::Resource::Size combinedVertexSize = totalVertexCount * sizeof(glm::vec3); - std::unique_ptr combinedVertexData{ new unsigned char[combinedVertexSize] }; - unsigned char* combinedVertexDataCursor = combinedVertexData.get(); - - gpu::Resource::Size combinedColorSize = totalColorCount * sizeof(glm::vec3); - std::unique_ptr combinedColorData{ new unsigned char[combinedColorSize] }; - unsigned char* combinedColorDataCursor = combinedColorData.get(); - - gpu::Resource::Size combinedNormalSize = totalNormalCount * sizeof(glm::vec3); - std::unique_ptr combinedNormalData{ new unsigned char[combinedNormalSize] }; - unsigned char* combinedNormalDataCursor = combinedNormalData.get(); - - gpu::Resource::Size combinedIndexSize = totalIndexCount * sizeof(uint32_t); - std::unique_ptr combinedIndexData{ new unsigned char[combinedIndexSize] }; - unsigned char* combinedIndexDataCursor = combinedIndexData.get(); - - uint32_t indexStartOffset { 0 }; - - foreach (const MeshProxy* meshProxy, in) { - MeshPointer mesh = meshProxy->getMeshPointer(); - mesh->forEach( - [&](glm::vec3 position){ - memcpy(combinedVertexDataCursor, &position, sizeof(position)); - combinedVertexDataCursor += sizeof(position); - }, - [&](glm::vec3 color){ - memcpy(combinedColorDataCursor, &color, sizeof(color)); - combinedColorDataCursor += sizeof(color); - }, - [&](glm::vec3 normal){ - memcpy(combinedNormalDataCursor, &normal, sizeof(normal)); - combinedNormalDataCursor += sizeof(normal); - }, - [&](uint32_t index){ - index += indexStartOffset; - memcpy(combinedIndexDataCursor, &index, sizeof(index)); - combinedIndexDataCursor += sizeof(index); - }); - - gpu::BufferView::Index numVertices = (gpu::BufferView::Index)mesh->getNumVertices(); - indexStartOffset += numVertices; - } - - graphics::MeshPointer result(new graphics::Mesh()); - - gpu::Element vertexElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); - gpu::Buffer* combinedVertexBuffer = new gpu::Buffer(combinedVertexSize, combinedVertexData.get()); - gpu::BufferPointer combinedVertexBufferPointer(combinedVertexBuffer); - gpu::BufferView combinedVertexBufferView(combinedVertexBufferPointer, vertexElement); - result->setVertexBuffer(combinedVertexBufferView); - - int attributeTypeColor = gpu::Stream::InputSlot::COLOR; // libraries/gpu/src/gpu/Stream.h - gpu::Element colorElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); - gpu::Buffer* combinedColorsBuffer = new gpu::Buffer(combinedColorSize, combinedColorData.get()); - gpu::BufferPointer combinedColorsBufferPointer(combinedColorsBuffer); - gpu::BufferView combinedColorsBufferView(combinedColorsBufferPointer, colorElement); - result->addAttribute(attributeTypeColor, combinedColorsBufferView); - - int attributeTypeNormal = gpu::Stream::InputSlot::NORMAL; // libraries/gpu/src/gpu/Stream.h - gpu::Element normalElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); - gpu::Buffer* combinedNormalsBuffer = new gpu::Buffer(combinedNormalSize, combinedNormalData.get()); - gpu::BufferPointer combinedNormalsBufferPointer(combinedNormalsBuffer); - gpu::BufferView combinedNormalsBufferView(combinedNormalsBufferPointer, normalElement); - result->addAttribute(attributeTypeNormal, combinedNormalsBufferView); - - gpu::Element indexElement = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::RAW); - gpu::Buffer* combinedIndexesBuffer = new gpu::Buffer(combinedIndexSize, combinedIndexData.get()); - gpu::BufferPointer combinedIndexesBufferPointer(combinedIndexesBuffer); - gpu::BufferView combinedIndexesBufferView(combinedIndexesBufferPointer, indexElement); - result->setIndexBuffer(combinedIndexesBufferView); - - std::vector parts; - parts.emplace_back(graphics::Mesh::Part((graphics::Index)0, // startIndex - (graphics::Index)result->getNumIndices(), // numIndices - (graphics::Index)0, // baseVertex - graphics::Mesh::TRIANGLES)); // topology - result->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(graphics::Mesh::Part), - (gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL)); - - - MeshProxy* resultProxy = new SimpleMeshProxy(result); - return meshToScriptValue(_modelScriptEngine, resultProxy); -} - -QScriptValue ModelScriptingInterface::transformMesh(glm::mat4 transform, MeshProxy* meshProxy) { - if (!meshProxy) { - return QScriptValue(false); - } - MeshPointer mesh = meshProxy->getMeshPointer(); - if (!mesh) { - return QScriptValue(false); - } - - const auto inverseTransposeTransform = glm::inverse(glm::transpose(transform)); - graphics::MeshPointer result = mesh->map([&](glm::vec3 position){ return glm::vec3(transform * glm::vec4(position, 1.0f)); }, - [&](glm::vec3 color){ return color; }, - [&](glm::vec3 normal){ return glm::vec3(inverseTransposeTransform * glm::vec4(normal, 0.0f)); }, - [&](uint32_t index){ return index; }); - MeshProxy* resultProxy = new SimpleMeshProxy(result); - return meshToScriptValue(_modelScriptEngine, resultProxy); -} - -QScriptValue ModelScriptingInterface::getVertexCount(MeshProxy* meshProxy) { - if (!meshProxy) { - return QScriptValue(false); - } - MeshPointer mesh = meshProxy->getMeshPointer(); - if (!mesh) { - return QScriptValue(false); - } - - gpu::BufferView::Index numVertices = (gpu::BufferView::Index)mesh->getNumVertices(); - - return numVertices; -} - -QScriptValue ModelScriptingInterface::getVertex(MeshProxy* meshProxy, int vertexIndex) { - if (!meshProxy) { - return QScriptValue(false); - } - MeshPointer mesh = meshProxy->getMeshPointer(); - if (!mesh) { - return QScriptValue(false); - } - - const gpu::BufferView& vertexBufferView = mesh->getVertexBuffer(); - gpu::BufferView::Index numVertices = (gpu::BufferView::Index)mesh->getNumVertices(); - - if (vertexIndex < 0 || vertexIndex >= numVertices) { - return QScriptValue(false); - } - - glm::vec3 pos = vertexBufferView.get(vertexIndex); - return vec3toScriptValue(_modelScriptEngine, pos); -} - - -QScriptValue ModelScriptingInterface::newMesh(const QVector& vertices, - const QVector& normals, - const QVector& faces) { - graphics::MeshPointer mesh(new graphics::Mesh()); - - // vertices - auto vertexBuffer = std::make_shared(vertices.size() * sizeof(glm::vec3), (gpu::Byte*)vertices.data()); - auto vertexBufferPtr = gpu::BufferPointer(vertexBuffer); - gpu::BufferView vertexBufferView(vertexBufferPtr, 0, vertexBufferPtr->getSize(), - sizeof(glm::vec3), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); - mesh->setVertexBuffer(vertexBufferView); - - if (vertices.size() == normals.size()) { - // normals - auto normalBuffer = std::make_shared(normals.size() * sizeof(glm::vec3), (gpu::Byte*)normals.data()); - auto normalBufferPtr = gpu::BufferPointer(normalBuffer); - gpu::BufferView normalBufferView(normalBufferPtr, 0, normalBufferPtr->getSize(), - sizeof(glm::vec3), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); - mesh->addAttribute(gpu::Stream::NORMAL, normalBufferView); - } else { - qCDebug(scriptengine) << "ModelScriptingInterface::newMesh normals must be same length as vertices"; - } - - // indices (faces) - int VERTICES_PER_TRIANGLE = 3; - int indexBufferSize = faces.size() * sizeof(uint32_t) * VERTICES_PER_TRIANGLE; - unsigned char* indexData = new unsigned char[indexBufferSize]; - unsigned char* indexDataCursor = indexData; - foreach(const MeshFace& meshFace, faces) { - for (int i = 0; i < VERTICES_PER_TRIANGLE; i++) { - memcpy(indexDataCursor, &meshFace.vertexIndices[i], sizeof(uint32_t)); - indexDataCursor += sizeof(uint32_t); - } - } - auto indexBuffer = std::make_shared(indexBufferSize, (gpu::Byte*)indexData); - auto indexBufferPtr = gpu::BufferPointer(indexBuffer); - gpu::BufferView indexBufferView(indexBufferPtr, gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::RAW)); - mesh->setIndexBuffer(indexBufferView); - - // parts - std::vector parts; - parts.emplace_back(graphics::Mesh::Part((graphics::Index)0, // startIndex - (graphics::Index)faces.size() * 3, // numIndices - (graphics::Index)0, // baseVertex - graphics::Mesh::TRIANGLES)); // topology - mesh->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(graphics::Mesh::Part), - (gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL)); - - - - MeshProxy* meshProxy = new SimpleMeshProxy(mesh); - return meshToScriptValue(_modelScriptEngine, meshProxy); -} diff --git a/libraries/script-engine/src/ModelScriptingInterface.h b/libraries/script-engine/src/ModelScriptingInterface.h deleted file mode 100644 index 3c239f006f..0000000000 --- a/libraries/script-engine/src/ModelScriptingInterface.h +++ /dev/null @@ -1,39 +0,0 @@ -// -// ModelScriptingInterface.h -// libraries/script-engine/src -// -// Created by Seth Alves on 2017-1-27. -// Copyright 2017 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 -// - -#ifndef hifi_ModelScriptingInterface_h -#define hifi_ModelScriptingInterface_h - -#include - -#include -class QScriptEngine; - -class ModelScriptingInterface : public QObject { - Q_OBJECT - -public: - ModelScriptingInterface(QObject* parent); - - Q_INVOKABLE QString meshToOBJ(MeshProxyList in); - Q_INVOKABLE QScriptValue appendMeshes(MeshProxyList in); - Q_INVOKABLE QScriptValue transformMesh(glm::mat4 transform, MeshProxy* meshProxy); - Q_INVOKABLE QScriptValue newMesh(const QVector& vertices, - const QVector& normals, - const QVector& faces); - Q_INVOKABLE QScriptValue getVertexCount(MeshProxy* meshProxy); - Q_INVOKABLE QScriptValue getVertex(MeshProxy* meshProxy, int vertexIndex); - -private: - QScriptEngine* _modelScriptEngine { nullptr }; -}; - -#endif // hifi_ModelScriptingInterface_h diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index c79ffffec7..ffb1b7bc74 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -73,8 +73,6 @@ #include "WebSocketClass.h" #include "RecordingScriptingInterface.h" #include "ScriptEngines.h" -#include "ModelScriptingInterface.h" - #include @@ -711,10 +709,6 @@ void ScriptEngine::init() { registerGlobalObject("DebugDraw", &DebugDraw::getInstance()); - registerGlobalObject("Model", new ModelScriptingInterface(this)); - qScriptRegisterMetaType(this, meshToScriptValue, meshFromScriptValue); - qScriptRegisterMetaType(this, meshesToScriptValue, meshesFromScriptValue); - registerGlobalObject("UserActivityLogger", DependencyManager::get().data()); } diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index 7b455beae5..9ad5c27072 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -855,68 +855,3 @@ QScriptValue animationDetailsToScriptValue(QScriptEngine* engine, const Animatio void animationDetailsFromScriptValue(const QScriptValue& object, AnimationDetails& details) { // nothing for now... } - -QScriptValue meshToScriptValue(QScriptEngine* engine, MeshProxy* const &in) { - return engine->newQObject(in, QScriptEngine::QtOwnership, - QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects); -} - -void meshFromScriptValue(const QScriptValue& value, MeshProxy* &out) { - out = qobject_cast(value.toQObject()); -} - -QScriptValue meshesToScriptValue(QScriptEngine* engine, const MeshProxyList &in) { - // QScriptValueList result; - QScriptValue result = engine->newArray(); - int i = 0; - foreach(MeshProxy* const meshProxy, in) { - result.setProperty(i++, meshToScriptValue(engine, meshProxy)); - } - return result; -} - -void meshesFromScriptValue(const QScriptValue& value, MeshProxyList &out) { - QScriptValueIterator itr(value); - - qDebug() << "in meshesFromScriptValue, value.length =" << value.property("length").toInt32(); - - while (itr.hasNext()) { - itr.next(); - MeshProxy* meshProxy = qscriptvalue_cast(itr.value()); - if (meshProxy) { - out.append(meshProxy); - } else { - qDebug() << "null meshProxy"; - } - } -} - - -QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const MeshFace &meshFace) { - QScriptValue obj = engine->newObject(); - obj.setProperty("vertices", qVectorIntToScriptValue(engine, meshFace.vertexIndices)); - return obj; -} - -void meshFaceFromScriptValue(const QScriptValue &object, MeshFace& meshFaceResult) { - qVectorIntFromScriptValue(object.property("vertices"), meshFaceResult.vertexIndices); -} - -QScriptValue qVectorMeshFaceToScriptValue(QScriptEngine* engine, const QVector& vector) { - QScriptValue array = engine->newArray(); - for (int i = 0; i < vector.size(); i++) { - array.setProperty(i, meshFaceToScriptValue(engine, vector.at(i))); - } - return array; -} - -void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector& result) { - int length = array.property("length").toInteger(); - result.clear(); - - for (int i = 0; i < length; i++) { - MeshFace meshFace = MeshFace(); - meshFaceFromScriptValue(array.property(i), meshFace); - result << meshFace; - } -} diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 25b2cec331..e8390c3a86 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -315,51 +315,9 @@ Q_DECLARE_METATYPE(AnimationDetails); QScriptValue animationDetailsToScriptValue(QScriptEngine* engine, const AnimationDetails& event); void animationDetailsFromScriptValue(const QScriptValue& object, AnimationDetails& event); -namespace graphics { - class Mesh; -} - -using MeshPointer = std::shared_ptr; -class MeshProxy : public QObject { - Q_OBJECT -public: - virtual MeshPointer getMeshPointer() const = 0; - Q_INVOKABLE virtual int getNumVertices() const = 0; - Q_INVOKABLE virtual glm::vec3 getPos3(int index) const = 0; -}; - -Q_DECLARE_METATYPE(MeshProxy*); - -class MeshProxyList : public QList {}; // typedef and using fight with the Qt macros/templates, do this instead -Q_DECLARE_METATYPE(MeshProxyList); - - -QScriptValue meshToScriptValue(QScriptEngine* engine, MeshProxy* const &in); -void meshFromScriptValue(const QScriptValue& value, MeshProxy* &out); - -QScriptValue meshesToScriptValue(QScriptEngine* engine, const MeshProxyList &in); -void meshesFromScriptValue(const QScriptValue& value, MeshProxyList &out); - -class MeshFace { - -public: - MeshFace() {} - ~MeshFace() {} - - QVector vertexIndices; - // TODO -- material... -}; - -Q_DECLARE_METATYPE(MeshFace) -Q_DECLARE_METATYPE(QVector) - -QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const MeshFace &meshFace); -void meshFaceFromScriptValue(const QScriptValue &object, MeshFace& meshFaceResult); -QScriptValue qVectorMeshFaceToScriptValue(QScriptEngine* engine, const QVector& vector); -void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector& result); #endif // hifi_RegisteredMetaTypes_h From a63393bc55cf1d68deff6dfceda6d744a9cba214 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 7 Feb 2018 13:32:30 -0800 Subject: [PATCH 02/49] undo part of #12312 --- interface/src/ui/overlays/Web3DOverlay.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 5fb4d58340..4f96e70aa9 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -80,7 +80,6 @@ Web3DOverlay::Web3DOverlay() { _webSurface->getSurfaceContext()->setContextProperty("GlobalServices", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED _webSurface->getSurfaceContext()->setContextProperty("AccountServices", AccountServicesScriptingInterface::getInstance()); _webSurface->getSurfaceContext()->setContextProperty("AddressManager", DependencyManager::get().data()); - } Web3DOverlay::Web3DOverlay(const Web3DOverlay* Web3DOverlay) : @@ -201,6 +200,11 @@ void Web3DOverlay::setupQmlSurface() { _webSurface->getSurfaceContext()->setContextProperty("offscreenFlags", flags); _webSurface->getSurfaceContext()->setContextProperty("AddressManager", DependencyManager::get().data()); + + _webSurface->getSurfaceContext()->setContextProperty("Account", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED + _webSurface->getSurfaceContext()->setContextProperty("GlobalServices", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED + _webSurface->getSurfaceContext()->setContextProperty("AccountServices", AccountServicesScriptingInterface::getInstance()); + // in Qt 5.10.0 there is already an "Audio" object in the QML context // though I failed to find it (from QtMultimedia??). So.. let it be "AudioScriptingInterface" _webSurface->getSurfaceContext()->setContextProperty("AudioScriptingInterface", DependencyManager::get().data()); From 3a7e626463de5401babf9f5286f36c8463a014c6 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 7 Feb 2018 10:19:30 -0800 Subject: [PATCH 03/49] Merge pull request #12316 from zfox23/commerce_inspectionCertRedesign Commerce: Inspection Certificate Redesign --- .../InspectionCertificate.qml | 548 ++++++++++++------ .../images/cert-bg-gold-split.png | Bin 0 -> 189708 bytes .../inspectionCertificate/images/cert-bg.jpg | Bin 64011 -> 0 bytes .../images/nocert-bg-split.png | Bin 0 -> 17201 bytes .../commerce/wallet/sendMoney/SendMoney.qml | 2 +- .../wallet/sendMoney/images/loader.gif | Bin 59412 -> 0 bytes 6 files changed, 362 insertions(+), 188 deletions(-) create mode 100644 interface/resources/qml/hifi/commerce/inspectionCertificate/images/cert-bg-gold-split.png delete mode 100644 interface/resources/qml/hifi/commerce/inspectionCertificate/images/cert-bg.jpg create mode 100644 interface/resources/qml/hifi/commerce/inspectionCertificate/images/nocert-bg-split.png delete mode 100644 interface/resources/qml/hifi/commerce/wallet/sendMoney/images/loader.gif diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml index 421fa4b074..2c7319be09 100644 --- a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml +++ b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml @@ -19,21 +19,30 @@ import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls import "../wallet" as HifiWallet -// references XXX from root context - Rectangle { HifiConstants { id: hifi; } id: root; - property string marketplaceUrl; - property string certificateId; + property string marketplaceUrl: ""; + property string certificateId: ""; property string itemName: "--"; property string itemOwner: "--"; property string itemEdition: "--"; property string dateOfPurchase: "--"; + property string itemCost: "--"; + property string certTitleTextColor: hifi.colors.darkGray; + property string certTextColor: hifi.colors.white; + property string infoTextColor: hifi.colors.blueAccent; + // 0 means replace none + // 4 means replace all but "Item Edition" + // 5 means replace all 5 replaceable fields + property int certInfoReplaceMode: 5; property bool isLightbox: false; property bool isMyCert: false; - property bool isCertificateInvalid: false; + property bool useGoldCert: true; + property bool certificateInfoPending: true; + property int certificateStatus: 0; + property bool certificateStatusPending: true; // Style color: hifi.colors.faintGray; Connections { @@ -45,71 +54,130 @@ Rectangle { } else { root.marketplaceUrl = result.data.marketplace_item_url; root.isMyCert = result.isMyCert ? result.isMyCert : false; - root.itemOwner = root.isCertificateInvalid ? "--" : (root.isMyCert ? Account.username : - "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"); - root.itemEdition = root.isCertificateInvalid ? "Uncertified Copy" : - (result.data.edition_number + "/" + (result.data.limited_run === -1 ? "\u221e" : result.data.limited_run)); - root.dateOfPurchase = root.isCertificateInvalid ? "" : getFormattedDate(result.data.transfer_created_at * 1000); - root.itemName = result.data.marketplace_item_name; + + if (root.certInfoReplaceMode > 3) { + root.itemName = result.data.marketplace_item_name; + // "\u2022" is the Unicode character 'BULLET' - it's what's used in password fields on the web, etc + root.itemOwner = root.isMyCert ? Account.username : + "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"; + root.dateOfPurchase = root.isMyCert ? getFormattedDate(result.data.transfer_created_at * 1000) : "Undisclosed"; + root.itemCost = (root.isMyCert && result.data.cost !== undefined) ? result.data.cost : "Undisclosed"; + } + if (root.certInfoReplaceMode > 4) { + root.itemEdition = result.data.edition_number + "/" + (result.data.limited_run === -1 ? "\u221e" : result.data.limited_run); + } + + if (root.certificateStatus === 4) { // CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED + if (root.isMyCert) { + errorText.text = "This item is an uncertified copy of an item you purchased."; + } else { + errorText.text = "The person who placed this item doesn't own it."; + } + } if (result.data.invalid_reason || result.data.transfer_status[0] === "failed") { - titleBarText.text = "Invalid Certificate"; - titleBarText.color = hifi.colors.redHighlight; + root.useGoldCert = false; + root.certTitleTextColor = hifi.colors.redHighlight; + root.certTextColor = hifi.colors.redHighlight; + root.infoTextColor = hifi.colors.redHighlight; + titleBarText.text = "Certificate\nNo Longer Valid"; popText.text = ""; + showInMarketplaceButton.visible = false; + // "Edition" text previously set above in this function + // "Owner" text previously set above in this function + // "Purchase Date" text previously set above in this function + // "Purchase Price" text previously set above in this function if (result.data.invalid_reason) { errorText.text = result.data.invalid_reason; } } else if (result.data.transfer_status[0] === "pending") { + root.useGoldCert = false; + root.certTitleTextColor = hifi.colors.redHighlight; + root.certTextColor = hifi.colors.redHighlight; + root.infoTextColor = hifi.colors.redHighlight; titleBarText.text = "Certificate Pending"; + popText.text = ""; + showInMarketplaceButton.visible = true; + // "Edition" text previously set above in this function + // "Owner" text previously set above in this function + // "Purchase Date" text previously set above in this function + // "Purchase Price" text previously set above in this function errorText.text = "The status of this item is still pending confirmation. If the purchase is not confirmed, " + "this entity will be cleaned up by the domain."; - errorText.color = hifi.colors.baseGray; } } + root.certificateInfoPending = false; } onUpdateCertificateStatus: { - if (certStatus === 1) { // CERTIFICATE_STATUS_VERIFICATION_SUCCESS - // NOP - } else if (certStatus === 2) { // CERTIFICATE_STATUS_VERIFICATION_TIMEOUT - root.isCertificateInvalid = true; - errorText.text = "Verification of this certificate timed out."; - errorText.color = hifi.colors.redHighlight; - } else if (certStatus === 3) { // CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED - root.isCertificateInvalid = true; - titleBarText.text = "Invalid Certificate"; - titleBarText.color = hifi.colors.redHighlight; - + root.certificateStatus = certStatus; + if (root.certificateStatus === 1) { // CERTIFICATE_STATUS_VERIFICATION_SUCCESS + root.useGoldCert = true; + root.certTitleTextColor = hifi.colors.darkGray; + root.certTextColor = hifi.colors.white; + root.infoTextColor = hifi.colors.blueAccent; + titleBarText.text = "Certificate"; + popText.text = "PROOF OF PROVENANCE"; + showInMarketplaceButton.visible = true; + root.certInfoReplaceMode = 5; + // "Item Name" text will be set in "onCertificateInfoResult()" + // "Edition" text will be set in "onCertificateInfoResult()" + // "Owner" text will be set in "onCertificateInfoResult()" + // "Purchase Date" text will be set in "onCertificateInfoResult()" + // "Purchase Price" text will be set in "onCertificateInfoResult()" + errorText.text = ""; + } else if (root.certificateStatus === 2) { // CERTIFICATE_STATUS_VERIFICATION_TIMEOUT + root.useGoldCert = false; + root.certTitleTextColor = hifi.colors.redHighlight; + root.certTextColor = hifi.colors.redHighlight; + root.infoTextColor = hifi.colors.redHighlight; + titleBarText.text = "Request Timed Out"; popText.text = ""; + showInMarketplaceButton.visible = false; + root.certInfoReplaceMode = 0; + root.itemName = ""; + root.itemEdition = ""; root.itemOwner = ""; - dateOfPurchaseHeader.text = ""; root.dateOfPurchase = ""; - root.itemEdition = "Uncertified Copy"; - + root.itemCost = ""; + errorText.text = "Your request to inspect this item timed out. Please try again later."; + } else if (root.certificateStatus === 3) { // CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED + root.useGoldCert = false; + root.certTitleTextColor = hifi.colors.redHighlight; + root.certTextColor = hifi.colors.redHighlight; + root.infoTextColor = hifi.colors.redHighlight; + titleBarText.text = "Certificate\nNo Longer Valid"; + popText.text = ""; + showInMarketplaceButton.visible = true; + root.certInfoReplaceMode = 5; + // "Item Name" text will be set in "onCertificateInfoResult()" + // "Edition" text will be set in "onCertificateInfoResult()" + // "Owner" text will be set in "onCertificateInfoResult()" + // "Purchase Date" text will be set in "onCertificateInfoResult()" + // "Purchase Price" text will be set in "onCertificateInfoResult()" errorText.text = "The information associated with this item has been modified and it no longer matches the original certified item."; - errorText.color = hifi.colors.baseGray; - } else if (certStatus === 4) { // CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED - root.isCertificateInvalid = true; + } else if (root.certificateStatus === 4) { // CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED + root.useGoldCert = false; + root.certTitleTextColor = hifi.colors.redHighlight; + root.certTextColor = hifi.colors.redHighlight; + root.infoTextColor = hifi.colors.redHighlight; titleBarText.text = "Invalid Certificate"; - titleBarText.color = hifi.colors.redHighlight; - popText.text = ""; - root.itemOwner = ""; - dateOfPurchaseHeader.text = ""; - root.dateOfPurchase = ""; - root.itemEdition = "Uncertified Copy"; - - errorText.text = "The avatar who rezzed this item doesn't own it."; - errorText.color = hifi.colors.baseGray; + showInMarketplaceButton.visible = true; + root.certInfoReplaceMode = 4; + // "Item Name" text will be set in "onCertificateInfoResult()" + root.itemEdition = "Uncertified Copy" + // "Owner" text will be set in "onCertificateInfoResult()" + // "Purchase Date" text will be set in "onCertificateInfoResult()" + // "Purchase Price" text will be set in "onCertificateInfoResult()" + // "Error Text" text will be set in "onCertificateInfoResult()" } else { console.log("Unknown certificate status received from ledger signal!"); } - } - } - - onCertificateIdChanged: { - if (certificateId !== "") { - Commerce.certificateInfo(certificateId); + + root.certificateStatusPending = false; + // We've gotten cert status - we are GO on getting the cert info + Commerce.certificateInfo(root.certificateId); } } @@ -122,9 +190,35 @@ Rectangle { hoverEnabled: true; } - Image { + Rectangle { + id: loadingOverlay; + z: 998; + + visible: root.certificateInfoPending || root.certificateStatusPending; anchors.fill: parent; - source: "images/cert-bg.jpg"; + color: Qt.rgba(0.0, 0.0, 0.0, 0.7); + + // This object is always used in a popup or full-screen Wallet section. + // This MouseArea is used to prevent a user from being + // able to click on a button/mouseArea underneath the popup/section. + MouseArea { + anchors.fill: parent; + propagateComposedEvents: false; + } + + AnimatedImage { + source: "../common/images/loader.gif" + width: 96; + height: width; + anchors.verticalCenter: parent.verticalCenter; + anchors.horizontalCenter: parent.horizontalCenter; + } + } + + Image { + id: backgroundImage; + anchors.fill: parent; + source: root.useGoldCert ? "images/cert-bg-gold-split.png" : "images/nocert-bg-split.png"; } // Title text @@ -137,16 +231,17 @@ Rectangle { anchors.top: parent.top; anchors.topMargin: 40; anchors.left: parent.left; - anchors.leftMargin: 45; + anchors.leftMargin: 36; anchors.right: parent.right; + anchors.rightMargin: 8; height: paintedHeight; // Style - color: hifi.colors.darkGray; + color: root.certTitleTextColor; + wrapMode: Text.WordWrap; } // Title text RalewayRegular { id: popText; - text: "Proof of Provenance"; // Text size size: 16; // Anchors @@ -154,9 +249,38 @@ Rectangle { anchors.topMargin: 4; anchors.left: titleBarText.left; anchors.right: titleBarText.right; - height: paintedHeight; + height: text === "" ? 0 : paintedHeight; // Style - color: hifi.colors.darkGray; + color: root.certTitleTextColor; + } + + // "Close" button + HiFiGlyphs { + id: closeGlyphButton; + text: hifi.glyphs.close; + color: hifi.colors.white; + size: 26; + anchors.top: parent.top; + anchors.topMargin: 10; + anchors.right: parent.right; + anchors.rightMargin: 10; + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + onEntered: { + parent.text = hifi.glyphs.closeInverted; + } + onExited: { + parent.text = hifi.glyphs.close; + } + onClicked: { + if (root.isLightbox) { + root.visible = false; + } else { + sendToScript({method: 'inspectionCertificate_closeClicked', closeGoesToPurchases: root.closeGoesToPurchases}); + } + } + } } // @@ -164,11 +288,13 @@ Rectangle { // Item { id: certificateContainer; - anchors.top: popText.bottom; - anchors.topMargin: 30; - anchors.bottom: buttonsContainer.top; + anchors.top: titleBarText.top; + anchors.topMargin: 110; + anchors.bottom: infoContainer.top; anchors.left: parent.left; + anchors.leftMargin: titleBarText.anchors.leftMargin; anchors.right: parent.right; + anchors.rightMargin: 24; RalewayRegular { id: itemNameHeader; @@ -178,9 +304,7 @@ Rectangle { // Anchors anchors.top: parent.top; anchors.left: parent.left; - anchors.leftMargin: 45; anchors.right: parent.right; - anchors.rightMargin: 16; height: paintedHeight; // Style color: hifi.colors.darkGray; @@ -197,79 +321,30 @@ Rectangle { anchors.right: itemNameHeader.right; height: paintedHeight; // Style - color: hifi.colors.white; + color: root.certTextColor; elide: Text.ElideRight; MouseArea { + enabled: showInMarketplaceButton.visible; anchors.fill: parent; hoverEnabled: enabled; onClicked: { sendToScript({method: 'inspectionCertificate_showInMarketplaceClicked', marketplaceUrl: root.marketplaceUrl}); } onEntered: itemName.color = hifi.colors.blueHighlight; - onExited: itemName.color = hifi.colors.white; + onExited: itemName.color = root.certTextColor; } } - RalewayRegular { - id: ownedByHeader; - text: "OWNER"; - // Text size - size: 16; - // Anchors - anchors.top: itemName.bottom; - anchors.topMargin: 28; - anchors.left: parent.left; - anchors.leftMargin: 45; - anchors.right: parent.right; - anchors.rightMargin: 16; - height: paintedHeight; - // Style - color: hifi.colors.darkGray; - } - RalewayRegular { - id: ownedBy; - text: root.itemOwner; - // Text size - size: 22; - // Anchors - anchors.top: ownedByHeader.bottom; - anchors.topMargin: 8; - anchors.left: ownedByHeader.left; - height: paintedHeight; - // Style - color: hifi.colors.white; - elide: Text.ElideRight; - } - AnonymousProRegular { - id: isMyCertText; - visible: root.isMyCert && !root.isCertificateInvalid; - text: "(Private)"; - size: 18; - // Anchors - anchors.top: ownedBy.top; - anchors.topMargin: 4; - anchors.bottom: ownedBy.bottom; - anchors.left: ownedBy.right; - anchors.leftMargin: 6; - anchors.right: ownedByHeader.right; - // Style - color: hifi.colors.white; - elide: Text.ElideRight; - verticalAlignment: Text.AlignVCenter; - } - RalewayRegular { id: editionHeader; text: "EDITION"; // Text size size: 16; // Anchors - anchors.top: ownedBy.bottom; + anchors.top: itemName.bottom; anchors.topMargin: 28; anchors.left: parent.left; - anchors.leftMargin: 45; anchors.right: parent.right; - anchors.rightMargin: 16; height: paintedHeight; // Style color: hifi.colors.darkGray; @@ -286,21 +361,117 @@ Rectangle { anchors.right: editionHeader.right; height: paintedHeight; // Style - color: hifi.colors.white; + color: root.certTextColor; + } + + // "Show In Marketplace" button + HifiControlsUit.Button { + id: showInMarketplaceButton; + enabled: root.marketplaceUrl; + color: hifi.buttons.blue; + colorScheme: hifi.colorSchemes.light; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 48; + anchors.right: parent.right; + width: 200; + height: 40; + text: "View In Market" + onClicked: { + sendToScript({method: 'inspectionCertificate_showInMarketplaceClicked', marketplaceUrl: root.marketplaceUrl}); + } + } + } + // + // "CERTIFICATE" END + // + + // + // "INFO CONTAINER" START + // + Item { + id: infoContainer; + anchors.bottom: parent.bottom; + anchors.left: parent.left; + anchors.leftMargin: titleBarText.anchors.leftMargin; + anchors.right: parent.right; + anchors.rightMargin: 24; + height: root.useGoldCert ? 220 : 372; + + RalewayRegular { + id: errorText; + visible: !root.useGoldCert; + // Text size + size: 20; + // Anchors + anchors.top: parent.top; + anchors.topMargin: 36; + anchors.left: parent.left; + anchors.right: parent.right; + height: 116; + // Style + wrapMode: Text.WordWrap; + color: hifi.colors.baseGray; + verticalAlignment: Text.AlignTop; + } + + RalewayRegular { + id: ownedByHeader; + text: "OWNER"; + // Text size + size: 16; + // Anchors + anchors.top: errorText.visible ? errorText.bottom : parent.top; + anchors.topMargin: 28; + anchors.left: parent.left; + anchors.right: parent.right; + height: paintedHeight; + // Style + color: hifi.colors.darkGray; + } + + RalewayRegular { + id: ownedBy; + text: root.itemOwner; + // Text size + size: 22; + // Anchors + anchors.top: ownedByHeader.bottom; + anchors.topMargin: 8; + anchors.left: ownedByHeader.left; + height: paintedHeight; + // Style + color: root.infoTextColor; + elide: Text.ElideRight; + } + AnonymousProRegular { + id: isMyCertText; + visible: root.isMyCert && ownedBy.text !== "--" && ownedBy.text !== ""; + text: "(Private)"; + size: 18; + // Anchors + anchors.top: ownedBy.top; + anchors.topMargin: 4; + anchors.bottom: ownedBy.bottom; + anchors.left: ownedBy.right; + anchors.leftMargin: 6; + anchors.right: ownedByHeader.right; + // Style + color: root.infoTextColor; + elide: Text.ElideRight; + verticalAlignment: Text.AlignVCenter; } RalewayRegular { id: dateOfPurchaseHeader; - text: "DATE OF PURCHASE"; + text: "PURCHASE DATE"; // Text size size: 16; // Anchors - anchors.top: edition.bottom; + anchors.top: ownedBy.bottom; anchors.topMargin: 28; anchors.left: parent.left; - anchors.leftMargin: 45; - anchors.right: parent.right; - anchors.rightMargin: 16; + anchors.right: parent.horizontalCenter; + anchors.rightMargin: 8; height: paintedHeight; // Style color: hifi.colors.darkGray; @@ -317,73 +488,58 @@ Rectangle { anchors.right: dateOfPurchaseHeader.right; height: paintedHeight; // Style - color: hifi.colors.white; + color: root.infoTextColor; } RalewayRegular { - id: errorText; + id: priceHeader; + text: "PURCHASE PRICE"; // Text size - size: 20; + size: 16; // Anchors - anchors.top: dateOfPurchase.bottom; - anchors.topMargin: 36; - anchors.left: dateOfPurchase.left; - anchors.right: dateOfPurchase.right; - anchors.bottom: parent.bottom; - // Style - wrapMode: Text.WordWrap; - color: hifi.colors.redHighlight; - verticalAlignment: Text.AlignTop; - } - } - // - // "CERTIFICATE" END - // - - Item { - id: buttonsContainer; - anchors.bottom: parent.bottom; - anchors.bottomMargin: 30; - anchors.left: parent.left; - anchors.right: parent.right; - height: 50; - - // "Cancel" button - HifiControlsUit.Button { - color: hifi.buttons.noneBorderlessWhite; - colorScheme: hifi.colorSchemes.light; - anchors.top: parent.top; - anchors.left: parent.left; - anchors.leftMargin: 30; - width: parent.width/2 - 50; - height: 50; - text: "close"; - onClicked: { - if (root.isLightbox) { - root.visible = false; - } else { - sendToScript({method: 'inspectionCertificate_closeClicked', closeGoesToPurchases: root.closeGoesToPurchases}); - } - } - } - - // "Show In Marketplace" button - HifiControlsUit.Button { - id: showInMarketplaceButton; - enabled: root.marketplaceUrl; - color: hifi.buttons.blue; - colorScheme: hifi.colorSchemes.light; - anchors.top: parent.top; + anchors.top: ownedBy.bottom; + anchors.topMargin: 28; + anchors.left: parent.horizontalCenter; anchors.right: parent.right; - anchors.rightMargin: 30; - width: parent.width/2 - 50; - height: 50; - text: "View In Market" - onClicked: { - sendToScript({method: 'inspectionCertificate_showInMarketplaceClicked', marketplaceUrl: root.marketplaceUrl}); - } + height: paintedHeight; + // Style + color: hifi.colors.darkGray; + } + HiFiGlyphs { + id: hfcGlyph; + visible: priceText.text !== "Undisclosed" && priceText.text !== ""; + text: hifi.glyphs.hfc; + // Size + size: 24; + // Anchors + anchors.top: priceHeader.bottom; + anchors.topMargin: 8; + anchors.left: priceHeader.left; + width: visible ? paintedWidth + 6 : 0; + height: 40; + // Style + color: root.infoTextColor; + verticalAlignment: Text.AlignTop; + horizontalAlignment: Text.AlignLeft; + } + AnonymousProRegular { + id: priceText; + text: root.itemCost; + // Text size + size: 18; + // Anchors + anchors.top: priceHeader.bottom; + anchors.topMargin: 8; + anchors.left: hfcGlyph.right; + anchors.right: priceHeader.right; + height: paintedHeight; + // Style + color: root.infoTextColor; } } + // + // "INFO CONTAINER" END + // // // FUNCTION DEFINITIONS START @@ -404,19 +560,11 @@ Rectangle { function fromScript(message) { switch (message.method) { case 'inspectionCertificate_setCertificateId': + resetCert(false); root.certificateId = message.certificateId; break; case 'inspectionCertificate_resetCert': - titleBarText.text = "Certificate"; - popText.text = "PROOF OF PURCHASE"; - root.certificateId = ""; - root.itemName = "--"; - root.itemOwner = "--"; - root.itemEdition = "--"; - root.dateOfPurchase = "--"; - root.marketplaceUrl = ""; - root.isMyCert = false; - errorText.text = ""; + resetCert(true); break; default: console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message)); @@ -424,7 +572,33 @@ Rectangle { } signal sendToScript(var message); + function resetCert(alsoResetCertID) { + if (alsoResetCertID) { + root.certificateId = ""; + } + root.certInfoReplaceMode = 5; + root.certificateInfoPending = true; + root.certificateStatusPending = true; + root.useGoldCert = true; + root.certTitleTextColor = hifi.colors.darkGray; + root.certTextColor = hifi.colors.white; + root.infoTextColor = hifi.colors.blueAccent; + titleBarText.text = "Certificate"; + popText.text = ""; + root.itemName = "--"; + root.itemOwner = "--"; + root.itemEdition = "--"; + root.dateOfPurchase = "--"; + root.marketplaceUrl = ""; + root.itemCost = "--"; + root.isMyCert = false; + errorText.text = ""; + } + function getFormattedDate(timestamp) { + if (timestamp === "--") { + return "--"; + } function addLeadingZero(n) { return n < 10 ? '0' + n : '' + n; } @@ -449,7 +623,7 @@ Rectangle { var min = addLeadingZero(a.getMinutes()); var sec = addLeadingZero(a.getSeconds()); - return year + '-' + month + '-' + day + '
' + drawnHour + ':' + min + amOrPm; + return year + '-' + month + '-' + day + ' ' + drawnHour + ':' + min + amOrPm; } // // FUNCTION DEFINITIONS END diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/images/cert-bg-gold-split.png b/interface/resources/qml/hifi/commerce/inspectionCertificate/images/cert-bg-gold-split.png new file mode 100644 index 0000000000000000000000000000000000000000..14a17df0b16f35320b67561440d6d9cf11cb2f29 GIT binary patch literal 189708 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 k+*$f8F` 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

0~BjTbon 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 literal 0 HcmV?d00001 diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/images/cert-bg.jpg b/interface/resources/qml/hifi/commerce/inspectionCertificate/images/cert-bg.jpg deleted file mode 100644 index b39a55e4e83f3290d7befdfeb1055cf9b73f4bfc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 64011 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 diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/images/nocert-bg-split.png b/interface/resources/qml/hifi/commerce/inspectionCertificate/images/nocert-bg-split.png new file mode 100644 index 0000000000000000000000000000000000000000..b2f5a492653b84bf6b739ae270180b1bb7b0d48d GIT binary patch literal 17201 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 F{|C0gl@I^` literal 0 HcmV?d00001 diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml b/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml index ee1246c0c4..7dd72b904e 100644 --- a/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml +++ b/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml @@ -1115,7 +1115,7 @@ Item { AnimatedImage { id: sendingMoneyImage; - source: "./images/loader.gif" + source: "../../common/images/loader.gif" width: 96; height: width; anchors.verticalCenter: parent.verticalCenter; diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/loader.gif b/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/loader.gif deleted file mode 100644 index 0536bd1884f1cb4a814b729803b1996c8347d182..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59412 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�)#8TsS*rVAU%J0tpt=B2=IG*djp3b$!Y|O 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 L8@}NiK8F7Q4CAa& From 679d53e2cf3cb4393c2507df731dcadfa1c3627d Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 7 Feb 2018 15:13:22 -0800 Subject: [PATCH 04/49] This works, but is it correct? --- .gitignore | 5 ++++- .../inspectionCertificate/InspectionCertificate.qml | 2 ++ .../src/ui/overlays/ContextOverlayInterface.cpp | 12 ++++++++---- interface/src/ui/overlays/ContextOverlayInterface.h | 2 +- scripts/system/marketplaces/marketplaces.js | 3 +++ 5 files changed, 18 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index f45572c388..1ffb93fe80 100644 --- a/.gitignore +++ b/.gitignore @@ -85,4 +85,7 @@ npm-debug.log android/app/src/main/assets # Resource binary file -interface/compiledResources \ No newline at end of file +interface/compiledResources + +# GPUCache +interface/resources/GPUCache/* \ No newline at end of file diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml index 2c7319be09..bef03bd4c1 100644 --- a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml +++ b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml @@ -256,6 +256,7 @@ Rectangle { // "Close" button HiFiGlyphs { + z: 999; id: closeGlyphButton; text: hifi.glyphs.close; color: hifi.colors.white; @@ -562,6 +563,7 @@ Rectangle { case 'inspectionCertificate_setCertificateId': resetCert(false); root.certificateId = message.certificateId; + sendToScript({method: 'inspectionCertificate_requestOwnershipVerification', certificateId: root.certificateId}); break; case 'inspectionCertificate_resetCert': resetCert(true); diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index ed7b811fb0..77284408cd 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -221,13 +221,13 @@ bool ContextOverlayInterface::destroyContextOverlay(const EntityItemID& entityIt void ContextOverlayInterface::contextOverlays_mousePressOnOverlay(const OverlayID& overlayID, const PointerEvent& event) { if (overlayID == _contextOverlayID && event.getButton() == PointerEvent::PrimaryButton) { qCDebug(context_overlay) << "Clicked Context Overlay. Entity ID:" << _currentEntityWithContextOverlay << "Overlay ID:" << overlayID; + emit contextOverlayClicked(_currentEntityWithContextOverlay); Setting::Handle _settingSwitch{ "commerce", true }; if (_settingSwitch.get()) { openInspectionCertificate(); } else { openMarketplace(); } - emit contextOverlayClicked(_currentEntityWithContextOverlay); _contextOverlayJustClicked = true; } } @@ -350,6 +350,12 @@ void ContextOverlayInterface::requestOwnershipVerification(const QUuid& entityID emit DependencyManager::get()->ownershipVerificationFailed(_lastInspectedEntity); qCDebug(context_overlay) << "Entity" << _lastInspectedEntity << "failed static certificate verification!"; } + } else { + // We don't currently verify ownership of entities that aren't Avatar Entities, + // so they always pass Ownership Verification. It's necessary to emit this signal + // so that the Inspection Certificate can continue its information-grabbing process. + auto ledger = DependencyManager::get(); + emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS)); } } @@ -357,12 +363,10 @@ static const QString INSPECTION_CERTIFICATE_QML_PATH = "hifi/commerce/inspection void ContextOverlayInterface::openInspectionCertificate() { // lets open the tablet to the inspection certificate QML if (!_currentEntityWithContextOverlay.isNull() && _entityMarketplaceID.length() > 0) { + setLastInspectedEntity(_currentEntityWithContextOverlay); auto tablet = dynamic_cast(_tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); tablet->loadQMLSource(INSPECTION_CERTIFICATE_QML_PATH); _hmdScriptingInterface->openTablet(); - - setLastInspectedEntity(_currentEntityWithContextOverlay); - requestOwnershipVerification(_lastInspectedEntity); } } diff --git a/interface/src/ui/overlays/ContextOverlayInterface.h b/interface/src/ui/overlays/ContextOverlayInterface.h index 6aad2a773b..fcdf2d5820 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.h +++ b/interface/src/ui/overlays/ContextOverlayInterface.h @@ -57,7 +57,7 @@ public: bool getEnabled() { return _enabled; } bool getIsInMarketplaceInspectionMode() { return _isInMarketplaceInspectionMode; } void setIsInMarketplaceInspectionMode(bool mode) { _isInMarketplaceInspectionMode = mode; } - void requestOwnershipVerification(const QUuid& entityID); + Q_INVOKABLE void requestOwnershipVerification(const QUuid& entityID); EntityPropertyFlags getEntityPropertyFlags() { return _entityPropertyFlags; } signals: diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index edcd488a01..cec139faae 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -583,6 +583,9 @@ var selectionDisplay = null; // for gridTool.js to ignore case 'inspectionCertificate_closeClicked': tablet.gotoHomeScreen(); break; + case 'inspectionCertificate_requestOwnershipVerification': + ContextOverlay.requestOwnershipVerification(message.certificateId); + break; case 'inspectionCertificate_showInMarketplaceClicked': tablet.gotoWebScreen(message.marketplaceUrl, MARKETPLACES_INJECT_SCRIPT_URL); break; From 704d4255a56ac04027cf47ccc78669f21aee2c6d Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 7 Feb 2018 15:44:21 -0800 Subject: [PATCH 05/49] Comment change --- interface/src/ui/overlays/ContextOverlayInterface.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index 77284408cd..4dacab8936 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -221,13 +221,13 @@ bool ContextOverlayInterface::destroyContextOverlay(const EntityItemID& entityIt void ContextOverlayInterface::contextOverlays_mousePressOnOverlay(const OverlayID& overlayID, const PointerEvent& event) { if (overlayID == _contextOverlayID && event.getButton() == PointerEvent::PrimaryButton) { qCDebug(context_overlay) << "Clicked Context Overlay. Entity ID:" << _currentEntityWithContextOverlay << "Overlay ID:" << overlayID; - emit contextOverlayClicked(_currentEntityWithContextOverlay); Setting::Handle _settingSwitch{ "commerce", true }; if (_settingSwitch.get()) { openInspectionCertificate(); } else { openMarketplace(); } + emit contextOverlayClicked(_currentEntityWithContextOverlay); _contextOverlayJustClicked = true; } } @@ -352,8 +352,8 @@ void ContextOverlayInterface::requestOwnershipVerification(const QUuid& entityID } } else { // We don't currently verify ownership of entities that aren't Avatar Entities, - // so they always pass Ownership Verification. It's necessary to emit this signal - // so that the Inspection Certificate can continue its information-grabbing process. + // so they always pass Ownership Verification. It's necessary to emit this signal + // so that the Inspection Certificate can continue its information-grabbing process. auto ledger = DependencyManager::get(); emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS)); } From ecc0a2f43bb98974fea6623e1fa1a31d73afa545 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 8 Feb 2018 10:35:34 -0800 Subject: [PATCH 06/49] Bugfix. --- .../InspectionCertificate.qml | 149 ++++++++++-------- .../ui/overlays/ContextOverlayInterface.cpp | 142 ++++++++--------- scripts/system/marketplaces/marketplaces.js | 3 +- 3 files changed, 153 insertions(+), 141 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml index bef03bd4c1..f493747c5e 100644 --- a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml +++ b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml @@ -24,6 +24,7 @@ Rectangle { id: root; property string marketplaceUrl: ""; + property string entityId: ""; property string certificateId: ""; property string itemName: "--"; property string itemOwner: "--"; @@ -110,77 +111,81 @@ Rectangle { } onUpdateCertificateStatus: { - root.certificateStatus = certStatus; - if (root.certificateStatus === 1) { // CERTIFICATE_STATUS_VERIFICATION_SUCCESS - root.useGoldCert = true; - root.certTitleTextColor = hifi.colors.darkGray; - root.certTextColor = hifi.colors.white; - root.infoTextColor = hifi.colors.blueAccent; - titleBarText.text = "Certificate"; - popText.text = "PROOF OF PROVENANCE"; - showInMarketplaceButton.visible = true; - root.certInfoReplaceMode = 5; - // "Item Name" text will be set in "onCertificateInfoResult()" - // "Edition" text will be set in "onCertificateInfoResult()" - // "Owner" text will be set in "onCertificateInfoResult()" - // "Purchase Date" text will be set in "onCertificateInfoResult()" - // "Purchase Price" text will be set in "onCertificateInfoResult()" - errorText.text = ""; - } else if (root.certificateStatus === 2) { // CERTIFICATE_STATUS_VERIFICATION_TIMEOUT - root.useGoldCert = false; - root.certTitleTextColor = hifi.colors.redHighlight; - root.certTextColor = hifi.colors.redHighlight; - root.infoTextColor = hifi.colors.redHighlight; - titleBarText.text = "Request Timed Out"; - popText.text = ""; - showInMarketplaceButton.visible = false; - root.certInfoReplaceMode = 0; - root.itemName = ""; - root.itemEdition = ""; - root.itemOwner = ""; - root.dateOfPurchase = ""; - root.itemCost = ""; - errorText.text = "Your request to inspect this item timed out. Please try again later."; - } else if (root.certificateStatus === 3) { // CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED - root.useGoldCert = false; - root.certTitleTextColor = hifi.colors.redHighlight; - root.certTextColor = hifi.colors.redHighlight; - root.infoTextColor = hifi.colors.redHighlight; - titleBarText.text = "Certificate\nNo Longer Valid"; - popText.text = ""; - showInMarketplaceButton.visible = true; - root.certInfoReplaceMode = 5; - // "Item Name" text will be set in "onCertificateInfoResult()" - // "Edition" text will be set in "onCertificateInfoResult()" - // "Owner" text will be set in "onCertificateInfoResult()" - // "Purchase Date" text will be set in "onCertificateInfoResult()" - // "Purchase Price" text will be set in "onCertificateInfoResult()" - errorText.text = "The information associated with this item has been modified and it no longer matches the original certified item."; - } else if (root.certificateStatus === 4) { // CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED - root.useGoldCert = false; - root.certTitleTextColor = hifi.colors.redHighlight; - root.certTextColor = hifi.colors.redHighlight; - root.infoTextColor = hifi.colors.redHighlight; - titleBarText.text = "Invalid Certificate"; - popText.text = ""; - showInMarketplaceButton.visible = true; - root.certInfoReplaceMode = 4; - // "Item Name" text will be set in "onCertificateInfoResult()" - root.itemEdition = "Uncertified Copy" - // "Owner" text will be set in "onCertificateInfoResult()" - // "Purchase Date" text will be set in "onCertificateInfoResult()" - // "Purchase Price" text will be set in "onCertificateInfoResult()" - // "Error Text" text will be set in "onCertificateInfoResult()" - } else { - console.log("Unknown certificate status received from ledger signal!"); - } - - root.certificateStatusPending = false; - // We've gotten cert status - we are GO on getting the cert info - Commerce.certificateInfo(root.certificateId); + updateCertificateStatus(certStatus); } } + function updateCertificateStatus(status) { + root.certificateStatus = status; + if (root.certificateStatus === 1) { // CERTIFICATE_STATUS_VERIFICATION_SUCCESS + root.useGoldCert = true; + root.certTitleTextColor = hifi.colors.darkGray; + root.certTextColor = hifi.colors.white; + root.infoTextColor = hifi.colors.blueAccent; + titleBarText.text = "Certificate"; + popText.text = "PROOF OF PROVENANCE"; + showInMarketplaceButton.visible = true; + root.certInfoReplaceMode = 5; + // "Item Name" text will be set in "onCertificateInfoResult()" + // "Edition" text will be set in "onCertificateInfoResult()" + // "Owner" text will be set in "onCertificateInfoResult()" + // "Purchase Date" text will be set in "onCertificateInfoResult()" + // "Purchase Price" text will be set in "onCertificateInfoResult()" + errorText.text = ""; + } else if (root.certificateStatus === 2) { // CERTIFICATE_STATUS_VERIFICATION_TIMEOUT + root.useGoldCert = false; + root.certTitleTextColor = hifi.colors.redHighlight; + root.certTextColor = hifi.colors.redHighlight; + root.infoTextColor = hifi.colors.redHighlight; + titleBarText.text = "Request Timed Out"; + popText.text = ""; + showInMarketplaceButton.visible = false; + root.certInfoReplaceMode = 0; + root.itemName = ""; + root.itemEdition = ""; + root.itemOwner = ""; + root.dateOfPurchase = ""; + root.itemCost = ""; + errorText.text = "Your request to inspect this item timed out. Please try again later."; + } else if (root.certificateStatus === 3) { // CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED + root.useGoldCert = false; + root.certTitleTextColor = hifi.colors.redHighlight; + root.certTextColor = hifi.colors.redHighlight; + root.infoTextColor = hifi.colors.redHighlight; + titleBarText.text = "Certificate\nNo Longer Valid"; + popText.text = ""; + showInMarketplaceButton.visible = true; + root.certInfoReplaceMode = 5; + // "Item Name" text will be set in "onCertificateInfoResult()" + // "Edition" text will be set in "onCertificateInfoResult()" + // "Owner" text will be set in "onCertificateInfoResult()" + // "Purchase Date" text will be set in "onCertificateInfoResult()" + // "Purchase Price" text will be set in "onCertificateInfoResult()" + errorText.text = "The information associated with this item has been modified and it no longer matches the original certified item."; + } else if (root.certificateStatus === 4) { // CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED + root.useGoldCert = false; + root.certTitleTextColor = hifi.colors.redHighlight; + root.certTextColor = hifi.colors.redHighlight; + root.infoTextColor = hifi.colors.redHighlight; + titleBarText.text = "Invalid Certificate"; + popText.text = ""; + showInMarketplaceButton.visible = true; + root.certInfoReplaceMode = 4; + // "Item Name" text will be set in "onCertificateInfoResult()" + root.itemEdition = "Uncertified Copy" + // "Owner" text will be set in "onCertificateInfoResult()" + // "Purchase Date" text will be set in "onCertificateInfoResult()" + // "Purchase Price" text will be set in "onCertificateInfoResult()" + // "Error Text" text will be set in "onCertificateInfoResult()" + } else { + console.log("Unknown certificate status received from ledger signal!"); + } + + root.certificateStatusPending = false; + // We've gotten cert status - we are GO on getting the cert info + Commerce.certificateInfo(root.certificateId); + } + // This object is always used in a popup. // This MouseArea is used to prevent a user from being // able to click on a button/mouseArea underneath the popup. @@ -563,7 +568,12 @@ Rectangle { case 'inspectionCertificate_setCertificateId': resetCert(false); root.certificateId = message.certificateId; - sendToScript({method: 'inspectionCertificate_requestOwnershipVerification', certificateId: root.certificateId}); + if (message.entityId === "") { + updateCertificateStatus(1); // CERTIFICATE_STATUS_VERIFICATION_SUCCESS + } else { + root.entityId = message.entityId; + sendToScript({method: 'inspectionCertificate_requestOwnershipVerification', entity: root.entityId}); + } break; case 'inspectionCertificate_resetCert': resetCert(true); @@ -576,6 +586,7 @@ Rectangle { function resetCert(alsoResetCertID) { if (alsoResetCertID) { + root.entityId = ""; root.certificateId = ""; } root.certInfoReplaceMode = 5; diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index 4dacab8936..dd05e5c6a8 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -274,88 +274,88 @@ void ContextOverlayInterface::requestOwnershipVerification(const QUuid& entityID auto nodeList = DependencyManager::get(); - if (entityProperties.getClientOnly()) { - if (entityProperties.verifyStaticCertificateProperties()) { - SharedNodePointer entityServer = nodeList->soloNodeOfType(NodeType::EntityServer); + if (entityProperties.verifyStaticCertificateProperties()) { + if (entityProperties.getClientOnly()) { + SharedNodePointer entityServer = nodeList->soloNodeOfType(NodeType::EntityServer); - if (entityServer) { - QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); - QNetworkRequest networkRequest; - networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); - networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL(); - requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/transfer"); - QJsonObject request; - request["certificate_id"] = entityProperties.getCertificateID(); - networkRequest.setUrl(requestURL); + if (entityServer) { + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkRequest networkRequest; + networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL(); + requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/transfer"); + QJsonObject request; + request["certificate_id"] = entityProperties.getCertificateID(); + networkRequest.setUrl(requestURL); - QNetworkReply* networkReply = NULL; - networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson()); + QNetworkReply* networkReply = NULL; + networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson()); - connect(networkReply, &QNetworkReply::finished, [=]() { - QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object(); - jsonObject = jsonObject["data"].toObject(); + connect(networkReply, &QNetworkReply::finished, [=]() { + QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object(); + jsonObject = jsonObject["data"].toObject(); - if (networkReply->error() == QNetworkReply::NoError) { - if (!jsonObject["invalid_reason"].toString().isEmpty()) { - qCDebug(entities) << "invalid_reason not empty"; - } else if (jsonObject["transfer_status"].toArray().first().toString() == "failed") { - qCDebug(entities) << "'transfer_status' is 'failed'"; - } else if (jsonObject["transfer_status"].toArray().first().toString() == "pending") { - qCDebug(entities) << "'transfer_status' is 'pending'"; - } else { - QString ownerKey = jsonObject["transfer_recipient_key"].toString(); - - QByteArray certID = entityProperties.getCertificateID().toUtf8(); - QByteArray text = DependencyManager::get()->getTree()->computeNonce(certID, ownerKey); - QByteArray nodeToChallengeByteArray = entityProperties.getOwningAvatarID().toRfc4122(); - - int certIDByteArraySize = certID.length(); - int textByteArraySize = text.length(); - int nodeToChallengeByteArraySize = nodeToChallengeByteArray.length(); - - auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest, - certIDByteArraySize + textByteArraySize + nodeToChallengeByteArraySize + 3 * sizeof(int), - true); - challengeOwnershipPacket->writePrimitive(certIDByteArraySize); - challengeOwnershipPacket->writePrimitive(textByteArraySize); - challengeOwnershipPacket->writePrimitive(nodeToChallengeByteArraySize); - challengeOwnershipPacket->write(certID); - challengeOwnershipPacket->write(text); - challengeOwnershipPacket->write(nodeToChallengeByteArray); - nodeList->sendPacket(std::move(challengeOwnershipPacket), *entityServer); - - // Kickoff a 10-second timeout timer that marks the cert if we don't get an ownership response in time - if (thread() != QThread::currentThread()) { - QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer"); - return; + if (networkReply->error() == QNetworkReply::NoError) { + if (!jsonObject["invalid_reason"].toString().isEmpty()) { + qCDebug(entities) << "invalid_reason not empty"; + } else if (jsonObject["transfer_status"].toArray().first().toString() == "failed") { + qCDebug(entities) << "'transfer_status' is 'failed'"; + } else if (jsonObject["transfer_status"].toArray().first().toString() == "pending") { + qCDebug(entities) << "'transfer_status' is 'pending'"; } else { - startChallengeOwnershipTimer(); - } - } - } else { - qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << - "More info:" << networkReply->readAll(); - } + QString ownerKey = jsonObject["transfer_recipient_key"].toString(); - networkReply->deleteLater(); - }); - } else { - qCWarning(context_overlay) << "Couldn't get Entity Server!"; - } + QByteArray certID = entityProperties.getCertificateID().toUtf8(); + QByteArray text = DependencyManager::get()->getTree()->computeNonce(certID, ownerKey); + QByteArray nodeToChallengeByteArray = entityProperties.getOwningAvatarID().toRfc4122(); + + int certIDByteArraySize = certID.length(); + int textByteArraySize = text.length(); + int nodeToChallengeByteArraySize = nodeToChallengeByteArray.length(); + + auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest, + certIDByteArraySize + textByteArraySize + nodeToChallengeByteArraySize + 3 * sizeof(int), + true); + challengeOwnershipPacket->writePrimitive(certIDByteArraySize); + challengeOwnershipPacket->writePrimitive(textByteArraySize); + challengeOwnershipPacket->writePrimitive(nodeToChallengeByteArraySize); + challengeOwnershipPacket->write(certID); + challengeOwnershipPacket->write(text); + challengeOwnershipPacket->write(nodeToChallengeByteArray); + nodeList->sendPacket(std::move(challengeOwnershipPacket), *entityServer); + + // Kickoff a 10-second timeout timer that marks the cert if we don't get an ownership response in time + if (thread() != QThread::currentThread()) { + QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer"); + return; + } else { + startChallengeOwnershipTimer(); + } + } + } else { + qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << + "More info:" << networkReply->readAll(); + } + + networkReply->deleteLater(); + }); + } else { + qCWarning(context_overlay) << "Couldn't get Entity Server!"; + } } else { + // We don't currently verify ownership of entities that aren't Avatar Entities, + // so they always pass Ownership Verification. It's necessary to emit this signal + // so that the Inspection Certificate can continue its information-grabbing process. auto ledger = DependencyManager::get(); - _challengeOwnershipTimeoutTimer.stop(); - emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED)); - emit DependencyManager::get()->ownershipVerificationFailed(_lastInspectedEntity); - qCDebug(context_overlay) << "Entity" << _lastInspectedEntity << "failed static certificate verification!"; + emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS)); } } else { - // We don't currently verify ownership of entities that aren't Avatar Entities, - // so they always pass Ownership Verification. It's necessary to emit this signal - // so that the Inspection Certificate can continue its information-grabbing process. auto ledger = DependencyManager::get(); - emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS)); + _challengeOwnershipTimeoutTimer.stop(); + emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED)); + emit DependencyManager::get()->ownershipVerificationFailed(_lastInspectedEntity); + qCDebug(context_overlay) << "Entity" << _lastInspectedEntity << "failed static certificate verification!"; } } diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index cec139faae..fd1275a251 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -163,6 +163,7 @@ var selectionDisplay = null; // for gridTool.js to ignore var certificateId = itemCertificateId || (Entities.getEntityProperties(currentEntityWithContextOverlay, ['certificateID']).certificateID); tablet.sendToQml({ method: 'inspectionCertificate_setCertificateId', + entityId: currentEntityWithContextOverlay, certificateId: certificateId }); } @@ -584,7 +585,7 @@ var selectionDisplay = null; // for gridTool.js to ignore tablet.gotoHomeScreen(); break; case 'inspectionCertificate_requestOwnershipVerification': - ContextOverlay.requestOwnershipVerification(message.certificateId); + ContextOverlay.requestOwnershipVerification(message.entity); break; case 'inspectionCertificate_showInMarketplaceClicked': tablet.gotoWebScreen(message.marketplaceUrl, MARKETPLACES_INJECT_SCRIPT_URL); From 725eb1416370efac3b40883455be10cce9662fc9 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Wed, 7 Feb 2018 16:43:15 -0800 Subject: [PATCH 07/49] Fix for deadlock triggering while loading QML engine --- interface/src/Application.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 3e7dd3e223..c22a370b1f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2404,7 +2404,9 @@ void Application::initializeUi() { tabletScriptingInterface->getTablet(SYSTEM_TABLET); } auto offscreenUi = DependencyManager::get(); + DeadlockWatchdogThread::pause(); offscreenUi->create(); + DeadlockWatchdogThread::resume(); auto surfaceContext = offscreenUi->getSurfaceContext(); From d2f5645f96e5b1b36b7258b6952bac4521a9a298 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Wed, 7 Feb 2018 15:27:34 -0800 Subject: [PATCH 08/49] Don't trigger a backtrace exception on quitting while in HMD --- interface/src/Application_render.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Application_render.cpp b/interface/src/Application_render.cpp index e1f198eed2..5cc072df37 100644 --- a/interface/src/Application_render.cpp +++ b/interface/src/Application_render.cpp @@ -55,7 +55,7 @@ void Application::paintGL() { // If a display plugin loses it's underlying support, it // needs to be able to signal us to not use it if (!displayPlugin->beginFrameRender(_renderFrameCount)) { - updateDisplayMode(); + QMetaObject::invokeMethod(this, "updateDisplayMode"); return; } } From 7eecc4767274a3e1aeba90b5670f963a3f0d7453 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Thu, 8 Feb 2018 10:23:25 -0800 Subject: [PATCH 09/49] Prevent deadlock crashes when building shaders at startup --- interface/src/Application.cpp | 45 +++++++++++++++++------------------ 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c22a370b1f..be2a54b8e9 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -318,7 +318,7 @@ static QTimer pingTimer; static bool DISABLE_WATCHDOG = true; #else static const QString DISABLE_WATCHDOG_FLAG{ "HIFI_DISABLE_WATCHDOG" }; -static bool DISABLE_WATCHDOG = QProcessEnvironment::systemEnvironment().contains(DISABLE_WATCHDOG_FLAG); +static bool DISABLE_WATCHDOG = nsightActive() || QProcessEnvironment::systemEnvironment().contains(DISABLE_WATCHDOG_FLAG); #endif #if defined(USE_GLES) @@ -415,20 +415,26 @@ public: *crashTrigger = 0xDEAD10CC; } + static void withPause(const std::function& lambda) { + pause(); + lambda(); + resume(); + } static void pause() { _paused = true; } static void resume() { - _paused = false; + // Update the heartbeat BEFORE resuming the checks updateHeartbeat(); + _paused = false; } void run() override { while (!_quit) { QThread::sleep(HEARTBEAT_UPDATE_INTERVAL_SECS); // Don't do heartbeat detection under nsight - if (nsightActive() || _paused) { + if (_paused) { continue; } uint64_t lastHeartbeat = _heartbeat; // sample atomic _heartbeat, because we could context switch away and have it updated on us @@ -2283,29 +2289,22 @@ void Application::initializeGL() { initDisplay(); qCDebug(interfaceapp, "Initialized Display."); -#ifdef Q_OS_OSX - // FIXME: on mac os the shaders take up to 1 minute to compile, so we pause the deadlock watchdog thread. - DeadlockWatchdogThread::pause(); -#endif - - // Set up the render engine - render::CullFunctor cullFunctor = LODManager::shouldRender; - static const QString RENDER_FORWARD = "HIFI_RENDER_FORWARD"; - _renderEngine->addJob("UpdateScene"); + // FIXME: on low end systems os the shaders take up to 1 minute to compile, so we pause the deadlock watchdog thread. + DeadlockWatchdogThread::withPause([&] { + // Set up the render engine + render::CullFunctor cullFunctor = LODManager::shouldRender; + static const QString RENDER_FORWARD = "HIFI_RENDER_FORWARD"; + _renderEngine->addJob("UpdateScene"); #ifndef Q_OS_ANDROID - _renderEngine->addJob("SecondaryCameraJob", cullFunctor, !DISABLE_DEFERRED); + _renderEngine->addJob("SecondaryCameraJob", cullFunctor, !DISABLE_DEFERRED); #endif - _renderEngine->addJob("RenderMainView", cullFunctor, !DISABLE_DEFERRED, render::ItemKey::TAG_BITS_0, render::ItemKey::TAG_BITS_0); + _renderEngine->addJob("RenderMainView", cullFunctor, !DISABLE_DEFERRED, render::ItemKey::TAG_BITS_0, render::ItemKey::TAG_BITS_0); + _renderEngine->load(); + _renderEngine->registerScene(_main3DScene); - _renderEngine->load(); - _renderEngine->registerScene(_main3DScene); - - // Now that OpenGL is initialized, we are sure we have a valid context and can create the various pipeline shaders with success. - DependencyManager::get()->initializeShapePipelines(); - -#ifdef Q_OS_OSX - DeadlockWatchdogThread::resume(); -#endif + // Now that OpenGL is initialized, we are sure we have a valid context and can create the various pipeline shaders with success. + DependencyManager::get()->initializeShapePipelines(); + }); _offscreenContext = new OffscreenGLCanvas(); _offscreenContext->setObjectName("MainThreadContext"); From 13ce6bbabd3c979fae0df3de03185ae91dc9c64f Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 6 Feb 2018 13:44:59 -0800 Subject: [PATCH 10/49] fix lasers going to origin --- scripts/system/libraries/pointersUtils.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/scripts/system/libraries/pointersUtils.js b/scripts/system/libraries/pointersUtils.js index 2af563f8d4..53959b91f8 100644 --- a/scripts/system/libraries/pointersUtils.js +++ b/scripts/system/libraries/pointersUtils.js @@ -30,7 +30,6 @@ Pointer = function(hudLayer, pickType, pointerData) { ignoreRayIntersection: true, // always ignore this drawInFront: !hudLayer, // Even when burried inside of something, show it. drawHUDLayer: hudLayer, - parentID: MyAvatar.SELF_ID }; this.halfEnd = { type: "sphere", @@ -53,7 +52,6 @@ Pointer = function(hudLayer, pickType, pointerData) { ignoreRayIntersection: true, // always ignore this drawInFront: !hudLayer, // Even when burried inside of something, show it. drawHUDLayer: hudLayer, - parentID: MyAvatar.SELF_ID }; this.fullEnd = { type: "sphere", @@ -76,7 +74,6 @@ Pointer = function(hudLayer, pickType, pointerData) { ignoreRayIntersection: true, // always ignore this drawInFront: !hudLayer, // Even when burried inside of something, show it. drawHUDLayer: hudLayer, - parentID: MyAvatar.SELF_ID }; this.renderStates = [ From 7c21db93a3c061b93c2cee6118baf87db2837be3 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 8 Feb 2018 17:08:51 -0800 Subject: [PATCH 11/49] fixing case when grabbed target is destroyed --- .../controllerModules/farActionGrabEntity.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index 32bf7316a9..b72a38f986 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -208,7 +208,7 @@ Script.include("/~/system/libraries/Xform.js"); var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); - var grabbedProperties = Entities.getEntityProperties(this.grabbedThingID, ["position"]); + var grabbedProperties = Entities.getEntityProperties(this.grabbedThingID, GRABBABLE_PROPERTIES); var now = Date.now(); var deltaObjectTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds this.currentObjectTime = now; @@ -369,6 +369,14 @@ Script.include("/~/system/libraries/Xform.js"); } }; + this.targetIsNull = function() { + var properties = Entities.getEntityProperties(this.grabbedThingID); + if (Object.keys(properties).length === 0 && this.distanceHolding) { + return true; + } + return false; + } + this.isReady = function (controllerData) { if (HMD.active) { if (this.notPointingAtEntity(controllerData)) { @@ -391,7 +399,7 @@ Script.include("/~/system/libraries/Xform.js"); this.run = function (controllerData) { if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || - this.notPointingAtEntity(controllerData)) { + this.notPointingAtEntity(controllerData) || this.targetIsNull()) { this.endNearGrabAction(); return makeRunningValues(false, [], []); } From 6471780e210e52cac921c02cdc1bbafcde4e8575 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 8 Feb 2018 17:17:04 -0800 Subject: [PATCH 12/49] allow overlay children to follow avatar from one domain to another --- interface/src/ui/overlays/Base3DOverlay.cpp | 2 ++ interface/src/ui/overlays/Line3DOverlay.cpp | 2 +- libraries/avatars/src/AvatarData.h | 6 +++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index 23e09fe5ca..ff5a202910 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -181,6 +181,8 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) { if (properties["parentID"].isValid()) { setParentID(QUuid(properties["parentID"].toString())); + bool success; + getParentPointer(success); // call this to hook-up the parent's back-pointers to its child overlays needRenderItemUpdate = true; } if (properties["parentJointIndex"].isValid()) { diff --git a/interface/src/ui/overlays/Line3DOverlay.cpp b/interface/src/ui/overlays/Line3DOverlay.cpp index 7200abf74e..c2e5ad1fb4 100644 --- a/interface/src/ui/overlays/Line3DOverlay.cpp +++ b/interface/src/ui/overlays/Line3DOverlay.cpp @@ -63,7 +63,7 @@ glm::vec3 Line3DOverlay::getEnd() const { localEnd = getLocalEnd(); worldEnd = localToWorld(localEnd, getParentID(), getParentJointIndex(), getScalesWithParent(), success); if (!success) { - qDebug() << "Line3DOverlay::getEnd failed"; + qDebug() << "Line3DOverlay::getEnd failed, parentID = " << getParentID(); } return worldEnd; } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index a363fb6d15..f24bd51bde 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -707,7 +707,11 @@ public slots: void setJointMappingsFromNetworkReply(); void setSessionUUID(const QUuid& sessionUUID) { if (sessionUUID != getID()) { - setID(sessionUUID); + if (sessionUUID == QUuid()) { + setID(AVATAR_SELF_ID); + } else { + setID(sessionUUID); + } emit sessionUUIDChanged(); } } From a08770c816e46a2d0c2f13e1c4c92076ac130b91 Mon Sep 17 00:00:00 2001 From: humbletim Date: Fri, 9 Feb 2018 02:14:32 -0500 Subject: [PATCH 13/49] cleanup --- libraries/fbx/src/OBJWriter.cpp | 6 +- .../graphics-scripting/BufferViewHelpers.cpp | 55 ++ .../graphics-scripting/BufferViewHelpers.h | 9 +- .../ModelScriptingInterface.cpp | 641 ++++-------------- .../ModelScriptingInterface.h | 16 +- .../src/graphics-scripting/ScriptableMesh.cpp | 403 ++++++++++- .../src/graphics-scripting/ScriptableMesh.h | 123 ++-- .../src/graphics-scripting/ScriptableModel.h | 67 +- libraries/render-utils/src/Model.cpp | 58 +- 9 files changed, 722 insertions(+), 656 deletions(-) diff --git a/libraries/fbx/src/OBJWriter.cpp b/libraries/fbx/src/OBJWriter.cpp index 37bced8458..5307f49f36 100644 --- a/libraries/fbx/src/OBJWriter.cpp +++ b/libraries/fbx/src/OBJWriter.cpp @@ -15,6 +15,9 @@ #include "OBJWriter.h" #include "ModelFormatLogging.h" +// FIXME: should this live in shared? (it depends on gpu/) +#include <../graphics-scripting/src/graphics-scripting/BufferViewHelpers.h> + static QString formatFloat(double n) { // limit precision to 6, but don't output trailing zeros. QString s = QString::number(n, 'f', 6); @@ -91,7 +94,8 @@ bool writeOBJToTextStream(QTextStream& out, QList meshes) { const gpu::BufferView& normalsBufferView = mesh->getAttributeBuffer(gpu::Stream::InputSlot::NORMAL); gpu::BufferView::Index numNormals = (gpu::BufferView::Index)normalsBufferView.getNumElements(); for (gpu::BufferView::Index i = 0; i < numNormals; i++) { - glm::vec3 normal = normalsBufferView.get(i); + glm::vec3 normal = glmVecFromVariant(bufferViewElementToVariant(normalsBufferView, i)); + //glm::vec3 normal = normalsBufferView.get(i); out << "vn "; out << formatFloat(normal[0]) << " "; out << formatFloat(normal[1]) << " "; diff --git a/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.cpp b/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.cpp index e865ed0e5a..d83322f360 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.cpp @@ -8,9 +8,15 @@ #include #include +namespace glm { + using hvec2 = glm::tvec2; + using hvec4 = glm::tvec4; +} +//#define DEBUG_BUFFERVIEW_SCRIPTING #ifdef DEBUG_BUFFERVIEW_SCRIPTING #include "DebugNames.h" + QLoggingCategory bufferview_helpers{"hifi.bufferview"}; #endif namespace { @@ -61,6 +67,9 @@ bool bufferViewElementFromVariant(const gpu::BufferView& view, quint32 index, co const auto dataType = element.getType(); const auto byteLength = element.getSize(); const auto BYTES_PER_ELEMENT = byteLength / vecN; +#ifdef DEBUG_BUFFERVIEW_SCRIPTING + qCDebug(bufferview_helpers) << "bufferViewElementFromVariant" << index << DebugNames::stringFrom(dataType) << BYTES_PER_ELEMENT << vecN; +#endif if (BYTES_PER_ELEMENT == 1) { switch(vecN) { case 2: setBufferViewElement(view, index, v); return true; @@ -71,16 +80,25 @@ bool bufferViewElementFromVariant(const gpu::BufferView& view, quint32 index, co glm::uint32 unused; packNormalAndTangent(glmVecFromVariant(v), glm::vec3(), rawColor, unused); view.edit(index) = rawColor; + return true; } else if (element == gpu::Element::VEC4F_NORMALIZED_XYZ10W2) { glm::uint32 packedNormal;// = glm::packSnorm3x10_1x2(glm::vec4(glmVecFromVariant(v), 0.0f)); glm::uint32 unused; packNormalAndTangent(glm::vec3(), glmVecFromVariant(v), unused, packedNormal); view.edit(index) = packedNormal; + return true; } setBufferViewElement(view, index, v); return true; } } } else if (BYTES_PER_ELEMENT == 2) { + if (dataType == gpu::HALF) { + switch(vecN) { + case 2: view.edit(index) = glm::packSnorm2x8(glmVecFromVariant(v)); return true; + case 4: view.edit(index) = glm::packSnorm4x8(glmVecFromVariant(v)); return true; + default: return false; + } + } switch(vecN) { case 2: setBufferViewElement(view, index, v); return true; case 3: setBufferViewElement(view, index, v); return true; @@ -112,6 +130,9 @@ QVariant bufferViewElementToVariant(const gpu::BufferView& view, quint32 index, const auto BYTES_PER_ELEMENT = byteLength / vecN; Q_ASSERT(index < view.getNumElements()); Q_ASSERT(index * vecN * BYTES_PER_ELEMENT < (view._size - vecN * BYTES_PER_ELEMENT)); +#ifdef DEBUG_BUFFERVIEW_SCRIPTING + qCDebug(bufferview_helpers) << "bufferViewElementToVariant" << index << DebugNames::stringFrom(dataType) << BYTES_PER_ELEMENT << vecN; +#endif if (BYTES_PER_ELEMENT == 1) { switch(vecN) { case 2: return getBufferViewElement(view, index, asArray); @@ -129,6 +150,12 @@ QVariant bufferViewElementToVariant(const gpu::BufferView& view, quint32 index, } } } else if (BYTES_PER_ELEMENT == 2) { + if (dataType == gpu::HALF) { + switch(vecN) { + case 2: return glmVecToVariant(glm::vec2(glm::unpackSnorm2x8(view.get(index)))); + case 4: return glmVecToVariant(glm::vec4(glm::unpackSnorm4x8(view.get(index)))); + } + } switch(vecN) { case 2: return getBufferViewElement(view, index, asArray); case 3: return getBufferViewElement(view, index, asArray); @@ -193,3 +220,31 @@ const T glmVecFromVariant(const QVariant& v) { return result; } +template +gpu::BufferView bufferViewFromVector(QVector elements, gpu::Element elementType) { + auto vertexBuffer = std::make_shared(elements.size() * sizeof(T), (gpu::Byte*)elements.data()); + return { vertexBuffer, 0, vertexBuffer->getSize(),sizeof(T), elementType }; +} + +template<> gpu::BufferView bufferViewFromVector(QVector elements, gpu::Element elementType) { return bufferViewFromVector(elements, elementType); } +template<> gpu::BufferView bufferViewFromVector(QVector elements, gpu::Element elementType) { return bufferViewFromVector(elements, elementType); } + +gpu::BufferView cloneBufferView(const gpu::BufferView& input) { + return gpu::BufferView( + std::make_shared(input._buffer->getSize(), input._buffer->getData()), + input._offset, input._size, input._stride, input._element + ); +} + +gpu::BufferView resizedBufferView(const gpu::BufferView& input, quint32 numElements) { + auto effectiveSize = input._buffer->getSize() / input.getNumElements(); + qDebug() << "resize input" << input.getNumElements() << input._buffer->getSize() << "effectiveSize" << effectiveSize; + auto vsize = input._element.getSize() * numElements; + gpu::Byte *data = new gpu::Byte[vsize]; + memset(data, 0, vsize); + auto buffer = new gpu::Buffer(vsize, (gpu::Byte*)data); + delete[] data; + auto output = gpu::BufferView(buffer, input._element); + qDebug() << "resized output" << output.getNumElements() << output._buffer->getSize(); + return output; +} diff --git a/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h b/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h index 0fe2602f6c..d0d42ca419 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h +++ b/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h @@ -8,11 +8,18 @@ #include -namespace gpu { class BufferView; } +namespace gpu { + class BufferView; + class Element; +} template QVariant glmVecToVariant(const T& v, bool asArray = false); template const T glmVecFromVariant(const QVariant& v); QVariant bufferViewElementToVariant(const gpu::BufferView& view, quint32 index, bool asArray = false, const char* hint = ""); bool bufferViewElementFromVariant(const gpu::BufferView& view, quint32 index, const QVariant& v); +template gpu::BufferView bufferViewFromVector(QVector elements, gpu::Element elementType); + +gpu::BufferView cloneBufferView(const gpu::BufferView& input); +gpu::BufferView resizedBufferView(const gpu::BufferView& input, quint32 numElements); diff --git a/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp index 68a00bc02c..ab85fb8265 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp @@ -17,27 +17,16 @@ #include "BaseScriptEngine.h" #include "ScriptEngineLogging.h" #include "OBJWriter.h" -#include "OBJReader.h" -//#include "ui/overlays/Base3DOverlay.h" -//#include "EntityTreeRenderer.h" -//#include "avatar/AvatarManager.h" -//#include "RenderableEntityItem.h" -#include #include - #include - #include - #include + #include "BufferViewScripting.h" - #include "ScriptableMesh.h" -using ScriptableMesh = scriptable::ScriptableMesh; - #include "ModelScriptingInterface.moc" namespace { @@ -50,13 +39,56 @@ ModelScriptingInterface::ModelScriptingInterface(QObject* parent) : QObject(pare } } +void ModelScriptingInterface::getMeshes(QUuid uuid, QScriptValue scopeOrCallback, QScriptValue methodOrName) { + auto handler = makeScopedHandlerObject(scopeOrCallback, methodOrName); + Q_ASSERT(handler.engine() == this->engine()); + QPointer engine = dynamic_cast(handler.engine()); + + scriptable::ScriptableModel* meshes{ nullptr }; + bool success = false; + QString error; + + auto appProvider = DependencyManager::get(); + qDebug() << "appProvider" << appProvider.data(); + scriptable::ModelProviderPointer provider = appProvider ? appProvider->lookupModelProvider(uuid) : nullptr; + QString providerType = provider ? provider->metadata.value("providerType").toString() : QString(); + if (providerType.isEmpty()) { + providerType = "unknown"; + } + if (provider) { + qCDebug(model_scripting) << "fetching meshes from " << providerType << "..."; + auto scriptableMeshes = provider->getScriptableModel(&success); + qCDebug(model_scripting) << "//fetched meshes from " << providerType << "success:" <objectName().isEmpty()) { + meshes->setObjectName(providerType+"::meshes"); + } + if (meshes->objectID.isNull()) { + meshes->objectID = uuid.toString(); + } + meshes->metadata["provider"] = provider->metadata; + } + } + if (!success) { + error = QString("failed to get meshes from %1 provider for uuid %2").arg(providerType).arg(uuid.toString()); + } + + if (!error.isEmpty()) { + qCWarning(model_scripting) << "ModelScriptingInterface::getMeshes ERROR" << error; + callScopedHandlerObject(handler, engine->makeError(error), QScriptValue::NullValue); + } else { + callScopedHandlerObject(handler, QScriptValue::NullValue, engine->newQObject(meshes, QScriptEngine::ScriptOwnership)); + } +} + QString ModelScriptingInterface::meshToOBJ(const scriptable::ScriptableModel& _in) { - const auto& in = _in.getMeshes(); + const auto& in = _in.getConstMeshes(); qCDebug(model_scripting) << "meshToOBJ" << in.size(); if (in.size()) { QList meshes; - foreach (const auto meshProxy, in) { - qCDebug(model_scripting) << "meshToOBJ" << meshProxy.get(); + foreach (auto meshProxy, in) { + qCDebug(model_scripting) << "meshToOBJ" << meshProxy; if (meshProxy) { meshes.append(getMeshPointer(meshProxy)); } @@ -77,7 +109,7 @@ QScriptValue ModelScriptingInterface::appendMeshes(scriptable::ScriptableModel _ size_t totalColorCount { 0 }; size_t totalNormalCount { 0 }; size_t totalIndexCount { 0 }; - foreach (const scriptable::ScriptableMeshPointer meshProxy, in) { + foreach (auto& meshProxy, in) { scriptable::MeshPointer mesh = getMeshPointer(meshProxy); totalVertexCount += mesh->getNumVertices(); @@ -113,7 +145,7 @@ QScriptValue ModelScriptingInterface::appendMeshes(scriptable::ScriptableModel _ uint32_t indexStartOffset { 0 }; - foreach (const scriptable::ScriptableMeshPointer meshProxy, in) { + foreach (const auto& meshProxy, in) { scriptable::MeshPointer mesh = getMeshPointer(meshProxy); mesh->forEach( [&](glm::vec3 position){ @@ -175,8 +207,7 @@ QScriptValue ModelScriptingInterface::appendMeshes(scriptable::ScriptableModel _ (gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL)); - scriptable::ScriptableMeshPointer resultProxy = scriptable::ScriptableMeshPointer(new ScriptableMesh(nullptr, result)); - return engine()->toScriptValue(result); + return engine()->toScriptValue(scriptable::ScriptableMeshPointer(new scriptable::ScriptableMesh(nullptr, result))); } QScriptValue ModelScriptingInterface::transformMesh(scriptable::ScriptableMeshPointer meshProxy, glm::mat4 transform) { @@ -189,7 +220,7 @@ QScriptValue ModelScriptingInterface::transformMesh(scriptable::ScriptableMeshPo [&](glm::vec3 color){ return color; }, [&](glm::vec3 normal){ return glm::vec3(transform * glm::vec4(normal, 0.0f)); }, [&](uint32_t index){ return index; }); - scriptable::ScriptableMeshPointer resultProxy = scriptable::ScriptableMeshPointer(new ScriptableMesh(nullptr, result)); + scriptable::ScriptableMeshPointer resultProxy = scriptable::ScriptableMeshPointer(new scriptable::ScriptableMesh(nullptr, result)); return engine()->toScriptValue(resultProxy); } @@ -201,8 +232,11 @@ QScriptValue ModelScriptingInterface::getVertexCount(scriptable::ScriptableMeshP return (uint32_t)mesh->getNumVertices(); } -QScriptValue ModelScriptingInterface::getVertex(scriptable::ScriptableMeshPointer meshProxy, mesh::uint32 vertexIndex) { +QScriptValue ModelScriptingInterface::getVertex(scriptable::ScriptableMeshPointer meshProxy, quint32 vertexIndex) { auto mesh = getMeshPointer(meshProxy); + if (!mesh) { + return QScriptValue(); + } const gpu::BufferView& vertexBufferView = mesh->getVertexBuffer(); auto numVertices = mesh->getNumVertices(); @@ -266,480 +300,46 @@ QScriptValue ModelScriptingInterface::newMesh(const QVector& vertices - scriptable::ScriptableMeshPointer meshProxy = scriptable::ScriptableMeshPointer(new ScriptableMesh(nullptr, mesh)); + scriptable::ScriptableMeshPointer meshProxy = scriptable::ScriptableMeshPointer(new scriptable::ScriptableMesh(nullptr, mesh)); return engine()->toScriptValue(meshProxy); } -QScriptValue ModelScriptingInterface::mapAttributeValues( - QScriptValue _in, - QScriptValue scopeOrCallback, - QScriptValue methodOrName - ) { - qCInfo(model_scripting) << "mapAttributeValues" << _in.toVariant().typeName() << _in.toVariant().toString() << _in.toQObject(); - auto in = qscriptvalue_cast(_in).getMeshes(); - if (in.size()) { - foreach (scriptable::ScriptableMeshPointer meshProxy, in) { - mapMeshAttributeValues(meshProxy, scopeOrCallback, methodOrName); - } - return thisObject(); - } else if (auto meshProxy = qobject_cast(_in.toQObject())) { - return mapMeshAttributeValues(meshProxy->shared_from_this(), scopeOrCallback, methodOrName); - } else { - context()->throwError("invalid ModelProxy || MeshProxyPointer"); - } - return false; -} - - -QScriptValue ModelScriptingInterface::unrollVertices(scriptable::ScriptableMeshPointer meshProxy, bool recalcNormals) { - auto mesh = getMeshPointer(meshProxy); - qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices" << !!mesh<< !!meshProxy; - if (!mesh) { - return QScriptValue(); - } - - auto positions = mesh->getVertexBuffer(); - auto indices = mesh->getIndexBuffer(); - quint32 numPoints = (quint32)indices.getNumElements(); - auto buffer = new gpu::Buffer(); - buffer->resize(numPoints * sizeof(uint32_t)); - auto newindices = gpu::BufferView(buffer, { gpu::SCALAR, gpu::UINT32, gpu::INDEX }); - qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices numPoints" << numPoints; - auto attributeViews = ScriptableMesh::gatherBufferViews(mesh); - for (const auto& a : attributeViews) { - auto& view = a.second; - auto sz = view._element.getSize(); - auto buffer = new gpu::Buffer(); - buffer->resize(numPoints * sz); - auto points = gpu::BufferView(buffer, view._element); - auto src = (uint8_t*)view._buffer->getData(); - auto dest = (uint8_t*)points._buffer->getData(); - auto slot = ScriptableMesh::ATTRIBUTES[a.first]; - qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices buffer" << a.first; - qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices source" << view.getNumElements(); - qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices dest" << points.getNumElements(); - qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices sz" << sz << src << dest << slot; - auto esize = indices._element.getSize(); - const char* hint= a.first.toStdString().c_str(); - for(quint32 i = 0; i < numPoints; i++) { - quint32 index = esize == 4 ? indices.get(i) : indices.get(i); - newindices.edit(i) = i; - bufferViewElementFromVariant( - points, i, - bufferViewElementToVariant(view, index, false, hint) - ); - } - if (slot == gpu::Stream::POSITION) { - mesh->setVertexBuffer(points); - } else { - mesh->addAttribute(slot, points); - } - } - mesh->setIndexBuffer(newindices); - if (recalcNormals) { - recalculateNormals(meshProxy); - } - return true; -} - namespace { - template - gpu::BufferView bufferViewFromVector(QVector elements, gpu::Element elementType) { - auto vertexBuffer = std::make_shared( - elements.size() * sizeof(T), - (gpu::Byte*)elements.data() - ); - return { vertexBuffer, 0, vertexBuffer->getSize(),sizeof(T), elementType }; - } - - gpu::BufferView cloneBufferView(const gpu::BufferView& input) { - //qCInfo(model_scripting) << "input" << input.getNumElements() << input._buffer->getSize(); - auto output = gpu::BufferView( - std::make_shared(input._buffer->getSize(), input._buffer->getData()), - input._offset, - input._size, - input._stride, - input._element - ); - //qCInfo(model_scripting) << "after" << output.getNumElements() << output._buffer->getSize(); - return output; - } - - gpu::BufferView resizedBufferView(const gpu::BufferView& input, quint32 numElements) { - auto effectiveSize = input._buffer->getSize() / input.getNumElements(); - qCInfo(model_scripting) << "resize input" << input.getNumElements() << input._buffer->getSize() << "effectiveSize" << effectiveSize; - auto vsize = input._element.getSize() * numElements; - gpu::Byte *data = new gpu::Byte[vsize]; - memset(data, 0, vsize); - auto buffer = new gpu::Buffer(vsize, (gpu::Byte*)data); - delete[] data; - auto output = gpu::BufferView(buffer, input._element); - qCInfo(model_scripting) << "resized output" << output.getNumElements() << output._buffer->getSize(); - return output; - } -} - -bool ModelScriptingInterface::replaceMeshData(scriptable::ScriptableMeshPointer dest, scriptable::ScriptableMeshPointer src, const QVector& attributeNames) { - auto target = getMeshPointer(dest); - auto source = getMeshPointer(src); - if (!target || !source) { - context()->throwError("ModelScriptingInterface::replaceMeshData -- expected dest and src to be valid mesh proxy pointers"); - return false; - } - - QVector attributes = attributeNames.isEmpty() ? src->getAttributeNames() : attributeNames; - - //qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData -- source:" << source->displayName << "target:" << target->displayName << "attributes:" << attributes; - - // remove attributes only found on target mesh, unless user has explicitly specified the relevant attribute names - if (attributeNames.isEmpty()) { - auto attributeViews = ScriptableMesh::gatherBufferViews(target); - for (const auto& a : attributeViews) { - auto slot = ScriptableMesh::ATTRIBUTES[a.first]; - if (!attributes.contains(a.first)) { - //qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData -- pruning target attribute" << a.first << slot; - target->removeAttribute(slot); - } + QScriptValue meshPointerToScriptValue(QScriptEngine* engine, scriptable::ScriptableMeshPointer const &in) { + if (!in) { + return QScriptValue::NullValue; } + return engine->newQObject(in, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects); } - target->setVertexBuffer(cloneBufferView(source->getVertexBuffer())); - target->setIndexBuffer(cloneBufferView(source->getIndexBuffer())); - target->setPartBuffer(cloneBufferView(source->getPartBuffer())); - - for (const auto& a : attributes) { - auto slot = ScriptableMesh::ATTRIBUTES[a]; - if (slot == gpu::Stream::POSITION) { - continue; - } - // auto& before = target->getAttributeBuffer(slot); - auto& input = source->getAttributeBuffer(slot); - if (input.getNumElements() == 0) { - //qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData buffer is empty -- pruning" << a << slot; - target->removeAttribute(slot); - } else { - // if (before.getNumElements() == 0) { - // qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData target buffer is empty -- adding" << a << slot; - // } else { - // qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData target buffer exists -- updating" << a << slot; - // } - target->addAttribute(slot, cloneBufferView(input)); - } - // auto& after = target->getAttributeBuffer(slot); - // qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData" << a << slot << before.getNumElements() << " -> " << after.getNumElements(); - } - - - return true; -} - -bool ModelScriptingInterface::dedupeVertices(scriptable::ScriptableMeshPointer meshProxy, float epsilon) { - auto mesh = getMeshPointer(meshProxy); - if (!mesh) { - return false; - } - auto positions = mesh->getVertexBuffer(); - auto numPositions = positions.getNumElements(); - const auto epsilon2 = epsilon*epsilon; - - QVector uniqueVerts; - uniqueVerts.reserve((int)numPositions); - QMap remapIndices; - - for (quint32 i = 0; i < numPositions; i++) { - const quint32 numUnique = uniqueVerts.size(); - const auto& position = positions.get(i); - bool unique = true; - for (quint32 j = 0; j < numUnique; j++) { - if (glm::length2(uniqueVerts[j] - position) <= epsilon2) { - remapIndices[i] = j; - unique = false; - break; - } - } - if (unique) { - uniqueVerts << position; - remapIndices[i] = numUnique; - } - } - - qCInfo(model_scripting) << "//VERTS before" << numPositions << "after" << uniqueVerts.size(); - - auto indices = mesh->getIndexBuffer(); - auto numIndices = indices.getNumElements(); - auto esize = indices._element.getSize(); - QVector newIndices; - newIndices.reserve((int)numIndices); - for (quint32 i = 0; i < numIndices; i++) { - quint32 index = esize == 4 ? indices.get(i) : indices.get(i); - if (remapIndices.contains(index)) { - //qCInfo(model_scripting) << i << index << "->" << remapIndices[index]; - newIndices << remapIndices[index]; - } else { - qCInfo(model_scripting) << i << index << "!remapIndices[index]"; - } - } - - mesh->setIndexBuffer(bufferViewFromVector(newIndices, { gpu::SCALAR, gpu::UINT32, gpu::INDEX })); - mesh->setVertexBuffer(bufferViewFromVector(uniqueVerts, { gpu::VEC3, gpu::FLOAT, gpu::XYZ })); - - auto attributeViews = ScriptableMesh::gatherBufferViews(mesh); - quint32 numUniqueVerts = uniqueVerts.size(); - for (const auto& a : attributeViews) { - auto& view = a.second; - auto slot = ScriptableMesh::ATTRIBUTES[a.first]; - if (slot == gpu::Stream::POSITION) { - continue; - } - qCInfo(model_scripting) << "ModelScriptingInterface::dedupeVertices" << a.first << slot << view.getNumElements(); - auto newView = resizedBufferView(view, numUniqueVerts); - qCInfo(model_scripting) << a.first << "before: #" << view.getNumElements() << "after: #" << newView.getNumElements(); - quint32 numElements = (quint32)view.getNumElements(); - for (quint32 i = 0; i < numElements; i++) { - quint32 fromVertexIndex = i; - quint32 toVertexIndex = remapIndices.contains(fromVertexIndex) ? remapIndices[fromVertexIndex] : fromVertexIndex; - bufferViewElementFromVariant( - newView, toVertexIndex, - bufferViewElementToVariant(view, fromVertexIndex, false, "dedupe") - ); - } - mesh->addAttribute(slot, newView); - } - return true; -} - -QScriptValue ModelScriptingInterface::cloneMesh(scriptable::ScriptableMeshPointer meshProxy, bool recalcNormals) { - auto mesh = getMeshPointer(meshProxy); - if (!mesh) { - return QScriptValue::NullValue; - } - graphics::MeshPointer clone(new graphics::Mesh()); - clone->displayName = mesh->displayName + "-clone"; - qCInfo(model_scripting) << "ModelScriptingInterface::cloneMesh" << !!mesh<< !!meshProxy; - if (!mesh) { - return QScriptValue::NullValue; - } - - clone->setIndexBuffer(cloneBufferView(mesh->getIndexBuffer())); - clone->setPartBuffer(cloneBufferView(mesh->getPartBuffer())); - auto attributeViews = ScriptableMesh::gatherBufferViews(mesh); - for (const auto& a : attributeViews) { - auto& view = a.second; - auto slot = ScriptableMesh::ATTRIBUTES[a.first]; - qCInfo(model_scripting) << "ModelScriptingInterface::cloneVertices buffer" << a.first << slot; - auto points = cloneBufferView(view); - qCInfo(model_scripting) << "ModelScriptingInterface::cloneVertices source" << view.getNumElements(); - qCInfo(model_scripting) << "ModelScriptingInterface::cloneVertices dest" << points.getNumElements(); - if (slot == gpu::Stream::POSITION) { - clone->setVertexBuffer(points); - } else { - clone->addAttribute(slot, points); - } - } - - auto result = scriptable::ScriptableMeshPointer(new ScriptableMesh(nullptr, clone)); - if (recalcNormals) { - recalculateNormals(result); - } - return engine()->toScriptValue(result); -} - -bool ModelScriptingInterface::recalculateNormals(scriptable::ScriptableMeshPointer meshProxy) { - qCInfo(model_scripting) << "Recalculating normals" << !!meshProxy; - auto mesh = getMeshPointer(meshProxy); - if (!mesh) { - return false; - } - ScriptableMesh::gatherBufferViews(mesh, { "normal", "color" }); // ensures #normals >= #positions - auto normals = mesh->getAttributeBuffer(gpu::Stream::NORMAL); - auto verts = mesh->getVertexBuffer(); - auto indices = mesh->getIndexBuffer(); - auto esize = indices._element.getSize(); - auto numPoints = indices.getNumElements(); - const auto TRIANGLE = 3; - quint32 numFaces = (quint32)numPoints / TRIANGLE; - //QVector faces; - QVector faceNormals; - QMap> vertexToFaces; - //faces.resize(numFaces); - faceNormals.resize(numFaces); - auto numNormals = normals.getNumElements(); - qCInfo(model_scripting) << QString("numFaces: %1, numNormals: %2, numPoints: %3").arg(numFaces).arg(numNormals).arg(numPoints); - if (normals.getNumElements() != verts.getNumElements()) { - return false; - } - for (quint32 i = 0; i < numFaces; i++) { - quint32 I = TRIANGLE * i; - quint32 i0 = esize == 4 ? indices.get(I+0) : indices.get(I+0); - quint32 i1 = esize == 4 ? indices.get(I+1) : indices.get(I+1); - quint32 i2 = esize == 4 ? indices.get(I+2) : indices.get(I+2); - - Triangle face = { - verts.get(i1), - verts.get(i2), - verts.get(i0) - }; - faceNormals[i] = face.getNormal(); - if (glm::isnan(faceNormals[i].x)) { - qCInfo(model_scripting) << i << i0 << i1 << i2 << vec3toVariant(face.v0) << vec3toVariant(face.v1) << vec3toVariant(face.v2); - break; - } - vertexToFaces[glm::to_string(face.v0).c_str()] << i; - vertexToFaces[glm::to_string(face.v1).c_str()] << i; - vertexToFaces[glm::to_string(face.v2).c_str()] << i; - } - for (quint32 j = 0; j < numNormals; j++) { - //auto v = verts.get(j); - glm::vec3 normal { 0.0f, 0.0f, 0.0f }; - QString key { glm::to_string(verts.get(j)).c_str() }; - const auto& faces = vertexToFaces.value(key); - if (faces.size()) { - for (const auto i : faces) { - normal += faceNormals[i]; - } - normal *= 1.0f / (float)faces.size(); - } else { - static int logged = 0; - if (logged++ < 10) { - qCInfo(model_scripting) << "no faces for key!?" << key; - } - normal = verts.get(j); - } - if (glm::isnan(normal.x)) { - static int logged = 0; - if (logged++ < 10) { - qCInfo(model_scripting) << "isnan(normal.x)" << j << vec3toVariant(normal); - } - break; - } - normals.edit(j) = glm::normalize(normal); - } - return true; -} - -QScriptValue ModelScriptingInterface::mapMeshAttributeValues( - scriptable::ScriptableMeshPointer meshProxy, QScriptValue scopeOrCallback, QScriptValue methodOrName -) { - auto mesh = getMeshPointer(meshProxy); - if (!mesh) { - return false; - } - auto scopedHandler = makeScopedHandlerObject(scopeOrCallback, methodOrName); - - // input buffers - gpu::BufferView positions = mesh->getVertexBuffer(); - - const auto nPositions = positions.getNumElements(); - - // destructure so we can still invoke callback scoped, but with a custom signature (obj, i, jsMesh) - auto scope = scopedHandler.property("scope"); - auto callback = scopedHandler.property("callback"); - auto js = engine(); // cache value to avoid resolving each iteration - auto meshPart = js->toScriptValue(meshProxy); - - auto obj = js->newObject(); - auto attributeViews = ScriptableMesh::gatherBufferViews(mesh, { "normal", "color" }); - for (uint32_t i=0; i < nPositions; i++) { - for (const auto& a : attributeViews) { - bool asArray = a.second._element.getType() != gpu::FLOAT; - obj.setProperty(a.first, bufferViewElementToScriptValue(js, a.second, i, asArray, a.first.toStdString().c_str())); - } - auto result = callback.call(scope, { obj, i, meshPart }); - if (js->hasUncaughtException()) { - context()->throwValue(js->uncaughtException()); - return false; - } - - if (result.isBool() && !result.toBool()) { - // bail without modifying data if user explicitly returns false - continue; - } - if (result.isObject() && !result.strictlyEquals(obj)) { - // user returned a new object (ie: instead of modifying input properties) - obj = result; - } - - for (const auto& a : attributeViews) { - const auto& attribute = obj.property(a.first); - auto& view = a.second; - if (attribute.isValid()) { - bufferViewElementFromScriptValue(attribute, view, i); - } - } - } - return thisObject(); -} - -void ModelScriptingInterface::getMeshes(QUuid uuid, QScriptValue scopeOrCallback, QScriptValue methodOrName) { - auto handler = makeScopedHandlerObject(scopeOrCallback, methodOrName); - Q_ASSERT(handler.engine() == this->engine()); - QPointer engine = dynamic_cast(handler.engine()); - - scriptable::ScriptableModel meshes; - bool success = false; - QString error; - - auto appProvider = DependencyManager::get(); - qDebug() << "appProvider" << appProvider.data(); - scriptable::ModelProviderPointer provider = appProvider ? appProvider->lookupModelProvider(uuid) : nullptr; - QString providerType = provider ? provider->metadata.value("providerType").toString() : QString(); - if (providerType.isEmpty()) { - providerType = "unknown"; - } - if (provider) { - qCDebug(model_scripting) << "fetching meshes from " << providerType << "..."; - auto scriptableMeshes = provider->getScriptableModel(&success); - qCDebug(model_scripting) << "//fetched meshes from " << providerType << "success:" <makeError(error), QScriptValue::NullValue); - } else { - callScopedHandlerObject(handler, QScriptValue::NullValue, engine->toScriptValue(meshes)); - } -} - -namespace { - QScriptValue meshToScriptValue(QScriptEngine* engine, scriptable::ScriptableMeshPointer const &in) { - return engine->newQObject(in.get(), QScriptEngine::QtOwnership, - QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects - ); - } - - void meshFromScriptValue(const QScriptValue& value, scriptable::ScriptableMeshPointer &out) { + void meshPointerFromScriptValue(const QScriptValue& value, scriptable::ScriptableMeshPointer &out) { auto obj = value.toQObject(); - //qDebug() << "meshFromScriptValue" << obj; + qDebug() << "meshPointerFromScriptValue" << obj; if (auto tmp = qobject_cast(obj)) { - out = tmp->shared_from_this(); + out = tmp; } // FIXME: Why does above cast not work on Win32!? if (!out) { - auto smp = static_cast(obj); - //qDebug() << "meshFromScriptValue2" << smp; - out = smp->shared_from_this(); + if (auto smp = static_cast(obj)) { + qDebug() << "meshPointerFromScriptValue2" << smp; + out = smp; + } } } - QScriptValue meshesToScriptValue(QScriptEngine* engine, const scriptable::ScriptableModelPointer &in) { - // QScriptValueList result; - QScriptValue result = engine->newArray(); - int i = 0; - foreach(scriptable::ScriptableMeshPointer const meshProxy, in->getMeshes()) { - result.setProperty(i++, meshToScriptValue(engine, meshProxy)); - } - return result; + QScriptValue modelPointerToScriptValue(QScriptEngine* engine, const scriptable::ScriptableModelPointer &in) { + return engine->newQObject(in, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects); + // QScriptValue result = engine->newArray(); + // int i = 0; + // foreach(auto& mesh, in->getMeshes()) { + // result.setProperty(i++, meshPointerToScriptValue(engine, mesh)); + // } + // return result; } - void meshesFromScriptValue(const QScriptValue& value, scriptable::ScriptableModelPointer &out) { + void modelPointerFromScriptValue(const QScriptValue& value, scriptable::ScriptableModelPointer &out) { const auto length = value.property("length").toInt32(); - qCDebug(model_scripting) << "in meshesFromScriptValue, length =" << length; + qCDebug(model_scripting) << "in modelPointerFromScriptValue, length =" << length; for (int i = 0; i < length; i++) { if (const auto meshProxy = qobject_cast(value.property(i).toQObject())) { out->meshes.append(meshProxy->getMeshPointer()); @@ -749,79 +349,64 @@ namespace { } } - void modelProxyFromScriptValue(const QScriptValue& object, scriptable::ScriptableModel &meshes) { - auto meshesProperty = object.property("meshes"); - if (meshesProperty.property("length").toInt32() > 0) { - //meshes._meshes = qobject_cast(meshesProperty.toQObject()); - // qDebug() << "modelProxyFromScriptValue" << meshesProperty.property("length").toInt32() << meshesProperty.toVariant().typeName(); - qScriptValueToSequence(meshesProperty, meshes.meshes); - } else if (auto mesh = qobject_cast(object.toQObject())) { - meshes.meshes << mesh->getMeshPointer(); - } else { - qDebug() << "modelProxyFromScriptValue -- unrecognized input" << object.toVariant().toString(); - } + // FIXME: MESHFACES: + // QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const mesh::MeshFace &meshFace) { + // QScriptValue obj = engine->newObject(); + // obj.setProperty("vertices", qVectorIntToScriptValue(engine, meshFace.vertexIndices)); + // return obj; + // } + // void meshFaceFromScriptValue(const QScriptValue &object, mesh::MeshFace& meshFaceResult) { + // qScriptValueToSequence(object.property("vertices"), meshFaceResult.vertexIndices); + // } + // QScriptValue qVectorMeshFaceToScriptValue(QScriptEngine* engine, const QVector& vector) { + // return qScriptValueFromSequence(engine, vector); + // } + // void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector& result) { + // qScriptValueToSequence(array, result); + // } - meshes.metadata = object.property("metadata").toVariant().toMap(); - } - - QScriptValue modelProxyToScriptValue(QScriptEngine* engine, const scriptable::ScriptableModel &in) { - QScriptValue obj = engine->newObject(); - obj.setProperty("meshes", qScriptValueFromSequence(engine, in.meshes)); - obj.setProperty("metadata", engine->toScriptValue(in.metadata)); - return obj; - } - - QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const mesh::MeshFace &meshFace) { - QScriptValue obj = engine->newObject(); - obj.setProperty("vertices", qVectorIntToScriptValue(engine, meshFace.vertexIndices)); - return obj; - } - - void meshFaceFromScriptValue(const QScriptValue &object, mesh::MeshFace& meshFaceResult) { - qScriptValueToSequence(object.property("vertices"), meshFaceResult.vertexIndices); - } - - QScriptValue qVectorMeshFaceToScriptValue(QScriptEngine* engine, const QVector& vector) { + QScriptValue qVectorUInt32ToScriptValue(QScriptEngine* engine, const QVector& vector) { return qScriptValueFromSequence(engine, vector); } - void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector& result) { - qScriptValueToSequence(array, result); - } - - QScriptValue qVectorUInt32ToScriptValue(QScriptEngine* engine, const QVector& vector) { - return qScriptValueFromSequence(engine, vector); - } - - void qVectorUInt32FromScriptValue(const QScriptValue& array, QVector& result) { + void qVectorUInt32FromScriptValue(const QScriptValue& array, QVector& result) { qScriptValueToSequence(array, result); } } -int meshUint32 = qRegisterMetaType(); +int meshUint32 = qRegisterMetaType(); namespace mesh { int meshUint32 = qRegisterMetaType(); } -int qVectorMeshUint32 = qRegisterMetaType>(); +int qVectorMeshUint32 = qRegisterMetaType>(); void ModelScriptingInterface::registerMetaTypes(QScriptEngine* engine) { qScriptRegisterSequenceMetaType>(engine); - qScriptRegisterSequenceMetaType(engine); - qScriptRegisterSequenceMetaType>(engine); - qScriptRegisterMetaType(engine, modelProxyToScriptValue, modelProxyFromScriptValue); + qScriptRegisterSequenceMetaType>(engine); qScriptRegisterMetaType(engine, qVectorUInt32ToScriptValue, qVectorUInt32FromScriptValue); - qScriptRegisterMetaType(engine, meshToScriptValue, meshFromScriptValue); - qScriptRegisterMetaType(engine, meshesToScriptValue, meshesFromScriptValue); - qScriptRegisterMetaType(engine, meshFaceToScriptValue, meshFaceFromScriptValue); - qScriptRegisterMetaType(engine, qVectorMeshFaceToScriptValue, qVectorMeshFaceFromScriptValue); + qScriptRegisterMetaType(engine, meshPointerToScriptValue, meshPointerFromScriptValue); + qScriptRegisterMetaType(engine, modelPointerToScriptValue, modelPointerFromScriptValue); + + // FIXME: MESHFACES: remove if MeshFace is not needed anywhere + // qScriptRegisterSequenceMetaType(engine); + // qScriptRegisterMetaType(engine, meshFaceToScriptValue, meshFaceFromScriptValue); + // qScriptRegisterMetaType(engine, qVectorMeshFaceToScriptValue, qVectorMeshFaceFromScriptValue); } +MeshPointer ModelScriptingInterface::getMeshPointer(const scriptable::ScriptableMesh& meshProxy) { + return meshProxy._mesh;//getMeshPointer(&meshProxy); +} +MeshPointer ModelScriptingInterface::getMeshPointer(scriptable::ScriptableMesh& meshProxy) { + return getMeshPointer(&meshProxy); +} MeshPointer ModelScriptingInterface::getMeshPointer(scriptable::ScriptableMeshPointer meshProxy) { MeshPointer result; if (!meshProxy) { if (context()){ context()->throwError("expected meshProxy as first parameter"); + } else { + qDebug() << "expected meshProxy as first parameter"; } return result; } @@ -829,6 +414,8 @@ MeshPointer ModelScriptingInterface::getMeshPointer(scriptable::ScriptableMeshPo if (!mesh) { if (context()) { context()->throwError("expected valid meshProxy as first parameter"); + } else { + qDebug() << "expected valid meshProxy as first parameter"; } return result; } diff --git a/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h index d10fd28170..eac4df3216 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h @@ -38,30 +38,20 @@ public slots: */ void getMeshes(QUuid uuid, QScriptValue scopeOrCallback, QScriptValue methodOrName = QScriptValue()); - bool dedupeVertices(scriptable::ScriptableMeshPointer meshProxy, float epsilon = 1e-6); - bool recalculateNormals(scriptable::ScriptableMeshPointer meshProxy); - QScriptValue cloneMesh(scriptable::ScriptableMeshPointer meshProxy, bool recalcNormals = true); - QScriptValue unrollVertices(scriptable::ScriptableMeshPointer meshProxy, bool recalcNormals = true); - QScriptValue mapAttributeValues(QScriptValue in, - QScriptValue scopeOrCallback, - QScriptValue methodOrName = QScriptValue()); - QScriptValue mapMeshAttributeValues(scriptable::ScriptableMeshPointer meshProxy, - QScriptValue scopeOrCallback, - QScriptValue methodOrName = QScriptValue()); - QString meshToOBJ(const scriptable::ScriptableModel& in); - bool replaceMeshData(scriptable::ScriptableMeshPointer dest, scriptable::ScriptableMeshPointer source, const QVector& attributeNames = QVector()); QScriptValue appendMeshes(scriptable::ScriptableModel in); QScriptValue transformMesh(scriptable::ScriptableMeshPointer meshProxy, glm::mat4 transform); QScriptValue newMesh(const QVector& vertices, const QVector& normals, const QVector& faces); QScriptValue getVertexCount(scriptable::ScriptableMeshPointer meshProxy); - QScriptValue getVertex(scriptable::ScriptableMeshPointer meshProxy, mesh::uint32 vertexIndex); + QScriptValue getVertex(scriptable::ScriptableMeshPointer meshProxy, quint32 vertexIndex); private: scriptable::MeshPointer getMeshPointer(scriptable::ScriptableMeshPointer meshProxy); + scriptable::MeshPointer getMeshPointer(scriptable::ScriptableMesh& meshProxy); + scriptable::MeshPointer getMeshPointer(const scriptable::ScriptableMesh& meshProxy); }; diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp index 47d91e9e59..1b16a6d263 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp @@ -14,14 +14,20 @@ #include #include #include +#include #include #include #include +#include #include #include "ScriptableMesh.moc" #include +#include +#include + +#include "OBJWriter.h" QLoggingCategory mesh_logging { "hifi.scripting.mesh" }; @@ -41,10 +47,26 @@ QMap ScriptableMesh::ATTRIBUTES{ {"texcoord4", gpu::Stream::TEXCOORD4 }, }; -QVector scriptable::ScriptableModel::getMeshes() const { + +QString scriptable::ScriptableModel::toString() const { + return QString("[ScriptableModel%1%2]") + .arg(objectID.isNull() ? "" : " objectID="+objectID.toString()) + .arg(objectName().isEmpty() ? "" : " name=" +objectName()); +} + +const QVector scriptable::ScriptableModel::getConstMeshes() const { + QVector out; + for(const auto& mesh : meshes) { + const scriptable::ScriptableMeshPointer m = scriptable::ScriptableMeshPointer(new scriptable::ScriptableMesh(const_cast(this), mesh)); + out << m; + } + return out; +} +QVector scriptable::ScriptableModel::getMeshes() { QVector out; for(auto& mesh : meshes) { - out << scriptable::ScriptableMeshPointer(new ScriptableMesh(std::const_pointer_cast(this->shared_from_this()), mesh)); + scriptable::ScriptableMeshPointer m{new scriptable::ScriptableMesh(this, mesh)}; + out << m; } return out; } @@ -134,15 +156,16 @@ QVariantMap ScriptableMesh::getVertexAttributes(quint32 vertexIndex) const { } bool ScriptableMesh::setVertexAttributes(quint32 vertexIndex, QVariantMap attributes) { - qDebug() << "setVertexAttributes" << vertexIndex << attributes; + //qDebug() << "setVertexAttributes" << vertexIndex << attributes; for (auto& a : gatherBufferViews(getMeshPointer())) { const auto& name = a.first; const auto& value = attributes.value(name); if (value.isValid()) { auto& view = a.second; + //qCDebug(mesh_logging) << "setVertexAttributes" << vertexIndex << name; bufferViewElementFromVariant(view, vertexIndex, value); } else { - qCDebug(mesh_logging) << "setVertexAttributes" << vertexIndex << name; + //qCDebug(mesh_logging) << "(skipping) setVertexAttributes" << vertexIndex << name; } } return true; @@ -357,3 +380,375 @@ std::map ScriptableMesh::gatherBufferViews(scriptable: } return attributeViews; } + +QScriptValue ScriptableModel::mapAttributeValues(QScriptValue scopeOrCallback, QScriptValue methodOrName) { + auto context = scopeOrCallback.engine()->currentContext(); + auto _in = context->thisObject(); + qCInfo(mesh_logging) << "mapAttributeValues" << _in.toVariant().typeName() << _in.toVariant().toString() << _in.toQObject(); + auto model = qscriptvalue_cast(_in); + QVector in = model.getMeshes(); + if (in.size()) { + foreach (scriptable::ScriptableMeshPointer meshProxy, in) { + meshProxy->mapAttributeValues(scopeOrCallback, methodOrName); + } + return _in; + } else if (auto meshProxy = qobject_cast(_in.toQObject())) { + return meshProxy->mapAttributeValues(scopeOrCallback, methodOrName); + } else { + context->throwError("invalid ModelProxy || MeshProxyPointer"); + } + return false; +} + + + +QScriptValue ScriptableMesh::mapAttributeValues(QScriptValue scopeOrCallback, QScriptValue methodOrName) { + auto mesh = getMeshPointer(); + if (!mesh) { + return false; + } + auto scopedHandler = makeScopedHandlerObject(scopeOrCallback, methodOrName); + + // input buffers + gpu::BufferView positions = mesh->getVertexBuffer(); + + const auto nPositions = positions.getNumElements(); + + // destructure so we can still invoke callback scoped, but with a custom signature (obj, i, jsMesh) + auto scope = scopedHandler.property("scope"); + auto callback = scopedHandler.property("callback"); + auto js = engine(); // cache value to avoid resolving each iteration + auto meshPart = thisObject();//js->toScriptValue(meshProxy); + + auto obj = js->newObject(); + auto attributeViews = ScriptableMesh::gatherBufferViews(mesh, { "normal", "color" }); + for (uint32_t i=0; i < nPositions; i++) { + for (const auto& a : attributeViews) { + bool asArray = a.second._element.getType() != gpu::FLOAT; + obj.setProperty(a.first, bufferViewElementToScriptValue(js, a.second, i, asArray, a.first.toStdString().c_str())); + } + auto result = callback.call(scope, { obj, i, meshPart }); + if (js->hasUncaughtException()) { + context()->throwValue(js->uncaughtException()); + return false; + } + + if (result.isBool() && !result.toBool()) { + // bail without modifying data if user explicitly returns false + continue; + } + if (result.isObject() && !result.strictlyEquals(obj)) { + // user returned a new object (ie: instead of modifying input properties) + obj = result; + } + + for (const auto& a : attributeViews) { + const auto& attribute = obj.property(a.first); + auto& view = a.second; + if (attribute.isValid()) { + bufferViewElementFromScriptValue(attribute, view, i); + } + } + } + return thisObject(); +} + +QScriptValue ScriptableMesh::unrollVertices(bool recalcNormals) { + auto meshProxy = this; + auto mesh = getMeshPointer(); + qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices" << !!mesh<< !!meshProxy; + if (!mesh) { + return QScriptValue(); + } + + auto positions = mesh->getVertexBuffer(); + auto indices = mesh->getIndexBuffer(); + quint32 numPoints = (quint32)indices.getNumElements(); + auto buffer = new gpu::Buffer(); + buffer->resize(numPoints * sizeof(uint32_t)); + auto newindices = gpu::BufferView(buffer, { gpu::SCALAR, gpu::UINT32, gpu::INDEX }); + qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices numPoints" << numPoints; + auto attributeViews = ScriptableMesh::gatherBufferViews(mesh); + for (const auto& a : attributeViews) { + auto& view = a.second; + auto sz = view._element.getSize(); + auto buffer = new gpu::Buffer(); + buffer->resize(numPoints * sz); + auto points = gpu::BufferView(buffer, view._element); + auto src = (uint8_t*)view._buffer->getData(); + auto dest = (uint8_t*)points._buffer->getData(); + auto slot = ScriptableMesh::ATTRIBUTES[a.first]; + qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices buffer" << a.first; + qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices source" << view.getNumElements(); + qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices dest" << points.getNumElements(); + qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices sz" << sz << src << dest << slot; + auto esize = indices._element.getSize(); + const char* hint= a.first.toStdString().c_str(); + for(quint32 i = 0; i < numPoints; i++) { + quint32 index = esize == 4 ? indices.get(i) : indices.get(i); + newindices.edit(i) = i; + bufferViewElementFromVariant( + points, i, + bufferViewElementToVariant(view, index, false, hint) + ); + } + if (slot == gpu::Stream::POSITION) { + mesh->setVertexBuffer(points); + } else { + mesh->addAttribute(slot, points); + } + } + mesh->setIndexBuffer(newindices); + if (recalcNormals) { + recalculateNormals(); + } + return true; +} + +bool ScriptableMesh::replaceMeshData(scriptable::ScriptableMeshPointer src, const QVector& attributeNames) { + auto target = getMeshPointer(); + auto source = src ? src->getMeshPointer() : nullptr; + if (!target || !source) { + context()->throwError("ScriptableMesh::replaceMeshData -- expected dest and src to be valid mesh proxy pointers"); + return false; + } + + QVector attributes = attributeNames.isEmpty() ? src->getAttributeNames() : attributeNames; + + //qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData -- source:" << source->displayName << "target:" << target->displayName << "attributes:" << attributes; + + // remove attributes only found on target mesh, unless user has explicitly specified the relevant attribute names + if (attributeNames.isEmpty()) { + auto attributeViews = ScriptableMesh::gatherBufferViews(target); + for (const auto& a : attributeViews) { + auto slot = ScriptableMesh::ATTRIBUTES[a.first]; + if (!attributes.contains(a.first)) { + //qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData -- pruning target attribute" << a.first << slot; + target->removeAttribute(slot); + } + } + } + + target->setVertexBuffer(cloneBufferView(source->getVertexBuffer())); + target->setIndexBuffer(cloneBufferView(source->getIndexBuffer())); + target->setPartBuffer(cloneBufferView(source->getPartBuffer())); + + for (const auto& a : attributes) { + auto slot = ScriptableMesh::ATTRIBUTES[a]; + if (slot == gpu::Stream::POSITION) { + continue; + } + // auto& before = target->getAttributeBuffer(slot); + auto& input = source->getAttributeBuffer(slot); + if (input.getNumElements() == 0) { + //qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData buffer is empty -- pruning" << a << slot; + target->removeAttribute(slot); + } else { + // if (before.getNumElements() == 0) { + // qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData target buffer is empty -- adding" << a << slot; + // } else { + // qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData target buffer exists -- updating" << a << slot; + // } + target->addAttribute(slot, cloneBufferView(input)); + } + // auto& after = target->getAttributeBuffer(slot); + // qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData" << a << slot << before.getNumElements() << " -> " << after.getNumElements(); + } + + + return true; +} + +bool ScriptableMesh::dedupeVertices(float epsilon) { + scriptable::ScriptableMeshPointer meshProxy = this; + auto mesh = getMeshPointer(); + if (!mesh) { + return false; + } + auto positions = mesh->getVertexBuffer(); + auto numPositions = positions.getNumElements(); + const auto epsilon2 = epsilon*epsilon; + + QVector uniqueVerts; + uniqueVerts.reserve((int)numPositions); + QMap remapIndices; + + for (quint32 i = 0; i < numPositions; i++) { + const quint32 numUnique = uniqueVerts.size(); + const auto& position = positions.get(i); + bool unique = true; + for (quint32 j = 0; j < numUnique; j++) { + if (glm::length2(uniqueVerts[j] - position) <= epsilon2) { + remapIndices[i] = j; + unique = false; + break; + } + } + if (unique) { + uniqueVerts << position; + remapIndices[i] = numUnique; + } + } + + qCInfo(mesh_logging) << "//VERTS before" << numPositions << "after" << uniqueVerts.size(); + + auto indices = mesh->getIndexBuffer(); + auto numIndices = indices.getNumElements(); + auto esize = indices._element.getSize(); + QVector newIndices; + newIndices.reserve((int)numIndices); + for (quint32 i = 0; i < numIndices; i++) { + quint32 index = esize == 4 ? indices.get(i) : indices.get(i); + if (remapIndices.contains(index)) { + //qCInfo(mesh_logging) << i << index << "->" << remapIndices[index]; + newIndices << remapIndices[index]; + } else { + qCInfo(mesh_logging) << i << index << "!remapIndices[index]"; + } + } + + mesh->setIndexBuffer(bufferViewFromVector(newIndices, { gpu::SCALAR, gpu::UINT32, gpu::INDEX })); + mesh->setVertexBuffer(bufferViewFromVector(uniqueVerts, { gpu::VEC3, gpu::FLOAT, gpu::XYZ })); + + auto attributeViews = ScriptableMesh::gatherBufferViews(mesh); + quint32 numUniqueVerts = uniqueVerts.size(); + for (const auto& a : attributeViews) { + auto& view = a.second; + auto slot = ScriptableMesh::ATTRIBUTES[a.first]; + if (slot == gpu::Stream::POSITION) { + continue; + } + qCInfo(mesh_logging) << "ScriptableMesh::dedupeVertices" << a.first << slot << view.getNumElements(); + auto newView = resizedBufferView(view, numUniqueVerts); + qCInfo(mesh_logging) << a.first << "before: #" << view.getNumElements() << "after: #" << newView.getNumElements(); + quint32 numElements = (quint32)view.getNumElements(); + for (quint32 i = 0; i < numElements; i++) { + quint32 fromVertexIndex = i; + quint32 toVertexIndex = remapIndices.contains(fromVertexIndex) ? remapIndices[fromVertexIndex] : fromVertexIndex; + bufferViewElementFromVariant( + newView, toVertexIndex, + bufferViewElementToVariant(view, fromVertexIndex, false, "dedupe") + ); + } + mesh->addAttribute(slot, newView); + } + return true; +} + +QScriptValue ScriptableMesh::cloneMesh(bool recalcNormals) { + auto mesh = getMeshPointer(); + if (!mesh) { + return QScriptValue::NullValue; + } + graphics::MeshPointer clone(new graphics::Mesh()); + clone->displayName = mesh->displayName + "-clone"; + qCInfo(mesh_logging) << "ScriptableMesh::cloneMesh" << !!mesh; + if (!mesh) { + return QScriptValue::NullValue; + } + + clone->setIndexBuffer(cloneBufferView(mesh->getIndexBuffer())); + clone->setPartBuffer(cloneBufferView(mesh->getPartBuffer())); + auto attributeViews = ScriptableMesh::gatherBufferViews(mesh); + for (const auto& a : attributeViews) { + auto& view = a.second; + auto slot = ScriptableMesh::ATTRIBUTES[a.first]; + qCInfo(mesh_logging) << "ScriptableMesh::cloneVertices buffer" << a.first << slot; + auto points = cloneBufferView(view); + qCInfo(mesh_logging) << "ScriptableMesh::cloneVertices source" << view.getNumElements(); + qCInfo(mesh_logging) << "ScriptableMesh::cloneVertices dest" << points.getNumElements(); + if (slot == gpu::Stream::POSITION) { + clone->setVertexBuffer(points); + } else { + clone->addAttribute(slot, points); + } + } + + auto result = scriptable::ScriptableMeshPointer(new ScriptableMesh(nullptr, clone)); + if (recalcNormals) { + result->recalculateNormals(); + } + return engine()->toScriptValue(result); +} + +bool ScriptableMesh::recalculateNormals() { + scriptable::ScriptableMeshPointer meshProxy = this; + qCInfo(mesh_logging) << "Recalculating normals" << !!meshProxy; + auto mesh = getMeshPointer(); + if (!mesh) { + return false; + } + ScriptableMesh::gatherBufferViews(mesh, { "normal", "color" }); // ensures #normals >= #positions + auto normals = mesh->getAttributeBuffer(gpu::Stream::NORMAL); + auto verts = mesh->getVertexBuffer(); + auto indices = mesh->getIndexBuffer(); + auto esize = indices._element.getSize(); + auto numPoints = indices.getNumElements(); + const auto TRIANGLE = 3; + quint32 numFaces = (quint32)numPoints / TRIANGLE; + //QVector faces; + QVector faceNormals; + QMap> vertexToFaces; + //faces.resize(numFaces); + faceNormals.resize(numFaces); + auto numNormals = normals.getNumElements(); + qCInfo(mesh_logging) << QString("numFaces: %1, numNormals: %2, numPoints: %3").arg(numFaces).arg(numNormals).arg(numPoints); + if (normals.getNumElements() != verts.getNumElements()) { + return false; + } + for (quint32 i = 0; i < numFaces; i++) { + quint32 I = TRIANGLE * i; + quint32 i0 = esize == 4 ? indices.get(I+0) : indices.get(I+0); + quint32 i1 = esize == 4 ? indices.get(I+1) : indices.get(I+1); + quint32 i2 = esize == 4 ? indices.get(I+2) : indices.get(I+2); + + Triangle face = { + verts.get(i1), + verts.get(i2), + verts.get(i0) + }; + faceNormals[i] = face.getNormal(); + if (glm::isnan(faceNormals[i].x)) { + qCInfo(mesh_logging) << i << i0 << i1 << i2 << vec3toVariant(face.v0) << vec3toVariant(face.v1) << vec3toVariant(face.v2); + break; + } + vertexToFaces[glm::to_string(face.v0).c_str()] << i; + vertexToFaces[glm::to_string(face.v1).c_str()] << i; + vertexToFaces[glm::to_string(face.v2).c_str()] << i; + } + for (quint32 j = 0; j < numNormals; j++) { + //auto v = verts.get(j); + glm::vec3 normal { 0.0f, 0.0f, 0.0f }; + QString key { glm::to_string(verts.get(j)).c_str() }; + const auto& faces = vertexToFaces.value(key); + if (faces.size()) { + for (const auto i : faces) { + normal += faceNormals[i]; + } + normal *= 1.0f / (float)faces.size(); + } else { + static int logged = 0; + if (logged++ < 10) { + qCInfo(mesh_logging) << "no faces for key!?" << key; + } + normal = verts.get(j); + } + if (glm::isnan(normal.x)) { + static int logged = 0; + if (logged++ < 10) { + qCInfo(mesh_logging) << "isnan(normal.x)" << j << vec3toVariant(normal); + } + break; + } + normals.edit(j) = glm::normalize(normal); + } + return true; +} + +QString ScriptableMesh::toOBJ() { + if (!getMeshPointer()) { + context()->throwError(QString("null mesh")); + } + return writeOBJToString({ getMeshPointer() }); +} + diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h index da11002906..257285fa90 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h @@ -6,12 +6,16 @@ #include #include #include +#include #include #include #include +#include +#include + namespace graphics { class Mesh; } @@ -19,91 +23,112 @@ namespace gpu { class BufferView; } namespace scriptable { - class ScriptableMesh : public QObject, public std::enable_shared_from_this { + class ScriptableMeshPart; + using ScriptableMeshPartPointer = QPointer; + class ScriptableMesh : public QObject, QScriptable { Q_OBJECT public: - ScriptableModelPointer _model; - scriptable::MeshPointer _mesh; - QVariantMap _metadata; - ScriptableMesh() : QObject() {} - ScriptableMesh(ScriptableModelPointer parent, scriptable::MeshPointer mesh) : QObject(), _model(parent), _mesh(mesh) {} - ScriptableMesh(const ScriptableMesh& other) : QObject(), _model(other._model), _mesh(other._mesh), _metadata(other._metadata) {} - ~ScriptableMesh() { qDebug() << "~ScriptableMesh" << this; } Q_PROPERTY(quint32 numParts READ getNumParts) Q_PROPERTY(quint32 numAttributes READ getNumAttributes) Q_PROPERTY(quint32 numVertices READ getNumVertices) Q_PROPERTY(quint32 numIndices READ getNumIndices) + Q_PROPERTY(QVariantMap metadata MEMBER _metadata) Q_PROPERTY(QVector attributeNames READ getAttributeNames) - virtual scriptable::MeshPointer getMeshPointer() const { return _mesh; } - Q_INVOKABLE virtual quint32 getNumParts() const; - Q_INVOKABLE virtual quint32 getNumVertices() const; - Q_INVOKABLE virtual quint32 getNumAttributes() const; - Q_INVOKABLE virtual quint32 getNumIndices() const { return 0; } - Q_INVOKABLE virtual QVector getAttributeNames() const; - Q_INVOKABLE virtual QVariantMap getVertexAttributes(quint32 vertexIndex) const; - Q_INVOKABLE virtual QVariantMap getVertexAttributes(quint32 vertexIndex, QVector attributes) const; - - Q_INVOKABLE virtual QVector getIndices() const; - Q_INVOKABLE virtual QVector findNearbyIndices(const glm::vec3& origin, float epsilon = 1e-6) const; - Q_INVOKABLE virtual QVariantMap getMeshExtents() const; - Q_INVOKABLE virtual bool setVertexAttributes(quint32 vertexIndex, QVariantMap attributes); - Q_INVOKABLE virtual QVariantMap scaleToFit(float unitScale); + static QMap ATTRIBUTES; + static std::map gatherBufferViews(MeshPointer mesh, const QStringList& expandToMatchPositions = QStringList()); - static QMap ATTRIBUTES; - static std::map gatherBufferViews(MeshPointer mesh, const QStringList& expandToMatchPositions = QStringList()); + ScriptableMesh& operator=(const ScriptableMesh& other) { _model=other._model; _mesh=other._mesh; _metadata=other._metadata; return *this; }; + ScriptableMesh() : QObject(), _model(nullptr) {} + ScriptableMesh(ScriptableModelPointer parent, scriptable::MeshPointer mesh) : QObject(), _model(parent), _mesh(mesh) {} + ScriptableMesh(const ScriptableMesh& other) : QObject(), _model(other._model), _mesh(other._mesh), _metadata(other._metadata) {} + ~ScriptableMesh() { qDebug() << "~ScriptableMesh" << this; } - Q_INVOKABLE QVariantList getAttributeValues(const QString& attributeName) const; + scriptable::MeshPointer getMeshPointer() const { return _mesh; } + public slots: + quint32 getNumParts() const; + quint32 getNumVertices() const; + quint32 getNumAttributes() const; + quint32 getNumIndices() const { return 0; } + QVector getAttributeNames() const; - Q_INVOKABLE int _getSlotNumber(const QString& attributeName) const; + QVariantMap getVertexAttributes(quint32 vertexIndex) const; + QVariantMap getVertexAttributes(quint32 vertexIndex, QVector attributes) const; - QVariantMap translate(const glm::vec3& translation); - QVariantMap scale(const glm::vec3& scale, const glm::vec3& origin = glm::vec3(NAN)); - QVariantMap rotateDegrees(const glm::vec3& eulerAngles, const glm::vec3& origin = glm::vec3(NAN)); - QVariantMap rotate(const glm::quat& rotation, const glm::vec3& origin = glm::vec3(NAN)); - Q_INVOKABLE QVariantMap transform(const glm::mat4& transform); + QVector getIndices() const; + QVector findNearbyIndices(const glm::vec3& origin, float epsilon = 1e-6) const; + QVariantMap getMeshExtents() const; + bool setVertexAttributes(quint32 vertexIndex, QVariantMap attributes); + QVariantMap scaleToFit(float unitScale); + + QVariantList getAttributeValues(const QString& attributeName) const; + + int _getSlotNumber(const QString& attributeName) const; + + QVariantMap translate(const glm::vec3& translation); + QVariantMap scale(const glm::vec3& scale, const glm::vec3& origin = glm::vec3(NAN)); + QVariantMap rotateDegrees(const glm::vec3& eulerAngles, const glm::vec3& origin = glm::vec3(NAN)); + QVariantMap rotate(const glm::quat& rotation, const glm::vec3& origin = glm::vec3(NAN)); + QVariantMap transform(const glm::mat4& transform); + + public: + operator bool() const { return _mesh != nullptr; } + ScriptableModelPointer _model; + scriptable::MeshPointer _mesh; + QVariantMap _metadata; + + public slots: + // QScriptEngine-specific wrappers + QScriptValue mapAttributeValues(QScriptValue scopeOrCallback, QScriptValue methodOrName = QScriptValue()); + bool dedupeVertices(float epsilon = 1e-6); + bool recalculateNormals(); + QScriptValue cloneMesh(bool recalcNormals = true); + QScriptValue unrollVertices(bool recalcNormals = true); + bool replaceMeshData(scriptable::ScriptableMeshPointer source, const QVector& attributeNames = QVector()); + QString toOBJ(); }; - // TODO: for now this is a part-specific wrapper around ScriptableMesh - class ScriptableMeshPart : public ScriptableMesh { + // TODO: part-specific wrapper for working with raw geometries + class ScriptableMeshPart : public QObject { Q_OBJECT public: - ScriptableMeshPart& operator=(const ScriptableMeshPart& view) { _model=view._model; _mesh=view._mesh; return *this; }; - ScriptableMeshPart(const ScriptableMeshPart& other) : ScriptableMesh(other._model, other._mesh) {} - ScriptableMeshPart() : ScriptableMesh(nullptr, nullptr) {} - ~ScriptableMeshPart() { qDebug() << "~ScriptableMeshPart" << this; } - ScriptableMeshPart(ScriptableMeshPointer mesh) : ScriptableMesh(mesh->_model, mesh->_mesh) {} Q_PROPERTY(QString topology READ getTopology) Q_PROPERTY(quint32 numFaces READ getNumFaces) - scriptable::MeshPointer parentMesh; - int partIndex; - QString getTopology() const { return "triangles"; } - Q_INVOKABLE virtual quint32 getNumFaces() const { return getIndices().size() / 3; } - Q_INVOKABLE virtual QVector getFace(quint32 faceIndex) const { - auto inds = getIndices(); + ScriptableMeshPart& operator=(const ScriptableMeshPart& view) { parentMesh=view.parentMesh; return *this; }; + ScriptableMeshPart(const ScriptableMeshPart& other) : parentMesh(other.parentMesh) {} + ScriptableMeshPart() {} + ~ScriptableMeshPart() { qDebug() << "~ScriptableMeshPart" << this; } + + public slots: + QString getTopology() const { return "triangles"; } + quint32 getNumFaces() const { return parentMesh.getIndices().size() / 3; } + QVector getFace(quint32 faceIndex) const { + auto inds = parentMesh.getIndices(); return faceIndex+2 < (quint32)inds.size() ? inds.mid(faceIndex*3, 3) : QVector(); } + + public: + scriptable::ScriptableMesh parentMesh; + int partIndex; }; class GraphicsScriptingInterface : public QObject { Q_OBJECT public: GraphicsScriptingInterface(QObject* parent = nullptr) : QObject(parent) {} - GraphicsScriptingInterface(const GraphicsScriptingInterface& other) {} + GraphicsScriptingInterface(const GraphicsScriptingInterface& other) {} public slots: ScriptableMeshPart exportMeshPart(ScriptableMesh mesh, int part) { return {}; } - }; } -Q_DECLARE_METATYPE(scriptable::ScriptableMesh) Q_DECLARE_METATYPE(scriptable::ScriptableMeshPointer) Q_DECLARE_METATYPE(QVector) -Q_DECLARE_METATYPE(scriptable::ScriptableMeshPart) +Q_DECLARE_METATYPE(scriptable::ScriptableMeshPartPointer) Q_DECLARE_METATYPE(scriptable::GraphicsScriptingInterface) -// FIXME: faces were supported in the original Model.* API -- are they still needed/used/useful for anything yet? +// FIXME: MESHFACES: faces were supported in the original Model.* API -- are they still needed/used/useful for anything yet? #include namespace mesh { diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h index e8cf6f1656..4ba5a993b1 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -16,50 +17,53 @@ namespace graphics { namespace gpu { class BufferView; } +class QScriptValue; + namespace scriptable { using Mesh = graphics::Mesh; using MeshPointer = std::shared_ptr; class ScriptableModel; + using ScriptableModelPointer = QPointer; class ScriptableMesh; - class ScriptableMeshPart; - using ScriptableModelPointer = std::shared_ptr; - using ScriptableMeshPointer = std::shared_ptr; - using ScriptableMeshPartPointer = std::shared_ptr; - class ScriptableModel : public QObject, public std::enable_shared_from_this { + using ScriptableMeshPointer = QPointer; + + // abstract container for holding one or more scriptable meshes + class ScriptableModel : public QObject { Q_OBJECT public: - Q_PROPERTY(QVector meshes READ getMeshes) - - Q_INVOKABLE QString toString() { return "[ScriptableModel " + objectName()+"]"; } - ScriptableModel(QObject* parent = nullptr) : QObject(parent) {} - ScriptableModel(const ScriptableModel& other) : objectID(other.objectID), metadata(other.metadata), meshes(other.meshes) {} - ScriptableModel& operator=(const ScriptableModel& view) { - objectID = view.objectID; - metadata = view.metadata; - meshes = view.meshes; - return *this; - } - ~ScriptableModel() { qDebug() << "~ScriptableModel" << this; } - void mixin(const ScriptableModel& other) { - for (const auto& key : other.metadata.keys()) { - metadata[key] = other.metadata[key]; - } - for(const auto&mesh : other.meshes) { - meshes << mesh; - } - } QUuid objectID; QVariantMap metadata; QVector meshes; - // TODO: in future accessors for these could go here - QVariantMap shapes; - QVariantMap materials; - QVariantMap armature; - QVector getMeshes() const; + Q_PROPERTY(QVector meshes READ getMeshes) + Q_PROPERTY(QUuid objectID MEMBER objectID CONSTANT) + Q_PROPERTY(QVariantMap metadata MEMBER metadata CONSTANT) + Q_INVOKABLE QString toString() const; + + ScriptableModel(QObject* parent = nullptr) : QObject(parent) {} + ScriptableModel(const ScriptableModel& other) : objectID(other.objectID), metadata(other.metadata), meshes(other.meshes) {} + ScriptableModel& operator=(const ScriptableModel& view) { objectID = view.objectID; metadata = view.metadata; meshes = view.meshes; return *this; } + ~ScriptableModel() { qDebug() << "~ScriptableModel" << this; } + + void mixin(const ScriptableModel& other) { + for (const auto& key : other.metadata.keys()) { metadata[key] = other.metadata[key]; } + for (const auto& mesh : other.meshes) { meshes << mesh; } + } + + // TODO: in future accessors for these could go here + // QVariantMap shapes; + // QVariantMap materials; + // QVariantMap armature; + + QVector getMeshes(); + const QVector getConstMeshes() const; + + // QScriptEngine-specific wrappers + Q_INVOKABLE QScriptValue mapAttributeValues(QScriptValue scopeOrCallback, QScriptValue methodOrName); }; + // mixin class for Avatar/Entity/Overlay Rendering that expose their in-memory graphics::Meshes class ModelProvider { public: QVariantMap metadata; @@ -67,11 +71,12 @@ namespace scriptable { virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) = 0; }; using ModelProviderPointer = std::shared_ptr; + + // mixin class for Application to resolve UUIDs into a corresponding ModelProvider class ModelProviderFactory : public Dependency { public: virtual scriptable::ModelProviderPointer lookupModelProvider(QUuid uuid) = 0; }; - } Q_DECLARE_METATYPE(scriptable::MeshPointer) diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index d595136c56..ae5ac5d61c 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -579,38 +579,9 @@ scriptable::ScriptableModel Model::getScriptableModel(bool* ok) { if (!isLoaded()) { qDebug() << "Model::getScriptableModel -- !isLoaded"; - if (ok) { - *ok = false; - } - return result; + return scriptable::ModelProvider::modelUnavailableError(ok); } -// TODO: remove -- this was an earlier approach using renderGeometry instead of FBXGeometry -#if 0 // renderGeometry approach - const Geometry::GeometryMeshes& meshes = renderGeometry->getMeshes(); - Transform offset; - offset.setScale(_scale); - offset.postTranslate(_offset); - glm::mat4 offsetMat = offset.getMatrix(); - - for (std::shared_ptr mesh : meshes) { - if (!mesh) { - continue; - } - qDebug() << "Model::getScriptableModel #" << i++ << mesh->displayName; - auto newmesh = mesh->map( - [=](glm::vec3 position) { - return glm::vec3(offsetMat * glm::vec4(position, 1.0f)); - }, - [=](glm::vec3 color) { return color; }, - [=](glm::vec3 normal) { - return glm::normalize(glm::vec3(offsetMat * glm::vec4(normal, 0.0f))); - }, - [&](uint32_t index) { return index; }); - newmesh->displayName = mesh->displayName; - result << newmesh; - } -#endif const FBXGeometry& geometry = getFBXGeometry(); auto mat4toVariant = [](const glm::mat4& mat4) -> QVariant { QVector floats; @@ -659,6 +630,33 @@ scriptable::ScriptableModel Model::getScriptableModel(bool* ok) { qDebug() << "//Model::getScriptableModel -- #" << result.meshes.size(); result.metadata["submeshes"] = submeshes; return result; + +// TODO: remove -- this was an earlier approach using renderGeometry instead of FBXGeometry +#if 0 // renderGeometry approach + const Geometry::GeometryMeshes& meshes = renderGeometry->getMeshes(); + Transform offset; + offset.setScale(_scale); + offset.postTranslate(_offset); + glm::mat4 offsetMat = offset.getMatrix(); + + for (std::shared_ptr mesh : meshes) { + if (!mesh) { + continue; + } + qDebug() << "Model::getScriptableModel #" << i++ << mesh->displayName; + auto newmesh = mesh->map( + [=](glm::vec3 position) { + return glm::vec3(offsetMat * glm::vec4(position, 1.0f)); + }, + [=](glm::vec3 color) { return color; }, + [=](glm::vec3 normal) { + return glm::normalize(glm::vec3(offsetMat * glm::vec4(normal, 0.0f))); + }, + [&](uint32_t index) { return index; }); + newmesh->displayName = mesh->displayName; + result << newmesh; + } +#endif } void Model::calculateTriangleSets() { From 522c577e732635b43dbed78e4d7868ad7dac5e64 Mon Sep 17 00:00:00 2001 From: samcake Date: Fri, 9 Feb 2018 14:08:55 -0800 Subject: [PATCH 14/49] FIxing the bad ambient lighting on scattering surfaces --- libraries/render-utils/src/LightAmbient.slh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/render-utils/src/LightAmbient.slh b/libraries/render-utils/src/LightAmbient.slh index 0502446db8..c45f036486 100644 --- a/libraries/render-utils/src/LightAmbient.slh +++ b/libraries/render-utils/src/LightAmbient.slh @@ -78,7 +78,7 @@ void evalLightingAmbient(out vec3 diffuse, out vec3 specular, LightAmbient ambie vec3 ambientSpaceSurfaceNormal = (ambient.transform * vec4(surface.normal, 0.0)).xyz; vec3 ambientSpaceSurfaceEyeDir = (ambient.transform * vec4(surface.eyeDir, 0.0)).xyz; <@if supportScattering@> - vec3 ambientSpaceLowNormalCurvature = (ambient.transform * lowNormalCurvature).xyz; + vec3 ambientSpaceLowNormal = (ambient.transform * vec4(lowNormalCurvature.xyz, 0.0)).xyz; <@endif@> vec3 ambientFresnel = fresnelSchlickAmbient(fresnelF0, surface.ndotv, 1.0-surface.roughness); @@ -99,7 +99,7 @@ void evalLightingAmbient(out vec3 diffuse, out vec3 specular, LightAmbient ambie obscurance = min(obscurance, ambientOcclusion); // Diffuse from ambient - diffuse = sphericalHarmonics_evalSphericalLight(getLightAmbientSphere(ambient), ambientSpaceLowNormalCurvature).xyz; + diffuse = sphericalHarmonics_evalSphericalLight(getLightAmbientSphere(ambient), ambientSpaceLowNormal).xyz; // Scattering ambient specular is the same as non scattering for now // TODO: we should use the same specular answer as for direct lighting From 611c67bf2f6914a7df7edc49be24477ded182572 Mon Sep 17 00:00:00 2001 From: Alexander Ivash Date: Thu, 15 Feb 2018 02:00:51 +0300 Subject: [PATCH 15/49] FB12297 - HMD: Disabled preview mode only occurs during step 2 of the wallet setup wizard --- interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml b/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml index fab27a29bb..bad592067c 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml @@ -441,7 +441,7 @@ Item { } Item { id: choosePassphraseContainer; - visible: root.activeView === "step_3"; + visible: root.hasShownSecurityImageTip && root.activeView === "step_3"; // Anchors anchors.top: titleBarContainer.bottom; anchors.topMargin: 30; @@ -451,7 +451,10 @@ Item { onVisibleChanged: { if (visible) { + sendSignalToWallet({method: 'disableHmdPreview'}); Commerce.getWalletAuthenticatedStatus(); + } else { + sendSignalToWallet({method: 'maybeEnableHmdPreview'}); } } From 145a0df082654b960a225c276ff1328ccac67a29 Mon Sep 17 00:00:00 2001 From: humbletim Date: Thu, 15 Feb 2018 14:14:07 -0500 Subject: [PATCH 16/49] interim checkin --- interface/src/Application.cpp | 4 +- interface/src/ui/overlays/Base3DOverlay.h | 4 +- interface/src/ui/overlays/ModelOverlay.cpp | 2 +- interface/src/ui/overlays/ModelOverlay.h | 2 +- interface/src/ui/overlays/Shape3DOverlay.cpp | 6 +- interface/src/ui/overlays/Shape3DOverlay.h | 2 +- libraries/entities-renderer/CMakeLists.txt | 2 +- .../src/RenderableEntityItem.h | 4 +- .../src/RenderableModelEntityItem.cpp | 19 +- .../src/RenderableModelEntityItem.h | 3 +- .../graphics-scripting/BufferViewHelpers.cpp | 416 +++++++++- .../graphics-scripting/BufferViewHelpers.h | 35 +- .../BufferViewScripting.cpp | 4 +- .../src/graphics-scripting/Forward.h | 107 +++ .../GraphicsScriptingUtil.cpp | 3 + .../GraphicsScriptingUtil.h | 85 +++ .../ModelScriptingInterface.cpp | 152 ++-- .../ModelScriptingInterface.h | 10 +- .../src/graphics-scripting/ScriptableMesh.cpp | 714 +++++++++--------- .../src/graphics-scripting/ScriptableMesh.h | 198 +++-- .../src/graphics-scripting/ScriptableModel.h | 81 +- 21 files changed, 1219 insertions(+), 634 deletions(-) create mode 100644 libraries/graphics-scripting/src/graphics-scripting/Forward.h create mode 100644 libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.cpp create mode 100644 libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index f24969ce60..bdef2f456b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -600,7 +600,9 @@ public: QString error; scriptable::ModelProviderPointer provider; - if (auto entityInterface = getEntityModelProvider(static_cast(uuid))) { + if (uuid.isNull()) { + provider = nullptr; + } else if (auto entityInterface = getEntityModelProvider(static_cast(uuid))) { provider = entityInterface; } else if (auto overlayInterface = getOverlayModelProvider(static_cast(uuid))) { provider = overlayInterface; diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index 0c8bc5aacb..6ccad338c9 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -13,7 +13,7 @@ #include #include -#include +#include #include "Overlay.h" namespace model { class Mesh; } @@ -37,7 +37,7 @@ public: virtual bool is3D() const override { return true; } virtual uint32_t fetchMetaSubItems(render::ItemIDs& subItems) const override { subItems.push_back(getRenderItemID()); return (uint32_t) subItems.size(); } - virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override { return scriptable::ModelProvider::modelUnavailableError(ok); } + virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) override { return scriptable::ModelProvider::modelUnavailableError(ok); } // TODO: consider implementing registration points in this class glm::vec3 getCenter() const { return getWorldPosition(); } diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index 5a80ca1abf..e007591ce0 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -630,7 +630,7 @@ uint32_t ModelOverlay::fetchMetaSubItems(render::ItemIDs& subItems) const { return 0; } -scriptable::ScriptableModel ModelOverlay::getScriptableModel(bool* ok) { +scriptable::ScriptableModelBase ModelOverlay::getScriptableModel(bool* ok) { if (!_model || !_model->isLoaded()) { return Base3DOverlay::getScriptableModel(ok); } diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index 32d9a08c70..8dc386c733 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -59,7 +59,7 @@ public: void setDrawInFront(bool drawInFront) override; void setDrawHUDLayer(bool drawHUDLayer) override; - virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override; + virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) override; protected: Transform evalRenderTransform() override; diff --git a/interface/src/ui/overlays/Shape3DOverlay.cpp b/interface/src/ui/overlays/Shape3DOverlay.cpp index 8bb3d16888..54423feef6 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.cpp +++ b/interface/src/ui/overlays/Shape3DOverlay.cpp @@ -180,15 +180,15 @@ Transform Shape3DOverlay::evalRenderTransform() { return transform; } -scriptable::ScriptableModel Shape3DOverlay::getScriptableModel(bool* ok) { +scriptable::ScriptableModelBase Shape3DOverlay::getScriptableModel(bool* ok) { auto geometryCache = DependencyManager::get(); auto vertexColor = ColorUtils::toVec3(_color); - scriptable::ScriptableModel result; + scriptable::ScriptableModelBase result; result.metadata = { { "origin", "Shape3DOverlay::"+shapeStrings[_shape] }, { "overlayID", getID() }, }; - result.meshes << geometryCache->meshFromShape(_shape, vertexColor); + result.append(geometryCache->meshFromShape(_shape, vertexColor), {{ "shape", shapeStrings[_shape] }}); if (ok) { *ok = true; } diff --git a/interface/src/ui/overlays/Shape3DOverlay.h b/interface/src/ui/overlays/Shape3DOverlay.h index 34f82af278..f5246d95ac 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.h +++ b/interface/src/ui/overlays/Shape3DOverlay.h @@ -37,7 +37,7 @@ public: void setProperties(const QVariantMap& properties) override; QVariant getProperty(const QString& property) override; - virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override; + virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) override; protected: Transform evalRenderTransform() override; diff --git a/libraries/entities-renderer/CMakeLists.txt b/libraries/entities-renderer/CMakeLists.txt index 27ea04f642..3aa561f927 100644 --- a/libraries/entities-renderer/CMakeLists.txt +++ b/libraries/entities-renderer/CMakeLists.txt @@ -13,7 +13,7 @@ include_hifi_library_headers(fbx) include_hifi_library_headers(entities) include_hifi_library_headers(avatars) include_hifi_library_headers(controllers) -include_hifi_library_headers(graphics-scripting) # for ScriptableModel.h +include_hifi_library_headers(graphics-scripting) # for Forward.h target_bullet() target_polyvox() diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index f07b67fbd0..74759f4fe4 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -17,7 +17,7 @@ #include #include "AbstractViewStateInterface.h" #include "EntitiesRendererLogging.h" -#include +#include class EntityTreeRenderer; @@ -55,7 +55,7 @@ public: const uint64_t& getUpdateTime() const { return _updateTime; } - virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override { return scriptable::ModelProvider::modelUnavailableError(ok); } + virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) override { return scriptable::ModelProvider::modelUnavailableError(ok); } protected: virtual bool needsRenderUpdateFromEntity() const final { return needsRenderUpdateFromEntity(_entity); } virtual void onAddToScene(const EntityItemPointer& entity); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 7b022fefac..3d6714a400 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -950,12 +950,9 @@ QStringList RenderableModelEntityItem::getJointNames() const { return result; } - -scriptable::ScriptableModel render::entities::ModelEntityRenderer::getScriptableModel(bool* ok) { +scriptable::ScriptableModelBase render::entities::ModelEntityRenderer::getScriptableModel(bool* ok) { ModelPointer model; - withReadLock([&] { - model = _model; - }); + withReadLock([&] { model = _model; }); if (!model || !model->isLoaded()) { return scriptable::ModelProvider::modelUnavailableError(ok); @@ -964,6 +961,18 @@ scriptable::ScriptableModel render::entities::ModelEntityRenderer::getScriptable return _model->getScriptableModel(ok); } +bool render::entities::ModelEntityRenderer::replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer newModel, int meshIndex, int partIndex) { + qCDebug(entitiesrenderer) << "REPLACING RenderableModelEntityItem" << newModel->objectName(); + ModelPointer model; + withReadLock([&] { model = _model; }); + + if (!model || !model->isLoaded()) { + return false; + } + + return _model->replaceScriptableModelMeshPart(newModel, meshIndex, partIndex); +} + void RenderableModelEntityItem::simulateRelayedJoints() { ModelPointer model = getModel(); if (model && model->isLoaded()) { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 3e952cb9a7..ffb83d3609 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -140,7 +140,8 @@ class ModelEntityRenderer : public TypedEntityRenderer #include +#include + +#include +#include + +#include #include +#include namespace glm { using hvec2 = glm::tvec2; using hvec4 = glm::tvec4; } //#define DEBUG_BUFFERVIEW_SCRIPTING -#ifdef DEBUG_BUFFERVIEW_SCRIPTING +//#ifdef DEBUG_BUFFERVIEW_SCRIPTING #include "DebugNames.h" - QLoggingCategory bufferview_helpers{"hifi.bufferview"}; -#endif +//#endif namespace { + QLoggingCategory bufferhelper_logging{"hifi.bufferview"}; const std::array XYZW = {{ "x", "y", "z", "w" }}; const std::array ZERO123 = {{ "0", "1", "2", "3" }}; } +gpu::BufferView buffer_helpers::getBufferView(graphics::MeshPointer mesh, gpu::Stream::Slot slot) { + return slot == gpu::Stream::POSITION ? mesh->getVertexBuffer() : mesh->getAttributeBuffer(slot); +} +QMap buffer_helpers::ATTRIBUTES{ + {"position", gpu::Stream::POSITION }, + {"normal", gpu::Stream::NORMAL }, + {"color", gpu::Stream::COLOR }, + {"tangent", gpu::Stream::TEXCOORD0 }, + {"skin_cluster_index", gpu::Stream::SKIN_CLUSTER_INDEX }, + {"skin_cluster_weight", gpu::Stream::SKIN_CLUSTER_WEIGHT }, + {"texcoord0", gpu::Stream::TEXCOORD0 }, + {"texcoord1", gpu::Stream::TEXCOORD1 }, + {"texcoord2", gpu::Stream::TEXCOORD2 }, + {"texcoord3", gpu::Stream::TEXCOORD3 }, + {"texcoord4", gpu::Stream::TEXCOORD4 }, +}; + + template QVariant getBufferViewElement(const gpu::BufferView& view, quint32 index, bool asArray = false) { return glmVecToVariant(view.get(index), asArray); @@ -61,14 +86,14 @@ static void packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, glm::uint3 packedTangent = tangentStruct.pack; } -bool bufferViewElementFromVariant(const gpu::BufferView& view, quint32 index, const QVariant& v) { +bool buffer_helpers::fromVariant(const gpu::BufferView& view, quint32 index, const QVariant& v) { const auto& element = view._element; const auto vecN = element.getScalarCount(); const auto dataType = element.getType(); const auto byteLength = element.getSize(); const auto BYTES_PER_ELEMENT = byteLength / vecN; #ifdef DEBUG_BUFFERVIEW_SCRIPTING - qCDebug(bufferview_helpers) << "bufferViewElementFromVariant" << index << DebugNames::stringFrom(dataType) << BYTES_PER_ELEMENT << vecN; + qCDebug(bufferhelper_logging) << "bufferViewElementFromVariant" << index << DebugNames::stringFrom(dataType) << BYTES_PER_ELEMENT << vecN; #endif if (BYTES_PER_ELEMENT == 1) { switch(vecN) { @@ -122,16 +147,34 @@ bool bufferViewElementFromVariant(const gpu::BufferView& view, quint32 index, co return false; } -QVariant bufferViewElementToVariant(const gpu::BufferView& view, quint32 index, bool asArray, const char* hint) { +bool boundsCheck(const gpu::BufferView& view, quint32 index) { + const auto byteLength = view._element.getSize(); + return ( + index < view.getNumElements() && + index * byteLength < (view._size - 1) * byteLength + ); +} + +QVariant buffer_helpers::toVariant(const gpu::BufferView& view, quint32 index, bool asArray, const char* hint) { const auto& element = view._element; const auto vecN = element.getScalarCount(); const auto dataType = element.getType(); const auto byteLength = element.getSize(); const auto BYTES_PER_ELEMENT = byteLength / vecN; Q_ASSERT(index < view.getNumElements()); - Q_ASSERT(index * vecN * BYTES_PER_ELEMENT < (view._size - vecN * BYTES_PER_ELEMENT)); + if (!boundsCheck(view, index)) { + // sanity checks + auto byteOffset = index * vecN * BYTES_PER_ELEMENT; + auto maxByteOffset = (view._size - 1) * vecN * BYTES_PER_ELEMENT; + if (byteOffset > maxByteOffset) { + qDebug() << "bufferViewElementToVariant -- byteOffset out of range " << byteOffset << " < " << maxByteOffset << DebugNames::stringFrom(dataType); + qDebug() << "bufferViewElementToVariant -- index: " << index << "numElements" << view.getNumElements(); + qDebug() << "bufferViewElementToVariant -- vecN: " << vecN << "byteLength" << byteLength << "BYTES_PER_ELEMENT" << BYTES_PER_ELEMENT; + } + Q_ASSERT(byteOffset <= maxByteOffset); + } #ifdef DEBUG_BUFFERVIEW_SCRIPTING - qCDebug(bufferview_helpers) << "bufferViewElementToVariant" << index << DebugNames::stringFrom(dataType) << BYTES_PER_ELEMENT << vecN; + qCDebug(bufferhelper_logging) << "bufferViewElementToVariant" << index << DebugNames::stringFrom(dataType) << BYTES_PER_ELEMENT << vecN; #endif if (BYTES_PER_ELEMENT == 1) { switch(vecN) { @@ -221,22 +264,137 @@ const T glmVecFromVariant(const QVariant& v) { } template -gpu::BufferView bufferViewFromVector(QVector elements, gpu::Element elementType) { +gpu::BufferView buffer_helpers::fromVector(const QVector& elements, const gpu::Element& elementType) { auto vertexBuffer = std::make_shared(elements.size() * sizeof(T), (gpu::Byte*)elements.data()); return { vertexBuffer, 0, vertexBuffer->getSize(),sizeof(T), elementType }; } +template<> gpu::BufferView buffer_helpers::fromVector(const QVector& elements, const gpu::Element& elementType) { return fromVector(elements, elementType); } +template<> gpu::BufferView buffer_helpers::fromVector(const QVector& elements, const gpu::Element& elementType) { return fromVector(elements, elementType); } -template<> gpu::BufferView bufferViewFromVector(QVector elements, gpu::Element elementType) { return bufferViewFromVector(elements, elementType); } -template<> gpu::BufferView bufferViewFromVector(QVector elements, gpu::Element elementType) { return bufferViewFromVector(elements, elementType); } +template struct getVec4;// { static T get(const gpu::BufferView& view, quint32 index, const char *hint); }; +template struct getScalar;// { static T get(const gpu::BufferView& view, quint32 index, const char *hint); }; -gpu::BufferView cloneBufferView(const gpu::BufferView& input) { +struct gotter { + static float error(const QString& name, const gpu::BufferView& view, quint32 index, const char *hint) { + qDebug() << QString("gotter:: unhandled type=%1(element=%2(%3)) size=%4(per=%5) vec%6 hint=%7 #%8") + .arg(name) + .arg(DebugNames::stringFrom(view._element.getType())) + .arg(view._element.getType()) + .arg(view._element.getSize()) + .arg(view._element.getSize() / view._element.getScalarCount()) + .arg(view._element.getScalarCount()) + .arg(hint) + .arg(view.getNumElements()); + Q_ASSERT(false); + assert(false); + return NAN; + } +}; +template struct getScalar : gotter { + static T get(const gpu::BufferView& view, quint32 index, const char *hint) { switch(view._element.getType()) { + case gpu::UINT32: return view.get(index); + case gpu::UINT16: return view.get(index); + case gpu::UINT8: return view.get(index); + case gpu::INT32: return view.get(index); + case gpu::INT16: return view.get(index); + case gpu::INT8: return view.get(index); + case gpu::FLOAT: return view.get(index); + case gpu::HALF: return T(glm::unpackSnorm1x8(view.get(index))); + default: break; + } return T(error("getScalar", view, index, hint)); + } +}; + +template struct getVec2 : gotter { static T get(const gpu::BufferView& view, quint32 index, const char *hint) { switch(view._element.getType()) { + case gpu::UINT32: return view.get(index); + case gpu::UINT16: return view.get(index); + case gpu::UINT8: return view.get(index); + case gpu::INT32: return view.get(index); + case gpu::INT16: return view.get(index); + case gpu::INT8: return view.get(index); + case gpu::FLOAT: return view.get(index); + case gpu::HALF: return glm::unpackSnorm2x8(view.get(index)); + default: break; + } return T(error("getVec2", view, index, hint)); }}; + + +template struct getVec3 : gotter { static T get(const gpu::BufferView& view, quint32 index, const char *hint) { switch(view._element.getType()) { + case gpu::UINT32: return view.get(index); + case gpu::UINT16: return view.get(index); + case gpu::UINT8: return view.get(index); + case gpu::INT32: return view.get(index); + case gpu::INT16: return view.get(index); + case gpu::INT8: return view.get(index); + case gpu::FLOAT: return view.get(index); + case gpu::HALF: + case gpu::NUINT8: + case gpu::NINT2_10_10_10: + if (view._element.getSize() == sizeof(glm::int32)) { + return getVec4::get(view, index, hint); + } + default: break; + } return T(error("getVec3", view, index, hint)); }}; + +template struct getVec4 : gotter { static T get(const gpu::BufferView& view, quint32 index, const char *hint) { + assert(view._element.getSize() == sizeof(glm::int32)); + switch(view._element.getType()) { + case gpu::UINT32: return view.get(index); + case gpu::UINT16: return view.get(index); + case gpu::UINT8: return view.get(index); + case gpu::INT32: return view.get(index); + case gpu::INT16: return view.get(index); + case gpu::INT8: return view.get(index); + case gpu::NUINT32: break; + case gpu::NUINT16: break; + case gpu::NUINT8: return glm::unpackUnorm4x8(view.get(index)); + case gpu::NUINT2: break; + case gpu::NINT32: break; + case gpu::NINT16: break; + case gpu::NINT8: break; + case gpu::COMPRESSED: break; + case gpu::NUM_TYPES: break; + case gpu::FLOAT: return view.get(index); + case gpu::HALF: return glm::unpackSnorm4x8(view.get(index)); + case gpu::NINT2_10_10_10: return glm::unpackSnorm3x10_1x2(view.get(index)); + } return T(error("getVec4", view, index, hint)); }}; + + +template +struct getVec { + static QVector __to_vector__(const gpu::BufferView& view, const char *hint) { + QVector result; + const quint32 count = (quint32)view.getNumElements(); + result.resize(count); + for (quint32 i = 0; i < count; i++) { + result[i] = FUNC::get(view, i, hint); + } + return result; + } + static T __to_scalar__(const gpu::BufferView& view, quint32 index, const char *hint) { + assert(boundsCheck(view, index)); + return FUNC::get(view, index, hint); + } +}; + +template <> QVector buffer_helpers::toVector(const gpu::BufferView& view, const char *hint) { return getVec,int>::__to_vector__(view, hint); } +template <> QVector buffer_helpers::toVector(const gpu::BufferView& view, const char *hint) { return getVec,glm::vec2>::__to_vector__(view, hint); } +template <> QVector buffer_helpers::toVector(const gpu::BufferView& view, const char *hint) { return getVec,glm::vec3>::__to_vector__(view, hint); } +template <> QVector buffer_helpers::toVector(const gpu::BufferView& view, const char *hint) { return getVec,glm::vec4>::__to_vector__(view, hint); } + + +template <> int buffer_helpers::convert(const gpu::BufferView& view, quint32 index, const char *hint) { return getVec,int>::__to_scalar__(view, index, hint); } +template <> glm::vec2 buffer_helpers::convert(const gpu::BufferView& view, quint32 index, const char *hint) { return getVec,glm::vec2>::__to_scalar__(view, index, hint); } +template <> glm::vec3 buffer_helpers::convert(const gpu::BufferView& view, quint32 index, const char *hint) { return getVec,glm::vec3>::__to_scalar__(view, index, hint); } +template <> glm::vec4 buffer_helpers::convert(const gpu::BufferView& view, quint32 index, const char *hint) { return getVec,glm::vec4>::__to_scalar__(view, index, hint); } + +gpu::BufferView buffer_helpers::clone(const gpu::BufferView& input) { return gpu::BufferView( std::make_shared(input._buffer->getSize(), input._buffer->getData()), input._offset, input._size, input._stride, input._element ); } -gpu::BufferView resizedBufferView(const gpu::BufferView& input, quint32 numElements) { +gpu::BufferView buffer_helpers::resize(const gpu::BufferView& input, quint32 numElements) { auto effectiveSize = input._buffer->getSize() / input.getNumElements(); qDebug() << "resize input" << input.getNumElements() << input._buffer->getSize() << "effectiveSize" << effectiveSize; auto vsize = input._element.getSize() * numElements; @@ -248,3 +406,235 @@ gpu::BufferView resizedBufferView(const gpu::BufferView& input, quint32 numEleme qDebug() << "resized output" << output.getNumElements() << output._buffer->getSize(); return output; } + +graphics::MeshPointer buffer_helpers::cloneMesh(graphics::MeshPointer mesh) { + auto clone = std::make_shared(); + //[](graphics::Mesh* blah) { + //qCDebug(bufferhelper_logging) << "--- DELETING MESH POINTER" << blah; + // delete blah; + //}); + clone->displayName = (QString::fromStdString(mesh->displayName) + "-clone").toStdString(); + //qCInfo(bufferhelper_logging) << "+++ ALLOCATED MESH POINTER ScriptableMesh::cloneMesh" << clone->displayName << clone.get() << !!mesh; + clone->setIndexBuffer(buffer_helpers::clone(mesh->getIndexBuffer())); + clone->setPartBuffer(buffer_helpers::clone(mesh->getPartBuffer())); + auto attributeViews = buffer_helpers::gatherBufferViews(mesh); + for (const auto& a : attributeViews) { + auto& view = a.second; + auto slot = buffer_helpers::ATTRIBUTES[a.first]; + auto points = buffer_helpers::clone(view); + if (slot == gpu::Stream::POSITION) { + clone->setVertexBuffer(points); + } else { + clone->addAttribute(slot, points); + } + } + return clone; +} + + +/// --- buffer view <-> variant helpers + +namespace { + // expand the corresponding attribute buffer (creating it if needed) so that it matches POSITIONS size and specified element type + gpu::BufferView _expandedAttributeBuffer(const graphics::MeshPointer mesh, gpu::Stream::Slot slot) { + gpu::BufferView bufferView = buffer_helpers::getBufferView(mesh, slot); + const auto& elementType = bufferView._element; + //auto vecN = element.getScalarCount(); + //auto type = element.getType(); + //gpu::Element elementType = getVecNElement(type, vecN); + + gpu::Size elementSize = elementType.getSize(); + auto nPositions = mesh->getNumVertices(); + auto vsize = nPositions * elementSize; + auto diffTypes = (elementType.getType() != bufferView._element.getType() || + elementType.getSize() > bufferView._element.getSize() || + elementType.getScalarCount() > bufferView._element.getScalarCount() || + vsize > bufferView._size + ); + QString hint = QString("%1").arg(slot); +#ifdef DEBUG_BUFFERVIEW_SCRIPTING + hint = DebugNames::stringFrom(slot); +#endif +#ifdef DEV_BUILD + auto beforeCount = bufferView.getNumElements(); + auto beforeTotal = bufferView._size; +#endif + if (bufferView.getNumElements() < nPositions || diffTypes) { + if (!bufferView._buffer || bufferView.getNumElements() == 0) { + qCInfo(bufferhelper_logging).nospace() << "ScriptableMesh -- adding missing mesh attribute '" << hint << "' for BufferView"; + gpu::Byte *data = new gpu::Byte[vsize]; + memset(data, 0, vsize); + auto buffer = new gpu::Buffer(vsize, (gpu::Byte*)data); + delete[] data; + bufferView = gpu::BufferView(buffer, elementType); + mesh->addAttribute(slot, bufferView); + } else { + qCInfo(bufferhelper_logging) << "ScriptableMesh -- resizing Buffer current:" << hint << bufferView._buffer->getSize() << "wanted:" << vsize; + bufferView._element = elementType; + bufferView._buffer->resize(vsize); + bufferView._size = bufferView._buffer->getSize(); + } + } +#ifdef DEV_BUILD + auto afterCount = bufferView.getNumElements(); + auto afterTotal = bufferView._size; + if (beforeTotal != afterTotal || beforeCount != afterCount) { + QString typeName = QString("%1").arg(bufferView._element.getType()); +#ifdef DEBUG_BUFFERVIEW_SCRIPTING + typeName = DebugNames::stringFrom(bufferView._element.getType()); +#endif + qCDebug(bufferhelper_logging, "NOTE:: _expandedAttributeBuffer.%s vec%d %s (before count=%lu bytes=%lu // after count=%lu bytes=%lu)", + hint.toStdString().c_str(), bufferView._element.getScalarCount(), + typeName.toStdString().c_str(), beforeCount, beforeTotal, afterCount, afterTotal); + } +#endif + return bufferView; + } + + gpu::BufferView expandAttributeToMatchPositions(graphics::MeshPointer mesh, gpu::Stream::Slot slot) { + if (slot == gpu::Stream::POSITION) { + return buffer_helpers::getBufferView(mesh, slot); + } + return _expandedAttributeBuffer(mesh, slot); + } +} + +std::map buffer_helpers::gatherBufferViews(graphics::MeshPointer mesh, const QStringList& expandToMatchPositions) { + std::map attributeViews; + if (!mesh) { + return attributeViews; + } + for (const auto& a : buffer_helpers::ATTRIBUTES.toStdMap()) { + auto name = a.first; + auto slot = a.second; + auto view = getBufferView(mesh, slot); + auto beforeCount = view.getNumElements(); + auto beforeTotal = view._size; + if (expandToMatchPositions.contains(name)) { + expandAttributeToMatchPositions(mesh, slot); + } + if (beforeCount > 0) { + auto element = view._element; + auto vecN = element.getScalarCount(); + //auto type = element.getType(); + QString typeName = QString("%1").arg(element.getType()); +#ifdef DEBUG_BUFFERVIEW_SCRIPTING + typeName = DebugNames::stringFrom(element.getType()); +#endif + + attributeViews[name] = getBufferView(mesh, slot); + +#if DEV_BUILD + auto afterTotal = attributeViews[name]._size; + auto afterCount = attributeViews[name].getNumElements(); + if (beforeTotal != afterTotal || beforeCount != afterCount) { + qCDebug(bufferhelper_logging, "NOTE:: gatherBufferViews.%s vec%d %s (before count=%lu bytes=%lu // after count=%lu bytes=%lu)", + name.toStdString().c_str(), vecN, typeName.toStdString().c_str(), beforeCount, beforeTotal, afterCount, afterTotal); + } +#endif + } + } + return attributeViews; +} + + +bool buffer_helpers::recalculateNormals(graphics::MeshPointer mesh) { + qCInfo(bufferhelper_logging) << "Recalculating normals" << !!mesh; + if (!mesh) { + return false; + } + buffer_helpers::gatherBufferViews(mesh, { "normal", "color" }); // ensures #normals >= #positions + auto normals = mesh->getAttributeBuffer(gpu::Stream::NORMAL); + auto verts = mesh->getVertexBuffer(); + auto indices = mesh->getIndexBuffer(); + auto esize = indices._element.getSize(); + auto numPoints = indices.getNumElements(); + const auto TRIANGLE = 3; + quint32 numFaces = (quint32)numPoints / TRIANGLE; + //QVector faces; + QVector faceNormals; + QMap> vertexToFaces; + //faces.resize(numFaces); + faceNormals.resize(numFaces); + auto numNormals = normals.getNumElements(); + qCInfo(bufferhelper_logging) << QString("numFaces: %1, numNormals: %2, numPoints: %3").arg(numFaces).arg(numNormals).arg(numPoints); + if (normals.getNumElements() != verts.getNumElements()) { + return false; + } + for (quint32 i = 0; i < numFaces; i++) { + quint32 I = TRIANGLE * i; + quint32 i0 = esize == 4 ? indices.get(I+0) : indices.get(I+0); + quint32 i1 = esize == 4 ? indices.get(I+1) : indices.get(I+1); + quint32 i2 = esize == 4 ? indices.get(I+2) : indices.get(I+2); + + Triangle face = { + verts.get(i1), + verts.get(i2), + verts.get(i0) + }; + faceNormals[i] = face.getNormal(); + if (glm::isnan(faceNormals[i].x)) { + qCInfo(bufferhelper_logging) << i << i0 << i1 << i2 << glmVecToVariant(face.v0) << glmVecToVariant(face.v1) << glmVecToVariant(face.v2); + break; + } + vertexToFaces[glm::to_string(face.v0).c_str()] << i; + vertexToFaces[glm::to_string(face.v1).c_str()] << i; + vertexToFaces[glm::to_string(face.v2).c_str()] << i; + } + for (quint32 j = 0; j < numNormals; j++) { + //auto v = verts.get(j); + glm::vec3 normal { 0.0f, 0.0f, 0.0f }; + QString key { glm::to_string(verts.get(j)).c_str() }; + const auto& faces = vertexToFaces.value(key); + if (faces.size()) { + for (const auto i : faces) { + normal += faceNormals[i]; + } + normal *= 1.0f / (float)faces.size(); + } else { + static int logged = 0; + if (logged++ < 10) { + qCInfo(bufferhelper_logging) << "no faces for key!?" << key; + } + normal = verts.get(j); + } + if (glm::isnan(normal.x)) { + static int logged = 0; + if (logged++ < 10) { + qCInfo(bufferhelper_logging) << "isnan(normal.x)" << j << glmVecToVariant(normal); + } + break; + } + normals.edit(j) = glm::normalize(normal); + } + return true; +} + +QVariant buffer_helpers::toVariant(const glm::mat4& mat4) { + QVector floats; + floats.resize(16); + memcpy(floats.data(), &mat4, sizeof(glm::mat4)); + QVariant v; + v.setValue>(floats); + return v; +}; + +QVariant buffer_helpers::toVariant(const Extents& box) { + return QVariantMap{ + { "center", glmVecToVariant(box.minimum + (box.size() / 2.0f)) }, + { "minimum", glmVecToVariant(box.minimum) }, + { "maximum", glmVecToVariant(box.maximum) }, + { "dimensions", glmVecToVariant(box.size()) }, + }; +} + +QVariant buffer_helpers::toVariant(const AABox& box) { + return QVariantMap{ + { "brn", glmVecToVariant(box.getCorner()) }, + { "tfl", glmVecToVariant(box.calcTopFarLeft()) }, + { "center", glmVecToVariant(box.calcCenter()) }, + { "minimum", glmVecToVariant(box.getMinimumPoint()) }, + { "maximum", glmVecToVariant(box.getMaximumPoint()) }, + { "dimensions", glmVecToVariant(box.getDimensions()) }, + }; +} diff --git a/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h b/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h index d0d42ca419..d963fd4b22 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h +++ b/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h @@ -7,6 +7,8 @@ #pragma once #include +#include +#include namespace gpu { class BufferView; @@ -15,11 +17,34 @@ namespace gpu { template QVariant glmVecToVariant(const T& v, bool asArray = false); template const T glmVecFromVariant(const QVariant& v); -QVariant bufferViewElementToVariant(const gpu::BufferView& view, quint32 index, bool asArray = false, const char* hint = ""); -bool bufferViewElementFromVariant(const gpu::BufferView& view, quint32 index, const QVariant& v); -template gpu::BufferView bufferViewFromVector(QVector elements, gpu::Element elementType); +namespace graphics { + class Mesh; + using MeshPointer = std::shared_ptr; +} -gpu::BufferView cloneBufferView(const gpu::BufferView& input); -gpu::BufferView resizedBufferView(const gpu::BufferView& input, quint32 numElements); +class Extents; +class AABox; +struct buffer_helpers { + static graphics::MeshPointer cloneMesh(graphics::MeshPointer mesh); + static QMap ATTRIBUTES; + static std::map gatherBufferViews(graphics::MeshPointer mesh, const QStringList& expandToMatchPositions = QStringList()); + static bool recalculateNormals(graphics::MeshPointer meshProxy); + static gpu::BufferView getBufferView(graphics::MeshPointer mesh, quint8 slot); + + static QVariant toVariant(const Extents& box); + static QVariant toVariant(const AABox& box); + static QVariant toVariant(const glm::mat4& mat4); + static QVariant toVariant(const gpu::BufferView& view, quint32 index, bool asArray = false, const char* hint = ""); + + static bool fromVariant(const gpu::BufferView& view, quint32 index, const QVariant& v); + + template static gpu::BufferView fromVector(const QVector& elements, const gpu::Element& elementType); + + template static QVector toVector(const gpu::BufferView& view, const char *hint = ""); + template static T convert(const gpu::BufferView& view, quint32 index, const char* hint = ""); + + static gpu::BufferView clone(const gpu::BufferView& input); + static gpu::BufferView resize(const gpu::BufferView& input, quint32 numElements); +}; diff --git a/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp b/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp index 367c0589e9..ab6f2c92be 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp @@ -26,7 +26,7 @@ QScriptValue getBufferViewElement(QScriptEngine* js, const gpu::BufferView& view } QScriptValue bufferViewElementToScriptValue(QScriptEngine* engine, const gpu::BufferView& view, quint32 index, bool asArray, const char* hint) { - QVariant result = bufferViewElementToVariant(view, index, asArray, hint); + QVariant result = buffer_helpers::toVariant(view, index, asArray, hint); if (!result.isValid()) { return QScriptValue::NullValue; } @@ -39,7 +39,7 @@ void setBufferViewElement(const gpu::BufferView& view, quint32 index, const QScr } bool bufferViewElementFromScriptValue(const QScriptValue& v, const gpu::BufferView& view, quint32 index) { - return bufferViewElementFromVariant(view, index, v.toVariant()); + return buffer_helpers::fromVariant(view, index, v.toVariant()); } // diff --git a/libraries/graphics-scripting/src/graphics-scripting/Forward.h b/libraries/graphics-scripting/src/graphics-scripting/Forward.h new file mode 100644 index 0000000000..15973b5852 --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/Forward.h @@ -0,0 +1,107 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +namespace graphics { + class Mesh; +} +namespace gpu { + class BufferView; +} +class QScriptEngine; + +namespace scriptable { + using Mesh = graphics::Mesh; + using MeshPointer = std::shared_ptr; + using WeakMeshPointer = std::weak_ptr; + + class ScriptableModelBase; + using ScriptableModelBasePointer = QPointer; + + class ModelProvider; + using ModelProviderPointer = std::shared_ptr; + using WeakModelProviderPointer = std::weak_ptr; + + class ScriptableMeshBase : public QObject { + Q_OBJECT + public: + WeakModelProviderPointer provider; + ScriptableModelBasePointer model; + WeakMeshPointer mesh; + MeshPointer ownedMesh; + QVariantMap metadata; + ScriptableMeshBase(WeakModelProviderPointer provider, ScriptableModelBasePointer model, WeakMeshPointer mesh, const QVariantMap& metadata); + ScriptableMeshBase(WeakMeshPointer mesh = WeakMeshPointer()); + ScriptableMeshBase(MeshPointer mesh, const QVariantMap& metadata); + ScriptableMeshBase(const ScriptableMeshBase& other) { *this = other; } + ScriptableMeshBase& operator=(const ScriptableMeshBase& view); + virtual ~ScriptableMeshBase(); + Q_INVOKABLE const scriptable::MeshPointer getMeshPointer() const { return mesh.lock(); } + Q_INVOKABLE const scriptable::ModelProviderPointer getModelProviderPointer() const { return provider.lock(); } + Q_INVOKABLE const scriptable::ScriptableModelBasePointer getModelBasePointer() const { return model; } + }; + + // abstract container for holding one or more references to mesh pointers + class ScriptableModelBase : public QObject { + Q_OBJECT + public: + WeakModelProviderPointer provider; + QUuid objectID; // spatially nestable ID + QVariantMap metadata; + QVector meshes; + + ScriptableModelBase(QObject* parent = nullptr) : QObject(parent) {} + ScriptableModelBase(const ScriptableModelBase& other) { *this = other; } + ScriptableModelBase& operator=(const ScriptableModelBase& other) { + provider = other.provider; + objectID = other.objectID; + metadata = other.metadata; + for (auto& mesh : other.meshes) { + append(mesh); + } + return *this; + } + virtual ~ScriptableModelBase(); + + void mixin(const QVariantMap& other); + void append(const ScriptableModelBase& other, const QVariantMap& modelMetadata = QVariantMap()); + void append(scriptable::WeakMeshPointer mesh, const QVariantMap& metadata = QVariantMap()); + void append(const ScriptableMeshBase& mesh, const QVariantMap& metadata = QVariantMap()); + // TODO: in future containers for these could go here + // QVariantMap shapes; + // QVariantMap materials; + // QVariantMap armature; + }; + + // mixin class for Avatar/Entity/Overlay Rendering that expose their in-memory graphics::Meshes + class ModelProvider { + public: + QVariantMap metadata{ { "providerType", "unknown" } }; + static scriptable::ScriptableModelBase modelUnavailableError(bool* ok) { if (ok) { *ok = false; } return {}; } + virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) = 0; + + virtual bool replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer model, int meshIndex, int partIndex) { return false; } + }; + + // mixin class for resolving UUIDs into a corresponding ModelProvider + class ModelProviderFactory : public Dependency { + public: + virtual scriptable::ModelProviderPointer lookupModelProvider(QUuid uuid) = 0; + }; + + using uint32 = quint32; + class ScriptableModel; + using ScriptableModelPointer = QPointer; + class ScriptableMesh; + using ScriptableMeshPointer = QPointer; + class ScriptableMeshPart; + using ScriptableMeshPartPointer = QPointer; + bool registerMetaTypes(QScriptEngine* engine); +} diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.cpp b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.cpp new file mode 100644 index 0000000000..aabf83ff66 --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.cpp @@ -0,0 +1,3 @@ +#include "GraphicsScriptingUtil.h" + +Q_LOGGING_CATEGORY(graphics_scripting, "hifi.scripting.graphics") diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h new file mode 100644 index 0000000000..a536fc413c --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h @@ -0,0 +1,85 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +Q_DECLARE_LOGGING_CATEGORY(graphics_scripting) + +namespace scriptable { + // derive current context's C++ QObject (based on current JS "this" value) + template T this_qobject_cast(QScriptEngine* engine) { + auto context = engine ? engine->currentContext() : nullptr; + return qscriptvalue_cast(context ? context->thisObject() : QScriptValue::NullValue); + } + // JS => QPointer + template QPointer qpointer_qobject_cast(const QScriptValue& value) { + auto obj = value.toQObject(); + qCInfo(graphics_scripting) << "qpointer_qobject_cast" << obj << value.toString(); + if (auto tmp = qobject_cast(obj)) { + return QPointer(tmp); + } + if (auto tmp = static_cast(obj)) { + return QPointer(tmp); + } + return nullptr; + } + inline QString toDebugString(QObject* tmp) { + return QString("%0 (0x%1%2)") + .arg(tmp ? tmp->metaObject()->className() : "QObject") + .arg(qulonglong(tmp), 16, 16, QChar('0')) + .arg(tmp && tmp->objectName().size() ? " name=" + tmp->objectName() : ""); + } + template QString toDebugString(std::shared_ptr tmp) { + return toDebugString(qobject_cast(tmp.get())); + } + + // C++ > QtOwned instance + template std::shared_ptr make_qtowned(Rest... rest) { + T* tmp = new T(rest...); + qCInfo(graphics_scripting) << "scriptable::make_qtowned" << toDebugString(tmp); + QString debug = toDebugString(tmp); + if (tmp) { + tmp->metadata["__ownership__"] = QScriptEngine::QtOwnership; + QObject::connect(tmp, &QObject::destroyed, [=]() { qCInfo(graphics_scripting) << "-------- ~scriptable::make_qtowned" << debug; }); + auto ptr = std::shared_ptr(tmp, [debug](T* tmp) { + //qDebug() << "~std::shared_ptr" << debug; + delete tmp; + }); + return ptr; + } else { + return std::shared_ptr(tmp); + } + } + // C++ > ScriptOwned JS instance + template QPointer make_scriptowned(Rest... rest) { + T* tmp = new T(rest...); + qCInfo(graphics_scripting) << "scriptable::make_scriptowned" << toDebugString(tmp); + if (tmp) { + tmp->metadata["__ownership__"] = QScriptEngine::ScriptOwnership; + //auto blah = (DeleterFunction)[](void* delme) { }; + return add_scriptowned_destructor(tmp); + } else { + return QPointer(tmp); + } + } + // C++ > ScriptOwned JS instance + template QPointer add_scriptowned_destructor(T* tmp) { + QString debug = toDebugString(tmp); + if (tmp) { + QObject::connect(tmp, &QObject::destroyed, [=]() { + qCInfo(graphics_scripting) << "-------- ~scriptable::make_scriptowned" << debug;// << !!customDeleter; + //if (customDeleter) { + // customDeleter(tmp); + //} + }); + } else { + qCInfo(graphics_scripting) << "add_scriptowned_destructor -- not connecting to null value" << debug; + } + return QPointer(tmp); + } +} diff --git a/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp index ab85fb8265..ab9403a8ed 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp @@ -26,12 +26,11 @@ #include "BufferViewScripting.h" #include "ScriptableMesh.h" +#include "GraphicsScriptingUtil.h" #include "ModelScriptingInterface.moc" -namespace { - QLoggingCategory model_scripting { "hifi.model.scripting" }; -} +#include "RegisteredMetaTypes.h" ModelScriptingInterface::ModelScriptingInterface(QObject* parent) : QObject(parent) { if (auto scriptEngine = qobject_cast(parent)) { @@ -39,8 +38,51 @@ ModelScriptingInterface::ModelScriptingInterface(QObject* parent) : QObject(pare } } -void ModelScriptingInterface::getMeshes(QUuid uuid, QScriptValue scopeOrCallback, QScriptValue methodOrName) { - auto handler = makeScopedHandlerObject(scopeOrCallback, methodOrName); +bool ModelScriptingInterface::updateMeshes(QUuid uuid, const scriptable::ScriptableMeshPointer mesh, int meshIndex, int partIndex) { + auto model = scriptable::make_qtowned(); + if (mesh) { + model->append(*mesh); + } + return updateMeshes(uuid, model.get()); +} + +bool ModelScriptingInterface::updateMeshes(QUuid uuid, const scriptable::ScriptableModelPointer model) { + auto appProvider = DependencyManager::get(); + qCDebug(graphics_scripting) << "appProvider" << appProvider.data(); + scriptable::ModelProviderPointer provider = appProvider ? appProvider->lookupModelProvider(uuid) : nullptr; + QString providerType = provider ? provider->metadata.value("providerType").toString() : QString(); + if (providerType.isEmpty()) { + providerType = "unknown"; + } + bool success = false; + if (provider) { + qCDebug(graphics_scripting) << "fetching meshes from " << providerType << "..."; + auto scriptableMeshes = provider->getScriptableModel(&success); + qCDebug(graphics_scripting) << "//fetched meshes from " << providerType << "success:" <operator scriptable::ScriptableModelBasePointer(); + qCDebug(graphics_scripting) << "as base" << base; + if (base) { + //auto meshes = model->getConstMeshes(); + success = provider->replaceScriptableModelMeshPart(base, -1, -1); + + // for (uint32_t m = 0; success && m < meshes.size(); m++) { + // const auto& mesh = meshes.at(m); + // for (int p = 0; success && p < mesh->getNumParts(); p++) { + // qCDebug(graphics_scripting) << "provider->replaceScriptableModelMeshPart" << "meshIndex" << m << "partIndex" << p; + // success = provider->replaceScriptableModelMeshPart(base, m, p); + // //if (!success) { + // qCDebug(graphics_scripting) << "//provider->replaceScriptableModelMeshPart" << "meshIndex" << m << "partIndex" << p << success; + // } + // } + } + } + } + return success; +} + +void ModelScriptingInterface::getMeshes(QUuid uuid, QScriptValue callback) { + auto handler = scriptable::jsBindCallback(callback); Q_ASSERT(handler.engine() == this->engine()); QPointer engine = dynamic_cast(handler.engine()); @@ -49,18 +91,23 @@ void ModelScriptingInterface::getMeshes(QUuid uuid, QScriptValue scopeOrCallback QString error; auto appProvider = DependencyManager::get(); - qDebug() << "appProvider" << appProvider.data(); + qCDebug(graphics_scripting) << "appProvider" << appProvider.data(); scriptable::ModelProviderPointer provider = appProvider ? appProvider->lookupModelProvider(uuid) : nullptr; QString providerType = provider ? provider->metadata.value("providerType").toString() : QString(); if (providerType.isEmpty()) { providerType = "unknown"; } if (provider) { - qCDebug(model_scripting) << "fetching meshes from " << providerType << "..."; + qCDebug(graphics_scripting) << "fetching meshes from " << providerType << "..."; auto scriptableMeshes = provider->getScriptableModel(&success); - qCDebug(model_scripting) << "//fetched meshes from " << providerType << "success:" <(scriptableMeshes); + QString debugString = scriptable::toDebugString(meshes); + QObject::connect(meshes, &QObject::destroyed, this, [=]() { + qCDebug(graphics_scripting) << "///fetched meshes" << debugString; + }); + if (meshes->objectName().isEmpty()) { meshes->setObjectName(providerType+"::meshes"); } @@ -75,20 +122,20 @@ void ModelScriptingInterface::getMeshes(QUuid uuid, QScriptValue scopeOrCallback } if (!error.isEmpty()) { - qCWarning(model_scripting) << "ModelScriptingInterface::getMeshes ERROR" << error; + qCWarning(graphics_scripting) << "ModelScriptingInterface::getMeshes ERROR" << error; callScopedHandlerObject(handler, engine->makeError(error), QScriptValue::NullValue); } else { - callScopedHandlerObject(handler, QScriptValue::NullValue, engine->newQObject(meshes, QScriptEngine::ScriptOwnership)); + callScopedHandlerObject(handler, QScriptValue::NullValue, engine->toScriptValue(meshes)); } } QString ModelScriptingInterface::meshToOBJ(const scriptable::ScriptableModel& _in) { const auto& in = _in.getConstMeshes(); - qCDebug(model_scripting) << "meshToOBJ" << in.size(); + qCDebug(graphics_scripting) << "meshToOBJ" << in.size(); if (in.size()) { QList meshes; foreach (auto meshProxy, in) { - qCDebug(model_scripting) << "meshToOBJ" << meshProxy; + qCDebug(graphics_scripting) << "meshToOBJ" << meshProxy; if (meshProxy) { meshes.append(getMeshPointer(meshProxy)); } @@ -207,7 +254,7 @@ QScriptValue ModelScriptingInterface::appendMeshes(scriptable::ScriptableModel _ (gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL)); - return engine()->toScriptValue(scriptable::ScriptableMeshPointer(new scriptable::ScriptableMesh(nullptr, result))); + return engine()->toScriptValue(scriptable::make_scriptowned(result)); } QScriptValue ModelScriptingInterface::transformMesh(scriptable::ScriptableMeshPointer meshProxy, glm::mat4 transform) { @@ -220,8 +267,7 @@ QScriptValue ModelScriptingInterface::transformMesh(scriptable::ScriptableMeshPo [&](glm::vec3 color){ return color; }, [&](glm::vec3 normal){ return glm::vec3(transform * glm::vec4(normal, 0.0f)); }, [&](uint32_t index){ return index; }); - scriptable::ScriptableMeshPointer resultProxy = scriptable::ScriptableMeshPointer(new scriptable::ScriptableMesh(nullptr, result)); - return engine()->toScriptValue(resultProxy); + return engine()->toScriptValue(scriptable::make_scriptowned(result)); } QScriptValue ModelScriptingInterface::getVertexCount(scriptable::ScriptableMeshPointer meshProxy) { @@ -270,7 +316,7 @@ QScriptValue ModelScriptingInterface::newMesh(const QVector& vertices sizeof(glm::vec3), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); mesh->addAttribute(gpu::Stream::NORMAL, normalBufferView); } else { - qCWarning(model_scripting, "ModelScriptingInterface::newMesh normals must be same length as vertices"); + qCWarning(graphics_scripting, "ModelScriptingInterface::newMesh normals must be same length as vertices"); } // indices (faces) @@ -300,54 +346,10 @@ QScriptValue ModelScriptingInterface::newMesh(const QVector& vertices - scriptable::ScriptableMeshPointer meshProxy = scriptable::ScriptableMeshPointer(new scriptable::ScriptableMesh(nullptr, mesh)); - return engine()->toScriptValue(meshProxy); + return engine()->toScriptValue(scriptable::make_scriptowned(mesh)); } namespace { - QScriptValue meshPointerToScriptValue(QScriptEngine* engine, scriptable::ScriptableMeshPointer const &in) { - if (!in) { - return QScriptValue::NullValue; - } - return engine->newQObject(in, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects); - } - - void meshPointerFromScriptValue(const QScriptValue& value, scriptable::ScriptableMeshPointer &out) { - auto obj = value.toQObject(); - qDebug() << "meshPointerFromScriptValue" << obj; - if (auto tmp = qobject_cast(obj)) { - out = tmp; - } - // FIXME: Why does above cast not work on Win32!? - if (!out) { - if (auto smp = static_cast(obj)) { - qDebug() << "meshPointerFromScriptValue2" << smp; - out = smp; - } - } - } - - QScriptValue modelPointerToScriptValue(QScriptEngine* engine, const scriptable::ScriptableModelPointer &in) { - return engine->newQObject(in, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects); - // QScriptValue result = engine->newArray(); - // int i = 0; - // foreach(auto& mesh, in->getMeshes()) { - // result.setProperty(i++, meshPointerToScriptValue(engine, mesh)); - // } - // return result; - } - - void modelPointerFromScriptValue(const QScriptValue& value, scriptable::ScriptableModelPointer &out) { - const auto length = value.property("length").toInt32(); - qCDebug(model_scripting) << "in modelPointerFromScriptValue, length =" << length; - for (int i = 0; i < length; i++) { - if (const auto meshProxy = qobject_cast(value.property(i).toQObject())) { - out->meshes.append(meshProxy->getMeshPointer()); - } else { - qCDebug(model_scripting) << "null meshProxy" << i; - } - } - } // FIXME: MESHFACES: // QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const mesh::MeshFace &meshFace) { @@ -365,29 +367,11 @@ namespace { // qScriptValueToSequence(array, result); // } - QScriptValue qVectorUInt32ToScriptValue(QScriptEngine* engine, const QVector& vector) { - return qScriptValueFromSequence(engine, vector); - } - - void qVectorUInt32FromScriptValue(const QScriptValue& array, QVector& result) { - qScriptValueToSequence(array, result); - } } -int meshUint32 = qRegisterMetaType(); -namespace mesh { - int meshUint32 = qRegisterMetaType(); -} -int qVectorMeshUint32 = qRegisterMetaType>(); void ModelScriptingInterface::registerMetaTypes(QScriptEngine* engine) { - qScriptRegisterSequenceMetaType>(engine); - qScriptRegisterSequenceMetaType>(engine); - - qScriptRegisterMetaType(engine, qVectorUInt32ToScriptValue, qVectorUInt32FromScriptValue); - qScriptRegisterMetaType(engine, meshPointerToScriptValue, meshPointerFromScriptValue); - qScriptRegisterMetaType(engine, modelPointerToScriptValue, modelPointerFromScriptValue); - + scriptable::registerMetaTypes(engine); // FIXME: MESHFACES: remove if MeshFace is not needed anywhere // qScriptRegisterSequenceMetaType(engine); // qScriptRegisterMetaType(engine, meshFaceToScriptValue, meshFaceFromScriptValue); @@ -395,7 +379,7 @@ void ModelScriptingInterface::registerMetaTypes(QScriptEngine* engine) { } MeshPointer ModelScriptingInterface::getMeshPointer(const scriptable::ScriptableMesh& meshProxy) { - return meshProxy._mesh;//getMeshPointer(&meshProxy); + return meshProxy.getMeshPointer(); } MeshPointer ModelScriptingInterface::getMeshPointer(scriptable::ScriptableMesh& meshProxy) { return getMeshPointer(&meshProxy); @@ -406,7 +390,7 @@ MeshPointer ModelScriptingInterface::getMeshPointer(scriptable::ScriptableMeshPo if (context()){ context()->throwError("expected meshProxy as first parameter"); } else { - qDebug() << "expected meshProxy as first parameter"; + qCDebug(graphics_scripting) << "expected meshProxy as first parameter"; } return result; } @@ -415,7 +399,7 @@ MeshPointer ModelScriptingInterface::getMeshPointer(scriptable::ScriptableMeshPo if (context()) { context()->throwError("expected valid meshProxy as first parameter"); } else { - qDebug() << "expected valid meshProxy as first parameter"; + qCDebug(graphics_scripting) << "expected valid meshProxy as first parameter"; } return result; } diff --git a/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h index eac4df3216..fa7b885014 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h @@ -15,19 +15,17 @@ #include #include -#include - #include #include #include "ScriptableMesh.h" #include + class ModelScriptingInterface : public QObject, public QScriptable, public Dependency { Q_OBJECT public: ModelScriptingInterface(QObject* parent = nullptr); - static void registerMetaTypes(QScriptEngine* engine); public slots: /**jsdoc @@ -36,7 +34,9 @@ public slots: * @function ModelScriptingInterface.getMeshes * @param {EntityID} entityID The ID of the entity whose meshes are to be retrieve */ - void getMeshes(QUuid uuid, QScriptValue scopeOrCallback, QScriptValue methodOrName = QScriptValue()); + void getMeshes(QUuid uuid, QScriptValue callback); + bool updateMeshes(QUuid uuid, const scriptable::ScriptableModelPointer model); + bool updateMeshes(QUuid uuid, const scriptable::ScriptableMeshPointer mesh, int meshIndex=0, int partIndex=0); QString meshToOBJ(const scriptable::ScriptableModel& in); @@ -48,6 +48,8 @@ public slots: QScriptValue getVertexCount(scriptable::ScriptableMeshPointer meshProxy); QScriptValue getVertex(scriptable::ScriptableMeshPointer meshProxy, quint32 vertexIndex); + static void registerMetaTypes(QScriptEngine* engine); + private: scriptable::MeshPointer getMeshPointer(scriptable::ScriptableMeshPointer meshProxy); scriptable::MeshPointer getMeshPointer(scriptable::ScriptableMesh& meshProxy); diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp index 1b16a6d263..b83b901acd 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp @@ -9,17 +9,16 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "GraphicsScriptingUtil.h" #include "ScriptableMesh.h" #include #include #include -#include #include #include #include #include -#include #include "ScriptableMesh.moc" @@ -29,49 +28,45 @@ #include "OBJWriter.h" -QLoggingCategory mesh_logging { "hifi.scripting.mesh" }; - -// FIXME: unroll/resolve before PR -using namespace scriptable; -QMap ScriptableMesh::ATTRIBUTES{ - {"position", gpu::Stream::POSITION }, - {"normal", gpu::Stream::NORMAL }, - {"color", gpu::Stream::COLOR }, - {"tangent", gpu::Stream::TEXCOORD0 }, - {"skin_cluster_index", gpu::Stream::SKIN_CLUSTER_INDEX }, - {"skin_cluster_weight", gpu::Stream::SKIN_CLUSTER_WEIGHT }, - {"texcoord0", gpu::Stream::TEXCOORD0 }, - {"texcoord1", gpu::Stream::TEXCOORD1 }, - {"texcoord2", gpu::Stream::TEXCOORD2 }, - {"texcoord3", gpu::Stream::TEXCOORD3 }, - {"texcoord4", gpu::Stream::TEXCOORD4 }, -}; - - -QString scriptable::ScriptableModel::toString() const { - return QString("[ScriptableModel%1%2]") - .arg(objectID.isNull() ? "" : " objectID="+objectID.toString()) - .arg(objectName().isEmpty() ? "" : " name=" +objectName()); +namespace scriptable { + // QScriptValue jsBindCallback(QScriptValue callback); + // template QPointer qpointer_qobject_cast(const QScriptValue& value); + // template T this_qobject_cast(QScriptEngine* engine); + // template QPointer make_scriptowned(Rest... rest); } -const QVector scriptable::ScriptableModel::getConstMeshes() const { - QVector out; - for(const auto& mesh : meshes) { - const scriptable::ScriptableMeshPointer m = scriptable::ScriptableMeshPointer(new scriptable::ScriptableMesh(const_cast(this), mesh)); - out << m; +scriptable::ScriptableMeshPart::ScriptableMeshPart(scriptable::ScriptableMeshPointer parentMesh, int partIndex) + : parentMesh(parentMesh), partIndex(partIndex) { + setObjectName(QString("%1.part[%2]").arg(parentMesh ? parentMesh->objectName() : "").arg(partIndex)); +} + +scriptable::ScriptableMesh::ScriptableMesh(const ScriptableMeshBase& other) + : ScriptableMeshBase(other) { + auto mesh = getMeshPointer(); + QString name = mesh ? QString::fromStdString(mesh->modelName) : ""; + if (name.isEmpty()) { + name = mesh ? QString::fromStdString(mesh->displayName) : ""; } - return out; + auto parentModel = getParentModel(); + setObjectName(QString("%1#%2").arg(parentModel ? parentModel->objectName() : "").arg(name)); } -QVector scriptable::ScriptableModel::getMeshes() { - QVector out; - for(auto& mesh : meshes) { - scriptable::ScriptableMeshPointer m{new scriptable::ScriptableMesh(this, mesh)}; - out << m; + +QVector scriptable::ScriptableMesh::getMeshParts() const { + QVector out; + for (quint32 i = 0; i < getNumParts(); i++) { + out << scriptable::make_scriptowned(getSelf(), i); } return out; } -quint32 ScriptableMesh::getNumVertices() const { +quint32 scriptable::ScriptableMesh::getNumIndices() const { + if (auto mesh = getMeshPointer()) { + return (quint32)mesh->getNumIndices(); + } + return 0; +} + +quint32 scriptable::ScriptableMesh::getNumVertices() const { if (auto mesh = getMeshPointer()) { return (quint32)mesh->getNumVertices(); } @@ -87,16 +82,10 @@ quint32 ScriptableMesh::getNumVertices() const { // return glm::vec3(NAN); // } -namespace { - gpu::BufferView getBufferView(scriptable::MeshPointer mesh, gpu::Stream::Slot slot) { - return slot == gpu::Stream::POSITION ? mesh->getVertexBuffer() : mesh->getAttributeBuffer(slot); - } -} - -QVector ScriptableMesh::findNearbyIndices(const glm::vec3& origin, float epsilon) const { +QVector scriptable::ScriptableMesh::findNearbyIndices(const glm::vec3& origin, float epsilon) const { QVector result; if (auto mesh = getMeshPointer()) { - const auto& pos = getBufferView(mesh, gpu::Stream::POSITION); + const auto& pos = buffer_helpers::getBufferView(mesh, gpu::Stream::POSITION); const uint32_t num = (uint32_t)pos.getNumElements(); for (uint32_t i = 0; i < num; i++) { const auto& position = pos.get(i); @@ -108,40 +97,45 @@ QVector ScriptableMesh::findNearbyIndices(const glm::vec3& origin, floa return result; } -QVector ScriptableMesh::getIndices() const { +QVector scriptable::ScriptableMesh::getIndices() const { QVector result; if (auto mesh = getMeshPointer()) { - qCDebug(mesh_logging, "getTriangleIndices mesh %p", mesh.get()); + qCDebug(graphics_scripting, "getTriangleIndices mesh %p", mesh.get()); gpu::BufferView indexBufferView = mesh->getIndexBuffer(); if (quint32 count = (quint32)indexBufferView.getNumElements()) { result.resize(count); - auto buffer = indexBufferView._buffer; - if (indexBufferView._element.getSize() == 4) { + switch(indexBufferView._element.getType()) { + case gpu::UINT32: // memcpy(result.data(), buffer->getData(), result.size()*sizeof(quint32)); for (quint32 i = 0; i < count; i++) { result[i] = indexBufferView.get(i); } - } else { + break; + case gpu::UINT16: for (quint32 i = 0; i < count; i++) { result[i] = indexBufferView.get(i); } + break; + default: + assert(false); + Q_ASSERT(false); } } } return result; } -quint32 ScriptableMesh::getNumAttributes() const { +quint32 scriptable::ScriptableMesh::getNumAttributes() const { if (auto mesh = getMeshPointer()) { return (quint32)mesh->getNumAttributes(); } return 0; } -QVector ScriptableMesh::getAttributeNames() const { +QVector scriptable::ScriptableMesh::getAttributeNames() const { QVector result; if (auto mesh = getMeshPointer()) { - for (const auto& a : ATTRIBUTES.toStdMap()) { - auto bufferView = getBufferView(mesh, a.second); + for (const auto& a : buffer_helpers::ATTRIBUTES.toStdMap()) { + auto bufferView = buffer_helpers::getBufferView(mesh, a.second); if (bufferView.getNumElements() > 0) { result << a.first; } @@ -151,55 +145,49 @@ QVector ScriptableMesh::getAttributeNames() const { } // override -QVariantMap ScriptableMesh::getVertexAttributes(quint32 vertexIndex) const { +QVariantMap scriptable::ScriptableMesh::getVertexAttributes(quint32 vertexIndex) const { return getVertexAttributes(vertexIndex, getAttributeNames()); } -bool ScriptableMesh::setVertexAttributes(quint32 vertexIndex, QVariantMap attributes) { - //qDebug() << "setVertexAttributes" << vertexIndex << attributes; - for (auto& a : gatherBufferViews(getMeshPointer())) { +bool scriptable::ScriptableMesh::setVertexAttributes(quint32 vertexIndex, QVariantMap attributes) { + //qCInfo(graphics_scripting) << "setVertexAttributes" << vertexIndex << attributes; + metadata["last-modified"] = QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate); + for (auto& a : buffer_helpers::gatherBufferViews(getMeshPointer())) { const auto& name = a.first; const auto& value = attributes.value(name); if (value.isValid()) { auto& view = a.second; - //qCDebug(mesh_logging) << "setVertexAttributes" << vertexIndex << name; - bufferViewElementFromVariant(view, vertexIndex, value); + //qCDebug(graphics_scripting) << "setVertexAttributes" << vertexIndex << name; + buffer_helpers::fromVariant(view, vertexIndex, value); } else { - //qCDebug(mesh_logging) << "(skipping) setVertexAttributes" << vertexIndex << name; + //qCDebug(graphics_scripting) << "(skipping) setVertexAttributes" << vertexIndex << name; } } return true; } -int ScriptableMesh::_getSlotNumber(const QString& attributeName) const { +int scriptable::ScriptableMesh::_getSlotNumber(const QString& attributeName) const { if (auto mesh = getMeshPointer()) { - return ATTRIBUTES.value(attributeName, -1); + return buffer_helpers::ATTRIBUTES.value(attributeName, -1); } return -1; } -QVariantMap ScriptableMesh::getMeshExtents() const { +QVariantMap scriptable::ScriptableMesh::getMeshExtents() const { auto mesh = getMeshPointer(); auto box = mesh ? mesh->evalPartsBound(0, (int)mesh->getNumParts()) : AABox(); - return { - { "brn", glmVecToVariant(box.getCorner()) }, - { "tfl", glmVecToVariant(box.calcTopFarLeft()) }, - { "center", glmVecToVariant(box.calcCenter()) }, - { "min", glmVecToVariant(box.getMinimumPoint()) }, - { "max", glmVecToVariant(box.getMaximumPoint()) }, - { "dimensions", glmVecToVariant(box.getDimensions()) }, - }; + return buffer_helpers::toVariant(box).toMap(); } -quint32 ScriptableMesh::getNumParts() const { +quint32 scriptable::ScriptableMesh::getNumParts() const { if (auto mesh = getMeshPointer()) { return (quint32)mesh->getNumParts(); } return 0; } -QVariantMap ScriptableMesh::scaleToFit(float unitScale) { +QVariantMap scriptable::ScriptableMeshPart::scaleToFit(float unitScale) { if (auto mesh = getMeshPointer()) { auto box = mesh->evalPartsBound(0, (int)mesh->getNumParts()); auto center = box.calcCenter(); @@ -208,10 +196,10 @@ QVariantMap ScriptableMesh::scaleToFit(float unitScale) { } return {}; } -QVariantMap ScriptableMesh::translate(const glm::vec3& translation) { +QVariantMap scriptable::ScriptableMeshPart::translate(const glm::vec3& translation) { return transform(glm::translate(translation)); } -QVariantMap ScriptableMesh::scale(const glm::vec3& scale, const glm::vec3& origin) { +QVariantMap scriptable::ScriptableMeshPart::scale(const glm::vec3& scale, const glm::vec3& origin) { if (auto mesh = getMeshPointer()) { auto box = mesh->evalPartsBound(0, (int)mesh->getNumParts()); glm::vec3 center = glm::isnan(origin.x) ? box.calcCenter() : origin; @@ -219,10 +207,10 @@ QVariantMap ScriptableMesh::scale(const glm::vec3& scale, const glm::vec3& origi } return {}; } -QVariantMap ScriptableMesh::rotateDegrees(const glm::vec3& eulerAngles, const glm::vec3& origin) { +QVariantMap scriptable::ScriptableMeshPart::rotateDegrees(const glm::vec3& eulerAngles, const glm::vec3& origin) { return rotate(glm::quat(glm::radians(eulerAngles)), origin); } -QVariantMap ScriptableMesh::rotate(const glm::quat& rotation, const glm::vec3& origin) { +QVariantMap scriptable::ScriptableMeshPart::rotate(const glm::quat& rotation, const glm::vec3& origin) { if (auto mesh = getMeshPointer()) { auto box = mesh->evalPartsBound(0, (int)mesh->getNumParts()); glm::vec3 center = glm::isnan(origin.x) ? box.calcCenter() : origin; @@ -230,184 +218,61 @@ QVariantMap ScriptableMesh::rotate(const glm::quat& rotation, const glm::vec3& o } return {}; } -QVariantMap ScriptableMesh::transform(const glm::mat4& transform) { +QVariantMap scriptable::ScriptableMeshPart::transform(const glm::mat4& transform) { if (auto mesh = getMeshPointer()) { - const auto& pos = getBufferView(mesh, gpu::Stream::POSITION); + const auto& pos = buffer_helpers::getBufferView(mesh, gpu::Stream::POSITION); const uint32_t num = (uint32_t)pos.getNumElements(); for (uint32_t i = 0; i < num; i++) { auto& position = pos.edit(i); position = transform * glm::vec4(position, 1.0f); } + return parentMesh->getMeshExtents(); } - return getMeshExtents(); + return {}; } -QVariantList ScriptableMesh::getAttributeValues(const QString& attributeName) const { +QVariantList scriptable::ScriptableMesh::getAttributeValues(const QString& attributeName) const { QVariantList result; auto slotNum = _getSlotNumber(attributeName); if (slotNum >= 0) { auto slot = (gpu::Stream::Slot)slotNum; - const auto& bufferView = getBufferView(getMeshPointer(), slot); + const auto& bufferView = buffer_helpers::getBufferView(getMeshPointer(), slot); if (auto len = bufferView.getNumElements()) { bool asArray = bufferView._element.getType() != gpu::FLOAT; for (quint32 i = 0; i < len; i++) { - result << bufferViewElementToVariant(bufferView, i, asArray, attributeName.toStdString().c_str()); + result << buffer_helpers::toVariant(bufferView, i, asArray, attributeName.toStdString().c_str()); } } } return result; } -QVariantMap ScriptableMesh::getVertexAttributes(quint32 vertexIndex, QVector names) const { +QVariantMap scriptable::ScriptableMesh::getVertexAttributes(quint32 vertexIndex, QVector names) const { QVariantMap result; auto mesh = getMeshPointer(); if (!mesh || vertexIndex >= getNumVertices()) { return result; } - for (const auto& a : ATTRIBUTES.toStdMap()) { + for (const auto& a : buffer_helpers::ATTRIBUTES.toStdMap()) { auto name = a.first; if (!names.contains(name)) { continue; } auto slot = a.second; - const gpu::BufferView& bufferView = getBufferView(mesh, slot); + const gpu::BufferView& bufferView = buffer_helpers::getBufferView(mesh, slot); if (vertexIndex < bufferView.getNumElements()) { bool asArray = bufferView._element.getType() != gpu::FLOAT; - result[name] = bufferViewElementToVariant(bufferView, vertexIndex, asArray, name.toStdString().c_str()); + result[name] = buffer_helpers::toVariant(bufferView, vertexIndex, asArray, name.toStdString().c_str()); } } return result; } -/// --- buffer view <-> variant helpers - -namespace { - // expand the corresponding attribute buffer (creating it if needed) so that it matches POSITIONS size and specified element type - gpu::BufferView _expandedAttributeBuffer(const scriptable::MeshPointer mesh, gpu::Stream::Slot slot, const gpu::Element& elementType) { - gpu::Size elementSize = elementType.getSize(); - gpu::BufferView bufferView = getBufferView(mesh, slot); - auto nPositions = mesh->getNumVertices(); - auto vsize = nPositions * elementSize; - auto diffTypes = (elementType.getType() != bufferView._element.getType() || - elementType.getSize() > bufferView._element.getSize() || - elementType.getScalarCount() > bufferView._element.getScalarCount() || - vsize > bufferView._size - ); - auto hint = DebugNames::stringFrom(slot); - -#ifdef DEV_BUILD - auto beforeCount = bufferView.getNumElements(); - auto beforeTotal = bufferView._size; -#endif - if (bufferView.getNumElements() < nPositions || diffTypes) { - if (!bufferView._buffer || bufferView.getNumElements() == 0) { - qCInfo(mesh_logging).nospace() << "ScriptableMesh -- adding missing mesh attribute '" << hint << "' for BufferView"; - gpu::Byte *data = new gpu::Byte[vsize]; - memset(data, 0, vsize); - auto buffer = new gpu::Buffer(vsize, (gpu::Byte*)data); - delete[] data; - bufferView = gpu::BufferView(buffer, elementType); - mesh->addAttribute(slot, bufferView); - } else { - qCInfo(mesh_logging) << "ScriptableMesh -- resizing Buffer current:" << hint << bufferView._buffer->getSize() << "wanted:" << vsize; - bufferView._element = elementType; - bufferView._buffer->resize(vsize); - bufferView._size = bufferView._buffer->getSize(); - } - } -#ifdef DEV_BUILD - auto afterCount = bufferView.getNumElements(); - auto afterTotal = bufferView._size; - if (beforeTotal != afterTotal || beforeCount != afterCount) { - auto typeName = DebugNames::stringFrom(bufferView._element.getType()); - qCDebug(mesh_logging, "NOTE:: _expandedAttributeBuffer.%s vec%d %s (before count=%lu bytes=%lu // after count=%lu bytes=%lu)", - hint.toStdString().c_str(), bufferView._element.getScalarCount(), - typeName.toStdString().c_str(), beforeCount, beforeTotal, afterCount, afterTotal); - } -#endif - return bufferView; - } - const gpu::Element UNUSED{ gpu::SCALAR, gpu::UINT8, gpu::RAW }; - - gpu::Element getVecNElement(gpu::Type T, int N) { - switch(N) { - case 2: return { gpu::VEC2, T, gpu::XY }; - case 3: return { gpu::VEC3, T, gpu::XYZ }; - case 4: return { gpu::VEC4, T, gpu::XYZW }; - } - Q_ASSERT(false); - return UNUSED; - } - - gpu::BufferView expandAttributeToMatchPositions(scriptable::MeshPointer mesh, gpu::Stream::Slot slot) { - if (slot == gpu::Stream::POSITION) { - return getBufferView(mesh, slot); - } - return _expandedAttributeBuffer(mesh, slot, getVecNElement(gpu::FLOAT, 3)); - } -} - -std::map ScriptableMesh::gatherBufferViews(scriptable::MeshPointer mesh, const QStringList& expandToMatchPositions) { - std::map attributeViews; - if (!mesh) { - return attributeViews; - } - for (const auto& a : ScriptableMesh::ATTRIBUTES.toStdMap()) { - auto name = a.first; - auto slot = a.second; - if (expandToMatchPositions.contains(name)) { - expandAttributeToMatchPositions(mesh, slot); - } - auto view = getBufferView(mesh, slot); - auto beforeCount = view.getNumElements(); - if (beforeCount > 0) { - auto element = view._element; - auto vecN = element.getScalarCount(); - auto type = element.getType(); - QString typeName = DebugNames::stringFrom(element.getType()); - auto beforeTotal = view._size; - - attributeViews[name] = _expandedAttributeBuffer(mesh, slot, getVecNElement(type, vecN)); - -#if DEV_BUILD - auto afterTotal = attributeViews[name]._size; - auto afterCount = attributeViews[name].getNumElements(); - if (beforeTotal != afterTotal || beforeCount != afterCount) { - qCDebug(mesh_logging, "NOTE:: gatherBufferViews.%s vec%d %s (before count=%lu bytes=%lu // after count=%lu bytes=%lu)", - name.toStdString().c_str(), vecN, typeName.toStdString().c_str(), beforeCount, beforeTotal, afterCount, afterTotal); - } -#endif - } - } - return attributeViews; -} - -QScriptValue ScriptableModel::mapAttributeValues(QScriptValue scopeOrCallback, QScriptValue methodOrName) { - auto context = scopeOrCallback.engine()->currentContext(); - auto _in = context->thisObject(); - qCInfo(mesh_logging) << "mapAttributeValues" << _in.toVariant().typeName() << _in.toVariant().toString() << _in.toQObject(); - auto model = qscriptvalue_cast(_in); - QVector in = model.getMeshes(); - if (in.size()) { - foreach (scriptable::ScriptableMeshPointer meshProxy, in) { - meshProxy->mapAttributeValues(scopeOrCallback, methodOrName); - } - return _in; - } else if (auto meshProxy = qobject_cast(_in.toQObject())) { - return meshProxy->mapAttributeValues(scopeOrCallback, methodOrName); - } else { - context->throwError("invalid ModelProxy || MeshProxyPointer"); - } - return false; -} - - - -QScriptValue ScriptableMesh::mapAttributeValues(QScriptValue scopeOrCallback, QScriptValue methodOrName) { +quint32 scriptable::ScriptableMesh::mapAttributeValues(QScriptValue _callback) { auto mesh = getMeshPointer(); if (!mesh) { - return false; + return 0; } - auto scopedHandler = makeScopedHandlerObject(scopeOrCallback, methodOrName); + auto scopedHandler = jsBindCallback(_callback); // input buffers gpu::BufferView positions = mesh->getVertexBuffer(); @@ -417,20 +282,25 @@ QScriptValue ScriptableMesh::mapAttributeValues(QScriptValue scopeOrCallback, QS // destructure so we can still invoke callback scoped, but with a custom signature (obj, i, jsMesh) auto scope = scopedHandler.property("scope"); auto callback = scopedHandler.property("callback"); - auto js = engine(); // cache value to avoid resolving each iteration - auto meshPart = thisObject();//js->toScriptValue(meshProxy); - + auto js = engine() ? engine() : scopedHandler.engine(); // cache value to avoid resolving each iteration + if (!js) { + return 0; + } + auto meshPart = js ? js->toScriptValue(getSelf()) : QScriptValue::NullValue; + qCInfo(graphics_scripting) << "mapAttributeValues" << mesh.get() << js->currentContext()->thisObject().toQObject(); auto obj = js->newObject(); - auto attributeViews = ScriptableMesh::gatherBufferViews(mesh, { "normal", "color" }); - for (uint32_t i=0; i < nPositions; i++) { + auto attributeViews = buffer_helpers::gatherBufferViews(mesh, { "normal", "color" }); + metadata["last-modified"] = QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate); + uint32_t i = 0; + for (; i < nPositions; i++) { for (const auto& a : attributeViews) { bool asArray = a.second._element.getType() != gpu::FLOAT; obj.setProperty(a.first, bufferViewElementToScriptValue(js, a.second, i, asArray, a.first.toStdString().c_str())); } auto result = callback.call(scope, { obj, i, meshPart }); if (js->hasUncaughtException()) { - context()->throwValue(js->uncaughtException()); - return false; + js->currentContext()->throwValue(js->uncaughtException()); + return i; } if (result.isBool() && !result.toBool()) { @@ -450,15 +320,19 @@ QScriptValue ScriptableMesh::mapAttributeValues(QScriptValue scopeOrCallback, QS } } } - return thisObject(); + return i; } -QScriptValue ScriptableMesh::unrollVertices(bool recalcNormals) { +quint32 scriptable::ScriptableMeshPart::mapAttributeValues(QScriptValue callback) { + return parentMesh ? parentMesh->mapAttributeValues(callback) : 0; +} + +bool scriptable::ScriptableMeshPart::unrollVertices(bool recalcNormals) { auto meshProxy = this; auto mesh = getMeshPointer(); - qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices" << !!mesh<< !!meshProxy; + qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices" << !!mesh<< !!meshProxy; if (!mesh) { - return QScriptValue(); + return false; } auto positions = mesh->getVertexBuffer(); @@ -467,8 +341,9 @@ QScriptValue ScriptableMesh::unrollVertices(bool recalcNormals) { auto buffer = new gpu::Buffer(); buffer->resize(numPoints * sizeof(uint32_t)); auto newindices = gpu::BufferView(buffer, { gpu::SCALAR, gpu::UINT32, gpu::INDEX }); - qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices numPoints" << numPoints; - auto attributeViews = ScriptableMesh::gatherBufferViews(mesh); + metadata["last-modified"] = QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate); + qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices numPoints" << numPoints; + auto attributeViews = buffer_helpers::gatherBufferViews(mesh); for (const auto& a : attributeViews) { auto& view = a.second; auto sz = view._element.getSize(); @@ -477,19 +352,21 @@ QScriptValue ScriptableMesh::unrollVertices(bool recalcNormals) { auto points = gpu::BufferView(buffer, view._element); auto src = (uint8_t*)view._buffer->getData(); auto dest = (uint8_t*)points._buffer->getData(); - auto slot = ScriptableMesh::ATTRIBUTES[a.first]; - qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices buffer" << a.first; - qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices source" << view.getNumElements(); - qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices dest" << points.getNumElements(); - qCInfo(mesh_logging) << "ScriptableMesh::unrollVertices sz" << sz << src << dest << slot; + auto slot = buffer_helpers::ATTRIBUTES[a.first]; + if (0) { + qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices buffer" << a.first; + qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices source" << view.getNumElements(); + qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices dest" << points.getNumElements(); + qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices sz" << sz << src << dest << slot; + } auto esize = indices._element.getSize(); const char* hint= a.first.toStdString().c_str(); for(quint32 i = 0; i < numPoints; i++) { quint32 index = esize == 4 ? indices.get(i) : indices.get(i); newindices.edit(i) = i; - bufferViewElementFromVariant( + buffer_helpers::fromVariant( points, i, - bufferViewElementToVariant(view, index, false, hint) + buffer_helpers::toVariant(view, index, false, hint) ); } if (slot == gpu::Stream::POSITION) { @@ -505,62 +382,70 @@ QScriptValue ScriptableMesh::unrollVertices(bool recalcNormals) { return true; } -bool ScriptableMesh::replaceMeshData(scriptable::ScriptableMeshPointer src, const QVector& attributeNames) { +bool scriptable::ScriptableMeshPart::replaceMeshData(scriptable::ScriptableMeshPartPointer src, const QVector& attributeNames) { auto target = getMeshPointer(); auto source = src ? src->getMeshPointer() : nullptr; if (!target || !source) { - context()->throwError("ScriptableMesh::replaceMeshData -- expected dest and src to be valid mesh proxy pointers"); + if (context()) { + context()->throwError("ScriptableMeshPart::replaceMeshData -- expected dest and src to be valid mesh proxy pointers"); + } else { + qCWarning(graphics_scripting) << "ScriptableMeshPart::replaceMeshData -- expected dest and src to be valid mesh proxy pointers"; + } return false; } - QVector attributes = attributeNames.isEmpty() ? src->getAttributeNames() : attributeNames; + QVector attributes = attributeNames.isEmpty() ? src->parentMesh->getAttributeNames() : attributeNames; - //qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData -- source:" << source->displayName << "target:" << target->displayName << "attributes:" << attributes; + qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData -- " << + "source:" << QString::fromStdString(source->displayName) << + "target:" << QString::fromStdString(target->displayName) << + "attributes:" << attributes; + + metadata["last-modified"] = QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate); // remove attributes only found on target mesh, unless user has explicitly specified the relevant attribute names if (attributeNames.isEmpty()) { - auto attributeViews = ScriptableMesh::gatherBufferViews(target); + auto attributeViews = buffer_helpers::gatherBufferViews(target); for (const auto& a : attributeViews) { - auto slot = ScriptableMesh::ATTRIBUTES[a.first]; + auto slot = buffer_helpers::ATTRIBUTES[a.first]; if (!attributes.contains(a.first)) { - //qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData -- pruning target attribute" << a.first << slot; + qCInfo(graphics_scripting) << "ScriptableMesh::replaceMeshData -- pruning target attribute" << a.first << slot; target->removeAttribute(slot); } } } - target->setVertexBuffer(cloneBufferView(source->getVertexBuffer())); - target->setIndexBuffer(cloneBufferView(source->getIndexBuffer())); - target->setPartBuffer(cloneBufferView(source->getPartBuffer())); + target->setVertexBuffer(buffer_helpers::clone(source->getVertexBuffer())); + target->setIndexBuffer(buffer_helpers::clone(source->getIndexBuffer())); + target->setPartBuffer(buffer_helpers::clone(source->getPartBuffer())); for (const auto& a : attributes) { - auto slot = ScriptableMesh::ATTRIBUTES[a]; + auto slot = buffer_helpers::ATTRIBUTES[a]; if (slot == gpu::Stream::POSITION) { continue; } - // auto& before = target->getAttributeBuffer(slot); + auto& before = target->getAttributeBuffer(slot); auto& input = source->getAttributeBuffer(slot); if (input.getNumElements() == 0) { - //qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData buffer is empty -- pruning" << a << slot; + qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData buffer is empty -- pruning" << a << slot; target->removeAttribute(slot); } else { - // if (before.getNumElements() == 0) { - // qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData target buffer is empty -- adding" << a << slot; - // } else { - // qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData target buffer exists -- updating" << a << slot; - // } - target->addAttribute(slot, cloneBufferView(input)); + if (before.getNumElements() == 0) { + qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData target buffer is empty -- adding" << a << slot; + } else { + qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData target buffer exists -- updating" << a << slot; + } + target->addAttribute(slot, buffer_helpers::clone(input)); } - // auto& after = target->getAttributeBuffer(slot); - // qCInfo(mesh_logging) << "ScriptableMesh::replaceMeshData" << a << slot << before.getNumElements() << " -> " << after.getNumElements(); + auto& after = target->getAttributeBuffer(slot); + qCInfo(graphics_scripting) << "ScriptableMeshPart::replaceMeshData" << a << slot << before.getNumElements() << " -> " << after.getNumElements(); } return true; } -bool ScriptableMesh::dedupeVertices(float epsilon) { - scriptable::ScriptableMeshPointer meshProxy = this; +bool scriptable::ScriptableMeshPart::dedupeVertices(float epsilon) { auto mesh = getMeshPointer(); if (!mesh) { return false; @@ -573,6 +458,7 @@ bool ScriptableMesh::dedupeVertices(float epsilon) { uniqueVerts.reserve((int)numPositions); QMap remapIndices; + metadata["last-modified"] = QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate); for (quint32 i = 0; i < numPositions; i++) { const quint32 numUnique = uniqueVerts.size(); const auto& position = positions.get(i); @@ -590,7 +476,7 @@ bool ScriptableMesh::dedupeVertices(float epsilon) { } } - qCInfo(mesh_logging) << "//VERTS before" << numPositions << "after" << uniqueVerts.size(); + qCInfo(graphics_scripting) << "//VERTS before" << numPositions << "after" << uniqueVerts.size(); auto indices = mesh->getIndexBuffer(); auto numIndices = indices.getNumElements(); @@ -600,34 +486,34 @@ bool ScriptableMesh::dedupeVertices(float epsilon) { for (quint32 i = 0; i < numIndices; i++) { quint32 index = esize == 4 ? indices.get(i) : indices.get(i); if (remapIndices.contains(index)) { - //qCInfo(mesh_logging) << i << index << "->" << remapIndices[index]; + //qCInfo(graphics_scripting) << i << index << "->" << remapIndices[index]; newIndices << remapIndices[index]; } else { - qCInfo(mesh_logging) << i << index << "!remapIndices[index]"; + qCInfo(graphics_scripting) << i << index << "!remapIndices[index]"; } } - mesh->setIndexBuffer(bufferViewFromVector(newIndices, { gpu::SCALAR, gpu::UINT32, gpu::INDEX })); - mesh->setVertexBuffer(bufferViewFromVector(uniqueVerts, { gpu::VEC3, gpu::FLOAT, gpu::XYZ })); + mesh->setIndexBuffer(buffer_helpers::fromVector(newIndices, { gpu::SCALAR, gpu::UINT32, gpu::INDEX })); + mesh->setVertexBuffer(buffer_helpers::fromVector(uniqueVerts, { gpu::VEC3, gpu::FLOAT, gpu::XYZ })); - auto attributeViews = ScriptableMesh::gatherBufferViews(mesh); + auto attributeViews = buffer_helpers::gatherBufferViews(mesh); quint32 numUniqueVerts = uniqueVerts.size(); for (const auto& a : attributeViews) { auto& view = a.second; - auto slot = ScriptableMesh::ATTRIBUTES[a.first]; + auto slot = buffer_helpers::ATTRIBUTES[a.first]; if (slot == gpu::Stream::POSITION) { continue; } - qCInfo(mesh_logging) << "ScriptableMesh::dedupeVertices" << a.first << slot << view.getNumElements(); - auto newView = resizedBufferView(view, numUniqueVerts); - qCInfo(mesh_logging) << a.first << "before: #" << view.getNumElements() << "after: #" << newView.getNumElements(); + qCInfo(graphics_scripting) << "ScriptableMeshPart::dedupeVertices" << a.first << slot << view.getNumElements(); + auto newView = buffer_helpers::resize(view, numUniqueVerts); + qCInfo(graphics_scripting) << a.first << "before: #" << view.getNumElements() << "after: #" << newView.getNumElements(); quint32 numElements = (quint32)view.getNumElements(); for (quint32 i = 0; i < numElements; i++) { quint32 fromVertexIndex = i; quint32 toVertexIndex = remapIndices.contains(fromVertexIndex) ? remapIndices[fromVertexIndex] : fromVertexIndex; - bufferViewElementFromVariant( + buffer_helpers::fromVariant( newView, toVertexIndex, - bufferViewElementToVariant(view, fromVertexIndex, false, "dedupe") + buffer_helpers::toVariant(view, fromVertexIndex, false, "dedupe") ); } mesh->addAttribute(slot, newView); @@ -635,120 +521,202 @@ bool ScriptableMesh::dedupeVertices(float epsilon) { return true; } -QScriptValue ScriptableMesh::cloneMesh(bool recalcNormals) { +scriptable::ScriptableMeshPointer scriptable::ScriptableMesh::cloneMesh(bool recalcNormals) { auto mesh = getMeshPointer(); if (!mesh) { - return QScriptValue::NullValue; + qCInfo(graphics_scripting) << "ScriptableMesh::cloneMesh -- !meshPointer"; + return nullptr; } - graphics::MeshPointer clone(new graphics::Mesh()); - clone->displayName = mesh->displayName + "-clone"; - qCInfo(mesh_logging) << "ScriptableMesh::cloneMesh" << !!mesh; - if (!mesh) { - return QScriptValue::NullValue; - } - - clone->setIndexBuffer(cloneBufferView(mesh->getIndexBuffer())); - clone->setPartBuffer(cloneBufferView(mesh->getPartBuffer())); - auto attributeViews = ScriptableMesh::gatherBufferViews(mesh); - for (const auto& a : attributeViews) { - auto& view = a.second; - auto slot = ScriptableMesh::ATTRIBUTES[a.first]; - qCInfo(mesh_logging) << "ScriptableMesh::cloneVertices buffer" << a.first << slot; - auto points = cloneBufferView(view); - qCInfo(mesh_logging) << "ScriptableMesh::cloneVertices source" << view.getNumElements(); - qCInfo(mesh_logging) << "ScriptableMesh::cloneVertices dest" << points.getNumElements(); - if (slot == gpu::Stream::POSITION) { - clone->setVertexBuffer(points); - } else { - clone->addAttribute(slot, points); - } - } - - auto result = scriptable::ScriptableMeshPointer(new ScriptableMesh(nullptr, clone)); + qCInfo(graphics_scripting) << "ScriptableMesh::cloneMesh..."; + auto clone = buffer_helpers::cloneMesh(mesh); + + qCInfo(graphics_scripting) << "ScriptableMesh::cloneMesh..."; if (recalcNormals) { - result->recalculateNormals(); + buffer_helpers::recalculateNormals(clone); } - return engine()->toScriptValue(result); + qCDebug(graphics_scripting) << clone.get();// << metadata; + auto meshPointer = scriptable::make_scriptowned(provider, model, clone, metadata); + clone.reset(); // free local reference + qCInfo(graphics_scripting) << "========= ScriptableMesh::cloneMesh..." << meshPointer << meshPointer->ownedMesh.use_count(); + //scriptable::MeshPointer* ppMesh = new scriptable::MeshPointer(); + //*ppMesh = clone; + + if (meshPointer) { + scriptable::WeakMeshPointer delme = meshPointer->mesh; + QString debugString = scriptable::toDebugString(meshPointer); + QObject::connect(meshPointer, &QObject::destroyed, meshPointer, [=]() { + qCWarning(graphics_scripting) << "*************** cloneMesh/Destroy"; + qCWarning(graphics_scripting) << "*************** " << debugString << delme.lock().get(); + if (!delme.expired()) { + qCWarning(graphics_scripting) << "cloneMesh -- potential memory leak..." << debugString << delme.lock().get(); + } + }); + } + + meshPointer->metadata["last-modified"] = QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate); + return scriptable::ScriptableMeshPointer(meshPointer); } -bool ScriptableMesh::recalculateNormals() { - scriptable::ScriptableMeshPointer meshProxy = this; - qCInfo(mesh_logging) << "Recalculating normals" << !!meshProxy; - auto mesh = getMeshPointer(); - if (!mesh) { - return false; - } - ScriptableMesh::gatherBufferViews(mesh, { "normal", "color" }); // ensures #normals >= #positions - auto normals = mesh->getAttributeBuffer(gpu::Stream::NORMAL); - auto verts = mesh->getVertexBuffer(); - auto indices = mesh->getIndexBuffer(); - auto esize = indices._element.getSize(); - auto numPoints = indices.getNumElements(); - const auto TRIANGLE = 3; - quint32 numFaces = (quint32)numPoints / TRIANGLE; - //QVector faces; - QVector faceNormals; - QMap> vertexToFaces; - //faces.resize(numFaces); - faceNormals.resize(numFaces); - auto numNormals = normals.getNumElements(); - qCInfo(mesh_logging) << QString("numFaces: %1, numNormals: %2, numPoints: %3").arg(numFaces).arg(numNormals).arg(numPoints); - if (normals.getNumElements() != verts.getNumElements()) { - return false; - } - for (quint32 i = 0; i < numFaces; i++) { - quint32 I = TRIANGLE * i; - quint32 i0 = esize == 4 ? indices.get(I+0) : indices.get(I+0); - quint32 i1 = esize == 4 ? indices.get(I+1) : indices.get(I+1); - quint32 i2 = esize == 4 ? indices.get(I+2) : indices.get(I+2); - - Triangle face = { - verts.get(i1), - verts.get(i2), - verts.get(i0) - }; - faceNormals[i] = face.getNormal(); - if (glm::isnan(faceNormals[i].x)) { - qCInfo(mesh_logging) << i << i0 << i1 << i2 << vec3toVariant(face.v0) << vec3toVariant(face.v1) << vec3toVariant(face.v2); - break; - } - vertexToFaces[glm::to_string(face.v0).c_str()] << i; - vertexToFaces[glm::to_string(face.v1).c_str()] << i; - vertexToFaces[glm::to_string(face.v2).c_str()] << i; - } - for (quint32 j = 0; j < numNormals; j++) { - //auto v = verts.get(j); - glm::vec3 normal { 0.0f, 0.0f, 0.0f }; - QString key { glm::to_string(verts.get(j)).c_str() }; - const auto& faces = vertexToFaces.value(key); - if (faces.size()) { - for (const auto i : faces) { - normal += faceNormals[i]; - } - normal *= 1.0f / (float)faces.size(); - } else { - static int logged = 0; - if (logged++ < 10) { - qCInfo(mesh_logging) << "no faces for key!?" << key; - } - normal = verts.get(j); - } - if (glm::isnan(normal.x)) { - static int logged = 0; - if (logged++ < 10) { - qCInfo(mesh_logging) << "isnan(normal.x)" << j << vec3toVariant(normal); - } - break; - } - normals.edit(j) = glm::normalize(normal); - } - return true; +scriptable::ScriptableMeshBase::ScriptableMeshBase(scriptable::WeakModelProviderPointer provider, scriptable::ScriptableModelBasePointer model, scriptable::WeakMeshPointer mesh, const QVariantMap& metadata) + : provider(provider), model(model), mesh(mesh), metadata(metadata) {} +scriptable::ScriptableMeshBase::ScriptableMeshBase(scriptable::WeakMeshPointer mesh) : scriptable::ScriptableMeshBase(scriptable::WeakModelProviderPointer(), nullptr, mesh, QVariantMap()) { } +scriptable::ScriptableMeshBase::ScriptableMeshBase(scriptable::MeshPointer mesh, const QVariantMap& metadata) + : ScriptableMeshBase(WeakModelProviderPointer(), nullptr, mesh, metadata) { + ownedMesh = mesh; +} +//scriptable::ScriptableMeshBase::ScriptableMeshBase(const scriptable::ScriptableMeshBase& other) { *this = other; } +scriptable::ScriptableMeshBase& scriptable::ScriptableMeshBase::operator=(const scriptable::ScriptableMeshBase& view) { + provider = view.provider; + model = view.model; + mesh = view.mesh; + ownedMesh = view.ownedMesh; + metadata = view.metadata; + return *this; +} + scriptable::ScriptableMeshBase::~ScriptableMeshBase() { + ownedMesh.reset(); + qCInfo(graphics_scripting) << "//~ScriptableMeshBase" << this << "ownedMesh:" << ownedMesh.use_count() << "mesh:" << mesh.use_count(); } -QString ScriptableMesh::toOBJ() { +scriptable::ScriptableMesh::~ScriptableMesh() { + ownedMesh.reset(); + qCInfo(graphics_scripting) << "//~ScriptableMesh" << this << "ownedMesh:" << ownedMesh.use_count() << "mesh:" << mesh.use_count(); +} + +QString scriptable::ScriptableMeshPart::toOBJ() { if (!getMeshPointer()) { - context()->throwError(QString("null mesh")); + if (context()) { + context()->throwError(QString("null mesh")); + } else { + qCWarning(graphics_scripting) << "null mesh"; + } + return QString(); } return writeOBJToString({ getMeshPointer() }); } +namespace { + template + QScriptValue qObjectToScriptValue(QScriptEngine* engine, const T& object) { + if (!object) { + return QScriptValue::NullValue; + } + auto ownership = object->metadata.value("__ownership__"); + return engine->newQObject( + object, + ownership.isValid() ? static_cast(ownership.toInt()) : QScriptEngine::QtOwnership + //, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects + ); + } + + QScriptValue meshPointerToScriptValue(QScriptEngine* engine, const scriptable::ScriptableMeshPointer& in) { + return qObjectToScriptValue(engine, in); + } + QScriptValue meshPartPointerToScriptValue(QScriptEngine* engine, const scriptable::ScriptableMeshPartPointer& in) { + return qObjectToScriptValue(engine, in); + } + QScriptValue modelPointerToScriptValue(QScriptEngine* engine, const scriptable::ScriptableModelPointer& in) { + return qObjectToScriptValue(engine, in); + } + + void meshPointerFromScriptValue(const QScriptValue& value, scriptable::ScriptableMeshPointer &out) { + out = scriptable::qpointer_qobject_cast(value); + } + void modelPointerFromScriptValue(const QScriptValue& value, scriptable::ScriptableModelPointer &out) { + out = scriptable::qpointer_qobject_cast(value); + } + void meshPartPointerFromScriptValue(const QScriptValue& value, scriptable::ScriptableMeshPartPointer &out) { + out = scriptable::qpointer_qobject_cast(value); + } + + // FIXME: MESHFACES: + // QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const mesh::MeshFace &meshFace) { + // QScriptValue obj = engine->newObject(); + // obj.setProperty("vertices", qVectorIntToScriptValue(engine, meshFace.vertexIndices)); + // return obj; + // } + // void meshFaceFromScriptValue(const QScriptValue &object, mesh::MeshFace& meshFaceResult) { + // qScriptValueToSequence(object.property("vertices"), meshFaceResult.vertexIndices); + // } + // QScriptValue qVectorMeshFaceToScriptValue(QScriptEngine* engine, const QVector& vector) { + // return qScriptValueFromSequence(engine, vector); + // } + // void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector& result) { + // qScriptValueToSequence(array, result); + // } + + QScriptValue qVectorUInt32ToScriptValue(QScriptEngine* engine, const QVector& vector) { + return qScriptValueFromSequence(engine, vector); + } + + void qVectorUInt32FromScriptValue(const QScriptValue& array, QVector& result) { + qScriptValueToSequence(array, result); + } + + QVector metaTypeIds{ + qRegisterMetaType("uint32"), + qRegisterMetaType("scriptable::uint32"), + qRegisterMetaType>(), + qRegisterMetaType>("QVector"), + qRegisterMetaType(), + qRegisterMetaType(), + qRegisterMetaType(), + }; +} + +namespace scriptable { + bool registerMetaTypes(QScriptEngine* engine) { + qScriptRegisterSequenceMetaType>(engine); + qScriptRegisterSequenceMetaType>(engine); + qScriptRegisterSequenceMetaType>(engine); + + qScriptRegisterMetaType(engine, qVectorUInt32ToScriptValue, qVectorUInt32FromScriptValue); + qScriptRegisterMetaType(engine, modelPointerToScriptValue, modelPointerFromScriptValue); + qScriptRegisterMetaType(engine, meshPointerToScriptValue, meshPointerFromScriptValue); + qScriptRegisterMetaType(engine, meshPartPointerToScriptValue, meshPartPointerFromScriptValue); + + return metaTypeIds.size(); + } + // callback helper that lets C++ method signatures remain simple (ie: taking a single callback argument) while + // still supporting extended Qt signal-like (scope, "methodName") and (scope, function(){}) "this" binding conventions + QScriptValue jsBindCallback(QScriptValue callback) { + if (callback.isObject() && callback.property("callback").isFunction()) { + return callback; + } + auto engine = callback.engine(); + auto context = engine ? engine->currentContext() : nullptr; + auto length = context ? context->argumentCount() : 0; + QScriptValue scope = context ? context->thisObject() : QScriptValue::NullValue; + QScriptValue method; + qCInfo(graphics_scripting) << "jsBindCallback" << engine << length << scope.toQObject() << method.toString(); + int i = 0; + for (; context && i < length; i++) { + if (context->argument(i).strictlyEquals(callback)) { + method = context->argument(i+1); + } + } + if (method.isFunction() || method.isString()) { + scope = callback; + } else { + method = callback; + } + qCInfo(graphics_scripting) << "scope:" << scope.toQObject() << "method:" << method.toString(); + return ::makeScopedHandlerObject(scope, method); + } +} + +bool scriptable::GraphicsScriptingInterface::updateMeshPart(ScriptableMeshPointer mesh, ScriptableMeshPartPointer part) { + Q_ASSERT(mesh); + Q_ASSERT(part); + Q_ASSERT(part->parentMesh); + auto tmp = exportMeshPart(mesh, part->partIndex); + if (part->parentMesh == mesh) { + qCInfo(graphics_scripting) << "updateMeshPart -- update via clone" << mesh << part; + tmp->replaceMeshData(part->cloneMeshPart()); + return false; + } else { + qCInfo(graphics_scripting) << "updateMeshPart -- update via inplace" << mesh << part; + tmp->replaceMeshData(part); + return true; + } +} diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h index 257285fa90..c655167c2b 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h @@ -11,142 +11,188 @@ #include +//#include #include +#include #include #include -namespace graphics { - class Mesh; -} -namespace gpu { - class BufferView; -} namespace scriptable { - class ScriptableMeshPart; - using ScriptableMeshPartPointer = QPointer; - class ScriptableMesh : public QObject, QScriptable { + + QScriptValue jsBindCallback(QScriptValue callback); + class ScriptableMesh : public ScriptableMeshBase, QScriptable { Q_OBJECT public: - Q_PROPERTY(quint32 numParts READ getNumParts) - Q_PROPERTY(quint32 numAttributes READ getNumAttributes) - Q_PROPERTY(quint32 numVertices READ getNumVertices) - Q_PROPERTY(quint32 numIndices READ getNumIndices) - Q_PROPERTY(QVariantMap metadata MEMBER _metadata) + Q_PROPERTY(uint32 numParts READ getNumParts) + Q_PROPERTY(uint32 numAttributes READ getNumAttributes) + Q_PROPERTY(uint32 numVertices READ getNumVertices) + Q_PROPERTY(uint32 numIndices READ getNumIndices) + Q_PROPERTY(QVariantMap metadata MEMBER metadata) Q_PROPERTY(QVector attributeNames READ getAttributeNames) + Q_PROPERTY(QVector parts READ getMeshParts) + Q_PROPERTY(bool valid READ hasValidMesh) + bool hasValidMesh() const { return (bool)getMeshPointer(); } + Q_PROPERTY(bool validOwned READ hasValidOwnedMesh) + bool hasValidOwnedMesh() const { return (bool)getOwnedMeshPointer(); } - static QMap ATTRIBUTES; - static std::map gatherBufferViews(MeshPointer mesh, const QStringList& expandToMatchPositions = QStringList()); + operator const ScriptableMeshBase*() const { return (qobject_cast(this)); } + ScriptableMesh(scriptable::MeshPointer mesh) : ScriptableMeshBase(mesh) { ownedMesh = mesh; } + ScriptableMesh(WeakModelProviderPointer provider, ScriptableModelBasePointer model, MeshPointer mesh, const QVariantMap& metadata) + : ScriptableMeshBase(provider, model, mesh, metadata) { ownedMesh = mesh; } + //ScriptableMesh& operator=(const ScriptableMesh& other) { model=other.model; mesh=other.mesh; metadata=other.metadata; return *this; }; + //ScriptableMesh() : QObject(), model(nullptr) {} + //ScriptableMesh(const ScriptableMesh& other) : QObject(), model(other.model), mesh(other.mesh), metadata(other.metadata) {} + ScriptableMesh(const ScriptableMeshBase& other); + ScriptableMesh(const ScriptableMesh& other) : ScriptableMeshBase(other) {}; + virtual ~ScriptableMesh(); - ScriptableMesh& operator=(const ScriptableMesh& other) { _model=other._model; _mesh=other._mesh; _metadata=other._metadata; return *this; }; - ScriptableMesh() : QObject(), _model(nullptr) {} - ScriptableMesh(ScriptableModelPointer parent, scriptable::MeshPointer mesh) : QObject(), _model(parent), _mesh(mesh) {} - ScriptableMesh(const ScriptableMesh& other) : QObject(), _model(other._model), _mesh(other._mesh), _metadata(other._metadata) {} - ~ScriptableMesh() { qDebug() << "~ScriptableMesh" << this; } - - scriptable::MeshPointer getMeshPointer() const { return _mesh; } + Q_INVOKABLE const scriptable::ScriptableModelPointer getParentModel() const { return qobject_cast(model); } + Q_INVOKABLE const scriptable::MeshPointer getOwnedMeshPointer() const { return ownedMesh; } + scriptable::ScriptableMeshPointer getSelf() const { return const_cast(this); } public slots: - quint32 getNumParts() const; - quint32 getNumVertices() const; - quint32 getNumAttributes() const; - quint32 getNumIndices() const { return 0; } + uint32 getNumParts() const; + uint32 getNumVertices() const; + uint32 getNumAttributes() const; + uint32 getNumIndices() const; QVector getAttributeNames() const; + QVector getMeshParts() const; - QVariantMap getVertexAttributes(quint32 vertexIndex) const; - QVariantMap getVertexAttributes(quint32 vertexIndex, QVector attributes) const; + QVariantMap getVertexAttributes(uint32 vertexIndex) const; + QVariantMap getVertexAttributes(uint32 vertexIndex, QVector attributes) const; - QVector getIndices() const; - QVector findNearbyIndices(const glm::vec3& origin, float epsilon = 1e-6) const; + QVector getIndices() const; + QVector findNearbyIndices(const glm::vec3& origin, float epsilon = 1e-6) const; QVariantMap getMeshExtents() const; - bool setVertexAttributes(quint32 vertexIndex, QVariantMap attributes); - QVariantMap scaleToFit(float unitScale); + bool setVertexAttributes(uint32 vertexIndex, QVariantMap attributes); QVariantList getAttributeValues(const QString& attributeName) const; int _getSlotNumber(const QString& attributeName) const; + scriptable::ScriptableMeshPointer cloneMesh(bool recalcNormals = false); + public: + operator bool() const { return !mesh.expired(); } + + public slots: + // QScriptEngine-specific wrappers + uint32 mapAttributeValues(QScriptValue callback); + }; + + // TODO: part-specific wrapper for working with raw geometries + class ScriptableMeshPart : public QObject, QScriptable { + Q_OBJECT + public: + Q_PROPERTY(uint32 partIndex MEMBER partIndex CONSTANT) + Q_PROPERTY(int numElementsPerFace MEMBER _elementsPerFace CONSTANT) + Q_PROPERTY(QString topology MEMBER _topology CONSTANT) + + Q_PROPERTY(uint32 numFaces READ getNumFaces) + Q_PROPERTY(uint32 numAttributes READ getNumAttributes) + Q_PROPERTY(uint32 numVertices READ getNumVertices) + Q_PROPERTY(uint32 numIndices READ getNumIndices) + Q_PROPERTY(QVector attributeNames READ getAttributeNames) + + Q_PROPERTY(QVariantMap metadata MEMBER metadata) + + //Q_PROPERTY(scriptable::ScriptableMeshPointer parentMesh MEMBER parentMesh CONSTANT HIDE) + + ScriptableMeshPart(scriptable::ScriptableMeshPointer parentMesh, int partIndex); + ScriptableMeshPart& operator=(const ScriptableMeshPart& view) { parentMesh=view.parentMesh; return *this; }; + ScriptableMeshPart(const ScriptableMeshPart& other) : parentMesh(other.parentMesh), partIndex(other.partIndex) {} + ~ScriptableMeshPart() { qDebug() << "~ScriptableMeshPart" << this; } + + public slots: + scriptable::ScriptableMeshPointer getParentMesh() const { return parentMesh; } + uint32 getNumAttributes() const { return parentMesh ? parentMesh->getNumAttributes() : 0; } + uint32 getNumVertices() const { return parentMesh ? parentMesh->getNumVertices() : 0; } + uint32 getNumIndices() const { return parentMesh ? parentMesh->getNumIndices() : 0; } + uint32 getNumFaces() const { return parentMesh ? parentMesh->getNumIndices() / _elementsPerFace : 0; } + QVector getAttributeNames() const { return parentMesh ? parentMesh->getAttributeNames() : QVector(); } + QVector getFace(uint32 faceIndex) const { + auto inds = parentMesh ? parentMesh->getIndices() : QVector(); + return faceIndex+2 < (uint32)inds.size() ? inds.mid(faceIndex*3, 3) : QVector(); + } + QVariantMap scaleToFit(float unitScale); QVariantMap translate(const glm::vec3& translation); QVariantMap scale(const glm::vec3& scale, const glm::vec3& origin = glm::vec3(NAN)); QVariantMap rotateDegrees(const glm::vec3& eulerAngles, const glm::vec3& origin = glm::vec3(NAN)); QVariantMap rotate(const glm::quat& rotation, const glm::vec3& origin = glm::vec3(NAN)); QVariantMap transform(const glm::mat4& transform); - public: - operator bool() const { return _mesh != nullptr; } - ScriptableModelPointer _model; - scriptable::MeshPointer _mesh; - QVariantMap _metadata; + bool unrollVertices(bool recalcNormals = false); + bool dedupeVertices(float epsilon = 1e-6); + bool recalculateNormals() { return buffer_helpers::recalculateNormals(getMeshPointer()); } + + bool replaceMeshData(scriptable::ScriptableMeshPartPointer source, const QVector& attributeNames = QVector()); + scriptable::ScriptableMeshPartPointer cloneMeshPart(bool recalcNormals = false) { + if (parentMesh) { + if (auto clone = parentMesh->cloneMesh(recalcNormals)) { + return clone->getMeshParts().value(partIndex); + } + } + return nullptr; + } + QString toOBJ(); public slots: // QScriptEngine-specific wrappers - QScriptValue mapAttributeValues(QScriptValue scopeOrCallback, QScriptValue methodOrName = QScriptValue()); - bool dedupeVertices(float epsilon = 1e-6); - bool recalculateNormals(); - QScriptValue cloneMesh(bool recalcNormals = true); - QScriptValue unrollVertices(bool recalcNormals = true); - bool replaceMeshData(scriptable::ScriptableMeshPointer source, const QVector& attributeNames = QVector()); - QString toOBJ(); - }; - - // TODO: part-specific wrapper for working with raw geometries - class ScriptableMeshPart : public QObject { - Q_OBJECT - public: - Q_PROPERTY(QString topology READ getTopology) - Q_PROPERTY(quint32 numFaces READ getNumFaces) - - ScriptableMeshPart& operator=(const ScriptableMeshPart& view) { parentMesh=view.parentMesh; return *this; }; - ScriptableMeshPart(const ScriptableMeshPart& other) : parentMesh(other.parentMesh) {} - ScriptableMeshPart() {} - ~ScriptableMeshPart() { qDebug() << "~ScriptableMeshPart" << this; } - - public slots: - QString getTopology() const { return "triangles"; } - quint32 getNumFaces() const { return parentMesh.getIndices().size() / 3; } - QVector getFace(quint32 faceIndex) const { - auto inds = parentMesh.getIndices(); - return faceIndex+2 < (quint32)inds.size() ? inds.mid(faceIndex*3, 3) : QVector(); - } + uint32 mapAttributeValues(QScriptValue callback); public: - scriptable::ScriptableMesh parentMesh; - int partIndex; + scriptable::ScriptableMeshPointer parentMesh; + uint32 partIndex; + QVariantMap metadata; + protected: + int _elementsPerFace{ 3 }; + QString _topology{ "triangles" }; + scriptable::MeshPointer getMeshPointer() const { return parentMesh ? parentMesh->getMeshPointer() : nullptr; } }; - class GraphicsScriptingInterface : public QObject { + class GraphicsScriptingInterface : public QObject, QScriptable { Q_OBJECT public: GraphicsScriptingInterface(QObject* parent = nullptr) : QObject(parent) {} GraphicsScriptingInterface(const GraphicsScriptingInterface& other) {} - public slots: - ScriptableMeshPart exportMeshPart(ScriptableMesh mesh, int part) { return {}; } + public slots: + ScriptableMeshPartPointer exportMeshPart(ScriptableMeshPointer mesh, int part=0) { + return ScriptableMeshPartPointer(new ScriptableMeshPart(mesh, part)); + } + bool updateMeshPart(ScriptableMeshPointer mesh, ScriptableMeshPartPointer part); }; + + // callback helper that lets C++ method signatures remain simple (ie: taking a single callback argument) while + // still supporting extended Qt signal-like (scope, "methodName") and (scope, function(){}) "this" binding conventions + QScriptValue jsBindCallback(QScriptValue callback); + + // derive a corresponding C++ class instance from the current script engine's thisObject + template T this_qobject_cast(QScriptEngine* engine); } Q_DECLARE_METATYPE(scriptable::ScriptableMeshPointer) Q_DECLARE_METATYPE(QVector) Q_DECLARE_METATYPE(scriptable::ScriptableMeshPartPointer) +Q_DECLARE_METATYPE(QVector) Q_DECLARE_METATYPE(scriptable::GraphicsScriptingInterface) // FIXME: MESHFACES: faces were supported in the original Model.* API -- are they still needed/used/useful for anything yet? #include namespace mesh { - using uint32 = quint32; class MeshFace; using MeshFaces = QVector; class MeshFace { public: MeshFace() {} - MeshFace(QVector vertexIndices) : vertexIndices(vertexIndices) {} + MeshFace(QVector vertexIndices) : vertexIndices(vertexIndices) {} ~MeshFace() {} - QVector vertexIndices; + QVector vertexIndices; // TODO -- material... }; }; Q_DECLARE_METATYPE(mesh::MeshFace) Q_DECLARE_METATYPE(QVector) -Q_DECLARE_METATYPE(mesh::uint32) -Q_DECLARE_METATYPE(QVector) +Q_DECLARE_METATYPE(scriptable::uint32) +Q_DECLARE_METATYPE(QVector) diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h index 4ba5a993b1..97a73ddd61 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h @@ -1,56 +1,24 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include +#include "Forward.h" -#include - -namespace graphics { - class Mesh; -} -namespace gpu { - class BufferView; -} class QScriptValue; - namespace scriptable { - using Mesh = graphics::Mesh; - using MeshPointer = std::shared_ptr; - - class ScriptableModel; - using ScriptableModelPointer = QPointer; - class ScriptableMesh; - using ScriptableMeshPointer = QPointer; - - // abstract container for holding one or more scriptable meshes - class ScriptableModel : public QObject { + class ScriptableModel : public ScriptableModelBase { Q_OBJECT public: - QUuid objectID; - QVariantMap metadata; - QVector meshes; - - Q_PROPERTY(QVector meshes READ getMeshes) Q_PROPERTY(QUuid objectID MEMBER objectID CONSTANT) Q_PROPERTY(QVariantMap metadata MEMBER metadata CONSTANT) - Q_INVOKABLE QString toString() const; + Q_PROPERTY(uint32 numMeshes READ getNumMeshes) + Q_PROPERTY(QVector meshes READ getMeshes) - ScriptableModel(QObject* parent = nullptr) : QObject(parent) {} - ScriptableModel(const ScriptableModel& other) : objectID(other.objectID), metadata(other.metadata), meshes(other.meshes) {} - ScriptableModel& operator=(const ScriptableModel& view) { objectID = view.objectID; metadata = view.metadata; meshes = view.meshes; return *this; } - ~ScriptableModel() { qDebug() << "~ScriptableModel" << this; } - - void mixin(const ScriptableModel& other) { - for (const auto& key : other.metadata.keys()) { metadata[key] = other.metadata[key]; } - for (const auto& mesh : other.meshes) { meshes << mesh; } - } + ScriptableModel(QObject* parent = nullptr) : ScriptableModelBase(parent) {} + ScriptableModel(const ScriptableModel& other) : ScriptableModelBase(other) {} + ScriptableModel(const ScriptableModelBase& other) : ScriptableModelBase(other) {} + ScriptableModel& operator=(const ScriptableModelBase& view) { ScriptableModelBase::operator=(view); return *this; } + virtual ~ScriptableModel() { qDebug() << "~ScriptableModel" << this; } + Q_INVOKABLE scriptable::ScriptableModelPointer cloneModel(const QVariantMap& options = QVariantMap()); // TODO: in future accessors for these could go here // QVariantMap shapes; // QVariantMap materials; @@ -58,28 +26,23 @@ namespace scriptable { QVector getMeshes(); const QVector getConstMeshes() const; + operator scriptable::ScriptableModelBasePointer() { + QPointer p; + p = qobject_cast(this); + return p; + } + // QScriptEngine-specific wrappers - Q_INVOKABLE QScriptValue mapAttributeValues(QScriptValue scopeOrCallback, QScriptValue methodOrName); + Q_INVOKABLE uint32 mapAttributeValues(QScriptValue callback); + Q_INVOKABLE QString toString() const; + Q_INVOKABLE uint32 getNumMeshes() { return meshes.size(); } }; - // mixin class for Avatar/Entity/Overlay Rendering that expose their in-memory graphics::Meshes - class ModelProvider { - public: - QVariantMap metadata; - static scriptable::ScriptableModel modelUnavailableError(bool* ok) { if (ok) { *ok = false; } return {}; } - virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) = 0; - }; - using ModelProviderPointer = std::shared_ptr; - - // mixin class for Application to resolve UUIDs into a corresponding ModelProvider - class ModelProviderFactory : public Dependency { - public: - virtual scriptable::ModelProviderPointer lookupModelProvider(QUuid uuid) = 0; - }; } Q_DECLARE_METATYPE(scriptable::MeshPointer) -Q_DECLARE_METATYPE(scriptable::ScriptableModel) +Q_DECLARE_METATYPE(scriptable::WeakMeshPointer) Q_DECLARE_METATYPE(scriptable::ScriptableModelPointer) - +Q_DECLARE_METATYPE(scriptable::ScriptableModelBase) +Q_DECLARE_METATYPE(scriptable::ScriptableModelBasePointer) From cee0bbf8a5914be7e254af8911bc22caedf82583 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 15 Feb 2018 11:32:29 -0800 Subject: [PATCH 17/49] Revert "FBX node IDs aren't alphanumerically ordered per logical structure" This reverts commit a7ec4501e65396d4c7bf2316dd73188cea7a3c7d. Because remainingModels is a QSet, the order is not guaranteed. Therefore the same code iterating over the same items will sometimes have a different ordering. See docs for QSet, http://doc.qt.io/qt-5/qset.html This was bug was causing scrambled avatars, because both the transmitter and receiver of the AvatarData packets make the strong assumption that the joint orders are same. When they are not the avatar's appear scrambled. (cherry picked from commit f07b1fa4c52af97f9adab2ba6e9678a75fe6aa2b) --- libraries/fbx/src/FBXReader.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 4ed1ca38dc..14f12b5d1b 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1481,6 +1481,11 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } while (!remainingModels.isEmpty()) { QString first = *remainingModels.constBegin(); + foreach (const QString& id, remainingModels) { + if (id < first) { + first = id; + } + } QString topID = getTopModelID(_connectionParentMap, models, first, url); appendModelIDs(_connectionParentMap.value(topID), _connectionChildMap, models, remainingModels, modelIDs, true); } From 52f576e6f6449d6f0b16d8b9e1e4753cbc38765a Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 15 Feb 2018 15:02:45 -0800 Subject: [PATCH 18/49] don't update avatar entities if the avatars ID is AVATAR_SELF_ID --- libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 86635cd3bf..1b1511e2c6 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -219,7 +219,7 @@ void Avatar::updateAvatarEntities() { return; } - if (getID() == QUuid()) { + if (getID() == QUuid() || getID() == AVATAR_SELF_ID) { return; // wait until MyAvatar gets an ID before doing this. } From 5791ca4c5111faa42e0f8f64bd9b0806bdf3606c Mon Sep 17 00:00:00 2001 From: humbletim Date: Tue, 20 Feb 2018 12:22:04 -0500 Subject: [PATCH 19/49] interim checkin --- interface/src/Application.cpp | 1 + .../src/avatars-renderer/Avatar.cpp | 22 +-- .../src/avatars-renderer/Avatar.h | 5 +- .../src/RenderableModelEntityItem.cpp | 3 +- .../src/RenderablePolyLineEntityItem.cpp | 2 +- .../src/RenderablePolyLineEntityItem.h | 2 +- .../src/RenderablePolyVoxEntityItem.cpp | 15 +- .../src/RenderablePolyVoxEntityItem.h | 4 +- .../src/RenderableShapeEntityItem.cpp | 9 +- .../src/RenderableShapeEntityItem.h | 2 +- libraries/fbx/src/FBXReader.cpp | 5 +- libraries/fbx/src/OBJWriter.cpp | 7 +- .../GraphicsScriptingUtil.h | 10 + .../ModelScriptingInterface.cpp | 8 +- .../src/graphics-scripting/ScriptableMesh.cpp | 31 ++- .../src/graphics-scripting/ScriptableMesh.h | 2 +- .../graphics-scripting/ScriptableModel.cpp | 149 ++++++++++++++ .../src/graphics-scripting/ScriptableModel.h | 2 +- libraries/graphics/src/graphics/Geometry.h | 3 +- libraries/render-utils/src/GeometryCache.cpp | 3 +- libraries/render-utils/src/Model.cpp | 184 ++++++++++++------ libraries/render-utils/src/Model.h | 5 +- .../src/Model_temporary_hack.cpp.h | 121 ++++++++++++ .../src/AssetScriptingInterface.cpp | 2 +- libraries/script-engine/src/ScriptEngines.cpp | 30 ++- libraries/script-engine/src/ScriptEngines.h | 11 +- .../src/shared/ScriptInitializerMixin.h | 38 ++++ 27 files changed, 557 insertions(+), 119 deletions(-) create mode 100644 libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp create mode 100644 libraries/render-utils/src/Model_temporary_hack.cpp.h create mode 100644 libraries/shared/src/shared/ScriptInitializerMixin.h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index bdef2f456b..5bfb1846a5 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -771,6 +771,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(std::bind(&Application::getUserAgent, qApp)); DependencyManager::set(); DependencyManager::set(ScriptEngine::CLIENT_SCRIPT); + DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 8e22f355e4..066afac8f5 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -35,7 +35,7 @@ #include "ModelEntityItem.h" #include "RenderableModelEntityItem.h" -#include +#include #include "Logging.h" @@ -1763,24 +1763,24 @@ float Avatar::getUnscaledEyeHeightFromSkeleton() const { } } -scriptable::ScriptableModel Avatar::getScriptableModel(bool* ok) { +scriptable::ScriptableModelBase Avatar::getScriptableModel(bool* ok) { qDebug() << "Avatar::getScriptableModel" ; if (!_skeletonModel || !_skeletonModel->isLoaded()) { return scriptable::ModelProvider::modelUnavailableError(ok); } - scriptable::ScriptableModel result; - result.metadata = { + scriptable::ScriptableModelBase result = _skeletonModel->getScriptableModel(ok); + result.objectID = getSessionUUID(); + result.mixin({ { "avatarID", getSessionUUID().toString() }, { "url", _skeletonModelURL.toString() }, { "origin", "Avatar/avatar::" + _displayName }, { "textures", _skeletonModel->getTextures() }, - }; - result.mixin(_skeletonModel->getScriptableModel(ok)); - - // FIXME: for now access to attachment models are merged with the main avatar model - for (auto& attachmentModel : _attachmentModels) { - if (attachmentModel->isLoaded()) { - result.mixin(attachmentModel->getScriptableModel(ok)); + }); + // FIXME: for now access to attachment models are merged into the main avatar ScriptableModel set + for (int i = 0; i < (int)_attachmentModels.size(); i++) { + auto& model = _attachmentModels.at(i); + if (model->isLoaded()) { + result.append(model->getScriptableModel(ok), _attachmentData.at(i).toVariant().toMap()); } } if (ok) { diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 5cfc399b65..50301a2507 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -1,3 +1,4 @@ + // // Avatar.h // interface/src/avatar @@ -20,7 +21,7 @@ #include #include #include -#include +#include #include @@ -274,7 +275,7 @@ public: virtual void setAvatarEntityDataChanged(bool value) override; - virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override; + virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) override; public slots: // FIXME - these should be migrated to use Pose data instead diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 3d6714a400..155580d885 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -962,7 +962,6 @@ scriptable::ScriptableModelBase render::entities::ModelEntityRenderer::getScript } bool render::entities::ModelEntityRenderer::replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer newModel, int meshIndex, int partIndex) { - qCDebug(entitiesrenderer) << "REPLACING RenderableModelEntityItem" << newModel->objectName(); ModelPointer model; withReadLock([&] { model = _model; }); @@ -970,7 +969,7 @@ bool render::entities::ModelEntityRenderer::replaceScriptableModelMeshPart(scrip return false; } - return _model->replaceScriptableModelMeshPart(newModel, meshIndex, partIndex); + return model->replaceScriptableModelMeshPart(newModel, meshIndex, partIndex); } void RenderableModelEntityItem::simulateRelayedJoints() { diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index e18bd2a7fe..a70a1613c3 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -281,7 +281,7 @@ std::vector PolyLineEntityRenderer::updateVertic return vertices; } -scriptable::ScriptableModel PolyLineEntityRenderer::getScriptableModel(bool *ok) { +scriptable::ScriptableModelBase PolyLineEntityRenderer::getScriptableModel(bool *ok) { // TODO: adapt polyline into a triangles mesh... return EntityRenderer::getScriptableModel(ok); } diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h index 3bb8901178..d9d770e64f 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h @@ -25,7 +25,7 @@ class PolyLineEntityRenderer : public TypedEntityRenderer { public: PolyLineEntityRenderer(const EntityItemPointer& entity); - virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override; + virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) override; protected: virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override; virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index fd923c40b0..454fba4f94 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -1414,14 +1414,14 @@ void RenderablePolyVoxEntityItem::bonkNeighbors() { } } -scriptable::ScriptableModel RenderablePolyVoxEntityItem::getScriptableModel(bool * ok) { +scriptable::ScriptableModelBase RenderablePolyVoxEntityItem::getScriptableModel(bool * ok) { if (!updateDependents() || !_mesh) { return scriptable::ModelProvider::modelUnavailableError(ok); } bool success = false; glm::mat4 transform = voxelToLocalMatrix(); - scriptable::ScriptableModel result; + scriptable::ScriptableModelBase result; withReadLock([&] { gpu::BufferView::Index numVertices = (gpu::BufferView::Index)_mesh->getNumVertices(); if (!_meshReady) { @@ -1433,11 +1433,12 @@ scriptable::ScriptableModel RenderablePolyVoxEntityItem::getScriptableModel(bool } else { success = true; // the mesh will be in voxel-space. transform it into object-space - result.meshes << - _mesh->map([=](glm::vec3 position){ return glm::vec3(transform * glm::vec4(position, 1.0f)); }, - [=](glm::vec3 color){ return color; }, - [=](glm::vec3 normal){ return glm::normalize(glm::vec3(transform * glm::vec4(normal, 0.0f))); }, - [&](uint32_t index){ return index; }); + result.append(_mesh->map( + [=](glm::vec3 position){ return glm::vec3(transform * glm::vec4(position, 1.0f)); }, + [=](glm::vec3 color){ return color; }, + [=](glm::vec3 normal){ return glm::normalize(glm::vec3(transform * glm::vec4(normal, 0.0f))); }, + [&](uint32_t index){ return index; } + )); } }); if (ok) { diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index 55b9be23d8..733d5b62f5 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -113,7 +113,7 @@ public: void setVolDataDirty() { withWriteLock([&] { _volDataDirty = true; _meshReady = false; }); } - virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override; + virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) override; private: bool updateOnCount(const ivec3& v, uint8_t toValue); @@ -163,7 +163,7 @@ class PolyVoxEntityRenderer : public TypedEntityRenderer()->getScriptableModel(ok); } diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index 746102681c..5ea1c9edb7 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -157,8 +157,8 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { args->_details._trianglesRendered += (int)triCount; } -scriptable::ScriptableModel ShapeEntityRenderer::getScriptableModel(bool* ok) { - scriptable::ScriptableModel result; +scriptable::ScriptableModelBase ShapeEntityRenderer::getScriptableModel(bool* ok) { + scriptable::ScriptableModelBase result; result.metadata = { { "entityID", getEntity()->getID().toString() }, { "shape", entity::stringFromShape(_shape) }, @@ -169,7 +169,10 @@ scriptable::ScriptableModel ShapeEntityRenderer::getScriptableModel(bool* ok) { auto vertexColor = glm::vec3(_color); auto success = false; if (auto mesh = geometryCache->meshFromShape(geometryShape, vertexColor)) { - result.meshes << mesh; + scriptable::ScriptableMeshBase base{ mesh, { + { "shape", entity::stringFromShape(_shape) }, + }}; + result.append(base); success = true; } if (ok) { diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.h b/libraries/entities-renderer/src/RenderableShapeEntityItem.h index 6ada7e7317..57f899641a 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.h +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.h @@ -22,7 +22,7 @@ class ShapeEntityRenderer : public TypedEntityRenderer { public: ShapeEntityRenderer(const EntityItemPointer& entity); - virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override; + virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) override; private: virtual bool needsRenderUpdate() const override; diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 50abe7928f..402afea2cc 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1897,7 +1897,8 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS geometry.meshes.append(extracted.mesh); int meshIndex = geometry.meshes.size() - 1; if (extracted.mesh._mesh) { - extracted.mesh._mesh->displayName = QString("%1#/mesh/%2").arg(url).arg(meshIndex); + extracted.mesh._mesh->displayName = QString("%1#/mesh/%2").arg(url).arg(meshIndex).toStdString(); + extracted.mesh._mesh->modelName = modelIDsToNames.value(modelID).toStdString(); } meshIDsToMeshIndices.insert(it.key(), meshIndex); } @@ -1983,7 +1984,7 @@ FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QStri reader._loadLightmaps = loadLightmaps; reader._lightmapLevel = lightmapLevel; - qDebug() << "Reading FBX: " << url; + qCDebug(modelformat) << "Reading FBX: " << url; return reader.extractFBXGeometry(mapping, url); } diff --git a/libraries/fbx/src/OBJWriter.cpp b/libraries/fbx/src/OBJWriter.cpp index 5307f49f36..621852f591 100644 --- a/libraries/fbx/src/OBJWriter.cpp +++ b/libraries/fbx/src/OBJWriter.cpp @@ -71,7 +71,8 @@ bool writeOBJToTextStream(QTextStream& out, QList meshes) { out << formatFloat(v[1]) << " "; out << formatFloat(v[2]); if (colorIndex < numColors) { - glm::vec3 color = colorsBufferView.get(colorIndex); + glm::vec3 color = glmVecFromVariant(buffer_helpers::toVariant(colorsBufferView, colorIndex)); + //glm::vec3 color = colorsBufferView.get(colorIndex); out << " " << formatFloat(color[0]); out << " " << formatFloat(color[1]); out << " " << formatFloat(color[2]); @@ -94,7 +95,7 @@ bool writeOBJToTextStream(QTextStream& out, QList meshes) { const gpu::BufferView& normalsBufferView = mesh->getAttributeBuffer(gpu::Stream::InputSlot::NORMAL); gpu::BufferView::Index numNormals = (gpu::BufferView::Index)normalsBufferView.getNumElements(); for (gpu::BufferView::Index i = 0; i < numNormals; i++) { - glm::vec3 normal = glmVecFromVariant(bufferViewElementToVariant(normalsBufferView, i)); + glm::vec3 normal = glmVecFromVariant(buffer_helpers::toVariant(normalsBufferView, i)); //glm::vec3 normal = normalsBufferView.get(i); out << "vn "; out << formatFloat(normal[0]) << " "; @@ -117,7 +118,7 @@ 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(mesh->displayName)) + 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); diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h index a536fc413c..cfa510f87f 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h @@ -19,7 +19,9 @@ namespace scriptable { // 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); } @@ -41,11 +43,15 @@ namespace scriptable { // C++ > QtOwned instance template std::shared_ptr make_qtowned(Rest... rest) { T* tmp = new T(rest...); +#ifdef SCRIPTABLE_MESH_DEBUG qCInfo(graphics_scripting) << "scriptable::make_qtowned" << toDebugString(tmp); +#endif QString debug = toDebugString(tmp); if (tmp) { tmp->metadata["__ownership__"] = QScriptEngine::QtOwnership; +#ifdef SCRIPTABLE_MESH_DEBUG QObject::connect(tmp, &QObject::destroyed, [=]() { qCInfo(graphics_scripting) << "-------- ~scriptable::make_qtowned" << debug; }); +#endif auto ptr = std::shared_ptr(tmp, [debug](T* tmp) { //qDebug() << "~std::shared_ptr" << debug; delete tmp; @@ -58,7 +64,9 @@ namespace scriptable { // C++ > ScriptOwned JS instance template QPointer make_scriptowned(Rest... rest) { T* tmp = new T(rest...); +#ifdef SCRIPTABLE_MESH_DEBUG qCInfo(graphics_scripting) << "scriptable::make_scriptowned" << toDebugString(tmp); +#endif if (tmp) { tmp->metadata["__ownership__"] = QScriptEngine::ScriptOwnership; //auto blah = (DeleterFunction)[](void* delme) { }; @@ -71,12 +79,14 @@ namespace scriptable { template QPointer add_scriptowned_destructor(T* tmp) { QString debug = toDebugString(tmp); if (tmp) { +#ifdef SCRIPTABLE_MESH_DEBUG QObject::connect(tmp, &QObject::destroyed, [=]() { qCInfo(graphics_scripting) << "-------- ~scriptable::make_scriptowned" << debug;// << !!customDeleter; //if (customDeleter) { // customDeleter(tmp); //} }); +#endif } else { qCInfo(graphics_scripting) << "add_scriptowned_destructor -- not connecting to null value" << debug; } diff --git a/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp index ab9403a8ed..d78f646087 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp @@ -48,7 +48,7 @@ bool ModelScriptingInterface::updateMeshes(QUuid uuid, const scriptable::Scripta bool ModelScriptingInterface::updateMeshes(QUuid uuid, const scriptable::ScriptableModelPointer model) { auto appProvider = DependencyManager::get(); - qCDebug(graphics_scripting) << "appProvider" << appProvider.data(); + //qCDebug(graphics_scripting) << "appProvider" << appProvider.data(); scriptable::ModelProviderPointer provider = appProvider ? appProvider->lookupModelProvider(uuid) : nullptr; QString providerType = provider ? provider->metadata.value("providerType").toString() : QString(); if (providerType.isEmpty()) { @@ -56,12 +56,12 @@ bool ModelScriptingInterface::updateMeshes(QUuid uuid, const scriptable::Scripta } bool success = false; if (provider) { - qCDebug(graphics_scripting) << "fetching meshes from " << providerType << "..."; + //qCDebug(graphics_scripting) << "fetching meshes from " << providerType << "..."; auto scriptableMeshes = provider->getScriptableModel(&success); - qCDebug(graphics_scripting) << "//fetched meshes from " << providerType << "success:" <operator scriptable::ScriptableModelBasePointer(); - qCDebug(graphics_scripting) << "as base" << base; + //qCDebug(graphics_scripting) << "as base" << base; if (base) { //auto meshes = model->getConstMeshes(); success = provider->replaceScriptableModelMeshPart(base, -1, -1); diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp index b83b901acd..c662371c89 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp @@ -28,6 +28,8 @@ #include "OBJWriter.h" +// #define SCRIPTABLE_MESH_DEBUG + namespace scriptable { // QScriptValue jsBindCallback(QScriptValue callback); // template QPointer qpointer_qobject_cast(const QScriptValue& value); @@ -287,7 +289,9 @@ 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" }); metadata["last-modified"] = QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate); @@ -328,9 +332,10 @@ quint32 scriptable::ScriptableMeshPart::mapAttributeValues(QScriptValue callback } bool scriptable::ScriptableMeshPart::unrollVertices(bool recalcNormals) { - auto meshProxy = this; auto mesh = getMeshPointer(); +#ifdef SCRIPTABLE_MESH_DEBUG qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices" << !!mesh<< !!meshProxy; +#endif if (!mesh) { return false; } @@ -527,28 +532,32 @@ scriptable::ScriptableMeshPointer scriptable::ScriptableMesh::cloneMesh(bool rec qCInfo(graphics_scripting) << "ScriptableMesh::cloneMesh -- !meshPointer"; return nullptr; } - qCInfo(graphics_scripting) << "ScriptableMesh::cloneMesh..."; + // qCInfo(graphics_scripting) << "ScriptableMesh::cloneMesh..."; auto clone = buffer_helpers::cloneMesh(mesh); - qCInfo(graphics_scripting) << "ScriptableMesh::cloneMesh..."; + // qCInfo(graphics_scripting) << "ScriptableMesh::cloneMesh..."; if (recalcNormals) { buffer_helpers::recalculateNormals(clone); } - qCDebug(graphics_scripting) << clone.get();// << metadata; + //qCDebug(graphics_scripting) << clone.get();// << metadata; auto meshPointer = scriptable::make_scriptowned(provider, model, clone, metadata); clone.reset(); // free local reference - qCInfo(graphics_scripting) << "========= ScriptableMesh::cloneMesh..." << meshPointer << meshPointer->ownedMesh.use_count(); + // qCInfo(graphics_scripting) << "========= ScriptableMesh::cloneMesh..." << meshPointer << meshPointer->ownedMesh.use_count(); //scriptable::MeshPointer* ppMesh = new scriptable::MeshPointer(); //*ppMesh = clone; - if (meshPointer) { + if (0 && meshPointer) { scriptable::WeakMeshPointer delme = meshPointer->mesh; QString debugString = scriptable::toDebugString(meshPointer); QObject::connect(meshPointer, &QObject::destroyed, meshPointer, [=]() { - qCWarning(graphics_scripting) << "*************** cloneMesh/Destroy"; - qCWarning(graphics_scripting) << "*************** " << debugString << delme.lock().get(); + // qCWarning(graphics_scripting) << "*************** cloneMesh/Destroy"; + // qCWarning(graphics_scripting) << "*************** " << debugString << delme.lock().get(); if (!delme.expired()) { - qCWarning(graphics_scripting) << "cloneMesh -- potential memory leak..." << debugString << delme.lock().get(); + QTimer::singleShot(250, this, [=]{ + if (!delme.expired()) { + qCWarning(graphics_scripting) << "cloneMesh -- potential memory leak..." << debugString << delme.use_count(); + } + }); } }); } @@ -575,12 +584,16 @@ scriptable::ScriptableMeshBase& scriptable::ScriptableMeshBase::operator=(const } scriptable::ScriptableMeshBase::~ScriptableMeshBase() { ownedMesh.reset(); +#ifdef SCRIPTABLE_MESH_DEBUG qCInfo(graphics_scripting) << "//~ScriptableMeshBase" << this << "ownedMesh:" << ownedMesh.use_count() << "mesh:" << mesh.use_count(); +#endif } scriptable::ScriptableMesh::~ScriptableMesh() { ownedMesh.reset(); +#ifdef SCRIPTABLE_MESH_DEBUG qCInfo(graphics_scripting) << "//~ScriptableMesh" << this << "ownedMesh:" << ownedMesh.use_count() << "mesh:" << mesh.use_count(); +#endif } QString scriptable::ScriptableMeshPart::toOBJ() { diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h index c655167c2b..ba0efa007d 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h @@ -100,7 +100,7 @@ namespace scriptable { ScriptableMeshPart(scriptable::ScriptableMeshPointer parentMesh, int partIndex); ScriptableMeshPart& operator=(const ScriptableMeshPart& view) { parentMesh=view.parentMesh; return *this; }; ScriptableMeshPart(const ScriptableMeshPart& other) : parentMesh(other.parentMesh), partIndex(other.partIndex) {} - ~ScriptableMeshPart() { qDebug() << "~ScriptableMeshPart" << this; } + // ~ScriptableMeshPart() { qDebug() << "~ScriptableMeshPart" << this; } public slots: scriptable::ScriptableMeshPointer getParentMesh() const { return parentMesh; } diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp new file mode 100644 index 0000000000..c8f1975249 --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp @@ -0,0 +1,149 @@ +// +// ScriptableModel.cpp +// libraries/graphics-scripting +// +// 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 "ScriptableModel.h" +#include "ScriptableMesh.h" + +#include +//#include "ScriptableModel.moc" + +void scriptable::ScriptableModelBase::mixin(const QVariantMap& modelMetaData) { + for (const auto& key : modelMetaData.keys()) { + const auto& value = modelMetaData[key]; + if (metadata.contains(key) && metadata[key].type() == (QVariant::Type)QMetaType::QVariantList) { + qCDebug(graphics_scripting) << "CONCATENATING" << key << metadata[key].toList().size() << "+" << value.toList().size(); + metadata[key] = metadata[key].toList() + value.toList(); + } else { + metadata[key] = modelMetaData[key]; + } + } +} + +scriptable::ScriptableModelBase::~ScriptableModelBase() { +#ifdef SCRIPTABLE_MESH_DEBUG + qCDebug(graphics_scripting) << "~ScriptableModelBase" << this; +#endif + for (auto& m : meshes) { + m.ownedMesh.reset(); + //qCDebug(graphics_scripting) << "~~~~ScriptableModelBase" << &m << m.mesh.use_count(); + } + meshes.clear(); + //qCDebug(graphics_scripting) << "//~ScriptableModelBase" << this; +} +void scriptable::ScriptableModelBase::append(scriptable::WeakMeshPointer mesh, const QVariantMap& metadata) { + //qCDebug(graphics_scripting) << "+ APPEND WeakMeshPointer" << mesh.lock().get(); + meshes << ScriptableMeshBase{ provider, this, mesh, metadata }; +} + +void scriptable::ScriptableModelBase::append(const ScriptableMeshBase& mesh, const QVariantMap& modelMetaData) { + //qCDebug(graphics_scripting) << "+ APPEND ScriptableMeshBase" << &mesh; + if (mesh.provider.lock().get() != provider.lock().get()) { + qCDebug(graphics_scripting) << "warning: appending mesh from different provider..." << mesh.provider.lock().get() << " != " << provider.lock().get(); + } + //if (mesh.model && mesh.model != this) { + // qCDebug(graphics_scripting) << "warning: appending mesh from different model..." << mesh.model << " != " << this; + //} + meshes << mesh; + mixin(modelMetaData); +} + +void scriptable::ScriptableModelBase::append(const ScriptableModelBase& other, const QVariantMap& modelMetaData) { + //qCDebug(graphics_scripting) << "+ APPEND ScriptableModelBase" << &other; + for (const auto& mesh : other.meshes) { append(mesh); } + mixin(other.metadata); + mixin(modelMetaData); +} + + +QString scriptable::ScriptableModel::toString() const { + return QString("[ScriptableModel%1%2]") + .arg(objectID.isNull() ? "" : " objectID="+objectID.toString()) + .arg(objectName().isEmpty() ? "" : " name=" +objectName()); +} + +scriptable::ScriptableModelPointer scriptable::ScriptableModel::cloneModel(const QVariantMap& options) { + scriptable::ScriptableModelPointer clone = scriptable::ScriptableModelPointer(new scriptable::ScriptableModel(*this)); + qCDebug(graphics_scripting) << "clone->getNumMeshes" << clone->getNumMeshes(); + clone->meshes.clear(); + qCDebug(graphics_scripting) << "..clone->getNumMeshes" << clone->getNumMeshes(); + for (const auto &mesh : getConstMeshes()) { + auto cloned = mesh->cloneMesh(options.value("recalculateNormals").toBool()); + if (auto tmp = qobject_cast(cloned)) { + qCDebug(graphics_scripting) << "++ APPEND" << tmp << tmp->ownedMesh.use_count() << tmp->metadata.value("__ownership__") << tmp->metadata.value("__native__"); + clone->meshes << *tmp; + tmp->deleteLater(); + qCDebug(graphics_scripting) << "//++ APPEND" << clone->meshes.constLast().ownedMesh.use_count();; + } else { + qCDebug(graphics_scripting) << "error cloning mesh" << cloned; + } + } + qCDebug(graphics_scripting) << "//clone->getNumMeshes" << clone->getNumMeshes(); + return clone; +} + + +const QVector scriptable::ScriptableModel::getConstMeshes() const { + QVector out; + for(const auto& mesh : meshes) { + const scriptable::ScriptableMesh* m = qobject_cast(&mesh); + if (!m) { + m = scriptable::make_scriptowned(mesh); + } else { + qCDebug(graphics_scripting) << "reusing scriptable mesh" << m; + } + const scriptable::ScriptableMeshPointer mp = scriptable::ScriptableMeshPointer(const_cast(m)); + out << mp; + } + return out; +} + +QVector scriptable::ScriptableModel::getMeshes() { + QVector out; + for(auto& mesh : meshes) { + scriptable::ScriptableMesh* m = qobject_cast(&mesh); + if (!m) { + m = scriptable::make_scriptowned(mesh); + } else { + qCDebug(graphics_scripting) << "reusing scriptable mesh" << m; + } + scriptable::ScriptableMeshPointer mp = scriptable::ScriptableMeshPointer(m); + out << mp; + } + return out; +} + +quint32 scriptable::ScriptableModel::mapAttributeValues(QScriptValue callback) { + quint32 result = 0; + QVector in = getMeshes(); + if (in.size()) { + foreach (scriptable::ScriptableMeshPointer meshProxy, in) { + result += meshProxy->mapAttributeValues(callback); + } + } + return result; +} + +/*namespace { + QScriptValue modelPointerToScriptValue(QScriptEngine* engine, scriptable::ScriptableModelPointer const &in) { + return qObjectToScriptValue(engine, in); + } + void modelPointerFromScriptValue(const QScriptValue& value, scriptable::ScriptableModelPointer &out) { + out = scriptable::qpointer_qobject_cast(value); + } +} + +namespace scriptable { + bool registerMetaTypes(QScriptEngine* engine) { + qScriptRegisterMetaType(engine, modelPointerToScriptValue, modelPointerFromScriptValue); + return true; + } +} +*/ diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h index 97a73ddd61..d2c50bd768 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h @@ -16,7 +16,7 @@ namespace scriptable { ScriptableModel(const ScriptableModel& other) : ScriptableModelBase(other) {} ScriptableModel(const ScriptableModelBase& other) : ScriptableModelBase(other) {} ScriptableModel& operator=(const ScriptableModelBase& view) { ScriptableModelBase::operator=(view); return *this; } - virtual ~ScriptableModel() { qDebug() << "~ScriptableModel" << this; } + //virtual ~ScriptableModel() { qDebug() << "~ScriptableModel" << this; } Q_INVOKABLE scriptable::ScriptableModelPointer cloneModel(const QVariantMap& options = QVariantMap()); // TODO: in future accessors for these could go here diff --git a/libraries/graphics/src/graphics/Geometry.h b/libraries/graphics/src/graphics/Geometry.h index 23ebec2965..a75fb1bf62 100755 --- a/libraries/graphics/src/graphics/Geometry.h +++ b/libraries/graphics/src/graphics/Geometry.h @@ -136,7 +136,8 @@ public: static MeshPointer createIndexedTriangles_P3F(uint32_t numVertices, uint32_t numTriangles, const glm::vec3* vertices = nullptr, const uint32_t* indices = nullptr); - QString displayName; + std::string modelName; + std::string displayName; protected: diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 6aa42cf6df..95985a48cb 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -2448,7 +2448,8 @@ graphics::MeshPointer GeometryCache::meshFromShape(Shape geometryShape, glm::vec auto partBuffer = new gpu::Buffer(sizeof(graphics::Mesh::Part), (gpu::Byte*)&part); mesh->setPartBuffer(gpu::BufferView(partBuffer, gpu::Element::PART_DRAWCALL)); - mesh->displayName = QString("GeometryCache/shape::%1").arg(GeometryCache::stringFromShape(geometryShape)); + mesh->modelName = GeometryCache::stringFromShape(geometryShape).toStdString(); + mesh->displayName = QString("GeometryCache/shape::%1").arg(GeometryCache::stringFromShape(geometryShape)).toStdString(); return mesh; } diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index ae5ac5d61c..81ff5433f7 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -9,6 +9,10 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include +namespace { QLoggingCategory wtf{ "tim.Model.cpp" }; } + + #include "Model.h" #include @@ -26,7 +30,7 @@ #include #include -#include +#include #include #include @@ -75,7 +79,7 @@ void initCollisionMaterials() { graphics::MaterialPointer material; material = std::make_shared(); int index = j * sectionWidth + i; - float red = component[index]; + float red = component[index % NUM_COLLISION_HULL_COLORS]; float green = component[(index + greenPhase) % NUM_COLLISION_HULL_COLORS]; float blue = component[(index + bluePhase) % NUM_COLLISION_HULL_COLORS]; material->setAlbedo(glm::vec3(red, green, blue)); @@ -573,35 +577,109 @@ bool Model::convexHullContains(glm::vec3 point) { return false; } -scriptable::ScriptableModel Model::getScriptableModel(bool* ok) { - scriptable::ScriptableModel 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); + + if (!isLoaded()) { + return false; + } + + { + // FIXME: temporary workaround for updating the whole FBXGeometry (to keep findRayIntersection in sync) + auto newRenderGeometry = new MyGeometryMappingResource( + _url, _renderGeometry, _newModel ? scriptable::make_qtowned(*_newModel) : nullptr + ); + _needsUpdateTextures = true; + _visualGeometryRequestFailed = false; + invalidCalculatedMeshBoxes(); + deleteGeometry(); + _renderGeometry.reset(newRenderGeometry); + onInvalidate(); + reset(); + _rig.destroyAnimGraph(); + assert(_rig.jointStatesEmpty()); + updateGeometry(); + calculateTriangleSets(); + computeMeshPartLocalBounds(); + _needsReload = false; + _needsFixupInScene = true; + invalidCalculatedMeshBoxes(); + setRenderItemsNeedUpdate(); + } + return true; +} + +scriptable::ScriptableModelBase Model::getScriptableModel(bool* ok) { + QMutexLocker lock(&_mutex); + scriptable::ScriptableModelBase result; const Geometry::Pointer& renderGeometry = getGeometry(); if (!isLoaded()) { - qDebug() << "Model::getScriptableModel -- !isLoaded"; + qCDebug(wtf) << "Model::getScriptableModel -- !isLoaded"; return scriptable::ModelProvider::modelUnavailableError(ok); } const FBXGeometry& geometry = getFBXGeometry(); - auto mat4toVariant = [](const glm::mat4& mat4) -> QVariant { - QVector floats; - floats.resize(16); - memcpy(floats.data(), &mat4, sizeof(glm::mat4)); - QVariant v; - v.setValue>(floats); - return v; - }; + Transform offset; + offset.setScale(_scale); + offset.postTranslate(_offset); + glm::mat4 offsetMat = offset.getMatrix(); + glm::mat4 meshToModelMatrix = glm::scale(_scale) * glm::translate(_offset); + glm::mat4 meshToWorldMatrix = createMatFromQuatAndPos(_rotation, _translation) * meshToModelMatrix; result.metadata = { { "url", _url.toString() }, { "textures", renderGeometry->getTextures() }, { "offset", vec3toVariant(_offset) }, - { "scale", vec3toVariant(_scale) }, - { "rotation", quatToVariant(_rotation) }, - { "translation", vec3toVariant(_translation) }, - { "meshToModel", mat4toVariant(glm::scale(_scale) * glm::translate(_offset)) }, - { "meshToWorld", mat4toVariant(createMatFromQuatAndPos(_rotation, _translation) * (glm::scale(_scale) * glm::translate(_offset))) }, - { "geometryOffset", mat4toVariant(geometry.offset) }, + { "scale", vec3toVariant(getScale()) }, + { "rotation", quatToVariant(getRotation()) }, + { "translation", vec3toVariant(getTranslation()) }, + { "meshToModel", buffer_helpers::toVariant(meshToModelMatrix) }, + { "meshToWorld", buffer_helpers::toVariant(meshToWorldMatrix) }, + { "geometryOffset", buffer_helpers::toVariant(geometry.offset) }, + { "naturalDimensions", vec3toVariant(getNaturalDimensions()) }, + { "meshExtents", buffer_helpers::toVariant(getMeshExtents()) }, + { "unscaledMeshExtents", buffer_helpers::toVariant(getUnscaledMeshExtents()) }, + { "meshBound", buffer_helpers::toVariant(Extents(getRenderableMeshBound())) }, + { "bindExtents", buffer_helpers::toVariant(getBindExtents()) }, + { "offsetMat", buffer_helpers::toVariant(offsetMat) }, + { "transform", buffer_helpers::toVariant(getTransform().getMatrix()) }, }; + { + Transform transform; + transform.setScale(getScale()); + transform.setTranslation(getTranslation()); + transform.setRotation(getRotation()); + result.metadata["_transform"] = buffer_helpers::toVariant(transform.getMatrix()); + } + { + glm::vec3 position = _translation; + glm::mat4 rotation = glm::mat4_cast(_rotation); + glm::mat4 translation = glm::translate(position); + glm::mat4 modelToWorldMatrix = translation * rotation; + //glm::mat4 worldToModelMatrix = glm::inverse(modelToWorldMatrix); + result.metadata["_modelToWorld"] = buffer_helpers::toVariant(modelToWorldMatrix); + } + { + glm::mat4 meshToModelMatrix = glm::scale(_scale) * glm::translate(_offset); + glm::mat4 meshToWorldMatrix = createMatFromQuatAndPos(_rotation, _translation) * meshToModelMatrix; + //glm::mat4 worldToMeshMatrix = glm::inverse(meshToWorldMatrix); + result.metadata["_meshToWorld"] = buffer_helpers::toVariant(meshToWorldMatrix); + } + { + Transform transform; + transform.setTranslation(_translation); + transform.setRotation(_rotation); + Transform offset; + offset.setScale(_scale); + offset.postTranslate(_offset); + Transform output; + Transform::mult( output, transform, offset); + result.metadata["_renderTransform"] = buffer_helpers::toVariant(output.getMatrix()); + } + QVariantList submeshes; int numberOfMeshes = geometry.meshes.size(); for (int i = 0; i < numberOfMeshes; i++) { @@ -610,53 +688,43 @@ scriptable::ScriptableModel Model::getScriptableModel(bool* ok) { if (!mesh) { continue; } - result.meshes << std::const_pointer_cast(mesh); - auto extraInfo = geometry.getModelNameOfMesh(i); - qDebug() << "Model::getScriptableModel #" << i << QString(mesh->displayName) << extraInfo; - submeshes << QVariantMap{ + auto name = geometry.getModelNameOfMesh(i); + qCDebug(wtf) << "Model::getScriptableModel #" << i << QString::fromStdString(mesh->displayName) << name; + const AABox& box = _modelSpaceMeshTriangleSets.value(i).getBounds(); + AABox hardbounds; + auto meshTransform = geometry.offset * fbxMesh.modelTransform; + for (const auto& v : fbxMesh.vertices) { + hardbounds += glm::vec3(meshTransform * glm::vec4(v,1)); + } + QVariantList renderIDs; + for (uint32_t m = 0; m < _modelMeshRenderItemIDs.size(); m++) { + auto meshIndex = _modelMeshRenderItemShapes.size() > m ? _modelMeshRenderItemShapes.at(m).meshIndex : -1; + if (meshIndex == i) { + renderIDs << _modelMeshRenderItemIDs[m]; + break; + } + } + + result.append(std::const_pointer_cast(mesh), { { "index", i }, + { "name", name }, + { "renderIDs", renderIDs }, { "meshIndex", fbxMesh.meshIndex }, - { "modelName", extraInfo }, - { "transform", mat4toVariant(fbxMesh.modelTransform) }, - { "extents", QVariantMap({ - { "minimum", vec3toVariant(fbxMesh.meshExtents.minimum) }, - { "maximum", vec3toVariant(fbxMesh.meshExtents.maximum) }, - })}, - }; + { "displayName", QString::fromStdString(mesh->displayName) }, + { "modelName", QString::fromStdString(mesh->modelName) }, + { "modelTransform", buffer_helpers::toVariant(fbxMesh.modelTransform) }, + { "transform", buffer_helpers::toVariant(geometry.offset * fbxMesh.modelTransform) }, + { "extents", buffer_helpers::toVariant(fbxMesh.meshExtents) }, + { "bounds", buffer_helpers::toVariant(Extents(box)) }, + { "hardbounds", buffer_helpers::toVariant(Extents(hardbounds)) }, + }); } if (ok) { *ok = true; } - qDebug() << "//Model::getScriptableModel -- #" << result.meshes.size(); + qCDebug(wtf) << "//Model::getScriptableModel -- #" << result.meshes.size(); result.metadata["submeshes"] = submeshes; return result; - -// TODO: remove -- this was an earlier approach using renderGeometry instead of FBXGeometry -#if 0 // renderGeometry approach - const Geometry::GeometryMeshes& meshes = renderGeometry->getMeshes(); - Transform offset; - offset.setScale(_scale); - offset.postTranslate(_offset); - glm::mat4 offsetMat = offset.getMatrix(); - - for (std::shared_ptr mesh : meshes) { - if (!mesh) { - continue; - } - qDebug() << "Model::getScriptableModel #" << i++ << mesh->displayName; - auto newmesh = mesh->map( - [=](glm::vec3 position) { - return glm::vec3(offsetMat * glm::vec4(position, 1.0f)); - }, - [=](glm::vec3 color) { return color; }, - [=](glm::vec3 normal) { - return glm::normalize(glm::vec3(offsetMat * glm::vec4(normal, 0.0f))); - }, - [&](uint32_t index) { return index; }); - newmesh->displayName = mesh->displayName; - result << newmesh; - } -#endif } void Model::calculateTriangleSets() { diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 4fd00c9f9a..375ee016ca 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -27,7 +27,7 @@ #include #include #include -#include +#include #include #include #include @@ -314,7 +314,8 @@ public: int getResourceDownloadAttempts() { return _renderWatcher.getResourceDownloadAttempts(); } int getResourceDownloadAttemptsRemaining() { return _renderWatcher.getResourceDownloadAttemptsRemaining(); } - Q_INVOKABLE virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override; + Q_INVOKABLE virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) override; + virtual bool replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer model, int meshIndex, int partIndex) override; void scaleToFit(); diff --git a/libraries/render-utils/src/Model_temporary_hack.cpp.h b/libraries/render-utils/src/Model_temporary_hack.cpp.h new file mode 100644 index 0000000000..bc31abcb37 --- /dev/null +++ b/libraries/render-utils/src/Model_temporary_hack.cpp.h @@ -0,0 +1,121 @@ +#include +#include +class MyGeometryMappingResource : public GeometryResource { +// Q_OBJECT +public: + virtual void init(bool resetLoaded = true) override { + qCDebug(wtf) << "############################# Snarfing init()..."; + } + + virtual void deleter() override { + qCDebug(wtf) << "############################# Snarfing deleter()..."; + } + shared_ptr fbxGeometry; + MyGeometryMappingResource(const QUrl& url, Geometry::Pointer originalGeometry, std::shared_ptr 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.author = original->author; + geometry.applicationName = original->applicationName; + for (const auto &j : original->joints) { + geometry.joints << j; + } + geometry.jointIndices = QHash{ original->jointIndices }; + + geometry.animationFrames = QVector{ original->animationFrames }; + geometry.meshIndicesToModelNames = QHash{ original->meshIndicesToModelNames }; + geometry.blendshapeChannelNames = QList{ original->blendshapeChannelNames }; + + geometry.hasSkeletonJoints = original->hasSkeletonJoints; + geometry.offset = original->offset; + geometry.leftEyeJointIndex = original->leftEyeJointIndex; + geometry.rightEyeJointIndex = original->rightEyeJointIndex; + geometry.neckJointIndex = original->neckJointIndex; + geometry.rootJointIndex = original->rootJointIndex; + geometry.leanJointIndex = original->leanJointIndex; + geometry.headJointIndex = original->headJointIndex; + geometry.leftHandJointIndex = original->leftHandJointIndex; + geometry.rightHandJointIndex = original->rightHandJointIndex; + geometry.leftToeJointIndex = original->leftToeJointIndex; + geometry.rightToeJointIndex = original->rightToeJointIndex; + geometry.leftEyeSize = original->leftEyeSize; + geometry.rightEyeSize = original->rightEyeSize; + geometry.humanIKJointIndices = original->humanIKJointIndices; + geometry.palmDirection = original->palmDirection; + geometry.neckPivot = original->neckPivot; + geometry.bindExtents = original->bindExtents; + + // Copy materials + QHash materialIDAtlas; + for (const FBXMaterial& material : original->materials) { + materialIDAtlas[material.materialID] = _materials.size(); + _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) { + 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"); + geometry.meshes << mesh; + // Copy mesh pointers + meshes->emplace_back(newMesh.getMeshPointer());//buffer_helpers::cloneMesh(ptr)); + int partID = 0; + const auto oldParts = mesh.parts; + mesh.parts.clear(); + for (const FBXMeshPart& fbxPart : oldParts) { + FBXMeshPart part; // copy; + part.materialID = fbxPart.materialID; + // Construct local parts + ///qCDebug(wtf) << "GeometryMappingResource -- meshes part" << meshID << partID << part.materialID; + part.triangleIndices = buffer_helpers::toVector(mesh._mesh->getIndexBuffer(), "part.triangleIndices"); + mesh.parts << part; + auto p = std::make_shared(meshID, partID, (int)materialIDAtlas[part.materialID]); + 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; + _animGraphOverrideUrl = originalGeometry ? originalGeometry->getAnimGraphOverrideUrl() : QUrl(); + _loaded = true; + _fbxGeometry = fbxGeometry; + finishedLoading(true); + }; +}; + diff --git a/libraries/script-engine/src/AssetScriptingInterface.cpp b/libraries/script-engine/src/AssetScriptingInterface.cpp index 1c811573fb..750e612781 100644 --- a/libraries/script-engine/src/AssetScriptingInterface.cpp +++ b/libraries/script-engine/src/AssetScriptingInterface.cpp @@ -440,7 +440,7 @@ void AssetScriptingInterface::saveToCache(const QUrl& rawURL, const QByteArray& JS_VERIFY(url.scheme() == "atp" || url.scheme() == "cache", "only 'atp' and 'cache' URL schemes supported"); JS_VERIFY(hash.isEmpty() || hash == hashDataHex(data), QString("invalid checksum hash for atp:HASH style URL (%1 != %2)").arg(hash, hashDataHex(data))); - qCDebug(scriptengine) << "saveToCache" << url.toDisplayString() << data << hash << metadata; + //qCDebug(scriptengine) << "saveToCache" << url.toDisplayString() << data << hash << metadata; jsPromiseReady(Parent::saveToCache(url, data, metadata), scope, callback); } diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index 78cb05fa0d..873c205706 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -132,6 +132,20 @@ QUrl expandScriptUrl(const QUrl& rawScriptURL) { QObject* scriptsModel(); +bool NativeScriptInitializers::registerNativeScriptInitializer(NativeScriptInitializer initializer) { + return registerScriptInitializer([=](ScriptEnginePointer engine) { + initializer(qobject_cast(engine.data())); + }); +} + +bool NativeScriptInitializers::registerScriptInitializer(ScriptInitializer initializer) { + if (auto scriptEngines = DependencyManager::get().data()) { + scriptEngines->registerScriptInitializer(initializer); + return true; + } + return false; +} + void ScriptEngines::registerScriptInitializer(ScriptInitializer initializer) { _scriptInitializers.push_back(initializer); } @@ -520,6 +534,17 @@ void ScriptEngines::onScriptEngineLoaded(const QString& rawScriptURL) { emit scriptCountChanged(); } +int ScriptEngines::runScriptInitializers(ScriptEnginePointer scriptEngine) { + // register our application services and set it off on its own thread + int ii=0; + for (auto initializer : _scriptInitializers) { + ii++; + qDebug() << "initializer" << ii; + initializer(scriptEngine); + } + return ii; +} + void ScriptEngines::launchScriptEngine(ScriptEnginePointer scriptEngine) { connect(scriptEngine.data(), &ScriptEngine::finished, this, &ScriptEngines::onScriptFinished, Qt::DirectConnection); connect(scriptEngine.data(), &ScriptEngine::loadScript, [&](const QString& scriptName, bool userLoaded) { @@ -529,10 +554,7 @@ void ScriptEngines::launchScriptEngine(ScriptEnginePointer scriptEngine) { loadScript(scriptName, userLoaded, false, false, true); }); - // register our application services and set it off on its own thread - for (auto initializer : _scriptInitializers) { - initializer(scriptEngine); - } + runScriptInitializers(scriptEngine); // FIXME disabling 'shift key' debugging for now. If you start up the application with // the shift key held down, it triggers a deadlock because of script interfaces running diff --git a/libraries/script-engine/src/ScriptEngines.h b/libraries/script-engine/src/ScriptEngines.h index 5a4b8f2f47..ea07ebe840 100644 --- a/libraries/script-engine/src/ScriptEngines.h +++ b/libraries/script-engine/src/ScriptEngines.h @@ -19,6 +19,7 @@ #include #include +#include #include "ScriptEngine.h" #include "ScriptsModel.h" @@ -26,6 +27,12 @@ class ScriptEngine; +class NativeScriptInitializers : public ScriptInitializerMixin { +public: + bool registerNativeScriptInitializer(NativeScriptInitializer initializer) override; + bool registerScriptInitializer(ScriptInitializer initializer) override; +}; + class ScriptEngines : public QObject, public Dependency { Q_OBJECT @@ -34,11 +41,11 @@ class ScriptEngines : public QObject, public Dependency { Q_PROPERTY(QString debugScriptUrl READ getDebugScriptUrl WRITE setDebugScriptUrl) public: - using ScriptInitializer = std::function; + using ScriptInitializer = ScriptInitializerMixin::ScriptInitializer; ScriptEngines(ScriptEngine::Context context); void registerScriptInitializer(ScriptInitializer initializer); - + int runScriptInitializers(ScriptEnginePointer engine); void loadScripts(); void saveScripts(); diff --git a/libraries/shared/src/shared/ScriptInitializerMixin.h b/libraries/shared/src/shared/ScriptInitializerMixin.h new file mode 100644 index 0000000000..50de553b0b --- /dev/null +++ b/libraries/shared/src/shared/ScriptInitializerMixin.h @@ -0,0 +1,38 @@ +// +// ScriptInitializerMixin.h +// libraries/shared/src/shared +// +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. + +#pragma once + +#include +#include +#include "../DependencyManager.h" + +class QScriptEngine; +class ScriptEngine; + +class ScriptInitializerMixin : public QObject, public Dependency { + Q_OBJECT +public: + // Lightweight `QScriptEngine*` initializer (only depends on built-in Qt components) + // example registration: + // eg: [&](QScriptEngine* engine) -> bool { + // engine->globalObject().setProperties("API", engine->newQObject(...instance...)) + // return true; + // } + using NativeScriptInitializer = std::function; + virtual bool registerNativeScriptInitializer(NativeScriptInitializer initializer) = 0; + + // Heavyweight `ScriptEngine*` initializer (tightly coupled to Interface and script-engine library internals) + // eg: [&](ScriptEnginePointer scriptEngine) -> bool { + // engine->registerGlobalObject("API", ...instance..); + // return true; + // } + using ScriptEnginePointer = QSharedPointer; + using ScriptInitializer = std::function; + virtual bool registerScriptInitializer(ScriptInitializer initializer) { return false; }; +}; From 4f0f6709c11d0c77fd63c8999e8b4656a9a46539 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 20 Feb 2018 13:56:06 -0800 Subject: [PATCH 20/49] fix build errors --- .../src/graphics-scripting/ScriptableModel.cpp | 1 + .../src/graphics-scripting/ScriptableModel.h | 1 - libraries/render-utils/CMakeLists.txt | 3 +-- tests/render-perf/CMakeLists.txt | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp index c8f1975249..05ff043820 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp @@ -38,6 +38,7 @@ scriptable::ScriptableModelBase::~ScriptableModelBase() { meshes.clear(); //qCDebug(graphics_scripting) << "//~ScriptableModelBase" << this; } + void scriptable::ScriptableModelBase::append(scriptable::WeakMeshPointer mesh, const QVariantMap& metadata) { //qCDebug(graphics_scripting) << "+ APPEND WeakMeshPointer" << mesh.lock().get(); meshes << ScriptableMeshBase{ provider, this, mesh, metadata }; diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h index d2c50bd768..bdfaac5d7c 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h @@ -16,7 +16,6 @@ namespace scriptable { ScriptableModel(const ScriptableModel& other) : ScriptableModelBase(other) {} ScriptableModel(const ScriptableModelBase& other) : ScriptableModelBase(other) {} ScriptableModel& operator=(const ScriptableModelBase& view) { ScriptableModelBase::operator=(view); return *this; } - //virtual ~ScriptableModel() { qDebug() << "~ScriptableModel" << this; } Q_INVOKABLE scriptable::ScriptableModelPointer cloneModel(const QVariantMap& options = QVariantMap()); // TODO: in future accessors for these could go here diff --git a/libraries/render-utils/CMakeLists.txt b/libraries/render-utils/CMakeLists.txt index 55762e38fd..54c0821569 100644 --- a/libraries/render-utils/CMakeLists.txt +++ b/libraries/render-utils/CMakeLists.txt @@ -3,11 +3,10 @@ AUTOSCRIBE_SHADER_LIB(gpu graphics render) # pull in the resources.qrc file qt5_add_resources(QT_RESOURCES_FILE "${CMAKE_CURRENT_SOURCE_DIR}/res/fonts/fonts.qrc") setup_hifi_library(Widgets OpenGL Network Qml Quick Script) -link_hifi_libraries(shared ktx gpu graphics model-networking render animation fbx image procedural) +link_hifi_libraries(shared ktx gpu graphics graphics-scripting model-networking render animation fbx image procedural) include_hifi_library_headers(networking) include_hifi_library_headers(octree) include_hifi_library_headers(audio) -include_hifi_library_headers(graphics-scripting) # for ScriptableModel.h if (NOT ANDROID) target_nsight() diff --git a/tests/render-perf/CMakeLists.txt b/tests/render-perf/CMakeLists.txt index 5314f7a45b..a4570e9b54 100644 --- a/tests/render-perf/CMakeLists.txt +++ b/tests/render-perf/CMakeLists.txt @@ -16,7 +16,7 @@ link_hifi_libraries( shared networking animation ktx image octree gl gpu render render-utils - graphics fbx model-networking + graphics fbx model-networking graphics-scripting entities entities-renderer audio avatars script-engine physics procedural midi ui ${PLATFORM_GL_BACKEND} From be8d79f53f74a4995319552703cf3a4f0a0499a3 Mon Sep 17 00:00:00 2001 From: humbletim Date: Thu, 22 Feb 2018 05:15:01 -0500 Subject: [PATCH 21/49] CR fedback and cleanup --- interface/src/Application.cpp | 8 +- interface/src/Application.h | 1 + interface/src/ui/overlays/Base3DOverlay.h | 1 - interface/src/ui/overlays/ModelOverlay.cpp | 4 + .../src/avatars-renderer/Avatar.cpp | 9 +- .../src/avatars-renderer/Avatar.h | 2 +- .../src/RenderableEntityItem.h | 1 + .../src/RenderableModelEntityItem.cpp | 30 ++ .../src/RenderableModelEntityItem.h | 2 + .../src/RenderablePolyVoxEntityItem.cpp | 37 +- .../src/RenderablePolyVoxEntityItem.h | 1 + .../src/RenderableShapeEntityItem.cpp | 6 +- libraries/entities/src/EntityItem.h | 4 + .../entities/src/EntityScriptingInterface.cpp | 29 +- .../entities/src/EntityScriptingInterface.h | 6 +- libraries/fbx/src/FBXReader.cpp | 41 +- .../graphics-scripting/BufferViewHelpers.cpp | 86 ++-- .../graphics-scripting/BufferViewHelpers.h | 8 +- .../BufferViewScripting.cpp | 2 - .../src/graphics-scripting/Forward.h | 16 +- .../GraphicsScriptingInterface.cpp | 160 +++++++ ...terface.h => GraphicsScriptingInterface.h} | 29 +- .../ModelScriptingInterface.cpp | 407 ------------------ .../graphics-scripting/ScriptableModel.cpp | 1 + .../src/graphics-scripting/ScriptableModel.h | 1 - .../src/model-networking/SimpleMeshProxy.cpp | 27 ++ .../src/model-networking/SimpleMeshProxy.h | 36 ++ libraries/render-utils/src/GeometryCache.cpp | 1 - libraries/render-utils/src/Model.cpp | 162 ++----- libraries/render-utils/src/Model.h | 6 +- .../src/ModelScriptingInterface.cpp | 251 +++++++++++ .../src/ModelScriptingInterface.h | 39 ++ libraries/script-engine/src/ScriptEngine.cpp | 6 + libraries/shared/src/RegisteredMetaTypes.cpp | 65 +++ libraries/shared/src/RegisteredMetaTypes.h | 42 ++ libraries/shared/src/SpatiallyNestable.h | 8 +- 36 files changed, 890 insertions(+), 645 deletions(-) create mode 100644 libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp rename libraries/graphics-scripting/src/graphics-scripting/{ModelScriptingInterface.h => GraphicsScriptingInterface.h} (54%) delete mode 100644 libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp create mode 100644 libraries/model-networking/src/model-networking/SimpleMeshProxy.cpp create mode 100644 libraries/model-networking/src/model-networking/SimpleMeshProxy.h create mode 100644 libraries/script-engine/src/ModelScriptingInterface.cpp create mode 100644 libraries/script-engine/src/ModelScriptingInterface.h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 5bfb1846a5..2e59541056 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -166,7 +166,7 @@ #include "scripting/AccountServicesScriptingInterface.h" #include "scripting/HMDScriptingInterface.h" #include "scripting/MenuScriptingInterface.h" -#include "graphics-scripting/ModelScriptingInterface.h" +#include "graphics-scripting/GraphicsScriptingInterface.h" #include "scripting/SettingsScriptingInterface.h" #include "scripting/WindowScriptingInterface.h" #include "scripting/ControllerScriptingInterface.h" @@ -199,6 +199,7 @@ #include #include #include +#include #include #include @@ -800,7 +801,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(true); - DependencyManager::set(); DependencyManager::registerInheritance(); DependencyManager::set(); DependencyManager::set(); @@ -5981,8 +5981,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe scriptEngine->registerGlobalObject("Scene", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Render", _renderEngine->getConfiguration().get()); - ModelScriptingInterface::registerMetaTypes(scriptEngine.data()); - scriptEngine->registerGlobalObject("Model", DependencyManager::get().data()); + GraphicsScriptingInterface::registerMetaTypes(scriptEngine.data()); + scriptEngine->registerGlobalObject("Graphics", DependencyManager::get().data()); scriptEngine->registerGlobalObject("ScriptDiscoveryService", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Reticle", getApplicationCompositor().getReticleInterface()); diff --git a/interface/src/Application.h b/interface/src/Application.h index ae07ebd9dd..ddb8ce11e5 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -72,6 +72,7 @@ #include #include +#include #include "FrameTimingsScriptingInterface.h" #include "Sound.h" diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index 6ccad338c9..25bacf2e7e 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -16,7 +16,6 @@ #include #include "Overlay.h" -namespace model { class Mesh; } class Base3DOverlay : public Overlay, public SpatiallyNestable, public scriptable::ModelProvider { Q_OBJECT using Parent = Overlay; diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index e007591ce0..17bc9e2263 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -75,6 +75,7 @@ void ModelOverlay::update(float deltatime) { render::ScenePointer scene = qApp->getMain3DScene(); render::Transaction transaction; if (_model->needsFixupInScene()) { + emit DependencyManager::get()->modelRemovedFromScene(getID(), NestableType::Overlay, _model); _model->removeFromScene(scene, transaction); _model->addToScene(scene, transaction); @@ -83,6 +84,7 @@ void ModelOverlay::update(float deltatime) { auto modelOverlay = static_cast(&data); modelOverlay->setSubRenderItemIDs(newRenderItemIDs); }); + emit DependencyManager::get()->modelAddedToScene(getID(), NestableType::Overlay, _model); } if (_visibleDirty) { _visibleDirty = false; @@ -107,12 +109,14 @@ void ModelOverlay::update(float deltatime) { bool ModelOverlay::addToScene(Overlay::Pointer overlay, const render::ScenePointer& scene, render::Transaction& transaction) { Volume3DOverlay::addToScene(overlay, scene, transaction); _model->addToScene(scene, transaction); + emit DependencyManager::get()->modelAddedToScene(getID(), NestableType::Overlay, _model); return true; } void ModelOverlay::removeFromScene(Overlay::Pointer overlay, const render::ScenePointer& scene, render::Transaction& transaction) { Volume3DOverlay::removeFromScene(overlay, scene, transaction); _model->removeFromScene(scene, transaction); + emit DependencyManager::get()->modelRemovedFromScene(getID(), NestableType::Overlay, _model); transaction.updateItem(getRenderItemID(), [](Overlay& data) { auto modelOverlay = static_cast(&data); modelOverlay->clearSubRenderItemIDs(); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 066afac8f5..aaf86be4bf 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -221,7 +221,7 @@ void Avatar::updateAvatarEntities() { return; } - if (getID() == QUuid()) { + if (getID() == QUuid() || getID() == AVATAR_SELF_ID) { return; // wait until MyAvatar gets an ID before doing this. } @@ -577,6 +577,7 @@ void Avatar::addToScene(AvatarSharedPointer self, const render::ScenePointer& sc } _mustFadeIn = true; + emit DependencyManager::get()->modelAddedToScene(getSessionUUID(), NestableType::Avatar, _skeletonModel); } void Avatar::fadeIn(render::ScenePointer scene) { @@ -626,6 +627,7 @@ void Avatar::removeFromScene(AvatarSharedPointer self, const render::ScenePointe for (auto& attachmentModel : _attachmentModels) { attachmentModel->removeFromScene(scene, transaction); } + emit DependencyManager::get()->modelRemovedFromScene(getSessionUUID(), NestableType::Avatar, _skeletonModel); } void Avatar::updateRenderItem(render::Transaction& transaction) { @@ -1764,12 +1766,11 @@ float Avatar::getUnscaledEyeHeightFromSkeleton() const { } scriptable::ScriptableModelBase Avatar::getScriptableModel(bool* ok) { - qDebug() << "Avatar::getScriptableModel" ; if (!_skeletonModel || !_skeletonModel->isLoaded()) { return scriptable::ModelProvider::modelUnavailableError(ok); } scriptable::ScriptableModelBase result = _skeletonModel->getScriptableModel(ok); - result.objectID = getSessionUUID(); + result.objectID = getSessionUUID(); result.mixin({ { "avatarID", getSessionUUID().toString() }, { "url", _skeletonModelURL.toString() }, @@ -1787,4 +1788,4 @@ scriptable::ScriptableModelBase Avatar::getScriptableModel(bool* ok) { *ok = true; } return result; -} +} \ No newline at end of file diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 50301a2507..16c50e08ae 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -1,4 +1,3 @@ - // // Avatar.h // interface/src/avatar @@ -276,6 +275,7 @@ public: virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) override; + public slots: // FIXME - these should be migrated to use Pose data instead diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index 74759f4fe4..6cf745d7b4 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -56,6 +56,7 @@ public: const uint64_t& getUpdateTime() const { return _updateTime; } virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) override { return scriptable::ModelProvider::modelUnavailableError(ok); } + protected: virtual bool needsRenderUpdateFromEntity() const final { return needsRenderUpdateFromEntity(_entity); } virtual void onAddToScene(const EntityItemPointer& entity); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 155580d885..8eb48cd1f6 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -950,6 +950,16 @@ QStringList RenderableModelEntityItem::getJointNames() const { return result; } +// FIXME: deprecated; remove >= RC67 +bool RenderableModelEntityItem::getMeshes(MeshProxyList& result) { + auto model = getModel(); + if (!model || !model->isLoaded()) { + return false; + } + BLOCKING_INVOKE_METHOD(model.get(), "getMeshes", Q_RETURN_ARG(MeshProxyList, result)); + return !result.isEmpty(); +} + scriptable::ScriptableModelBase render::entities::ModelEntityRenderer::getScriptableModel(bool* ok) { ModelPointer model; withReadLock([&] { model = _model; }); @@ -998,6 +1008,7 @@ void RenderableModelEntityItem::copyAnimationJointDataToModel() { return; } + bool changed { false }; // relay any inbound joint changes from scripts/animation/network to the model/rig _jointDataLock.withWriteLock([&] { for (int index = 0; index < _localJointData.size(); ++index) { @@ -1005,13 +1016,21 @@ void RenderableModelEntityItem::copyAnimationJointDataToModel() { if (jointData.rotationDirty) { model->setJointRotation(index, true, jointData.joint.rotation, 1.0f); jointData.rotationDirty = false; + changed = true; } if (jointData.translationDirty) { model->setJointTranslation(index, true, jointData.joint.translation, 1.0f); jointData.translationDirty = false; + changed = true; } } }); + + if (changed) { + forEachChild([&](SpatiallyNestablePointer object) { + object->locationChanged(false); + }); + } } using namespace render; @@ -1054,6 +1073,11 @@ void ModelEntityRenderer::removeFromScene(const ScenePointer& scene, Transaction void ModelEntityRenderer::onRemoveFromSceneTyped(const TypedEntityPointer& entity) { entity->setModel({}); + //emit DependencyManager::get()->modelRemovedFromScene(entity->getID(), NestableType::Entity, _model); +} + +void ModelEntityRenderer::onAddToSceneTyped(const TypedEntityPointer& entity) { + //emit DependencyManager::get()->modelAddedToScene(entity->getID(), NestableType::Entity, _model); } @@ -1280,6 +1304,7 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce auto entityRenderer = static_cast(&data); entityRenderer->clearSubRenderItemIDs(); }); + emit DependencyManager::get()->modelRemovedFromScene(entity->getEntityItemID(), NestableType::Entity, _model); } return; } @@ -1290,6 +1315,11 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce connect(model.get(), &Model::setURLFinished, this, [&](bool didVisualGeometryRequestSucceed) { setKey(didVisualGeometryRequestSucceed); emit requestRenderUpdate(); + auto factory = DependencyManager::get().data(); + qDebug() << "leopoly didVisualGeometryRequestSucceed" << didVisualGeometryRequestSucceed << QThread::currentThread() << _model.get(); + if(didVisualGeometryRequestSucceed) { + emit factory->modelAddedToScene(entity->getEntityItemID(), NestableType::Entity, _model); + } }); connect(model.get(), &Model::requestRenderUpdate, this, &ModelEntityRenderer::requestRenderUpdate); connect(entity.get(), &RenderableModelEntityItem::requestCollisionGeometryUpdate, this, &ModelEntityRenderer::flagForCollisionGeometryUpdate); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index ffb83d3609..f72ddad6b5 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -111,6 +111,7 @@ public: virtual int getJointIndex(const QString& name) const override; virtual QStringList getJointNames() const override; + bool getMeshes(MeshProxyList& result) override; // deprecated const void* getCollisionMeshKey() const { return _collisionMeshKey; } signals: @@ -137,6 +138,7 @@ namespace render { namespace entities { class ModelEntityRenderer : public TypedEntityRenderer { using Parent = TypedEntityRenderer; friend class EntityRenderer; + Q_OBJECT public: ModelEntityRenderer(const EntityItemPointer& entity); diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 454fba4f94..931e55f7ef 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -20,6 +20,8 @@ #include #include +#include +#include #include #include #include @@ -97,7 +99,7 @@ const float MARCHING_CUBE_COLLISION_HULL_OFFSET = 0.5; In RenderablePolyVoxEntityItem::render, these flags are checked and changes are propagated along the chain. decompressVolumeData() is called to decompress _voxelData into _volData. recomputeMesh() is called to invoke the - polyVox surface extractor to create _mesh (as well as set Simulation _dirtyFlags). Because Simulation::DIRTY_SHAPE + polyVox surface extractor to create _mesh (as well as set Simulation _flags). Because Simulation::DIRTY_SHAPE is set, isReadyToComputeShape() gets called and _shape is created either from _volData or _shape, depending on the surface style. @@ -1414,6 +1416,39 @@ void RenderablePolyVoxEntityItem::bonkNeighbors() { } } +// deprecated +bool RenderablePolyVoxEntityItem::getMeshes(MeshProxyList& result) { + if (!updateDependents()) { + return false; + } + + bool success = false; + if (_mesh) { + MeshProxy* meshProxy = nullptr; + glm::mat4 transform = voxelToLocalMatrix(); + withReadLock([&] { + gpu::BufferView::Index numVertices = (gpu::BufferView::Index)_mesh->getNumVertices(); + if (!_meshReady) { + // we aren't ready to return a mesh. the caller will have to try again later. + success = false; + } else if (numVertices == 0) { + // we are ready, but there are no triangles in the mesh. + success = true; + } else { + success = true; + // the mesh will be in voxel-space. transform it into object-space + meshProxy = new SimpleMeshProxy( + _mesh->map([=](glm::vec3 position) { return glm::vec3(transform * glm::vec4(position, 1.0f)); }, + [=](glm::vec3 color) { return color; }, + [=](glm::vec3 normal) { return glm::normalize(glm::vec3(transform * glm::vec4(normal, 0.0f))); }, + [&](uint32_t index) { return index; })); + result << meshProxy; + } + }); + } + return success; +} + scriptable::ScriptableModelBase RenderablePolyVoxEntityItem::getScriptableModel(bool * ok) { if (!updateDependents() || !_mesh) { return scriptable::ModelProvider::modelUnavailableError(ok); diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index 733d5b62f5..3fb79110e6 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -113,6 +113,7 @@ public: void setVolDataDirty() { withWriteLock([&] { _volDataDirty = true; _meshReady = false; }); } + bool getMeshes(MeshProxyList& result) override; // deprecated virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) override; private: diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index 5ea1c9edb7..aefcc196fa 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -16,8 +16,8 @@ #include #include -#include -#include +#include "render-utils/simple_vert.h" +#include "render-utils/simple_frag.h" //#define SHAPE_ENTITY_USE_FADE_EFFECT #ifdef SHAPE_ENTITY_USE_FADE_EFFECT @@ -112,8 +112,6 @@ bool ShapeEntityRenderer::isTransparent() const { return Parent::isTransparent(); } - - void ShapeEntityRenderer::doRender(RenderArgs* args) { PerformanceTimer perfTimer("RenderableShapeEntityItem::render"); Q_ASSERT(args->_batch); diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 5c9324fc8a..4c398b8a29 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -57,6 +57,8 @@ using EntityTreeElementExtraEncodeDataPointer = std::shared_ptrgetLocked()) { shouldDelete = false; } else { - _entityTree->deleteEntity(entityID); + // only delete local entities, server entities will round trip through the server filters + if (entity->getClientOnly()) { + _entityTree->deleteEntity(entityID); + } } } }); @@ -1805,6 +1808,30 @@ bool EntityScriptingInterface::AABoxIntersectsCapsule(const glm::vec3& low, cons return aaBox.findCapsulePenetration(start, end, radius, penetration); } +void EntityScriptingInterface::getMeshes(QUuid entityID, QScriptValue callback) { + PROFILE_RANGE(script_entities, __FUNCTION__); + + EntityItemPointer entity = static_cast(_entityTree->findEntityByEntityItemID(entityID)); + if (!entity) { + qCDebug(entities) << "EntityScriptingInterface::getMeshes no entity with ID" << entityID; + QScriptValueList args { callback.engine()->undefinedValue(), false }; + callback.call(QScriptValue(), args); + return; + } + + MeshProxyList result; + bool success = entity->getMeshes(result); + + if (success) { + QScriptValue resultAsScriptValue = meshesToScriptValue(callback.engine(), result); + QScriptValueList args { resultAsScriptValue, true }; + callback.call(QScriptValue(), args); + } else { + QScriptValueList args { callback.engine()->undefinedValue(), false }; + callback.call(QScriptValue(), args); + } +} + glm::mat4 EntityScriptingInterface::getEntityTransform(const QUuid& entityID) { glm::mat4 result; if (_entityTree) { diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index da201f93eb..d1b321dbca 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -37,6 +37,7 @@ #include "BaseScriptEngine.h" class EntityTree; +class MeshProxy; // helper factory to compose standardized, async metadata queries for "magic" Entity properties // like .script and .serverScripts. This is used for automated testing of core scripting features @@ -138,7 +139,7 @@ public slots: Q_INVOKABLE bool canRezTmpCertified(); /**jsdoc - * @function Entities.canWriteAsseets + * @function Entities.canWriteAssets * @return {bool} `true` if the DomainServer will allow this Node/Avatar to write to the asset server */ Q_INVOKABLE bool canWriteAssets(); @@ -400,6 +401,9 @@ public slots: Q_INVOKABLE bool AABoxIntersectsCapsule(const glm::vec3& low, const glm::vec3& dimensions, const glm::vec3& start, const glm::vec3& end, float radius); + // FIXME move to a renderable entity interface + Q_INVOKABLE void getMeshes(QUuid entityID, QScriptValue callback); + /**jsdoc * Returns object to world transform, excluding scale * diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 402afea2cc..837d7fe80e 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -408,7 +408,16 @@ static void createTangents(const FBXMesh& mesh, bool generateFromTexCoords, } } -static void createMeshTangents(FBXMesh& mesh, bool generateFromTexCoords) { +static void _createBlendShapeTangents(FBXMesh& mesh, bool generateFromTexCoords, FBXBlendshape& blendShape); + +void FBXMesh::createBlendShapeTangents(bool generateTangents) { + for (auto& blendShape : blendshapes) { + _createBlendShapeTangents(*this, generateTangents, blendShape); + } +} + +void FBXMesh::createMeshTangents(bool generateFromTexCoords) { + FBXMesh& mesh = *this; // This is the only workaround I've found to trick the compiler into understanding that mesh.tangents isn't // const in the lambda function. auto& tangents = mesh.tangents; @@ -421,7 +430,7 @@ static void createMeshTangents(FBXMesh& mesh, bool generateFromTexCoords) { }); } -static void createBlendShapeTangents(FBXMesh& mesh, bool generateFromTexCoords, FBXBlendshape& blendShape) { +static void _createBlendShapeTangents(FBXMesh& mesh, bool generateFromTexCoords, FBXBlendshape& blendShape) { // Create lookup to get index in blend shape from vertex index in mesh std::vector reverseIndices; reverseIndices.resize(mesh.vertices.size()); @@ -1455,18 +1464,22 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS QSet remainingModels; for (QHash::const_iterator model = models.constBegin(); model != models.constEnd(); model++) { // models with clusters must be parented to the cluster top - foreach (const QString& deformerID, _connectionChildMap.values(model.key())) { - foreach (const QString& clusterID, _connectionChildMap.values(deformerID)) { - if (!clusters.contains(clusterID)) { - continue; + // Unless the model is a root node. + bool isARootNode = !modelIDs.contains(_connectionParentMap.value(model.key())); + if (!isARootNode) { + foreach(const QString& deformerID, _connectionChildMap.values(model.key())) { + foreach(const QString& clusterID, _connectionChildMap.values(deformerID)) { + if (!clusters.contains(clusterID)) { + continue; + } + QString topID = getTopModelID(_connectionParentMap, models, _connectionChildMap.value(clusterID), url); + _connectionChildMap.remove(_connectionParentMap.take(model.key()), model.key()); + _connectionParentMap.insert(model.key(), topID); + goto outerBreak; } - QString topID = getTopModelID(_connectionParentMap, models, _connectionChildMap.value(clusterID), url); - _connectionChildMap.remove(_connectionParentMap.take(model.key()), model.key()); - _connectionParentMap.insert(model.key(), topID); - goto outerBreak; } + outerBreak: ; } - outerBreak: // make sure the parent is in the child map QString parent = _connectionParentMap.value(model.key()); @@ -1714,10 +1727,8 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } } - createMeshTangents(extracted.mesh, generateTangents); - for (auto& blendShape : extracted.mesh.blendshapes) { - createBlendShapeTangents(extracted.mesh, generateTangents, blendShape); - } + extracted.mesh.createMeshTangents(generateTangents); + extracted.mesh.createBlendShapeTangents(generateTangents); // find the clusters with which the mesh is associated QVector clusterIDs; diff --git a/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.cpp b/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.cpp index 68cebe2d78..e4eeee856e 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.cpp @@ -22,7 +22,7 @@ namespace glm { //#define DEBUG_BUFFERVIEW_SCRIPTING //#ifdef DEBUG_BUFFERVIEW_SCRIPTING - #include "DebugNames.h" +#include "DebugNames.h" //#endif namespace { @@ -59,8 +59,8 @@ void setBufferViewElement(const gpu::BufferView& view, quint32 index, const QVar view.edit(index) = glmVecFromVariant(v); } -//FIXME copied from Model.cpp -static void packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, glm::uint32& packedNormal, glm::uint32& packedTangent) { + +void buffer_helpers::packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, glm::uint32& packedNormal, glm::uint32& packedTangent) { auto absNormal = glm::abs(normal); auto absTangent = glm::abs(tangent); normal /= glm::max(1e-6f, glm::max(glm::max(absNormal.x, absNormal.y), absNormal.z)); @@ -152,9 +152,9 @@ bool boundsCheck(const gpu::BufferView& view, quint32 index) { return ( index < view.getNumElements() && index * byteLength < (view._size - 1) * byteLength - ); + ); } - + QVariant buffer_helpers::toVariant(const gpu::BufferView& view, quint32 index, bool asArray, const char* hint) { const auto& element = view._element; const auto vecN = element.getScalarCount(); @@ -189,7 +189,7 @@ QVariant buffer_helpers::toVariant(const gpu::BufferView& view, quint32 index, b return glmVecToVariant(glm::vec3(glm::unpackSnorm3x10_1x2(packedNormal))); } - return getBufferViewElement(view, index, asArray); + return getBufferViewElement(view, index, asArray); } } } else if (BYTES_PER_ELEMENT == 2) { @@ -271,12 +271,12 @@ gpu::BufferView buffer_helpers::fromVector(const QVector& elements, const gpu 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 getVec4;// { static T get(const gpu::BufferView& view, quint32 index, const char *hint); }; +template struct GpuVec4ToGlm;// { static T get(const gpu::BufferView& view, quint32 index, const char *hint); }; template struct getScalar;// { static T get(const gpu::BufferView& view, quint32 index, const char *hint); }; -struct gotter { +struct GpuToGlmAdapter { static float error(const QString& name, const gpu::BufferView& view, quint32 index, const char *hint) { - qDebug() << QString("gotter:: unhandled type=%1(element=%2(%3)) size=%4(per=%5) vec%6 hint=%7 #%8") + qDebug() << QString("GpuToGlmAdapter:: unhandled type=%1(element=%2(%3)) size=%4(per=%5) vec%6 hint=%7 #%8") .arg(name) .arg(DebugNames::stringFrom(view._element.getType())) .arg(view._element.getType()) @@ -288,9 +288,9 @@ struct gotter { Q_ASSERT(false); assert(false); return NAN; - } + } }; -template struct getScalar : gotter { +template struct getScalar : 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); @@ -305,7 +305,7 @@ template struct getScalar : gotter { } }; -template struct getVec2 : gotter { static T get(const gpu::BufferView& view, quint32 index, const char *hint) { switch(view._element.getType()) { +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); @@ -315,10 +315,10 @@ template struct getVec2 : gotter { static T get(const gpu::BufferVi case gpu::FLOAT: return view.get(index); case gpu::HALF: return glm::unpackSnorm2x8(view.get(index)); default: break; - } return T(error("getVec2", view, index, hint)); }}; + } return T(error("GpuVec2ToGlm", view, index, hint)); }}; -template struct getVec3 : gotter { static T get(const gpu::BufferView& view, quint32 index, const char *hint) { switch(view._element.getType()) { +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); @@ -330,12 +330,12 @@ template struct getVec3 : gotter { static T get(const gpu::Buffer case gpu::NUINT8: case gpu::NINT2_10_10_10: if (view._element.getSize() == sizeof(glm::int32)) { - return getVec4::get(view, index, hint); + return GpuVec4ToGlm::get(view, index, hint); } default: break; - } return T(error("getVec3", view, index, hint)); }}; + } return T(error("GpuVec3ToGlm", view, index, hint)); }}; -template struct getVec4 : gotter { static T get(const gpu::BufferView& view, quint32 index, const char *hint) { +template struct GpuVec4ToGlm : GpuToGlmAdapter { static T get(const gpu::BufferView& view, quint32 index, const char *hint) { assert(view._element.getSize() == sizeof(glm::int32)); switch(view._element.getType()) { case gpu::UINT32: return view.get(index); @@ -346,7 +346,7 @@ template struct getVec4 : gotter { static T get(const gpu::BufferVi 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::NUINT8: return glm::unpackUnorm4x8(view.get(index)); case gpu::NUINT2: break; case gpu::NINT32: break; case gpu::NINT16: break; @@ -356,7 +356,7 @@ template struct getVec4 : gotter { static T get(const gpu::BufferVi case gpu::FLOAT: return view.get(index); case gpu::HALF: return glm::unpackSnorm4x8(view.get(index)); case gpu::NINT2_10_10_10: return glm::unpackSnorm3x10_1x2(view.get(index)); - } return T(error("getVec4", view, index, hint)); }}; + } return T(error("GpuVec4ToGlm", view, index, hint)); }}; template @@ -376,16 +376,32 @@ struct getVec { } }; -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::toVector(const gpu::BufferView& view, const char *hint) { + return getVec,int>::__to_vector__(view, hint); +} +template <> QVector buffer_helpers::toVector(const gpu::BufferView& view, const char *hint) { + return getVec,glm::vec2>::__to_vector__(view, hint); +} +template <> QVector buffer_helpers::toVector(const gpu::BufferView& view, const char *hint) { + return getVec,glm::vec3>::__to_vector__(view, hint); +} +template <> QVector buffer_helpers::toVector(const gpu::BufferView& view, const char *hint) { + return getVec,glm::vec4>::__to_vector__(view, hint); +} -template <> int buffer_helpers::convert(const gpu::BufferView& view, quint32 index, const char *hint) { return getVec,int>::__to_scalar__(view, index, hint); } -template <> glm::vec2 buffer_helpers::convert(const gpu::BufferView& view, quint32 index, const char *hint) { return getVec,glm::vec2>::__to_scalar__(view, index, hint); } -template <> glm::vec3 buffer_helpers::convert(const gpu::BufferView& view, quint32 index, const char *hint) { return getVec,glm::vec3>::__to_scalar__(view, index, hint); } -template <> glm::vec4 buffer_helpers::convert(const gpu::BufferView& view, quint32 index, const char *hint) { return getVec,glm::vec4>::__to_scalar__(view, index, hint); } +template <> int buffer_helpers::convert(const gpu::BufferView& view, quint32 index, const char *hint) { + return getVec,int>::__to_scalar__(view, index, hint); +} +template <> glm::vec2 buffer_helpers::convert(const gpu::BufferView& view, quint32 index, const char *hint) { + return getVec,glm::vec2>::__to_scalar__(view, index, hint); +} +template <> glm::vec3 buffer_helpers::convert(const gpu::BufferView& view, quint32 index, const char *hint) { + return getVec,glm::vec3>::__to_scalar__(view, index, hint); +} +template <> glm::vec4 buffer_helpers::convert(const gpu::BufferView& view, quint32 index, const char *hint) { + return getVec,glm::vec4>::__to_scalar__(view, index, hint); +} gpu::BufferView buffer_helpers::clone(const gpu::BufferView& input) { return gpu::BufferView( @@ -410,7 +426,7 @@ gpu::BufferView buffer_helpers::resize(const gpu::BufferView& input, quint32 num graphics::MeshPointer buffer_helpers::cloneMesh(graphics::MeshPointer mesh) { auto clone = std::make_shared(); //[](graphics::Mesh* blah) { - //qCDebug(bufferhelper_logging) << "--- DELETING MESH POINTER" << blah; + //qCDebug(bufferhelper_logging) << "--- DELETING MESH POINTER" << blah; // delete blah; //}); clone->displayName = (QString::fromStdString(mesh->displayName) + "-clone").toStdString(); @@ -437,12 +453,12 @@ graphics::MeshPointer buffer_helpers::cloneMesh(graphics::MeshPointer mesh) { 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); + gpu::BufferView bufferView = buffer_helpers::getBufferView(mesh, slot); const auto& elementType = bufferView._element; //auto vecN = element.getScalarCount(); //auto type = element.getType(); //gpu::Element elementType = getVecNElement(type, vecN); - + gpu::Size elementSize = elementType.getSize(); auto nPositions = mesh->getNumVertices(); auto vsize = nPositions * elementSize; @@ -484,8 +500,8 @@ namespace { typeName = DebugNames::stringFrom(bufferView._element.getType()); #endif qCDebug(bufferhelper_logging, "NOTE:: _expandedAttributeBuffer.%s vec%d %s (before count=%lu bytes=%lu // after count=%lu bytes=%lu)", - hint.toStdString().c_str(), bufferView._element.getScalarCount(), - typeName.toStdString().c_str(), beforeCount, beforeTotal, afterCount, afterTotal); + hint.toStdString().c_str(), bufferView._element.getScalarCount(), + typeName.toStdString().c_str(), beforeCount, beforeTotal, afterCount, afterTotal); } #endif return bufferView; @@ -529,7 +545,7 @@ std::map buffer_helpers::gatherBufferViews(graphics::M 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); + name.toStdString().c_str(), vecN, typeName.toStdString().c_str(), beforeCount, beforeTotal, afterCount, afterTotal); } #endif } @@ -538,7 +554,7 @@ std::map buffer_helpers::gatherBufferViews(graphics::M } -bool buffer_helpers::recalculateNormals(graphics::MeshPointer mesh) { +bool buffer_helpers::recalculateNormals(graphics::MeshPointer mesh) { qCInfo(bufferhelper_logging) << "Recalculating normals" << !!mesh; if (!mesh) { return false; @@ -605,7 +621,7 @@ bool buffer_helpers::recalculateNormals(graphics::MeshPointer mesh) { } break; } - normals.edit(j) = glm::normalize(normal); + buffer_helpers::fromVariant(normals, j, glmVecToVariant(glm::normalize(normal))); } return true; } diff --git a/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h b/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h index d963fd4b22..e0c2e1eee1 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h +++ b/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h @@ -42,9 +42,11 @@ struct buffer_helpers { template static gpu::BufferView fromVector(const QVector& elements, const gpu::Element& elementType); - template static QVector toVector(const gpu::BufferView& view, const char *hint = ""); + template static QVector toVector(const gpu::BufferView& view, const char *hint = ""); template static T convert(const gpu::BufferView& view, quint32 index, const char* hint = ""); - + static gpu::BufferView clone(const gpu::BufferView& input); - static gpu::BufferView resize(const gpu::BufferView& input, quint32 numElements); + static gpu::BufferView resize(const gpu::BufferView& input, quint32 numElements); + + static void packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, glm::uint32& packedNormal, glm::uint32& packedTangent); }; diff --git a/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp b/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp index ab6f2c92be..775aedad52 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp @@ -42,8 +42,6 @@ bool bufferViewElementFromScriptValue(const QScriptValue& v, const gpu::BufferVi return buffer_helpers::fromVariant(view, index, v.toVariant()); } -// - template QScriptValue glmVecToScriptValue(QScriptEngine *js, const T& v, bool asArray) { static const auto len = T().length(); diff --git a/libraries/graphics-scripting/src/graphics-scripting/Forward.h b/libraries/graphics-scripting/src/graphics-scripting/Forward.h index 15973b5852..95fcf51921 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/Forward.h +++ b/libraries/graphics-scripting/src/graphics-scripting/Forward.h @@ -8,10 +8,12 @@ #include #include - +#include namespace graphics { class Mesh; } +class Model; +using ModelPointer = std::shared_ptr; namespace gpu { class BufferView; } @@ -83,7 +85,7 @@ namespace scriptable { // mixin class for Avatar/Entity/Overlay Rendering that expose their in-memory graphics::Meshes class ModelProvider { public: - QVariantMap metadata{ { "providerType", "unknown" } }; + NestableType modelProviderType; static scriptable::ScriptableModelBase modelUnavailableError(bool* ok) { if (ok) { *ok = false; } return {}; } virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) = 0; @@ -91,9 +93,13 @@ namespace scriptable { }; // mixin class for resolving UUIDs into a corresponding ModelProvider - class ModelProviderFactory : public Dependency { + class ModelProviderFactory : public QObject, public Dependency { + Q_OBJECT public: - virtual scriptable::ModelProviderPointer lookupModelProvider(QUuid uuid) = 0; + virtual scriptable::ModelProviderPointer lookupModelProvider(const QUuid& uuid) = 0; + signals: + void modelAddedToScene(const QUuid& objectID, NestableType nestableType, const ModelPointer& sender); + void modelRemovedFromScene(const QUuid& objectID, NestableType nestableType, const ModelPointer& sender); }; using uint32 = quint32; @@ -105,3 +111,5 @@ namespace scriptable { using ScriptableMeshPartPointer = QPointer; bool registerMetaTypes(QScriptEngine* engine); } + +Q_DECLARE_METATYPE(NestableType) diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp new file mode 100644 index 0000000000..71ef57bd82 --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp @@ -0,0 +1,160 @@ +// +// GraphicsScriptingInterface.cpp +// libraries/script-engine/src +// +// Created by Seth Alves on 2017-1-27. +// Copyright 2017 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 "GraphicsScriptingInterface.h" +#include +#include +#include +#include +#include "BaseScriptEngine.h" +#include "ScriptEngineLogging.h" +#include "OBJWriter.h" + +#include +#include + +#include +#include + +#include "BufferViewScripting.h" +#include "ScriptableMesh.h" +#include "GraphicsScriptingUtil.h" + +#include "GraphicsScriptingInterface.moc" + +#include "RegisteredMetaTypes.h" + +GraphicsScriptingInterface::GraphicsScriptingInterface(QObject* parent) : QObject(parent) { + if (auto scriptEngine = qobject_cast(parent)) { + this->registerMetaTypes(scriptEngine); + } +} + +bool GraphicsScriptingInterface::updateMeshes(QUuid uuid, const scriptable::ScriptableMeshPointer mesh, int meshIndex, int partIndex) { + auto model = scriptable::make_qtowned(); + if (mesh) { + model->append(*mesh); + } + return updateMeshes(uuid, model.get()); +} + +bool GraphicsScriptingInterface::updateMeshes(QUuid uuid, const scriptable::ScriptableModelPointer model) { + auto appProvider = DependencyManager::get(); + scriptable::ModelProviderPointer provider = appProvider ? appProvider->lookupModelProvider(uuid) : nullptr; + QString providerType = provider ? SpatiallyNestable::nestableTypeToString(provider->modelProviderType) : QString(); + if (providerType.isEmpty()) { + providerType = "unknown"; + } + bool success = false; + if (provider) { + auto scriptableMeshes = provider->getScriptableModel(&success); + if (success) { + const scriptable::ScriptableModelBasePointer base = model->operator scriptable::ScriptableModelBasePointer(); + if (base) { + success = provider->replaceScriptableModelMeshPart(base, -1, -1); + } + } + } + return success; +} + +QScriptValue GraphicsScriptingInterface::getMeshes(QUuid uuid) { + scriptable::ScriptableModel* meshes{ nullptr }; + bool success = false; + QString error; + + auto appProvider = DependencyManager::get(); + qCDebug(graphics_scripting) << "appProvider" << appProvider.data(); + scriptable::ModelProviderPointer provider = appProvider ? appProvider->lookupModelProvider(uuid) : nullptr; + QString providerType = provider ? SpatiallyNestable::nestableTypeToString(provider->modelProviderType) : QString(); + if (providerType.isEmpty()) { + providerType = "unknown"; + } + if (provider) { + auto scriptableMeshes = provider->getScriptableModel(&success); + if (success) { + meshes = scriptable::make_scriptowned(scriptableMeshes); + if (meshes->objectName().isEmpty()) { + meshes->setObjectName(providerType+"::meshes"); + } + if (meshes->objectID.isNull()) { + meshes->objectID = uuid.toString(); + } + meshes->metadata["provider"] = SpatiallyNestable::nestableTypeToString(provider->modelProviderType); + } + } + if (!success) { + error = QString("failed to get meshes from %1 provider for uuid %2").arg(providerType).arg(uuid.toString()); + } + + QPointer scriptEngine = dynamic_cast(engine()); + QScriptValue result = error.isEmpty() ? scriptEngine->toScriptValue(meshes) : scriptEngine->makeError(error); + if (result.isError()) { + qCWarning(graphics_scripting) << "GraphicsScriptingInterface::getMeshes ERROR" << result.toString(); + if (context()) { + context()->throwValue(error); + } else { + qCWarning(graphics_scripting) << "GraphicsScriptingInterface::getMeshes ERROR" << result.toString(); + } + return QScriptValue::NullValue; + } + return scriptEngine->toScriptValue(meshes); +} + +QString GraphicsScriptingInterface::meshToOBJ(const scriptable::ScriptableModel& _in) { + const auto& in = _in.getConstMeshes(); + if (in.size()) { + QList meshes; + foreach (auto meshProxy, in) { + if (meshProxy) { + meshes.append(getMeshPointer(meshProxy)); + } + } + if (meshes.size()) { + return writeOBJToString(meshes); + } + } + if (context()) { + context()->throwError(QString("null mesh")); + } + return QString(); +} +void GraphicsScriptingInterface::registerMetaTypes(QScriptEngine* engine) { + scriptable::registerMetaTypes(engine); +} + +MeshPointer GraphicsScriptingInterface::getMeshPointer(const scriptable::ScriptableMesh& meshProxy) { + return meshProxy.getMeshPointer(); +} +MeshPointer GraphicsScriptingInterface::getMeshPointer(scriptable::ScriptableMesh& meshProxy) { + return getMeshPointer(&meshProxy); +} +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"; + } + 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"; + } + return result; + } + return mesh; +} diff --git a/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h similarity index 54% rename from libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h rename to libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h index fa7b885014..6afa549a19 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h @@ -1,16 +1,15 @@ // -// ModelScriptingInterface.h -// libraries/script-engine/src +// GraphicsScriptingInterface.h +// libraries/graphics-scripting/src // -// Created by Seth Alves on 2017-1-27. // Copyright 2017 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 // -#ifndef hifi_ModelScriptingInterface_h -#define hifi_ModelScriptingInterface_h +#ifndef hifi_GraphicsScriptingInterface_h +#define hifi_GraphicsScriptingInterface_h #include #include @@ -21,35 +20,27 @@ #include "ScriptableMesh.h" #include -class ModelScriptingInterface : public QObject, public QScriptable, public Dependency { +class GraphicsScriptingInterface : public QObject, public QScriptable, public Dependency { Q_OBJECT public: - ModelScriptingInterface(QObject* parent = nullptr); + GraphicsScriptingInterface(QObject* parent = nullptr); public slots: /**jsdoc * Returns the meshes associated with a UUID (entityID, overlayID, or avatarID) * - * @function ModelScriptingInterface.getMeshes + * @function GraphicsScriptingInterface.getMeshes * @param {EntityID} entityID The ID of the entity whose meshes are to be retrieve */ - void getMeshes(QUuid uuid, QScriptValue callback); + QScriptValue getMeshes(QUuid uuid); bool updateMeshes(QUuid uuid, const scriptable::ScriptableModelPointer model); bool updateMeshes(QUuid uuid, const scriptable::ScriptableMeshPointer mesh, int meshIndex=0, int partIndex=0); QString meshToOBJ(const scriptable::ScriptableModel& in); - QScriptValue appendMeshes(scriptable::ScriptableModel in); - QScriptValue transformMesh(scriptable::ScriptableMeshPointer meshProxy, glm::mat4 transform); - QScriptValue newMesh(const QVector& vertices, - const QVector& normals, - const QVector& faces); - QScriptValue getVertexCount(scriptable::ScriptableMeshPointer meshProxy); - QScriptValue getVertex(scriptable::ScriptableMeshPointer meshProxy, quint32 vertexIndex); - static void registerMetaTypes(QScriptEngine* engine); - + private: scriptable::MeshPointer getMeshPointer(scriptable::ScriptableMeshPointer meshProxy); scriptable::MeshPointer getMeshPointer(scriptable::ScriptableMesh& meshProxy); @@ -57,4 +48,4 @@ private: }; -#endif // hifi_ModelScriptingInterface_h +#endif // hifi_GraphicsScriptingInterface_h diff --git a/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp deleted file mode 100644 index d78f646087..0000000000 --- a/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp +++ /dev/null @@ -1,407 +0,0 @@ -// -// ModelScriptingInterface.cpp -// libraries/script-engine/src -// -// Created by Seth Alves on 2017-1-27. -// Copyright 2017 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 "ModelScriptingInterface.h" -#include -#include -#include -#include -#include "BaseScriptEngine.h" -#include "ScriptEngineLogging.h" -#include "OBJWriter.h" - -#include -#include - -#include -#include - -#include "BufferViewScripting.h" -#include "ScriptableMesh.h" -#include "GraphicsScriptingUtil.h" - -#include "ModelScriptingInterface.moc" - -#include "RegisteredMetaTypes.h" - -ModelScriptingInterface::ModelScriptingInterface(QObject* parent) : QObject(parent) { - if (auto scriptEngine = qobject_cast(parent)) { - this->registerMetaTypes(scriptEngine); - } -} - -bool ModelScriptingInterface::updateMeshes(QUuid uuid, const scriptable::ScriptableMeshPointer mesh, int meshIndex, int partIndex) { - auto model = scriptable::make_qtowned(); - if (mesh) { - model->append(*mesh); - } - return updateMeshes(uuid, model.get()); -} - -bool ModelScriptingInterface::updateMeshes(QUuid uuid, const scriptable::ScriptableModelPointer model) { - auto appProvider = DependencyManager::get(); - //qCDebug(graphics_scripting) << "appProvider" << appProvider.data(); - scriptable::ModelProviderPointer provider = appProvider ? appProvider->lookupModelProvider(uuid) : nullptr; - QString providerType = provider ? provider->metadata.value("providerType").toString() : QString(); - if (providerType.isEmpty()) { - providerType = "unknown"; - } - bool success = false; - if (provider) { - //qCDebug(graphics_scripting) << "fetching meshes from " << providerType << "..."; - auto scriptableMeshes = provider->getScriptableModel(&success); - //qCDebug(graphics_scripting) << "//fetched meshes from " << providerType << "success:" <operator scriptable::ScriptableModelBasePointer(); - //qCDebug(graphics_scripting) << "as base" << base; - if (base) { - //auto meshes = model->getConstMeshes(); - success = provider->replaceScriptableModelMeshPart(base, -1, -1); - - // for (uint32_t m = 0; success && m < meshes.size(); m++) { - // const auto& mesh = meshes.at(m); - // for (int p = 0; success && p < mesh->getNumParts(); p++) { - // qCDebug(graphics_scripting) << "provider->replaceScriptableModelMeshPart" << "meshIndex" << m << "partIndex" << p; - // success = provider->replaceScriptableModelMeshPart(base, m, p); - // //if (!success) { - // qCDebug(graphics_scripting) << "//provider->replaceScriptableModelMeshPart" << "meshIndex" << m << "partIndex" << p << success; - // } - // } - } - } - } - return success; -} - -void ModelScriptingInterface::getMeshes(QUuid uuid, QScriptValue callback) { - auto handler = scriptable::jsBindCallback(callback); - Q_ASSERT(handler.engine() == this->engine()); - QPointer engine = dynamic_cast(handler.engine()); - - scriptable::ScriptableModel* meshes{ nullptr }; - bool success = false; - QString error; - - auto appProvider = DependencyManager::get(); - qCDebug(graphics_scripting) << "appProvider" << appProvider.data(); - scriptable::ModelProviderPointer provider = appProvider ? appProvider->lookupModelProvider(uuid) : nullptr; - QString providerType = provider ? provider->metadata.value("providerType").toString() : QString(); - if (providerType.isEmpty()) { - providerType = "unknown"; - } - if (provider) { - qCDebug(graphics_scripting) << "fetching meshes from " << providerType << "..."; - auto scriptableMeshes = provider->getScriptableModel(&success); - qCDebug(graphics_scripting) << "//fetched meshes from " << providerType << "success:" <(scriptableMeshes); - QString debugString = scriptable::toDebugString(meshes); - QObject::connect(meshes, &QObject::destroyed, this, [=]() { - qCDebug(graphics_scripting) << "///fetched meshes" << debugString; - }); - - if (meshes->objectName().isEmpty()) { - meshes->setObjectName(providerType+"::meshes"); - } - if (meshes->objectID.isNull()) { - meshes->objectID = uuid.toString(); - } - meshes->metadata["provider"] = provider->metadata; - } - } - if (!success) { - error = QString("failed to get meshes from %1 provider for uuid %2").arg(providerType).arg(uuid.toString()); - } - - if (!error.isEmpty()) { - qCWarning(graphics_scripting) << "ModelScriptingInterface::getMeshes ERROR" << error; - callScopedHandlerObject(handler, engine->makeError(error), QScriptValue::NullValue); - } else { - callScopedHandlerObject(handler, QScriptValue::NullValue, engine->toScriptValue(meshes)); - } -} - -QString ModelScriptingInterface::meshToOBJ(const scriptable::ScriptableModel& _in) { - const auto& in = _in.getConstMeshes(); - qCDebug(graphics_scripting) << "meshToOBJ" << in.size(); - if (in.size()) { - QList meshes; - foreach (auto meshProxy, in) { - qCDebug(graphics_scripting) << "meshToOBJ" << meshProxy; - if (meshProxy) { - meshes.append(getMeshPointer(meshProxy)); - } - } - if (meshes.size()) { - return writeOBJToString(meshes); - } - } - context()->throwError(QString("null mesh")); - return QString(); -} - -QScriptValue ModelScriptingInterface::appendMeshes(scriptable::ScriptableModel _in) { - const auto& in = _in.getMeshes(); - - // figure out the size of the resulting mesh - size_t totalVertexCount { 0 }; - size_t totalColorCount { 0 }; - size_t totalNormalCount { 0 }; - size_t totalIndexCount { 0 }; - foreach (auto& meshProxy, in) { - scriptable::MeshPointer mesh = getMeshPointer(meshProxy); - totalVertexCount += mesh->getNumVertices(); - - int attributeTypeColor = gpu::Stream::InputSlot::COLOR; // libraries/gpu/src/gpu/Stream.h - const gpu::BufferView& colorsBufferView = mesh->getAttributeBuffer(attributeTypeColor); - gpu::BufferView::Index numColors = (gpu::BufferView::Index)colorsBufferView.getNumElements(); - totalColorCount += numColors; - - int attributeTypeNormal = gpu::Stream::InputSlot::NORMAL; // libraries/gpu/src/gpu/Stream.h - const gpu::BufferView& normalsBufferView = mesh->getAttributeBuffer(attributeTypeNormal); - gpu::BufferView::Index numNormals = (gpu::BufferView::Index)normalsBufferView.getNumElements(); - totalNormalCount += numNormals; - - totalIndexCount += mesh->getNumIndices(); - } - - // alloc the resulting mesh - gpu::Resource::Size combinedVertexSize = totalVertexCount * sizeof(glm::vec3); - unsigned char* combinedVertexData = new unsigned char[combinedVertexSize]; - unsigned char* combinedVertexDataCursor = combinedVertexData; - - gpu::Resource::Size combinedColorSize = totalColorCount * sizeof(glm::vec3); - unsigned char* combinedColorData = new unsigned char[combinedColorSize]; - unsigned char* combinedColorDataCursor = combinedColorData; - - gpu::Resource::Size combinedNormalSize = totalNormalCount * sizeof(glm::vec3); - unsigned char* combinedNormalData = new unsigned char[combinedNormalSize]; - unsigned char* combinedNormalDataCursor = combinedNormalData; - - gpu::Resource::Size combinedIndexSize = totalIndexCount * sizeof(uint32_t); - unsigned char* combinedIndexData = new unsigned char[combinedIndexSize]; - unsigned char* combinedIndexDataCursor = combinedIndexData; - - uint32_t indexStartOffset { 0 }; - - foreach (const auto& meshProxy, in) { - scriptable::MeshPointer mesh = getMeshPointer(meshProxy); - mesh->forEach( - [&](glm::vec3 position){ - memcpy(combinedVertexDataCursor, &position, sizeof(position)); - combinedVertexDataCursor += sizeof(position); - }, - [&](glm::vec3 color){ - memcpy(combinedColorDataCursor, &color, sizeof(color)); - combinedColorDataCursor += sizeof(color); - }, - [&](glm::vec3 normal){ - memcpy(combinedNormalDataCursor, &normal, sizeof(normal)); - combinedNormalDataCursor += sizeof(normal); - }, - [&](uint32_t index){ - index += indexStartOffset; - memcpy(combinedIndexDataCursor, &index, sizeof(index)); - combinedIndexDataCursor += sizeof(index); - }); - - gpu::BufferView::Index numVertices = (gpu::BufferView::Index)mesh->getNumVertices(); - indexStartOffset += numVertices; - } - - graphics::MeshPointer result(new graphics::Mesh()); - - gpu::Element vertexElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); - gpu::Buffer* combinedVertexBuffer = new gpu::Buffer(combinedVertexSize, combinedVertexData); - gpu::BufferPointer combinedVertexBufferPointer(combinedVertexBuffer); - gpu::BufferView combinedVertexBufferView(combinedVertexBufferPointer, vertexElement); - result->setVertexBuffer(combinedVertexBufferView); - - int attributeTypeColor = gpu::Stream::InputSlot::COLOR; // libraries/gpu/src/gpu/Stream.h - gpu::Element colorElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); - gpu::Buffer* combinedColorsBuffer = new gpu::Buffer(combinedColorSize, combinedColorData); - gpu::BufferPointer combinedColorsBufferPointer(combinedColorsBuffer); - gpu::BufferView combinedColorsBufferView(combinedColorsBufferPointer, colorElement); - result->addAttribute(attributeTypeColor, combinedColorsBufferView); - - int attributeTypeNormal = gpu::Stream::InputSlot::NORMAL; // libraries/gpu/src/gpu/Stream.h - gpu::Element normalElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); - gpu::Buffer* combinedNormalsBuffer = new gpu::Buffer(combinedNormalSize, combinedNormalData); - gpu::BufferPointer combinedNormalsBufferPointer(combinedNormalsBuffer); - gpu::BufferView combinedNormalsBufferView(combinedNormalsBufferPointer, normalElement); - result->addAttribute(attributeTypeNormal, combinedNormalsBufferView); - - gpu::Element indexElement = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::RAW); - gpu::Buffer* combinedIndexesBuffer = new gpu::Buffer(combinedIndexSize, combinedIndexData); - gpu::BufferPointer combinedIndexesBufferPointer(combinedIndexesBuffer); - gpu::BufferView combinedIndexesBufferView(combinedIndexesBufferPointer, indexElement); - result->setIndexBuffer(combinedIndexesBufferView); - - std::vector parts; - parts.emplace_back(graphics::Mesh::Part((graphics::Index)0, // startIndex - (graphics::Index)result->getNumIndices(), // numIndices - (graphics::Index)0, // baseVertex - graphics::Mesh::TRIANGLES)); // topology - result->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(graphics::Mesh::Part), - (gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL)); - - - return engine()->toScriptValue(scriptable::make_scriptowned(result)); -} - -QScriptValue ModelScriptingInterface::transformMesh(scriptable::ScriptableMeshPointer meshProxy, glm::mat4 transform) { - auto mesh = getMeshPointer(meshProxy); - if (!mesh) { - return false; - } - - graphics::MeshPointer result = mesh->map([&](glm::vec3 position){ return glm::vec3(transform * glm::vec4(position, 1.0f)); }, - [&](glm::vec3 color){ return color; }, - [&](glm::vec3 normal){ return glm::vec3(transform * glm::vec4(normal, 0.0f)); }, - [&](uint32_t index){ return index; }); - return engine()->toScriptValue(scriptable::make_scriptowned(result)); -} - -QScriptValue ModelScriptingInterface::getVertexCount(scriptable::ScriptableMeshPointer meshProxy) { - auto mesh = getMeshPointer(meshProxy); - if (!mesh) { - return -1; - } - return (uint32_t)mesh->getNumVertices(); -} - -QScriptValue ModelScriptingInterface::getVertex(scriptable::ScriptableMeshPointer meshProxy, quint32 vertexIndex) { - auto mesh = getMeshPointer(meshProxy); - if (!mesh) { - return QScriptValue(); - } - - const gpu::BufferView& vertexBufferView = mesh->getVertexBuffer(); - auto numVertices = mesh->getNumVertices(); - - if (vertexIndex >= numVertices) { - context()->throwError(QString("invalid index: %1 [0,%2)").arg(vertexIndex).arg(numVertices)); - return QScriptValue::NullValue; - } - - glm::vec3 pos = vertexBufferView.get(vertexIndex); - return engine()->toScriptValue(pos); -} - -QScriptValue ModelScriptingInterface::newMesh(const QVector& vertices, - const QVector& normals, - const QVector& faces) { - graphics::MeshPointer mesh(new graphics::Mesh()); - - // vertices - auto vertexBuffer = std::make_shared(vertices.size() * sizeof(glm::vec3), (gpu::Byte*)vertices.data()); - auto vertexBufferPtr = gpu::BufferPointer(vertexBuffer); - gpu::BufferView vertexBufferView(vertexBufferPtr, 0, vertexBufferPtr->getSize(), - sizeof(glm::vec3), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); - mesh->setVertexBuffer(vertexBufferView); - - if (vertices.size() == normals.size()) { - // normals - auto normalBuffer = std::make_shared(normals.size() * sizeof(glm::vec3), (gpu::Byte*)normals.data()); - auto normalBufferPtr = gpu::BufferPointer(normalBuffer); - gpu::BufferView normalBufferView(normalBufferPtr, 0, normalBufferPtr->getSize(), - sizeof(glm::vec3), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); - mesh->addAttribute(gpu::Stream::NORMAL, normalBufferView); - } else { - qCWarning(graphics_scripting, "ModelScriptingInterface::newMesh normals must be same length as vertices"); - } - - // indices (faces) - int VERTICES_PER_TRIANGLE = 3; - int indexBufferSize = faces.size() * sizeof(uint32_t) * VERTICES_PER_TRIANGLE; - unsigned char* indexData = new unsigned char[indexBufferSize]; - unsigned char* indexDataCursor = indexData; - foreach(const mesh::MeshFace& meshFace, faces) { - for (int i = 0; i < VERTICES_PER_TRIANGLE; i++) { - memcpy(indexDataCursor, &meshFace.vertexIndices[i], sizeof(uint32_t)); - indexDataCursor += sizeof(uint32_t); - } - } - auto indexBuffer = std::make_shared(indexBufferSize, (gpu::Byte*)indexData); - auto indexBufferPtr = gpu::BufferPointer(indexBuffer); - gpu::BufferView indexBufferView(indexBufferPtr, gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::RAW)); - mesh->setIndexBuffer(indexBufferView); - - // parts - std::vector parts; - parts.emplace_back(graphics::Mesh::Part((graphics::Index)0, // startIndex - (graphics::Index)faces.size() * 3, // numIndices - (graphics::Index)0, // baseVertex - graphics::Mesh::TRIANGLES)); // topology - mesh->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(graphics::Mesh::Part), - (gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL)); - - - - return engine()->toScriptValue(scriptable::make_scriptowned(mesh)); -} - -namespace { - - // FIXME: MESHFACES: - // QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const mesh::MeshFace &meshFace) { - // QScriptValue obj = engine->newObject(); - // obj.setProperty("vertices", qVectorIntToScriptValue(engine, meshFace.vertexIndices)); - // return obj; - // } - // void meshFaceFromScriptValue(const QScriptValue &object, mesh::MeshFace& meshFaceResult) { - // qScriptValueToSequence(object.property("vertices"), meshFaceResult.vertexIndices); - // } - // QScriptValue qVectorMeshFaceToScriptValue(QScriptEngine* engine, const QVector& vector) { - // return qScriptValueFromSequence(engine, vector); - // } - // void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector& result) { - // qScriptValueToSequence(array, result); - // } - -} - - -void ModelScriptingInterface::registerMetaTypes(QScriptEngine* engine) { - scriptable::registerMetaTypes(engine); - // FIXME: MESHFACES: remove if MeshFace is not needed anywhere - // qScriptRegisterSequenceMetaType(engine); - // qScriptRegisterMetaType(engine, meshFaceToScriptValue, meshFaceFromScriptValue); - // qScriptRegisterMetaType(engine, qVectorMeshFaceToScriptValue, qVectorMeshFaceFromScriptValue); -} - -MeshPointer ModelScriptingInterface::getMeshPointer(const scriptable::ScriptableMesh& meshProxy) { - return meshProxy.getMeshPointer(); -} -MeshPointer ModelScriptingInterface::getMeshPointer(scriptable::ScriptableMesh& meshProxy) { - return getMeshPointer(&meshProxy); -} -MeshPointer ModelScriptingInterface::getMeshPointer(scriptable::ScriptableMeshPointer meshProxy) { - MeshPointer result; - if (!meshProxy) { - if (context()){ - context()->throwError("expected meshProxy as first parameter"); - } else { - qCDebug(graphics_scripting) << "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"; - } - return result; - } - return mesh; -} diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp index c8f1975249..05ff043820 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp @@ -38,6 +38,7 @@ scriptable::ScriptableModelBase::~ScriptableModelBase() { meshes.clear(); //qCDebug(graphics_scripting) << "//~ScriptableModelBase" << this; } + void scriptable::ScriptableModelBase::append(scriptable::WeakMeshPointer mesh, const QVariantMap& metadata) { //qCDebug(graphics_scripting) << "+ APPEND WeakMeshPointer" << mesh.lock().get(); meshes << ScriptableMeshBase{ provider, this, mesh, metadata }; diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h index d2c50bd768..bdfaac5d7c 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h @@ -16,7 +16,6 @@ namespace scriptable { ScriptableModel(const ScriptableModel& other) : ScriptableModelBase(other) {} ScriptableModel(const ScriptableModelBase& other) : ScriptableModelBase(other) {} ScriptableModel& operator=(const ScriptableModelBase& view) { ScriptableModelBase::operator=(view); return *this; } - //virtual ~ScriptableModel() { qDebug() << "~ScriptableModel" << this; } Q_INVOKABLE scriptable::ScriptableModelPointer cloneModel(const QVariantMap& options = QVariantMap()); // TODO: in future accessors for these could go here diff --git a/libraries/model-networking/src/model-networking/SimpleMeshProxy.cpp b/libraries/model-networking/src/model-networking/SimpleMeshProxy.cpp new file mode 100644 index 0000000000..741478789e --- /dev/null +++ b/libraries/model-networking/src/model-networking/SimpleMeshProxy.cpp @@ -0,0 +1,27 @@ +// +// SimpleMeshProxy.cpp +// libraries/model-networking/src/model-networking/ +// +// Created by Seth Alves on 2017-3-22. +// Copyright 2017 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 "SimpleMeshProxy.h" + +#include + +MeshPointer SimpleMeshProxy::getMeshPointer() const { + return _mesh; +} + +int SimpleMeshProxy::getNumVertices() const { + return (int)_mesh->getNumVertices(); +} + +glm::vec3 SimpleMeshProxy::getPos3(int index) const { + return _mesh->getPos3(index); +} + diff --git a/libraries/model-networking/src/model-networking/SimpleMeshProxy.h b/libraries/model-networking/src/model-networking/SimpleMeshProxy.h new file mode 100644 index 0000000000..24c3fca27e --- /dev/null +++ b/libraries/model-networking/src/model-networking/SimpleMeshProxy.h @@ -0,0 +1,36 @@ +// +// SimpleMeshProxy.h +// libraries/model-networking/src/model-networking/ +// +// Created by Seth Alves on 2017-1-27. +// Copyright 2017 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 +// + +#ifndef hifi_SimpleMeshProxy_h +#define hifi_SimpleMeshProxy_h + +#include +#include +#include + +#include + +class SimpleMeshProxy : public MeshProxy { +public: + SimpleMeshProxy(const MeshPointer& mesh) : _mesh(mesh) { } + + MeshPointer getMeshPointer() const override; + + int getNumVertices() const override; + + glm::vec3 getPos3(int index) const override; + + +protected: + const MeshPointer _mesh; +}; + +#endif // hifi_SimpleMeshProxy_h diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 95985a48cb..3856a333c3 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -1318,7 +1318,6 @@ void GeometryCache::renderUnitQuad(gpu::Batch& batch, const glm::vec4& color, in renderQuad(batch, topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight, color, id); } - void GeometryCache::renderQuad(gpu::Batch& batch, const glm::vec2& minCorner, const glm::vec2& maxCorner, const glm::vec2& texCoordMinCorner, const glm::vec2& texCoordMaxCorner, const glm::vec4& color, int id) { diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 81ff5433f7..281fc89b16 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -31,6 +31,7 @@ namespace { QLoggingCategory wtf{ "tim.Model.cpp" }; } #include #include +#include #include #include @@ -333,34 +334,6 @@ void Model::reset() { } } -#if FBX_PACK_NORMALS -static void packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, glm::uint32& packedNormal, glm::uint32& packedTangent) { - auto absNormal = glm::abs(normal); - auto absTangent = glm::abs(tangent); - normal /= glm::max(1e-6f, glm::max(glm::max(absNormal.x, absNormal.y), absNormal.z)); - tangent /= glm::max(1e-6f, glm::max(glm::max(absTangent.x, absTangent.y), absTangent.z)); - normal = glm::clamp(normal, -1.0f, 1.0f); - tangent = glm::clamp(tangent, -1.0f, 1.0f); - normal *= 511.0f; - tangent *= 511.0f; - normal = glm::round(normal); - tangent = glm::round(tangent); - - glm::detail::i10i10i10i2 normalStruct; - glm::detail::i10i10i10i2 tangentStruct; - normalStruct.data.x = int(normal.x); - normalStruct.data.y = int(normal.y); - normalStruct.data.z = int(normal.z); - normalStruct.data.w = 0; - tangentStruct.data.x = int(tangent.x); - tangentStruct.data.y = int(tangent.y); - tangentStruct.data.z = int(tangent.z); - tangentStruct.data.w = 0; - packedNormal = normalStruct.pack; - packedTangent = tangentStruct.pack; -} -#endif - bool Model::updateGeometry() { bool needFullUpdate = false; @@ -591,23 +564,26 @@ bool Model::replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointe // FIXME: temporary workaround for updating the whole FBXGeometry (to keep findRayIntersection in sync) auto newRenderGeometry = new MyGeometryMappingResource( _url, _renderGeometry, _newModel ? scriptable::make_qtowned(*_newModel) : nullptr - ); - _needsUpdateTextures = true; + ); + //_needsUpdateTextures = true; _visualGeometryRequestFailed = false; - invalidCalculatedMeshBoxes(); + //invalidCalculatedMeshBoxes(); deleteGeometry(); _renderGeometry.reset(newRenderGeometry); - onInvalidate(); - reset(); + //onInvalidate(); + //reset(); _rig.destroyAnimGraph(); - assert(_rig.jointStatesEmpty()); + //assert(_rig.jointStatesEmpty()); updateGeometry(); calculateTriangleSets(); - computeMeshPartLocalBounds(); - _needsReload = false; + //computeMeshPartLocalBounds(); + //_needsReload = false; _needsFixupInScene = true; - invalidCalculatedMeshBoxes(); + //invalidCalculatedMeshBoxes(); setRenderItemsNeedUpdate(); + //_hasCalculatedTextureInfo = false; + //calculateTextureInfo(); + //updateRenderItems(); } return true; } @@ -615,7 +591,6 @@ bool Model::replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointe scriptable::ScriptableModelBase Model::getScriptableModel(bool* ok) { QMutexLocker lock(&_mutex); scriptable::ScriptableModelBase result; - const Geometry::Pointer& renderGeometry = getGeometry(); if (!isLoaded()) { qCDebug(wtf) << "Model::getScriptableModel -- !isLoaded"; @@ -623,107 +598,26 @@ scriptable::ScriptableModelBase Model::getScriptableModel(bool* ok) { } const FBXGeometry& geometry = getFBXGeometry(); - Transform offset; - offset.setScale(_scale); - offset.postTranslate(_offset); - glm::mat4 offsetMat = offset.getMatrix(); - glm::mat4 meshToModelMatrix = glm::scale(_scale) * glm::translate(_offset); - glm::mat4 meshToWorldMatrix = createMatFromQuatAndPos(_rotation, _translation) * meshToModelMatrix; - result.metadata = { - { "url", _url.toString() }, - { "textures", renderGeometry->getTextures() }, - { "offset", vec3toVariant(_offset) }, - { "scale", vec3toVariant(getScale()) }, - { "rotation", quatToVariant(getRotation()) }, - { "translation", vec3toVariant(getTranslation()) }, - { "meshToModel", buffer_helpers::toVariant(meshToModelMatrix) }, - { "meshToWorld", buffer_helpers::toVariant(meshToWorldMatrix) }, - { "geometryOffset", buffer_helpers::toVariant(geometry.offset) }, - { "naturalDimensions", vec3toVariant(getNaturalDimensions()) }, - { "meshExtents", buffer_helpers::toVariant(getMeshExtents()) }, - { "unscaledMeshExtents", buffer_helpers::toVariant(getUnscaledMeshExtents()) }, - { "meshBound", buffer_helpers::toVariant(Extents(getRenderableMeshBound())) }, - { "bindExtents", buffer_helpers::toVariant(getBindExtents()) }, - { "offsetMat", buffer_helpers::toVariant(offsetMat) }, - { "transform", buffer_helpers::toVariant(getTransform().getMatrix()) }, - }; - { - Transform transform; - transform.setScale(getScale()); - transform.setTranslation(getTranslation()); - transform.setRotation(getRotation()); - result.metadata["_transform"] = buffer_helpers::toVariant(transform.getMatrix()); - } - { - glm::vec3 position = _translation; - glm::mat4 rotation = glm::mat4_cast(_rotation); - glm::mat4 translation = glm::translate(position); - glm::mat4 modelToWorldMatrix = translation * rotation; - //glm::mat4 worldToModelMatrix = glm::inverse(modelToWorldMatrix); - result.metadata["_modelToWorld"] = buffer_helpers::toVariant(modelToWorldMatrix); - } - { - glm::mat4 meshToModelMatrix = glm::scale(_scale) * glm::translate(_offset); - glm::mat4 meshToWorldMatrix = createMatFromQuatAndPos(_rotation, _translation) * meshToModelMatrix; - //glm::mat4 worldToMeshMatrix = glm::inverse(meshToWorldMatrix); - result.metadata["_meshToWorld"] = buffer_helpers::toVariant(meshToWorldMatrix); - } - { - Transform transform; - transform.setTranslation(_translation); - transform.setRotation(_rotation); - Transform offset; - offset.setScale(_scale); - offset.postTranslate(_offset); - Transform output; - Transform::mult( output, transform, offset); - result.metadata["_renderTransform"] = buffer_helpers::toVariant(output.getMatrix()); - } - - QVariantList submeshes; int numberOfMeshes = geometry.meshes.size(); for (int i = 0; i < numberOfMeshes; i++) { const FBXMesh& fbxMesh = geometry.meshes.at(i); - auto mesh = fbxMesh._mesh; - if (!mesh) { - continue; + if (auto mesh = fbxMesh._mesh) { + auto name = geometry.getModelNameOfMesh(i); + result.append(std::const_pointer_cast(mesh), { + { "index", i }, + { "name", name }, + { "meshIndex", fbxMesh.meshIndex }, + { "displayName", QString::fromStdString(mesh->displayName) }, + { "modelName", QString::fromStdString(mesh->modelName) }, + { "modelTransform", buffer_helpers::toVariant(fbxMesh.modelTransform) }, + { "transform", buffer_helpers::toVariant(geometry.offset * fbxMesh.modelTransform) }, + { "extents", buffer_helpers::toVariant(fbxMesh.meshExtents) }, + }); } - auto name = geometry.getModelNameOfMesh(i); - qCDebug(wtf) << "Model::getScriptableModel #" << i << QString::fromStdString(mesh->displayName) << name; - const AABox& box = _modelSpaceMeshTriangleSets.value(i).getBounds(); - AABox hardbounds; - auto meshTransform = geometry.offset * fbxMesh.modelTransform; - for (const auto& v : fbxMesh.vertices) { - hardbounds += glm::vec3(meshTransform * glm::vec4(v,1)); - } - QVariantList renderIDs; - for (uint32_t m = 0; m < _modelMeshRenderItemIDs.size(); m++) { - auto meshIndex = _modelMeshRenderItemShapes.size() > m ? _modelMeshRenderItemShapes.at(m).meshIndex : -1; - if (meshIndex == i) { - renderIDs << _modelMeshRenderItemIDs[m]; - break; - } - } - - result.append(std::const_pointer_cast(mesh), { - { "index", i }, - { "name", name }, - { "renderIDs", renderIDs }, - { "meshIndex", fbxMesh.meshIndex }, - { "displayName", QString::fromStdString(mesh->displayName) }, - { "modelName", QString::fromStdString(mesh->modelName) }, - { "modelTransform", buffer_helpers::toVariant(fbxMesh.modelTransform) }, - { "transform", buffer_helpers::toVariant(geometry.offset * fbxMesh.modelTransform) }, - { "extents", buffer_helpers::toVariant(fbxMesh.meshExtents) }, - { "bounds", buffer_helpers::toVariant(Extents(box)) }, - { "hardbounds", buffer_helpers::toVariant(Extents(hardbounds)) }, - }); } if (ok) { *ok = true; } - qCDebug(wtf) << "//Model::getScriptableModel -- #" << result.meshes.size(); - result.metadata["submeshes"] = submeshes; return result; } @@ -1458,7 +1352,7 @@ void Model::setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geo #if FBX_PACK_NORMALS glm::uint32 finalNormal; glm::uint32 finalTangent; - packNormalAndTangent(*normalIt, *tangentIt, finalNormal, finalTangent); + buffer_helpers::packNormalAndTangent(*normalIt, *tangentIt, finalNormal, finalTangent); #else const auto finalNormal = *normalIt; const auto finalTangent = *tangentIt; @@ -1481,7 +1375,7 @@ void Model::setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geo #if FBX_PACK_NORMALS glm::uint32 finalNormal; glm::uint32 finalTangent; - packNormalAndTangent(*normalIt, *tangentIt, finalNormal, finalTangent); + buffer_helpers::packNormalAndTangent(*normalIt, *tangentIt, finalNormal, finalTangent); #else const auto finalNormal = *normalIt; const auto finalTangent = *tangentIt; @@ -1555,7 +1449,7 @@ void Model::createVisibleRenderItemSet() { // all of our mesh vectors must match in size if (meshes.size() != _meshStates.size()) { - qCDebug(renderutils) << "WARNING!!!! Mesh Sizes don't match! We will not segregate mesh groups yet."; + qCDebug(renderutils) << "WARNING!!!! Mesh Sizes don't match! " << meshes.size() << _meshStates.size() << " We will not segregate mesh groups yet."; return; } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 375ee016ca..a0fad7b11f 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -140,7 +140,7 @@ public: /// Returns a reference to the shared collision geometry. const Geometry::Pointer& getCollisionGeometry() const { return _collisionGeometry; } - const QVariantMap getTextures() const { assert(isLoaded()); return _renderGeometry->getTextures(); } + const QVariantMap getTextures() const { assert(isLoaded()); return getGeometry()->getTextures(); } Q_INVOKABLE virtual void setTextures(const QVariantMap& textures); /// Provided as a convenience, will crash if !isLoaded() @@ -314,7 +314,7 @@ public: int getResourceDownloadAttempts() { return _renderWatcher.getResourceDownloadAttempts(); } int getResourceDownloadAttemptsRemaining() { return _renderWatcher.getResourceDownloadAttemptsRemaining(); } - Q_INVOKABLE virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) override; + virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) override; virtual bool replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer model, int meshIndex, int partIndex) override; void scaleToFit(); @@ -407,7 +407,7 @@ protected: int _blendNumber; int _appliedBlendNumber; - QMutex _mutex; + mutable QMutex _mutex{ QMutex::Recursive }; bool _overrideModelTransform { false }; bool _triangleSetsValid { false }; diff --git a/libraries/script-engine/src/ModelScriptingInterface.cpp b/libraries/script-engine/src/ModelScriptingInterface.cpp new file mode 100644 index 0000000000..c693083ebf --- /dev/null +++ b/libraries/script-engine/src/ModelScriptingInterface.cpp @@ -0,0 +1,251 @@ +// +// ModelScriptingInterface.cpp +// libraries/script-engine/src +// +// Created by Seth Alves on 2017-1-27. +// Copyright 2017 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 "ModelScriptingInterface.h" +#include +#include +#include +#include +#include "ScriptEngine.h" +#include "ScriptEngineLogging.h" +#include "OBJWriter.h" + +ModelScriptingInterface::ModelScriptingInterface(QObject* parent) : QObject(parent) { + _modelScriptEngine = qobject_cast(parent); + + qScriptRegisterSequenceMetaType>(_modelScriptEngine); + qScriptRegisterMetaType(_modelScriptEngine, meshFaceToScriptValue, meshFaceFromScriptValue); + qScriptRegisterMetaType(_modelScriptEngine, qVectorMeshFaceToScriptValue, qVectorMeshFaceFromScriptValue); +} + +QString ModelScriptingInterface::meshToOBJ(MeshProxyList in) { + QList meshes; + foreach (const MeshProxy* meshProxy, in) { + meshes.append(meshProxy->getMeshPointer()); + } + + return writeOBJToString(meshes); +} + +QScriptValue ModelScriptingInterface::appendMeshes(MeshProxyList in) { + // figure out the size of the resulting mesh + size_t totalVertexCount { 0 }; + size_t totalColorCount { 0 }; + size_t totalNormalCount { 0 }; + size_t totalIndexCount { 0 }; + foreach (const MeshProxy* meshProxy, in) { + MeshPointer mesh = meshProxy->getMeshPointer(); + totalVertexCount += mesh->getNumVertices(); + + int attributeTypeColor = gpu::Stream::InputSlot::COLOR; // libraries/gpu/src/gpu/Stream.h + const gpu::BufferView& colorsBufferView = mesh->getAttributeBuffer(attributeTypeColor); + gpu::BufferView::Index numColors = (gpu::BufferView::Index)colorsBufferView.getNumElements(); + totalColorCount += numColors; + + int attributeTypeNormal = gpu::Stream::InputSlot::NORMAL; // libraries/gpu/src/gpu/Stream.h + const gpu::BufferView& normalsBufferView = mesh->getAttributeBuffer(attributeTypeNormal); + gpu::BufferView::Index numNormals = (gpu::BufferView::Index)normalsBufferView.getNumElements(); + totalNormalCount += numNormals; + + totalIndexCount += mesh->getNumIndices(); + } + + // alloc the resulting mesh + gpu::Resource::Size combinedVertexSize = totalVertexCount * sizeof(glm::vec3); + std::unique_ptr combinedVertexData{ new unsigned char[combinedVertexSize] }; + unsigned char* combinedVertexDataCursor = combinedVertexData.get(); + + gpu::Resource::Size combinedColorSize = totalColorCount * sizeof(glm::vec3); + std::unique_ptr combinedColorData{ new unsigned char[combinedColorSize] }; + unsigned char* combinedColorDataCursor = combinedColorData.get(); + + gpu::Resource::Size combinedNormalSize = totalNormalCount * sizeof(glm::vec3); + std::unique_ptr combinedNormalData{ new unsigned char[combinedNormalSize] }; + unsigned char* combinedNormalDataCursor = combinedNormalData.get(); + + gpu::Resource::Size combinedIndexSize = totalIndexCount * sizeof(uint32_t); + std::unique_ptr combinedIndexData{ new unsigned char[combinedIndexSize] }; + unsigned char* combinedIndexDataCursor = combinedIndexData.get(); + + uint32_t indexStartOffset { 0 }; + + foreach (const MeshProxy* meshProxy, in) { + MeshPointer mesh = meshProxy->getMeshPointer(); + mesh->forEach( + [&](glm::vec3 position){ + memcpy(combinedVertexDataCursor, &position, sizeof(position)); + combinedVertexDataCursor += sizeof(position); + }, + [&](glm::vec3 color){ + memcpy(combinedColorDataCursor, &color, sizeof(color)); + combinedColorDataCursor += sizeof(color); + }, + [&](glm::vec3 normal){ + memcpy(combinedNormalDataCursor, &normal, sizeof(normal)); + combinedNormalDataCursor += sizeof(normal); + }, + [&](uint32_t index){ + index += indexStartOffset; + memcpy(combinedIndexDataCursor, &index, sizeof(index)); + combinedIndexDataCursor += sizeof(index); + }); + + gpu::BufferView::Index numVertices = (gpu::BufferView::Index)mesh->getNumVertices(); + indexStartOffset += numVertices; + } + + graphics::MeshPointer result(new graphics::Mesh()); + + gpu::Element vertexElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); + gpu::Buffer* combinedVertexBuffer = new gpu::Buffer(combinedVertexSize, combinedVertexData.get()); + gpu::BufferPointer combinedVertexBufferPointer(combinedVertexBuffer); + gpu::BufferView combinedVertexBufferView(combinedVertexBufferPointer, vertexElement); + result->setVertexBuffer(combinedVertexBufferView); + + int attributeTypeColor = gpu::Stream::InputSlot::COLOR; // libraries/gpu/src/gpu/Stream.h + gpu::Element colorElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); + gpu::Buffer* combinedColorsBuffer = new gpu::Buffer(combinedColorSize, combinedColorData.get()); + gpu::BufferPointer combinedColorsBufferPointer(combinedColorsBuffer); + gpu::BufferView combinedColorsBufferView(combinedColorsBufferPointer, colorElement); + result->addAttribute(attributeTypeColor, combinedColorsBufferView); + + int attributeTypeNormal = gpu::Stream::InputSlot::NORMAL; // libraries/gpu/src/gpu/Stream.h + gpu::Element normalElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); + gpu::Buffer* combinedNormalsBuffer = new gpu::Buffer(combinedNormalSize, combinedNormalData.get()); + gpu::BufferPointer combinedNormalsBufferPointer(combinedNormalsBuffer); + gpu::BufferView combinedNormalsBufferView(combinedNormalsBufferPointer, normalElement); + result->addAttribute(attributeTypeNormal, combinedNormalsBufferView); + + gpu::Element indexElement = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::RAW); + gpu::Buffer* combinedIndexesBuffer = new gpu::Buffer(combinedIndexSize, combinedIndexData.get()); + gpu::BufferPointer combinedIndexesBufferPointer(combinedIndexesBuffer); + gpu::BufferView combinedIndexesBufferView(combinedIndexesBufferPointer, indexElement); + result->setIndexBuffer(combinedIndexesBufferView); + + std::vector parts; + parts.emplace_back(graphics::Mesh::Part((graphics::Index)0, // startIndex + (graphics::Index)result->getNumIndices(), // numIndices + (graphics::Index)0, // baseVertex + graphics::Mesh::TRIANGLES)); // topology + result->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(graphics::Mesh::Part), + (gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL)); + + + MeshProxy* resultProxy = new SimpleMeshProxy(result); + return meshToScriptValue(_modelScriptEngine, resultProxy); +} + +QScriptValue ModelScriptingInterface::transformMesh(glm::mat4 transform, MeshProxy* meshProxy) { + if (!meshProxy) { + return QScriptValue(false); + } + MeshPointer mesh = meshProxy->getMeshPointer(); + if (!mesh) { + return QScriptValue(false); + } + + const auto inverseTransposeTransform = glm::inverse(glm::transpose(transform)); + graphics::MeshPointer result = mesh->map([&](glm::vec3 position){ return glm::vec3(transform * glm::vec4(position, 1.0f)); }, + [&](glm::vec3 color){ return color; }, + [&](glm::vec3 normal){ return glm::vec3(inverseTransposeTransform * glm::vec4(normal, 0.0f)); }, + [&](uint32_t index){ return index; }); + MeshProxy* resultProxy = new SimpleMeshProxy(result); + return meshToScriptValue(_modelScriptEngine, resultProxy); +} + +QScriptValue ModelScriptingInterface::getVertexCount(MeshProxy* meshProxy) { + if (!meshProxy) { + return QScriptValue(false); + } + MeshPointer mesh = meshProxy->getMeshPointer(); + if (!mesh) { + return QScriptValue(false); + } + + gpu::BufferView::Index numVertices = (gpu::BufferView::Index)mesh->getNumVertices(); + + return numVertices; +} + +QScriptValue ModelScriptingInterface::getVertex(MeshProxy* meshProxy, int vertexIndex) { + if (!meshProxy) { + return QScriptValue(false); + } + MeshPointer mesh = meshProxy->getMeshPointer(); + if (!mesh) { + return QScriptValue(false); + } + + const gpu::BufferView& vertexBufferView = mesh->getVertexBuffer(); + gpu::BufferView::Index numVertices = (gpu::BufferView::Index)mesh->getNumVertices(); + + if (vertexIndex < 0 || vertexIndex >= numVertices) { + return QScriptValue(false); + } + + glm::vec3 pos = vertexBufferView.get(vertexIndex); + return vec3toScriptValue(_modelScriptEngine, pos); +} + + +QScriptValue ModelScriptingInterface::newMesh(const QVector& vertices, + const QVector& normals, + const QVector& faces) { + graphics::MeshPointer mesh(new graphics::Mesh()); + + // vertices + auto vertexBuffer = std::make_shared(vertices.size() * sizeof(glm::vec3), (gpu::Byte*)vertices.data()); + auto vertexBufferPtr = gpu::BufferPointer(vertexBuffer); + gpu::BufferView vertexBufferView(vertexBufferPtr, 0, vertexBufferPtr->getSize(), + sizeof(glm::vec3), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); + mesh->setVertexBuffer(vertexBufferView); + + if (vertices.size() == normals.size()) { + // normals + auto normalBuffer = std::make_shared(normals.size() * sizeof(glm::vec3), (gpu::Byte*)normals.data()); + auto normalBufferPtr = gpu::BufferPointer(normalBuffer); + gpu::BufferView normalBufferView(normalBufferPtr, 0, normalBufferPtr->getSize(), + sizeof(glm::vec3), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); + mesh->addAttribute(gpu::Stream::NORMAL, normalBufferView); + } else { + qCDebug(scriptengine) << "ModelScriptingInterface::newMesh normals must be same length as vertices"; + } + + // indices (faces) + int VERTICES_PER_TRIANGLE = 3; + int indexBufferSize = faces.size() * sizeof(uint32_t) * VERTICES_PER_TRIANGLE; + unsigned char* indexData = new unsigned char[indexBufferSize]; + unsigned char* indexDataCursor = indexData; + foreach(const MeshFace& meshFace, faces) { + for (int i = 0; i < VERTICES_PER_TRIANGLE; i++) { + memcpy(indexDataCursor, &meshFace.vertexIndices[i], sizeof(uint32_t)); + indexDataCursor += sizeof(uint32_t); + } + } + auto indexBuffer = std::make_shared(indexBufferSize, (gpu::Byte*)indexData); + auto indexBufferPtr = gpu::BufferPointer(indexBuffer); + gpu::BufferView indexBufferView(indexBufferPtr, gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::RAW)); + mesh->setIndexBuffer(indexBufferView); + + // parts + std::vector parts; + parts.emplace_back(graphics::Mesh::Part((graphics::Index)0, // startIndex + (graphics::Index)faces.size() * 3, // numIndices + (graphics::Index)0, // baseVertex + graphics::Mesh::TRIANGLES)); // topology + mesh->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(graphics::Mesh::Part), + (gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL)); + + + + MeshProxy* meshProxy = new SimpleMeshProxy(mesh); + return meshToScriptValue(_modelScriptEngine, meshProxy); +} diff --git a/libraries/script-engine/src/ModelScriptingInterface.h b/libraries/script-engine/src/ModelScriptingInterface.h new file mode 100644 index 0000000000..3c239f006f --- /dev/null +++ b/libraries/script-engine/src/ModelScriptingInterface.h @@ -0,0 +1,39 @@ +// +// ModelScriptingInterface.h +// libraries/script-engine/src +// +// Created by Seth Alves on 2017-1-27. +// Copyright 2017 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 +// + +#ifndef hifi_ModelScriptingInterface_h +#define hifi_ModelScriptingInterface_h + +#include + +#include +class QScriptEngine; + +class ModelScriptingInterface : public QObject { + Q_OBJECT + +public: + ModelScriptingInterface(QObject* parent); + + Q_INVOKABLE QString meshToOBJ(MeshProxyList in); + Q_INVOKABLE QScriptValue appendMeshes(MeshProxyList in); + Q_INVOKABLE QScriptValue transformMesh(glm::mat4 transform, MeshProxy* meshProxy); + Q_INVOKABLE QScriptValue newMesh(const QVector& vertices, + const QVector& normals, + const QVector& faces); + Q_INVOKABLE QScriptValue getVertexCount(MeshProxy* meshProxy); + Q_INVOKABLE QScriptValue getVertex(MeshProxy* meshProxy, int vertexIndex); + +private: + QScriptEngine* _modelScriptEngine { nullptr }; +}; + +#endif // hifi_ModelScriptingInterface_h diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index ffb1b7bc74..c79ffffec7 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -73,6 +73,8 @@ #include "WebSocketClass.h" #include "RecordingScriptingInterface.h" #include "ScriptEngines.h" +#include "ModelScriptingInterface.h" + #include @@ -709,6 +711,10 @@ void ScriptEngine::init() { registerGlobalObject("DebugDraw", &DebugDraw::getInstance()); + registerGlobalObject("Model", new ModelScriptingInterface(this)); + qScriptRegisterMetaType(this, meshToScriptValue, meshFromScriptValue); + qScriptRegisterMetaType(this, meshesToScriptValue, meshesFromScriptValue); + registerGlobalObject("UserActivityLogger", DependencyManager::get().data()); } diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index 9ad5c27072..7b455beae5 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -855,3 +855,68 @@ QScriptValue animationDetailsToScriptValue(QScriptEngine* engine, const Animatio void animationDetailsFromScriptValue(const QScriptValue& object, AnimationDetails& details) { // nothing for now... } + +QScriptValue meshToScriptValue(QScriptEngine* engine, MeshProxy* const &in) { + return engine->newQObject(in, QScriptEngine::QtOwnership, + QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects); +} + +void meshFromScriptValue(const QScriptValue& value, MeshProxy* &out) { + out = qobject_cast(value.toQObject()); +} + +QScriptValue meshesToScriptValue(QScriptEngine* engine, const MeshProxyList &in) { + // QScriptValueList result; + QScriptValue result = engine->newArray(); + int i = 0; + foreach(MeshProxy* const meshProxy, in) { + result.setProperty(i++, meshToScriptValue(engine, meshProxy)); + } + return result; +} + +void meshesFromScriptValue(const QScriptValue& value, MeshProxyList &out) { + QScriptValueIterator itr(value); + + qDebug() << "in meshesFromScriptValue, value.length =" << value.property("length").toInt32(); + + while (itr.hasNext()) { + itr.next(); + MeshProxy* meshProxy = qscriptvalue_cast(itr.value()); + if (meshProxy) { + out.append(meshProxy); + } else { + qDebug() << "null meshProxy"; + } + } +} + + +QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const MeshFace &meshFace) { + QScriptValue obj = engine->newObject(); + obj.setProperty("vertices", qVectorIntToScriptValue(engine, meshFace.vertexIndices)); + return obj; +} + +void meshFaceFromScriptValue(const QScriptValue &object, MeshFace& meshFaceResult) { + qVectorIntFromScriptValue(object.property("vertices"), meshFaceResult.vertexIndices); +} + +QScriptValue qVectorMeshFaceToScriptValue(QScriptEngine* engine, const QVector& vector) { + QScriptValue array = engine->newArray(); + for (int i = 0; i < vector.size(); i++) { + array.setProperty(i, meshFaceToScriptValue(engine, vector.at(i))); + } + return array; +} + +void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector& result) { + int length = array.property("length").toInteger(); + result.clear(); + + for (int i = 0; i < length; i++) { + MeshFace meshFace = MeshFace(); + meshFaceFromScriptValue(array.property(i), meshFace); + result << meshFace; + } +} diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index e8390c3a86..25b2cec331 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -315,9 +315,51 @@ Q_DECLARE_METATYPE(AnimationDetails); QScriptValue animationDetailsToScriptValue(QScriptEngine* engine, const AnimationDetails& event); void animationDetailsFromScriptValue(const QScriptValue& object, AnimationDetails& event); +namespace graphics { + class Mesh; +} + +using MeshPointer = std::shared_ptr; +class MeshProxy : public QObject { + Q_OBJECT +public: + virtual MeshPointer getMeshPointer() const = 0; + Q_INVOKABLE virtual int getNumVertices() const = 0; + Q_INVOKABLE virtual glm::vec3 getPos3(int index) const = 0; +}; + +Q_DECLARE_METATYPE(MeshProxy*); + +class MeshProxyList : public QList {}; // typedef and using fight with the Qt macros/templates, do this instead +Q_DECLARE_METATYPE(MeshProxyList); + + +QScriptValue meshToScriptValue(QScriptEngine* engine, MeshProxy* const &in); +void meshFromScriptValue(const QScriptValue& value, MeshProxy* &out); + +QScriptValue meshesToScriptValue(QScriptEngine* engine, const MeshProxyList &in); +void meshesFromScriptValue(const QScriptValue& value, MeshProxyList &out); + +class MeshFace { + +public: + MeshFace() {} + ~MeshFace() {} + + QVector vertexIndices; + // TODO -- material... +}; + +Q_DECLARE_METATYPE(MeshFace) +Q_DECLARE_METATYPE(QVector) + +QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const MeshFace &meshFace); +void meshFaceFromScriptValue(const QScriptValue &object, MeshFace& meshFaceResult); +QScriptValue qVectorMeshFaceToScriptValue(QScriptEngine* engine, const QVector& vector); +void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector& result); #endif // hifi_RegisteredMetaTypes_h diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index 090ca4c266..5d4793ba4e 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -207,6 +207,10 @@ public: void dump(const QString& prefix = "") const; + virtual void locationChanged(bool tellPhysics = true); // called when a this object's location has changed + virtual void dimensionsChanged() { _queryAACubeSet = false; } // called when a this object's dimensions have changed + virtual void parentDeleted() { } // called on children of a deleted parent + protected: const NestableType _nestableType; // EntityItem or an AvatarData QUuid _id; @@ -218,10 +222,6 @@ protected: mutable ReadWriteLockable _childrenLock; mutable QHash _children; - virtual void locationChanged(bool tellPhysics = true); // called when a this object's location has changed - virtual void dimensionsChanged() { _queryAACubeSet = false; } // called when a this object's dimensions have changed - virtual void parentDeleted() { } // called on children of a deleted parent - // _queryAACube is used to decide where something lives in the octree mutable AACube _queryAACube; mutable bool _queryAACubeSet { false }; From caab532d80babef35e2075fe3fc6fb23fe3cf6e4 Mon Sep 17 00:00:00 2001 From: humbletim Date: Thu, 22 Feb 2018 06:00:03 -0500 Subject: [PATCH 22/49] remove dupe line --- interface/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 80e47a220f..3ed5445493 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -204,7 +204,6 @@ endif() # link required hifi libraries link_hifi_libraries( - shared octree ktx gpu gl procedural graphics graphics-scripting render shared task octree ktx gpu gl procedural graphics graphics-scripting render pointers recording fbx networking model-networking entities avatars trackers From e4a2a589a51ba243568f9553966870b150c28a85 Mon Sep 17 00:00:00 2001 From: humbletim Date: Thu, 22 Feb 2018 07:56:09 -0500 Subject: [PATCH 23/49] CR fedback and cleanup --- interface/src/Application.cpp | 9 +- interface/src/ui/overlays/ModelOverlay.cpp | 1 + .../src/RenderableModelEntityItem.cpp | 6 - libraries/fbx/src/FBX.h | 3 + libraries/fbx/src/OBJWriter.cpp | 21 +-- libraries/graphics-scripting/CMakeLists.txt | 1 - .../BufferViewScripting.cpp | 4 +- .../GraphicsScriptingInterface.cpp | 29 ++-- .../GraphicsScriptingUtil.h | 52 ++----- .../src/graphics-scripting/ScriptableMesh.cpp | 39 ++--- .../src/graphics-scripting/ScriptableMesh.h | 26 ++-- .../src/graphics}/BufferViewHelpers.cpp | 145 ++++++++++-------- .../src/graphics}/BufferViewHelpers.h | 6 +- libraries/render-utils/src/Model.cpp | 22 +-- .../src/Model_temporary_hack.cpp.h | 16 +- 15 files changed, 159 insertions(+), 221 deletions(-) rename libraries/{graphics-scripting/src/graphics-scripting => graphics/src/graphics}/BufferViewHelpers.cpp (87%) rename libraries/{graphics-scripting/src/graphics-scripting => graphics/src/graphics}/BufferViewHelpers.h (91%) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 0a0c6e15d1..1be22d5087 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -607,7 +607,7 @@ void messageHandler(QtMsgType type, const QMessageLogContext& context, const QSt class ApplicationMeshProvider : public scriptable::ModelProviderFactory { public: - virtual scriptable::ModelProviderPointer lookupModelProvider(QUuid uuid) { + virtual scriptable::ModelProviderPointer lookupModelProvider(const QUuid& uuid) { QString error; scriptable::ModelProviderPointer provider; @@ -631,7 +631,7 @@ public: if (auto entity = entityTree->findEntityByID(entityID)) { if (auto renderer = entityTreeRenderer->renderableForEntityId(entityID)) { provider = std::dynamic_pointer_cast(renderer); - provider->metadata["providerType"] = "entity"; + provider->modelProviderType = NestableType::Entity; } else { qCWarning(interfaceapp) << "no renderer for entity ID" << entityID.toString(); } @@ -645,7 +645,7 @@ public: if (auto overlay = overlays.getOverlay(overlayID)) { if (auto base3d = std::dynamic_pointer_cast(overlay)) { provider = std::dynamic_pointer_cast(base3d); - provider->metadata["providerType"] = "overlay"; + provider->modelProviderType = NestableType::Overlay; } else { qCWarning(interfaceapp) << "no renderer for overlay ID" << overlayID.toString(); } @@ -659,7 +659,7 @@ public: if (auto avatar = avatarManager->getAvatarBySessionID(sessionUUID)) { if (avatar->getSessionUUID() == sessionUUID) { provider = std::dynamic_pointer_cast(avatar); - provider->metadata["providerType"] = "avatar"; + provider->modelProviderType = NestableType::Avatar; } } return provider; @@ -811,6 +811,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(true); + DependencyManager::set(); DependencyManager::registerInheritance(); DependencyManager::set(); DependencyManager::set(); diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index e610fa5c2e..b6b79fd8c9 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -119,6 +119,7 @@ bool ModelOverlay::addToScene(Overlay::Pointer overlay, const render::ScenePoint void ModelOverlay::removeFromScene(Overlay::Pointer overlay, const render::ScenePointer& scene, render::Transaction& transaction) { Volume3DOverlay::removeFromScene(overlay, scene, transaction); _model->removeFromScene(scene, transaction); + emit DependencyManager::get()->modelRemovedFromScene(getID(), NestableType::Overlay, _model); transaction.updateItem(getRenderItemID(), [](Overlay& data) { auto modelOverlay = static_cast(&data); modelOverlay->clearSubRenderItemIDs(); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 1c4a7f1055..c93bcb2055 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1073,14 +1073,8 @@ void ModelEntityRenderer::removeFromScene(const ScenePointer& scene, Transaction void ModelEntityRenderer::onRemoveFromSceneTyped(const TypedEntityPointer& entity) { entity->setModel({}); - //emit DependencyManager::get()->modelRemovedFromScene(entity->getID(), NestableType::Entity, _model); } -void ModelEntityRenderer::onAddToSceneTyped(const TypedEntityPointer& entity) { - //emit DependencyManager::get()->modelAddedToScene(entity->getID(), NestableType::Entity, _model); -} - - void ModelEntityRenderer::animate(const TypedEntityPointer& entity) { if (!_animation || !_animation->isLoaded()) { return; diff --git a/libraries/fbx/src/FBX.h b/libraries/fbx/src/FBX.h index 224d19fe96..a609d85fc8 100644 --- a/libraries/fbx/src/FBX.h +++ b/libraries/fbx/src/FBX.h @@ -242,6 +242,9 @@ public: graphics::MeshPointer _mesh; bool wasCompressed { false }; + + void createMeshTangents(bool generateFromTexCoords); + void createBlendShapeTangents(bool generateTangents); }; class ExtractedMesh { diff --git a/libraries/fbx/src/OBJWriter.cpp b/libraries/fbx/src/OBJWriter.cpp index 621852f591..a53a3c7c5d 100644 --- a/libraries/fbx/src/OBJWriter.cpp +++ b/libraries/fbx/src/OBJWriter.cpp @@ -9,15 +9,14 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "OBJWriter.h" + #include #include -#include "graphics/Geometry.h" -#include "OBJWriter.h" +#include +#include #include "ModelFormatLogging.h" -// FIXME: should this live in shared? (it depends on gpu/) -#include <../graphics-scripting/src/graphics-scripting/BufferViewHelpers.h> - static QString formatFloat(double n) { // limit precision to 6, but don't output trailing zeros. QString s = QString::number(n, 'f', 6); @@ -71,8 +70,10 @@ bool writeOBJToTextStream(QTextStream& out, QList meshes) { out << formatFloat(v[1]) << " "; out << formatFloat(v[2]); if (colorIndex < numColors) { - glm::vec3 color = glmVecFromVariant(buffer_helpers::toVariant(colorsBufferView, colorIndex)); - //glm::vec3 color = colorsBufferView.get(colorIndex); + glm::vec3 color = buffer_helpers::convert(colorsBufferView, colorIndex); + // TODO: still verifying that the above decodes properly; previous variations were: + // glm::vec3 color = buffer_helpers::glmVecFromVariant(buffer_helpers::toVariant(colorsBufferView, colorIndex)); + // glm::vec3 color = colorsBufferView.get(colorIndex); out << " " << formatFloat(color[0]); out << " " << formatFloat(color[1]); out << " " << formatFloat(color[2]); @@ -95,8 +96,10 @@ bool writeOBJToTextStream(QTextStream& out, QList meshes) { const gpu::BufferView& normalsBufferView = mesh->getAttributeBuffer(gpu::Stream::InputSlot::NORMAL); gpu::BufferView::Index numNormals = (gpu::BufferView::Index)normalsBufferView.getNumElements(); for (gpu::BufferView::Index i = 0; i < numNormals; i++) { - glm::vec3 normal = glmVecFromVariant(buffer_helpers::toVariant(normalsBufferView, i)); - //glm::vec3 normal = normalsBufferView.get(i); + glm::vec3 normal = buffer_helpers::convert(normalsBufferView, i); + // TODO: still verifying that the above decodes properly; previous variations were: + // glm::vec3 normal = buffer_helpers::glmVecFromVariant(buffer_helpers::toVariant(normalsBufferView, i)); + // glm::vec3 normal = normalsBufferView.get(i); out << "vn "; out << formatFloat(normal[0]) << " "; out << formatFloat(normal[1]) << " "; diff --git a/libraries/graphics-scripting/CMakeLists.txt b/libraries/graphics-scripting/CMakeLists.txt index e7fa3de155..ad8055b647 100644 --- a/libraries/graphics-scripting/CMakeLists.txt +++ b/libraries/graphics-scripting/CMakeLists.txt @@ -2,4 +2,3 @@ set(TARGET_NAME graphics-scripting) setup_hifi_library() link_hifi_libraries(shared networking graphics fbx model-networking script-engine) include_hifi_library_headers(gpu) -include_hifi_library_headers(graphics-scripting) diff --git a/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp b/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp index 775aedad52..31cf5ff8f3 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp @@ -9,10 +9,10 @@ #include #include -#include +#include #ifdef DEBUG_BUFFERVIEW_SCRIPTING - #include + #include "DebugNames.h" #endif namespace { diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp index 71ef57bd82..58fcd7a064 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp @@ -1,8 +1,7 @@ // // GraphicsScriptingInterface.cpp -// libraries/script-engine/src +// libraries/graphics-scripting/src // -// Created by Seth Alves on 2017-1-27. // Copyright 2017 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. @@ -10,28 +9,24 @@ // #include "GraphicsScriptingInterface.h" -#include -#include -#include -#include #include "BaseScriptEngine.h" -#include "ScriptEngineLogging.h" +#include "BufferViewScripting.h" +#include "DebugNames.h" +#include "GraphicsScriptingUtil.h" #include "OBJWriter.h" - +#include "RegisteredMetaTypes.h" +#include "ScriptEngineLogging.h" +#include "ScriptableMesh.h" #include +#include +#include +#include +#include +#include #include -#include -#include - -#include "BufferViewScripting.h" -#include "ScriptableMesh.h" -#include "GraphicsScriptingUtil.h" - #include "GraphicsScriptingInterface.moc" -#include "RegisteredMetaTypes.h" - GraphicsScriptingInterface::GraphicsScriptingInterface(QObject* parent) : QObject(parent) { if (auto scriptEngine = qobject_cast(parent)) { this->registerMetaTypes(scriptEngine); diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h index cfa510f87f..594e09bb32 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h @@ -12,12 +12,15 @@ 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) { + template + T this_qobject_cast(QScriptEngine* engine) { auto context = engine ? engine->currentContext() : nullptr; return qscriptvalue_cast(context ? context->thisObject() : QScriptValue::NullValue); } + // JS => QPointer - template QPointer qpointer_qobject_cast(const QScriptValue& value) { + 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(); @@ -41,54 +44,21 @@ namespace scriptable { } // C++ > QtOwned instance - template std::shared_ptr make_qtowned(Rest... rest) { + template + std::shared_ptr make_qtowned(Rest... rest) { T* tmp = new T(rest...); -#ifdef SCRIPTABLE_MESH_DEBUG - qCInfo(graphics_scripting) << "scriptable::make_qtowned" << toDebugString(tmp); -#endif - QString debug = toDebugString(tmp); if (tmp) { tmp->metadata["__ownership__"] = QScriptEngine::QtOwnership; -#ifdef SCRIPTABLE_MESH_DEBUG - QObject::connect(tmp, &QObject::destroyed, [=]() { qCInfo(graphics_scripting) << "-------- ~scriptable::make_qtowned" << debug; }); -#endif - auto ptr = std::shared_ptr(tmp, [debug](T* tmp) { - //qDebug() << "~std::shared_ptr" << debug; - delete tmp; - }); - return ptr; - } else { - return std::shared_ptr(tmp); } + return std::shared_ptr(tmp); } + // C++ > ScriptOwned JS instance - template QPointer make_scriptowned(Rest... rest) { + template + QPointer make_scriptowned(Rest... rest) { T* tmp = new T(rest...); -#ifdef SCRIPTABLE_MESH_DEBUG - qCInfo(graphics_scripting) << "scriptable::make_scriptowned" << toDebugString(tmp); -#endif if (tmp) { tmp->metadata["__ownership__"] = QScriptEngine::ScriptOwnership; - //auto blah = (DeleterFunction)[](void* delme) { }; - return add_scriptowned_destructor(tmp); - } else { - return QPointer(tmp); - } - } - // C++ > ScriptOwned JS instance - template QPointer add_scriptowned_destructor(T* tmp) { - QString debug = toDebugString(tmp); - if (tmp) { -#ifdef SCRIPTABLE_MESH_DEBUG - QObject::connect(tmp, &QObject::destroyed, [=]() { - qCInfo(graphics_scripting) << "-------- ~scriptable::make_scriptowned" << debug;// << !!customDeleter; - //if (customDeleter) { - // customDeleter(tmp); - //} - }); -#endif - } else { - qCInfo(graphics_scripting) << "add_scriptowned_destructor -- not connecting to null value" << debug; } return QPointer(tmp); } diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp index c662371c89..28e57692f6 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp @@ -1,41 +1,26 @@ // -// SimpleMeshProxy.cpp -// libraries/model-networking/src/model-networking/ -// -// Created by Seth Alves on 2017-3-22. -// Copyright 2017 High Fidelity, Inc. +// 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 "ScriptableMesh.h" -#include -#include -#include -#include -#include -#include -#include - -#include "ScriptableMesh.moc" - -#include +#include "BufferViewScripting.h" +#include "DebugNames.h" +#include "GraphicsScriptingUtil.h" +#include "OBJWriter.h" #include #include +#include +#include +#include +#include +#include +#include -#include "OBJWriter.h" - -// #define SCRIPTABLE_MESH_DEBUG - -namespace scriptable { - // QScriptValue jsBindCallback(QScriptValue callback); - // template QPointer qpointer_qobject_cast(const QScriptValue& value); - // template T this_qobject_cast(QScriptEngine* engine); - // template QPointer make_scriptowned(Rest... rest); -} +#include "ScriptableMesh.moc" scriptable::ScriptableMeshPart::ScriptableMeshPart(scriptable::ScriptableMeshPointer parentMesh, int partIndex) : parentMesh(parentMesh), partIndex(partIndex) { diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h index ba0efa007d..2b9399d6ef 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h @@ -1,26 +1,22 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include +#include "ScriptableModel.h" +#include +#include #include -//#include -#include -#include - -#include +#include +#include +#include +#include +#include +#include +#include #include +#include namespace scriptable { - - QScriptValue jsBindCallback(QScriptValue callback); class ScriptableMesh : public ScriptableMeshBase, QScriptable { Q_OBJECT public: diff --git a/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.cpp b/libraries/graphics/src/graphics/BufferViewHelpers.cpp similarity index 87% rename from libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.cpp rename to libraries/graphics/src/graphics/BufferViewHelpers.cpp index e4eeee856e..ea511d82f4 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.cpp +++ b/libraries/graphics/src/graphics/BufferViewHelpers.cpp @@ -1,4 +1,11 @@ -#include "./graphics-scripting/BufferViewHelpers.h" +// +// 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 "BufferViewHelpers.h" #include #include @@ -7,7 +14,7 @@ #include #include -#include +#include "Geometry.h" #include #include @@ -15,25 +22,26 @@ #include #include #include + namespace glm { using hvec2 = glm::tvec2; using hvec4 = glm::tvec4; } -//#define DEBUG_BUFFERVIEW_SCRIPTING -//#ifdef DEBUG_BUFFERVIEW_SCRIPTING -#include "DebugNames.h" -//#endif +#ifdef DEBUG_BUFFERVIEW_SCRIPTING +#include "../../graphics-scripting/src/graphics-scripting/DebugNames.h" +#endif namespace { - QLoggingCategory bufferhelper_logging{"hifi.bufferview"}; - const std::array XYZW = {{ "x", "y", "z", "w" }}; - const std::array ZERO123 = {{ "0", "1", "2", "3" }}; + QLoggingCategory bufferhelper_logging{ "hifi.bufferview" }; + const std::array XYZW = { { "x", "y", "z", "w" } }; + const std::array ZERO123 = { { "0", "1", "2", "3" } }; } gpu::BufferView buffer_helpers::getBufferView(graphics::MeshPointer mesh, gpu::Stream::Slot slot) { return slot == gpu::Stream::POSITION ? mesh->getVertexBuffer() : mesh->getAttributeBuffer(slot); } + QMap buffer_helpers::ATTRIBUTES{ {"position", gpu::Stream::POSITION }, {"normal", gpu::Stream::NORMAL }, @@ -49,16 +57,23 @@ QMap buffer_helpers::ATTRIBUTES{ }; -template -QVariant getBufferViewElement(const gpu::BufferView& view, quint32 index, bool asArray = false) { - return glmVecToVariant(view.get(index), asArray); -} +namespace { + bool boundsCheck(const gpu::BufferView& view, quint32 index) { + const auto byteLength = view._element.getSize(); + return ( + index < view.getNumElements() && + index * byteLength < (view._size - 1) * byteLength + ); + } -template -void setBufferViewElement(const gpu::BufferView& view, quint32 index, const QVariant& v) { - view.edit(index) = glmVecFromVariant(v); -} + 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) { auto absNormal = glm::abs(normal); @@ -147,14 +162,6 @@ bool buffer_helpers::fromVariant(const gpu::BufferView& view, quint32 index, con return false; } -bool boundsCheck(const gpu::BufferView& view, quint32 index) { - const auto byteLength = view._element.getSize(); - return ( - index < view.getNumElements() && - index * byteLength < (view._size - 1) * byteLength - ); -} - QVariant buffer_helpers::toVariant(const gpu::BufferView& view, quint32 index, bool asArray, const char* hint) { const auto& element = view._element; const auto vecN = element.getScalarCount(); @@ -167,14 +174,17 @@ QVariant buffer_helpers::toVariant(const gpu::BufferView& view, quint32 index, b auto byteOffset = index * vecN * BYTES_PER_ELEMENT; auto maxByteOffset = (view._size - 1) * vecN * BYTES_PER_ELEMENT; if (byteOffset > maxByteOffset) { - qDebug() << "bufferViewElementToVariant -- byteOffset out of range " << byteOffset << " < " << maxByteOffset << DebugNames::stringFrom(dataType); - qDebug() << "bufferViewElementToVariant -- index: " << index << "numElements" << view.getNumElements(); - qDebug() << "bufferViewElementToVariant -- vecN: " << vecN << "byteLength" << byteLength << "BYTES_PER_ELEMENT" << BYTES_PER_ELEMENT; +#ifdef DEBUG_BUFFERVIEW_SCRIPTING + qDebug() << "toVariant -- " << DebugNames::stringFrom(dataType) +#endif + 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); } #ifdef DEBUG_BUFFERVIEW_SCRIPTING - qCDebug(bufferhelper_logging) << "bufferViewElementToVariant" << index << DebugNames::stringFrom(dataType) << BYTES_PER_ELEMENT << vecN; + qCDebug(bufferhelper_logging) << "toVariant -- " << index << DebugNames::stringFrom(dataType) << BYTES_PER_ELEMENT << vecN; #endif if (BYTES_PER_ELEMENT == 1) { switch(vecN) { @@ -223,7 +233,7 @@ QVariant buffer_helpers::toVariant(const gpu::BufferView& view, quint32 index, b } template -QVariant glmVecToVariant(const T& v, bool asArray /*= false*/) { +QVariant buffer_helpers::glmVecToVariant(const T& v, bool asArray /*= false*/) { static const auto len = T().length(); if (asArray) { QVariantList list; @@ -239,8 +249,9 @@ QVariant glmVecToVariant(const T& v, bool asArray /*= false*/) { return obj; } } + template -const T glmVecFromVariant(const QVariant& v) { +const T buffer_helpers::glmVecFromVariant(const QVariant& v) { auto isMap = v.type() == (QVariant::Type)QMetaType::QVariantMap; static const auto len = T().length(); const auto& components = isMap ? XYZW : ZERO123; @@ -255,9 +266,11 @@ const T glmVecFromVariant(const QVariant& v) { } else { value = list.value(i).toFloat(); } +#ifdef DEBUG_BUFFERVIEW_SCRIPTING if (value != value) { // NAN qWarning().nospace()<< "vec" << len << "." << components[i] << " NAN received from script.... " << v.toString(); } +#endif result[i] = value; } return result; @@ -268,17 +281,26 @@ gpu::BufferView buffer_helpers::fromVector(const QVector& elements, const gpu auto vertexBuffer = std::make_shared(elements.size() * sizeof(T), (gpu::Byte*)elements.data()); return { vertexBuffer, 0, vertexBuffer->getSize(),sizeof(T), elementType }; } -template<> gpu::BufferView buffer_helpers::fromVector(const QVector& elements, const gpu::Element& elementType) { return fromVector(elements, elementType); } -template<> gpu::BufferView buffer_helpers::fromVector(const QVector& elements, const gpu::Element& elementType) { return fromVector(elements, elementType); } +template<> gpu::BufferView buffer_helpers::fromVector( + const QVector& elements, const gpu::Element& elementType +) { return fromVector(elements, elementType); } -template struct GpuVec4ToGlm;// { static T get(const gpu::BufferView& view, quint32 index, const char *hint); }; -template struct getScalar;// { static T get(const gpu::BufferView& view, quint32 index, const char *hint); }; +template<> gpu::BufferView buffer_helpers::fromVector( + const QVector& elements, const gpu::Element& elementType +) { return fromVector(elements, elementType); } + +template struct GpuVec4ToGlm; +template struct GpuScalarToGlm; struct GpuToGlmAdapter { static float error(const QString& name, const gpu::BufferView& view, quint32 index, const char *hint) { + QString debugName; +#ifdef DEBUG_BUFFERVIEW_SCRIPTING + debugName = DebugNames::stringFrom(view._element.getType()) +#endif qDebug() << QString("GpuToGlmAdapter:: unhandled type=%1(element=%2(%3)) size=%4(per=%5) vec%6 hint=%7 #%8") .arg(name) - .arg(DebugNames::stringFrom(view._element.getType())) + .arg(debugName) .arg(view._element.getType()) .arg(view._element.getSize()) .arg(view._element.getSize() / view._element.getScalarCount()) @@ -290,7 +312,8 @@ struct GpuToGlmAdapter { return NAN; } }; -template struct getScalar : GpuToGlmAdapter { + +template struct GpuScalarToGlm : 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); @@ -301,7 +324,7 @@ template struct getScalar : GpuToGlmAdapter { case gpu::FLOAT: return view.get(index); case gpu::HALF: return T(glm::unpackSnorm1x8(view.get(index))); default: break; - } return T(error("getScalar", view, index, hint)); + } return T(error("GpuScalarToGlm", view, index, hint)); } }; @@ -376,8 +399,9 @@ struct getVec { } }; +// BufferView => QVector template <> QVector buffer_helpers::toVector(const gpu::BufferView& view, const char *hint) { - return getVec,int>::__to_vector__(view, 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); @@ -390,8 +414,9 @@ template <> QVector buffer_helpers::toVector(const gpu::Bu } +// indexed conversion accessors (similar to "view.convert(i)" existed) template <> int buffer_helpers::convert(const gpu::BufferView& view, quint32 index, const char *hint) { - return getVec,int>::__to_scalar__(view, index, hint); + return getVec,int>::__to_scalar__(view, index, hint); } template <> glm::vec2 buffer_helpers::convert(const gpu::BufferView& view, quint32 index, const char *hint) { return getVec,glm::vec2>::__to_scalar__(view, index, hint); @@ -407,30 +432,25 @@ gpu::BufferView buffer_helpers::clone(const gpu::BufferView& input) { return gpu::BufferView( std::make_shared(input._buffer->getSize(), input._buffer->getData()), input._offset, input._size, input._stride, input._element - ); + ); } +// TODO: preserve existing data gpu::BufferView buffer_helpers::resize(const gpu::BufferView& input, quint32 numElements) { auto effectiveSize = input._buffer->getSize() / input.getNumElements(); - qDebug() << "resize input" << input.getNumElements() << input._buffer->getSize() << "effectiveSize" << effectiveSize; + qCDebug(bufferhelper_logging) << "resize input" << input.getNumElements() << input._buffer->getSize() << "effectiveSize" << effectiveSize; auto vsize = input._element.getSize() * numElements; - gpu::Byte *data = new gpu::Byte[vsize]; - memset(data, 0, vsize); - auto buffer = new gpu::Buffer(vsize, (gpu::Byte*)data); - delete[] data; + std::unique_ptr data{ new gpu::Byte[vsize] }; + memset(data.get(), 0, vsize); + auto buffer = new gpu::Buffer(vsize, data.get()); auto output = gpu::BufferView(buffer, input._element); - qDebug() << "resized output" << output.getNumElements() << output._buffer->getSize(); + qCDebug(bufferhelper_logging) << "resized output" << output.getNumElements() << output._buffer->getSize(); return output; } graphics::MeshPointer buffer_helpers::cloneMesh(graphics::MeshPointer mesh) { auto clone = std::make_shared(); - //[](graphics::Mesh* blah) { - //qCDebug(bufferhelper_logging) << "--- DELETING MESH POINTER" << blah; - // delete blah; - //}); clone->displayName = (QString::fromStdString(mesh->displayName) + "-clone").toStdString(); - //qCInfo(bufferhelper_logging) << "+++ ALLOCATED MESH POINTER ScriptableMesh::cloneMesh" << clone->displayName << clone.get() << !!mesh; clone->setIndexBuffer(buffer_helpers::clone(mesh->getIndexBuffer())); clone->setPartBuffer(buffer_helpers::clone(mesh->getPartBuffer())); auto attributeViews = buffer_helpers::gatherBufferViews(mesh); @@ -447,18 +467,11 @@ graphics::MeshPointer buffer_helpers::cloneMesh(graphics::MeshPointer mesh) { return clone; } - -/// --- buffer view <-> variant helpers - namespace { // expand the corresponding attribute buffer (creating it if needed) so that it matches POSITIONS size and specified element type gpu::BufferView _expandedAttributeBuffer(const graphics::MeshPointer mesh, gpu::Stream::Slot slot) { gpu::BufferView bufferView = buffer_helpers::getBufferView(mesh, slot); const auto& elementType = bufferView._element; - //auto vecN = element.getScalarCount(); - //auto type = element.getType(); - //gpu::Element elementType = getVecNElement(type, vecN); - gpu::Size elementSize = elementType.getSize(); auto nPositions = mesh->getNumVertices(); auto vsize = nPositions * elementSize; @@ -478,10 +491,9 @@ namespace { if (bufferView.getNumElements() < nPositions || diffTypes) { if (!bufferView._buffer || bufferView.getNumElements() == 0) { qCInfo(bufferhelper_logging).nospace() << "ScriptableMesh -- adding missing mesh attribute '" << hint << "' for BufferView"; - gpu::Byte *data = new gpu::Byte[vsize]; - memset(data, 0, vsize); - auto buffer = new gpu::Buffer(vsize, (gpu::Byte*)data); - delete[] data; + 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 { @@ -553,7 +565,6 @@ std::map buffer_helpers::gatherBufferViews(graphics::M return attributeViews; } - bool buffer_helpers::recalculateNormals(graphics::MeshPointer mesh) { qCInfo(bufferhelper_logging) << "Recalculating normals" << !!mesh; if (!mesh) { @@ -567,10 +578,8 @@ bool buffer_helpers::recalculateNormals(graphics::MeshPointer mesh) { auto numPoints = indices.getNumElements(); const auto TRIANGLE = 3; quint32 numFaces = (quint32)numPoints / TRIANGLE; - //QVector faces; QVector faceNormals; QMap> vertexToFaces; - //faces.resize(numFaces); faceNormals.resize(numFaces); auto numNormals = normals.getNumElements(); qCInfo(bufferhelper_logging) << QString("numFaces: %1, numNormals: %2, numPoints: %3").arg(numFaces).arg(numNormals).arg(numPoints); @@ -590,7 +599,9 @@ bool buffer_helpers::recalculateNormals(graphics::MeshPointer mesh) { }; 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(face.v0).c_str()] << i; @@ -615,10 +626,12 @@ bool buffer_helpers::recalculateNormals(graphics::MeshPointer mesh) { 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))); diff --git a/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h b/libraries/graphics/src/graphics/BufferViewHelpers.h similarity index 91% rename from libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h rename to libraries/graphics/src/graphics/BufferViewHelpers.h index e0c2e1eee1..53fddfe581 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h +++ b/libraries/graphics/src/graphics/BufferViewHelpers.h @@ -15,9 +15,6 @@ namespace gpu { class Element; } -template QVariant glmVecToVariant(const T& v, bool asArray = false); -template const T glmVecFromVariant(const QVariant& v); - namespace graphics { class Mesh; using MeshPointer = std::shared_ptr; @@ -27,6 +24,9 @@ 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); + static graphics::MeshPointer cloneMesh(graphics::MeshPointer mesh); static QMap ATTRIBUTES; static std::map gatherBufferViews(graphics::MeshPointer mesh, const QStringList& expandToMatchPositions = QStringList()); diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index ffc93340b0..9dbef5c6fc 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -9,10 +9,6 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include -namespace { QLoggingCategory wtf{ "tim.Model.cpp" }; } - - #include "Model.h" #include @@ -31,7 +27,7 @@ namespace { QLoggingCategory wtf{ "tim.Model.cpp" }; } #include #include -#include +#include #include #include @@ -372,7 +368,7 @@ bool Model::updateGeometry() { #if FBX_PACK_NORMALS glm::uint32 finalNormal; glm::uint32 finalTangent; - packNormalAndTangent(*normalIt, *tangentIt, finalNormal, finalTangent); + buffer_helpers::packNormalAndTangent(*normalIt, *tangentIt, finalNormal, finalTangent); #else const auto finalNormal = *normalIt; const auto finalTangent = *tangentIt; @@ -569,25 +565,15 @@ bool Model::replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointe auto newRenderGeometry = new MyGeometryMappingResource( _url, _renderGeometry, _newModel ? scriptable::make_qtowned(*_newModel) : nullptr ); - //_needsUpdateTextures = true; _visualGeometryRequestFailed = false; - //invalidCalculatedMeshBoxes(); deleteGeometry(); _renderGeometry.reset(newRenderGeometry); - //onInvalidate(); - //reset(); _rig.destroyAnimGraph(); - //assert(_rig.jointStatesEmpty()); updateGeometry(); calculateTriangleSets(); - //computeMeshPartLocalBounds(); - //_needsReload = false; + _needsReload = false; _needsFixupInScene = true; - //invalidCalculatedMeshBoxes(); setRenderItemsNeedUpdate(); - //_hasCalculatedTextureInfo = false; - //calculateTextureInfo(); - //updateRenderItems(); } return true; } @@ -597,7 +583,7 @@ scriptable::ScriptableModelBase Model::getScriptableModel(bool* ok) { scriptable::ScriptableModelBase result; if (!isLoaded()) { - qCDebug(wtf) << "Model::getScriptableModel -- !isLoaded"; + qCDebug(renderutils) << "Model::getScriptableModel -- !isLoaded"; return scriptable::ModelProvider::modelUnavailableError(ok); } diff --git a/libraries/render-utils/src/Model_temporary_hack.cpp.h b/libraries/render-utils/src/Model_temporary_hack.cpp.h index bc31abcb37..9b0e0bcf9a 100644 --- a/libraries/render-utils/src/Model_temporary_hack.cpp.h +++ b/libraries/render-utils/src/Model_temporary_hack.cpp.h @@ -1,15 +1,7 @@ -#include +#include #include class MyGeometryMappingResource : public GeometryResource { -// Q_OBJECT public: - virtual void init(bool resetLoaded = true) override { - qCDebug(wtf) << "############################# Snarfing init()..."; - } - - virtual void deleter() override { - qCDebug(wtf) << "############################# Snarfing deleter()..."; - } shared_ptr fbxGeometry; MyGeometryMappingResource(const QUrl& url, Geometry::Pointer originalGeometry, std::shared_ptr newModel) : GeometryResource(url) { fbxGeometry = std::make_shared(); @@ -77,6 +69,8 @@ public: 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());//buffer_helpers::cloneMesh(ptr)); @@ -84,10 +78,9 @@ public: const auto oldParts = mesh.parts; mesh.parts.clear(); for (const FBXMeshPart& fbxPart : oldParts) { - FBXMeshPart part; // copy; + FBXMeshPart part; // new copy part.materialID = fbxPart.materialID; // Construct local parts - ///qCDebug(wtf) << "GeometryMappingResource -- meshes part" << meshID << partID << part.materialID; part.triangleIndices = buffer_helpers::toVector(mesh._mesh->getIndexBuffer(), "part.triangleIndices"); mesh.parts << part; auto p = std::make_shared(meshID, partID, (int)materialIDAtlas[part.materialID]); @@ -115,7 +108,6 @@ public: _animGraphOverrideUrl = originalGeometry ? originalGeometry->getAnimGraphOverrideUrl() : QUrl(); _loaded = true; _fbxGeometry = fbxGeometry; - finishedLoading(true); }; }; From fe9b67344b854b31d537deea3b01452d0ce94a15 Mon Sep 17 00:00:00 2001 From: humbletim Date: Thu, 22 Feb 2018 07:58:49 -0500 Subject: [PATCH 24/49] remove unnecessary UUID check --- interface/src/Application.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 1be22d5087..f344c94161 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -657,10 +657,8 @@ public: scriptable::ModelProviderPointer provider; auto avatarManager = DependencyManager::get(); if (auto avatar = avatarManager->getAvatarBySessionID(sessionUUID)) { - if (avatar->getSessionUUID() == sessionUUID) { - provider = std::dynamic_pointer_cast(avatar); - provider->modelProviderType = NestableType::Avatar; - } + provider = std::dynamic_pointer_cast(avatar); + provider->modelProviderType = NestableType::Avatar; } return provider; } From 4fd3d9bf7bc4d1ef7f36a4de52b3e768864c44fe Mon Sep 17 00:00:00 2001 From: humbletim Date: Thu, 22 Feb 2018 08:18:38 -0500 Subject: [PATCH 25/49] cleanup; formalize data structures --- interface/src/ui/overlays/ModelOverlay.cpp | 4 +++- interface/src/ui/overlays/Shape3DOverlay.cpp | 5 +---- .../avatars-renderer/src/avatars-renderer/Avatar.cpp | 7 +------ .../src/RenderableModelEntityItem.cpp | 4 +++- .../src/RenderablePolyVoxEntityItem.cpp | 1 + .../src/RenderableShapeEntityItem.cpp | 11 ++--------- 6 files changed, 11 insertions(+), 21 deletions(-) diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index b6b79fd8c9..4ff5499a12 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -669,5 +669,7 @@ scriptable::ScriptableModelBase ModelOverlay::getScriptableModel(bool* ok) { if (!_model || !_model->isLoaded()) { return Base3DOverlay::getScriptableModel(ok); } - return _model->getScriptableModel(ok); + auto result = _model->getScriptableModel(ok); + result.objectID = getID(); + return result; } \ No newline at end of file diff --git a/interface/src/ui/overlays/Shape3DOverlay.cpp b/interface/src/ui/overlays/Shape3DOverlay.cpp index 54423feef6..04557bcf5d 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.cpp +++ b/interface/src/ui/overlays/Shape3DOverlay.cpp @@ -184,10 +184,7 @@ scriptable::ScriptableModelBase Shape3DOverlay::getScriptableModel(bool* ok) { auto geometryCache = DependencyManager::get(); auto vertexColor = ColorUtils::toVec3(_color); scriptable::ScriptableModelBase result; - result.metadata = { - { "origin", "Shape3DOverlay::"+shapeStrings[_shape] }, - { "overlayID", getID() }, - }; + result.objectID = getID(); result.append(geometryCache->meshFromShape(_shape, vertexColor), {{ "shape", shapeStrings[_shape] }}); if (ok) { *ok = true; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 5934ffed12..48f0c70dfa 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -1801,12 +1801,7 @@ scriptable::ScriptableModelBase Avatar::getScriptableModel(bool* ok) { } scriptable::ScriptableModelBase result = _skeletonModel->getScriptableModel(ok); result.objectID = getSessionUUID(); - result.mixin({ - { "avatarID", getSessionUUID().toString() }, - { "url", _skeletonModelURL.toString() }, - { "origin", "Avatar/avatar::" + _displayName }, - { "textures", _skeletonModel->getTextures() }, - }); + result.mixin({{ "textures", _skeletonModel->getTextures() }}); // FIXME: for now access to attachment models are merged into the main avatar ScriptableModel set for (int i = 0; i < (int)_attachmentModels.size(); i++) { auto& model = _attachmentModels.at(i); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index c93bcb2055..0e23ff3bff 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -968,7 +968,9 @@ scriptable::ScriptableModelBase render::entities::ModelEntityRenderer::getScript return scriptable::ModelProvider::modelUnavailableError(ok); } - return _model->getScriptableModel(ok); + auto result = _model->getScriptableModel(ok); + result.objectID = getEntity()->getID(); + return result; } bool render::entities::ModelEntityRenderer::replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer newModel, int meshIndex, int partIndex) { diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index a547926343..37e03b2590 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -1459,6 +1459,7 @@ scriptable::ScriptableModelBase RenderablePolyVoxEntityItem::getScriptableModel( bool success = false; glm::mat4 transform = voxelToLocalMatrix(); scriptable::ScriptableModelBase result; + result.objectID = getThisPointer()->getID(); withReadLock([&] { gpu::BufferView::Index numVertices = (gpu::BufferView::Index)_mesh->getNumVertices(); if (!_meshReady) { diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index 0ff8ac17f0..2e65eba5e5 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -166,11 +166,7 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { scriptable::ScriptableModelBase ShapeEntityRenderer::getScriptableModel(bool* ok) { scriptable::ScriptableModelBase result; - result.metadata = { - { "entityID", getEntity()->getID().toString() }, - { "shape", entity::stringFromShape(_shape) }, - { "userData", getEntity()->getUserData() }, - }; + result.objectID = getEntity()->getID(); auto geometryCache = DependencyManager::get(); auto geometryShape = geometryCache->getShapeForEntityShape(_shape); glm::vec3 vertexColor; @@ -179,10 +175,7 @@ scriptable::ScriptableModelBase ShapeEntityRenderer::getScriptableModel(bool* ok } auto success = false; if (auto mesh = geometryCache->meshFromShape(geometryShape, vertexColor)) { - scriptable::ScriptableMeshBase base{ mesh, { - { "shape", entity::stringFromShape(_shape) }, - }}; - result.append(base); + result.append({ mesh, {{ "shape", entity::stringFromShape(_shape) }}}); success = true; } if (ok) { From e3c269d5820207e6521e8bf2ad64b2529205b3a1 Mon Sep 17 00:00:00 2001 From: humbletim Date: Thu, 22 Feb 2018 08:22:13 -0500 Subject: [PATCH 26/49] remove debug string --- libraries/fbx/src/OBJWriter.cpp | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/libraries/fbx/src/OBJWriter.cpp b/libraries/fbx/src/OBJWriter.cpp index a53a3c7c5d..ffc34573d4 100644 --- a/libraries/fbx/src/OBJWriter.cpp +++ b/libraries/fbx/src/OBJWriter.cpp @@ -156,16 +156,7 @@ bool writeOBJToTextStream(QTextStream& out, QList meshes) { // graphics::Mesh::TRIANGLES / graphics::Mesh::QUADS // TODO -- handle other formats uint32_t len = part._startIndex + part._numIndices; - auto stringFromTopology = [&](graphics::Mesh::Topology topo) -> QString { - return topo == graphics::Mesh::Topology::QUADS ? "QUADS" : - topo == graphics::Mesh::Topology::QUAD_STRIP ? "QUAD_STRIP" : - topo == graphics::Mesh::Topology::TRIANGLES ? "TRIANGLES" : - topo == graphics::Mesh::Topology::TRIANGLE_STRIP ? "TRIANGLE_STRIP" : - topo == graphics::Mesh::Topology::QUAD_STRIP ? "QUAD_STRIP" : - QString("topo:%1").arg((int)topo); - }; - - qCDebug(modelformat) << "OBJWriter -- part" << partIndex << "topo" << stringFromTopology(part._topology) << "index elements" << (shorts ? "uint16_t" : "uint32_t"); + qCDebug(modelformat) << "OBJWriter -- part" << partIndex << "topo" << part._topology << "index elements" << (shorts ? "uint16_t" : "uint32_t"); if (part._topology == graphics::Mesh::TRIANGLES && len % 3 != 0) { qCDebug(modelformat) << "OBJWriter -- index buffer length isn't a multiple of 3" << len; } From d52dce8e092847024e7cdece7091d0568fd9fa67 Mon Sep 17 00:00:00 2001 From: humbletim Date: Thu, 22 Feb 2018 08:24:56 -0500 Subject: [PATCH 27/49] restore code for debugging --- libraries/fbx/src/FBXReader.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 837d7fe80e..278a68f312 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1979,7 +1979,22 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } } } - + { + int i = 0; + for (const auto& mesh : geometry.meshes) { + auto name = geometry.getModelNameOfMesh(i++); + if (!name.isEmpty()) { + if (mesh._mesh) { + mesh._mesh->modelName = name.toStdString(); + if (!mesh._mesh->displayName.size()) { + mesh._mesh->displayName = mesh._mesh->displayName + "#" + name; + } + } else { + qDebug() << "modelName but no mesh._mesh" << name; + } + } + } + } return geometryPtr; } From ec4f9fdc11bc2584f289276b6dc577e2559699f5 Mon Sep 17 00:00:00 2001 From: humbletim Date: Thu, 22 Feb 2018 08:31:05 -0500 Subject: [PATCH 28/49] fix displayName --- .../entities-renderer/src/RenderableModelEntityItem.cpp | 8 ++++---- libraries/fbx/src/FBXReader.cpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 0e23ff3bff..6eed62522f 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1300,7 +1300,8 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce auto entityRenderer = static_cast(&data); entityRenderer->clearSubRenderItemIDs(); }); - emit DependencyManager::get()->modelRemovedFromScene(entity->getEntityItemID(), NestableType::Entity, _model); + emit DependencyManager::get()-> + modelRemovedFromScene(entity->getEntityItemID(), NestableType::Entity, _model); } return; } @@ -1311,10 +1312,9 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce connect(model.get(), &Model::setURLFinished, this, [&](bool didVisualGeometryRequestSucceed) { setKey(didVisualGeometryRequestSucceed); emit requestRenderUpdate(); - auto factory = DependencyManager::get().data(); - qDebug() << "leopoly didVisualGeometryRequestSucceed" << didVisualGeometryRequestSucceed << QThread::currentThread() << _model.get(); if(didVisualGeometryRequestSucceed) { - emit factory->modelAddedToScene(entity->getEntityItemID(), NestableType::Entity, _model); + emit DependencyManager::get()-> + modelAddedToScene(entity->getEntityItemID(), NestableType::Entity, _model); } }); connect(model.get(), &Model::requestRenderUpdate, this, &ModelEntityRenderer::requestRenderUpdate); diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 278a68f312..1e59646795 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1987,7 +1987,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS if (mesh._mesh) { mesh._mesh->modelName = name.toStdString(); if (!mesh._mesh->displayName.size()) { - mesh._mesh->displayName = mesh._mesh->displayName + "#" + name; + mesh._mesh->displayName = QString("#%1").arg(name).toStdString(); } } else { qDebug() << "modelName but no mesh._mesh" << name; From b0f444e179824dd6c91a1da790dc923d098d4e32 Mon Sep 17 00:00:00 2001 From: humbletim Date: Thu, 22 Feb 2018 08:36:37 -0500 Subject: [PATCH 29/49] remove dead code --- .../src/graphics-scripting/ScriptableModel.cpp | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp index 05ff043820..5d0d459446 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp @@ -131,20 +131,3 @@ quint32 scriptable::ScriptableModel::mapAttributeValues(QScriptValue callback) { } return result; } - -/*namespace { - QScriptValue modelPointerToScriptValue(QScriptEngine* engine, scriptable::ScriptableModelPointer const &in) { - return qObjectToScriptValue(engine, in); - } - void modelPointerFromScriptValue(const QScriptValue& value, scriptable::ScriptableModelPointer &out) { - out = scriptable::qpointer_qobject_cast(value); - } -} - -namespace scriptable { - bool registerMetaTypes(QScriptEngine* engine) { - qScriptRegisterMetaType(engine, modelPointerToScriptValue, modelPointerFromScriptValue); - return true; - } -} -*/ From 4d43e543466bdb2138f87042e55d889588359bc6 Mon Sep 17 00:00:00 2001 From: humbletim Date: Thu, 22 Feb 2018 08:41:15 -0500 Subject: [PATCH 30/49] use shared buffer_helpers::clone --- libraries/render-utils/src/GeometryCache.cpp | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 525ef51a57..7455da13b6 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -32,6 +32,7 @@ #include "gpu/StandardShaderLib.h" #include "graphics/TextureMap.h" +#include "graphics/BufferViewHelpers.h" #include "render/Args.h" #include "standardTransformPNTC_vert.h" @@ -2409,26 +2410,15 @@ graphics::MeshPointer GeometryCache::meshFromShape(Shape geometryShape, glm::vec qDebug() << "GeometryCache::getMeshProxyListFromShape" << shapeData << stringFromShape(geometryShape); - auto cloneBufferView = [](const gpu::BufferView& in) -> gpu::BufferView { - auto buffer = std::make_shared(*in._buffer); // copy - // FIXME: gpu::BufferView seems to have a bug where constructing a new instance from an existing one - // results in over-multiplied buffer/view sizes -- hence constructing manually here from each input prop - auto out = gpu::BufferView(buffer, in._offset, in._size, in._stride, in._element); - Q_ASSERT(out.getNumElements() == in.getNumElements()); - Q_ASSERT(out._size == in._size); - Q_ASSERT(out._buffer->getSize() == in._buffer->getSize()); - return out; - }; - - auto positionsBufferView = cloneBufferView(shapeData->_positionView); - auto normalsBufferView = cloneBufferView(shapeData->_normalView); - auto indexBufferView = cloneBufferView(shapeData->_indicesView); + auto positionsBufferView = buffer_helpers::clone(shapeData->_positionView); + auto normalsBufferView = buffer_helpers::clone(shapeData->_normalView); + auto indexBufferView = buffer_helpers::clone(shapeData->_indicesView); gpu::BufferView::Size numVertices = positionsBufferView.getNumElements(); Q_ASSERT(numVertices == normalsBufferView.getNumElements()); // apply input color across all vertices - auto colorsBufferView = cloneBufferView(shapeData->_normalView); + auto colorsBufferView = buffer_helpers::clone(shapeData->_normalView); for (gpu::BufferView::Size i = 0; i < numVertices; i++) { colorsBufferView.edit((gpu::BufferView::Index)i) = color; } From 010c714abcd9f86464c07e52501048e856831c35 Mon Sep 17 00:00:00 2001 From: humbletim Date: Thu, 22 Feb 2018 08:51:54 -0500 Subject: [PATCH 31/49] restore original Entities.getMeshes --- libraries/render-utils/src/Model.cpp | 38 ++++++++++++++++++++++++++++ libraries/render-utils/src/Model.h | 2 ++ 2 files changed, 40 insertions(+) diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 9dbef5c6fc..783659a6a1 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -550,6 +551,43 @@ bool Model::convexHullContains(glm::vec3 point) { return false; } +// TODO: deprecate and remove +MeshProxyList Model::getMeshes() const { + MeshProxyList result; + const Geometry::Pointer& renderGeometry = getGeometry(); + const Geometry::GeometryMeshes& meshes = renderGeometry->getMeshes(); + + if (!isLoaded()) { + return result; + } + + Transform offset; + offset.setScale(_scale); + offset.postTranslate(_offset); + glm::mat4 offsetMat = offset.getMatrix(); + + for (std::shared_ptr mesh : meshes) { + if (!mesh) { + continue; + } + + MeshProxy* meshProxy = new SimpleMeshProxy( + mesh->map( + [=](glm::vec3 position) { + return glm::vec3(offsetMat * glm::vec4(position, 1.0f)); + }, + [=](glm::vec3 color) { return color; }, + [=](glm::vec3 normal) { + return glm::normalize(glm::vec3(offsetMat * glm::vec4(normal, 0.0f))); + }, + [&](uint32_t index) { return index; })); + meshProxy->setObjectName(mesh->displayName.c_str()); + result << meshProxy; + } + + return result; +} + // FIXME: temporary workaround that updates the whole FBXGeometry (to keep findRayIntersection in sync) #include "Model_temporary_hack.cpp.h" diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 3a86b3b7e4..5cbdb2d300 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -322,6 +322,8 @@ public: void scaleToFit(); + Q_INVOKABLE MeshProxyList getMeshes() const; + void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName); void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName); From dd90c8c515561ec4c480d12975a84ae722ee2d15 Mon Sep 17 00:00:00 2001 From: humbletim Date: Thu, 22 Feb 2018 09:36:17 -0500 Subject: [PATCH 32/49] fix ubuntu compile warnings --- interface/src/Application.cpp | 2 +- .../src/graphics-scripting/Forward.h | 14 +++------- .../GraphicsScriptingInterface.cpp | 2 +- .../src/graphics-scripting/ScriptableMesh.cpp | 13 ++-------- .../src/graphics-scripting/ScriptableMesh.h | 18 +++++-------- .../graphics-scripting/ScriptableModel.cpp | 10 +++++++ .../src/graphics/BufferViewHelpers.cpp | 26 +++++++++++++------ 7 files changed, 41 insertions(+), 44 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index f344c94161..69be01fc0c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -607,7 +607,7 @@ void messageHandler(QtMsgType type, const QMessageLogContext& context, const QSt class ApplicationMeshProvider : public scriptable::ModelProviderFactory { public: - virtual scriptable::ModelProviderPointer lookupModelProvider(const QUuid& uuid) { + virtual scriptable::ModelProviderPointer lookupModelProvider(const QUuid& uuid) override { QString error; scriptable::ModelProviderPointer provider; diff --git a/libraries/graphics-scripting/src/graphics-scripting/Forward.h b/libraries/graphics-scripting/src/graphics-scripting/Forward.h index 95fcf51921..7b2126cf8f 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/Forward.h +++ b/libraries/graphics-scripting/src/graphics-scripting/Forward.h @@ -42,7 +42,7 @@ namespace scriptable { ScriptableMeshBase(WeakModelProviderPointer provider, ScriptableModelBasePointer model, WeakMeshPointer mesh, const QVariantMap& metadata); ScriptableMeshBase(WeakMeshPointer mesh = WeakMeshPointer()); ScriptableMeshBase(MeshPointer mesh, const QVariantMap& metadata); - ScriptableMeshBase(const ScriptableMeshBase& other) { *this = other; } + ScriptableMeshBase(const ScriptableMeshBase& other) : QObject() { *this = other; } ScriptableMeshBase& operator=(const ScriptableMeshBase& view); virtual ~ScriptableMeshBase(); Q_INVOKABLE const scriptable::MeshPointer getMeshPointer() const { return mesh.lock(); } @@ -60,16 +60,8 @@ namespace scriptable { QVector meshes; ScriptableModelBase(QObject* parent = nullptr) : QObject(parent) {} - ScriptableModelBase(const ScriptableModelBase& other) { *this = other; } - ScriptableModelBase& operator=(const ScriptableModelBase& other) { - provider = other.provider; - objectID = other.objectID; - metadata = other.metadata; - for (auto& mesh : other.meshes) { - append(mesh); - } - return *this; - } + ScriptableModelBase(const ScriptableModelBase& other) : QObject() { *this = other; } + ScriptableModelBase& operator=(const ScriptableModelBase& other); virtual ~ScriptableModelBase(); void mixin(const QVariantMap& other); diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp index 58fcd7a064..28d5d0edfc 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp @@ -27,7 +27,7 @@ #include "GraphicsScriptingInterface.moc" -GraphicsScriptingInterface::GraphicsScriptingInterface(QObject* parent) : QObject(parent) { +GraphicsScriptingInterface::GraphicsScriptingInterface(QObject* parent) : QObject(parent), QScriptable() { if (auto scriptEngine = qobject_cast(parent)) { this->registerMetaTypes(scriptEngine); } diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp index 28e57692f6..3678c54b7b 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp @@ -23,12 +23,12 @@ #include "ScriptableMesh.moc" scriptable::ScriptableMeshPart::ScriptableMeshPart(scriptable::ScriptableMeshPointer parentMesh, int partIndex) - : parentMesh(parentMesh), partIndex(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) { + : ScriptableMeshBase(other), QScriptable() { auto mesh = getMeshPointer(); QString name = mesh ? QString::fromStdString(mesh->modelName) : ""; if (name.isEmpty()) { @@ -60,15 +60,6 @@ quint32 scriptable::ScriptableMesh::getNumVertices() const { return 0; } -// glm::vec3 ScriptableMesh::getPos3(quint32 index) const { -// if (auto mesh = getMeshPointer()) { -// if (index < getNumVertices()) { -// return mesh->getPos3(index); -// } -// } -// return glm::vec3(NAN); -// } - QVector scriptable::ScriptableMesh::findNearbyIndices(const glm::vec3& origin, float epsilon) const { QVector result; if (auto mesh = getMeshPointer()) { diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h index 2b9399d6ef..2cba33edde 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h @@ -33,14 +33,11 @@ namespace scriptable { bool hasValidOwnedMesh() const { return (bool)getOwnedMeshPointer(); } operator const ScriptableMeshBase*() const { return (qobject_cast(this)); } - ScriptableMesh(scriptable::MeshPointer mesh) : ScriptableMeshBase(mesh) { ownedMesh = mesh; } + ScriptableMesh(scriptable::MeshPointer mesh) : ScriptableMeshBase(mesh), QScriptable() { ownedMesh = mesh; } ScriptableMesh(WeakModelProviderPointer provider, ScriptableModelBasePointer model, MeshPointer mesh, const QVariantMap& metadata) - : ScriptableMeshBase(provider, model, mesh, metadata) { ownedMesh = mesh; } - //ScriptableMesh& operator=(const ScriptableMesh& other) { model=other.model; mesh=other.mesh; metadata=other.metadata; return *this; }; - //ScriptableMesh() : QObject(), model(nullptr) {} - //ScriptableMesh(const ScriptableMesh& other) : QObject(), model(other.model), mesh(other.mesh), metadata(other.metadata) {} + : ScriptableMeshBase(provider, model, mesh, metadata), QScriptable() { ownedMesh = mesh; } ScriptableMesh(const ScriptableMeshBase& other); - ScriptableMesh(const ScriptableMesh& other) : ScriptableMeshBase(other) {}; + ScriptableMesh(const ScriptableMesh& other) : ScriptableMeshBase(other), QScriptable() {}; virtual ~ScriptableMesh(); Q_INVOKABLE const scriptable::ScriptableModelPointer getParentModel() const { return qobject_cast(model); } @@ -91,12 +88,9 @@ namespace scriptable { Q_PROPERTY(QVariantMap metadata MEMBER metadata) - //Q_PROPERTY(scriptable::ScriptableMeshPointer parentMesh MEMBER parentMesh CONSTANT HIDE) - ScriptableMeshPart(scriptable::ScriptableMeshPointer parentMesh, int partIndex); ScriptableMeshPart& operator=(const ScriptableMeshPart& view) { parentMesh=view.parentMesh; return *this; }; - ScriptableMeshPart(const ScriptableMeshPart& other) : parentMesh(other.parentMesh), partIndex(other.partIndex) {} - // ~ScriptableMeshPart() { qDebug() << "~ScriptableMeshPart" << this; } + ScriptableMeshPart(const ScriptableMeshPart& other) : QObject(), QScriptable(), parentMesh(other.parentMesh), partIndex(other.partIndex) {} public slots: scriptable::ScriptableMeshPointer getParentMesh() const { return parentMesh; } @@ -148,8 +142,8 @@ namespace scriptable { class GraphicsScriptingInterface : public QObject, QScriptable { Q_OBJECT public: - GraphicsScriptingInterface(QObject* parent = nullptr) : QObject(parent) {} - GraphicsScriptingInterface(const GraphicsScriptingInterface& other) {} + GraphicsScriptingInterface(QObject* parent = nullptr) : QObject(parent), QScriptable() {} + GraphicsScriptingInterface(const GraphicsScriptingInterface& other) : QObject(), QScriptable() {} public slots: ScriptableMeshPartPointer exportMeshPart(ScriptableMeshPointer mesh, int part=0) { return ScriptableMeshPartPointer(new ScriptableMeshPart(mesh, part)); diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp index 5d0d459446..cc882aa7bb 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp @@ -27,6 +27,16 @@ void scriptable::ScriptableModelBase::mixin(const QVariantMap& modelMetaData) { } } +scriptable::ScriptableModelBase& scriptable::ScriptableModelBase::operator=(const scriptable::ScriptableModelBase& other) { + provider = other.provider; + objectID = other.objectID; + metadata = other.metadata; + for (auto& mesh : other.meshes) { + append(mesh); + } + return *this; +} + scriptable::ScriptableModelBase::~ScriptableModelBase() { #ifdef SCRIPTABLE_MESH_DEBUG qCDebug(graphics_scripting) << "~ScriptableModelBase" << this; diff --git a/libraries/graphics/src/graphics/BufferViewHelpers.cpp b/libraries/graphics/src/graphics/BufferViewHelpers.cpp index ea511d82f4..a15ce1b998 100644 --- a/libraries/graphics/src/graphics/BufferViewHelpers.cpp +++ b/libraries/graphics/src/graphics/BufferViewHelpers.cpp @@ -281,13 +281,22 @@ gpu::BufferView buffer_helpers::fromVector(const QVector& elements, const gpu 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) { + 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); } +) { return _fromVector(elements, elementType); } template<> gpu::BufferView buffer_helpers::fromVector( const QVector& elements, const gpu::Element& elementType -) { return fromVector(elements, elementType); } +) { return _fromVector(elements, elementType); } template struct GpuVec4ToGlm; template struct GpuScalarToGlm; @@ -537,14 +546,14 @@ std::map buffer_helpers::gatherBufferViews(graphics::M 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; - auto vecN = element.getScalarCount(); - //auto type = element.getType(); QString typeName = QString("%1").arg(element.getType()); #ifdef DEBUG_BUFFERVIEW_SCRIPTING typeName = DebugNames::stringFrom(element.getType()); @@ -553,6 +562,7 @@ std::map buffer_helpers::gatherBufferViews(graphics::M 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) { @@ -604,14 +614,14 @@ bool buffer_helpers::recalculateNormals(graphics::MeshPointer mesh) { #endif break; } - vertexToFaces[glm::to_string(face.v0).c_str()] << i; - vertexToFaces[glm::to_string(face.v1).c_str()] << i; - vertexToFaces[glm::to_string(face.v2).c_str()] << i; + 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(verts.get(j)).c_str() }; + 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) { From 80c0f2a21e6f549fff129a460d80b15a3f47d48f Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Fri, 2 Feb 2018 11:28:25 -0800 Subject: [PATCH 33/49] Fix crash when passing --checkMinSpec flag That flag caused a DLL to be loaded before Application was instanced. This triggers a Qt bug inside Q_COREAPP_STARTUP_FUNC that causes the previous registration pointing the startup function in the main executable to be overridden with the address of the function in the DLL (Since they both link the same static library) This leads to the correct function running in the wrong address space (the DLLs), hence not initializing some global variables correctly. --- assignment-client/src/main.cpp | 3 + domain-server/src/main.cpp | 2 + interface/src/main.cpp | 2 + libraries/shared/src/SettingInterface.cpp | 69 ++++++++++++++--------- libraries/shared/src/SettingManager.cpp | 6 +- libraries/shared/src/SharedUtil.cpp | 9 ++- libraries/shared/src/SharedUtil.h | 34 +---------- tools/ac-client/src/main.cpp | 5 +- tools/atp-client/src/main.cpp | 3 +- tools/oven/src/main.cpp | 8 ++- 10 files changed, 68 insertions(+), 73 deletions(-) diff --git a/assignment-client/src/main.cpp b/assignment-client/src/main.cpp index 4f64bf8f7f..bf4497984f 100644 --- a/assignment-client/src/main.cpp +++ b/assignment-client/src/main.cpp @@ -23,9 +23,12 @@ int main(int argc, char* argv[]) { QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); QCoreApplication::setApplicationVersion(BuildInfo::VERSION); + setupGlobalInstances(); + qInstallMessageHandler(LogHandler::verboseMessageHandler); qInfo() << "Starting."; + AssignmentClientApp app(argc, argv); int acReturn = app.exec(); diff --git a/domain-server/src/main.cpp b/domain-server/src/main.cpp index dc3ee54fe7..e258626223 100644 --- a/domain-server/src/main.cpp +++ b/domain-server/src/main.cpp @@ -29,6 +29,8 @@ int main(int argc, char* argv[]) { QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); QCoreApplication::setApplicationVersion(BuildInfo::VERSION); + setupGlobalInstances(); + Setting::init(); #ifndef WIN32 diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 30e8439985..0f8134b253 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -62,6 +62,8 @@ int main(int argc, const char* argv[]) { QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); QCoreApplication::setApplicationVersion(BuildInfo::VERSION); + setupGlobalInstances(); + Setting::init(); // Instance UserActivityLogger now that the settings are loaded diff --git a/libraries/shared/src/SettingInterface.cpp b/libraries/shared/src/SettingInterface.cpp index 327668574e..62f116795e 100644 --- a/libraries/shared/src/SettingInterface.cpp +++ b/libraries/shared/src/SettingInterface.cpp @@ -23,43 +23,53 @@ #include "SharedUtil.h" namespace Setting { - static QSharedPointer globalManager; - // cleans up the settings private instance. Should only be run once at closing down. void cleanupPrivateInstance() { + auto globalManager = DependencyManager::get(); + Q_ASSERT(qApp && globalManager); + // grab the thread before we nuke the instance - QThread* settingsManagerThread = DependencyManager::get()->thread(); + QThread* settingsManagerThread = globalManager->thread(); - // tell the private instance to clean itself up on its thread - DependencyManager::destroy(); - - globalManager.reset(); - - // quit the settings manager thread and wait on it to make sure it's gone + // quit the settings manager thread settingsManagerThread->quit(); settingsManagerThread->wait(); + + // Save all settings + globalManager->saveAll(); + + qCDebug(shared) << "Settings thread stopped."; } void setupPrivateInstance() { - // Ensure Setting::init has already ran and qApp exists - if (qApp && globalManager) { - // Let's set up the settings Private instance on its own thread - QThread* thread = new QThread(); - Q_CHECK_PTR(thread); - thread->setObjectName("Settings Thread"); + auto globalManager = DependencyManager::get(); + Q_ASSERT(qApp && globalManager); - QObject::connect(thread, SIGNAL(started()), globalManager.data(), SLOT(startTimer())); - QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); - QObject::connect(thread, SIGNAL(finished()), globalManager.data(), SLOT(deleteLater())); - globalManager->moveToThread(thread); - thread->start(); - qCDebug(shared) << "Settings thread started."; + // Let's set up the settings private instance on its own thread + QThread* thread = new QThread(qApp); + Q_CHECK_PTR(thread); + thread->setObjectName("Settings Thread"); - // Register cleanupPrivateInstance to run inside QCoreApplication's destructor. - qAddPostRoutine(cleanupPrivateInstance); - } + // Setup setting periodical save timer + QObject::connect(thread, &QThread::started, globalManager.data(), &Manager::startTimer); + QObject::connect(thread, &QThread::finished, globalManager.data(), &Manager::stopTimer); + + // Setup manager threading affinity + globalManager->moveToThread(thread); + QObject::connect(thread, &QThread::finished, globalManager.data(), [] { + auto globalManager = DependencyManager::get(); + Q_ASSERT(qApp && globalManager); + + // Move manager back to the main thread (has to be done on owning thread) + globalManager->moveToThread(qApp->thread()); + }); + + thread->start(); + qCDebug(shared) << "Settings thread started."; + + // Register cleanupPrivateInstance to run inside QCoreApplication's destructor. + qAddPostRoutine(cleanupPrivateInstance); } - FIXED_Q_COREAPP_STARTUP_FUNCTION(setupPrivateInstance) // Sets up the settings private instance. Should only be run once at startup. preInit() must be run beforehand, void init() { @@ -68,6 +78,7 @@ namespace Setting { QSettings settings; qCDebug(shared) << "Settings file:" << settings.fileName(); + // Backward compatibility for old settings file if (settings.allKeys().isEmpty()) { loadOldINIFile(settings); } @@ -80,11 +91,13 @@ namespace Setting { qCDebug(shared) << (deleted ? "Deleted" : "Failed to delete") << "settings lock file" << settingsLockFilename; } - globalManager = DependencyManager::set(); + // Setup settings manager + DependencyManager::set(); - setupPrivateInstance(); + // Add pre-routine to setup threading + qAddPreRoutine(setupPrivateInstance); } - + void Interface::init() { if (!DependencyManager::isSet()) { // WARNING: As long as we are using QSettings this should always be triggered for each Setting::Handle diff --git a/libraries/shared/src/SettingManager.cpp b/libraries/shared/src/SettingManager.cpp index 6c246d4cea..2e0850255a 100644 --- a/libraries/shared/src/SettingManager.cpp +++ b/libraries/shared/src/SettingManager.cpp @@ -23,11 +23,7 @@ namespace Setting { // Cleanup timer stopTimer(); delete _saveTimer; - - // Save all settings before exit - saveAll(); - - // sync will be called in the QSettings destructor + _saveTimer = nullptr; } // Custom deleter does nothing, because we need to shutdown later than the dependency manager diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index 8e5c30711c..772340b631 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -63,12 +63,12 @@ extern "C" FILE * __cdecl __iob_func(void) { #include "OctalCode.h" #include "SharedLogging.h" +static std::mutex stagedGlobalInstancesMutex; static std::unordered_map stagedGlobalInstances; std::mutex& globalInstancesMutex() { - static std::mutex mutex; - return mutex; + return stagedGlobalInstancesMutex; } static void commitGlobalInstances() { @@ -78,7 +78,10 @@ static void commitGlobalInstances() { } stagedGlobalInstances.clear(); } -FIXED_Q_COREAPP_STARTUP_FUNCTION(commitGlobalInstances) + +void setupGlobalInstances() { + qAddPreRoutine(commitGlobalInstances); +} QVariant getGlobalInstance(const char* propertyName) { if (qApp) { diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index 5a1e48d9c0..64bbc6585b 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -25,23 +25,6 @@ #include #include -// Workaround for https://bugreports.qt.io/browse/QTBUG-54479 -// Wrap target function inside another function that holds -// a unique string identifier and uses it to ensure it only runs once -// by storing a state within the qApp -// We cannot used std::call_once with a static once_flag because -// this is used in shared libraries that are linked by several DLLs -// (ie. plugins), meaning the static will be useless in that case -#define FIXED_Q_COREAPP_STARTUP_FUNCTION(AFUNC) \ - static void AFUNC ## _fixed() { \ - const auto propertyName = std::string(Q_FUNC_INFO) + __FILE__; \ - if (!qApp->property(propertyName.c_str()).toBool()) { \ - AFUNC(); \ - qApp->setProperty(propertyName.c_str(), QVariant(true)); \ - } \ - } \ - Q_COREAPP_STARTUP_FUNCTION(AFUNC ## _fixed) - // When writing out avatarEntities to a QByteArray, if the parentID is the ID of MyAvatar, use this ID instead. This allows // the value to be reset when the sessionID changes. const QUuid AVATAR_SELF_ID = QUuid("{00000000-0000-0000-0000-000000000001}"); @@ -53,21 +36,7 @@ std::unique_ptr& globalInstancePointer() { return instancePtr; } -template -void setGlobalInstance(const char* propertyName, T* instance) { - globalInstancePointer().reset(instance); -} - -template -bool destroyGlobalInstance() { - std::unique_ptr& instancePtr = globalInstancePointer(); - if (instancePtr.get()) { - instancePtr.reset(); - return true; - } - return false; -} - +void setupGlobalInstances(); std::mutex& globalInstancesMutex(); QVariant getGlobalInstance(const char* propertyName); void setGlobalInstance(const char* propertyName, const QVariant& variant); @@ -78,7 +47,6 @@ void setGlobalInstance(const char* propertyName, const QVariant& variant); template T* globalInstance(const char* propertyName, Args&&... args) { static T* resultInstance { nullptr }; - static std::mutex mutex; if (!resultInstance) { std::unique_lock lock(globalInstancesMutex()); if (!resultInstance) { diff --git a/tools/ac-client/src/main.cpp b/tools/ac-client/src/main.cpp index c9affde3b5..139e44bc9a 100644 --- a/tools/ac-client/src/main.cpp +++ b/tools/ac-client/src/main.cpp @@ -19,15 +19,16 @@ using namespace std; -int main(int argc, char * argv[]) { +int main(int argc, char* argv[]) { QCoreApplication::setApplicationName(BuildInfo::AC_CLIENT_SERVER_NAME); QCoreApplication::setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION); QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); QCoreApplication::setApplicationVersion(BuildInfo::VERSION); + setupGlobalInstances(); + Setting::init(); ACClientApp app(argc, argv); - return app.exec(); } diff --git a/tools/atp-client/src/main.cpp b/tools/atp-client/src/main.cpp index 830c049bc7..0a8274fedd 100644 --- a/tools/atp-client/src/main.cpp +++ b/tools/atp-client/src/main.cpp @@ -25,9 +25,10 @@ int main(int argc, char * argv[]) { QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); QCoreApplication::setApplicationVersion(BuildInfo::VERSION); + setupGlobalInstances(); + Setting::init(); ATPClientApp app(argc, argv); - return app.exec(); } diff --git a/tools/oven/src/main.cpp b/tools/oven/src/main.cpp index 788470b75e..be0c22a0c6 100644 --- a/tools/oven/src/main.cpp +++ b/tools/oven/src/main.cpp @@ -10,11 +10,17 @@ #include "Oven.h" +#include #include +#include int main (int argc, char** argv) { - QCoreApplication::setOrganizationName("High Fidelity"); QCoreApplication::setApplicationName("Oven"); + QCoreApplication::setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION); + QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); + QCoreApplication::setApplicationVersion(BuildInfo::VERSION); + + setupGlobalInstances(); // init the settings interface so we can save and load settings Setting::init(); From 277e556b48666cddee5e04ac181caa1cd969fa8f Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 22 Feb 2018 16:22:18 -0800 Subject: [PATCH 34/49] Create a standard function to init Hifi Apps --- assignment-client/src/main.cpp | 16 ++-------------- domain-server/src/main.cpp | 16 +--------------- ice-server/src/main.cpp | 11 +++-------- interface/src/Application.cpp | 7 ++----- interface/src/main.cpp | 11 +---------- libraries/shared/src/LogHandler.cpp | 12 ++++++++++-- libraries/shared/src/SharedUtil.cpp | 20 ++++++++++++++++++++ libraries/shared/src/SharedUtil.h | 1 + tests/entities/src/main.cpp | 3 +++ tests/gpu-test/src/main.cpp | 5 ++++- tests/ktx/src/KtxTests.cpp | 3 ++- tests/qml/src/main.cpp | 12 ++---------- tests/qt59/src/main.cpp | 7 ++----- tests/recording/src/main.cpp | 16 +++++----------- tests/render-perf/src/main.cpp | 18 ++---------------- tests/render-texture-load/src/main.cpp | 16 ++-------------- tests/render-utils/src/main.cpp | 17 +++-------------- tests/shaders/src/main.cpp | 16 +++------------- tools/ac-client/src/main.cpp | 7 +------ tools/atp-client/src/main.cpp | 7 +------ tools/auto-tester/src/main.cpp | 2 +- tools/ice-client/src/main.cpp | 4 ++++ tools/oven/src/main.cpp | 7 +------ tools/skeleton-dump/src/main.cpp | 4 ++++ tools/udt-test/src/UDTTest.cpp | 2 -- tools/udt-test/src/main.cpp | 4 ++++ tools/vhacd-util/src/main.cpp | 4 ++++ 27 files changed, 88 insertions(+), 160 deletions(-) diff --git a/assignment-client/src/main.cpp b/assignment-client/src/main.cpp index bf4497984f..971e9ed272 100644 --- a/assignment-client/src/main.cpp +++ b/assignment-client/src/main.cpp @@ -9,25 +9,13 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include +#include #include #include "AssignmentClientApp.h" -#include int main(int argc, char* argv[]) { - disableQtBearerPoll(); // Fixes wifi ping spikes - - QCoreApplication::setApplicationName(BuildInfo::ASSIGNMENT_CLIENT_NAME); - QCoreApplication::setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION); - QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); - QCoreApplication::setApplicationVersion(BuildInfo::VERSION); - - setupGlobalInstances(); - - qInstallMessageHandler(LogHandler::verboseMessageHandler); - qInfo() << "Starting."; - + setupHifiApplication(BuildInfo::ASSIGNMENT_CLIENT_NAME); AssignmentClientApp app(argc, argv); diff --git a/domain-server/src/main.cpp b/domain-server/src/main.cpp index e258626223..d7856bf867 100644 --- a/domain-server/src/main.cpp +++ b/domain-server/src/main.cpp @@ -22,24 +22,10 @@ #include "DomainServer.h" int main(int argc, char* argv[]) { - disableQtBearerPoll(); // Fixes wifi ping spikes + setupHifiApplication(BuildInfo::DOMAIN_SERVER_NAME); - QCoreApplication::setApplicationName(BuildInfo::DOMAIN_SERVER_NAME); - QCoreApplication::setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION); - QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); - QCoreApplication::setApplicationVersion(BuildInfo::VERSION); - - setupGlobalInstances(); - Setting::init(); -#ifndef WIN32 - setvbuf(stdout, NULL, _IOLBF, 0); -#endif - - qInstallMessageHandler(LogHandler::verboseMessageHandler); - qInfo() << "Starting."; - int currentExitCode = 0; // use a do-while to handle domain-server restart diff --git a/ice-server/src/main.cpp b/ice-server/src/main.cpp index ec8b9957cf..aac6cc0422 100644 --- a/ice-server/src/main.cpp +++ b/ice-server/src/main.cpp @@ -11,18 +11,13 @@ #include -#include +#include #include "IceServer.h" int main(int argc, char* argv[]) { -#ifndef WIN32 - setvbuf(stdout, NULL, _IOLBF, 0); -#endif - - qInstallMessageHandler(LogHandler::verboseMessageHandler); - qInfo() << "Starting."; + setupHifiApplication("Ice Server"); IceServer iceServer(argc, argv); return iceServer.exec(); -} \ No newline at end of file +} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 3aa0f3d889..e956195ca6 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -575,10 +575,7 @@ void messageHandler(QtMsgType type, const QMessageLogContext& context, const QSt QString logMessage = LogHandler::getInstance().printMessage((LogMsgType) type, context, message); if (!logMessage.isEmpty()) { -#ifdef Q_OS_WIN - OutputDebugStringA(logMessage.toLocal8Bit().constData()); - OutputDebugStringA("\n"); -#elif defined Q_OS_ANDROID +#ifdef Q_OS_ANDROID const char * local=logMessage.toStdString().c_str(); switch (type) { case QtDebugMsg: @@ -599,7 +596,7 @@ void messageHandler(QtMsgType type, const QMessageLogContext& context, const QSt abort(); } #endif - qApp->getLogger()->addMessage(qPrintable(logMessage + "\n")); + qApp->getLogger()->addMessage(qPrintable(logMessage)); } } diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 0f8134b253..e80dc1d213 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -38,6 +38,7 @@ extern "C" { #endif int main(int argc, const char* argv[]) { + setupHifiApplication(BuildInfo::INTERFACE_NAME); #ifdef Q_OS_LINUX QApplication::setAttribute(Qt::AA_DontUseNativeMenuBar); @@ -51,19 +52,9 @@ int main(int argc, const char* argv[]) { QCoreApplication::setAttribute(Qt::AA_UseOpenGLES); #endif - disableQtBearerPoll(); // Fixes wifi ping spikes - QElapsedTimer startupTime; startupTime.start(); - // Set application infos - QCoreApplication::setApplicationName(BuildInfo::INTERFACE_NAME); - QCoreApplication::setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION); - QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); - QCoreApplication::setApplicationVersion(BuildInfo::VERSION); - - setupGlobalInstances(); - Setting::init(); // Instance UserActivityLogger now that the settings are loaded diff --git a/libraries/shared/src/LogHandler.cpp b/libraries/shared/src/LogHandler.cpp index aa67c14c4b..ff0d68dcce 100644 --- a/libraries/shared/src/LogHandler.cpp +++ b/libraries/shared/src/LogHandler.cpp @@ -14,6 +14,10 @@ #include +#ifdef Q_OS_WIN +#include +#endif + #include #include #include @@ -184,8 +188,12 @@ QString LogHandler::printMessage(LogMsgType type, const QMessageLogContext& cont } } - QString logMessage = QString("%1 %2").arg(prefixString, message.split('\n').join('\n' + prefixString + " ")); - fprintf(stdout, "%s\n", qPrintable(logMessage)); + QString logMessage = QString("%1 %2\n").arg(prefixString, message.split('\n').join('\n' + prefixString + " ")); + + fprintf(stdout, "%s", qPrintable(logMessage)); +#ifdef Q_OS_WIN + OutputDebugStringA(qPrintable(logMessage)); +#endif return logMessage; } diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index 772340b631..d05e940322 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -59,6 +59,9 @@ extern "C" FILE * __cdecl __iob_func(void) { #include #include +#include + +#include "LogHandler.h" #include "NumericalConstants.h" #include "OctalCode.h" #include "SharedLogging.h" @@ -1179,6 +1182,23 @@ void watchParentProcess(int parentPID) { } #endif +void setupHifiApplication(QString applicationName) { + disableQtBearerPoll(); // Fixes wifi ping spikes + + QCoreApplication::setApplicationName(applicationName); + QCoreApplication::setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION); + QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); + QCoreApplication::setApplicationVersion(BuildInfo::VERSION); + + setupGlobalInstances(); + +#ifndef WIN32 + setvbuf(stdout, NULL, _IOLBF, 0); +#endif + + qInstallMessageHandler(LogHandler::verboseMessageHandler); + qInfo() << "Starting."; +} #ifdef Q_OS_WIN QString getLastErrorAsString() { diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index 64bbc6585b..7cb750d3e3 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -228,6 +228,7 @@ void watchParentProcess(int parentPID); bool processIsRunning(int64_t pid); +void setupHifiApplication(QString applicationName); #ifdef Q_OS_WIN void* createProcessGroup(); diff --git a/tests/entities/src/main.cpp b/tests/entities/src/main.cpp index 792ef7d9c6..bf79f9d3e9 100644 --- a/tests/entities/src/main.cpp +++ b/tests/entities/src/main.cpp @@ -20,6 +20,7 @@ #include #include #include +#include const QString& getTestResourceDir() { static QString dir; @@ -136,6 +137,8 @@ void testPropertyFlags() { } int main(int argc, char** argv) { + setupHifiApplication("Entities Test"); + QCoreApplication app(argc, argv); { auto start = usecTimestampNow(); diff --git a/tests/gpu-test/src/main.cpp b/tests/gpu-test/src/main.cpp index 6a509afe4e..77ce015e3f 100644 --- a/tests/gpu-test/src/main.cpp +++ b/tests/gpu-test/src/main.cpp @@ -45,6 +45,7 @@ #include #include #include +#include #include #include @@ -192,7 +193,9 @@ void testSparseRectify() { } } -int main(int argc, char** argv) { +int main(int argc, char** argv) { + setupHifiApplication("GPU Test"); + testSparseRectify(); // FIXME this test appears to be broken diff --git a/tests/ktx/src/KtxTests.cpp b/tests/ktx/src/KtxTests.cpp index 94e5d7e8e7..65d9cbec3d 100644 --- a/tests/ktx/src/KtxTests.cpp +++ b/tests/ktx/src/KtxTests.cpp @@ -149,7 +149,8 @@ static const QString TEST_FOLDER { "H:/ktx_cacheold" }; static const QString EXTENSIONS { "*.ktx" }; int mainTemp(int, char**) { - qInstallMessageHandler(messageHandler); + setupHifiApplication("KTX Tests"); + auto fileInfoList = QDir { TEST_FOLDER }.entryInfoList(QStringList { EXTENSIONS }); for (auto fileInfo : fileInfoList) { qDebug() << fileInfo.filePath(); diff --git a/tests/qml/src/main.cpp b/tests/qml/src/main.cpp index 219efa5996..022f7290f4 100644 --- a/tests/qml/src/main.cpp +++ b/tests/qml/src/main.cpp @@ -179,18 +179,10 @@ void TestWindow::resizeEvent(QResizeEvent* ev) { resizeWindow(ev->size()); } -void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { - if (!message.isEmpty()) { -#ifdef Q_OS_WIN - OutputDebugStringA(message.toLocal8Bit().constData()); - OutputDebugStringA("\n"); -#endif - } -} +int main(int argc, char** argv) { + setupHifiApplication("QML Test"); -int main(int argc, char** argv) { QGuiApplication app(argc, argv); - qInstallMessageHandler(messageHandler); TestWindow window; app.exec(); return 0; diff --git a/tests/qt59/src/main.cpp b/tests/qt59/src/main.cpp index 7b95cabd6c..19b922de9f 100644 --- a/tests/qt59/src/main.cpp +++ b/tests/qt59/src/main.cpp @@ -63,14 +63,11 @@ void Qt59TestApp::finish(int exitCode) { int main(int argc, char * argv[]) { - QCoreApplication::setApplicationName("Qt59Test"); - QCoreApplication::setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION); - QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); - QCoreApplication::setApplicationVersion(BuildInfo::VERSION); + setupHifiApplication("Qt59Test"); Qt59TestApp app(argc, argv); return app.exec(); } -#include "main.moc" \ No newline at end of file +#include "main.moc" diff --git a/tests/recording/src/main.cpp b/tests/recording/src/main.cpp index f4049b04b7..1b4e5adf6d 100644 --- a/tests/recording/src/main.cpp +++ b/tests/recording/src/main.cpp @@ -10,6 +10,8 @@ #include #include +#include + #include "Constants.h" using namespace recording; @@ -97,18 +99,10 @@ void testClipOrdering() { Q_UNUSED(lastFrameTimeOffset); // FIXME - Unix build not yet upgraded to Qt 5.5.1 we can remove this once it is } -#ifdef Q_OS_WIN32 -void myMessageHandler(QtMsgType type, const QMessageLogContext & context, const QString & msg) { - OutputDebugStringA(msg.toLocal8Bit().toStdString().c_str()); - OutputDebugStringA("\n"); -} -#endif - int main(int, const char**) { -#ifdef Q_OS_WIN32 - qInstallMessageHandler(myMessageHandler); -#endif + setupHifiApplication("Recording Test"); + testFrameTypeRegistration(); testFilePersist(); testClipOrdering(); -} \ No newline at end of file +} diff --git a/tests/render-perf/src/main.cpp b/tests/render-perf/src/main.cpp index 2bf0c74067..ec56555f68 100644 --- a/tests/render-perf/src/main.cpp +++ b/tests/render-perf/src/main.cpp @@ -1138,31 +1138,17 @@ private: bool QTestWindow::_cullingEnabled = true; -void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { - QString logMessage = LogHandler::getInstance().printMessage((LogMsgType)type, context, message); - - if (!logMessage.isEmpty()) { -#ifdef Q_OS_WIN - OutputDebugStringA(logMessage.toLocal8Bit().constData()); - OutputDebugStringA("\n"); -#endif - logger->addMessage(qPrintable(logMessage + "\n")); - } -} - const char * LOG_FILTER_RULES = R"V0G0N( hifi.gpu=true )V0G0N"; int main(int argc, char** argv) { + setupHifiApplication("RenderPerf"); + QApplication app(argc, argv); - QCoreApplication::setApplicationName("RenderPerf"); - QCoreApplication::setOrganizationName("High Fidelity"); - QCoreApplication::setOrganizationDomain("highfidelity.com"); logger.reset(new FileLogger()); - qInstallMessageHandler(messageHandler); QLoggingCategory::setFilterRules(LOG_FILTER_RULES); QTestWindow::setup(); QTestWindow window; diff --git a/tests/render-texture-load/src/main.cpp b/tests/render-texture-load/src/main.cpp index 066b39fc40..62c970cab5 100644 --- a/tests/render-texture-load/src/main.cpp +++ b/tests/render-texture-load/src/main.cpp @@ -610,16 +610,6 @@ private: bool _ready { false }; }; -void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { - if (!message.isEmpty()) { -#ifdef Q_OS_WIN - OutputDebugStringA(message.toLocal8Bit().constData()); - OutputDebugStringA("\n"); -#endif - std::cout << message.toLocal8Bit().constData() << std::endl; - } -} - const char * LOG_FILTER_RULES = R"V0G0N( hifi.gpu=true )V0G0N"; @@ -645,11 +635,9 @@ void unzipTestData(const QByteArray& zipData) { } int main(int argc, char** argv) { + setupHifiApplication("RenderPerf"); + QApplication app(argc, argv); - QCoreApplication::setApplicationName("RenderPerf"); - QCoreApplication::setOrganizationName("High Fidelity"); - QCoreApplication::setOrganizationDomain("highfidelity.com"); - qInstallMessageHandler(messageHandler); QLoggingCategory::setFilterRules(LOG_FILTER_RULES); if (!DATA_DIR.exists()) { diff --git a/tests/render-utils/src/main.cpp b/tests/render-utils/src/main.cpp index 741fdbdddd..e30a80f3d9 100644 --- a/tests/render-utils/src/main.cpp +++ b/tests/render-utils/src/main.cpp @@ -179,25 +179,14 @@ void QTestWindow::draw() { } } -void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { - if (!message.isEmpty()) { -#ifdef Q_OS_WIN - OutputDebugStringA(message.toLocal8Bit().constData()); - OutputDebugStringA("\n"); -#else - std::cout << message.toLocal8Bit().constData() << std::endl; -#endif - } -} - - const char * LOG_FILTER_RULES = R"V0G0N( hifi.gpu=true )V0G0N"; -int main(int argc, char** argv) { +int main(int argc, char** argv) { + setupHifiApplication("Render Utils Test"); + QGuiApplication app(argc, argv); - qInstallMessageHandler(messageHandler); QLoggingCategory::setFilterRules(LOG_FILTER_RULES); QTestWindow window; QTimer timer; diff --git a/tests/shaders/src/main.cpp b/tests/shaders/src/main.cpp index 585bc432a9..a3b4196031 100644 --- a/tests/shaders/src/main.cpp +++ b/tests/shaders/src/main.cpp @@ -214,24 +214,14 @@ void QTestWindow::draw() { _context.swapBuffers(this); } -void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { - if (!message.isEmpty()) { -#ifdef Q_OS_WIN - OutputDebugStringA(message.toLocal8Bit().constData()); - OutputDebugStringA("\n"); -#else - std::cout << message.toLocal8Bit().constData() << std::endl; -#endif - } -} - const char * LOG_FILTER_RULES = R"V0G0N( hifi.gpu=true )V0G0N"; -int main(int argc, char** argv) { +int main(int argc, char** argv) { + setupHifiApplication("Shaders Test"); + QGuiApplication app(argc, argv); - qInstallMessageHandler(messageHandler); QLoggingCategory::setFilterRules(LOG_FILTER_RULES); QTestWindow window; QTimer timer; diff --git a/tools/ac-client/src/main.cpp b/tools/ac-client/src/main.cpp index 139e44bc9a..024a891d3c 100644 --- a/tools/ac-client/src/main.cpp +++ b/tools/ac-client/src/main.cpp @@ -20,12 +20,7 @@ using namespace std; int main(int argc, char* argv[]) { - QCoreApplication::setApplicationName(BuildInfo::AC_CLIENT_SERVER_NAME); - QCoreApplication::setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION); - QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); - QCoreApplication::setApplicationVersion(BuildInfo::VERSION); - - setupGlobalInstances(); + setupHifiApplication(BuildInfo::AC_CLIENT_SERVER_NAME); Setting::init(); diff --git a/tools/atp-client/src/main.cpp b/tools/atp-client/src/main.cpp index 0a8274fedd..2a267c088c 100644 --- a/tools/atp-client/src/main.cpp +++ b/tools/atp-client/src/main.cpp @@ -20,12 +20,7 @@ using namespace std; int main(int argc, char * argv[]) { - QCoreApplication::setApplicationName(BuildInfo::AC_CLIENT_SERVER_NAME); - QCoreApplication::setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION); - QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); - QCoreApplication::setApplicationVersion(BuildInfo::VERSION); - - setupGlobalInstances(); + setupHifiApplication("ATP Client"); Setting::init(); diff --git a/tools/auto-tester/src/main.cpp b/tools/auto-tester/src/main.cpp index 45a3743482..6e5e06b732 100644 --- a/tools/auto-tester/src/main.cpp +++ b/tools/auto-tester/src/main.cpp @@ -17,4 +17,4 @@ int main(int argc, char *argv[]) { autoTester.show(); return application.exec(); -} \ No newline at end of file +} diff --git a/tools/ice-client/src/main.cpp b/tools/ice-client/src/main.cpp index c70a7eb7d7..60a5d4e0e4 100644 --- a/tools/ice-client/src/main.cpp +++ b/tools/ice-client/src/main.cpp @@ -13,11 +13,15 @@ #include #include +#include + #include "ICEClientApp.h" using namespace std; int main(int argc, char * argv[]) { + setupHifiApplication("ICE Client"); + ICEClientApp app(argc, argv); return app.exec(); } diff --git a/tools/oven/src/main.cpp b/tools/oven/src/main.cpp index be0c22a0c6..3f4afe1f15 100644 --- a/tools/oven/src/main.cpp +++ b/tools/oven/src/main.cpp @@ -15,12 +15,7 @@ #include int main (int argc, char** argv) { - QCoreApplication::setApplicationName("Oven"); - QCoreApplication::setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION); - QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); - QCoreApplication::setApplicationVersion(BuildInfo::VERSION); - - setupGlobalInstances(); + setupHifiApplication("Oven"); // init the settings interface so we can save and load settings Setting::init(); diff --git a/tools/skeleton-dump/src/main.cpp b/tools/skeleton-dump/src/main.cpp index 6cf4d41f31..d5919c4a88 100644 --- a/tools/skeleton-dump/src/main.cpp +++ b/tools/skeleton-dump/src/main.cpp @@ -14,9 +14,13 @@ #include #include +#include + #include "SkeletonDumpApp.h" int main(int argc, char * argv[]) { + setupHifiApplication("Skeleton Dump App"); + SkeletonDumpApp app(argc, argv); return app.getReturnCode(); } diff --git a/tools/udt-test/src/UDTTest.cpp b/tools/udt-test/src/UDTTest.cpp index 6161dbfdbc..ce89f04ce5 100644 --- a/tools/udt-test/src/UDTTest.cpp +++ b/tools/udt-test/src/UDTTest.cpp @@ -71,8 +71,6 @@ const QStringList SERVER_STATS_TABLE_HEADERS { UDTTest::UDTTest(int& argc, char** argv) : QCoreApplication(argc, argv) { - qInstallMessageHandler(LogHandler::verboseMessageHandler); - parseArguments(); // randomize the seed for packet size randomization diff --git a/tools/udt-test/src/main.cpp b/tools/udt-test/src/main.cpp index ccb7d0af0f..d88218c0d0 100644 --- a/tools/udt-test/src/main.cpp +++ b/tools/udt-test/src/main.cpp @@ -10,9 +10,13 @@ #include +#include + #include "UDTTest.h" int main(int argc, char* argv[]) { + setupHifiApplication("UDT Test); + UDTTest app(argc, argv); return app.exec(); } diff --git a/tools/vhacd-util/src/main.cpp b/tools/vhacd-util/src/main.cpp index 42c9db9513..817e77bf8e 100644 --- a/tools/vhacd-util/src/main.cpp +++ b/tools/vhacd-util/src/main.cpp @@ -15,6 +15,8 @@ #include #include +#include + #include "VHACDUtilApp.h" using namespace std; @@ -22,6 +24,8 @@ using namespace VHACD; int main(int argc, char * argv[]) { + setupHifiApplication("VHACD Util"); + VHACDUtilApp app(argc, argv); return app.getReturnCode(); } From d1c4bde677bb5949751488eeb8ed8937c83fe38a Mon Sep 17 00:00:00 2001 From: humbletim Date: Thu, 22 Feb 2018 20:53:27 -0500 Subject: [PATCH 35/49] revert OBJWriter.cpp --- libraries/fbx/src/OBJWriter.cpp | 85 +++++++++++---------------------- 1 file changed, 28 insertions(+), 57 deletions(-) diff --git a/libraries/fbx/src/OBJWriter.cpp b/libraries/fbx/src/OBJWriter.cpp index ffc34573d4..4441ae6649 100644 --- a/libraries/fbx/src/OBJWriter.cpp +++ b/libraries/fbx/src/OBJWriter.cpp @@ -9,12 +9,10 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "OBJWriter.h" - #include #include -#include -#include +#include "graphics/Geometry.h" +#include "OBJWriter.h" #include "ModelFormatLogging.h" static QString formatFloat(double n) { @@ -48,12 +46,9 @@ 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(); @@ -70,10 +65,7 @@ bool writeOBJToTextStream(QTextStream& out, QList meshes) { out << formatFloat(v[1]) << " "; out << formatFloat(v[2]); if (colorIndex < numColors) { - glm::vec3 color = buffer_helpers::convert(colorsBufferView, colorIndex); - // TODO: still verifying that the above decodes properly; previous variations were: - // glm::vec3 color = buffer_helpers::glmVecFromVariant(buffer_helpers::toVariant(colorsBufferView, colorIndex)); - // glm::vec3 color = colorsBufferView.get(colorIndex); + glm::vec3 color = colorsBufferView.get(colorIndex); out << " " << formatFloat(color[0]); out << " " << formatFloat(color[1]); out << " " << formatFloat(color[2]); @@ -89,17 +81,12 @@ bool writeOBJToTextStream(QTextStream& out, QList meshes) { // 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 = buffer_helpers::convert(normalsBufferView, i); - // TODO: still verifying that the above decodes properly; previous variations were: - // glm::vec3 normal = buffer_helpers::glmVecFromVariant(buffer_helpers::toVariant(normalsBufferView, i)); - // glm::vec3 normal = normalsBufferView.get(i); + glm::vec3 normal = normalsBufferView.get(i); out << "vn "; out << formatFloat(normal[0]) << " "; out << formatFloat(normal[1]) << " "; @@ -111,9 +98,7 @@ bool writeOBJToTextStream(QTextStream& out, QList meshes) { // write out faces int nth = 0; - subMeshIndex = 0; foreach (const MeshPointer& mesh, meshes) { - out << "# faces::subMeshIndex " << subMeshIndex++ << "\n"; currentVertexStartOffset = meshVertexStartOffset.takeFirst(); currentNormalStartOffset = meshNormalStartOffset.takeFirst(); @@ -121,25 +106,35 @@ 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 << QString("g %1-%2-%3\n").arg(subMeshIndex, 3, 10, QChar('0')).arg(name).arg(partIndex); + out << "g part-" << nth++ << "\n"; - const bool shorts = indexBuffer._element == gpu::Element::INDEX_UINT16; - auto face = [&](uint32_t i0, uint32_t i1, uint32_t i2) { - uint32_t index0, index1, index2; - if (shorts) { - index0 = indexBuffer.get(i0); - index1 = indexBuffer.get(i1); - index2 = indexBuffer.get(i2); - } else { - index0 = indexBuffer.get(i0); - index1 = indexBuffer.get(i1); - index2 = indexBuffer.get(i2); + // 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 << "f "; if (haveNormals) { @@ -151,30 +146,6 @@ bool writeOBJToTextStream(QTextStream& out, QList meshes) { out << currentVertexStartOffset + index1 + 1 << " "; out << currentVertexStartOffset + index2 + 1 << "\n"; } - }; - - // graphics::Mesh::TRIANGLES / graphics::Mesh::QUADS - // TODO -- handle other formats - uint32_t len = part._startIndex + part._numIndices; - qCDebug(modelformat) << "OBJWriter -- part" << partIndex << "topo" << part._topology << "index elements" << (shorts ? "uint16_t" : "uint32_t"); - 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"; } From 0dd367216278a5bf640eca89774ddf1043c62b57 Mon Sep 17 00:00:00 2001 From: humbletim Date: Thu, 22 Feb 2018 21:20:56 -0500 Subject: [PATCH 36/49] CR feedback / cleanup --- .../BufferViewScripting.cpp | 13 +--- .../src/graphics-scripting/DebugNames.h | 72 ------------------- .../GraphicsScriptingInterface.cpp | 1 - .../GraphicsScriptingInterface.h | 3 +- .../src/graphics-scripting/ScriptableMesh.cpp | 25 ++----- .../src/graphics-scripting/ScriptableMesh.h | 2 + .../graphics-scripting/ScriptableModel.cpp | 20 ++---- .../src/graphics-scripting/ScriptableModel.h | 1 + .../src/graphics/BufferViewHelpers.cpp | 6 +- .../graphics/src/graphics/BufferViewHelpers.h | 3 + 10 files changed, 22 insertions(+), 124 deletions(-) delete mode 100644 libraries/graphics-scripting/src/graphics-scripting/DebugNames.h diff --git a/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp b/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp index 31cf5ff8f3..9d7a0e1f30 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp @@ -11,15 +11,6 @@ #include -#ifdef DEBUG_BUFFERVIEW_SCRIPTING - #include "DebugNames.h" -#endif - -namespace { - const std::array XYZW = {{ "x", "y", "z", "w" }}; - const std::array ZERO123 = {{ "0", "1", "2", "3" }}; -} - template QScriptValue getBufferViewElement(QScriptEngine* js, const gpu::BufferView& view, quint32 index, bool asArray = false) { return glmVecToScriptValue(js, view.get(index), asArray); @@ -45,7 +36,7 @@ bool bufferViewElementFromScriptValue(const QScriptValue& v, const gpu::BufferVi template QScriptValue glmVecToScriptValue(QScriptEngine *js, const T& v, bool asArray) { static const auto len = T().length(); - const auto& components = asArray ? ZERO123 : XYZW; + 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]; @@ -65,7 +56,7 @@ QScriptValue glmVecToScriptValue(QScriptEngine *js, const T& v, bool asArray) { template const T glmVecFromScriptValue(const QScriptValue& v) { static const auto len = T().length(); - const auto& components = v.property("x").isValid() ? XYZW : ZERO123; + 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]; diff --git a/libraries/graphics-scripting/src/graphics-scripting/DebugNames.h b/libraries/graphics-scripting/src/graphics-scripting/DebugNames.h deleted file mode 100644 index e5edf1c9d8..0000000000 --- a/libraries/graphics-scripting/src/graphics-scripting/DebugNames.h +++ /dev/null @@ -1,72 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -//#include - -Q_DECLARE_METATYPE(gpu::Type); -#ifdef QT_MOC_RUN -class DebugNames { - Q_OBJECT -public: -#else - namespace DebugNames { - Q_NAMESPACE - #endif - -enum Type : uint8_t { - - FLOAT = 0, - INT32, - UINT32, - HALF, - INT16, - UINT16, - INT8, - UINT8, - - NINT32, - NUINT32, - NINT16, - NUINT16, - NINT8, - NUINT8, - - COMPRESSED, - - NUM_TYPES, - - BOOL = UINT8, - NORMALIZED_START = NINT32, -}; - - Q_ENUM_NS(Type) - enum InputSlot { - POSITION = 0, - NORMAL = 1, - COLOR = 2, - TEXCOORD0 = 3, - TEXCOORD = TEXCOORD0, - TANGENT = 4, - SKIN_CLUSTER_INDEX = 5, - SKIN_CLUSTER_WEIGHT = 6, - TEXCOORD1 = 7, - TEXCOORD2 = 8, - TEXCOORD3 = 9, - TEXCOORD4 = 10, - - NUM_INPUT_SLOTS, - - DRAW_CALL_INFO = 15, // Reserve last input slot for draw call infos - }; - - Q_ENUM_NS(InputSlot) - inline QString stringFrom(Type t) { return QVariant::fromValue(t).toString(); } - inline QString stringFrom(InputSlot t) { return QVariant::fromValue(t).toString(); } - inline QString stringFrom(gpu::Type t) { return stringFrom((Type)t); } - inline QString stringFrom(gpu::Stream::Slot t) { return stringFrom((InputSlot)t); } - - extern const QMetaObject staticMetaObject; - }; diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp index 28d5d0edfc..01e68c5328 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp @@ -11,7 +11,6 @@ #include "GraphicsScriptingInterface.h" #include "BaseScriptEngine.h" #include "BufferViewScripting.h" -#include "DebugNames.h" #include "GraphicsScriptingUtil.h" #include "OBJWriter.h" #include "RegisteredMetaTypes.h" diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h index 6afa549a19..ab2e5467db 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h @@ -24,6 +24,7 @@ class GraphicsScriptingInterface : public QObject, public QScriptable, public De Q_OBJECT public: + static void registerMetaTypes(QScriptEngine* engine); GraphicsScriptingInterface(QObject* parent = nullptr); public slots: @@ -39,8 +40,6 @@ public slots: QString meshToOBJ(const scriptable::ScriptableModel& in); - static void registerMetaTypes(QScriptEngine* engine); - private: scriptable::MeshPointer getMeshPointer(scriptable::ScriptableMeshPointer meshProxy); scriptable::MeshPointer getMeshPointer(scriptable::ScriptableMesh& meshProxy); diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp index 3678c54b7b..643debf475 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp @@ -8,7 +8,6 @@ #include "ScriptableMesh.h" #include "BufferViewScripting.h" -#include "DebugNames.h" #include "GraphicsScriptingUtil.h" #include "OBJWriter.h" #include @@ -508,14 +507,11 @@ scriptable::ScriptableMeshPointer scriptable::ScriptableMesh::cloneMesh(bool rec qCInfo(graphics_scripting) << "ScriptableMesh::cloneMesh -- !meshPointer"; return nullptr; } - // qCInfo(graphics_scripting) << "ScriptableMesh::cloneMesh..."; auto clone = buffer_helpers::cloneMesh(mesh); - // qCInfo(graphics_scripting) << "ScriptableMesh::cloneMesh..."; if (recalcNormals) { buffer_helpers::recalculateNormals(clone); } - //qCDebug(graphics_scripting) << clone.get();// << metadata; auto meshPointer = scriptable::make_scriptowned(provider, model, clone, metadata); clone.reset(); // free local reference // qCInfo(graphics_scripting) << "========= ScriptableMesh::cloneMesh..." << meshPointer << meshPointer->ownedMesh.use_count(); @@ -549,7 +545,6 @@ scriptable::ScriptableMeshBase::ScriptableMeshBase(scriptable::MeshPointer mesh, : ScriptableMeshBase(WeakModelProviderPointer(), nullptr, mesh, metadata) { ownedMesh = mesh; } -//scriptable::ScriptableMeshBase::ScriptableMeshBase(const scriptable::ScriptableMeshBase& other) { *this = other; } scriptable::ScriptableMeshBase& scriptable::ScriptableMeshBase::operator=(const scriptable::ScriptableMeshBase& view) { provider = view.provider; model = view.model; @@ -617,22 +612,6 @@ namespace { void meshPartPointerFromScriptValue(const QScriptValue& value, scriptable::ScriptableMeshPartPointer &out) { out = scriptable::qpointer_qobject_cast(value); } - - // FIXME: MESHFACES: - // QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const mesh::MeshFace &meshFace) { - // QScriptValue obj = engine->newObject(); - // obj.setProperty("vertices", qVectorIntToScriptValue(engine, meshFace.vertexIndices)); - // return obj; - // } - // void meshFaceFromScriptValue(const QScriptValue &object, mesh::MeshFace& meshFaceResult) { - // qScriptValueToSequence(object.property("vertices"), meshFaceResult.vertexIndices); - // } - // QScriptValue qVectorMeshFaceToScriptValue(QScriptEngine* engine, const QVector& vector) { - // return qScriptValueFromSequence(engine, vector); - // } - // void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector& result) { - // qScriptValueToSequence(array, result); - // } QScriptValue qVectorUInt32ToScriptValue(QScriptEngine* engine, const QVector& vector) { return qScriptValueFromSequence(engine, vector); @@ -700,11 +679,15 @@ bool scriptable::GraphicsScriptingInterface::updateMeshPart(ScriptableMeshPointe Q_ASSERT(part->parentMesh); auto tmp = exportMeshPart(mesh, part->partIndex); if (part->parentMesh == mesh) { +#ifdef SCRIPTABLE_MESH_DEBUG qCInfo(graphics_scripting) << "updateMeshPart -- update via clone" << mesh << part; +#endif tmp->replaceMeshData(part->cloneMeshPart()); return false; } else { +#ifdef SCRIPTABLE_MESH_DEBUG qCInfo(graphics_scripting) << "updateMeshPart -- update via inplace" << mesh << part; +#endif tmp->replaceMeshData(part); return true; } diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h index 2cba33edde..459613135a 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h @@ -16,6 +16,8 @@ #include #include +#include "GraphicsScriptingUtil.h" + namespace scriptable { class ScriptableMesh : public ScriptableMeshBase, QScriptable { Q_OBJECT diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp index cc882aa7bb..229d56adab 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp @@ -41,34 +41,29 @@ scriptable::ScriptableModelBase::~ScriptableModelBase() { #ifdef SCRIPTABLE_MESH_DEBUG qCDebug(graphics_scripting) << "~ScriptableModelBase" << this; #endif + // makes cleanup order more deterministic to help with debugging for (auto& m : meshes) { m.ownedMesh.reset(); - //qCDebug(graphics_scripting) << "~~~~ScriptableModelBase" << &m << m.mesh.use_count(); } meshes.clear(); - //qCDebug(graphics_scripting) << "//~ScriptableModelBase" << this; } void scriptable::ScriptableModelBase::append(scriptable::WeakMeshPointer mesh, const QVariantMap& metadata) { - //qCDebug(graphics_scripting) << "+ APPEND WeakMeshPointer" << mesh.lock().get(); meshes << ScriptableMeshBase{ provider, this, mesh, metadata }; } void scriptable::ScriptableModelBase::append(const ScriptableMeshBase& mesh, const QVariantMap& modelMetaData) { - //qCDebug(graphics_scripting) << "+ APPEND ScriptableMeshBase" << &mesh; if (mesh.provider.lock().get() != provider.lock().get()) { qCDebug(graphics_scripting) << "warning: appending mesh from different provider..." << mesh.provider.lock().get() << " != " << provider.lock().get(); } - //if (mesh.model && mesh.model != this) { - // qCDebug(graphics_scripting) << "warning: appending mesh from different model..." << mesh.model << " != " << this; - //} meshes << mesh; mixin(modelMetaData); } void scriptable::ScriptableModelBase::append(const ScriptableModelBase& other, const QVariantMap& modelMetaData) { - //qCDebug(graphics_scripting) << "+ APPEND ScriptableModelBase" << &other; - for (const auto& mesh : other.meshes) { append(mesh); } + for (const auto& mesh : other.meshes) { + append(mesh); + } mixin(other.metadata); mixin(modelMetaData); } @@ -82,21 +77,16 @@ QString scriptable::ScriptableModel::toString() const { scriptable::ScriptableModelPointer scriptable::ScriptableModel::cloneModel(const QVariantMap& options) { scriptable::ScriptableModelPointer clone = scriptable::ScriptableModelPointer(new scriptable::ScriptableModel(*this)); - qCDebug(graphics_scripting) << "clone->getNumMeshes" << clone->getNumMeshes(); clone->meshes.clear(); - qCDebug(graphics_scripting) << "..clone->getNumMeshes" << clone->getNumMeshes(); for (const auto &mesh : getConstMeshes()) { auto cloned = mesh->cloneMesh(options.value("recalculateNormals").toBool()); if (auto tmp = qobject_cast(cloned)) { - qCDebug(graphics_scripting) << "++ APPEND" << tmp << tmp->ownedMesh.use_count() << tmp->metadata.value("__ownership__") << tmp->metadata.value("__native__"); clone->meshes << *tmp; - tmp->deleteLater(); - qCDebug(graphics_scripting) << "//++ APPEND" << clone->meshes.constLast().ownedMesh.use_count();; + tmp->deleteLater(); // schedule our copy for cleanup } else { qCDebug(graphics_scripting) << "error cloning mesh" << cloned; } } - qCDebug(graphics_scripting) << "//clone->getNumMeshes" << clone->getNumMeshes(); return clone; } diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h index bdfaac5d7c..a78c9a4ef5 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h @@ -1,6 +1,7 @@ #pragma once #include "Forward.h" +#include "GraphicsScriptingUtil.h" class QScriptValue; namespace scriptable { diff --git a/libraries/graphics/src/graphics/BufferViewHelpers.cpp b/libraries/graphics/src/graphics/BufferViewHelpers.cpp index a15ce1b998..ddf3d84c89 100644 --- a/libraries/graphics/src/graphics/BufferViewHelpers.cpp +++ b/libraries/graphics/src/graphics/BufferViewHelpers.cpp @@ -34,10 +34,12 @@ namespace glm { namespace { QLoggingCategory bufferhelper_logging{ "hifi.bufferview" }; - const std::array XYZW = { { "x", "y", "z", "w" } }; - const std::array ZERO123 = { { "0", "1", "2", "3" } }; } + +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) { return slot == gpu::Stream::POSITION ? mesh->getVertexBuffer() : mesh->getAttributeBuffer(slot); } diff --git a/libraries/graphics/src/graphics/BufferViewHelpers.h b/libraries/graphics/src/graphics/BufferViewHelpers.h index 53fddfe581..6d4908a2c7 100644 --- a/libraries/graphics/src/graphics/BufferViewHelpers.h +++ b/libraries/graphics/src/graphics/BufferViewHelpers.h @@ -49,4 +49,7 @@ struct buffer_helpers { static gpu::BufferView resize(const gpu::BufferView& input, quint32 numElements); static void packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, glm::uint32& packedNormal, glm::uint32& packedTangent); + + static const std::array XYZW; + static const std::array ZERO123; }; From 6ea4b660b747745bde7c3466c3721713f6ee99c7 Mon Sep 17 00:00:00 2001 From: humbletim Date: Fri, 23 Feb 2018 07:47:39 -0500 Subject: [PATCH 37/49] CR feedback; remove ok bool pointer and all metadata; cleanup --- interface/src/ui/overlays/Base3DOverlay.h | 2 +- interface/src/ui/overlays/ModelOverlay.cpp | 8 +- interface/src/ui/overlays/ModelOverlay.h | 2 +- interface/src/ui/overlays/Shape3DOverlay.cpp | 7 +- interface/src/ui/overlays/Shape3DOverlay.h | 2 +- .../src/avatars-renderer/Avatar.cpp | 17 +- .../src/avatars-renderer/Avatar.h | 2 +- .../src/RenderableEntityItem.h | 2 +- .../src/RenderableModelEntityItem.cpp | 8 +- .../src/RenderableModelEntityItem.h | 2 +- .../src/RenderablePolyLineEntityItem.cpp | 4 +- .../src/RenderablePolyLineEntityItem.h | 2 +- .../src/RenderablePolyVoxEntityItem.cpp | 7 +- .../src/RenderablePolyVoxEntityItem.h | 6 +- .../src/RenderableShapeEntityItem.cpp | 11 +- .../src/RenderableShapeEntityItem.h | 2 +- .../src/graphics-scripting/Forward.h | 26 +-- .../GraphicsScriptingInterface.cpp | 174 +++++++++++------- .../GraphicsScriptingInterface.h | 22 ++- .../GraphicsScriptingUtil.h | 22 +-- .../src/graphics-scripting/ScriptableMesh.cpp | 149 ++++++--------- .../src/graphics-scripting/ScriptableMesh.h | 68 ++----- .../graphics-scripting/ScriptableModel.cpp | 41 ++--- .../src/graphics-scripting/ScriptableModel.h | 15 +- .../src/graphics/BufferViewHelpers.cpp | 42 +---- libraries/render-utils/src/Model.cpp | 53 +++--- libraries/render-utils/src/Model.h | 2 +- .../src/Model_temporary_hack.cpp.h | 46 +---- tools/jsdoc/package-lock.json | 138 -------------- 29 files changed, 307 insertions(+), 575 deletions(-) delete mode 100644 tools/jsdoc/package-lock.json diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index 25bacf2e7e..bbf064fddd 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -36,7 +36,7 @@ public: virtual bool is3D() const override { return true; } virtual uint32_t fetchMetaSubItems(render::ItemIDs& subItems) const override { subItems.push_back(getRenderItemID()); return (uint32_t) subItems.size(); } - virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) override { return scriptable::ModelProvider::modelUnavailableError(ok); } + virtual scriptable::ScriptableModelBase getScriptableModel() override { return scriptable::ScriptableModelBase(); } // TODO: consider implementing registration points in this class glm::vec3 getCenter() const { return getWorldPosition(); } diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index 4ff5499a12..ffcc18032c 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -665,11 +665,11 @@ void ModelOverlay::processMaterials() { } } -scriptable::ScriptableModelBase ModelOverlay::getScriptableModel(bool* ok) { +scriptable::ScriptableModelBase ModelOverlay::getScriptableModel() { if (!_model || !_model->isLoaded()) { - return Base3DOverlay::getScriptableModel(ok); + return Base3DOverlay::getScriptableModel(); } - auto result = _model->getScriptableModel(ok); + auto result = _model->getScriptableModel(); result.objectID = getID(); return result; -} \ No newline at end of file +} diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index 60b970425d..fa399d40f9 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -62,7 +62,7 @@ public: void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) override; void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) override; - virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) override; + virtual scriptable::ScriptableModelBase getScriptableModel() override; protected: Transform evalRenderTransform() override; diff --git a/interface/src/ui/overlays/Shape3DOverlay.cpp b/interface/src/ui/overlays/Shape3DOverlay.cpp index 04557bcf5d..8f7f8b70d9 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.cpp +++ b/interface/src/ui/overlays/Shape3DOverlay.cpp @@ -180,14 +180,13 @@ Transform Shape3DOverlay::evalRenderTransform() { return transform; } -scriptable::ScriptableModelBase Shape3DOverlay::getScriptableModel(bool* ok) { +scriptable::ScriptableModelBase Shape3DOverlay::getScriptableModel() { auto geometryCache = DependencyManager::get(); auto vertexColor = ColorUtils::toVec3(_color); scriptable::ScriptableModelBase result; result.objectID = getID(); - result.append(geometryCache->meshFromShape(_shape, vertexColor), {{ "shape", shapeStrings[_shape] }}); - if (ok) { - *ok = true; + if (auto mesh = geometryCache->meshFromShape(_shape, vertexColor)) { + result.append(mesh); } return result; } diff --git a/interface/src/ui/overlays/Shape3DOverlay.h b/interface/src/ui/overlays/Shape3DOverlay.h index f5246d95ac..447ee47e13 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.h +++ b/interface/src/ui/overlays/Shape3DOverlay.h @@ -37,7 +37,7 @@ public: void setProperties(const QVariantMap& properties) override; QVariant getProperty(const QString& property) override; - virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) 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 48f0c70dfa..25ddfba670 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -1795,22 +1795,11 @@ void Avatar::processMaterials() { } } -scriptable::ScriptableModelBase Avatar::getScriptableModel(bool* ok) { +scriptable::ScriptableModelBase Avatar::getScriptableModel() { if (!_skeletonModel || !_skeletonModel->isLoaded()) { - return scriptable::ModelProvider::modelUnavailableError(ok); + return scriptable::ScriptableModelBase(); } - scriptable::ScriptableModelBase result = _skeletonModel->getScriptableModel(ok); + auto result = _skeletonModel->getScriptableModel(); result.objectID = getSessionUUID(); - result.mixin({{ "textures", _skeletonModel->getTextures() }}); - // FIXME: for now access to attachment models are merged into the main avatar ScriptableModel set - for (int i = 0; i < (int)_attachmentModels.size(); i++) { - auto& model = _attachmentModels.at(i); - if (model->isLoaded()) { - result.append(model->getScriptableModel(ok), _attachmentData.at(i).toVariant().toMap()); - } - } - if (ok) { - *ok = true; - } return result; } \ No newline at end of file diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 3b3917ad4d..bf0adc5c05 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -276,7 +276,7 @@ public: void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) override; void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) override; - virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) override; + virtual scriptable::ScriptableModelBase getScriptableModel() override; public slots: diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index 5526927c13..d34a1127ae 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -58,7 +58,7 @@ public: virtual void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName); virtual void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName); - virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) override { return scriptable::ModelProvider::modelUnavailableError(ok); } + virtual scriptable::ScriptableModelBase getScriptableModel() override { return scriptable::ScriptableModelBase(); } protected: virtual bool needsRenderUpdateFromEntity() const final { return needsRenderUpdateFromEntity(_entity); } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 6eed62522f..f3c99cde2d 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -960,15 +960,15 @@ bool RenderableModelEntityItem::getMeshes(MeshProxyList& result) { return !result.isEmpty(); } -scriptable::ScriptableModelBase render::entities::ModelEntityRenderer::getScriptableModel(bool* ok) { +scriptable::ScriptableModelBase render::entities::ModelEntityRenderer::getScriptableModel() { ModelPointer model; withReadLock([&] { model = _model; }); if (!model || !model->isLoaded()) { - return scriptable::ModelProvider::modelUnavailableError(ok); + return scriptable::ScriptableModelBase(); } - auto result = _model->getScriptableModel(ok); + auto result = _model->getScriptableModel(); result.objectID = getEntity()->getID(); return result; } @@ -981,7 +981,7 @@ bool render::entities::ModelEntityRenderer::replaceScriptableModelMeshPart(scrip return false; } - return model->replaceScriptableModelMeshPart(newModel, meshIndex, partIndex); + return model->replaceScriptableModelMeshPart(newModel, meshIndex, partIndex); } void RenderableModelEntityItem::simulateRelayedJoints() { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index dc73add823..7edaef264d 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -142,7 +142,7 @@ class ModelEntityRenderer : public TypedEntityRenderer PolyLineEntityRenderer::updateVertic return vertices; } -scriptable::ScriptableModelBase PolyLineEntityRenderer::getScriptableModel(bool *ok) { +scriptable::ScriptableModelBase PolyLineEntityRenderer::getScriptableModel() { // TODO: adapt polyline into a triangles mesh... - return EntityRenderer::getScriptableModel(ok); + return EntityRenderer::getScriptableModel(); } void PolyLineEntityRenderer::doRender(RenderArgs* args) { diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h index d9d770e64f..f460baac59 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h @@ -25,7 +25,7 @@ class PolyLineEntityRenderer : public TypedEntityRenderer { public: PolyLineEntityRenderer(const EntityItemPointer& entity); - virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) override; + virtual scriptable::ScriptableModelBase getScriptableModel() override; protected: virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override; virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 37e03b2590..2b1de8d11b 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -1451,9 +1451,9 @@ bool RenderablePolyVoxEntityItem::getMeshes(MeshProxyList& result) { return success; } -scriptable::ScriptableModelBase RenderablePolyVoxEntityItem::getScriptableModel(bool * ok) { +scriptable::ScriptableModelBase RenderablePolyVoxEntityItem::getScriptableModel() { if (!updateDependents() || !_mesh) { - return scriptable::ModelProvider::modelUnavailableError(ok); + return scriptable::ScriptableModelBase(); } bool success = false; @@ -1479,9 +1479,6 @@ scriptable::ScriptableModelBase RenderablePolyVoxEntityItem::getScriptableModel( )); } }); - if (ok) { - *ok = success; - } return result; } diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index 295a4066ba..0a00d1cb73 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -114,7 +114,7 @@ public: void setVolDataDirty() { withWriteLock([&] { _volDataDirty = true; _meshReady = false; }); } bool getMeshes(MeshProxyList& result) override; // deprecated - virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) override; + virtual scriptable::ScriptableModelBase getScriptableModel() override; private: bool updateOnCount(const ivec3& v, uint8_t toValue); @@ -164,8 +164,8 @@ class PolyVoxEntityRenderer : public TypedEntityRenderer()->getScriptableModel(ok); + virtual scriptable::ScriptableModelBase getScriptableModel() override { + return asTypedEntity()->getScriptableModel(); } protected: diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index 2e65eba5e5..22cd98b08a 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -164,22 +164,17 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { args->_details._trianglesRendered += (int)triCount; } -scriptable::ScriptableModelBase ShapeEntityRenderer::getScriptableModel(bool* ok) { +scriptable::ScriptableModelBase ShapeEntityRenderer::getScriptableModel() { scriptable::ScriptableModelBase result; - result.objectID = getEntity()->getID(); auto geometryCache = DependencyManager::get(); auto geometryShape = geometryCache->getShapeForEntityShape(_shape); glm::vec3 vertexColor; if (_materials["0"].top().material) { vertexColor = _materials["0"].top().material->getAlbedo(); } - auto success = false; if (auto mesh = geometryCache->meshFromShape(geometryShape, vertexColor)) { - result.append({ mesh, {{ "shape", entity::stringFromShape(_shape) }}}); - success = true; - } - if (ok) { - *ok = success; + result.objectID = getEntity()->getID(); + result.append(mesh); } return result; } diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.h b/libraries/entities-renderer/src/RenderableShapeEntityItem.h index bd6c77d81d..de855ce0c6 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.h +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.h @@ -22,7 +22,7 @@ class ShapeEntityRenderer : public TypedEntityRenderer { public: ShapeEntityRenderer(const EntityItemPointer& entity); - virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) override; + virtual scriptable::ScriptableModelBase getScriptableModel() override; private: virtual bool needsRenderUpdate() const override; diff --git a/libraries/graphics-scripting/src/graphics-scripting/Forward.h b/libraries/graphics-scripting/src/graphics-scripting/Forward.h index 7b2126cf8f..94a96446a0 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/Forward.h +++ b/libraries/graphics-scripting/src/graphics-scripting/Forward.h @@ -36,16 +36,14 @@ namespace scriptable { public: WeakModelProviderPointer provider; ScriptableModelBasePointer model; - WeakMeshPointer mesh; - MeshPointer ownedMesh; - QVariantMap metadata; - ScriptableMeshBase(WeakModelProviderPointer provider, ScriptableModelBasePointer model, WeakMeshPointer mesh, const QVariantMap& metadata); - ScriptableMeshBase(WeakMeshPointer mesh = WeakMeshPointer()); - ScriptableMeshBase(MeshPointer mesh, const QVariantMap& metadata); - ScriptableMeshBase(const ScriptableMeshBase& other) : QObject() { *this = other; } + WeakMeshPointer weakMesh; + MeshPointer strongMesh; + ScriptableMeshBase(WeakModelProviderPointer provider, ScriptableModelBasePointer model, WeakMeshPointer weakMesh, QObject* parent); + ScriptableMeshBase(WeakMeshPointer weakMesh = WeakMeshPointer(), QObject* parent = nullptr); + ScriptableMeshBase(const ScriptableMeshBase& other, QObject* parent = nullptr) : QObject(parent) { *this = other; } ScriptableMeshBase& operator=(const ScriptableMeshBase& view); virtual ~ScriptableMeshBase(); - Q_INVOKABLE const scriptable::MeshPointer getMeshPointer() const { return mesh.lock(); } + Q_INVOKABLE const scriptable::MeshPointer getMeshPointer() const { return weakMesh.lock(); } Q_INVOKABLE const scriptable::ModelProviderPointer getModelProviderPointer() const { return provider.lock(); } Q_INVOKABLE const scriptable::ScriptableModelBasePointer getModelBasePointer() const { return model; } }; @@ -56,18 +54,15 @@ namespace scriptable { public: WeakModelProviderPointer provider; QUuid objectID; // spatially nestable ID - QVariantMap metadata; QVector meshes; ScriptableModelBase(QObject* parent = nullptr) : QObject(parent) {} - ScriptableModelBase(const ScriptableModelBase& other) : QObject() { *this = other; } + ScriptableModelBase(const ScriptableModelBase& other) : QObject(other.parent()) { *this = other; } ScriptableModelBase& operator=(const ScriptableModelBase& other); virtual ~ScriptableModelBase(); - void mixin(const QVariantMap& other); - void append(const ScriptableModelBase& other, const QVariantMap& modelMetadata = QVariantMap()); - void append(scriptable::WeakMeshPointer mesh, const QVariantMap& metadata = QVariantMap()); - void append(const ScriptableMeshBase& mesh, const QVariantMap& metadata = QVariantMap()); + void append(const ScriptableMeshBase& mesh); + void append(scriptable::WeakMeshPointer mesh); // TODO: in future containers for these could go here // QVariantMap shapes; // QVariantMap materials; @@ -78,8 +73,7 @@ namespace scriptable { class ModelProvider { public: NestableType modelProviderType; - static scriptable::ScriptableModelBase modelUnavailableError(bool* ok) { if (ok) { *ok = false; } return {}; } - virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) = 0; + virtual scriptable::ScriptableModelBase getScriptableModel() = 0; virtual bool replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer model, int meshIndex, int partIndex) { return false; } }; diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp index 01e68c5328..c336e77762 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp @@ -9,7 +9,6 @@ // #include "GraphicsScriptingInterface.h" -#include "BaseScriptEngine.h" #include "BufferViewScripting.h" #include "GraphicsScriptingUtil.h" #include "OBJWriter.h" @@ -24,86 +23,119 @@ #include #include -#include "GraphicsScriptingInterface.moc" - GraphicsScriptingInterface::GraphicsScriptingInterface(QObject* parent) : QObject(parent), QScriptable() { if (auto scriptEngine = qobject_cast(parent)) { this->registerMetaTypes(scriptEngine); } } -bool GraphicsScriptingInterface::updateMeshes(QUuid uuid, const scriptable::ScriptableMeshPointer mesh, int meshIndex, int partIndex) { - auto model = scriptable::make_qtowned(); - if (mesh) { - model->append(*mesh); - } - return updateMeshes(uuid, model.get()); -} - -bool GraphicsScriptingInterface::updateMeshes(QUuid uuid, const scriptable::ScriptableModelPointer model) { - auto appProvider = DependencyManager::get(); - scriptable::ModelProviderPointer provider = appProvider ? appProvider->lookupModelProvider(uuid) : nullptr; - QString providerType = provider ? SpatiallyNestable::nestableTypeToString(provider->modelProviderType) : QString(); - if (providerType.isEmpty()) { - providerType = "unknown"; - } - bool success = false; - if (provider) { - auto scriptableMeshes = provider->getScriptableModel(&success); - if (success) { - const scriptable::ScriptableModelBasePointer base = model->operator scriptable::ScriptableModelBasePointer(); - if (base) { - success = provider->replaceScriptableModelMeshPart(base, -1, -1); - } - } - } - return success; -} - -QScriptValue GraphicsScriptingInterface::getMeshes(QUuid uuid) { - scriptable::ScriptableModel* meshes{ nullptr }; - bool success = false; - QString error; - - auto appProvider = DependencyManager::get(); - qCDebug(graphics_scripting) << "appProvider" << appProvider.data(); - scriptable::ModelProviderPointer provider = appProvider ? appProvider->lookupModelProvider(uuid) : nullptr; - QString providerType = provider ? SpatiallyNestable::nestableTypeToString(provider->modelProviderType) : QString(); - if (providerType.isEmpty()) { - providerType = "unknown"; - } - if (provider) { - auto scriptableMeshes = provider->getScriptableModel(&success); - if (success) { - meshes = scriptable::make_scriptowned(scriptableMeshes); - if (meshes->objectName().isEmpty()) { - meshes->setObjectName(providerType+"::meshes"); - } - if (meshes->objectID.isNull()) { - meshes->objectID = uuid.toString(); - } - meshes->metadata["provider"] = SpatiallyNestable::nestableTypeToString(provider->modelProviderType); - } - } - if (!success) { - error = QString("failed to get meshes from %1 provider for uuid %2").arg(providerType).arg(uuid.toString()); - } - - QPointer scriptEngine = dynamic_cast(engine()); - QScriptValue result = error.isEmpty() ? scriptEngine->toScriptValue(meshes) : scriptEngine->makeError(error); - if (result.isError()) { - qCWarning(graphics_scripting) << "GraphicsScriptingInterface::getMeshes ERROR" << result.toString(); - if (context()) { - context()->throwValue(error); +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 { - qCWarning(graphics_scripting) << "GraphicsScriptingInterface::getMeshes ERROR" << result.toString(); + qDebug() << "replaceScriptableModelMeshPart -- !base" << model << base << -1 << -1; } - return QScriptValue::NullValue; + } else { + qDebug() << "replaceScriptableModelMeshPart -- !provider"; } - return scriptEngine->toScriptValue(meshes); + + return false; } -QString GraphicsScriptingInterface::meshToOBJ(const scriptable::ScriptableModel& _in) { +scriptable::ModelProviderPointer GraphicsScriptingInterface::getModelProvider(QUuid uuid) { + QString error; + if (auto appProvider = DependencyManager::get()) { + if (auto provider = appProvider->lookupModelProvider(uuid)) { + return provider; + } else { + error = "provider unavailable for " + uuid.toString(); + } + } else { + error = "appProvider unavailable"; + } + if (context()) { + context()->throwError(error); + } else { + qCWarning(graphics_scripting) << "GraphicsScriptingInterface::getModelProvider ERROR" << error; + } + return nullptr; +} + +scriptable::ScriptableModelPointer GraphicsScriptingInterface::newModelObject(QVector meshes) { + auto modelWrapper = scriptable::make_scriptowned(); + modelWrapper->setObjectName("js::model"); + if (meshes.isEmpty()) { + if (context()) { + context()->throwError("expected [meshes] array as first argument"); + } + } else { + int i = 0; + for (const auto& mesh : meshes) { + if (mesh) { + modelWrapper->append(*mesh); + } else if (context()) { + context()->throwError(QString("invalid mesh at index: %1").arg(i)); + } + i++; + } + } + 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; + } else { + error = "no meshes available: " + modelObject.objectID.toString(); + } + } else { + error = "objectID mismatch: " + modelObject.objectID.toString(); + } + } 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); + } + return nullptr; +} + +#ifdef SCRIPTABLE_MESH_TODO +bool GraphicsScriptingInterface::updateMeshPart(scriptable::ScriptableMeshPointer mesh, scriptable::ScriptableMeshPartPointer part) { + Q_ASSERT(mesh); + Q_ASSERT(part); + Q_ASSERT(part->parentMesh); + auto tmp = exportMeshPart(mesh, part->partIndex); + if (part->parentMesh == mesh) { +#ifdef SCRIPTABLE_MESH_DEBUG + qCInfo(graphics_scripting) << "updateMeshPart -- update via clone" << mesh << part; +#endif + tmp->replaceMeshData(part->cloneMeshPart()); + return false; + } else { +#ifdef SCRIPTABLE_MESH_DEBUG + qCInfo(graphics_scripting) << "updateMeshPart -- update via inplace" << mesh << part; +#endif + tmp->replaceMeshData(part); + return true; + } +} +#endif + +QString GraphicsScriptingInterface::exportModelToOBJ(const scriptable::ScriptableModel& _in) { const auto& in = _in.getConstMeshes(); if (in.size()) { QList meshes; @@ -152,3 +184,5 @@ MeshPointer GraphicsScriptingInterface::getMeshPointer(scriptable::ScriptableMes } return mesh; } + +#include "GraphicsScriptingInterface.moc" diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h index ab2e5467db..9866b5585c 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h @@ -29,16 +29,24 @@ public: public slots: /**jsdoc - * Returns the meshes associated with a UUID (entityID, overlayID, or avatarID) + * Returns the model/meshes associated with a UUID (entityID, overlayID, or avatarID) * - * @function GraphicsScriptingInterface.getMeshes - * @param {EntityID} entityID The ID of the entity whose meshes are to be retrieve + * @function GraphicsScriptingInterface.getModel + * @param {UUID} The objectID of the model whose meshes are to be retrieve */ - QScriptValue getMeshes(QUuid uuid); - bool updateMeshes(QUuid uuid, const scriptable::ScriptableModelPointer model); - bool updateMeshes(QUuid uuid, const scriptable::ScriptableMeshPointer mesh, int meshIndex=0, int partIndex=0); + scriptable::ModelProviderPointer getModelProvider(QUuid uuid); + scriptable::ScriptableModelPointer getModelObject(QUuid uuid); + bool updateModelObject(QUuid uuid, const scriptable::ScriptableModelPointer model); + scriptable::ScriptableModelPointer newModelObject(QVector meshes); - QString meshToOBJ(const scriptable::ScriptableModel& in); +#ifdef SCRIPTABLE_MESH_TODO + scriptable::ScriptableMeshPartPointer exportMeshPart(scriptable::ScriptableMeshPointer mesh, int part=0) { + return scriptable::make_scriptowned(mesh, part); + } + bool updateMeshPart(scriptable::ScriptableMeshPointer mesh, scriptable::ScriptableMeshPartPointer part); +#endif + + QString exportModelToOBJ(const scriptable::ScriptableModel& in); private: scriptable::MeshPointer getMeshPointer(scriptable::ScriptableMeshPointer meshProxy); diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h index 594e09bb32..9b86ddee82 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h @@ -43,23 +43,13 @@ namespace scriptable { return toDebugString(qobject_cast(tmp.get())); } - // C++ > QtOwned instance - template - std::shared_ptr make_qtowned(Rest... rest) { - T* tmp = new T(rest...); - if (tmp) { - tmp->metadata["__ownership__"] = QScriptEngine::QtOwnership; - } - return std::shared_ptr(tmp); - } - - // C++ > ScriptOwned JS instance + // 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) { - T* tmp = new T(rest...); - if (tmp) { - tmp->metadata["__ownership__"] = QScriptEngine::ScriptOwnership; - } - return QPointer(tmp); + 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 643debf475..52cd225fba 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp @@ -5,6 +5,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "Forward.h" + #include "ScriptableMesh.h" #include "BufferViewScripting.h" @@ -19,7 +21,7 @@ #include #include -#include "ScriptableMesh.moc" +// #define SCRIPTABLE_MESH_DEBUG 1 scriptable::ScriptableMeshPart::ScriptableMeshPart(scriptable::ScriptableMeshPointer parentMesh, int partIndex) : QObject(), parentMesh(parentMesh), partIndex(partIndex) { @@ -77,7 +79,9 @@ QVector scriptable::ScriptableMesh::findNearbyIndices(const glm::vec3& QVector scriptable::ScriptableMesh::getIndices() const { QVector result; if (auto mesh = getMeshPointer()) { +#ifdef SCRIPTABLE_MESH_DEBUG qCDebug(graphics_scripting, "getTriangleIndices mesh %p", mesh.get()); +#endif gpu::BufferView indexBufferView = mesh->getIndexBuffer(); if (quint32 count = (quint32)indexBufferView.getNumElements()) { result.resize(count); @@ -121,20 +125,16 @@ QVector scriptable::ScriptableMesh::getAttributeNames() const { return result; } -// override QVariantMap scriptable::ScriptableMesh::getVertexAttributes(quint32 vertexIndex) const { return getVertexAttributes(vertexIndex, getAttributeNames()); } bool scriptable::ScriptableMesh::setVertexAttributes(quint32 vertexIndex, QVariantMap attributes) { - //qCInfo(graphics_scripting) << "setVertexAttributes" << vertexIndex << attributes; - metadata["last-modified"] = QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate); for (auto& a : buffer_helpers::gatherBufferViews(getMeshPointer())) { const auto& name = a.first; const auto& value = attributes.value(name); if (value.isValid()) { auto& view = a.second; - //qCDebug(graphics_scripting) << "setVertexAttributes" << vertexIndex << name; buffer_helpers::fromVariant(view, vertexIndex, value); } else { //qCDebug(graphics_scripting) << "(skipping) setVertexAttributes" << vertexIndex << name; @@ -269,7 +269,6 @@ quint32 scriptable::ScriptableMesh::mapAttributeValues(QScriptValue _callback) { #endif auto obj = js->newObject(); auto attributeViews = buffer_helpers::gatherBufferViews(mesh, { "normal", "color" }); - metadata["last-modified"] = QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate); uint32_t i = 0; for (; i < nPositions; i++) { for (const auto& a : attributeViews) { @@ -303,13 +302,13 @@ quint32 scriptable::ScriptableMesh::mapAttributeValues(QScriptValue _callback) { } quint32 scriptable::ScriptableMeshPart::mapAttributeValues(QScriptValue callback) { - return parentMesh ? parentMesh->mapAttributeValues(callback) : 0; + return parentMesh ? parentMesh->mapAttributeValues(callback) : 0; } bool scriptable::ScriptableMeshPart::unrollVertices(bool recalcNormals) { auto mesh = getMeshPointer(); #ifdef SCRIPTABLE_MESH_DEBUG - qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices" << !!mesh<< !!meshProxy; + qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices" << !!mesh<< !!parentMesh; #endif if (!mesh) { return false; @@ -321,8 +320,9 @@ bool scriptable::ScriptableMeshPart::unrollVertices(bool recalcNormals) { auto buffer = new gpu::Buffer(); buffer->resize(numPoints * sizeof(uint32_t)); auto newindices = gpu::BufferView(buffer, { gpu::SCALAR, gpu::UINT32, gpu::INDEX }); - metadata["last-modified"] = QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate); +#ifdef SCRIPTABLE_MESH_DEBUG qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices numPoints" << numPoints; +#endif auto attributeViews = buffer_helpers::gatherBufferViews(mesh); for (const auto& a : attributeViews) { auto& view = a.second; @@ -330,15 +330,17 @@ bool scriptable::ScriptableMeshPart::unrollVertices(bool recalcNormals) { auto buffer = new gpu::Buffer(); buffer->resize(numPoints * sz); auto points = gpu::BufferView(buffer, view._element); - auto src = (uint8_t*)view._buffer->getData(); - auto dest = (uint8_t*)points._buffer->getData(); auto slot = buffer_helpers::ATTRIBUTES[a.first]; +#ifdef SCRIPTABLE_MESH_DEBUG if (0) { + auto src = (uint8_t*)view._buffer->getData(); + auto dest = (uint8_t*)points._buffer->getData(); qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices buffer" << a.first; qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices source" << view.getNumElements(); qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices dest" << points.getNumElements(); qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices sz" << sz << src << dest << slot; } +#endif auto esize = indices._element.getSize(); const char* hint= a.first.toStdString().c_str(); for(quint32 i = 0; i < numPoints; i++) { @@ -381,8 +383,6 @@ bool scriptable::ScriptableMeshPart::replaceMeshData(scriptable::ScriptableMeshP "target:" << QString::fromStdString(target->displayName) << "attributes:" << attributes; - metadata["last-modified"] = QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate); - // remove attributes only found on target mesh, unless user has explicitly specified the relevant attribute names if (attributeNames.isEmpty()) { auto attributeViews = buffer_helpers::gatherBufferViews(target); @@ -438,7 +438,6 @@ bool scriptable::ScriptableMeshPart::dedupeVertices(float epsilon) { uniqueVerts.reserve((int)numPositions); QMap remapIndices; - metadata["last-modified"] = QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate); for (quint32 i = 0; i < numPositions; i++) { const quint32 numUnique = uniqueVerts.size(); const auto& position = positions.get(i); @@ -508,63 +507,51 @@ scriptable::ScriptableMeshPointer scriptable::ScriptableMesh::cloneMesh(bool rec return nullptr; } auto clone = buffer_helpers::cloneMesh(mesh); - + if (recalcNormals) { buffer_helpers::recalculateNormals(clone); } - auto meshPointer = scriptable::make_scriptowned(provider, model, clone, metadata); - clone.reset(); // free local reference - // qCInfo(graphics_scripting) << "========= ScriptableMesh::cloneMesh..." << meshPointer << meshPointer->ownedMesh.use_count(); - //scriptable::MeshPointer* ppMesh = new scriptable::MeshPointer(); - //*ppMesh = clone; - - if (0 && meshPointer) { - scriptable::WeakMeshPointer delme = meshPointer->mesh; - QString debugString = scriptable::toDebugString(meshPointer); - QObject::connect(meshPointer, &QObject::destroyed, meshPointer, [=]() { - // qCWarning(graphics_scripting) << "*************** cloneMesh/Destroy"; - // qCWarning(graphics_scripting) << "*************** " << debugString << delme.lock().get(); - if (!delme.expired()) { - QTimer::singleShot(250, this, [=]{ - if (!delme.expired()) { - qCWarning(graphics_scripting) << "cloneMesh -- potential memory leak..." << debugString << delme.use_count(); - } - }); - } - }); - } - - meshPointer->metadata["last-modified"] = QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate); + auto meshPointer = scriptable::make_scriptowned(provider, model, clone, nullptr); return scriptable::ScriptableMeshPointer(meshPointer); } -scriptable::ScriptableMeshBase::ScriptableMeshBase(scriptable::WeakModelProviderPointer provider, scriptable::ScriptableModelBasePointer model, scriptable::WeakMeshPointer mesh, const QVariantMap& metadata) - : provider(provider), model(model), mesh(mesh), metadata(metadata) {} -scriptable::ScriptableMeshBase::ScriptableMeshBase(scriptable::WeakMeshPointer mesh) : scriptable::ScriptableMeshBase(scriptable::WeakModelProviderPointer(), nullptr, mesh, QVariantMap()) { } -scriptable::ScriptableMeshBase::ScriptableMeshBase(scriptable::MeshPointer mesh, const QVariantMap& metadata) - : ScriptableMeshBase(WeakModelProviderPointer(), nullptr, mesh, metadata) { - ownedMesh = mesh; + + +// note: we don't always want the JS side to prevent mesh data from being freed + +scriptable::ScriptableMeshBase::ScriptableMeshBase( + scriptable::WeakModelProviderPointer provider, scriptable::ScriptableModelBasePointer model, scriptable::WeakMeshPointer weakMesh, QObject* parent + ) : QObject(parent), provider(provider), model(model), weakMesh(weakMesh) { + if (parent) { + qCDebug(graphics_scripting) << "ScriptableMeshBase -- have parent QObject, creating strong neshref" << weakMesh.lock().get() << parent; + strongMesh = weakMesh.lock(); + } } + +scriptable::ScriptableMeshBase::ScriptableMeshBase(scriptable::WeakMeshPointer weakMesh, QObject* parent) : + scriptable::ScriptableMeshBase(scriptable::WeakModelProviderPointer(), nullptr, weakMesh, parent) { +} + scriptable::ScriptableMeshBase& scriptable::ScriptableMeshBase::operator=(const scriptable::ScriptableMeshBase& view) { provider = view.provider; model = view.model; - mesh = view.mesh; - ownedMesh = view.ownedMesh; - metadata = view.metadata; + weakMesh = view.weakMesh; + strongMesh = view.strongMesh; return *this; } - scriptable::ScriptableMeshBase::~ScriptableMeshBase() { - ownedMesh.reset(); + +scriptable::ScriptableMeshBase::~ScriptableMeshBase() { #ifdef SCRIPTABLE_MESH_DEBUG - qCInfo(graphics_scripting) << "//~ScriptableMeshBase" << this << "ownedMesh:" << ownedMesh.use_count() << "mesh:" << mesh.use_count(); + qCInfo(graphics_scripting) << "//~ScriptableMeshBase" << this << "strongMesh:" << strongMesh.use_count() << "weakMesh:" << weakMesh.use_count(); #endif + strongMesh.reset(); } scriptable::ScriptableMesh::~ScriptableMesh() { - ownedMesh.reset(); #ifdef SCRIPTABLE_MESH_DEBUG - qCInfo(graphics_scripting) << "//~ScriptableMesh" << this << "ownedMesh:" << ownedMesh.use_count() << "mesh:" << mesh.use_count(); + qCInfo(graphics_scripting) << "//~ScriptableMesh" << this << "strongMesh:" << strongMesh.use_count() << "weakMesh:" << weakMesh.use_count(); #endif + strongMesh.reset(); } QString scriptable::ScriptableMeshPart::toOBJ() { @@ -573,7 +560,7 @@ QString scriptable::ScriptableMeshPart::toOBJ() { context()->throwError(QString("null mesh")); } else { qCWarning(graphics_scripting) << "null mesh"; - } + } return QString(); } return writeOBJToString({ getMeshPointer() }); @@ -585,12 +572,7 @@ namespace { if (!object) { return QScriptValue::NullValue; } - auto ownership = object->metadata.value("__ownership__"); - return engine->newQObject( - object, - ownership.isValid() ? static_cast(ownership.toInt()) : QScriptEngine::QtOwnership - //, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects - ); + return engine->newQObject(object, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater); } QScriptValue meshPointerToScriptValue(QScriptEngine* engine, const scriptable::ScriptableMeshPointer& in) { @@ -637,7 +619,7 @@ namespace scriptable { qScriptRegisterSequenceMetaType>(engine); qScriptRegisterSequenceMetaType>(engine); qScriptRegisterSequenceMetaType>(engine); - + qScriptRegisterMetaType(engine, qVectorUInt32ToScriptValue, qVectorUInt32FromScriptValue); qScriptRegisterMetaType(engine, modelPointerToScriptValue, modelPointerFromScriptValue); qScriptRegisterMetaType(engine, meshPointerToScriptValue, meshPointerFromScriptValue); @@ -645,50 +627,41 @@ namespace scriptable { return metaTypeIds.size(); } + // callback helper that lets C++ method signatures remain simple (ie: taking a single callback argument) while // still supporting extended Qt signal-like (scope, "methodName") and (scope, function(){}) "this" binding conventions - QScriptValue jsBindCallback(QScriptValue callback) { - if (callback.isObject() && callback.property("callback").isFunction()) { - return callback; + QScriptValue jsBindCallback(QScriptValue value) { + if (value.isObject() && value.property("callback").isFunction()) { + // value is already a bound callback + return value; } - auto engine = callback.engine(); + 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(); - int i = 0; - for (; context && i < length; i++) { - if (context->argument(i).strictlyEquals(callback)) { +#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()) { - scope = callback; + // interpret as `API.func(..., scope, function callback(){})` or `API.func(..., scope, "methodName")` + scope = value; } else { - method = callback; + // 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); } } -bool scriptable::GraphicsScriptingInterface::updateMeshPart(ScriptableMeshPointer mesh, ScriptableMeshPartPointer part) { - Q_ASSERT(mesh); - Q_ASSERT(part); - Q_ASSERT(part->parentMesh); - auto tmp = exportMeshPart(mesh, part->partIndex); - if (part->parentMesh == mesh) { -#ifdef SCRIPTABLE_MESH_DEBUG - qCInfo(graphics_scripting) << "updateMeshPart -- update via clone" << mesh << part; -#endif - tmp->replaceMeshData(part->cloneMeshPart()); - return false; - } else { -#ifdef SCRIPTABLE_MESH_DEBUG - qCInfo(graphics_scripting) << "updateMeshPart -- update via inplace" << mesh << part; -#endif - tmp->replaceMeshData(part); - return true; - } -} +#include "ScriptableMesh.moc" diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h index 459613135a..f070fdf21e 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h @@ -1,3 +1,10 @@ +// +// 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 "ScriptableModel.h" @@ -26,25 +33,26 @@ namespace scriptable { Q_PROPERTY(uint32 numAttributes READ getNumAttributes) Q_PROPERTY(uint32 numVertices READ getNumVertices) Q_PROPERTY(uint32 numIndices READ getNumIndices) - Q_PROPERTY(QVariantMap metadata MEMBER metadata) Q_PROPERTY(QVector attributeNames READ getAttributeNames) Q_PROPERTY(QVector parts READ getMeshParts) Q_PROPERTY(bool valid READ hasValidMesh) - bool hasValidMesh() const { return (bool)getMeshPointer(); } - Q_PROPERTY(bool validOwned READ hasValidOwnedMesh) - bool hasValidOwnedMesh() const { return (bool)getOwnedMeshPointer(); } + Q_PROPERTY(bool strong READ hasValidStrongMesh) operator const ScriptableMeshBase*() const { return (qobject_cast(this)); } - ScriptableMesh(scriptable::MeshPointer mesh) : ScriptableMeshBase(mesh), QScriptable() { ownedMesh = mesh; } - ScriptableMesh(WeakModelProviderPointer provider, ScriptableModelBasePointer model, MeshPointer mesh, const QVariantMap& metadata) - : ScriptableMeshBase(provider, model, mesh, metadata), QScriptable() { ownedMesh = mesh; } + + ScriptableMesh(WeakModelProviderPointer provider, ScriptableModelBasePointer model, MeshPointer mesh, QObject* parent) + : ScriptableMeshBase(provider, model, mesh, parent), QScriptable() { strongMesh = mesh; } + ScriptableMesh(MeshPointer mesh, QObject* parent) + : ScriptableMeshBase(WeakModelProviderPointer(), nullptr, mesh, parent), QScriptable() { strongMesh = mesh; } ScriptableMesh(const ScriptableMeshBase& other); 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 ownedMesh; } + Q_INVOKABLE const scriptable::MeshPointer getOwnedMeshPointer() const { return strongMesh; } scriptable::ScriptableMeshPointer getSelf() const { return const_cast(this); } + bool hasValidMesh() const { return !weakMesh.expired(); } + bool hasValidStrongMesh() const { return (bool)strongMesh; } public slots: uint32 getNumParts() const; uint32 getNumVertices() const; @@ -65,9 +73,9 @@ namespace scriptable { int _getSlotNumber(const QString& attributeName) const; - scriptable::ScriptableMeshPointer cloneMesh(bool recalcNormals = false); + scriptable::ScriptableMeshPointer cloneMesh(bool recalcNormals = false); public: - operator bool() const { return !mesh.expired(); } + operator bool() const { return !weakMesh.expired(); } public slots: // QScriptEngine-specific wrappers @@ -88,11 +96,9 @@ namespace scriptable { Q_PROPERTY(uint32 numIndices READ getNumIndices) Q_PROPERTY(QVector attributeNames READ getAttributeNames) - Q_PROPERTY(QVariantMap metadata MEMBER metadata) - ScriptableMeshPart(scriptable::ScriptableMeshPointer parentMesh, int partIndex); ScriptableMeshPart& operator=(const ScriptableMeshPart& view) { parentMesh=view.parentMesh; return *this; }; - ScriptableMeshPart(const ScriptableMeshPart& other) : QObject(), QScriptable(), parentMesh(other.parentMesh), partIndex(other.partIndex) {} + ScriptableMeshPart(const ScriptableMeshPart& other) : QObject(other.parent()), QScriptable(), parentMesh(other.parentMesh), partIndex(other.partIndex) {} public slots: scriptable::ScriptableMeshPointer getParentMesh() const { return parentMesh; } @@ -134,23 +140,10 @@ namespace scriptable { public: scriptable::ScriptableMeshPointer parentMesh; uint32 partIndex; - QVariantMap metadata; protected: int _elementsPerFace{ 3 }; QString _topology{ "triangles" }; - scriptable::MeshPointer getMeshPointer() const { return parentMesh ? parentMesh->getMeshPointer() : nullptr; } - }; - - class GraphicsScriptingInterface : public QObject, QScriptable { - Q_OBJECT - public: - GraphicsScriptingInterface(QObject* parent = nullptr) : QObject(parent), QScriptable() {} - GraphicsScriptingInterface(const GraphicsScriptingInterface& other) : QObject(), QScriptable() {} - public slots: - ScriptableMeshPartPointer exportMeshPart(ScriptableMeshPointer mesh, int part=0) { - return ScriptableMeshPartPointer(new ScriptableMeshPart(mesh, part)); - } - bool updateMeshPart(ScriptableMeshPointer mesh, ScriptableMeshPartPointer part); + 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 @@ -165,26 +158,5 @@ Q_DECLARE_METATYPE(scriptable::ScriptableMeshPointer) Q_DECLARE_METATYPE(QVector) Q_DECLARE_METATYPE(scriptable::ScriptableMeshPartPointer) Q_DECLARE_METATYPE(QVector) -Q_DECLARE_METATYPE(scriptable::GraphicsScriptingInterface) - -// FIXME: MESHFACES: faces were supported in the original Model.* API -- are they still needed/used/useful for anything yet? -#include - -namespace mesh { - class MeshFace; - using MeshFaces = QVector; - class MeshFace { - public: - MeshFace() {} - MeshFace(QVector vertexIndices) : vertexIndices(vertexIndices) {} - ~MeshFace() {} - - QVector vertexIndices; - // TODO -- material... - }; -}; - -Q_DECLARE_METATYPE(mesh::MeshFace) -Q_DECLARE_METATYPE(QVector) Q_DECLARE_METATYPE(scriptable::uint32) 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 229d56adab..306f1b18af 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp @@ -13,25 +13,13 @@ #include "ScriptableMesh.h" #include -//#include "ScriptableModel.moc" -void scriptable::ScriptableModelBase::mixin(const QVariantMap& modelMetaData) { - for (const auto& key : modelMetaData.keys()) { - const auto& value = modelMetaData[key]; - if (metadata.contains(key) && metadata[key].type() == (QVariant::Type)QMetaType::QVariantList) { - qCDebug(graphics_scripting) << "CONCATENATING" << key << metadata[key].toList().size() << "+" << value.toList().size(); - metadata[key] = metadata[key].toList() + value.toList(); - } else { - metadata[key] = modelMetaData[key]; - } - } -} +// #define SCRIPTABLE_MESH_DEBUG 1 scriptable::ScriptableModelBase& scriptable::ScriptableModelBase::operator=(const scriptable::ScriptableModelBase& other) { provider = other.provider; objectID = other.objectID; - metadata = other.metadata; - for (auto& mesh : other.meshes) { + for (const auto& mesh : other.meshes) { append(mesh); } return *this; @@ -43,36 +31,27 @@ scriptable::ScriptableModelBase::~ScriptableModelBase() { #endif // makes cleanup order more deterministic to help with debugging for (auto& m : meshes) { - m.ownedMesh.reset(); + m.strongMesh.reset(); } meshes.clear(); } -void scriptable::ScriptableModelBase::append(scriptable::WeakMeshPointer mesh, const QVariantMap& metadata) { - meshes << ScriptableMeshBase{ provider, this, mesh, metadata }; +void scriptable::ScriptableModelBase::append(scriptable::WeakMeshPointer mesh) { + meshes << ScriptableMeshBase{ provider, this, mesh, this /*parent*/ }; } -void scriptable::ScriptableModelBase::append(const ScriptableMeshBase& mesh, const QVariantMap& modelMetaData) { +void scriptable::ScriptableModelBase::append(const ScriptableMeshBase& mesh) { if (mesh.provider.lock().get() != provider.lock().get()) { qCDebug(graphics_scripting) << "warning: appending mesh from different provider..." << mesh.provider.lock().get() << " != " << provider.lock().get(); } meshes << mesh; - mixin(modelMetaData); } -void scriptable::ScriptableModelBase::append(const ScriptableModelBase& other, const QVariantMap& modelMetaData) { - for (const auto& mesh : other.meshes) { - append(mesh); - } - mixin(other.metadata); - mixin(modelMetaData); -} - - QString scriptable::ScriptableModel::toString() const { - return QString("[ScriptableModel%1%2]") + return QString("[ScriptableModel%1%2 numMeshes=%3]") .arg(objectID.isNull() ? "" : " objectID="+objectID.toString()) - .arg(objectName().isEmpty() ? "" : " name=" +objectName()); + .arg(objectName().isEmpty() ? "" : " name=" +objectName()) + .arg(meshes.size()); } scriptable::ScriptableModelPointer scriptable::ScriptableModel::cloneModel(const QVariantMap& options) { @@ -131,3 +110,5 @@ quint32 scriptable::ScriptableModel::mapAttributeValues(QScriptValue callback) { } return result; } + +#include "ScriptableModel.moc" diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h index a78c9a4ef5..4ed1cc9554 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h @@ -1,15 +1,22 @@ +// +// 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 "Forward.h" #include "GraphicsScriptingUtil.h" class QScriptValue; + namespace scriptable { class ScriptableModel : public ScriptableModelBase { Q_OBJECT public: Q_PROPERTY(QUuid objectID MEMBER objectID CONSTANT) - Q_PROPERTY(QVariantMap metadata MEMBER metadata CONSTANT) Q_PROPERTY(uint32 numMeshes READ getNumMeshes) Q_PROPERTY(QVector meshes READ getMeshes) @@ -27,11 +34,9 @@ namespace scriptable { QVector getMeshes(); const QVector getConstMeshes() const; operator scriptable::ScriptableModelBasePointer() { - QPointer p; - p = qobject_cast(this); - return p; + return QPointer(qobject_cast(this)); } - + // QScriptEngine-specific wrappers Q_INVOKABLE uint32 mapAttributeValues(QScriptValue callback); diff --git a/libraries/graphics/src/graphics/BufferViewHelpers.cpp b/libraries/graphics/src/graphics/BufferViewHelpers.cpp index ddf3d84c89..29dcbd58e3 100644 --- a/libraries/graphics/src/graphics/BufferViewHelpers.cpp +++ b/libraries/graphics/src/graphics/BufferViewHelpers.cpp @@ -28,10 +28,6 @@ namespace glm { using hvec4 = glm::tvec4; } -#ifdef DEBUG_BUFFERVIEW_SCRIPTING -#include "../../graphics-scripting/src/graphics-scripting/DebugNames.h" -#endif - namespace { QLoggingCategory bufferhelper_logging{ "hifi.bufferview" }; } @@ -109,9 +105,7 @@ bool buffer_helpers::fromVariant(const gpu::BufferView& view, quint32 index, con const auto dataType = element.getType(); const auto byteLength = element.getSize(); const auto BYTES_PER_ELEMENT = byteLength / vecN; -#ifdef DEBUG_BUFFERVIEW_SCRIPTING - qCDebug(bufferhelper_logging) << "bufferViewElementFromVariant" << index << DebugNames::stringFrom(dataType) << BYTES_PER_ELEMENT << vecN; -#endif + if (BYTES_PER_ELEMENT == 1) { switch(vecN) { case 2: setBufferViewElement(view, index, v); return true; @@ -176,18 +170,12 @@ QVariant buffer_helpers::toVariant(const gpu::BufferView& view, quint32 index, b auto byteOffset = index * vecN * BYTES_PER_ELEMENT; auto maxByteOffset = (view._size - 1) * vecN * BYTES_PER_ELEMENT; if (byteOffset > maxByteOffset) { -#ifdef DEBUG_BUFFERVIEW_SCRIPTING - qDebug() << "toVariant -- " << DebugNames::stringFrom(dataType) -#endif 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); } -#ifdef DEBUG_BUFFERVIEW_SCRIPTING - qCDebug(bufferhelper_logging) << "toVariant -- " << index << DebugNames::stringFrom(dataType) << BYTES_PER_ELEMENT << vecN; -#endif if (BYTES_PER_ELEMENT == 1) { switch(vecN) { case 2: return getBufferViewElement(view, index, asArray); @@ -305,13 +293,8 @@ template struct GpuScalarToGlm; struct GpuToGlmAdapter { static float error(const QString& name, const gpu::BufferView& view, quint32 index, const char *hint) { - QString debugName; -#ifdef DEBUG_BUFFERVIEW_SCRIPTING - debugName = DebugNames::stringFrom(view._element.getType()) -#endif - qDebug() << QString("GpuToGlmAdapter:: unhandled type=%1(element=%2(%3)) size=%4(per=%5) vec%6 hint=%7 #%8") + qDebug() << QString("GpuToGlmAdapter:: unhandled type=%1(element=%2) size=%3(per=%4) vec%5 hint=%6 #%7") .arg(name) - .arg(debugName) .arg(view._element.getType()) .arg(view._element.getSize()) .arg(view._element.getSize() / view._element.getScalarCount()) @@ -404,7 +387,7 @@ struct getVec { } return result; } - static T __to_scalar__(const gpu::BufferView& view, quint32 index, const char *hint) { + static T __to_value__(const gpu::BufferView& view, quint32 index, const char *hint) { assert(boundsCheck(view, index)); return FUNC::get(view, index, hint); } @@ -425,18 +408,18 @@ template <> QVector buffer_helpers::toVector(const gpu::Bu } -// indexed conversion accessors (similar to "view.convert(i)" existed) +// 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_scalar__(view, index, hint); + return getVec,int>::__to_value__(view, index, hint); } template <> glm::vec2 buffer_helpers::convert(const gpu::BufferView& view, quint32 index, const char *hint) { - return getVec,glm::vec2>::__to_scalar__(view, index, hint); + return getVec,glm::vec2>::__to_value__(view, index, hint); } template <> glm::vec3 buffer_helpers::convert(const gpu::BufferView& view, quint32 index, const char *hint) { - return getVec,glm::vec3>::__to_scalar__(view, index, hint); + return getVec,glm::vec3>::__to_value__(view, index, hint); } template <> glm::vec4 buffer_helpers::convert(const gpu::BufferView& view, quint32 index, const char *hint) { - return getVec,glm::vec4>::__to_scalar__(view, index, hint); + return getVec,glm::vec4>::__to_value__(view, index, hint); } gpu::BufferView buffer_helpers::clone(const gpu::BufferView& input) { @@ -492,9 +475,6 @@ namespace { vsize > bufferView._size ); QString hint = QString("%1").arg(slot); -#ifdef DEBUG_BUFFERVIEW_SCRIPTING - hint = DebugNames::stringFrom(slot); -#endif #ifdef DEV_BUILD auto beforeCount = bufferView.getNumElements(); auto beforeTotal = bufferView._size; @@ -519,9 +499,6 @@ namespace { auto afterTotal = bufferView._size; if (beforeTotal != afterTotal || beforeCount != afterCount) { QString typeName = QString("%1").arg(bufferView._element.getType()); -#ifdef DEBUG_BUFFERVIEW_SCRIPTING - typeName = DebugNames::stringFrom(bufferView._element.getType()); -#endif qCDebug(bufferhelper_logging, "NOTE:: _expandedAttributeBuffer.%s vec%d %s (before count=%lu bytes=%lu // after count=%lu bytes=%lu)", hint.toStdString().c_str(), bufferView._element.getScalarCount(), typeName.toStdString().c_str(), beforeCount, beforeTotal, afterCount, afterTotal); @@ -557,9 +534,6 @@ std::map buffer_helpers::gatherBufferViews(graphics::M if (beforeCount > 0) { auto element = view._element; QString typeName = QString("%1").arg(element.getType()); -#ifdef DEBUG_BUFFERVIEW_SCRIPTING - typeName = DebugNames::stringFrom(element.getType()); -#endif attributeViews[name] = getBufferView(mesh, slot); diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 783659a6a1..a083b027f3 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -492,7 +492,6 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g { "v2", vec3toVariant(bestModelTriangle.v2) }, }; } - } } @@ -591,38 +590,41 @@ MeshProxyList Model::getMeshes() const { // 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) { +bool Model::replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer newModel, int meshIndex, int partIndex) { QMutexLocker lock(&_mutex); if (!isLoaded()) { + qDebug() << "!isLoaded" << this; return false; } - { - // FIXME: temporary workaround for updating the whole FBXGeometry (to keep findRayIntersection in sync) - auto newRenderGeometry = new MyGeometryMappingResource( - _url, _renderGeometry, _newModel ? scriptable::make_qtowned(*_newModel) : nullptr - ); - _visualGeometryRequestFailed = false; - deleteGeometry(); - _renderGeometry.reset(newRenderGeometry); - _rig.destroyAnimGraph(); - updateGeometry(); - calculateTriangleSets(); - _needsReload = false; - _needsFixupInScene = true; - setRenderItemsNeedUpdate(); + if (!newModel || !newModel->meshes.size()) { + qDebug() << "!newModel.meshes.size()" << this; + return false; } + + auto resource = new MyGeometryResource(_url, _renderGeometry, newModel); + _needsReload = false; + _needsUpdateTextures = false; + _visualGeometryRequestFailed = false; + _needsFixupInScene = true; + + invalidCalculatedMeshBoxes(); + deleteGeometry(); + _renderGeometry.reset(resource); + updateGeometry(); + calculateTriangleSets(); + setRenderItemsNeedUpdate(); return true; } -scriptable::ScriptableModelBase Model::getScriptableModel(bool* ok) { +scriptable::ScriptableModelBase Model::getScriptableModel() { QMutexLocker lock(&_mutex); scriptable::ScriptableModelBase result; if (!isLoaded()) { qCDebug(renderutils) << "Model::getScriptableModel -- !isLoaded"; - return scriptable::ModelProvider::modelUnavailableError(ok); + return result; } const FBXGeometry& geometry = getFBXGeometry(); @@ -630,22 +632,9 @@ scriptable::ScriptableModelBase Model::getScriptableModel(bool* ok) { for (int i = 0; i < numberOfMeshes; i++) { const FBXMesh& fbxMesh = geometry.meshes.at(i); if (auto mesh = fbxMesh._mesh) { - auto name = geometry.getModelNameOfMesh(i); - result.append(std::const_pointer_cast(mesh), { - { "index", i }, - { "name", name }, - { "meshIndex", fbxMesh.meshIndex }, - { "displayName", QString::fromStdString(mesh->displayName) }, - { "modelName", QString::fromStdString(mesh->modelName) }, - { "modelTransform", buffer_helpers::toVariant(fbxMesh.modelTransform) }, - { "transform", buffer_helpers::toVariant(geometry.offset * fbxMesh.modelTransform) }, - { "extents", buffer_helpers::toVariant(fbxMesh.meshExtents) }, - }); + result.append(mesh); } } - if (ok) { - *ok = true; - } return result; } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 5cbdb2d300..603153d535 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -317,7 +317,7 @@ public: int getResourceDownloadAttempts() { return _renderWatcher.getResourceDownloadAttempts(); } int getResourceDownloadAttemptsRemaining() { return _renderWatcher.getResourceDownloadAttemptsRemaining(); } - virtual scriptable::ScriptableModelBase getScriptableModel(bool* ok = nullptr) override; + virtual scriptable::ScriptableModelBase getScriptableModel() override; virtual bool replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer model, int meshIndex, int partIndex) override; void scaleToFit(); diff --git a/libraries/render-utils/src/Model_temporary_hack.cpp.h b/libraries/render-utils/src/Model_temporary_hack.cpp.h index 9b0e0bcf9a..51318656e0 100644 --- a/libraries/render-utils/src/Model_temporary_hack.cpp.h +++ b/libraries/render-utils/src/Model_temporary_hack.cpp.h @@ -1,9 +1,9 @@ #include #include -class MyGeometryMappingResource : public GeometryResource { +class MyGeometryResource : public GeometryResource { public: shared_ptr fbxGeometry; - MyGeometryMappingResource(const QUrl& url, Geometry::Pointer originalGeometry, std::shared_ptr newModel) : GeometryResource(url) { + MyGeometryResource(const QUrl& url, Geometry::Pointer originalGeometry, scriptable::ScriptableModelBasePointer newModel) : GeometryResource(url) { fbxGeometry = std::make_shared(); FBXGeometry& geometry = *fbxGeometry.get(); const FBXGeometry* original; @@ -15,41 +15,12 @@ public: original = tmpGeometry.get(); } geometry.originalURL = original->originalURL; + geometry.bindExtents = original->bindExtents; - geometry.author = original->author; - geometry.applicationName = original->applicationName; for (const auto &j : original->joints) { geometry.joints << j; } - geometry.jointIndices = QHash{ original->jointIndices }; - - geometry.animationFrames = QVector{ original->animationFrames }; - geometry.meshIndicesToModelNames = QHash{ original->meshIndicesToModelNames }; - geometry.blendshapeChannelNames = QList{ original->blendshapeChannelNames }; - - geometry.hasSkeletonJoints = original->hasSkeletonJoints; - geometry.offset = original->offset; - geometry.leftEyeJointIndex = original->leftEyeJointIndex; - geometry.rightEyeJointIndex = original->rightEyeJointIndex; - geometry.neckJointIndex = original->neckJointIndex; - geometry.rootJointIndex = original->rootJointIndex; - geometry.leanJointIndex = original->leanJointIndex; - geometry.headJointIndex = original->headJointIndex; - geometry.leftHandJointIndex = original->leftHandJointIndex; - geometry.rightHandJointIndex = original->rightHandJointIndex; - geometry.leftToeJointIndex = original->leftToeJointIndex; - geometry.rightToeJointIndex = original->rightToeJointIndex; - geometry.leftEyeSize = original->leftEyeSize; - geometry.rightEyeSize = original->rightEyeSize; - geometry.humanIKJointIndices = original->humanIKJointIndices; - geometry.palmDirection = original->palmDirection; - geometry.neckPivot = original->neckPivot; - geometry.bindExtents = original->bindExtents; - - // Copy materials - QHash materialIDAtlas; for (const FBXMaterial& material : original->materials) { - materialIDAtlas[material.materialID] = _materials.size(); _materials.push_back(std::make_shared(material, _textureBaseUrl)); } std::shared_ptr meshes = std::make_shared(); @@ -58,6 +29,7 @@ public: 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 @@ -73,7 +45,7 @@ public: mesh.createBlendShapeTangents(false); geometry.meshes << mesh; // Copy mesh pointers - meshes->emplace_back(newMesh.getMeshPointer());//buffer_helpers::cloneMesh(ptr)); + meshes->emplace_back(newMesh.getMeshPointer()); int partID = 0; const auto oldParts = mesh.parts; mesh.parts.clear(); @@ -83,7 +55,7 @@ public: // Construct local parts part.triangleIndices = buffer_helpers::toVector(mesh._mesh->getIndexBuffer(), "part.triangleIndices"); mesh.parts << part; - auto p = std::make_shared(meshID, partID, (int)materialIDAtlas[part.materialID]); + auto p = std::make_shared(meshID, partID, 0); parts->push_back(p); partID++; } @@ -94,20 +66,18 @@ public: 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; - _animGraphOverrideUrl = originalGeometry ? originalGeometry->getAnimGraphOverrideUrl() : QUrl(); _loaded = true; _fbxGeometry = fbxGeometry; - }; + }; }; diff --git a/tools/jsdoc/package-lock.json b/tools/jsdoc/package-lock.json deleted file mode 100644 index 073bbf60f6..0000000000 --- a/tools/jsdoc/package-lock.json +++ /dev/null @@ -1,138 +0,0 @@ -{ - "name": "hifiJSDoc", - "requires": true, - "lockfileVersion": 1, - "dependencies": { - "babylon": { - "version": "7.0.0-beta.19", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.19.tgz", - "integrity": "sha512-Vg0C9s/REX6/WIXN37UKpv5ZhRi6A4pjHlpkE34+8/a6c2W1Q692n3hmc+SZG5lKRnaExLUbxtJ1SVT+KaCQ/A==" - }, - "bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" - }, - "catharsis": { - "version": "0.8.9", - "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.8.9.tgz", - "integrity": "sha1-mMyJDKZS3S7w5ws3klMQ/56Q/Is=", - "requires": { - "underscore-contrib": "0.3.0" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" - }, - "js2xmlparser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-3.0.0.tgz", - "integrity": "sha1-P7YOqgicVED5MZ9RdgzNB+JJlzM=", - "requires": { - "xmlcreate": "1.0.2" - } - }, - "jsdoc": { - "version": "3.5.5", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.5.5.tgz", - "integrity": "sha512-6PxB65TAU4WO0Wzyr/4/YhlGovXl0EVYfpKbpSroSj0qBxT4/xod/l40Opkm38dRHRdQgdeY836M0uVnJQG7kg==", - "requires": { - "babylon": "7.0.0-beta.19", - "bluebird": "3.5.1", - "catharsis": "0.8.9", - "escape-string-regexp": "1.0.5", - "js2xmlparser": "3.0.0", - "klaw": "2.0.0", - "marked": "0.3.12", - "mkdirp": "0.5.1", - "requizzle": "0.2.1", - "strip-json-comments": "2.0.1", - "taffydb": "2.6.2", - "underscore": "1.8.3" - } - }, - "klaw": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-2.0.0.tgz", - "integrity": "sha1-WcEo4Nxc5BAgEVEZTuucv4WGUPY=", - "requires": { - "graceful-fs": "4.1.11" - } - }, - "marked": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.12.tgz", - "integrity": "sha512-k4NaW+vS7ytQn6MgJn3fYpQt20/mOgYM5Ft9BYMfQJDz2QT6yEeS9XJ8k2Nw8JTeWK/znPPW2n3UJGzyYEiMoA==" - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - } - }, - "requizzle": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.1.tgz", - "integrity": "sha1-aUPDUwxNmn5G8c3dUcFY/GcM294=", - "requires": { - "underscore": "1.6.0" - }, - "dependencies": { - "underscore": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", - "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=" - } - } - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" - }, - "taffydb": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", - "integrity": "sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=" - }, - "underscore": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", - "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" - }, - "underscore-contrib": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/underscore-contrib/-/underscore-contrib-0.3.0.tgz", - "integrity": "sha1-ZltmwkeD+PorGMn4y7Dix9SMJsc=", - "requires": { - "underscore": "1.6.0" - }, - "dependencies": { - "underscore": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", - "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=" - } - } - }, - "xmlcreate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-1.0.2.tgz", - "integrity": "sha1-+mv3YqYKQT+z3Y9LA8WyaSONMI8=" - } - } -} From ebdb5b3c173f7fe4e14e849ae64174772caee489 Mon Sep 17 00:00:00 2001 From: humbletim Date: Fri, 23 Feb 2018 08:57:48 -0500 Subject: [PATCH 38/49] CR feedback; remove unrollVertices; update getFace; guard more debug logging --- .../src/graphics-scripting/ScriptableMesh.cpp | 81 ++++--------------- .../src/graphics-scripting/ScriptableMesh.h | 7 +- 2 files changed, 19 insertions(+), 69 deletions(-) diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp index 52cd225fba..0f689e3d2f 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp @@ -305,65 +305,6 @@ quint32 scriptable::ScriptableMeshPart::mapAttributeValues(QScriptValue callback return parentMesh ? parentMesh->mapAttributeValues(callback) : 0; } -bool scriptable::ScriptableMeshPart::unrollVertices(bool recalcNormals) { - auto mesh = getMeshPointer(); -#ifdef SCRIPTABLE_MESH_DEBUG - qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices" << !!mesh<< !!parentMesh; -#endif - if (!mesh) { - return false; - } - - auto positions = mesh->getVertexBuffer(); - auto indices = mesh->getIndexBuffer(); - quint32 numPoints = (quint32)indices.getNumElements(); - auto buffer = new gpu::Buffer(); - buffer->resize(numPoints * sizeof(uint32_t)); - auto newindices = gpu::BufferView(buffer, { gpu::SCALAR, gpu::UINT32, gpu::INDEX }); -#ifdef SCRIPTABLE_MESH_DEBUG - qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices numPoints" << numPoints; -#endif - auto attributeViews = buffer_helpers::gatherBufferViews(mesh); - for (const auto& a : attributeViews) { - auto& view = a.second; - auto sz = view._element.getSize(); - auto buffer = new gpu::Buffer(); - buffer->resize(numPoints * sz); - auto points = gpu::BufferView(buffer, view._element); - auto slot = buffer_helpers::ATTRIBUTES[a.first]; -#ifdef SCRIPTABLE_MESH_DEBUG - if (0) { - auto src = (uint8_t*)view._buffer->getData(); - auto dest = (uint8_t*)points._buffer->getData(); - qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices buffer" << a.first; - qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices source" << view.getNumElements(); - qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices dest" << points.getNumElements(); - qCInfo(graphics_scripting) << "ScriptableMeshPart::unrollVertices sz" << sz << src << dest << slot; - } -#endif - auto esize = indices._element.getSize(); - const char* hint= a.first.toStdString().c_str(); - for(quint32 i = 0; i < numPoints; i++) { - quint32 index = esize == 4 ? indices.get(i) : indices.get(i); - newindices.edit(i) = i; - buffer_helpers::fromVariant( - points, i, - buffer_helpers::toVariant(view, index, false, hint) - ); - } - if (slot == gpu::Stream::POSITION) { - mesh->setVertexBuffer(points); - } else { - mesh->addAttribute(slot, points); - } - } - mesh->setIndexBuffer(newindices); - if (recalcNormals) { - recalculateNormals(); - } - return true; -} - bool scriptable::ScriptableMeshPart::replaceMeshData(scriptable::ScriptableMeshPartPointer src, const QVector& attributeNames) { auto target = getMeshPointer(); auto source = src ? src->getMeshPointer() : nullptr; @@ -389,7 +330,9 @@ bool scriptable::ScriptableMeshPart::replaceMeshData(scriptable::ScriptableMeshP 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); } } @@ -404,21 +347,29 @@ bool scriptable::ScriptableMeshPart::replaceMeshData(scriptable::ScriptableMeshP 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 } @@ -465,7 +416,6 @@ bool scriptable::ScriptableMeshPart::dedupeVertices(float epsilon) { for (quint32 i = 0; i < numIndices; i++) { quint32 index = esize == 4 ? indices.get(i) : indices.get(i); if (remapIndices.contains(index)) { - //qCInfo(graphics_scripting) << i << index << "->" << remapIndices[index]; newIndices << remapIndices[index]; } else { qCInfo(graphics_scripting) << i << index << "!remapIndices[index]"; @@ -483,9 +433,11 @@ bool scriptable::ScriptableMeshPart::dedupeVertices(float epsilon) { if (slot == gpu::Stream::POSITION) { continue; } - qCInfo(graphics_scripting) << "ScriptableMeshPart::dedupeVertices" << a.first << slot << view.getNumElements(); 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; @@ -493,7 +445,7 @@ bool scriptable::ScriptableMeshPart::dedupeVertices(float epsilon) { buffer_helpers::fromVariant( newView, toVertexIndex, buffer_helpers::toVariant(view, fromVertexIndex, false, "dedupe") - ); + ); } mesh->addAttribute(slot, newView); } @@ -515,10 +467,7 @@ scriptable::ScriptableMeshPointer scriptable::ScriptableMesh::cloneMesh(bool rec return scriptable::ScriptableMeshPointer(meshPointer); } - - -// note: we don't always want the JS side to prevent mesh data from being freed - +// note: we don't always want the JS side to prevent mesh data from being freed (hence weak pointers unless parented QObject) scriptable::ScriptableMeshBase::ScriptableMeshBase( scriptable::WeakModelProviderPointer provider, scriptable::ScriptableModelBasePointer model, scriptable::WeakMeshPointer weakMesh, QObject* parent ) : QObject(parent), provider(provider), model(model), weakMesh(weakMesh) { diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h index f070fdf21e..48ef6c3a81 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h @@ -108,8 +108,10 @@ namespace scriptable { uint32 getNumFaces() const { return parentMesh ? parentMesh->getNumIndices() / _elementsPerFace : 0; } QVector getAttributeNames() const { return parentMesh ? parentMesh->getAttributeNames() : QVector(); } QVector getFace(uint32 faceIndex) const { - auto inds = parentMesh ? parentMesh->getIndices() : QVector(); - return faceIndex+2 < (uint32)inds.size() ? inds.mid(faceIndex*3, 3) : QVector(); + 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); @@ -118,7 +120,6 @@ namespace scriptable { QVariantMap rotate(const glm::quat& rotation, const glm::vec3& origin = glm::vec3(NAN)); QVariantMap transform(const glm::mat4& transform); - bool unrollVertices(bool recalcNormals = false); bool dedupeVertices(float epsilon = 1e-6); bool recalculateNormals() { return buffer_helpers::recalculateNormals(getMeshPointer()); } From f8fe06213d56156687333a28878f36219bf3f45a Mon Sep 17 00:00:00 2001 From: humbletim Date: Fri, 23 Feb 2018 09:11:23 -0500 Subject: [PATCH 39/49] CR feedback; whitespace / remove unnecessary diffs --- .../src/graphics-scripting/ScriptableModel.cpp | 4 ++-- libraries/render-utils/src/Model.cpp | 4 ++-- libraries/render-utils/src/Model.h | 3 +-- libraries/render-utils/src/Model_temporary_hack.cpp.h | 1 + libraries/script-engine/src/AssetScriptingInterface.cpp | 2 +- libraries/script-engine/src/ScriptEngines.cpp | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp index 306f1b18af..8ceb7de6a2 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp @@ -72,7 +72,7 @@ scriptable::ScriptableModelPointer scriptable::ScriptableModel::cloneModel(const const QVector scriptable::ScriptableModel::getConstMeshes() const { QVector out; - for(const auto& mesh : meshes) { + for (const auto& mesh : meshes) { const scriptable::ScriptableMesh* m = qobject_cast(&mesh); if (!m) { m = scriptable::make_scriptowned(mesh); @@ -87,7 +87,7 @@ const QVector scriptable::ScriptableModel::ge QVector scriptable::ScriptableModel::getMeshes() { QVector out; - for(auto& mesh : meshes) { + for (auto& mesh : meshes) { scriptable::ScriptableMesh* m = qobject_cast(&mesh); if (!m) { m = scriptable::make_scriptowned(mesh); diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index a083b027f3..6d735497c0 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -24,9 +24,9 @@ #include #include #include -#include #include +#include #include #include #include @@ -885,7 +885,7 @@ void Model::renderDebugMeshBoxes(gpu::Batch& batch) { DependencyManager::get()->bindSimpleProgram(batch, false, false, false, true, true); - for(const auto& triangleSet : _modelSpaceMeshTriangleSets) { + for (const auto& triangleSet : _modelSpaceMeshTriangleSets) { auto box = triangleSet.getBounds(); if (_debugMeshBoxesID == GeometryCache::UNKNOWN_ID) { diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 603153d535..b3c7551eb3 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -317,13 +317,12 @@ public: int getResourceDownloadAttempts() { return _renderWatcher.getResourceDownloadAttempts(); } int getResourceDownloadAttemptsRemaining() { return _renderWatcher.getResourceDownloadAttemptsRemaining(); } + Q_INVOKABLE MeshProxyList getMeshes() const; virtual scriptable::ScriptableModelBase getScriptableModel() override; virtual bool replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer model, int meshIndex, int partIndex) override; void scaleToFit(); - Q_INVOKABLE MeshProxyList getMeshes() const; - void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName); void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName); diff --git a/libraries/render-utils/src/Model_temporary_hack.cpp.h b/libraries/render-utils/src/Model_temporary_hack.cpp.h index 51318656e0..cfa6945571 100644 --- a/libraries/render-utils/src/Model_temporary_hack.cpp.h +++ b/libraries/render-utils/src/Model_temporary_hack.cpp.h @@ -1,3 +1,4 @@ +// FIXME: temporary workaround for duplicating the FBXModel when dynamically replacing an underlying mesh part #include #include class MyGeometryResource : public GeometryResource { diff --git a/libraries/script-engine/src/AssetScriptingInterface.cpp b/libraries/script-engine/src/AssetScriptingInterface.cpp index 750e612781..68c24ecc33 100644 --- a/libraries/script-engine/src/AssetScriptingInterface.cpp +++ b/libraries/script-engine/src/AssetScriptingInterface.cpp @@ -440,7 +440,7 @@ void AssetScriptingInterface::saveToCache(const QUrl& rawURL, const QByteArray& JS_VERIFY(url.scheme() == "atp" || url.scheme() == "cache", "only 'atp' and 'cache' URL schemes supported"); JS_VERIFY(hash.isEmpty() || hash == hashDataHex(data), QString("invalid checksum hash for atp:HASH style URL (%1 != %2)").arg(hash, hashDataHex(data))); - //qCDebug(scriptengine) << "saveToCache" << url.toDisplayString() << data << hash << metadata; + // qCDebug(scriptengine) << "saveToCache" << url.toDisplayString() << data << hash << metadata; jsPromiseReady(Parent::saveToCache(url, data, metadata), scope, callback); } diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index 873c205706..871705d74b 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -535,7 +535,6 @@ void ScriptEngines::onScriptEngineLoaded(const QString& rawScriptURL) { } int ScriptEngines::runScriptInitializers(ScriptEnginePointer scriptEngine) { - // register our application services and set it off on its own thread int ii=0; for (auto initializer : _scriptInitializers) { ii++; @@ -554,6 +553,7 @@ void ScriptEngines::launchScriptEngine(ScriptEnginePointer scriptEngine) { loadScript(scriptName, userLoaded, false, false, true); }); + // register our application services and set it off on its own thread runScriptInitializers(scriptEngine); // FIXME disabling 'shift key' debugging for now. If you start up the application with From 058e4d1926874246054870581415b933f4de1ab9 Mon Sep 17 00:00:00 2001 From: humbletim Date: Fri, 23 Feb 2018 09:12:33 -0500 Subject: [PATCH 40/49] CR feedback; whitespace / remove unnecessary diffs --- libraries/render-utils/src/Model.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index b3c7551eb3..bbb323d1e7 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -143,7 +143,7 @@ public: /// Returns a reference to the shared collision geometry. const Geometry::Pointer& getCollisionGeometry() const { return _collisionGeometry; } - const QVariantMap getTextures() const { assert(isLoaded()); return getGeometry()->getTextures(); } + const QVariantMap getTextures() const { assert(isLoaded()); return _renderGeometry->getTextures(); } Q_INVOKABLE virtual void setTextures(const QVariantMap& textures); /// Provided as a convenience, will crash if !isLoaded() From 7c571cd43198f7cce1594983ed1f887b67c0971c Mon Sep 17 00:00:00 2001 From: humbletim Date: Fri, 23 Feb 2018 10:31:57 -0500 Subject: [PATCH 41/49] cleanup / addition error message detail --- .../src/graphics-scripting/GraphicsScriptingInterface.cpp | 2 +- .../src/graphics-scripting/ScriptableMesh.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp index c336e77762..787905520a 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp @@ -100,7 +100,7 @@ scriptable::ScriptableModelPointer GraphicsScriptingInterface::getModelObject(QU error = "no meshes available: " + modelObject.objectID.toString(); } } else { - error = "objectID mismatch: " + modelObject.objectID.toString(); + error = QString("objectID mismatch: %1 (containing %2 meshes)").arg(modelObject.objectID.toString()).arg(modelObject.meshes.size()); } } else { error = "provider unavailable"; diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp index 0f689e3d2f..76741947fd 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp @@ -472,7 +472,9 @@ scriptable::ScriptableMeshBase::ScriptableMeshBase( scriptable::WeakModelProviderPointer provider, scriptable::ScriptableModelBasePointer model, scriptable::WeakMeshPointer weakMesh, QObject* parent ) : QObject(parent), provider(provider), model(model), weakMesh(weakMesh) { if (parent) { +#ifdef SCRIPTABLE_MESH_DEBUG qCDebug(graphics_scripting) << "ScriptableMeshBase -- have parent QObject, creating strong neshref" << weakMesh.lock().get() << parent; +#endif strongMesh = weakMesh.lock(); } } From a27a5a7e1f3e676b763a5729cb9f9d9ebef8dc5e Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Fri, 23 Feb 2018 18:16:38 +0100 Subject: [PATCH 42/49] export boolean properties properly --- libraries/entities/src/EntityItemPropertiesMacros.h | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/entities/src/EntityItemPropertiesMacros.h b/libraries/entities/src/EntityItemPropertiesMacros.h index 51a23b6867..3bbff6cfa6 100644 --- a/libraries/entities/src/EntityItemPropertiesMacros.h +++ b/libraries/entities/src/EntityItemPropertiesMacros.h @@ -105,6 +105,7 @@ inline QScriptValue convertScriptValue(QScriptEngine* e, const glm::vec2& v) { r inline QScriptValue convertScriptValue(QScriptEngine* e, const glm::vec3& v) { return vec3toScriptValue(e, v); } inline QScriptValue convertScriptValue(QScriptEngine* e, float v) { return QScriptValue(v); } inline QScriptValue convertScriptValue(QScriptEngine* e, int v) { return QScriptValue(v); } +inline QScriptValue convertScriptValue(QScriptEngine* e, bool v) { return QScriptValue(v); } inline QScriptValue convertScriptValue(QScriptEngine* e, quint16 v) { return QScriptValue(v); } inline QScriptValue convertScriptValue(QScriptEngine* e, quint32 v) { return QScriptValue(v); } inline QScriptValue convertScriptValue(QScriptEngine* e, quint64 v) { return QScriptValue((qsreal)v); } From 800d15b4055d30076855735652af5f082cdd40db Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Fri, 23 Feb 2018 16:23:50 -0800 Subject: [PATCH 43/49] Make sure settings timer is running --- interface/src/Application.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e956195ca6..3dd75e2c25 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1512,6 +1512,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo settingsTimer->setSingleShot(false); settingsTimer->setInterval(SAVE_SETTINGS_INTERVAL); // 10s, Qt::CoarseTimer acceptable QObject::connect(settingsTimer, &QTimer::timeout, this, &Application::saveSettings); + settingsTimer->start(); }, QThread::LowestPriority); if (Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson)) { From f824edd04e98d3cbd5da12a4a60f5779f4c5bd7a Mon Sep 17 00:00:00 2001 From: humbletim Date: Mon, 26 Feb 2018 04:58:22 -0500 Subject: [PATCH 44/49] * 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; - }; -}; - From 54dc0240b0cb5c30fc4c7480391812e9930ae6b5 Mon Sep 17 00:00:00 2001 From: humbletim Date: Mon, 26 Feb 2018 05:26:35 -0500 Subject: [PATCH 45/49] remove bufferviewscripting refs; fix compiler warnings --- .../GraphicsScriptingInterface.cpp | 1 - .../src/graphics-scripting/ScriptableMesh.cpp | 1 - .../graphics-scripting/ScriptableMeshPart.cpp | 1 - .../src/graphics/BufferViewHelpers.cpp | 300 +++++++++--------- 4 files changed, 149 insertions(+), 154 deletions(-) diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp index 22ef346df6..ed8982b9c7 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp @@ -9,7 +9,6 @@ // #include "GraphicsScriptingInterface.h" -#include "BufferViewScripting.h" #include "GraphicsScriptingUtil.h" #include "OBJWriter.h" #include "RegisteredMetaTypes.h" diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp index 2cef4fef64..e5e58b847a 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp @@ -10,7 +10,6 @@ #include "ScriptableMesh.h" #include "ScriptableMeshPart.h" -#include "BufferViewScripting.h" #include "GraphicsScriptingUtil.h" #include "OBJWriter.h" #include diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.cpp index 0d4bb7bdc5..9d6359bee3 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.cpp @@ -9,7 +9,6 @@ #include "ScriptableMeshPart.h" -#include "BufferViewScripting.h" #include "GraphicsScriptingUtil.h" #include "OBJWriter.h" #include diff --git a/libraries/graphics/src/graphics/BufferViewHelpers.cpp b/libraries/graphics/src/graphics/BufferViewHelpers.cpp index 46bd39bb45..5c7e6ff892 100644 --- a/libraries/graphics/src/graphics/BufferViewHelpers.cpp +++ b/libraries/graphics/src/graphics/BufferViewHelpers.cpp @@ -32,14 +32,12 @@ 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" } }; +namespace buffer_helpers { -gpu::BufferView buffer_helpers::mesh::getBufferView(const graphics::MeshPointer& mesh, gpu::Stream::Slot slot) { - return slot == gpu::Stream::POSITION ? mesh->getVertexBuffer() : mesh->getAttributeBuffer(slot); -} +const std::array XYZW = { { "x", "y", "z", "w" } }; +const std::array ZERO123 = { { "0", "1", "2", "3" } }; -QMap buffer_helpers::ATTRIBUTES{ +QMap ATTRIBUTES{ {"position", gpu::Stream::POSITION }, {"normal", gpu::Stream::NORMAL }, {"color", gpu::Stream::COLOR }, @@ -64,7 +62,7 @@ namespace { } } -void buffer_helpers::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) { auto absNormal = glm::abs(normal); auto absTangent = glm::abs(tangent); normal /= glm::max(1e-6f, glm::max(glm::max(absNormal.x, absNormal.y), absNormal.z)); @@ -90,27 +88,25 @@ void buffer_helpers::packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, g packedTangent = tangentStruct.pack; } -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; - } +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 i; } -template<> glm::uint32 buffer_helpers::forEach(const gpu::BufferView& view, std::function func) { +template<> glm::uint32 forEach(const gpu::BufferView& view, std::function func) { return forEachGlmVec(view, func); } template -QVariant buffer_helpers::glmVecToVariant(const T& v, bool asArray /*= false*/) { +QVariant glmVecToVariant(const T& v, bool asArray /*= false*/) { static const auto len = T().length(); if (asArray) { QVariantList list; @@ -128,7 +124,7 @@ QVariant buffer_helpers::glmVecToVariant(const T& v, bool asArray /*= false*/) { } template -const T buffer_helpers::glmVecFromVariant(const QVariant& v) { +const T glmVecFromVariant(const QVariant& v) { auto isMap = v.type() == (QVariant::Type)QMetaType::QVariantMap; static const auto len = T().length(); const auto& components = isMap ? XYZW : ZERO123; @@ -155,23 +151,21 @@ const T buffer_helpers::glmVecFromVariant(const QVariant& v) { // QVector => BufferView template -gpu::BufferView buffer_helpers::newFromVector(const QVector& elements, const gpu::Element& elementType) { +gpu::BufferView 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 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 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::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); } +template<> gpu::BufferView newFromVector(const QVector& elements, const gpu::Element& elementType) { return bufferViewFromVector(elements, elementType); } +template<> gpu::BufferView newFromVector(const QVector& elements, const gpu::Element& elementType) { return bufferViewFromVector(elements, elementType); } +template<> gpu::BufferView newFromVector( const QVector& elements, const gpu::Element& elementType) { return bufferViewFromVector(elements, elementType); } +template<> gpu::BufferView newFromVector(const QVector& elements, const gpu::Element& elementType) { return bufferViewFromVector(elements, elementType); } +template<> gpu::BufferView newFromVector(const QVector& elements, const gpu::Element& elementType) { return bufferViewFromVector(elements, elementType); } struct GpuToGlmAdapter { static float error(const QString& name, const gpu::BufferView& view, glm::uint32 index, const char *hint) { @@ -356,24 +350,24 @@ struct GpuValueResolver { }; // BufferView => QVector -template QVector buffer_helpers::bufferToVector(const gpu::BufferView& view, const char *hint) { return GpuValueResolver,U>::toVector(view, hint); } +template QVector 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); } +template<> QVector bufferToVector(const gpu::BufferView& view, const char *hint) { return GpuValueResolver,int>::toVector(view, hint); } +template<> QVector bufferToVector(const gpu::BufferView& view, const char *hint) { return GpuValueResolver,glm::uint16>::toVector(view, hint); } +template<> QVector bufferToVector(const gpu::BufferView& view, const char *hint) { return GpuValueResolver,glm::uint32>::toVector(view, hint); } +template<> QVector bufferToVector(const gpu::BufferView& view, const char *hint) { return GpuValueResolver,glm::vec2>::toVector(view, hint); } +template<> QVector bufferToVector(const gpu::BufferView& view, const char *hint) { return GpuValueResolver,glm::vec3>::toVector(view, hint); } +template<> QVector 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); } +template<> int getValue(const gpu::BufferView& view, glm::uint32 index, const char *hint) { return GpuScalarToGlm::get(view, index, hint); } +template<> glm::uint32 getValue(const gpu::BufferView& view, glm::uint32 index, const char *hint) { return GpuScalarToGlm::get(view, index, hint); } +template<> glm::vec2 getValue(const gpu::BufferView& view, glm::uint32 index, const char *hint) { return GpuVec2ToGlm::get(view, index, hint); } +template<> glm::vec3 getValue(const gpu::BufferView& view, glm::uint32 index, const char *hint) { return GpuVec3ToGlm::get(view, index, hint); } +template<> glm::vec4 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) { +template<> QVariant getValue(const gpu::BufferView& view, glm::uint32 index, const char* hint) { if (!boundsCheck(view, index)) { qDebug() << "getValue -- out of bounds" << index << hint; return false; @@ -402,24 +396,8 @@ template<> QVariant buffer_helpers::getValue(const gpu::BufferView& vi 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; -} - // view.edit with conversion between types -template<> bool buffer_helpers::setValue(const gpu::BufferView& view, glm::uint32 index, const QVariant& v, const char* hint) { +template<> bool 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; @@ -450,80 +428,51 @@ template<> bool buffer_helpers::setValue(const gpu::BufferView& view, return false; } -template<> bool buffer_helpers::setValue(const gpu::BufferView& view, glm::uint32 index, const glm::uint32& value, const char* hint) { +template<> bool setValue(const gpu::BufferView& view, glm::uint32 index, const glm::uint32& value, const char* hint) { return GpuScalarToGlm::set(view, index, value, hint); } -template<> bool buffer_helpers::setValue(const gpu::BufferView& view, glm::uint32 index, const glm::uint16& value, const char* hint) { +template<> bool setValue(const gpu::BufferView& view, glm::uint32 index, const glm::uint16& value, const char* hint) { return GpuScalarToGlm::set(view, index, value, hint); } -template<> bool buffer_helpers::setValue(const gpu::BufferView& view, glm::uint32 index, const glm::vec2& value, const char* hint) { +template<> bool 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) { +template<> bool 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) { +template<> bool 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 qVariantListToGlmVector(const QVariantList& list) { + QVector output; + output.resize(list.size()); + int i = 0; + for (const auto& v : list) { + output[i++] = glmVecFromVariant(v); } - 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; + 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 QVector variantToVector(const QVariant& value) { qDebug() << "variantToVector[class]"; return qVariantListToGlmVector(value.toList()); } +template<> QVector variantToVector(const QVariant& value) { return qVariantListToScalarVector(value.toList()); } +template<> QVector variantToVector(const QVariant& value) { return qVariantListToScalarVector(value.toList()); } +template<> QVector variantToVector(const QVariant& value) { return qVariantListToScalarVector(value.toList()); } +template<> QVector variantToVector(const QVariant& value) { return qVariantListToGlmVector(value.toList()); } +template<> QVector variantToVector(const QVariant& value) { return qVariantListToGlmVector(value.toList()); } +template<> QVector variantToVector(const QVariant& value) { return qVariantListToGlmVector(value.toList()); } -template<> gpu::BufferView buffer_helpers::newFromVector(const QVector& _elements, const gpu::Element& elementType) { +template<> gpu::BufferView newFromVector(const QVector& _elements, const gpu::Element& elementType) { glm::uint32 numElements = _elements.size(); auto buffer = new gpu::Buffer(); buffer->resize(elementType.getSize() * numElements); @@ -535,14 +484,14 @@ template<> gpu::BufferView buffer_helpers::newFromVector(const QVector } -gpu::BufferView buffer_helpers::clone(const gpu::BufferView& input) { +gpu::BufferView clone(const gpu::BufferView& input) { return gpu::BufferView( std::make_shared(input._buffer->getSize(), input._buffer->getData()), input._offset, input._size, input._stride, input._element ); } -gpu::BufferView buffer_helpers::resized(const gpu::BufferView& input, glm::uint32 numElements) { +gpu::BufferView 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; @@ -559,36 +508,85 @@ gpu::BufferView buffer_helpers::resized(const gpu::BufferView& input, glm::uint3 return output; } -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::mesh::getAllBufferViews(mesh); - for (const auto& a : attributeViews) { - auto& view = a.second; - auto slot = buffer_helpers::ATTRIBUTES[a.first]; - auto points = buffer_helpers::clone(view); - if (slot == gpu::Stream::POSITION) { - clone->setVertexBuffer(points); - } else { - clone->addAttribute(slot, points); - } +// mesh helpers +namespace mesh { + gpu::BufferView getBufferView(const graphics::MeshPointer& mesh, gpu::Stream::Slot slot) { + return slot == gpu::Stream::POSITION ? mesh->getVertexBuffer() : mesh->getAttributeBuffer(slot); } - return clone; -} -std::map buffer_helpers::mesh::getAllBufferViews(const graphics::MeshPointer& mesh) { - std::map attributeViews; - if (!mesh) { + glm::uint32 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] = getValue(a.second, i, qUtf8Printable(a.first)); + } + if (!func(i, values)) { + break; + } + } + return i; + } + bool 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; + setValue(view, index, value); + } else { + ok = false; + //qCDebug(graphics_scripting) << "(skipping) setVertexAttributes" << vertexIndex << name; + } + } + } + return ok; + } + + QVariant getVertexAttributes(const graphics::MeshPointer& mesh, glm::uint32 vertexIndex) { + auto attributeViews = getAllBufferViews(mesh); + QVariantMap values; + for (const auto& a : attributeViews) { + values[a.first] = getValue(a.second, vertexIndex, qUtf8Printable(a.first)); + } + return values; + } + + graphics::MeshPointer clone(const graphics::MeshPointer& mesh) { + auto clonedMesh = std::make_shared(); + clonedMesh->displayName = (QString::fromStdString(mesh->displayName) + "-clone").toStdString(); + clonedMesh->setIndexBuffer(buffer_helpers::clone(mesh->getIndexBuffer())); + clonedMesh->setPartBuffer(buffer_helpers::clone(mesh->getPartBuffer())); + auto attributeViews = getAllBufferViews(mesh); + for (const auto& a : attributeViews) { + auto& view = a.second; + auto slot = ATTRIBUTES[a.first]; + auto points = buffer_helpers::clone(view); + if (slot == gpu::Stream::POSITION) { + clonedMesh->setVertexBuffer(points); + } else { + clonedMesh->addAttribute(slot, points); + } + } + return clonedMesh; + } + + std::map getAllBufferViews(const graphics::MeshPointer& mesh) { + std::map attributeViews; + if (!mesh) { + return attributeViews; + } + for (const auto& a : ATTRIBUTES.toStdMap()) { + auto bufferView = getBufferView(mesh, a.second); + if (bufferView.getNumElements()) { + attributeViews[a.first] = bufferView; + } + } return attributeViews; } - for (const auto& a : buffer_helpers::ATTRIBUTES.toStdMap()) { - auto bufferView = getBufferView(mesh, a.second); - if (bufferView.getNumElements()) { - attributeViews[a.first] = bufferView; - } - } - return attributeViews; -} - +} // mesh +} // buffer_helpers From 3dbe5d79bb9900f43dd8edfd6e1e8db164d3eb3d Mon Sep 17 00:00:00 2001 From: humbletim Date: Mon, 26 Feb 2018 06:01:16 -0500 Subject: [PATCH 46/49] fix windows punctilious warnings --- .../src/graphics-scripting/ScriptableMesh.cpp | 14 +++++++------- libraries/render-utils/src/Model.cpp | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp index e5e58b847a..5ff916275e 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp @@ -131,10 +131,10 @@ QVariantMap scriptable::ScriptableMesh::getBufferFormats() const { 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 }, + { "length", (glm::uint32)bufferView.getNumElements() }, + { "byteLength", (glm::uint32)bufferView._size }, + { "offset", (glm::uint32) bufferView._offset }, + { "stride", (glm::uint32)bufferView._stride }, { "element", scriptable::toVariant(bufferView._element) }, }; } @@ -171,7 +171,7 @@ glm::uint32 scriptable::ScriptableMesh::addAttribute(const QString& attributeNam return values.size(); } else { auto bufferView = buffer_helpers::mesh::getBufferView(mesh, slot); - auto current = bufferView.getNumElements(); + auto current = (glm::uint32)bufferView.getNumElements(); if (current < numVertices) { bufferView = buffer_helpers::resized(bufferView, numVertices); for (glm::uint32 i = current; i < numVertices; i++) { @@ -220,7 +220,7 @@ QVariantList scriptable::ScriptableMesh::queryVertexAttributes(QVariant selector } auto slotNum = _getSlotNumber(attributeName); const auto& bufferView = buffer_helpers::mesh::getBufferView(getMeshPointer(), static_cast(slotNum)); - glm::uint32 numElements = bufferView.getNumElements(); + glm::uint32 numElements = (glm::uint32)bufferView.getNumElements(); for (glm::uint32 i = 0; i < numElements; i++) { result << buffer_helpers::getValue(bufferView, i, qUtf8Printable(attributeName)); } @@ -339,7 +339,7 @@ bool scriptable::ScriptableMesh::isValidIndex(glm::uint32 vertexIndex, const QSt return false; } auto view = buffer_helpers::mesh::getBufferView(getMeshPointer(), static_cast(slotNum)); - if (vertexIndex >= view.getNumElements()) { + if (vertexIndex >= (glm::uint32)view.getNumElements()) { if (context()) { context()->throwError(QString("vertexIndex=%1 out of range (attribute=%2, numElements=%3)").arg(vertexIndex).arg(attributeName).arg(view.getNumElements())); } diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 0abeb70254..f6e3ce20f0 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -635,7 +635,7 @@ bool Model::replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointe FBXMesh mesh; mesh._mesh = newMesh.getMeshPointer(); mesh.vertices = buffer_helpers::mesh::attributeToVector(mesh._mesh, gpu::Stream::POSITION); - int numParts = newMesh.getMeshPointer()->getNumParts(); + int numParts = (int)newMesh.getMeshPointer()->getNumParts(); for (int partID = 0; partID < numParts; partID++) { FBXMeshPart part; part.triangleIndices = buffer_helpers::bufferToVector(mesh._mesh->getIndexBuffer(), "part.triangleIndices"); From 767569bc4015e61bb5211e8ffc6da77ca2d2b19d Mon Sep 17 00:00:00 2001 From: humbletim Date: Mon, 26 Feb 2018 18:00:22 -0500 Subject: [PATCH 47/49] * changes per CR feedback * make MeshPart.topology read-only * JS Graphics API starter documentation * remove redundant qscriptvalue sequence registrations --- .../src/graphics-scripting/Forward.h | 7 +- .../GraphicsScriptingInterface.cpp | 74 +++++++------------ .../GraphicsScriptingInterface.h | 35 +++++++-- .../src/graphics-scripting/ScriptableMesh.h | 11 ++- .../graphics-scripting/ScriptableMeshPart.cpp | 29 ++++++-- .../graphics-scripting/ScriptableMeshPart.h | 15 +++- .../src/graphics-scripting/ScriptableModel.h | 10 ++- .../graphics/src/graphics/GpuHelpers.cpp | 3 +- 8 files changed, 116 insertions(+), 68 deletions(-) diff --git a/libraries/graphics-scripting/src/graphics-scripting/Forward.h b/libraries/graphics-scripting/src/graphics-scripting/Forward.h index 2f2f191dce..38f45fbfd0 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/Forward.h +++ b/libraries/graphics-scripting/src/graphics-scripting/Forward.h @@ -94,7 +94,12 @@ namespace scriptable { using ScriptableMeshPointer = QPointer; class ScriptableMeshPart; using ScriptableMeshPartPointer = QPointer; - bool registerMetaTypes(QScriptEngine* engine); } +Q_DECLARE_METATYPE(glm::uint32) +Q_DECLARE_METATYPE(QVector) Q_DECLARE_METATYPE(NestableType) +Q_DECLARE_METATYPE(scriptable::MeshPointer) +Q_DECLARE_METATYPE(scriptable::WeakMeshPointer) +Q_DECLARE_METATYPE(scriptable::ModelProviderPointer) + diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp index ed8982b9c7..f3ba5e8406 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp @@ -26,9 +26,6 @@ #include GraphicsScriptingInterface::GraphicsScriptingInterface(QObject* parent) : QObject(parent), QScriptable() { - if (auto scriptEngine = qobject_cast(parent)) { - this->registerMetaTypes(scriptEngine); - } } void GraphicsScriptingInterface::jsThrowError(const QString& error) { @@ -179,12 +176,17 @@ scriptable::ScriptableMeshPointer GraphicsScriptingInterface::newMesh(const QVar const auto numVertices = vertices.size(); const auto numIndices = indices.size(); - const auto topology = graphics::Mesh::TRIANGLES; + const auto topology = graphics::TOPOLOGIES.key(topologyName); + + // TODO: support additional topologies (POINTS and LINES ought to "just work" -- + // if MeshPartPayload::drawCall is updated to actually check the Mesh::Part::_topology value + // (TRIANGLE_STRIP, TRIANGLE_FAN, QUADS, QUAD_STRIP may need additional conversion code though) + static const QStringList acceptableTopologies{ "triangles" }; // sanity checks QString error; - if (!topologyName.isEmpty() && topologyName != "triangles") { - error = "expected 'triangles' or undefined for .topology"; + if (!topologyName.isEmpty() && !acceptableTopologies.contains(topologyName)) { + error = QString("expected .topology to be %1").arg(acceptableTopologies.join(" | ")); } 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) { @@ -227,8 +229,8 @@ scriptable::ScriptableMeshPointer GraphicsScriptingInterface::newMesh(const QVar 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) + // TODO: newFromVector does inbound type conversion, but not compression or packing + // (later we should autodetect if fitting into gpu::INDEX_UINT16 and reduce / pack normals etc.) mesh->setIndexBuffer(buffer_helpers::newFromVector(indices, gpu::Format::INDEX_INT32)); mesh->setVertexBuffer(buffer_helpers::newFromVector(vertices, gpu::Format::VEC3F_XYZ)); if (normals.size()) { @@ -240,7 +242,7 @@ scriptable::ScriptableMeshPointer GraphicsScriptingInterface::newMesh(const QVar if (texCoords0.size()) { mesh->addAttribute(gpu::Stream::TEXCOORD0, buffer_helpers::newFromVector(texCoords0, gpu::Format::VEC2F_UV)); } - QVector parts = {{ 0, indices.size(), 0, topology}}; + QVector parts = {{ 0, indices.size(), 0, topology }}; mesh->setPartBuffer(buffer_helpers::newFromVector(parts, gpu::Element::PART_DRAWCALL)); return scriptable::make_scriptowned(mesh, nullptr); } @@ -262,10 +264,6 @@ QString GraphicsScriptingInterface::exportModelToOBJ(const scriptable::Scriptabl return QString(); } -void GraphicsScriptingInterface::registerMetaTypes(QScriptEngine* engine) { - scriptable::registerMetaTypes(engine); -} - MeshPointer GraphicsScriptingInterface::getMeshPointer(const scriptable::ScriptableMesh& meshProxy) { return meshProxy.getMeshPointer(); } @@ -287,14 +285,6 @@ MeshPointer GraphicsScriptingInterface::getMeshPointer(scriptable::ScriptableMes } 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"), @@ -312,7 +302,7 @@ namespace { } namespace scriptable { - template int registerQPointerThing(QScriptEngine* engine) { + template int registerQPointerMetaType(QScriptEngine* engine) { qScriptRegisterSequenceMetaType>>(engine); return qScriptRegisterMetaType>( engine, @@ -346,44 +336,32 @@ namespace scriptable { } template int registerDebugEnum(QScriptEngine* engine, const DebugEnums& debugEnums) { - static const DebugEnums& poop = debugEnums; + static const DebugEnums& instance = debugEnums; return qScriptRegisterMetaType( engine, [](QScriptEngine* engine, const T& topology) -> QScriptValue { - return poop.value(topology); + return instance.value(topology); }, [](const QScriptValue& value, T& topology) { - topology = poop.key(value.toString()); + topology = instance.key(value.toString()); } ); } +} - bool registerMetaTypes(QScriptEngine* engine) { - qScriptRegisterSequenceMetaType>(engine); +void GraphicsScriptingInterface::registerMetaTypes(QScriptEngine* engine) { + qScriptRegisterSequenceMetaType>(engine); - qScriptRegisterMetaType(engine, qVectorUInt32ToScriptValue, qVectorUInt32FromScriptValue); + scriptable::registerQPointerMetaType(engine); + scriptable::registerQPointerMetaType(engine); + scriptable::registerQPointerMetaType(engine); - 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(); - } + scriptable::registerDebugEnum(engine, graphics::TOPOLOGIES); + scriptable::registerDebugEnum(engine, gpu::TYPES); + scriptable::registerDebugEnum(engine, gpu::SEMANTICS); + scriptable::registerDebugEnum(engine, gpu::DIMENSIONS); + Q_UNUSED(metaTypeIds); } #include "GraphicsScriptingInterface.moc" diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h index a66e382bc7..2e86ab01e2 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h @@ -20,6 +20,12 @@ #include "ScriptableMesh.h" #include + +/**jsdoc + * The experimental Graphics API (experimental) lets you query and manage certain graphics-related structures (like underlying meshes and textures) from scripting. + * @namespace Graphics + */ + class GraphicsScriptingInterface : public QObject, public QScriptable, public Dependency { Q_OBJECT @@ -29,15 +35,36 @@ public: public slots: /**jsdoc - * Returns the model/meshes associated with a UUID (entityID, overlayID, or avatarID) + * Returns a model reference object associated with the specified UUID ({@link EntityID}, {@link OverlayID}, or {@link AvatarID}). * - * @function GraphicsScriptingInterface.getModel - * @param {UUID} The objectID of the model whose meshes are to be retrieve + * @function Graphics.getModel + * @param {UUID} The objectID of the model whose meshes are to be retrieved. + * @return {Graphics.Model} the resulting Model object */ 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); + + /**jsdoc + * Create a new Mesh / Mesh Part with the specified data buffers. + * + * @function Graphics.newMesh + * @param {Graphics.IFSData} ifsMeshData Index-Faced Set (IFS) arrays used to create the new mesh. + * @return {Graphics.Mesh} the resulting Mesh / Mesh Part object + */ + /**jsdoc + * @typedef {object} Graphics.IFSData + * @property {string} [name] - mesh name (useful for debugging / debug prints). + * @property {number[]} indices - vertex indices to use for the mesh faces. + * @property {Vec3[]} vertices - vertex positions (model space) + * @property {Vec3[]} [normals] - vertex normals (normalized) + * @property {Vec3[]} [colors] - vertex colors (normalized) + * @property {Vec2[]} [texCoords0] - vertex texture coordinates (normalized) + */ scriptable::ScriptableMeshPointer newMesh(const QVariantMap& ifsMeshData); #ifdef SCRIPTABLE_MESH_TODO @@ -58,6 +85,4 @@ private: }; -Q_DECLARE_METATYPE(scriptable::ModelProviderPointer) - #endif // hifi_GraphicsScriptingInterface_h diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h index 16393de8c7..50299b8d20 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h @@ -28,6 +28,15 @@ #include namespace scriptable { + /**jsdoc + * @typedef {object} Graphics.Mesh + * @property {Graphics.MeshPart[]} parts - Array of submesh part references. + * @property {string[]} attributeNames - Vertex attribute names (color, normal, etc.) + * @property {number} numParts - The number of parts contained in the mesh. + * @property {number} numIndices - Total number of vertex indices in the mesh. + * @property {number} numVertices - Total number of vertices in the Mesh. + * @property {number} numAttributes - Number of currently defined vertex attributes. + */ class ScriptableMesh : public ScriptableMeshBase, QScriptable { Q_OBJECT public: @@ -97,5 +106,3 @@ namespace scriptable { Q_DECLARE_METATYPE(scriptable::ScriptableMeshPointer) 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 index 9d6359bee3..ac48b23323 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.cpp @@ -21,6 +21,7 @@ #include #include + QString scriptable::ScriptableMeshPart::toOBJ() { if (!getMeshPointer()) { if (context()) { @@ -371,6 +372,7 @@ bool scriptable::ScriptableMeshPart::setIndices(const QVector& indi 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())); + return false; } auto mesh = getMeshPointer(); auto indexBuffer = mesh->getIndexBuffer(); @@ -397,18 +399,24 @@ const graphics::Mesh::Part& scriptable::ScriptableMeshPart::getMeshPart() const return getMeshPointer()->getPartBuffer().get(partIndex); } +// FIXME: how we handle topology will need to be reworked if wanting to support TRIANGLE_STRIP, QUADS and QUAD_STRIP 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) { + switch (topology) { +#ifdef DEV_BUILD + case graphics::Mesh::Topology::POINTS: + case graphics::Mesh::Topology::LINES: +#endif + case graphics::Mesh::Topology::TRIANGLES: part._topology = topology; return true; + default: + context()->throwError("changing topology to " + graphics::toString(topology) + " is not yet supported"); + return false; } - return false; } glm::uint32 scriptable::ScriptableMeshPart::getTopologyLength() const { @@ -416,16 +424,23 @@ glm::uint32 scriptable::ScriptableMeshPart::getTopologyLength() const { case graphics::Mesh::Topology::POINTS: return 1; case graphics::Mesh::Topology::LINES: return 2; case graphics::Mesh::Topology::TRIANGLES: return 3; + case graphics::Mesh::Topology::QUADS: return 4; 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()); + switch (getTopology()) { + case graphics::Mesh::Topology::POINTS: + case graphics::Mesh::Topology::LINES: + case graphics::Mesh::Topology::TRIANGLES: + case graphics::Mesh::Topology::QUADS: + if (faceIndex < getNumFaces()) { + return getIndices().mid(faceIndex * getTopologyLength(), getTopologyLength()); + } + default: return QVector(); } - return QVector(); } QVariantMap scriptable::ScriptableMeshPart::getPartExtents() const { diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h index 4ef0465ca3..dd71d9b998 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h @@ -10,6 +10,18 @@ #include "ScriptableMesh.h" namespace scriptable { + /**jsdoc + * @typedef {object} Graphics.MeshPart + * @property {number} partIndex - The part index (within the containing Mesh). + * @property {Graphics.Topology} topology - element interpretation (currently only 'triangles' is supported). + * @property {string[]} attributeNames - Vertex attribute names (color, normal, etc.) + * @property {number} numIndices - Number of vertex indices that this mesh part refers to. + * @property {number} numVerticesPerFace - Number of vertices per face (eg: 3 when topology is 'triangles'). + * @property {number} numFaces - Number of faces represented by the mesh part (numIndices / numVerticesPerFace). + * @property {number} numVertices - Total number of vertices in the containing Mesh. + * @property {number} numAttributes - Number of currently defined vertex attributes. + */ + class ScriptableMeshPart : public QObject, QScriptable { Q_OBJECT Q_PROPERTY(bool valid READ isValid) @@ -18,7 +30,8 @@ namespace scriptable { 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) + // NOTE: making read-only for now (see also GraphicsScriptingInterface::newMesh and MeshPartPayload::drawCall) + Q_PROPERTY(graphics::Mesh::Topology topology READ getTopology) Q_PROPERTY(glm::uint32 numFaces READ getNumFaces) Q_PROPERTY(glm::uint32 numAttributes READ getNumAttributes) diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h index d821f1224d..d496323c1c 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h @@ -15,6 +15,14 @@ class QScriptValue; namespace scriptable { using ScriptableMeshes = QVector; + + /**jsdoc + * @typedef {object} Graphics.Model + * @property {Uuid} objectID - UUID of corresponding inworld object (if model is associated) + * @property {number} numMeshes - The number of submeshes contained in the model. + * @property {Graphics.Mesh[]} meshes - Array of submesh references. + */ + class ScriptableModel : public ScriptableModelBase { Q_OBJECT Q_PROPERTY(QUuid objectID MEMBER objectID CONSTANT) @@ -45,8 +53,6 @@ namespace scriptable { } -Q_DECLARE_METATYPE(scriptable::MeshPointer) -Q_DECLARE_METATYPE(scriptable::WeakMeshPointer) Q_DECLARE_METATYPE(scriptable::ScriptableModelPointer) Q_DECLARE_METATYPE(scriptable::ScriptableModelBase) Q_DECLARE_METATYPE(scriptable::ScriptableModelBasePointer) diff --git a/libraries/graphics/src/graphics/GpuHelpers.cpp b/libraries/graphics/src/graphics/GpuHelpers.cpp index 63393df5e1..0c3bd945e1 100644 --- a/libraries/graphics/src/graphics/GpuHelpers.cpp +++ b/libraries/graphics/src/graphics/GpuHelpers.cpp @@ -21,8 +21,7 @@ namespace graphics { }; } namespace gpu { - - DebugEnums TYPES{ + DebugEnums TYPES{ { Type::FLOAT, "float" }, { Type::INT32, "int32" }, { Type::UINT32, "uint32" }, From d3bae870660a43c24e8b8738dfc6be51ac63e2a3 Mon Sep 17 00:00:00 2001 From: humbletim Date: Mon, 26 Feb 2018 19:07:55 -0500 Subject: [PATCH 48/49] * remove unneeded Q_DECLARE_METATYPEs * CR feedback --- .../src/graphics-scripting/Forward.h | 8 -------- .../GraphicsScriptingInterface.h | 4 ++++ .../src/graphics-scripting/ScriptableMesh.cpp | 18 +++++++++--------- .../src/graphics-scripting/ScriptableMesh.h | 3 +-- .../graphics-scripting/ScriptableMeshPart.cpp | 4 ++-- .../src/graphics-scripting/ScriptableModel.h | 5 +---- libraries/render-utils/src/Model.cpp | 11 ++++++++--- 7 files changed, 25 insertions(+), 28 deletions(-) diff --git a/libraries/graphics-scripting/src/graphics-scripting/Forward.h b/libraries/graphics-scripting/src/graphics-scripting/Forward.h index 38f45fbfd0..a0e186dda5 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/Forward.h +++ b/libraries/graphics-scripting/src/graphics-scripting/Forward.h @@ -95,11 +95,3 @@ namespace scriptable { class ScriptableMeshPart; using ScriptableMeshPartPointer = QPointer; } - -Q_DECLARE_METATYPE(glm::uint32) -Q_DECLARE_METATYPE(QVector) -Q_DECLARE_METATYPE(NestableType) -Q_DECLARE_METATYPE(scriptable::MeshPointer) -Q_DECLARE_METATYPE(scriptable::WeakMeshPointer) -Q_DECLARE_METATYPE(scriptable::ModelProviderPointer) - diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h index 2e86ab01e2..84c6cb6fa8 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h @@ -85,4 +85,8 @@ private: }; +Q_DECLARE_METATYPE(glm::uint32) +Q_DECLARE_METATYPE(QVector) +Q_DECLARE_METATYPE(NestableType) + #endif // hifi_GraphicsScriptingInterface_h diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp index 5ff916275e..bc31d45229 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp @@ -118,7 +118,7 @@ bool scriptable::ScriptableMesh::setVertexAttributes(glm::uint32 vertexIndex, co return buffer_helpers::mesh::setVertexAttributes(getMeshPointer(), vertexIndex, attributes); } -int scriptable::ScriptableMesh::_getSlotNumber(const QString& attributeName) const { +int scriptable::ScriptableMesh::getSlotNumber(const QString& attributeName) const { if (auto mesh = getMeshPointer()) { return buffer_helpers::ATTRIBUTES.value(attributeName, -1); } @@ -142,9 +142,9 @@ QVariantMap scriptable::ScriptableMesh::getBufferFormats() const { } bool scriptable::ScriptableMesh::removeAttribute(const QString& attributeName) { - auto slot = isValid() ? _getSlotNumber(attributeName) : -1; + auto slot = isValid() ? getSlotNumber(attributeName) : -1; if (slot < 0) { - return 0; + return false; } if (slot == gpu::Stream::POSITION) { context()->throwError("cannot remove .position attribute"); @@ -158,7 +158,7 @@ bool scriptable::ScriptableMesh::removeAttribute(const QString& attributeName) { } glm::uint32 scriptable::ScriptableMesh::addAttribute(const QString& attributeName, const QVariant& defaultValue) { - auto slot = isValid() ? _getSlotNumber(attributeName) : -1; + auto slot = isValid() ? getSlotNumber(attributeName) : -1; if (slot < 0) { return 0; } @@ -187,7 +187,7 @@ glm::uint32 scriptable::ScriptableMesh::addAttribute(const QString& attributeNam } glm::uint32 scriptable::ScriptableMesh::fillAttribute(const QString& attributeName, const QVariant& value) { - auto slot = isValid() ? _getSlotNumber(attributeName) : -1; + auto slot = isValid() ? getSlotNumber(attributeName) : -1; if (slot < 0) { return 0; } @@ -218,7 +218,7 @@ QVariantList scriptable::ScriptableMesh::queryVertexAttributes(QVariant selector if (!isValidIndex(0, attributeName)) { return result; } - auto slotNum = _getSlotNumber(attributeName); + auto slotNum = getSlotNumber(attributeName); const auto& bufferView = buffer_helpers::mesh::getBufferView(getMeshPointer(), static_cast(slotNum)); glm::uint32 numElements = (glm::uint32)bufferView.getNumElements(); for (glm::uint32 i = 0; i < numElements; i++) { @@ -231,7 +231,7 @@ QVariant scriptable::ScriptableMesh::getVertexProperty(glm::uint32 vertexIndex, if (!isValidIndex(vertexIndex, attributeName)) { return QVariant(); } - auto slotNum = _getSlotNumber(attributeName); + auto slotNum = getSlotNumber(attributeName); const auto& bufferView = buffer_helpers::mesh::getBufferView(getMeshPointer(), static_cast(slotNum)); return buffer_helpers::getValue(bufferView, vertexIndex, qUtf8Printable(attributeName)); } @@ -240,7 +240,7 @@ bool scriptable::ScriptableMesh::setVertexProperty(glm::uint32 vertexIndex, cons if (!isValidIndex(vertexIndex, attributeName)) { return false; } - auto slotNum = _getSlotNumber(attributeName); + auto slotNum = getSlotNumber(attributeName); const auto& bufferView = buffer_helpers::mesh::getBufferView(getMeshPointer(), static_cast(slotNum)); return buffer_helpers::setValue(bufferView, vertexIndex, value); } @@ -331,7 +331,7 @@ bool scriptable::ScriptableMesh::isValidIndex(glm::uint32 vertexIndex, const QSt return false; } if (!attributeName.isEmpty()) { - auto slotNum = _getSlotNumber(attributeName); + auto slotNum = getSlotNumber(attributeName); if (slotNum < 0) { if (context()) { context()->throwError(QString("invalid attributeName=%1").arg(attributeName)); diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h index 50299b8d20..62a67aa5e6 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h @@ -74,9 +74,8 @@ namespace scriptable { QVector getMeshParts() const; QVariantMap getMeshExtents() const; - // TODO: remove Q_INVOKABLE (curently exposed for debugging ) - Q_INVOKABLE int _getSlotNumber(const QString& attributeName) const; operator bool() const { return !weakMesh.expired(); } + int getSlotNumber(const QString& attributeName) const; public slots: const scriptable::ScriptableModelPointer getParentModel() const { return qobject_cast(model); } diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.cpp index ac48b23323..4414b0ad7e 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.cpp @@ -57,7 +57,7 @@ bool scriptable::ScriptableMeshPart::setVertexProperty(glm::uint32 vertexIndex, if (!isValidIndex(vertexIndex, attributeName)) { return false; } - auto slotNum = parentMesh->_getSlotNumber(attributeName); + auto slotNum = parentMesh->getSlotNumber(attributeName); const auto& bufferView = buffer_helpers::mesh::getBufferView(getMeshPointer(), static_cast(slotNum)); return buffer_helpers::setValue(bufferView, vertexIndex, value); } @@ -369,7 +369,7 @@ bool scriptable::ScriptableMeshPart::setIndices(const QVector& indi return false; } glm::uint32 len = indices.size(); - if (len != getNumVertices()) { + if (len != getNumIndices()) { context()->throwError(QString("setIndices: currently new indicies must be assign 1:1 across old indicies (indicies.size()=%1, numIndices=%2)") .arg(len).arg(getNumIndices())); return false; diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h index d496323c1c..9a4c4f695b 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h @@ -44,8 +44,6 @@ namespace scriptable { scriptable::ScriptableModelPointer cloneModel(const QVariantMap& options = QVariantMap()); QString toString() const; - // QScriptEngine-specific wrappers - //glm::uint32 forEachMeshVertexAttribute(QScriptValue callback); protected: glm::uint32 getNumMeshes() { return meshes.size(); } @@ -54,5 +52,4 @@ namespace scriptable { } Q_DECLARE_METATYPE(scriptable::ScriptableModelPointer) -Q_DECLARE_METATYPE(scriptable::ScriptableModelBase) -Q_DECLARE_METATYPE(scriptable::ScriptableModelBasePointer) +Q_DECLARE_METATYPE(QVector) diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index f6e3ce20f0..700ced1502 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -604,15 +604,20 @@ bool Model::replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointe render::Transaction transaction; const render::ScenePointer& scene = AbstractViewStateInterface::instance()->getMain3DScene(); - meshIndex = meshIndex >= 0 ? meshIndex : 0; - partIndex = partIndex >= 0 ? partIndex : 0; + meshIndex = max(meshIndex, 0); + partIndex = max(partIndex, 0); - if (meshIndex >= meshes.size()) { + if (meshIndex >= (int)meshes.size()) { qDebug() << meshIndex << "meshIndex >= newModel.meshes.size()" << meshes.size(); return false; } auto mesh = meshes[meshIndex].getMeshPointer(); + + if (partIndex >= (int)mesh->getNumParts()) { + qDebug() << partIndex << "partIndex >= mesh->getNumParts()" << mesh->getNumParts(); + return false; + } { // update visual geometry render::Transaction transaction; From 2a204533f4344ccf4f116de169f6fd2ff75087ea Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 26 Feb 2018 15:56:11 -0800 Subject: [PATCH 49/49] Add comments to startup processes --- libraries/shared/src/LogHandler.cpp | 1 + libraries/shared/src/SettingInterface.cpp | 31 ++++++++++++--------- libraries/shared/src/SettingManager.h | 4 +-- libraries/shared/src/SharedUtil.cpp | 33 ++++++++++++++++++++--- libraries/shared/src/SharedUtil.h | 5 ++++ 5 files changed, 56 insertions(+), 18 deletions(-) diff --git a/libraries/shared/src/LogHandler.cpp b/libraries/shared/src/LogHandler.cpp index ff0d68dcce..cb3c0d07b2 100644 --- a/libraries/shared/src/LogHandler.cpp +++ b/libraries/shared/src/LogHandler.cpp @@ -192,6 +192,7 @@ QString LogHandler::printMessage(LogMsgType type, const QMessageLogContext& cont fprintf(stdout, "%s", qPrintable(logMessage)); #ifdef Q_OS_WIN + // On windows, this will output log lines into the Visual Studio "output" tab OutputDebugStringA(qPrintable(logMessage)); #endif return logMessage; diff --git a/libraries/shared/src/SettingInterface.cpp b/libraries/shared/src/SettingInterface.cpp index 62f116795e..04da35656e 100644 --- a/libraries/shared/src/SettingInterface.cpp +++ b/libraries/shared/src/SettingInterface.cpp @@ -23,25 +23,27 @@ #include "SharedUtil.h" namespace Setting { - // cleans up the settings private instance. Should only be run once at closing down. - void cleanupPrivateInstance() { + // This should only run as a post-routine in the QCoreApplication destructor + void cleanupSettingsSaveThread() { auto globalManager = DependencyManager::get(); Q_ASSERT(qApp && globalManager); - // grab the thread before we nuke the instance + // Grab the settings thread to shut it down QThread* settingsManagerThread = globalManager->thread(); - // quit the settings manager thread + // Quit the settings manager thread and wait for it so we + // don't get concurrent accesses when we save all settings below settingsManagerThread->quit(); settingsManagerThread->wait(); - // Save all settings + // [IMPORTANT] Save all settings when the QApplication goes down globalManager->saveAll(); qCDebug(shared) << "Settings thread stopped."; } - void setupPrivateInstance() { + // This should only run as a pre-routine in the QCoreApplication constructor + void setupSettingsSaveThread() { auto globalManager = DependencyManager::get(); Q_ASSERT(qApp && globalManager); @@ -55,6 +57,9 @@ namespace Setting { QObject::connect(thread, &QThread::finished, globalManager.data(), &Manager::stopTimer); // Setup manager threading affinity + // This makes the timer fire on the settings thread so we don't block the main + // thread with a lot of file I/O. + // We bring back the manager to the main thread when the QApplication goes down globalManager->moveToThread(thread); QObject::connect(thread, &QThread::finished, globalManager.data(), [] { auto globalManager = DependencyManager::get(); @@ -63,15 +68,17 @@ namespace Setting { // Move manager back to the main thread (has to be done on owning thread) globalManager->moveToThread(qApp->thread()); }); - + + // Start the settings save thread thread->start(); qCDebug(shared) << "Settings thread started."; - // Register cleanupPrivateInstance to run inside QCoreApplication's destructor. - qAddPostRoutine(cleanupPrivateInstance); + // Register cleanupSettingsSaveThread to run inside QCoreApplication's destructor. + // This will cleanup the settings thread and save all settings before shut down. + qAddPostRoutine(cleanupSettingsSaveThread); } - // Sets up the settings private instance. Should only be run once at startup. preInit() must be run beforehand, + // Sets up the settings private instance. Should only be run once at startup. void init() { // Set settings format QSettings::setDefaultFormat(JSON_FORMAT); @@ -91,11 +98,11 @@ namespace Setting { qCDebug(shared) << (deleted ? "Deleted" : "Failed to delete") << "settings lock file" << settingsLockFilename; } - // Setup settings manager + // Setup settings manager, the manager will live until the process shuts down DependencyManager::set(); // Add pre-routine to setup threading - qAddPreRoutine(setupPrivateInstance); + qAddPreRoutine(setupSettingsSaveThread); } void Interface::init() { diff --git a/libraries/shared/src/SettingManager.h b/libraries/shared/src/SettingManager.h index ffdd4ba42a..6696a1ecf4 100644 --- a/libraries/shared/src/SettingManager.h +++ b/libraries/shared/src/SettingManager.h @@ -50,8 +50,8 @@ namespace Setting { QHash _pendingChanges; friend class Interface; - friend void cleanupPrivateInstance(); - friend void setupPrivateInstance(); + friend void cleanupSettingsSaveThread(); + friend void setupSettingsSaveThread(); }; } diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index d05e940322..412ea59dfe 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -66,10 +66,23 @@ extern "C" FILE * __cdecl __iob_func(void) { #include "OctalCode.h" #include "SharedLogging.h" +// Global instances are stored inside the QApplication properties +// to provide a single instance across DLL boundaries. +// This is something we cannot do here since several DLLs +// and our main binaries statically link this "shared" library +// resulting in multiple static memory blocks in different constexts +// But we need to be able to use global instances before the QApplication +// is setup, so to accomplish that we stage the global instances in a local +// map and setup a pre routine (commitGlobalInstances) that will run in the +// QApplication constructor and commit all the staged instances to the +// QApplication properties. +// Note: One of the side effects of this, is that no DLL loaded before +// the QApplication is constructed, can expect to access the existing staged +// global instanced. For this reason, we advise all DLLs be loaded after +// the QApplication is instanced. static std::mutex stagedGlobalInstancesMutex; static std::unordered_map stagedGlobalInstances; - std::mutex& globalInstancesMutex() { return stagedGlobalInstancesMutex; } @@ -82,6 +95,9 @@ static void commitGlobalInstances() { stagedGlobalInstances.clear(); } +// This call is necessary for global instances to work across DLL boundaries +// Ideally, this founction would be called at the top of the main function. +// See description at the top of the file. void setupGlobalInstances() { qAddPreRoutine(commitGlobalInstances); } @@ -819,8 +835,8 @@ bool similarStrings(const QString& stringA, const QString& stringB) { } void disableQtBearerPoll() { - // to disable the Qt constant wireless scanning, set the env for polling interval - qDebug() << "Disabling Qt wireless polling by using a negative value for QTimer::setInterval"; + // To disable the Qt constant wireless scanning, set the env for polling interval to -1 + // The constant polling causes ping spikes on windows every 10 seconds or so that affect the audio const QByteArray DISABLE_BEARER_POLL_TIMEOUT = QString::number(-1).toLocal8Bit(); qputenv("QT_BEARER_POLL_TIMEOUT", DISABLE_BEARER_POLL_TIMEOUT); } @@ -1185,19 +1201,28 @@ void watchParentProcess(int parentPID) { void setupHifiApplication(QString applicationName) { disableQtBearerPoll(); // Fixes wifi ping spikes + // Those calls are necessary to format the log correctly + // and to direct the application to the correct location + // for read/writes into AppData and other platform equivalents. QCoreApplication::setApplicationName(applicationName); QCoreApplication::setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION); QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); QCoreApplication::setApplicationVersion(BuildInfo::VERSION); + // This ensures the global instances mechanism is correctly setup. + // You can find more details as to why this is important in the SharedUtil.h/cpp files setupGlobalInstances(); #ifndef WIN32 + // Windows tends to hold onto log lines until it has a sizeable buffer + // This makes the log feel unresponsive and trap useful log data in the log buffer + // when a crash occurs. + //Force windows to flush the buffer on each new line character to avoid this. setvbuf(stdout, NULL, _IOLBF, 0); #endif + // Install the standard hifi message handler so we get consistant log formatting qInstallMessageHandler(LogHandler::verboseMessageHandler); - qInfo() << "Starting."; } #ifdef Q_OS_WIN diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index 7cb750d3e3..51a84bed06 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -36,7 +36,12 @@ std::unique_ptr& globalInstancePointer() { return instancePtr; } +// Sets up the global instances for use +// This NEEDS to be called on startup +// for any binary planing on using global instances +// More details in cpp file void setupGlobalInstances(); + std::mutex& globalInstancesMutex(); QVariant getGlobalInstance(const char* propertyName); void setGlobalInstance(const char* propertyName, const QVariant& variant);