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

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

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,10 +81,24 @@ 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);
float radius = 0.0f;
@ -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,19 +371,71 @@ 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()
@ -382,6 +448,8 @@ void AudioMixer::run() {
nodeList->writeDatagram(clientMixBuffer, NETWORK_BUFFER_LENGTH_BYTES_STEREO + numBytesPacketHeader, node);
}
}
_numClientsMixedInFrame = 0;
// push forward the next output pointers for any audio buffers we used
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
@ -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

@ -23,6 +23,8 @@ class OctreeSendThread : public GenericThread {
public:
OctreeSendThread(const QUuid& nodeUUID, OctreeServer* myServer);
virtual ~OctreeSendThread();
void setIsShuttingDown();
static quint64 _totalBytes;
static quint64 _totalWastedBytes;
@ -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();
}
trackLastMouseX = event.x;
trackLastMouseY = event.y;
trackAsDelete = event.isControl;
trackAsRecolor = event.isShifted;
trackAsEyedropper = event.isMeta;
showPreviewGuides();
}
function trackKeyPressEvent(event) {
@ -742,6 +735,7 @@ function trackKeyReleaseEvent(event) {
if (event.text == "TAB") {
editToolsOn = !editToolsOn;
moveTools();
showPreviewGuides();
Audio.playSound(clickSound, audioOptions);
}
@ -788,67 +782,64 @@ 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});
// If the user clicked on the thumb, handle the slider logic
if (clickedOverlay == thumb) {
isMovingSlider = true;
thumbClickOffsetX = event.x - (sliderX + thumbX); // this should be the position of the mouse relative to the thumb
clickedOnSomething = true;
Overlays.editOverlay(thumb, { imageURL: toolIconUrl + "voxel-size-slider-handle.svg", });
} else if (clickedOverlay == voxelTool) {
voxelToolSelected = true;
recolorToolSelected = false;
eyedropperToolSelected = false;
moveTools();
clickedOnSomething = true;
} else if (clickedOverlay == recolorTool) {
voxelToolSelected = false;
recolorToolSelected = true;
eyedropperToolSelected = false;
moveTools();
clickedOnSomething = true;
} else if (clickedOverlay == eyedropperTool) {
voxelToolSelected = false;
recolorToolSelected = false;
eyedropperToolSelected = true;
moveTools();
clickedOnSomething = true;
} else if (clickedOverlay == slider) {
if (event.x < sliderX + minThumbX) {
thumbX -= thumbDeltaPerStep;
calcScaleFromThumb(thumbX);
}
if (event.x > sliderX + maxThumbX) {
thumbX += thumbDeltaPerStep;
calcScaleFromThumb(thumbX);
}
moveTools();
clickedOnSomething = true;
} else {
// if the user clicked on one of the color swatches, update the selectedSwatch
for (s = 0; s < numColors; s++) {
if (clickedOverlay == swatches[s]) {
whichColor = s;
moveTools();
clickedOnSomething = true;
break;
}
}
var clickedOnSomething = false;
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
// If the user clicked on the thumb, handle the slider logic
if (clickedOverlay == thumb) {
isMovingSlider = true;
thumbClickOffsetX = event.x - (sliderX + thumbX); // this should be the position of the mouse relative to the thumb
clickedOnSomething = true;
Overlays.editOverlay(thumb, { imageURL: toolIconUrl + "voxel-size-slider-handle.svg", });
} else if (clickedOverlay == voxelTool) {
voxelToolSelected = true;
recolorToolSelected = false;
eyedropperToolSelected = false;
moveTools();
clickedOnSomething = true;
} else if (clickedOverlay == recolorTool) {
voxelToolSelected = false;
recolorToolSelected = true;
eyedropperToolSelected = false;
moveTools();
clickedOnSomething = true;
} else if (clickedOverlay == eyedropperTool) {
voxelToolSelected = false;
recolorToolSelected = false;
eyedropperToolSelected = true;
moveTools();
clickedOnSomething = true;
} else if (clickedOverlay == slider) {
if (event.x < sliderX + minThumbX) {
thumbX -= thumbDeltaPerStep;
calcScaleFromThumb(thumbX);
}
if (clickedOnSomething) {
return; // no further processing
if (event.x > sliderX + maxThumbX) {
thumbX += thumbDeltaPerStep;
calcScaleFromThumb(thumbX);
}
moveTools();
clickedOnSomething = true;
} else {
// if the user clicked on one of the color swatches, update the selectedSwatch
for (s = 0; s < numColors; s++) {
if (clickedOverlay == swatches[s]) {
whichColor = s;
moveTools();
clickedOnSomething = true;
break;
}
}
}
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
mouseX = event.x;
@ -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

@ -71,6 +71,8 @@ public:
void jump() { _shouldJump = true; };
bool isMyAvatar() { return true; }
virtual int parseDataAtOffset(const QByteArray& packet, int offset);
static void sendKillAvatar();

View file

@ -67,7 +67,20 @@ void AudioRingBuffer::updateAverageLoudnessForBoundarySamples(int numSamples) {
nextLoudness /= numSamples;
nextLoudness /= MAX_SAMPLE_VALUE;
_averageLoudness = nextLoudness;
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);
@ -189,102 +192,204 @@ 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;
// 50 bytes of "plain old data" (POD)
// 1 byte for messageSize (0)
// 1 byte for pupilSize
// 1 byte for numJoints (0)
int minPossibleSize = 53;
// Body world position
memcpy(&_position, sourceBuffer, sizeof(float) * 3);
sourceBuffer += sizeof(float) * 3;
// Body 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
sourceBuffer += unpackFloatRatioFromTwoByte(sourceBuffer, _targetScale);
// 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);
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headRoll);
_headData->setYaw(headYaw);
_headData->setPitch(headPitch);
_headData->setRoll(headRoll);
// Head position relative to pelvis
memcpy(&_headData->_leanSideways, sourceBuffer, sizeof(_headData->_leanSideways));
sourceBuffer += sizeof(float);
memcpy(&_headData->_leanForward, sourceBuffer, sizeof(_headData->_leanForward));
sourceBuffer += sizeof(_headData->_leanForward);
// Lookat Position
memcpy(&_headData->_lookAtPosition, sourceBuffer, sizeof(_headData->_lookAtPosition));
sourceBuffer += sizeof(_headData->_lookAtPosition);
// Instantaneous audio loudness (used to drive facial animation)
memcpy(&_headData->_audioLoudness, sourceBuffer, sizeof(float));
sourceBuffer += sizeof(float);
// the rest is a chat message
int chatMessageSize = *sourceBuffer++;
_chatMessage = string((char*)sourceBuffer, chatMessageSize);
sourceBuffer += chatMessageSize * sizeof(char);
// voxel sending features...
unsigned char bitItems = 0;
bitItems = (unsigned char)*sourceBuffer++;
// key state, stored as a semi-nibble in the bitItems
_keyState = (KeyState)getSemiNibbleAt(bitItems,KEY_STATE_START_BIT);
// hand state, stored as a semi-nibble in the bitItems
_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) {
memcpy(&_headData->_leftEyeBlink, sourceBuffer, sizeof(float));
sourceBuffer += sizeof(float);
memcpy(&_headData->_rightEyeBlink, sourceBuffer, sizeof(float));
sourceBuffer += sizeof(float);
memcpy(&_headData->_averageLoudness, sourceBuffer, sizeof(float));
sourceBuffer += sizeof(float);
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 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;
// 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);
// scale
sourceBuffer += unpackFloatRatioFromTwoByte(sourceBuffer, _targetScale);
} // 20 bytes
// pupil dilation
sourceBuffer += unpackFloatFromByte(sourceBuffer, _headData->_pupilDilation, 1.0f);
{ // 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);
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headRoll);
_headData->setYaw(headYaw);
_headData->setPitch(headPitch);
_headData->setRoll(headRoll);
} // 6 bytes
// Head lean (relative to pelvis)
{
memcpy(&_headData->_leanSideways, sourceBuffer, sizeof(_headData->_leanSideways));
sourceBuffer += sizeof(float);
memcpy(&_headData->_leanForward, sourceBuffer, sizeof(_headData->_leanForward));
sourceBuffer += sizeof(float);
} // 8 bytes
{ // 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
// 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
{ // bitFlags and face data
unsigned char bitItems = 0;
bitItems = (unsigned char)*sourceBuffer++;
// key state, stored as a semi-nibble in the bitItems
_keyState = (KeyState)getSemiNibbleAt(bitItems,KEY_STATE_START_BIT);
// hand state, stored as a semi-nibble in the bitItems
_handState = getSemiNibbleAt(bitItems,HAND_STATE_START_BIT);
_headData->_isFaceshiftConnected = oneAtBit(bitItems, IS_FACESHIFT_CONNECTED);
_isChatCirclingEnabled = oneAtBit(bitItems, IS_CHAT_CIRCLING_ENABLED);
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);
memcpy(&_headData->_rightEyeBlink, sourceBuffer, sizeof(float));
sourceBuffer += sizeof(float);
memcpy(&_headData->_averageLoudness, sourceBuffer, sizeof(float));
sourceBuffer += sizeof(float);
memcpy(&_headData->_browAudioLift, sourceBuffer, sizeof(float));
sourceBuffer += 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;
}
_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);
unsigned char validity = 0;
int validityBit = 0;
for (int i = 0; i < jointCount; i++) {
if (validityBit == 0) {
validity = *sourceBuffer++;
}
_jointData[i].valid = (bool)(validity & (1 << validityBit));
validityBit = (validityBit + 1) % BITS_IN_BYTE;
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;
}
for (int i = 0; i < jointCount; i++) {
JointData& data = _jointData[i];
if (data.valid) {
sourceBuffer += unpackOrientationQuatFromBytes(sourceBuffer, data.rotation);
int numValidJoints = 0;
_jointData.resize(numJoints);
{ // validity bits
unsigned char validity = 0;
int validityBit = 0;
for (int i = 0; i < numJoints; i++) {
if (validityBit == 0) {
validity = *sourceBuffer++;
}
bool valid = (bool)(validity & (1 << validityBit));
if (valid) {
++numValidJoints;
}
_jointData[i].valid = valid;
validityBit = (validityBit + 1) % BITS_IN_BYTE;
}
}
// 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) {
QMutexLocker locker(&_nodeHashMutex);
return _nodeHash.value(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);
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,