From 6b90a3994d439d2d0e61e253f58685949bcb8c6f Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 20 Feb 2014 19:22:59 -0800 Subject: [PATCH] Working on avatar billboards. --- interface/src/Application.cpp | 148 +++++++++++++----------- interface/src/Application.h | 7 +- interface/src/avatar/MyAvatar.cpp | 21 +++- interface/src/avatar/MyAvatar.h | 5 + interface/src/renderer/Model.cpp | 15 +++ interface/src/renderer/Model.h | 2 + interface/src/renderer/TextureCache.cpp | 7 +- interface/src/renderer/TextureCache.h | 3 + 8 files changed, 137 insertions(+), 71 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index dfd98863a7..30a6fb4e74 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -527,73 +527,8 @@ void Application::paintGL() { _glowEffect.render(); if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) { - - bool eyeRelativeCamera = false; - if (_rearMirrorTools->getZoomLevel() == BODY) { - _mirrorCamera.setDistance(MIRROR_REARVIEW_BODY_DISTANCE * _myAvatar->getScale()); - _mirrorCamera.setTargetPosition(_myAvatar->getChestPosition()); - } else { // HEAD zoom level - _mirrorCamera.setDistance(MIRROR_REARVIEW_DISTANCE * _myAvatar->getScale()); - if (_myAvatar->getSkeletonModel().isActive() && _myAvatar->getHead()->getFaceModel().isActive()) { - // as a hack until we have a better way of dealing with coordinate precision issues, reposition the - // face/body so that the average eye position lies at the origin - eyeRelativeCamera = true; - _mirrorCamera.setTargetPosition(glm::vec3()); - - } else { - _mirrorCamera.setTargetPosition(_myAvatar->getHead()->calculateAverageEyePosition()); - } - } - - _mirrorCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PIf, 0.0f))); - _mirrorCamera.update(1.0f/_fps); - - // set the bounds of rear mirror view - glViewport(_mirrorViewRect.x(), _glWidget->height() - _mirrorViewRect.y() - _mirrorViewRect.height(), - _mirrorViewRect.width(), _mirrorViewRect.height()); - glScissor(_mirrorViewRect.x(), _glWidget->height() - _mirrorViewRect.y() - _mirrorViewRect.height(), - _mirrorViewRect.width(), _mirrorViewRect.height()); - bool updateViewFrustum = false; - updateProjectionMatrix(_mirrorCamera, updateViewFrustum); - glEnable(GL_SCISSOR_TEST); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - // render rear mirror view - glPushMatrix(); - if (eyeRelativeCamera) { - // save absolute translations - glm::vec3 absoluteSkeletonTranslation = _myAvatar->getSkeletonModel().getTranslation(); - glm::vec3 absoluteFaceTranslation = _myAvatar->getHead()->getFaceModel().getTranslation(); - - // get the eye positions relative to the neck and use them to set the face translation - glm::vec3 leftEyePosition, rightEyePosition; - _myAvatar->getHead()->getFaceModel().setTranslation(glm::vec3()); - _myAvatar->getHead()->getFaceModel().getEyePositions(leftEyePosition, rightEyePosition); - _myAvatar->getHead()->getFaceModel().setTranslation((leftEyePosition + rightEyePosition) * -0.5f); - - // get the neck position relative to the body and use it to set the skeleton translation - glm::vec3 neckPosition; - _myAvatar->getSkeletonModel().setTranslation(glm::vec3()); - _myAvatar->getSkeletonModel().getNeckPosition(neckPosition); - _myAvatar->getSkeletonModel().setTranslation(_myAvatar->getHead()->getFaceModel().getTranslation() - - neckPosition); - - displaySide(_mirrorCamera, true); - - // restore absolute translations - _myAvatar->getSkeletonModel().setTranslation(absoluteSkeletonTranslation); - _myAvatar->getHead()->getFaceModel().setTranslation(absoluteFaceTranslation); - } else { - displaySide(_mirrorCamera, true); - } - glPopMatrix(); - - _rearMirrorTools->render(false); - - // reset Viewport and projection matrix - glViewport(0, 0, _glWidget->width(), _glWidget->height()); - glDisable(GL_SCISSOR_TEST); - updateProjectionMatrix(_myCamera, updateViewFrustum); + renderRearViewMirror(); + } else if (Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)) { _rearMirrorTools->render(true); } @@ -2743,6 +2678,14 @@ void Application::setupWorldLight() { glMateriali(GL_FRONT, GL_SHININESS, 96); } +QImage Application::renderAvatarBillboard() { + renderRearViewMirror(true); + + QImage image(_glWidget->width(), _glWidget->height(), QImage::Format_ARGB32); + glReadPixels(0, 0, _glWidget->width(), _glWidget->height(), GL_BGRA, GL_UNSIGNED_BYTE, image.bits()); + return image; +} + void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide()"); // transform by eye offset @@ -3660,6 +3603,77 @@ void Application::renderCoverageMapsRecursively(CoverageMap* map) { } } +void Application::renderRearViewMirror(bool billboard) { + bool eyeRelativeCamera = false; + if (_rearMirrorTools->getZoomLevel() == BODY && !billboard) { + _mirrorCamera.setDistance(MIRROR_REARVIEW_BODY_DISTANCE * _myAvatar->getScale()); + _mirrorCamera.setTargetPosition(_myAvatar->getChestPosition()); + } else { // HEAD zoom level + _mirrorCamera.setDistance(MIRROR_REARVIEW_DISTANCE * _myAvatar->getScale()); + if (_myAvatar->getSkeletonModel().isActive() && _myAvatar->getHead()->getFaceModel().isActive()) { + // as a hack until we have a better way of dealing with coordinate precision issues, reposition the + // face/body so that the average eye position lies at the origin + eyeRelativeCamera = true; + _mirrorCamera.setTargetPosition(glm::vec3()); + + } else { + _mirrorCamera.setTargetPosition(_myAvatar->getHead()->calculateAverageEyePosition()); + } + } + + _mirrorCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PIf, 0.0f))); + _mirrorCamera.update(1.0f/_fps); + + // set the bounds of rear mirror view + glViewport(_mirrorViewRect.x(), _glWidget->height() - _mirrorViewRect.y() - _mirrorViewRect.height(), + _mirrorViewRect.width(), _mirrorViewRect.height()); + glScissor(_mirrorViewRect.x(), _glWidget->height() - _mirrorViewRect.y() - _mirrorViewRect.height(), + _mirrorViewRect.width(), _mirrorViewRect.height()); + bool updateViewFrustum = false; + updateProjectionMatrix(_mirrorCamera, updateViewFrustum); + glEnable(GL_SCISSOR_TEST); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // render rear mirror view + glPushMatrix(); + if (eyeRelativeCamera) { + // save absolute translations + glm::vec3 absoluteSkeletonTranslation = _myAvatar->getSkeletonModel().getTranslation(); + glm::vec3 absoluteFaceTranslation = _myAvatar->getHead()->getFaceModel().getTranslation(); + + // get the eye positions relative to the neck and use them to set the face translation + glm::vec3 leftEyePosition, rightEyePosition; + _myAvatar->getHead()->getFaceModel().setTranslation(glm::vec3()); + _myAvatar->getHead()->getFaceModel().getEyePositions(leftEyePosition, rightEyePosition); + _myAvatar->getHead()->getFaceModel().setTranslation((leftEyePosition + rightEyePosition) * -0.5f); + + // get the neck position relative to the body and use it to set the skeleton translation + glm::vec3 neckPosition; + _myAvatar->getSkeletonModel().setTranslation(glm::vec3()); + _myAvatar->getSkeletonModel().getNeckPosition(neckPosition); + _myAvatar->getSkeletonModel().setTranslation(_myAvatar->getHead()->getFaceModel().getTranslation() - + neckPosition); + + displaySide(_mirrorCamera, true); + + // restore absolute translations + _myAvatar->getSkeletonModel().setTranslation(absoluteSkeletonTranslation); + _myAvatar->getHead()->getFaceModel().setTranslation(absoluteFaceTranslation); + } else { + displaySide(_mirrorCamera, true); + } + glPopMatrix(); + + if (!billboard) { + _rearMirrorTools->render(false); + } + + // reset Viewport and projection matrix + glViewport(0, 0, _glWidget->width(), _glWidget->height()); + glDisable(GL_SCISSOR_TEST); + updateProjectionMatrix(_myCamera, updateViewFrustum); +} + // renderViewFrustum() // // Description: this will render the view frustum bounds for EITHER the head diff --git a/interface/src/Application.h b/interface/src/Application.h index abe51b8bb1..b35e4b4e08 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -185,6 +186,8 @@ public: void setupWorldLight(); + QImage renderAvatarBillboard(); + void displaySide(Camera& whichCamera, bool selfAvatarOnly = false); /// Loads a view matrix that incorporates the specified model translation without the precision issues that can @@ -200,6 +203,8 @@ public: void computeOffAxisFrustum(float& left, float& right, float& bottom, float& top, float& nearVal, float& farVal, glm::vec4& nearClipPlane, glm::vec4& farClipPlane) const; + + VoxelShader& getVoxelShader() { return _voxelShader; } PointShader& getPointShader() { return _pointShader; } FileLogger* getLogger() { return _logger; } @@ -328,7 +333,7 @@ private: void displayStats(); void checkStatsClick(); void toggleStatsExpanded(); - void renderAvatars(bool forceRenderHead, bool selfAvatarOnly = false); + void renderRearViewMirror(bool billboard = false); void renderViewFrustum(ViewFrustum& viewFrustum); void checkBandwidthMeterClick(); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 016159f415..7358a34fda 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -57,7 +57,8 @@ MyAvatar::MyAvatar() : _thrustMultiplier(1.0f), _moveTarget(0,0,0), _moveTargetStepCounter(0), - _lookAtTargetAvatar() + _lookAtTargetAvatar(), + _billboardValid(false) { for (int i = 0; i < MAX_DRIVE_KEYS; i++) { _driveKeys[i] = 0.0f; @@ -332,7 +333,13 @@ void MyAvatar::simulate(float deltaTime) { // Zero thrust out now that we've added it to velocity in this frame _thrust = glm::vec3(0, 0, 0); - + + // consider updating our billboard + if (!_billboardValid && _skeletonModel.isLoadedWithTextures() && getHead()->getFaceModel().isLoadedWithTextures()) { + QImage image = Application::getInstance()->renderAvatarBillboard(); + image.save("test.png"); + _billboardValid = true; + } } const float MAX_PITCH = 90.0f; @@ -712,6 +719,16 @@ glm::vec3 MyAvatar::getUprightHeadPosition() const { return _position + getWorldAlignedOrientation() * glm::vec3(0.0f, getPelvisToHeadLength(), 0.0f); } +void MyAvatar::setFaceModelURL(const QUrl& faceModelURL) { + Avatar::setFaceModelURL(faceModelURL); + _billboardValid = false; +} + +void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { + Avatar::setSkeletonModelURL(skeletonModelURL); + _billboardValid = false; +} + void MyAvatar::renderBody(bool forceRenderHead) { // Render the body's voxels and head _skeletonModel.render(1.0f); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 1bc5de204b..29f8f6d3bf 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -84,6 +84,9 @@ public: void updateLookAtTargetAvatar(glm::vec3& eyePosition); void clearLookAtTargetAvatar(); + virtual void setFaceModelURL(const QUrl& faceModelURL); + virtual void setSkeletonModelURL(const QUrl& skeletonModelURL); + public slots: void goHome(); void increaseSize(); @@ -119,6 +122,8 @@ private: glm::vec3 _transmitterPickStart; glm::vec3 _transmitterPickEnd; + bool _billboardValid; + // private methods void renderBody(bool forceRenderHead); void updateThrust(float deltaTime); diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index f1916db4d1..ecb741ceb8 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -46,6 +46,21 @@ void Model::initSkinProgram(ProgramObject& program, Model::SkinLocations& locati program.release(); } +bool Model::isLoadedWithTextures() const { + if (!isActive()) { + return false; + } + foreach (const NetworkMesh& mesh, _geometry->getMeshes()) { + foreach (const NetworkMeshPart& part, mesh.parts) { + if (part.diffuseTexture && !part.diffuseTexture->isLoaded() || + part.normalTexture && !part.normalTexture->isLoaded()) { + return false; + } + } + } + return true; +} + void Model::init() { if (!_program.isLinked()) { switchToResourcesParentIfRequired(); diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 28189d0379..5da2891cfe 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -46,6 +46,8 @@ public: bool isActive() const { return _geometry && _geometry->isLoaded(); } + bool isLoadedWithTextures() const; + void init(); void reset(); void simulate(float deltaTime); diff --git a/interface/src/renderer/TextureCache.cpp b/interface/src/renderer/TextureCache.cpp index dc6883a5d0..8bfef5a742 100644 --- a/interface/src/renderer/TextureCache.cpp +++ b/interface/src/renderer/TextureCache.cpp @@ -258,9 +258,11 @@ NetworkTexture::NetworkTexture(const QUrl& url, bool normalMap) : _reply(NULL), _attempts(0), _averageColor(1.0f, 1.0f, 1.0f, 1.0f), - _translucent(false) { + _translucent(false), + _loaded(false) { if (!url.isValid()) { + _loaded = true; return; } _request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); @@ -298,6 +300,7 @@ void NetworkTexture::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTo _reply->disconnect(this); _reply->deleteLater(); _reply = NULL; + _loaded = true; QImage image = QImage::fromData(entirety).convertToFormat(QImage::Format_ARGB32); @@ -345,6 +348,8 @@ void NetworkTexture::handleReplyError() { QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(makeRequest())); debug << " -- retrying..."; + } else { + _loaded = true; } } diff --git a/interface/src/renderer/TextureCache.h b/interface/src/renderer/TextureCache.h index e560acf6f7..ca7bf67a32 100644 --- a/interface/src/renderer/TextureCache.h +++ b/interface/src/renderer/TextureCache.h @@ -118,6 +118,8 @@ public: NetworkTexture(const QUrl& url, bool normalMap); ~NetworkTexture(); + bool isLoaded() const { return _loaded; } + /// Returns the average color over the entire texture. const glm::vec4& getAverageColor() const { return _averageColor; } @@ -142,6 +144,7 @@ private: int _attempts; glm::vec4 _averageColor; bool _translucent; + bool _loaded; }; /// Caches derived, dilated textures.