merge fix

This commit is contained in:
Philip Rosedale 2014-07-11 20:26:58 -05:00
commit b0e44be1c0
65 changed files with 2027 additions and 542 deletions

View file

@ -405,7 +405,8 @@ void AudioMixer::readPendingDatagrams() {
if (mixerPacketType == PacketTypeMicrophoneAudioNoEcho
|| mixerPacketType == PacketTypeMicrophoneAudioWithEcho
|| mixerPacketType == PacketTypeInjectAudio
|| mixerPacketType == PacketTypeSilentAudioFrame) {
|| mixerPacketType == PacketTypeSilentAudioFrame
|| mixerPacketType == PacketTypeAudioStreamStats) {
nodeList->findNodeAndUpdateWithDataFromPacket(receivedPacket);
} else if (mixerPacketType == PacketTypeMuteEnvironment) {
@ -640,9 +641,6 @@ void AudioMixer::run() {
++framesSinceCutoffEvent;
}
const quint64 TOO_LONG_SINCE_LAST_SEND_AUDIO_STREAM_STATS = 1 * USECS_PER_SECOND;
bool sendAudioStreamStats = false;
quint64 now = usecTimestampNow();
if (now - _lastSendAudioStreamStatsTime > TOO_LONG_SINCE_LAST_SEND_AUDIO_STREAM_STATS) {

View file

@ -21,6 +21,8 @@ class AvatarAudioRingBuffer;
const int SAMPLE_PHASE_DELAY_AT_90 = 20;
const quint64 TOO_LONG_SINCE_LAST_SEND_AUDIO_STREAM_STATS = 1 * USECS_PER_SECOND;
/// Handles assignments of type AudioMixer - mixing streams of audio and re-distributing to various clients.
class AudioMixer : public ThreadedAssignment {
Q_OBJECT

View file

@ -18,11 +18,15 @@
#include "AudioMixer.h"
#include "AudioMixerClientData.h"
#include "MovingMinMaxAvg.h"
const int INCOMING_SEQ_STATS_HISTORY_LENGTH = INCOMING_SEQ_STATS_HISTORY_LENGTH_SECONDS /
(TOO_LONG_SINCE_LAST_SEND_AUDIO_STREAM_STATS / USECS_PER_SECOND);
AudioMixerClientData::AudioMixerClientData() :
_ringBuffers(),
_outgoingMixedAudioSequenceNumber(0),
_incomingAvatarAudioSequenceNumberStats()
_incomingAvatarAudioSequenceNumberStats(INCOMING_SEQ_STATS_HISTORY_LENGTH)
{
}
@ -82,12 +86,15 @@ int AudioMixerClientData::parseData(const QByteArray& packet) {
// ask the AvatarAudioRingBuffer instance to parse the data
avatarRingBuffer->parseData(packet);
} else {
} else if (packetType == PacketTypeInjectAudio) {
// this is injected audio
// grab the stream identifier for this injected audio
QUuid streamIdentifier = QUuid::fromRfc4122(packet.mid(numBytesForPacketHeader(packet) + sizeof(quint16), NUM_BYTES_RFC4122_UUID));
if (!_incomingInjectedAudioSequenceNumberStatsMap.contains(streamIdentifier)) {
_incomingInjectedAudioSequenceNumberStatsMap.insert(streamIdentifier, SequenceNumberStats(INCOMING_SEQ_STATS_HISTORY_LENGTH));
}
_incomingInjectedAudioSequenceNumberStatsMap[streamIdentifier].sequenceNumberReceived(sequence);
InjectedAudioRingBuffer* matchingInjectedRingBuffer = NULL;
@ -106,6 +113,15 @@ int AudioMixerClientData::parseData(const QByteArray& packet) {
}
matchingInjectedRingBuffer->parseData(packet);
} else if (packetType == PacketTypeAudioStreamStats) {
const char* dataAt = packet.data();
// skip over header, appendFlag, and num stats packed
dataAt += (numBytesPacketHeader + sizeof(quint8) + sizeof(quint16));
// read the downstream audio stream stats
memcpy(&_downstreamAudioStreamStats, dataAt, sizeof(AudioStreamStats));
}
return 0;
@ -159,31 +175,51 @@ void AudioMixerClientData::pushBuffersAfterFrameSend() {
}
AudioStreamStats AudioMixerClientData::getAudioStreamStatsOfStream(const PositionalAudioRingBuffer* ringBuffer) const {
AudioStreamStats streamStats;
SequenceNumberStats streamSequenceNumberStats;
streamStats._streamType = ringBuffer->getType();
if (streamStats._streamType == PositionalAudioRingBuffer::Injector) {
streamStats._streamIdentifier = ((InjectedAudioRingBuffer*)ringBuffer)->getStreamIdentifier();
streamSequenceNumberStats = _incomingInjectedAudioSequenceNumberStatsMap.value(streamStats._streamIdentifier);
const SequenceNumberStats& sequenceNumberStats = _incomingInjectedAudioSequenceNumberStatsMap[streamStats._streamIdentifier];
streamStats._packetStreamStats = sequenceNumberStats.getStats();
streamStats._packetStreamWindowStats = sequenceNumberStats.getStatsForHistoryWindow();
} else {
streamSequenceNumberStats = _incomingAvatarAudioSequenceNumberStats;
streamStats._packetStreamStats = _incomingAvatarAudioSequenceNumberStats.getStats();
streamStats._packetStreamWindowStats = _incomingAvatarAudioSequenceNumberStats.getStatsForHistoryWindow();
}
streamStats._jitterBufferFrames = ringBuffer->getCurrentJitterBufferFrames();
streamStats._packetsReceived = streamSequenceNumberStats.getNumReceived();
streamStats._packetsUnreasonable = streamSequenceNumberStats.getNumUnreasonable();
streamStats._packetsEarly = streamSequenceNumberStats.getNumEarly();
streamStats._packetsLate = streamSequenceNumberStats.getNumLate();
streamStats._packetsLost = streamSequenceNumberStats.getNumLost();
streamStats._packetsRecovered = streamSequenceNumberStats.getNumRecovered();
streamStats._packetsDuplicate = streamSequenceNumberStats.getNumDuplicate();
const MovingMinMaxAvg<quint64>& timeGapStats = ringBuffer->getInterframeTimeGapStatsForStatsPacket();
streamStats._timeGapMin = timeGapStats.getMin();
streamStats._timeGapMax = timeGapStats.getMax();
streamStats._timeGapAverage = timeGapStats.getAverage();
streamStats._timeGapWindowMin = timeGapStats.getWindowMin();
streamStats._timeGapWindowMax = timeGapStats.getWindowMax();
streamStats._timeGapWindowAverage = timeGapStats.getWindowAverage();
streamStats._ringBufferFramesAvailable = ringBuffer->framesAvailable();
streamStats._ringBufferCurrentJitterBufferFrames = ringBuffer->getCurrentJitterBufferFrames();
streamStats._ringBufferDesiredJitterBufferFrames = ringBuffer->getDesiredJitterBufferFrames();
streamStats._ringBufferStarveCount = ringBuffer->getStarveCount();
streamStats._ringBufferConsecutiveNotMixedCount = ringBuffer->getConsecutiveNotMixedCount();
streamStats._ringBufferOverflowCount = ringBuffer->getOverflowCount();
streamStats._ringBufferSilentFramesDropped = ringBuffer->getSilentFramesDropped();
return streamStats;
}
void AudioMixerClientData::sendAudioStreamStatsPackets(const SharedNodePointer& destinationNode) const {
void AudioMixerClientData::sendAudioStreamStatsPackets(const SharedNodePointer& destinationNode) {
// have all the seq number stats of each audio stream push their current stats into their history,
// which moves that history window 1 second forward (since that's how long since the last stats were pushed into history)
_incomingAvatarAudioSequenceNumberStats.pushStatsToHistory();
QHash<QUuid, SequenceNumberStats>::Iterator i = _incomingInjectedAudioSequenceNumberStatsMap.begin();
QHash<QUuid, SequenceNumberStats>::Iterator end = _incomingInjectedAudioSequenceNumberStatsMap.end();
while (i != end) {
i.value().pushStatsToHistory();
i++;
}
char packet[MAX_PACKET_SIZE];
NodeList* nodeList = NodeList::getInstance();
@ -234,46 +270,63 @@ void AudioMixerClientData::sendAudioStreamStatsPackets(const SharedNodePointer&
QString AudioMixerClientData::getAudioStreamStatsString() const {
QString result;
AudioStreamStats streamStats = _downstreamAudioStreamStats;
result += "DOWNSTREAM.desired:" + QString::number(streamStats._ringBufferDesiredJitterBufferFrames)
+ " current: ?"
+ " available:" + QString::number(streamStats._ringBufferFramesAvailable)
+ " starves:" + QString::number(streamStats._ringBufferStarveCount)
+ " not_mixed:" + QString::number(streamStats._ringBufferConsecutiveNotMixedCount)
+ " overflows:" + QString::number(streamStats._ringBufferOverflowCount)
+ " silents_dropped: ?"
+ " lost%:" + QString::number(streamStats._packetStreamStats.getLostRate() * 100.0f, 'f', 2)
+ " lost%_30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate() * 100.0f, 'f', 2)
+ " min_gap:" + QString::number(streamStats._timeGapMin)
+ " max_gap:" + QString::number(streamStats._timeGapMax)
+ " avg_gap:" + QString::number(streamStats._timeGapAverage, 'f', 2)
+ " min_gap_30s:" + QString::number(streamStats._timeGapWindowMin)
+ " max_gap_30s:" + QString::number(streamStats._timeGapWindowMax)
+ " avg_gap_30s:" + QString::number(streamStats._timeGapWindowAverage, 'f', 2);
AvatarAudioRingBuffer* avatarRingBuffer = getAvatarAudioRingBuffer();
if (avatarRingBuffer) {
int desiredJitterBuffer = avatarRingBuffer->getDesiredJitterBufferFrames();
int calculatedJitterBuffer = avatarRingBuffer->getCalculatedDesiredJitterBufferFrames();
int currentJitterBuffer = avatarRingBuffer->getCurrentJitterBufferFrames();
int overflowCount = avatarRingBuffer->getOverflowCount();
int samplesAvailable = avatarRingBuffer->samplesAvailable();
int framesAvailable = (samplesAvailable / avatarRingBuffer->getSamplesPerFrame());
AudioStreamStats streamStats = getAudioStreamStatsOfStream(avatarRingBuffer);
result += "mic.desired:" + QString::number(desiredJitterBuffer)
+ " calculated:" + QString::number(calculatedJitterBuffer)
+ " current:" + QString::number(currentJitterBuffer)
+ " available:" + QString::number(framesAvailable)
+ " samples:" + QString::number(samplesAvailable)
+ " overflows:" + QString::number(overflowCount)
+ " early:" + QString::number(streamStats._packetsEarly)
+ " late:" + QString::number(streamStats._packetsLate)
+ " lost:" + QString::number(streamStats._packetsLost);
result += " UPSTREAM.mic.desired:" + QString::number(streamStats._ringBufferDesiredJitterBufferFrames)
+ " current:" + QString::number(streamStats._ringBufferCurrentJitterBufferFrames)
+ " available:" + QString::number(streamStats._ringBufferFramesAvailable)
+ " starves:" + QString::number(streamStats._ringBufferStarveCount)
+ " not_mixed:" + QString::number(streamStats._ringBufferConsecutiveNotMixedCount)
+ " overflows:" + QString::number(streamStats._ringBufferOverflowCount)
+ " silents_dropped:" + QString::number(streamStats._ringBufferSilentFramesDropped)
+ " lost%:" + QString::number(streamStats._packetStreamStats.getLostRate() * 100.0f, 'f', 2)
+ " lost%_30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate() * 100.0f, 'f', 2)
+ " min_gap:" + QString::number(streamStats._timeGapMin)
+ " max_gap:" + QString::number(streamStats._timeGapMax)
+ " avg_gap:" + QString::number(streamStats._timeGapAverage, 'f', 2)
+ " min_gap_30s:" + QString::number(streamStats._timeGapWindowMin)
+ " max_gap_30s:" + QString::number(streamStats._timeGapWindowMax)
+ " avg_gap_30s:" + QString::number(streamStats._timeGapWindowAverage, 'f', 2);
} else {
result = "mic unknown";
}
for (int i = 0; i < _ringBuffers.size(); i++) {
if (_ringBuffers[i]->getType() == PositionalAudioRingBuffer::Injector) {
int desiredJitterBuffer = _ringBuffers[i]->getDesiredJitterBufferFrames();
int calculatedJitterBuffer = _ringBuffers[i]->getCalculatedDesiredJitterBufferFrames();
int currentJitterBuffer = _ringBuffers[i]->getCurrentJitterBufferFrames();
int overflowCount = _ringBuffers[i]->getOverflowCount();
int samplesAvailable = _ringBuffers[i]->samplesAvailable();
int framesAvailable = (samplesAvailable / _ringBuffers[i]->getSamplesPerFrame());
AudioStreamStats streamStats = getAudioStreamStatsOfStream(_ringBuffers[i]);
result += "| injected[" + QString::number(i) + "].desired:" + QString::number(desiredJitterBuffer)
+ " calculated:" + QString::number(calculatedJitterBuffer)
+ " current:" + QString::number(currentJitterBuffer)
+ " available:" + QString::number(framesAvailable)
+ " samples:" + QString::number(samplesAvailable)
+ " overflows:" + QString::number(overflowCount)
+ " early:" + QString::number(streamStats._packetsEarly)
+ " late:" + QString::number(streamStats._packetsLate)
+ " lost:" + QString::number(streamStats._packetsLost);
result += " UPSTREAM.inj.desired:" + QString::number(streamStats._ringBufferDesiredJitterBufferFrames)
+ " current:" + QString::number(streamStats._ringBufferCurrentJitterBufferFrames)
+ " available:" + QString::number(streamStats._ringBufferFramesAvailable)
+ " starves:" + QString::number(streamStats._ringBufferStarveCount)
+ " not_mixed:" + QString::number(streamStats._ringBufferConsecutiveNotMixedCount)
+ " overflows:" + QString::number(streamStats._ringBufferOverflowCount)
+ " silents_dropped:" + QString::number(streamStats._ringBufferSilentFramesDropped)
+ " lost%:" + QString::number(streamStats._packetStreamStats.getLostRate() * 100.0f, 'f', 2)
+ " lost%_30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate() * 100.0f, 'f', 2)
+ " min_gap:" + QString::number(streamStats._timeGapMin)
+ " max_gap:" + QString::number(streamStats._timeGapMax)
+ " avg_gap:" + QString::number(streamStats._timeGapAverage, 'f', 2)
+ " min_gap_30s:" + QString::number(streamStats._timeGapWindowMin)
+ " max_gap_30s:" + QString::number(streamStats._timeGapWindowMax)
+ " avg_gap_30s:" + QString::number(streamStats._timeGapWindowAverage, 'f', 2);
}
}
return result;

View file

@ -20,6 +20,9 @@
#include "AudioStreamStats.h"
#include "SequenceNumberStats.h"
const int INCOMING_SEQ_STATS_HISTORY_LENGTH_SECONDS = 30;
class AudioMixerClientData : public NodeData {
public:
AudioMixerClientData();
@ -35,7 +38,7 @@ public:
AudioStreamStats getAudioStreamStatsOfStream(const PositionalAudioRingBuffer* ringBuffer) const;
QString getAudioStreamStatsString() const;
void sendAudioStreamStatsPackets(const SharedNodePointer& destinationNode) const;
void sendAudioStreamStatsPackets(const SharedNodePointer& destinationNode);
void incrementOutgoingMixedAudioSequenceNumber() { _outgoingMixedAudioSequenceNumber++; }
quint16 getOutgoingSequenceNumber() const { return _outgoingMixedAudioSequenceNumber; }
@ -46,6 +49,8 @@ private:
quint16 _outgoingMixedAudioSequenceNumber;
SequenceNumberStats _incomingAvatarAudioSequenceNumberStats;
QHash<QUuid, SequenceNumberStats> _incomingInjectedAudioSequenceNumberStatsMap;
AudioStreamStats _downstreamAudioStreamStats;
};
#endif // hifi_AudioMixerClientData_h

View file

@ -19,7 +19,7 @@ AvatarAudioRingBuffer::AvatarAudioRingBuffer(bool isStereo, bool dynamicJitterBu
}
int AvatarAudioRingBuffer::parseData(const QByteArray& packet) {
_interframeTimeGapStats.frameReceived();
timeGapStatsFrameReceived();
updateDesiredJitterBufferFrames();
_shouldLoopbackForNode = (packetTypeForPacket(packet) == PacketTypeMicrophoneAudioWithEcho);

View file

@ -10,6 +10,9 @@
//
#include <QDateTime>
#include <QFile>
#include <QSaveFile>
#include <QThread>
#include <PacketHeaders.h>
@ -44,6 +47,18 @@ void MetavoxelServer::run() {
_lastSend = QDateTime::currentMSecsSinceEpoch();
_sendTimer.start(SEND_INTERVAL);
// initialize Bitstream before using it in multiple threads
Bitstream::preThreadingInit();
// create the persister and start it in its own thread
_persister = new MetavoxelPersister(this);
QThread* persistenceThread = new QThread(this);
_persister->moveToThread(persistenceThread);
persistenceThread->start();
// queue up the load
QMetaObject::invokeMethod(_persister, "load");
}
void MetavoxelServer::readPendingDatagrams() {
@ -67,6 +82,12 @@ void MetavoxelServer::readPendingDatagrams() {
}
}
void MetavoxelServer::aboutToFinish() {
QMetaObject::invokeMethod(_persister, "save", Q_ARG(const MetavoxelData&, _data));
_persister->thread()->quit();
_persister->thread()->wait();
}
void MetavoxelServer::maybeAttachSession(const SharedNodePointer& node) {
if (node->getType() == NodeType::Agent) {
QMutexLocker locker(&node->getMutex());
@ -193,3 +214,44 @@ void MetavoxelSession::sendPacketGroup(int alreadySent) {
_sequencer.endPacket();
}
}
MetavoxelPersister::MetavoxelPersister(MetavoxelServer* server) :
_server(server) {
}
const char* SAVE_FILE = "metavoxels.dat";
void MetavoxelPersister::load() {
QFile file(SAVE_FILE);
if (!file.exists()) {
return;
}
MetavoxelData data;
{
QDebug debug = qDebug() << "Reading from" << SAVE_FILE << "...";
file.open(QIODevice::ReadOnly);
QDataStream inStream(&file);
Bitstream in(inStream);
try {
in >> data;
} catch (const BitstreamException& e) {
debug << "failed, " << e.getDescription();
return;
}
QMetaObject::invokeMethod(_server, "setData", Q_ARG(const MetavoxelData&, data));
debug << "done.";
}
data.dumpStats();
}
void MetavoxelPersister::save(const MetavoxelData& data) {
QDebug debug = qDebug() << "Writing to" << SAVE_FILE << "...";
QSaveFile file(SAVE_FILE);
file.open(QIODevice::WriteOnly);
QDataStream outStream(&file);
Bitstream out(outStream);
out << data;
out.flush();
file.commit();
debug << "done.";
}

View file

@ -20,6 +20,7 @@
#include <Endpoint.h>
class MetavoxelEditMessage;
class MetavoxelPersister;
class MetavoxelSession;
/// Maintains a shared metavoxel system, accepting change requests and broadcasting updates.
@ -33,11 +34,15 @@ public:
void applyEdit(const MetavoxelEditMessage& edit);
const MetavoxelData& getData() const { return _data; }
Q_INVOKABLE void setData(const MetavoxelData& data) { _data = data; }
virtual void run();
virtual void readPendingDatagrams();
virtual void aboutToFinish();
private slots:
void maybeAttachSession(const SharedNodePointer& node);
@ -45,6 +50,8 @@ private slots:
private:
MetavoxelPersister* _persister;
QTimer _sendTimer;
qint64 _lastSend;
@ -88,4 +95,20 @@ private:
int _reliableDeltaID;
};
/// Handles persistence in a separate thread.
class MetavoxelPersister : public QObject {
Q_OBJECT
public:
MetavoxelPersister(MetavoxelServer* server);
Q_INVOKABLE void load();
Q_INVOKABLE void save(const MetavoxelData& data);
private:
MetavoxelServer* _server;
};
#endif // hifi_MetavoxelServer_h

View file

@ -31,8 +31,7 @@ var toolWidth = 50;
var LASER_WIDTH = 4;
var LASER_COLOR = { red: 255, green: 0, blue: 0 };
var LASER_LENGTH_FACTOR = 500
;
var LASER_LENGTH_FACTOR = 500;
var MIN_ANGULAR_SIZE = 2;
var MAX_ANGULAR_SIZE = 45;

View file

@ -31,7 +31,7 @@ var count=0; // iterations
var enableFlyTowardPoints = true; // some birds have a point they want to fly to
var enabledClustedFlyTowardPoints = true; // the flyToward points will be generally near each other
var flyToFrames = 10; // number of frames the bird would like to attempt to fly to it's flyTo point
var flyToFrames = 100; // number of frames the bird would like to attempt to fly to it's flyTo point
var PROBABILITY_OF_FLY_TOWARD_CHANGE = 0.01; // chance the bird will decide to change its flyTo point
var PROBABILITY_EACH_BIRD_WILL_FLY_TOWARD = 0.2; // chance the bird will decide to flyTo, otherwise it follows
var flyingToCount = 0; // count of birds currently flying to someplace
@ -56,11 +56,11 @@ var PROBABILITY_TO_LEAD = 0.1; // probability a bird will choose to lead
var birds = new Array(); // array of bird state data
var flockStartPosition = { x: 100, y: 10, z: 100};
var flockStartPosition = MyAvatar.position;
var flockStartVelocity = { x: 0, y: 0, z: 0};
var flockStartThrust = { x: 0, y: 0, z: 0}; // slightly upward against gravity
var INITIAL_XY_VELOCITY_SCALE = 2;
var birdRadius = 0.0625;
var birdRadius = 0.0925;
var baseBirdColor = { red: 0, green: 255, blue: 255 };
var glidingColor = { red: 255, green: 0, blue: 0 };
var thrustUpwardColor = { red: 0, green: 255, blue: 0 };

View file

@ -43,7 +43,10 @@ var animationLenght = 2.0;
var avatarOldPosition = { x: 0, y: 0, z: 0 };
var sitting = false;
var sittingSettingsHandle = "SitJsSittingPosition";
var sitting = Settings.getValue(sittingSettingsHandle, false) == "true";
print("Original sitting status: " + sitting);
var frame = 0;
var seat = new Object();
var hiddingSeats = false;
@ -123,10 +126,12 @@ var goToSeatAnimation = function(deltaTime) {
function sitDown() {
sitting = true;
Settings.setValue(sittingSettingsHandle, sitting);
print("sitDown sitting status: " + Settings.getValue(sittingSettingsHandle, false));
passedTime = 0.0;
startPosition = MyAvatar.position;
storeStartPoseAndTransition();
try{
try {
Script.update.disconnect(standingUpAnimation);
} catch(e){
// no need to handle. if it wasn't connected no harm done
@ -138,6 +143,8 @@ function sitDown() {
function standUp() {
sitting = false;
Settings.setValue(sittingSettingsHandle, sitting);
print("standUp sitting status: " + Settings.getValue(sittingSettingsHandle, false));
passedTime = 0.0;
startPosition = MyAvatar.position;
try{
@ -159,14 +166,16 @@ function SeatIndicator(modelProperties, seatIndex) {
modelProperties.sittingPoints[seatIndex].rotation);
this.scale = MyAvatar.scale / 12;
this.sphere = Overlays.addOverlay("sphere", {
position: this.position,
size: this.scale,
solid: true,
color: { red: 0, green: 0, blue: 255 },
alpha: 0.3,
visible: true
});
this.sphere = Overlays.addOverlay("billboard", {
subImage: { x: 0, y: buttonHeight, width: buttonWidth, height: buttonHeight},
url: buttonImageUrl,
position: this.position,
scale: this.scale * 4,
solid: true,
color: { red: 0, green: 0, blue: 255 },
alpha: 0.3,
visible: true
});
this.show = function(doShow) {
Overlays.editOverlay(this.sphere, { visible: doShow });
@ -218,33 +227,6 @@ Controller.mousePressEvent.connect(function(event) {
try{ Script.update.disconnect(sittingDownAnimation); } catch(e){}
Script.update.connect(goToSeatAnimation);
}
return;
var intersection = Models.findRayIntersection(pickRay);
if (intersection.accurate && intersection.intersects && false) {
var properties = intersection.modelProperties;
print("Intersecting with model, let's check for seats.");
if (properties.sittingPoints.length > 0) {
print("Available seats, going to the first one: " + properties.sittingPoints[0].name);
seat.position = Vec3.sum(properties.position, Vec3.multiplyQbyV(properties.modelRotation, properties.sittingPoints[0].position));
Vec3.print("Seat position: ", seat.position);
seat.rotation = Quat.multiply(properties.modelRotation, properties.sittingPoints[0].rotation);
Quat.print("Seat rotation: ", seat.rotation);
passedTime = 0.0;
startPosition = MyAvatar.position;
startRotation = MyAvatar.orientation;
try{ Script.update.disconnect(standingUpAnimation); } catch(e){}
try{ Script.update.disconnect(sittingDownAnimation); } catch(e){}
Script.update.connect(goToSeatAnimation);
} else {
print ("Sorry, no seats here.");
}
}
}
})
@ -257,13 +239,29 @@ function update(deltaTime){
Overlays.editOverlay( standUpButton, {x: newX, y: newY} );
Overlays.editOverlay( sitDownButton, {x: newX, y: newY} );
}
// For a weird reason avatar joint don't update till the 10th frame
// Set the update frame to 20 to be safe
var UPDATE_FRAME = 20;
if (frame <= UPDATE_FRAME) {
if (frame == UPDATE_FRAME) {
if (sitting == true) {
print("Was seated: " + sitting);
storeStartPoseAndTransition();
updateJoints(1.0);
Overlays.editOverlay(sitDownButton, { visible: false });
Overlays.editOverlay(standUpButton, { visible: true });
}
}
frame++;
}
if (MyAvatar.position.x != avatarOldPosition.x &&
MyAvatar.position.y != avatarOldPosition.y &&
MyAvatar.position.z != avatarOldPosition.z) {
avatarOldPosition = MyAvatar.position;
var SEARCH_RADIUS = 5;
var SEARCH_RADIUS = 10;
var foundModels = Models.findModels(MyAvatar.position, SEARCH_RADIUS);
// Let's remove indicator that got out of radius
for (model in models) {

View file

@ -172,7 +172,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
_runningScriptsWidget(NULL),
_runningScriptsWidgetWasVisible(false),
_trayIcon(new QSystemTrayIcon(_window)),
_lastNackTime(usecTimestampNow())
_lastNackTime(usecTimestampNow()),
_lastSendDownstreamAudioStats(usecTimestampNow())
{
// read the ApplicationInfo.ini file for Name/Version/Domain information
QSettings applicationInfo(Application::resourcesPath() + "info/ApplicationInfo.ini", QSettings::IniFormat);
@ -723,7 +724,6 @@ void Application::resizeGL(int width, int height) {
resetCamerasOnResizeGL(_myCamera, width, height);
glViewport(0, 0, width, height); // shouldn't this account for the menu???
_applicationOverlay.resize();
updateProjectionMatrix();
glLoadIdentity();
@ -2125,10 +2125,11 @@ void Application::updateMyAvatar(float deltaTime) {
loadViewFrustum(_myCamera, _viewFrustum);
}
quint64 now = usecTimestampNow();
// Update my voxel servers with my current voxel query...
{
PerformanceTimer perfTimer("queryOctree");
quint64 now = usecTimestampNow();
quint64 sinceLastQuery = now - _lastQueriedTime;
const quint64 TOO_LONG_SINCE_LAST_QUERY = 3 * USECS_PER_SECOND;
bool queryIsDue = sinceLastQuery > TOO_LONG_SINCE_LAST_QUERY;
@ -2146,7 +2147,6 @@ void Application::updateMyAvatar(float deltaTime) {
// sent nack packets containing missing sequence numbers of received packets from nodes
{
quint64 now = usecTimestampNow();
quint64 sinceLastNack = now - _lastNackTime;
const quint64 TOO_LONG_SINCE_LAST_NACK = 1 * USECS_PER_SECOND;
if (sinceLastNack > TOO_LONG_SINCE_LAST_NACK) {
@ -2154,6 +2154,15 @@ void Application::updateMyAvatar(float deltaTime) {
sendNackPackets();
}
}
{
quint64 sinceLastNack = now - _lastSendDownstreamAudioStats;
if (sinceLastNack > TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS) {
_lastSendDownstreamAudioStats = now;
QMetaObject::invokeMethod(&_audio, "sendDownstreamAudioStatsPacket", Qt::QueuedConnection);
}
}
}
int Application::sendNackPackets() {
@ -2786,6 +2795,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
bool mirrorMode = (whichCamera.getInterpolatedMode() == CAMERA_MODE_MIRROR);
{
PerformanceTimer perfTimer("avatars");
_avatarManager.renderAvatars(mirrorMode ? Avatar::MIRROR_RENDER_MODE : Avatar::NORMAL_RENDER_MODE, selfAvatarOnly);
}

View file

@ -125,6 +125,8 @@ static const float MIRROR_REARVIEW_DISTANCE = 0.65f;
static const float MIRROR_REARVIEW_BODY_DISTANCE = 2.3f;
static const float MIRROR_FIELD_OF_VIEW = 30.0f;
static const quint64 TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS = 1 * USECS_PER_SECOND;
class Application : public QApplication {
Q_OBJECT
@ -586,6 +588,7 @@ private:
QSystemTrayIcon* _trayIcon;
quint64 _lastNackTime;
quint64 _lastSendDownstreamAudioStats;
};
#endif // hifi_Application_h

View file

@ -48,9 +48,18 @@ static const float AUDIO_CALLBACK_MSECS = (float) NETWORK_BUFFER_LENGTH_SAMPLES_
static const int NUMBER_OF_NOISE_SAMPLE_FRAMES = 300;
// audio frames time gap stats (min/max/avg) for last ~30 seconds are recalculated every ~1 second
static const int TIME_GAPS_STATS_INTERVAL_SAMPLES = USECS_PER_SECOND / BUFFER_SEND_INTERVAL_USECS;
static const int TIME_GAP_STATS_WINDOW_INTERVALS = 30;
// incoming sequence number stats history will cover last 30s
static const int INCOMING_SEQ_STATS_HISTORY_LENGTH = INCOMING_SEQ_STATS_HISTORY_LENGTH_SECONDS /
(TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS / USECS_PER_SECOND);
// Mute icon configration
static const int MUTE_ICON_SIZE = 24;
Audio::Audio(int16_t initialJitterBufferSamples, QObject* parent) :
AbstractAudioInterface(parent),
_audioInput(NULL),
@ -103,8 +112,12 @@ Audio::Audio(int16_t initialJitterBufferSamples, QObject* parent) :
_scopeInput(0),
_scopeOutputLeft(0),
_scopeOutputRight(0),
_audioMixerAvatarStreamStats(),
_outgoingAvatarAudioSequenceNumber(0)
_audioMixerAvatarStreamAudioStats(),
_outgoingAvatarAudioSequenceNumber(0),
_incomingMixedAudioSequenceNumberStats(INCOMING_SEQ_STATS_HISTORY_LENGTH),
_interframeTimeGapStats(TIME_GAPS_STATS_INTERVAL_SAMPLES, TIME_GAP_STATS_WINDOW_INTERVALS),
_starveCount(0),
_consecutiveNotMixedCount(0)
{
// clear the array of locally injected samples
memset(_localProceduralSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL);
@ -120,8 +133,14 @@ void Audio::init(QGLWidget *parent) {
void Audio::reset() {
_ringBuffer.reset();
_starveCount = 0;
_consecutiveNotMixedCount = 0;
_audioMixerAvatarStreamAudioStats = AudioStreamStats();
_audioMixerInjectedStreamAudioStatsMap.clear();
_outgoingAvatarAudioSequenceNumber = 0;
_audioMixerInjectedStreamStatsMap.clear();
_incomingMixedAudioSequenceNumberStats.reset();
}
@ -689,7 +708,9 @@ void Audio::addReceivedAudioToBuffer(const QByteArray& audioByteArray) {
_totalPacketsReceived++;
double timeDiff = (double)_timeSinceLastReceived.nsecsElapsed() / 1000000.0; // ns to ms
double timeDiff = (double)_timeSinceLastReceived.nsecsElapsed() / 1000.0; // ns to us
_interframeTimeGapStats.update((quint64)timeDiff);
timeDiff /= USECS_PER_MSEC; // us to ms
_timeSinceLastReceived.start();
// Discard first few received packets for computing jitter (often they pile up on start)
@ -726,7 +747,7 @@ void Audio::parseAudioStreamStatsPacket(const QByteArray& packet) {
quint8 appendFlag = *(reinterpret_cast<const quint16*>(dataAt));
dataAt += sizeof(quint8);
if (!appendFlag) {
_audioMixerInjectedStreamStatsMap.clear();
_audioMixerInjectedStreamAudioStatsMap.clear();
}
// parse the number of stream stats structs to follow
@ -740,13 +761,72 @@ void Audio::parseAudioStreamStatsPacket(const QByteArray& packet) {
dataAt += sizeof(AudioStreamStats);
if (streamStats._streamType == PositionalAudioRingBuffer::Microphone) {
_audioMixerAvatarStreamStats = streamStats;
_audioMixerAvatarStreamAudioStats = streamStats;
} else {
_audioMixerInjectedStreamStatsMap[streamStats._streamIdentifier] = streamStats;
_audioMixerInjectedStreamAudioStatsMap[streamStats._streamIdentifier] = streamStats;
}
}
}
AudioStreamStats Audio::getDownstreamAudioStreamStats() const {
AudioStreamStats stats;
stats._streamType = PositionalAudioRingBuffer::Microphone;
stats._timeGapMin = _interframeTimeGapStats.getMin();
stats._timeGapMax = _interframeTimeGapStats.getMax();
stats._timeGapAverage = _interframeTimeGapStats.getAverage();
stats._timeGapWindowMin = _interframeTimeGapStats.getWindowMin();
stats._timeGapWindowMax = _interframeTimeGapStats.getWindowMax();
stats._timeGapWindowAverage = _interframeTimeGapStats.getWindowAverage();
stats._ringBufferFramesAvailable = _ringBuffer.framesAvailable();
stats._ringBufferCurrentJitterBufferFrames = 0;
stats._ringBufferDesiredJitterBufferFrames = getDesiredJitterBufferFrames();
stats._ringBufferStarveCount = _starveCount;
stats._ringBufferConsecutiveNotMixedCount = _consecutiveNotMixedCount;
stats._ringBufferOverflowCount = _ringBuffer.getOverflowCount();
stats._ringBufferSilentFramesDropped = 0;
stats._packetStreamStats = _incomingMixedAudioSequenceNumberStats.getStats();
stats._packetStreamWindowStats = _incomingMixedAudioSequenceNumberStats.getStatsForHistoryWindow();
return stats;
}
void Audio::sendDownstreamAudioStatsPacket() {
// push the current seq number stats into history, which moves the history window forward 1s
// (since that's how often pushStatsToHistory() is called)
_incomingMixedAudioSequenceNumberStats.pushStatsToHistory();
char packet[MAX_PACKET_SIZE];
// pack header
int numBytesPacketHeader = populatePacketHeader(packet, PacketTypeAudioStreamStats);
char* dataAt = packet + numBytesPacketHeader;
// pack append flag
quint8 appendFlag = 0;
memcpy(dataAt, &appendFlag, sizeof(quint8));
dataAt += sizeof(quint8);
// pack number of stats packed
quint16 numStreamStatsToPack = 1;
memcpy(dataAt, &numStreamStatsToPack, sizeof(quint16));
dataAt += sizeof(quint16);
// pack downstream audio stream stats
AudioStreamStats stats = getDownstreamAudioStreamStats();
memcpy(dataAt, &stats, sizeof(AudioStreamStats));
dataAt += sizeof(AudioStreamStats);
// send packet
NodeList* nodeList = NodeList::getInstance();
SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer);
nodeList->writeDatagram(packet, dataAt - packet, audioMixer);
}
// NOTE: numSamples is the total number of single channel samples, since callers will always call this with stereo
// data we know that we will have 2x samples for each stereo time sample at the format's sample rate
void Audio::addSpatialAudioToBuffer(unsigned int sampleTime, const QByteArray& spatialAudio, unsigned int numSamples) {
@ -867,6 +947,9 @@ void Audio::processReceivedAudio(const QByteArray& audioByteArray) {
//qDebug() << "Audio output just starved.";
_ringBuffer.setIsStarved(true);
_numFramesDisplayStarve = 10;
_starveCount++;
_consecutiveNotMixedCount = 0;
}
int numNetworkOutputSamples;
@ -886,6 +969,7 @@ void Audio::processReceivedAudio(const QByteArray& audioByteArray) {
if (!_ringBuffer.isNotStarvedOrHasMinimumSamples(numSamplesNeededToStartPlayback)) {
// We are still waiting for enough samples to begin playback
// qDebug() << numNetworkOutputSamples << " samples so far, waiting for " << numSamplesNeededToStartPlayback;
_consecutiveNotMixedCount++;
} else {
int numDeviceOutputSamples = numNetworkOutputSamples / networkOutputToOutputRatio;

View file

@ -17,6 +17,8 @@
#include "InterfaceConfig.h"
#include "AudioStreamStats.h"
#include "RingBufferHistory.h"
#include "MovingMinMaxAvg.h"
#include <QAudio>
#include <QAudioInput>
@ -34,6 +36,8 @@
static const int NUM_AUDIO_CHANNELS = 2;
static const int INCOMING_SEQ_STATS_HISTORY_LENGTH_SECONDS = 30;
class QAudioInput;
class QAudioOutput;
class QIODevice;
@ -97,6 +101,9 @@ public slots:
virtual void handleAudioByteArray(const QByteArray& audioByteArray);
AudioStreamStats getDownstreamAudioStreamStats() const;
void sendDownstreamAudioStatsPacket();
bool switchInputToAudioDevice(const QString& inputDeviceName);
bool switchOutputToAudioDevice(const QString& outputDeviceName);
QString getDeviceName(QAudio::Mode mode) const { return (mode == QAudio::AudioInput) ?
@ -107,8 +114,16 @@ public slots:
float getInputVolume() const { return (_audioInput) ? _audioInput->volume() : 0.0f; }
void setInputVolume(float volume) { if (_audioInput) _audioInput->setVolume(volume); }
const AudioStreamStats& getAudioMixerAvatarStreamStats() const { return _audioMixerAvatarStreamStats; }
const QHash<QUuid, AudioStreamStats>& getAudioMixerInjectedStreamStatsMap() const { return _audioMixerInjectedStreamStatsMap; }
const AudioRingBuffer& getDownstreamRingBuffer() const { return _ringBuffer; }
int getDesiredJitterBufferFrames() const { return _jitterBufferSamples / _ringBuffer.getNumFrameSamples(); }
int getStarveCount() const { return _starveCount; }
int getConsecutiveNotMixedCount() const { return _consecutiveNotMixedCount; }
const AudioStreamStats& getAudioMixerAvatarStreamAudioStats() const { return _audioMixerAvatarStreamAudioStats; }
const QHash<QUuid, AudioStreamStats>& getAudioMixerInjectedStreamAudioStatsMap() const { return _audioMixerInjectedStreamAudioStatsMap; }
const MovingMinMaxAvg<quint64>& getInterframeTimeGapStats() const { return _interframeTimeGapStats; }
signals:
bool muteToggled();
@ -241,11 +256,16 @@ private:
QByteArray* _scopeOutputLeft;
QByteArray* _scopeOutputRight;
AudioStreamStats _audioMixerAvatarStreamStats;
QHash<QUuid, AudioStreamStats> _audioMixerInjectedStreamStatsMap;
int _starveCount;
int _consecutiveNotMixedCount;
AudioStreamStats _audioMixerAvatarStreamAudioStats;
QHash<QUuid, AudioStreamStats> _audioMixerInjectedStreamAudioStatsMap;
quint16 _outgoingAvatarAudioSequenceNumber;
SequenceNumberStats _incomingMixedAudioSequenceNumberStats;
MovingMinMaxAvg<quint64> _interframeTimeGapStats;
};

View file

@ -60,7 +60,7 @@ BuckyBalls::BuckyBalls() {
void BuckyBalls::grab(PalmData& palm, float deltaTime) {
float penetration;
glm::vec3 fingerTipPosition = palm.getFingerTipPosition();
glm::vec3 fingerTipPosition = palm.getTipPosition();
if (palm.getControllerButtons() & BUTTON_FWD) {
if (!_bballIsGrabbed[palm.getSixenseID()]) {

View file

@ -23,6 +23,11 @@ GLCanvas::GLCanvas() : QGLWidget(QGLFormat(QGL::NoDepthBuffer)),
_throttleRendering(false),
_idleRenderInterval(MSECS_PER_FRAME_WHEN_THROTTLED)
{
#ifdef Q_OS_LINUX
// Cause GLCanvas::eventFilter to be called.
// It wouldn't hurt to do this on Mac and PC too; but apparently it's only needed on linux.
qApp->installEventFilter(this);
#endif
}
bool GLCanvas::isThrottleRendering() const {
@ -162,3 +167,35 @@ void GLCanvas::dragEnterEvent(QDragEnterEvent* event) {
void GLCanvas::dropEvent(QDropEvent* event) {
Application::getInstance()->dropEvent(event);
}
// Pressing Alt (and Meta) key alone activates the menubar because its style inherits the
// SHMenuBarAltKeyNavigation from QWindowsStyle. This makes it impossible for a scripts to
// receive keyPress events for the Alt (and Meta) key in a reliable manner.
//
// This filter catches events before QMenuBar can steal the keyboard focus.
// The idea was borrowed from
// http://www.archivum.info/qt-interest@trolltech.com/2006-09/00053/Re-(Qt4)-Alt-key-focus-QMenuBar-(solved).html
bool GLCanvas::eventFilter(QObject*, QEvent* event) {
switch (event->type()) {
case QEvent::KeyPress:
case QEvent::KeyRelease:
case QEvent::ShortcutOverride:
{
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
if (keyEvent->key() == Qt::Key_Alt || keyEvent->key() == Qt::Key_Meta) {
if (event->type() == QEvent::KeyPress) {
keyPressEvent(keyEvent);
} else if (event->type() == QEvent::KeyRelease) {
keyReleaseEvent(keyEvent);
} else {
QGLWidget::event(event);
}
return true;
}
}
default:
break;
}
return false;
}

View file

@ -50,6 +50,7 @@ protected:
private slots:
void activeChanged(Qt::ApplicationState state);
void throttleRender();
bool eventFilter(QObject*, QEvent* event);
};
#endif // hifi_GLCanvas_h

View file

@ -441,13 +441,11 @@ Menu::Menu() :
QMenu* timingMenu = developerMenu->addMenu("Timing and Statistics Tools");
QMenu* perfTimerMenu = timingMenu->addMenu("Performance Timer");
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::DisplayTimingDetails, 0, true);
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandDisplaySideTiming, 0, false);
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandAvatarSimulateTiming, 0, false);
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandAvatarUpdateTiming, 0, false);
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandMiscAvatarTiming, 0, false);
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandIdleTiming, 0, false);
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandPaintGLTiming, 0, false);
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandUpdateTiming, 0, false);
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandMyAvatarTiming, 0, false);
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandMyAvatarSimulateTiming, 0, false);
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandOtherAvatarTiming, 0, false);
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandPaintGLTiming, 0, false);
addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::TestPing, 0, true);
addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::FrameTimer);

View file

@ -357,13 +357,11 @@ namespace MenuOption {
const QString EnableGlowEffect = "Enable Glow Effect (Warning: Poor Oculus Performance)";
const QString Enable3DTVMode = "Enable 3DTV Mode";
const QString EnableVRMode = "Enable VR Mode";
const QString ExpandMiscAvatarTiming = "Expand Misc MyAvatar Timing";
const QString ExpandAvatarUpdateTiming = "Expand MyAvatar update Timing";
const QString ExpandAvatarSimulateTiming = "Expand MyAvatar simulate Timing";
const QString ExpandDisplaySideTiming = "Expand Display Side Timing";
const QString ExpandIdleTiming = "Expand Idle Timing";
const QString ExpandPaintGLTiming = "Expand PaintGL Timing";
const QString ExpandUpdateTiming = "Expand Update Timing";
const QString ExpandMyAvatarSimulateTiming = "Expand /myAvatar/simulation";
const QString ExpandMyAvatarTiming = "Expand /myAvatar";
const QString ExpandOtherAvatarTiming = "Expand /otherAvatar";
const QString ExpandPaintGLTiming = "Expand /paintGL";
const QString ExpandUpdateTiming = "Expand /update";
const QString Faceplus = "Faceplus";
const QString Faceshift = "Faceshift";
const QString FilterSixense = "Smooth Sixense Movement";

View file

@ -48,8 +48,10 @@ void MetavoxelSystem::init() {
}
MetavoxelLOD MetavoxelSystem::getLOD() const {
const float FIXED_LOD_THRESHOLD = 0.01f;
return MetavoxelLOD(Application::getInstance()->getCamera()->getPosition(), FIXED_LOD_THRESHOLD);
// the LOD threshold is temporarily tied to the avatar LOD parameter
const float BASE_LOD_THRESHOLD = 0.01f;
return MetavoxelLOD(Application::getInstance()->getCamera()->getPosition(),
BASE_LOD_THRESHOLD * Menu::getInstance()->getAvatarLODDistanceMultiplier());
}
void MetavoxelSystem::simulate(float deltaTime) {

View file

@ -61,7 +61,7 @@ Avatar::Avatar() :
_mouseRayDirection(0.0f, 0.0f, 0.0f),
_moving(false),
_collisionGroups(0),
_numLocalLights(1),
_numLocalLights(2),
_initialized(false),
_shouldRenderBillboard(true)
{
@ -89,14 +89,14 @@ void Avatar::init() {
_localLightDirections[i] = glm::vec3(0.0f, 0.0f, 0.0f);
}
glm::vec3 darkGrayColor(0.3f, 0.3f, 0.3f);
glm::vec3 darkGrayColor(0.4f, 0.4f, 0.4f);
glm::vec3 greenColor(0.0f, 1.0f, 0.0f);
glm::vec3 directionX(1.0f, 0.0f, 0.0f);
glm::vec3 directionY(0.0f, 1.0f, 0.0f);
// initialize local lights
_localLightColors[0] = darkGrayColor;
_localLightColors[1] = greenColor;
_localLightColors[1] = darkGrayColor;
_localLightDirections[0] = directionX;
_localLightDirections[1] = directionY;
@ -145,20 +145,20 @@ void Avatar::simulate(float deltaTime) {
_skeletonModel.setLODDistance(getLODDistance());
if (!_shouldRenderBillboard && inViewFrustum) {
if (_hasNewJointRotations) {
PerformanceTimer perfTimer("skeleton");
for (int i = 0; i < _jointData.size(); i++) {
const JointData& data = _jointData.at(i);
_skeletonModel.setJointState(i, data.valid, data.rotation);
}
_skeletonModel.simulate(deltaTime);
}
{
PerformanceTimer perfTimer("head");
PerformanceTimer perfTimer("skeleton");
if (_hasNewJointRotations) {
for (int i = 0; i < _jointData.size(); i++) {
const JointData& data = _jointData.at(i);
_skeletonModel.setJointState(i, data.valid, data.rotation);
}
}
_skeletonModel.simulate(deltaTime, _hasNewJointRotations);
simulateAttachments(deltaTime);
_hasNewJointRotations = false;
}
{
PerformanceTimer perfTimer("head");
glm::vec3 headPosition = _position;
_skeletonModel.getHeadPosition(headPosition);
Head* head = getHead();
@ -752,6 +752,11 @@ glm::quat Avatar::getJointCombinedRotation(const QString& name) const {
return rotation;
}
void Avatar::scaleVectorRelativeToPosition(glm::vec3 &positionToScale) const {
//Scale a world space vector as if it was relative to the position
positionToScale = _position + _scale * (positionToScale - _position);
}
void Avatar::setFaceModelURL(const QUrl& faceModelURL) {
AvatarData::setFaceModelURL(faceModelURL);
const QUrl DEFAULT_FACE_MODEL_URL = QUrl::fromLocalFile(Application::resourcesPath() + "meshes/defaultAvatar_head.fst");

View file

@ -152,6 +152,10 @@ public:
glm::vec3 getAcceleration() const { return _acceleration; }
glm::vec3 getAngularVelocity() const { return _angularVelocity; }
/// Scales a world space position vector relative to the avatar position and scale
/// \param vector position to be scaled. Will store the result
void scaleVectorRelativeToPosition(glm::vec3 &positionToScale) const;
public slots:
void updateCollisionGroups();

View file

@ -41,7 +41,7 @@ void AvatarManager::init() {
}
void AvatarManager::updateOtherAvatars(float deltaTime) {
if (_avatarHash.size() > 1) {
if (_avatarHash.size() < 2) {
return;
}
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);

View file

@ -130,6 +130,9 @@ void Hand::render(bool isMine, Model::RenderMode renderMode) {
void Hand::renderHandTargets(bool isMine) {
glPushMatrix();
MyAvatar* myAvatar = Application::getInstance()->getAvatar();
const float avatarScale = Application::getInstance()->getAvatar()->getScale();
const float alpha = 1.0f;
const glm::vec3 handColor(1.0, 0.0, 0.0); // Color the hand targets red to be different than skin
@ -142,7 +145,7 @@ void Hand::renderHandTargets(bool isMine) {
if (!palm.isActive()) {
continue;
}
glm::vec3 targetPosition = palm.getFingerTipPosition();
glm::vec3 targetPosition = palm.getTipPosition();
glPushMatrix();
glTranslatef(targetPosition.x, targetPosition.y, targetPosition.z);
@ -153,18 +156,23 @@ void Hand::renderHandTargets(bool isMine) {
}
}
const float PALM_BALL_RADIUS = 0.03f;
const float PALM_DISK_RADIUS = 0.06f;
const float PALM_DISK_THICKNESS = 0.01f;
const float PALM_FINGER_ROD_RADIUS = 0.003f;
const float PALM_BALL_RADIUS = 0.03f * avatarScale;
const float PALM_DISK_RADIUS = 0.06f * avatarScale;
const float PALM_DISK_THICKNESS = 0.01f * avatarScale;
const float PALM_FINGER_ROD_RADIUS = 0.003f * avatarScale;
// Draw the palm ball and disk
for (size_t i = 0; i < getNumPalms(); ++i) {
PalmData& palm = getPalms()[i];
if (palm.isActive()) {
glColor4f(handColor.r, handColor.g, handColor.b, alpha);
glm::vec3 tip = palm.getFingerTipPosition();
glm::vec3 tip = palm.getTipPosition();
glm::vec3 root = palm.getPosition();
//Scale the positions based on avatar scale
myAvatar->scaleVectorRelativeToPosition(tip);
myAvatar->scaleVectorRelativeToPosition(root);
Avatar::renderJointConnectingCone(root, tip, PALM_FINGER_ROD_RADIUS, PALM_FINGER_ROD_RADIUS);
// Render sphere at palm/finger root
glm::vec3 offsetFromPalm = root + palm.getNormal() * PALM_DISK_THICKNESS;

View file

@ -269,8 +269,7 @@ void OculusManager::display(const glm::quat &bodyOrientation, const glm::vec3 &p
// We only need to render the overlays to a texture once, then we just render the texture on the hemisphere
// PrioVR will only work if renderOverlay is called, calibration is connected to Application::renderingOverlay()
applicationOverlay.renderOverlay(true);
const bool displayOverlays = Menu::getInstance()->isOptionChecked(MenuOption::UserInterface);
//Bind our framebuffer object. If we are rendering the glow effect, we let the glow effect shader take care of it
if (Menu::getInstance()->isOptionChecked(MenuOption::EnableGlowEffect)) {
Application::getInstance()->getGlowEffect()->prepare();
@ -325,9 +324,7 @@ void OculusManager::display(const glm::quat &bodyOrientation, const glm::vec3 &p
Application::getInstance()->displaySide(*_camera);
if (displayOverlays) {
applicationOverlay.displayOverlayTextureOculus(*_camera);
}
applicationOverlay.displayOverlayTextureOculus(*_camera);
}
//Wait till time-warp to reduce latency
@ -443,7 +440,15 @@ void OculusManager::getEulerAngles(float& yaw, float& pitch, float& roll) {
ovrPosef pose = ss.Predicted.Pose;
Quatf orientation = Quatf(pose.Orientation);
orientation.GetEulerAngles<Axis_Y, Axis_X, Axis_Z, Rotate_CCW, Handed_R>(&yaw, &pitch, &roll);
} else {
yaw = 0.0f;
pitch = 0.0f;
roll = 0.0f;
}
#else
yaw = 0.0f;
pitch = 0.0f;
roll = 0.0f;
#endif
}
@ -459,3 +464,50 @@ QSize OculusManager::getRenderTargetSize() {
#endif
}
//Renders sixense laser pointers for UI selection in the oculus
void OculusManager::renderLaserPointers() {
#ifdef HAVE_LIBOVR
const float PALM_TIP_ROD_RADIUS = 0.002f;
MyAvatar* myAvatar = Application::getInstance()->getAvatar();
//If the Oculus is enabled, we will draw a blue cursor ray
for (size_t i = 0; i < myAvatar->getHand()->getNumPalms(); ++i) {
PalmData& palm = myAvatar->getHand()->getPalms()[i];
if (palm.isActive()) {
glColor4f(0, 1, 1, 1);
glm::vec3 tip = getLaserPointerTipPosition(&palm);
glm::vec3 root = palm.getPosition();
//Scale the root vector with the avatar scale
myAvatar->scaleVectorRelativeToPosition(root);
Avatar::renderJointConnectingCone(root, tip, PALM_TIP_ROD_RADIUS, PALM_TIP_ROD_RADIUS);
}
}
#endif
}
//Gets the tip position for the laser pointer
glm::vec3 OculusManager::getLaserPointerTipPosition(const PalmData* palm) {
#ifdef HAVE_LIBOVR
const ApplicationOverlay& applicationOverlay = Application::getInstance()->getApplicationOverlay();
const float PALM_TIP_ROD_LENGTH_MULT = 40.0f;
glm::vec3 direction = glm::normalize(palm->getTipPosition() - palm->getPosition());
glm::vec3 position = palm->getPosition();
//scale the position with the avatar
Application::getInstance()->getAvatar()->scaleVectorRelativeToPosition(position);
glm::vec3 result;
if (applicationOverlay.calculateRayUICollisionPoint(position, direction, result)) {
return result;
}
return palm->getPosition();
#endif
return glm::vec3(0.0f);
}

View file

@ -23,6 +23,7 @@
const float DEFAULT_OCULUS_UI_ANGULAR_SIZE = 72.0f;
class Camera;
class PalmData;
/// Handles interaction with the Oculus Rift.
class OculusManager {
@ -41,6 +42,10 @@ public:
/// param \roll[out] roll in radians
static void getEulerAngles(float& yaw, float& pitch, float& roll);
static QSize getRenderTargetSize();
/// Renders a laser pointer for UI picking
static void renderLaserPointers();
static glm::vec3 getLaserPointerTipPosition(const PalmData* palm);
private:
#ifdef HAVE_LIBOVR

View file

@ -15,6 +15,7 @@
#include "Application.h"
#include "SixenseManager.h"
#include "devices/OculusManager.h"
#include "UserActivityLogger.h"
#ifdef HAVE_SIXENSE
@ -172,8 +173,10 @@ void SixenseManager::update(float deltaTime) {
// Use a velocity sensitive filter to damp small motions and preserve large ones with
// no latency.
float velocityFilter = glm::clamp(1.0f - glm::length(rawVelocity), 0.0f, 1.0f);
palm->setRawPosition(palm->getRawPosition() * velocityFilter + position * (1.0f - velocityFilter));
palm->setRawRotation(safeMix(palm->getRawRotation(), rotation, 1.0f - velocityFilter));
position = palm->getRawPosition() * velocityFilter + position * (1.0f - velocityFilter);
rotation = safeMix(palm->getRawRotation(), rotation, 1.0f - velocityFilter);
palm->setRawPosition(position);
palm->setRawRotation(rotation);
} else {
palm->setRawPosition(position);
palm->setRawRotation(rotation);
@ -366,9 +369,7 @@ void SixenseManager::emulateMouse(PalmData* palm, int index) {
MyAvatar* avatar = application->getAvatar();
QGLWidget* widget = application->getGLWidget();
QPoint pos;
// Get directon relative to avatar orientation
glm::vec3 direction = glm::inverse(avatar->getOrientation()) * palm->getFingerDirection();
Qt::MouseButton bumperButton;
Qt::MouseButton triggerButton;
@ -380,19 +381,27 @@ void SixenseManager::emulateMouse(PalmData* palm, int index) {
triggerButton = Qt::LeftButton;
}
// Get the angles, scaled between (-0.5,0.5)
float xAngle = (atan2(direction.z, direction.x) + M_PI_2);
float yAngle = 0.5f - ((atan2(direction.z, direction.y) + M_PI_2));
if (OculusManager::isConnected()) {
pos = application->getApplicationOverlay().getOculusPalmClickLocation(palm);
} else {
// Get directon relative to avatar orientation
glm::vec3 direction = glm::inverse(avatar->getOrientation()) * palm->getFingerDirection();
// Get the pixel range over which the xAngle and yAngle are scaled
float cursorRange = widget->width() * getCursorPixelRangeMult();
// Get the angles, scaled between (-0.5,0.5)
float xAngle = (atan2(direction.z, direction.x) + M_PI_2);
float yAngle = 0.5f - ((atan2(direction.z, direction.y) + M_PI_2));
pos.setX(widget->width() / 2.0f + cursorRange * xAngle);
pos.setY(widget->height() / 2.0f + cursorRange * yAngle);
// Get the pixel range over which the xAngle and yAngle are scaled
float cursorRange = widget->width() * getCursorPixelRangeMult();
pos.setX(widget->width() / 2.0f + cursorRange * xAngle);
pos.setY(widget->height() / 2.0f + cursorRange * yAngle);
}
//If we are off screen then we should stop processing, and if a trigger or bumper is pressed,
//we should unpress them.
if (pos.x() < 0 || pos.x() > widget->width() || pos.y() < 0 || pos.y() > widget->height()) {
if (pos.x() == INT_MAX) {
if (_bumperPressed[index]) {
QMouseEvent mouseEvent(QEvent::MouseButtonRelease, pos, bumperButton, bumperButton, 0);

View file

@ -40,6 +40,7 @@ ApplicationOverlay::ApplicationOverlay() :
_framebufferObject(NULL),
_textureFov(DEFAULT_OCULUS_UI_ANGULAR_SIZE * RADIANS_PER_DEGREE),
_alpha(1.0f),
_oculusuiRadius(1.0f),
_crosshairTexture(0) {
memset(_reticleActive, 0, sizeof(_reticleActive));
@ -164,7 +165,7 @@ void ApplicationOverlay::displayOverlayTexture() {
}
void ApplicationOverlay::computeOculusPickRay(float x, float y, glm::vec3& direction) const {
glm::quat rot = Application::getInstance()->getAvatar()->getOrientation();
MyAvatar* myAvatar = Application::getInstance()->getAvatar();
//invert y direction
y = 1.0 - y;
@ -176,8 +177,11 @@ void ApplicationOverlay::computeOculusPickRay(float x, float y, glm::vec3& direc
float dist = sqrt(x * x + y * y);
float z = -sqrt(1.0f - dist * dist);
glm::vec3 relativePosition = myAvatar->getHead()->calculateAverageEyePosition() +
glm::normalize(myAvatar->getOrientation() * glm::vec3(x, y, z));
//Rotate the UI pick ray by the avatar orientation
direction = glm::normalize(rot * glm::vec3(x, y, z));
direction = glm::normalize(relativePosition - Application::getInstance()->getCamera()->getPosition());
}
// Calculates the click location on the screen by taking into account any
@ -186,7 +190,7 @@ void ApplicationOverlay::getClickLocation(int &x, int &y) const {
int dx;
int dy;
const float xRange = MAGNIFY_WIDTH * MAGNIFY_MULT / 2.0f;
const float yRange = MAGNIFY_WIDTH * MAGNIFY_MULT / 2.0f;
const float yRange = MAGNIFY_HEIGHT * MAGNIFY_MULT / 2.0f;
//Loop through all magnification windows
for (int i = 0; i < NUMBER_OF_MAGNIFIERS; i++) {
@ -204,6 +208,126 @@ void ApplicationOverlay::getClickLocation(int &x, int &y) const {
}
}
//Checks if the given ray intersects the sphere at the origin. result will store a multiplier that should
//be multiplied by dir and added to origin to get the location of the collision
bool raySphereIntersect(const glm::vec3 &dir, const glm::vec3 &origin, float r, float* result)
{
//Source: http://wiki.cgsociety.org/index.php/Ray_Sphere_Intersection
//Compute A, B and C coefficients
float a = glm::dot(dir, dir);
float b = 2 * glm::dot(dir, origin);
float c = glm::dot(origin, origin) - (r * r);
//Find discriminant
float disc = b * b - 4 * a * c;
// if discriminant is negative there are no real roots, so return
// false as ray misses sphere
if (disc < 0) {
return false;
}
// compute q as described above
float distSqrt = sqrtf(disc);
float q;
if (b < 0) {
q = (-b - distSqrt) / 2.0;
} else {
q = (-b + distSqrt) / 2.0;
}
// compute t0 and t1
float t0 = q / a;
float t1 = c / q;
// make sure t0 is smaller than t1
if (t0 > t1) {
// if t0 is bigger than t1 swap them around
float temp = t0;
t0 = t1;
t1 = temp;
}
// if t1 is less than zero, the object is in the ray's negative direction
// and consequently the ray misses the sphere
if (t1 < 0) {
return false;
}
// if t0 is less than zero, the intersection point is at t1
if (t0 < 0) {
*result = t1;
return true;
} else { // else the intersection point is at t0
*result = t0;
return true;
}
}
//Caculate the click location using one of the sixense controllers. Scale is not applied
QPoint ApplicationOverlay::getOculusPalmClickLocation(const PalmData *palm) const {
Application* application = Application::getInstance();
QGLWidget* glWidget = application->getGLWidget();
MyAvatar* myAvatar = application->getAvatar();
glm::vec3 tip = OculusManager::getLaserPointerTipPosition(palm);
glm::vec3 eyePos = myAvatar->getHead()->calculateAverageEyePosition();
glm::quat orientation = glm::inverse(myAvatar->getOrientation());
glm::vec3 dir = orientation * glm::normalize(application->getCamera()->getPosition() - tip); //direction of ray goes towards camera
glm::vec3 tipPos = orientation * (tip - eyePos);
QPoint rv;
float t;
//We back the ray up by dir to ensure that it will not start inside the UI.
glm::vec3 adjustedPos = tipPos - dir;
//Find intersection of crosshair ray.
if (raySphereIntersect(dir, adjustedPos, _oculusuiRadius * myAvatar->getScale(), &t)){
glm::vec3 collisionPos = adjustedPos + dir * t;
//Normalize it in case its not a radius of 1
collisionPos = glm::normalize(collisionPos);
//If we hit the back hemisphere, mark it as not a collision
if (collisionPos.z > 0) {
rv.setX(INT_MAX);
rv.setY(INT_MAX);
} else {
float u = asin(collisionPos.x) / (_textureFov)+0.5f;
float v = 1.0 - (asin(collisionPos.y) / (_textureFov)+0.5f);
rv.setX(u * glWidget->width());
rv.setY(v * glWidget->height());
}
} else {
//if they did not click on the overlay, just set the coords to INT_MAX
rv.setX(INT_MAX);
rv.setY(INT_MAX);
}
return rv;
}
//Finds the collision point of a world space ray
bool ApplicationOverlay::calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction, glm::vec3& result) const {
Application* application = Application::getInstance();
MyAvatar* myAvatar = application->getAvatar();
glm::quat orientation = myAvatar->getOrientation();
glm::vec3 relativePosition = orientation * (position - myAvatar->getHead()->calculateAverageEyePosition());
glm::vec3 relativeDirection = orientation * direction;
float t;
if (raySphereIntersect(relativeDirection, relativePosition, _oculusuiRadius * myAvatar->getScale(), &t)){
result = position + direction * t;
return true;
}
return false;
}
// Draws the FBO texture for Oculus rift.
void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) {
@ -214,41 +338,37 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) {
Application* application = Application::getInstance();
MyAvatar* myAvatar = application->getAvatar();
const glm::vec3& viewMatrixTranslation = application->getViewMatrixTranslation();
//Render the sixense lasers
OculusManager::renderLaserPointers();
glActiveTexture(GL_TEXTURE0);
glEnable(GL_BLEND);
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE);
glBindTexture(GL_TEXTURE_2D, getFramebufferObject()->texture());
glEnable(GL_DEPTH_TEST);
glDisable(GL_LIGHTING);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, getFramebufferObject()->texture());
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
// Transform to world space
glm::quat rotation = whichCamera.getRotation();
glm::vec3 axis2 = glm::axis(rotation);
glRotatef(-glm::degrees(glm::angle(rotation)), axis2.x, axis2.y, axis2.z);
glTranslatef(viewMatrixTranslation.x, viewMatrixTranslation.y, viewMatrixTranslation.z);
// Translate to the front of the camera
glm::vec3 pos = whichCamera.getPosition();
glm::quat rot = myAvatar->getOrientation();
glm::vec3 axis = glm::axis(rot);
glTranslatef(pos.x, pos.y, pos.z);
glRotatef(glm::degrees(glm::angle(rot)), axis.x, axis.y, axis.z);
glDepthMask(GL_TRUE);
glEnable(GL_ALPHA_TEST);
glAlphaFunc(GL_GREATER, 0.01f);
//Update and draw the magnifiers
glPushMatrix();
const glm::quat& orientation = myAvatar->getOrientation();
const glm::vec3& position = myAvatar->getHead()->calculateAverageEyePosition();
glm::mat4 rotation = glm::toMat4(orientation);
glTranslatef(position.x, position.y, position.z);
glMultMatrixf(&rotation[0][0]);
for (int i = 0; i < NUMBER_OF_MAGNIFIERS; i++) {
if (_magActive[i]) {
@ -268,6 +388,7 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) {
renderMagnifier(_magX[i], _magY[i], _magSizeMult[i], i != MOUSE);
}
}
glPopMatrix();
glDepthMask(GL_FALSE);
glDisable(GL_ALPHA_TEST);
@ -275,10 +396,8 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) {
glColor4f(1.0f, 1.0f, 1.0f, _alpha);
renderTexturedHemisphere();
renderControllerPointersOculus();
glPopMatrix();
renderPointersOculus(whichCamera.getPosition());
glDepthMask(GL_TRUE);
glBindTexture(GL_TEXTURE_2D, 0);
@ -333,7 +452,6 @@ void ApplicationOverlay::displayOverlayTexture3DTV(Camera& whichCamera, float as
glColor4f(1.0f, 1.0f, 1.0f, _alpha);
//Render
// fov -= RADIANS_PER_DEGREE * 2.5f; //reduce by 5 degrees so it fits in the view
const GLfloat distance = 1.0f;
const GLfloat halfQuadHeight = distance * tan(fov);
@ -405,7 +523,6 @@ void ApplicationOverlay::renderPointers() {
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, _crosshairTexture);
if (OculusManager::isConnected() && application->getLastMouseMoveType() == QEvent::MouseMove) {
//If we are in oculus, render reticle later
_reticleActive[MOUSE] = true;
@ -414,7 +531,6 @@ void ApplicationOverlay::renderPointers() {
_mouseY[MOUSE] = application->getMouseY();
_magX[MOUSE] = _mouseX[MOUSE];
_magY[MOUSE] = _mouseY[MOUSE];
_reticleActive[LEFT_CONTROLLER] = false;
_reticleActive[RIGHT_CONTROLLER] = false;
@ -503,13 +619,32 @@ void ApplicationOverlay::renderControllerPointers() {
} else {
bumperPressed[index] = false;
}
//if we have the oculus, we should make the cursor smaller since it will be
//magnified
if (OculusManager::isConnected()) {
QPoint point = getOculusPalmClickLocation(palmData);
_mouseX[index] = point.x();
_mouseY[index] = point.y();
//When button 2 is pressed we drag the mag window
if (isPressed[index]) {
_magActive[index] = true;
_magX[index] = point.x();
_magY[index] = point.y();
}
// If oculus is enabled, we draw the crosshairs later
continue;
}
// Get directon relative to avatar orientation
glm::vec3 direction = glm::inverse(myAvatar->getOrientation()) * palmData->getFingerDirection();
// Get the angles, scaled between (-0.5,0.5)
float xAngle = (atan2(direction.z, direction.x) + M_PI_2) ;
float xAngle = (atan2(direction.z, direction.x) + M_PI_2);
float yAngle = 0.5f - ((atan2(direction.z, direction.y) + M_PI_2));
// Get the pixel range over which the xAngle and yAngle are scaled
@ -524,24 +659,7 @@ void ApplicationOverlay::renderControllerPointers() {
continue;
}
_reticleActive[index] = true;
//if we have the oculus, we should make the cursor smaller since it will be
//magnified
if (OculusManager::isConnected()) {
_mouseX[index] = mouseX;
_mouseY[index] = mouseY;
//When button 2 is pressed we drag the mag window
if (isPressed[index]) {
_magActive[index] = true;
_magX[index] = mouseX;
_magY[index] = mouseY;
}
// If oculus is enabled, we draw the crosshairs later
continue;
}
const float reticleSize = 40.0f;
@ -561,9 +679,11 @@ void ApplicationOverlay::renderControllerPointers() {
}
}
void ApplicationOverlay::renderControllerPointersOculus() {
void ApplicationOverlay::renderPointersOculus(const glm::vec3& eyePos) {
Application* application = Application::getInstance();
QGLWidget* glWidget = application->getGLWidget();
glm::vec3 cursorVerts[4];
const int widgetWidth = glWidget->width();
const int widgetHeight = glWidget->height();
@ -572,19 +692,86 @@ void ApplicationOverlay::renderControllerPointersOculus() {
glBindTexture(GL_TEXTURE_2D, _crosshairTexture);
glDisable(GL_DEPTH_TEST);
for (int i = 0; i < NUMBER_OF_MAGNIFIERS; i++) {
glMatrixMode(GL_MODELVIEW);
MyAvatar* myAvatar = application->getAvatar();
//Dont render the reticle if its inactive
if (!_reticleActive[i]) {
continue;
}
//Controller Pointers
for (int i = 0; i < (int)myAvatar->getHand()->getNumPalms(); i++) {
float mouseX = (float)_mouseX[i];
float mouseY = (float)_mouseY[i];
PalmData& palm = myAvatar->getHand()->getPalms()[i];
if (palm.isActive()) {
glm::vec3 tip = OculusManager::getLaserPointerTipPosition(&palm);
glm::vec3 tipPos = (tip - eyePos);
float length = glm::length(eyePos - tip);
float size = 0.03f * length;
glm::vec3 up = glm::vec3(0.0, 1.0, 0.0) * size;
glm::vec3 right = glm::vec3(1.0, 0.0, 0.0) * size;
cursorVerts[0] = -right + up;
cursorVerts[1] = right + up;
cursorVerts[2] = right - up;
cursorVerts[3] = -right - up;
glPushMatrix();
// objToCamProj is the vector in world coordinates from the
// local origin to the camera projected in the XZ plane
glm::vec3 cursorToCameraXZ(-tipPos.x, 0, -tipPos.z);
cursorToCameraXZ = glm::normalize(cursorToCameraXZ);
//Translate the cursor to the tip of the oculus ray
glTranslatef(tip.x, tip.y, tip.z);
glm::vec3 direction(0, 0, 1);
// easy fix to determine wether the angle is negative or positive
// for positive angles upAux will be a vector pointing in the
// positive y direction, otherwise upAux will point downwards
// effectively reversing the rotation.
glm::vec3 upAux = glm::cross(direction, cursorToCameraXZ);
// compute the angle
float angleCosine = glm::dot(direction, cursorToCameraXZ);
//Rotate in XZ direction
glRotatef(acos(angleCosine) * DEGREES_PER_RADIAN, upAux[0], upAux[1], upAux[2]);
glm::vec3 cursorToCamera = glm::normalize(-tipPos);
// Compute the angle between cursorToCameraXZ and cursorToCamera,
angleCosine = glm::dot(cursorToCameraXZ, cursorToCamera);
//Rotate in Y direction
if (cursorToCamera.y < 0) {
glRotatef(acos(angleCosine) * DEGREES_PER_RADIAN, 1, 0, 0);
} else {
glRotatef(acos(angleCosine) * DEGREES_PER_RADIAN, -1, 0, 0);
}
glBegin(GL_QUADS);
glColor4f(RETICLE_COLOR[0], RETICLE_COLOR[1], RETICLE_COLOR[2], _alpha);
glTexCoord2f(0.0f, 0.0f); glVertex3f(cursorVerts[0].x, cursorVerts[0].y, cursorVerts[0].z);
glTexCoord2f(1.0f, 0.0f); glVertex3f(cursorVerts[1].x, cursorVerts[1].y, cursorVerts[1].z);
glTexCoord2f(1.0f, 1.0f); glVertex3f(cursorVerts[2].x, cursorVerts[2].y, cursorVerts[2].z);
glTexCoord2f(0.0f, 1.0f); glVertex3f(cursorVerts[3].x, cursorVerts[3].y, cursorVerts[3].z);
glEnd();
glPopMatrix();
}
}
//Mouse Pointer
if (_reticleActive[MOUSE]) {
float mouseX = (float)_mouseX[MOUSE];
float mouseY = (float)_mouseY[MOUSE];
mouseX -= reticleSize / 2;
mouseY += reticleSize / 2;
//Get new UV coordinates from our magnification window
float newULeft = mouseX / widgetWidth;
float newURight = (mouseX + reticleSize) / widgetWidth;
@ -614,15 +801,22 @@ void ApplicationOverlay::renderControllerPointersOculus() {
glBegin(GL_QUADS);
glColor4f(RETICLE_COLOR[0], RETICLE_COLOR[1], RETICLE_COLOR[2], _alpha);
glTexCoord2f(0.0f, 0.0f); glVertex3f(lX, tY, -tlZ);
glTexCoord2f(1.0f, 0.0f); glVertex3f(rX, tY, -trZ);
glTexCoord2f(1.0f, 1.0f); glVertex3f(rX, bY, -brZ);
glTexCoord2f(0.0f, 1.0f); glVertex3f(lX, bY, -blZ);
const glm::quat& orientation = myAvatar->getOrientation();
cursorVerts[0] = orientation * glm::vec3(lX, tY, -tlZ) + eyePos;
cursorVerts[1] = orientation * glm::vec3(rX, tY, -trZ) + eyePos;
cursorVerts[2] = orientation * glm::vec3(rX, bY, -brZ) + eyePos;
cursorVerts[3] = orientation * glm::vec3(lX, bY, -blZ) + eyePos;
glTexCoord2f(0.0f, 0.0f); glVertex3f(cursorVerts[0].x, cursorVerts[0].y, cursorVerts[0].z);
glTexCoord2f(1.0f, 0.0f); glVertex3f(cursorVerts[1].x, cursorVerts[1].y, cursorVerts[1].z);
glTexCoord2f(1.0f, 1.0f); glVertex3f(cursorVerts[2].x, cursorVerts[2].y, cursorVerts[2].z);
glTexCoord2f(0.0f, 1.0f); glVertex3f(cursorVerts[3].x, cursorVerts[3].y, cursorVerts[3].z);
glEnd();
}
glEnable(GL_DEPTH_TEST);
}
@ -663,18 +857,22 @@ void ApplicationOverlay::renderMagnifier(int mouseX, int mouseY, float sizeMult,
float newVTop = 1.0 - (newMouseY - newHeight) / widgetHeight;
// Project our position onto the hemisphere using the UV coordinates
float lX = sin((newULeft - 0.5f) * _textureFov);
float rX = sin((newURight - 0.5f) * _textureFov);
float bY = sin((newVBottom - 0.5f) * _textureFov);
float tY = sin((newVTop - 0.5f) * _textureFov);
float radius = _oculusuiRadius * application->getAvatar()->getScale();
float radius2 = radius * radius;
float lX = radius * sin((newULeft - 0.5f) * _textureFov);
float rX = radius * sin((newURight - 0.5f) * _textureFov);
float bY = radius * sin((newVBottom - 0.5f) * _textureFov);
float tY = radius * sin((newVTop - 0.5f) * _textureFov);
float blZ, tlZ, brZ, trZ;
float dist;
float discriminant;
//Bottom Left
dist = sqrt(lX * lX + bY * bY);
discriminant = 1.0f - dist * dist;
discriminant = radius2 - dist * dist;
if (discriminant > 0) {
blZ = sqrt(discriminant);
} else {
@ -682,7 +880,7 @@ void ApplicationOverlay::renderMagnifier(int mouseX, int mouseY, float sizeMult,
}
//Top Left
dist = sqrt(lX * lX + tY * tY);
discriminant = 1.0f - dist * dist;
discriminant = radius2 - dist * dist;
if (discriminant > 0) {
tlZ = sqrt(discriminant);
} else {
@ -690,7 +888,7 @@ void ApplicationOverlay::renderMagnifier(int mouseX, int mouseY, float sizeMult,
}
//Bottom Right
dist = sqrt(rX * rX + bY * bY);
discriminant = 1.0f - dist * dist;
discriminant = radius2 - dist * dist;
if (discriminant > 0) {
brZ = sqrt(discriminant);
} else {
@ -698,7 +896,7 @@ void ApplicationOverlay::renderMagnifier(int mouseX, int mouseY, float sizeMult,
}
//Top Right
dist = sqrt(rX * rX + tY * tY);
discriminant = 1.0f - dist * dist;
discriminant = radius2 - dist * dist;
if (discriminant > 0) {
trZ = sqrt(discriminant);
} else {
@ -988,9 +1186,25 @@ void ApplicationOverlay::renderTexturedHemisphere() {
glVertexPointer(3, GL_FLOAT, sizeof(TextureVertex), (void*)0);
glTexCoordPointer(2, GL_FLOAT, sizeof(TextureVertex), (void*)12);
glPushMatrix();
Application* application = Application::getInstance();
MyAvatar* myAvatar = application->getAvatar();
const glm::quat& orientation = myAvatar->getOrientation();
const glm::vec3& position = myAvatar->getHead()->calculateAverageEyePosition();
glm::mat4 rotation = glm::toMat4(orientation);
glTranslatef(position.x, position.y, position.z);
glMultMatrixf(&rotation[0][0]);
const float scale = _oculusuiRadius * myAvatar->getScale();
glScalef(scale, scale, scale);
glDrawRangeElements(GL_TRIANGLES, 0, vertices - 1, indices, GL_UNSIGNED_SHORT, 0);
glPopMatrix();
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
@ -999,17 +1213,13 @@ void ApplicationOverlay::renderTexturedHemisphere() {
}
void ApplicationOverlay::resize() {
if (_framebufferObject != NULL) {
delete _framebufferObject;
_framebufferObject = NULL;
}
// _framebufferObject is recreated at the correct size the next time it is accessed via getFramebufferObject().
}
QOpenGLFramebufferObject* ApplicationOverlay::getFramebufferObject() {
if (!_framebufferObject) {
_framebufferObject = new QOpenGLFramebufferObject(Application::getInstance()->getGLWidget()->size());
QSize size = Application::getInstance()->getGLWidget()->size();
if (!_framebufferObject || _framebufferObject->size() != size) {
delete _framebufferObject;
_framebufferObject = new QOpenGLFramebufferObject(size);
glBindTexture(GL_TEXTURE_2D, _framebufferObject->texture());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

View file

@ -32,7 +32,9 @@ public:
void displayOverlayTexture3DTV(Camera& whichCamera, float aspectRatio, float fov);
void computeOculusPickRay(float x, float y, glm::vec3& direction) const;
void getClickLocation(int &x, int &y) const;
void resize();
QPoint getOculusPalmClickLocation(const PalmData *palm) const;
bool calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction, glm::vec3& result) const;
// Getters
QOpenGLFramebufferObject* getFramebufferObject();
@ -49,7 +51,7 @@ private:
void renderPointers();
void renderControllerPointers();
void renderControllerPointersOculus();
void renderPointersOculus(const glm::vec3& eyePos);
void renderMagnifier(int mouseX, int mouseY, float sizeMult, bool showBorder) const;
void renderAudioMeter();
void renderStatsAndLogs();
@ -69,6 +71,7 @@ private:
float _magSizeMult[NUMBER_OF_MAGNIFIERS];
float _alpha;
float _oculusuiRadius;
GLuint _crosshairTexture;
};

View file

@ -24,6 +24,7 @@
#include "InterfaceConfig.h"
#include "Menu.h"
#include "Util.h"
#include "SequenceNumberStats.h"
using namespace std;
@ -161,36 +162,25 @@ void Stats::drawBackground(unsigned int rgba, int x, int y, int width, int heigh
}
bool Stats::includeTimingRecord(const QString& name) {
bool included = false;
if (Menu::getInstance()->isOptionChecked(MenuOption::DisplayTimingDetails)) {
if (name == "idle/update") {
included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandUpdateTiming) ||
Menu::getInstance()->isOptionChecked(MenuOption::ExpandIdleTiming);
} else if (name == "idle/updateGL") {
included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandIdleTiming);
} else if (name.startsWith("idle/update")) {
included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandUpdateTiming);
} else if (name.startsWith("idle/")) {
included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandIdleTiming);
} else if (name.startsWith("MyAvatar::simulate")) {
included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandAvatarSimulateTiming);
} else if (name.startsWith("MyAvatar::update/") || name.startsWith("updateMyAvatar")) {
included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandAvatarUpdateTiming);
} else if (name.startsWith("MyAvatar::")) {
included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandMiscAvatarTiming);
} else if (name == "paintGL/displaySide") {
included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandDisplaySideTiming) ||
Menu::getInstance()->isOptionChecked(MenuOption::ExpandPaintGLTiming);
} else if (name.startsWith("paintGL/displaySide/")) {
included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandDisplaySideTiming);
} else if (name.startsWith("paintGL/")) {
included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandPaintGLTiming);
} else {
included = true; // include everything else
if (name.startsWith("/idle/update/")) {
if (name.startsWith("/idle/update/myAvatar/")) {
if (name.startsWith("/idle/update/myAvatar/simulate/")) {
return Menu::getInstance()->isOptionChecked(MenuOption::ExpandMyAvatarSimulateTiming);
}
return Menu::getInstance()->isOptionChecked(MenuOption::ExpandMyAvatarTiming);
} else if (name.startsWith("/idle/update/otherAvatars/")) {
return Menu::getInstance()->isOptionChecked(MenuOption::ExpandOtherAvatarTiming);
}
return Menu::getInstance()->isOptionChecked(MenuOption::ExpandUpdateTiming);
} else if (name.startsWith("/idle/updateGL/paintGL/")) {
return Menu::getInstance()->isOptionChecked(MenuOption::ExpandPaintGLTiming);
} else if (name.startsWith("/paintGL/")) {
return Menu::getInstance()->isOptionChecked(MenuOption::ExpandPaintGLTiming);
}
return true;
}
return included;
return false;
}
// display expanded or contracted stats
@ -288,15 +278,12 @@ void Stats::display(
Audio* audio = Application::getInstance()->getAudio();
const AudioStreamStats& audioMixerAvatarStreamStats = audio->getAudioMixerAvatarStreamStats();
const QHash<QUuid, AudioStreamStats>& audioMixerInjectedStreamStatsMap = audio->getAudioMixerInjectedStreamStatsMap();
const QHash<QUuid, AudioStreamStats>& audioMixerInjectedStreamAudioStatsMap = audio->getAudioMixerInjectedStreamAudioStatsMap();
lines = _expanded ? 10 + audioMixerInjectedStreamStatsMap.size(): 3;
lines = _expanded ? 11 + (audioMixerInjectedStreamAudioStatsMap.size() + 2) * 3 : 3;
drawBackground(backgroundColor, horizontalOffset, 0, _pingStatsWidth, lines * STATS_PELS_PER_LINE + 10);
horizontalOffset += 5;
char audioJitter[30];
sprintf(audioJitter,
"Buffer msecs %.1f",
@ -328,43 +315,103 @@ void Stats::display(
drawText(horizontalOffset, verticalOffset, scale, rotation, font, voxelMaxPing, color);
char audioMixerStatsLabelString[] = "AudioMixer stats:";
char streamStatsFormatLabelString[] = "early/late/lost, jframes";
char streamStatsFormatLabelString[] = "lost%/30s_lost%";
char streamStatsFormatLabelString2[] = "avail/currJ/desiredJ";
char streamStatsFormatLabelString3[] = "gaps: min/max/avg, starv/ovfl";
char streamStatsFormatLabelString4[] = "30s gaps: (same), notmix/sdrop";
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, audioMixerStatsLabelString, color);
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, streamStatsFormatLabelString, color);
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, streamStatsFormatLabelString2, color);
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, streamStatsFormatLabelString3, color);
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, streamStatsFormatLabelString4, color);
char downstreamLabelString[] = " Downstream:";
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamLabelString, color);
const SequenceNumberStats& downstreamAudioSequenceNumberStats = audio->getIncomingMixedAudioSequenceNumberStats();
char downstreamAudioStatsString[30];
sprintf(downstreamAudioStatsString, " mix: %d/%d/%d, %d", downstreamAudioSequenceNumberStats.getNumEarly(),
downstreamAudioSequenceNumberStats.getNumLate(), downstreamAudioSequenceNumberStats.getNumLost(),
audio->getJitterBufferSamples() / NETWORK_BUFFER_LENGTH_SAMPLES_STEREO);
AudioStreamStats downstreamAudioStreamStats = audio->getDownstreamAudioStreamStats();
sprintf(downstreamAudioStatsString, " mix: %.1f%%/%.1f%%, %u/?/%u", downstreamAudioStreamStats._packetStreamStats.getLostRate()*100.0f,
downstreamAudioStreamStats._packetStreamWindowStats.getLostRate() * 100.0f,
downstreamAudioStreamStats._ringBufferFramesAvailable, downstreamAudioStreamStats._ringBufferDesiredJitterBufferFrames);
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamAudioStatsString, color);
sprintf(downstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", downstreamAudioStreamStats._timeGapMin,
downstreamAudioStreamStats._timeGapMax, downstreamAudioStreamStats._timeGapAverage,
downstreamAudioStreamStats._ringBufferStarveCount, downstreamAudioStreamStats._ringBufferOverflowCount);
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamAudioStatsString, color);
sprintf(downstreamAudioStatsString, " %llu/%llu/%.2f, %u/?", downstreamAudioStreamStats._timeGapWindowMin,
downstreamAudioStreamStats._timeGapWindowMax, downstreamAudioStreamStats._timeGapWindowAverage,
downstreamAudioStreamStats._ringBufferConsecutiveNotMixedCount);
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamAudioStatsString, color);
char upstreamLabelString[] = " Upstream:";
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamLabelString, color);
char upstreamAudioStatsString[30];
sprintf(upstreamAudioStatsString, " mic: %d/%d/%d, %d", audioMixerAvatarStreamStats._packetsEarly,
audioMixerAvatarStreamStats._packetsLate, audioMixerAvatarStreamStats._packetsLost,
audioMixerAvatarStreamStats._jitterBufferFrames);
const AudioStreamStats& audioMixerAvatarAudioStreamStats = audio->getAudioMixerAvatarStreamAudioStats();
sprintf(upstreamAudioStatsString, " mic: %.1f%%/%.1f%%, %u/%u/%u", audioMixerAvatarAudioStreamStats._packetStreamStats.getLostRate()*100.0f,
audioMixerAvatarAudioStreamStats._packetStreamWindowStats.getLostRate() * 100.0f,
audioMixerAvatarAudioStreamStats._ringBufferFramesAvailable, audioMixerAvatarAudioStreamStats._ringBufferCurrentJitterBufferFrames,
audioMixerAvatarAudioStreamStats._ringBufferDesiredJitterBufferFrames);
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color);
foreach(AudioStreamStats injectedStreamStats, audioMixerInjectedStreamStatsMap) {
sprintf(upstreamAudioStatsString, " inj: %d/%d/%d, %d", injectedStreamStats._packetsEarly,
injectedStreamStats._packetsLate, injectedStreamStats._packetsLost, injectedStreamStats._jitterBufferFrames);
sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", audioMixerAvatarAudioStreamStats._timeGapMin,
audioMixerAvatarAudioStreamStats._timeGapMax, audioMixerAvatarAudioStreamStats._timeGapAverage,
audioMixerAvatarAudioStreamStats._ringBufferStarveCount, audioMixerAvatarAudioStreamStats._ringBufferOverflowCount);
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color);
sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", audioMixerAvatarAudioStreamStats._timeGapWindowMin,
audioMixerAvatarAudioStreamStats._timeGapWindowMax, audioMixerAvatarAudioStreamStats._timeGapWindowAverage,
audioMixerAvatarAudioStreamStats._ringBufferConsecutiveNotMixedCount, audioMixerAvatarAudioStreamStats._ringBufferSilentFramesDropped);
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color);
foreach(const AudioStreamStats& injectedStreamAudioStats, audioMixerInjectedStreamAudioStatsMap) {
sprintf(upstreamAudioStatsString, " inj: %.1f%%/%.1f%%, %u/%u/%u", injectedStreamAudioStats._packetStreamStats.getLostRate()*100.0f,
injectedStreamAudioStats._packetStreamWindowStats.getLostRate() * 100.0f,
injectedStreamAudioStats._ringBufferFramesAvailable, injectedStreamAudioStats._ringBufferCurrentJitterBufferFrames,
injectedStreamAudioStats._ringBufferDesiredJitterBufferFrames);
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color);
sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", injectedStreamAudioStats._timeGapMin,
injectedStreamAudioStats._timeGapMax, injectedStreamAudioStats._timeGapAverage,
injectedStreamAudioStats._ringBufferStarveCount, injectedStreamAudioStats._ringBufferOverflowCount);
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color);
sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", injectedStreamAudioStats._timeGapWindowMin,
injectedStreamAudioStats._timeGapWindowMax, injectedStreamAudioStats._timeGapWindowAverage,
injectedStreamAudioStats._ringBufferConsecutiveNotMixedCount, injectedStreamAudioStats._ringBufferSilentFramesDropped);
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color);
}
@ -377,7 +424,7 @@ void Stats::display(
MyAvatar* myAvatar = Application::getInstance()->getAvatar();
glm::vec3 avatarPos = myAvatar->getPosition();
lines = _expanded ? 5 : 3;
lines = _expanded ? 8 : 3;
drawBackground(backgroundColor, horizontalOffset, 0, _geoStatsWidth, lines * STATS_PELS_PER_LINE + 10);
horizontalOffset += 5;
@ -419,6 +466,41 @@ void Stats::display(
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, downloads.str().c_str(), color);
int internal = 0, leaves = 0;
int sendProgress = 0, sendTotal = 0;
int receiveProgress = 0, receiveTotal = 0;
foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
if (node->getType() == NodeType::MetavoxelServer) {
QMutexLocker locker(&node->getMutex());
MetavoxelClient* client = static_cast<MetavoxelSystemClient*>(node->getLinkedData());
if (client) {
client->getData().countNodes(internal, leaves, Application::getInstance()->getMetavoxels()->getLOD());
client->getSequencer().addReliableChannelStats(sendProgress, sendTotal, receiveProgress, receiveTotal);
}
}
}
stringstream nodes;
nodes << "Metavoxels: " << (internal + leaves);
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, nodes.str().c_str(), color);
stringstream nodeTypes;
nodeTypes << "Internal: " << internal << " Leaves: " << leaves;
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, nodeTypes.str().c_str(), color);
if (sendTotal > 0 || receiveTotal > 0) {
stringstream reliableStats;
if (sendTotal > 0) {
reliableStats << "Upload: " << (sendProgress * 100 / sendTotal) << "% ";
}
if (receiveTotal > 0) {
reliableStats << "Download: " << (receiveProgress * 100 / receiveTotal) << "%";
}
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, reliableStats.str().c_str(), color);
}
}
verticalOffset = 0;

View file

@ -14,28 +14,35 @@
#include "BillboardOverlay.h"
BillboardOverlay::BillboardOverlay()
: _scale(1.0f),
: _fromImage(-1,-1,-1,-1),
_scale(1.0f),
_isFacingAvatar(true) {
}
void BillboardOverlay::render() {
if (_billboard.isEmpty()) {
if (!_visible) {
return;
}
if (!_billboardTexture) {
QImage image = QImage::fromData(_billboard);
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
if (!_billboard.isEmpty()) {
if (!_billboardTexture) {
QImage image = QImage::fromData(_billboard);
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
}
_size = image.size();
if (_fromImage.x() == -1) {
_fromImage.setRect(0, 0, _size.width(), _size.height());
}
_billboardTexture.reset(new Texture());
glBindTexture(GL_TEXTURE_2D, _billboardTexture->getID());
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _size.width(), _size.height(), 0,
GL_BGRA, GL_UNSIGNED_BYTE, image.constBits());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
} else {
glBindTexture(GL_TEXTURE_2D, _billboardTexture->getID());
}
_size = image.size();
_billboardTexture.reset(new Texture());
glBindTexture(GL_TEXTURE_2D, _billboardTexture->getID());
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _size.width(), _size.height(), 0,
GL_BGRA, GL_UNSIGNED_BYTE, image.constBits());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
} else {
glBindTexture(GL_TEXTURE_2D, _billboardTexture->getID());
}
glEnable(GL_ALPHA_TEST);
@ -58,21 +65,35 @@ void BillboardOverlay::render() {
}
glScalef(_scale, _scale, _scale);
float maxSize = glm::max(_size.width(), _size.height());
float x = _size.width() / (2.0f * maxSize);
float y = -_size.height() / (2.0f * maxSize);
glColor3f(1.0f, 1.0f, 1.0f);
glBegin(GL_QUADS); {
glTexCoord2f(0.0f, 0.0f);
glVertex2f(-x, -y);
glTexCoord2f(1.0f, 0.0f);
glVertex2f(x, -y);
glTexCoord2f(1.0f, 1.0f);
glVertex2f(x, y);
glTexCoord2f(0.0f, 1.0f);
glVertex2f(-x, y);
} glEnd();
if (_billboardTexture) {
float maxSize = glm::max(_fromImage.width(), _fromImage.height());
float x = _fromImage.width() / (2.0f * maxSize);
float y = -_fromImage.height() / (2.0f * maxSize);
glColor3f(1.0f, 1.0f, 1.0f);
glBegin(GL_QUADS); {
glTexCoord2f((float)_fromImage.x() / (float)_size.width(),
(float)_fromImage.y() / (float)_size.height());
glVertex2f(-x, -y);
glTexCoord2f(((float)_fromImage.x() + (float)_fromImage.width()) / (float)_size.width(),
(float)_fromImage.y() / (float)_size.height());
glVertex2f(x, -y);
glTexCoord2f(((float)_fromImage.x() + (float)_fromImage.width()) / (float)_size.width(),
((float)_fromImage.y() + (float)_fromImage.height()) / _size.height());
glVertex2f(x, y);
glTexCoord2f((float)_fromImage.x() / (float)_size.width(),
((float)_fromImage.y() + (float)_fromImage.height()) / (float)_size.height());
glVertex2f(-x, y);
} glEnd();
} else {
glColor4f(0.5f, 0.5f, 0.5f, 1.0f);
glBegin(GL_QUADS); {
glVertex2f(-1.0f, -1.0f);
glVertex2f(1.0f, -1.0f);
glVertex2f(1.0f, 1.0f);
glVertex2f(-1.0f, 1.0f);
} glEnd();
}
} glPopMatrix();
@ -93,6 +114,33 @@ void BillboardOverlay::setProperties(const QScriptValue &properties) {
setBillboardURL(_url);
}
QScriptValue subImageBounds = properties.property("subImage");
if (subImageBounds.isValid()) {
QRect oldSubImageRect = _fromImage;
QRect subImageRect = _fromImage;
if (subImageBounds.property("x").isValid()) {
subImageRect.setX(subImageBounds.property("x").toVariant().toInt());
} else {
subImageRect.setX(oldSubImageRect.x());
}
if (subImageBounds.property("y").isValid()) {
subImageRect.setY(subImageBounds.property("y").toVariant().toInt());
} else {
subImageRect.setY(oldSubImageRect.y());
}
if (subImageBounds.property("width").isValid()) {
subImageRect.setWidth(subImageBounds.property("width").toVariant().toInt());
} else {
subImageRect.setWidth(oldSubImageRect.width());
}
if (subImageBounds.property("height").isValid()) {
subImageRect.setHeight(subImageBounds.property("height").toVariant().toInt());
} else {
subImageRect.setHeight(oldSubImageRect.height());
}
setClipFromSource(subImageRect);
}
QScriptValue scaleValue = properties.property("scale");
if (scaleValue.isValid()) {
_scale = scaleValue.toVariant().toFloat();

View file

@ -25,7 +25,8 @@ public:
virtual void render();
virtual void setProperties(const QScriptValue& properties);
void setClipFromSource(const QRect& bounds) { _fromImage = bounds; }
private slots:
void replyFinished();
@ -37,6 +38,8 @@ private:
QSize _size;
QScopedPointer<Texture> _billboardTexture;
QRect _fromImage; // where from in the image to sample
glm::quat _rotation;
float _scale;
bool _isFacingAvatar;

View file

@ -35,6 +35,10 @@ void ModelOverlay::update(float deltatime) {
}
void ModelOverlay::render() {
if (!_visible) {
return;
}
if (_model.isActive()) {
if (_model.isRenderable()) {

View file

@ -20,6 +20,7 @@
#include <QtCore/QIODevice>
#include "NodeData.h"
#include "SharedUtil.h"
const int SAMPLE_RATE = 24000;
@ -29,7 +30,7 @@ const int NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL = 512;
const int NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL = NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL / sizeof(int16_t);
const unsigned int BUFFER_SEND_INTERVAL_USECS = floorf((NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL
/ (float) SAMPLE_RATE) * 1000 * 1000);
/ (float) SAMPLE_RATE) * USECS_PER_SECOND);
const int MAX_SAMPLE_VALUE = std::numeric_limits<int16_t>::max();
const int MIN_SAMPLE_VALUE = std::numeric_limits<int16_t>::min();
@ -65,6 +66,9 @@ public:
void shiftReadPosition(unsigned int numSamples);
int samplesAvailable() const;
int framesAvailable() const { return samplesAvailable() / _numFrameSamples; }
int getNumFrameSamples() const { return _numFrameSamples; }
bool isNotStarvedOrHasMinimumSamples(int numRequiredSamples) const;

View file

@ -13,34 +13,50 @@
#define hifi_AudioStreamStats_h
#include "PositionalAudioRingBuffer.h"
#include "SequenceNumberStats.h"
class AudioStreamStats {
public:
AudioStreamStats()
: _streamType(PositionalAudioRingBuffer::Microphone),
_streamIdentifier(),
_jitterBufferFrames(0),
_packetsReceived(0),
_packetsUnreasonable(0),
_packetsEarly(0),
_packetsLate(0),
_packetsLost(0),
_packetsRecovered(0),
_packetsDuplicate(0)
_timeGapMin(0),
_timeGapMax(0),
_timeGapAverage(0.0f),
_timeGapWindowMin(0),
_timeGapWindowMax(0),
_timeGapWindowAverage(0.0f),
_ringBufferFramesAvailable(0),
_ringBufferCurrentJitterBufferFrames(0),
_ringBufferDesiredJitterBufferFrames(0),
_ringBufferStarveCount(0),
_ringBufferConsecutiveNotMixedCount(0),
_ringBufferOverflowCount(0),
_ringBufferSilentFramesDropped(0),
_packetStreamStats(),
_packetStreamWindowStats()
{}
PositionalAudioRingBuffer::Type _streamType;
QUuid _streamIdentifier;
quint16 _jitterBufferFrames;
quint64 _timeGapMin;
quint64 _timeGapMax;
float _timeGapAverage;
quint64 _timeGapWindowMin;
quint64 _timeGapWindowMax;
float _timeGapWindowAverage;
quint32 _packetsReceived;
quint32 _packetsUnreasonable;
quint32 _packetsEarly;
quint32 _packetsLate;
quint32 _packetsLost;
quint32 _packetsRecovered;
quint32 _packetsDuplicate;
quint32 _ringBufferFramesAvailable;
quint16 _ringBufferCurrentJitterBufferFrames;
quint16 _ringBufferDesiredJitterBufferFrames;
quint32 _ringBufferStarveCount;
quint32 _ringBufferConsecutiveNotMixedCount;
quint32 _ringBufferOverflowCount;
quint32 _ringBufferSilentFramesDropped;
PacketStreamStats _packetStreamStats;
PacketStreamStats _packetStreamWindowStats;
};
#endif // hifi_AudioStreamStats_h

View file

@ -31,7 +31,7 @@ InjectedAudioRingBuffer::InjectedAudioRingBuffer(const QUuid& streamIdentifier,
const uchar MAX_INJECTOR_VOLUME = 255;
int InjectedAudioRingBuffer::parseData(const QByteArray& packet) {
_interframeTimeGapStats.frameReceived();
timeGapStatsFrameReceived();
updateDesiredJitterBufferFrames();
// setup a data stream to read from this packet

View file

@ -21,70 +21,6 @@
#include "PositionalAudioRingBuffer.h"
#include "SharedUtil.h"
InterframeTimeGapStats::InterframeTimeGapStats()
: _lastFrameReceivedTime(0),
_numSamplesInCurrentInterval(0),
_currentIntervalMaxGap(0),
_newestIntervalMaxGapAt(0),
_windowMaxGap(0),
_newWindowMaxGapAvailable(false)
{
memset(_intervalMaxGaps, 0, TIME_GAP_NUM_INTERVALS_IN_WINDOW * sizeof(quint64));
}
void InterframeTimeGapStats::frameReceived() {
quint64 now = usecTimestampNow();
// make sure this isn't the first time frameReceived() is called so can actually calculate a gap.
if (_lastFrameReceivedTime != 0) {
quint64 gap = now - _lastFrameReceivedTime;
// update the current interval max
if (gap > _currentIntervalMaxGap) {
_currentIntervalMaxGap = gap;
// keep the window max gap at least as large as the current interval max
// this allows the window max gap to respond immediately to a sudden spike in gap times
// also, this prevents the window max gap from staying at 0 until the first interval of samples filled up
if (_currentIntervalMaxGap > _windowMaxGap) {
_windowMaxGap = _currentIntervalMaxGap;
_newWindowMaxGapAvailable = true;
}
}
_numSamplesInCurrentInterval++;
// if the current interval of samples is now full, record it in our interval maxes
if (_numSamplesInCurrentInterval == TIME_GAP_NUM_SAMPLES_IN_INTERVAL) {
// find location to insert this interval's max (increment index cyclically)
_newestIntervalMaxGapAt = _newestIntervalMaxGapAt == TIME_GAP_NUM_INTERVALS_IN_WINDOW - 1 ? 0 : _newestIntervalMaxGapAt + 1;
// record the current interval's max gap as the newest
_intervalMaxGaps[_newestIntervalMaxGapAt] = _currentIntervalMaxGap;
// update the window max gap, which is the max out of all the past intervals' max gaps
_windowMaxGap = 0;
for (int i = 0; i < TIME_GAP_NUM_INTERVALS_IN_WINDOW; i++) {
if (_intervalMaxGaps[i] > _windowMaxGap) {
_windowMaxGap = _intervalMaxGaps[i];
}
}
_newWindowMaxGapAvailable = true;
// reset the current interval
_numSamplesInCurrentInterval = 0;
_currentIntervalMaxGap = 0;
}
}
_lastFrameReceivedTime = now;
}
quint64 InterframeTimeGapStats::getWindowMaxGap() {
_newWindowMaxGapAvailable = false;
return _windowMaxGap;
}
PositionalAudioRingBuffer::PositionalAudioRingBuffer(PositionalAudioRingBuffer::Type type, bool isStereo, bool dynamicJitterBuffers) :
AudioRingBuffer(isStereo ? NETWORK_BUFFER_LENGTH_SAMPLES_STEREO : NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL,
@ -97,10 +33,15 @@ PositionalAudioRingBuffer::PositionalAudioRingBuffer(PositionalAudioRingBuffer::
_shouldOutputStarveDebug(true),
_isStereo(isStereo),
_listenerUnattenuatedZone(NULL),
_lastFrameReceivedTime(0),
_interframeTimeGapStatsForJitterCalc(TIME_GAPS_FOR_JITTER_CALC_INTERVAL_SAMPLES, TIME_GAPS_FOR_JITTER_CALC_WINDOW_INTERVALS),
_interframeTimeGapStatsForStatsPacket(TIME_GAPS_FOR_STATS_PACKET_INTERVAL_SAMPLES, TIME_GAPS_FOR_STATS_PACKET_WINDOW_INTERVALS),
_desiredJitterBufferFrames(1),
_currentJitterBufferFrames(-1),
_dynamicJitterBuffers(dynamicJitterBuffers),
_consecutiveNotMixedCount(0)
_consecutiveNotMixedCount(0),
_starveCount(0),
_silentFramesDropped(0)
{
}
@ -143,9 +84,12 @@ int PositionalAudioRingBuffer::parseData(const QByteArray& packet) {
addSilentFrame(numSilentFramesToAdd * samplesPerFrame);
_currentJitterBufferFrames = _desiredJitterBufferFrames;
_silentFramesDropped += numFramesToDropDesired;
} else {
// we need to drop all frames to get the jitter buffer close as possible to its desired length
_currentJitterBufferFrames -= numSilentFrames;
_silentFramesDropped += numSilentFrames;
}
} else {
addSilentFrame(numSilentSamples);
@ -217,6 +161,7 @@ bool PositionalAudioRingBuffer::shouldBeAddedToMix() {
} else if (samplesAvailable() < samplesPerFrame) {
// if the buffer doesn't have a full frame of samples to take for mixing, it is starved
_isStarved = true;
_starveCount++;
// set to -1 to indicate the jitter buffer is starved
_currentJitterBufferFrames = -1;
@ -224,7 +169,7 @@ bool PositionalAudioRingBuffer::shouldBeAddedToMix() {
// reset our _shouldOutputStarveDebug to true so the next is printed
_shouldOutputStarveDebug = true;
_consecutiveNotMixedCount++;
_consecutiveNotMixedCount = 1;
return false;
}
@ -234,7 +179,6 @@ bool PositionalAudioRingBuffer::shouldBeAddedToMix() {
// minus one (since a frame will be read immediately after this) is the length of the jitter buffer
_currentJitterBufferFrames = samplesAvailable() / samplesPerFrame - 1;
_isStarved = false;
_consecutiveNotMixedCount = 0;
}
// since we've read data from ring buffer at least once - we've started
@ -247,21 +191,31 @@ int PositionalAudioRingBuffer::getCalculatedDesiredJitterBufferFrames() const {
int calculatedDesiredJitterBufferFrames = 1;
const float USECS_PER_FRAME = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * USECS_PER_SECOND / (float)SAMPLE_RATE;
calculatedDesiredJitterBufferFrames = ceilf((float)_interframeTimeGapStats.peekWindowMaxGap() / USECS_PER_FRAME);
calculatedDesiredJitterBufferFrames = ceilf((float)_interframeTimeGapStatsForJitterCalc.getWindowMax() / USECS_PER_FRAME);
if (calculatedDesiredJitterBufferFrames < 1) {
calculatedDesiredJitterBufferFrames = 1;
}
return calculatedDesiredJitterBufferFrames;
}
void PositionalAudioRingBuffer::timeGapStatsFrameReceived() {
quint64 now = usecTimestampNow();
if (_lastFrameReceivedTime != 0) {
quint64 gap = now - _lastFrameReceivedTime;
_interframeTimeGapStatsForJitterCalc.update(gap);
_interframeTimeGapStatsForStatsPacket.update(gap);
}
_lastFrameReceivedTime = now;
}
void PositionalAudioRingBuffer::updateDesiredJitterBufferFrames() {
if (_interframeTimeGapStats.hasNewWindowMaxGapAvailable()) {
if (_interframeTimeGapStatsForJitterCalc.getNewStatsAvailableFlag()) {
if (!_dynamicJitterBuffers) {
_desiredJitterBufferFrames = 1; // HACK to see if this fixes the audio silence
} else {
const float USECS_PER_FRAME = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * USECS_PER_SECOND / (float)SAMPLE_RATE;
_desiredJitterBufferFrames = ceilf((float)_interframeTimeGapStats.getWindowMaxGap() / USECS_PER_FRAME);
_desiredJitterBufferFrames = ceilf((float)_interframeTimeGapStatsForJitterCalc.getWindowMax() / USECS_PER_FRAME);
if (_desiredJitterBufferFrames < 1) {
_desiredJitterBufferFrames = 1;
}
@ -270,5 +224,6 @@ void PositionalAudioRingBuffer::updateDesiredJitterBufferFrames() {
_desiredJitterBufferFrames = maxDesired;
}
}
_interframeTimeGapStatsForJitterCalc.clearNewStatsAvailableFlag();
}
}

View file

@ -17,31 +17,17 @@
#include <AABox.h>
#include "AudioRingBuffer.h"
#include "MovingMinMaxAvg.h"
// this means that every 500 samples, the max for the past 10*500 samples will be calculated
const int TIME_GAP_NUM_SAMPLES_IN_INTERVAL = 500;
const int TIME_GAP_NUM_INTERVALS_IN_WINDOW = 10;
// the time gaps stats for _desiredJitterBufferFrames calculation
// will recalculate the max for the past 5000 samples every 500 samples
const int TIME_GAPS_FOR_JITTER_CALC_INTERVAL_SAMPLES = 500;
const int TIME_GAPS_FOR_JITTER_CALC_WINDOW_INTERVALS = 10;
// class used to track time between incoming frames for the purpose of varying the jitter buffer length
class InterframeTimeGapStats {
public:
InterframeTimeGapStats();
void frameReceived();
bool hasNewWindowMaxGapAvailable() const { return _newWindowMaxGapAvailable; }
quint64 peekWindowMaxGap() const { return _windowMaxGap; }
quint64 getWindowMaxGap();
private:
quint64 _lastFrameReceivedTime;
int _numSamplesInCurrentInterval;
quint64 _currentIntervalMaxGap;
quint64 _intervalMaxGaps[TIME_GAP_NUM_INTERVALS_IN_WINDOW];
int _newestIntervalMaxGapAt;
quint64 _windowMaxGap;
bool _newWindowMaxGapAvailable;
};
// the time gap stats for constructing AudioStreamStats will
// recalculate min/max/avg every ~1 second for the past ~30 seconds of time gap data
const int TIME_GAPS_FOR_STATS_PACKET_INTERVAL_SAMPLES = USECS_PER_SECOND / BUFFER_SEND_INTERVAL_USECS;
const int TIME_GAPS_FOR_STATS_PACKET_WINDOW_INTERVALS = 30;
const int AUDIOMIXER_INBOUND_RING_BUFFER_FRAME_CAPACITY = 100;
@ -79,17 +65,22 @@ public:
int getSamplesPerFrame() const { return _isStereo ? NETWORK_BUFFER_LENGTH_SAMPLES_STEREO : NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL; }
const MovingMinMaxAvg<quint64>& getInterframeTimeGapStatsForStatsPacket() const { return _interframeTimeGapStatsForStatsPacket; }
int getCalculatedDesiredJitterBufferFrames() const; /// returns what we would calculate our desired as if asked
int getDesiredJitterBufferFrames() const { return _desiredJitterBufferFrames; }
int getCurrentJitterBufferFrames() const { return _currentJitterBufferFrames; }
int getConsecutiveNotMixedCount() const { return _consecutiveNotMixedCount; }
int getStarveCount() const { return _starveCount; }
int getSilentFramesDropped() const { return _silentFramesDropped; }
protected:
// disallow copying of PositionalAudioRingBuffer objects
PositionalAudioRingBuffer(const PositionalAudioRingBuffer&);
PositionalAudioRingBuffer& operator= (const PositionalAudioRingBuffer&);
void timeGapStatsFrameReceived();
void updateDesiredJitterBufferFrames();
PositionalAudioRingBuffer::Type _type;
@ -103,13 +94,18 @@ protected:
float _nextOutputTrailingLoudness;
AABox* _listenerUnattenuatedZone;
InterframeTimeGapStats _interframeTimeGapStats;
quint64 _lastFrameReceivedTime;
MovingMinMaxAvg<quint64> _interframeTimeGapStatsForJitterCalc;
MovingMinMaxAvg<quint64> _interframeTimeGapStatsForStatsPacket;
int _desiredJitterBufferFrames;
int _currentJitterBufferFrames;
bool _dynamicJitterBuffers;
// extra stats
int _consecutiveNotMixedCount;
int _starveCount;
int _silentFramesDropped;
};
#endif // hifi_PositionalAudioRingBuffer_h

View file

@ -490,11 +490,11 @@ int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) {
for (int i = 0; i < numJoints; i++) {
JointData& data = _jointData[i];
if (data.valid) {
_hasNewJointRotations = true;
sourceBuffer += unpackOrientationQuatFromBytes(sourceBuffer, data.rotation);
}
}
} // numJoints * 8 bytes
_hasNewJointRotations = true;
return sourceBuffer - startPosition;
}

View file

@ -108,12 +108,6 @@ glm::quat HandData::getBaseOrientation() const {
glm::vec3 HandData::getBasePosition() const {
return _owningAvatarData->getPosition();
}
glm::vec3 PalmData::getFingerTipPosition() const {
glm::vec3 fingerOffset(0.0f, 0.0f, 0.3f);
glm::vec3 palmOffset(0.0f, -0.08f, 0.0f);
return getPosition() + _owningHandData->localToWorldDirection(_rawRotation * (fingerOffset + palmOffset));
}
glm::vec3 PalmData::getFingerDirection() const {
const glm::vec3 LOCAL_FINGER_DIRECTION(0.0f, 0.0f, 1.0f);

View file

@ -140,7 +140,6 @@ public:
void getBallHoldPosition(glm::vec3& position) const;
// return world-frame:
glm::vec3 getFingerTipPosition() const;
glm::vec3 getFingerDirection() const;
glm::vec3 getNormal() const;

View file

@ -9,7 +9,9 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QReadLocker>
#include <QScriptEngine>
#include <QWriteLocker>
#include "AttributeRegistry.h"
#include "MetavoxelData.h"
@ -69,6 +71,7 @@ AttributePointer AttributeRegistry::registerAttribute(AttributePointer attribute
if (!attribute) {
return attribute;
}
QWriteLocker locker(&_attributesLock);
AttributePointer& pointer = _attributes[attribute->getName()];
if (!pointer) {
pointer = attribute;
@ -77,9 +80,15 @@ AttributePointer AttributeRegistry::registerAttribute(AttributePointer attribute
}
void AttributeRegistry::deregisterAttribute(const QString& name) {
QWriteLocker locker(&_attributesLock);
_attributes.remove(name);
}
AttributePointer AttributeRegistry::getAttribute(const QString& name) {
QReadLocker locker(&_attributesLock);
return _attributes.value(name);
}
QScriptValue AttributeRegistry::getAttribute(QScriptContext* context, QScriptEngine* engine) {
return engine->newQObject(getInstance()->getAttribute(context->argument(0).toString()).data(), QScriptEngine::QtOwnership,
QScriptEngine::PreferExistingWrapperObject);
@ -559,6 +568,10 @@ void SpannerSetAttribute::readMetavoxelRoot(MetavoxelData& data, MetavoxelStream
}
data.insert(state.attribute, object);
}
// even if the root is empty, it should still exist
if (!data.getRoot(state.attribute)) {
data.createRoot(state.attribute);
}
}
void SpannerSetAttribute::writeMetavoxelRoot(const MetavoxelNode& root, MetavoxelStreamState& state) {
@ -577,6 +590,10 @@ void SpannerSetAttribute::readMetavoxelDelta(MetavoxelData& data,
}
data.toggle(state.attribute, object);
}
// even if the root is empty, it should still exist
if (!data.getRoot(state.attribute)) {
data.createRoot(state.attribute);
}
}
void SpannerSetAttribute::writeMetavoxelDelta(const MetavoxelNode& root,

View file

@ -14,6 +14,7 @@
#include <QHash>
#include <QObject>
#include <QReadWriteLock>
#include <QSharedPointer>
#include <QString>
#include <QWidget>
@ -61,11 +62,14 @@ public:
void deregisterAttribute(const QString& name);
/// Retrieves an attribute by name.
AttributePointer getAttribute(const QString& name) const { return _attributes.value(name); }
AttributePointer getAttribute(const QString& name);
/// Returns a reference to the attribute hash.
const QHash<QString, AttributePointer>& getAttributes() const { return _attributes; }
/// Returns a reference to the attributes lock.
QReadWriteLock& getAttributesLock() { return _attributesLock; }
/// Returns a reference to the standard SharedObjectPointer "guide" attribute.
const AttributePointer& getGuideAttribute() const { return _guideAttribute; }
@ -92,6 +96,8 @@ private:
static QScriptValue getAttribute(QScriptContext* context, QScriptEngine* engine);
QHash<QString, AttributePointer> _attributes;
QReadWriteLock _attributesLock;
AttributePointer _guideAttribute;
AttributePointer _spannersAttribute;
AttributePointer _colorAttribute;

View file

@ -87,6 +87,12 @@ IDStreamer& IDStreamer::operator>>(int& value) {
return *this;
}
void Bitstream::preThreadingInit() {
getObjectStreamers();
getEnumStreamers();
getEnumStreamersByName();
}
int Bitstream::registerMetaObject(const char* className, const QMetaObject* metaObject) {
getMetaObjects().insert(className, metaObject);

View file

@ -290,6 +290,11 @@ public:
QHash<int, SharedObjectPointer> sharedObjectValues;
};
/// Performs all of the various lazily initializations (of object streamers, etc.) If multiple threads need to use
/// Bitstream instances, call this beforehand to prevent errors from occurring when multiple threads attempt lazy
/// initialization simultaneously.
static void preThreadingInit();
/// Registers a metaobject under its name so that instances of it can be streamed. Consider using the REGISTER_META_OBJECT
/// at the top level of the source file associated with the class rather than calling this function directly.
/// \return zero; the function only returns a value so that it can be used in static initialization

View file

@ -79,6 +79,24 @@ ReliableChannel* DatagramSequencer::getReliableInputChannel(int index) {
return channel;
}
void DatagramSequencer::addReliableChannelStats(int& sendProgress, int& sendTotal,
int& receiveProgress, int& receiveTotal) const {
foreach (ReliableChannel* channel, _reliableOutputChannels) {
int sent, total;
if (channel->getMessageSendProgress(sent, total)) {
sendProgress += sent;
sendTotal += total;
}
}
foreach (ReliableChannel* channel, _reliableInputChannels) {
int received, total;
if (channel->getMessageReceiveProgress(received, total)) {
receiveProgress += received;
receiveTotal += total;
}
}
}
int DatagramSequencer::notePacketGroup(int desiredPackets) {
// figure out how much data we have enqueued and increase the number of packets desired
int totalAvailable = 0;
@ -684,6 +702,8 @@ void ReliableChannel::endMessage() {
quint32 length = _buffer.pos() - _messageLengthPlaceholder;
_buffer.writeBytes(_messageLengthPlaceholder, sizeof(quint32), (const char*)&length);
_messageReceivedOffset = getBytesWritten();
_messageSize = length;
}
void ReliableChannel::sendMessage(const QVariant& message) {
@ -692,6 +712,26 @@ void ReliableChannel::sendMessage(const QVariant& message) {
endMessage();
}
bool ReliableChannel::getMessageSendProgress(int& sent, int& total) const {
if (!_messagesEnabled || _offset >= _messageReceivedOffset) {
return false;
}
sent = qMax(0, _messageSize - (_messageReceivedOffset - _offset));
total = _messageSize;
return true;
}
bool ReliableChannel::getMessageReceiveProgress(int& received, int& total) const {
if (!_messagesEnabled || _buffer.bytesAvailable() < (int)sizeof(quint32)) {
return false;
}
quint32 length;
_buffer.readBytes(_buffer.pos(), sizeof(quint32), (char*)&length);
total = length;
received = _buffer.bytesAvailable();
return true;
}
void ReliableChannel::sendClearSharedObjectMessage(int id) {
ClearSharedObjectMessage message = { id };
sendMessage(QVariant::fromValue(message));
@ -717,7 +757,8 @@ ReliableChannel::ReliableChannel(DatagramSequencer* sequencer, int index, bool o
_offset(0),
_writePosition(0),
_writePositionResetPacketNumber(0),
_messagesEnabled(true) {
_messagesEnabled(true),
_messageReceivedOffset(0) {
_buffer.open(output ? QIODevice::WriteOnly : QIODevice::ReadOnly);
_dataStream.setByteOrder(QDataStream::LittleEndian);

View file

@ -108,6 +108,9 @@ public:
/// Returns the intput channel at the specified index, creating it if necessary.
ReliableChannel* getReliableInputChannel(int index = 0);
/// Adds stats for all reliable channels to the referenced variables.
void addReliableChannelStats(int& sendProgress, int& sendTotal, int& receiveProgress, int& receiveTotal) const;
/// Notes that we're sending a group of packets.
/// \param desiredPackets the number of packets we'd like to write in the group
/// \return the number of packets to write in the group
@ -376,6 +379,14 @@ public:
/// writes the message to the bitstream, then calls endMessage).
void sendMessage(const QVariant& message);
/// Determines the number of bytes uploaded towards the currently pending message.
/// \return true if there is a message pending, in which case the sent and total arguments will be set
bool getMessageSendProgress(int& sent, int& total) const;
/// Determines the number of bytes downloaded towards the currently pending message.
/// \return true if there is a message pending, in which case the received and total arguments will be set
bool getMessageReceiveProgress(int& received, int& total) const;
signals:
/// Fired when a framed message has been received on this channel.
@ -416,6 +427,8 @@ private:
SpanList _acknowledged;
bool _messagesEnabled;
int _messageLengthPlaceholder;
int _messageReceivedOffset;
int _messageSize;
};
#endif // hifi_DatagramSequencer_h

View file

@ -32,6 +32,8 @@ public:
PacketRecord* baselineReceiveRecord = NULL);
virtual ~Endpoint();
const DatagramSequencer& getSequencer() const { return _sequencer; }
virtual void update();
virtual int parseData(const QByteArray& packet);

View file

@ -10,6 +10,7 @@
//
#include <QDateTime>
#include <QDebugStateSaver>
#include <QScriptEngine>
#include <QtDebug>
@ -627,6 +628,33 @@ bool MetavoxelData::deepEquals(const MetavoxelData& other, const MetavoxelLOD& l
return true;
}
void MetavoxelData::countNodes(int& internal, int& leaves, const MetavoxelLOD& lod) const {
glm::vec3 minimum = getMinimum();
for (QHash<AttributePointer, MetavoxelNode*>::const_iterator it = _roots.constBegin(); it != _roots.constEnd(); it++) {
it.value()->countNodes(it.key(), minimum, _size, lod, internal, leaves);
}
}
void MetavoxelData::dumpStats(QDebug debug) const {
QDebugStateSaver saver(debug);
debug.nospace() << "[size=" << _size << ", roots=[";
int totalInternal = 0, totalLeaves = 0;
glm::vec3 minimum = getMinimum();
for (QHash<AttributePointer, MetavoxelNode*>::const_iterator it = _roots.constBegin(); it != _roots.constEnd(); it++) {
if (it != _roots.constBegin()) {
debug << ", ";
}
debug << it.key()->getName() << " (" << it.key()->metaObject()->className() << "): ";
int internal = 0, leaves = 0;
it.value()->countNodes(it.key(), minimum, _size, MetavoxelLOD(), internal, leaves);
debug << internal << " internal, " << leaves << " leaves, " << (internal + leaves) << " total";
totalInternal += internal;
totalLeaves += leaves;
}
debug << "], totalInternal=" << totalInternal << ", totalLeaves=" << totalLeaves <<
", grandTotal=" << (totalInternal + totalLeaves) << "]";
}
bool MetavoxelData::operator==(const MetavoxelData& other) const {
return _size == other._size && _roots == other._roots;
}
@ -1056,8 +1084,20 @@ void MetavoxelNode::getSpanners(const AttributePointer& attribute, const glm::ve
}
float nextSize = size * 0.5f;
for (int i = 0; i < CHILD_COUNT; i++) {
glm::vec3 nextMinimum = getNextMinimum(minimum, nextSize, i);
_children[i]->getSpanners(attribute, nextMinimum, nextSize, lod, results);
_children[i]->getSpanners(attribute, getNextMinimum(minimum, nextSize, i), nextSize, lod, results);
}
}
void MetavoxelNode::countNodes(const AttributePointer& attribute, const glm::vec3& minimum,
float size, const MetavoxelLOD& lod, int& internal, int& leaves) const {
if (isLeaf() || !lod.shouldSubdivide(minimum, size, attribute->getLODThresholdMultiplier())) {
leaves++;
return;
}
internal++;
float nextSize = size * 0.5f;
for (int i = 0; i < CHILD_COUNT; i++) {
_children[i]->countNodes(attribute, getNextMinimum(minimum, nextSize, i), nextSize, lod, internal, leaves);
}
}

View file

@ -125,6 +125,11 @@ public:
/// shallow comparison).
bool deepEquals(const MetavoxelData& other, const MetavoxelLOD& lod = MetavoxelLOD()) const;
/// Counts the nodes in the data.
void countNodes(int& internalNodes, int& leaves, const MetavoxelLOD& lod = MetavoxelLOD()) const;
void dumpStats(QDebug debug = QDebug(QtDebugMsg)) const;
bool operator==(const MetavoxelData& other) const;
bool operator!=(const MetavoxelData& other) const;
@ -221,6 +226,9 @@ public:
void getSpanners(const AttributePointer& attribute, const glm::vec3& minimum,
float size, const MetavoxelLOD& lod, SharedObjectSet& results) const;
void countNodes(const AttributePointer& attribute, const glm::vec3& minimum,
float size, const MetavoxelLOD& lod, int& internalNodes, int& leaves) const;
private:
Q_DISABLE_COPY(MetavoxelNode)

View file

@ -331,6 +331,7 @@ ModelItem ModelItem::fromEditPacket(const unsigned char* data, int length, int&
newModelItem.setCreatorTokenID(creatorTokenID);
newModelItem._newlyCreated = true;
valid = true;
} else {
// look up the existing modelItem
@ -339,20 +340,19 @@ ModelItem ModelItem::fromEditPacket(const unsigned char* data, int length, int&
// copy existing properties before over-writing with new properties
if (existingModelItem) {
newModelItem = *existingModelItem;
valid = true;
} else {
// the user attempted to edit a modelItem that doesn't exist
qDebug() << "user attempted to edit a modelItem that doesn't exist...";
qDebug() << "user attempted to edit a modelItem that doesn't exist... editID=" << editID;
// NOTE: even though this is a bad editID, we have to consume the edit details, so that
// the buffer doesn't get corrupted for further processing...
valid = false;
return newModelItem;
}
newModelItem._id = editID;
newModelItem._newlyCreated = false;
}
// if we got this far, then our result will be valid
valid = true;
// lastEdited
memcpy(&newModelItem._lastEdited, dataAt, sizeof(newModelItem._lastEdited));
dataAt += sizeof(newModelItem._lastEdited);

View file

@ -69,8 +69,9 @@ ModelItemProperties ModelsScriptingInterface::getModelProperties(ModelItemID mod
}
if (_modelTree) {
_modelTree->lockForRead();
const ModelItem* model = _modelTree->findModelByID(identity.id, true);
ModelItem* model = const_cast<ModelItem*>(_modelTree->findModelByID(identity.id, true));
if (model) {
model->setSittingPoints(_modelTree->getGeometryForModel(*model)->sittingPoints);
results.copyFromModelItem(*model);
} else {
results.setIsUnknownID();

View file

@ -78,6 +78,8 @@ PacketVersion versionForPacketType(PacketType type) {
return 2;
case PacketTypeModelErase:
return 1;
case PacketTypeAudioStreamStats:
return 1;
default:
return 0;
}

View file

@ -14,8 +14,6 @@
SentPacketHistory::SentPacketHistory(int size)
: _sentPackets(size),
_newestPacketAt(0),
_numExistingPackets(0),
_newestSequenceNumber(std::numeric_limits<uint16_t>::max())
{
}
@ -29,16 +27,8 @@ void SentPacketHistory::packetSent(uint16_t sequenceNumber, const QByteArray& pa
qDebug() << "Unexpected sequence number passed to SentPacketHistory::packetSent()!"
<< "Expected:" << expectedSequenceNumber << "Actual:" << sequenceNumber;
}
_newestSequenceNumber = sequenceNumber;
// increment _newestPacketAt cyclically, insert new packet there.
// this will overwrite the oldest packet in the buffer
_newestPacketAt = (_newestPacketAt == _sentPackets.size() - 1) ? 0 : _newestPacketAt + 1;
_sentPackets[_newestPacketAt] = packet;
if (_numExistingPackets < _sentPackets.size()) {
_numExistingPackets++;
}
_sentPackets.insert(packet);
}
const QByteArray* SentPacketHistory::getPacket(uint16_t sequenceNumber) const {
@ -51,13 +41,6 @@ const QByteArray* SentPacketHistory::getPacket(uint16_t sequenceNumber) const {
if (seqDiff < 0) {
seqDiff += UINT16_RANGE;
}
// if desired sequence number is too old to be found in the history, return null
if (seqDiff >= _numExistingPackets) {
return NULL;
}
int packetAt = _newestPacketAt - seqDiff;
if (packetAt < 0) {
packetAt += _sentPackets.size();
}
return &_sentPackets.at(packetAt);
return _sentPackets.get(seqDiff);
}

View file

@ -13,7 +13,7 @@
#include <stdint.h>
#include <qbytearray.h>
#include <qvector.h>
#include "RingBufferHistory.h"
#include "SequenceNumberStats.h"
@ -26,9 +26,7 @@ public:
const QByteArray* getPacket(uint16_t sequenceNumber) const;
private:
QVector<QByteArray> _sentPackets; // circular buffer
int _newestPacketAt;
int _numExistingPackets;
RingBufferHistory<QByteArray> _sentPackets; // circular buffer
uint16_t _newestSequenceNumber;
};

View file

@ -13,29 +13,19 @@
#include <limits>
SequenceNumberStats::SequenceNumberStats()
SequenceNumberStats::SequenceNumberStats(int statsHistoryLength)
: _lastReceived(std::numeric_limits<quint16>::max()),
_missingSet(),
_numReceived(0),
_numUnreasonable(0),
_numEarly(0),
_numLate(0),
_numLost(0),
_numRecovered(0),
_numDuplicate(0),
_lastSenderUUID()
_stats(),
_lastSenderUUID(),
_statsHistory(statsHistoryLength)
{
}
void SequenceNumberStats::reset() {
_missingSet.clear();
_numReceived = 0;
_numUnreasonable = 0;
_numEarly = 0;
_numLate = 0;
_numLost = 0;
_numRecovered = 0;
_numDuplicate = 0;
_stats = PacketStreamStats();
_statsHistory.clear();
}
static const int UINT16_RANGE = std::numeric_limits<uint16_t>::max() + 1;
@ -51,9 +41,9 @@ void SequenceNumberStats::sequenceNumberReceived(quint16 incoming, QUuid senderU
}
// determine our expected sequence number... handle rollover appropriately
quint16 expected = _numReceived > 0 ? _lastReceived + (quint16)1 : incoming;
quint16 expected = _stats._numReceived > 0 ? _lastReceived + (quint16)1 : incoming;
_numReceived++;
_stats._numReceived++;
if (incoming == expected) { // on time
_lastReceived = incoming;
@ -80,7 +70,7 @@ void SequenceNumberStats::sequenceNumberReceived(quint16 incoming, QUuid senderU
// ignore packet if gap is unreasonable
qDebug() << "ignoring unreasonable sequence number:" << incoming
<< "previous:" << _lastReceived;
_numUnreasonable++;
_stats._numUnreasonable++;
return;
}
@ -92,8 +82,8 @@ void SequenceNumberStats::sequenceNumberReceived(quint16 incoming, QUuid senderU
qDebug() << ">>>>>>>> missing gap=" << (incomingInt - expectedInt);
}
_numEarly++;
_numLost += (incomingInt - expectedInt);
_stats._numEarly++;
_stats._numLost += (incomingInt - expectedInt);
_lastReceived = incoming;
// add all sequence numbers that were skipped to the missing sequence numbers list
@ -110,7 +100,7 @@ void SequenceNumberStats::sequenceNumberReceived(quint16 incoming, QUuid senderU
if (wantExtraDebugging) {
qDebug() << "this packet is later than expected...";
}
_numLate++;
_stats._numLate++;
// do not update _lastReceived; it shouldn't become smaller
@ -119,13 +109,13 @@ void SequenceNumberStats::sequenceNumberReceived(quint16 incoming, QUuid senderU
if (wantExtraDebugging) {
qDebug() << "found it in _missingSet";
}
_numLost--;
_numRecovered++;
_stats._numLost--;
_stats._numRecovered++;
} else {
if (wantExtraDebugging) {
qDebug() << "sequence:" << incoming << "was NOT found in _missingSet and is probably a duplicate";
}
_numDuplicate++;
_stats._numDuplicate++;
}
}
}
@ -180,3 +170,26 @@ void SequenceNumberStats::pruneMissingSet(const bool wantExtraDebugging) {
}
}
}
PacketStreamStats SequenceNumberStats::getStatsForHistoryWindow() const {
const PacketStreamStats* newestStats = _statsHistory.getNewestEntry();
const PacketStreamStats* oldestStats = _statsHistory.get(_statsHistory.getNumEntries() - 1);
// this catches cases where history is length 1 or 0 (both are NULL in case of 0)
if (newestStats == oldestStats) {
return PacketStreamStats();
}
// calculate difference between newest stats and oldest stats to get window stats
PacketStreamStats windowStats;
windowStats._numReceived = newestStats->_numReceived - oldestStats->_numReceived;
windowStats._numUnreasonable = newestStats->_numUnreasonable - oldestStats->_numUnreasonable;
windowStats._numEarly = newestStats->_numEarly - oldestStats->_numEarly;
windowStats._numLate = newestStats->_numLate - oldestStats->_numLate;
windowStats._numLost = newestStats->_numLost - oldestStats->_numLost;
windowStats._numRecovered = newestStats->_numRecovered - oldestStats->_numRecovered;
windowStats._numDuplicate = newestStats->_numDuplicate - oldestStats->_numDuplicate;
return windowStats;
}

View file

@ -13,31 +13,29 @@
#define hifi_SequenceNumberStats_h
#include "SharedUtil.h"
#include "RingBufferHistory.h"
#include <quuid.h>
const int MAX_REASONABLE_SEQUENCE_GAP = 1000;
class SequenceNumberStats {
class PacketStreamStats {
public:
SequenceNumberStats();
PacketStreamStats()
: _numReceived(0),
_numUnreasonable(0),
_numEarly(0),
_numLate(0),
_numLost(0),
_numRecovered(0),
_numDuplicate(0)
{}
void reset();
void sequenceNumberReceived(quint16 incoming, QUuid senderUUID = QUuid(), const bool wantExtraDebugging = false);
void pruneMissingSet(const bool wantExtraDebugging = false);
quint32 getNumReceived() const { return _numReceived; }
quint32 getNumUnreasonable() const { return _numUnreasonable; }
quint32 getNumOutOfOrder() const { return _numEarly + _numLate; }
quint32 getNumEarly() const { return _numEarly; }
quint32 getNumLate() const { return _numLate; }
quint32 getNumLost() const { return _numLost; }
quint32 getNumRecovered() const { return _numRecovered; }
quint32 getNumDuplicate() const { return _numDuplicate; }
const QSet<quint16>& getMissingSet() const { return _missingSet; }
private:
quint16 _lastReceived;
QSet<quint16> _missingSet;
float getUnreasonableRate() const { return (float)_numUnreasonable / _numReceived; }
float getNumEaryRate() const { return (float)_numEarly / _numReceived; }
float getLateRate() const { return (float)_numLate / _numReceived; }
float getLostRate() const { return (float)_numLost / _numReceived; }
float getRecoveredRate() const { return (float)_numRecovered / _numReceived; }
float getDuplicateRate() const { return (float)_numDuplicate / _numReceived; }
quint32 _numReceived;
quint32 _numUnreasonable;
@ -46,8 +44,38 @@ private:
quint32 _numLost;
quint32 _numRecovered;
quint32 _numDuplicate;
};
class SequenceNumberStats {
public:
SequenceNumberStats(int statsHistoryLength = 0);
void reset();
void sequenceNumberReceived(quint16 incoming, QUuid senderUUID = QUuid(), const bool wantExtraDebugging = false);
void pruneMissingSet(const bool wantExtraDebugging = false);
void pushStatsToHistory() { _statsHistory.insert(_stats); }
quint32 getNumReceived() const { return _stats._numReceived; }
quint32 getNumUnreasonable() const { return _stats._numUnreasonable; }
quint32 getNumOutOfOrder() const { return _stats._numEarly + _stats._numLate; }
quint32 getNumEarly() const { return _stats._numEarly; }
quint32 getNumLate() const { return _stats._numLate; }
quint32 getNumLost() const { return _stats._numLost; }
quint32 getNumRecovered() const { return _stats._numRecovered; }
quint32 getNumDuplicate() const { return _stats._numDuplicate; }
const PacketStreamStats& getStats() const { return _stats; }
PacketStreamStats getStatsForHistoryWindow() const;
const QSet<quint16>& getMissingSet() const { return _missingSet; }
private:
quint16 _lastReceived;
QSet<quint16> _missingSet;
PacketStreamStats _stats;
QUuid _lastSenderUUID;
RingBufferHistory<PacketStreamStats> _statsHistory;
};
#endif // hifi_SequenceNumberStats_h

View file

@ -34,7 +34,6 @@ OctreeEditPacketSender::OctreeEditPacketSender() :
_maxPendingMessages(DEFAULT_MAX_PENDING_MESSAGES),
_releaseQueuedMessagesPending(false),
_serverJurisdictions(NULL),
_sequenceNumber(0),
_maxPacketSize(MAX_PACKET_SIZE) {
}
@ -88,7 +87,7 @@ bool OctreeEditPacketSender::serversExist() const {
// This method is called when the edit packet layer has determined that it has a fully formed packet destined for
// a known nodeID.
void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, const unsigned char* buffer, ssize_t length) {
void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, unsigned char* buffer, ssize_t length) {
NodeList* nodeList = NodeList::getInstance();
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
@ -96,13 +95,18 @@ void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, const unsi
if (node->getType() == getMyNodeType() &&
((node->getUUID() == nodeUUID) || (nodeUUID.isNull()))) {
if (node->getActiveSocket()) {
// pack sequence number
int numBytesPacketHeader = numBytesForPacketHeader(reinterpret_cast<char*>(buffer));
unsigned char* sequenceAt = buffer + numBytesPacketHeader;
quint16 sequence = _outgoingSequenceNumbers[nodeUUID]++;
memcpy(sequenceAt, &sequence, sizeof(quint16));
// send packet
QByteArray packet(reinterpret_cast<const char*>(buffer), length);
queuePacketForSending(node, packet);
// extract sequence number and add packet to history
int numBytesPacketHeader = numBytesForPacketHeader(packet);
const char* dataAt = reinterpret_cast<const char*>(packet.data()) + numBytesPacketHeader;
unsigned short int sequence = *((unsigned short int*)dataAt);
// add packet to history
_sentPacketHistories[nodeUUID].packetSent(sequence, packet);
// debugging output...
@ -312,11 +316,8 @@ void OctreeEditPacketSender::releaseQueuedPacket(EditPacketBuffer& packetBuffer)
void OctreeEditPacketSender::initializePacket(EditPacketBuffer& packetBuffer, PacketType type) {
packetBuffer._currentSize = populatePacketHeader(reinterpret_cast<char*>(&packetBuffer._currentBuffer[0]), type);
// pack in sequence numbers
unsigned short int* sequenceAt = (unsigned short int*)&packetBuffer._currentBuffer[packetBuffer._currentSize];
*sequenceAt = _sequenceNumber;
packetBuffer._currentSize += sizeof(unsigned short int); // nudge past sequence
_sequenceNumber++;
// skip over sequence number for now; will be packed when packet is ready to be sent out
packetBuffer._currentSize += sizeof(quint16);
// pack in timestamp
quint64 now = usecTimestampNow();
@ -373,5 +374,6 @@ void OctreeEditPacketSender::nodeKilled(SharedNodePointer node) {
// TODO: add locks
QUuid nodeUUID = node->getUUID();
_pendingEditPackets.remove(nodeUUID);
_outgoingSequenceNumbers.remove(nodeUUID);
_sentPacketHistories.remove(nodeUUID);
}

View file

@ -21,7 +21,7 @@
/// Used for construction of edit packets
class EditPacketBuffer {
public:
EditPacketBuffer() : _nodeUUID(), _currentType(PacketTypeUnknown), _currentSize(0) { }
EditPacketBuffer() : _nodeUUID(), _currentType(PacketTypeUnknown), _currentSize(0) { }
EditPacketBuffer(PacketType type, unsigned char* codeColorBuffer, ssize_t length, const QUuid nodeUUID = QUuid());
QUuid _nodeUUID;
PacketType _currentType;
@ -100,7 +100,7 @@ public:
protected:
bool _shouldSend;
void queuePacketToNode(const QUuid& nodeID, const unsigned char* buffer, ssize_t length);
void queuePacketToNode(const QUuid& nodeID, unsigned char* buffer, ssize_t length);
void queuePendingPacketToNodes(PacketType type, unsigned char* buffer, ssize_t length);
void queuePacketToNodes(unsigned char* buffer, ssize_t length);
void initializePacket(EditPacketBuffer& packetBuffer, PacketType type);
@ -120,12 +120,12 @@ protected:
NodeToJurisdictionMap* _serverJurisdictions;
unsigned short int _sequenceNumber;
int _maxPacketSize;
QMutex _releaseQueuedPacketMutex;
// TODO: add locks for this and _pendingEditPackets
QHash<QUuid, SentPacketHistory> _sentPacketHistories;
QHash<QUuid, quint16> _outgoingSequenceNumbers;
};
#endif // hifi_OctreeEditPacketSender_h

View file

@ -357,15 +357,23 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
processedBytes = 0;
// the first part of the data is our octcode...
int octets = numberOfThreeBitSectionsInCode(data);
int octets = numberOfThreeBitSectionsInCode(data, length);
int lengthOfOctcode = bytesRequiredForCodeLength(octets);
// we don't actually do anything with this octcode...
dataAt += lengthOfOctcode;
processedBytes += lengthOfOctcode;
// id
uint32_t editID;
// check to make sure we have enough content to keep reading...
if (processedBytes + sizeof(editID) > length) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
}
memcpy(&editID, dataAt, sizeof(editID));
dataAt += sizeof(editID);
processedBytes += sizeof(editID);
@ -377,6 +385,14 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
// If this is a NEW_PARTICLE, then we assume that there's an additional uint32_t creatorToken, that
// we want to send back to the creator as an map to the actual id
uint32_t creatorTokenID;
// check to make sure we have enough content to keep reading...
if (processedBytes + sizeof(creatorTokenID) > length) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
}
memcpy(&creatorTokenID, dataAt, sizeof(creatorTokenID));
dataAt += sizeof(creatorTokenID);
processedBytes += sizeof(creatorTokenID);
@ -385,6 +401,8 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
newParticle._newlyCreated = true;
newParticle.setAge(0); // this guy is new!
valid = true;
} else {
// look up the existing particle
const Particle* existingParticle = tree->findParticleByID(editID, true);
@ -392,21 +410,27 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
// copy existing properties before over-writing with new properties
if (existingParticle) {
newParticle = *existingParticle;
valid = true;
} else {
// the user attempted to edit a particle that doesn't exist
qDebug() << "user attempted to edit a particle that doesn't exist...";
qDebug() << "user attempted to edit a particle that doesn't exist... editID=" << editID;
// NOTE: even though this is a bad particle ID, we have to consume the edit details, so that
// the buffer doesn't get corrupted for further processing...
valid = false;
return newParticle;
}
newParticle._id = editID;
newParticle._newlyCreated = false;
}
// if we got this far, then our result will be valid
valid = true;
// lastEdited
// check to make sure we have enough content to keep reading...
if (processedBytes + sizeof(newParticle._lastEdited) > length) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
}
memcpy(&newParticle._lastEdited, dataAt, sizeof(newParticle._lastEdited));
dataAt += sizeof(newParticle._lastEdited);
processedBytes += sizeof(newParticle._lastEdited);
@ -415,6 +439,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
// properties included bits
uint16_t packetContainsBits = 0;
if (!isNewParticle) {
if (processedBytes + sizeof(packetContainsBits) > length) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
}
memcpy(&packetContainsBits, dataAt, sizeof(packetContainsBits));
dataAt += sizeof(packetContainsBits);
processedBytes += sizeof(packetContainsBits);
@ -423,6 +452,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
// radius
if (isNewParticle || ((packetContainsBits & CONTAINS_RADIUS) == CONTAINS_RADIUS)) {
if (processedBytes + sizeof(newParticle._radius) > length) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
}
memcpy(&newParticle._radius, dataAt, sizeof(newParticle._radius));
dataAt += sizeof(newParticle._radius);
processedBytes += sizeof(newParticle._radius);
@ -430,6 +464,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
// position
if (isNewParticle || ((packetContainsBits & CONTAINS_POSITION) == CONTAINS_POSITION)) {
if (processedBytes + sizeof(newParticle._position) > length) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
}
memcpy(&newParticle._position, dataAt, sizeof(newParticle._position));
dataAt += sizeof(newParticle._position);
processedBytes += sizeof(newParticle._position);
@ -437,6 +476,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
// color
if (isNewParticle || ((packetContainsBits & CONTAINS_COLOR) == CONTAINS_COLOR)) {
if (processedBytes + sizeof(newParticle._color) > length) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
}
memcpy(newParticle._color, dataAt, sizeof(newParticle._color));
dataAt += sizeof(newParticle._color);
processedBytes += sizeof(newParticle._color);
@ -444,6 +488,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
// velocity
if (isNewParticle || ((packetContainsBits & CONTAINS_VELOCITY) == CONTAINS_VELOCITY)) {
if (processedBytes + sizeof(newParticle._velocity) > length) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
}
memcpy(&newParticle._velocity, dataAt, sizeof(newParticle._velocity));
dataAt += sizeof(newParticle._velocity);
processedBytes += sizeof(newParticle._velocity);
@ -451,6 +500,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
// gravity
if (isNewParticle || ((packetContainsBits & CONTAINS_GRAVITY) == CONTAINS_GRAVITY)) {
if (processedBytes + sizeof(newParticle._gravity) > length) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
}
memcpy(&newParticle._gravity, dataAt, sizeof(newParticle._gravity));
dataAt += sizeof(newParticle._gravity);
processedBytes += sizeof(newParticle._gravity);
@ -458,6 +512,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
// damping
if (isNewParticle || ((packetContainsBits & CONTAINS_DAMPING) == CONTAINS_DAMPING)) {
if (processedBytes + sizeof(newParticle._damping) > length) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
}
memcpy(&newParticle._damping, dataAt, sizeof(newParticle._damping));
dataAt += sizeof(newParticle._damping);
processedBytes += sizeof(newParticle._damping);
@ -465,6 +524,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
// lifetime
if (isNewParticle || ((packetContainsBits & CONTAINS_LIFETIME) == CONTAINS_LIFETIME)) {
if (processedBytes + sizeof(newParticle._lifetime) > length) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
}
memcpy(&newParticle._lifetime, dataAt, sizeof(newParticle._lifetime));
dataAt += sizeof(newParticle._lifetime);
processedBytes += sizeof(newParticle._lifetime);
@ -473,6 +537,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
// TODO: make inHand and shouldDie into single bits
// inHand
if (isNewParticle || ((packetContainsBits & CONTAINS_INHAND) == CONTAINS_INHAND)) {
if (processedBytes + sizeof(newParticle._inHand) > length) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
}
memcpy(&newParticle._inHand, dataAt, sizeof(newParticle._inHand));
dataAt += sizeof(newParticle._inHand);
processedBytes += sizeof(newParticle._inHand);
@ -480,6 +549,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
// shouldDie
if (isNewParticle || ((packetContainsBits & CONTAINS_SHOULDDIE) == CONTAINS_SHOULDDIE)) {
if (processedBytes + sizeof(newParticle._shouldDie) > length) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
}
memcpy(&newParticle._shouldDie, dataAt, sizeof(newParticle._shouldDie));
dataAt += sizeof(newParticle._shouldDie);
processedBytes += sizeof(newParticle._shouldDie);
@ -488,9 +562,20 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
// script
if (isNewParticle || ((packetContainsBits & CONTAINS_SCRIPT) == CONTAINS_SCRIPT)) {
uint16_t scriptLength;
if (processedBytes + sizeof(scriptLength) > length) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
}
memcpy(&scriptLength, dataAt, sizeof(scriptLength));
dataAt += sizeof(scriptLength);
processedBytes += sizeof(scriptLength);
if (processedBytes + scriptLength > length) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
}
QString tempString((const char*)dataAt);
newParticle._script = tempString;
dataAt += scriptLength;
@ -500,9 +585,20 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
// modelURL
if (isNewParticle || ((packetContainsBits & CONTAINS_MODEL_URL) == CONTAINS_MODEL_URL)) {
uint16_t modelURLLength;
if (processedBytes + sizeof(modelURLLength) > length) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
}
memcpy(&modelURLLength, dataAt, sizeof(modelURLLength));
dataAt += sizeof(modelURLLength);
processedBytes += sizeof(modelURLLength);
if (processedBytes + modelURLLength > length) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
}
QString tempString((const char*)dataAt);
newParticle._modelURL = tempString;
dataAt += modelURLLength;
@ -511,6 +607,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
// modelScale
if (isNewParticle || ((packetContainsBits & CONTAINS_MODEL_SCALE) == CONTAINS_MODEL_SCALE)) {
if (processedBytes + sizeof(newParticle._modelScale) > length) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
}
memcpy(&newParticle._modelScale, dataAt, sizeof(newParticle._modelScale));
dataAt += sizeof(newParticle._modelScale);
processedBytes += sizeof(newParticle._modelScale);
@ -518,6 +619,11 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
// modelTranslation
if (isNewParticle || ((packetContainsBits & CONTAINS_MODEL_TRANSLATION) == CONTAINS_MODEL_TRANSLATION)) {
if (processedBytes + sizeof(newParticle._modelTranslation) > length) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
}
memcpy(&newParticle._modelTranslation, dataAt, sizeof(newParticle._modelTranslation));
dataAt += sizeof(newParticle._modelTranslation);
processedBytes += sizeof(newParticle._modelTranslation);
@ -525,6 +631,12 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr
// modelRotation
if (isNewParticle || ((packetContainsBits & CONTAINS_MODEL_ROTATION) == CONTAINS_MODEL_ROTATION)) {
const int expectedBytesForPackedQuat = sizeof(uint16_t) * 4; // this is how we pack the quats
if (processedBytes + expectedBytesForPackedQuat > length) {
valid = false;
processedBytes = length;
return newParticle; // fail as if we read the entire buffer
}
int bytes = unpackOrientationQuatFromBytes(dataAt, newParticle._modelRotation);
dataAt += bytes;
processedBytes += bytes;

View file

@ -0,0 +1,150 @@
//
// MovingMinMaxAvg.h
// libraries/shared/src
//
// Created by Yixin Wang on 7/8/2014
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_MovingMinMaxAvg_h
#define hifi_MovingMinMaxAvg_h
#include <limits>
#include "RingBufferHistory.h"
template <typename T>
class MovingMinMaxAvg {
private:
class Stats {
public:
Stats()
: _min(std::numeric_limits<T>::max()),
_max(std::numeric_limits<T>::min()),
_average(0.0) {}
void updateWithSample(T sample, int& numSamplesInAverage) {
if (sample < _min) {
_min = sample;
}
if (sample > _max) {
_max = sample;
}
_average = _average * ((double)numSamplesInAverage / (numSamplesInAverage + 1))
+ (double)sample / (numSamplesInAverage + 1);
numSamplesInAverage++;
}
void updateWithOtherStats(const Stats& other, int& numStatsInAverage) {
if (other._min < _min) {
_min = other._min;
}
if (other._max > _max) {
_max = other._max;
}
_average = _average * ((double)numStatsInAverage / (numStatsInAverage + 1))
+ other._average / (numStatsInAverage + 1);
numStatsInAverage++;
}
T _min;
T _max;
double _average;
};
public:
// This class collects 3 stats (min, max, avg) over a moving window of samples.
// The moving window contains _windowIntervals * _intervalLength samples.
// Those stats are updated every _intervalLength samples collected. When that happens, _newStatsAvaialble is set
// to true and it's up to the user to clear that flag.
// For example, if you want a moving avg of the past 5000 samples updated every 100 samples, you would instantiate
// this class with MovingMinMaxAvg(100, 50). If you want a moving min of the past 100 samples updated on every
// new sample, instantiate this class with MovingMinMaxAvg(1, 100).
MovingMinMaxAvg(int intervalLength, int windowIntervals)
: _intervalLength(intervalLength),
_windowIntervals(windowIntervals),
_overallStats(),
_samplesCollected(0),
_windowStats(),
_existingSamplesInCurrentInterval(0),
_currentIntervalStats(),
_intervalStats(windowIntervals),
_newStatsAvailable(false)
{}
void reset() {
_overallStats = Stats();
_samplesCollected = 0;
_windowStats = Stats();
_existingSamplesInCurrentInterval = 0;
_currentIntervalStats = Stats();
_intervalStats.clear();
_newStatsAvailable = false;
}
void update(T newSample) {
// update overall stats
_overallStats.updateWithSample(newSample, _samplesCollected);
// update the current interval stats
_currentIntervalStats.updateWithSample(newSample, _existingSamplesInCurrentInterval);
// if the current interval of samples is now full, record its stats into our past intervals' stats
if (_existingSamplesInCurrentInterval == _intervalLength) {
// record current interval's stats, then reset them
_intervalStats.insert(_currentIntervalStats);
_currentIntervalStats = Stats();
_existingSamplesInCurrentInterval = 0;
// update the window's stats by combining the intervals' stats
typename RingBufferHistory<Stats>::Iterator i = _intervalStats.begin();
typename RingBufferHistory<Stats>::Iterator end = _intervalStats.end();
_windowStats = Stats();
int intervalsIncludedInWindowStats = 0;
while (i != end) {
_windowStats.updateWithOtherStats(*i, intervalsIncludedInWindowStats);
i++;
}
_newStatsAvailable = true;
}
}
bool getNewStatsAvailableFlag() const { return _newStatsAvailable; }
void clearNewStatsAvailableFlag() { _newStatsAvailable = false; }
T getMin() const { return _overallStats._min; }
T getMax() const { return _overallStats._max; }
double getAverage() const { return _overallStats._average; }
T getWindowMin() const { return _windowStats._min; }
T getWindowMax() const { return _windowStats._max; }
double getWindowAverage() const { return _windowStats._average; }
private:
int _intervalLength;
int _windowIntervals;
// these are min/max/avg stats for all samples collected.
Stats _overallStats;
int _samplesCollected;
// these are the min/max/avg stats for the samples in the moving window
Stats _windowStats;
int _existingSamplesInCurrentInterval;
// these are the min/max/avg stats for the current interval
Stats _currentIntervalStats;
// these are stored stats for the past intervals in the window
RingBufferHistory<Stats> _intervalStats;
bool _newStatsAvailable;
};
#endif // hifi_MovingMinMaxAvg_h

View file

@ -0,0 +1,122 @@
//
// RingBufferHistory.h
// libraries/shared/src
//
// Created by Yixin Wang on 7/9/2014
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_RingBufferHistory_h
#define hifi_RingBufferHistory_h
#include <stdlib.h>
#include <iterator>
#include <qvector.h>
template <typename T>
class RingBufferHistory {
public:
RingBufferHistory(int capacity = 10)
: _size(capacity + 1),
_capacity(capacity),
_newestEntryAtIndex(0),
_numEntries(0),
_buffer(capacity + 1)
{
}
void clear() {
_numEntries = 0;
}
void insert(const T& entry) {
// increment newest entry index cyclically
_newestEntryAtIndex = (_newestEntryAtIndex == _size - 1) ? 0 : _newestEntryAtIndex + 1;
// insert new entry
_buffer[_newestEntryAtIndex] = entry;
if (_numEntries < _capacity) {
_numEntries++;
}
}
// 0 retrieves the most recent entry, _numEntries - 1 retrieves the oldest.
// returns NULL if entryAge not within [0, _numEntries-1]
const T* get(int entryAge) const {
if (!(entryAge >= 0 && entryAge < _numEntries)) {
return NULL;
}
int entryAt = _newestEntryAtIndex - entryAge;
if (entryAt < 0) {
entryAt += _size;
}
return &_buffer[entryAt];
}
T* get(int entryAge) {
return const_cast<T*>((static_cast<const RingBufferHistory*>(this))->get(entryAge));
}
const T* getNewestEntry() const {
return _numEntries == 0 ? NULL : &_buffer[_newestEntryAtIndex];
}
T* getNewestEntry() {
return _numEntries == 0 ? NULL : &_buffer[_newestEntryAtIndex];
}
int getCapacity() const { return _capacity; }
int getNumEntries() const { return _numEntries; }
private:
int _size;
int _capacity;
int _newestEntryAtIndex;
int _numEntries;
QVector<T> _buffer;
public:
class Iterator : public std::iterator < std::forward_iterator_tag, T > {
public:
Iterator(T* bufferFirst, T* bufferLast, T* at) : _bufferFirst(bufferFirst), _bufferLast(bufferLast), _at(at) {}
bool operator==(const Iterator& rhs) { return _at == rhs._at; }
bool operator!=(const Iterator& rhs) { return _at != rhs._at; }
T& operator*() { return *_at; }
T* operator->() { return _at; }
Iterator& operator++() {
_at = (_at == _bufferFirst) ? _bufferLast : _at - 1;
return *this;
}
Iterator operator++(int) {
Iterator tmp(*this);
++(*this);
return tmp;
}
private:
T* const _bufferFirst;
T* const _bufferLast;
T* _at;
};
Iterator begin() { return Iterator(&_buffer.first(), &_buffer.last(), &_buffer[_newestEntryAtIndex]); }
Iterator end() {
int endAtIndex = _newestEntryAtIndex - _numEntries;
if (endAtIndex < 0) {
endAtIndex += _size;
}
return Iterator(&_buffer.first(), &_buffer.last(), &_buffer[endAtIndex]);
}
};
#endif // hifi_RingBufferHistory_h

View file

@ -0,0 +1,218 @@
//
// MovingMinMaxAvgTests.cpp
// tests/shared/src
//
// Created by Yixin Wang on 7/8/2014
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "MovingMinMaxAvgTests.h"
#include <qqueue.h>
quint64 MovingMinMaxAvgTests::randQuint64() {
quint64 ret = 0;
for (int i = 0; i < 32; i++) {
ret = (ret + rand() % 4);
ret *= 4;
}
return ret;
}
void MovingMinMaxAvgTests::runAllTests() {
{
// quint64 test
const int INTERVAL_LENGTH = 100;
const int WINDOW_INTERVALS = 50;
MovingMinMaxAvg<quint64> stats(INTERVAL_LENGTH, WINDOW_INTERVALS);
quint64 min = std::numeric_limits<quint64>::max();
quint64 max = 0;
double average = 0.0;
int totalSamples = 0;
quint64 windowMin;
quint64 windowMax;
double windowAverage;
QQueue<quint64> windowSamples;
// fill window samples
for (int i = 0; i < 100000; i++) {
quint64 sample = randQuint64();
windowSamples.enqueue(sample);
if (windowSamples.size() > INTERVAL_LENGTH * WINDOW_INTERVALS) {
windowSamples.dequeue();
}
stats.update(sample);
min = std::min(min, sample);
max = std::max(max, sample);
average = (average * totalSamples + sample) / (totalSamples + 1);
totalSamples++;
assert(stats.getMin() == min);
assert(stats.getMax() == max);
assert(abs(stats.getAverage() / average - 1.0) < 0.000001 || abs(stats.getAverage() - average) < 0.000001);
if ((i + 1) % INTERVAL_LENGTH == 0) {
assert(stats.getNewStatsAvailableFlag());
stats.clearNewStatsAvailableFlag();
windowMin = std::numeric_limits<quint64>::max();
windowMax = 0;
windowAverage = 0.0;
foreach(quint64 s, windowSamples) {
windowMin = std::min(windowMin, s);
windowMax = std::max(windowMax, s);
windowAverage += (double)s;
}
windowAverage /= (double)windowSamples.size();
assert(stats.getWindowMin() == windowMin);
assert(stats.getWindowMax() == windowMax);
assert(abs(stats.getAverage() / average - 1.0) < 0.000001 || abs(stats.getAverage() - average) < 0.000001);
} else {
assert(!stats.getNewStatsAvailableFlag());
}
}
}
{
// int test
const int INTERVAL_LENGTH = 1;
const int WINDOW_INTERVALS = 75;
MovingMinMaxAvg<int> stats(INTERVAL_LENGTH, WINDOW_INTERVALS);
int min = std::numeric_limits<int>::max();
int max = 0;
double average = 0.0;
int totalSamples = 0;
int windowMin;
int windowMax;
double windowAverage;
QQueue<int> windowSamples;
// fill window samples
for (int i = 0; i < 100000; i++) {
int sample = rand();
windowSamples.enqueue(sample);
if (windowSamples.size() > INTERVAL_LENGTH * WINDOW_INTERVALS) {
windowSamples.dequeue();
}
stats.update(sample);
min = std::min(min, sample);
max = std::max(max, sample);
average = (average * totalSamples + sample) / (totalSamples + 1);
totalSamples++;
assert(stats.getMin() == min);
assert(stats.getMax() == max);
assert(abs(stats.getAverage() / average - 1.0) < 0.000001);
if ((i + 1) % INTERVAL_LENGTH == 0) {
assert(stats.getNewStatsAvailableFlag());
stats.clearNewStatsAvailableFlag();
windowMin = std::numeric_limits<int>::max();
windowMax = 0;
windowAverage = 0.0;
foreach(int s, windowSamples) {
windowMin = std::min(windowMin, s);
windowMax = std::max(windowMax, s);
windowAverage += (double)s;
}
windowAverage /= (double)windowSamples.size();
assert(stats.getWindowMin() == windowMin);
assert(stats.getWindowMax() == windowMax);
assert(abs(stats.getAverage() / average - 1.0) < 0.000001);
} else {
assert(!stats.getNewStatsAvailableFlag());
}
}
}
{
// float test
const int INTERVAL_LENGTH = 57;
const int WINDOW_INTERVALS = 1;
MovingMinMaxAvg<float> stats(INTERVAL_LENGTH, WINDOW_INTERVALS);
float min = std::numeric_limits<float>::max();
float max = 0;
double average = 0.0;
int totalSamples = 0;
float windowMin;
float windowMax;
double windowAverage;
QQueue<float> windowSamples;
// fill window samples
for (int i = 0; i < 100000; i++) {
float sample = randFloat();
windowSamples.enqueue(sample);
if (windowSamples.size() > INTERVAL_LENGTH * WINDOW_INTERVALS) {
windowSamples.dequeue();
}
stats.update(sample);
min = std::min(min, sample);
max = std::max(max, sample);
average = (average * totalSamples + sample) / (totalSamples + 1);
totalSamples++;
assert(stats.getMin() == min);
assert(stats.getMax() == max);
assert(abs(stats.getAverage() / average - 1.0) < 0.000001);
if ((i + 1) % INTERVAL_LENGTH == 0) {
assert(stats.getNewStatsAvailableFlag());
stats.clearNewStatsAvailableFlag();
windowMin = std::numeric_limits<float>::max();
windowMax = 0;
windowAverage = 0.0;
foreach(float s, windowSamples) {
windowMin = std::min(windowMin, s);
windowMax = std::max(windowMax, s);
windowAverage += (double)s;
}
windowAverage /= (double)windowSamples.size();
assert(stats.getWindowMin() == windowMin);
assert(stats.getWindowMax() == windowMax);
assert(abs(stats.getAverage() / average - 1.0) < 0.000001);
} else {
assert(!stats.getNewStatsAvailableFlag());
}
}
}
printf("moving min/max/avg test passed!\n");
}

View file

@ -0,0 +1,25 @@
//
// MovingMinMaxAvgTests.h
// tests/shared/src
//
// Created by Yixin Wang on 7/8/2014
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_MovingMinMaxAvgTests_h
#define hifi_MovingMinMaxAvgTests_h
#include "MovingMinMaxAvg.h"
#include "SharedUtil.h"
namespace MovingMinMaxAvgTests {
quint64 randQuint64();
void runAllTests();
}
#endif // hifi_MovingMinMaxAvgTests_h

View file

@ -10,9 +10,12 @@
#include "AngularConstraintTests.h"
#include "MovingPercentileTests.h"
#include "MovingMinMaxAvgTests.h"
int main(int argc, char** argv) {
MovingMinMaxAvgTests::runAllTests();
MovingPercentileTests::runAllTests();
AngularConstraintTests::runAllTests();
getchar();
return 0;
}