Merge branch 'master' of https://github.com/worklist/hifi into 19508

This commit is contained in:
stojce 2014-03-22 10:19:30 +01:00
commit beb8fb5cc7
22 changed files with 441 additions and 215 deletions

View file

@ -63,4 +63,5 @@ To test things out you'll want to run the Interface client.
To access your local domain in Interface, open your Preferences -- on OS X this is available in the Interface menu, on Linux you'll find it in the File menu. Enter "localhost" in the "Domain server" field.
If everything worked you should see that you are connected to at least one server. Nice work!
If everything worked you should see that you are connected to at least one server.
Nice work!

View file

@ -189,4 +189,9 @@ void Agent::run() {
_scriptEngine.setScriptContents(scriptContents);
_scriptEngine.run();
setFinished(true);
}
void Agent::aboutToFinish() {
_scriptEngine.stop();
}

View file

@ -42,6 +42,8 @@ public:
void setIsListeningToAudioStream(bool isListeningToAudioStream)
{ _scriptEngine.setIsListeningToAudioStream(isListeningToAudioStream); }
virtual void aboutToFinish();
public slots:
void run();
void readPendingDatagrams();

View file

@ -53,6 +53,8 @@
const short JITTER_BUFFER_MSECS = 12;
const short JITTER_BUFFER_SAMPLES = JITTER_BUFFER_MSECS * (SAMPLE_RATE / 1000.0);
const float LOUDNESS_TO_DISTANCE_RATIO = 0.00305f;
const QString AUDIO_MIXER_LOGGING_TARGET_NAME = "audio-mixer";
void attachNewBufferToNode(Node *newNode) {
@ -64,10 +66,8 @@ void attachNewBufferToNode(Node *newNode) {
AudioMixer::AudioMixer(const QByteArray& packet) :
ThreadedAssignment(packet),
_trailingSleepRatio(1.0f),
_minSourceLoudnessInFrame(1.0f),
_maxSourceLoudnessInFrame(0.0f),
_loudnessCutoffRatio(0.0f),
_minRequiredLoudness(0.0f)
_minAudibilityThreshold(LOUDNESS_TO_DISTANCE_RATIO / 2.0f),
_numClientsMixedInFrame(0)
{
}
@ -81,8 +81,22 @@ void AudioMixer::addBufferToMixForListeningNodeWithBuffer(PositionalAudioRingBuf
if (bufferToAdd != listeningNodeBuffer) {
// if the two buffer pointers do not match then these are different buffers
glm::vec3 relativePosition = bufferToAdd->getPosition() - listeningNodeBuffer->getPosition();
float distanceBetween = glm::length(relativePosition);
if (distanceBetween < EPSILON) {
distanceBetween = EPSILON;
}
if (bufferToAdd->getAverageLoudness() / distanceBetween <= _minAudibilityThreshold) {
// according to mixer performance we have decided this does not get to be mixed in
// bail out
return;
}
++_numClientsMixedInFrame;
glm::quat inverseOrientation = glm::inverse(listeningNodeBuffer->getOrientation());
float distanceSquareToSource = glm::dot(relativePosition, relativePosition);
@ -306,7 +320,7 @@ void AudioMixer::prepareMixForListeningNode(Node* node) {
if ((*otherNode != *node
|| otherNodeBuffer->shouldLoopbackForNode())
&& otherNodeBuffer->willBeAddedToMix()
&& otherNodeBuffer->getAverageLoudness() > _minRequiredLoudness) {
&& otherNodeBuffer->getAverageLoudness() > 0) {
addBufferToMixForListeningNodeWithBuffer(otherNodeBuffer, nodeRingBuffer);
}
}
@ -357,20 +371,72 @@ void AudioMixer::run() {
+ numBytesForPacketHeaderGivenPacketType(PacketTypeMixedAudio)];
int usecToSleep = BUFFER_SEND_INTERVAL_USECS;
float audabilityCutoffRatio = 0;
const int TRAILING_AVERAGE_FRAMES = 100;
int framesSinceCutoffEvent = TRAILING_AVERAGE_FRAMES;
while (!_isFinished) {
_minSourceLoudnessInFrame = 1.0f;
_maxSourceLoudnessInFrame = 0.0f;
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
if (node->getLinkedData()) {
((AudioMixerClientData*) node->getLinkedData())->checkBuffersBeforeFrameSend(JITTER_BUFFER_SAMPLES,
_minSourceLoudnessInFrame,
_maxSourceLoudnessInFrame);
((AudioMixerClientData*) node->getLinkedData())->checkBuffersBeforeFrameSend(JITTER_BUFFER_SAMPLES);
}
}
const float STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.10f;
const float BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.20f;
const float CUTOFF_DELTA = 0.02f;
const float CURRENT_FRAME_RATIO = 1.0f / TRAILING_AVERAGE_FRAMES;
const float PREVIOUS_FRAMES_RATIO = 1.0f - CURRENT_FRAME_RATIO;
if (usecToSleep < 0) {
usecToSleep = 0;
}
_trailingSleepRatio = (PREVIOUS_FRAMES_RATIO * _trailingSleepRatio)
+ (usecToSleep * CURRENT_FRAME_RATIO / (float) BUFFER_SEND_INTERVAL_USECS);
float lastCutoffRatio = audabilityCutoffRatio;
bool hasRatioChanged = false;
if (framesSinceCutoffEvent >= TRAILING_AVERAGE_FRAMES) {
if (_trailingSleepRatio <= STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD) {
// we're struggling - change our min required loudness to reduce some load
audabilityCutoffRatio += CUTOFF_DELTA;
if (audabilityCutoffRatio >= 1) {
audabilityCutoffRatio = 1 - CUTOFF_DELTA;
}
qDebug() << "Mixer is struggling, sleeping" << _trailingSleepRatio * 100 << "% of frame time. Old cutoff was"
<< lastCutoffRatio << "and is now" << audabilityCutoffRatio;
hasRatioChanged = true;
} else if (_trailingSleepRatio >= BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD && audabilityCutoffRatio != 0) {
// we've recovered and can back off the required loudness
audabilityCutoffRatio -= CUTOFF_DELTA;
if (audabilityCutoffRatio < 0) {
audabilityCutoffRatio = 0;
}
qDebug() << "Mixer is recovering, sleeping" << _trailingSleepRatio * 100 << "% of frame time. Old cutoff was"
<< lastCutoffRatio << "and is now" << audabilityCutoffRatio;
hasRatioChanged = true;
}
if (hasRatioChanged) {
// set out min audability threshold from the new ratio
_minAudibilityThreshold = LOUDNESS_TO_DISTANCE_RATIO / (2.0f * (1.0f - audabilityCutoffRatio));
qDebug() << "Minimum audability required to be mixed is now" << _minAudibilityThreshold;
framesSinceCutoffEvent = 0;
}
} else {
framesSinceCutoffEvent++;
}
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
if (node->getType() == NodeType::Agent && node->getActiveSocket() && node->getLinkedData()
&& ((AudioMixerClientData*) node->getLinkedData())->getAvatarAudioRingBuffer()) {
@ -383,6 +449,8 @@ void AudioMixer::run() {
}
}
_numClientsMixedInFrame = 0;
// push forward the next output pointers for any audio buffers we used
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
if (node->getLinkedData()) {
@ -400,10 +468,7 @@ void AudioMixer::run() {
if (usecToSleep > 0) {
usleep(usecToSleep);
} else {
qDebug() << "AudioMixer loop took" << -usecToSleep << "of extra time. Not sleeping.";
}
}
delete[] clientMixBuffer;

View file

@ -41,10 +41,8 @@ private:
int16_t _clientSamples[NETWORK_BUFFER_LENGTH_SAMPLES_STEREO + (SAMPLE_PHASE_DELAY_AT_90 * 2)];
float _trailingSleepRatio;
float _minSourceLoudnessInFrame;
float _maxSourceLoudnessInFrame;
float _loudnessCutoffRatio;
float _minRequiredLoudness;
float _minAudibilityThreshold;
int _numClientsMixedInFrame;
};
#endif /* defined(__hifi__AudioMixer__) */

View file

@ -83,20 +83,16 @@ int AudioMixerClientData::parseData(const QByteArray& packet) {
return 0;
}
void AudioMixerClientData::checkBuffersBeforeFrameSend(int jitterBufferLengthSamples,
float& currentMinLoudness,
float& currentMaxLoudness) {
void AudioMixerClientData::checkBuffersBeforeFrameSend(int jitterBufferLengthSamples) {
for (unsigned int i = 0; i < _ringBuffers.size(); i++) {
if (_ringBuffers[i]->shouldBeAddedToMix(jitterBufferLengthSamples)) {
// this is a ring buffer that is ready to go
// set its flag so we know to push its buffer when all is said and done
_ringBuffers[i]->setWillBeAddedToMix(true);
// calculate the average loudness for the next NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL
// that would be mixed in
_ringBuffers[i]->updateAverageLoudnessForBoundarySamples(NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL);
}
}
}

View file

@ -25,7 +25,7 @@ public:
AvatarAudioRingBuffer* getAvatarAudioRingBuffer() const;
int parseData(const QByteArray& packet);
void checkBuffersBeforeFrameSend(int jitterBufferLengthSamples, float& currentMinLoudness, float& currentMaxLoudness);
void checkBuffersBeforeFrameSend(int jitterBufferLengthSamples);
void pushBuffersAfterFrameSend();
private:
std::vector<PositionalAudioRingBuffer*> _ringBuffers;

View file

@ -67,6 +67,14 @@ OctreeQueryNode::~OctreeQueryNode() {
}
void OctreeQueryNode::deleteLater() {
_isShuttingDown = true;
if (_octreeSendThread) {
_octreeSendThread->setIsShuttingDown();
}
OctreeQuery::deleteLater();
}
void OctreeQueryNode::initializeOctreeSendThread(OctreeServer* octreeServer, const QUuid& nodeUUID) {
// Create octree sending thread...

View file

@ -27,6 +27,7 @@ class OctreeQueryNode : public OctreeQuery {
public:
OctreeQueryNode();
virtual ~OctreeQueryNode();
virtual void deleteLater();
void init(); // called after creation to set up some virtual items
virtual PacketType getMyPacketType() const = 0;

View file

@ -5,6 +5,8 @@
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
#include <QMutexLocker>
#include <NodeList.h>
#include <PacketHeaders.h>
#include <PerfStat.h>
@ -21,7 +23,9 @@ OctreeSendThread::OctreeSendThread(const QUuid& nodeUUID, OctreeServer* myServer
_nodeUUID(nodeUUID),
_myServer(myServer),
_packetData(),
_nodeMissingCount(0)
_nodeMissingCount(0),
_processLock(),
_isShuttingDown(false)
{
QString safeServerName("Octree");
if (_myServer) {
@ -43,8 +47,19 @@ OctreeSendThread::~OctreeSendThread() {
OctreeServer::clientDisconnected();
}
void OctreeSendThread::setIsShuttingDown() {
QMutexLocker locker(&_processLock); // this will cause us to wait till the process loop is complete
_isShuttingDown = true;
}
bool OctreeSendThread::process() {
QMutexLocker locker(&_processLock);
if (_isShuttingDown) {
return false; // exit early if we're shutting down
}
const int MAX_NODE_MISSING_CHECKS = 10;
if (_nodeMissingCount > MAX_NODE_MISSING_CHECKS) {
qDebug() << "our target node:" << _nodeUUID << "has been missing the last" << _nodeMissingCount
@ -56,7 +71,10 @@ bool OctreeSendThread::process() {
// don't do any send processing until the initial load of the octree is complete...
if (_myServer->isInitialLoadComplete()) {
SharedNodePointer node = NodeList::getInstance()->nodeWithUUID(_nodeUUID);
// see if we can get access to our node, but don't wait on the lock, if the nodeList is busy
// it might not return a node that is known, but that's ok we can handle that case.
SharedNodePointer node = NodeList::getInstance()->nodeWithUUID(_nodeUUID, false);
if (node) {
_nodeMissingCount = 0;
@ -113,19 +131,6 @@ int OctreeSendThread::handlePacketSend(const SharedNodePointer& node,
bool packetSent = false; // did we send a packet?
int packetsSent = 0;
// double check that the node has an active socket, otherwise, don't send...
quint64 lockWaitStart = usecTimestampNow();
QMutexLocker locker(&node->getMutex());
quint64 lockWaitEnd = usecTimestampNow();
float lockWaitElapsedUsec = (float)(lockWaitEnd - lockWaitStart);
OctreeServer::trackNodeWaitTime(lockWaitElapsedUsec);
const HifiSockAddr* nodeAddress = node->getActiveSocket();
if (!nodeAddress) {
return packetsSent; // without sending...
}
// Here's where we check to see if this packet is a duplicate of the last packet. If it is, we will silently
// obscure the packet and not send it. This allows the callers and upper level logic to not need to know about
// this rate control savings.

View file

@ -24,6 +24,8 @@ public:
OctreeSendThread(const QUuid& nodeUUID, OctreeServer* myServer);
virtual ~OctreeSendThread();
void setIsShuttingDown();
static quint64 _totalBytes;
static quint64 _totalWastedBytes;
static quint64 _totalPackets;
@ -45,6 +47,8 @@ private:
OctreePacketData _packetData;
int _nodeMissingCount;
QMutex _processLock; // don't allow us to have our nodeData, or our thread to be deleted while we're processing
bool _isShuttingDown;
};
#endif // __octree_server__OctreeSendThread__

View file

@ -32,8 +32,6 @@ var MIN_PASTE_VOXEL_SCALE = .256;
var zFightingSizeAdjust = 0.002; // used to adjust preview voxels to prevent z fighting
var previewLineWidth = 1.5;
var oldMode = Camera.getMode();
var trackAsOrbitOrPan = false;
var isAdding = false;
var isExtruding = false;
var extrudeDirection = { x: 0, y: 0, z: 0 };
@ -614,8 +612,6 @@ function showPreviewVoxel() {
var guidePosition;
if (trackAsRecolor || recolorToolSelected || trackAsEyedropper || eyedropperToolSelected) {
Overlays.editOverlay(voxelPreview, { visible: true });
} else if (trackAsOrbitOrPan) {
Overlays.editOverlay(voxelPreview, { visible: false });
} else if (voxelToolSelected && !isExtruding) {
Overlays.editOverlay(voxelPreview, { visible: true });
} else if (isExtruding) {
@ -706,15 +702,12 @@ function showPreviewGuides() {
}
function trackMouseEvent(event) {
if (!trackAsOrbitOrPan) {
trackLastMouseX = event.x;
trackLastMouseY = event.y;
trackAsDelete = event.isControl;
trackAsRecolor = event.isShifted;
trackAsEyedropper = event.isMeta;
trackAsOrbitOrPan = event.isAlt; // TODO: double check this...??
showPreviewGuides();
}
}
function trackKeyPressEvent(event) {
@ -742,6 +735,7 @@ function trackKeyReleaseEvent(event) {
if (event.text == "TAB") {
editToolsOn = !editToolsOn;
moveTools();
showPreviewGuides();
Audio.playSound(clickSound, audioOptions);
}
@ -788,8 +782,6 @@ function mousePressEvent(event) {
return;
}
// no clicking on overlays while in panning mode
if (!trackAsOrbitOrPan) {
var clickedOnSomething = false;
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
@ -847,7 +839,6 @@ function mousePressEvent(event) {
if (clickedOnSomething) {
return; // no further processing
}
}
// TODO: does any of this stuff need to execute if we're panning or orbiting?
trackMouseEvent(event); // used by preview support
@ -1071,7 +1062,7 @@ function mouseMoveEvent(event) {
}
if (!trackAsOrbitOrPan && isMovingSlider) {
if (isMovingSlider) {
thumbX = (event.x - thumbClickOffsetX) - sliderX;
if (thumbX < minThumbX) {
thumbX = minThumbX;
@ -1081,7 +1072,7 @@ function mouseMoveEvent(event) {
}
calcScaleFromThumb(thumbX);
} else if (!trackAsOrbitOrPan && isAdding) {
} else if (isAdding) {
// Watch the drag direction to tell which way to 'extrude' this voxel
if (!isExtruding) {
var pickRay = Camera.computePickRay(event.x, event.y);

View file

@ -14,9 +14,11 @@
// Dragging the mouse will move your camera according to the mode you are in.
//
var PI = 3.14 // No need for something more precise
var AZIMUTH_RATE = 90.0;
var ALTITUDE_RATE = 200.0;
var RADIUS_RATE = 20.0;
var RADIUS_RATE = 1.0 / 100.0;
var PAN_RATE = 50.0;
var alt = false;
@ -46,7 +48,7 @@ var altitude = 0.0;
function handleRadialMode(dx, dy) {
azimuth += dx / AZIMUTH_RATE;
radius += radius * dy / RADIUS_RATE;
radius += radius * dy * RADIUS_RATE;
if (radius < 1) {
radius = 1;
}
@ -61,6 +63,12 @@ function handleRadialMode(dx, dy) {
function handleOrbitMode(dx, dy) {
azimuth += dx / AZIMUTH_RATE;
altitude += dy / ALTITUDE_RATE;
if (altitude > PI / 2.0) {
altitude = PI / 2.0;
}
if (altitude < -PI / 2.0) {
altitude = -PI / 2.0;
}
vector = { x:(Math.cos(altitude) * Math.cos(azimuth)) * radius,
y:Math.sin(altitude) * radius,
@ -165,7 +173,7 @@ function keyReleaseEvent(event) {
}
function mousePressEvent(event) {
if (alt) {
if (alt && !isActive) {
isActive = true;
mouseLastX = event.x;
mouseLastY = event.y;

View file

@ -3252,6 +3252,9 @@ void Application::domainChanged(const QString& domainHostname) {
// reset the particle renderer
_particles.clear();
// reset the voxels renderer
_voxels.killLocalVoxels();
}
void Application::connectedToDomain(const QString& hostname) {

View file

@ -133,7 +133,7 @@ public:
void setShowDisplayName(bool showDisplayName);
int parseDataAtOffset(const QByteArray& packet, int offset);
virtual int parseDataAtOffset(const QByteArray& packet, int offset);
static void renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2, float radius1, float radius2);

View file

@ -551,6 +551,14 @@ void MyAvatar::loadData(QSettings* settings) {
settings->endGroup();
}
int MyAvatar::parseDataAtOffset(const QByteArray& packet, int offset) {
qDebug() << "Error: ignoring update packet for MyAvatar"
<< " packetLength = " << packet.size()
<< " offset = " << offset;
// this packet is just bad, so we pretend that we unpacked it ALL
return packet.size() - offset;
}
void MyAvatar::sendKillAvatar() {
QByteArray killPacket = byteArrayWithPopulatedHeader(PacketTypeKillAvatar);
NodeList::getInstance()->broadcastToNodes(killPacket, NodeSet() << NodeType::AvatarMixer);

View file

@ -72,6 +72,8 @@ public:
bool isMyAvatar() { return true; }
virtual int parseDataAtOffset(const QByteArray& packet, int offset);
static void sendKillAvatar();
void orbit(const glm::vec3& position, int deltaX, int deltaY);

View file

@ -67,7 +67,20 @@ void AudioRingBuffer::updateAverageLoudnessForBoundarySamples(int numSamples) {
nextLoudness /= numSamples;
nextLoudness /= MAX_SAMPLE_VALUE;
const int TRAILING_AVERAGE_FRAMES = 100;
const float CURRENT_FRAME_RATIO = 1.0f / TRAILING_AVERAGE_FRAMES;
const float PREVIOUS_FRAMES_RATIO = 1.0f - CURRENT_FRAME_RATIO;
const float LOUDNESS_EPSILON = 0.01f;
if (nextLoudness >= _averageLoudness) {
_averageLoudness = nextLoudness;
} else {
_averageLoudness = (_averageLoudness * PREVIOUS_FRAMES_RATIO) + (CURRENT_FRAME_RATIO * nextLoudness);
if (_averageLoudness < LOUDNESS_EPSILON) {
_averageLoudness = 0;
}
}
}
qint64 AudioRingBuffer::readSamples(int16_t* destination, qint64 maxSamples) {

View file

@ -12,6 +12,7 @@
#include <QtCore/QDataStream>
#include <QtCore/QThread>
#include <QtCore/QUuid>
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkReply>
#include <QtNetwork/QNetworkRequest>
@ -24,6 +25,8 @@
#include "AvatarData.h"
quint64 DEFAULT_FILTERED_LOG_EXPIRY = 20 * USECS_PER_SECOND;
using namespace std;
QNetworkAccessManager* AvatarData::networkAccessManager = NULL;
@ -42,7 +45,8 @@ AvatarData::AvatarData() :
_displayNameBoundingRect(),
_displayNameTargetAlpha(0.0f),
_displayNameAlpha(0.0f),
_billboard()
_billboard(),
_debugLogExpiry(0)
{
}
@ -176,7 +180,6 @@ QByteArray AvatarData::toByteArray() {
// read data in packet starting at byte offset and return number of bytes parsed
int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) {
// lazily allocate memory for HeadData in case we're not an Avatar instance
if (!_headData) {
_headData = new HeadData(this);
@ -190,19 +193,40 @@ int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) {
const unsigned char* startPosition = reinterpret_cast<const unsigned char*>(packet.data()) + offset;
const unsigned char* sourceBuffer = startPosition;
// Body world position
// 50 bytes of "plain old data" (POD)
// 1 byte for messageSize (0)
// 1 byte for pupilSize
// 1 byte for numJoints (0)
int minPossibleSize = 53;
int maxAvailableSize = packet.size() - offset;
if (minPossibleSize > maxAvailableSize) {
// this packet is malformed so we pretend to read to the end
quint64 now = usecTimestampNow();
if (now > _debugLogExpiry) {
qDebug() << "Malformed AvatarData packet at the start: minPossibleSize = " << minPossibleSize
<< " but maxAvailableSize = " << maxAvailableSize;
_debugLogExpiry = now + DEFAULT_FILTERED_LOG_EXPIRY;
}
return maxAvailableSize;
}
{ // Body world position, rotation, and scale
// position
memcpy(&_position, sourceBuffer, sizeof(float) * 3);
sourceBuffer += sizeof(float) * 3;
// Body rotation (NOTE: This needs to become a quaternion to save two bytes)
// rotation (NOTE: This needs to become a quaternion to save two bytes)
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &_bodyYaw);
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &_bodyPitch);
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &_bodyRoll);
// Body scale
// scale
sourceBuffer += unpackFloatRatioFromTwoByte(sourceBuffer, _targetScale);
} // 20 bytes
// Head rotation (NOTE: This needs to become a quaternion to save two bytes)
{ // Head rotation
//(NOTE: This needs to become a quaternion to save two bytes)
float headYaw, headPitch, headRoll;
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headYaw);
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headPitch);
@ -210,27 +234,46 @@ int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) {
_headData->setYaw(headYaw);
_headData->setPitch(headPitch);
_headData->setRoll(headRoll);
} // 6 bytes
// Head position relative to pelvis
// Head lean (relative to pelvis)
{
memcpy(&_headData->_leanSideways, sourceBuffer, sizeof(_headData->_leanSideways));
sourceBuffer += sizeof(float);
memcpy(&_headData->_leanForward, sourceBuffer, sizeof(_headData->_leanForward));
sourceBuffer += sizeof(_headData->_leanForward);
sourceBuffer += sizeof(float);
} // 8 bytes
// Lookat Position
{ // Lookat Position
memcpy(&_headData->_lookAtPosition, sourceBuffer, sizeof(_headData->_lookAtPosition));
sourceBuffer += sizeof(_headData->_lookAtPosition);
} // 12 bytes
{ // AudioLoudness
// Instantaneous audio loudness (used to drive facial animation)
memcpy(&_headData->_audioLoudness, sourceBuffer, sizeof(float));
sourceBuffer += sizeof(float);
} // 4 bytes
// the rest is a chat message
// chat
int chatMessageSize = *sourceBuffer++;
minPossibleSize += chatMessageSize;
if (minPossibleSize > maxAvailableSize) {
// this packet is malformed so we pretend to read to the end
quint64 now = usecTimestampNow();
if (now > _debugLogExpiry) {
qDebug() << "Malformed AvatarData packet before ChatMessage: minPossibleSize = " << minPossibleSize
<< " but maxAvailableSize = " << maxAvailableSize;
_debugLogExpiry = now + DEFAULT_FILTERED_LOG_EXPIRY;
}
return maxAvailableSize;
}
{ // chat payload
_chatMessage = string((char*)sourceBuffer, chatMessageSize);
sourceBuffer += chatMessageSize * sizeof(char);
} // 1 + chatMessageSize bytes
// voxel sending features...
{ // bitFlags and face data
unsigned char bitItems = 0;
bitItems = (unsigned char)*sourceBuffer++;
@ -241,11 +284,21 @@ int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) {
_handState = getSemiNibbleAt(bitItems,HAND_STATE_START_BIT);
_headData->_isFaceshiftConnected = oneAtBit(bitItems, IS_FACESHIFT_CONNECTED);
_isChatCirclingEnabled = oneAtBit(bitItems, IS_CHAT_CIRCLING_ENABLED);
// If it is connected, pack up the data
if (_headData->_isFaceshiftConnected) {
minPossibleSize += 4 * sizeof(float) + 1; // four floats + one byte for blendDataSize
if (minPossibleSize > maxAvailableSize) {
// this packet is malformed so we pretend to read to the end
quint64 now = usecTimestampNow();
if (now > _debugLogExpiry) {
qDebug() << "Malformed AvatarData packet after BitItems: minPossibleSize = " << minPossibleSize
<< " but maxAvailableSize = " << maxAvailableSize;
_debugLogExpiry = now + DEFAULT_FILTERED_LOG_EXPIRY;
}
return maxAvailableSize;
}
// unpack face data
memcpy(&_headData->_leftEyeBlink, sourceBuffer, sizeof(float));
sourceBuffer += sizeof(float);
@ -258,33 +311,85 @@ int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) {
memcpy(&_headData->_browAudioLift, sourceBuffer, sizeof(float));
sourceBuffer += sizeof(float);
_headData->_blendshapeCoefficients.resize(*sourceBuffer++);
memcpy(_headData->_blendshapeCoefficients.data(), sourceBuffer,
_headData->_blendshapeCoefficients.size() * sizeof(float));
sourceBuffer += _headData->_blendshapeCoefficients.size() * sizeof(float);
int numCoefficients = (int)(*sourceBuffer++);
int blendDataSize = numCoefficients * sizeof(float);
minPossibleSize += blendDataSize;
if (minPossibleSize > maxAvailableSize) {
// this packet is malformed so we pretend to read to the end
quint64 now = usecTimestampNow();
if (now > _debugLogExpiry) {
qDebug() << "Malformed AvatarData packet after Blendshapes: minPossibleSize = " << minPossibleSize
<< " but maxAvailableSize = " << maxAvailableSize;
_debugLogExpiry = now + DEFAULT_FILTERED_LOG_EXPIRY;
}
return maxAvailableSize;
}
// pupil dilation
_headData->_blendshapeCoefficients.resize(numCoefficients);
memcpy(_headData->_blendshapeCoefficients.data(), sourceBuffer, blendDataSize);
sourceBuffer += numCoefficients * sizeof(float);
//bitItemsDataSize = 4 * sizeof(float) + 1 + blendDataSize;
}
} // 1 + bitItemsDataSize bytes
{ // pupil dilation
sourceBuffer += unpackFloatFromByte(sourceBuffer, _headData->_pupilDilation, 1.0f);
} // 1 byte
// joint data
int jointCount = *sourceBuffer++;
_jointData.resize(jointCount);
int numJoints = *sourceBuffer++;
int bytesOfValidity = (int)ceil((float)numJoints / 8.f);
minPossibleSize += bytesOfValidity;
if (minPossibleSize > maxAvailableSize) {
// this packet is malformed so we pretend to read to the end
quint64 now = usecTimestampNow();
if (now > _debugLogExpiry) {
qDebug() << "Malformed AvatarData packet after JointValidityBits: minPossibleSize = " << minPossibleSize
<< " but maxAvailableSize = " << maxAvailableSize;
_debugLogExpiry = now + DEFAULT_FILTERED_LOG_EXPIRY;
}
return maxAvailableSize;
}
int numValidJoints = 0;
_jointData.resize(numJoints);
{ // validity bits
unsigned char validity = 0;
int validityBit = 0;
for (int i = 0; i < jointCount; i++) {
for (int i = 0; i < numJoints; i++) {
if (validityBit == 0) {
validity = *sourceBuffer++;
}
_jointData[i].valid = (bool)(validity & (1 << validityBit));
bool valid = (bool)(validity & (1 << validityBit));
if (valid) {
++numValidJoints;
}
_jointData[i].valid = valid;
validityBit = (validityBit + 1) % BITS_IN_BYTE;
}
for (int i = 0; i < jointCount; i++) {
}
// 1 + bytesOfValidity bytes
minPossibleSize += numValidJoints * 8; // 8 bytes per quaternion
if (minPossibleSize > maxAvailableSize) {
// this packet is malformed so we pretend to read to the end
quint64 now = usecTimestampNow();
if (now > _debugLogExpiry) {
qDebug() << "Malformed AvatarData packet after JointData: minPossibleSize = " << minPossibleSize
<< " but maxAvailableSize = " << maxAvailableSize;
_debugLogExpiry = now + DEFAULT_FILTERED_LOG_EXPIRY;
}
return maxAvailableSize;
}
{ // joint data
for (int i = 0; i < numJoints; i++) {
JointData& data = _jointData[i];
if (data.valid) {
sourceBuffer += unpackOrientationQuatFromBytes(sourceBuffer, data.rotation);
}
}
} // numJoints * 8 bytes
return sourceBuffer - startPosition;
}

View file

@ -33,7 +33,6 @@ typedef unsigned long long quint64;
#include <QtCore/QObject>
#include <QtCore/QStringList>
#include <QtCore/QUrl>
#include <QtCore/QUuid>
#include <QtCore/QVector>
#include <QtCore/QVariantMap>
#include <QRect>
@ -256,6 +255,8 @@ protected:
static QNetworkAccessManager* networkAccessManager;
quint64 _debugLogExpiry;
private:
// privatize the copy constructor and assignment operator so they cannot be called
AvatarData(const AvatarData&);

View file

@ -308,9 +308,18 @@ int NodeList::findNodeAndUpdateWithDataFromPacket(const QByteArray& packet) {
return 0;
}
SharedNodePointer NodeList::nodeWithUUID(const QUuid& nodeUUID) {
SharedNodePointer NodeList::nodeWithUUID(const QUuid& nodeUUID, bool blockingLock) {
SharedNodePointer node;
// if caller wants us to block and guarantee the correct answer, then honor that request
if (blockingLock) {
// this will block till we can get access
QMutexLocker locker(&_nodeHashMutex);
return _nodeHash.value(nodeUUID);
node = _nodeHash.value(nodeUUID);
} else if (_nodeHashMutex.tryLock()) { // some callers are willing to get wrong answers but not block
node = _nodeHash.value(nodeUUID);
_nodeHashMutex.unlock();
}
return node;
}
SharedNodePointer NodeList::sendingNodeForPacket(const QByteArray& packet) {

View file

@ -101,7 +101,8 @@ public:
QByteArray constructPingReplyPacket(const QByteArray& pingPacket);
void pingPublicAndLocalSocketsForInactiveNode(const SharedNodePointer& node);
SharedNodePointer nodeWithUUID(const QUuid& nodeUUID);
/// passing false for blockingLock, will tryLock, and may return NULL when a node with the UUID actually does exist
SharedNodePointer nodeWithUUID(const QUuid& nodeUUID, bool blockingLock = true);
SharedNodePointer sendingNodeForPacket(const QByteArray& packet);
SharedNodePointer addOrUpdateNode(const QUuid& uuid, char nodeType,