Merge pull request #5175 from hyperlogic/ajt/first-person-avatar

Render a different model when in first person view.
This commit is contained in:
Brad Hefta-Gaub 2015-06-24 18:54:47 -07:00
commit 77bbb23a1e
11 changed files with 231 additions and 21 deletions

View file

@ -3306,6 +3306,11 @@ namespace render {
void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool selfAvatarOnly, bool billboard) {
// FIXME: This preRender call is temporary until we create a separate render::scene for the mirror rendering.
// Then we can move this logic into the Avatar::simulate call.
_myAvatar->preRender(renderArgs);
activeRenderingThread = QThread::currentThread();
PROFILE_RANGE(__FUNCTION__);
PerformanceTimer perfTimer("display");

View file

@ -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;
}

View file

@ -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);

View file

@ -27,6 +27,7 @@
#include <GeometryUtil.h>
#include <NodeList.h>
#include <PacketHeaders.h>
#include <PathUtils.h>
#include <PerfStat.h>
#include <ShapeCollider.h>
#include <SharedUtil.h>
@ -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),
_firstPersonSkeletonModel(this),
_prevShouldDrawHead(true)
{
_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()) {
@ -986,16 +993,35 @@ QString MyAvatar::getModelDescription() const {
}
void MyAvatar::setFaceModelURL(const QUrl& faceModelURL) {
Avatar::setFaceModelURL(faceModelURL);
render::ScenePointer scene = Application::getInstance()->getMain3DScene();
getHead()->getFaceModel().setVisibleInScene(_prevShouldDrawHead, scene);
_billboardValid = false;
}
void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
Avatar::setSkeletonModelURL(skeletonModelURL);
render::ScenePointer scene = Application::getInstance()->getMain3DScene();
_billboardValid = false;
if (_useFullAvatar) {
_skeletonModel.setVisibleInScene(_prevShouldDrawHead, scene);
const QUrl DEFAULT_SKELETON_MODEL_URL = QUrl::fromLocalFile(PathUtils::resourcesPath() + "meshes/defaultAvatar_body.fst");
_firstPersonSkeletonModel.setURL(_skeletonModelURL, DEFAULT_SKELETON_MODEL_URL, true, !isMyAvatar());
_firstPersonSkeletonModel.setVisibleInScene(!_prevShouldDrawHead, scene);
} else {
_skeletonModel.setVisibleInScene(true, scene);
_firstPersonSkeletonModel.setVisibleInScene(false, scene);
_firstPersonSkeletonModel.reset();
}
}
void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelName) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "useFullAvatarURL", Qt::BlockingQueuedConnection,
Q_ARG(const QUrl&, fullAvatarURL),
@ -1177,17 +1203,13 @@ 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
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 +1217,51 @@ void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, bo
}
}
void MyAvatar::setVisibleInSceneIfReady(Model* model, render::ScenePointer scene, bool visible) {
if (model->isActive() && model->isRenderable()) {
model->setVisibleInScene(visible, scene);
}
}
void MyAvatar::preRender(RenderArgs* renderArgs) {
render::ScenePointer scene = Application::getInstance()->getMain3DScene();
const bool shouldDrawHead = shouldRenderHead(renderArgs);
_skeletonModel.initWhenReady(scene);
if (_useFullAvatar) {
_firstPersonSkeletonModel.initWhenReady(scene);
}
if (shouldDrawHead != _prevShouldDrawHead) {
if (_useFullAvatar) {
if (shouldDrawHead) {
_skeletonModel.setVisibleInScene(true, scene);
_firstPersonSkeletonModel.setVisibleInScene(false, scene);
} else {
_skeletonModel.setVisibleInScene(false, scene);
_firstPersonSkeletonModel.setVisibleInScene(true, scene);
}
} else {
getHead()->getFaceModel().setVisibleInScene(shouldDrawHead, scene);
}
}
_prevShouldDrawHead = shouldDrawHead;
}
const float RENDER_HEAD_CUTOFF_DISTANCE = 0.50f;
bool MyAvatar::shouldRenderHead(const RenderArgs* renderArgs, const glm::vec3& cameraPosition) const {
bool MyAvatar::cameraInsideHead() const {
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);
const glm::vec3 cameraPosition = Application::getInstance()->getCamera()->getPosition();
return glm::length(cameraPosition - head->getEyePosition()) < (RENDER_HEAD_CUTOFF_DISTANCE * _scale);
}
bool MyAvatar::shouldRenderHead(const RenderArgs* renderArgs) const {
return ((renderArgs->_renderMode != RenderArgs::DEFAULT_RENDER_MODE) ||
(Application::getInstance()->getCamera()->getMode() != CAMERA_MODE_FIRST_PERSON) ||
!cameraInsideHead());
}
void MyAvatar::updateOrientation(float deltaTime) {

View file

@ -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
@ -206,10 +207,14 @@ signals:
private:
bool cameraInsideHead() const;
// These are made private for MyAvatar so that you will use the "use" methods instead
virtual void setFaceModelURL(const QUrl& faceModelURL);
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
void setVisibleInSceneIfReady(Model* model, render::ScenePointer scene, bool visiblity);
glm::vec3 _gravity;
float _driveKeys[MAX_DRIVE_KEYS];
@ -265,6 +270,10 @@ private:
QString _headModelName;
QString _bodyModelName;
QString _fullAvatarModelName;
// used for rendering when in first person view or when in an HMD.
SkeletonModel _firstPersonSkeletonModel;
bool _prevShouldDrawHead;
};
#endif // hifi_MyAvatar_h

View file

@ -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<int> 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();
}

View file

@ -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<int> _headBones;
};
#endif // hifi_SkeletonModel_h

View file

@ -30,8 +30,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.08f;
const float DEFAULT_FAR_CLIP = (float)TREE_SCALE;
class ViewFrustum {

View file

@ -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

View file

@ -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<GeometryCache>()->getGeometry(url, fallback, delayLoad);
_nextLODHysteresis = NetworkGeometry::NO_HYSTERESIS;
@ -1368,6 +1370,7 @@ void Model::simulate(float deltaTime, bool fullUpdate) {
// because ray picking needs valid boxes to work
_calculatedMeshBoxesValid = false;
_calculatedMeshTrianglesValid = false;
onInvalidate();
// check for scale to fit
if (_scaleToFit && !_scaledToFit) {
@ -1380,6 +1383,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 +2143,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) {

View file

@ -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<NetworkGeometry> _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;