overte/libraries/render-utils/src/Model.h
Anthony J. Thibault 26a5b5ed91 Fix for rendering of tablet ModelEntity in desktop mode.
The Model pendingChanges.updateItem() lambda now directly calls into StaticallyNestable.  This is late enough
in the update loop to have the most up-to-date camera transform used for rendering.
2017-01-11 18:37:57 -08:00

464 lines
17 KiB
C++

//
// 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 <QBitArray>
#include <QObject>
#include <QUrl>
#include <QMutex>
#include <unordered_map>
#include <unordered_set>
#include <functional>
#include <AABox.h>
#include <DependencyManager.h>
#include <GeometryUtil.h>
#include <gpu/Batch.h>
#include <render/Scene.h>
#include <Transform.h>
#include <SpatiallyNestable.h>
#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<MeshPartPayload>& a, uint seed) {
return qHash(a.get(), seed);
}
class Model;
using ModelPointer = std::shared_ptr<Model>;
using ModelWeakPointer = std::weak_ptr<Model>;
/// A generic 3D model displaying geometry loaded from a URL.
class Model : public QObject, public std::enable_shared_from_this<Model> {
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<Model>(std::const_pointer_cast<Model>(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<render::Scene> scene);
void setLayeredInFront(bool layered, std::shared_ptr<render::Scene> scene);
bool needsFixupInScene() const;
bool needsReload() const { return _needsReload; }
bool initWhenReady(render::ScenePointer scene);
bool addToScene(std::shared_ptr<render::Scene> scene,
render::PendingChanges& pendingChanges) {
auto getters = render::Item::Status::Getters(0);
return addToScene(scene, pendingChanges, getters);
}
bool addToScene(std::shared_ptr<render::Scene> scene,
render::PendingChanges& pendingChanges,
render::Item::Status::Getters& statusGetters);
void removeFromScene(std::shared_ptr<render::Scene> 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<glm::vec3>& vertices, const QVector<glm::vec3>& 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<int>& getCauterizeBoneSet() const { return _cauterizeBoneSet; }
void setCauterizeBoneSet(const std::unordered_set<int>& 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<float>& coefficients) { _blendshapeCoefficients = coefficients; }
const QVector<float>& 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<glm::mat4> clusterMatrices;
QVector<glm::mat4> cauterizedClusterMatrices;
gpu::BufferPointer clusterBuffer;
gpu::BufferPointer cauterizedClusterBuffer;
};
QVector<MeshState> _meshStates;
std::unordered_set<int> _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<float> _blendshapeCoefficients;
QUrl _url;
bool _isVisible;
gpu::Buffers _blendedVertexBuffers;
QVector<QVector<QSharedPointer<Texture> > > _dilatedTextures;
QVector<float> _blendedBlendshapeCoefficients;
int _blendNumber;
int _appliedBlendNumber;
QHash<QPair<int,int>, AABox> _calculatedMeshPartBoxes; // world coordinate AABoxes for all sub mesh part boxes
bool _calculatedMeshPartBoxesValid;
QVector<AABox> _calculatedMeshBoxes; // world coordinate AABoxes for all sub mesh boxes
bool _calculatedMeshBoxesValid;
QVector< QVector<Triangle> > _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<std::shared_ptr<MeshPartPayload>> _collisionRenderItemsSet;
QMap<render::ItemID, render::PayloadPointer> _collisionRenderItems;
QSet<std::shared_ptr<ModelMeshPartPayload>> _modelMeshRenderItemsSet;
QMap<render::ItemID, render::PayloadPointer> _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<glm::vec3>& vertices, const QVector<glm::vec3>& normals);
private:
using Mutex = std::mutex;
using Lock = std::unique_lock<Mutex>;
ModelBlender();
virtual ~ModelBlender();
std::set<ModelWeakPointer, std::owner_less<ModelWeakPointer>> _modelsRequiringBlends;
int _pendingBlenders;
Mutex _mutex;
};
#endif // hifi_Model_h