From 99a03bac21950c7b1861711f1602a08a3e6a7a7e Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 17 Jun 2015 18:36:48 -0700 Subject: [PATCH] Render a different model when in first person view. Currently this model is identical to the third person model, except that the head bones have been 'cauterized' by applying a zero scale transform. This allows us to set the near clip back to a reasonable value. --- interface/src/Application.cpp | 3 ++ interface/src/avatar/Avatar.cpp | 7 +-- interface/src/avatar/Avatar.h | 2 +- interface/src/avatar/MyAvatar.cpp | 62 ++++++++++++++++++----- interface/src/avatar/MyAvatar.h | 10 +++- interface/src/avatar/SkeletonModel.cpp | 65 ++++++++++++++++++++++++- interface/src/avatar/SkeletonModel.h | 14 +++++- libraries/octree/src/ViewFrustum.h | 2 +- libraries/render-utils/src/JointState.h | 3 ++ libraries/render-utils/src/Model.cpp | 52 +++++++++++++++++++- libraries/render-utils/src/Model.h | 8 +++ 11 files changed, 205 insertions(+), 23 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index bde557c430..461633460c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3306,6 +3306,9 @@ namespace render { void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool selfAvatarOnly, bool billboard) { + + _myAvatar->preRender(renderArgs); + activeRenderingThread = QThread::currentThread(); PROFILE_RANGE(__FUNCTION__); PerformanceTimer perfTimer("display"); diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 0213afb675..d0778481a6 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -445,10 +445,10 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition, boo _skeletonModel.renderJointCollisionShapes(0.7f); } - if (renderHead && shouldRenderHead(renderArgs, cameraPosition)) { + if (renderHead && shouldRenderHead(renderArgs)) { getHead()->getFaceModel().renderJointCollisionShapes(0.7f); } - if (renderBounding && shouldRenderHead(renderArgs, cameraPosition)) { + if (renderBounding && shouldRenderHead(renderArgs)) { _skeletonModel.renderBoundingCollisionShapes(0.7f); } @@ -533,6 +533,7 @@ glm::quat Avatar::computeRotationFromBodyToWorldUp(float proportion) const { } void Avatar::fixupModelsInScene() { + // check to see if when we added our models to the scene they were ready, if they were not ready, then // fix them up in the scene render::ScenePointer scene = Application::getInstance()->getMain3DScene(); @@ -581,7 +582,7 @@ void Avatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, bool getHead()->render(renderArgs, 1.0f, renderFrustum, postLighting); } -bool Avatar::shouldRenderHead(const RenderArgs* renderArgs, const glm::vec3& cameraPosition) const { +bool Avatar::shouldRenderHead(const RenderArgs* renderArgs) const { return true; } diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index ecb16f6010..236b04864b 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -237,7 +237,7 @@ protected: Transform calculateDisplayNameTransform(const ViewFrustum& frustum, float fontSize) const; void renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum) const; virtual void renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, bool postLighting, float glowLevel = 0.0f); - virtual bool shouldRenderHead(const RenderArgs* renderArgs, const glm::vec3& cameraPosition) const; + virtual bool shouldRenderHead(const RenderArgs* renderArgs) const; virtual void fixupModelsInScene(); void simulateAttachments(float deltaTime); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 6904a1f975..61bff58d89 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -96,8 +97,12 @@ MyAvatar::MyAvatar() : _feetTouchFloor(true), _isLookingAtLeftEye(true), _realWorldFieldOfView("realWorldFieldOfView", - DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES) + DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES), + _currentSkeletonModel(nullptr), + _firstPersonSkeletonModel(this) { + _firstPersonSkeletonModel.setIsFirstPerson(true); + ShapeCollider::initDispatchTable(); for (int i = 0; i < MAX_DRIVE_KEYS; i++) { _driveKeys[i] = 0.0f; @@ -131,6 +136,7 @@ QByteArray MyAvatar::toByteArray() { void MyAvatar::reset() { _skeletonModel.reset(); + _firstPersonSkeletonModel.reset(); getHead()->reset(); _targetVelocity = glm::vec3(0.0f); @@ -189,6 +195,7 @@ void MyAvatar::simulate(float deltaTime) { { PerformanceTimer perfTimer("skeleton"); _skeletonModel.simulate(deltaTime); + _firstPersonSkeletonModel.simulate(deltaTime); } if (!_skeletonModel.hasSkeleton()) { @@ -993,6 +1000,11 @@ void MyAvatar::setFaceModelURL(const QUrl& faceModelURL) { void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { Avatar::setSkeletonModelURL(skeletonModelURL); _billboardValid = false; + + if (_useFullAvatar) { + const QUrl DEFAULT_SKELETON_MODEL_URL = QUrl::fromLocalFile(PathUtils::resourcesPath() + "meshes/defaultAvatar_body.fst"); + _firstPersonSkeletonModel.setURL(_skeletonModelURL, DEFAULT_SKELETON_MODEL_URL, true, !isMyAvatar()); + } } void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelName) { @@ -1176,18 +1188,12 @@ void MyAvatar::attach(const QString& modelURL, const QString& jointName, const g void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, bool postLighting, float glowLevel) { - if (!(_skeletonModel.isRenderable() && getHead()->getFaceModel().isRenderable())) { - return; // wait until both models are loaded + if (!(_skeletonModel.isRenderable() && _firstPersonSkeletonModel.isRenderable() && getHead()->getFaceModel().isRenderable())) { + return; // wait until all models are loaded } - // check to see if when we added our models to the scene they were ready, if they were not ready, then - // fix them up in the scene - fixupModelsInScene(); - - const glm::vec3 cameraPos = Application::getInstance()->getCamera()->getPosition(); - // Render head so long as the camera isn't inside it - if (shouldRenderHead(renderArgs, cameraPos)) { + if (shouldRenderHead(renderArgs)) { getHead()->render(renderArgs, 1.0f, renderFrustum, postLighting); } if (postLighting) { @@ -1195,12 +1201,42 @@ void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, bo } } +void MyAvatar::setCurrentSkeletonModel(SkeletonModel* skeletonModel) { + if (_currentSkeletonModel != skeletonModel && skeletonModel->isActive() && skeletonModel->isRenderable()) { + + render::ScenePointer scene = Application::getInstance()->getMain3DScene(); + + if (_currentSkeletonModel) { + _currentSkeletonModel->setVisibleInScene(false, scene); + } + skeletonModel->setVisibleInScene(true, scene); + _currentSkeletonModel = skeletonModel; + } +} + +void MyAvatar::preRender(RenderArgs* renderArgs) { + + render::ScenePointer scene = Application::getInstance()->getMain3DScene(); + + _skeletonModel.initWhenReady(scene); + _firstPersonSkeletonModel.initWhenReady(scene); + + // set visiblity on each model + if (shouldRenderHead(renderArgs)) { + setCurrentSkeletonModel(&_skeletonModel); + } else { + setCurrentSkeletonModel(&_firstPersonSkeletonModel); + } +} + const float RENDER_HEAD_CUTOFF_DISTANCE = 0.50f; -bool MyAvatar::shouldRenderHead(const RenderArgs* renderArgs, const glm::vec3& cameraPosition) const { +bool MyAvatar::shouldRenderHead(const RenderArgs* renderArgs) const { + const glm::vec3 cameraPos = Application::getInstance()->getCamera()->getPosition(); const Head* head = getHead(); - return (renderArgs->_renderMode != RenderArgs::NORMAL_RENDER_MODE) || (Application::getInstance()->getCamera()->getMode() != CAMERA_MODE_FIRST_PERSON) || - (glm::length(cameraPosition - head->getEyePosition()) > RENDER_HEAD_CUTOFF_DISTANCE * _scale); + return ((renderArgs->_renderMode != RenderArgs::DEFAULT_RENDER_MODE) || + (Application::getInstance()->getCamera()->getMode() != CAMERA_MODE_FIRST_PERSON) || + (glm::length(cameraPos - head->getEyePosition()) > RENDER_HEAD_CUTOFF_DISTANCE * _scale)); } void MyAvatar::updateOrientation(float deltaTime) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 948cec9eb1..9dda29a918 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -35,11 +35,12 @@ public: void reset(); void update(float deltaTime); void simulate(float deltaTime); + void preRender(RenderArgs* renderArgs); void updateFromTrackers(float deltaTime); virtual void render(RenderArgs* renderArgs, const glm::vec3& cameraPosition, bool postLighting = false) override; virtual void renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, bool postLighting, float glowLevel = 0.0f) override; - virtual bool shouldRenderHead(const RenderArgs* renderArgs, const glm::vec3& cameraPosition) const override; + virtual bool shouldRenderHead(const RenderArgs* renderArgs) const override; void renderDebugBodyPoints(); // setters @@ -210,6 +211,9 @@ private: virtual void setFaceModelURL(const QUrl& faceModelURL); virtual void setSkeletonModelURL(const QUrl& skeletonModelURL); + void setCurrentSkeletonModel(SkeletonModel* skeletonModel); + void initModelWhenReady(Model* model); + glm::vec3 _gravity; float _driveKeys[MAX_DRIVE_KEYS]; @@ -265,6 +269,10 @@ private: QString _headModelName; QString _bodyModelName; QString _fullAvatarModelName; + + // used for rendering when in first person view or when in an HMD. + SkeletonModel* _currentSkeletonModel; + SkeletonModel _firstPersonSkeletonModel; }; #endif // hifi_MyAvatar_h diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 2aa08d36f3..2f66e84b50 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -21,6 +21,7 @@ #include "Menu.h" #include "SkeletonModel.h" #include "Util.h" +#include "InterfaceLogging.h" enum StandingFootState { LEFT_FOOT, @@ -38,7 +39,8 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent) : _standingFoot(NO_FOOT), _standingOffset(0.0f), _clampedFootPosition(0.0f), - _headClipDistance(DEFAULT_NEAR_CLIP) + _headClipDistance(DEFAULT_NEAR_CLIP), + _isFirstPerson(false) { assert(_owningAvatar); _enableShapes = true; @@ -98,7 +100,7 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { setRotation(_owningAvatar->getOrientation() * refOrientation); setScale(glm::vec3(1.0f, 1.0f, 1.0f) * _owningAvatar->getScale()); setBlendshapeCoefficients(_owningAvatar->getHead()->getBlendshapeCoefficients()); - + Model::simulate(deltaTime, fullUpdate); if (!isActive() || !_owningAvatar->isMyAvatar()) { @@ -140,6 +142,11 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { applyPalmData(geometry.rightHandJointIndex, hand->getPalms()[rightPalmIndex]); } + if (_isFirstPerson) { + cauterizeHead(); + updateClusterMatrices(); + } + _boundingShape.setTranslation(_translation + _rotation * _boundingShapeLocalOffset); _boundingShape.setRotation(_rotation); } @@ -806,3 +813,57 @@ void SkeletonModel::renderBoundingCollisionShapes(float alpha) { bool SkeletonModel::hasSkeleton() { return isActive() ? _geometry->getFBXGeometry().rootJointIndex != -1 : false; } + +void SkeletonModel::initHeadBones() { + _headBones.clear(); + const FBXGeometry& fbxGeometry = _geometry->getFBXGeometry(); + const int neckJointIndex = fbxGeometry.neckJointIndex; + std::queue q; + q.push(neckJointIndex); + _headBones.push_back(neckJointIndex); + + // fbxJoints only hold links to parents not children, so we have to do a bit of extra work here. + while (q.size() > 0) { + int jointIndex = q.front(); + for (int i = 0; i < fbxGeometry.joints.size(); i++) { + const FBXJoint& fbxJoint = fbxGeometry.joints[i]; + if (jointIndex == fbxJoint.parentIndex) { + _headBones.push_back(i); + q.push(i); + } + } + q.pop(); + } +} + +void SkeletonModel::invalidateHeadBones() { + _headBones.clear(); +} + +void SkeletonModel::cauterizeHead() { + if (isActive()) { + const FBXGeometry& geometry = _geometry->getFBXGeometry(); + const int neckJointIndex = geometry.neckJointIndex; + if (neckJointIndex > 0 && neckJointIndex < _jointStates.size()) { + + // lazy init of headBones + if (_headBones.size() == 0) { + initHeadBones(); + } + + // preserve the translation for the neck + glm::vec4 trans = _jointStates[neckJointIndex].getTransform()[3]; + glm::vec4 zero(0, 0, 0, 0); + for (const int &i : _headBones) { + JointState& joint = _jointStates[i]; + glm::mat4 newXform(zero, zero, zero, trans); + joint.setTransform(newXform); + joint.setVisibleTransform(newXform); + } + } + } +} + +void SkeletonModel::onInvalidate() { + invalidateHeadBones(); +} diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h index e28988326a..bffdc58659 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -112,6 +112,11 @@ public: float getHeadClipDistance() const { return _headClipDistance; } + void setIsFirstPerson(bool value) { _isFirstPerson = value; } + bool getIsFirstPerson() const { return _isFirstPerson; } + + virtual void onInvalidate() override; + signals: void skeletonLoaded(); @@ -132,7 +137,11 @@ protected: void maybeUpdateLeanRotation(const JointState& parentState, JointState& state); void maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state); void maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state); - + + void cauterizeHead(); + void initHeadBones(); + void invalidateHeadBones(); + private: void renderJointConstraints(int jointIndex); @@ -164,6 +173,9 @@ private: glm::vec3 _clampedFootPosition; float _headClipDistance; // Near clip distance to use if no separate head model + + bool _isFirstPerson; + std::vector _headBones; }; #endif // hifi_SkeletonModel_h diff --git a/libraries/octree/src/ViewFrustum.h b/libraries/octree/src/ViewFrustum.h index cd1e010818..6c279585e2 100644 --- a/libraries/octree/src/ViewFrustum.h +++ b/libraries/octree/src/ViewFrustum.h @@ -31,7 +31,7 @@ const float DEFAULT_KEYHOLE_RADIUS = 3.0f; const float DEFAULT_FIELD_OF_VIEW_DEGREES = 45.0f; const float DEFAULT_ASPECT_RATIO = 16.0f/9.0f; //const float DEFAULT_NEAR_CLIP = 0.08f; -const float DEFAULT_NEAR_CLIP = 0.25f; +const float DEFAULT_NEAR_CLIP = 0.1f; const float DEFAULT_FAR_CLIP = (float)TREE_SCALE; class ViewFrustum { diff --git a/libraries/render-utils/src/JointState.h b/libraries/render-utils/src/JointState.h index fbe2e9c986..0ef84e50c4 100644 --- a/libraries/render-utils/src/JointState.h +++ b/libraries/render-utils/src/JointState.h @@ -106,6 +106,9 @@ public: glm::quat computeParentRotation() const; glm::quat computeVisibleParentRotation() const; + void setTransform(const glm::mat4& transform) { _transform = transform; } + void setVisibleTransform(const glm::mat4& transform) { _visibleTransform = transform; } + private: void setRotationInConstrainedFrameInternal(const glm::quat& targetRotation); /// debug helper function diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 1397512f4a..d6fe5cea1a 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1102,9 +1102,11 @@ void Model::setURL(const QUrl& url, const QUrl& fallback, bool retainCurrent, bo _readyWhenAdded = false; // reset out render items. _needsReload = true; invalidCalculatedMeshBoxes(); - + _url = url; + onInvalidate(); + // if so instructed, keep the current geometry until the new one is loaded _nextBaseGeometry = _nextGeometry = DependencyManager::get()->getGeometry(url, fallback, delayLoad); _nextLODHysteresis = NetworkGeometry::NO_HYSTERESIS; @@ -1380,6 +1382,26 @@ void Model::simulate(float deltaTime, bool fullUpdate) { } } +void Model::updateClusterMatrices() { + const FBXGeometry& geometry = _geometry->getFBXGeometry(); + glm::mat4 modelToWorld = glm::mat4_cast(_rotation); + for (int i = 0; i < _meshStates.size(); i++) { + MeshState& state = _meshStates[i]; + const FBXMesh& mesh = geometry.meshes.at(i); + if (_showTrueJointTransforms) { + for (int j = 0; j < mesh.clusters.size(); j++) { + const FBXCluster& cluster = mesh.clusters.at(j); + state.clusterMatrices[j] = modelToWorld * _jointStates[cluster.jointIndex].getTransform() * cluster.inverseBindMatrix; + } + } else { + for (int j = 0; j < mesh.clusters.size(); j++) { + const FBXCluster& cluster = mesh.clusters.at(j); + state.clusterMatrices[j] = modelToWorld * _jointStates[cluster.jointIndex].getVisibleTransform() * cluster.inverseBindMatrix; + } + } + } +} + void Model::simulateInternal(float deltaTime) { // update the world space transforms for all joints @@ -2120,6 +2142,34 @@ void Model::pickPrograms(gpu::Batch& batch, RenderMode mode, bool translucent, f } } +bool Model::initWhenReady(render::ScenePointer scene) { + if (isActive() && isRenderable() && !_meshGroupsKnown && isLoadedWithTextures()) { + segregateMeshGroups(); + + render::PendingChanges pendingChanges; + + foreach (auto renderItem, _transparentRenderItems) { + auto item = scene->allocateID(); + auto renderData = MeshPartPayload::Pointer(renderItem); + auto renderPayload = render::PayloadPointer(new MeshPartPayload::Payload(renderData)); + _renderItems.insert(item, renderPayload); + pendingChanges.resetItem(item, renderPayload); + } + + foreach (auto renderItem, _opaqueRenderItems) { + auto item = scene->allocateID(); + auto renderData = MeshPartPayload::Pointer(renderItem); + auto renderPayload = render::PayloadPointer(new MeshPartPayload::Payload(renderData)); + _renderItems.insert(item, renderPayload); + pendingChanges.resetItem(item, renderPayload); + } + scene->enqueuePendingChanges(pendingChanges); + + _readyWhenAdded = true; + return true; + } + return false; +} ModelBlender::ModelBlender() : _pendingBlenders(0) { diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 6dfe223581..3748403b97 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -240,6 +240,8 @@ public: AABox getPartBounds(int meshIndex, int partIndex); void renderPart(RenderArgs* args, int meshIndex, int partIndex, bool translucent); + bool initWhenReady(render::ScenePointer scene); + protected: QSharedPointer _geometry; @@ -312,6 +314,12 @@ protected: _calculatedMeshTrianglesValid = false; } + // rebuild the clusterMatrices from the current jointStates + void updateClusterMatrices(); + + // hook for derived classes to be notified when setUrl invalidates the current model. + virtual void onInvalidate() {}; + private: friend class AnimationHandle;