mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-13 13:40:10 +02:00
merge upstream/master into andrew/thermonuclear (AKA samcake/19734)
Conflicts: interface/src/Application.cpp
This commit is contained in:
commit
8ffc3aa73d
122 changed files with 3626 additions and 1206 deletions
BUILD.mdCMakeLists.txt
assignment-client/src
cmake/modules
examples
avatarLocalLight.jsclap.jsconcertCamera_kims.jsconcertCamera_kyrs.jseditModels.jsflockingBirds.jslocationsMenu.jssit.jssqueezeHands.js
interface
CMakeLists.txt
external/rtmidi
interface.icnsresources/shaders
src
Application.cppApplication.hAudio.cppAudio.hBuckyBalls.cppGLCanvas.cppGLCanvas.hHair.cppHair.hMenu.cppMenu.hMetavoxelSystem.cpp
avatar
Avatar.cppAvatar.hAvatarManager.cppFaceModel.cppHand.cppMyAvatar.cppSkeletonModel.cppSkeletonModel.h
devices
Faceshift.cppJoystickManager.cppMIDIManager.cppMIDIManager.hOculusManager.cppOculusManager.hPrioVR.cppSixenseManager.cppSixenseManager.hTV3DManager.cppVisage.cpp
renderer
ui
voxels
libraries
audio/src
AudioRingBuffer.hAudioStreamStats.hInjectedAudioRingBuffer.cppPositionalAudioRingBuffer.cppPositionalAudioRingBuffer.h
avatars/src
metavoxels/src
AttributeRegistry.cppAttributeRegistry.hBitstream.cppBitstream.hDatagramSequencer.cppDatagramSequencer.hEndpoint.cppEndpoint.hMetavoxelClientManager.cppMetavoxelClientManager.hMetavoxelData.cppMetavoxelData.hMetavoxelMessages.h
models/src
7
BUILD.md
7
BUILD.md
|
@ -24,9 +24,12 @@ In order for CMake to find the Qt5 find modules, you will need to set an ENV var
|
|||
|
||||
For example, a Qt5 5.2.0 installation to /usr/local/qt5 would require that QT_CMAKE_PREFIX_PATH be set with the following command. This can either be entered directly into your shell session before you build or in your shell profile (e.g.: ~/.bash_profile, ~/.bashrc, ~/.zshrc - this depends on your shell and environment).
|
||||
|
||||
export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.2.0/clang_64/lib/cmake/
|
||||
The path it needs to be set to will depend on where and how Qt5 was installed. e.g.
|
||||
|
||||
export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.2.0/clang_64/lib/cmake/
|
||||
export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.2.1/lib/cmake
|
||||
export QT_CMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/cmake
|
||||
|
||||
The path it needs to be set to will depend on where and how Qt5 was installed.
|
||||
|
||||
####Generating build files
|
||||
Create a build directory in the root of your checkout and then run the CMake build from there. This will keep the rest of the directory clean.
|
||||
|
|
|
@ -32,14 +32,6 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
|||
# Instruct CMake to run moc automatically when needed.
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
|
||||
if (APPLE)
|
||||
exec_program(uname ARGS -v OUTPUT_VARIABLE DARWIN_VERSION)
|
||||
string(REGEX MATCH "[0-9]+" DARWIN_VERSION ${DARWIN_VERSION})
|
||||
if (DARWIN_VERSION GREATER 12)
|
||||
set(CMAKE_CXX_FLAGS "-stdlib=libstdc++")
|
||||
endif (DARWIN_VERSION GREATER 12)
|
||||
endif (APPLE)
|
||||
|
||||
# targets not supported on windows
|
||||
if (NOT WIN32)
|
||||
add_subdirectory(animation-server)
|
||||
|
|
|
@ -214,7 +214,6 @@ void Agent::run() {
|
|||
QNetworkDiskCache* cache = new QNetworkDiskCache();
|
||||
QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
|
||||
cache->setCacheDirectory(!cachePath.isEmpty() ? cachePath : "agentCache");
|
||||
cache->moveToThread(networkAccessManager.thread());
|
||||
networkAccessManager.setCache(cache);
|
||||
|
||||
qDebug() << "Downloading script at" << scriptURL.toString();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
@ -138,11 +154,14 @@ void AudioMixerClientData::pushBuffersAfterFrameSend() {
|
|||
// this was a used buffer, push the output pointer forwards
|
||||
PositionalAudioRingBuffer* audioBuffer = *i;
|
||||
|
||||
const int INJECTOR_CONSECUTIVE_NOT_MIXED_THRESHOLD = 100;
|
||||
|
||||
if (audioBuffer->willBeAddedToMix()) {
|
||||
audioBuffer->shiftReadPosition(audioBuffer->getSamplesPerFrame());
|
||||
audioBuffer->setWillBeAddedToMix(false);
|
||||
} else if (audioBuffer->getType() == PositionalAudioRingBuffer::Injector
|
||||
&& audioBuffer->hasStarted() && audioBuffer->isStarved()) {
|
||||
&& audioBuffer->hasStarted() && audioBuffer->isStarved()
|
||||
&& audioBuffer->getConsecutiveNotMixedCount() > INJECTOR_CONSECUTIVE_NOT_MIXED_THRESHOLD) {
|
||||
// this is an empty audio buffer that has starved, safe to delete
|
||||
// also delete its sequence number stats
|
||||
QUuid streamIdentifier = ((InjectedAudioRingBuffer*)audioBuffer)->getStreamIdentifier();
|
||||
|
@ -156,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();
|
||||
|
||||
|
@ -231,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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -19,7 +19,7 @@ AvatarAudioRingBuffer::AvatarAudioRingBuffer(bool isStereo, bool dynamicJitterBu
|
|||
}
|
||||
|
||||
int AvatarAudioRingBuffer::parseData(const QByteArray& packet) {
|
||||
_interframeTimeGapStats.frameReceived();
|
||||
timeGapStatsFrameReceived();
|
||||
updateDesiredJitterBufferFrames();
|
||||
|
||||
_shouldLoopbackForNode = (packetTypeForPacket(packet) == PacketTypeMicrophoneAudioWithEcho);
|
||||
|
|
|
@ -10,6 +10,9 @@
|
|||
//
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QFile>
|
||||
#include <QSaveFile>
|
||||
#include <QThread>
|
||||
|
||||
#include <PacketHeaders.h>
|
||||
|
||||
|
@ -21,7 +24,8 @@
|
|||
const int SEND_INTERVAL = 50;
|
||||
|
||||
MetavoxelServer::MetavoxelServer(const QByteArray& packet) :
|
||||
ThreadedAssignment(packet) {
|
||||
ThreadedAssignment(packet),
|
||||
_sendTimer(this) {
|
||||
|
||||
_sendTimer.setSingleShot(true);
|
||||
connect(&_sendTimer, SIGNAL(timeout()), SLOT(sendDeltas()));
|
||||
|
@ -43,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() {
|
||||
|
@ -66,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());
|
||||
|
@ -86,29 +108,62 @@ void MetavoxelServer::sendDeltas() {
|
|||
int elapsed = now - _lastSend;
|
||||
_lastSend = now;
|
||||
|
||||
_sendTimer.start(qMax(0, 2 * SEND_INTERVAL - elapsed));
|
||||
_sendTimer.start(qMax(0, 2 * SEND_INTERVAL - qMax(elapsed, SEND_INTERVAL)));
|
||||
}
|
||||
|
||||
MetavoxelSession::MetavoxelSession(const SharedNodePointer& node, MetavoxelServer* server) :
|
||||
Endpoint(node, new PacketRecord(), NULL),
|
||||
_server(server) {
|
||||
_server(server),
|
||||
_reliableDeltaChannel(NULL),
|
||||
_reliableDeltaID(0) {
|
||||
|
||||
connect(&_sequencer, SIGNAL(receivedHighPriorityMessage(const QVariant&)), SLOT(handleMessage(const QVariant&)));
|
||||
connect(_sequencer.getReliableInputChannel(), SIGNAL(receivedMessage(const QVariant&)),
|
||||
SLOT(handleMessage(const QVariant&)));
|
||||
connect(&_sequencer, SIGNAL(sendAcknowledged(int)), SLOT(checkReliableDeltaReceived()));
|
||||
connect(_sequencer.getReliableInputChannel(), SIGNAL(receivedMessage(const QVariant&, Bitstream&)),
|
||||
SLOT(handleMessage(const QVariant&, Bitstream&)));
|
||||
}
|
||||
|
||||
void MetavoxelSession::update() {
|
||||
// wait until we have a valid lod
|
||||
if (_lod.isValid()) {
|
||||
Endpoint::update();
|
||||
// wait until we have a valid lod before sending
|
||||
if (!_lod.isValid()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelSession::writeUpdateMessage(Bitstream& out) {
|
||||
// if we're sending a reliable delta, wait until it's acknowledged
|
||||
if (_reliableDeltaChannel) {
|
||||
sendPacketGroup();
|
||||
return;
|
||||
}
|
||||
Bitstream& out = _sequencer.startPacket();
|
||||
int start = _sequencer.getOutputStream().getUnderlying().device()->pos();
|
||||
out << QVariant::fromValue(MetavoxelDeltaMessage());
|
||||
PacketRecord* sendRecord = getLastAcknowledgedSendRecord();
|
||||
_server->getData().writeDelta(sendRecord->getData(), sendRecord->getLOD(), out, _lod);
|
||||
out.flush();
|
||||
int end = _sequencer.getOutputStream().getUnderlying().device()->pos();
|
||||
if (end > _sequencer.getMaxPacketSize()) {
|
||||
// we need to send the delta on the reliable channel
|
||||
_reliableDeltaChannel = _sequencer.getReliableOutputChannel(RELIABLE_DELTA_CHANNEL_INDEX);
|
||||
_reliableDeltaChannel->startMessage();
|
||||
_reliableDeltaChannel->getBuffer().write(_sequencer.getOutgoingPacketData().constData() + start, end - start);
|
||||
_reliableDeltaChannel->endMessage();
|
||||
|
||||
_reliableDeltaWriteMappings = out.getAndResetWriteMappings();
|
||||
_reliableDeltaReceivedOffset = _reliableDeltaChannel->getBytesWritten();
|
||||
_reliableDeltaData = _server->getData();
|
||||
_reliableDeltaLOD = _lod;
|
||||
|
||||
// go back to the beginning with the current packet and note that there's a delta pending
|
||||
_sequencer.getOutputStream().getUnderlying().device()->seek(start);
|
||||
MetavoxelDeltaPendingMessage msg = { ++_reliableDeltaID };
|
||||
out << QVariant::fromValue(msg);
|
||||
_sequencer.endPacket();
|
||||
|
||||
} else {
|
||||
_sequencer.endPacket();
|
||||
}
|
||||
|
||||
// perhaps send additional packets to fill out the group
|
||||
sendPacketGroup(1);
|
||||
}
|
||||
|
||||
void MetavoxelSession::handleMessage(const QVariant& message, Bitstream& in) {
|
||||
|
@ -116,7 +171,8 @@ void MetavoxelSession::handleMessage(const QVariant& message, Bitstream& in) {
|
|||
}
|
||||
|
||||
PacketRecord* MetavoxelSession::maybeCreateSendRecord() const {
|
||||
return new PacketRecord(_lod, _server->getData());
|
||||
return _reliableDeltaChannel ? new PacketRecord(_reliableDeltaLOD, _reliableDeltaData) :
|
||||
new PacketRecord(_lod, _server->getData());
|
||||
}
|
||||
|
||||
void MetavoxelSession::handleMessage(const QVariant& message) {
|
||||
|
@ -134,3 +190,68 @@ void MetavoxelSession::handleMessage(const QVariant& message) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelSession::checkReliableDeltaReceived() {
|
||||
if (!_reliableDeltaChannel || _reliableDeltaChannel->getOffset() < _reliableDeltaReceivedOffset) {
|
||||
return;
|
||||
}
|
||||
_sequencer.getOutputStream().persistWriteMappings(_reliableDeltaWriteMappings);
|
||||
_reliableDeltaWriteMappings = Bitstream::WriteMappings();
|
||||
_reliableDeltaData = MetavoxelData();
|
||||
_reliableDeltaChannel = NULL;
|
||||
}
|
||||
|
||||
void MetavoxelSession::sendPacketGroup(int alreadySent) {
|
||||
int additionalPackets = _sequencer.notePacketGroup() - alreadySent;
|
||||
for (int i = 0; i < additionalPackets; i++) {
|
||||
Bitstream& out = _sequencer.startPacket();
|
||||
if (_reliableDeltaChannel) {
|
||||
MetavoxelDeltaPendingMessage msg = { _reliableDeltaID };
|
||||
out << QVariant::fromValue(msg);
|
||||
} else {
|
||||
out << QVariant();
|
||||
}
|
||||
_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.";
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -63,7 +70,6 @@ public:
|
|||
|
||||
protected:
|
||||
|
||||
virtual void writeUpdateMessage(Bitstream& out);
|
||||
virtual void handleMessage(const QVariant& message, Bitstream& in);
|
||||
|
||||
virtual PacketRecord* maybeCreateSendRecord() const;
|
||||
|
@ -71,12 +77,38 @@ protected:
|
|||
private slots:
|
||||
|
||||
void handleMessage(const QVariant& message);
|
||||
void checkReliableDeltaReceived();
|
||||
|
||||
private:
|
||||
|
||||
void sendPacketGroup(int alreadySent = 0);
|
||||
|
||||
MetavoxelServer* _server;
|
||||
|
||||
MetavoxelLOD _lod;
|
||||
|
||||
ReliableChannel* _reliableDeltaChannel;
|
||||
int _reliableDeltaReceivedOffset;
|
||||
MetavoxelData _reliableDeltaData;
|
||||
MetavoxelLOD _reliableDeltaLOD;
|
||||
Bitstream::WriteMappings _reliableDeltaWriteMappings;
|
||||
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
|
||||
|
|
|
@ -26,8 +26,8 @@ else ()
|
|||
set(RTMIDI_SEARCH_DIRS "${RTMIDI_ROOT_DIR}" "$ENV{HIFI_LIB_DIR}/rtmidi")
|
||||
|
||||
find_path(RTMIDI_INCLUDE_DIR RtMidi.h PATH_SUFFIXES include HINTS ${RTMIDI_SEARCH_DIRS})
|
||||
find_file(RTMIDI_CPP NAMES RtMidi.cpp PATH_SUFFIXES src HINTS ${RTMIDI_SEARCH_DIRS})
|
||||
find_library(RTMIDI_LIBRARY NAMES rtmidi PATH_SUFFIXES lib HINTS ${RTMIDI_SEARCH_DIRS})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(RTMIDI DEFAULT_MSG RTMIDI_INCLUDE_DIR RTMIDI_CPP)
|
||||
find_package_handle_standard_args(RTMIDI DEFAULT_MSG RTMIDI_INCLUDE_DIR RTMIDI_LIBRARY)
|
||||
endif ()
|
126
examples/avatarLocalLight.js
Normal file
126
examples/avatarLocalLight.js
Normal file
|
@ -0,0 +1,126 @@
|
|||
//
|
||||
// avatarLocalLight.js
|
||||
//
|
||||
// Created by Tony Peng on July 2nd, 2014
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Set the local light direction and color on the avatar
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
var localLightDirections = [ {x: 1.0, y:0.0, z: 0.0}, {x: 0.0, y:1.0, z: 1.0}, {x: 0.0, y:0.0, z: 1.0}, {x: 1.0, y:1.0, z: 1.0} ];
|
||||
var localLightColors = [ {x: 0.0, y:0.0, z: 0.0}, {x: 0.0, y:0.0, z: 0.0}, {x: 0.0, y:0.0, z: 0.0}, {x: 0.0, y:0.0, z: 0.0} ];
|
||||
|
||||
var currentSelection = 0;
|
||||
var currentNumLights = 1;
|
||||
var maxNumLights = 2;
|
||||
|
||||
function keyPressEvent(event) {
|
||||
|
||||
var choice = parseInt(event.text);
|
||||
|
||||
if (event.text == "1") {
|
||||
currentSelection = 0;
|
||||
print("light election = " + currentSelection);
|
||||
}
|
||||
else if (event.text == "2" ) {
|
||||
currentSelection = 1;
|
||||
print("light selection = " + currentSelection);
|
||||
}
|
||||
else if (event.text == "3" ) {
|
||||
currentSelection = 2;
|
||||
print("light selection = " + currentSelection);
|
||||
}
|
||||
else if (event.text == "4" ) {
|
||||
currentSelection = 3;
|
||||
print("light selection = " + currentSelection);
|
||||
}
|
||||
else if (event.text == "5" ) {
|
||||
localLightColors[currentSelection].x += 0.01;
|
||||
if ( localLightColors[currentSelection].x > 1.0) {
|
||||
localLightColors[currentSelection].x = 0.0;
|
||||
}
|
||||
|
||||
MyAvatar.setLocalLightColor(localLightColors[currentSelection], currentSelection);
|
||||
}
|
||||
else if (event.text == "6" ) {
|
||||
localLightColors[currentSelection].y += 0.01;
|
||||
if ( localLightColors[currentSelection].y > 1.0) {
|
||||
localLightColors[currentSelection].y = 0.0;
|
||||
}
|
||||
|
||||
MyAvatar.setLocalLightColor(localLightColors[currentSelection], currentSelection);
|
||||
}
|
||||
else if (event.text == "7" ) {
|
||||
localLightColors[currentSelection].z += 0.01;
|
||||
if ( localLightColors[currentSelection].z > 1.0) {
|
||||
localLightColors[currentSelection].z = 0.0;
|
||||
}
|
||||
|
||||
MyAvatar.setLocalLightColor(localLightColors[currentSelection], currentSelection);
|
||||
}
|
||||
else if (event.text == "8" ) {
|
||||
localLightDirections[currentSelection].x += 0.01;
|
||||
if (localLightDirections[currentSelection].x > 1.0) {
|
||||
localLightDirections[currentSelection].x = -1.0;
|
||||
}
|
||||
|
||||
MyAvatar.setLocalLightDirection(localLightDirections[currentSelection], currentSelection);
|
||||
}
|
||||
else if (event.text == "9" ) {
|
||||
localLightDirections[currentSelection].x -= 0.01;
|
||||
if (localLightDirections[currentSelection].x < -1.0) {
|
||||
localLightDirections[currentSelection].x = 1.0;
|
||||
}
|
||||
|
||||
MyAvatar.setLocalLightDirection(localLightDirections[currentSelection], currentSelection);
|
||||
}
|
||||
else if (event.text == "[" ) {
|
||||
localLightDirections[currentSelection].y += 0.01;
|
||||
if (localLightDirections[currentSelection].y > 1.0) {
|
||||
localLightDirections[currentSelection].y = -1.0;
|
||||
}
|
||||
|
||||
MyAvatar.setLocalLightDirection(localLightDirections[currentSelection], currentSelection);
|
||||
}
|
||||
else if (event.text == "]" ) {
|
||||
localLightDirections[currentSelection].y -= 0.01;
|
||||
if (localLightDirections[currentSelection].y < -1.0) {
|
||||
localLightDirections[currentSelection].y = 1.0;
|
||||
}
|
||||
|
||||
MyAvatar.setLocalLightDirection(localLightDirections[currentSelection], currentSelection);
|
||||
}
|
||||
else if (event.text == "," ) {
|
||||
if (currentNumLights + 1 <= maxNumLights) {
|
||||
var darkGrayColor = {x:0.3, y:0.3, z:0.3};
|
||||
|
||||
// default light
|
||||
localLightColors[currentNumLights].x = darkGrayColor.x;
|
||||
localLightColors[currentNumLights].y = darkGrayColor.y;
|
||||
localLightColors[currentNumLights].z = darkGrayColor.z;
|
||||
|
||||
MyAvatar.addLocalLight();
|
||||
MyAvatar.setLocalLightColor(localLightColors[currentNumLights], currentNumLights);
|
||||
MyAvatar.setLocalLightDirection(localLightDirections[currentNumLights], currentNumLights);
|
||||
|
||||
++currentNumLights;
|
||||
}
|
||||
}
|
||||
else if (event.text == "." ) {
|
||||
if (currentNumLights - 1 >= 0 ) {
|
||||
|
||||
// no light contribution
|
||||
localLightColors[currentNumLights - 1].x = 0.0;
|
||||
localLightColors[currentNumLights - 1].y = 0.0;
|
||||
localLightColors[currentNumLights - 1].z = 0.0;
|
||||
|
||||
MyAvatar.removeLocalLight();
|
||||
--currentNumLights;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Controller.keyPressEvent.connect(keyPressEvent);
|
173
examples/clap.js
173
examples/clap.js
|
@ -1,72 +1,147 @@
|
|||
//
|
||||
// cameraExample.js
|
||||
// clap.js
|
||||
// examples
|
||||
//
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// This sample script watches your hydra hands and makes clapping sound when they come close together fast
|
||||
// This sample script watches your hydra hands and makes clapping sound when they come close together fast,
|
||||
// and also watches for the 'shift' key and claps when that key is pressed. Clapping multiple times by pressing
|
||||
// the shift key again makes the animation and sound match your pace of clapping.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
function length(v) {
|
||||
return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
|
||||
}
|
||||
var clapAnimation = "https://s3-us-west-1.amazonaws.com/highfidelity-public/animations/ClapAnimations/ClapHands_Standing.fbx";
|
||||
var ANIMATION_FRAMES_PER_CLAP = 10.0;
|
||||
var startEndFrames = [];
|
||||
startEndFrames.push({ start: 0, end: 10});
|
||||
startEndFrames.push({ start: 10, end: 20});
|
||||
startEndFrames.push({ start: 20, end: 30});
|
||||
startEndFrames.push({ start: 30, end: 40});
|
||||
startEndFrames.push({ start: 41, end: 51});
|
||||
startEndFrames.push({ start: 53, end: 0});
|
||||
|
||||
var lastClapFrame = 0;
|
||||
var lastAnimFrame = 0;
|
||||
|
||||
function printVector(v) {
|
||||
print(v.x + ", " + v.y + ", " + v.z + "\n");
|
||||
}
|
||||
var claps = [];
|
||||
claps.push(new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/claps/BClap1Rvb.wav"));
|
||||
claps.push(new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/claps/BClap2Rvb.wav"));
|
||||
claps.push(new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/claps/BClap3Rvb.wav"));
|
||||
claps.push(new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/claps/BClap4Rvb.wav"));
|
||||
claps.push(new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/claps/BClap5Rvb.wav"));
|
||||
claps.push(new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/claps/BClap6Rvb.wav"));
|
||||
claps.push(new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/claps/BClap7Rvb.wav"));
|
||||
claps.push(new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/claps/BClap8Rvb.wav"));
|
||||
claps.push(new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/claps/BClap9Rvb.wav"));
|
||||
claps.push(new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/claps/BClap10Rvb.wav"));
|
||||
var numberOfSounds = claps.length;
|
||||
|
||||
function vMinus(a, b) {
|
||||
var rval = { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z };
|
||||
return rval;
|
||||
}
|
||||
var clappingNow = false;
|
||||
var collectedClicks = 0;
|
||||
|
||||
// First, load the clap sound from a URL
|
||||
var clap1 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/clap1.raw");
|
||||
var clap2 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/clap2.raw");
|
||||
var clap3 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/clap3.raw");
|
||||
var clap4 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/clap4.raw");
|
||||
var clap5 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/clap5.raw");
|
||||
var clap6 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/claps/clap6.raw");
|
||||
|
||||
var clapping = new Array();
|
||||
clapping[0] = false;
|
||||
clapping[1] = false;
|
||||
var clickStartTime, clickEndTime;
|
||||
var clickClappingNow = false;
|
||||
var CLAP_START_RATE = 15.0;
|
||||
var clapRate = CLAP_START_RATE;
|
||||
var startedTimer = false;
|
||||
|
||||
function maybePlaySound(deltaTime) {
|
||||
// Set the location and other info for the sound to play
|
||||
var palm1Position = Controller.getSpatialControlPosition(0);
|
||||
var palm2Position = Controller.getSpatialControlPosition(2);
|
||||
var distanceBetween = length(vMinus(palm1Position, palm2Position));
|
||||
|
||||
for (var palm = 0; palm < 2; palm++) {
|
||||
var palmVelocity = Controller.getSpatialControlVelocity(palm * 2 + 1);
|
||||
var speed = length(palmVelocity);
|
||||
|
||||
const CLAP_SPEED = 0.2;
|
||||
const CLAP_DISTANCE = 0.2;
|
||||
var animationDetails = MyAvatar.getAnimationDetails(clapAnimation);
|
||||
|
||||
if (!clapping[palm] && (distanceBetween < CLAP_DISTANCE) && (speed > CLAP_SPEED)) {
|
||||
var options = new AudioInjectionOptions();
|
||||
options.position = palm1Position;
|
||||
options.volume = speed / 2.0;
|
||||
if (options.volume > 1.0) options.volume = 1.0;
|
||||
which = Math.floor((Math.random() * 6) + 1);
|
||||
if (which == 1) { Audio.playSound(clap1, options); }
|
||||
else if (which == 2) { Audio.playSound(clap2, options); }
|
||||
else if (which == 3) { Audio.playSound(clap3, options); }
|
||||
else if (which == 4) { Audio.playSound(clap4, options); }
|
||||
else if (which == 5) { Audio.playSound(clap5, options); }
|
||||
else { Audio.playSound(clap6, options); }
|
||||
Audio.playSound(clap, options);
|
||||
clapping[palm] = true;
|
||||
} else if (clapping[palm] && (speed < (CLAP_SPEED / 4.0))) {
|
||||
clapping[palm] = false;
|
||||
}
|
||||
var frame = Math.floor(animationDetails.frameIndex);
|
||||
|
||||
if (frame != lastAnimFrame) {
|
||||
lastAnimFrame = frame;
|
||||
}
|
||||
|
||||
for (var i = 0; i < startEndFrames.length; i++) {
|
||||
if (frame == startEndFrames[i].start && (frame != lastClapFrame)) {
|
||||
playClap(1.0, Camera.getPosition());
|
||||
lastClapFrame = frame;
|
||||
}
|
||||
}
|
||||
|
||||
var palm1Position = MyAvatar.getLeftPalmPosition();
|
||||
var palm2Position = MyAvatar.getRightPalmPosition();
|
||||
var distanceBetween = Vec3.length(Vec3.subtract(palm1Position, palm2Position));
|
||||
|
||||
var palm1Velocity = Controller.getSpatialControlVelocity(1);
|
||||
var palm2Velocity = Controller.getSpatialControlVelocity(3);
|
||||
var closingVelocity = Vec3.length(Vec3.subtract(palm1Velocity, palm2Velocity));
|
||||
|
||||
const CLAP_SPEED = 0.7;
|
||||
const CLAP_DISTANCE = 0.15;
|
||||
|
||||
if ((closingVelocity > CLAP_SPEED) && (distanceBetween < CLAP_DISTANCE) && !clappingNow) {
|
||||
var volume = closingVelocity / 2.0;
|
||||
if (volume > 1.0) volume = 1.0;
|
||||
playClap(volume, palm1Position);
|
||||
clappingNow = true;
|
||||
} else if (clappingNow && (distanceBetween > CLAP_DISTANCE * 1.2)) {
|
||||
clappingNow = false;
|
||||
}
|
||||
}
|
||||
|
||||
function playClap(volume, position) {
|
||||
var options = new AudioInjectionOptions();
|
||||
options.position = position;
|
||||
options.volume = 1.0;
|
||||
var clip = Math.floor(Math.random() * numberOfSounds);
|
||||
Audio.playSound(claps[clip], options);
|
||||
}
|
||||
|
||||
var FASTEST_CLAP_INTERVAL = 100.0;
|
||||
var SLOWEST_CLAP_INTERVAL = 2000.0;
|
||||
|
||||
Controller.keyPressEvent.connect(function(event) {
|
||||
if(event.text == "SHIFT") {
|
||||
if (!clickClappingNow) {
|
||||
clickClappingNow = true;
|
||||
clickStartTime = new Date();
|
||||
playClap(1.0, Camera.getPosition());
|
||||
lastClapFrame = 0;
|
||||
MyAvatar.startAnimation(clapAnimation, clapRate, 1.0, true, false);
|
||||
} else {
|
||||
// Adjust animation speed for measured clicking interval
|
||||
clickEndTime = new Date();
|
||||
var milliseconds = clickEndTime - clickStartTime;
|
||||
clickStartTime = new Date();
|
||||
if ((milliseconds < SLOWEST_CLAP_INTERVAL) && (milliseconds > FASTEST_CLAP_INTERVAL)) {
|
||||
clapRate = ANIMATION_FRAMES_PER_CLAP * (1000.0 / milliseconds);
|
||||
playClap(1.0, Camera.getPosition());
|
||||
MyAvatar.stopAnimation(clapAnimation);
|
||||
MyAvatar.startAnimation(clapAnimation, clapRate, 1.0, true, false);
|
||||
}
|
||||
collectedClicks = collectedClicks + 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var CLAP_END_WAIT_MSECS = 300;
|
||||
Controller.keyReleaseEvent.connect(function(event) {
|
||||
if (event.text == "SHIFT") {
|
||||
collectedClicks = 0;
|
||||
if (!startedTimer) {
|
||||
collectedClicks = 0;
|
||||
Script.setTimeout(stopClapping, CLAP_END_WAIT_MSECS);
|
||||
startedTimer = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function stopClapping() {
|
||||
if (collectedClicks == 0) {
|
||||
startedTimer = false;
|
||||
MyAvatar.stopAnimation(clapAnimation);
|
||||
clapRate = CLAP_START_RATE;
|
||||
clickClappingNow = false;
|
||||
} else {
|
||||
startedTimer = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Connect a call back that happens every frame
|
||||
|
|
72
examples/concertCamera_kims.js
Normal file
72
examples/concertCamera_kims.js
Normal file
|
@ -0,0 +1,72 @@
|
|||
//
|
||||
// concertCamera.js
|
||||
//
|
||||
// Created by Philip Rosedale on June 24, 2014
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Move a camera through a series of pre-set locations by pressing number keys
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
var oldMode;
|
||||
var avatarPosition;
|
||||
|
||||
var cameraNumber = 0;
|
||||
var freeCamera = false;
|
||||
|
||||
var cameraLocations = [ {x: 8027.5, y: 237.5, z: 7305.7}, {x: 8027.5, y: 237.5, z: 7306.6}, {x: 8027.5, y: 237.5, z: 7308.0}, {x: 8027.5, y: 237.5, z: 7303.0}, {x: 8030.8, y: 238.6, z: 7311.4}, {x: 8030.9, y: 237.1, z: 7308.0} ];
|
||||
var cameraLookAts = [ {x: 8027.5, y: 237.5, z: 7304.0}, {x: 8027.5, y: 237.5, z: 7305.7}, {x: 8027.5, y: 237.5, z: 7304.0}, {x: 8027.5, y: 237.5, z: 7304.0}, {x: 8027.5, y: 237.5, z: 7304.0}, {x: 8027.5, y: 237.5, z: 7304.0} ];
|
||||
|
||||
function saveCameraState() {
|
||||
oldMode = Camera.getMode();
|
||||
avatarPosition = MyAvatar.position;
|
||||
Camera.setModeShiftPeriod(0.0);
|
||||
Camera.setMode("independent");
|
||||
}
|
||||
|
||||
function restoreCameraState() {
|
||||
Camera.stopLooking();
|
||||
Camera.setMode(oldMode);
|
||||
}
|
||||
|
||||
function update(deltaTime) {
|
||||
if (freeCamera) {
|
||||
var delta = Vec3.subtract(MyAvatar.position, avatarPosition);
|
||||
if (Vec3.length(delta) > 0.05) {
|
||||
cameraNumber = 0;
|
||||
freeCamera = false;
|
||||
restoreCameraState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function keyPressEvent(event) {
|
||||
|
||||
var choice = parseInt(event.text);
|
||||
|
||||
if ((choice > 0) && (choice <= cameraLocations.length)) {
|
||||
print("camera " + choice);
|
||||
if (!freeCamera) {
|
||||
saveCameraState();
|
||||
freeCamera = true;
|
||||
}
|
||||
Camera.setMode("independent");
|
||||
Camera.setPosition(cameraLocations[choice - 1]);
|
||||
Camera.keepLookingAt(cameraLookAts[choice - 1]);
|
||||
}
|
||||
if (event.text == "ESC") {
|
||||
cameraNumber = 0;
|
||||
freeCamera = false;
|
||||
restoreCameraState();
|
||||
}
|
||||
if (event.text == "0") {
|
||||
// Show camera location in log
|
||||
var cameraLocation = Camera.getPosition();
|
||||
print(cameraLocation.x + ", " + cameraLocation.y + ", " + cameraLocation.z);
|
||||
}
|
||||
}
|
||||
|
||||
Script.update.connect(update);
|
||||
Controller.keyPressEvent.connect(keyPressEvent);
|
72
examples/concertCamera_kyrs.js
Normal file
72
examples/concertCamera_kyrs.js
Normal file
|
@ -0,0 +1,72 @@
|
|||
//
|
||||
// concertCamera.js
|
||||
//
|
||||
// Created by Philip Rosedale on June 24, 2014
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Move a camera through a series of pre-set locations by pressing number keys
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
var oldMode;
|
||||
var avatarPosition;
|
||||
|
||||
var cameraNumber = 0;
|
||||
var freeCamera = false;
|
||||
|
||||
var cameraLocations = [ {x: 2921.5, y: 251.3, z: 8254.8}, {x: 2921.5, y: 251.3, z: 8254.4}, {x: 2921.5, y: 251.3, z: 8252.2}, {x: 2921.5, y: 251.3, z: 8247.2}, {x: 2921.4, y: 251.3, z: 8255.7} ];
|
||||
var cameraLookAts = [ {x: 2921.5, y: 251.3, z: 8255.7}, {x: 2921.5, y: 251.3, z: 8255.7}, {x: 2921.5, y: 251.3, z: 8255.7}, {x: 2921.5, y: 251.3, z: 8255.7}, {x: 2921.4 , y: 251.3, z: 8255.1} ];
|
||||
|
||||
function saveCameraState() {
|
||||
oldMode = Camera.getMode();
|
||||
avatarPosition = MyAvatar.position;
|
||||
Camera.setModeShiftPeriod(0.0);
|
||||
Camera.setMode("independent");
|
||||
}
|
||||
|
||||
function restoreCameraState() {
|
||||
Camera.stopLooking();
|
||||
Camera.setMode(oldMode);
|
||||
}
|
||||
|
||||
function update(deltaTime) {
|
||||
if (freeCamera) {
|
||||
var delta = Vec3.subtract(MyAvatar.position, avatarPosition);
|
||||
if (Vec3.length(delta) > 0.05) {
|
||||
cameraNumber = 0;
|
||||
freeCamera = false;
|
||||
restoreCameraState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function keyPressEvent(event) {
|
||||
|
||||
var choice = parseInt(event.text);
|
||||
|
||||
if ((choice > 0) && (choice <= cameraLocations.length)) {
|
||||
print("camera " + choice);
|
||||
if (!freeCamera) {
|
||||
saveCameraState();
|
||||
freeCamera = true;
|
||||
}
|
||||
Camera.setMode("independent");
|
||||
Camera.setPosition(cameraLocations[choice - 1]);
|
||||
Camera.keepLookingAt(cameraLookAts[choice - 1]);
|
||||
}
|
||||
if (event.text == "ESC") {
|
||||
cameraNumber = 0;
|
||||
freeCamera = false;
|
||||
restoreCameraState();
|
||||
}
|
||||
if (event.text == "0") {
|
||||
// Show camera location in log
|
||||
var cameraLocation = Camera.getPosition();
|
||||
print(cameraLocation.x + ", " + cameraLocation.y + ", " + cameraLocation.z);
|
||||
}
|
||||
}
|
||||
|
||||
Script.update.connect(update);
|
||||
Controller.keyPressEvent.connect(keyPressEvent);
|
|
@ -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;
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -57,7 +57,7 @@ var LocationMenu = function(opts) {
|
|||
y: 0,
|
||||
width: menuWidth + 10,
|
||||
height: (menuHeight * (pageSize + 1)) + 10,
|
||||
color: { red: 0, green: 0, blue: 0},
|
||||
backgroundColor: { red: 0, green: 0, blue: 0},
|
||||
topMargin: 4,
|
||||
leftMargin: 4,
|
||||
text: "",
|
||||
|
@ -71,7 +71,7 @@ var LocationMenu = function(opts) {
|
|||
y: 0,
|
||||
width: menuWidth,
|
||||
height: menuHeight,
|
||||
color: inactiveColor,
|
||||
backgroundColor: inactiveColor,
|
||||
topMargin: margin,
|
||||
leftMargin: margin,
|
||||
text: (i == 0) ? "Loading..." : "",
|
||||
|
@ -85,7 +85,7 @@ var LocationMenu = function(opts) {
|
|||
y: 0,
|
||||
width: menuWidth / 2,
|
||||
height: menuHeight,
|
||||
color: disabledColor,
|
||||
backgroundColor: disabledColor,
|
||||
topMargin: margin,
|
||||
leftMargin: margin,
|
||||
text: "Previous",
|
||||
|
@ -97,7 +97,7 @@ var LocationMenu = function(opts) {
|
|||
y: 0,
|
||||
width: menuWidth / 2,
|
||||
height: menuHeight,
|
||||
color: disabledColor,
|
||||
backgroundColor: disabledColor,
|
||||
topMargin: margin,
|
||||
leftMargin: margin,
|
||||
text: "Next",
|
||||
|
@ -175,10 +175,10 @@ var LocationMenu = function(opts) {
|
|||
if (start + i < this.locations.length) {
|
||||
location = this.locations[start + i];
|
||||
update.text = (start + i + 1) + ". " + location.username;
|
||||
update.color = inactiveColor;
|
||||
update.backgroundColor = inactiveColor;
|
||||
} else {
|
||||
update.text = "";
|
||||
update.color = disabledColor;
|
||||
update.backgroundColor = disabledColor;
|
||||
}
|
||||
Overlays.editOverlay(this.menuItems[i].overlay, update);
|
||||
this.menuItems[i].location = location;
|
||||
|
@ -187,8 +187,8 @@ var LocationMenu = function(opts) {
|
|||
this.previousEnabled = pageNumber > 0;
|
||||
this.nextEnabled = pageNumber < (this.numPages - 1);
|
||||
|
||||
Overlays.editOverlay(this.previousButton, { color: this.previousEnabled ? prevNextColor : disabledColor});
|
||||
Overlays.editOverlay(this.nextButton, { color: this.nextEnabled ? prevNextColor : disabledColor });
|
||||
Overlays.editOverlay(this.previousButton, { backgroundColor: this.previousEnabled ? prevNextColor : disabledColor});
|
||||
Overlays.editOverlay(this.nextButton, { backgroundColor: this.nextEnabled ? prevNextColor : disabledColor });
|
||||
}
|
||||
|
||||
this.mousePressEvent = function(event) {
|
||||
|
@ -198,17 +198,17 @@ var LocationMenu = function(opts) {
|
|||
self.toggleMenu();
|
||||
} else if (clickedOverlay == self.previousButton) {
|
||||
if (self.previousEnabled) {
|
||||
Overlays.editOverlay(clickedOverlay, { color: activeColor });
|
||||
Overlays.editOverlay(clickedOverlay, { backgroundColor: activeColor });
|
||||
}
|
||||
} else if (clickedOverlay == self.nextButton) {
|
||||
if (self.nextEnabled) {
|
||||
Overlays.editOverlay(clickedOverlay, { color: activeColor });
|
||||
Overlays.editOverlay(clickedOverlay, { backgroundColor: activeColor });
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < self.menuItems.length; i++) {
|
||||
if (clickedOverlay == self.menuItems[i].overlay) {
|
||||
if (self.menuItems[i].location != null) {
|
||||
Overlays.editOverlay(clickedOverlay, { color: activeColor });
|
||||
Overlays.editOverlay(clickedOverlay, { backgroundColor: activeColor });
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -221,19 +221,19 @@ var LocationMenu = function(opts) {
|
|||
|
||||
if (clickedOverlay == self.previousButton) {
|
||||
if (self.previousEnabled) {
|
||||
Overlays.editOverlay(clickedOverlay, { color: inactiveColor });
|
||||
Overlays.editOverlay(clickedOverlay, { backgroundColor: inactiveColor });
|
||||
self.goToPage(self.page - 1);
|
||||
}
|
||||
} else if (clickedOverlay == self.nextButton) {
|
||||
if (self.nextEnabled) {
|
||||
Overlays.editOverlay(clickedOverlay, { color: inactiveColor });
|
||||
Overlays.editOverlay(clickedOverlay, { backgroundColor: inactiveColor });
|
||||
self.goToPage(self.page + 1);
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < self.menuItems.length; i++) {
|
||||
if (clickedOverlay == self.menuItems[i].overlay) {
|
||||
if (self.menuItems[i].location != null) {
|
||||
Overlays.editOverlay(clickedOverlay, { color: inactiveColor });
|
||||
Overlays.editOverlay(clickedOverlay, { backgroundColor: inactiveColor });
|
||||
var location = self.menuItems[i].location;
|
||||
Window.location = "hifi://" + location.domain + "/"
|
||||
+ location.x + "," + location.y + "," + location.z;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -18,13 +18,16 @@ var RIGHT = 1;
|
|||
var lastLeftFrame = 0;
|
||||
var lastRightFrame = 0;
|
||||
|
||||
var LAST_FRAME = 11.0; // What is the number of the last frame we want to use in the animation?
|
||||
var SMOOTH_FACTOR = 0.80;
|
||||
var leftDirection = true;
|
||||
var rightDirection = true;
|
||||
|
||||
var LAST_FRAME = 15.0; // What is the number of the last frame we want to use in the animation?
|
||||
var SMOOTH_FACTOR = 0.0;
|
||||
var MAX_FRAMES = 30.0;
|
||||
|
||||
Script.update.connect(function(deltaTime) {
|
||||
var leftTriggerValue = Math.sqrt(Controller.getTriggerValue(LEFT));
|
||||
var rightTriggerValue = Math.sqrt(Controller.getTriggerValue(RIGHT));
|
||||
var leftTriggerValue = Controller.getTriggerValue(LEFT);
|
||||
var rightTriggerValue = Controller.getTriggerValue(RIGHT);
|
||||
|
||||
var leftFrame, rightFrame;
|
||||
|
||||
|
@ -32,10 +35,31 @@ Script.update.connect(function(deltaTime) {
|
|||
leftFrame = (leftTriggerValue * LAST_FRAME) * (1.0 - SMOOTH_FACTOR) + lastLeftFrame * SMOOTH_FACTOR;
|
||||
rightFrame = (rightTriggerValue * LAST_FRAME) * (1.0 - SMOOTH_FACTOR) + lastRightFrame * SMOOTH_FACTOR;
|
||||
|
||||
|
||||
if (!leftDirection) {
|
||||
leftFrame = MAX_FRAMES - leftFrame;
|
||||
}
|
||||
if (!rightDirection) {
|
||||
rightFrame = MAX_FRAMES - rightFrame;
|
||||
}
|
||||
|
||||
if ((leftTriggerValue == 1.0) && (leftDirection == true)) {
|
||||
leftDirection = false;
|
||||
lastLeftFrame = MAX_FRAMES - leftFrame;
|
||||
} else if ((leftTriggerValue == 0.0) && (leftDirection == false)) {
|
||||
leftDirection = true;
|
||||
lastLeftFrame = leftFrame;
|
||||
}
|
||||
if ((rightTriggerValue == 1.0) && (rightDirection == true)) {
|
||||
rightDirection = false;
|
||||
lastRightFrame = MAX_FRAMES - rightFrame;
|
||||
} else if ((rightTriggerValue == 0.0) && (rightDirection == false)) {
|
||||
rightDirection = true;
|
||||
lastRightFrame = rightFrame;
|
||||
}
|
||||
|
||||
if ((leftFrame != lastLeftFrame) && leftHandAnimation.length){
|
||||
MyAvatar.stopAnimation(leftHandAnimation);
|
||||
MyAvatar.startAnimation(leftHandAnimation, 30.0, 1.0, false, true, leftFrame, leftFrame);
|
||||
MyAvatar.startAnimation(leftHandAnimation, 30.0, 1.0, false, true, leftFrame, leftFrame);
|
||||
}
|
||||
if ((rightFrame != lastRightFrame) && rightHandAnimation.length) {
|
||||
MyAvatar.stopAnimation(rightHandAnimation);
|
||||
|
|
|
@ -113,16 +113,6 @@ if (APPLE)
|
|||
SET(INTERFACE_SRCS ${INTERFACE_SRCS} "${CMAKE_CURRENT_SOURCE_DIR}/interface.icns")
|
||||
endif()
|
||||
|
||||
# RtMidi for scripted MIDI control
|
||||
find_package(RtMidi)
|
||||
|
||||
if (RTMIDI_FOUND AND NOT DISABLE_RTMIDI)
|
||||
add_definitions(-DHAVE_RTMIDI)
|
||||
include_directories(SYSTEM ${RTMIDI_INCLUDE_DIR})
|
||||
|
||||
set(INTERFACE_SRCS ${INTERFACE_SRCS} "${RTMIDI_CPP}")
|
||||
endif ()
|
||||
|
||||
# create the executable, make it a bundle on OS X
|
||||
add_executable(${TARGET_NAME} MACOSX_BUNDLE ${INTERFACE_SRCS} ${QM})
|
||||
|
||||
|
@ -154,6 +144,7 @@ find_package(Visage)
|
|||
find_package(LeapMotion)
|
||||
find_package(ZLIB)
|
||||
find_package(Qxmpp)
|
||||
find_package(RtMidi)
|
||||
|
||||
# include the Sixense library for Razer Hydra if available
|
||||
if (SIXENSE_FOUND AND NOT DISABLE_SIXENSE)
|
||||
|
@ -237,11 +228,18 @@ if (QXMPP_FOUND AND NOT DISABLE_QXMPP)
|
|||
target_link_libraries(${TARGET_NAME} "${QXMPP_LIBRARY}")
|
||||
endif (QXMPP_FOUND AND NOT DISABLE_QXMPP)
|
||||
|
||||
# link CoreMIDI if we're using RtMidi
|
||||
if (RTMIDI_FOUND AND APPLE)
|
||||
find_library(CoreMIDI CoreMIDI)
|
||||
add_definitions(-D__MACOSX_CORE__)
|
||||
target_link_libraries(${TARGET_NAME} ${CoreMIDI})
|
||||
# and with RtMidi for RtMidi control
|
||||
if (RTMIDI_FOUND AND NOT DISABLE_RTMIDI)
|
||||
|
||||
add_definitions(-DHAVE_RTMIDI)
|
||||
include_directories(SYSTEM ${RTMIDI_INCLUDE_DIR})
|
||||
target_link_libraries(${TARGET_NAME} "${RTMIDI_LIBRARY}")
|
||||
|
||||
if (APPLE)
|
||||
find_library(CoreMIDI CoreMIDI)
|
||||
add_definitions(-D__MACOSX_CORE__)
|
||||
target_link_libraries(${TARGET_NAME} ${CoreMIDI})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# include headers for interface and InterfaceConfig.
|
||||
|
|
4
interface/external/rtmidi/readme.txt
vendored
4
interface/external/rtmidi/readme.txt
vendored
|
@ -7,7 +7,9 @@ Stephen Birarda, June 30, 2014
|
|||
|
||||
2. Copy RtMidi.h to externals/rtmidi/include.
|
||||
|
||||
3. Copy RtMidi.cpp to externals/rtmidi/src
|
||||
3. Compile the RtMidi library.
|
||||
|
||||
3. Copy either librtmidi.dylib (dynamic) or librtmidi.a (static) to externals/rtmidi/lib
|
||||
|
||||
4. Delete your build directory, run cmake and build, and you should be all set.
|
||||
|
||||
|
|
Binary file not shown.
|
@ -11,9 +11,16 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
|
||||
// the diffuse texture
|
||||
uniform sampler2D diffuseMap;
|
||||
|
||||
// local lights
|
||||
const int MAX_LOCAL_LIGHTS = 2; // 2 lights for now, will probably need more later on
|
||||
uniform int numLocalLights;
|
||||
uniform vec3 localLightDirections[MAX_LOCAL_LIGHTS];
|
||||
uniform vec3 localLightColors[MAX_LOCAL_LIGHTS];
|
||||
|
||||
// the interpolated position
|
||||
varying vec4 position;
|
||||
|
||||
|
@ -25,8 +32,19 @@ void main(void) {
|
|||
vec4 normalizedNormal = normalize(normal);
|
||||
float diffuse = dot(normalizedNormal, gl_LightSource[0].position);
|
||||
float facingLight = step(0.0, diffuse);
|
||||
|
||||
// the local light that is always present
|
||||
vec4 totalLocalLight = vec4(0.0, 0.0, 0.0, 1.0);
|
||||
for (int i = 0; i < numLocalLights; i++) {
|
||||
float localDiffuse = dot(normalizedNormal, vec4(localLightDirections[i], 1.0));
|
||||
float localLight = step(0.0, localDiffuse);
|
||||
float localLightVal = localDiffuse * localLight;
|
||||
|
||||
totalLocalLight += (localLightVal * vec4( localLightColors[i], 0.0));
|
||||
}
|
||||
|
||||
vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient +
|
||||
gl_FrontLightProduct[0].diffuse * (diffuse * facingLight));
|
||||
gl_FrontLightProduct[0].diffuse * (diffuse * facingLight) + totalLocalLight);
|
||||
|
||||
// compute the specular component (sans exponent)
|
||||
float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position - normalize(vec4(position.xyz, 0.0))),
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
const int MAX_LOCAL_LIGHTS = 4;
|
||||
|
||||
// the interpolated position
|
||||
varying vec4 position;
|
||||
|
||||
|
@ -37,3 +39,4 @@ void main(void) {
|
|||
// use standard pipeline transform
|
||||
gl_Position = ftransform();
|
||||
}
|
||||
|
||||
|
|
|
@ -37,9 +37,14 @@ void main(void) {
|
|||
normalizedBitangent * localNormal.y + normalizedNormal * localNormal.z, 0.0);
|
||||
float diffuse = dot(viewNormal, gl_LightSource[0].position);
|
||||
float facingLight = step(0.0, diffuse);
|
||||
float localDiffuse = dot(viewNormal, gl_LightSource[1].position);
|
||||
float localLight = step(0.0, localDiffuse);
|
||||
vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient +
|
||||
gl_FrontLightProduct[0].diffuse * (diffuse * facingLight));
|
||||
gl_FrontLightProduct[0].diffuse * (diffuse * facingLight) + gl_FrontLightProduct[1].diffuse * (localDiffuse * localLight));
|
||||
|
||||
|
||||
|
||||
|
||||
// compute the specular component (sans exponent)
|
||||
float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position -
|
||||
normalize(vec4(vec3(interpolatedPosition), 0.0))), viewNormal));
|
||||
|
|
|
@ -10,6 +10,11 @@
|
|||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
const int MAX_LOCAL_LIGHTS = 2;
|
||||
|
||||
uniform int numLocalLights;
|
||||
uniform vec3 localLightDirections[MAX_LOCAL_LIGHTS];
|
||||
uniform vec3 localLightColors[MAX_LOCAL_LIGHTS];
|
||||
|
||||
// the diffuse texture
|
||||
uniform sampler2D diffuseMap;
|
||||
|
@ -28,8 +33,19 @@ void main(void) {
|
|||
vec4 normalizedNormal = normalize(normal);
|
||||
float diffuse = dot(normalizedNormal, gl_LightSource[0].position);
|
||||
float facingLight = step(0.0, diffuse);
|
||||
|
||||
// the local light that is always present
|
||||
vec4 totalLocalLight = vec4(0.0, 0.0, 0.0, 1.0);
|
||||
for (int i = 0; i < numLocalLights; i++) {
|
||||
float localDiffuse = dot(normalizedNormal, vec4(localLightDirections[i], 1.0));
|
||||
float localLight = step(0.0, localDiffuse);
|
||||
float localLightVal = localDiffuse * localLight;
|
||||
|
||||
totalLocalLight += (localLightVal * vec4( localLightColors[i], 0.0));
|
||||
}
|
||||
|
||||
vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient +
|
||||
gl_FrontLightProduct[0].diffuse * (diffuse * facingLight));
|
||||
gl_FrontLightProduct[0].diffuse * (diffuse * facingLight) + totalLocalLight);
|
||||
|
||||
// compute the specular component (sans exponent)
|
||||
float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position - normalize(vec4(position.xyz, 0.0))),
|
||||
|
@ -38,4 +54,5 @@ void main(void) {
|
|||
// modulate texture by base color and add specular contribution
|
||||
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) + vec4(pow(specular, gl_FrontMaterial.shininess) *
|
||||
gl_FrontLightProduct[0].specular.rgb * texture2D(specularMap, gl_TexCoord[0].st).rgb, 0.0);
|
||||
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ void main(void) {
|
|||
position += clusterMatrix * gl_Vertex * clusterWeight;
|
||||
normal += clusterMatrix * vec4(gl_Normal, 0.0) * clusterWeight;
|
||||
}
|
||||
|
||||
position = gl_ModelViewMatrix * position;
|
||||
normal = normalize(gl_ModelViewMatrix * normal);
|
||||
|
||||
|
|
|
@ -174,7 +174,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);
|
||||
|
@ -318,12 +319,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
|||
QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
|
||||
|
||||
NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
|
||||
// Make sure cache on same thread than its parent (NetworkAccessManager)
|
||||
QNetworkDiskCache* cache = new QNetworkDiskCache();
|
||||
cache->moveToThread(networkAccessManager.thread());
|
||||
cache->setParent(&networkAccessManager);
|
||||
|
||||
cache->setCacheDirectory(!cachePath.isEmpty() ? cachePath : "interfaceCache");
|
||||
networkAccessManager.setCache(cache);
|
||||
|
||||
|
@ -361,6 +357,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
|||
|
||||
// Set the sixense filtering
|
||||
_sixenseManager.setFilter(Menu::getInstance()->isOptionChecked(MenuOption::FilterSixense));
|
||||
|
||||
// Set hand controller velocity filtering
|
||||
_sixenseManager.setLowVelocityFilter(Menu::getInstance()->isOptionChecked(MenuOption::LowVelocityFilter));
|
||||
|
||||
checkVersion();
|
||||
|
||||
|
@ -608,9 +607,19 @@ void Application::paintGL() {
|
|||
|
||||
} else if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
|
||||
_myCamera.setTightness(0.0f);
|
||||
_myCamera.setDistance(MIRROR_FULLSCREEN_DISTANCE * _scaleMirror);
|
||||
_myCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f)));
|
||||
_myCamera.setTargetPosition(_myAvatar->getHead()->calculateAverageEyePosition() + glm::vec3(0, _raiseMirror * _myAvatar->getScale(), 0));
|
||||
//Only behave like a true mirror when in the OR
|
||||
if (OculusManager::isConnected()) {
|
||||
_myCamera.setDistance(MIRROR_FULLSCREEN_DISTANCE * _scaleMirror);
|
||||
_myCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f)));
|
||||
_myCamera.setTargetPosition(_myAvatar->getHead()->calculateAverageEyePosition() + glm::vec3(0, _raiseMirror * _myAvatar->getScale(), 0));
|
||||
} else {
|
||||
_myCamera.setTightness(0.0f);
|
||||
glm::vec3 eyePosition = _myAvatar->getHead()->calculateAverageEyePosition();
|
||||
float headHeight = eyePosition.y - _myAvatar->getPosition().y;
|
||||
_myCamera.setDistance(MIRROR_FULLSCREEN_DISTANCE * _scaleMirror);
|
||||
_myCamera.setTargetPosition(_myAvatar->getPosition() + glm::vec3(0, headHeight + (_raiseMirror * _myAvatar->getScale()), 0));
|
||||
_myCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f)));
|
||||
}
|
||||
}
|
||||
|
||||
// Update camera position
|
||||
|
@ -689,13 +698,10 @@ void Application::paintGL() {
|
|||
}
|
||||
|
||||
{
|
||||
PerformanceTimer perfTimer("paintGL/renderOverlay");
|
||||
//If alpha is 1, we can render directly to the screen.
|
||||
if (_applicationOverlay.getAlpha() == 1.0f) {
|
||||
_applicationOverlay.renderOverlay();
|
||||
} else {
|
||||
//Render to to texture so we can fade it
|
||||
_applicationOverlay.renderOverlay(true);
|
||||
PerformanceTimer perfTimer("renderOverlay");
|
||||
// PrioVR will only work if renderOverlay is called, calibration is connected to Application::renderingOverlay()
|
||||
_applicationOverlay.renderOverlay(true);
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::UserInterface)) {
|
||||
_applicationOverlay.displayOverlayTexture();
|
||||
}
|
||||
}
|
||||
|
@ -1017,6 +1023,9 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
|||
Menu::getInstance()->triggerOption(MenuOption::FullscreenMirror);
|
||||
}
|
||||
break;
|
||||
case Qt::Key_Slash:
|
||||
Menu::getInstance()->triggerOption(MenuOption::UserInterface);
|
||||
break;
|
||||
case Qt::Key_F:
|
||||
if (isShifted) {
|
||||
Menu::getInstance()->triggerOption(MenuOption::DisplayFrustum);
|
||||
|
@ -1036,7 +1045,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
|||
}
|
||||
break;
|
||||
break;
|
||||
case Qt::Key_Slash:
|
||||
case Qt::Key_Percent:
|
||||
Menu::getInstance()->triggerOption(MenuOption::Stats);
|
||||
break;
|
||||
case Qt::Key_Plus:
|
||||
|
@ -1362,18 +1371,18 @@ void Application::idle() {
|
|||
if (timeSinceLastUpdate > IDLE_SIMULATE_MSECS) {
|
||||
_lastTimeUpdated.start();
|
||||
{
|
||||
PerformanceTimer perfTimer("idle/update");
|
||||
PerformanceTimer perfTimer("update");
|
||||
PerformanceWarning warn(showWarnings, "Application::idle()... update()");
|
||||
const float BIGGEST_DELTA_TIME_SECS = 0.25f;
|
||||
update(glm::clamp((float)timeSinceLastUpdate / 1000.f, 0.f, BIGGEST_DELTA_TIME_SECS));
|
||||
}
|
||||
{
|
||||
PerformanceTimer perfTimer("idle/updateGL");
|
||||
PerformanceTimer perfTimer("updateGL");
|
||||
PerformanceWarning warn(showWarnings, "Application::idle()... updateGL()");
|
||||
_glWidget->updateGL();
|
||||
}
|
||||
{
|
||||
PerformanceTimer perfTimer("idle/rest");
|
||||
PerformanceTimer perfTimer("rest");
|
||||
PerformanceWarning warn(showWarnings, "Application::idle()... rest of it");
|
||||
_idleLoopStdev.addValue(timeSinceLastUpdate);
|
||||
|
||||
|
@ -1385,7 +1394,7 @@ void Application::idle() {
|
|||
}
|
||||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::BuckyBalls)) {
|
||||
PerformanceTimer perfTimer("idle/rest/_buckyBalls");
|
||||
PerformanceTimer perfTimer("buckyBalls");
|
||||
_buckyBalls.simulate(timeSinceLastUpdate / 1000.f, Application::getInstance()->getAvatar()->getHandData());
|
||||
}
|
||||
|
||||
|
@ -1433,6 +1442,10 @@ void Application::setRenderVoxels(bool voxelRender) {
|
|||
}
|
||||
}
|
||||
|
||||
void Application::setLowVelocityFilter(bool lowVelocityFilter) {
|
||||
getSixenseManager()->setLowVelocityFilter(lowVelocityFilter);
|
||||
}
|
||||
|
||||
void Application::doKillLocalVoxels() {
|
||||
_wantToKillLocalVoxels = true;
|
||||
}
|
||||
|
@ -1801,7 +1814,7 @@ bool Application::isLookingAtMyAvatar(Avatar* avatar) {
|
|||
}
|
||||
|
||||
void Application::updateLOD() {
|
||||
PerformanceTimer perfTimer("idle/update/updateLOD");
|
||||
PerformanceTimer perfTimer("LOD");
|
||||
// adjust it unless we were asked to disable this feature, or if we're currently in throttleRendering mode
|
||||
if (!Menu::getInstance()->isOptionChecked(MenuOption::DisableAutoAdjustLOD) && !isThrottleRendering()) {
|
||||
Menu::getInstance()->autoAdjustLOD(_fps);
|
||||
|
@ -1811,7 +1824,7 @@ void Application::updateLOD() {
|
|||
}
|
||||
|
||||
void Application::updateMouseRay() {
|
||||
PerformanceTimer perfTimer("idle/update/updateMouseRay");
|
||||
PerformanceTimer perfTimer("mouseRay");
|
||||
|
||||
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
||||
PerformanceWarning warn(showWarnings, "Application::updateMouseRay()");
|
||||
|
@ -1844,8 +1857,6 @@ void Application::updateMouseRay() {
|
|||
}
|
||||
|
||||
void Application::updateFaceshift() {
|
||||
PerformanceTimer perfTimer("idle/update/updateFaceshift");
|
||||
|
||||
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
||||
PerformanceWarning warn(showWarnings, "Application::updateFaceshift()");
|
||||
|
||||
|
@ -1859,8 +1870,6 @@ void Application::updateFaceshift() {
|
|||
}
|
||||
|
||||
void Application::updateVisage() {
|
||||
PerformanceTimer perfTimer("idle/update/updateVisage");
|
||||
|
||||
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
||||
PerformanceWarning warn(showWarnings, "Application::updateVisage()");
|
||||
|
||||
|
@ -1869,11 +1878,11 @@ void Application::updateVisage() {
|
|||
}
|
||||
|
||||
void Application::updateMyAvatarLookAtPosition() {
|
||||
PerformanceTimer perfTimer("idle/update/updateMyAvatarLookAtPosition");
|
||||
|
||||
PerformanceTimer perfTimer("lookAt");
|
||||
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
||||
PerformanceWarning warn(showWarnings, "Application::updateMyAvatarLookAtPosition()");
|
||||
|
||||
_myAvatar->updateLookAtTargetAvatar();
|
||||
FaceTracker* tracker = getActiveFaceTracker();
|
||||
|
||||
bool isLookingAtSomeone = false;
|
||||
|
@ -1936,7 +1945,7 @@ void Application::updateMyAvatarLookAtPosition() {
|
|||
}
|
||||
|
||||
void Application::updateThreads(float deltaTime) {
|
||||
PerformanceTimer perfTimer("idle/update/updateThreads");
|
||||
PerformanceTimer perfTimer("updateThreads");
|
||||
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
||||
PerformanceWarning warn(showWarnings, "Application::updateThreads()");
|
||||
|
||||
|
@ -1951,7 +1960,7 @@ void Application::updateThreads(float deltaTime) {
|
|||
}
|
||||
|
||||
void Application::updateMetavoxels(float deltaTime) {
|
||||
PerformanceTimer perfTimer("idle/update/updateMetavoxels");
|
||||
PerformanceTimer perfTimer("updateMetavoxels");
|
||||
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
||||
PerformanceWarning warn(showWarnings, "Application::updateMetavoxels()");
|
||||
|
||||
|
@ -1981,7 +1990,7 @@ void Application::cameraMenuChanged() {
|
|||
}
|
||||
|
||||
void Application::updateCamera(float deltaTime) {
|
||||
PerformanceTimer perfTimer("idle/update/updateCamera");
|
||||
PerformanceTimer perfTimer("updateCamera");
|
||||
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
||||
PerformanceWarning warn(showWarnings, "Application::updateCamera()");
|
||||
|
||||
|
@ -1999,7 +2008,7 @@ void Application::updateCamera(float deltaTime) {
|
|||
}
|
||||
|
||||
void Application::updateDialogs(float deltaTime) {
|
||||
PerformanceTimer perfTimer("idle/update/updateDialogs");
|
||||
PerformanceTimer perfTimer("updateDialogs");
|
||||
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
||||
PerformanceWarning warn(showWarnings, "Application::updateDialogs()");
|
||||
|
||||
|
@ -2016,7 +2025,7 @@ void Application::updateDialogs(float deltaTime) {
|
|||
}
|
||||
|
||||
void Application::updateCursor(float deltaTime) {
|
||||
PerformanceTimer perfTimer("idle/update/updateCursor");
|
||||
PerformanceTimer perfTimer("updateCursor");
|
||||
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
||||
PerformanceWarning warn(showWarnings, "Application::updateCursor()");
|
||||
|
||||
|
@ -2041,34 +2050,24 @@ void Application::updateCursor(float deltaTime) {
|
|||
}
|
||||
|
||||
void Application::update(float deltaTime) {
|
||||
//PerformanceTimer perfTimer("idle/update"); // NOTE: we track this above in Application::idle()
|
||||
|
||||
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
||||
PerformanceWarning warn(showWarnings, "Application::update()");
|
||||
|
||||
updateLOD();
|
||||
updateMouseRay(); // check what's under the mouse and update the mouse voxel
|
||||
|
||||
DeviceTracker::updateAll();
|
||||
|
||||
updateFaceshift();
|
||||
updateVisage();
|
||||
|
||||
{
|
||||
PerformanceTimer perfTimer("idle/update/updateLookAtTargetAvatar");
|
||||
_myAvatar->updateLookAtTargetAvatar();
|
||||
}
|
||||
updateMyAvatarLookAtPosition();
|
||||
{
|
||||
PerformanceTimer perfTimer("idle/update/sixense,joystick,prioVR");
|
||||
PerformanceTimer perfTimer("devices");
|
||||
DeviceTracker::updateAll();
|
||||
updateFaceshift();
|
||||
updateVisage();
|
||||
_sixenseManager.update(deltaTime);
|
||||
_joystickManager.update();
|
||||
_prioVR.update(deltaTime);
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
PerformanceTimer perfTimer("idle/update/updateMyAvatar");
|
||||
PerformanceTimer perfTimer("myAvatar");
|
||||
updateMyAvatarLookAtPosition();
|
||||
updateMyAvatar(deltaTime); // Sample hardware, update view frustum if needed, and send avatar data to mixer/nodes
|
||||
}
|
||||
|
||||
|
@ -2078,54 +2077,48 @@ void Application::update(float deltaTime) {
|
|||
|
||||
updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process...
|
||||
|
||||
{
|
||||
PerformanceTimer perfTimer("idle/update/_avatarManager");
|
||||
_avatarManager.updateOtherAvatars(deltaTime); //loop through all the other avatars and simulate them...
|
||||
}
|
||||
_avatarManager.updateOtherAvatars(deltaTime); //loop through all the other avatars and simulate them...
|
||||
|
||||
updateMetavoxels(deltaTime); // update metavoxels
|
||||
updateCamera(deltaTime); // handle various camera tweaks like off axis projection
|
||||
updateDialogs(deltaTime); // update various stats dialogs if present
|
||||
updateCursor(deltaTime); // Handle cursor updates
|
||||
|
||||
{
|
||||
PerformanceTimer perfTimer("idle/update/_particles");
|
||||
PerformanceTimer perfTimer("particles");
|
||||
_particles.update(); // update the particles...
|
||||
}
|
||||
{
|
||||
PerformanceTimer perfTimer("idle/update/_particleCollisionSystem");
|
||||
_particleCollisionSystem.update(); // collide the particles...
|
||||
{
|
||||
PerformanceTimer perfTimer("collisions");
|
||||
_particleCollisionSystem.update(); // collide the particles...
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
PerformanceTimer perfTimer("idle/update/_models");
|
||||
PerformanceTimer perfTimer("models");
|
||||
_models.update(); // update the models...
|
||||
}
|
||||
|
||||
{
|
||||
PerformanceTimer perfTimer("idle/update/_overlays");
|
||||
PerformanceTimer perfTimer("overlays");
|
||||
_overlays.update(deltaTime);
|
||||
}
|
||||
|
||||
{
|
||||
PerformanceTimer perfTimer("idle/update/emit simulating");
|
||||
PerformanceTimer perfTimer("emitSimulating");
|
||||
// let external parties know we're updating
|
||||
emit simulating(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::updateMyAvatar(float deltaTime) {
|
||||
PerformanceTimer perfTimer("updateMyAvatar");
|
||||
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
||||
PerformanceWarning warn(showWarnings, "Application::updateMyAvatar()");
|
||||
|
||||
{
|
||||
PerformanceTimer perfTimer("updateMyAvatar/_myAvatar->update()");
|
||||
_myAvatar->update(deltaTime);
|
||||
}
|
||||
_myAvatar->update(deltaTime);
|
||||
|
||||
{
|
||||
// send head/hand data to the avatar mixer and voxel server
|
||||
PerformanceTimer perfTimer("updateMyAvatar/sendToAvatarMixer");
|
||||
PerformanceTimer perfTimer("send");
|
||||
QByteArray packet = byteArrayWithPopulatedHeader(PacketTypeAvatarData);
|
||||
packet.append(_myAvatar->toByteArray());
|
||||
controlledBroadcastToNodes(packet, NodeSet() << NodeType::AvatarMixer);
|
||||
|
@ -2138,14 +2131,15 @@ void Application::updateMyAvatar(float deltaTime) {
|
|||
// actually need to calculate the view frustum planes to send these details
|
||||
// to the server.
|
||||
{
|
||||
PerformanceTimer perfTimer("updateMyAvatar/loadViewFrustum");
|
||||
PerformanceTimer perfTimer("loadViewFrustum");
|
||||
loadViewFrustum(_myCamera, _viewFrustum);
|
||||
}
|
||||
|
||||
quint64 now = usecTimestampNow();
|
||||
|
||||
// Update my voxel servers with my current voxel query...
|
||||
{
|
||||
PerformanceTimer perfTimer("updateMyAvatar/queryOctree");
|
||||
quint64 now = usecTimestampNow();
|
||||
PerformanceTimer perfTimer("queryOctree");
|
||||
quint64 sinceLastQuery = now - _lastQueriedTime;
|
||||
const quint64 TOO_LONG_SINCE_LAST_QUERY = 3 * USECS_PER_SECOND;
|
||||
bool queryIsDue = sinceLastQuery > TOO_LONG_SINCE_LAST_QUERY;
|
||||
|
@ -2163,7 +2157,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) {
|
||||
|
@ -2171,6 +2164,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() {
|
||||
|
@ -2477,7 +2479,7 @@ glm::vec3 Application::getSunDirection() {
|
|||
}
|
||||
|
||||
void Application::updateShadowMap() {
|
||||
PerformanceTimer perfTimer("paintGL/updateShadowMap");
|
||||
PerformanceTimer perfTimer("shadowMap");
|
||||
QOpenGLFramebufferObject* fbo = _textureCache.getShadowFramebufferObject();
|
||||
fbo->bind();
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
|
@ -2639,7 +2641,7 @@ QImage Application::renderAvatarBillboard() {
|
|||
}
|
||||
|
||||
void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
|
||||
PerformanceTimer perfTimer("paintGL/displaySide");
|
||||
PerformanceTimer perfTimer("display");
|
||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide()");
|
||||
// transform by eye offset
|
||||
|
||||
|
@ -2673,7 +2675,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
|
|||
|
||||
// Setup 3D lights (after the camera transform, so that they are positioned in world space)
|
||||
{
|
||||
PerformanceTimer perfTimer("paintGL/displaySide/setupWorldLight");
|
||||
PerformanceTimer perfTimer("lights");
|
||||
setupWorldLight();
|
||||
}
|
||||
|
||||
|
@ -2692,7 +2694,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
|
|||
}
|
||||
|
||||
if (!selfAvatarOnly && Menu::getInstance()->isOptionChecked(MenuOption::Stars)) {
|
||||
PerformanceTimer perfTimer("paintGL/displaySide/stars");
|
||||
PerformanceTimer perfTimer("stars");
|
||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||
"Application::displaySide() ... stars...");
|
||||
if (!_stars.isStarsLoaded()) {
|
||||
|
@ -2721,7 +2723,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
|
|||
|
||||
// draw the sky dome
|
||||
if (!selfAvatarOnly && Menu::getInstance()->isOptionChecked(MenuOption::Atmosphere)) {
|
||||
PerformanceTimer perfTimer("paintGL/displaySide/atmosphere");
|
||||
PerformanceTimer perfTimer("atmosphere");
|
||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||
"Application::displaySide() ... atmosphere...");
|
||||
_environment.renderAtmospheres(whichCamera);
|
||||
|
@ -2742,13 +2744,13 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
|
|||
|
||||
// draw the audio reflector overlay
|
||||
{
|
||||
PerformanceTimer perfTimer("paintGL/displaySide/audioReflector");
|
||||
PerformanceTimer perfTimer("audio");
|
||||
_audioReflector.render();
|
||||
}
|
||||
|
||||
// Draw voxels
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) {
|
||||
PerformanceTimer perfTimer("paintGL/displaySide/voxels");
|
||||
PerformanceTimer perfTimer("voxels");
|
||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||
"Application::displaySide() ... voxels...");
|
||||
_voxels.render();
|
||||
|
@ -2756,14 +2758,14 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
|
|||
|
||||
// also, metavoxels
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Metavoxels)) {
|
||||
PerformanceTimer perfTimer("paintGL/displaySide/metavoxels");
|
||||
PerformanceTimer perfTimer("metavoxels");
|
||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||
"Application::displaySide() ... metavoxels...");
|
||||
_metavoxels.render();
|
||||
}
|
||||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::BuckyBalls)) {
|
||||
PerformanceTimer perfTimer("paintGL/displaySide/buckyBalls");
|
||||
PerformanceTimer perfTimer("buckyBalls");
|
||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||
"Application::displaySide() ... bucky balls...");
|
||||
_buckyBalls.render();
|
||||
|
@ -2771,7 +2773,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
|
|||
|
||||
// render particles...
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Particles)) {
|
||||
PerformanceTimer perfTimer("paintGL/displaySide/particles");
|
||||
PerformanceTimer perfTimer("particles");
|
||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||
"Application::displaySide() ... particles...");
|
||||
_particles.render();
|
||||
|
@ -2779,7 +2781,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
|
|||
|
||||
// render models...
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Models)) {
|
||||
PerformanceTimer perfTimer("paintGL/displaySide/models");
|
||||
PerformanceTimer perfTimer("models");
|
||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||
"Application::displaySide() ... models...");
|
||||
_models.render();
|
||||
|
@ -2787,7 +2789,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
|
|||
|
||||
// render the ambient occlusion effect if enabled
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::AmbientOcclusion)) {
|
||||
PerformanceTimer perfTimer("paintGL/displaySide/AmbientOcclusion");
|
||||
PerformanceTimer perfTimer("ambientOcclusion");
|
||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||
"Application::displaySide() ... AmbientOcclusion...");
|
||||
_ambientOcclusionEffect.render();
|
||||
|
@ -2802,20 +2804,22 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
|
|||
|
||||
bool mirrorMode = (whichCamera.getInterpolatedMode() == CAMERA_MODE_MIRROR);
|
||||
{
|
||||
PerformanceTimer perfTimer("paintGL/displaySide/renderAvatars");
|
||||
PerformanceTimer perfTimer("avatars");
|
||||
|
||||
_avatarManager.renderAvatars(mirrorMode ? Avatar::MIRROR_RENDER_MODE : Avatar::NORMAL_RENDER_MODE, selfAvatarOnly);
|
||||
}
|
||||
|
||||
if (!selfAvatarOnly) {
|
||||
// Render the world box
|
||||
if (whichCamera.getMode() != CAMERA_MODE_MIRROR && Menu::getInstance()->isOptionChecked(MenuOption::Stats)) {
|
||||
PerformanceTimer perfTimer("paintGL/displaySide/renderWorldBox");
|
||||
if (whichCamera.getMode() != CAMERA_MODE_MIRROR && Menu::getInstance()->isOptionChecked(MenuOption::Stats) &&
|
||||
Menu::getInstance()->isOptionChecked(MenuOption::UserInterface)) {
|
||||
PerformanceTimer perfTimer("worldBox");
|
||||
renderWorldBox();
|
||||
}
|
||||
|
||||
// view frustum for debugging
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::DisplayFrustum) && whichCamera.getMode() != CAMERA_MODE_MIRROR) {
|
||||
PerformanceTimer perfTimer("paintGL/displaySide/ViewFrustum");
|
||||
PerformanceTimer perfTimer("viewFrustum");
|
||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||
"Application::displaySide() ... renderViewFrustum...");
|
||||
renderViewFrustum(_viewFrustum);
|
||||
|
@ -2823,7 +2827,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
|
|||
|
||||
// render voxel fades if they exist
|
||||
if (_voxelFades.size() > 0) {
|
||||
PerformanceTimer perfTimer("paintGL/displaySide/voxel fades");
|
||||
PerformanceTimer perfTimer("voxelFades");
|
||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||
"Application::displaySide() ... voxel fades...");
|
||||
_voxelFadesLock.lockForWrite();
|
||||
|
@ -2840,13 +2844,13 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
|
|||
|
||||
// give external parties a change to hook in
|
||||
{
|
||||
PerformanceTimer perfTimer("paintGL/displaySide/inWorldInterface");
|
||||
PerformanceTimer perfTimer("inWorldInterface");
|
||||
emit renderingInWorldInterface();
|
||||
}
|
||||
|
||||
// render JS/scriptable overlays
|
||||
{
|
||||
PerformanceTimer perfTimer("paintGL/displaySide/3dOverlays");
|
||||
PerformanceTimer perfTimer("3dOverlays");
|
||||
_overlays.render3D();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
@ -317,6 +319,7 @@ public slots:
|
|||
void nudgeVoxelsByVector(const VoxelDetail& sourceVoxel, const glm::vec3& nudgeVec);
|
||||
|
||||
void setRenderVoxels(bool renderVoxels);
|
||||
void setLowVelocityFilter(bool lowVelocityFilter);
|
||||
void doKillLocalVoxels();
|
||||
void loadDialog();
|
||||
void loadScriptURLDialog();
|
||||
|
@ -585,6 +588,7 @@ private:
|
|||
QSystemTrayIcon* _trayIcon;
|
||||
|
||||
quint64 _lastNackTime;
|
||||
quint64 _lastSendDownstreamAudioStats;
|
||||
};
|
||||
|
||||
#endif // hifi_Application_h
|
||||
|
|
|
@ -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),
|
||||
|
@ -67,7 +76,7 @@ Audio::Audio(int16_t initialJitterBufferSamples, QObject* parent) :
|
|||
_proceduralAudioOutput(NULL),
|
||||
_proceduralOutputDevice(NULL),
|
||||
_inputRingBuffer(0),
|
||||
_ringBuffer(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO),
|
||||
_ringBuffer(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO, false, 100),
|
||||
_isStereoInput(false),
|
||||
_averagedLatency(0.0),
|
||||
_measuredJitter(0),
|
||||
|
@ -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,16 +947,21 @@ void Audio::processReceivedAudio(const QByteArray& audioByteArray) {
|
|||
//qDebug() << "Audio output just starved.";
|
||||
_ringBuffer.setIsStarved(true);
|
||||
_numFramesDisplayStarve = 10;
|
||||
|
||||
_starveCount++;
|
||||
_consecutiveNotMixedCount = 0;
|
||||
}
|
||||
|
||||
// if there is anything in the ring buffer, decide what to do
|
||||
if (_ringBuffer.samplesAvailable() > 0) {
|
||||
|
||||
int numNetworkOutputSamples = _ringBuffer.samplesAvailable();
|
||||
int numDeviceOutputSamples = numNetworkOutputSamples / networkOutputToOutputRatio;
|
||||
|
||||
QByteArray outputBuffer;
|
||||
outputBuffer.resize(numDeviceOutputSamples * sizeof(int16_t));
|
||||
int numNetworkOutputSamples;
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::DisableQAudioOutputOverflowCheck)) {
|
||||
numNetworkOutputSamples = _ringBuffer.samplesAvailable();
|
||||
} else {
|
||||
int numSamplesAudioOutputRoomFor = _audioOutput->bytesFree() / sizeof(int16_t);
|
||||
numNetworkOutputSamples = std::min(_ringBuffer.samplesAvailable(), (int)(numSamplesAudioOutputRoomFor * networkOutputToOutputRatio));
|
||||
}
|
||||
|
||||
// if there is data in the ring buffer and room in the audio output, decide what to do
|
||||
if (numNetworkOutputSamples > 0) {
|
||||
|
||||
int numSamplesNeededToStartPlayback = std::min(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO + (_jitterBufferSamples * 2),
|
||||
_ringBuffer.getSampleCapacity());
|
||||
|
@ -884,7 +969,13 @@ 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;
|
||||
|
||||
QByteArray outputBuffer;
|
||||
outputBuffer.resize(numDeviceOutputSamples * sizeof(int16_t));
|
||||
|
||||
// We are either already playing back, or we have enough audio to start playing back.
|
||||
//qDebug() << "pushing " << numNetworkOutputSamples;
|
||||
_ringBuffer.setIsStarved(false);
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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()]) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ protected:
|
|||
private slots:
|
||||
void activeChanged(Qt::ApplicationState state);
|
||||
void throttleRender();
|
||||
bool eventFilter(QObject*, QEvent* event);
|
||||
};
|
||||
|
||||
#endif // hifi_GLCanvas_h
|
||||
|
|
35
interface/src/Hair.cpp
Normal file
35
interface/src/Hair.cpp
Normal file
|
@ -0,0 +1,35 @@
|
|||
//
|
||||
// Hair.cpp
|
||||
// interface/src
|
||||
//
|
||||
// Created by Philip on June 26, 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
|
||||
//
|
||||
// Creates single flexible vertlet-integrated strands that can be used for hair/fur/grass
|
||||
|
||||
#include "Hair.h"
|
||||
|
||||
#include "Util.h"
|
||||
#include "world.h"
|
||||
|
||||
|
||||
Hair::Hair() {
|
||||
qDebug() << "Creating Hair";
|
||||
}
|
||||
|
||||
void Hair::simulate(float deltaTime) {
|
||||
}
|
||||
|
||||
void Hair::render() {
|
||||
//
|
||||
// Before calling this function, translate/rotate to the origin of the owning object
|
||||
glPushMatrix();
|
||||
glColor3f(1.0f, 1.0f, 0.0f);
|
||||
glutSolidSphere(1.0f, 15, 15);
|
||||
glPopMatrix();
|
||||
}
|
||||
|
||||
|
35
interface/src/Hair.h
Normal file
35
interface/src/Hair.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
//
|
||||
// Hair.h
|
||||
// interface/src
|
||||
//
|
||||
// Created by Philip on June 26, 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_Hair_h
|
||||
#define hifi_Hair_h
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include "GeometryUtil.h"
|
||||
#include "InterfaceConfig.h"
|
||||
#include "Util.h"
|
||||
|
||||
|
||||
class Hair {
|
||||
public:
|
||||
Hair();
|
||||
void simulate(float deltaTime);
|
||||
void render();
|
||||
|
||||
private:
|
||||
|
||||
};
|
||||
|
||||
#endif // hifi_Hair_h
|
|
@ -276,6 +276,7 @@ Menu::Menu() :
|
|||
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Mirror, Qt::SHIFT | Qt::Key_H, true);
|
||||
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FullscreenMirror, Qt::Key_H, false,
|
||||
appInstance, SLOT(cameraMenuChanged()));
|
||||
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::UserInterface, Qt::Key_Slash, true);
|
||||
|
||||
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::EnableVRMode, 0,
|
||||
false,
|
||||
|
@ -326,7 +327,7 @@ Menu::Menu() :
|
|||
|
||||
|
||||
addDisabledActionAndSeparator(viewMenu, "Stats");
|
||||
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Stats, Qt::Key_Slash);
|
||||
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Stats, Qt::Key_Percent);
|
||||
addActionToQMenuAndActionHash(viewMenu, MenuOption::Log, Qt::CTRL | Qt::Key_L, appInstance, SLOT(toggleLogDialog()));
|
||||
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Bandwidth, 0, true);
|
||||
addActionToQMenuAndActionHash(viewMenu, MenuOption::BandwidthDetails, 0, this, SLOT(bandwidthDetails()));
|
||||
|
@ -406,9 +407,7 @@ Menu::Menu() :
|
|||
|
||||
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::GlowWhenSpeaking, 0, true);
|
||||
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::ChatCircling, 0, false);
|
||||
|
||||
QMenu* oculusOptionsMenu = developerMenu->addMenu("Oculus Options");
|
||||
addCheckableActionToQMenuAndActionHash(oculusOptionsMenu, MenuOption::DisplayOculusOverlays, 0, true);
|
||||
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::FocusIndicators, 0, false);
|
||||
|
||||
QMenu* sixenseOptionsMenu = developerMenu->addMenu("Sixense Options");
|
||||
addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu, MenuOption::SixenseMouseInput, 0, true);
|
||||
|
@ -421,6 +420,13 @@ Menu::Menu() :
|
|||
true,
|
||||
appInstance->getSixenseManager(),
|
||||
SLOT(setFilter(bool)));
|
||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu,
|
||||
MenuOption::LowVelocityFilter,
|
||||
0,
|
||||
true,
|
||||
appInstance,
|
||||
SLOT(setLowVelocityFilter(bool)));
|
||||
|
||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHands, 0, true);
|
||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::HandsCollideWithSelf, 0, false);
|
||||
|
@ -435,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);
|
||||
|
@ -575,6 +579,8 @@ Menu::Menu() :
|
|||
Qt::CTRL | Qt::SHIFT | Qt::Key_U,
|
||||
false);
|
||||
|
||||
addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::DisableQAudioOutputOverflowCheck, 0, false);
|
||||
|
||||
addActionToQMenuAndActionHash(developerMenu, MenuOption::PasteToVoxel,
|
||||
Qt::CTRL | Qt::SHIFT | Qt::Key_V,
|
||||
this,
|
||||
|
@ -1006,7 +1012,6 @@ void Menu::goToDomainDialog() {
|
|||
domainDialog.setWindowTitle("Go to Domain");
|
||||
domainDialog.setLabelText("Domain server:");
|
||||
domainDialog.setTextValue(currentDomainHostname);
|
||||
domainDialog.setWindowFlags(Qt::Sheet);
|
||||
domainDialog.resize(domainDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW, domainDialog.size().height());
|
||||
|
||||
int dialogReturn = domainDialog.exec();
|
||||
|
@ -1044,7 +1049,6 @@ void Menu::goTo() {
|
|||
QString destination = QString();
|
||||
|
||||
gotoDialog.setTextValue(destination);
|
||||
gotoDialog.setWindowFlags(Qt::Sheet);
|
||||
gotoDialog.resize(gotoDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW, gotoDialog.size().height());
|
||||
|
||||
int dialogReturn = gotoDialog.exec();
|
||||
|
@ -1160,7 +1164,6 @@ void Menu::goToLocation() {
|
|||
coordinateDialog.setWindowTitle("Go to Location");
|
||||
coordinateDialog.setLabelText("Coordinate as x,y,z:");
|
||||
coordinateDialog.setTextValue(currentLocation);
|
||||
coordinateDialog.setWindowFlags(Qt::Sheet);
|
||||
coordinateDialog.resize(coordinateDialog.parentWidget()->size().width() * 0.30, coordinateDialog.size().height());
|
||||
|
||||
int dialogReturn = coordinateDialog.exec();
|
||||
|
@ -1225,7 +1228,6 @@ void Menu::nameLocation() {
|
|||
"(wherever you are standing and looking now) as you.\n\n"
|
||||
"Location name:");
|
||||
|
||||
nameDialog.setWindowFlags(Qt::Sheet);
|
||||
nameDialog.resize((int) (nameDialog.parentWidget()->size().width() * 0.30), nameDialog.size().height());
|
||||
|
||||
if (nameDialog.exec() == QDialog::Accepted) {
|
||||
|
|
|
@ -343,13 +343,13 @@ namespace MenuOption {
|
|||
const QString DecreaseVoxelSize = "Decrease Voxel Size";
|
||||
const QString DisableAutoAdjustLOD = "Disable Automatically Adjusting LOD";
|
||||
const QString DisableNackPackets = "Disable NACK Packets";
|
||||
const QString DisableQAudioOutputOverflowCheck = "Disable QAudioOutput Overflow Check";
|
||||
const QString DisplayFrustum = "Display Frustum";
|
||||
const QString DisplayHands = "Display Hands";
|
||||
const QString DisplayHandTargets = "Display Hand Targets";
|
||||
const QString DisplayModelBounds = "Display Model Bounds";
|
||||
const QString DisplayModelElementProxy = "Display Model Element Bounds";
|
||||
const QString DisplayModelElementChildProxies = "Display Model Element Children";
|
||||
const QString DisplayOculusOverlays = "Display Oculus Overlays";
|
||||
const QString DisplayTimingDetails = "Display Timing Details";
|
||||
const QString DontFadeOnVoxelServerChanges = "Don't Fade In/Out on Voxel Server Changes";
|
||||
const QString EchoLocalAudio = "Echo Local Audio";
|
||||
|
@ -357,17 +357,17 @@ 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";
|
||||
const QString LowVelocityFilter = "Low Velocity Filter";
|
||||
const QString FirstPerson = "First Person";
|
||||
const QString FocusIndicators = "Focus Indicators";
|
||||
const QString FrameTimer = "Show Timer";
|
||||
const QString FrustumRenderMode = "Render Mode";
|
||||
const QString Fullscreen = "Fullscreen";
|
||||
|
@ -438,6 +438,7 @@ namespace MenuOption {
|
|||
const QString UploadAttachment = "Upload Attachment Model";
|
||||
const QString UploadHead = "Upload Head Model";
|
||||
const QString UploadSkeleton = "Upload Skeleton Model";
|
||||
const QString UserInterface = "User Interface";
|
||||
const QString Visage = "Visage";
|
||||
const QString VoxelMode = "Cycle Voxel Mode";
|
||||
const QString Voxels = "Voxels";
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -14,12 +14,13 @@
|
|||
#include <glm/glm.hpp>
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
#include <glm/gtx/vector_angle.hpp>
|
||||
|
||||
#include <NodeList.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
#include <GeometryUtil.h>
|
||||
#include <NodeList.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include <PerfStat.h>
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "Avatar.h"
|
||||
|
@ -59,6 +60,7 @@ Avatar::Avatar() :
|
|||
_mouseRayDirection(0.0f, 0.0f, 0.0f),
|
||||
_moving(false),
|
||||
_collisionGroups(0),
|
||||
_numLocalLights(2),
|
||||
_initialized(false),
|
||||
_shouldRenderBillboard(true)
|
||||
{
|
||||
|
@ -81,6 +83,23 @@ void Avatar::init() {
|
|||
_initialized = true;
|
||||
_shouldRenderBillboard = (getLODDistance() >= BILLBOARD_LOD_DISTANCE);
|
||||
initializeHair();
|
||||
|
||||
for (int i = 0; i < MAX_LOCAL_LIGHTS; i++) {
|
||||
_localLightColors[i] = glm::vec3(0.0f, 0.0f, 0.0f);
|
||||
_localLightDirections[i] = glm::vec3(0.0f, 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
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] = darkGrayColor;
|
||||
|
||||
_localLightDirections[0] = directionX;
|
||||
_localLightDirections[1] = directionY;
|
||||
}
|
||||
|
||||
glm::vec3 Avatar::getChestPosition() const {
|
||||
|
@ -99,6 +118,7 @@ float Avatar::getLODDistance() const {
|
|||
}
|
||||
|
||||
void Avatar::simulate(float deltaTime) {
|
||||
PerformanceTimer perfTimer("simulate");
|
||||
if (_scale != _targetScale) {
|
||||
setScale(_targetScale);
|
||||
}
|
||||
|
@ -118,31 +138,43 @@ void Avatar::simulate(float deltaTime) {
|
|||
bool inViewFrustum = Application::getInstance()->getViewFrustum()->sphereInFrustum(_position, boundingRadius) !=
|
||||
ViewFrustum::OUTSIDE;
|
||||
|
||||
getHand()->simulate(deltaTime, false);
|
||||
{
|
||||
PerformanceTimer perfTimer("hand");
|
||||
getHand()->simulate(deltaTime, false);
|
||||
}
|
||||
_skeletonModel.setLODDistance(getLODDistance());
|
||||
|
||||
if (!_shouldRenderBillboard && inViewFrustum) {
|
||||
if (_hasNewJointRotations) {
|
||||
for (int i = 0; i < _jointData.size(); i++) {
|
||||
const JointData& data = _jointData.at(i);
|
||||
_skeletonModel.setJointState(i, data.valid, data.rotation);
|
||||
{
|
||||
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);
|
||||
_skeletonModel.simulate(deltaTime, _hasNewJointRotations);
|
||||
simulateAttachments(deltaTime);
|
||||
_hasNewJointRotations = false;
|
||||
}
|
||||
{
|
||||
PerformanceTimer perfTimer("head");
|
||||
glm::vec3 headPosition = _position;
|
||||
_skeletonModel.getHeadPosition(headPosition);
|
||||
Head* head = getHead();
|
||||
head->setPosition(headPosition);
|
||||
head->setScale(_scale);
|
||||
head->simulate(deltaTime, false, _shouldRenderBillboard);
|
||||
}
|
||||
_skeletonModel.simulate(deltaTime, _hasNewJointRotations);
|
||||
simulateAttachments(deltaTime);
|
||||
_hasNewJointRotations = false;
|
||||
|
||||
glm::vec3 headPosition = _position;
|
||||
_skeletonModel.getHeadPosition(headPosition);
|
||||
Head* head = getHead();
|
||||
head->setPosition(headPosition);
|
||||
head->setScale(_scale);
|
||||
head->simulate(deltaTime, false, _shouldRenderBillboard);
|
||||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) {
|
||||
PerformanceTimer perfTimer("hair");
|
||||
simulateHair(deltaTime);
|
||||
}
|
||||
|
||||
foreach (Hair* hair, _hairs) {
|
||||
hair->simulate(deltaTime);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// update position by velocity, and subtract the change added earlier for gravity
|
||||
|
@ -219,7 +251,7 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) {
|
|||
const float GLOW_DISTANCE = 20.0f;
|
||||
const float GLOW_MAX_LOUDNESS = 2500.0f;
|
||||
const float MAX_GLOW = 0.5f;
|
||||
|
||||
|
||||
float GLOW_FROM_AVERAGE_LOUDNESS = ((this == Application::getInstance()->getAvatar())
|
||||
? 0.0f
|
||||
: MAX_GLOW * getHeadData()->getAudioLoudness() / GLOW_MAX_LOUDNESS);
|
||||
|
@ -230,7 +262,23 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) {
|
|||
float glowLevel = _moving && distanceToTarget > GLOW_DISTANCE && renderMode == NORMAL_RENDER_MODE
|
||||
? 1.0f
|
||||
: GLOW_FROM_AVERAGE_LOUDNESS;
|
||||
|
||||
|
||||
|
||||
// local lights directions and colors
|
||||
getSkeletonModel().setNumLocalLights(_numLocalLights);
|
||||
getHead()->getFaceModel().setNumLocalLights(_numLocalLights);
|
||||
for (int i = 0; i < MAX_LOCAL_LIGHTS; i++) {
|
||||
glm::vec3 normalized = glm::normalize(_localLightDirections[i]);
|
||||
|
||||
// body
|
||||
getSkeletonModel().setLocalLightColor(_localLightColors[i], i);
|
||||
getSkeletonModel().setLocalLightDirection(normalized, i);
|
||||
|
||||
// head
|
||||
getHead()->getFaceModel().setLocalLightColor(_localLightColors[i], i);
|
||||
getHead()->getFaceModel().setLocalLightDirection(_localLightDirections[i], i);
|
||||
}
|
||||
|
||||
// render body
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Avatars)) {
|
||||
renderBody(renderMode, glowLevel);
|
||||
|
@ -252,7 +300,7 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) {
|
|||
}
|
||||
|
||||
// If this is the avatar being looked at, render a little ball above their head
|
||||
if (_isLookAtTarget) {
|
||||
if (_isLookAtTarget && Menu::getInstance()->isOptionChecked(MenuOption::FocusIndicators)) {
|
||||
const float LOOK_AT_INDICATOR_RADIUS = 0.03f;
|
||||
const float LOOK_AT_INDICATOR_OFFSET = 0.22f;
|
||||
const float LOOK_AT_INDICATOR_COLOR[] = { 0.8f, 0.0f, 0.0f, 0.75f };
|
||||
|
@ -380,6 +428,9 @@ void Avatar::renderBody(RenderMode renderMode, float glowLevel) {
|
|||
getHead()->render(1.0f, modelRenderMode);
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) {
|
||||
renderHair();
|
||||
foreach (Hair* hair, _hairs) {
|
||||
hair->render();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -603,7 +654,6 @@ void Avatar::initializeHair() {
|
|||
|
||||
}
|
||||
}
|
||||
qDebug() << "Initialize Hair";
|
||||
}
|
||||
|
||||
bool Avatar::shouldRenderHead(const glm::vec3& cameraPosition, RenderMode renderMode) const {
|
||||
|
@ -920,6 +970,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");
|
||||
|
@ -1107,3 +1162,29 @@ void Avatar::setShowDisplayName(bool showDisplayName) {
|
|||
|
||||
}
|
||||
|
||||
void Avatar::setLocalLightDirection(const glm::vec3& direction, int lightIndex) {
|
||||
_localLightDirections[lightIndex] = direction;
|
||||
qDebug( "set light %d direction ( %f, %f, %f )\n", lightIndex, direction.x, direction.y, direction.z );
|
||||
}
|
||||
|
||||
void Avatar::setLocalLightColor(const glm::vec3& color, int lightIndex) {
|
||||
_localLightColors[lightIndex] = color;
|
||||
qDebug( "set light %d color ( %f, %f, %f )\n", lightIndex, color.x, color.y, color.z );
|
||||
}
|
||||
|
||||
void Avatar::addLocalLight() {
|
||||
if (_numLocalLights + 1 <= MAX_LOCAL_LIGHTS) {
|
||||
++_numLocalLights;
|
||||
}
|
||||
|
||||
qDebug("ADD LOCAL LIGHT (numLocalLights = %d)\n", _numLocalLights);
|
||||
}
|
||||
|
||||
void Avatar::removeLocalLight() {
|
||||
if (_numLocalLights - 1 >= 0) {
|
||||
--_numLocalLights;
|
||||
}
|
||||
|
||||
qDebug("REMOVE LOCAL LIGHT (numLocalLights = %d)\n", _numLocalLights);
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
#include <AvatarData.h>
|
||||
|
||||
#include "Hair.h"
|
||||
#include "Hand.h"
|
||||
#include "Head.h"
|
||||
#include "InterfaceConfig.h"
|
||||
|
@ -151,14 +152,23 @@ 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();
|
||||
|
||||
void setLocalLightDirection(const glm::vec3& direction, int lightIndex);
|
||||
void setLocalLightColor(const glm::vec3& color, int lightIndex);
|
||||
void addLocalLight();
|
||||
void removeLocalLight();
|
||||
|
||||
signals:
|
||||
void collisionWithAvatar(const QUuid& myUUID, const QUuid& theirUUID, const CollisionInfo& collision);
|
||||
|
||||
protected:
|
||||
QVector<Hair*> _hairs;
|
||||
SkeletonModel _skeletonModel;
|
||||
QVector<Model*> _attachmentModels;
|
||||
float _bodyYawDelta;
|
||||
|
@ -174,9 +184,14 @@ protected:
|
|||
glm::vec3 _mouseRayDirection;
|
||||
float _stringLength;
|
||||
bool _moving; ///< set when position is changing
|
||||
|
||||
|
||||
quint32 _collisionGroups;
|
||||
|
||||
|
||||
// always-present local lighting for the avatar
|
||||
glm::vec3 _localLightDirections[MAX_LOCAL_LIGHTS];
|
||||
glm::vec3 _localLightColors[MAX_LOCAL_LIGHTS];
|
||||
int _numLocalLights;
|
||||
|
||||
// protected methods...
|
||||
glm::vec3 getBodyRightDirection() const { return getOrientation() * IDENTITY_RIGHT; }
|
||||
glm::vec3 getBodyUpDirection() const { return getOrientation() * IDENTITY_UP; }
|
||||
|
|
|
@ -41,9 +41,13 @@ void AvatarManager::init() {
|
|||
}
|
||||
|
||||
void AvatarManager::updateOtherAvatars(float deltaTime) {
|
||||
if (_avatarHash.size() < 2) {
|
||||
return;
|
||||
}
|
||||
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
||||
PerformanceWarning warn(showWarnings, "Application::updateAvatars()");
|
||||
|
||||
PerformanceTimer perfTimer("otherAvatars");
|
||||
Application* applicationInstance = Application::getInstance();
|
||||
glm::vec3 mouseOrigin = applicationInstance->getMouseRayOrigin();
|
||||
glm::vec3 mouseDirection = applicationInstance->getMouseRayDirection();
|
||||
|
|
|
@ -49,9 +49,9 @@ void FaceModel::simulate(float deltaTime, bool fullUpdate) {
|
|||
void FaceModel::maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) {
|
||||
// get the rotation axes in joint space and use them to adjust the rotation
|
||||
glm::mat3 axes = glm::mat3_cast(glm::quat());
|
||||
glm::mat3 inverse = glm::mat3(glm::inverse(parentState.getTransform() * glm::translate(state.getDefaultTranslationInParentFrame()) *
|
||||
glm::mat3 inverse = glm::mat3(glm::inverse(parentState.getTransform() * glm::translate(state.getDefaultTranslationInConstrainedFrame()) *
|
||||
joint.preTransform * glm::mat4_cast(joint.preRotation)));
|
||||
state.setRotationInParentFrame(glm::angleAxis(- RADIANS_PER_DEGREE * _owningHead->getFinalRoll(), glm::normalize(inverse * axes[2]))
|
||||
state.setRotationInConstrainedFrame(glm::angleAxis(- RADIANS_PER_DEGREE * _owningHead->getFinalRoll(), glm::normalize(inverse * axes[2]))
|
||||
* glm::angleAxis(RADIANS_PER_DEGREE * _owningHead->getFinalYaw(), glm::normalize(inverse * axes[1]))
|
||||
* glm::angleAxis(- RADIANS_PER_DEGREE * _owningHead->getFinalPitch(), glm::normalize(inverse * axes[0]))
|
||||
* joint.rotation);
|
||||
|
@ -61,14 +61,14 @@ void FaceModel::maybeUpdateEyeRotation(const JointState& parentState, const FBXJ
|
|||
// likewise with the eye joints
|
||||
// NOTE: at the moment we do the math in the world-frame, hence the inverse transform is more complex than usual.
|
||||
glm::mat4 inverse = glm::inverse(glm::mat4_cast(_rotation) * parentState.getTransform() *
|
||||
glm::translate(state.getDefaultTranslationInParentFrame()) *
|
||||
glm::translate(state.getDefaultTranslationInConstrainedFrame()) *
|
||||
joint.preTransform * glm::mat4_cast(joint.preRotation * joint.rotation));
|
||||
glm::vec3 front = glm::vec3(inverse * glm::vec4(_owningHead->getFinalOrientationInWorldFrame() * IDENTITY_FRONT, 0.0f));
|
||||
glm::vec3 lookAt = glm::vec3(inverse * glm::vec4(_owningHead->getLookAtPosition() +
|
||||
_owningHead->getSaccade() - _translation, 1.0f));
|
||||
glm::quat between = rotationBetween(front, lookAt);
|
||||
const float MAX_ANGLE = 30.0f * RADIANS_PER_DEGREE;
|
||||
state.setRotationInParentFrame(glm::angleAxis(glm::clamp(glm::angle(between), -MAX_ANGLE, MAX_ANGLE), glm::axis(between)) *
|
||||
state.setRotationInConstrainedFrame(glm::angleAxis(glm::clamp(glm::angle(between), -MAX_ANGLE, MAX_ANGLE), glm::axis(between)) *
|
||||
joint.rotation);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -108,15 +108,10 @@ void MyAvatar::reset() {
|
|||
}
|
||||
|
||||
void MyAvatar::update(float deltaTime) {
|
||||
PerformanceTimer perfTimer("MyAvatar::update/");
|
||||
Head* head = getHead();
|
||||
head->relaxLean(deltaTime);
|
||||
{
|
||||
PerformanceTimer perfTimer("MyAvatar::update/updateFromTrackers");
|
||||
updateFromTrackers(deltaTime);
|
||||
}
|
||||
updateFromTrackers(deltaTime);
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::MoveWithLean)) {
|
||||
PerformanceTimer perfTimer("MyAvatar::update/moveWithLean");
|
||||
// Faceshift drive is enabled, set the avatar drive based on the head position
|
||||
moveWithLean();
|
||||
}
|
||||
|
@ -127,19 +122,14 @@ void MyAvatar::update(float deltaTime) {
|
|||
head->setAudioAverageLoudness(audio->getAudioAverageInputLoudness());
|
||||
|
||||
if (_motionBehaviors & AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY) {
|
||||
PerformanceTimer perfTimer("MyAvatar::update/gravityWork");
|
||||
setGravity(Application::getInstance()->getEnvironment()->getGravity(getPosition()));
|
||||
}
|
||||
|
||||
{
|
||||
PerformanceTimer perfTimer("MyAvatar::update/simulate");
|
||||
simulate(deltaTime);
|
||||
}
|
||||
simulate(deltaTime);
|
||||
}
|
||||
|
||||
void MyAvatar::simulate(float deltaTime) {
|
||||
PerformanceTimer perfTimer("MyAvatar::simulate");
|
||||
|
||||
PerformanceTimer perfTimer("simulate");
|
||||
if (_scale != _targetScale) {
|
||||
float scale = (1.0f - SMOOTHING_RATIO) * _scale + SMOOTHING_RATIO * _targetScale;
|
||||
setScale(scale);
|
||||
|
@ -150,31 +140,28 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
_handState = HAND_STATE_NULL;
|
||||
|
||||
{
|
||||
PerformanceTimer perfTimer("MyAvatar::simulate/updateOrientation");
|
||||
PerformanceTimer perfTimer("transform");
|
||||
updateOrientation(deltaTime);
|
||||
}
|
||||
{
|
||||
PerformanceTimer perfTimer("MyAvatar::simulate/updatePosition");
|
||||
updatePosition(deltaTime);
|
||||
}
|
||||
|
||||
{
|
||||
PerformanceTimer perfTimer("MyAvatar::simulate/hand Collision,simulate");
|
||||
PerformanceTimer perfTimer("hand");
|
||||
// update avatar skeleton and simulate hand and head
|
||||
getHand()->simulate(deltaTime, true);
|
||||
}
|
||||
|
||||
{
|
||||
PerformanceTimer perfTimer("MyAvatar::simulate/_skeletonModel.simulate()");
|
||||
PerformanceTimer perfTimer("skeleton");
|
||||
_skeletonModel.simulate(deltaTime);
|
||||
}
|
||||
{
|
||||
PerformanceTimer perfTimer("MyAvatar::simulate/simulateAttachments");
|
||||
PerformanceTimer perfTimer("attachments");
|
||||
simulateAttachments(deltaTime);
|
||||
}
|
||||
|
||||
{
|
||||
PerformanceTimer perfTimer("MyAvatar::simulate/copy joints");
|
||||
PerformanceTimer perfTimer("joints");
|
||||
// copy out the skeleton joints from the model
|
||||
_jointData.resize(_skeletonModel.getJointStateCount());
|
||||
for (int i = 0; i < _jointData.size(); i++) {
|
||||
|
@ -184,7 +171,7 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
}
|
||||
|
||||
{
|
||||
PerformanceTimer perfTimer("MyAvatar::simulate/head Simulate");
|
||||
PerformanceTimer perfTimer("head");
|
||||
Head* head = getHead();
|
||||
glm::vec3 headPosition;
|
||||
if (!_skeletonModel.getHeadPosition(headPosition)) {
|
||||
|
@ -196,14 +183,17 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
}
|
||||
|
||||
{
|
||||
PerformanceTimer perfTimer("MyAvatar::simulate/hair Simulate");
|
||||
PerformanceTimer perfTimer("hair");
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) {
|
||||
simulateHair(deltaTime);
|
||||
foreach (Hair* hair, _hairs) {
|
||||
hair->simulate(deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
PerformanceTimer perfTimer("MyAvatar::simulate/ragdoll");
|
||||
PerformanceTimer perfTimer("ragdoll");
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::CollideAsRagdoll)) {
|
||||
const int minError = 0.01f;
|
||||
const float maxIterations = 10;
|
||||
|
@ -216,7 +206,7 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
|
||||
// now that we're done stepping the avatar forward in time, compute new collisions
|
||||
if (_collisionGroups != 0) {
|
||||
PerformanceTimer perfTimer("MyAvatar::simulate/_collisionGroups");
|
||||
PerformanceTimer perfTimer("collisions");
|
||||
Camera* myCamera = Application::getInstance()->getCamera();
|
||||
|
||||
float radius = getSkeletonHeight() * COLLISION_RADIUS_SCALE;
|
||||
|
@ -225,18 +215,18 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
radius *= COLLISION_RADIUS_SCALAR;
|
||||
}
|
||||
if (_collisionGroups & COLLISION_GROUP_ENVIRONMENT) {
|
||||
PerformanceTimer perfTimer("MyAvatar::simulate/updateCollisionWithEnvironment");
|
||||
PerformanceTimer perfTimer("environment");
|
||||
updateCollisionWithEnvironment(deltaTime, radius);
|
||||
}
|
||||
if (_collisionGroups & COLLISION_GROUP_VOXELS) {
|
||||
PerformanceTimer perfTimer("MyAvatar::simulate/updateCollisionWithVoxels");
|
||||
PerformanceTimer perfTimer("voxels");
|
||||
updateCollisionWithVoxels(deltaTime, radius);
|
||||
} else {
|
||||
_trapDuration = 0.0f;
|
||||
}
|
||||
/* TODO: Andrew to make this work
|
||||
if (_collisionGroups & COLLISION_GROUP_AVATARS) {
|
||||
PerformanceTimer perfTimer("MyAvatar::simulate/updateCollisionWithAvatars");
|
||||
PerformanceTimer perfTimer("avatars");
|
||||
updateCollisionWithAvatars(deltaTime);
|
||||
}
|
||||
*/
|
||||
|
@ -896,6 +886,9 @@ void MyAvatar::renderBody(RenderMode renderMode, float glowLevel) {
|
|||
getHead()->render(1.0f, modelRenderMode);
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) {
|
||||
renderHair();
|
||||
foreach (Hair* hair, _hairs) {
|
||||
hair->render();
|
||||
}
|
||||
}
|
||||
}
|
||||
getHand()->render(true, modelRenderMode);
|
||||
|
@ -910,7 +903,6 @@ bool MyAvatar::shouldRenderHead(const glm::vec3& cameraPosition, RenderMode rend
|
|||
}
|
||||
|
||||
float MyAvatar::computeDistanceToFloor(const glm::vec3& startPoint) {
|
||||
PerformanceTimer perfTimer("MyAvatar::computeDistanceToFloor()");
|
||||
glm::vec3 direction = -_worldUpDirection;
|
||||
OctreeElement* elementHit; // output from findRayIntersection
|
||||
float distance = FLT_MAX; // output from findRayIntersection
|
||||
|
@ -976,7 +968,6 @@ void MyAvatar::updateOrientation(float deltaTime) {
|
|||
const float NEARBY_FLOOR_THRESHOLD = 5.0f;
|
||||
|
||||
void MyAvatar::updatePosition(float deltaTime) {
|
||||
PerformanceTimer perfTimer("MyAvatar::updatePosition");
|
||||
float keyboardInput = fabsf(_driveKeys[FWD] - _driveKeys[BACK]) +
|
||||
fabsf(_driveKeys[RIGHT] - _driveKeys[LEFT]) +
|
||||
fabsf(_driveKeys[UP] - _driveKeys[DOWN]);
|
||||
|
|
|
@ -219,7 +219,7 @@ void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) {
|
|||
JointState& parentState = _jointStates[parentJointIndex];
|
||||
parentState.setRotationFromBindFrame(palmRotation, PALM_PRIORITY);
|
||||
// lock hand to forearm by slamming its rotation (in parent-frame) to identity
|
||||
_jointStates[jointIndex].setRotationInParentFrame(glm::quat());
|
||||
_jointStates[jointIndex].setRotationInConstrainedFrame(glm::quat());
|
||||
} else {
|
||||
inverseKinematics(jointIndex, palmPosition, palmRotation, PALM_PRIORITY);
|
||||
}
|
||||
|
@ -255,9 +255,9 @@ void SkeletonModel::maybeUpdateLeanRotation(const JointState& parentState, const
|
|||
}
|
||||
// get the rotation axes in joint space and use them to adjust the rotation
|
||||
glm::mat3 axes = glm::mat3_cast(glm::quat());
|
||||
glm::mat3 inverse = glm::mat3(glm::inverse(parentState.getTransform() * glm::translate(state.getDefaultTranslationInParentFrame()) *
|
||||
glm::mat3 inverse = glm::mat3(glm::inverse(parentState.getTransform() * glm::translate(state.getDefaultTranslationInConstrainedFrame()) *
|
||||
joint.preTransform * glm::mat4_cast(joint.preRotation * joint.rotation)));
|
||||
state.setRotationInParentFrame(glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanSideways(),
|
||||
state.setRotationInConstrainedFrame(glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanSideways(),
|
||||
glm::normalize(inverse * axes[2])) * glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanForward(),
|
||||
glm::normalize(inverse * axes[0])) * joint.rotation);
|
||||
}
|
||||
|
|
|
@ -109,6 +109,7 @@ public:
|
|||
void resetShapePositionsToDefaultPose(); // DEBUG method
|
||||
|
||||
void renderRagdoll();
|
||||
|
||||
protected:
|
||||
|
||||
// virtual overrrides from Ragdoll
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include <QTimer>
|
||||
|
||||
#include <PerfStat.h>
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include "Application.h"
|
||||
|
@ -75,6 +76,7 @@ void Faceshift::update() {
|
|||
if (!isActive()) {
|
||||
return;
|
||||
}
|
||||
PerformanceTimer perfTimer("faceshift");
|
||||
// get the euler angles relative to the window
|
||||
glm::vec3 eulers = glm::degrees(safeEulerAngles(_headRotation * glm::quat(glm::radians(glm::vec3(
|
||||
(_eyeGazeLeftPitch + _eyeGazeRightPitch) / 2.0f, (_eyeGazeLeftYaw + _eyeGazeRightYaw) / 2.0f, 0.0f)))));
|
||||
|
|
|
@ -12,9 +12,10 @@
|
|||
#include <limits>
|
||||
|
||||
#include <QtDebug>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <PerfStat.h>
|
||||
|
||||
#include "JoystickManager.h"
|
||||
|
||||
using namespace std;
|
||||
|
@ -46,6 +47,7 @@ JoystickManager::~JoystickManager() {
|
|||
|
||||
void JoystickManager::update() {
|
||||
#ifdef HAVE_SDL
|
||||
PerformanceTimer perfTimer("joystick");
|
||||
SDL_JoystickUpdate();
|
||||
|
||||
for (int i = 0; i < _joystickStates.size(); i++) {
|
||||
|
|
|
@ -13,14 +13,13 @@
|
|||
|
||||
#include "MIDIManager.h"
|
||||
|
||||
#ifdef HAVE_RTMIDI
|
||||
|
||||
MIDIManager& MIDIManager::getInstance() {
|
||||
static MIDIManager sharedInstance;
|
||||
return sharedInstance;
|
||||
}
|
||||
|
||||
void MIDIManager::midiCallback(double deltaTime, std::vector<unsigned char>* message, void* userData) {
|
||||
#ifdef HAVE_RTMIDI
|
||||
|
||||
MIDIEvent callbackEvent;
|
||||
callbackEvent.deltaTime = deltaTime;
|
||||
|
@ -36,15 +35,19 @@ void MIDIManager::midiCallback(double deltaTime, std::vector<unsigned char>* mes
|
|||
}
|
||||
|
||||
emit getInstance().midiEvent(callbackEvent);
|
||||
#endif
|
||||
}
|
||||
|
||||
MIDIManager::~MIDIManager() {
|
||||
#ifdef HAVE_RTMIDI
|
||||
delete _midiInput;
|
||||
#endif
|
||||
}
|
||||
|
||||
const int DEFAULT_MIDI_PORT = 0;
|
||||
|
||||
void MIDIManager::openDefaultPort() {
|
||||
#ifdef HAVE_RTMIDI
|
||||
if (!_midiInput) {
|
||||
_midiInput = new RtMidiIn();
|
||||
|
||||
|
@ -63,6 +66,5 @@ void MIDIManager::openDefaultPort() {
|
|||
_midiInput = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
}
|
|
@ -12,14 +12,14 @@
|
|||
#ifndef hifi_MIDIManager_h
|
||||
#define hifi_MIDIManager_h
|
||||
|
||||
#ifdef HAVE_RTMIDI
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtScript/QScriptEngine>
|
||||
|
||||
#include <MIDIEvent.h>
|
||||
|
||||
#ifdef HAVE_RTMIDI
|
||||
#include <RtMidi.h>
|
||||
#endif
|
||||
|
||||
class MIDIManager : public QObject {
|
||||
Q_OBJECT
|
||||
|
@ -36,7 +36,9 @@ public:
|
|||
~MIDIManager();
|
||||
|
||||
void openDefaultPort();
|
||||
#ifdef HAVE_RTMIDI
|
||||
bool hasDevice() const { return !!_midiInput; }
|
||||
#endif
|
||||
public slots:
|
||||
unsigned int NoteOn() const { return 144; }
|
||||
unsigned int NoteOff() const { return 128; }
|
||||
|
@ -46,10 +48,10 @@ signals:
|
|||
void midiEvent(const MIDIEvent& event);
|
||||
|
||||
private:
|
||||
#ifdef HAVE_RTMIDI
|
||||
RtMidiIn* _midiInput;
|
||||
};
|
||||
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
#endif // hifi_MIDIManager_h
|
||||
|
|
|
@ -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::DisplayOculusOverlays);
|
||||
|
||||
|
||||
//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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <QtDebug>
|
||||
|
||||
#include <FBXReader.h>
|
||||
#include <PerfStat.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "PrioVR.h"
|
||||
|
@ -166,6 +167,7 @@ void PrioVR::update(float deltaTime) {
|
|||
if (!_skeletalDevice) {
|
||||
return;
|
||||
}
|
||||
PerformanceTimer perfTimer("PrioVR");
|
||||
unsigned int timestamp;
|
||||
yei_getLastStreamDataAll(_skeletalDevice, (char*)_jointRotations.data(),
|
||||
_jointRotations.size() * sizeof(glm::quat), ×tamp);
|
||||
|
|
|
@ -11,8 +11,11 @@
|
|||
|
||||
#include <vector>
|
||||
|
||||
#include <PerfStat.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "SixenseManager.h"
|
||||
#include "devices/OculusManager.h"
|
||||
#include "UserActivityLogger.h"
|
||||
|
||||
#ifdef HAVE_SIXENSE
|
||||
|
@ -32,6 +35,7 @@ SixenseManager::SixenseManager() {
|
|||
#ifdef HAVE_SIXENSE
|
||||
_lastMovement = 0;
|
||||
_amountMoved = glm::vec3(0.0f);
|
||||
_lowVelocityFilter = false;
|
||||
|
||||
_calibrationState = CALIBRATION_STATE_IDLE;
|
||||
// By default we assume the _neckBase (in orb frame) is as high above the orb
|
||||
|
@ -60,10 +64,8 @@ SixenseManager::~SixenseManager() {
|
|||
void SixenseManager::setFilter(bool filter) {
|
||||
#ifdef HAVE_SIXENSE
|
||||
if (filter) {
|
||||
qDebug("Sixense Filter ON");
|
||||
sixenseSetFilterEnabled(1);
|
||||
} else {
|
||||
qDebug("Sixense Filter OFF");
|
||||
sixenseSetFilterEnabled(0);
|
||||
}
|
||||
#endif
|
||||
|
@ -84,7 +86,10 @@ void SixenseManager::update(float deltaTime) {
|
|||
if (sixenseGetNumActiveControllers() == 0) {
|
||||
_hydrasConnected = false;
|
||||
return;
|
||||
} else if (!_hydrasConnected) {
|
||||
}
|
||||
|
||||
PerformanceTimer perfTimer("sixense");
|
||||
if (!_hydrasConnected) {
|
||||
_hydrasConnected = true;
|
||||
UserActivityLogger::getInstance().connectedDevice("spatial_controller", "hydra");
|
||||
}
|
||||
|
@ -160,17 +165,23 @@ void SixenseManager::update(float deltaTime) {
|
|||
}
|
||||
palm->setRawVelocity(rawVelocity); // meters/sec
|
||||
|
||||
// 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));
|
||||
|
||||
// adjustment for hydra controllers fit into hands
|
||||
float sign = (i == 0) ? -1.0f : 1.0f;
|
||||
rotation *= glm::angleAxis(sign * PI/4.0f, glm::vec3(0.0f, 0.0f, 1.0f));
|
||||
|
||||
palm->setRawRotation(safeMix(palm->getRawRotation(), rotation, 1.0f - velocityFilter));
|
||||
|
||||
if (_lowVelocityFilter) {
|
||||
// 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);
|
||||
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);
|
||||
}
|
||||
|
||||
// use the velocity to determine whether there's any movement (if the hand isn't new)
|
||||
const float MOVEMENT_DISTANCE_THRESHOLD = 0.003f;
|
||||
_amountMoved += rawVelocity * deltaTime;
|
||||
|
@ -358,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;
|
||||
|
||||
|
@ -372,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);
|
||||
|
||||
|
|
|
@ -47,6 +47,7 @@ public:
|
|||
public slots:
|
||||
|
||||
void setFilter(bool filter);
|
||||
void setLowVelocityFilter(bool lowVelocityFilter) { _lowVelocityFilter = lowVelocityFilter; };
|
||||
|
||||
private:
|
||||
#ifdef HAVE_SIXENSE
|
||||
|
@ -80,6 +81,8 @@ private:
|
|||
bool _bumperPressed[2];
|
||||
int _oldX[2];
|
||||
int _oldY[2];
|
||||
|
||||
bool _lowVelocityFilter;
|
||||
};
|
||||
|
||||
#endif // hifi_SixenseManager_h
|
||||
|
|
|
@ -100,6 +100,7 @@ void TV3DManager::display(Camera& whichCamera) {
|
|||
// We only need to render the overlays to a texture once, then we just render the texture as a quad
|
||||
// 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);
|
||||
|
||||
if (glowEnabled) {
|
||||
Application::getInstance()->getGlowEffect()->prepare();
|
||||
|
@ -128,7 +129,9 @@ void TV3DManager::display(Camera& whichCamera) {
|
|||
glLoadIdentity();
|
||||
Application::getInstance()->displaySide(whichCamera);
|
||||
|
||||
applicationOverlay.displayOverlayTexture3DTV(whichCamera, _aspect, fov);
|
||||
if (displayOverlays) {
|
||||
applicationOverlay.displayOverlayTexture3DTV(whichCamera, _aspect, fov);
|
||||
}
|
||||
}
|
||||
glPopMatrix();
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
|
@ -154,7 +157,9 @@ void TV3DManager::display(Camera& whichCamera) {
|
|||
glLoadIdentity();
|
||||
Application::getInstance()->displaySide(whichCamera);
|
||||
|
||||
applicationOverlay.displayOverlayTexture3DTV(whichCamera, _aspect, fov);
|
||||
if (displayOverlays) {
|
||||
applicationOverlay.displayOverlayTexture3DTV(whichCamera, _aspect, fov);
|
||||
}
|
||||
}
|
||||
glPopMatrix();
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include <QHash>
|
||||
|
||||
#include <PerfStat.h>
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include <FBXReader.h>
|
||||
|
@ -128,6 +129,7 @@ void Visage::update() {
|
|||
if (!_active) {
|
||||
return;
|
||||
}
|
||||
PerformanceTimer perfTimer("visage");
|
||||
_headRotation = glm::quat(glm::vec3(-_data->faceRotation[0], -_data->faceRotation[1], _data->faceRotation[2]));
|
||||
_headTranslation = (glm::vec3(_data->faceTranslation[0], _data->faceTranslation[1], _data->faceTranslation[2]) -
|
||||
_headOrigin) * TRANSLATION_SCALE;
|
||||
|
|
|
@ -121,7 +121,7 @@ static void maybeRelease(QOpenGLFramebufferObject* fbo) {
|
|||
}
|
||||
|
||||
QOpenGLFramebufferObject* GlowEffect::render(bool toTexture) {
|
||||
PerformanceTimer perfTimer("paintGL/glowEffect");
|
||||
PerformanceTimer perfTimer("glowEffect");
|
||||
|
||||
QOpenGLFramebufferObject* primaryFBO = Application::getInstance()->getTextureCache()->getPrimaryFramebufferObject();
|
||||
primaryFBO->release();
|
||||
|
|
|
@ -26,7 +26,7 @@ JointState::JointState() :
|
|||
JointState::JointState(const JointState& other) : _constraint(NULL) {
|
||||
_transform = other._transform;
|
||||
_rotation = other._rotation;
|
||||
_rotationInParentFrame = other._rotationInParentFrame;
|
||||
_rotationInConstrainedFrame = other._rotationInConstrainedFrame;
|
||||
_animationPriority = other._animationPriority;
|
||||
_fbxJoint = other._fbxJoint;
|
||||
// DO NOT copy _constraint
|
||||
|
@ -43,7 +43,7 @@ JointState::~JointState() {
|
|||
|
||||
void JointState::setFBXJoint(const FBXJoint* joint) {
|
||||
assert(joint != NULL);
|
||||
_rotationInParentFrame = joint->rotation;
|
||||
_rotationInConstrainedFrame = joint->rotation;
|
||||
// NOTE: JointState does not own the FBXJoint to which it points.
|
||||
_fbxJoint = joint;
|
||||
if (_constraint) {
|
||||
|
@ -68,24 +68,24 @@ void JointState::copyState(const JointState& state) {
|
|||
_animationPriority = state._animationPriority;
|
||||
_transform = state._transform;
|
||||
_rotation = extractRotation(_transform);
|
||||
_rotationInParentFrame = state._rotationInParentFrame;
|
||||
_rotationInConstrainedFrame = state._rotationInConstrainedFrame;
|
||||
|
||||
_visibleTransform = state._visibleTransform;
|
||||
_visibleRotation = extractRotation(_visibleTransform);
|
||||
_visibleRotationInParentFrame = state._visibleRotationInParentFrame;
|
||||
_visibleRotationInConstrainedFrame = state._visibleRotationInConstrainedFrame;
|
||||
// DO NOT copy _fbxJoint or _constraint
|
||||
}
|
||||
|
||||
void JointState::computeTransform(const glm::mat4& parentTransform) {
|
||||
glm::quat modifiedRotation = _fbxJoint->preRotation * _rotationInParentFrame * _fbxJoint->postRotation;
|
||||
glm::mat4 modifiedTransform = _fbxJoint->preTransform * glm::mat4_cast(modifiedRotation) * _fbxJoint->postTransform;
|
||||
glm::quat rotationInConstrainedFrame = _fbxJoint->preRotation * _rotationInConstrainedFrame * _fbxJoint->postRotation;
|
||||
glm::mat4 modifiedTransform = _fbxJoint->preTransform * glm::mat4_cast(rotationInConstrainedFrame) * _fbxJoint->postTransform;
|
||||
_transform = parentTransform * glm::translate(_fbxJoint->translation) * modifiedTransform;
|
||||
_rotation = extractRotation(_transform);
|
||||
}
|
||||
|
||||
void JointState::computeVisibleTransform(const glm::mat4& parentTransform) {
|
||||
glm::quat modifiedRotation = _fbxJoint->preRotation * _visibleRotationInParentFrame * _fbxJoint->postRotation;
|
||||
glm::mat4 modifiedTransform = _fbxJoint->preTransform * glm::mat4_cast(modifiedRotation) * _fbxJoint->postTransform;
|
||||
glm::quat rotationInConstrainedFrame = _fbxJoint->preRotation * _visibleRotationInConstrainedFrame * _fbxJoint->postRotation;
|
||||
glm::mat4 modifiedTransform = _fbxJoint->preTransform * glm::mat4_cast(rotationInConstrainedFrame) * _fbxJoint->postTransform;
|
||||
_visibleTransform = parentTransform * glm::translate(_fbxJoint->translation) * modifiedTransform;
|
||||
_visibleRotation = extractRotation(_visibleTransform);
|
||||
}
|
||||
|
@ -97,7 +97,7 @@ glm::quat JointState::getRotationFromBindToModelFrame() const {
|
|||
void JointState::restoreRotation(float fraction, float priority) {
|
||||
assert(_fbxJoint != NULL);
|
||||
if (priority == _animationPriority || _animationPriority == 0.0f) {
|
||||
setRotationInParentFrame(safeMix(_rotationInParentFrame, _fbxJoint->rotation, fraction));
|
||||
setRotationInConstrainedFrame(safeMix(_rotationInConstrainedFrame, _fbxJoint->rotation, fraction));
|
||||
_animationPriority = 0.0f;
|
||||
}
|
||||
}
|
||||
|
@ -106,11 +106,11 @@ void JointState::setRotationFromBindFrame(const glm::quat& rotation, float prior
|
|||
// rotation is from bind- to model-frame
|
||||
assert(_fbxJoint != NULL);
|
||||
if (priority >= _animationPriority) {
|
||||
glm::quat targetRotation = _rotationInParentFrame * glm::inverse(_rotation) * rotation * glm::inverse(_fbxJoint->inverseBindRotation);
|
||||
glm::quat targetRotation = _rotationInConstrainedFrame * glm::inverse(_rotation) * rotation * glm::inverse(_fbxJoint->inverseBindRotation);
|
||||
if (constrain && _constraint) {
|
||||
_constraint->softClamp(targetRotation, _rotationInParentFrame, 0.5f);
|
||||
_constraint->softClamp(targetRotation, _rotationInConstrainedFrame, 0.5f);
|
||||
}
|
||||
setRotationInParentFrame(targetRotation);
|
||||
setRotationInConstrainedFrame(targetRotation);
|
||||
_animationPriority = priority;
|
||||
}
|
||||
}
|
||||
|
@ -137,12 +137,12 @@ void JointState::applyRotationDelta(const glm::quat& delta, bool constrain, floa
|
|||
_animationPriority = priority;
|
||||
if (!constrain || _constraint == NULL) {
|
||||
// no constraints
|
||||
_rotationInParentFrame = _rotationInParentFrame * glm::inverse(_rotation) * delta * _rotation;
|
||||
_rotationInConstrainedFrame = _rotationInConstrainedFrame * glm::inverse(_rotation) * delta * _rotation;
|
||||
_rotation = delta * _rotation;
|
||||
return;
|
||||
}
|
||||
glm::quat targetRotation = _rotationInParentFrame * glm::inverse(_rotation) * delta * _rotation;
|
||||
setRotationInParentFrame(targetRotation);
|
||||
glm::quat targetRotation = _rotationInConstrainedFrame * glm::inverse(_rotation) * delta * _rotation;
|
||||
setRotationInConstrainedFrame(targetRotation);
|
||||
}
|
||||
|
||||
/// Applies delta rotation to joint but mixes a little bit of the default pose as well.
|
||||
|
@ -154,30 +154,30 @@ void JointState::mixRotationDelta(const glm::quat& delta, float mixFactor, float
|
|||
return;
|
||||
}
|
||||
_animationPriority = priority;
|
||||
glm::quat targetRotation = _rotationInParentFrame * glm::inverse(_rotation) * delta * _rotation;
|
||||
glm::quat targetRotation = _rotationInConstrainedFrame * glm::inverse(_rotation) * delta * _rotation;
|
||||
if (mixFactor > 0.0f && mixFactor <= 1.0f) {
|
||||
targetRotation = safeMix(targetRotation, _fbxJoint->rotation, mixFactor);
|
||||
}
|
||||
if (_constraint) {
|
||||
_constraint->softClamp(targetRotation, _rotationInParentFrame, 0.5f);
|
||||
_constraint->softClamp(targetRotation, _rotationInConstrainedFrame, 0.5f);
|
||||
}
|
||||
setRotationInParentFrame(targetRotation);
|
||||
setRotationInConstrainedFrame(targetRotation);
|
||||
}
|
||||
|
||||
glm::quat JointState::computeParentRotation() const {
|
||||
// R = Rp * Rpre * r * Rpost
|
||||
// Rp = R * (Rpre * r * Rpost)^
|
||||
return _rotation * glm::inverse(_fbxJoint->preRotation * _rotationInParentFrame * _fbxJoint->postRotation);
|
||||
return _rotation * glm::inverse(_fbxJoint->preRotation * _rotationInConstrainedFrame * _fbxJoint->postRotation);
|
||||
}
|
||||
|
||||
void JointState::setRotationInParentFrame(const glm::quat& targetRotation) {
|
||||
void JointState::setRotationInConstrainedFrame(const glm::quat& targetRotation) {
|
||||
glm::quat parentRotation = computeParentRotation();
|
||||
_rotationInParentFrame = targetRotation;
|
||||
_rotationInConstrainedFrame = targetRotation;
|
||||
// R' = Rp * Rpre * r' * Rpost
|
||||
_rotation = parentRotation * _fbxJoint->preRotation * _rotationInParentFrame * _fbxJoint->postRotation;
|
||||
_rotation = parentRotation * _fbxJoint->preRotation * _rotationInConstrainedFrame * _fbxJoint->postRotation;
|
||||
}
|
||||
|
||||
const glm::vec3& JointState::getDefaultTranslationInParentFrame() const {
|
||||
const glm::vec3& JointState::getDefaultTranslationInConstrainedFrame() const {
|
||||
assert(_fbxJoint != NULL);
|
||||
return _fbxJoint->translation;
|
||||
}
|
||||
|
@ -185,5 +185,5 @@ const glm::vec3& JointState::getDefaultTranslationInParentFrame() const {
|
|||
void JointState::slaveVisibleTransform() {
|
||||
_visibleTransform = _transform;
|
||||
_visibleRotation = _rotation;
|
||||
_visibleRotationInParentFrame = _rotationInParentFrame;
|
||||
_visibleRotationInConstrainedFrame = _rotationInConstrainedFrame;
|
||||
}
|
||||
|
|
|
@ -66,14 +66,14 @@ public:
|
|||
void restoreRotation(float fraction, float priority);
|
||||
|
||||
/// \param rotation is from bind- to model-frame
|
||||
/// computes and sets new _rotationInParentFrame
|
||||
/// computes and sets new _rotationInConstrainedFrame
|
||||
/// NOTE: the JointState's model-frame transform/rotation are NOT updated!
|
||||
void setRotationFromBindFrame(const glm::quat& rotation, float priority, bool constrain = false);
|
||||
|
||||
void setRotationInParentFrame(const glm::quat& targetRotation);
|
||||
const glm::quat& getRotationInParentFrame() const { return _rotationInParentFrame; }
|
||||
void setRotationInConstrainedFrame(const glm::quat& targetRotation);
|
||||
const glm::quat& getRotationInConstrainedFrame() const { return _rotationInConstrainedFrame; }
|
||||
|
||||
const glm::vec3& getDefaultTranslationInParentFrame() const;
|
||||
const glm::vec3& getDefaultTranslationInConstrainedFrame() const;
|
||||
|
||||
|
||||
void clearTransformTranslation();
|
||||
|
@ -92,11 +92,11 @@ private:
|
|||
|
||||
glm::mat4 _transform; // joint- to model-frame
|
||||
glm::quat _rotation; // joint- to model-frame
|
||||
glm::quat _rotationInParentFrame; // joint- to parentJoint-frame
|
||||
glm::quat _rotationInConstrainedFrame; // rotation in frame where angular constraints would be applied
|
||||
|
||||
glm::mat4 _visibleTransform;
|
||||
glm::quat _visibleRotation;
|
||||
glm::quat _visibleRotationInParentFrame;
|
||||
glm::quat _visibleRotationInConstrainedFrame;
|
||||
|
||||
const FBXJoint* _fbxJoint; // JointState does NOT own its FBXJoint
|
||||
AngularConstraint* _constraint; // JointState owns its AngularConstraint
|
||||
|
|
|
@ -460,7 +460,7 @@ void Model::reset() {
|
|||
}
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
for (int i = 0; i < _jointStates.size(); i++) {
|
||||
_jointStates[i].setRotationInParentFrame(geometry.joints.at(i).rotation);
|
||||
_jointStates[i].setRotationInConstrainedFrame(geometry.joints.at(i).rotation);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -688,7 +688,7 @@ bool Model::getJointState(int index, glm::quat& rotation) const {
|
|||
if (index == -1 || index >= _jointStates.size()) {
|
||||
return false;
|
||||
}
|
||||
rotation = _jointStates.at(index).getRotationInParentFrame();
|
||||
rotation = _jointStates.at(index).getRotationInConstrainedFrame();
|
||||
const glm::quat& defaultRotation = _geometry->getFBXGeometry().joints.at(index).rotation;
|
||||
return glm::abs(rotation.x - defaultRotation.x) >= EPSILON ||
|
||||
glm::abs(rotation.y - defaultRotation.y) >= EPSILON ||
|
||||
|
@ -701,7 +701,7 @@ void Model::setJointState(int index, bool valid, const glm::quat& rotation, floa
|
|||
JointState& state = _jointStates[index];
|
||||
if (priority >= state._animationPriority) {
|
||||
if (valid) {
|
||||
state.setRotationInParentFrame(rotation);
|
||||
state.setRotationInConstrainedFrame(rotation);
|
||||
state._animationPriority = priority;
|
||||
} else {
|
||||
state.restoreRotation(1.0f, priority);
|
||||
|
@ -1488,14 +1488,19 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent, bool re
|
|||
if (cascadedShadows) {
|
||||
program->setUniform(skinLocations->shadowDistances, Application::getInstance()->getShadowDistances());
|
||||
}
|
||||
} else {
|
||||
|
||||
// local light uniforms
|
||||
skinProgram->setUniformValue("numLocalLights", _numLocalLights);
|
||||
skinProgram->setUniformArray("localLightDirections", _localLightDirections, MAX_LOCAL_LIGHTS);
|
||||
skinProgram->setUniformArray("localLightColors", _localLightColors, MAX_LOCAL_LIGHTS);
|
||||
} else {
|
||||
glMultMatrixf((const GLfloat*)&state.clusterMatrices[0]);
|
||||
program->bind();
|
||||
if (cascadedShadows) {
|
||||
program->setUniform(shadowDistancesLocation, Application::getInstance()->getShadowDistances());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (mesh.blendshapes.isEmpty()) {
|
||||
if (!(mesh.tangents.isEmpty() || mode == SHADOW_RENDER_MODE)) {
|
||||
activeProgram->setAttributeBuffer(tangentLocation, GL_FLOAT, vertexCount * 2 * sizeof(glm::vec3), 3);
|
||||
|
@ -1622,6 +1627,20 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent, bool re
|
|||
}
|
||||
}
|
||||
|
||||
void Model::setLocalLightDirection(const glm::vec3& direction, int lightIndex) {
|
||||
assert(lightIndex >= 0 && lightIndex < MAX_LOCAL_LIGHTS);
|
||||
_localLightDirections[lightIndex] = direction;
|
||||
}
|
||||
|
||||
void Model::setLocalLightColor(const glm::vec3& color, int lightIndex) {
|
||||
assert(lightIndex >= 0 && lightIndex < MAX_LOCAL_LIGHTS);
|
||||
_localLightColors[lightIndex] = color;
|
||||
}
|
||||
|
||||
void Model::setNumLocalLights(int numLocalLights) {
|
||||
_numLocalLights = numLocalLights;
|
||||
}
|
||||
|
||||
void AnimationHandle::setURL(const QUrl& url) {
|
||||
if (_url != url) {
|
||||
_animation = Application::getInstance()->getAnimationCache()->getAnimation(_url = url);
|
||||
|
@ -1768,7 +1787,7 @@ void AnimationHandle::applyFrame(float frameIndex) {
|
|||
if (mapping != -1) {
|
||||
JointState& state = _model->_jointStates[mapping];
|
||||
if (_priority >= state._animationPriority) {
|
||||
state.setRotationInParentFrame(safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction));
|
||||
state.setRotationInConstrainedFrame(safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction));
|
||||
state._animationPriority = _priority;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,8 @@ class Shape;
|
|||
typedef QSharedPointer<AnimationHandle> AnimationHandlePointer;
|
||||
typedef QWeakPointer<AnimationHandle> WeakAnimationHandlePointer;
|
||||
|
||||
const int MAX_LOCAL_LIGHTS = 2;
|
||||
|
||||
/// A generic 3D model displaying geometry loaded from a URL.
|
||||
class Model : public QObject, public PhysicsEntity {
|
||||
Q_OBJECT
|
||||
|
@ -143,6 +145,10 @@ public:
|
|||
/// Sets blended vertices computed in a separate thread.
|
||||
void setBlendedVertices(const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals);
|
||||
|
||||
void setLocalLightDirection(const glm::vec3& direction, int lightIndex);
|
||||
void setLocalLightColor(const glm::vec3& color, int lightIndex);
|
||||
void setNumLocalLights(int numLocalLights);
|
||||
|
||||
protected:
|
||||
QSharedPointer<NetworkGeometry> _geometry;
|
||||
|
||||
|
@ -158,6 +164,10 @@ protected:
|
|||
bool _showTrueJointTransforms;
|
||||
int _rootIndex;
|
||||
|
||||
glm::vec3 _localLightDirections[MAX_LOCAL_LIGHTS];
|
||||
glm::vec3 _localLightColors[MAX_LOCAL_LIGHTS];
|
||||
int _numLocalLights;
|
||||
|
||||
QVector<JointState> _jointStates;
|
||||
|
||||
class MeshState {
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
//
|
||||
|
||||
#include "ProgramObject.h"
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
ProgramObject::ProgramObject(QObject* parent) : QGLShaderProgram(parent) {
|
||||
}
|
||||
|
@ -22,3 +23,17 @@ void ProgramObject::setUniform(const char* name, const glm::vec3& value) {
|
|||
setUniformValue(name, value.x, value.y, value.z);
|
||||
}
|
||||
|
||||
void ProgramObject::setUniformArray(const char* name, const glm::vec3* values, int count) {
|
||||
GLfloat* floatVal = new GLfloat[count*3];
|
||||
int index = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
assert(index < count*3);
|
||||
const float* valPtr = glm::value_ptr(values[i]);
|
||||
floatVal[index++] = valPtr[0];
|
||||
floatVal[index++] = valPtr[1];
|
||||
floatVal[index++] = valPtr[2];
|
||||
}
|
||||
|
||||
setUniformValueArray(name, floatVal, count, 3);
|
||||
delete[] floatVal;
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ public:
|
|||
|
||||
void setUniform(int location, const glm::vec3& value);
|
||||
void setUniform(const char* name, const glm::vec3& value);
|
||||
void setUniformArray(const char* name, const glm::vec3* values, int count);
|
||||
};
|
||||
|
||||
#endif // hifi_ProgramObject_h
|
||||
|
|
|
@ -40,7 +40,7 @@ ApplicationOverlay::ApplicationOverlay() :
|
|||
_framebufferObject(NULL),
|
||||
_textureFov(DEFAULT_OCULUS_UI_ANGULAR_SIZE * RADIANS_PER_DEGREE),
|
||||
_alpha(1.0f),
|
||||
_active(true),
|
||||
_oculusuiRadius(1.0f),
|
||||
_crosshairTexture(0) {
|
||||
|
||||
memset(_reticleActive, 0, sizeof(_reticleActive));
|
||||
|
@ -70,8 +70,8 @@ void ApplicationOverlay::renderOverlay(bool renderToTexture) {
|
|||
QGLWidget* glWidget = application->getGLWidget();
|
||||
MyAvatar* myAvatar = application->getAvatar();
|
||||
|
||||
//Handle fadeing and deactivation/activation of UI
|
||||
if (_active) {
|
||||
//Handle fading and deactivation/activation of UI
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::UserInterface)) {
|
||||
_alpha += FADE_SPEED;
|
||||
if (_alpha > 1.0f) {
|
||||
_alpha = 1.0f;
|
||||
|
@ -165,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;
|
||||
|
@ -177,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
|
||||
|
@ -187,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++) {
|
||||
|
@ -205,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) {
|
||||
|
||||
|
@ -215,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]) {
|
||||
|
@ -269,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);
|
||||
|
@ -276,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);
|
||||
|
@ -334,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);
|
||||
|
@ -406,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;
|
||||
|
@ -415,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;
|
||||
|
||||
|
@ -485,7 +600,8 @@ void ApplicationOverlay::renderControllerPointers() {
|
|||
if (palmData->getTrigger() == 1.0f) {
|
||||
if (!triggerPressed[index]) {
|
||||
if (bumperPressed[index]) {
|
||||
_active = !_active;
|
||||
Menu::getInstance()->setIsOptionChecked(MenuOption::UserInterface,
|
||||
!Menu::getInstance()->isOptionChecked(MenuOption::UserInterface));
|
||||
}
|
||||
triggerPressed[index] = true;
|
||||
}
|
||||
|
@ -495,20 +611,40 @@ void ApplicationOverlay::renderControllerPointers() {
|
|||
if ((controllerButtons & BUTTON_FWD)) {
|
||||
if (!bumperPressed[index]) {
|
||||
if (triggerPressed[index]) {
|
||||
_active = !_active;
|
||||
Menu::getInstance()->setIsOptionChecked(MenuOption::UserInterface,
|
||||
!Menu::getInstance()->isOptionChecked(MenuOption::UserInterface));
|
||||
}
|
||||
bumperPressed[index] = true;
|
||||
}
|
||||
} 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
|
||||
|
@ -523,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;
|
||||
|
||||
|
@ -560,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();
|
||||
|
@ -571,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;
|
||||
|
@ -613,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);
|
||||
}
|
||||
|
||||
|
@ -662,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 {
|
||||
|
@ -681,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 {
|
||||
|
@ -689,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 {
|
||||
|
@ -697,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 {
|
||||
|
@ -987,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,8 +1214,12 @@ void ApplicationOverlay::renderTexturedHemisphere() {
|
|||
}
|
||||
|
||||
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);
|
||||
|
|
|
@ -32,6 +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;
|
||||
QPoint getOculusPalmClickLocation(const PalmData *palm) const;
|
||||
bool calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction, glm::vec3& result) const;
|
||||
|
||||
|
||||
// Getters
|
||||
QOpenGLFramebufferObject* getFramebufferObject();
|
||||
|
@ -48,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();
|
||||
|
@ -68,7 +71,7 @@ private:
|
|||
float _magSizeMult[NUMBER_OF_MAGNIFIERS];
|
||||
|
||||
float _alpha;
|
||||
bool _active;
|
||||
float _oculusuiRadius;
|
||||
|
||||
GLuint _crosshairTexture;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
@ -601,6 +683,8 @@ void Stats::display(
|
|||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color);
|
||||
}
|
||||
|
||||
PerformanceTimer::tallyAllTimerRecords();
|
||||
|
||||
// TODO: the display of these timing details should all be moved to JavaScript
|
||||
if (_expanded && Menu::getInstance()->isOptionChecked(MenuOption::DisplayTimingDetails)) {
|
||||
// Timing details...
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -35,6 +35,10 @@ void ModelOverlay::update(float deltatime) {
|
|||
}
|
||||
|
||||
void ModelOverlay::render() {
|
||||
if (!_visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_model.isActive()) {
|
||||
|
||||
if (_model.isRenderable()) {
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
Overlay::Overlay() :
|
||||
_parent(NULL),
|
||||
_alpha(DEFAULT_ALPHA),
|
||||
_color(DEFAULT_BACKGROUND_COLOR),
|
||||
_color(DEFAULT_OVERLAY_COLOR),
|
||||
_visible(true),
|
||||
_anchor(NO_ANCHOR)
|
||||
{
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
#include <SharedUtil.h> // for xColor
|
||||
|
||||
const xColor DEFAULT_BACKGROUND_COLOR = { 255, 255, 255 };
|
||||
const xColor DEFAULT_OVERLAY_COLOR = { 255, 255, 255 };
|
||||
const float DEFAULT_ALPHA = 0.7f;
|
||||
|
||||
class Overlay : public QObject {
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "ui/TextRenderer.h"
|
||||
|
||||
TextOverlay::TextOverlay() :
|
||||
_backgroundColor(DEFAULT_BACKGROUND_COLOR),
|
||||
_leftMargin(DEFAULT_MARGIN),
|
||||
_topMargin(DEFAULT_MARGIN),
|
||||
_fontSize(DEFAULT_FONTSIZE)
|
||||
|
@ -33,7 +34,7 @@ void TextOverlay::render() {
|
|||
}
|
||||
|
||||
const float MAX_COLOR = 255;
|
||||
glColor4f(0 / MAX_COLOR, 0 / MAX_COLOR, 0 / MAX_COLOR, _alpha);
|
||||
glColor4f(_backgroundColor.red / MAX_COLOR, _backgroundColor.green / MAX_COLOR, _backgroundColor.blue / MAX_COLOR, _alpha);
|
||||
|
||||
glBegin(GL_QUADS);
|
||||
glVertex2f(_bounds.left(), _bounds.top());
|
||||
|
@ -82,6 +83,18 @@ void TextOverlay::setProperties(const QScriptValue& properties) {
|
|||
setText(text.toVariant().toString());
|
||||
}
|
||||
|
||||
QScriptValue backgroundColor = properties.property("backgroundColor");
|
||||
if (backgroundColor.isValid()) {
|
||||
QScriptValue red = backgroundColor.property("red");
|
||||
QScriptValue green = backgroundColor.property("green");
|
||||
QScriptValue blue = backgroundColor.property("blue");
|
||||
if (red.isValid() && green.isValid() && blue.isValid()) {
|
||||
_backgroundColor.red = red.toVariant().toInt();
|
||||
_backgroundColor.green = green.toVariant().toInt();
|
||||
_backgroundColor.blue = blue.toVariant().toInt();
|
||||
}
|
||||
}
|
||||
|
||||
if (properties.property("leftMargin").isValid()) {
|
||||
setLeftMargin(properties.property("leftMargin").toVariant().toInt());
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include "Overlay.h"
|
||||
#include "Overlay2D.h"
|
||||
|
||||
const xColor DEFAULT_BACKGROUND_COLOR = { 0, 0, 0 };
|
||||
const int DEFAULT_MARGIN = 10;
|
||||
const int DEFAULT_FONTSIZE = 11;
|
||||
|
||||
|
@ -54,6 +55,7 @@ public:
|
|||
private:
|
||||
|
||||
QString _text;
|
||||
xColor _backgroundColor;
|
||||
int _leftMargin;
|
||||
int _topMargin;
|
||||
int _fontSize;
|
||||
|
|
|
@ -21,6 +21,8 @@
|
|||
#include <SharedUtil.h>
|
||||
#include <NodeList.h>
|
||||
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
#include "Application.h"
|
||||
#include "InterfaceConfig.h"
|
||||
#include "Menu.h"
|
||||
|
@ -57,6 +59,8 @@ GLubyte identityIndicesRight[] = { 1, 2, 6, 1, 6, 5 };
|
|||
GLubyte identityIndicesFront[] = { 0, 2, 1, 0, 3, 2 };
|
||||
GLubyte identityIndicesBack[] = { 4, 5, 6, 4, 6, 7 };
|
||||
|
||||
static glm::vec3 grayColor = glm::vec3(0.3f, 0.3f, 0.3f);
|
||||
|
||||
VoxelSystem::VoxelSystem(float treeScale, int maxVoxels, VoxelTree* tree)
|
||||
: NodeData(),
|
||||
_treeScale(treeScale),
|
||||
|
@ -67,7 +71,10 @@ VoxelSystem::VoxelSystem(float treeScale, int maxVoxels, VoxelTree* tree)
|
|||
_inOcclusions(false),
|
||||
_showCulledSharedFaces(false),
|
||||
_usePrimitiveRenderer(false),
|
||||
_renderer(0)
|
||||
_renderer(0),
|
||||
_drawHaze(false),
|
||||
_farHazeDistance(300.0f),
|
||||
_hazeColor(grayColor)
|
||||
{
|
||||
|
||||
_voxelsInReadArrays = _voxelsInWriteArrays = _voxelsUpdated = 0;
|
||||
|
@ -373,6 +380,7 @@ void VoxelSystem::cleanupVoxelMemory() {
|
|||
delete[] _readVoxelDirtyArray;
|
||||
_writeVoxelDirtyArray = _readVoxelDirtyArray = NULL;
|
||||
_readArraysLock.unlock();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -454,6 +462,7 @@ void VoxelSystem::initVoxelMemory() {
|
|||
|
||||
_readVoxelShaderData = new VoxelShaderVBOData[_maxVoxels];
|
||||
_memoryUsageRAM += (sizeof(VoxelShaderVBOData) * _maxVoxels);
|
||||
|
||||
} else {
|
||||
|
||||
// Global Normals mode uses a technique of not including normals on any voxel vertices, and instead
|
||||
|
@ -499,17 +508,7 @@ void VoxelSystem::initVoxelMemory() {
|
|||
_memoryUsageRAM += (sizeof(GLubyte) * vertexPointsPerVoxel * _maxVoxels);
|
||||
|
||||
// create our simple fragment shader if we're the first system to init
|
||||
if (!_perlinModulateProgram.isLinked()) {
|
||||
_perlinModulateProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath()
|
||||
+ "shaders/perlin_modulate.vert");
|
||||
_perlinModulateProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath()
|
||||
+ "shaders/perlin_modulate.frag");
|
||||
_perlinModulateProgram.link();
|
||||
|
||||
_perlinModulateProgram.bind();
|
||||
_perlinModulateProgram.setUniformValue("permutationNormalTexture", 0);
|
||||
_perlinModulateProgram.release();
|
||||
|
||||
if (!_shadowMapProgram.isLinked()) {
|
||||
_shadowMapProgram.addShaderFromSourceFile(QGLShader::Vertex,
|
||||
Application::resourcesPath() + "shaders/shadow_map.vert");
|
||||
_shadowMapProgram.addShaderFromSourceFile(QGLShader::Fragment,
|
||||
|
@ -531,13 +530,23 @@ void VoxelSystem::initVoxelMemory() {
|
|||
_shadowDistancesLocation = _cascadedShadowMapProgram.uniformLocation("shadowDistances");
|
||||
_cascadedShadowMapProgram.release();
|
||||
}
|
||||
|
||||
}
|
||||
_renderer = new PrimitiveRenderer(_maxVoxels);
|
||||
|
||||
_initialized = true;
|
||||
|
||||
|
||||
_writeArraysLock.unlock();
|
||||
_readArraysLock.unlock();
|
||||
|
||||
// fog for haze
|
||||
if (_drawHaze) {
|
||||
GLfloat fogColor[] = {_hazeColor.x, _hazeColor.y, _hazeColor.z, 1.0f};
|
||||
glFogi(GL_FOG_MODE, GL_LINEAR);
|
||||
glFogfv(GL_FOG_COLOR, fogColor);
|
||||
glFogf(GL_FOG_START, 0.0f);
|
||||
glFogf(GL_FOG_END, _farHazeDistance);
|
||||
}
|
||||
}
|
||||
|
||||
int VoxelSystem::parseData(const QByteArray& packet) {
|
||||
|
@ -1124,6 +1133,7 @@ int VoxelSystem::updateNodeInArrays(VoxelTreeElement* node, bool reuseIndex, boo
|
|||
node->setBufferIndex(nodeIndex);
|
||||
node->setVoxelSystem(this);
|
||||
}
|
||||
|
||||
// populate the array with points for the 8 vertices and RGB color for each added vertex
|
||||
updateArraysDetails(nodeIndex, startVertex, voxelScale, node->getColor());
|
||||
}
|
||||
|
@ -1141,11 +1151,13 @@ int VoxelSystem::updateNodeInArrays(VoxelTreeElement* node, bool reuseIndex, boo
|
|||
|
||||
void VoxelSystem::updateArraysDetails(glBufferIndex nodeIndex, const glm::vec3& startVertex,
|
||||
float voxelScale, const nodeColor& color) {
|
||||
|
||||
|
||||
if (_initialized && nodeIndex <= _maxVoxels) {
|
||||
_writeVoxelDirtyArray[nodeIndex] = true;
|
||||
|
||||
|
||||
if (_useVoxelShader) {
|
||||
// write in position, scale, and color for the voxel
|
||||
|
||||
if (_writeVoxelShaderData) {
|
||||
VoxelShaderVBOData* writeVerticesAt = &_writeVoxelShaderData[nodeIndex];
|
||||
writeVerticesAt->x = startVertex.x * TREE_SCALE;
|
||||
|
@ -1167,6 +1179,7 @@ void VoxelSystem::updateArraysDetails(glBufferIndex nodeIndex, const glm::vec3&
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1417,6 +1430,10 @@ void VoxelSystem::render() {
|
|||
}
|
||||
} else
|
||||
if (!_usePrimitiveRenderer) {
|
||||
if (_drawHaze) {
|
||||
glEnable(GL_FOG);
|
||||
}
|
||||
|
||||
PerformanceWarning warn(showWarnings, "render().. TRIANGLES...");
|
||||
|
||||
{
|
||||
|
@ -1488,6 +1505,10 @@ void VoxelSystem::render() {
|
|||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
if (_drawHaze) {
|
||||
glDisable(GL_FOG);
|
||||
}
|
||||
}
|
||||
else {
|
||||
applyScaleAndBindProgram(texture);
|
||||
|
@ -1509,7 +1530,7 @@ void VoxelSystem::applyScaleAndBindProgram(bool texture) {
|
|||
glBindTexture(GL_TEXTURE_2D, Application::getInstance()->getTextureCache()->getShadowDepthTextureID());
|
||||
|
||||
} else if (texture) {
|
||||
_perlinModulateProgram.bind();
|
||||
bindPerlinModulateProgram();
|
||||
glBindTexture(GL_TEXTURE_2D, Application::getInstance()->getTextureCache()->getPermutationNormalTextureID());
|
||||
}
|
||||
|
||||
|
@ -2159,6 +2180,22 @@ unsigned long VoxelSystem::getVoxelMemoryUsageGPU() {
|
|||
return (_initialMemoryUsageGPU - currentFreeMemory);
|
||||
}
|
||||
|
||||
void VoxelSystem::bindPerlinModulateProgram() {
|
||||
if (!_perlinModulateProgram.isLinked()) {
|
||||
_perlinModulateProgram.addShaderFromSourceFile(QGLShader::Vertex,
|
||||
Application::resourcesPath() + "shaders/perlin_modulate.vert");
|
||||
_perlinModulateProgram.addShaderFromSourceFile(QGLShader::Fragment,
|
||||
Application::resourcesPath() + "shaders/perlin_modulate.frag");
|
||||
_perlinModulateProgram.link();
|
||||
|
||||
_perlinModulateProgram.bind();
|
||||
_perlinModulateProgram.setUniformValue("permutationNormalTexture", 0);
|
||||
|
||||
} else {
|
||||
_perlinModulateProgram.bind();
|
||||
}
|
||||
}
|
||||
|
||||
// Swizzle value of bit pairs of the value of index
|
||||
unsigned short VoxelSystem::_sSwizzledOcclusionBits[64] = {
|
||||
0x0000, // 00000000
|
||||
|
|
|
@ -236,6 +236,8 @@ private:
|
|||
static ProgramObject _cascadedShadowMapProgram;
|
||||
static int _shadowDistancesLocation;
|
||||
|
||||
static void bindPerlinModulateProgram();
|
||||
|
||||
int _hookID;
|
||||
std::vector<glBufferIndex> _freeIndexes;
|
||||
QMutex _freeIndexLock;
|
||||
|
@ -271,7 +273,11 @@ private:
|
|||
static unsigned short _sSwizzledOcclusionBits[64]; ///< Swizzle value of bit pairs of the value of index
|
||||
static unsigned char _sOctantIndexToBitMask[8]; ///< Map octant index to partition mask
|
||||
static unsigned char _sOctantIndexToSharedBitMask[8][8]; ///< Map octant indices to shared partition mask
|
||||
|
||||
|
||||
// haze
|
||||
bool _drawHaze;
|
||||
float _farHazeDistance;
|
||||
glm::vec3 _hazeColor;
|
||||
};
|
||||
|
||||
#endif // hifi_VoxelSystem_h
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,9 +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)
|
||||
_dynamicJitterBuffers(dynamicJitterBuffers),
|
||||
_consecutiveNotMixedCount(0),
|
||||
_starveCount(0),
|
||||
_silentFramesDropped(0)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -129,7 +71,7 @@ int PositionalAudioRingBuffer::parseData(const QByteArray& packet) {
|
|||
numSilentSamples = getSamplesPerFrame();
|
||||
|
||||
if (numSilentSamples > 0) {
|
||||
if (_currentJitterBufferFrames > _desiredJitterBufferFrames) {
|
||||
if (_dynamicJitterBuffers && _currentJitterBufferFrames > _desiredJitterBufferFrames) {
|
||||
// our current jitter buffer size exceeds its desired value, so ignore some silent
|
||||
// frames to get that size as close to desired as possible
|
||||
int samplesPerFrame = getSamplesPerFrame();
|
||||
|
@ -142,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);
|
||||
|
@ -206,15 +151,17 @@ bool PositionalAudioRingBuffer::shouldBeAddedToMix() {
|
|||
if (!isNotStarvedOrHasMinimumSamples(samplesPerFrame + desiredJitterBufferSamples)) {
|
||||
// if the buffer was starved, allow it to accrue at least the desired number of
|
||||
// jitter buffer frames before we start taking frames from it for mixing
|
||||
|
||||
|
||||
if (_shouldOutputStarveDebug) {
|
||||
_shouldOutputStarveDebug = false;
|
||||
}
|
||||
|
||||
_consecutiveNotMixedCount++;
|
||||
return false;
|
||||
} 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;
|
||||
|
@ -222,6 +169,7 @@ bool PositionalAudioRingBuffer::shouldBeAddedToMix() {
|
|||
// reset our _shouldOutputStarveDebug to true so the next is printed
|
||||
_shouldOutputStarveDebug = true;
|
||||
|
||||
_consecutiveNotMixedCount = 1;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -243,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;
|
||||
}
|
||||
|
@ -266,5 +224,6 @@ void PositionalAudioRingBuffer::updateDesiredJitterBufferFrames() {
|
|||
_desiredJitterBufferFrames = maxDesired;
|
||||
}
|
||||
}
|
||||
_interframeTimeGapStatsForJitterCalc.clearNewStatsAvailableFlag();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,15 +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;
|
||||
|
@ -101,15 +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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
@ -274,6 +280,26 @@ void Bitstream::persistAndResetReadMappings() {
|
|||
persistReadMappings(getAndResetReadMappings());
|
||||
}
|
||||
|
||||
void Bitstream::copyPersistentMappings(const Bitstream& other) {
|
||||
_objectStreamerStreamer.copyPersistentMappings(other._objectStreamerStreamer);
|
||||
_typeStreamerStreamer.copyPersistentMappings(other._typeStreamerStreamer);
|
||||
_attributeStreamer.copyPersistentMappings(other._attributeStreamer);
|
||||
_scriptStringStreamer.copyPersistentMappings(other._scriptStringStreamer);
|
||||
_sharedObjectStreamer.copyPersistentMappings(other._sharedObjectStreamer);
|
||||
_sharedObjectReferences = other._sharedObjectReferences;
|
||||
_weakSharedObjectHash = other._weakSharedObjectHash;
|
||||
}
|
||||
|
||||
void Bitstream::clearPersistentMappings() {
|
||||
_objectStreamerStreamer.clearPersistentMappings();
|
||||
_typeStreamerStreamer.clearPersistentMappings();
|
||||
_attributeStreamer.clearPersistentMappings();
|
||||
_scriptStringStreamer.clearPersistentMappings();
|
||||
_sharedObjectStreamer.clearPersistentMappings();
|
||||
_sharedObjectReferences.clear();
|
||||
_weakSharedObjectHash.clear();
|
||||
}
|
||||
|
||||
void Bitstream::clearSharedObject(int id) {
|
||||
SharedObjectPointer object = _sharedObjectStreamer.takePersistentValue(id);
|
||||
if (object) {
|
||||
|
@ -1122,7 +1148,7 @@ Bitstream& Bitstream::operator>(ObjectStreamerPointer& streamer) {
|
|||
}
|
||||
if (_metadataType == NO_METADATA) {
|
||||
if (!metaObject) {
|
||||
qWarning() << "Unknown class name:" << className;
|
||||
throw BitstreamException(QString("Unknown class name: ") + className);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
@ -1232,7 +1258,7 @@ Bitstream& Bitstream::operator>(TypeStreamerPointer& streamer) {
|
|||
}
|
||||
if (_metadataType == NO_METADATA) {
|
||||
if (!baseStreamer) {
|
||||
qWarning() << "Unknown type name:" << typeName;
|
||||
throw BitstreamException(QString("Unknown type name: ") + typeName);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
@ -1240,7 +1266,7 @@ Bitstream& Bitstream::operator>(TypeStreamerPointer& streamer) {
|
|||
*this >> category;
|
||||
if (category == TypeStreamer::SIMPLE_CATEGORY) {
|
||||
if (!streamer) {
|
||||
qWarning() << "Unknown type name:" << typeName;
|
||||
throw BitstreamException(QString("Unknown type name: ") + typeName);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
@ -1441,7 +1467,7 @@ Bitstream& Bitstream::operator>(SharedObjectPointer& object) {
|
|||
_objectStreamerStreamer >> objectStreamer;
|
||||
if (delta) {
|
||||
if (!reference) {
|
||||
qWarning() << "Delta without reference" << id << originID;
|
||||
throw BitstreamException(QString("Delta without reference [id=%1, originID=%2]").arg(id).arg(originID));
|
||||
}
|
||||
objectStreamer->readRawDelta(*this, reference.data(), pointer.data());
|
||||
} else {
|
||||
|
@ -1451,7 +1477,7 @@ Bitstream& Bitstream::operator>(SharedObjectPointer& object) {
|
|||
QObject* rawObject;
|
||||
if (delta) {
|
||||
if (!reference) {
|
||||
qWarning() << "Delta without reference" << id << originID;
|
||||
throw BitstreamException(QString("Delta without reference [id=%1, originID=%2]").arg(id).arg(originID));
|
||||
}
|
||||
readRawDelta(rawObject, (const QObject*)reference.data());
|
||||
} else {
|
||||
|
@ -1682,6 +1708,10 @@ const TypeStreamer* Bitstream::createInvalidTypeStreamer() {
|
|||
return streamer;
|
||||
}
|
||||
|
||||
BitstreamException::BitstreamException(const QString& description) :
|
||||
_description(description) {
|
||||
}
|
||||
|
||||
QJsonValue JSONWriter::getData(bool value) {
|
||||
return value;
|
||||
}
|
||||
|
|
|
@ -102,6 +102,9 @@ public:
|
|||
|
||||
V takePersistentValue(int id) { V value = _persistentValues.take(id); _valueIDs.remove(value); return value; }
|
||||
|
||||
void copyPersistentMappings(const RepeatedValueStreamer& other);
|
||||
void clearPersistentMappings();
|
||||
|
||||
RepeatedValueStreamer& operator<<(K value);
|
||||
RepeatedValueStreamer& operator>>(V& value);
|
||||
|
||||
|
@ -199,6 +202,29 @@ template<class K, class P, class V> inline RepeatedValueStreamer<K, P, V>&
|
|||
return *this;
|
||||
}
|
||||
|
||||
template<class K, class P, class V> inline void RepeatedValueStreamer<K, P, V>::copyPersistentMappings(
|
||||
const RepeatedValueStreamer<K, P, V>& other) {
|
||||
_lastPersistentID = other._lastPersistentID;
|
||||
_idStreamer.setBitsFromValue(_lastPersistentID);
|
||||
_persistentIDs = other._persistentIDs;
|
||||
_transientOffsets.clear();
|
||||
_lastTransientOffset = 0;
|
||||
_persistentValues = other._persistentValues;
|
||||
_transientValues.clear();
|
||||
_valueIDs = other._valueIDs;
|
||||
}
|
||||
|
||||
template<class K, class P, class V> inline void RepeatedValueStreamer<K, P, V>::clearPersistentMappings() {
|
||||
_lastPersistentID = 0;
|
||||
_idStreamer.setBitsFromValue(_lastPersistentID);
|
||||
_persistentIDs.clear();
|
||||
_transientOffsets.clear();
|
||||
_lastTransientOffset = 0;
|
||||
_persistentValues.clear();
|
||||
_transientValues.clear();
|
||||
_valueIDs.clear();
|
||||
}
|
||||
|
||||
/// A stream for bit-aligned data. Through a combination of code generation, reflection, macros, and templates, provides a
|
||||
/// serialization mechanism that may be used for both networking and persistent storage. For unreliable networking, the
|
||||
/// class provides a mapping system that resends mappings for ids until they are acknowledged (and thus persisted). For
|
||||
|
@ -264,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
|
||||
|
@ -303,6 +334,9 @@ public:
|
|||
Bitstream(QDataStream& underlying, MetadataType metadataType = NO_METADATA,
|
||||
GenericsMode = NO_GENERICS, QObject* parent = NULL);
|
||||
|
||||
/// Returns a reference to the underlying data stream.
|
||||
QDataStream& getUnderlying() { return _underlying; }
|
||||
|
||||
/// Substitutes the supplied metaobject for the given class name's default mapping. This is mostly useful for testing the
|
||||
/// process of mapping between different types, but may in the future be used for permanently renaming classes.
|
||||
void addMetaObjectSubstitution(const QByteArray& className, const QMetaObject* metaObject);
|
||||
|
@ -347,6 +381,12 @@ public:
|
|||
/// Immediately persists and resets the read mappings.
|
||||
void persistAndResetReadMappings();
|
||||
|
||||
/// Copies the persistent mappings from the specified other stream.
|
||||
void copyPersistentMappings(const Bitstream& other);
|
||||
|
||||
/// Clears the persistent mappings for this stream.
|
||||
void clearPersistentMappings();
|
||||
|
||||
/// Returns a reference to the weak hash storing shared objects for this stream.
|
||||
const WeakSharedObjectHash& getWeakSharedObjectHash() const { return _weakSharedObjectHash; }
|
||||
|
||||
|
@ -823,6 +863,19 @@ template<class K, class V> inline Bitstream& Bitstream::operator>>(QHash<K, V>&
|
|||
return *this;
|
||||
}
|
||||
|
||||
/// Thrown for unrecoverable errors.
|
||||
class BitstreamException {
|
||||
public:
|
||||
|
||||
BitstreamException(const QString& description);
|
||||
|
||||
const QString& getDescription() const { return _description; }
|
||||
|
||||
private:
|
||||
|
||||
QString _description;
|
||||
};
|
||||
|
||||
/// Provides a means of writing Bitstream-able data to JSON rather than the usual binary format in a manner that allows it to
|
||||
/// be manipulated and re-read, converted to binary, etc. To use, create a JSONWriter, stream values in using the << operator,
|
||||
/// and call getDocument to obtain the JSON data.
|
||||
|
|
|
@ -79,7 +79,25 @@ ReliableChannel* DatagramSequencer::getReliableInputChannel(int index) {
|
|||
return channel;
|
||||
}
|
||||
|
||||
int DatagramSequencer::startPacketGroup(int desiredPackets) {
|
||||
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;
|
||||
foreach (ReliableChannel* channel, _reliableOutputChannels) {
|
||||
|
@ -113,17 +131,16 @@ Bitstream& DatagramSequencer::startPacket() {
|
|||
_outgoingPacketStream << (quint32)record.packetNumber;
|
||||
}
|
||||
|
||||
// write the high-priority messages
|
||||
_outgoingPacketStream << (quint32)_highPriorityMessages.size();
|
||||
foreach (const HighPriorityMessage& message, _highPriorityMessages) {
|
||||
_outputStream << message.data;
|
||||
}
|
||||
|
||||
// return the stream, allowing the caller to write the rest
|
||||
return _outputStream;
|
||||
}
|
||||
|
||||
void DatagramSequencer::endPacket() {
|
||||
// write the high-priority messages
|
||||
_outputStream << _highPriorityMessages.size();
|
||||
foreach (const HighPriorityMessage& message, _highPriorityMessages) {
|
||||
_outputStream << message.data;
|
||||
}
|
||||
_outputStream.flush();
|
||||
|
||||
// if we have space remaining, send some data from our reliable channels
|
||||
|
@ -222,22 +239,22 @@ void DatagramSequencer::receivedDatagram(const QByteArray& datagram) {
|
|||
_sendRecords.erase(_sendRecords.begin(), it + 1);
|
||||
}
|
||||
|
||||
// alert external parties so that they can read the middle
|
||||
emit readyToRead(_inputStream);
|
||||
|
||||
// read and dispatch the high-priority messages
|
||||
quint32 highPriorityMessageCount;
|
||||
_incomingPacketStream >> highPriorityMessageCount;
|
||||
int highPriorityMessageCount;
|
||||
_inputStream >> highPriorityMessageCount;
|
||||
int newHighPriorityMessages = highPriorityMessageCount - _receivedHighPriorityMessages;
|
||||
for (quint32 i = 0; i < highPriorityMessageCount; i++) {
|
||||
for (int i = 0; i < highPriorityMessageCount; i++) {
|
||||
QVariant data;
|
||||
_inputStream >> data;
|
||||
if ((int)i >= _receivedHighPriorityMessages) {
|
||||
if (i >= _receivedHighPriorityMessages) {
|
||||
emit receivedHighPriorityMessage(data);
|
||||
}
|
||||
}
|
||||
_receivedHighPriorityMessages = highPriorityMessageCount;
|
||||
|
||||
// alert external parties so that they can read the middle
|
||||
emit readyToRead(_inputStream);
|
||||
|
||||
// read the reliable data, if any
|
||||
quint32 reliableChannels;
|
||||
_incomingPacketStream >> reliableChannels;
|
||||
|
@ -253,6 +270,8 @@ void DatagramSequencer::receivedDatagram(const QByteArray& datagram) {
|
|||
// record the receipt
|
||||
ReceiveRecord record = { _incomingPacketNumber, _inputStream.getAndResetReadMappings(), newHighPriorityMessages };
|
||||
_receiveRecords.append(record);
|
||||
|
||||
emit receiveRecorded();
|
||||
}
|
||||
|
||||
void DatagramSequencer::sendClearSharedObjectMessage(int id) {
|
||||
|
@ -274,6 +293,11 @@ void DatagramSequencer::handleHighPriorityMessage(const QVariant& data) {
|
|||
}
|
||||
}
|
||||
|
||||
void DatagramSequencer::clearReliableChannel(QObject* object) {
|
||||
ReliableChannel* channel = static_cast<ReliableChannel*>(object);
|
||||
(channel->isOutput() ? _reliableOutputChannels : _reliableInputChannels).remove(channel->getIndex());
|
||||
}
|
||||
|
||||
void DatagramSequencer::sendRecordAcknowledged(const SendRecord& record) {
|
||||
// stop acknowledging the recorded packets
|
||||
while (!_receiveRecords.isEmpty() && _receiveRecords.first().packetNumber <= record.lastReceivedPacketNumber) {
|
||||
|
@ -295,7 +319,10 @@ void DatagramSequencer::sendRecordAcknowledged(const SendRecord& record) {
|
|||
|
||||
// acknowledge the received spans
|
||||
foreach (const ChannelSpan& span, record.spans) {
|
||||
getReliableOutputChannel(span.channel)->spanAcknowledged(span);
|
||||
ReliableChannel* channel = _reliableOutputChannels.value(span.channel);
|
||||
if (channel) {
|
||||
channel->spanAcknowledged(span);
|
||||
}
|
||||
}
|
||||
|
||||
// increase the packet rate with every ack until we pass the slow start threshold; then, every round trip
|
||||
|
@ -310,7 +337,10 @@ void DatagramSequencer::sendRecordAcknowledged(const SendRecord& record) {
|
|||
void DatagramSequencer::sendRecordLost(const SendRecord& record) {
|
||||
// notify the channels of their lost spans
|
||||
foreach (const ChannelSpan& span, record.spans) {
|
||||
getReliableOutputChannel(span.channel)->spanLost(record.packetNumber, _outgoingPacketNumber + 1);
|
||||
ReliableChannel* channel = _reliableOutputChannels.value(span.channel);
|
||||
if (channel) {
|
||||
channel->spanLost(record.packetNumber, _outgoingPacketNumber + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// halve the rate and remember as threshold
|
||||
|
@ -364,6 +394,8 @@ void DatagramSequencer::sendPacket(const QByteArray& packet, const QVector<Chann
|
|||
_outputStream.getAndResetWriteMappings(), spans };
|
||||
_sendRecords.append(record);
|
||||
|
||||
emit sendRecorded();
|
||||
|
||||
// write the sequence number and size, which are the same between all fragments
|
||||
_outgoingDatagramBuffer.seek(_datagramHeaderSize);
|
||||
_outgoingDatagramStream << (quint32)_outgoingPacketNumber;
|
||||
|
@ -658,16 +690,46 @@ int ReliableChannel::getBytesAvailable() const {
|
|||
return _buffer.size() - _acknowledged.getTotalSet();
|
||||
}
|
||||
|
||||
void ReliableChannel::sendMessage(const QVariant& message) {
|
||||
// write a placeholder for the length, then fill it in when we know what it is
|
||||
int placeholder = _buffer.pos();
|
||||
_dataStream << (quint32)0;
|
||||
_bitstream << message;
|
||||
void ReliableChannel::startMessage() {
|
||||
// write a placeholder for the length; we'll fill it in when we know what it is
|
||||
_messageLengthPlaceholder = _buffer.pos();
|
||||
_dataStream << (quint32)0;
|
||||
}
|
||||
|
||||
void ReliableChannel::endMessage() {
|
||||
_bitstream.flush();
|
||||
_bitstream.persistAndResetWriteMappings();
|
||||
|
||||
quint32 length = _buffer.pos() - placeholder;
|
||||
_buffer.writeBytes(placeholder, sizeof(quint32), (const char*)&length);
|
||||
quint32 length = _buffer.pos() - _messageLengthPlaceholder;
|
||||
_buffer.writeBytes(_messageLengthPlaceholder, sizeof(quint32), (const char*)&length);
|
||||
_messageReceivedOffset = getBytesWritten();
|
||||
_messageSize = length;
|
||||
}
|
||||
|
||||
void ReliableChannel::sendMessage(const QVariant& message) {
|
||||
startMessage();
|
||||
_bitstream << 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) {
|
||||
|
@ -675,7 +737,7 @@ void ReliableChannel::sendClearSharedObjectMessage(int id) {
|
|||
sendMessage(QVariant::fromValue(message));
|
||||
}
|
||||
|
||||
void ReliableChannel::handleMessage(const QVariant& message) {
|
||||
void ReliableChannel::handleMessage(const QVariant& message, Bitstream& in) {
|
||||
if (message.userType() == ClearSharedObjectMessage::Type) {
|
||||
_bitstream.clearSharedObject(message.value<ClearSharedObjectMessage>().id);
|
||||
|
||||
|
@ -688,19 +750,23 @@ void ReliableChannel::handleMessage(const QVariant& message) {
|
|||
ReliableChannel::ReliableChannel(DatagramSequencer* sequencer, int index, bool output) :
|
||||
QObject(sequencer),
|
||||
_index(index),
|
||||
_output(output),
|
||||
_dataStream(&_buffer),
|
||||
_bitstream(_dataStream),
|
||||
_priority(1.0f),
|
||||
_offset(0),
|
||||
_writePosition(0),
|
||||
_writePositionResetPacketNumber(0),
|
||||
_messagesEnabled(true) {
|
||||
_messagesEnabled(true),
|
||||
_messageReceivedOffset(0) {
|
||||
|
||||
_buffer.open(output ? QIODevice::WriteOnly : QIODevice::ReadOnly);
|
||||
_dataStream.setByteOrder(QDataStream::LittleEndian);
|
||||
|
||||
connect(&_bitstream, SIGNAL(sharedObjectCleared(int)), SLOT(sendClearSharedObjectMessage(int)));
|
||||
connect(this, SIGNAL(receivedMessage(const QVariant&)), SLOT(handleMessage(const QVariant&)));
|
||||
connect(this, SIGNAL(receivedMessage(const QVariant&, Bitstream&)), SLOT(handleMessage(const QVariant&, Bitstream&)));
|
||||
|
||||
sequencer->connect(this, SIGNAL(destroyed(QObject*)), SLOT(clearReliableChannel(QObject*)));
|
||||
}
|
||||
|
||||
void ReliableChannel::writeData(QDataStream& out, int bytes, QVector<DatagramSequencer::ChannelSpan>& spans) {
|
||||
|
@ -843,9 +909,9 @@ void ReliableChannel::readData(QDataStream& in) {
|
|||
_dataStream.skipRawData(sizeof(quint32));
|
||||
QVariant message;
|
||||
_bitstream >> message;
|
||||
emit receivedMessage(message, _bitstream);
|
||||
_bitstream.reset();
|
||||
_bitstream.persistAndResetReadMappings();
|
||||
emit receivedMessage(message);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,6 +78,15 @@ public:
|
|||
/// Returns the packet number of the last packet received (or the packet currently being assembled).
|
||||
int getIncomingPacketNumber() const { return _incomingPacketNumber; }
|
||||
|
||||
/// Returns a reference to the stream used to read packets.
|
||||
Bitstream& getInputStream() { return _inputStream; }
|
||||
|
||||
/// Returns a reference to the stream used to write packets.
|
||||
Bitstream& getOutputStream() { return _outputStream; }
|
||||
|
||||
/// Returns a reference to the outgoing packet data.
|
||||
const QByteArray& getOutgoingPacketData() const { return _outgoingPacketData; }
|
||||
|
||||
/// Returns the packet number of the sent packet at the specified index.
|
||||
int getSentPacketNumber(int index) const { return _sendRecords.at(index).packetNumber; }
|
||||
|
||||
|
@ -99,10 +108,13 @@ public:
|
|||
/// Returns the intput channel at the specified index, creating it if necessary.
|
||||
ReliableChannel* getReliableInputChannel(int index = 0);
|
||||
|
||||
/// Starts a packet group.
|
||||
/// 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
|
||||
int startPacketGroup(int desiredPackets = 1);
|
||||
int notePacketGroup(int desiredPackets = 1);
|
||||
|
||||
/// Starts a new packet for transmission.
|
||||
/// \return a reference to the Bitstream to use for writing to the packet
|
||||
|
@ -126,9 +138,15 @@ signals:
|
|||
/// Emitted when a packet is available to read.
|
||||
void readyToRead(Bitstream& input);
|
||||
|
||||
/// Emitted when we've received a high-priority message
|
||||
/// Emitted when we've received a high-priority message.
|
||||
void receivedHighPriorityMessage(const QVariant& data);
|
||||
|
||||
/// Emitted when we've recorded the transmission of a packet.
|
||||
void sendRecorded();
|
||||
|
||||
/// Emitted when we've recorded the receipt of a packet (that is, at the end of packet processing).
|
||||
void receiveRecorded();
|
||||
|
||||
/// Emitted when a sent packet has been acknowledged by the remote side.
|
||||
/// \param index the index of the packet in our list of send records
|
||||
void sendAcknowledged(int index);
|
||||
|
@ -141,6 +159,7 @@ private slots:
|
|||
|
||||
void sendClearSharedObjectMessage(int id);
|
||||
void handleHighPriorityMessage(const QVariant& data);
|
||||
void clearReliableChannel(QObject* object);
|
||||
|
||||
private:
|
||||
|
||||
|
@ -319,6 +338,9 @@ public:
|
|||
/// Returns the channel's index in the sequencer's channel map.
|
||||
int getIndex() const { return _index; }
|
||||
|
||||
/// Checks whether this is an output channel.
|
||||
bool isOutput() const { return _output; }
|
||||
|
||||
/// Returns a reference to the buffer used to write/read data to/from this channel.
|
||||
CircularBuffer& getBuffer() { return _buffer; }
|
||||
|
||||
|
@ -336,22 +358,44 @@ public:
|
|||
/// Returns the number of bytes available to read from this channel.
|
||||
int getBytesAvailable() const;
|
||||
|
||||
/// Returns the offset, which represents the total number of bytes acknowledged
|
||||
/// (on the write end) or received completely (on the read end).
|
||||
int getOffset() const { return _offset; }
|
||||
|
||||
/// Returns the total number of bytes written to this channel.
|
||||
int getBytesWritten() const { return _offset + _buffer.pos(); }
|
||||
|
||||
/// Sets whether we expect to write/read framed messages.
|
||||
void setMessagesEnabled(bool enabled) { _messagesEnabled = enabled; }
|
||||
bool getMessagesEnabled() const { return _messagesEnabled; }
|
||||
|
||||
/// Sends a framed message on this channel.
|
||||
/// Starts a framed message on this channel.
|
||||
void startMessage();
|
||||
|
||||
/// Ends a framed message on this channel.
|
||||
void endMessage();
|
||||
|
||||
/// Sends a framed message on this channel (convenience function that calls startMessage,
|
||||
/// 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.
|
||||
void receivedMessage(const QVariant& message);
|
||||
void receivedMessage(const QVariant& message, Bitstream& in);
|
||||
|
||||
private slots:
|
||||
|
||||
void sendClearSharedObjectMessage(int id);
|
||||
void handleMessage(const QVariant& message);
|
||||
void handleMessage(const QVariant& message, Bitstream& in);
|
||||
|
||||
private:
|
||||
|
||||
|
@ -370,6 +414,7 @@ private:
|
|||
void readData(QDataStream& in);
|
||||
|
||||
int _index;
|
||||
bool _output;
|
||||
CircularBuffer _buffer;
|
||||
CircularBuffer _assemblyBuffer;
|
||||
QDataStream _dataStream;
|
||||
|
@ -381,6 +426,9 @@ private:
|
|||
int _writePositionResetPacketNumber;
|
||||
SpanList _acknowledged;
|
||||
bool _messagesEnabled;
|
||||
int _messageLengthPlaceholder;
|
||||
int _messageReceivedOffset;
|
||||
int _messageSize;
|
||||
};
|
||||
|
||||
#endif // hifi_DatagramSequencer_h
|
||||
|
|
|
@ -19,6 +19,8 @@ Endpoint::Endpoint(const SharedNodePointer& node, PacketRecord* baselineSendReco
|
|||
|
||||
connect(&_sequencer, SIGNAL(readyToWrite(const QByteArray&)), SLOT(sendDatagram(const QByteArray&)));
|
||||
connect(&_sequencer, SIGNAL(readyToRead(Bitstream&)), SLOT(readMessage(Bitstream&)));
|
||||
connect(&_sequencer, SIGNAL(sendRecorded()), SLOT(recordSend()));
|
||||
connect(&_sequencer, SIGNAL(receiveRecorded()), SLOT(recordReceive()));
|
||||
connect(&_sequencer, SIGNAL(sendAcknowledged(int)), SLOT(clearSendRecordsBefore(int)));
|
||||
connect(&_sequencer, SIGNAL(receiveAcknowledged(int)), SLOT(clearReceiveRecordsBefore(int)));
|
||||
|
||||
|
@ -37,12 +39,12 @@ Endpoint::~Endpoint() {
|
|||
}
|
||||
|
||||
void Endpoint::update() {
|
||||
Bitstream& out = _sequencer.startPacket();
|
||||
writeUpdateMessage(out);
|
||||
_sequencer.endPacket();
|
||||
|
||||
// record the send
|
||||
_sendRecords.append(maybeCreateSendRecord());
|
||||
int packetsToSend = _sequencer.notePacketGroup();
|
||||
for (int i = 0; i < packetsToSend; i++) {
|
||||
Bitstream& out = _sequencer.startPacket();
|
||||
writeUpdateMessage(out);
|
||||
_sequencer.endPacket();
|
||||
}
|
||||
}
|
||||
|
||||
int Endpoint::parseData(const QByteArray& packet) {
|
||||
|
@ -59,8 +61,21 @@ void Endpoint::readMessage(Bitstream& in) {
|
|||
QVariant message;
|
||||
in >> message;
|
||||
handleMessage(message, in);
|
||||
|
||||
// record the receipt
|
||||
}
|
||||
|
||||
void Endpoint::handleMessage(const QVariant& message, Bitstream& in) {
|
||||
if (message.userType() == QMetaType::QVariantList) {
|
||||
foreach (const QVariant& element, message.toList()) {
|
||||
handleMessage(element, in);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Endpoint::recordSend() {
|
||||
_sendRecords.append(maybeCreateSendRecord());
|
||||
}
|
||||
|
||||
void Endpoint::recordReceive() {
|
||||
_receiveRecords.append(maybeCreateReceiveRecord());
|
||||
}
|
||||
|
||||
|
@ -84,14 +99,6 @@ void Endpoint::writeUpdateMessage(Bitstream& out) {
|
|||
out << QVariant();
|
||||
}
|
||||
|
||||
void Endpoint::handleMessage(const QVariant& message, Bitstream& in) {
|
||||
if (message.userType() == QMetaType::QVariantList) {
|
||||
foreach (const QVariant& element, message.toList()) {
|
||||
handleMessage(element, in);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PacketRecord* Endpoint::maybeCreateSendRecord() const {
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
@ -24,11 +24,16 @@ class Endpoint : public NodeData {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
/// The index of the input/output channel used to transmit reliable deltas.
|
||||
static const int RELIABLE_DELTA_CHANNEL_INDEX = 1;
|
||||
|
||||
Endpoint(const SharedNodePointer& node, PacketRecord* baselineSendRecord = NULL,
|
||||
PacketRecord* baselineReceiveRecord = NULL);
|
||||
virtual ~Endpoint();
|
||||
|
||||
const DatagramSequencer& getSequencer() const { return _sequencer; }
|
||||
|
||||
virtual void update();
|
||||
|
||||
virtual int parseData(const QByteArray& packet);
|
||||
|
@ -37,6 +42,10 @@ protected slots:
|
|||
|
||||
virtual void sendDatagram(const QByteArray& data);
|
||||
virtual void readMessage(Bitstream& in);
|
||||
virtual void handleMessage(const QVariant& message, Bitstream& in);
|
||||
|
||||
void recordSend();
|
||||
void recordReceive();
|
||||
|
||||
void clearSendRecordsBefore(int index);
|
||||
void clearReceiveRecordsBefore(int index);
|
||||
|
@ -44,7 +53,6 @@ protected slots:
|
|||
protected:
|
||||
|
||||
virtual void writeUpdateMessage(Bitstream& out);
|
||||
virtual void handleMessage(const QVariant& message, Bitstream& in);
|
||||
|
||||
virtual PacketRecord* maybeCreateSendRecord() const;
|
||||
virtual PacketRecord* maybeCreateReceiveRecord() const;
|
||||
|
|
|
@ -86,7 +86,12 @@ void MetavoxelClientManager::updateClient(MetavoxelClient* client) {
|
|||
|
||||
MetavoxelClient::MetavoxelClient(const SharedNodePointer& node, MetavoxelClientManager* manager) :
|
||||
Endpoint(node, new PacketRecord(), new PacketRecord()),
|
||||
_manager(manager) {
|
||||
_manager(manager),
|
||||
_reliableDeltaChannel(NULL),
|
||||
_reliableDeltaID(0) {
|
||||
|
||||
connect(_sequencer.getReliableInputChannel(RELIABLE_DELTA_CHANNEL_INDEX),
|
||||
SIGNAL(receivedMessage(const QVariant&, Bitstream&)), SLOT(handleMessage(const QVariant&, Bitstream&)));
|
||||
}
|
||||
|
||||
void MetavoxelClient::guide(MetavoxelVisitor& visitor) {
|
||||
|
@ -112,31 +117,50 @@ void MetavoxelClient::writeUpdateMessage(Bitstream& out) {
|
|||
out << QVariant::fromValue(state);
|
||||
}
|
||||
|
||||
void MetavoxelClient::readMessage(Bitstream& in) {
|
||||
Endpoint::readMessage(in);
|
||||
|
||||
// reapply local edits
|
||||
foreach (const DatagramSequencer::HighPriorityMessage& message, _sequencer.getHighPriorityMessages()) {
|
||||
if (message.data.userType() == MetavoxelEditMessage::Type) {
|
||||
message.data.value<MetavoxelEditMessage>().apply(_data, _sequencer.getWeakSharedObjectHash());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelClient::handleMessage(const QVariant& message, Bitstream& in) {
|
||||
if (message.userType() == MetavoxelDeltaMessage::Type) {
|
||||
int userType = message.userType();
|
||||
if (userType == MetavoxelDeltaMessage::Type) {
|
||||
PacketRecord* receiveRecord = getLastAcknowledgedReceiveRecord();
|
||||
_data.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in, getLastAcknowledgedSendRecord()->getLOD());
|
||||
|
||||
if (_reliableDeltaChannel) {
|
||||
_remoteData.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in, _remoteDataLOD = _reliableDeltaLOD);
|
||||
_sequencer.getInputStream().persistReadMappings(in.getAndResetReadMappings());
|
||||
in.clearPersistentMappings();
|
||||
_reliableDeltaChannel = NULL;
|
||||
|
||||
} else {
|
||||
_remoteData.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in,
|
||||
_remoteDataLOD = getLastAcknowledgedSendRecord()->getLOD());
|
||||
in.reset();
|
||||
}
|
||||
// copy to local and reapply local edits
|
||||
_data = _remoteData;
|
||||
foreach (const DatagramSequencer::HighPriorityMessage& message, _sequencer.getHighPriorityMessages()) {
|
||||
if (message.data.userType() == MetavoxelEditMessage::Type) {
|
||||
message.data.value<MetavoxelEditMessage>().apply(_data, _sequencer.getWeakSharedObjectHash());
|
||||
}
|
||||
}
|
||||
} else if (userType == MetavoxelDeltaPendingMessage::Type) {
|
||||
// check the id to make sure this is not a delta we've already processed
|
||||
int id = message.value<MetavoxelDeltaPendingMessage>().id;
|
||||
if (id > _reliableDeltaID) {
|
||||
_reliableDeltaID = id;
|
||||
_reliableDeltaChannel = _sequencer.getReliableInputChannel(RELIABLE_DELTA_CHANNEL_INDEX);
|
||||
_reliableDeltaChannel->getBitstream().copyPersistentMappings(_sequencer.getInputStream());
|
||||
_reliableDeltaLOD = getLastAcknowledgedSendRecord()->getLOD();
|
||||
PacketRecord* receiveRecord = getLastAcknowledgedReceiveRecord();
|
||||
_remoteDataLOD = receiveRecord->getLOD();
|
||||
_remoteData = receiveRecord->getData();
|
||||
}
|
||||
} else {
|
||||
Endpoint::handleMessage(message, in);
|
||||
}
|
||||
}
|
||||
|
||||
PacketRecord* MetavoxelClient::maybeCreateSendRecord() const {
|
||||
return new PacketRecord(_manager->getLOD());
|
||||
return new PacketRecord(_reliableDeltaChannel ? _reliableDeltaLOD : _manager->getLOD());
|
||||
}
|
||||
|
||||
PacketRecord* MetavoxelClient::maybeCreateReceiveRecord() const {
|
||||
return new PacketRecord(getLastAcknowledgedSendRecord()->getLOD(), _data);
|
||||
return new PacketRecord(_remoteDataLOD, _remoteData);
|
||||
}
|
||||
|
||||
|
|
|
@ -60,7 +60,6 @@ public:
|
|||
protected:
|
||||
|
||||
virtual void writeUpdateMessage(Bitstream& out);
|
||||
virtual void readMessage(Bitstream& in);
|
||||
virtual void handleMessage(const QVariant& message, Bitstream& in);
|
||||
|
||||
virtual PacketRecord* maybeCreateSendRecord() const;
|
||||
|
@ -70,6 +69,12 @@ private:
|
|||
|
||||
MetavoxelClientManager* _manager;
|
||||
MetavoxelData _data;
|
||||
MetavoxelData _remoteData;
|
||||
MetavoxelLOD _remoteDataLOD;
|
||||
|
||||
ReliableChannel* _reliableDeltaChannel;
|
||||
MetavoxelLOD _reliableDeltaLOD;
|
||||
int _reliableDeltaID;
|
||||
};
|
||||
|
||||
#endif // hifi_MetavoxelClientManager_h
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -61,6 +61,17 @@ class MetavoxelDeltaMessage {
|
|||
|
||||
DECLARE_STREAMABLE_METATYPE(MetavoxelDeltaMessage)
|
||||
|
||||
/// A message indicating that metavoxel delta information is being sent on a reliable channel.
|
||||
class MetavoxelDeltaPendingMessage {
|
||||
STREAMABLE
|
||||
|
||||
public:
|
||||
|
||||
STREAM int id;
|
||||
};
|
||||
|
||||
DECLARE_STREAMABLE_METATYPE(MetavoxelDeltaPendingMessage)
|
||||
|
||||
/// A simple streamable edit.
|
||||
class MetavoxelEditMessage {
|
||||
STREAMABLE
|
||||
|
|
|
@ -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);
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue