diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 85007f5f15..3ed5445493 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -204,7 +204,7 @@ endif() # link required hifi libraries link_hifi_libraries( - shared task octree ktx gpu gl procedural graphics render + shared task 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 d9fbe89d7c..214546b1bc 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -165,6 +165,7 @@ #include "scripting/AccountServicesScriptingInterface.h" #include "scripting/HMDScriptingInterface.h" #include "scripting/MenuScriptingInterface.h" +#include "graphics-scripting/GraphicsScriptingInterface.h" #include "scripting/SettingsScriptingInterface.h" #include "scripting/WindowScriptingInterface.h" #include "scripting/ControllerScriptingInterface.h" @@ -599,6 +600,71 @@ void messageHandler(QtMsgType type, const QMessageLogContext& context, const QSt } } + +class ApplicationMeshProvider : public scriptable::ModelProviderFactory { +public: + virtual scriptable::ModelProviderPointer lookupModelProvider(const QUuid& uuid) override { + 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 nullptr; + } + +private: + 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->modelProviderType = NestableType::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->modelProviderType = NestableType::Overlay; + } else { + qCWarning(interfaceapp) << "no renderer for overlay ID" << overlayID.toString(); + } + } else { + qCWarning(interfaceapp) << "overlay not found" << overlayID.toString(); + } + return provider; + } + + scriptable::ModelProviderPointer getAvatarModelProvider(QUuid sessionUUID) { + scriptable::ModelProviderPointer provider; + auto avatarManager = DependencyManager::get(); + if (auto avatar = avatarManager->getAvatarBySessionID(sessionUUID)) { + provider = std::dynamic_pointer_cast(avatar); + provider->modelProviderType = NestableType::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"; @@ -715,6 +781,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(); @@ -743,6 +810,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(); @@ -6023,6 +6093,9 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe scriptEngine->registerGlobalObject("Scene", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Render", _renderEngine->getConfiguration().get()); + 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/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index df0f3c4728..bbf064fddd 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -13,10 +13,10 @@ #include #include - +#include #include "Overlay.h" -class Base3DOverlay : public Overlay, public SpatiallyNestable { +class Base3DOverlay : public Overlay, public SpatiallyNestable, public scriptable::ModelProvider { Q_OBJECT using Parent = Overlay; @@ -36,6 +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() 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/Cube3DOverlay.cpp b/interface/src/ui/overlays/Cube3DOverlay.cpp index 29c4f592f5..c98d9330df 100644 --- a/interface/src/ui/overlays/Cube3DOverlay.cpp +++ b/interface/src/ui/overlays/Cube3DOverlay.cpp @@ -187,3 +187,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 5d83cbb2c1..d28d11920a 100644 --- a/interface/src/ui/overlays/Cube3DOverlay.h +++ b/interface/src/ui/overlays/Cube3DOverlay.h @@ -32,6 +32,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 e95f13f8ff..6603a44d46 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); @@ -84,6 +85,7 @@ void ModelOverlay::update(float deltatime) { modelOverlay->setSubRenderItemIDs(newRenderItemIDs); }); processMaterials(); + emit DependencyManager::get()->modelAddedToScene(getID(), NestableType::Overlay, _model); } if (_visibleDirty) { _visibleDirty = false; @@ -110,12 +112,14 @@ bool ModelOverlay::addToScene(Overlay::Pointer overlay, const render::ScenePoint Volume3DOverlay::addToScene(overlay, scene, transaction); _model->addToScene(scene, transaction); processMaterials(); + 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(); @@ -659,4 +663,23 @@ void ModelOverlay::processMaterials() { material.pop(); } } -} \ No newline at end of file +} + +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(); + } + auto result = _model->getScriptableModel(); + result.objectID = getID(); + return result; +} diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index b38d5cd6d9..88a1729d68 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -62,6 +62,10 @@ 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() 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/Shape3DOverlay.cpp b/interface/src/ui/overlays/Shape3DOverlay.cpp index 64acdcccdb..e9ec0d6cf4 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.cpp +++ b/interface/src/ui/overlays/Shape3DOverlay.cpp @@ -167,3 +167,14 @@ Transform Shape3DOverlay::evalRenderTransform() { transform.setRotation(rotation); return transform; } + +scriptable::ScriptableModelBase Shape3DOverlay::getScriptableModel() { + auto geometryCache = DependencyManager::get(); + auto vertexColor = ColorUtils::toVec3(_color); + scriptable::ScriptableModelBase result; + result.objectID = getID(); + 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 0196c707d7..60af287af0 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.h +++ b/interface/src/ui/overlays/Shape3DOverlay.h @@ -33,6 +33,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/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/CMakeLists.txt b/libraries/avatars-renderer/CMakeLists.txt index 40e1607b2a..6f4f819793 100644 --- a/libraries/avatars-renderer/CMakeLists.txt +++ b/libraries/avatars-renderer/CMakeLists.txt @@ -14,5 +14,6 @@ include_hifi_library_headers(audio) include_hifi_library_headers(entities) include_hifi_library_headers(octree) include_hifi_library_headers(task) +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 55a40f3ae3..f493bb14dd 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; @@ -576,6 +578,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) { @@ -625,6 +628,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) { @@ -1789,4 +1793,13 @@ void Avatar::processMaterials() { material.pop(); } } +} + +scriptable::ScriptableModelBase Avatar::getScriptableModel() { + if (!_skeletonModel || !_skeletonModel->isLoaded()) { + return scriptable::ScriptableModelBase(); + } + auto result = _skeletonModel->getScriptableModel(); + result.objectID = getSessionUUID().isNull() ? AVATAR_SELF_ID : getSessionUUID(); + 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 b24fbeaef2..bf0adc5c05 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 @@ -275,6 +276,8 @@ 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() 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 0f33a73e40..3ee063ff7a 100644 --- a/libraries/entities-renderer/CMakeLists.txt +++ b/libraries/entities-renderer/CMakeLists.txt @@ -14,6 +14,7 @@ include_hifi_library_headers(entities) include_hifi_library_headers(avatars) include_hifi_library_headers(controllers) include_hifi_library_headers(task) +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 823debf02b..ada57c8ab0 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; } @@ -57,6 +58,8 @@ 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() override { return scriptable::ScriptableModelBase(); } + 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 f759198e72..b7c5caf8ee 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -955,6 +955,7 @@ QStringList RenderableModelEntityItem::getJointNames() const { return result; } +// FIXME: deprecated; remove >= RC67 bool RenderableModelEntityItem::getMeshes(MeshProxyList& result) { auto model = getModel(); if (!model || !model->isLoaded()) { @@ -964,6 +965,34 @@ bool RenderableModelEntityItem::getMeshes(MeshProxyList& result) { return !result.isEmpty(); } +scriptable::ScriptableModelBase render::entities::ModelEntityRenderer::getScriptableModel() { + auto model = resultWithReadLock([this]{ return _model; }); + + if (!model || !model->isLoaded()) { + return scriptable::ScriptableModelBase(); + } + + auto result = _model->getScriptableModel(); + result.objectID = getEntity()->getID(); + 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) { + auto model = resultWithReadLock([this]{ return _model; }); + + if (!model || !model->isLoaded()) { + return false; + } + + return model->replaceScriptableModelMeshPart(newModel, meshIndex, partIndex); +} + void RenderableModelEntityItem::simulateRelayedJoints() { ModelPointer model = getModel(); if (model && model->isLoaded()) { @@ -1057,7 +1086,6 @@ void ModelEntityRenderer::onRemoveFromSceneTyped(const TypedEntityPointer& entit entity->setModel({}); } - void ModelEntityRenderer::animate(const TypedEntityPointer& entity) { if (!_animation || !_animation->isLoaded()) { return; @@ -1281,6 +1309,8 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce auto entityRenderer = static_cast(&data); entityRenderer->clearSubRenderItemIDs(); }); + emit DependencyManager::get()-> + modelRemovedFromScene(entity->getEntityItemID(), NestableType::Entity, _model); } return; } @@ -1291,6 +1321,10 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce connect(model.get(), &Model::setURLFinished, this, [&](bool didVisualGeometryRequestSucceed) { setKey(didVisualGeometryRequestSucceed); emit requestRenderUpdate(); + if(didVisualGeometryRequestSucceed) { + emit DependencyManager::get()-> + 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 25ae668a8e..5d7d84b7bc 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -111,7 +111,7 @@ public: virtual int getJointIndex(const QString& name) const override; virtual QStringList getJointNames() const override; - bool getMeshes(MeshProxyList& result) override; + bool getMeshes(MeshProxyList& result) override; // deprecated const void* getCollisionMeshKey() const { return _collisionMeshKey; } signals: @@ -142,6 +142,9 @@ class ModelEntityRenderer : public TypedEntityRenderer PolyLineEntityRenderer::updateVertic return vertices; } +scriptable::ScriptableModelBase PolyLineEntityRenderer::getScriptableModel() { + // TODO: adapt polyline into a triangles mesh... + return EntityRenderer::getScriptableModel(); +} 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..f460baac59 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::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 78a363d63c..2b1de8d11b 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -1418,6 +1418,7 @@ void RenderablePolyVoxEntityItem::bonkNeighbors() { } } +// deprecated bool RenderablePolyVoxEntityItem::getMeshes(MeshProxyList& result) { if (!updateDependents()) { return false; @@ -1450,6 +1451,37 @@ bool RenderablePolyVoxEntityItem::getMeshes(MeshProxyList& result) { return success; } +scriptable::ScriptableModelBase RenderablePolyVoxEntityItem::getScriptableModel() { + if (!updateDependents() || !_mesh) { + return scriptable::ScriptableModelBase(); + } + + 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) { + // 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.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; } + )); + } + }); + return result; +} + using namespace render; using namespace render::entities; diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index 1e97067795..0a00d1cb73 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,8 @@ public: void setVolDataDirty() { withWriteLock([&] { _volDataDirty = true; _meshReady = false; }); } - bool getMeshes(MeshProxyList& result) override; + bool getMeshes(MeshProxyList& result) override; // deprecated + virtual scriptable::ScriptableModelBase getScriptableModel() override; private: bool updateOnCount(const ivec3& v, uint8_t toValue); @@ -163,6 +164,9 @@ class PolyVoxEntityRenderer : public TypedEntityRenderer()->getScriptableModel(); + } protected: virtual ItemKey getKey() override { return ItemKey::Builder::opaqueShape().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1); } diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index d561e8d5e8..22cd98b08a 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -163,3 +163,18 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { const auto triCount = geometryCache->getShapeTriangleCount(geometryShape); args->_details._trianglesRendered += (int)triCount; } + +scriptable::ScriptableModelBase ShapeEntityRenderer::getScriptableModel() { + scriptable::ScriptableModelBase result; + auto geometryCache = DependencyManager::get(); + auto geometryShape = geometryCache->getShapeForEntityShape(_shape); + glm::vec3 vertexColor; + if (_materials["0"].top().material) { + vertexColor = _materials["0"].top().material->getAlbedo(); + } + if (auto mesh = geometryCache->meshFromShape(geometryShape, vertexColor)) { + 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 c913544255..de855ce0c6 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::ScriptableModelBase getScriptableModel() override; + private: virtual bool needsRenderUpdate() const override; virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override; 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/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 14f12b5d1b..1e59646795 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()); @@ -1718,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; @@ -1901,7 +1908,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); } @@ -1977,7 +1985,10 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS auto name = geometry.getModelNameOfMesh(i++); if (!name.isEmpty()) { if (mesh._mesh) { - mesh._mesh->displayName += "#" + name; + mesh._mesh->modelName = name.toStdString(); + if (!mesh._mesh->displayName.size()) { + mesh._mesh->displayName = QString("#%1").arg(name).toStdString(); + } } else { qDebug() << "modelName but no mesh._mesh" << name; } diff --git a/libraries/fbx/src/OBJWriter.cpp b/libraries/fbx/src/OBJWriter.cpp index 4441ae6649..630ac95b9e 100644 --- a/libraries/fbx/src/OBJWriter.cpp +++ b/libraries/fbx/src/OBJWriter.cpp @@ -9,10 +9,12 @@ // 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" 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/CMakeLists.txt b/libraries/graphics-scripting/CMakeLists.txt new file mode 100644 index 0000000000..ad8055b647 --- /dev/null +++ b/libraries/graphics-scripting/CMakeLists.txt @@ -0,0 +1,4 @@ +set(TARGET_NAME graphics-scripting) +setup_hifi_library() +link_hifi_libraries(shared networking graphics fbx model-networking script-engine) +include_hifi_library_headers(gpu) 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..a0e186dda5 --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/Forward.h @@ -0,0 +1,97 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include +namespace graphics { + class Mesh; +} +class Model; +using ModelPointer = std::shared_ptr; +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 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 weakMesh.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 + QVector meshes; + + ScriptableModelBase(QObject* parent = nullptr) : QObject(parent) {} + ScriptableModelBase(const ScriptableModelBase& other) : QObject(other.parent()) { *this = other; } + ScriptableModelBase& operator=(const ScriptableModelBase& other); + virtual ~ScriptableModelBase(); + + void append(const ScriptableMeshBase& mesh); + void append(scriptable::WeakMeshPointer mesh); + // 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: + 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; } + }; + + // mixin class for resolving UUIDs into a corresponding ModelProvider + class ModelProviderFactory : public QObject, public Dependency { + Q_OBJECT + public: + 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); + }; + + class ScriptableModel; + using ScriptableModelPointer = QPointer; + class ScriptableMesh; + using ScriptableMeshPointer = QPointer; + class ScriptableMeshPart; + using ScriptableMeshPartPointer = QPointer; +} 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..f3ba5e8406 --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp @@ -0,0 +1,367 @@ +// +// GraphicsScriptingInterface.cpp +// libraries/graphics-scripting/src +// +// 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 "GraphicsScriptingUtil.h" +#include "OBJWriter.h" +#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() { +} + +void GraphicsScriptingInterface::jsThrowError(const QString& error) { + if (context()) { + context()->throwError(error); + } else { + 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"); + } + + 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) { + 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"; + } + jsThrowError(error); + return nullptr; +} + +scriptable::ScriptableModelPointer GraphicsScriptingInterface::newModel(const scriptable::ScriptableMeshes& meshes) { + auto modelWrapper = scriptable::make_scriptowned(); + modelWrapper->setObjectName("js::model"); + if (meshes.isEmpty()) { + 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 { + jsThrowError(QString("invalid mesh at index: %1").arg(i)); + break; + } + i++; + } + } + 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 = QString("objectID mismatch: %1 (result contained %2 meshes)").arg(modelObject.objectID.toString()).arg(modelObject.meshes.size()); + } + } else { + error = "model provider unavailable"; + } + } else { + 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; +} + +#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 + +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::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() && !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) { + 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 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()) { + 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()) { + QList meshes; + foreach (auto meshProxy, in) { + if (meshProxy) { + meshes.append(getMeshPointer(meshProxy)); + } + } + if (meshes.size()) { + return writeOBJToString(meshes); + } + } + jsThrowError("null mesh"); + return QString(); +} + +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) { + jsThrowError("expected meshProxy as first parameter"); + return result; + } + auto mesh = meshProxy->getMeshPointer(); + if (!mesh) { + jsThrowError("expected valid meshProxy as first parameter"); + return result; + } + return mesh; +} + +namespace { + 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 registerQPointerMetaType(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& instance = debugEnums; + return qScriptRegisterMetaType( + engine, + [](QScriptEngine* engine, const T& topology) -> QScriptValue { + return instance.value(topology); + }, + [](const QScriptValue& value, T& topology) { + topology = instance.key(value.toString()); + } + ); + } +} + +void GraphicsScriptingInterface::registerMetaTypes(QScriptEngine* engine) { + qScriptRegisterSequenceMetaType>(engine); + + scriptable::registerQPointerMetaType(engine); + scriptable::registerQPointerMetaType(engine); + scriptable::registerQPointerMetaType(engine); + + 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 new file mode 100644 index 0000000000..84c6cb6fa8 --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h @@ -0,0 +1,92 @@ +// +// GraphicsScriptingInterface.h +// libraries/graphics-scripting/src +// +// 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_GraphicsScriptingInterface_h +#define hifi_GraphicsScriptingInterface_h + +#include +#include + +#include +#include + +#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 + +public: + static void registerMetaTypes(QScriptEngine* engine); + GraphicsScriptingInterface(QObject* parent = nullptr); + +public slots: + /**jsdoc + * Returns a model reference object associated with the specified UUID ({@link EntityID}, {@link OverlayID}, or {@link AvatarID}). + * + * @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 + scriptable::ScriptableMeshPartPointer exportMeshPart(scriptable::ScriptableMeshPointer mesh, int partNumber = -1) { + return scriptable::make_scriptowned(mesh, part); + } + bool updateMeshPart(scriptable::ScriptableMeshPointer mesh, scriptable::ScriptableMeshPartPointer part); +#endif + + 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(glm::uint32) +Q_DECLARE_METATYPE(QVector) +Q_DECLARE_METATYPE(NestableType) + +#endif // hifi_GraphicsScriptingInterface_h 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..da582b2d21 --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.cpp @@ -0,0 +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 new file mode 100644 index 0000000000..1ca62277ff --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class Extents; +class AABox; +namespace gpu { + class Element; +} +Q_DECLARE_LOGGING_CATEGORY(graphics_scripting) + +namespace scriptable { + QVariant toVariant(const Extents& box); + QVariant toVariant(const AABox& box); + QVariant toVariant(const gpu::Element& element); + QVariant toVariant(const glm::mat4& mat4); + + // 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 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 new file mode 100644 index 0000000000..bc31d45229 --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp @@ -0,0 +1,405 @@ +// +// 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 "ScriptableMesh.h" +#include "ScriptableMeshPart.h" + +#include "GraphicsScriptingUtil.h" +#include "OBJWriter.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// #define SCRIPTABLE_MESH_DEBUG 1 + +scriptable::ScriptableMesh::ScriptableMesh(const ScriptableMeshBase& other) + : ScriptableMeshBase(other), QScriptable() { + auto mesh = getMeshPointer(); + QString name = mesh ? QString::fromStdString(mesh->modelName) : ""; + if (name.isEmpty()) { + name = mesh ? QString::fromStdString(mesh->displayName) : ""; + } + auto parentModel = getParentModel(); + setObjectName(QString("%1#%2").arg(parentModel ? parentModel->objectName() : "").arg(name)); +} + +QVector scriptable::ScriptableMesh::getMeshParts() const { + QVector out; + for (glm::uint32 i = 0; i < getNumParts(); i++) { + out << scriptable::make_scriptowned(getSelf(), i); + } + return out; +} + +glm::uint32 scriptable::ScriptableMesh::getNumIndices() const { + if (auto mesh = getMeshPointer()) { + return (glm::uint32)mesh->getNumIndices(); + } + return 0; +} + +glm::uint32 scriptable::ScriptableMesh::getNumVertices() const { + if (auto mesh = getMeshPointer()) { + return (glm::uint32)mesh->getNumVertices(); + } + return 0; +} + +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 { + 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(); +} + + +glm::uint32 scriptable::ScriptableMesh::getNumAttributes() const { + if (auto mesh = getMeshPointer()) { + return (glm::uint32)mesh->getNumAttributes() + 1; + } + return 0; +} +QVector scriptable::ScriptableMesh::getAttributeNames() const { + QVector result; + if (auto mesh = getMeshPointer()) { + for (const auto& a : buffer_helpers::ATTRIBUTES.toStdMap()) { + auto bufferView = buffer_helpers::mesh::getBufferView(mesh, a.second); + if (bufferView.getNumElements() > 0) { + result << a.first; + } + } + } + return result; +} + +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(glm::uint32 vertexIndex, const QVariantMap& attributes) { + for (const auto& name : attributes.keys()) { + if (!isValidIndex(vertexIndex, name)) { + return false; + } + } + return buffer_helpers::mesh::setVertexAttributes(getMeshPointer(), vertexIndex, attributes); +} + +int scriptable::ScriptableMesh::getSlotNumber(const QString& attributeName) const { + if (auto mesh = getMeshPointer()) { + return buffer_helpers::ATTRIBUTES.value(attributeName, -1); + } + return -1; +} + +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", (glm::uint32)bufferView.getNumElements() }, + { "byteLength", (glm::uint32)bufferView._size }, + { "offset", (glm::uint32) bufferView._offset }, + { "stride", (glm::uint32)bufferView._stride }, + { "element", scriptable::toVariant(bufferView._element) }, + }; + } + return result; +} + +bool scriptable::ScriptableMesh::removeAttribute(const QString& attributeName) { + auto slot = isValid() ? getSlotNumber(attributeName) : -1; + if (slot < 0) { + return false; + } + 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 = (glm::uint32)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; +} + +glm::uint32 scriptable::ScriptableMesh::fillAttribute(const QString& attributeName, const QVariant& value) { + auto slot = isValid() ? getSlotNumber(attributeName) : -1; + if (slot < 0) { + return 0; + } + 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; +} + +QVariantMap scriptable::ScriptableMesh::getMeshExtents() const { + auto mesh = getMeshPointer(); + 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; + } + 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++) { + result << buffer_helpers::getValue(bufferView, i, qUtf8Printable(attributeName)); + } + return result; +} + +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); + + // 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; + 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 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 + 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); + if (attribute.isValid()) { + 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; + } + const auto last = getNumVertices() - 1; + if (vertexIndex > last) { + if (context()) { + context()->throwError(QString("vertexIndex=%1 out of range (firstVertexIndex=%2, lastVertexIndex=%3)").arg(vertexIndex).arg(0).arg(last)); + } + return false; + } + if (!attributeName.isEmpty()) { + auto slotNum = getSlotNumber(attributeName); + if (slotNum < 0) { + if (context()) { + context()->throwError(QString("invalid attributeName=%1").arg(attributeName)); + } + return false; + } + auto view = buffer_helpers::mesh::getBufferView(getMeshPointer(), static_cast(slotNum)); + 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())); + } + return false; + } + } + return true; +} + + +scriptable::ScriptableMeshPointer scriptable::ScriptableMesh::cloneMesh() { + auto mesh = getMeshPointer(); + if (!mesh) { + qCInfo(graphics_scripting) << "ScriptableMesh::cloneMesh -- !meshPointer"; + return nullptr; + } + auto clone = buffer_helpers::mesh::clone(mesh); + + auto meshPointer = scriptable::make_scriptowned(provider, model, clone, nullptr); + return scriptable::ScriptableMeshPointer(meshPointer); +} + +// 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) { + if (parent) { +#ifdef SCRIPTABLE_MESH_DEBUG + qCDebug(graphics_scripting) << "ScriptableMeshBase -- have parent QObject, creating strong neshref" << weakMesh.lock().get() << parent; +#endif + 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; + weakMesh = view.weakMesh; + strongMesh = view.strongMesh; + return *this; +} + +scriptable::ScriptableMeshBase::~ScriptableMeshBase() { +#ifdef SCRIPTABLE_MESH_DEBUG + qCInfo(graphics_scripting) << "//~ScriptableMeshBase" << this << "strongMesh:" << strongMesh.use_count() << "weakMesh:" << weakMesh.use_count(); +#endif + strongMesh.reset(); +} + +scriptable::ScriptableMesh::~ScriptableMesh() { +#ifdef SCRIPTABLE_MESH_DEBUG + qCInfo(graphics_scripting) << "//~ScriptableMesh" << this << "strongMesh:" << strongMesh.use_count() << "weakMesh:" << weakMesh.use_count(); +#endif + strongMesh.reset(); +} + + +#include "ScriptableMesh.moc" + 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..62a67aa5e6 --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h @@ -0,0 +1,107 @@ +// +// 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" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "GraphicsScriptingUtil.h" + +#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: + 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 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)); } + + 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(); + + const scriptable::MeshPointer getOwnedMeshPointer() const { return strongMesh; } + scriptable::ScriptableMeshPointer getSelf() const { return const_cast(this); } + bool isValid() const { return !weakMesh.expired(); } + bool hasValidStrongMesh() const { return (bool)strongMesh; } + glm::uint32 getNumParts() const; + glm::uint32 getNumVertices() const; + glm::uint32 getNumAttributes() const; + glm::uint32 getNumIndices() const; + QVector getAttributeNames() const; + QVector getMeshParts() const; + QVariantMap getMeshExtents() const; + + operator bool() const { return !weakMesh.expired(); } + int getSlotNumber(const QString& attributeName) const; + + 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 + glm::uint32 updateVertexAttributes(QScriptValue callback); + glm::uint32 forEachVertex(QScriptValue callback); + bool isValidIndex(glm::uint32 vertexIndex, const QString& attributeName = QString()) const; + }; + +} + +Q_DECLARE_METATYPE(scriptable::ScriptableMeshPointer) +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..4414b0ad7e --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.cpp @@ -0,0 +1,452 @@ +// +// 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 "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 != 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; + } + 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); +} + +// 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); + 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; + } +} + +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; + 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 { + 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(); + } +} + +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..dd71d9b998 --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h @@ -0,0 +1,119 @@ +// +// 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 { + /**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) + 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) + // 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) + 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 new file mode 100644 index 0000000000..36322d170d --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp @@ -0,0 +1,116 @@ +// +// 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 + +// #define SCRIPTABLE_MESH_DEBUG 1 + +scriptable::ScriptableModelBase& scriptable::ScriptableModelBase::operator=(const scriptable::ScriptableModelBase& other) { + provider = other.provider; + objectID = other.objectID; + for (const auto& mesh : other.meshes) { + append(mesh); + } + return *this; +} + +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.strongMesh.reset(); + } + meshes.clear(); +} + +void scriptable::ScriptableModelBase::append(scriptable::WeakMeshPointer mesh) { + meshes << ScriptableMeshBase{ provider, this, mesh, this /*parent*/ }; +} + +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; +} + +QString scriptable::ScriptableModel::toString() const { + return QString("[ScriptableModel%1%2 numMeshes=%3]") + .arg(objectID.isNull() ? "" : " objectID="+objectID.toString()) + .arg(objectName().isEmpty() ? "" : " name=" +objectName()) + .arg(meshes.size()); +} + +scriptable::ScriptableModelPointer scriptable::ScriptableModel::cloneModel(const QVariantMap& options) { + scriptable::ScriptableModelPointer clone = scriptable::ScriptableModelPointer(new scriptable::ScriptableModel(*this)); + clone->meshes.clear(); + for (const auto &mesh : getConstMeshes()) { + auto cloned = mesh->cloneMesh(); + if (auto tmp = qobject_cast(cloned)) { + clone->meshes << *tmp; + tmp->deleteLater(); // schedule our copy for cleanup + } else { + qCDebug(graphics_scripting) << "error cloning mesh" << cloned; + } + } + return clone; +} + + +const scriptable::ScriptableMeshes scriptable::ScriptableModel::getConstMeshes() const { + scriptable::ScriptableMeshes 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; +} + +scriptable::ScriptableMeshes scriptable::ScriptableModel::getMeshes() { + scriptable::ScriptableMeshes 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; +} + +#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); + } + } + 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 new file mode 100644 index 0000000000..9a4c4f695b --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h @@ -0,0 +1,55 @@ +// +// 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 { + + 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) + 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; } + 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; + + protected: + glm::uint32 getNumMeshes() { return meshes.size(); } + + }; + +} + +Q_DECLARE_METATYPE(scriptable::ScriptableModelPointer) +Q_DECLARE_METATYPE(QVector) diff --git a/libraries/graphics/src/graphics/BufferViewHelpers.cpp b/libraries/graphics/src/graphics/BufferViewHelpers.cpp new file mode 100644 index 0000000000..5c7e6ff892 --- /dev/null +++ b/libraries/graphics/src/graphics/BufferViewHelpers.cpp @@ -0,0 +1,592 @@ +// +// 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 + +#include +#include +#include + +#include "Geometry.h" +#include "GpuHelpers.h" + +#include +#include + +#include +#include + +namespace glm { + using hvec2 = glm::tvec2; + using hvec4 = glm::tvec4; +} + +namespace { + QLoggingCategory bufferhelper_logging{ "hifi.bufferview" }; +} + +namespace buffer_helpers { + +const std::array XYZW = { { "x", "y", "z", "w" } }; +const std::array ZERO123 = { { "0", "1", "2", "3" } }; + +QMap 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 }, +}; + + +namespace { + 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 + ); + } +} + +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; +} + +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; +} + +template<> glm::uint32 forEach(const gpu::BufferView& view, std::function func) { + return forEachGlmVec(view, func); +} + +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(); + } +#ifdef DEBUG_BUFFERVIEW_HELPERS + if (value != value) { // NAN + qWarning().nospace()<< "vec" << len << "." << components[i] << " NAN received from script.... " << v.toString(); + } +#endif + result[i] = value; + } + return result; +} + +// QVector => BufferView +template +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 }; +} + +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 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) { + qDebug() << QString("GpuToGlmAdapter:: unhandled type=%1(element=%2) size=%3(location=%4,per=%5) vec%6 hint=%7 #%8 %9 %10") + .arg(name) + .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(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, 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); + 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::unpackHalf1x16(view.get(index))); + case gpu::NUINT8: return T(glm::unpackUnorm1x8(view.get(index))); + default: break; + } 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, 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 GpuVec4ToGlm; + +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); + 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: 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; + case gpu::NINT8: break; + case gpu::COMPRESSED: break; + case gpu::NUM_TYPES: break; + case gpu::FLOAT: return 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::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 GpuValueResolver { + static QVector toVector(const gpu::BufferView& view, const char *hint) { + QVector result; + const glm::uint32 count = (glm::uint32)view.getNumElements(); + result.resize(count); + for (glm::uint32 i = 0; i < count; i++) { + result[i] = FUNC::get(view, i, hint); + } + return result; + } + static T toValue(const gpu::BufferView& view, glm::uint32 index, const char *hint) { + return FUNC::get(view, index, hint); + } +}; + +// BufferView => QVector +template QVector 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,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 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 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(); +} + +// view.edit with conversion between types +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; + } + 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<> 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 setValue(const gpu::BufferView& view, glm::uint32 index, const glm::uint16& value, const char* hint) { + return GpuScalarToGlm::set(view, index, value, 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 setValue(const gpu::BufferView& view, glm::uint32 index, const glm::vec3& value, const char* hint) { + return GpuVec3ToGlm::set(view, index, value, 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); +} + +// QVariantList => QVector +template QVector qVariantListToGlmVector(const QVariantList& list) { + QVector output; + output.resize(list.size()); + int i = 0; + for (const auto& v : list) { + output[i++] = 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 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 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 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 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; +} + +// 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); + } + + 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; + } +} // mesh +} // buffer_helpers diff --git a/libraries/graphics/src/graphics/BufferViewHelpers.h b/libraries/graphics/src/graphics/BufferViewHelpers.h new file mode 100644 index 0000000000..f877341d50 --- /dev/null +++ b/libraries/graphics/src/graphics/BufferViewHelpers.h @@ -0,0 +1,60 @@ +// +// 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 "GpuHelpers.h" + +namespace graphics { + class Mesh; + using MeshPointer = std::shared_ptr; +} + +class Extents; +class AABox; + +namespace buffer_helpers { + extern QMap ATTRIBUTES; + extern const std::array XYZW; + extern const std::array ZERO123; + + template QVariant glmVecToVariant(const T& v, bool asArray = false); + template const T glmVecFromVariant(const QVariant& v); + + glm::uint32 forEachVariant(const gpu::BufferView& view, std::function func, const char* hint = ""); + template glm::uint32 forEach(const gpu::BufferView& view, std::function func); + + template gpu::BufferView newFromVector(const QVector& elements, const gpu::Element& elementType); + template gpu::BufferView newFromVariantList(const QVariantList& list, const gpu::Element& elementType); + + template QVector variantToVector(const QVariant& list); + template QVector bufferToVector(const gpu::BufferView& view, 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 = ""); + + gpu::BufferView clone(const gpu::BufferView& input); + gpu::BufferView resized(const gpu::BufferView& input, glm::uint32 numElements); + + void packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, glm::uint32& packedNormal, glm::uint32& packedTangent); + + 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/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..a75fb1bf62 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 @@ -135,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/graphics/src/graphics/GpuHelpers.cpp b/libraries/graphics/src/graphics/GpuHelpers.cpp new file mode 100644 index 0000000000..0c3bd945e1 --- /dev/null +++ b/libraries/graphics/src/graphics/GpuHelpers.cpp @@ -0,0 +1,121 @@ +// +// 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/CMakeLists.txt b/libraries/render-utils/CMakeLists.txt index 3e01fd2643..7fece45b2f 100644 --- a/libraries/render-utils/CMakeLists.txt +++ b/libraries/render-utils/CMakeLists.txt @@ -3,7 +3,7 @@ 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(Gui Network Qml Quick Script) -link_hifi_libraries(shared task ktx gpu graphics model-networking render animation fbx image procedural) +link_hifi_libraries(shared task ktx gpu graphics graphics-scripting model-networking render animation fbx image procedural) include_hifi_library_headers(audio) include_hifi_library_headers(networking) include_hifi_library_headers(octree) diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index c57bb23f0a..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" @@ -2403,3 +2404,38 @@ 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 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 = buffer_helpers::clone(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->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/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 79aa3dcf68..593780709a 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -27,6 +27,8 @@ #include #include +#include +#include #include #include @@ -75,7 +77,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)); @@ -345,34 +347,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; @@ -408,7 +382,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; @@ -479,7 +453,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); @@ -531,7 +505,6 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g { "v2", vec3toVariant(bestModelTriangle.v2) }, }; } - } } @@ -565,7 +538,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... @@ -590,6 +563,7 @@ bool Model::convexHullContains(glm::vec3 point) { return false; } +// TODO: deprecate and remove MeshProxyList Model::getMeshes() const { MeshProxyList result; const Geometry::Pointer& renderGeometry = getGeometry(); @@ -619,16 +593,111 @@ MeshProxyList Model::getMeshes() const { 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; } -void Model::calculateTriangleSets() { - PROFILE_RANGE(render, __FUNCTION__); +bool Model::replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer newModel, int meshIndex, int partIndex) { + QMutexLocker lock(&_mutex); + + if (!isLoaded()) { + qDebug() << "!isLoaded" << this; + return false; + } + + if (!newModel || !newModel->meshes.size()) { + qDebug() << "!newModel.meshes.size()" << this; + return false; + } + + const auto& meshes = newModel->meshes; + render::Transaction transaction; + const render::ScenePointer& scene = AbstractViewStateInterface::instance()->getMain3DScene(); + + meshIndex = max(meshIndex, 0); + partIndex = max(partIndex, 0); + + 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; + 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 = (int)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; +} + +scriptable::ScriptableModelBase Model::getScriptableModel() { + QMutexLocker lock(&_mutex); + scriptable::ScriptableModelBase result; + + if (!isLoaded()) { + qCDebug(renderutils) << "Model::getScriptableModel -- !isLoaded"; + return result; + } const FBXGeometry& geometry = getFBXGeometry(); + int numberOfMeshes = geometry.meshes.size(); + for (int i = 0; i < numberOfMeshes; i++) { + const FBXMesh& fbxMesh = geometry.meshes.at(i); + if (auto mesh = fbxMesh._mesh) { + result.append(mesh); + } + } + return result; +} + +void Model::calculateTriangleSets(const FBXGeometry& geometry) { + PROFILE_RANGE(render, __FUNCTION__); + int numberOfMeshes = geometry.meshes.size(); _triangleSetsValid = true; @@ -651,7 +720,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; @@ -893,7 +962,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) { @@ -1398,7 +1467,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; @@ -1421,7 +1490,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; @@ -1495,7 +1564,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 3d532f7d7c..2b14a7c055 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: @@ -315,6 +316,8 @@ public: 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(); bool getUseDualQuaternionSkinning() const { return _useDualQuaternionSkinning; } @@ -414,11 +417,11 @@ protected: int _blendNumber; int _appliedBlendNumber; - QMutex _mutex; + mutable QMutex _mutex{ QMutex::Recursive }; 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/script-engine/src/AssetScriptingInterface.cpp b/libraries/script-engine/src/AssetScriptingInterface.cpp index 1c811573fb..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 78cb05fa0d..871705d74b 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,16 @@ void ScriptEngines::onScriptEngineLoaded(const QString& rawScriptURL) { emit scriptCountChanged(); } +int ScriptEngines::runScriptInitializers(ScriptEnginePointer scriptEngine) { + 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) { @@ -530,9 +554,7 @@ void ScriptEngines::launchScriptEngine(ScriptEnginePointer scriptEngine) { }); // 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; }; +}; diff --git a/tests/render-perf/CMakeLists.txt b/tests/render-perf/CMakeLists.txt index 7ad38c5795..66ca28f7db 100644 --- a/tests/render-perf/CMakeLists.txt +++ b/tests/render-perf/CMakeLists.txt @@ -16,7 +16,7 @@ link_hifi_libraries( shared task networking animation ktx image octree gl gpu gpu-gl render render-utils - graphics fbx model-networking + graphics fbx model-networking graphics-scripting entities entities-renderer audio avatars script-engine physics procedural midi qml ui ${PLATFORM_GL_BACKEND}