// // 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 #include #include #include #include "RenderHifi.h" #include "GeometryCache.h" #include "TextureCache.h" #include "Rig.h" #include "PrimitiveMode.h" // Use dual quaternion skinning! // Must match define in Skinning.slh #define SKIN_DQ class AbstractViewStateInterface; class QScriptEngine; class ViewFrustum; namespace render { class Scene; class Transaction; 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; struct SortedTriangleSet { SortedTriangleSet(float distance, TriangleSet* triangleSet, int partIndex, int shapeID, int subMeshIndex) : distance(distance), triangleSet(triangleSet), partIndex(partIndex), shapeID(shapeID), subMeshIndex(subMeshIndex) {} float distance; TriangleSet* triangleSet; int partIndex; int shapeID; int subMeshIndex; }; struct BlendshapeOffsetPacked { glm::uvec4 packedPosNorTan; }; struct BlendshapeOffsetUnpacked { glm::vec3 positionOffset; glm::vec3 normalOffset; glm::vec3 tangentOffset; }; using BlendshapeOffset = BlendshapeOffsetPacked; using BlendShapeOperator = std::function&, const QVector&, const render::ItemIDs&)>; /// A generic 3D model displaying geometry loaded from a URL. class Model : public QObject, public std::enable_shared_from_this, public scriptable::ModelProvider { Q_OBJECT public: typedef RenderArgs::RenderMode RenderMode; static void setAbstractViewStateInterface(AbstractViewStateInterface* viewState) { _viewState = viewState; } Model(QObject* parent = nullptr, SpatiallyNestable* spatiallyNestableOverride = 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 virtual void setURL(const QUrl& url); const QUrl& getURL() const { return _url; } // new Scene/Engine rendering support void setVisibleInScene(bool isVisible, const render::ScenePointer& scene = nullptr); bool isVisible() const; render::hifi::Tag getTagMask() const; void setTagMask(uint8_t mask, const render::ScenePointer& scene = nullptr); bool isGroupCulled() const; void setGroupCulled(bool isGroupCulled, const render::ScenePointer& scene = nullptr); bool canCastShadow() const; void setCanCastShadow(bool canCastShadow, const render::ScenePointer& scene = nullptr); void setHifiRenderLayer(render::hifi::Layer layer, const render::ScenePointer& scene = nullptr); bool isCauterized() const { return _cauterized; } void setCauterized(bool value, const render::ScenePointer& scene); // Access the current RenderItemKey Global Flags used by the model and applied to the render items representing the parts of the model. const render::ItemKey getRenderItemKeyGlobalFlags() const; bool needsFixupInScene() const; bool needsReload() const { return _needsReload; } bool addToScene(const render::ScenePointer& scene, render::Transaction& transaction) { auto getters = render::Item::Status::Getters(0); return addToScene(scene, transaction, getters); } bool addToScene(const render::ScenePointer& scene, render::Transaction& transaction, BlendShapeOperator modelBlendshapeOperator) { auto getters = render::Item::Status::Getters(0); return addToScene(scene, transaction, getters, modelBlendshapeOperator); } bool addToScene(const render::ScenePointer& scene, render::Transaction& transaction, render::Item::Status::Getters& statusGetters, BlendShapeOperator modelBlendshapeOperator = nullptr); void removeFromScene(const render::ScenePointer& scene, render::Transaction& transaction); bool isRenderable() const; void updateRenderItemsKey(const render::ScenePointer& scene); virtual void updateRenderItems(); void setRenderItemsNeedUpdate(); bool getRenderItemsNeedUpdate() { return _renderItemsNeedUpdate; } AABox getRenderableMeshBound() const; const render::ItemIDs& fetchRenderItemIDs() const; bool maybeStartBlender(); bool isLoaded() const { return (bool)_renderGeometry && _renderGeometry->isHFMModelLoaded(); } bool isAddedToScene() const { return _addedToScene; } void setPrimitiveMode(PrimitiveMode primitiveMode); PrimitiveMode getPrimitiveMode() const { return _primitiveMode; } void reset(); void setSnapModelToRegistrationPoint(bool snapModelToRegistrationPoint, const glm::vec3& registrationPoint); bool getSnapModelToRegistrationPoint() { return _snapModelToRegistrationPoint; } virtual void simulate(float deltaTime, bool fullUpdate = true); virtual void updateClusterMatrices(); /// Returns a reference to the shared geometry. const NetworkModel::Pointer& getNetworkModel() const { return _renderGeometry; } const QVariantMap getTextures() const { assert(isLoaded()); return _renderGeometry->getTextures(); } Q_INVOKABLE virtual void setTextures(const QVariantMap& textures); /// Provided as a convenience, will crash if !isLoaded() // And so that getHFMModel() isn't chained everywhere const HFMModel& getHFMModel() const { assert(isLoaded()); return _renderGeometry->getHFMModel(); } const MaterialMapping& getMaterialMapping() const { assert(isLoaded()); return _renderGeometry->getMaterialMapping(); } bool isActive() const { return isLoaded(); } bool didVisualGeometryRequestFail() const { return _visualGeometryRequestFailed; } bool didCollisionGeometryRequestFail() const { return _collisionGeometryRequestFailed; } glm::mat4 getWorldToHFMMatrix() const; 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, QVariantMap& extraInfo, bool pickAgainstTriangles = false, bool allowBackface = false); bool findParabolaIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool pickAgainstTriangles = false, bool allowBackface = 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); void setScaleToFit(bool scaleToFit, const glm::vec3& dimensions, 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; /// \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; /// Returns the extents of the model in its bind pose. Extents getBindExtents() const; /// Returns the extents of the model's mesh Extents getMeshExtents() const; /// Returns the unscaled extents of the model's mesh Extents getUnscaledMeshExtents() const; void setTranslation(const glm::vec3& translation); void setRotation(const glm::quat& rotation); void overrideModelTransformAndOffset(const Transform& transform, const glm::vec3& offset); bool isOverridingModelTransformAndOffset() { return _overrideModelTransform; }; void stopTransformAndOffsetOverride() { _overrideModelTransform = false; }; void setTransformNoUpdateRenderItems(const Transform& transform); // temporary HACK const glm::vec3& getTranslation() const { return _translation; } const glm::quat& getRotation() const { return _rotation; } const glm::vec3& getOverrideTranslation() const { return _overrideTranslation; } const glm::quat& getOverrideRotation() const { return _overrideRotation; } glm::vec3 getNaturalDimensions() const; 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 int getBlendshapeCoefficientsNum() const { return _blendshapeCoefficients.size(); } float getBlendshapeCoefficient(int index) const { return ((index < 0) && (index >= _blendshapeCoefficients.size())) ? 0.0f : _blendshapeCoefficients.at(index); } Rig& getRig() { return _rig; } const Rig& getRig() const { return _rig; } const glm::vec3& getRegistrationPoint() const { return _registrationPoint; } // returns 'true' if needs fullUpdate after geometry change virtual bool updateGeometry(); 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; } class TransformDualQuaternion { public: TransformDualQuaternion() {} TransformDualQuaternion(const glm::mat4& m) { AnimPose p(m); _scale.x = p.scale().x; _scale.y = p.scale().y; _scale.z = p.scale().z; _scale.w = 0.0f; _dq = DualQuaternion(p.rot(), p.trans()); } TransformDualQuaternion(const glm::vec3& scale, const glm::quat& rot, const glm::vec3& trans) { _scale.x = scale.x; _scale.y = scale.y; _scale.z = scale.z; _scale.w = 0.0f; _dq = DualQuaternion(rot, trans); } TransformDualQuaternion(const Transform& transform) { _scale = glm::vec4(transform.getScale(), 0.0f); _scale.w = 0.0f; _dq = DualQuaternion(transform.getRotation(), transform.getTranslation()); } glm::vec3 getScale() const { return glm::vec3(_scale); } glm::quat getRotation() const { return _dq.getRotation(); } glm::vec3 getTranslation() const { return _dq.getTranslation(); } glm::mat4 getMatrix() const { return createMatFromScaleQuatAndPos(getScale(), getRotation(), getTranslation()); }; void setCauterizationParameters(float cauterizationAmount, const glm::vec3& cauterizedPosition) { _scale.w = cauterizationAmount; _cauterizedPosition = glm::vec4(cauterizedPosition, 1.0f); } protected: glm::vec4 _scale { 1.0f, 1.0f, 1.0f, 0.0f }; DualQuaternion _dq; glm::vec4 _cauterizedPosition { 0.0f, 0.0f, 0.0f, 1.0f }; }; class MeshState { public: std::vector clusterDualQuaternions; std::vector clusterMatrices; }; const MeshState& getMeshState(int index) { return _meshStates.at(index); } class ShapeState { public: glm::mat4 _rootFromJointTransform; }; const ShapeState& getShapeState(int index) { return _shapeStates.at(index); } uint32_t getGeometryCounter() const { return _deleteGeometryCounter; } const QMap& getRenderItems() const { return _modelMeshRenderItemsMap; } BlendShapeOperator getModelBlendshapeOperator() const { return _modelBlendshapeOperator; } void renderDebugMeshBoxes(gpu::Batch& batch, bool forward); int getResourceDownloadAttempts() { return _renderWatcher.getResourceDownloadAttempts(); } int getResourceDownloadAttemptsRemaining() { return _renderWatcher.getResourceDownloadAttemptsRemaining(); } Q_INVOKABLE MeshProxyList getMeshes() const; virtual scriptable::ScriptableModelBase getScriptableModel() override; virtual bool replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer model, int meshIndex, int partIndex) override; void scaleToFit(); bool getUseDualQuaternionSkinning() const { return _useDualQuaternionSkinning; } void setUseDualQuaternionSkinning(bool value); void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName); void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName); public slots: void loadURLFinished(bool success); signals: void setURLFinished(bool success); void setCollisionModelURLFinished(bool success); void requestRenderUpdate(); void rigReady(); void rigReset(); protected: std::unordered_map _priorityMap; // only used for materialMapping std::unordered_map> _materialMapping; // generated during applyMaterialMapping std::mutex _materialMappingMutex; void applyMaterialMapping(); void setBlendshapeCoefficients(const QVector& coefficients) { _blendshapeCoefficients = coefficients; } const QVector& getBlendshapeCoefficients() const { return _blendshapeCoefficients; } /// Clear the joint states void clearJointState(int index); /// \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; NetworkModel::Pointer _renderGeometry; // only ever set by its watcher ModelResourceWatcher _renderWatcher; SpatiallyNestable* _spatiallyNestableOverride; glm::vec3 _translation; // this is the translation in world coordinates to the model's registration point glm::quat _rotation; glm::vec3 _scale { 1.0f }; glm::vec3 _overrideTranslation; glm::quat _overrideRotation; // For entity models this is the translation for the minimum extent of the model (in original mesh coordinate space) // to the model's registration point. For avatar models this is the translation from the avatar's hips, as determined // by the default pose, to the origin. 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 std::vector _meshStates; std::vector _shapeStates; void updateShapeStatesFromRig(); virtual void initJointStates(); void setScaleInternal(const glm::vec3& scale); void snapToRegistrationPoint(); virtual void updateRig(float deltaTime, glm::mat4 parentTransform); /// Allow sub classes to force invalidating the bboxes void invalidCalculatedMeshBoxes() { _triangleSetsValid = false; } // hook for derived classes to be notified when setUrl invalidates the current model. virtual void onInvalidate() {}; virtual void deleteGeometry(); QUrl _url; BlendShapeOperator _modelBlendshapeOperator { nullptr }; QVector _blendshapeCoefficients; QVector _blendedBlendshapeCoefficients; int _blendNumber { 0 }; mutable QMutex _mutex{ QMutex::Recursive }; bool _overrideModelTransform { false }; bool _triangleSetsValid { false }; void calculateTriangleSets(const HFMModel& hfmModel); std::vector> _modelSpaceMeshTriangleSets; // model space triangles for all sub meshes virtual void createRenderItemSet(); PrimitiveMode _primitiveMode { PrimitiveMode::SOLID }; bool _useDualQuaternionSkinning { false }; // debug rendering support int _debugMeshBoxesID = GeometryCache::UNKNOWN_ID; static AbstractViewStateInterface* _viewState; QVector> _modelMeshRenderItems; QMap _modelMeshRenderItemsMap; render::ItemIDs _modelMeshRenderItemIDs; using ShapeInfo = struct { int meshIndex; uint32_t deformerIndex{ hfm::UNDEFINED_KEY }; }; std::vector _modelMeshRenderItemShapes; std::vector _modelMeshMaterialNames; 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 }; QVariantMap _pendingTextures { }; friend class ModelMeshPartPayload; Rig _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 }; // This Render ItemKey Global Flags capture the Model wide global set of flags that should be communicated to all the render items representing the Model. // The flags concerned are: // - isVisible: if true the Model is visible globally in the scene, regardless of the other flags in the item keys (tags or layer or shadow caster). // - TagBits: the view mask defined through the TagBits telling in which view the Model is rendered if visible. // - Layer: In which Layer this Model lives. // - CastShadow: if true and visible and rendered in the view, the Model cast shadows if in a Light volume casting shadows. // - CullGroup: if true, the render items representing the parts of the Model are culled by a single Meta render item that knows about them, they are not culled individually. // For this to work, a Meta RI must exists and knows about the RIs of this Model. // render::ItemKey _renderItemKeyGlobalFlags; bool _cauterized { false }; bool shouldInvalidatePayloadShapeKey(int meshIndex); private: float _loadingPriority { 0.0f }; void calculateTextureInfo(); std::set getMeshIDsFromMaterialID(QString parentMaterialName); }; Q_DECLARE_METATYPE(ModelPointer) Q_DECLARE_METATYPE(NetworkModel::WeakPointer) Q_DECLARE_METATYPE(BlendshapeOffset) /// 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); bool shouldComputeBlendshapes() { return _computeBlendshapes; } public slots: void setBlendedVertices(ModelPointer model, int blendNumber, QVector blendshapeOffsets, QVector blendedMeshSizes); void setComputeBlendshapes(bool computeBlendshapes) { _computeBlendshapes = computeBlendshapes; } private: using Mutex = std::mutex; using Lock = std::unique_lock; ModelBlender(); virtual ~ModelBlender(); std::queue _modelsRequiringBlendsQueue; std::set> _modelsRequiringBlendsSet; int _pendingBlenders; Mutex _mutex; bool _computeBlendshapes { true }; }; #endif // hifi_Model_h