mirror of
https://github.com/overte-org/overte.git
synced 2025-08-04 05:03:31 +02:00
Merge branch 'master' of https://github.com/worklist/hifi into 19508
This commit is contained in:
commit
beb8fb5cc7
22 changed files with 441 additions and 215 deletions
|
@ -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!
|
||||
|
|
|
@ -189,4 +189,9 @@ void Agent::run() {
|
|||
|
||||
_scriptEngine.setScriptContents(scriptContents);
|
||||
_scriptEngine.run();
|
||||
setFinished(true);
|
||||
}
|
||||
|
||||
void Agent::aboutToFinish() {
|
||||
_scriptEngine.stop();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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__) */
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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...
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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__
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -71,6 +71,8 @@ public:
|
|||
void jump() { _shouldJump = true; };
|
||||
|
||||
bool isMyAvatar() { return true; }
|
||||
|
||||
virtual int parseDataAtOffset(const QByteArray& packet, int offset);
|
||||
|
||||
static void sendKillAvatar();
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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&);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue