// // Model.h // interface/src/renderer // // Created by Andrzej Kapolka on 10/18/13. // Copyright 2013 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_Model_h #define hifi_Model_h #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "GeometryCache.h" #include "TextureCache.h" #include "Rig.h" class AbstractViewStateInterface; class QScriptEngine; #include "RenderArgs.h" class ViewFrustum; namespace render { class Scene; class PendingChanges; typedef unsigned int ItemID; } class MeshPartPayload; class ModelMeshPartPayload; class ModelRenderLocations; inline uint qHash(const std::shared_ptr& a, uint seed) { return qHash(a.get(), seed); } class Model; using ModelPointer = std::shared_ptr; 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 { Q_OBJECT public: typedef RenderArgs::RenderMode RenderMode; static void setAbstractViewStateInterface(AbstractViewStateInterface* viewState) { _viewState = viewState; } Model(RigPointer rig, QObject* parent = nullptr); virtual ~Model(); inline ModelPointer getThisPointer() const { return std::static_pointer_cast(std::const_pointer_cast(shared_from_this())); } /// Sets the URL of the model to render. // Should only be called from the model's rendering thread to avoid access violations of changed geometry. Q_INVOKABLE void setURL(const QUrl& url); const QUrl& getURL() const { return _url; } // new Scene/Engine rendering support void setVisibleInScene(bool newValue, std::shared_ptr scene); void setLayeredInFront(bool layered, std::shared_ptr scene); bool needsFixupInScene() const; bool needsReload() const { return _needsReload; } bool initWhenReady(render::ScenePointer scene); bool addToScene(std::shared_ptr scene, render::PendingChanges& pendingChanges) { auto getters = render::Item::Status::Getters(0); return addToScene(scene, pendingChanges, getters); } bool addToScene(std::shared_ptr scene, render::PendingChanges& pendingChanges, render::Item::Status::Getters& statusGetters); void removeFromScene(std::shared_ptr scene, render::PendingChanges& pendingChanges); void renderSetup(RenderArgs* args); bool isRenderable() const; bool isVisible() const { return _isVisible; } bool isLayeredInFront() const { return _isLayeredInFront; } void updateRenderItems(); void setRenderItemsNeedUpdate() { _renderItemsNeedUpdate = true; } bool getRenderItemsNeedUpdate() { return _renderItemsNeedUpdate; } AABox getRenderableMeshBound() const; bool maybeStartBlender(); /// Sets blended vertices computed in a separate thread. void setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geometry, const QVector& vertices, const QVector& normals); bool isLoaded() const { return (bool)_renderGeometry; } void setIsWireframe(bool isWireframe) { _isWireframe = isWireframe; } bool isWireframe() const { return _isWireframe; } void init(); void reset(); void setScaleToFit(bool scaleToFit, const glm::vec3& dimensions); void setSnapModelToRegistrationPoint(bool snapModelToRegistrationPoint, const glm::vec3& registrationPoint); bool getSnapModelToRegistrationPoint() { return _snapModelToRegistrationPoint; } virtual void simulate(float deltaTime, bool fullUpdate = true); virtual void updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrientation); /// Returns a reference to the shared geometry. const Geometry::Pointer& getGeometry() const { return _renderGeometry; } /// Returns a reference to the shared collision geometry. const Geometry::Pointer& getCollisionGeometry() const { return _collisionGeometry; } const QVariantMap getTextures() const { assert(isLoaded()); return _renderGeometry->getTextures(); } Q_INVOKABLE void setTextures(const QVariantMap& textures); /// Provided as a convenience, will crash if !isLoaded() // And so that getGeometry() isn't chained everywhere const FBXGeometry& getFBXGeometry() const { assert(isLoaded()); return _renderGeometry->getFBXGeometry(); } bool isActive() const { return isLoaded(); } bool didVisualGeometryRequestFail() const { return _visualGeometryRequestFailed; } bool didCollisionGeometryRequestFail() const { return _collisionGeometryRequestFailed; } bool convexHullContains(glm::vec3 point); QStringList getJointNames() const; /// Sets the joint state at the specified index. void setJointState(int index, bool valid, const glm::quat& rotation, const glm::vec3& translation, float priority); void setJointRotation(int index, bool valid, const glm::quat& rotation, float priority); void setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority); bool findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QString& extraInfo, bool pickAgainstTriangles = false); void setOffset(const glm::vec3& offset); const glm::vec3& getOffset() const { return _offset; } void setScaleToFit(bool scaleToFit, float largestDimension = 0.0f, bool forceRescale = false); bool getScaleToFit() const { return _scaleToFit; } /// is scale to fit enabled void setSnapModelToCenter(bool snapModelToCenter) { setSnapModelToRegistrationPoint(snapModelToCenter, glm::vec3(0.5f,0.5f,0.5f)); }; bool getSnapModelToCenter() { return _snapModelToRegistrationPoint && _registrationPoint == glm::vec3(0.5f,0.5f,0.5f); } /// Returns the number of joint states in the model. int getJointStateCount() const { return (int)_rig->getJointStateCount(); } bool getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const; bool getJointRotationInWorldFrame(int jointIndex, glm::quat& rotation) const; bool getJointCombinedRotation(int jointIndex, glm::quat& rotation) const; /// \param jointIndex index of joint in model structure /// \param rotation[out] rotation of joint in model-frame /// \return true if joint exists bool getJointRotation(int jointIndex, glm::quat& rotation) const; bool getJointTranslation(int jointIndex, glm::vec3& translation) const; // model frame bool getAbsoluteJointRotationInRigFrame(int jointIndex, glm::quat& rotationOut) const; bool getAbsoluteJointTranslationInRigFrame(int jointIndex, glm::vec3& translationOut) const; bool getRelativeDefaultJointRotation(int jointIndex, glm::quat& rotationOut) const; bool getRelativeDefaultJointTranslation(int jointIndex, glm::vec3& translationOut) const; /// Returns the index of the parent of the indexed joint, or -1 if not found. int getParentJointIndex(int jointIndex) const; void inverseKinematics(int jointIndex, glm::vec3 position, const glm::quat& rotation, float priority); /// Returns the extents of the model in its bind pose. Extents getBindExtents() const; /// Returns the extents of the model's mesh Extents getMeshExtents() const; void setTranslation(const glm::vec3& translation); void setRotation(const glm::quat& rotation); void setSpatiallyNestableOverride(SpatiallyNestablePointer ptr); const glm::vec3& getTranslation() const { return _translation; } const glm::quat& getRotation() const { return _rotation; } Transform getTransform() const; void setScale(const glm::vec3& scale); const glm::vec3& getScale() const { return _scale; } /// enables/disables scale to fit behavior, the model will be automatically scaled to the specified largest dimension bool getIsScaledToFit() const { return _scaledToFit; } /// is model scaled to fit glm::vec3 getScaleToFitDimensions() const; /// the dimensions model is scaled to, including inferred y/z void setCauterizeBones(bool flag) { _cauterizeBones = flag; } bool getCauterizeBones() const { return _cauterizeBones; } const std::unordered_set& getCauterizeBoneSet() const { return _cauterizeBoneSet; } void setCauterizeBoneSet(const std::unordered_set& boneSet) { _cauterizeBoneSet = boneSet; } int getBlendshapeCoefficientsNum() const { return _blendshapeCoefficients.size(); } float getBlendshapeCoefficient(int index) const { return ((index < 0) && (index >= _blendshapeCoefficients.size())) ? 0.0f : _blendshapeCoefficients.at(index); } virtual RigPointer getRig() const { return _rig; } const glm::vec3& getRegistrationPoint() const { return _registrationPoint; } // returns 'true' if needs fullUpdate after geometry change bool updateGeometry(); void setCollisionMesh(model::MeshPointer mesh); void setLoadingPriority(float priority) { _loadingPriority = priority; } size_t getRenderInfoVertexCount() const { return _renderInfoVertexCount; } size_t getRenderInfoTextureSize(); int getRenderInfoTextureCount(); int getRenderInfoDrawCalls() const { return _renderInfoDrawCalls; } bool getRenderInfoHasTransparent() const { return _renderInfoHasTransparent; } public slots: void loadURLFinished(bool success); signals: void setURLFinished(bool success); void setCollisionModelURLFinished(bool success); protected: bool addedToScene() const { return _addedToScene; } void setPupilDilation(float dilation) { _pupilDilation = dilation; } float getPupilDilation() const { return _pupilDilation; } void setBlendshapeCoefficients(const QVector& coefficients) { _blendshapeCoefficients = coefficients; } const QVector& getBlendshapeCoefficients() const { return _blendshapeCoefficients; } /// Returns the unscaled extents of the model's mesh Extents getUnscaledMeshExtents() const; /// Returns the scaled equivalent of some extents in model space. Extents calculateScaledOffsetExtents(const Extents& extents, glm::vec3 modelPosition, glm::quat modelOrientation) const; /// Returns the world space equivalent of some box in model space. AABox calculateScaledOffsetAABox(const AABox& box, glm::vec3 modelPosition, glm::quat modelOrientation) const; /// Returns the scaled equivalent of a point in model space. glm::vec3 calculateScaledOffsetPoint(const glm::vec3& point) const; /// Clear the joint states void clearJointState(int index); /// Returns the index of the last free ancestor of the indexed joint, or -1 if not found. int getLastFreeJointIndex(int jointIndex) const; /// \param jointIndex index of joint in model structure /// \param position[out] position of joint in model-frame /// \return true if joint exists bool getJointPosition(int jointIndex, glm::vec3& position) const; Geometry::Pointer _renderGeometry; // only ever set by its watcher Geometry::Pointer _collisionGeometry; GeometryResourceWatcher _renderWatcher; glm::vec3 _translation; glm::quat _rotation; glm::vec3 _scale; SpatiallyNestableWeakPointer _spatiallyNestableOverride; glm::vec3 _offset; static float FAKE_DIMENSION_PLACEHOLDER; bool _scaleToFit; /// If you set scaleToFit, we will calculate scale based on MeshExtents glm::vec3 _scaleToFitDimensions; /// this is the dimensions that scale to fit will use bool _scaledToFit; /// have we scaled to fit bool _snapModelToRegistrationPoint; /// is the model's offset automatically adjusted to a registration point in model space bool _snappedToRegistrationPoint; /// are we currently snapped to a registration point glm::vec3 _registrationPoint = glm::vec3(0.5f); /// the point in model space our center is snapped to class MeshState { public: QVector clusterMatrices; QVector cauterizedClusterMatrices; gpu::BufferPointer clusterBuffer; gpu::BufferPointer cauterizedClusterBuffer; }; QVector _meshStates; std::unordered_set _cauterizeBoneSet; bool _cauterizeBones; virtual void initJointStates(); void setScaleInternal(const glm::vec3& scale); void scaleToFit(); void snapToRegistrationPoint(); void simulateInternal(float deltaTime); virtual void updateRig(float deltaTime, glm::mat4 parentTransform); /// Restores the indexed joint to its default position. /// \param fraction the fraction of the default position to apply (i.e., 0.25f to slerp one fourth of the way to /// the original position /// \return true if the joint was found bool restoreJointPosition(int jointIndex, float fraction = 1.0f, float priority = 0.0f); /// Computes and returns the extended length of the limb terminating at the specified joint and starting at the joint's /// first free ancestor. float getLimbLength(int jointIndex) const; /// Allow sub classes to force invalidating the bboxes void invalidCalculatedMeshBoxes() { _calculatedMeshBoxesValid = false; _calculatedMeshPartBoxesValid = false; _calculatedMeshTrianglesValid = false; } // hook for derived classes to be notified when setUrl invalidates the current model. virtual void onInvalidate() {}; protected: void deleteGeometry(); void initJointTransforms(); float _pupilDilation; QVector _blendshapeCoefficients; QUrl _url; bool _isVisible; gpu::Buffers _blendedVertexBuffers; QVector > > _dilatedTextures; QVector _blendedBlendshapeCoefficients; int _blendNumber; int _appliedBlendNumber; QHash, AABox> _calculatedMeshPartBoxes; // world coordinate AABoxes for all sub mesh part boxes bool _calculatedMeshPartBoxesValid; QVector _calculatedMeshBoxes; // world coordinate AABoxes for all sub mesh boxes bool _calculatedMeshBoxesValid; QVector< QVector > _calculatedMeshTriangles; // world coordinate triangles for all sub meshes bool _calculatedMeshTrianglesValid; QMutex _mutex; void recalculateMeshBoxes(bool pickAgainstTriangles = false); void createRenderItemSet(); void createVisibleRenderItemSet(); void createCollisionRenderItemSet(); bool _isWireframe; // debug rendering support void renderDebugMeshBoxes(gpu::Batch& batch); int _debugMeshBoxesID = GeometryCache::UNKNOWN_ID; static AbstractViewStateInterface* _viewState; QSet> _collisionRenderItemsSet; QMap _collisionRenderItems; QSet> _modelMeshRenderItemsSet; QMap _modelMeshRenderItems; bool _addedToScene { false }; // has been added to scene bool _needsFixupInScene { true }; // needs to be removed/re-added to scene bool _needsReload { true }; bool _needsUpdateClusterMatrices { true }; mutable bool _needsUpdateTextures { true }; friend class ModelMeshPartPayload; RigPointer _rig; uint32_t _deleteGeometryCounter { 0 }; bool _visualGeometryRequestFailed { false }; bool _collisionGeometryRequestFailed { false }; bool _renderItemsNeedUpdate { false }; size_t _renderInfoVertexCount { 0 }; int _renderInfoTextureCount { 0 }; size_t _renderInfoTextureSize { 0 }; bool _hasCalculatedTextureInfo { false }; int _renderInfoDrawCalls { 0 }; int _renderInfoHasTransparent { false }; bool _isLayeredInFront { false }; private: float _loadingPriority { 0.0f }; void calculateTextureInfo(); }; Q_DECLARE_METATYPE(ModelPointer) Q_DECLARE_METATYPE(Geometry::WeakPointer) /// Handle management of pending models that need blending class ModelBlender : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY public: /// Adds the specified model to the list requiring vertex blends. void noteRequiresBlend(ModelPointer model); public slots: void setBlendedVertices(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry, const QVector& vertices, const QVector& normals); private: using Mutex = std::mutex; using Lock = std::unique_lock; ModelBlender(); virtual ~ModelBlender(); std::set> _modelsRequiringBlends; int _pendingBlenders; Mutex _mutex; }; #endif // hifi_Model_h