diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index ee2997e216..081eeae02e 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -191,7 +191,7 @@ endif() # link required hifi libraries link_hifi_libraries( - shared octree ktx gpu gl procedural graphics render + shared octree ktx gpu gl procedural graphics graphics-scripting render pointers recording fbx networking model-networking entities avatars trackers audio audio-client animation script-engine physics diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 99bd4d5758..f24969ce60 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -166,6 +166,7 @@ #include "scripting/AccountServicesScriptingInterface.h" #include "scripting/HMDScriptingInterface.h" #include "scripting/MenuScriptingInterface.h" +#include "graphics-scripting/ModelScriptingInterface.h" #include "scripting/SettingsScriptingInterface.h" #include "scripting/WindowScriptingInterface.h" #include "scripting/ControllerScriptingInterface.h" @@ -198,7 +199,6 @@ #include #include #include -#include #include #include @@ -593,6 +593,66 @@ void messageHandler(QtMsgType type, const QMessageLogContext& context, const QSt } } + +class ApplicationMeshProvider : public scriptable::ModelProviderFactory { +public: + virtual scriptable::ModelProviderPointer lookupModelProvider(QUuid uuid) { + QString error; + + scriptable::ModelProviderPointer provider; + if (auto entityInterface = getEntityModelProvider(static_cast(uuid))) { + provider = entityInterface; + } else if (auto overlayInterface = getOverlayModelProvider(static_cast(uuid))) { + provider = overlayInterface; + } else if (auto avatarInterface = getAvatarModelProvider(uuid)) { + provider = avatarInterface; + } + + return provider; + } + + scriptable::ModelProviderPointer getEntityModelProvider(EntityItemID entityID) { + scriptable::ModelProviderPointer provider; + auto entityTreeRenderer = qApp->getEntities(); + auto entityTree = entityTreeRenderer->getTree(); + if (auto entity = entityTree->findEntityByID(entityID)) { + if (auto renderer = entityTreeRenderer->renderableForEntityId(entityID)) { + provider = std::dynamic_pointer_cast(renderer); + provider->metadata["providerType"] = "entity"; + } else { + qCWarning(interfaceapp) << "no renderer for entity ID" << entityID.toString(); + } + } + return provider; + } + + scriptable::ModelProviderPointer getOverlayModelProvider(OverlayID overlayID) { + scriptable::ModelProviderPointer provider; + auto &overlays = qApp->getOverlays(); + if (auto overlay = overlays.getOverlay(overlayID)) { + if (auto base3d = std::dynamic_pointer_cast(overlay)) { + provider = std::dynamic_pointer_cast(base3d); + provider->metadata["providerType"] = "overlay"; + } else { + qCWarning(interfaceapp) << "no renderer for overlay ID" << overlayID.toString(); + } + } + return provider; + } + + scriptable::ModelProviderPointer getAvatarModelProvider(QUuid sessionUUID) { + scriptable::ModelProviderPointer provider; + auto avatarManager = DependencyManager::get(); + if (auto avatar = avatarManager->getAvatarBySessionID(sessionUUID)) { + if (avatar->getSessionUUID() == sessionUUID) { + provider = std::dynamic_pointer_cast(avatar); + provider->metadata["providerType"] = "avatar"; + } + } + return provider; + } +}; + static const QString STATE_IN_HMD = "InHMD"; static const QString STATE_CAMERA_FULL_SCREEN_MIRROR = "CameraFSM"; static const QString STATE_CAMERA_FIRST_PERSON = "CameraFirstPerson"; @@ -737,6 +797,9 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(true); + DependencyManager::set(); + DependencyManager::registerInheritance(); + DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -5915,6 +5978,9 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe scriptEngine->registerGlobalObject("Scene", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Render", _renderEngine->getConfiguration().get()); + ModelScriptingInterface::registerMetaTypes(scriptEngine.data()); + scriptEngine->registerGlobalObject("Model", DependencyManager::get().data()); + scriptEngine->registerGlobalObject("ScriptDiscoveryService", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Reticle", getApplicationCompositor().getReticleInterface()); diff --git a/interface/src/Application.h b/interface/src/Application.h index ddb8ce11e5..ae07ebd9dd 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -72,7 +72,6 @@ #include #include -#include #include "FrameTimingsScriptingInterface.h" #include "Sound.h" diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index df0f3c4728..0c8bc5aacb 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -13,10 +13,11 @@ #include #include - +#include #include "Overlay.h" -class Base3DOverlay : public Overlay, public SpatiallyNestable { +namespace model { class Mesh; } +class Base3DOverlay : public Overlay, public SpatiallyNestable, public scriptable::ModelProvider { Q_OBJECT using Parent = Overlay; @@ -36,6 +37,7 @@ public: virtual bool is3D() const override { return true; } virtual uint32_t fetchMetaSubItems(render::ItemIDs& subItems) const override { subItems.push_back(getRenderItemID()); return (uint32_t) subItems.size(); } + virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override { return scriptable::ModelProvider::modelUnavailableError(ok); } // TODO: consider implementing registration points in this class glm::vec3 getCenter() const { return getWorldPosition(); } diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index 310dbf78d8..5a80ca1abf 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -629,3 +629,10 @@ uint32_t ModelOverlay::fetchMetaSubItems(render::ItemIDs& subItems) const { } return 0; } + +scriptable::ScriptableModel ModelOverlay::getScriptableModel(bool* ok) { + if (!_model || !_model->isLoaded()) { + return Base3DOverlay::getScriptableModel(ok); + } + return _model->getScriptableModel(ok); +} diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index 60ba90e568..32d9a08c70 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -59,6 +59,7 @@ public: void setDrawInFront(bool drawInFront) override; void setDrawHUDLayer(bool drawHUDLayer) override; + virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override; protected: Transform evalRenderTransform() override; diff --git a/interface/src/ui/overlays/Shape3DOverlay.cpp b/interface/src/ui/overlays/Shape3DOverlay.cpp index 97342a80ab..8bb3d16888 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.cpp +++ b/interface/src/ui/overlays/Shape3DOverlay.cpp @@ -179,3 +179,18 @@ Transform Shape3DOverlay::evalRenderTransform() { transform.setRotation(rotation); return transform; } + +scriptable::ScriptableModel Shape3DOverlay::getScriptableModel(bool* ok) { + auto geometryCache = DependencyManager::get(); + auto vertexColor = ColorUtils::toVec3(_color); + scriptable::ScriptableModel result; + result.metadata = { + { "origin", "Shape3DOverlay::"+shapeStrings[_shape] }, + { "overlayID", getID() }, + }; + result.meshes << geometryCache->meshFromShape(_shape, vertexColor); + if (ok) { + *ok = true; + } + return result; +} diff --git a/interface/src/ui/overlays/Shape3DOverlay.h b/interface/src/ui/overlays/Shape3DOverlay.h index 7fc95ec981..34f82af278 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.h +++ b/interface/src/ui/overlays/Shape3DOverlay.h @@ -37,6 +37,7 @@ public: void setProperties(const QVariantMap& properties) override; QVariant getProperty(const QString& property) override; + virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override; protected: Transform evalRenderTransform() override; diff --git a/libraries/avatars-renderer/CMakeLists.txt b/libraries/avatars-renderer/CMakeLists.txt index 53edc692f2..f7e951500b 100644 --- a/libraries/avatars-renderer/CMakeLists.txt +++ b/libraries/avatars-renderer/CMakeLists.txt @@ -13,5 +13,6 @@ include_hifi_library_headers(entities-renderer) include_hifi_library_headers(audio) include_hifi_library_headers(entities) include_hifi_library_headers(octree) +include_hifi_library_headers(graphics-scripting) # for ScriptableModel.h target_bullet() diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 500a24763d..8e22f355e4 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -35,6 +35,8 @@ #include "ModelEntityItem.h" #include "RenderableModelEntityItem.h" +#include + #include "Logging.h" using namespace std; @@ -1760,3 +1762,29 @@ float Avatar::getUnscaledEyeHeightFromSkeleton() const { return DEFAULT_AVATAR_EYE_HEIGHT; } } + +scriptable::ScriptableModel Avatar::getScriptableModel(bool* ok) { + qDebug() << "Avatar::getScriptableModel" ; + if (!_skeletonModel || !_skeletonModel->isLoaded()) { + return scriptable::ModelProvider::modelUnavailableError(ok); + } + scriptable::ScriptableModel result; + result.metadata = { + { "avatarID", getSessionUUID().toString() }, + { "url", _skeletonModelURL.toString() }, + { "origin", "Avatar/avatar::" + _displayName }, + { "textures", _skeletonModel->getTextures() }, + }; + result.mixin(_skeletonModel->getScriptableModel(ok)); + + // FIXME: for now access to attachment models are merged with the main avatar model + for (auto& attachmentModel : _attachmentModels) { + if (attachmentModel->isLoaded()) { + result.mixin(attachmentModel->getScriptableModel(ok)); + } + } + if (ok) { + *ok = true; + } + return result; +} diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index c2b404a925..5cfc399b65 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -20,6 +20,7 @@ #include #include #include +#include #include @@ -53,7 +54,7 @@ class Texture; using AvatarPhysicsCallback = std::function; -class Avatar : public AvatarData { +class Avatar : public AvatarData, public scriptable::ModelProvider { Q_OBJECT /**jsdoc @@ -272,6 +273,8 @@ public: virtual void setAvatarEntityDataChanged(bool value) override; + + virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override; public slots: // FIXME - these should be migrated to use Pose data instead diff --git a/libraries/entities-renderer/CMakeLists.txt b/libraries/entities-renderer/CMakeLists.txt index ea75367e1e..27ea04f642 100644 --- a/libraries/entities-renderer/CMakeLists.txt +++ b/libraries/entities-renderer/CMakeLists.txt @@ -13,6 +13,7 @@ include_hifi_library_headers(fbx) include_hifi_library_headers(entities) include_hifi_library_headers(avatars) include_hifi_library_headers(controllers) +include_hifi_library_headers(graphics-scripting) # for ScriptableModel.h target_bullet() target_polyvox() diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index 8eb82e2c6e..f07b67fbd0 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -17,13 +17,14 @@ #include #include "AbstractViewStateInterface.h" #include "EntitiesRendererLogging.h" +#include class EntityTreeRenderer; namespace render { namespace entities { // Base class for all renderable entities -class EntityRenderer : public QObject, public std::enable_shared_from_this, public PayloadProxyInterface, protected ReadWriteLockable { +class EntityRenderer : public QObject, public std::enable_shared_from_this, public PayloadProxyInterface, protected ReadWriteLockable, public scriptable::ModelProvider { Q_OBJECT using Pointer = std::shared_ptr; @@ -37,7 +38,7 @@ public: virtual bool wantsKeyboardFocus() const { return false; } virtual void setProxyWindow(QWindow* proxyWindow) {} virtual QObject* getEventHandler() { return nullptr; } - const EntityItemPointer& getEntity() { return _entity; } + const EntityItemPointer& getEntity() const { return _entity; } const ItemID& getRenderItemID() const { return _renderItemID; } const SharedSoundPointer& getCollisionSound() { return _collisionSound; } @@ -54,6 +55,7 @@ public: const uint64_t& getUpdateTime() const { return _updateTime; } + virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override { return scriptable::ModelProvider::modelUnavailableError(ok); } protected: virtual bool needsRenderUpdateFromEntity() const final { return needsRenderUpdateFromEntity(_entity); } virtual void onAddToScene(const EntityItemPointer& entity); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 9fcb7640ef..7b022fefac 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -950,13 +950,18 @@ QStringList RenderableModelEntityItem::getJointNames() const { return result; } -bool RenderableModelEntityItem::getMeshes(MeshProxyList& result) { - auto model = getModel(); + +scriptable::ScriptableModel render::entities::ModelEntityRenderer::getScriptableModel(bool* ok) { + ModelPointer model; + withReadLock([&] { + model = _model; + }); + if (!model || !model->isLoaded()) { - return false; + return scriptable::ModelProvider::modelUnavailableError(ok); } - BLOCKING_INVOKE_METHOD(model.get(), "getMeshes", Q_RETURN_ARG(MeshProxyList, result)); - return !result.isEmpty(); + + return _model->getScriptableModel(ok); } void RenderableModelEntityItem::simulateRelayedJoints() { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 33fc9910a0..3e952cb9a7 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -111,7 +111,6 @@ public: virtual int getJointIndex(const QString& name) const override; virtual QStringList getJointNames() const override; - bool getMeshes(MeshProxyList& result) override; const void* getCollisionMeshKey() const { return _collisionMeshKey; } signals: @@ -141,6 +140,7 @@ class ModelEntityRenderer : public TypedEntityRenderer PolyLineEntityRenderer::updateVertic return vertices; } +scriptable::ScriptableModel PolyLineEntityRenderer::getScriptableModel(bool *ok) { + // TODO: adapt polyline into a triangles mesh... + return EntityRenderer::getScriptableModel(ok); +} void PolyLineEntityRenderer::doRender(RenderArgs* args) { if (_empty) { @@ -319,4 +323,4 @@ void PolyLineEntityRenderer::doRender(RenderArgs* args) { #endif batch.draw(gpu::TRIANGLE_STRIP, _numVertices, 0); -} \ No newline at end of file +} diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h index 1e27ac9ae7..3bb8901178 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h @@ -25,6 +25,7 @@ class PolyLineEntityRenderer : public TypedEntityRenderer { public: PolyLineEntityRenderer(const EntityItemPointer& entity); + virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override; protected: virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override; virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index ade3790df6..fd923c40b0 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -20,8 +20,6 @@ #include #include -#include -#include #include #include #include @@ -1416,36 +1414,36 @@ void RenderablePolyVoxEntityItem::bonkNeighbors() { } } -bool RenderablePolyVoxEntityItem::getMeshes(MeshProxyList& result) { - if (!updateDependents()) { - return false; +scriptable::ScriptableModel RenderablePolyVoxEntityItem::getScriptableModel(bool * ok) { + if (!updateDependents() || !_mesh) { + return scriptable::ModelProvider::modelUnavailableError(ok); } bool success = false; - if (_mesh) { - MeshProxy* meshProxy = nullptr; - glm::mat4 transform = voxelToLocalMatrix(); - withReadLock([&] { - gpu::BufferView::Index numVertices = (gpu::BufferView::Index)_mesh->getNumVertices(); - if (!_meshReady) { - // we aren't ready to return a mesh. the caller will have to try again later. - success = false; - } else if (numVertices == 0) { - // we are ready, but there are no triangles in the mesh. - success = true; - } else { - success = true; - // the mesh will be in voxel-space. transform it into object-space - meshProxy = new SimpleMeshProxy( - _mesh->map([=](glm::vec3 position) { return glm::vec3(transform * glm::vec4(position, 1.0f)); }, - [=](glm::vec3 color) { return color; }, - [=](glm::vec3 normal) { return glm::normalize(glm::vec3(transform * glm::vec4(normal, 0.0f))); }, - [&](uint32_t index) { return index; })); - result << meshProxy; - } - }); + glm::mat4 transform = voxelToLocalMatrix(); + scriptable::ScriptableModel result; + withReadLock([&] { + gpu::BufferView::Index numVertices = (gpu::BufferView::Index)_mesh->getNumVertices(); + if (!_meshReady) { + // we aren't ready to return a mesh. the caller will have to try again later. + success = false; + } else if (numVertices == 0) { + // we are ready, but there are no triangles in the mesh. + success = true; + } else { + success = true; + // the mesh will be in voxel-space. transform it into object-space + result.meshes << + _mesh->map([=](glm::vec3 position){ return glm::vec3(transform * glm::vec4(position, 1.0f)); }, + [=](glm::vec3 color){ return color; }, + [=](glm::vec3 normal){ return glm::normalize(glm::vec3(transform * glm::vec4(normal, 0.0f))); }, + [&](uint32_t index){ return index; }); + } + }); + if (ok) { + *ok = success; } - return success; + return result; } using namespace render; diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index db0f0b729a..55b9be23d8 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -32,7 +32,7 @@ namespace render { namespace entities { class PolyVoxEntityRenderer; } } -class RenderablePolyVoxEntityItem : public PolyVoxEntityItem { +class RenderablePolyVoxEntityItem : public PolyVoxEntityItem, public scriptable::ModelProvider { friend class render::entities::PolyVoxEntityRenderer; public: @@ -113,7 +113,7 @@ public: void setVolDataDirty() { withWriteLock([&] { _volDataDirty = true; _meshReady = false; }); } - bool getMeshes(MeshProxyList& result) override; + virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override; private: bool updateOnCount(const ivec3& v, uint8_t toValue); @@ -163,6 +163,9 @@ class PolyVoxEntityRenderer : public TypedEntityRenderer()->getScriptableModel(ok); + } protected: virtual ItemKey getKey() override { return ItemKey::Builder::opaqueShape(); } diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index cdee2c5ec9..746102681c 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -156,3 +156,24 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { const auto triCount = geometryCache->getShapeTriangleCount(geometryShape); args->_details._trianglesRendered += (int)triCount; } + +scriptable::ScriptableModel ShapeEntityRenderer::getScriptableModel(bool* ok) { + scriptable::ScriptableModel result; + result.metadata = { + { "entityID", getEntity()->getID().toString() }, + { "shape", entity::stringFromShape(_shape) }, + { "userData", getEntity()->getUserData() }, + }; + auto geometryCache = DependencyManager::get(); + auto geometryShape = geometryCache->getShapeForEntityShape(_shape); + auto vertexColor = glm::vec3(_color); + auto success = false; + if (auto mesh = geometryCache->meshFromShape(geometryShape, vertexColor)) { + result.meshes << mesh; + success = true; + } + if (ok) { + *ok = success; + } + return result; +} diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.h b/libraries/entities-renderer/src/RenderableShapeEntityItem.h index 433cb41ad2..6ada7e7317 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.h +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.h @@ -22,6 +22,8 @@ class ShapeEntityRenderer : public TypedEntityRenderer { public: ShapeEntityRenderer(const EntityItemPointer& entity); + virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override; + private: virtual bool needsRenderUpdate() const override; virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override; diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 4c398b8a29..5c9324fc8a 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -57,8 +57,6 @@ using EntityTreeElementExtraEncodeDataPointer = std::shared_ptr(_entityTree->findEntityByEntityItemID(entityID)); - if (!entity) { - qCDebug(entities) << "EntityScriptingInterface::getMeshes no entity with ID" << entityID; - QScriptValueList args { callback.engine()->undefinedValue(), false }; - callback.call(QScriptValue(), args); - return; - } - - MeshProxyList result; - bool success = entity->getMeshes(result); - - if (success) { - QScriptValue resultAsScriptValue = meshesToScriptValue(callback.engine(), result); - QScriptValueList args { resultAsScriptValue, true }; - callback.call(QScriptValue(), args); - } else { - QScriptValueList args { callback.engine()->undefinedValue(), false }; - callback.call(QScriptValue(), args); - } -} - glm::mat4 EntityScriptingInterface::getEntityTransform(const QUuid& entityID) { glm::mat4 result; if (_entityTree) { diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 4c4e2ffbfd..da201f93eb 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -37,7 +37,6 @@ #include "BaseScriptEngine.h" class EntityTree; -class MeshProxy; // helper factory to compose standardized, async metadata queries for "magic" Entity properties // like .script and .serverScripts. This is used for automated testing of core scripting features @@ -401,9 +400,6 @@ public slots: Q_INVOKABLE bool AABoxIntersectsCapsule(const glm::vec3& low, const glm::vec3& dimensions, const glm::vec3& start, const glm::vec3& end, float radius); - // FIXME move to a renderable entity interface - Q_INVOKABLE void getMeshes(QUuid entityID, QScriptValue callback); - /**jsdoc * Returns object to world transform, excluding scale * diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 14462e0558..50abe7928f 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1967,19 +1967,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } } } - { - int i = 0; - for (const auto& mesh : geometry.meshes) { - auto name = geometry.getModelNameOfMesh(i++); - if (!name.isEmpty()) { - if (mesh._mesh) { - mesh._mesh->displayName += "#" + name; - } else { - qDebug() << "modelName but no mesh._mesh" << name; - } - } - } - } + return geometryPtr; } @@ -1995,7 +1983,7 @@ FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QStri reader._loadLightmaps = loadLightmaps; reader._lightmapLevel = lightmapLevel; - qCDebug(modelformat) << "Reading FBX: " << url; + qDebug() << "Reading FBX: " << url; return reader.extractFBXGeometry(mapping, url); } diff --git a/libraries/fbx/src/OBJWriter.cpp b/libraries/fbx/src/OBJWriter.cpp index 4441ae6649..37bced8458 100644 --- a/libraries/fbx/src/OBJWriter.cpp +++ b/libraries/fbx/src/OBJWriter.cpp @@ -46,9 +46,12 @@ bool writeOBJToTextStream(QTextStream& out, QList meshes) { QList meshNormalStartOffset; int currentVertexStartOffset = 0; int currentNormalStartOffset = 0; + int subMeshIndex = 0; + out << "# OBJWriter::writeOBJToTextStream\n"; // write out vertices (and maybe colors) foreach (const MeshPointer& mesh, meshes) { + out << "# vertices::subMeshIndex " << subMeshIndex++ << "\n"; meshVertexStartOffset.append(currentVertexStartOffset); const gpu::BufferView& vertexBuffer = mesh->getVertexBuffer(); @@ -81,7 +84,9 @@ bool writeOBJToTextStream(QTextStream& out, QList meshes) { // write out normals bool haveNormals = true; + subMeshIndex = 0; foreach (const MeshPointer& mesh, meshes) { + out << "# normals::subMeshIndex " << subMeshIndex++ << "\n"; meshNormalStartOffset.append(currentNormalStartOffset); const gpu::BufferView& normalsBufferView = mesh->getAttributeBuffer(gpu::Stream::InputSlot::NORMAL); gpu::BufferView::Index numNormals = (gpu::BufferView::Index)normalsBufferView.getNumElements(); @@ -98,7 +103,9 @@ bool writeOBJToTextStream(QTextStream& out, QList meshes) { // write out faces int nth = 0; + subMeshIndex = 0; foreach (const MeshPointer& mesh, meshes) { + out << "# faces::subMeshIndex " << subMeshIndex++ << "\n"; currentVertexStartOffset = meshVertexStartOffset.takeFirst(); currentNormalStartOffset = meshNormalStartOffset.takeFirst(); @@ -106,35 +113,25 @@ bool writeOBJToTextStream(QTextStream& out, QList meshes) { const gpu::BufferView& indexBuffer = mesh->getIndexBuffer(); graphics::Index partCount = (graphics::Index)mesh->getNumParts(); + QString name = (!mesh->displayName.size() ? QString("mesh-%1-part").arg(nth) : QString(mesh->displayName)) + .replace(QRegExp("[^-_a-zA-Z0-9]"), "_"); for (int partIndex = 0; partIndex < partCount; partIndex++) { const graphics::Mesh::Part& part = partBuffer.get(partIndex); - out << "g part-" << nth++ << "\n"; + out << QString("g %1-%2-%3\n").arg(subMeshIndex, 3, 10, QChar('0')).arg(name).arg(partIndex); - // graphics::Mesh::TRIANGLES - // TODO -- handle other formats - gpu::BufferView::Iterator indexItr = indexBuffer.cbegin(); - indexItr += part._startIndex; - - int indexCount = 0; - while (indexItr != indexBuffer.cend() && indexCount < part._numIndices) { - uint32_t index0 = *indexItr; - indexItr++; - indexCount++; - if (indexItr == indexBuffer.cend() || indexCount >= part._numIndices) { - qCDebug(modelformat) << "OBJWriter -- index buffer length isn't multiple of 3"; - break; + const bool shorts = indexBuffer._element == gpu::Element::INDEX_UINT16; + auto face = [&](uint32_t i0, uint32_t i1, uint32_t i2) { + uint32_t index0, index1, index2; + if (shorts) { + index0 = indexBuffer.get(i0); + index1 = indexBuffer.get(i1); + index2 = indexBuffer.get(i2); + } else { + index0 = indexBuffer.get(i0); + index1 = indexBuffer.get(i1); + index2 = indexBuffer.get(i2); } - uint32_t index1 = *indexItr; - indexItr++; - indexCount++; - if (indexItr == indexBuffer.cend() || indexCount >= part._numIndices) { - qCDebug(modelformat) << "OBJWriter -- index buffer length isn't multiple of 3"; - break; - } - uint32_t index2 = *indexItr; - indexItr++; - indexCount++; out << "f "; if (haveNormals) { @@ -146,6 +143,39 @@ bool writeOBJToTextStream(QTextStream& out, QList meshes) { out << currentVertexStartOffset + index1 + 1 << " "; out << currentVertexStartOffset + index2 + 1 << "\n"; } + }; + + // graphics::Mesh::TRIANGLES / graphics::Mesh::QUADS + // TODO -- handle other formats + uint32_t len = part._startIndex + part._numIndices; + auto stringFromTopology = [&](graphics::Mesh::Topology topo) -> QString { + return topo == graphics::Mesh::Topology::QUADS ? "QUADS" : + topo == graphics::Mesh::Topology::QUAD_STRIP ? "QUAD_STRIP" : + topo == graphics::Mesh::Topology::TRIANGLES ? "TRIANGLES" : + topo == graphics::Mesh::Topology::TRIANGLE_STRIP ? "TRIANGLE_STRIP" : + topo == graphics::Mesh::Topology::QUAD_STRIP ? "QUAD_STRIP" : + QString("topo:%1").arg((int)topo); + }; + + qCDebug(modelformat) << "OBJWriter -- part" << partIndex << "topo" << stringFromTopology(part._topology) << "index elements" << (shorts ? "uint16_t" : "uint32_t"); + if (part._topology == graphics::Mesh::TRIANGLES && len % 3 != 0) { + qCDebug(modelformat) << "OBJWriter -- index buffer length isn't a multiple of 3" << len; + } + if (part._topology == graphics::Mesh::QUADS && len % 4 != 0) { + qCDebug(modelformat) << "OBJWriter -- index buffer length isn't a multiple of 4" << len; + } + if (len > indexBuffer.getNumElements()) { + qCDebug(modelformat) << "OBJWriter -- len > index size" << len << indexBuffer.getNumElements(); + } + if (part._topology == graphics::Mesh::QUADS) { + for (uint32_t idx = part._startIndex; idx+3 < len; idx += 4) { + face(idx+0, idx+1, idx+3); + face(idx+1, idx+2, idx+3); + } + } else if (part._topology == graphics::Mesh::TRIANGLES) { + for (uint32_t idx = part._startIndex; idx+2 < len; idx += 3) { + face(idx+0, idx+1, idx+2); + } } out << "\n"; } diff --git a/libraries/graphics-scripting/CMakeLists.txt b/libraries/graphics-scripting/CMakeLists.txt new file mode 100644 index 0000000000..e7fa3de155 --- /dev/null +++ b/libraries/graphics-scripting/CMakeLists.txt @@ -0,0 +1,5 @@ +set(TARGET_NAME graphics-scripting) +setup_hifi_library() +link_hifi_libraries(shared networking graphics fbx model-networking script-engine) +include_hifi_library_headers(gpu) +include_hifi_library_headers(graphics-scripting) diff --git a/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.cpp b/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.cpp new file mode 100644 index 0000000000..e865ed0e5a --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.cpp @@ -0,0 +1,195 @@ +#include "./graphics-scripting/BufferViewHelpers.h" + +#include +#include + +#include +#include +#include + +#include + +#ifdef DEBUG_BUFFERVIEW_SCRIPTING + #include "DebugNames.h" +#endif + +namespace { + const std::array XYZW = {{ "x", "y", "z", "w" }}; + const std::array ZERO123 = {{ "0", "1", "2", "3" }}; +} + +template +QVariant getBufferViewElement(const gpu::BufferView& view, quint32 index, bool asArray = false) { + return glmVecToVariant(view.get(index), asArray); +} + +template +void setBufferViewElement(const gpu::BufferView& view, quint32 index, const QVariant& v) { + view.edit(index) = glmVecFromVariant(v); +} + +//FIXME copied from Model.cpp +static void packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, glm::uint32& packedNormal, glm::uint32& packedTangent) { + auto absNormal = glm::abs(normal); + auto absTangent = glm::abs(tangent); + normal /= glm::max(1e-6f, glm::max(glm::max(absNormal.x, absNormal.y), absNormal.z)); + tangent /= glm::max(1e-6f, glm::max(glm::max(absTangent.x, absTangent.y), absTangent.z)); + normal = glm::clamp(normal, -1.0f, 1.0f); + tangent = glm::clamp(tangent, -1.0f, 1.0f); + normal *= 511.0f; + tangent *= 511.0f; + normal = glm::round(normal); + tangent = glm::round(tangent); + + glm::detail::i10i10i10i2 normalStruct; + glm::detail::i10i10i10i2 tangentStruct; + normalStruct.data.x = int(normal.x); + normalStruct.data.y = int(normal.y); + normalStruct.data.z = int(normal.z); + normalStruct.data.w = 0; + tangentStruct.data.x = int(tangent.x); + tangentStruct.data.y = int(tangent.y); + tangentStruct.data.z = int(tangent.z); + tangentStruct.data.w = 0; + packedNormal = normalStruct.pack; + packedTangent = tangentStruct.pack; +} + +bool bufferViewElementFromVariant(const gpu::BufferView& view, quint32 index, const QVariant& v) { + const auto& element = view._element; + const auto vecN = element.getScalarCount(); + const auto dataType = element.getType(); + const auto byteLength = element.getSize(); + const auto BYTES_PER_ELEMENT = byteLength / vecN; + if (BYTES_PER_ELEMENT == 1) { + switch(vecN) { + case 2: setBufferViewElement(view, index, v); return true; + case 3: setBufferViewElement(view, index, v); return true; + case 4: { + if (element == gpu::Element::COLOR_RGBA_32) { + glm::uint32 rawColor;// = glm::packUnorm4x8(glm::vec4(glmVecFromVariant(v), 0.0f)); + glm::uint32 unused; + packNormalAndTangent(glmVecFromVariant(v), glm::vec3(), rawColor, unused); + view.edit(index) = rawColor; + } else if (element == gpu::Element::VEC4F_NORMALIZED_XYZ10W2) { + glm::uint32 packedNormal;// = glm::packSnorm3x10_1x2(glm::vec4(glmVecFromVariant(v), 0.0f)); + glm::uint32 unused; + packNormalAndTangent(glm::vec3(), glmVecFromVariant(v), unused, packedNormal); + view.edit(index) = packedNormal; + } + setBufferViewElement(view, index, v); return true; + } + } + } else if (BYTES_PER_ELEMENT == 2) { + switch(vecN) { + case 2: setBufferViewElement(view, index, v); return true; + case 3: setBufferViewElement(view, index, v); return true; + case 4: setBufferViewElement(view, index, v); return true; + } + } else if (BYTES_PER_ELEMENT == 4) { + if (dataType == gpu::FLOAT) { + switch(vecN) { + case 2: setBufferViewElement(view, index, v); return true; + case 3: setBufferViewElement(view, index, v); return true; + case 4: setBufferViewElement(view, index, v); return true; + } + } else { + switch(vecN) { + case 2: setBufferViewElement(view, index, v); return true; + case 3: setBufferViewElement(view, index, v); return true; + case 4: setBufferViewElement(view, index, v); return true; + } + } + } + return false; +} + +QVariant bufferViewElementToVariant(const gpu::BufferView& view, quint32 index, bool asArray, const char* hint) { + const auto& element = view._element; + const auto vecN = element.getScalarCount(); + const auto dataType = element.getType(); + const auto byteLength = element.getSize(); + const auto BYTES_PER_ELEMENT = byteLength / vecN; + Q_ASSERT(index < view.getNumElements()); + Q_ASSERT(index * vecN * BYTES_PER_ELEMENT < (view._size - vecN * BYTES_PER_ELEMENT)); + if (BYTES_PER_ELEMENT == 1) { + switch(vecN) { + case 2: return getBufferViewElement(view, index, asArray); + case 3: return getBufferViewElement(view, index, asArray); + case 4: { + if (element == gpu::Element::COLOR_RGBA_32) { + auto rawColor = view.get(index); + return glmVecToVariant(glm::vec3(glm::unpackUnorm4x8(rawColor))); + } else if (element == gpu::Element::VEC4F_NORMALIZED_XYZ10W2) { + auto packedNormal = view.get(index); + return glmVecToVariant(glm::vec3(glm::unpackSnorm3x10_1x2(packedNormal))); + } + + return getBufferViewElement(view, index, asArray); + } + } + } else if (BYTES_PER_ELEMENT == 2) { + switch(vecN) { + case 2: return getBufferViewElement(view, index, asArray); + case 3: return getBufferViewElement(view, index, asArray); + case 4: return getBufferViewElement(view, index, asArray); + } + } else if (BYTES_PER_ELEMENT == 4) { + if (dataType == gpu::FLOAT) { + switch(vecN) { + case 2: return getBufferViewElement(view, index, asArray); + case 3: return getBufferViewElement(view, index, asArray); + case 4: return getBufferViewElement(view, index, asArray); + } + } else { + switch(vecN) { + case 2: return getBufferViewElement(view, index, asArray); + case 3: return getBufferViewElement(view, index, asArray); + case 4: return getBufferViewElement(view, index, asArray); + } + } + } + return QVariant(); +} + +template +QVariant glmVecToVariant(const T& v, bool asArray /*= false*/) { + static const auto len = T().length(); + if (asArray) { + QVariantList list; + for (int i = 0; i < len ; i++) { + list << v[i]; + } + return list; + } else { + QVariantMap obj; + for (int i = 0; i < len ; i++) { + obj[XYZW[i]] = v[i]; + } + return obj; + } +} +template +const T glmVecFromVariant(const QVariant& v) { + auto isMap = v.type() == (QVariant::Type)QMetaType::QVariantMap; + static const auto len = T().length(); + const auto& components = isMap ? XYZW : ZERO123; + T result; + QVariantMap map; + QVariantList list; + if (isMap) map = v.toMap(); else list = v.toList(); + for (int i = 0; i < len ; i++) { + float value; + if (isMap) { + value = map.value(components[i]).toFloat(); + } else { + value = list.value(i).toFloat(); + } + if (value != value) { // NAN + qWarning().nospace()<< "vec" << len << "." << components[i] << " NAN received from script.... " << v.toString(); + } + result[i] = value; + } + return result; +} + diff --git a/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h b/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h new file mode 100644 index 0000000000..0fe2602f6c --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/BufferViewHelpers.h @@ -0,0 +1,18 @@ +// +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#include + +namespace gpu { class BufferView; } + +template QVariant glmVecToVariant(const T& v, bool asArray = false); +template const T glmVecFromVariant(const QVariant& v); +QVariant bufferViewElementToVariant(const gpu::BufferView& view, quint32 index, bool asArray = false, const char* hint = ""); +bool bufferViewElementFromVariant(const gpu::BufferView& view, quint32 index, const QVariant& v); + + diff --git a/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp b/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp new file mode 100644 index 0000000000..367c0589e9 --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.cpp @@ -0,0 +1,83 @@ +#include "BufferViewScripting.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#ifdef DEBUG_BUFFERVIEW_SCRIPTING + #include +#endif + +namespace { + const std::array XYZW = {{ "x", "y", "z", "w" }}; + const std::array ZERO123 = {{ "0", "1", "2", "3" }}; +} + +template +QScriptValue getBufferViewElement(QScriptEngine* js, const gpu::BufferView& view, quint32 index, bool asArray = false) { + return glmVecToScriptValue(js, view.get(index), asArray); +} + +QScriptValue bufferViewElementToScriptValue(QScriptEngine* engine, const gpu::BufferView& view, quint32 index, bool asArray, const char* hint) { + QVariant result = bufferViewElementToVariant(view, index, asArray, hint); + if (!result.isValid()) { + return QScriptValue::NullValue; + } + return engine->toScriptValue(result); +} + +template +void setBufferViewElement(const gpu::BufferView& view, quint32 index, const QScriptValue& v) { + view.edit(index) = glmVecFromScriptValue(v); +} + +bool bufferViewElementFromScriptValue(const QScriptValue& v, const gpu::BufferView& view, quint32 index) { + return bufferViewElementFromVariant(view, index, v.toVariant()); +} + +// + +template +QScriptValue glmVecToScriptValue(QScriptEngine *js, const T& v, bool asArray) { + static const auto len = T().length(); + const auto& components = asArray ? ZERO123 : XYZW; + auto obj = asArray ? js->newArray() : js->newObject(); + for (int i = 0; i < len ; i++) { + const auto key = components[i]; + const auto value = v[i]; + if (value != value) { // NAN +#ifdef DEV_BUILD + qWarning().nospace()<< "vec" << len << "." << key << " converting NAN to javascript NaN.... " << value; +#endif + obj.setProperty(key, js->globalObject().property("NaN")); + } else { + obj.setProperty(key, value); + } + } + return obj; +} + +template +const T glmVecFromScriptValue(const QScriptValue& v) { + static const auto len = T().length(); + const auto& components = v.property("x").isValid() ? XYZW : ZERO123; + T result; + for (int i = 0; i < len ; i++) { + const auto key = components[i]; + const auto value = v.property(key).toNumber(); +#ifdef DEV_BUILD + if (value != value) { // NAN + qWarning().nospace()<< "vec" << len << "." << key << " NAN received from script.... " << v.toVariant().toString(); + } +#endif + result[i] = value; + } + return result; +} diff --git a/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.h b/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.h new file mode 100644 index 0000000000..f2e3fe734e --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/BufferViewScripting.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +namespace gpu { class BufferView; } +class QScriptValue; +class QScriptEngine; +template QScriptValue glmVecToScriptValue(QScriptEngine *js, const T& v, bool asArray = false); +template const T glmVecFromScriptValue(const QScriptValue& v); +QScriptValue bufferViewElementToScriptValue(QScriptEngine* engine, const gpu::BufferView& view, quint32 index, bool asArray = false, const char* hint = ""); +bool bufferViewElementFromScriptValue(const QScriptValue& v, const gpu::BufferView& view, quint32 index); diff --git a/libraries/graphics-scripting/src/graphics-scripting/DebugNames.h b/libraries/graphics-scripting/src/graphics-scripting/DebugNames.h new file mode 100644 index 0000000000..e5edf1c9d8 --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/DebugNames.h @@ -0,0 +1,72 @@ +#pragma once +#include +#include +#include +#include +#include +//#include + +Q_DECLARE_METATYPE(gpu::Type); +#ifdef QT_MOC_RUN +class DebugNames { + Q_OBJECT +public: +#else + namespace DebugNames { + Q_NAMESPACE + #endif + +enum Type : uint8_t { + + FLOAT = 0, + INT32, + UINT32, + HALF, + INT16, + UINT16, + INT8, + UINT8, + + NINT32, + NUINT32, + NINT16, + NUINT16, + NINT8, + NUINT8, + + COMPRESSED, + + NUM_TYPES, + + BOOL = UINT8, + NORMALIZED_START = NINT32, +}; + + Q_ENUM_NS(Type) + enum InputSlot { + POSITION = 0, + NORMAL = 1, + COLOR = 2, + TEXCOORD0 = 3, + TEXCOORD = TEXCOORD0, + TANGENT = 4, + SKIN_CLUSTER_INDEX = 5, + SKIN_CLUSTER_WEIGHT = 6, + TEXCOORD1 = 7, + TEXCOORD2 = 8, + TEXCOORD3 = 9, + TEXCOORD4 = 10, + + NUM_INPUT_SLOTS, + + DRAW_CALL_INFO = 15, // Reserve last input slot for draw call infos + }; + + Q_ENUM_NS(InputSlot) + inline QString stringFrom(Type t) { return QVariant::fromValue(t).toString(); } + inline QString stringFrom(InputSlot t) { return QVariant::fromValue(t).toString(); } + inline QString stringFrom(gpu::Type t) { return stringFrom((Type)t); } + inline QString stringFrom(gpu::Stream::Slot t) { return stringFrom((InputSlot)t); } + + extern const QMetaObject staticMetaObject; + }; diff --git a/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp new file mode 100644 index 0000000000..68a00bc02c --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.cpp @@ -0,0 +1,836 @@ +// +// ModelScriptingInterface.cpp +// libraries/script-engine/src +// +// Created by Seth Alves on 2017-1-27. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ModelScriptingInterface.h" +#include +#include +#include +#include +#include "BaseScriptEngine.h" +#include "ScriptEngineLogging.h" +#include "OBJWriter.h" +#include "OBJReader.h" +//#include "ui/overlays/Base3DOverlay.h" +//#include "EntityTreeRenderer.h" +//#include "avatar/AvatarManager.h" +//#include "RenderableEntityItem.h" + +#include +#include + +#include + + +#include + +#include +#include "BufferViewScripting.h" + +#include "ScriptableMesh.h" + +using ScriptableMesh = scriptable::ScriptableMesh; + +#include "ModelScriptingInterface.moc" + +namespace { + QLoggingCategory model_scripting { "hifi.model.scripting" }; +} + +ModelScriptingInterface::ModelScriptingInterface(QObject* parent) : QObject(parent) { + if (auto scriptEngine = qobject_cast(parent)) { + this->registerMetaTypes(scriptEngine); + } +} + +QString ModelScriptingInterface::meshToOBJ(const scriptable::ScriptableModel& _in) { + const auto& in = _in.getMeshes(); + qCDebug(model_scripting) << "meshToOBJ" << in.size(); + if (in.size()) { + QList meshes; + foreach (const auto meshProxy, in) { + qCDebug(model_scripting) << "meshToOBJ" << meshProxy.get(); + if (meshProxy) { + meshes.append(getMeshPointer(meshProxy)); + } + } + if (meshes.size()) { + return writeOBJToString(meshes); + } + } + context()->throwError(QString("null mesh")); + return QString(); +} + +QScriptValue ModelScriptingInterface::appendMeshes(scriptable::ScriptableModel _in) { + const auto& in = _in.getMeshes(); + + // figure out the size of the resulting mesh + size_t totalVertexCount { 0 }; + size_t totalColorCount { 0 }; + size_t totalNormalCount { 0 }; + size_t totalIndexCount { 0 }; + foreach (const scriptable::ScriptableMeshPointer meshProxy, in) { + scriptable::MeshPointer mesh = getMeshPointer(meshProxy); + totalVertexCount += mesh->getNumVertices(); + + int attributeTypeColor = gpu::Stream::InputSlot::COLOR; // libraries/gpu/src/gpu/Stream.h + const gpu::BufferView& colorsBufferView = mesh->getAttributeBuffer(attributeTypeColor); + gpu::BufferView::Index numColors = (gpu::BufferView::Index)colorsBufferView.getNumElements(); + totalColorCount += numColors; + + int attributeTypeNormal = gpu::Stream::InputSlot::NORMAL; // libraries/gpu/src/gpu/Stream.h + const gpu::BufferView& normalsBufferView = mesh->getAttributeBuffer(attributeTypeNormal); + gpu::BufferView::Index numNormals = (gpu::BufferView::Index)normalsBufferView.getNumElements(); + totalNormalCount += numNormals; + + totalIndexCount += mesh->getNumIndices(); + } + + // alloc the resulting mesh + gpu::Resource::Size combinedVertexSize = totalVertexCount * sizeof(glm::vec3); + unsigned char* combinedVertexData = new unsigned char[combinedVertexSize]; + unsigned char* combinedVertexDataCursor = combinedVertexData; + + gpu::Resource::Size combinedColorSize = totalColorCount * sizeof(glm::vec3); + unsigned char* combinedColorData = new unsigned char[combinedColorSize]; + unsigned char* combinedColorDataCursor = combinedColorData; + + gpu::Resource::Size combinedNormalSize = totalNormalCount * sizeof(glm::vec3); + unsigned char* combinedNormalData = new unsigned char[combinedNormalSize]; + unsigned char* combinedNormalDataCursor = combinedNormalData; + + gpu::Resource::Size combinedIndexSize = totalIndexCount * sizeof(uint32_t); + unsigned char* combinedIndexData = new unsigned char[combinedIndexSize]; + unsigned char* combinedIndexDataCursor = combinedIndexData; + + uint32_t indexStartOffset { 0 }; + + foreach (const scriptable::ScriptableMeshPointer meshProxy, in) { + scriptable::MeshPointer mesh = getMeshPointer(meshProxy); + mesh->forEach( + [&](glm::vec3 position){ + memcpy(combinedVertexDataCursor, &position, sizeof(position)); + combinedVertexDataCursor += sizeof(position); + }, + [&](glm::vec3 color){ + memcpy(combinedColorDataCursor, &color, sizeof(color)); + combinedColorDataCursor += sizeof(color); + }, + [&](glm::vec3 normal){ + memcpy(combinedNormalDataCursor, &normal, sizeof(normal)); + combinedNormalDataCursor += sizeof(normal); + }, + [&](uint32_t index){ + index += indexStartOffset; + memcpy(combinedIndexDataCursor, &index, sizeof(index)); + combinedIndexDataCursor += sizeof(index); + }); + + gpu::BufferView::Index numVertices = (gpu::BufferView::Index)mesh->getNumVertices(); + indexStartOffset += numVertices; + } + + graphics::MeshPointer result(new graphics::Mesh()); + + gpu::Element vertexElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); + gpu::Buffer* combinedVertexBuffer = new gpu::Buffer(combinedVertexSize, combinedVertexData); + gpu::BufferPointer combinedVertexBufferPointer(combinedVertexBuffer); + gpu::BufferView combinedVertexBufferView(combinedVertexBufferPointer, vertexElement); + result->setVertexBuffer(combinedVertexBufferView); + + int attributeTypeColor = gpu::Stream::InputSlot::COLOR; // libraries/gpu/src/gpu/Stream.h + gpu::Element colorElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); + gpu::Buffer* combinedColorsBuffer = new gpu::Buffer(combinedColorSize, combinedColorData); + gpu::BufferPointer combinedColorsBufferPointer(combinedColorsBuffer); + gpu::BufferView combinedColorsBufferView(combinedColorsBufferPointer, colorElement); + result->addAttribute(attributeTypeColor, combinedColorsBufferView); + + int attributeTypeNormal = gpu::Stream::InputSlot::NORMAL; // libraries/gpu/src/gpu/Stream.h + gpu::Element normalElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); + gpu::Buffer* combinedNormalsBuffer = new gpu::Buffer(combinedNormalSize, combinedNormalData); + gpu::BufferPointer combinedNormalsBufferPointer(combinedNormalsBuffer); + gpu::BufferView combinedNormalsBufferView(combinedNormalsBufferPointer, normalElement); + result->addAttribute(attributeTypeNormal, combinedNormalsBufferView); + + gpu::Element indexElement = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::RAW); + gpu::Buffer* combinedIndexesBuffer = new gpu::Buffer(combinedIndexSize, combinedIndexData); + gpu::BufferPointer combinedIndexesBufferPointer(combinedIndexesBuffer); + gpu::BufferView combinedIndexesBufferView(combinedIndexesBufferPointer, indexElement); + result->setIndexBuffer(combinedIndexesBufferView); + + std::vector parts; + parts.emplace_back(graphics::Mesh::Part((graphics::Index)0, // startIndex + (graphics::Index)result->getNumIndices(), // numIndices + (graphics::Index)0, // baseVertex + graphics::Mesh::TRIANGLES)); // topology + result->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(graphics::Mesh::Part), + (gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL)); + + + scriptable::ScriptableMeshPointer resultProxy = scriptable::ScriptableMeshPointer(new ScriptableMesh(nullptr, result)); + return engine()->toScriptValue(result); +} + +QScriptValue ModelScriptingInterface::transformMesh(scriptable::ScriptableMeshPointer meshProxy, glm::mat4 transform) { + auto mesh = getMeshPointer(meshProxy); + if (!mesh) { + return false; + } + + graphics::MeshPointer result = mesh->map([&](glm::vec3 position){ return glm::vec3(transform * glm::vec4(position, 1.0f)); }, + [&](glm::vec3 color){ return color; }, + [&](glm::vec3 normal){ return glm::vec3(transform * glm::vec4(normal, 0.0f)); }, + [&](uint32_t index){ return index; }); + scriptable::ScriptableMeshPointer resultProxy = scriptable::ScriptableMeshPointer(new ScriptableMesh(nullptr, result)); + return engine()->toScriptValue(resultProxy); +} + +QScriptValue ModelScriptingInterface::getVertexCount(scriptable::ScriptableMeshPointer meshProxy) { + auto mesh = getMeshPointer(meshProxy); + if (!mesh) { + return -1; + } + return (uint32_t)mesh->getNumVertices(); +} + +QScriptValue ModelScriptingInterface::getVertex(scriptable::ScriptableMeshPointer meshProxy, mesh::uint32 vertexIndex) { + auto mesh = getMeshPointer(meshProxy); + + const gpu::BufferView& vertexBufferView = mesh->getVertexBuffer(); + auto numVertices = mesh->getNumVertices(); + + if (vertexIndex >= numVertices) { + context()->throwError(QString("invalid index: %1 [0,%2)").arg(vertexIndex).arg(numVertices)); + return QScriptValue::NullValue; + } + + glm::vec3 pos = vertexBufferView.get(vertexIndex); + return engine()->toScriptValue(pos); +} + +QScriptValue ModelScriptingInterface::newMesh(const QVector& vertices, + const QVector& normals, + const QVector& faces) { + graphics::MeshPointer mesh(new graphics::Mesh()); + + // vertices + auto vertexBuffer = std::make_shared(vertices.size() * sizeof(glm::vec3), (gpu::Byte*)vertices.data()); + auto vertexBufferPtr = gpu::BufferPointer(vertexBuffer); + gpu::BufferView vertexBufferView(vertexBufferPtr, 0, vertexBufferPtr->getSize(), + sizeof(glm::vec3), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); + mesh->setVertexBuffer(vertexBufferView); + + if (vertices.size() == normals.size()) { + // normals + auto normalBuffer = std::make_shared(normals.size() * sizeof(glm::vec3), (gpu::Byte*)normals.data()); + auto normalBufferPtr = gpu::BufferPointer(normalBuffer); + gpu::BufferView normalBufferView(normalBufferPtr, 0, normalBufferPtr->getSize(), + sizeof(glm::vec3), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); + mesh->addAttribute(gpu::Stream::NORMAL, normalBufferView); + } else { + qCWarning(model_scripting, "ModelScriptingInterface::newMesh normals must be same length as vertices"); + } + + // indices (faces) + int VERTICES_PER_TRIANGLE = 3; + int indexBufferSize = faces.size() * sizeof(uint32_t) * VERTICES_PER_TRIANGLE; + unsigned char* indexData = new unsigned char[indexBufferSize]; + unsigned char* indexDataCursor = indexData; + foreach(const mesh::MeshFace& meshFace, faces) { + for (int i = 0; i < VERTICES_PER_TRIANGLE; i++) { + memcpy(indexDataCursor, &meshFace.vertexIndices[i], sizeof(uint32_t)); + indexDataCursor += sizeof(uint32_t); + } + } + auto indexBuffer = std::make_shared(indexBufferSize, (gpu::Byte*)indexData); + auto indexBufferPtr = gpu::BufferPointer(indexBuffer); + gpu::BufferView indexBufferView(indexBufferPtr, gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::RAW)); + mesh->setIndexBuffer(indexBufferView); + + // parts + std::vector parts; + parts.emplace_back(graphics::Mesh::Part((graphics::Index)0, // startIndex + (graphics::Index)faces.size() * 3, // numIndices + (graphics::Index)0, // baseVertex + graphics::Mesh::TRIANGLES)); // topology + mesh->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(graphics::Mesh::Part), + (gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL)); + + + + scriptable::ScriptableMeshPointer meshProxy = scriptable::ScriptableMeshPointer(new ScriptableMesh(nullptr, mesh)); + return engine()->toScriptValue(meshProxy); +} + +QScriptValue ModelScriptingInterface::mapAttributeValues( + QScriptValue _in, + QScriptValue scopeOrCallback, + QScriptValue methodOrName + ) { + qCInfo(model_scripting) << "mapAttributeValues" << _in.toVariant().typeName() << _in.toVariant().toString() << _in.toQObject(); + auto in = qscriptvalue_cast(_in).getMeshes(); + if (in.size()) { + foreach (scriptable::ScriptableMeshPointer meshProxy, in) { + mapMeshAttributeValues(meshProxy, scopeOrCallback, methodOrName); + } + return thisObject(); + } else if (auto meshProxy = qobject_cast(_in.toQObject())) { + return mapMeshAttributeValues(meshProxy->shared_from_this(), scopeOrCallback, methodOrName); + } else { + context()->throwError("invalid ModelProxy || MeshProxyPointer"); + } + return false; +} + + +QScriptValue ModelScriptingInterface::unrollVertices(scriptable::ScriptableMeshPointer meshProxy, bool recalcNormals) { + auto mesh = getMeshPointer(meshProxy); + qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices" << !!mesh<< !!meshProxy; + if (!mesh) { + return QScriptValue(); + } + + auto positions = mesh->getVertexBuffer(); + auto indices = mesh->getIndexBuffer(); + quint32 numPoints = (quint32)indices.getNumElements(); + auto buffer = new gpu::Buffer(); + buffer->resize(numPoints * sizeof(uint32_t)); + auto newindices = gpu::BufferView(buffer, { gpu::SCALAR, gpu::UINT32, gpu::INDEX }); + qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices numPoints" << numPoints; + auto attributeViews = ScriptableMesh::gatherBufferViews(mesh); + for (const auto& a : attributeViews) { + auto& view = a.second; + auto sz = view._element.getSize(); + auto buffer = new gpu::Buffer(); + buffer->resize(numPoints * sz); + auto points = gpu::BufferView(buffer, view._element); + auto src = (uint8_t*)view._buffer->getData(); + auto dest = (uint8_t*)points._buffer->getData(); + auto slot = ScriptableMesh::ATTRIBUTES[a.first]; + qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices buffer" << a.first; + qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices source" << view.getNumElements(); + qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices dest" << points.getNumElements(); + qCInfo(model_scripting) << "ModelScriptingInterface::unrollVertices sz" << sz << src << dest << slot; + auto esize = indices._element.getSize(); + const char* hint= a.first.toStdString().c_str(); + for(quint32 i = 0; i < numPoints; i++) { + quint32 index = esize == 4 ? indices.get(i) : indices.get(i); + newindices.edit(i) = i; + bufferViewElementFromVariant( + points, i, + bufferViewElementToVariant(view, index, false, hint) + ); + } + if (slot == gpu::Stream::POSITION) { + mesh->setVertexBuffer(points); + } else { + mesh->addAttribute(slot, points); + } + } + mesh->setIndexBuffer(newindices); + if (recalcNormals) { + recalculateNormals(meshProxy); + } + return true; +} + +namespace { + template + gpu::BufferView bufferViewFromVector(QVector elements, gpu::Element elementType) { + auto vertexBuffer = std::make_shared( + elements.size() * sizeof(T), + (gpu::Byte*)elements.data() + ); + return { vertexBuffer, 0, vertexBuffer->getSize(),sizeof(T), elementType }; + } + + gpu::BufferView cloneBufferView(const gpu::BufferView& input) { + //qCInfo(model_scripting) << "input" << input.getNumElements() << input._buffer->getSize(); + auto output = gpu::BufferView( + std::make_shared(input._buffer->getSize(), input._buffer->getData()), + input._offset, + input._size, + input._stride, + input._element + ); + //qCInfo(model_scripting) << "after" << output.getNumElements() << output._buffer->getSize(); + return output; + } + + gpu::BufferView resizedBufferView(const gpu::BufferView& input, quint32 numElements) { + auto effectiveSize = input._buffer->getSize() / input.getNumElements(); + qCInfo(model_scripting) << "resize input" << input.getNumElements() << input._buffer->getSize() << "effectiveSize" << effectiveSize; + auto vsize = input._element.getSize() * numElements; + gpu::Byte *data = new gpu::Byte[vsize]; + memset(data, 0, vsize); + auto buffer = new gpu::Buffer(vsize, (gpu::Byte*)data); + delete[] data; + auto output = gpu::BufferView(buffer, input._element); + qCInfo(model_scripting) << "resized output" << output.getNumElements() << output._buffer->getSize(); + return output; + } +} + +bool ModelScriptingInterface::replaceMeshData(scriptable::ScriptableMeshPointer dest, scriptable::ScriptableMeshPointer src, const QVector& attributeNames) { + auto target = getMeshPointer(dest); + auto source = getMeshPointer(src); + if (!target || !source) { + context()->throwError("ModelScriptingInterface::replaceMeshData -- expected dest and src to be valid mesh proxy pointers"); + return false; + } + + QVector attributes = attributeNames.isEmpty() ? src->getAttributeNames() : attributeNames; + + //qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData -- source:" << source->displayName << "target:" << target->displayName << "attributes:" << attributes; + + // remove attributes only found on target mesh, unless user has explicitly specified the relevant attribute names + if (attributeNames.isEmpty()) { + auto attributeViews = ScriptableMesh::gatherBufferViews(target); + for (const auto& a : attributeViews) { + auto slot = ScriptableMesh::ATTRIBUTES[a.first]; + if (!attributes.contains(a.first)) { + //qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData -- pruning target attribute" << a.first << slot; + target->removeAttribute(slot); + } + } + } + + target->setVertexBuffer(cloneBufferView(source->getVertexBuffer())); + target->setIndexBuffer(cloneBufferView(source->getIndexBuffer())); + target->setPartBuffer(cloneBufferView(source->getPartBuffer())); + + for (const auto& a : attributes) { + auto slot = ScriptableMesh::ATTRIBUTES[a]; + if (slot == gpu::Stream::POSITION) { + continue; + } + // auto& before = target->getAttributeBuffer(slot); + auto& input = source->getAttributeBuffer(slot); + if (input.getNumElements() == 0) { + //qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData buffer is empty -- pruning" << a << slot; + target->removeAttribute(slot); + } else { + // if (before.getNumElements() == 0) { + // qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData target buffer is empty -- adding" << a << slot; + // } else { + // qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData target buffer exists -- updating" << a << slot; + // } + target->addAttribute(slot, cloneBufferView(input)); + } + // auto& after = target->getAttributeBuffer(slot); + // qCInfo(model_scripting) << "ModelScriptingInterface::replaceMeshData" << a << slot << before.getNumElements() << " -> " << after.getNumElements(); + } + + + return true; +} + +bool ModelScriptingInterface::dedupeVertices(scriptable::ScriptableMeshPointer meshProxy, float epsilon) { + auto mesh = getMeshPointer(meshProxy); + if (!mesh) { + return false; + } + auto positions = mesh->getVertexBuffer(); + auto numPositions = positions.getNumElements(); + const auto epsilon2 = epsilon*epsilon; + + QVector uniqueVerts; + uniqueVerts.reserve((int)numPositions); + QMap remapIndices; + + for (quint32 i = 0; i < numPositions; i++) { + const quint32 numUnique = uniqueVerts.size(); + const auto& position = positions.get(i); + bool unique = true; + for (quint32 j = 0; j < numUnique; j++) { + if (glm::length2(uniqueVerts[j] - position) <= epsilon2) { + remapIndices[i] = j; + unique = false; + break; + } + } + if (unique) { + uniqueVerts << position; + remapIndices[i] = numUnique; + } + } + + qCInfo(model_scripting) << "//VERTS before" << numPositions << "after" << uniqueVerts.size(); + + auto indices = mesh->getIndexBuffer(); + auto numIndices = indices.getNumElements(); + auto esize = indices._element.getSize(); + QVector newIndices; + newIndices.reserve((int)numIndices); + for (quint32 i = 0; i < numIndices; i++) { + quint32 index = esize == 4 ? indices.get(i) : indices.get(i); + if (remapIndices.contains(index)) { + //qCInfo(model_scripting) << i << index << "->" << remapIndices[index]; + newIndices << remapIndices[index]; + } else { + qCInfo(model_scripting) << i << index << "!remapIndices[index]"; + } + } + + mesh->setIndexBuffer(bufferViewFromVector(newIndices, { gpu::SCALAR, gpu::UINT32, gpu::INDEX })); + mesh->setVertexBuffer(bufferViewFromVector(uniqueVerts, { gpu::VEC3, gpu::FLOAT, gpu::XYZ })); + + auto attributeViews = ScriptableMesh::gatherBufferViews(mesh); + quint32 numUniqueVerts = uniqueVerts.size(); + for (const auto& a : attributeViews) { + auto& view = a.second; + auto slot = ScriptableMesh::ATTRIBUTES[a.first]; + if (slot == gpu::Stream::POSITION) { + continue; + } + qCInfo(model_scripting) << "ModelScriptingInterface::dedupeVertices" << a.first << slot << view.getNumElements(); + auto newView = resizedBufferView(view, numUniqueVerts); + qCInfo(model_scripting) << a.first << "before: #" << view.getNumElements() << "after: #" << newView.getNumElements(); + quint32 numElements = (quint32)view.getNumElements(); + for (quint32 i = 0; i < numElements; i++) { + quint32 fromVertexIndex = i; + quint32 toVertexIndex = remapIndices.contains(fromVertexIndex) ? remapIndices[fromVertexIndex] : fromVertexIndex; + bufferViewElementFromVariant( + newView, toVertexIndex, + bufferViewElementToVariant(view, fromVertexIndex, false, "dedupe") + ); + } + mesh->addAttribute(slot, newView); + } + return true; +} + +QScriptValue ModelScriptingInterface::cloneMesh(scriptable::ScriptableMeshPointer meshProxy, bool recalcNormals) { + auto mesh = getMeshPointer(meshProxy); + if (!mesh) { + return QScriptValue::NullValue; + } + graphics::MeshPointer clone(new graphics::Mesh()); + clone->displayName = mesh->displayName + "-clone"; + qCInfo(model_scripting) << "ModelScriptingInterface::cloneMesh" << !!mesh<< !!meshProxy; + if (!mesh) { + return QScriptValue::NullValue; + } + + clone->setIndexBuffer(cloneBufferView(mesh->getIndexBuffer())); + clone->setPartBuffer(cloneBufferView(mesh->getPartBuffer())); + auto attributeViews = ScriptableMesh::gatherBufferViews(mesh); + for (const auto& a : attributeViews) { + auto& view = a.second; + auto slot = ScriptableMesh::ATTRIBUTES[a.first]; + qCInfo(model_scripting) << "ModelScriptingInterface::cloneVertices buffer" << a.first << slot; + auto points = cloneBufferView(view); + qCInfo(model_scripting) << "ModelScriptingInterface::cloneVertices source" << view.getNumElements(); + qCInfo(model_scripting) << "ModelScriptingInterface::cloneVertices dest" << points.getNumElements(); + if (slot == gpu::Stream::POSITION) { + clone->setVertexBuffer(points); + } else { + clone->addAttribute(slot, points); + } + } + + auto result = scriptable::ScriptableMeshPointer(new ScriptableMesh(nullptr, clone)); + if (recalcNormals) { + recalculateNormals(result); + } + return engine()->toScriptValue(result); +} + +bool ModelScriptingInterface::recalculateNormals(scriptable::ScriptableMeshPointer meshProxy) { + qCInfo(model_scripting) << "Recalculating normals" << !!meshProxy; + auto mesh = getMeshPointer(meshProxy); + if (!mesh) { + return false; + } + ScriptableMesh::gatherBufferViews(mesh, { "normal", "color" }); // ensures #normals >= #positions + auto normals = mesh->getAttributeBuffer(gpu::Stream::NORMAL); + auto verts = mesh->getVertexBuffer(); + auto indices = mesh->getIndexBuffer(); + auto esize = indices._element.getSize(); + auto numPoints = indices.getNumElements(); + const auto TRIANGLE = 3; + quint32 numFaces = (quint32)numPoints / TRIANGLE; + //QVector faces; + QVector faceNormals; + QMap> vertexToFaces; + //faces.resize(numFaces); + faceNormals.resize(numFaces); + auto numNormals = normals.getNumElements(); + qCInfo(model_scripting) << QString("numFaces: %1, numNormals: %2, numPoints: %3").arg(numFaces).arg(numNormals).arg(numPoints); + if (normals.getNumElements() != verts.getNumElements()) { + return false; + } + for (quint32 i = 0; i < numFaces; i++) { + quint32 I = TRIANGLE * i; + quint32 i0 = esize == 4 ? indices.get(I+0) : indices.get(I+0); + quint32 i1 = esize == 4 ? indices.get(I+1) : indices.get(I+1); + quint32 i2 = esize == 4 ? indices.get(I+2) : indices.get(I+2); + + Triangle face = { + verts.get(i1), + verts.get(i2), + verts.get(i0) + }; + faceNormals[i] = face.getNormal(); + if (glm::isnan(faceNormals[i].x)) { + qCInfo(model_scripting) << i << i0 << i1 << i2 << vec3toVariant(face.v0) << vec3toVariant(face.v1) << vec3toVariant(face.v2); + break; + } + vertexToFaces[glm::to_string(face.v0).c_str()] << i; + vertexToFaces[glm::to_string(face.v1).c_str()] << i; + vertexToFaces[glm::to_string(face.v2).c_str()] << i; + } + for (quint32 j = 0; j < numNormals; j++) { + //auto v = verts.get(j); + glm::vec3 normal { 0.0f, 0.0f, 0.0f }; + QString key { glm::to_string(verts.get(j)).c_str() }; + const auto& faces = vertexToFaces.value(key); + if (faces.size()) { + for (const auto i : faces) { + normal += faceNormals[i]; + } + normal *= 1.0f / (float)faces.size(); + } else { + static int logged = 0; + if (logged++ < 10) { + qCInfo(model_scripting) << "no faces for key!?" << key; + } + normal = verts.get(j); + } + if (glm::isnan(normal.x)) { + static int logged = 0; + if (logged++ < 10) { + qCInfo(model_scripting) << "isnan(normal.x)" << j << vec3toVariant(normal); + } + break; + } + normals.edit(j) = glm::normalize(normal); + } + return true; +} + +QScriptValue ModelScriptingInterface::mapMeshAttributeValues( + scriptable::ScriptableMeshPointer meshProxy, QScriptValue scopeOrCallback, QScriptValue methodOrName +) { + auto mesh = getMeshPointer(meshProxy); + if (!mesh) { + return false; + } + auto scopedHandler = makeScopedHandlerObject(scopeOrCallback, methodOrName); + + // input buffers + gpu::BufferView positions = mesh->getVertexBuffer(); + + const auto nPositions = positions.getNumElements(); + + // destructure so we can still invoke callback scoped, but with a custom signature (obj, i, jsMesh) + auto scope = scopedHandler.property("scope"); + auto callback = scopedHandler.property("callback"); + auto js = engine(); // cache value to avoid resolving each iteration + auto meshPart = js->toScriptValue(meshProxy); + + auto obj = js->newObject(); + auto attributeViews = ScriptableMesh::gatherBufferViews(mesh, { "normal", "color" }); + for (uint32_t i=0; i < nPositions; i++) { + for (const auto& a : attributeViews) { + bool asArray = a.second._element.getType() != gpu::FLOAT; + obj.setProperty(a.first, bufferViewElementToScriptValue(js, a.second, i, asArray, a.first.toStdString().c_str())); + } + auto result = callback.call(scope, { obj, i, meshPart }); + if (js->hasUncaughtException()) { + context()->throwValue(js->uncaughtException()); + return false; + } + + if (result.isBool() && !result.toBool()) { + // bail without modifying data if user explicitly returns false + continue; + } + if (result.isObject() && !result.strictlyEquals(obj)) { + // user returned a new object (ie: instead of modifying input properties) + obj = result; + } + + for (const auto& a : attributeViews) { + const auto& attribute = obj.property(a.first); + auto& view = a.second; + if (attribute.isValid()) { + bufferViewElementFromScriptValue(attribute, view, i); + } + } + } + return thisObject(); +} + +void ModelScriptingInterface::getMeshes(QUuid uuid, QScriptValue scopeOrCallback, QScriptValue methodOrName) { + auto handler = makeScopedHandlerObject(scopeOrCallback, methodOrName); + Q_ASSERT(handler.engine() == this->engine()); + QPointer engine = dynamic_cast(handler.engine()); + + scriptable::ScriptableModel meshes; + bool success = false; + QString error; + + auto appProvider = DependencyManager::get(); + qDebug() << "appProvider" << appProvider.data(); + scriptable::ModelProviderPointer provider = appProvider ? appProvider->lookupModelProvider(uuid) : nullptr; + QString providerType = provider ? provider->metadata.value("providerType").toString() : QString(); + if (providerType.isEmpty()) { + providerType = "unknown"; + } + if (provider) { + qCDebug(model_scripting) << "fetching meshes from " << providerType << "..."; + auto scriptableMeshes = provider->getScriptableModel(&success); + qCDebug(model_scripting) << "//fetched meshes from " << providerType << "success:" <makeError(error), QScriptValue::NullValue); + } else { + callScopedHandlerObject(handler, QScriptValue::NullValue, engine->toScriptValue(meshes)); + } +} + +namespace { + QScriptValue meshToScriptValue(QScriptEngine* engine, scriptable::ScriptableMeshPointer const &in) { + return engine->newQObject(in.get(), QScriptEngine::QtOwnership, + QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects + ); + } + + void meshFromScriptValue(const QScriptValue& value, scriptable::ScriptableMeshPointer &out) { + auto obj = value.toQObject(); + //qDebug() << "meshFromScriptValue" << obj; + if (auto tmp = qobject_cast(obj)) { + out = tmp->shared_from_this(); + } + // FIXME: Why does above cast not work on Win32!? + if (!out) { + auto smp = static_cast(obj); + //qDebug() << "meshFromScriptValue2" << smp; + out = smp->shared_from_this(); + } + } + + QScriptValue meshesToScriptValue(QScriptEngine* engine, const scriptable::ScriptableModelPointer &in) { + // QScriptValueList result; + QScriptValue result = engine->newArray(); + int i = 0; + foreach(scriptable::ScriptableMeshPointer const meshProxy, in->getMeshes()) { + result.setProperty(i++, meshToScriptValue(engine, meshProxy)); + } + return result; + } + + void meshesFromScriptValue(const QScriptValue& value, scriptable::ScriptableModelPointer &out) { + const auto length = value.property("length").toInt32(); + qCDebug(model_scripting) << "in meshesFromScriptValue, length =" << length; + for (int i = 0; i < length; i++) { + if (const auto meshProxy = qobject_cast(value.property(i).toQObject())) { + out->meshes.append(meshProxy->getMeshPointer()); + } else { + qCDebug(model_scripting) << "null meshProxy" << i; + } + } + } + + void modelProxyFromScriptValue(const QScriptValue& object, scriptable::ScriptableModel &meshes) { + auto meshesProperty = object.property("meshes"); + if (meshesProperty.property("length").toInt32() > 0) { + //meshes._meshes = qobject_cast(meshesProperty.toQObject()); + // qDebug() << "modelProxyFromScriptValue" << meshesProperty.property("length").toInt32() << meshesProperty.toVariant().typeName(); + qScriptValueToSequence(meshesProperty, meshes.meshes); + } else if (auto mesh = qobject_cast(object.toQObject())) { + meshes.meshes << mesh->getMeshPointer(); + } else { + qDebug() << "modelProxyFromScriptValue -- unrecognized input" << object.toVariant().toString(); + } + + meshes.metadata = object.property("metadata").toVariant().toMap(); + } + + QScriptValue modelProxyToScriptValue(QScriptEngine* engine, const scriptable::ScriptableModel &in) { + QScriptValue obj = engine->newObject(); + obj.setProperty("meshes", qScriptValueFromSequence(engine, in.meshes)); + obj.setProperty("metadata", engine->toScriptValue(in.metadata)); + return obj; + } + + QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const mesh::MeshFace &meshFace) { + QScriptValue obj = engine->newObject(); + obj.setProperty("vertices", qVectorIntToScriptValue(engine, meshFace.vertexIndices)); + return obj; + } + + void meshFaceFromScriptValue(const QScriptValue &object, mesh::MeshFace& meshFaceResult) { + qScriptValueToSequence(object.property("vertices"), meshFaceResult.vertexIndices); + } + + QScriptValue qVectorMeshFaceToScriptValue(QScriptEngine* engine, const QVector& vector) { + return qScriptValueFromSequence(engine, vector); + } + + void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector& result) { + qScriptValueToSequence(array, result); + } + + QScriptValue qVectorUInt32ToScriptValue(QScriptEngine* engine, const QVector& vector) { + return qScriptValueFromSequence(engine, vector); + } + + void qVectorUInt32FromScriptValue(const QScriptValue& array, QVector& result) { + qScriptValueToSequence(array, result); + } +} + +int meshUint32 = qRegisterMetaType(); +namespace mesh { + int meshUint32 = qRegisterMetaType(); +} +int qVectorMeshUint32 = qRegisterMetaType>(); + +void ModelScriptingInterface::registerMetaTypes(QScriptEngine* engine) { + qScriptRegisterSequenceMetaType>(engine); + qScriptRegisterSequenceMetaType(engine); + qScriptRegisterSequenceMetaType>(engine); + qScriptRegisterMetaType(engine, modelProxyToScriptValue, modelProxyFromScriptValue); + + qScriptRegisterMetaType(engine, qVectorUInt32ToScriptValue, qVectorUInt32FromScriptValue); + qScriptRegisterMetaType(engine, meshToScriptValue, meshFromScriptValue); + qScriptRegisterMetaType(engine, meshesToScriptValue, meshesFromScriptValue); + qScriptRegisterMetaType(engine, meshFaceToScriptValue, meshFaceFromScriptValue); + qScriptRegisterMetaType(engine, qVectorMeshFaceToScriptValue, qVectorMeshFaceFromScriptValue); +} + +MeshPointer ModelScriptingInterface::getMeshPointer(scriptable::ScriptableMeshPointer meshProxy) { + MeshPointer result; + if (!meshProxy) { + if (context()){ + context()->throwError("expected meshProxy as first parameter"); + } + return result; + } + auto mesh = meshProxy->getMeshPointer(); + if (!mesh) { + if (context()) { + context()->throwError("expected valid meshProxy as first parameter"); + } + return result; + } + return mesh; +} diff --git a/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h new file mode 100644 index 0000000000..d10fd28170 --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/ModelScriptingInterface.h @@ -0,0 +1,68 @@ +// +// ModelScriptingInterface.h +// libraries/script-engine/src +// +// Created by Seth Alves on 2017-1-27. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ModelScriptingInterface_h +#define hifi_ModelScriptingInterface_h + +#include +#include + +#include + +#include +#include + +#include "ScriptableMesh.h" +#include +class ModelScriptingInterface : public QObject, public QScriptable, public Dependency { + Q_OBJECT + +public: + ModelScriptingInterface(QObject* parent = nullptr); + static void registerMetaTypes(QScriptEngine* engine); + +public slots: + /**jsdoc + * Returns the meshes associated with a UUID (entityID, overlayID, or avatarID) + * + * @function ModelScriptingInterface.getMeshes + * @param {EntityID} entityID The ID of the entity whose meshes are to be retrieve + */ + void getMeshes(QUuid uuid, QScriptValue scopeOrCallback, QScriptValue methodOrName = QScriptValue()); + + bool dedupeVertices(scriptable::ScriptableMeshPointer meshProxy, float epsilon = 1e-6); + bool recalculateNormals(scriptable::ScriptableMeshPointer meshProxy); + QScriptValue cloneMesh(scriptable::ScriptableMeshPointer meshProxy, bool recalcNormals = true); + QScriptValue unrollVertices(scriptable::ScriptableMeshPointer meshProxy, bool recalcNormals = true); + QScriptValue mapAttributeValues(QScriptValue in, + QScriptValue scopeOrCallback, + QScriptValue methodOrName = QScriptValue()); + QScriptValue mapMeshAttributeValues(scriptable::ScriptableMeshPointer meshProxy, + QScriptValue scopeOrCallback, + QScriptValue methodOrName = QScriptValue()); + + QString meshToOBJ(const scriptable::ScriptableModel& in); + + bool replaceMeshData(scriptable::ScriptableMeshPointer dest, scriptable::ScriptableMeshPointer source, const QVector& attributeNames = QVector()); + QScriptValue appendMeshes(scriptable::ScriptableModel in); + QScriptValue transformMesh(scriptable::ScriptableMeshPointer meshProxy, glm::mat4 transform); + QScriptValue newMesh(const QVector& vertices, + const QVector& normals, + const QVector& faces); + QScriptValue getVertexCount(scriptable::ScriptableMeshPointer meshProxy); + QScriptValue getVertex(scriptable::ScriptableMeshPointer meshProxy, mesh::uint32 vertexIndex); + +private: + scriptable::MeshPointer getMeshPointer(scriptable::ScriptableMeshPointer meshProxy); + +}; + +#endif // hifi_ModelScriptingInterface_h diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp new file mode 100644 index 0000000000..47d91e9e59 --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp @@ -0,0 +1,359 @@ +// +// SimpleMeshProxy.cpp +// libraries/model-networking/src/model-networking/ +// +// Created by Seth Alves on 2017-3-22. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ScriptableMesh.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "ScriptableMesh.moc" + +#include + +QLoggingCategory mesh_logging { "hifi.scripting.mesh" }; + +// FIXME: unroll/resolve before PR +using namespace scriptable; +QMap ScriptableMesh::ATTRIBUTES{ + {"position", gpu::Stream::POSITION }, + {"normal", gpu::Stream::NORMAL }, + {"color", gpu::Stream::COLOR }, + {"tangent", gpu::Stream::TEXCOORD0 }, + {"skin_cluster_index", gpu::Stream::SKIN_CLUSTER_INDEX }, + {"skin_cluster_weight", gpu::Stream::SKIN_CLUSTER_WEIGHT }, + {"texcoord0", gpu::Stream::TEXCOORD0 }, + {"texcoord1", gpu::Stream::TEXCOORD1 }, + {"texcoord2", gpu::Stream::TEXCOORD2 }, + {"texcoord3", gpu::Stream::TEXCOORD3 }, + {"texcoord4", gpu::Stream::TEXCOORD4 }, +}; + +QVector scriptable::ScriptableModel::getMeshes() const { + QVector out; + for(auto& mesh : meshes) { + out << scriptable::ScriptableMeshPointer(new ScriptableMesh(std::const_pointer_cast(this->shared_from_this()), mesh)); + } + return out; +} + +quint32 ScriptableMesh::getNumVertices() const { + if (auto mesh = getMeshPointer()) { + return (quint32)mesh->getNumVertices(); + } + return 0; +} + +// glm::vec3 ScriptableMesh::getPos3(quint32 index) const { +// if (auto mesh = getMeshPointer()) { +// if (index < getNumVertices()) { +// return mesh->getPos3(index); +// } +// } +// return glm::vec3(NAN); +// } + +namespace { + gpu::BufferView getBufferView(scriptable::MeshPointer mesh, gpu::Stream::Slot slot) { + return slot == gpu::Stream::POSITION ? mesh->getVertexBuffer() : mesh->getAttributeBuffer(slot); + } +} + +QVector ScriptableMesh::findNearbyIndices(const glm::vec3& origin, float epsilon) const { + QVector result; + if (auto mesh = getMeshPointer()) { + const auto& pos = getBufferView(mesh, gpu::Stream::POSITION); + const uint32_t num = (uint32_t)pos.getNumElements(); + for (uint32_t i = 0; i < num; i++) { + const auto& position = pos.get(i); + if (glm::distance(position, origin) <= epsilon) { + result << i; + } + } + } + return result; +} + +QVector ScriptableMesh::getIndices() const { + QVector result; + if (auto mesh = getMeshPointer()) { + qCDebug(mesh_logging, "getTriangleIndices mesh %p", mesh.get()); + gpu::BufferView indexBufferView = mesh->getIndexBuffer(); + if (quint32 count = (quint32)indexBufferView.getNumElements()) { + result.resize(count); + auto buffer = indexBufferView._buffer; + if (indexBufferView._element.getSize() == 4) { + // memcpy(result.data(), buffer->getData(), result.size()*sizeof(quint32)); + for (quint32 i = 0; i < count; i++) { + result[i] = indexBufferView.get(i); + } + } else { + for (quint32 i = 0; i < count; i++) { + result[i] = indexBufferView.get(i); + } + } + } + } + return result; +} + +quint32 ScriptableMesh::getNumAttributes() const { + if (auto mesh = getMeshPointer()) { + return (quint32)mesh->getNumAttributes(); + } + return 0; +} +QVector ScriptableMesh::getAttributeNames() const { + QVector result; + if (auto mesh = getMeshPointer()) { + for (const auto& a : ATTRIBUTES.toStdMap()) { + auto bufferView = getBufferView(mesh, a.second); + if (bufferView.getNumElements() > 0) { + result << a.first; + } + } + } + return result; +} + +// override +QVariantMap ScriptableMesh::getVertexAttributes(quint32 vertexIndex) const { + return getVertexAttributes(vertexIndex, getAttributeNames()); +} + +bool ScriptableMesh::setVertexAttributes(quint32 vertexIndex, QVariantMap attributes) { + qDebug() << "setVertexAttributes" << vertexIndex << attributes; + for (auto& a : gatherBufferViews(getMeshPointer())) { + const auto& name = a.first; + const auto& value = attributes.value(name); + if (value.isValid()) { + auto& view = a.second; + bufferViewElementFromVariant(view, vertexIndex, value); + } else { + qCDebug(mesh_logging) << "setVertexAttributes" << vertexIndex << name; + } + } + return true; +} + +int ScriptableMesh::_getSlotNumber(const QString& attributeName) const { + if (auto mesh = getMeshPointer()) { + return ATTRIBUTES.value(attributeName, -1); + } + return -1; +} + + +QVariantMap ScriptableMesh::getMeshExtents() const { + auto mesh = getMeshPointer(); + auto box = mesh ? mesh->evalPartsBound(0, (int)mesh->getNumParts()) : AABox(); + return { + { "brn", glmVecToVariant(box.getCorner()) }, + { "tfl", glmVecToVariant(box.calcTopFarLeft()) }, + { "center", glmVecToVariant(box.calcCenter()) }, + { "min", glmVecToVariant(box.getMinimumPoint()) }, + { "max", glmVecToVariant(box.getMaximumPoint()) }, + { "dimensions", glmVecToVariant(box.getDimensions()) }, + }; +} + +quint32 ScriptableMesh::getNumParts() const { + if (auto mesh = getMeshPointer()) { + return (quint32)mesh->getNumParts(); + } + return 0; +} + +QVariantMap ScriptableMesh::scaleToFit(float unitScale) { + if (auto mesh = getMeshPointer()) { + auto box = mesh->evalPartsBound(0, (int)mesh->getNumParts()); + auto center = box.calcCenter(); + float maxDimension = glm::distance(box.getMaximumPoint(), box.getMinimumPoint()); + return scale(glm::vec3(unitScale / maxDimension), center); + } + return {}; +} +QVariantMap ScriptableMesh::translate(const glm::vec3& translation) { + return transform(glm::translate(translation)); +} +QVariantMap ScriptableMesh::scale(const glm::vec3& scale, const glm::vec3& origin) { + if (auto mesh = getMeshPointer()) { + auto box = mesh->evalPartsBound(0, (int)mesh->getNumParts()); + glm::vec3 center = glm::isnan(origin.x) ? box.calcCenter() : origin; + return transform(glm::translate(center) * glm::scale(scale)); + } + return {}; +} +QVariantMap ScriptableMesh::rotateDegrees(const glm::vec3& eulerAngles, const glm::vec3& origin) { + return rotate(glm::quat(glm::radians(eulerAngles)), origin); +} +QVariantMap ScriptableMesh::rotate(const glm::quat& rotation, const glm::vec3& origin) { + if (auto mesh = getMeshPointer()) { + auto box = mesh->evalPartsBound(0, (int)mesh->getNumParts()); + glm::vec3 center = glm::isnan(origin.x) ? box.calcCenter() : origin; + return transform(glm::translate(center) * glm::toMat4(rotation)); + } + return {}; +} +QVariantMap ScriptableMesh::transform(const glm::mat4& transform) { + if (auto mesh = getMeshPointer()) { + const auto& pos = getBufferView(mesh, gpu::Stream::POSITION); + const uint32_t num = (uint32_t)pos.getNumElements(); + for (uint32_t i = 0; i < num; i++) { + auto& position = pos.edit(i); + position = transform * glm::vec4(position, 1.0f); + } + } + return getMeshExtents(); +} + +QVariantList ScriptableMesh::getAttributeValues(const QString& attributeName) const { + QVariantList result; + auto slotNum = _getSlotNumber(attributeName); + if (slotNum >= 0) { + auto slot = (gpu::Stream::Slot)slotNum; + const auto& bufferView = getBufferView(getMeshPointer(), slot); + if (auto len = bufferView.getNumElements()) { + bool asArray = bufferView._element.getType() != gpu::FLOAT; + for (quint32 i = 0; i < len; i++) { + result << bufferViewElementToVariant(bufferView, i, asArray, attributeName.toStdString().c_str()); + } + } + } + return result; +} +QVariantMap ScriptableMesh::getVertexAttributes(quint32 vertexIndex, QVector names) const { + QVariantMap result; + auto mesh = getMeshPointer(); + if (!mesh || vertexIndex >= getNumVertices()) { + return result; + } + for (const auto& a : ATTRIBUTES.toStdMap()) { + auto name = a.first; + if (!names.contains(name)) { + continue; + } + auto slot = a.second; + const gpu::BufferView& bufferView = getBufferView(mesh, slot); + if (vertexIndex < bufferView.getNumElements()) { + bool asArray = bufferView._element.getType() != gpu::FLOAT; + result[name] = bufferViewElementToVariant(bufferView, vertexIndex, asArray, name.toStdString().c_str()); + } + } + return result; +} + +/// --- buffer view <-> variant helpers + +namespace { + // expand the corresponding attribute buffer (creating it if needed) so that it matches POSITIONS size and specified element type + gpu::BufferView _expandedAttributeBuffer(const scriptable::MeshPointer mesh, gpu::Stream::Slot slot, const gpu::Element& elementType) { + gpu::Size elementSize = elementType.getSize(); + gpu::BufferView bufferView = getBufferView(mesh, slot); + auto nPositions = mesh->getNumVertices(); + auto vsize = nPositions * elementSize; + auto diffTypes = (elementType.getType() != bufferView._element.getType() || + elementType.getSize() > bufferView._element.getSize() || + elementType.getScalarCount() > bufferView._element.getScalarCount() || + vsize > bufferView._size + ); + auto hint = DebugNames::stringFrom(slot); + +#ifdef DEV_BUILD + auto beforeCount = bufferView.getNumElements(); + auto beforeTotal = bufferView._size; +#endif + if (bufferView.getNumElements() < nPositions || diffTypes) { + if (!bufferView._buffer || bufferView.getNumElements() == 0) { + qCInfo(mesh_logging).nospace() << "ScriptableMesh -- adding missing mesh attribute '" << hint << "' for BufferView"; + gpu::Byte *data = new gpu::Byte[vsize]; + memset(data, 0, vsize); + auto buffer = new gpu::Buffer(vsize, (gpu::Byte*)data); + delete[] data; + bufferView = gpu::BufferView(buffer, elementType); + mesh->addAttribute(slot, bufferView); + } else { + qCInfo(mesh_logging) << "ScriptableMesh -- resizing Buffer current:" << hint << bufferView._buffer->getSize() << "wanted:" << vsize; + bufferView._element = elementType; + bufferView._buffer->resize(vsize); + bufferView._size = bufferView._buffer->getSize(); + } + } +#ifdef DEV_BUILD + auto afterCount = bufferView.getNumElements(); + auto afterTotal = bufferView._size; + if (beforeTotal != afterTotal || beforeCount != afterCount) { + auto typeName = DebugNames::stringFrom(bufferView._element.getType()); + qCDebug(mesh_logging, "NOTE:: _expandedAttributeBuffer.%s vec%d %s (before count=%lu bytes=%lu // after count=%lu bytes=%lu)", + hint.toStdString().c_str(), bufferView._element.getScalarCount(), + typeName.toStdString().c_str(), beforeCount, beforeTotal, afterCount, afterTotal); + } +#endif + return bufferView; + } + const gpu::Element UNUSED{ gpu::SCALAR, gpu::UINT8, gpu::RAW }; + + gpu::Element getVecNElement(gpu::Type T, int N) { + switch(N) { + case 2: return { gpu::VEC2, T, gpu::XY }; + case 3: return { gpu::VEC3, T, gpu::XYZ }; + case 4: return { gpu::VEC4, T, gpu::XYZW }; + } + Q_ASSERT(false); + return UNUSED; + } + + gpu::BufferView expandAttributeToMatchPositions(scriptable::MeshPointer mesh, gpu::Stream::Slot slot) { + if (slot == gpu::Stream::POSITION) { + return getBufferView(mesh, slot); + } + return _expandedAttributeBuffer(mesh, slot, getVecNElement(gpu::FLOAT, 3)); + } +} + +std::map ScriptableMesh::gatherBufferViews(scriptable::MeshPointer mesh, const QStringList& expandToMatchPositions) { + std::map attributeViews; + if (!mesh) { + return attributeViews; + } + for (const auto& a : ScriptableMesh::ATTRIBUTES.toStdMap()) { + auto name = a.first; + auto slot = a.second; + if (expandToMatchPositions.contains(name)) { + expandAttributeToMatchPositions(mesh, slot); + } + auto view = getBufferView(mesh, slot); + auto beforeCount = view.getNumElements(); + if (beforeCount > 0) { + auto element = view._element; + auto vecN = element.getScalarCount(); + auto type = element.getType(); + QString typeName = DebugNames::stringFrom(element.getType()); + auto beforeTotal = view._size; + + attributeViews[name] = _expandedAttributeBuffer(mesh, slot, getVecNElement(type, vecN)); + +#if DEV_BUILD + auto afterTotal = attributeViews[name]._size; + auto afterCount = attributeViews[name].getNumElements(); + if (beforeTotal != afterTotal || beforeCount != afterCount) { + qCDebug(mesh_logging, "NOTE:: gatherBufferViews.%s vec%d %s (before count=%lu bytes=%lu // after count=%lu bytes=%lu)", + name.toStdString().c_str(), vecN, typeName.toStdString().c_str(), beforeCount, beforeTotal, afterCount, afterTotal); + } +#endif + } + } + return attributeViews; +} diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h new file mode 100644 index 0000000000..da11002906 --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h @@ -0,0 +1,127 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +namespace graphics { + class Mesh; +} +namespace gpu { + class BufferView; +} +namespace scriptable { + class ScriptableMesh : public QObject, public std::enable_shared_from_this { + Q_OBJECT + public: + ScriptableModelPointer _model; + scriptable::MeshPointer _mesh; + QVariantMap _metadata; + ScriptableMesh() : QObject() {} + ScriptableMesh(ScriptableModelPointer parent, scriptable::MeshPointer mesh) : QObject(), _model(parent), _mesh(mesh) {} + ScriptableMesh(const ScriptableMesh& other) : QObject(), _model(other._model), _mesh(other._mesh), _metadata(other._metadata) {} + ~ScriptableMesh() { qDebug() << "~ScriptableMesh" << this; } + Q_PROPERTY(quint32 numParts READ getNumParts) + Q_PROPERTY(quint32 numAttributes READ getNumAttributes) + Q_PROPERTY(quint32 numVertices READ getNumVertices) + Q_PROPERTY(quint32 numIndices READ getNumIndices) + Q_PROPERTY(QVector attributeNames READ getAttributeNames) + + virtual scriptable::MeshPointer getMeshPointer() const { return _mesh; } + Q_INVOKABLE virtual quint32 getNumParts() const; + Q_INVOKABLE virtual quint32 getNumVertices() const; + Q_INVOKABLE virtual quint32 getNumAttributes() const; + Q_INVOKABLE virtual quint32 getNumIndices() const { return 0; } + Q_INVOKABLE virtual QVector getAttributeNames() const; + Q_INVOKABLE virtual QVariantMap getVertexAttributes(quint32 vertexIndex) const; + Q_INVOKABLE virtual QVariantMap getVertexAttributes(quint32 vertexIndex, QVector attributes) const; + + Q_INVOKABLE virtual QVector getIndices() const; + Q_INVOKABLE virtual QVector findNearbyIndices(const glm::vec3& origin, float epsilon = 1e-6) const; + Q_INVOKABLE virtual QVariantMap getMeshExtents() const; + Q_INVOKABLE virtual bool setVertexAttributes(quint32 vertexIndex, QVariantMap attributes); + Q_INVOKABLE virtual QVariantMap scaleToFit(float unitScale); + + static QMap ATTRIBUTES; + static std::map gatherBufferViews(MeshPointer mesh, const QStringList& expandToMatchPositions = QStringList()); + + Q_INVOKABLE QVariantList getAttributeValues(const QString& attributeName) const; + + Q_INVOKABLE int _getSlotNumber(const QString& attributeName) const; + + QVariantMap translate(const glm::vec3& translation); + QVariantMap scale(const glm::vec3& scale, const glm::vec3& origin = glm::vec3(NAN)); + QVariantMap rotateDegrees(const glm::vec3& eulerAngles, const glm::vec3& origin = glm::vec3(NAN)); + QVariantMap rotate(const glm::quat& rotation, const glm::vec3& origin = glm::vec3(NAN)); + Q_INVOKABLE QVariantMap transform(const glm::mat4& transform); + }; + + // TODO: for now this is a part-specific wrapper around ScriptableMesh + class ScriptableMeshPart : public ScriptableMesh { + Q_OBJECT + public: + ScriptableMeshPart& operator=(const ScriptableMeshPart& view) { _model=view._model; _mesh=view._mesh; return *this; }; + ScriptableMeshPart(const ScriptableMeshPart& other) : ScriptableMesh(other._model, other._mesh) {} + ScriptableMeshPart() : ScriptableMesh(nullptr, nullptr) {} + ~ScriptableMeshPart() { qDebug() << "~ScriptableMeshPart" << this; } + ScriptableMeshPart(ScriptableMeshPointer mesh) : ScriptableMesh(mesh->_model, mesh->_mesh) {} + Q_PROPERTY(QString topology READ getTopology) + Q_PROPERTY(quint32 numFaces READ getNumFaces) + + scriptable::MeshPointer parentMesh; + int partIndex; + QString getTopology() const { return "triangles"; } + Q_INVOKABLE virtual quint32 getNumFaces() const { return getIndices().size() / 3; } + Q_INVOKABLE virtual QVector getFace(quint32 faceIndex) const { + auto inds = getIndices(); + return faceIndex+2 < (quint32)inds.size() ? inds.mid(faceIndex*3, 3) : QVector(); + } + }; + + class GraphicsScriptingInterface : public QObject { + Q_OBJECT + public: + GraphicsScriptingInterface(QObject* parent = nullptr) : QObject(parent) {} + GraphicsScriptingInterface(const GraphicsScriptingInterface& other) {} + public slots: + ScriptableMeshPart exportMeshPart(ScriptableMesh mesh, int part) { return {}; } + + }; +} + +Q_DECLARE_METATYPE(scriptable::ScriptableMesh) +Q_DECLARE_METATYPE(scriptable::ScriptableMeshPointer) +Q_DECLARE_METATYPE(QVector) +Q_DECLARE_METATYPE(scriptable::ScriptableMeshPart) +Q_DECLARE_METATYPE(scriptable::GraphicsScriptingInterface) + +// FIXME: faces were supported in the original Model.* API -- are they still needed/used/useful for anything yet? +#include + +namespace mesh { + using uint32 = quint32; + class MeshFace; + using MeshFaces = QVector; + class MeshFace { + public: + MeshFace() {} + MeshFace(QVector vertexIndices) : vertexIndices(vertexIndices) {} + ~MeshFace() {} + + QVector vertexIndices; + // TODO -- material... + }; +}; + +Q_DECLARE_METATYPE(mesh::MeshFace) +Q_DECLARE_METATYPE(QVector) +Q_DECLARE_METATYPE(mesh::uint32) +Q_DECLARE_METATYPE(QVector) diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h new file mode 100644 index 0000000000..e8cf6f1656 --- /dev/null +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace graphics { + class Mesh; +} +namespace gpu { + class BufferView; +} +namespace scriptable { + using Mesh = graphics::Mesh; + using MeshPointer = std::shared_ptr; + + class ScriptableModel; + class ScriptableMesh; + class ScriptableMeshPart; + using ScriptableModelPointer = std::shared_ptr; + using ScriptableMeshPointer = std::shared_ptr; + using ScriptableMeshPartPointer = std::shared_ptr; + class ScriptableModel : public QObject, public std::enable_shared_from_this { + Q_OBJECT + public: + Q_PROPERTY(QVector meshes READ getMeshes) + + Q_INVOKABLE QString toString() { return "[ScriptableModel " + objectName()+"]"; } + ScriptableModel(QObject* parent = nullptr) : QObject(parent) {} + ScriptableModel(const ScriptableModel& other) : objectID(other.objectID), metadata(other.metadata), meshes(other.meshes) {} + ScriptableModel& operator=(const ScriptableModel& view) { + objectID = view.objectID; + metadata = view.metadata; + meshes = view.meshes; + return *this; + } + ~ScriptableModel() { qDebug() << "~ScriptableModel" << this; } + void mixin(const ScriptableModel& other) { + for (const auto& key : other.metadata.keys()) { + metadata[key] = other.metadata[key]; + } + for(const auto&mesh : other.meshes) { + meshes << mesh; + } + } + QUuid objectID; + QVariantMap metadata; + QVector meshes; + // TODO: in future accessors for these could go here + QVariantMap shapes; + QVariantMap materials; + QVariantMap armature; + + QVector getMeshes() const; + }; + + class ModelProvider { + public: + QVariantMap metadata; + static scriptable::ScriptableModel modelUnavailableError(bool* ok) { if (ok) { *ok = false; } return {}; } + virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) = 0; + }; + using ModelProviderPointer = std::shared_ptr; + class ModelProviderFactory : public Dependency { + public: + virtual scriptable::ModelProviderPointer lookupModelProvider(QUuid uuid) = 0; + }; + +} + +Q_DECLARE_METATYPE(scriptable::MeshPointer) +Q_DECLARE_METATYPE(scriptable::ScriptableModel) +Q_DECLARE_METATYPE(scriptable::ScriptableModelPointer) + diff --git a/libraries/graphics/src/graphics/Geometry.cpp b/libraries/graphics/src/graphics/Geometry.cpp index ba5afcbc62..d43c773249 100755 --- a/libraries/graphics/src/graphics/Geometry.cpp +++ b/libraries/graphics/src/graphics/Geometry.cpp @@ -42,6 +42,11 @@ void Mesh::addAttribute(Slot slot, const BufferView& buffer) { evalVertexFormat(); } +void Mesh::removeAttribute(Slot slot) { + _attributeBuffers.erase(slot); + evalVertexFormat(); +} + const BufferView Mesh::getAttributeBuffer(int attrib) const { auto attribBuffer = _attributeBuffers.find(attrib); if (attribBuffer != _attributeBuffers.end()) { @@ -224,6 +229,7 @@ graphics::MeshPointer Mesh::map(std::function vertexFunc, } graphics::MeshPointer result(new graphics::Mesh()); + result->displayName = displayName; gpu::Element vertexElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); gpu::Buffer* resultVertexBuffer = new gpu::Buffer(vertexSize, resultVertexData.get()); diff --git a/libraries/graphics/src/graphics/Geometry.h b/libraries/graphics/src/graphics/Geometry.h index 642aa9e38d..23ebec2965 100755 --- a/libraries/graphics/src/graphics/Geometry.h +++ b/libraries/graphics/src/graphics/Geometry.h @@ -56,6 +56,7 @@ public: // Attribute Buffers size_t getNumAttributes() const { return _attributeBuffers.size(); } void addAttribute(Slot slot, const BufferView& buffer); + void removeAttribute(Slot slot); const BufferView getAttributeBuffer(int attrib) const; // Stream format diff --git a/libraries/model-networking/src/model-networking/SimpleMeshProxy.cpp b/libraries/model-networking/src/model-networking/SimpleMeshProxy.cpp deleted file mode 100644 index 741478789e..0000000000 --- a/libraries/model-networking/src/model-networking/SimpleMeshProxy.cpp +++ /dev/null @@ -1,27 +0,0 @@ -// -// SimpleMeshProxy.cpp -// libraries/model-networking/src/model-networking/ -// -// Created by Seth Alves on 2017-3-22. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "SimpleMeshProxy.h" - -#include - -MeshPointer SimpleMeshProxy::getMeshPointer() const { - return _mesh; -} - -int SimpleMeshProxy::getNumVertices() const { - return (int)_mesh->getNumVertices(); -} - -glm::vec3 SimpleMeshProxy::getPos3(int index) const { - return _mesh->getPos3(index); -} - diff --git a/libraries/model-networking/src/model-networking/SimpleMeshProxy.h b/libraries/model-networking/src/model-networking/SimpleMeshProxy.h deleted file mode 100644 index 24c3fca27e..0000000000 --- a/libraries/model-networking/src/model-networking/SimpleMeshProxy.h +++ /dev/null @@ -1,36 +0,0 @@ -// -// SimpleMeshProxy.h -// libraries/model-networking/src/model-networking/ -// -// Created by Seth Alves on 2017-1-27. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_SimpleMeshProxy_h -#define hifi_SimpleMeshProxy_h - -#include -#include -#include - -#include - -class SimpleMeshProxy : public MeshProxy { -public: - SimpleMeshProxy(const MeshPointer& mesh) : _mesh(mesh) { } - - MeshPointer getMeshPointer() const override; - - int getNumVertices() const override; - - glm::vec3 getPos3(int index) const override; - - -protected: - const MeshPointer _mesh; -}; - -#endif // hifi_SimpleMeshProxy_h diff --git a/libraries/render-utils/CMakeLists.txt b/libraries/render-utils/CMakeLists.txt index 6be3057c93..55762e38fd 100644 --- a/libraries/render-utils/CMakeLists.txt +++ b/libraries/render-utils/CMakeLists.txt @@ -7,6 +7,7 @@ link_hifi_libraries(shared ktx gpu graphics model-networking render animation fb include_hifi_library_headers(networking) include_hifi_library_headers(octree) include_hifi_library_headers(audio) +include_hifi_library_headers(graphics-scripting) # for ScriptableModel.h if (NOT ANDROID) target_nsight() diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 23473e74f2..6aa42cf6df 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -2407,3 +2407,48 @@ void GeometryCache::renderWireCubeInstance(RenderArgs* args, gpu::Batch& batch, assert(pipeline != nullptr); renderInstances(args, batch, color, true, pipeline, GeometryCache::Cube); } + +graphics::MeshPointer GeometryCache::meshFromShape(Shape geometryShape, glm::vec3 color) { + auto shapeData = getShapeData(geometryShape); + + qDebug() << "GeometryCache::getMeshProxyListFromShape" << shapeData << stringFromShape(geometryShape); + + auto cloneBufferView = [](const gpu::BufferView& in) -> gpu::BufferView { + auto buffer = std::make_shared(*in._buffer); // copy + // FIXME: gpu::BufferView seems to have a bug where constructing a new instance from an existing one + // results in over-multiplied buffer/view sizes -- hence constructing manually here from each input prop + auto out = gpu::BufferView(buffer, in._offset, in._size, in._stride, in._element); + Q_ASSERT(out.getNumElements() == in.getNumElements()); + Q_ASSERT(out._size == in._size); + Q_ASSERT(out._buffer->getSize() == in._buffer->getSize()); + return out; + }; + + auto positionsBufferView = cloneBufferView(shapeData->_positionView); + auto normalsBufferView = cloneBufferView(shapeData->_normalView); + auto indexBufferView = cloneBufferView(shapeData->_indicesView); + + gpu::BufferView::Size numVertices = positionsBufferView.getNumElements(); + Q_ASSERT(numVertices == normalsBufferView.getNumElements()); + + // apply input color across all vertices + auto colorsBufferView = cloneBufferView(shapeData->_normalView); + for (gpu::BufferView::Size i = 0; i < numVertices; i++) { + colorsBufferView.edit((gpu::BufferView::Index)i) = color; + } + + graphics::MeshPointer mesh(new graphics::Mesh()); + mesh->setVertexBuffer(positionsBufferView); + mesh->setIndexBuffer(indexBufferView); + mesh->addAttribute(gpu::Stream::NORMAL, normalsBufferView); + mesh->addAttribute(gpu::Stream::COLOR, colorsBufferView); + + const auto startIndex = 0, baseVertex = 0; + graphics::Mesh::Part part(startIndex, (graphics::Index)indexBufferView.getNumElements(), baseVertex, graphics::Mesh::TRIANGLES); + auto partBuffer = new gpu::Buffer(sizeof(graphics::Mesh::Part), (gpu::Byte*)&part); + mesh->setPartBuffer(gpu::BufferView(partBuffer, gpu::Element::PART_DRAWCALL)); + + mesh->displayName = QString("GeometryCache/shape::%1").arg(GeometryCache::stringFromShape(geometryShape)); + + return mesh; +} diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index 63af30bb79..998043b80e 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -375,6 +375,7 @@ public: /// otherwise nullptr in the event of an error. const ShapeData * getShapeData(Shape shape) const; + graphics::MeshPointer meshFromShape(Shape geometryShape, glm::vec3 color); private: GeometryCache(); diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index b0763c0fb3..d595136c56 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -26,7 +26,7 @@ #include #include -#include +#include #include #include @@ -573,15 +573,21 @@ bool Model::convexHullContains(glm::vec3 point) { return false; } -MeshProxyList Model::getMeshes() const { - MeshProxyList result; +scriptable::ScriptableModel Model::getScriptableModel(bool* ok) { + scriptable::ScriptableModel result; const Geometry::Pointer& renderGeometry = getGeometry(); - const Geometry::GeometryMeshes& meshes = renderGeometry->getMeshes(); if (!isLoaded()) { + qDebug() << "Model::getScriptableModel -- !isLoaded"; + if (ok) { + *ok = false; + } return result; } +// TODO: remove -- this was an earlier approach using renderGeometry instead of FBXGeometry +#if 0 // renderGeometry approach + const Geometry::GeometryMeshes& meshes = renderGeometry->getMeshes(); Transform offset; offset.setScale(_scale); offset.postTranslate(_offset); @@ -591,20 +597,67 @@ MeshProxyList Model::getMeshes() const { if (!mesh) { continue; } - - MeshProxy* meshProxy = new SimpleMeshProxy( - mesh->map( - [=](glm::vec3 position) { - return glm::vec3(offsetMat * glm::vec4(position, 1.0f)); - }, - [=](glm::vec3 color) { return color; }, - [=](glm::vec3 normal) { - return glm::normalize(glm::vec3(offsetMat * glm::vec4(normal, 0.0f))); - }, - [&](uint32_t index) { return index; })); - result << meshProxy; + qDebug() << "Model::getScriptableModel #" << i++ << mesh->displayName; + auto newmesh = mesh->map( + [=](glm::vec3 position) { + return glm::vec3(offsetMat * glm::vec4(position, 1.0f)); + }, + [=](glm::vec3 color) { return color; }, + [=](glm::vec3 normal) { + return glm::normalize(glm::vec3(offsetMat * glm::vec4(normal, 0.0f))); + }, + [&](uint32_t index) { return index; }); + newmesh->displayName = mesh->displayName; + result << newmesh; } - +#endif + const FBXGeometry& geometry = getFBXGeometry(); + auto mat4toVariant = [](const glm::mat4& mat4) -> QVariant { + QVector floats; + floats.resize(16); + memcpy(floats.data(), &mat4, sizeof(glm::mat4)); + QVariant v; + v.setValue>(floats); + return v; + }; + result.metadata = { + { "url", _url.toString() }, + { "textures", renderGeometry->getTextures() }, + { "offset", vec3toVariant(_offset) }, + { "scale", vec3toVariant(_scale) }, + { "rotation", quatToVariant(_rotation) }, + { "translation", vec3toVariant(_translation) }, + { "meshToModel", mat4toVariant(glm::scale(_scale) * glm::translate(_offset)) }, + { "meshToWorld", mat4toVariant(createMatFromQuatAndPos(_rotation, _translation) * (glm::scale(_scale) * glm::translate(_offset))) }, + { "geometryOffset", mat4toVariant(geometry.offset) }, + }; + QVariantList submeshes; + int numberOfMeshes = geometry.meshes.size(); + for (int i = 0; i < numberOfMeshes; i++) { + const FBXMesh& fbxMesh = geometry.meshes.at(i); + auto mesh = fbxMesh._mesh; + if (!mesh) { + continue; + } + result.meshes << std::const_pointer_cast(mesh); + auto extraInfo = geometry.getModelNameOfMesh(i); + qDebug() << "Model::getScriptableModel #" << i << QString(mesh->displayName) << extraInfo; + submeshes << QVariantMap{ + { "index", i }, + { "meshIndex", fbxMesh.meshIndex }, + { "modelName", extraInfo }, + { "transform", mat4toVariant(fbxMesh.modelTransform) }, + { "extents", QVariantMap({ + { "minimum", vec3toVariant(fbxMesh.meshExtents.minimum) }, + { "maximum", vec3toVariant(fbxMesh.meshExtents.maximum) }, + })}, + }; + } + if (ok) { + *ok = true; + } + qDebug() << "//Model::getScriptableModel -- #" << result.meshes.size(); + result.metadata["submeshes"] = submeshes; return result; } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 027d52ecfd..4fd00c9f9a 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -64,7 +65,7 @@ using ModelWeakPointer = std::weak_ptr; /// A generic 3D model displaying geometry loaded from a URL. -class Model : public QObject, public std::enable_shared_from_this { +class Model : public QObject, public std::enable_shared_from_this, public scriptable::ModelProvider { Q_OBJECT public: @@ -313,7 +314,7 @@ public: int getResourceDownloadAttempts() { return _renderWatcher.getResourceDownloadAttempts(); } int getResourceDownloadAttemptsRemaining() { return _renderWatcher.getResourceDownloadAttemptsRemaining(); } - Q_INVOKABLE MeshProxyList getMeshes() const; + Q_INVOKABLE virtual scriptable::ScriptableModel getScriptableModel(bool* ok = nullptr) override; void scaleToFit(); diff --git a/libraries/script-engine/src/ModelScriptingInterface.cpp b/libraries/script-engine/src/ModelScriptingInterface.cpp deleted file mode 100644 index c693083ebf..0000000000 --- a/libraries/script-engine/src/ModelScriptingInterface.cpp +++ /dev/null @@ -1,251 +0,0 @@ -// -// ModelScriptingInterface.cpp -// libraries/script-engine/src -// -// Created by Seth Alves on 2017-1-27. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "ModelScriptingInterface.h" -#include -#include -#include -#include -#include "ScriptEngine.h" -#include "ScriptEngineLogging.h" -#include "OBJWriter.h" - -ModelScriptingInterface::ModelScriptingInterface(QObject* parent) : QObject(parent) { - _modelScriptEngine = qobject_cast(parent); - - qScriptRegisterSequenceMetaType>(_modelScriptEngine); - qScriptRegisterMetaType(_modelScriptEngine, meshFaceToScriptValue, meshFaceFromScriptValue); - qScriptRegisterMetaType(_modelScriptEngine, qVectorMeshFaceToScriptValue, qVectorMeshFaceFromScriptValue); -} - -QString ModelScriptingInterface::meshToOBJ(MeshProxyList in) { - QList meshes; - foreach (const MeshProxy* meshProxy, in) { - meshes.append(meshProxy->getMeshPointer()); - } - - return writeOBJToString(meshes); -} - -QScriptValue ModelScriptingInterface::appendMeshes(MeshProxyList in) { - // figure out the size of the resulting mesh - size_t totalVertexCount { 0 }; - size_t totalColorCount { 0 }; - size_t totalNormalCount { 0 }; - size_t totalIndexCount { 0 }; - foreach (const MeshProxy* meshProxy, in) { - MeshPointer mesh = meshProxy->getMeshPointer(); - totalVertexCount += mesh->getNumVertices(); - - int attributeTypeColor = gpu::Stream::InputSlot::COLOR; // libraries/gpu/src/gpu/Stream.h - const gpu::BufferView& colorsBufferView = mesh->getAttributeBuffer(attributeTypeColor); - gpu::BufferView::Index numColors = (gpu::BufferView::Index)colorsBufferView.getNumElements(); - totalColorCount += numColors; - - int attributeTypeNormal = gpu::Stream::InputSlot::NORMAL; // libraries/gpu/src/gpu/Stream.h - const gpu::BufferView& normalsBufferView = mesh->getAttributeBuffer(attributeTypeNormal); - gpu::BufferView::Index numNormals = (gpu::BufferView::Index)normalsBufferView.getNumElements(); - totalNormalCount += numNormals; - - totalIndexCount += mesh->getNumIndices(); - } - - // alloc the resulting mesh - gpu::Resource::Size combinedVertexSize = totalVertexCount * sizeof(glm::vec3); - std::unique_ptr combinedVertexData{ new unsigned char[combinedVertexSize] }; - unsigned char* combinedVertexDataCursor = combinedVertexData.get(); - - gpu::Resource::Size combinedColorSize = totalColorCount * sizeof(glm::vec3); - std::unique_ptr combinedColorData{ new unsigned char[combinedColorSize] }; - unsigned char* combinedColorDataCursor = combinedColorData.get(); - - gpu::Resource::Size combinedNormalSize = totalNormalCount * sizeof(glm::vec3); - std::unique_ptr combinedNormalData{ new unsigned char[combinedNormalSize] }; - unsigned char* combinedNormalDataCursor = combinedNormalData.get(); - - gpu::Resource::Size combinedIndexSize = totalIndexCount * sizeof(uint32_t); - std::unique_ptr combinedIndexData{ new unsigned char[combinedIndexSize] }; - unsigned char* combinedIndexDataCursor = combinedIndexData.get(); - - uint32_t indexStartOffset { 0 }; - - foreach (const MeshProxy* meshProxy, in) { - MeshPointer mesh = meshProxy->getMeshPointer(); - mesh->forEach( - [&](glm::vec3 position){ - memcpy(combinedVertexDataCursor, &position, sizeof(position)); - combinedVertexDataCursor += sizeof(position); - }, - [&](glm::vec3 color){ - memcpy(combinedColorDataCursor, &color, sizeof(color)); - combinedColorDataCursor += sizeof(color); - }, - [&](glm::vec3 normal){ - memcpy(combinedNormalDataCursor, &normal, sizeof(normal)); - combinedNormalDataCursor += sizeof(normal); - }, - [&](uint32_t index){ - index += indexStartOffset; - memcpy(combinedIndexDataCursor, &index, sizeof(index)); - combinedIndexDataCursor += sizeof(index); - }); - - gpu::BufferView::Index numVertices = (gpu::BufferView::Index)mesh->getNumVertices(); - indexStartOffset += numVertices; - } - - graphics::MeshPointer result(new graphics::Mesh()); - - gpu::Element vertexElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); - gpu::Buffer* combinedVertexBuffer = new gpu::Buffer(combinedVertexSize, combinedVertexData.get()); - gpu::BufferPointer combinedVertexBufferPointer(combinedVertexBuffer); - gpu::BufferView combinedVertexBufferView(combinedVertexBufferPointer, vertexElement); - result->setVertexBuffer(combinedVertexBufferView); - - int attributeTypeColor = gpu::Stream::InputSlot::COLOR; // libraries/gpu/src/gpu/Stream.h - gpu::Element colorElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); - gpu::Buffer* combinedColorsBuffer = new gpu::Buffer(combinedColorSize, combinedColorData.get()); - gpu::BufferPointer combinedColorsBufferPointer(combinedColorsBuffer); - gpu::BufferView combinedColorsBufferView(combinedColorsBufferPointer, colorElement); - result->addAttribute(attributeTypeColor, combinedColorsBufferView); - - int attributeTypeNormal = gpu::Stream::InputSlot::NORMAL; // libraries/gpu/src/gpu/Stream.h - gpu::Element normalElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); - gpu::Buffer* combinedNormalsBuffer = new gpu::Buffer(combinedNormalSize, combinedNormalData.get()); - gpu::BufferPointer combinedNormalsBufferPointer(combinedNormalsBuffer); - gpu::BufferView combinedNormalsBufferView(combinedNormalsBufferPointer, normalElement); - result->addAttribute(attributeTypeNormal, combinedNormalsBufferView); - - gpu::Element indexElement = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::RAW); - gpu::Buffer* combinedIndexesBuffer = new gpu::Buffer(combinedIndexSize, combinedIndexData.get()); - gpu::BufferPointer combinedIndexesBufferPointer(combinedIndexesBuffer); - gpu::BufferView combinedIndexesBufferView(combinedIndexesBufferPointer, indexElement); - result->setIndexBuffer(combinedIndexesBufferView); - - std::vector parts; - parts.emplace_back(graphics::Mesh::Part((graphics::Index)0, // startIndex - (graphics::Index)result->getNumIndices(), // numIndices - (graphics::Index)0, // baseVertex - graphics::Mesh::TRIANGLES)); // topology - result->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(graphics::Mesh::Part), - (gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL)); - - - MeshProxy* resultProxy = new SimpleMeshProxy(result); - return meshToScriptValue(_modelScriptEngine, resultProxy); -} - -QScriptValue ModelScriptingInterface::transformMesh(glm::mat4 transform, MeshProxy* meshProxy) { - if (!meshProxy) { - return QScriptValue(false); - } - MeshPointer mesh = meshProxy->getMeshPointer(); - if (!mesh) { - return QScriptValue(false); - } - - const auto inverseTransposeTransform = glm::inverse(glm::transpose(transform)); - graphics::MeshPointer result = mesh->map([&](glm::vec3 position){ return glm::vec3(transform * glm::vec4(position, 1.0f)); }, - [&](glm::vec3 color){ return color; }, - [&](glm::vec3 normal){ return glm::vec3(inverseTransposeTransform * glm::vec4(normal, 0.0f)); }, - [&](uint32_t index){ return index; }); - MeshProxy* resultProxy = new SimpleMeshProxy(result); - return meshToScriptValue(_modelScriptEngine, resultProxy); -} - -QScriptValue ModelScriptingInterface::getVertexCount(MeshProxy* meshProxy) { - if (!meshProxy) { - return QScriptValue(false); - } - MeshPointer mesh = meshProxy->getMeshPointer(); - if (!mesh) { - return QScriptValue(false); - } - - gpu::BufferView::Index numVertices = (gpu::BufferView::Index)mesh->getNumVertices(); - - return numVertices; -} - -QScriptValue ModelScriptingInterface::getVertex(MeshProxy* meshProxy, int vertexIndex) { - if (!meshProxy) { - return QScriptValue(false); - } - MeshPointer mesh = meshProxy->getMeshPointer(); - if (!mesh) { - return QScriptValue(false); - } - - const gpu::BufferView& vertexBufferView = mesh->getVertexBuffer(); - gpu::BufferView::Index numVertices = (gpu::BufferView::Index)mesh->getNumVertices(); - - if (vertexIndex < 0 || vertexIndex >= numVertices) { - return QScriptValue(false); - } - - glm::vec3 pos = vertexBufferView.get(vertexIndex); - return vec3toScriptValue(_modelScriptEngine, pos); -} - - -QScriptValue ModelScriptingInterface::newMesh(const QVector& vertices, - const QVector& normals, - const QVector& faces) { - graphics::MeshPointer mesh(new graphics::Mesh()); - - // vertices - auto vertexBuffer = std::make_shared(vertices.size() * sizeof(glm::vec3), (gpu::Byte*)vertices.data()); - auto vertexBufferPtr = gpu::BufferPointer(vertexBuffer); - gpu::BufferView vertexBufferView(vertexBufferPtr, 0, vertexBufferPtr->getSize(), - sizeof(glm::vec3), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); - mesh->setVertexBuffer(vertexBufferView); - - if (vertices.size() == normals.size()) { - // normals - auto normalBuffer = std::make_shared(normals.size() * sizeof(glm::vec3), (gpu::Byte*)normals.data()); - auto normalBufferPtr = gpu::BufferPointer(normalBuffer); - gpu::BufferView normalBufferView(normalBufferPtr, 0, normalBufferPtr->getSize(), - sizeof(glm::vec3), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); - mesh->addAttribute(gpu::Stream::NORMAL, normalBufferView); - } else { - qCDebug(scriptengine) << "ModelScriptingInterface::newMesh normals must be same length as vertices"; - } - - // indices (faces) - int VERTICES_PER_TRIANGLE = 3; - int indexBufferSize = faces.size() * sizeof(uint32_t) * VERTICES_PER_TRIANGLE; - unsigned char* indexData = new unsigned char[indexBufferSize]; - unsigned char* indexDataCursor = indexData; - foreach(const MeshFace& meshFace, faces) { - for (int i = 0; i < VERTICES_PER_TRIANGLE; i++) { - memcpy(indexDataCursor, &meshFace.vertexIndices[i], sizeof(uint32_t)); - indexDataCursor += sizeof(uint32_t); - } - } - auto indexBuffer = std::make_shared(indexBufferSize, (gpu::Byte*)indexData); - auto indexBufferPtr = gpu::BufferPointer(indexBuffer); - gpu::BufferView indexBufferView(indexBufferPtr, gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::RAW)); - mesh->setIndexBuffer(indexBufferView); - - // parts - std::vector parts; - parts.emplace_back(graphics::Mesh::Part((graphics::Index)0, // startIndex - (graphics::Index)faces.size() * 3, // numIndices - (graphics::Index)0, // baseVertex - graphics::Mesh::TRIANGLES)); // topology - mesh->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(graphics::Mesh::Part), - (gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL)); - - - - MeshProxy* meshProxy = new SimpleMeshProxy(mesh); - return meshToScriptValue(_modelScriptEngine, meshProxy); -} diff --git a/libraries/script-engine/src/ModelScriptingInterface.h b/libraries/script-engine/src/ModelScriptingInterface.h deleted file mode 100644 index 3c239f006f..0000000000 --- a/libraries/script-engine/src/ModelScriptingInterface.h +++ /dev/null @@ -1,39 +0,0 @@ -// -// ModelScriptingInterface.h -// libraries/script-engine/src -// -// Created by Seth Alves on 2017-1-27. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_ModelScriptingInterface_h -#define hifi_ModelScriptingInterface_h - -#include - -#include -class QScriptEngine; - -class ModelScriptingInterface : public QObject { - Q_OBJECT - -public: - ModelScriptingInterface(QObject* parent); - - Q_INVOKABLE QString meshToOBJ(MeshProxyList in); - Q_INVOKABLE QScriptValue appendMeshes(MeshProxyList in); - Q_INVOKABLE QScriptValue transformMesh(glm::mat4 transform, MeshProxy* meshProxy); - Q_INVOKABLE QScriptValue newMesh(const QVector& vertices, - const QVector& normals, - const QVector& faces); - Q_INVOKABLE QScriptValue getVertexCount(MeshProxy* meshProxy); - Q_INVOKABLE QScriptValue getVertex(MeshProxy* meshProxy, int vertexIndex); - -private: - QScriptEngine* _modelScriptEngine { nullptr }; -}; - -#endif // hifi_ModelScriptingInterface_h diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index c79ffffec7..ffb1b7bc74 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -73,8 +73,6 @@ #include "WebSocketClass.h" #include "RecordingScriptingInterface.h" #include "ScriptEngines.h" -#include "ModelScriptingInterface.h" - #include @@ -711,10 +709,6 @@ void ScriptEngine::init() { registerGlobalObject("DebugDraw", &DebugDraw::getInstance()); - registerGlobalObject("Model", new ModelScriptingInterface(this)); - qScriptRegisterMetaType(this, meshToScriptValue, meshFromScriptValue); - qScriptRegisterMetaType(this, meshesToScriptValue, meshesFromScriptValue); - registerGlobalObject("UserActivityLogger", DependencyManager::get().data()); } diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index 7b455beae5..9ad5c27072 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -855,68 +855,3 @@ QScriptValue animationDetailsToScriptValue(QScriptEngine* engine, const Animatio void animationDetailsFromScriptValue(const QScriptValue& object, AnimationDetails& details) { // nothing for now... } - -QScriptValue meshToScriptValue(QScriptEngine* engine, MeshProxy* const &in) { - return engine->newQObject(in, QScriptEngine::QtOwnership, - QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects); -} - -void meshFromScriptValue(const QScriptValue& value, MeshProxy* &out) { - out = qobject_cast(value.toQObject()); -} - -QScriptValue meshesToScriptValue(QScriptEngine* engine, const MeshProxyList &in) { - // QScriptValueList result; - QScriptValue result = engine->newArray(); - int i = 0; - foreach(MeshProxy* const meshProxy, in) { - result.setProperty(i++, meshToScriptValue(engine, meshProxy)); - } - return result; -} - -void meshesFromScriptValue(const QScriptValue& value, MeshProxyList &out) { - QScriptValueIterator itr(value); - - qDebug() << "in meshesFromScriptValue, value.length =" << value.property("length").toInt32(); - - while (itr.hasNext()) { - itr.next(); - MeshProxy* meshProxy = qscriptvalue_cast(itr.value()); - if (meshProxy) { - out.append(meshProxy); - } else { - qDebug() << "null meshProxy"; - } - } -} - - -QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const MeshFace &meshFace) { - QScriptValue obj = engine->newObject(); - obj.setProperty("vertices", qVectorIntToScriptValue(engine, meshFace.vertexIndices)); - return obj; -} - -void meshFaceFromScriptValue(const QScriptValue &object, MeshFace& meshFaceResult) { - qVectorIntFromScriptValue(object.property("vertices"), meshFaceResult.vertexIndices); -} - -QScriptValue qVectorMeshFaceToScriptValue(QScriptEngine* engine, const QVector& vector) { - QScriptValue array = engine->newArray(); - for (int i = 0; i < vector.size(); i++) { - array.setProperty(i, meshFaceToScriptValue(engine, vector.at(i))); - } - return array; -} - -void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector& result) { - int length = array.property("length").toInteger(); - result.clear(); - - for (int i = 0; i < length; i++) { - MeshFace meshFace = MeshFace(); - meshFaceFromScriptValue(array.property(i), meshFace); - result << meshFace; - } -} diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 25b2cec331..e8390c3a86 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -315,51 +315,9 @@ Q_DECLARE_METATYPE(AnimationDetails); QScriptValue animationDetailsToScriptValue(QScriptEngine* engine, const AnimationDetails& event); void animationDetailsFromScriptValue(const QScriptValue& object, AnimationDetails& event); -namespace graphics { - class Mesh; -} - -using MeshPointer = std::shared_ptr; -class MeshProxy : public QObject { - Q_OBJECT -public: - virtual MeshPointer getMeshPointer() const = 0; - Q_INVOKABLE virtual int getNumVertices() const = 0; - Q_INVOKABLE virtual glm::vec3 getPos3(int index) const = 0; -}; - -Q_DECLARE_METATYPE(MeshProxy*); - -class MeshProxyList : public QList {}; // typedef and using fight with the Qt macros/templates, do this instead -Q_DECLARE_METATYPE(MeshProxyList); - - -QScriptValue meshToScriptValue(QScriptEngine* engine, MeshProxy* const &in); -void meshFromScriptValue(const QScriptValue& value, MeshProxy* &out); - -QScriptValue meshesToScriptValue(QScriptEngine* engine, const MeshProxyList &in); -void meshesFromScriptValue(const QScriptValue& value, MeshProxyList &out); - -class MeshFace { - -public: - MeshFace() {} - ~MeshFace() {} - - QVector vertexIndices; - // TODO -- material... -}; - -Q_DECLARE_METATYPE(MeshFace) -Q_DECLARE_METATYPE(QVector) - -QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const MeshFace &meshFace); -void meshFaceFromScriptValue(const QScriptValue &object, MeshFace& meshFaceResult); -QScriptValue qVectorMeshFaceToScriptValue(QScriptEngine* engine, const QVector& vector); -void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector& result); #endif // hifi_RegisteredMetaTypes_h