From 989a76fe6ebbff3d6dca43a16b2bd409282233f9 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 20 Feb 2014 16:56:40 -0800 Subject: [PATCH 01/10] Hooked up Clipboard preview --- interface/src/ui/ClipboardOverlay.cpp | 42 +++++++++++++++++++++++++++ interface/src/ui/ClipboardOverlay.h | 25 ++++++++++++++++ interface/src/ui/Overlays.cpp | 7 ++++- 3 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 interface/src/ui/ClipboardOverlay.cpp create mode 100644 interface/src/ui/ClipboardOverlay.h diff --git a/interface/src/ui/ClipboardOverlay.cpp b/interface/src/ui/ClipboardOverlay.cpp new file mode 100644 index 0000000000..1eaf5e75f0 --- /dev/null +++ b/interface/src/ui/ClipboardOverlay.cpp @@ -0,0 +1,42 @@ +// +// ClipboardOverlay.cpp +// hifi +// +// Created by Clément Brisset on 2/20/14. +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// + +// include this before QGLWidget, which includes an earlier version of OpenGL +#include "InterfaceConfig.h" + +#include +#include + +#include "ClipboardOverlay.h" +#include "../Application.h" + +ClipboardOverlay::ClipboardOverlay() { +} + +ClipboardOverlay::~ClipboardOverlay() { +} + +void ClipboardOverlay::render() { + if (!_visible) { + return; // do nothing if we're not visible + } + + VoxelSystem* voxelSystem = Application::getInstance()->getSharedVoxelSystem(); + VoxelTree* clipboard = Application::getInstance()->getClipboard(); + if (voxelSystem->getTree() != clipboard) { + voxelSystem->changeTree(clipboard); + } + + glPushMatrix(); + glTranslatef(_position.x, _position.y, _position.z); + glScalef(_size, _size, _size); + + voxelSystem->render(); + + glPopMatrix(); +} \ No newline at end of file diff --git a/interface/src/ui/ClipboardOverlay.h b/interface/src/ui/ClipboardOverlay.h new file mode 100644 index 0000000000..49daa73c20 --- /dev/null +++ b/interface/src/ui/ClipboardOverlay.h @@ -0,0 +1,25 @@ +// +// ClipboardOverlay.h +// hifi +// +// Created by Clément Brisset on 2/20/14. +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__ClipboardOverlay__ +#define __interface__ClipboardOverlay__ + +#include "Volume3DOverlay.h" + +class ClipboardOverlay : public Volume3DOverlay { + Q_OBJECT + +public: + ClipboardOverlay(); + ~ClipboardOverlay(); + + virtual void render(); +}; + + +#endif /* defined(__interface__ClipboardOverlay__) */ diff --git a/interface/src/ui/Overlays.cpp b/interface/src/ui/Overlays.cpp index c35c4fc5ec..e247486e8e 100644 --- a/interface/src/ui/Overlays.cpp +++ b/interface/src/ui/Overlays.cpp @@ -12,7 +12,7 @@ #include "Overlays.h" #include "Sphere3DOverlay.h" #include "TextOverlay.h" - +#include "ClipboardOverlay.h" unsigned int Overlays::_nextOverlayID = 1; @@ -73,6 +73,11 @@ unsigned int Overlays::addOverlay(const QString& type, const QScriptValue& prope thisOverlay->setProperties(properties); created = true; is3D = true; + } else if (type == "clipboard") { + thisOverlay = new ClipboardOverlay(); + thisOverlay->init(_parent); + thisOverlay->setProperties(properties); + created = true; } if (created) { From 6b90a3994d439d2d0e61e253f58685949bcb8c6f Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 20 Feb 2014 19:22:59 -0800 Subject: [PATCH 02/10] 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. From 18ea1d6a855a1e57989c4fd43781b768b41c6d9d Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Fri, 21 Feb 2014 10:16:06 -0800 Subject: [PATCH 03/10] More work on billboards. --- interface/src/Application.cpp | 32 +++++++++++++++++++--------- interface/src/Application.h | 2 +- libraries/shared/src/PacketHeaders.h | 3 ++- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 30a6fb4e74..9ef3fdc76b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -527,7 +527,7 @@ void Application::paintGL() { _glowEffect.render(); if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) { - renderRearViewMirror(); + renderRearViewMirror(_mirrorViewRect); } else if (Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)) { _rearMirrorTools->render(true); @@ -2679,10 +2679,20 @@ void Application::setupWorldLight() { } QImage Application::renderAvatarBillboard() { - renderRearViewMirror(true); + _textureCache.getPrimaryFramebufferObject()->bind(); + + glDisable(GL_BLEND); + + const int BILLBOARD_SIZE = 128; + renderRearViewMirror(QRect(0, _glWidget->height() - BILLBOARD_SIZE, BILLBOARD_SIZE, BILLBOARD_SIZE), true); + + QImage image(BILLBOARD_SIZE, BILLBOARD_SIZE, QImage::Format_ARGB32); + glReadPixels(0, 0, BILLBOARD_SIZE, BILLBOARD_SIZE, GL_BGRA, GL_UNSIGNED_BYTE, image.bits()); + + glEnable(GL_BLEND); + + _textureCache.getPrimaryFramebufferObject()->release(); - 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; } @@ -3603,9 +3613,13 @@ void Application::renderCoverageMapsRecursively(CoverageMap* map) { } } -void Application::renderRearViewMirror(bool billboard) { +void Application::renderRearViewMirror(const QRect& region, bool billboard) { bool eyeRelativeCamera = false; - if (_rearMirrorTools->getZoomLevel() == BODY && !billboard) { + if (billboard) { + const float BILLBOARD_DISTANCE = 5.0f; + _mirrorCamera.setDistance(BILLBOARD_DISTANCE * _myAvatar->getScale()); + _mirrorCamera.setTargetPosition(_myAvatar->getPosition()); + } else if (_rearMirrorTools->getZoomLevel() == BODY) { _mirrorCamera.setDistance(MIRROR_REARVIEW_BODY_DISTANCE * _myAvatar->getScale()); _mirrorCamera.setTargetPosition(_myAvatar->getChestPosition()); } else { // HEAD zoom level @@ -3625,10 +3639,8 @@ void Application::renderRearViewMirror(bool billboard) { _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()); + glViewport(region.x(), _glWidget->height() - region.y() - region.height(), region.width(), region.height()); + glScissor(region.x(), _glWidget->height() - region.y() - region.height(), region.width(), region.height()); bool updateViewFrustum = false; updateProjectionMatrix(_mirrorCamera, updateViewFrustum); glEnable(GL_SCISSOR_TEST); diff --git a/interface/src/Application.h b/interface/src/Application.h index b35e4b4e08..d7bafc72b1 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -333,7 +333,7 @@ private: void displayStats(); void checkStatsClick(); void toggleStatsExpanded(); - void renderRearViewMirror(bool billboard = false); + void renderRearViewMirror(const QRect& region, bool billboard = false); void renderViewFrustum(ViewFrustum& viewFrustum); void checkBandwidthMeterClick(); diff --git a/libraries/shared/src/PacketHeaders.h b/libraries/shared/src/PacketHeaders.h index c1a5a34114..bb5821dd67 100644 --- a/libraries/shared/src/PacketHeaders.h +++ b/libraries/shared/src/PacketHeaders.h @@ -53,7 +53,8 @@ enum PacketType { PacketTypeParticleErase, PacketTypeParticleAddResponse, PacketTypeMetavoxelData, - PacketTypeAvatarIdentity + PacketTypeAvatarIdentity, + PacketTypeAvatarBillboard }; typedef char PacketVersion; From 10ce2cb3f0e0e61872ce11a0f1dd03620cd55899 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Fri, 21 Feb 2014 11:49:29 -0800 Subject: [PATCH 04/10] Working on sending the billboards over the network. --- assignment-client/src/avatars/AvatarMixer.cpp | 43 +++++++++++++++++++ .../src/avatars/AvatarMixerClientData.cpp | 3 +- .../src/avatars/AvatarMixerClientData.h | 6 +++ interface/src/Application.cpp | 5 +++ interface/src/avatar/MyAvatar.cpp | 22 +++++++--- interface/src/avatar/MyAvatar.h | 1 + libraries/avatars/src/AvatarData.cpp | 16 +++++++ libraries/avatars/src/AvatarData.h | 10 +++++ 8 files changed, 100 insertions(+), 6 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 63bdccbab7..d7d11b78b0 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -123,6 +123,23 @@ void broadcastIdentityPacket() { } } +void broadcastBillboardPacket(const SharedNodePointer& node) { + +} + +void broadcastBillboardPackets() { + + NodeList* nodeList = NodeList::getInstance(); + + foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { + if (node->getLinkedData() && node->getType() == NodeType::Agent) { + AvatarMixerClientData* nodeData = static_cast(node->getLinkedData()); + broadcastBillboardPacket(node); + nodeData->setHasSentBillboardBetweenKeyFrames(false); + } + } +} + void AvatarMixer::nodeKilled(SharedNodePointer killedNode) { if (killedNode->getType() == NodeType::Agent && killedNode->getLinkedData()) { @@ -170,6 +187,23 @@ void AvatarMixer::readPendingDatagrams() { nodeList->broadcastToNodes(identityPacket, NodeSet() << NodeType::Agent); } } + break; + } + case PacketTypeAvatarBillboard: { + + // check if we have a matching node in our list + SharedNodePointer avatarNode = nodeList->sendingNodeForPacket(receivedPacket); + + if (avatarNode && avatarNode->getLinkedData()) { + AvatarMixerClientData* nodeData = static_cast(avatarNode->getLinkedData()); + if (nodeData->hasBillboardChangedAfterParsing(receivedPacket) + && !nodeData->hasSentBillboardBetweenKeyFrames()) { + // this avatar changed their billboard and we haven't sent a packet in this keyframe + broadcastBillboardPacket(avatarNode); + nodeData->setHasSentBillboardBetweenKeyFrames(true); + } + } + break; } case PacketTypeKillAvatar: { nodeList->processKillNode(receivedPacket); @@ -185,6 +219,7 @@ void AvatarMixer::readPendingDatagrams() { } const qint64 AVATAR_IDENTITY_KEYFRAME_MSECS = 5000; +const qint64 AVATAR_BILLBOARD_KEYFRAME_MSECS = 5000; void AvatarMixer::run() { commonInit(AVATAR_MIXER_LOGGING_NAME, NodeType::AvatarMixer); @@ -202,6 +237,9 @@ void AvatarMixer::run() { QElapsedTimer identityTimer; identityTimer.start(); + QElapsedTimer billboardTimer; + billboardTimer.start(); + while (!_isFinished) { QCoreApplication::processEvents(); @@ -219,6 +257,11 @@ void AvatarMixer::run() { // restart the timer so we do it again in AVATAR_IDENTITY_KEYFRAME_MSECS identityTimer.restart(); } + + if (billboardTimer.elapsed() >= AVATAR_BILLBOARD_KEYFRAME_MSECS) { + broadcastBillboardPackets(); + billboardTimer.restart(); + } int usecToSleep = usecTimestamp(&startTime) + (++nextFrame * AVATAR_DATA_SEND_INTERVAL_USECS) - usecTimestampNow(); diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 0261613532..388d6f6488 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -9,7 +9,8 @@ #include "AvatarMixerClientData.h" AvatarMixerClientData::AvatarMixerClientData() : - _hasSentIdentityBetweenKeyFrames(false) + _hasSentIdentityBetweenKeyFrames(false), + _hasSentBillboardBetweenKeyFrames(false) { } diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index 8e046d9212..7240288306 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -21,9 +21,15 @@ public: bool hasSentIdentityBetweenKeyFrames() const { return _hasSentIdentityBetweenKeyFrames; } void setHasSentIdentityBetweenKeyFrames(bool hasSentIdentityBetweenKeyFrames) { _hasSentIdentityBetweenKeyFrames = hasSentIdentityBetweenKeyFrames; } + + bool hasSentBillboardBetweenKeyFrames() const { return _hasSentBillboardBetweenKeyFrames; } + void setHasSentBillboardBetweenKeyFrames(bool hasSentBillboardBetweenKeyFrames) + { _hasSentBillboardBetweenKeyFrames = hasSentBillboardBetweenKeyFrames; } + private: bool _hasSentIdentityBetweenKeyFrames; + bool _hasSentBillboardBetweenKeyFrames; }; #endif /* defined(__hifi__AvatarMixerClientData__) */ diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index a71cedec70..35793cb730 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -255,6 +255,11 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : connect(identityPacketTimer, &QTimer::timeout, _myAvatar, &MyAvatar::sendIdentityPacket); identityPacketTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS); + // send the billboard packet for our avatar every few seconds + QTimer* billboardPacketTimer = new QTimer(); + connect(billboardPacketTimer, &QTimer::timeout, _myAvatar, &MyAvatar::sendBillboardPacket); + billboardPacketTimer->start(AVATAR_BILLBOARD_PACKET_SEND_INTERVAL_MSECS); + QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation); _networkAccessManager = new QNetworkAccessManager(this); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index f388f89701..61d1a8b8a8 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -9,6 +9,8 @@ #include #include +#include + #include #include @@ -335,11 +337,7 @@ void MyAvatar::simulate(float deltaTime) { _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; - } + maybeUpdateBillboard(); } const float MAX_PITCH = 90.0f; @@ -1141,6 +1139,20 @@ void MyAvatar::updateChatCircle(float deltaTime) { _position = glm::mix(_position, targetPosition, APPROACH_RATE); } +void MyAvatar::maybeUpdateBillboard() { + if (_billboardValid || !(_skeletonModel.isLoadedWithTextures() && getHead()->getFaceModel().isLoadedWithTextures())) { + return; + } + QImage image = Application::getInstance()->renderAvatarBillboard(); + _billboard.clear(); + QBuffer buffer(&_billboard); + buffer.open(QIODevice::WriteOnly); + image.save(&buffer, "JPG"); + _billboardValid = true; + + sendBillboardPacket(); +} + void MyAvatar::setGravity(glm::vec3 gravity) { _gravity = gravity; getHead()->setGravity(_gravity); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index cbefd6ba0a..2b5be47419 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -133,6 +133,7 @@ private: void applyHardCollision(const glm::vec3& penetration, float elasticity, float damping); void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency); void updateChatCircle(float deltaTime); + void maybeUpdateBillboard(); }; #endif diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 3c50f2622c..26c8abefea 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -305,6 +305,15 @@ QByteArray AvatarData::identityByteArray() { return identityData; } +bool AvatarData::hasBillboardChangedAfterParsing(const QByteArray& packet) { + QByteArray newBillboard = packet.mid(numBytesForPacketHeader(packet)); + if (newBillboard == _billboard) { + return false; + } + _billboard = newBillboard; + return true; +} + void AvatarData::setFaceModelURL(const QUrl& faceModelURL) { _faceModelURL = faceModelURL.isEmpty() ? DEFAULT_HEAD_MODEL_URL : faceModelURL; @@ -344,3 +353,10 @@ void AvatarData::sendIdentityPacket() { NodeList::getInstance()->broadcastToNodes(identityPacket, NodeSet() << NodeType::AvatarMixer); } + +void AvatarData::sendBillboardPacket() { + QByteArray billboardPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarBillboard); + billboardPacket.append(_billboard); + + NodeList::getInstance()->broadcastToNodes(billboardPacket, NodeSet() << NodeType::AvatarMixer); +} diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 07c774d5e6..f597edcc3a 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -29,6 +29,7 @@ typedef unsigned long long quint64; #include #include +#include #include #include #include @@ -54,6 +55,7 @@ static const float MIN_AVATAR_SCALE = .005f; const float MAX_AUDIO_LOUDNESS = 1000.0; // close enough for mouth animation const int AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS = 1000; +const int AVATAR_BILLBOARD_PACKET_SEND_INTERVAL_MSECS = 5000; const QUrl DEFAULT_HEAD_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_head.fst"); const QUrl DEFAULT_BODY_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_body.fst"); @@ -151,6 +153,8 @@ public: bool hasIdentityChangedAfterParsing(const QByteArray& packet); QByteArray identityByteArray(); + bool hasBillboardChangedAfterParsing(const QByteArray& packet); + const QUrl& getFaceModelURL() const { return _faceModelURL; } QString getFaceModelURLString() const { return _faceModelURL.toString(); } const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; } @@ -159,6 +163,9 @@ public: virtual void setSkeletonModelURL(const QUrl& skeletonModelURL); virtual void setDisplayName(const QString& displayName); + void setBillboard(const QByteArray& billboard) { _billboard = billboard; } + const QByteArray& getBillboard() const { return _billboard; } + QString getFaceModelURLFromScript() const { return _faceModelURL.toString(); } void setFaceModelURLFromScript(const QString& faceModelString) { setFaceModelURL(faceModelString); } @@ -169,6 +176,7 @@ public: public slots: void sendIdentityPacket(); + void sendBillboardPacket(); protected: glm::vec3 _position; @@ -204,6 +212,8 @@ protected: float _displayNameTargetAlpha; float _displayNameAlpha; + QByteArray _billboard; + private: // privatize the copy constructor and assignment operator so they cannot be called AvatarData(const AvatarData&); From fc7692417f6baf709cec2a78aaa83368dc13d727 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Fri, 21 Feb 2014 15:15:16 -0800 Subject: [PATCH 05/10] Fix is3D not set to true --- interface/src/ui/Overlays.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/src/ui/Overlays.cpp b/interface/src/ui/Overlays.cpp index e247486e8e..84944332f1 100644 --- a/interface/src/ui/Overlays.cpp +++ b/interface/src/ui/Overlays.cpp @@ -78,6 +78,7 @@ unsigned int Overlays::addOverlay(const QString& type, const QScriptValue& prope thisOverlay->init(_parent); thisOverlay->setProperties(properties); created = true; + is3D = true; } if (created) { From e8c9dc50d0871eb774f4f669069e2f2ec8ea23f4 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Fri, 21 Feb 2014 15:23:28 -0800 Subject: [PATCH 06/10] Hack to get preview working --- interface/src/ui/ClipboardOverlay.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/interface/src/ui/ClipboardOverlay.cpp b/interface/src/ui/ClipboardOverlay.cpp index 1eaf5e75f0..12c72c7690 100644 --- a/interface/src/ui/ClipboardOverlay.cpp +++ b/interface/src/ui/ClipboardOverlay.cpp @@ -15,6 +15,8 @@ #include "ClipboardOverlay.h" #include "../Application.h" +static int lastVoxelCount = 0; + ClipboardOverlay::ClipboardOverlay() { } @@ -36,6 +38,15 @@ void ClipboardOverlay::render() { glTranslatef(_position.x, _position.y, _position.z); glScalef(_size, _size, _size); + qDebug() << "[DEBUG] " << voxelSystem->getVoxelsRendered() << " " + << clipboard->getOctreeElementsCount(); + + // TODO : replace that hack to get the preview working corectly + if (lastVoxelCount != clipboard->getOctreeElementsCount()) { + voxelSystem->forceRedrawEntireTree(); + lastVoxelCount = clipboard->getOctreeElementsCount(); + } + voxelSystem->render(); glPopMatrix(); From ac02609bc2a239491b2bc0bcdf9f174bacc05777 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Fri, 21 Feb 2014 15:38:49 -0800 Subject: [PATCH 07/10] The basic billboard behavior, closes #1864. --- assignment-client/src/avatars/AvatarMixer.cpp | 17 ++-- interface/src/Application.cpp | 12 ++- interface/src/Application.h | 3 + interface/src/DatagramProcessor.cpp | 3 +- interface/src/avatar/Avatar.cpp | 77 ++++++++++++++++++- interface/src/avatar/Avatar.h | 9 +++ interface/src/avatar/AvatarManager.cpp | 17 ++++ interface/src/avatar/AvatarManager.h | 1 + interface/src/avatar/Head.cpp | 3 + interface/src/avatar/MyAvatar.cpp | 2 +- interface/src/renderer/Model.cpp | 7 +- interface/src/renderer/Model.h | 6 +- libraries/avatars/src/AvatarData.cpp | 6 ++ libraries/avatars/src/AvatarData.h | 2 +- 14 files changed, 146 insertions(+), 19 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index db534c7f53..75265bdf10 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -123,15 +123,22 @@ void broadcastIdentityPacket() { } } -void broadcastBillboardPacket(const SharedNodePointer& node) { +void broadcastBillboardPacket(const SharedNodePointer& sendingNode) { + AvatarMixerClientData* nodeData = static_cast(sendingNode->getLinkedData()); + QByteArray packet = byteArrayWithPopulatedHeader(PacketTypeAvatarBillboard); + packet.append(sendingNode->getUUID().toRfc4122()); + packet.append(nodeData->getBillboard()); + NodeList* nodeList = NodeList::getInstance(); + foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { + if (node->getType() == NodeType::Agent && node != sendingNode) { + nodeList->writeDatagram(packet, node); + } + } } void broadcastBillboardPackets() { - - NodeList* nodeList = NodeList::getInstance(); - - foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { + foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) { if (node->getLinkedData() && node->getType() == NodeType::Agent) { AvatarMixerClientData* nodeData = static_cast(node->getLinkedData()); broadcastBillboardPacket(node); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 35793cb730..7738be1d3b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -98,6 +98,7 @@ const int MIRROR_VIEW_HEIGHT = 215; const float MIRROR_FULLSCREEN_DISTANCE = 0.35f; const float MIRROR_REARVIEW_DISTANCE = 0.65f; const float MIRROR_REARVIEW_BODY_DISTANCE = 2.3f; +const float MIRROR_FIELD_OF_VIEW = 30.0f; const QString CHECK_VERSION_URL = "http://highfidelity.io/latestVersion.xml"; const QString SKIP_FILENAME = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/hifi.skipversion"; @@ -2688,7 +2689,7 @@ QImage Application::renderAvatarBillboard() { glDisable(GL_BLEND); - const int BILLBOARD_SIZE = 128; + const int BILLBOARD_SIZE = 64; renderRearViewMirror(QRect(0, _glWidget->height() - BILLBOARD_SIZE, BILLBOARD_SIZE, BILLBOARD_SIZE), true); QImage image(BILLBOARD_SIZE, BILLBOARD_SIZE, QImage::Format_ARGB32); @@ -3621,13 +3622,17 @@ void Application::renderCoverageMapsRecursively(CoverageMap* map) { void Application::renderRearViewMirror(const QRect& region, bool billboard) { bool eyeRelativeCamera = false; if (billboard) { - const float BILLBOARD_DISTANCE = 5.0f; + _mirrorCamera.setFieldOfView(BILLBOARD_FIELD_OF_VIEW); _mirrorCamera.setDistance(BILLBOARD_DISTANCE * _myAvatar->getScale()); _mirrorCamera.setTargetPosition(_myAvatar->getPosition()); + } else if (_rearMirrorTools->getZoomLevel() == BODY) { + _mirrorCamera.setFieldOfView(MIRROR_FIELD_OF_VIEW); _mirrorCamera.setDistance(MIRROR_REARVIEW_BODY_DISTANCE * _myAvatar->getScale()); _mirrorCamera.setTargetPosition(_myAvatar->getChestPosition()); + } else { // HEAD zoom level + _mirrorCamera.setFieldOfView(MIRROR_FIELD_OF_VIEW); _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 @@ -3639,7 +3644,8 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) { _mirrorCamera.setTargetPosition(_myAvatar->getHead()->calculateAverageEyePosition()); } } - + _mirrorCamera.setAspectRatio((float)region.width() / region.height()); + _mirrorCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PIf, 0.0f))); _mirrorCamera.update(1.0f/_fps); diff --git a/interface/src/Application.h b/interface/src/Application.h index d7bafc72b1..cb1ba2f949 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -97,6 +97,9 @@ static const float NODE_KILLED_BLUE = 0.0f; static const QString SNAPSHOT_EXTENSION = ".jpg"; +static const float BILLBOARD_FIELD_OF_VIEW = 30.0f; +static const float BILLBOARD_DISTANCE = 5.0f; + class Application : public QApplication { Q_OBJECT diff --git a/interface/src/DatagramProcessor.cpp b/interface/src/DatagramProcessor.cpp index d8447168cd..e5fd37af4f 100644 --- a/interface/src/DatagramProcessor.cpp +++ b/interface/src/DatagramProcessor.cpp @@ -98,7 +98,8 @@ void DatagramProcessor::processDatagrams() { break; case PacketTypeBulkAvatarData: case PacketTypeKillAvatar: - case PacketTypeAvatarIdentity: { + case PacketTypeAvatarIdentity: + case PacketTypeAvatarBillboard: { // update having heard from the avatar-mixer and record the bytes received SharedNodePointer avatarMixer = nodeList->sendingNodeForPacket(incomingPacket); diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index dcecd0258d..e41d1207fb 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -26,6 +26,7 @@ #include "Physics.h" #include "world.h" #include "devices/OculusManager.h" +#include "renderer/TextureCache.h" #include "ui/TextRenderer.h" using namespace std; @@ -107,6 +108,10 @@ glm::quat Avatar::getWorldAlignedOrientation () const { return computeRotationFromBodyToWorldUp() * getOrientation(); } +float Avatar::getLODDistance() const { + return glm::distance(Application::getInstance()->getCamera()->getPosition(), _position) / _scale; +} + void Avatar::simulate(float deltaTime) { if (_scale != _targetScale) { setScale(_targetScale); @@ -116,6 +121,7 @@ void Avatar::simulate(float deltaTime) { glm::vec3 oldVelocity = getVelocity(); getHand()->simulate(deltaTime, false); + _skeletonModel.setLODDistance(getLODDistance()); _skeletonModel.simulate(deltaTime); Head* head = getHead(); head->setBodyRotation(glm::vec3(_bodyPitch, _bodyYaw, _bodyRoll)); @@ -282,9 +288,11 @@ glm::quat Avatar::computeRotationFromBodyToWorldUp(float proportion) const { } void Avatar::renderBody(bool forceRenderHead) { - // Render the body's voxels and head - glm::vec3 pos = getPosition(); - //printf("Render other at %.3f, %.2f, %.2f\n", pos.x, pos.y, pos.z); + const float BILLBOARD_DISTANCE = 40.0f; + if (!_billboard.isEmpty() && getLODDistance() >= BILLBOARD_DISTANCE) { + renderBillboard(); + return; + } _skeletonModel.render(1.0f); if (forceRenderHead) { getHead()->render(1.0f); @@ -292,6 +300,62 @@ void Avatar::renderBody(bool forceRenderHead) { getHand()->render(false); } +void Avatar::renderBillboard() { + if (!_billboardTexture) { + QImage image = QImage::fromData(_billboard).convertToFormat(QImage::Format_ARGB32); + + _billboardTexture.reset(new Texture()); + glBindTexture(GL_TEXTURE_2D, _billboardTexture->getID()); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 1, + GL_BGRA, GL_UNSIGNED_BYTE, image.constBits()); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + } else { + glBindTexture(GL_TEXTURE_2D, _billboardTexture->getID()); + } + + glEnable(GL_ALPHA_TEST); + glAlphaFunc(GL_GREATER, 0.5f); + + glEnable(GL_TEXTURE_2D); + glDisable(GL_LIGHTING); + + glPushMatrix(); + glTranslatef(_position.x, _position.y, _position.z); + + // rotate about vertical to face the camera + glm::quat rotation = getOrientation(); + glm::vec3 cameraVector = glm::inverse(rotation) * (Application::getInstance()->getCamera()->getPosition() - _position); + rotation = rotation * glm::angleAxis(glm::degrees(atan2f(-cameraVector.x, -cameraVector.z)), 0.0f, 1.0f, 0.0f); + glm::vec3 axis = glm::axis(rotation); + glRotatef(glm::angle(rotation), axis.x, axis.y, axis.z); + + // compute the size from the billboard camera parameters and scale + float size = _scale * BILLBOARD_DISTANCE * tanf(glm::radians(BILLBOARD_FIELD_OF_VIEW / 2.0f)); + glScalef(size, size, size); + + glColor3f(1.0f, 1.0f, 1.0f); + + glBegin(GL_QUADS); + glTexCoord2f(0.0f, 0.0f); + glVertex2f(-1.0f, -1.0f); + glTexCoord2f(1.0f, 0.0f); + glVertex2f(1.0f, -1.0f); + glTexCoord2f(1.0f, 1.0f); + glVertex2f(1.0f, 1.0f); + glTexCoord2f(0.0f, 1.0f); + glVertex2f(-1.0f, 1.0f); + glEnd(); + + glPopMatrix(); + + glDisable(GL_TEXTURE_2D); + glEnable(GL_LIGHTING); + glDisable(GL_ALPHA_TEST); + + glBindTexture(GL_TEXTURE_2D, 0); +} + void Avatar::renderDisplayName() { if (_displayName.isEmpty() || _displayNameAlpha == 0.0f) { @@ -502,6 +566,13 @@ void Avatar::setDisplayName(const QString& displayName) { _displayNameBoundingRect = textRenderer(DISPLAYNAME)->metrics().tightBoundingRect(displayName); } +void Avatar::setBillboard(const QByteArray& billboard) { + AvatarData::setBillboard(billboard); + + // clear out any existing billboard texture + _billboardTexture.reset(); +} + int Avatar::parseData(const QByteArray& packet) { // change in position implies movement glm::vec3 oldPosition = _position; diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 6fa0f203e9..b9433f15dc 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -62,6 +63,8 @@ enum ScreenTintLayer { // Grayson as he's building a street around here for demo dinner 2 const glm::vec3 START_LOCATION(0.485f * TREE_SCALE, 0.f, 0.5f * TREE_SCALE); +class Texture; + class Avatar : public AvatarData { Q_OBJECT @@ -87,6 +90,9 @@ public: Head* getHead() { return static_cast(_headData); } Hand* getHand() { return static_cast(_handData); } glm::quat getWorldAlignedOrientation() const; + + /// Returns the distance to use as a LOD parameter. + float getLODDistance() const; Node* getOwningAvatarMixer() { return _owningAvatarMixer.data(); } void setOwningAvatarMixer(const QWeakPointer& owningAvatarMixer) { _owningAvatarMixer = owningAvatarMixer; } @@ -114,6 +120,7 @@ public: virtual void setFaceModelURL(const QUrl& faceModelURL); virtual void setSkeletonModelURL(const QUrl& skeletonModelURL); virtual void setDisplayName(const QString& displayName); + virtual void setBillboard(const QByteArray& billboard); void setShowDisplayName(bool showDisplayName); @@ -167,8 +174,10 @@ protected: private: bool _initialized; + QScopedPointer _billboardTexture; void renderBody(bool forceRenderHead); + void renderBillboard(); void renderDisplayName(); }; diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index b873b10017..f65566fe14 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -133,6 +133,9 @@ void AvatarManager::processAvatarMixerDatagram(const QByteArray& datagram, const case PacketTypeAvatarIdentity: processAvatarIdentityPacket(datagram); break; + case PacketTypeAvatarBillboard: + processAvatarBillboardPacket(datagram); + break; case PacketTypeKillAvatar: processKillAvatar(datagram); break; @@ -212,6 +215,20 @@ void AvatarManager::processAvatarIdentityPacket(const QByteArray &packet) { } } +void AvatarManager::processAvatarBillboardPacket(const QByteArray& packet) { + int headerSize = numBytesForPacketHeader(packet); + QUuid nodeUUID = QUuid::fromRfc4122(QByteArray::fromRawData(packet.constData() + headerSize, NUM_BYTES_RFC4122_UUID)); + + AvatarSharedPointer matchingAvatar = _avatarHash.value(nodeUUID); + if (matchingAvatar) { + Avatar* avatar = static_cast(matchingAvatar.data()); + QByteArray billboard = packet.mid(headerSize + NUM_BYTES_RFC4122_UUID); + if (avatar->getBillboard() != billboard) { + avatar->setBillboard(billboard); + } + } +} + void AvatarManager::processKillAvatar(const QByteArray& datagram) { // read the node id QUuid nodeUUID = QUuid::fromRfc4122(datagram.mid(numBytesForPacketHeader(datagram), NUM_BYTES_RFC4122_UUID)); diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 35dbf8dbb0..115423e618 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -42,6 +42,7 @@ private: void processAvatarDataPacket(const QByteArray& packet, const QWeakPointer& mixerWeakPointer); void processAvatarIdentityPacket(const QByteArray& packet); + void processAvatarBillboardPacket(const QByteArray& packet); void processKillAvatar(const QByteArray& datagram); void simulateAvatarFades(float deltaTime); diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index 5c6100764a..1653597d3e 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -158,6 +158,9 @@ void Head::simulate(float deltaTime, bool isMine) { glm::clamp(sqrt(_averageLoudness * JAW_OPEN_SCALE) - JAW_OPEN_DEAD_ZONE, 0.0f, 1.0f), _blendshapeCoefficients); } + if (!isMine) { + _faceModel.setLODDistance(static_cast(_owningAvatar)->getLODDistance()); + } _faceModel.simulate(deltaTime); // the blend face may have custom eye meshes diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 61d1a8b8a8..9d457cedd7 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1147,7 +1147,7 @@ void MyAvatar::maybeUpdateBillboard() { _billboard.clear(); QBuffer buffer(&_billboard); buffer.open(QIODevice::WriteOnly); - image.save(&buffer, "JPG"); + image.save(&buffer, "PNG"); _billboardValid = true; sendBillboardPacket(); diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index ecb741ceb8..c3aae7146a 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -17,8 +17,8 @@ using namespace std; Model::Model(QObject* parent) : QObject(parent), - _pupilDilation(0.0f) -{ + _lodDistance(0.0f), + _pupilDilation(0.0f) { // we may have been created in the network thread, but we live in the main thread moveToThread(Application::getInstance()->thread()); } @@ -107,8 +107,7 @@ void Model::reset() { void Model::simulate(float deltaTime) { // update our LOD if (_geometry) { - QSharedPointer geometry = _geometry->getLODOrFallback(glm::distance(_translation, - Application::getInstance()->getCamera()->getPosition()), _lodHysteresis); + QSharedPointer geometry = _geometry->getLODOrFallback(_lodDistance, _lodHysteresis); if (_geometry != geometry) { deleteGeometry(); _dilatedTextures.clear(); diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 5da2891cfe..cf6e1fea6d 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -56,6 +56,9 @@ public: Q_INVOKABLE void setURL(const QUrl& url, const QUrl& fallback = QUrl()); const QUrl& getURL() const { return _url; } + /// Sets the distance parameter used for LOD computations. + void setLODDistance(float distance) { _lodDistance = distance; } + /// Returns the extents of the model in its bind pose. Extents getBindExtents() const; @@ -230,13 +233,14 @@ private: void renderMeshes(float alpha, bool translucent); QSharedPointer _baseGeometry; ///< reference required to prevent collection of base + float _lodDistance; float _lodHysteresis; float _pupilDilation; std::vector _blendshapeCoefficients; QUrl _url; - + QVector _blendedVertexBufferIDs; QVector > > _dilatedTextures; bool _resetStates; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 26c8abefea..2cb719c446 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -332,6 +332,12 @@ void AvatarData::setDisplayName(const QString& displayName) { qDebug() << "Changing display name for avatar to" << displayName; } +void AvatarData::setBillboard(const QByteArray& billboard) { + _billboard = billboard; + + qDebug() << "Changing billboard for avatar."; +} + void AvatarData::setClampedTargetScale(float targetScale) { targetScale = glm::clamp(targetScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index f597edcc3a..f6bde135b4 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -163,7 +163,7 @@ public: virtual void setSkeletonModelURL(const QUrl& skeletonModelURL); virtual void setDisplayName(const QString& displayName); - void setBillboard(const QByteArray& billboard) { _billboard = billboard; } + virtual void setBillboard(const QByteArray& billboard); const QByteArray& getBillboard() const { return _billboard; } QString getFaceModelURLFromScript() const { return _faceModelURL.toString(); } From e66ec6b702e603b34a1326859c90046cba8b8117 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Fri, 21 Feb 2014 15:47:40 -0800 Subject: [PATCH 08/10] Final fix for clipboard preview --- interface/src/ui/ClipboardOverlay.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/interface/src/ui/ClipboardOverlay.cpp b/interface/src/ui/ClipboardOverlay.cpp index 12c72c7690..7eee318ec9 100644 --- a/interface/src/ui/ClipboardOverlay.cpp +++ b/interface/src/ui/ClipboardOverlay.cpp @@ -38,10 +38,7 @@ void ClipboardOverlay::render() { glTranslatef(_position.x, _position.y, _position.z); glScalef(_size, _size, _size); - qDebug() << "[DEBUG] " << voxelSystem->getVoxelsRendered() << " " - << clipboard->getOctreeElementsCount(); - - // TODO : replace that hack to get the preview working corectly + // We only force the redraw when the clipboard content has changed if (lastVoxelCount != clipboard->getOctreeElementsCount()) { voxelSystem->forceRedrawEntireTree(); lastVoxelCount = clipboard->getOctreeElementsCount(); From 6fb2d57e403ebcc1b29d076feb4771cce5282ad4 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Fri, 21 Feb 2014 16:04:15 -0800 Subject: [PATCH 09/10] Add clipboardOverlay example to OverlaysExample --- examples/overlaysExample.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/examples/overlaysExample.js b/examples/overlaysExample.js index b57f82e7e9..d97ec9e8fd 100644 --- a/examples/overlaysExample.js +++ b/examples/overlaysExample.js @@ -156,6 +156,13 @@ var line3d = Overlays.addOverlay("line3d", { lineWidth: 5 }); +// this will display the content of your clipboard at the origin of the domain +var clipboardPreview = Overlays.addOverlay("clipboard", { + position: { x: 0, y: 0, z: 0}, + size: 1 / 32, + visible: true + }); + // When our script shuts down, we should clean up all of our overlays function scriptEnding() { @@ -170,6 +177,7 @@ function scriptEnding() { Overlays.deleteOverlay(solidCube); Overlays.deleteOverlay(sphere); Overlays.deleteOverlay(line3d); + Overlays.deleteOverlay(clipboardPreview); } Script.scriptEnding.connect(scriptEnding); From 6f8f15018c26db4a78a54521fccb034ba05cb753 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Fri, 21 Feb 2014 17:52:20 -0800 Subject: [PATCH 10/10] Allow setting the combined head orientation, which changes the body yaw and applies the rest to the head. Closes #2050. --- examples/editVoxels.js | 2 +- interface/src/avatar/Avatar.cpp | 1 - interface/src/avatar/Head.cpp | 7 +------ interface/src/avatar/Head.h | 3 --- interface/src/avatar/MyAvatar.cpp | 1 - libraries/avatars/src/AvatarData.h | 4 ++++ libraries/avatars/src/HeadData.cpp | 23 +++++++++++++++++++++++ libraries/avatars/src/HeadData.h | 4 ++++ 8 files changed, 33 insertions(+), 12 deletions(-) diff --git a/examples/editVoxels.js b/examples/editVoxels.js index 81e3000566..393dd52fef 100644 --- a/examples/editVoxels.js +++ b/examples/editVoxels.js @@ -866,7 +866,7 @@ function mouseReleaseEvent(event) { var cameraOrientation = Camera.getOrientation(); var eulers = Quat.safeEulerAngles(cameraOrientation); MyAvatar.position = Camera.getPosition(); - MyAvatar.orientation = cameraOrientation; + MyAvatar.headOrientation = cameraOrientation; Camera.stopLooking(); Camera.setMode(oldMode); Camera.setOrientation(cameraOrientation); diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index dcecd0258d..0f820f871a 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -118,7 +118,6 @@ void Avatar::simulate(float deltaTime) { getHand()->simulate(deltaTime, false); _skeletonModel.simulate(deltaTime); Head* head = getHead(); - head->setBodyRotation(glm::vec3(_bodyPitch, _bodyYaw, _bodyRoll)); glm::vec3 headPosition; if (!_skeletonModel.getHeadPosition(headPosition)) { headPosition = _position; diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index 5c6100764a..f6c8fabc0d 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -28,7 +28,6 @@ Head::Head(Avatar* owningAvatar) : _gravity(0.0f, -1.0f, 0.0f), _lastLoudness(0.0f), _audioAttack(0.0f), - _bodyRotation(0.0f, 0.0f, 0.0f), _angularVelocity(0,0,0), _renderLookatVectors(false), _saccade(0.0f, 0.0f, 0.0f), @@ -180,12 +179,8 @@ void Head::setScale (float scale) { _scale = scale; } -glm::quat Head::getOrientation() const { - return glm::quat(glm::radians(_bodyRotation)) * glm::quat(glm::radians(glm::vec3(_pitch, _yaw, _roll))); -} - glm::quat Head::getTweakedOrientation() const { - return glm::quat(glm::radians(_bodyRotation)) * glm::quat(glm::radians(glm::vec3(getTweakedPitch(), getTweakedYaw(), getTweakedRoll() ))); + return _owningAvatar->getOrientation() * glm::quat(glm::radians(glm::vec3(getTweakedPitch(), getTweakedYaw(), getTweakedRoll() ))); } glm::quat Head::getCameraOrientation () const { diff --git a/interface/src/avatar/Head.h b/interface/src/avatar/Head.h index c88e654d95..39a2f4eeb6 100644 --- a/interface/src/avatar/Head.h +++ b/interface/src/avatar/Head.h @@ -40,13 +40,11 @@ public: void render(float alpha); void setScale(float scale); void setPosition(glm::vec3 position) { _position = position; } - void setBodyRotation(glm::vec3 bodyRotation) { _bodyRotation = bodyRotation; } void setGravity(glm::vec3 gravity) { _gravity = gravity; } void setAverageLoudness(float averageLoudness) { _averageLoudness = averageLoudness; } void setReturnToCenter (bool returnHeadToCenter) { _returnHeadToCenter = returnHeadToCenter; } void setRenderLookatVectors(bool onOff) { _renderLookatVectors = onOff; } - glm::quat getOrientation() const; glm::quat getTweakedOrientation() const; glm::quat getCameraOrientation () const; const glm::vec3& getAngularVelocity() const { return _angularVelocity; } @@ -97,7 +95,6 @@ private: glm::vec3 _gravity; float _lastLoudness; float _audioAttack; - glm::vec3 _bodyRotation; glm::vec3 _angularVelocity; bool _renderLookatVectors; glm::vec3 _saccade; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 90ef18848b..60af1560ce 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -321,7 +321,6 @@ void MyAvatar::simulate(float deltaTime) { _skeletonModel.simulate(deltaTime); Head* head = getHead(); - head->setBodyRotation(glm::vec3(_bodyPitch, _bodyYaw, _bodyRoll)); glm::vec3 headPosition; if (!_skeletonModel.getHeadPosition(headPosition)) { headPosition = _position; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 07c774d5e6..8f6ce850a6 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -78,6 +78,7 @@ class AvatarData : public NodeData { Q_PROPERTY(QString chatMessage READ getQStringChatMessage WRITE setChatMessage) Q_PROPERTY(glm::quat orientation READ getOrientation WRITE setOrientation) + Q_PROPERTY(glm::quat headOrientation READ getHeadOrientation WRITE setHeadOrientation) Q_PROPERTY(float headPitch READ getHeadPitch WRITE setHeadPitch) Q_PROPERTY(float audioLoudness READ getAudioLoudness WRITE setAudioLoudness) @@ -109,6 +110,9 @@ public: glm::quat getOrientation() const { return glm::quat(glm::radians(glm::vec3(_bodyPitch, _bodyYaw, _bodyRoll))); } void setOrientation(const glm::quat& orientation); + glm::quat getHeadOrientation() const { return _headData->getOrientation(); } + void setHeadOrientation(const glm::quat& orientation) { _headData->setOrientation(orientation); } + // access to Head().set/getMousePitch float getHeadPitch() const { return _headData->getPitch(); } void setHeadPitch(float value) { _headData->setPitch(value); }; diff --git a/libraries/avatars/src/HeadData.cpp b/libraries/avatars/src/HeadData.cpp index 62e8276bd3..68a5c2c826 100644 --- a/libraries/avatars/src/HeadData.cpp +++ b/libraries/avatars/src/HeadData.cpp @@ -6,6 +6,11 @@ // Copyright (c) 2013 High Fidelity, Inc. All rights reserved. // +#include + +#include + +#include "AvatarData.h" #include "HeadData.h" HeadData::HeadData(AvatarData* owningAvatar) : @@ -26,6 +31,24 @@ HeadData::HeadData(AvatarData* owningAvatar) : } +glm::quat HeadData::getOrientation() const { + return _owningAvatar->getOrientation() * glm::quat(glm::radians(glm::vec3(_pitch, _yaw, _roll))); +} + +void HeadData::setOrientation(const glm::quat& orientation) { + // rotate body about vertical axis + glm::quat bodyOrientation = _owningAvatar->getOrientation(); + glm::vec3 newFront = glm::inverse(bodyOrientation) * (orientation * IDENTITY_FRONT); + bodyOrientation = bodyOrientation * glm::angleAxis(glm::degrees(atan2f(-newFront.x, -newFront.z)), 0.0f, 1.0f, 0.0f); + _owningAvatar->setOrientation(bodyOrientation); + + // the rest goes to the head + glm::vec3 eulers = safeEulerAngles(glm::inverse(bodyOrientation) * orientation); + _pitch = eulers.x; + _yaw = eulers.y; + _roll = eulers.z; +} + void HeadData::addYaw(float yaw) { setYaw(_yaw + yaw); } diff --git a/libraries/avatars/src/HeadData.h b/libraries/avatars/src/HeadData.h index 04d5fe5b46..618de89b31 100644 --- a/libraries/avatars/src/HeadData.h +++ b/libraries/avatars/src/HeadData.h @@ -13,6 +13,7 @@ #include #include +#include const float MIN_HEAD_YAW = -110; const float MAX_HEAD_YAW = 110; @@ -42,6 +43,9 @@ public: float getRoll() const { return _roll; } void setRoll(float roll) { _roll = glm::clamp(roll, MIN_HEAD_ROLL, MAX_HEAD_ROLL); } + glm::quat getOrientation() const; + void setOrientation(const glm::quat& orientation); + float getAudioLoudness() const { return _audioLoudness; } void setAudioLoudness(float audioLoudness) { _audioLoudness = audioLoudness; }