From ac02609bc2a239491b2bc0bcdf9f174bacc05777 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Fri, 21 Feb 2014 15:38:49 -0800 Subject: [PATCH] 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(); }