mirror of
https://github.com/overte-org/overte.git
synced 2025-08-08 23:57:13 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into inspect_js
This commit is contained in:
commit
4604f5333d
50 changed files with 977 additions and 866 deletions
|
@ -26,8 +26,7 @@
|
||||||
Agent::Agent(const QByteArray& packet) :
|
Agent::Agent(const QByteArray& packet) :
|
||||||
ThreadedAssignment(packet),
|
ThreadedAssignment(packet),
|
||||||
_voxelEditSender(),
|
_voxelEditSender(),
|
||||||
_particleEditSender(),
|
_particleEditSender()
|
||||||
_avatarAudioStream(NULL)
|
|
||||||
{
|
{
|
||||||
// be the parent of the script engine so it gets moved when we do
|
// be the parent of the script engine so it gets moved when we do
|
||||||
_scriptEngine.setParent(this);
|
_scriptEngine.setParent(this);
|
||||||
|
@ -36,30 +35,6 @@ Agent::Agent(const QByteArray& packet) :
|
||||||
_scriptEngine.getParticlesScriptingInterface()->setPacketSender(&_particleEditSender);
|
_scriptEngine.getParticlesScriptingInterface()->setPacketSender(&_particleEditSender);
|
||||||
}
|
}
|
||||||
|
|
||||||
Agent::~Agent() {
|
|
||||||
delete _avatarAudioStream;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int SCRIPT_AUDIO_BUFFER_SAMPLES = floor(((SCRIPT_DATA_CALLBACK_USECS * SAMPLE_RATE) / (1000 * 1000)) + 0.5);
|
|
||||||
|
|
||||||
void Agent::setSendAvatarAudioStream(bool sendAvatarAudioStream) {
|
|
||||||
if (sendAvatarAudioStream) {
|
|
||||||
// the agentAudioStream number of samples is related to the ScriptEngine callback rate
|
|
||||||
_avatarAudioStream = new int16_t[SCRIPT_AUDIO_BUFFER_SAMPLES];
|
|
||||||
|
|
||||||
// fill the _audioStream with zeroes to start
|
|
||||||
memset(_avatarAudioStream, 0, SCRIPT_AUDIO_BUFFER_SAMPLES * sizeof(int16_t));
|
|
||||||
|
|
||||||
_scriptEngine.setNumAvatarAudioBufferSamples(SCRIPT_AUDIO_BUFFER_SAMPLES);
|
|
||||||
_scriptEngine.setAvatarAudioBuffer(_avatarAudioStream);
|
|
||||||
} else {
|
|
||||||
delete _avatarAudioStream;
|
|
||||||
_avatarAudioStream = NULL;
|
|
||||||
|
|
||||||
_scriptEngine.setAvatarAudioBuffer(NULL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Agent::readPendingDatagrams() {
|
void Agent::readPendingDatagrams() {
|
||||||
QByteArray receivedPacket;
|
QByteArray receivedPacket;
|
||||||
HifiSockAddr senderSockAddr;
|
HifiSockAddr senderSockAddr;
|
||||||
|
@ -95,12 +70,19 @@ void Agent::readPendingDatagrams() {
|
||||||
// also give our local particle tree a chance to remap any internal locally created particles
|
// also give our local particle tree a chance to remap any internal locally created particles
|
||||||
_particleViewer.getTree()->handleAddParticleResponse(receivedPacket);
|
_particleViewer.getTree()->handleAddParticleResponse(receivedPacket);
|
||||||
|
|
||||||
|
// Make sure our Node and NodeList knows we've heard from this node.
|
||||||
|
SharedNodePointer sourceNode = nodeList->sendingNodeForPacket(receivedPacket);
|
||||||
|
sourceNode->setLastHeardMicrostamp(usecTimestampNow());
|
||||||
|
|
||||||
} else if (datagramPacketType == PacketTypeParticleData
|
} else if (datagramPacketType == PacketTypeParticleData
|
||||||
|| datagramPacketType == PacketTypeParticleErase
|
|| datagramPacketType == PacketTypeParticleErase
|
||||||
|| datagramPacketType == PacketTypeOctreeStats
|
|| datagramPacketType == PacketTypeOctreeStats
|
||||||
|| datagramPacketType == PacketTypeVoxelData
|
|| datagramPacketType == PacketTypeVoxelData
|
||||||
) {
|
) {
|
||||||
|
// Make sure our Node and NodeList knows we've heard from this node.
|
||||||
SharedNodePointer sourceNode = nodeList->sendingNodeForPacket(receivedPacket);
|
SharedNodePointer sourceNode = nodeList->sendingNodeForPacket(receivedPacket);
|
||||||
|
sourceNode->setLastHeardMicrostamp(usecTimestampNow());
|
||||||
|
|
||||||
QByteArray mutablePacket = receivedPacket;
|
QByteArray mutablePacket = receivedPacket;
|
||||||
ssize_t messageLength = mutablePacket.size();
|
ssize_t messageLength = mutablePacket.size();
|
||||||
|
|
||||||
|
|
|
@ -28,20 +28,24 @@ class Agent : public ThreadedAssignment {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
Q_PROPERTY(bool isAvatar READ isAvatar WRITE setIsAvatar)
|
Q_PROPERTY(bool isAvatar READ isAvatar WRITE setIsAvatar)
|
||||||
Q_PROPERTY(bool sendAvatarAudioStream READ isSendingAvatarAudioStream WRITE setSendAvatarAudioStream)
|
Q_PROPERTY(bool isPlayingAvatarSound READ isPlayingAvatarSound)
|
||||||
|
Q_PROPERTY(bool isListeningToAudioStream READ isListeningToAudioStream WRITE setIsListeningToAudioStream)
|
||||||
public:
|
public:
|
||||||
Agent(const QByteArray& packet);
|
Agent(const QByteArray& packet);
|
||||||
~Agent();
|
|
||||||
|
|
||||||
void setIsAvatar(bool isAvatar) { QMetaObject::invokeMethod(&_scriptEngine, "setIsAvatar", Q_ARG(bool, isAvatar)); }
|
void setIsAvatar(bool isAvatar) { QMetaObject::invokeMethod(&_scriptEngine, "setIsAvatar", Q_ARG(bool, isAvatar)); }
|
||||||
bool isAvatar() const { return _scriptEngine.isAvatar(); }
|
bool isAvatar() const { return _scriptEngine.isAvatar(); }
|
||||||
|
|
||||||
void setSendAvatarAudioStream(bool sendAvatarAudioStream);
|
bool isPlayingAvatarSound() const { return _scriptEngine.isPlayingAvatarSound(); }
|
||||||
bool isSendingAvatarAudioStream() const { return (bool) _scriptEngine.sendsAvatarAudioStream(); }
|
|
||||||
|
bool isListeningToAudioStream() const { return _scriptEngine.isListeningToAudioStream(); }
|
||||||
|
void setIsListeningToAudioStream(bool isListeningToAudioStream)
|
||||||
|
{ _scriptEngine.setIsListeningToAudioStream(isListeningToAudioStream); }
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void run();
|
void run();
|
||||||
void readPendingDatagrams();
|
void readPendingDatagrams();
|
||||||
|
void playAvatarSound(Sound* avatarSound) { _scriptEngine.setAvatarSound(avatarSound); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ScriptEngine _scriptEngine;
|
ScriptEngine _scriptEngine;
|
||||||
|
@ -50,8 +54,6 @@ private:
|
||||||
|
|
||||||
ParticleTreeHeadlessViewer _particleViewer;
|
ParticleTreeHeadlessViewer _particleViewer;
|
||||||
VoxelTreeHeadlessViewer _voxelViewer;
|
VoxelTreeHeadlessViewer _voxelViewer;
|
||||||
|
|
||||||
int16_t* _avatarAudioStream;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* defined(__hifi__Agent__) */
|
#endif /* defined(__hifi__Agent__) */
|
||||||
|
|
|
@ -200,13 +200,13 @@ void AssignmentClient::assignmentCompleted() {
|
||||||
qDebug("Assignment finished or never started - waiting for new assignment.");
|
qDebug("Assignment finished or never started - waiting for new assignment.");
|
||||||
|
|
||||||
NodeList* nodeList = NodeList::getInstance();
|
NodeList* nodeList = NodeList::getInstance();
|
||||||
|
|
||||||
// have us handle incoming NodeList datagrams again
|
// have us handle incoming NodeList datagrams again
|
||||||
disconnect(&nodeList->getNodeSocket(), 0, _currentAssignment, 0);
|
disconnect(&nodeList->getNodeSocket(), 0, _currentAssignment, 0);
|
||||||
connect(&nodeList->getNodeSocket(), &QUdpSocket::readyRead, this, &AssignmentClient::readPendingDatagrams);
|
connect(&nodeList->getNodeSocket(), &QUdpSocket::readyRead, this, &AssignmentClient::readPendingDatagrams);
|
||||||
|
|
||||||
_currentAssignment = NULL;
|
_currentAssignment = NULL;
|
||||||
|
|
||||||
// reset our NodeList by switching back to unassigned and clearing the list
|
// reset our NodeList by switching back to unassigned and clearing the list
|
||||||
nodeList->setOwnerType(NodeType::Unassigned);
|
nodeList->setOwnerType(NodeType::Unassigned);
|
||||||
nodeList->reset();
|
nodeList->reset();
|
||||||
|
|
|
@ -44,7 +44,7 @@ typedef std::map<QUuid, SingleSenderStats>::iterator NodeToSenderStatsMapIterato
|
||||||
/// Handles processing of incoming network packets for the voxel-server. As with other ReceivedPacketProcessor classes
|
/// Handles processing of incoming network packets for the voxel-server. As with other ReceivedPacketProcessor classes
|
||||||
/// the user is responsible for reading inbound packets and adding them to the processing queue by calling queueReceivedPacket()
|
/// the user is responsible for reading inbound packets and adding them to the processing queue by calling queueReceivedPacket()
|
||||||
class OctreeInboundPacketProcessor : public ReceivedPacketProcessor {
|
class OctreeInboundPacketProcessor : public ReceivedPacketProcessor {
|
||||||
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
OctreeInboundPacketProcessor(OctreeServer* myServer);
|
OctreeInboundPacketProcessor(OctreeServer* myServer);
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,8 @@ OctreeQueryNode::OctreeQueryNode() :
|
||||||
_lastClientOctreeSizeScale(DEFAULT_OCTREE_SIZE_SCALE),
|
_lastClientOctreeSizeScale(DEFAULT_OCTREE_SIZE_SCALE),
|
||||||
_lodChanged(false),
|
_lodChanged(false),
|
||||||
_lodInitialized(false),
|
_lodInitialized(false),
|
||||||
_sequenceNumber(0)
|
_sequenceNumber(0),
|
||||||
|
_lastRootTimestamp(0)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ class OctreeSendThread;
|
||||||
class OctreeServer;
|
class OctreeServer;
|
||||||
|
|
||||||
class OctreeQueryNode : public OctreeQuery {
|
class OctreeQueryNode : public OctreeQuery {
|
||||||
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
OctreeQueryNode();
|
OctreeQueryNode();
|
||||||
virtual ~OctreeQueryNode();
|
virtual ~OctreeQueryNode();
|
||||||
|
@ -85,6 +86,12 @@ public:
|
||||||
|
|
||||||
void dumpOutOfView();
|
void dumpOutOfView();
|
||||||
|
|
||||||
|
quint64 getLastRootTimestamp() const { return _lastRootTimestamp; }
|
||||||
|
void setLastRootTimestamp(quint64 timestamp) { _lastRootTimestamp = timestamp; }
|
||||||
|
unsigned int getlastOctreePacketLength() const { return _lastOctreePacketLength; }
|
||||||
|
int getDuplicatePacketCount() const { return _duplicatePacketCount; }
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
OctreeQueryNode(const OctreeQueryNode &);
|
OctreeQueryNode(const OctreeQueryNode &);
|
||||||
OctreeQueryNode& operator= (const OctreeQueryNode&);
|
OctreeQueryNode& operator= (const OctreeQueryNode&);
|
||||||
|
@ -119,6 +126,7 @@ private:
|
||||||
bool _lodInitialized;
|
bool _lodInitialized;
|
||||||
|
|
||||||
OCTREE_PACKET_SEQUENCE _sequenceNumber;
|
OCTREE_PACKET_SEQUENCE _sequenceNumber;
|
||||||
|
quint64 _lastRootTimestamp;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* defined(__hifi__OctreeQueryNode__) */
|
#endif /* defined(__hifi__OctreeQueryNode__) */
|
||||||
|
|
|
@ -23,18 +23,28 @@ OctreeSendThread::OctreeSendThread(const QUuid& nodeUUID, OctreeServer* myServer
|
||||||
_packetData(),
|
_packetData(),
|
||||||
_nodeMissingCount(0)
|
_nodeMissingCount(0)
|
||||||
{
|
{
|
||||||
qDebug() << "client connected - starting sending thread";
|
QString safeServerName("Octree");
|
||||||
|
if (_myServer) {
|
||||||
|
safeServerName = _myServer->getMyServerName();
|
||||||
|
}
|
||||||
|
qDebug() << qPrintable(safeServerName) << "server [" << _myServer << "]: client connected "
|
||||||
|
"- starting sending thread [" << this << "]";
|
||||||
|
|
||||||
OctreeServer::clientConnected();
|
OctreeServer::clientConnected();
|
||||||
}
|
}
|
||||||
|
|
||||||
OctreeSendThread::~OctreeSendThread() {
|
OctreeSendThread::~OctreeSendThread() {
|
||||||
qDebug() << "client disconnected - ending sending thread";
|
QString safeServerName("Octree");
|
||||||
|
if (_myServer) {
|
||||||
|
safeServerName = _myServer->getMyServerName();
|
||||||
|
}
|
||||||
|
qDebug() << qPrintable(safeServerName) << "server [" << _myServer << "]: client disconnected "
|
||||||
|
"- ending sending thread [" << this << "]";
|
||||||
OctreeServer::clientDisconnected();
|
OctreeServer::clientDisconnected();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool OctreeSendThread::process() {
|
bool OctreeSendThread::process() {
|
||||||
|
|
||||||
const int MAX_NODE_MISSING_CHECKS = 10;
|
const int MAX_NODE_MISSING_CHECKS = 10;
|
||||||
if (_nodeMissingCount > MAX_NODE_MISSING_CHECKS) {
|
if (_nodeMissingCount > MAX_NODE_MISSING_CHECKS) {
|
||||||
qDebug() << "our target node:" << _nodeUUID << "has been missing the last" << _nodeMissingCount
|
qDebug() << "our target node:" << _nodeUUID << "has been missing the last" << _nodeMissingCount
|
||||||
|
@ -57,18 +67,11 @@ bool OctreeSendThread::process() {
|
||||||
// Sometimes the node data has not yet been linked, in which case we can't really do anything
|
// Sometimes the node data has not yet been linked, in which case we can't really do anything
|
||||||
if (nodeData) {
|
if (nodeData) {
|
||||||
bool viewFrustumChanged = nodeData->updateCurrentViewFrustum();
|
bool viewFrustumChanged = nodeData->updateCurrentViewFrustum();
|
||||||
if (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug()) {
|
|
||||||
printf("nodeData->updateCurrentViewFrustum() changed=%s\n", debug::valueOf(viewFrustumChanged));
|
|
||||||
}
|
|
||||||
packetDistributor(node, nodeData, viewFrustumChanged);
|
packetDistributor(node, nodeData, viewFrustumChanged);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_nodeMissingCount++;
|
_nodeMissingCount++;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug()) {
|
|
||||||
qDebug("OctreeSendThread::process() waiting for isInitialLoadComplete()");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only sleep if we're still running and we got the lock last time we tried, otherwise try to get the lock asap
|
// Only sleep if we're still running and we got the lock last time we tried, otherwise try to get the lock asap
|
||||||
|
@ -81,10 +84,6 @@ bool OctreeSendThread::process() {
|
||||||
PerformanceWarning warn(false,"OctreeSendThread... usleep()",false,&_usleepTime,&_usleepCalls);
|
PerformanceWarning warn(false,"OctreeSendThread... usleep()",false,&_usleepTime,&_usleepCalls);
|
||||||
usleep(usecToSleep);
|
usleep(usecToSleep);
|
||||||
} else {
|
} else {
|
||||||
if (true || (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug())) {
|
|
||||||
qDebug() << "Last send took too much time (" << (elapsed / USECS_PER_MSEC)
|
|
||||||
<<" msecs), barely sleeping 1 usec!\n";
|
|
||||||
}
|
|
||||||
const int MIN_USEC_TO_SLEEP = 1;
|
const int MIN_USEC_TO_SLEEP = 1;
|
||||||
usleep(MIN_USEC_TO_SLEEP);
|
usleep(MIN_USEC_TO_SLEEP);
|
||||||
}
|
}
|
||||||
|
@ -114,7 +113,7 @@ int OctreeSendThread::handlePacketSend(const SharedNodePointer& node, OctreeQuer
|
||||||
quint64 lockWaitEnd = usecTimestampNow();
|
quint64 lockWaitEnd = usecTimestampNow();
|
||||||
float lockWaitElapsedUsec = (float)(lockWaitEnd - lockWaitStart);
|
float lockWaitElapsedUsec = (float)(lockWaitEnd - lockWaitStart);
|
||||||
OctreeServer::trackNodeWaitTime(lockWaitElapsedUsec);
|
OctreeServer::trackNodeWaitTime(lockWaitElapsedUsec);
|
||||||
|
|
||||||
const HifiSockAddr* nodeAddress = node->getActiveSocket();
|
const HifiSockAddr* nodeAddress = node->getActiveSocket();
|
||||||
if (!nodeAddress) {
|
if (!nodeAddress) {
|
||||||
return packetsSent; // without sending...
|
return packetsSent; // without sending...
|
||||||
|
@ -235,8 +234,6 @@ int OctreeSendThread::handlePacketSend(const SharedNodePointer& node, OctreeQuer
|
||||||
|
|
||||||
/// Version of voxel distributor that sends the deepest LOD level at once
|
/// Version of voxel distributor that sends the deepest LOD level at once
|
||||||
int OctreeSendThread::packetDistributor(const SharedNodePointer& node, OctreeQueryNode* nodeData, bool viewFrustumChanged) {
|
int OctreeSendThread::packetDistributor(const SharedNodePointer& node, OctreeQueryNode* nodeData, bool viewFrustumChanged) {
|
||||||
bool forceDebugging = false;
|
|
||||||
|
|
||||||
int truePacketsSent = 0;
|
int truePacketsSent = 0;
|
||||||
int trueBytesSent = 0;
|
int trueBytesSent = 0;
|
||||||
int packetsSentThisInterval = 0;
|
int packetsSentThisInterval = 0;
|
||||||
|
@ -259,72 +256,22 @@ int OctreeSendThread::packetDistributor(const SharedNodePointer& node, OctreeQue
|
||||||
// then let's just send that waiting packet.
|
// then let's just send that waiting packet.
|
||||||
if (!nodeData->getCurrentPacketFormatMatches()) {
|
if (!nodeData->getCurrentPacketFormatMatches()) {
|
||||||
if (nodeData->isPacketWaiting()) {
|
if (nodeData->isPacketWaiting()) {
|
||||||
if (forceDebugging || (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug())) {
|
|
||||||
qDebug("about to call handlePacketSend() .... line: %d -- format change "
|
|
||||||
"wantColor=%s wantCompression=%s SENDING PARTIAL PACKET! currentPacketIsColor=%s "
|
|
||||||
"currentPacketIsCompressed=%s",
|
|
||||||
__LINE__,
|
|
||||||
debug::valueOf(wantColor), debug::valueOf(wantCompression),
|
|
||||||
debug::valueOf(nodeData->getCurrentPacketIsColor()),
|
|
||||||
debug::valueOf(nodeData->getCurrentPacketIsCompressed()) );
|
|
||||||
}
|
|
||||||
packetsSentThisInterval += handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent);
|
packetsSentThisInterval += handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent);
|
||||||
} else {
|
} else {
|
||||||
if (forceDebugging || (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug())) {
|
|
||||||
qDebug("wantColor=%s wantCompression=%s FIXING HEADER! currentPacketIsColor=%s currentPacketIsCompressed=%s",
|
|
||||||
debug::valueOf(wantColor), debug::valueOf(wantCompression),
|
|
||||||
debug::valueOf(nodeData->getCurrentPacketIsColor()),
|
|
||||||
debug::valueOf(nodeData->getCurrentPacketIsCompressed()) );
|
|
||||||
}
|
|
||||||
nodeData->resetOctreePacket();
|
nodeData->resetOctreePacket();
|
||||||
}
|
}
|
||||||
int targetSize = MAX_OCTREE_PACKET_DATA_SIZE;
|
int targetSize = MAX_OCTREE_PACKET_DATA_SIZE;
|
||||||
if (wantCompression) {
|
if (wantCompression) {
|
||||||
targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE);
|
targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE);
|
||||||
}
|
}
|
||||||
if (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug()) {
|
|
||||||
qDebug("line:%d _packetData.changeSettings() wantCompression=%s targetSize=%d", __LINE__,
|
|
||||||
debug::valueOf(wantCompression), targetSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
_packetData.changeSettings(wantCompression, targetSize);
|
_packetData.changeSettings(wantCompression, targetSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug()) {
|
|
||||||
qDebug("wantColor/isColor=%s/%s wantCompression/isCompressed=%s/%s viewFrustumChanged=%s, getWantLowResMoving()=%s",
|
|
||||||
debug::valueOf(wantColor), debug::valueOf(nodeData->getCurrentPacketIsColor()),
|
|
||||||
debug::valueOf(wantCompression), debug::valueOf(nodeData->getCurrentPacketIsCompressed()),
|
|
||||||
debug::valueOf(viewFrustumChanged), debug::valueOf(nodeData->getWantLowResMoving()));
|
|
||||||
}
|
|
||||||
|
|
||||||
const ViewFrustum* lastViewFrustum = wantDelta ? &nodeData->getLastKnownViewFrustum() : NULL;
|
const ViewFrustum* lastViewFrustum = wantDelta ? &nodeData->getLastKnownViewFrustum() : NULL;
|
||||||
|
|
||||||
if (forceDebugging || (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug())) {
|
|
||||||
qDebug("packetDistributor() viewFrustumChanged=%s, nodeBag.isEmpty=%s, viewSent=%s",
|
|
||||||
debug::valueOf(viewFrustumChanged), debug::valueOf(nodeData->nodeBag.isEmpty()),
|
|
||||||
debug::valueOf(nodeData->getViewSent())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the current view frustum has changed OR we have nothing to send, then search against
|
// If the current view frustum has changed OR we have nothing to send, then search against
|
||||||
// the current view frustum for things to send.
|
// the current view frustum for things to send.
|
||||||
if (viewFrustumChanged || nodeData->nodeBag.isEmpty()) {
|
if (viewFrustumChanged || nodeData->nodeBag.isEmpty()) {
|
||||||
quint64 now = usecTimestampNow();
|
|
||||||
if (forceDebugging || (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug())) {
|
|
||||||
qDebug("(viewFrustumChanged=%s || nodeData->nodeBag.isEmpty() =%s)...",
|
|
||||||
debug::valueOf(viewFrustumChanged), debug::valueOf(nodeData->nodeBag.isEmpty()));
|
|
||||||
if (nodeData->getLastTimeBagEmpty() > 0) {
|
|
||||||
float elapsedSceneSend = (now - nodeData->getLastTimeBagEmpty()) / 1000000.0f;
|
|
||||||
if (viewFrustumChanged) {
|
|
||||||
qDebug("viewFrustumChanged resetting after elapsed time to send scene = %f seconds", elapsedSceneSend);
|
|
||||||
} else {
|
|
||||||
qDebug("elapsed time to send scene = %f seconds", elapsedSceneSend);
|
|
||||||
}
|
|
||||||
qDebug("[ occlusionCulling:%s, wantDelta:%s, wantColor:%s ]",
|
|
||||||
debug::valueOf(nodeData->getWantOcclusionCulling()), debug::valueOf(wantDelta),
|
|
||||||
debug::valueOf(wantColor));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if our view has changed, we need to reset these things...
|
// if our view has changed, we need to reset these things...
|
||||||
if (viewFrustumChanged) {
|
if (viewFrustumChanged) {
|
||||||
|
@ -342,44 +289,25 @@ int OctreeSendThread::packetDistributor(const SharedNodePointer& node, OctreeQue
|
||||||
|
|
||||||
// track completed scenes and send out the stats packet accordingly
|
// track completed scenes and send out the stats packet accordingly
|
||||||
nodeData->stats.sceneCompleted();
|
nodeData->stats.sceneCompleted();
|
||||||
::endSceneSleepTime = _usleepTime;
|
nodeData->setLastRootTimestamp(_myServer->getOctree()->getRoot()->getLastChanged());
|
||||||
unsigned long sleepTime = ::endSceneSleepTime - ::startSceneSleepTime;
|
|
||||||
|
|
||||||
unsigned long encodeTime = nodeData->stats.getTotalEncodeTime();
|
// TODO: add these to stats page
|
||||||
unsigned long elapsedTime = nodeData->stats.getElapsedTime();
|
//::endSceneSleepTime = _usleepTime;
|
||||||
|
//unsigned long sleepTime = ::endSceneSleepTime - ::startSceneSleepTime;
|
||||||
|
//unsigned long encodeTime = nodeData->stats.getTotalEncodeTime();
|
||||||
|
//unsigned long elapsedTime = nodeData->stats.getElapsedTime();
|
||||||
|
|
||||||
if (forceDebugging || (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug())) {
|
|
||||||
qDebug("about to call handlePacketSend() .... line: %d -- completed scene", __LINE__ );
|
|
||||||
}
|
|
||||||
int packetsJustSent = handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent);
|
int packetsJustSent = handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent);
|
||||||
packetsSentThisInterval += packetsJustSent;
|
packetsSentThisInterval += packetsJustSent;
|
||||||
if (forceDebugging) {
|
|
||||||
qDebug("packetsJustSent=%d packetsSentThisInterval=%d", packetsJustSent, packetsSentThisInterval);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (forceDebugging || _myServer->wantsDebugSending()) {
|
|
||||||
qDebug() << "Scene completed at " << usecTimestampNow()
|
|
||||||
<< "encodeTime:" << encodeTime
|
|
||||||
<< " sleepTime:" << sleepTime
|
|
||||||
<< " elapsed:" << elapsedTime
|
|
||||||
<< " Packets:" << _totalPackets
|
|
||||||
<< " Bytes:" << _totalBytes
|
|
||||||
<< " Wasted:" << _totalWastedBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're starting a full scene, then definitely we want to empty the nodeBag
|
// If we're starting a full scene, then definitely we want to empty the nodeBag
|
||||||
if (isFullScene) {
|
if (isFullScene) {
|
||||||
nodeData->nodeBag.deleteAll();
|
nodeData->nodeBag.deleteAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (forceDebugging || _myServer->wantsDebugSending()) {
|
// TODO: add these to stats page
|
||||||
qDebug() << "Scene started at " << usecTimestampNow()
|
//::startSceneSleepTime = _usleepTime;
|
||||||
<< " Packets:" << _totalPackets
|
|
||||||
<< " Bytes:" << _totalBytes
|
|
||||||
<< " Wasted:" << _totalWastedBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
::startSceneSleepTime = _usleepTime;
|
|
||||||
// start tracking our stats
|
// start tracking our stats
|
||||||
nodeData->stats.sceneStarted(isFullScene, viewFrustumChanged, _myServer->getOctree()->getRoot(), _myServer->getJurisdiction());
|
nodeData->stats.sceneStarted(isFullScene, viewFrustumChanged, _myServer->getOctree()->getRoot(), _myServer->getJurisdiction());
|
||||||
|
|
||||||
|
@ -398,60 +326,79 @@ int OctreeSendThread::packetDistributor(const SharedNodePointer& node, OctreeQue
|
||||||
if (!nodeData->nodeBag.isEmpty()) {
|
if (!nodeData->nodeBag.isEmpty()) {
|
||||||
int bytesWritten = 0;
|
int bytesWritten = 0;
|
||||||
quint64 start = usecTimestampNow();
|
quint64 start = usecTimestampNow();
|
||||||
quint64 startCompressTimeMsecs = OctreePacketData::getCompressContentTime() / 1000;
|
|
||||||
quint64 startCompressCalls = OctreePacketData::getCompressContentCalls();
|
// TODO: add these to stats page
|
||||||
|
//quint64 startCompressTimeMsecs = OctreePacketData::getCompressContentTime() / 1000;
|
||||||
|
//quint64 startCompressCalls = OctreePacketData::getCompressContentCalls();
|
||||||
|
|
||||||
int clientMaxPacketsPerInterval = std::max(1,(nodeData->getMaxOctreePacketsPerSecond() / INTERVALS_PER_SECOND));
|
int clientMaxPacketsPerInterval = std::max(1,(nodeData->getMaxOctreePacketsPerSecond() / INTERVALS_PER_SECOND));
|
||||||
int maxPacketsPerInterval = std::min(clientMaxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval());
|
int maxPacketsPerInterval = std::min(clientMaxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval());
|
||||||
|
|
||||||
if (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug()) {
|
|
||||||
qDebug("truePacketsSent=%d packetsSentThisInterval=%d maxPacketsPerInterval=%d server PPI=%d nodePPS=%d nodePPI=%d",
|
|
||||||
truePacketsSent, packetsSentThisInterval, maxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval(),
|
|
||||||
nodeData->getMaxOctreePacketsPerSecond(), clientMaxPacketsPerInterval);
|
|
||||||
}
|
|
||||||
|
|
||||||
int extraPackingAttempts = 0;
|
int extraPackingAttempts = 0;
|
||||||
bool completedScene = false;
|
bool completedScene = false;
|
||||||
while (somethingToSend && packetsSentThisInterval < maxPacketsPerInterval) {
|
while (somethingToSend && packetsSentThisInterval < maxPacketsPerInterval) {
|
||||||
if (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug()) {
|
float lockWaitElapsedUsec = OctreeServer::SKIP_TIME;
|
||||||
qDebug("truePacketsSent=%d packetsSentThisInterval=%d maxPacketsPerInterval=%d server PPI=%d nodePPS=%d nodePPI=%d",
|
float encodeElapsedUsec = OctreeServer::SKIP_TIME;
|
||||||
truePacketsSent, packetsSentThisInterval, maxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval(),
|
float compressAndWriteElapsedUsec = OctreeServer::SKIP_TIME;
|
||||||
nodeData->getMaxOctreePacketsPerSecond(), clientMaxPacketsPerInterval);
|
float packetSendingElapsedUsec = OctreeServer::SKIP_TIME;
|
||||||
}
|
|
||||||
|
quint64 startInside = usecTimestampNow();
|
||||||
|
|
||||||
bool lastNodeDidntFit = false; // assume each node fits
|
bool lastNodeDidntFit = false; // assume each node fits
|
||||||
if (!nodeData->nodeBag.isEmpty()) {
|
if (!nodeData->nodeBag.isEmpty()) {
|
||||||
OctreeElement* subTree = nodeData->nodeBag.extract();
|
OctreeElement* subTree = nodeData->nodeBag.extract();
|
||||||
|
|
||||||
|
/* TODO: Looking for a way to prevent locking and encoding a tree that is not
|
||||||
|
// going to result in any packets being sent...
|
||||||
|
//
|
||||||
|
// If our node is root, and the root hasn't changed, and our view hasn't changed,
|
||||||
|
// and we've already seen at least one duplicate packet, then we probably don't need
|
||||||
|
// to lock the tree and encode, because the result should be that no bytes will be
|
||||||
|
// encoded, and this will be a duplicate packet from the last one we sent...
|
||||||
|
OctreeElement* root = _myServer->getOctree()->getRoot();
|
||||||
|
bool skipEncode = false;
|
||||||
|
if (
|
||||||
|
(subTree == root)
|
||||||
|
&& (nodeData->getLastRootTimestamp() == root->getLastChanged())
|
||||||
|
&& !viewFrustumChanged
|
||||||
|
&& (nodeData->getDuplicatePacketCount() > 0)
|
||||||
|
) {
|
||||||
|
qDebug() << "is root, root not changed, view not changed, already seen a duplicate!"
|
||||||
|
<< "Can we skip it?";
|
||||||
|
skipEncode = true;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
bool wantOcclusionCulling = nodeData->getWantOcclusionCulling();
|
bool wantOcclusionCulling = nodeData->getWantOcclusionCulling();
|
||||||
CoverageMap* coverageMap = wantOcclusionCulling ? &nodeData->map : IGNORE_COVERAGE_MAP;
|
CoverageMap* coverageMap = wantOcclusionCulling ? &nodeData->map : IGNORE_COVERAGE_MAP;
|
||||||
|
|
||||||
float voxelSizeScale = nodeData->getOctreeSizeScale();
|
float voxelSizeScale = nodeData->getOctreeSizeScale();
|
||||||
int boundaryLevelAdjustClient = nodeData->getBoundaryLevelAdjust();
|
int boundaryLevelAdjustClient = nodeData->getBoundaryLevelAdjust();
|
||||||
|
|
||||||
int boundaryLevelAdjust = boundaryLevelAdjustClient + (viewFrustumChanged && nodeData->getWantLowResMoving()
|
int boundaryLevelAdjust = boundaryLevelAdjustClient + (viewFrustumChanged && nodeData->getWantLowResMoving()
|
||||||
? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST);
|
? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST);
|
||||||
|
|
||||||
EncodeBitstreamParams params(INT_MAX, &nodeData->getCurrentViewFrustum(), wantColor,
|
EncodeBitstreamParams params(INT_MAX, &nodeData->getCurrentViewFrustum(), wantColor,
|
||||||
WANT_EXISTS_BITS, DONT_CHOP, wantDelta, lastViewFrustum,
|
WANT_EXISTS_BITS, DONT_CHOP, wantDelta, lastViewFrustum,
|
||||||
wantOcclusionCulling, coverageMap, boundaryLevelAdjust, voxelSizeScale,
|
wantOcclusionCulling, coverageMap, boundaryLevelAdjust, voxelSizeScale,
|
||||||
nodeData->getLastTimeBagEmpty(),
|
nodeData->getLastTimeBagEmpty(),
|
||||||
isFullScene, &nodeData->stats, _myServer->getJurisdiction());
|
isFullScene, &nodeData->stats, _myServer->getJurisdiction());
|
||||||
|
|
||||||
|
// TODO: should this include the lock time or not? This stat is sent down to the client,
|
||||||
|
// it seems like it may be a good idea to include the lock time as part of the encode time
|
||||||
|
// are reported to client. Since you can encode without the lock
|
||||||
|
nodeData->stats.encodeStarted();
|
||||||
|
|
||||||
quint64 lockWaitStart = usecTimestampNow();
|
quint64 lockWaitStart = usecTimestampNow();
|
||||||
_myServer->getOctree()->lockForRead();
|
_myServer->getOctree()->lockForRead();
|
||||||
quint64 lockWaitEnd = usecTimestampNow();
|
quint64 lockWaitEnd = usecTimestampNow();
|
||||||
float lockWaitElapsedUsec = (float)(lockWaitEnd - lockWaitStart);
|
lockWaitElapsedUsec = (float)(lockWaitEnd - lockWaitStart);
|
||||||
OctreeServer::trackTreeWaitTime(lockWaitElapsedUsec);
|
|
||||||
|
|
||||||
nodeData->stats.encodeStarted();
|
|
||||||
|
|
||||||
quint64 encodeStart = usecTimestampNow();
|
quint64 encodeStart = usecTimestampNow();
|
||||||
bytesWritten = _myServer->getOctree()->encodeTreeBitstream(subTree, &_packetData, nodeData->nodeBag, params);
|
bytesWritten = _myServer->getOctree()->encodeTreeBitstream(subTree, &_packetData, nodeData->nodeBag, params);
|
||||||
quint64 encodeEnd = usecTimestampNow();
|
quint64 encodeEnd = usecTimestampNow();
|
||||||
int encodeElapsedMsec = (encodeEnd - encodeStart)/USECS_PER_MSEC;
|
encodeElapsedUsec = (float)(encodeEnd - encodeStart);
|
||||||
OctreeServer::trackEncodeTime(encodeElapsedMsec);
|
|
||||||
|
|
||||||
// If after calling encodeTreeBitstream() there are no nodes left to send, then we know we've
|
// If after calling encodeTreeBitstream() there are no nodes left to send, then we know we've
|
||||||
// sent the entire scene. We want to know this below so we'll actually write this content into
|
// sent the entire scene. We want to know this below so we'll actually write this content into
|
||||||
// the packet and send it
|
// the packet and send it
|
||||||
|
@ -490,6 +437,9 @@ int OctreeSendThread::packetDistributor(const SharedNodePointer& node, OctreeQue
|
||||||
// mean we should send the previous packet contents and reset it.
|
// mean we should send the previous packet contents and reset it.
|
||||||
if (completedScene || lastNodeDidntFit) {
|
if (completedScene || lastNodeDidntFit) {
|
||||||
if (_packetData.hasContent()) {
|
if (_packetData.hasContent()) {
|
||||||
|
|
||||||
|
quint64 compressAndWriteStart = usecTimestampNow();
|
||||||
|
|
||||||
// if for some reason the finalized size is greater than our available size, then probably the "compressed"
|
// if for some reason the finalized size is greater than our available size, then probably the "compressed"
|
||||||
// form actually inflated beyond our padding, and in this case we will send the current packet, then
|
// form actually inflated beyond our padding, and in this case we will send the current packet, then
|
||||||
// write to out new packet...
|
// write to out new packet...
|
||||||
|
@ -498,27 +448,19 @@ int OctreeSendThread::packetDistributor(const SharedNodePointer& node, OctreeQue
|
||||||
|
|
||||||
|
|
||||||
if (writtenSize > nodeData->getAvailable()) {
|
if (writtenSize > nodeData->getAvailable()) {
|
||||||
if (forceDebugging || (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug())) {
|
|
||||||
qDebug("about to call handlePacketSend() .... line: %d -- "
|
|
||||||
"writtenSize[%d] > available[%d] too big, sending packet as is.",
|
|
||||||
__LINE__, writtenSize, nodeData->getAvailable());
|
|
||||||
}
|
|
||||||
packetsSentThisInterval += handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent);
|
packetsSentThisInterval += handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (forceDebugging || (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug())) {
|
|
||||||
qDebug(">>>>>> calling writeToPacket() available=%d compressedSize=%d uncompressedSize=%d target=%u",
|
|
||||||
nodeData->getAvailable(), _packetData.getFinalizedSize(),
|
|
||||||
_packetData.getUncompressedSize(), _packetData.getTargetSize());
|
|
||||||
}
|
|
||||||
nodeData->writeToPacket(_packetData.getFinalizedData(), _packetData.getFinalizedSize());
|
nodeData->writeToPacket(_packetData.getFinalizedData(), _packetData.getFinalizedSize());
|
||||||
extraPackingAttempts = 0;
|
extraPackingAttempts = 0;
|
||||||
|
quint64 compressAndWriteEnd = usecTimestampNow();
|
||||||
|
compressAndWriteElapsedUsec = (float)(compressAndWriteEnd - compressAndWriteStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we're not running compressed, then we know we can just send now. Or if we're running compressed, but
|
// If we're not running compressed, then we know we can just send now. Or if we're running compressed, but
|
||||||
// the packet doesn't have enough space to bother attempting to pack more...
|
// the packet doesn't have enough space to bother attempting to pack more...
|
||||||
bool sendNow = true;
|
bool sendNow = true;
|
||||||
|
|
||||||
if (nodeData->getCurrentPacketIsCompressed() &&
|
if (nodeData->getCurrentPacketIsCompressed() &&
|
||||||
nodeData->getAvailable() >= MINIMUM_ATTEMPT_MORE_PACKING &&
|
nodeData->getAvailable() >= MINIMUM_ATTEMPT_MORE_PACKING &&
|
||||||
extraPackingAttempts <= REASONABLE_NUMBER_OF_PACKING_ATTEMPTS) {
|
extraPackingAttempts <= REASONABLE_NUMBER_OF_PACKING_ATTEMPTS) {
|
||||||
|
@ -527,10 +469,11 @@ int OctreeSendThread::packetDistributor(const SharedNodePointer& node, OctreeQue
|
||||||
|
|
||||||
int targetSize = MAX_OCTREE_PACKET_DATA_SIZE;
|
int targetSize = MAX_OCTREE_PACKET_DATA_SIZE;
|
||||||
if (sendNow) {
|
if (sendNow) {
|
||||||
if (forceDebugging) {
|
quint64 packetSendingStart = usecTimestampNow();
|
||||||
qDebug("about to call handlePacketSend() .... line: %d -- sendNow = TRUE", __LINE__);
|
|
||||||
}
|
|
||||||
packetsSentThisInterval += handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent);
|
packetsSentThisInterval += handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent);
|
||||||
|
quint64 packetSendingEnd = usecTimestampNow();
|
||||||
|
packetSendingElapsedUsec = (float)(packetSendingEnd - packetSendingStart);
|
||||||
|
|
||||||
if (wantCompression) {
|
if (wantCompression) {
|
||||||
targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE);
|
targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE);
|
||||||
}
|
}
|
||||||
|
@ -543,12 +486,17 @@ int OctreeSendThread::packetDistributor(const SharedNodePointer& node, OctreeQue
|
||||||
// a larger compressed size then uncompressed size
|
// a larger compressed size then uncompressed size
|
||||||
targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE) - COMPRESS_PADDING;
|
targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE) - COMPRESS_PADDING;
|
||||||
}
|
}
|
||||||
if (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug()) {
|
|
||||||
qDebug("line:%d _packetData.changeSettings() wantCompression=%s targetSize=%d",__LINE__,
|
|
||||||
debug::valueOf(nodeData->getWantCompression()), targetSize);
|
|
||||||
}
|
|
||||||
_packetData.changeSettings(nodeData->getWantCompression(), targetSize); // will do reset
|
_packetData.changeSettings(nodeData->getWantCompression(), targetSize); // will do reset
|
||||||
|
|
||||||
}
|
}
|
||||||
|
OctreeServer::trackTreeWaitTime(lockWaitElapsedUsec);
|
||||||
|
OctreeServer::trackEncodeTime(encodeElapsedUsec);
|
||||||
|
OctreeServer::trackCompressAndWriteTime(compressAndWriteElapsedUsec);
|
||||||
|
OctreeServer::trackPacketSendingTime(packetSendingElapsedUsec);
|
||||||
|
|
||||||
|
quint64 endInside = usecTimestampNow();
|
||||||
|
quint64 elapsedInsideUsecs = endInside - startInside;
|
||||||
|
OctreeServer::trackInsideTime((float)elapsedInsideUsecs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -566,53 +514,21 @@ int OctreeSendThread::packetDistributor(const SharedNodePointer& node, OctreeQue
|
||||||
int elapsedmsec = (end - start)/USECS_PER_MSEC;
|
int elapsedmsec = (end - start)/USECS_PER_MSEC;
|
||||||
OctreeServer::trackLoopTime(elapsedmsec);
|
OctreeServer::trackLoopTime(elapsedmsec);
|
||||||
|
|
||||||
quint64 endCompressCalls = OctreePacketData::getCompressContentCalls();
|
// TODO: add these to stats page
|
||||||
int elapsedCompressCalls = endCompressCalls - startCompressCalls;
|
//quint64 endCompressCalls = OctreePacketData::getCompressContentCalls();
|
||||||
|
//int elapsedCompressCalls = endCompressCalls - startCompressCalls;
|
||||||
quint64 endCompressTimeMsecs = OctreePacketData::getCompressContentTime() / 1000;
|
//quint64 endCompressTimeMsecs = OctreePacketData::getCompressContentTime() / 1000;
|
||||||
int elapsedCompressTimeMsecs = endCompressTimeMsecs - startCompressTimeMsecs;
|
//int elapsedCompressTimeMsecs = endCompressTimeMsecs - startCompressTimeMsecs;
|
||||||
|
|
||||||
if (elapsedmsec > 100) {
|
|
||||||
if (elapsedmsec > 1000) {
|
|
||||||
int elapsedsec = (end - start)/1000000;
|
|
||||||
qDebug("WARNING! packetLoop() took %d seconds [%d milliseconds %d calls in compress] "
|
|
||||||
"to generate %d bytes in %d packets %d nodes still to send",
|
|
||||||
elapsedsec, elapsedCompressTimeMsecs, elapsedCompressCalls,
|
|
||||||
trueBytesSent, truePacketsSent, nodeData->nodeBag.count());
|
|
||||||
} else {
|
|
||||||
qDebug("WARNING! packetLoop() took %d milliseconds [%d milliseconds %d calls in compress] "
|
|
||||||
"to generate %d bytes in %d packets, %d nodes still to send",
|
|
||||||
elapsedmsec, elapsedCompressTimeMsecs, elapsedCompressCalls,
|
|
||||||
trueBytesSent, truePacketsSent, nodeData->nodeBag.count());
|
|
||||||
}
|
|
||||||
} else if (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug()) {
|
|
||||||
qDebug("packetLoop() took %d milliseconds [%d milliseconds %d calls in compress] "
|
|
||||||
"to generate %d bytes in %d packets, %d nodes still to send",
|
|
||||||
elapsedmsec, elapsedCompressTimeMsecs, elapsedCompressCalls,
|
|
||||||
trueBytesSent, truePacketsSent, nodeData->nodeBag.count());
|
|
||||||
}
|
|
||||||
|
|
||||||
// if after sending packets we've emptied our bag, then we want to remember that we've sent all
|
// if after sending packets we've emptied our bag, then we want to remember that we've sent all
|
||||||
// the voxels from the current view frustum
|
// the voxels from the current view frustum
|
||||||
if (nodeData->nodeBag.isEmpty()) {
|
if (nodeData->nodeBag.isEmpty()) {
|
||||||
nodeData->updateLastKnownViewFrustum();
|
nodeData->updateLastKnownViewFrustum();
|
||||||
nodeData->setViewSent(true);
|
nodeData->setViewSent(true);
|
||||||
if (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug()) {
|
|
||||||
nodeData->map.printStats();
|
|
||||||
}
|
|
||||||
nodeData->map.erase(); // It would be nice if we could save this, and only reset it when the view frustum changes
|
nodeData->map.erase(); // It would be nice if we could save this, and only reset it when the view frustum changes
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug()) {
|
|
||||||
qDebug("truePacketsSent=%d packetsSentThisInterval=%d maxPacketsPerInterval=%d "
|
|
||||||
"server PPI=%d nodePPS=%d nodePPI=%d",
|
|
||||||
truePacketsSent, packetsSentThisInterval, maxPacketsPerInterval,
|
|
||||||
_myServer->getPacketsPerClientPerInterval(), nodeData->getMaxOctreePacketsPerSecond(),
|
|
||||||
clientMaxPacketsPerInterval);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // end if bag wasn't empty, and so we sent stuff...
|
} // end if bag wasn't empty, and so we sent stuff...
|
||||||
|
|
||||||
return truePacketsSent;
|
return truePacketsSent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
/// Threaded processor for sending voxel packets to a single client
|
/// Threaded processor for sending voxel packets to a single client
|
||||||
class OctreeSendThread : public GenericThread {
|
class OctreeSendThread : public GenericThread {
|
||||||
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
OctreeSendThread(const QUuid& nodeUUID, OctreeServer* myServer);
|
OctreeSendThread(const QUuid& nodeUUID, OctreeServer* myServer);
|
||||||
virtual ~OctreeSendThread();
|
virtual ~OctreeSendThread();
|
||||||
|
|
|
@ -20,10 +20,150 @@
|
||||||
|
|
||||||
OctreeServer* OctreeServer::_instance = NULL;
|
OctreeServer* OctreeServer::_instance = NULL;
|
||||||
int OctreeServer::_clientCount = 0;
|
int OctreeServer::_clientCount = 0;
|
||||||
SimpleMovingAverage OctreeServer::_averageLoopTime(10000);
|
const int MOVING_AVERAGE_SAMPLE_COUNTS = 1000000;
|
||||||
SimpleMovingAverage OctreeServer::_averageEncodeTime(10000);
|
|
||||||
SimpleMovingAverage OctreeServer::_averageTreeWaitTime(10000);
|
float OctreeServer::SKIP_TIME = -1.0f; // use this for trackXXXTime() calls for non-times
|
||||||
SimpleMovingAverage OctreeServer::_averageNodeWaitTime(10000);
|
|
||||||
|
SimpleMovingAverage OctreeServer::_averageLoopTime(MOVING_AVERAGE_SAMPLE_COUNTS);
|
||||||
|
SimpleMovingAverage OctreeServer::_averageInsideTime(MOVING_AVERAGE_SAMPLE_COUNTS);
|
||||||
|
|
||||||
|
SimpleMovingAverage OctreeServer::_averageEncodeTime(MOVING_AVERAGE_SAMPLE_COUNTS);
|
||||||
|
SimpleMovingAverage OctreeServer::_averageShortEncodeTime(MOVING_AVERAGE_SAMPLE_COUNTS);
|
||||||
|
SimpleMovingAverage OctreeServer::_averageLongEncodeTime(MOVING_AVERAGE_SAMPLE_COUNTS);
|
||||||
|
SimpleMovingAverage OctreeServer::_averageExtraLongEncodeTime(MOVING_AVERAGE_SAMPLE_COUNTS);
|
||||||
|
int OctreeServer::_extraLongEncode = 0;
|
||||||
|
int OctreeServer::_longEncode = 0;
|
||||||
|
int OctreeServer::_shortEncode = 0;
|
||||||
|
int OctreeServer::_noEncode = 0;
|
||||||
|
|
||||||
|
SimpleMovingAverage OctreeServer::_averageTreeWaitTime(MOVING_AVERAGE_SAMPLE_COUNTS);
|
||||||
|
SimpleMovingAverage OctreeServer::_averageTreeShortWaitTime(MOVING_AVERAGE_SAMPLE_COUNTS);
|
||||||
|
SimpleMovingAverage OctreeServer::_averageTreeLongWaitTime(MOVING_AVERAGE_SAMPLE_COUNTS);
|
||||||
|
SimpleMovingAverage OctreeServer::_averageTreeExtraLongWaitTime(MOVING_AVERAGE_SAMPLE_COUNTS);
|
||||||
|
int OctreeServer::_extraLongTreeWait = 0;
|
||||||
|
int OctreeServer::_longTreeWait = 0;
|
||||||
|
int OctreeServer::_shortTreeWait = 0;
|
||||||
|
int OctreeServer::_noTreeWait = 0;
|
||||||
|
|
||||||
|
SimpleMovingAverage OctreeServer::_averageNodeWaitTime(MOVING_AVERAGE_SAMPLE_COUNTS);
|
||||||
|
|
||||||
|
SimpleMovingAverage OctreeServer::_averageCompressAndWriteTime(MOVING_AVERAGE_SAMPLE_COUNTS);
|
||||||
|
SimpleMovingAverage OctreeServer::_averageShortCompressTime(MOVING_AVERAGE_SAMPLE_COUNTS);
|
||||||
|
SimpleMovingAverage OctreeServer::_averageLongCompressTime(MOVING_AVERAGE_SAMPLE_COUNTS);
|
||||||
|
SimpleMovingAverage OctreeServer::_averageExtraLongCompressTime(MOVING_AVERAGE_SAMPLE_COUNTS);
|
||||||
|
int OctreeServer::_extraLongCompress = 0;
|
||||||
|
int OctreeServer::_longCompress = 0;
|
||||||
|
int OctreeServer::_shortCompress = 0;
|
||||||
|
int OctreeServer::_noCompress = 0;
|
||||||
|
|
||||||
|
SimpleMovingAverage OctreeServer::_averagePacketSendingTime(MOVING_AVERAGE_SAMPLE_COUNTS);
|
||||||
|
int OctreeServer::_noSend = 0;
|
||||||
|
|
||||||
|
|
||||||
|
void OctreeServer::resetSendingStats() {
|
||||||
|
_averageLoopTime.reset();
|
||||||
|
|
||||||
|
_averageEncodeTime.reset();
|
||||||
|
_averageShortEncodeTime.reset();
|
||||||
|
_averageLongEncodeTime.reset();
|
||||||
|
_averageExtraLongEncodeTime.reset();
|
||||||
|
_extraLongEncode = 0;
|
||||||
|
_longEncode = 0;
|
||||||
|
_shortEncode = 0;
|
||||||
|
_noEncode = 0;
|
||||||
|
|
||||||
|
_averageInsideTime.reset();
|
||||||
|
_averageTreeWaitTime.reset();
|
||||||
|
_averageTreeShortWaitTime.reset();
|
||||||
|
_averageTreeLongWaitTime.reset();
|
||||||
|
_averageTreeExtraLongWaitTime.reset();
|
||||||
|
_extraLongTreeWait = 0;
|
||||||
|
_longTreeWait = 0;
|
||||||
|
_shortTreeWait = 0;
|
||||||
|
_noTreeWait = 0;
|
||||||
|
|
||||||
|
_averageNodeWaitTime.reset();
|
||||||
|
|
||||||
|
_averageCompressAndWriteTime.reset();
|
||||||
|
_averageShortCompressTime.reset();
|
||||||
|
_averageLongCompressTime.reset();
|
||||||
|
_averageExtraLongCompressTime.reset();
|
||||||
|
_extraLongCompress = 0;
|
||||||
|
_longCompress = 0;
|
||||||
|
_shortCompress = 0;
|
||||||
|
_noCompress = 0;
|
||||||
|
|
||||||
|
_averagePacketSendingTime.reset();
|
||||||
|
_noSend = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OctreeServer::trackEncodeTime(float time) {
|
||||||
|
const float MAX_SHORT_TIME = 10.0f;
|
||||||
|
const float MAX_LONG_TIME = 100.0f;
|
||||||
|
|
||||||
|
if (time == SKIP_TIME) {
|
||||||
|
_noEncode++;
|
||||||
|
time = 0.0f;
|
||||||
|
} else if (time <= MAX_SHORT_TIME) {
|
||||||
|
_shortEncode++;
|
||||||
|
_averageShortEncodeTime.updateAverage(time);
|
||||||
|
} else if (time <= MAX_LONG_TIME) {
|
||||||
|
_longEncode++;
|
||||||
|
_averageLongEncodeTime.updateAverage(time);
|
||||||
|
} else {
|
||||||
|
_extraLongEncode++;
|
||||||
|
_averageExtraLongEncodeTime.updateAverage(time);
|
||||||
|
}
|
||||||
|
_averageEncodeTime.updateAverage(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OctreeServer::trackTreeWaitTime(float time) {
|
||||||
|
const float MAX_SHORT_TIME = 10.0f;
|
||||||
|
const float MAX_LONG_TIME = 100.0f;
|
||||||
|
if (time == SKIP_TIME) {
|
||||||
|
_noTreeWait++;
|
||||||
|
time = 0.0f;
|
||||||
|
} else if (time <= MAX_SHORT_TIME) {
|
||||||
|
_shortTreeWait++;
|
||||||
|
_averageTreeShortWaitTime.updateAverage(time);
|
||||||
|
} else if (time <= MAX_LONG_TIME) {
|
||||||
|
_longTreeWait++;
|
||||||
|
_averageTreeLongWaitTime.updateAverage(time);
|
||||||
|
} else {
|
||||||
|
_extraLongTreeWait++;
|
||||||
|
_averageTreeExtraLongWaitTime.updateAverage(time);
|
||||||
|
}
|
||||||
|
_averageTreeWaitTime.updateAverage(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OctreeServer::trackCompressAndWriteTime(float time) {
|
||||||
|
const float MAX_SHORT_TIME = 10.0f;
|
||||||
|
const float MAX_LONG_TIME = 100.0f;
|
||||||
|
if (time == SKIP_TIME) {
|
||||||
|
_noCompress++;
|
||||||
|
time = 0.0f;
|
||||||
|
} else if (time <= MAX_SHORT_TIME) {
|
||||||
|
_shortCompress++;
|
||||||
|
_averageShortCompressTime.updateAverage(time);
|
||||||
|
} else if (time <= MAX_LONG_TIME) {
|
||||||
|
_longCompress++;
|
||||||
|
_averageLongCompressTime.updateAverage(time);
|
||||||
|
} else {
|
||||||
|
_extraLongCompress++;
|
||||||
|
_averageExtraLongCompressTime.updateAverage(time);
|
||||||
|
}
|
||||||
|
_averageCompressAndWriteTime.updateAverage(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OctreeServer::trackPacketSendingTime(float time) {
|
||||||
|
if (time == SKIP_TIME) {
|
||||||
|
_noSend++;
|
||||||
|
time = 0.0f;
|
||||||
|
}
|
||||||
|
_averagePacketSendingTime.updateAverage(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void OctreeServer::attachQueryNodeToNode(Node* newNode) {
|
void OctreeServer::attachQueryNodeToNode(Node* newNode) {
|
||||||
if (!newNode->getLinkedData()) {
|
if (!newNode->getLinkedData()) {
|
||||||
|
@ -55,9 +195,11 @@ OctreeServer::OctreeServer(const QByteArray& packet) :
|
||||||
{
|
{
|
||||||
_instance = this;
|
_instance = this;
|
||||||
_averageLoopTime.updateAverage(0);
|
_averageLoopTime.updateAverage(0);
|
||||||
|
qDebug() << "Octree server starting... [" << this << "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
OctreeServer::~OctreeServer() {
|
OctreeServer::~OctreeServer() {
|
||||||
|
qDebug() << qPrintable(_safeServerName) << "server shutting down... [" << this << "]";
|
||||||
if (_parsedArgV) {
|
if (_parsedArgV) {
|
||||||
for (int i = 0; i < _argc; i++) {
|
for (int i = 0; i < _argc; i++) {
|
||||||
delete[] _parsedArgV[i];
|
delete[] _parsedArgV[i];
|
||||||
|
@ -82,7 +224,7 @@ OctreeServer::~OctreeServer() {
|
||||||
|
|
||||||
delete _jurisdiction;
|
delete _jurisdiction;
|
||||||
_jurisdiction = NULL;
|
_jurisdiction = NULL;
|
||||||
qDebug() << "OctreeServer::~OctreeServer()... DONE";
|
qDebug() << qPrintable(_safeServerName) << "server DONE shutting down... [" << this << "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
void OctreeServer::initHTTPManager(int port) {
|
void OctreeServer::initHTTPManager(int port) {
|
||||||
|
@ -121,6 +263,7 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString&
|
||||||
showStats = true;
|
showStats = true;
|
||||||
} else if (path == "/resetStats") {
|
} else if (path == "/resetStats") {
|
||||||
_octreeInboundPacketProcessor->resetStats();
|
_octreeInboundPacketProcessor->resetStats();
|
||||||
|
resetSendingStats();
|
||||||
showStats = true;
|
showStats = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -255,7 +398,9 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString&
|
||||||
statsString += "\r\n";
|
statsString += "\r\n";
|
||||||
|
|
||||||
// display outbound packet stats
|
// display outbound packet stats
|
||||||
statsString += QString("<b>%1 Outbound Packet Statistics...</b>\r\n").arg(getMyServerName());
|
statsString += QString("<b>%1 Outbound Packet Statistics... "
|
||||||
|
"<a href='/resetStats'>[RESET]</a></b>\r\n").arg(getMyServerName());
|
||||||
|
|
||||||
quint64 totalOutboundPackets = OctreeSendThread::_totalPackets;
|
quint64 totalOutboundPackets = OctreeSendThread::_totalPackets;
|
||||||
quint64 totalOutboundBytes = OctreeSendThread::_totalBytes;
|
quint64 totalOutboundBytes = OctreeSendThread::_totalBytes;
|
||||||
quint64 totalWastedBytes = OctreeSendThread::_totalWastedBytes;
|
quint64 totalWastedBytes = OctreeSendThread::_totalWastedBytes;
|
||||||
|
@ -263,7 +408,7 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString&
|
||||||
quint64 totalBytesOfBitMasks = OctreePacketData::getTotalBytesOfBitMasks();
|
quint64 totalBytesOfBitMasks = OctreePacketData::getTotalBytesOfBitMasks();
|
||||||
quint64 totalBytesOfColor = OctreePacketData::getTotalBytesOfColor();
|
quint64 totalBytesOfColor = OctreePacketData::getTotalBytesOfColor();
|
||||||
|
|
||||||
const int COLUMN_WIDTH = 10;
|
const int COLUMN_WIDTH = 19;
|
||||||
statsString += QString(" Configured Max PPS/Client: %1 pps/client\r\n")
|
statsString += QString(" Configured Max PPS/Client: %1 pps/client\r\n")
|
||||||
.arg(locale.toString((uint)getPacketsPerClientPerSecond()).rightJustified(COLUMN_WIDTH, ' '));
|
.arg(locale.toString((uint)getPacketsPerClientPerSecond()).rightJustified(COLUMN_WIDTH, ' '));
|
||||||
statsString += QString(" Configured Max PPS/Server: %1 pps/server\r\n\r\n")
|
statsString += QString(" Configured Max PPS/Server: %1 pps/server\r\n\r\n")
|
||||||
|
@ -272,21 +417,130 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString&
|
||||||
.arg(locale.toString((uint)getCurrentClientCount()).rightJustified(COLUMN_WIDTH, ' '));
|
.arg(locale.toString((uint)getCurrentClientCount()).rightJustified(COLUMN_WIDTH, ' '));
|
||||||
|
|
||||||
float averageLoopTime = getAverageLoopTime();
|
float averageLoopTime = getAverageLoopTime();
|
||||||
statsString += QString().sprintf(" Average packetLoop() time: %5.2f msecs\r\n", averageLoopTime);
|
statsString += QString().sprintf(" Average packetLoop() time: %7.2f msecs\r\n", averageLoopTime);
|
||||||
qDebug() << "averageLoopTime=" << averageLoopTime;
|
|
||||||
|
|
||||||
float averageEncodeTime = getAverageEncodeTime();
|
float averageInsideTime = getAverageInsideTime();
|
||||||
statsString += QString().sprintf(" Average encode time: %5.2f msecs\r\n", averageEncodeTime);
|
statsString += QString().sprintf(" Average 'inside' time: %9.2f usecs\r\n\r\n", averageInsideTime);
|
||||||
qDebug() << "averageEncodeTime=" << averageEncodeTime;
|
|
||||||
|
|
||||||
|
int allWaitTimes = _extraLongTreeWait +_longTreeWait + _shortTreeWait + _noTreeWait;
|
||||||
|
|
||||||
float averageTreeWaitTime = getAverageTreeWaitTime();
|
float averageTreeWaitTime = getAverageTreeWaitTime();
|
||||||
statsString += QString().sprintf(" Average tree lock wait time: %7.2f usecs\r\n", averageTreeWaitTime);
|
statsString += QString().sprintf(" Average tree lock wait time:"
|
||||||
qDebug() << "averageTreeWaitTime=" << averageTreeWaitTime;
|
" %9.2f usecs samples: %12d \r\n",
|
||||||
|
averageTreeWaitTime, allWaitTimes);
|
||||||
|
|
||||||
|
float zeroVsTotal = (allWaitTimes > 0) ? ((float)_noTreeWait / (float)allWaitTimes) : 0.0f;
|
||||||
|
statsString += QString().sprintf(" No Lock Wait:"
|
||||||
|
" (%6.2f%%) samples: %12d \r\n",
|
||||||
|
zeroVsTotal * AS_PERCENT, _noTreeWait);
|
||||||
|
|
||||||
|
float shortVsTotal = (allWaitTimes > 0) ? ((float)_shortTreeWait / (float)allWaitTimes) : 0.0f;
|
||||||
|
statsString += QString().sprintf(" Avg tree lock short wait time:"
|
||||||
|
" %9.2f usecs (%6.2f%%) samples: %12d \r\n",
|
||||||
|
_averageTreeShortWaitTime.getAverage(),
|
||||||
|
shortVsTotal * AS_PERCENT, _shortTreeWait);
|
||||||
|
|
||||||
|
float longVsTotal = (allWaitTimes > 0) ? ((float)_longTreeWait / (float)allWaitTimes) : 0.0f;
|
||||||
|
statsString += QString().sprintf(" Avg tree lock long wait time:"
|
||||||
|
" %9.2f usecs (%6.2f%%) samples: %12d \r\n",
|
||||||
|
_averageTreeLongWaitTime.getAverage(),
|
||||||
|
longVsTotal * AS_PERCENT, _longTreeWait);
|
||||||
|
|
||||||
|
float extraLongVsTotal = (allWaitTimes > 0) ? ((float)_extraLongTreeWait / (float)allWaitTimes) : 0.0f;
|
||||||
|
statsString += QString().sprintf(" Avg tree lock extra long wait time:"
|
||||||
|
" %9.2f usecs (%6.2f%%) samples: %12d \r\n\r\n",
|
||||||
|
_averageTreeExtraLongWaitTime.getAverage(),
|
||||||
|
extraLongVsTotal * AS_PERCENT, _extraLongTreeWait);
|
||||||
|
|
||||||
|
float averageEncodeTime = getAverageEncodeTime();
|
||||||
|
statsString += QString().sprintf(" Average encode time: %9.2f usecs\r\n", averageEncodeTime);
|
||||||
|
|
||||||
|
int allEncodeTimes = _noEncode + _shortEncode + _longEncode + _extraLongEncode;
|
||||||
|
|
||||||
|
float zeroVsTotalEncode = (allEncodeTimes > 0) ? ((float)_noEncode / (float)allEncodeTimes) : 0.0f;
|
||||||
|
statsString += QString().sprintf(" No Encode:"
|
||||||
|
" (%6.2f%%) samples: %12d \r\n",
|
||||||
|
zeroVsTotalEncode * AS_PERCENT, _noEncode);
|
||||||
|
|
||||||
|
float shortVsTotalEncode = (allEncodeTimes > 0) ? ((float)_shortEncode / (float)allEncodeTimes) : 0.0f;
|
||||||
|
statsString += QString().sprintf(" Avg short encode time:"
|
||||||
|
" %9.2f usecs (%6.2f%%) samples: %12d \r\n",
|
||||||
|
_averageShortEncodeTime.getAverage(),
|
||||||
|
shortVsTotalEncode * AS_PERCENT, _shortEncode);
|
||||||
|
|
||||||
|
float longVsTotalEncode = (allEncodeTimes > 0) ? ((float)_longEncode / (float)allEncodeTimes) : 0.0f;
|
||||||
|
statsString += QString().sprintf(" Avg long encode time:"
|
||||||
|
" %9.2f usecs (%6.2f%%) samples: %12d \r\n",
|
||||||
|
_averageLongEncodeTime.getAverage(),
|
||||||
|
longVsTotalEncode * AS_PERCENT, _longEncode);
|
||||||
|
|
||||||
|
float extraLongVsTotalEncode = (allEncodeTimes > 0) ? ((float)_extraLongEncode / (float)allEncodeTimes) : 0.0f;
|
||||||
|
statsString += QString().sprintf(" Avg extra long encode time:"
|
||||||
|
" %9.2f usecs (%6.2f%%) samples: %12d \r\n\r\n",
|
||||||
|
_averageExtraLongEncodeTime.getAverage(),
|
||||||
|
extraLongVsTotalEncode * AS_PERCENT, _extraLongEncode);
|
||||||
|
|
||||||
|
|
||||||
|
float averageCompressAndWriteTime = getAverageCompressAndWriteTime();
|
||||||
|
statsString += QString().sprintf(" Average compress and write time: %9.2f usecs\r\n", averageCompressAndWriteTime);
|
||||||
|
|
||||||
|
int allCompressTimes = _noCompress + _shortCompress + _longCompress + _extraLongCompress;
|
||||||
|
|
||||||
|
float zeroVsTotalCompress = (allCompressTimes > 0) ? ((float)_noCompress / (float)allCompressTimes) : 0.0f;
|
||||||
|
statsString += QString().sprintf(" No compression:"
|
||||||
|
" (%6.2f%%) samples: %12d \r\n",
|
||||||
|
zeroVsTotalCompress * AS_PERCENT, _noCompress);
|
||||||
|
|
||||||
|
float shortVsTotalCompress = (allCompressTimes > 0) ? ((float)_shortCompress / (float)allCompressTimes) : 0.0f;
|
||||||
|
statsString += QString().sprintf(" Avg short compress time:"
|
||||||
|
" %9.2f usecs (%6.2f%%) samples: %12d \r\n",
|
||||||
|
_averageShortCompressTime.getAverage(),
|
||||||
|
shortVsTotalCompress * AS_PERCENT, _shortCompress);
|
||||||
|
|
||||||
|
float longVsTotalCompress = (allCompressTimes > 0) ? ((float)_longCompress / (float)allCompressTimes) : 0.0f;
|
||||||
|
statsString += QString().sprintf(" Avg long compress time:"
|
||||||
|
" %9.2f usecs (%6.2f%%) samples: %12d \r\n",
|
||||||
|
_averageLongCompressTime.getAverage(),
|
||||||
|
longVsTotalCompress * AS_PERCENT, _longCompress);
|
||||||
|
|
||||||
|
float extraLongVsTotalCompress = (allCompressTimes > 0) ? ((float)_extraLongCompress / (float)allCompressTimes) : 0.0f;
|
||||||
|
statsString += QString().sprintf(" Avg extra long compress time:"
|
||||||
|
" %9.2f usecs (%6.2f%%) samples: %12d \r\n\r\n",
|
||||||
|
_averageExtraLongCompressTime.getAverage(),
|
||||||
|
extraLongVsTotalCompress * AS_PERCENT, _extraLongCompress);
|
||||||
|
|
||||||
|
float averagePacketSendingTime = getAveragePacketSendingTime();
|
||||||
|
statsString += QString().sprintf(" Average packet sending time: %9.2f usecs (includes node lock)\r\n",
|
||||||
|
averagePacketSendingTime);
|
||||||
|
|
||||||
|
float noVsTotalSend = (_averagePacketSendingTime.getSampleCount() > 0) ?
|
||||||
|
((float)_noSend / (float)_averagePacketSendingTime.getSampleCount()) : 0.0f;
|
||||||
|
statsString += QString().sprintf(" Not sending:"
|
||||||
|
" (%6.2f%%) samples: %12d \r\n",
|
||||||
|
noVsTotalSend * AS_PERCENT, _noSend);
|
||||||
|
|
||||||
float averageNodeWaitTime = getAverageNodeWaitTime();
|
float averageNodeWaitTime = getAverageNodeWaitTime();
|
||||||
statsString += QString().sprintf(" Average node lock wait time: %7.2f usecs\r\n", averageNodeWaitTime);
|
statsString += QString().sprintf(" Average node lock wait time: %9.2f usecs\r\n", averageNodeWaitTime);
|
||||||
qDebug() << "averageNodeWaitTime=" << averageNodeWaitTime;
|
|
||||||
|
statsString += QString().sprintf("--------------------------------------------------------------\r\n");
|
||||||
|
|
||||||
|
float encodeToInsidePercent = averageInsideTime == 0.0f ? 0.0f : (averageEncodeTime / averageInsideTime) * AS_PERCENT;
|
||||||
|
statsString += QString().sprintf(" encode ratio: %5.2f%%\r\n",
|
||||||
|
encodeToInsidePercent);
|
||||||
|
|
||||||
|
float waitToInsidePercent = averageInsideTime == 0.0f ? 0.0f
|
||||||
|
: ((averageTreeWaitTime + averageNodeWaitTime) / averageInsideTime) * AS_PERCENT;
|
||||||
|
statsString += QString().sprintf(" waiting ratio: %5.2f%%\r\n", waitToInsidePercent);
|
||||||
|
|
||||||
|
float compressAndWriteToInsidePercent = averageInsideTime == 0.0f ? 0.0f
|
||||||
|
: (averageCompressAndWriteTime / averageInsideTime) * AS_PERCENT;
|
||||||
|
statsString += QString().sprintf(" compress and write ratio: %5.2f%%\r\n",
|
||||||
|
compressAndWriteToInsidePercent);
|
||||||
|
|
||||||
|
float sendingToInsidePercent = averageInsideTime == 0.0f ? 0.0f
|
||||||
|
: (averagePacketSendingTime / averageInsideTime) * AS_PERCENT;
|
||||||
|
statsString += QString().sprintf(" sending ratio: %5.2f%%\r\n", sendingToInsidePercent);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
statsString += QString("\r\n");
|
statsString += QString("\r\n");
|
||||||
|
@ -556,6 +810,7 @@ void OctreeServer::readPendingDatagrams() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void OctreeServer::run() {
|
void OctreeServer::run() {
|
||||||
|
_safeServerName = getMyServerName();
|
||||||
// Before we do anything else, create our tree...
|
// Before we do anything else, create our tree...
|
||||||
_tree = createTree();
|
_tree = createTree();
|
||||||
|
|
||||||
|
@ -611,6 +866,7 @@ void OctreeServer::run() {
|
||||||
connect(nodeList, SIGNAL(nodeAdded(SharedNodePointer)), SLOT(nodeAdded(SharedNodePointer)));
|
connect(nodeList, SIGNAL(nodeAdded(SharedNodePointer)), SLOT(nodeAdded(SharedNodePointer)));
|
||||||
connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)),SLOT(nodeKilled(SharedNodePointer)));
|
connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)),SLOT(nodeKilled(SharedNodePointer)));
|
||||||
|
|
||||||
|
|
||||||
// we need to ask the DS about agents so we can ping/reply with them
|
// we need to ask the DS about agents so we can ping/reply with them
|
||||||
nodeList->addNodeTypeToInterestSet(NodeType::Agent);
|
nodeList->addNodeTypeToInterestSet(NodeType::Agent);
|
||||||
|
|
||||||
|
@ -732,17 +988,26 @@ void OctreeServer::run() {
|
||||||
|
|
||||||
void OctreeServer::nodeAdded(SharedNodePointer node) {
|
void OctreeServer::nodeAdded(SharedNodePointer node) {
|
||||||
// we might choose to use this notifier to track clients in a pending state
|
// we might choose to use this notifier to track clients in a pending state
|
||||||
|
qDebug() << qPrintable(_safeServerName) << "server added node:" << *node;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OctreeServer::nodeKilled(SharedNodePointer node) {
|
void OctreeServer::nodeKilled(SharedNodePointer node) {
|
||||||
|
qDebug() << qPrintable(_safeServerName) << "server killed node:" << *node;
|
||||||
OctreeQueryNode* nodeData = static_cast<OctreeQueryNode*>(node->getLinkedData());
|
OctreeQueryNode* nodeData = static_cast<OctreeQueryNode*>(node->getLinkedData());
|
||||||
if (nodeData) {
|
if (nodeData) {
|
||||||
// Note: It should be safe to do this without locking the node, because if any other threads
|
qDebug() << qPrintable(_safeServerName) << "server resetting Linked Data for node:" << *node;
|
||||||
// are using the SharedNodePointer, then they have a reference to the SharedNodePointer and the deleteLater()
|
node->setLinkedData(NULL); // set this first in case another thread comes through and tryes to acces this
|
||||||
// won't actually delete it until all threads have released their references to the pointer.
|
qDebug() << qPrintable(_safeServerName) << "server deleting Linked Data for node:" << *node;
|
||||||
// But we can and should clear the linked data so that no one else tries to access it.
|
|
||||||
nodeData->deleteLater();
|
nodeData->deleteLater();
|
||||||
node->setLinkedData(NULL);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OctreeServer::aboutToFinish() {
|
||||||
|
qDebug() << qPrintable(_safeServerName) << "server STARTING about to finish...";
|
||||||
|
foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
|
||||||
|
qDebug() << qPrintable(_safeServerName) << "server about to finish while node still connected node:" << *node;
|
||||||
|
nodeKilled(node);
|
||||||
|
}
|
||||||
|
qDebug() << qPrintable(_safeServerName) << "server ENDING about to finish...";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -73,19 +73,34 @@ public:
|
||||||
virtual int sendSpecialPacket(const SharedNodePointer& node) { return 0; }
|
virtual int sendSpecialPacket(const SharedNodePointer& node) { return 0; }
|
||||||
|
|
||||||
static void attachQueryNodeToNode(Node* newNode);
|
static void attachQueryNodeToNode(Node* newNode);
|
||||||
|
|
||||||
|
static float SKIP_TIME; // use this for trackXXXTime() calls for non-times
|
||||||
|
|
||||||
static void trackLoopTime(float time) { _averageLoopTime.updateAverage(time); }
|
static void trackLoopTime(float time) { _averageLoopTime.updateAverage(time); }
|
||||||
static float getAverageLoopTime() { return _averageLoopTime.getAverage(); }
|
static float getAverageLoopTime() { return _averageLoopTime.getAverage(); }
|
||||||
|
|
||||||
static void trackEncodeTime(float time) { _averageEncodeTime.updateAverage(time); }
|
static void trackEncodeTime(float time);
|
||||||
static float getAverageEncodeTime() { return _averageEncodeTime.getAverage(); }
|
static float getAverageEncodeTime() { return _averageEncodeTime.getAverage(); }
|
||||||
|
|
||||||
static void trackTreeWaitTime(float time) { _averageTreeWaitTime.updateAverage(time); }
|
static void trackInsideTime(float time) { _averageInsideTime.updateAverage(time); }
|
||||||
|
static float getAverageInsideTime() { return _averageInsideTime.getAverage(); }
|
||||||
|
|
||||||
|
static void trackTreeWaitTime(float time);
|
||||||
static float getAverageTreeWaitTime() { return _averageTreeWaitTime.getAverage(); }
|
static float getAverageTreeWaitTime() { return _averageTreeWaitTime.getAverage(); }
|
||||||
|
|
||||||
static void trackNodeWaitTime(float time) { _averageNodeWaitTime.updateAverage(time); }
|
static void trackNodeWaitTime(float time) { _averageNodeWaitTime.updateAverage(time); }
|
||||||
static float getAverageNodeWaitTime() { return _averageNodeWaitTime.getAverage(); }
|
static float getAverageNodeWaitTime() { return _averageNodeWaitTime.getAverage(); }
|
||||||
|
|
||||||
|
static void trackCompressAndWriteTime(float time);
|
||||||
|
static float getAverageCompressAndWriteTime() { return _averageCompressAndWriteTime.getAverage(); }
|
||||||
|
|
||||||
|
static void trackPacketSendingTime(float time);
|
||||||
|
static float getAveragePacketSendingTime() { return _averagePacketSendingTime.getAverage(); }
|
||||||
|
|
||||||
bool handleHTTPRequest(HTTPConnection* connection, const QString& path);
|
bool handleHTTPRequest(HTTPConnection* connection, const QString& path);
|
||||||
|
|
||||||
|
virtual void aboutToFinish();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
/// runs the voxel server assignment
|
/// runs the voxel server assignment
|
||||||
void run();
|
void run();
|
||||||
|
@ -96,6 +111,7 @@ public slots:
|
||||||
protected:
|
protected:
|
||||||
void parsePayload();
|
void parsePayload();
|
||||||
void initHTTPManager(int port);
|
void initHTTPManager(int port);
|
||||||
|
void resetSendingStats();
|
||||||
|
|
||||||
int _argc;
|
int _argc;
|
||||||
const char** _argv;
|
const char** _argv;
|
||||||
|
@ -120,12 +136,44 @@ protected:
|
||||||
|
|
||||||
time_t _started;
|
time_t _started;
|
||||||
quint64 _startedUSecs;
|
quint64 _startedUSecs;
|
||||||
|
QString _safeServerName;
|
||||||
|
|
||||||
static int _clientCount;
|
static int _clientCount;
|
||||||
static SimpleMovingAverage _averageLoopTime;
|
static SimpleMovingAverage _averageLoopTime;
|
||||||
|
|
||||||
static SimpleMovingAverage _averageEncodeTime;
|
static SimpleMovingAverage _averageEncodeTime;
|
||||||
|
static SimpleMovingAverage _averageShortEncodeTime;
|
||||||
|
static SimpleMovingAverage _averageLongEncodeTime;
|
||||||
|
static SimpleMovingAverage _averageExtraLongEncodeTime;
|
||||||
|
static int _extraLongEncode;
|
||||||
|
static int _longEncode;
|
||||||
|
static int _shortEncode;
|
||||||
|
static int _noEncode;
|
||||||
|
|
||||||
|
static SimpleMovingAverage _averageInsideTime;
|
||||||
static SimpleMovingAverage _averageTreeWaitTime;
|
static SimpleMovingAverage _averageTreeWaitTime;
|
||||||
|
static SimpleMovingAverage _averageTreeShortWaitTime;
|
||||||
|
static SimpleMovingAverage _averageTreeLongWaitTime;
|
||||||
|
static SimpleMovingAverage _averageTreeExtraLongWaitTime;
|
||||||
|
static int _extraLongTreeWait;
|
||||||
|
static int _longTreeWait;
|
||||||
|
static int _shortTreeWait;
|
||||||
|
static int _noTreeWait;
|
||||||
|
|
||||||
static SimpleMovingAverage _averageNodeWaitTime;
|
static SimpleMovingAverage _averageNodeWaitTime;
|
||||||
|
|
||||||
|
static SimpleMovingAverage _averageCompressAndWriteTime;
|
||||||
|
static SimpleMovingAverage _averageShortCompressTime;
|
||||||
|
static SimpleMovingAverage _averageLongCompressTime;
|
||||||
|
static SimpleMovingAverage _averageExtraLongCompressTime;
|
||||||
|
static int _extraLongCompress;
|
||||||
|
static int _longCompress;
|
||||||
|
static int _shortCompress;
|
||||||
|
static int _noCompress;
|
||||||
|
|
||||||
|
static SimpleMovingAverage _averagePacketSendingTime;
|
||||||
|
static int _noSend;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // __octree_server__OctreeServer__
|
#endif // __octree_server__OctreeServer__
|
||||||
|
|
|
@ -29,7 +29,6 @@ var CHANCE_OF_BIG_MOVE = 0.1;
|
||||||
|
|
||||||
var isMoving = false;
|
var isMoving = false;
|
||||||
var isTurningHead = false;
|
var isTurningHead = false;
|
||||||
var isPlayingAudio = false;
|
|
||||||
|
|
||||||
var X_MIN = 0.0;
|
var X_MIN = 0.0;
|
||||||
var X_MAX = 5.0;
|
var X_MAX = 5.0;
|
||||||
|
@ -60,20 +59,11 @@ function clamp(val, min, max){
|
||||||
}
|
}
|
||||||
|
|
||||||
// Play a random sound from a list of conversational audio clips
|
// Play a random sound from a list of conversational audio clips
|
||||||
function audioDone() {
|
|
||||||
isPlayingAudio = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var AVERAGE_AUDIO_LENGTH = 8000;
|
var AVERAGE_AUDIO_LENGTH = 8000;
|
||||||
function playRandomSound(position) {
|
function playRandomSound() {
|
||||||
if (!isPlayingAudio) {
|
if (!Agent.isPlayingAvatarSound) {
|
||||||
var whichSound = Math.floor((Math.random() * sounds.length) % sounds.length);
|
var whichSound = Math.floor((Math.random() * sounds.length) % sounds.length);
|
||||||
var audioOptions = new AudioInjectionOptions();
|
Agent.playAvatarSound(sounds[whichSound]);
|
||||||
audioOptions.volume = 0.25 + (Math.random() * 0.75);
|
|
||||||
audioOptions.position = position;
|
|
||||||
Audio.playSound(sounds[whichSound], audioOptions);
|
|
||||||
isPlayingAudio = true;
|
|
||||||
Script.setTimeout(audioDone, AVERAGE_AUDIO_LENGTH);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,6 +94,7 @@ Avatar.skeletonModelURL = "https://s3-us-west-1.amazonaws.com/highfidelity-publi
|
||||||
Avatar.billboardURL = "https://s3-us-west-1.amazonaws.com/highfidelity-public/meshes/billboards/bot" + botNumber + ".png";
|
Avatar.billboardURL = "https://s3-us-west-1.amazonaws.com/highfidelity-public/meshes/billboards/bot" + botNumber + ".png";
|
||||||
|
|
||||||
Agent.isAvatar = true;
|
Agent.isAvatar = true;
|
||||||
|
Agent.isListeningToAudioStream = true;
|
||||||
|
|
||||||
// change the avatar's position to the random one
|
// change the avatar's position to the random one
|
||||||
Avatar.position = firstPosition;
|
Avatar.position = firstPosition;
|
||||||
|
@ -111,10 +102,10 @@ printVector("New bot, position = ", Avatar.position);
|
||||||
|
|
||||||
function updateBehavior(deltaTime) {
|
function updateBehavior(deltaTime) {
|
||||||
if (Math.random() < CHANCE_OF_SOUND) {
|
if (Math.random() < CHANCE_OF_SOUND) {
|
||||||
playRandomSound(Avatar.position);
|
playRandomSound();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPlayingAudio) {
|
if (Agent.isPlayingAvatarSound) {
|
||||||
Avatar.handPosition = Vec3.sum(Avatar.position, Quat.getFront(Avatar.orientation));
|
Avatar.handPosition = Vec3.sum(Avatar.position, Quat.getFront(Avatar.orientation));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,18 +13,23 @@ var yawDirection = -1;
|
||||||
var yaw = 45;
|
var yaw = 45;
|
||||||
var yawMax = 70;
|
var yawMax = 70;
|
||||||
var yawMin = 20;
|
var yawMin = 20;
|
||||||
|
var vantagePoint = {x: 5000, y: 500, z: 5000};
|
||||||
|
|
||||||
var isLocal = false;
|
var isLocal = false;
|
||||||
|
|
||||||
// set up our VoxelViewer with a position and orientation
|
// set up our VoxelViewer with a position and orientation
|
||||||
var orientation = Quat.fromPitchYawRollDegrees(0, yaw, 0);
|
var orientation = Quat.fromPitchYawRollDegrees(0, yaw, 0);
|
||||||
|
|
||||||
|
function getRandomInt(min, max) {
|
||||||
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
|
}
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
if (isLocal) {
|
if (isLocal) {
|
||||||
MyAvatar.position = {x: 5000, y: 500, z: 5000};
|
MyAvatar.position = vantagePoint;
|
||||||
MyAvatar.orientation = orientation;
|
MyAvatar.orientation = orientation;
|
||||||
} else {
|
} else {
|
||||||
VoxelViewer.setPosition({x: 5000, y: 500, z: 5000});
|
VoxelViewer.setPosition(vantagePoint);
|
||||||
VoxelViewer.setOrientation(orientation);
|
VoxelViewer.setOrientation(orientation);
|
||||||
VoxelViewer.queryOctree();
|
VoxelViewer.queryOctree();
|
||||||
Agent.isAvatar = true;
|
Agent.isAvatar = true;
|
||||||
|
@ -38,21 +43,26 @@ function keepLooking(deltaTime) {
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
count++;
|
count++;
|
||||||
if (count % 10 == 0) {
|
if (count % getRandomInt(5, 15) == 0) {
|
||||||
yaw += yawDirection;
|
yaw += yawDirection;
|
||||||
orientation = Quat.fromPitchYawRollDegrees(0, yaw, 0);
|
orientation = Quat.fromPitchYawRollDegrees(0, yaw, 0);
|
||||||
if (yaw > yawMax || yaw < yawMin) {
|
if (yaw > yawMax || yaw < yawMin) {
|
||||||
yawDirection = yawDirection * -1;
|
yawDirection = yawDirection * -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
print("calling VoxelViewer.queryOctree()... count=" + count + " yaw=" + yaw);
|
if (count % 10000 == 0) {
|
||||||
|
print("calling VoxelViewer.queryOctree()... count=" + count + " yaw=" + yaw);
|
||||||
|
}
|
||||||
|
|
||||||
if (isLocal) {
|
if (isLocal) {
|
||||||
MyAvatar.orientation = orientation;
|
MyAvatar.orientation = orientation;
|
||||||
} else {
|
} else {
|
||||||
VoxelViewer.setOrientation(orientation);
|
VoxelViewer.setOrientation(orientation);
|
||||||
VoxelViewer.queryOctree();
|
VoxelViewer.queryOctree();
|
||||||
print("VoxelViewer.getOctreeElementsCount()=" + VoxelViewer.getOctreeElementsCount());
|
|
||||||
|
if (count % 10000 == 0) {
|
||||||
|
print("VoxelViewer.getOctreeElementsCount()=" + VoxelViewer.getOctreeElementsCount());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@ detect_strip_roi_width 2
|
||||||
detect_strip_roi_height 4
|
detect_strip_roi_height 4
|
||||||
|
|
||||||
smoothing_factors
|
smoothing_factors
|
||||||
150 15 -2 100 -1 50 50 0
|
150 5 -2 100 -1 50 50 0
|
||||||
#translation rotation action_units eyebrows mouth gaze eye_closure other
|
#translation rotation action_units eyebrows mouth gaze eye_closure other
|
||||||
|
|
||||||
process_eyes 1
|
process_eyes 1
|
||||||
|
|
|
@ -167,7 +167,7 @@ Menu::Menu() :
|
||||||
addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::Gravity, Qt::SHIFT | Qt::Key_G, false);
|
addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::Gravity, Qt::SHIFT | Qt::Key_G, false);
|
||||||
|
|
||||||
|
|
||||||
addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::ClickToFly);
|
|
||||||
|
|
||||||
addAvatarCollisionSubMenu(editMenu);
|
addAvatarCollisionSubMenu(editMenu);
|
||||||
|
|
||||||
|
|
|
@ -272,7 +272,6 @@ namespace MenuOption {
|
||||||
const QString OffAxisProjection = "Off-Axis Projection";
|
const QString OffAxisProjection = "Off-Axis Projection";
|
||||||
const QString OldVoxelCullingMode = "Old Voxel Culling Mode";
|
const QString OldVoxelCullingMode = "Old Voxel Culling Mode";
|
||||||
const QString TurnWithHead = "Turn using Head";
|
const QString TurnWithHead = "Turn using Head";
|
||||||
const QString ClickToFly = "Fly to voxel on click";
|
|
||||||
const QString LoadScript = "Open and Run Script...";
|
const QString LoadScript = "Open and Run Script...";
|
||||||
const QString Oscilloscope = "Audio Oscilloscope";
|
const QString Oscilloscope = "Audio Oscilloscope";
|
||||||
const QString Pair = "Pair";
|
const QString Pair = "Pair";
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
/// Generalized threaded processor for handling received inbound packets.
|
/// Generalized threaded processor for handling received inbound packets.
|
||||||
class VoxelHideShowThread : public GenericThread {
|
class VoxelHideShowThread : public GenericThread {
|
||||||
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
|
||||||
VoxelHideShowThread(VoxelSystem* theSystem);
|
VoxelHideShowThread(VoxelSystem* theSystem);
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
/// Handles processing of incoming voxel packets for the interface application. As with other ReceivedPacketProcessor classes
|
/// Handles processing of incoming voxel packets for the interface application. As with other ReceivedPacketProcessor classes
|
||||||
/// the user is responsible for reading inbound packets and adding them to the processing queue by calling queueReceivedPacket()
|
/// the user is responsible for reading inbound packets and adding them to the processing queue by calling queueReceivedPacket()
|
||||||
class VoxelPacketProcessor : public ReceivedPacketProcessor {
|
class VoxelPacketProcessor : public ReceivedPacketProcessor {
|
||||||
|
Q_OBJECT
|
||||||
protected:
|
protected:
|
||||||
virtual void processPacket(const SharedNodePointer& sendingNode, const QByteArray& packet);
|
virtual void processPacket(const SharedNodePointer& sendingNode, const QByteArray& packet);
|
||||||
};
|
};
|
||||||
|
|
|
@ -424,11 +424,6 @@ void MyAvatar::updateFromGyros(float deltaTime) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static TextRenderer* textRenderer() {
|
|
||||||
static TextRenderer* renderer = new TextRenderer(SANS_FONT_FAMILY, 24, -1, false, TextRenderer::SHADOW_EFFECT);
|
|
||||||
return renderer;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MyAvatar::renderDebugBodyPoints() {
|
void MyAvatar::renderDebugBodyPoints() {
|
||||||
glm::vec3 torsoPosition(getPosition());
|
glm::vec3 torsoPosition(getPosition());
|
||||||
glm::vec3 headPosition(getHead()->getEyePosition());
|
glm::vec3 headPosition(getHead()->getEyePosition());
|
||||||
|
|
|
@ -103,7 +103,7 @@ void Faceshift::reset() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Faceshift::updateFakeCoefficients(float leftBlink, float rightBlink, float browUp,
|
void Faceshift::updateFakeCoefficients(float leftBlink, float rightBlink, float browUp,
|
||||||
float jawOpen, std::vector<float>& coefficients) const {
|
float jawOpen, QVector<float>& coefficients) const {
|
||||||
coefficients.resize(max((int)coefficients.size(), _jawOpenIndex + 1));
|
coefficients.resize(max((int)coefficients.size(), _jawOpenIndex + 1));
|
||||||
qFill(coefficients.begin(), coefficients.end(), 0.0f);
|
qFill(coefficients.begin(), coefficients.end(), 0.0f);
|
||||||
coefficients[_leftBlinkIndex] = leftBlink;
|
coefficients[_leftBlinkIndex] = leftBlink;
|
||||||
|
@ -204,7 +204,7 @@ void Faceshift::receive(const QByteArray& buffer) {
|
||||||
_eyeGazeLeftYaw = data.m_eyeGazeLeftYaw;
|
_eyeGazeLeftYaw = data.m_eyeGazeLeftYaw;
|
||||||
_eyeGazeRightPitch = -data.m_eyeGazeRightPitch;
|
_eyeGazeRightPitch = -data.m_eyeGazeRightPitch;
|
||||||
_eyeGazeRightYaw = data.m_eyeGazeRightYaw;
|
_eyeGazeRightYaw = data.m_eyeGazeRightYaw;
|
||||||
_blendshapeCoefficients = data.m_coeffs;
|
_blendshapeCoefficients = QVector<float>::fromStdVector(data.m_coeffs);
|
||||||
|
|
||||||
_lastTrackingStateReceived = usecTimestampNow();
|
_lastTrackingStateReceived = usecTimestampNow();
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,6 @@
|
||||||
#ifndef __interface__Faceshift__
|
#ifndef __interface__Faceshift__
|
||||||
#define __interface__Faceshift__
|
#define __interface__Faceshift__
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <QTcpSocket>
|
#include <QTcpSocket>
|
||||||
#include <QUdpSocket>
|
#include <QUdpSocket>
|
||||||
|
|
||||||
|
@ -47,7 +45,7 @@ public:
|
||||||
float getEstimatedEyePitch() const { return _estimatedEyePitch; }
|
float getEstimatedEyePitch() const { return _estimatedEyePitch; }
|
||||||
float getEstimatedEyeYaw() const { return _estimatedEyeYaw; }
|
float getEstimatedEyeYaw() const { return _estimatedEyeYaw; }
|
||||||
|
|
||||||
const std::vector<float>& getBlendshapeCoefficients() const { return _blendshapeCoefficients; }
|
const QVector<float>& getBlendshapeCoefficients() const { return _blendshapeCoefficients; }
|
||||||
|
|
||||||
float getLeftBlink() const { return getBlendshapeCoefficient(_leftBlinkIndex); }
|
float getLeftBlink() const { return getBlendshapeCoefficient(_leftBlinkIndex); }
|
||||||
float getRightBlink() const { return getBlendshapeCoefficient(_rightBlinkIndex); }
|
float getRightBlink() const { return getBlendshapeCoefficient(_rightBlinkIndex); }
|
||||||
|
@ -68,7 +66,7 @@ public:
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
void updateFakeCoefficients(float leftBlink, float rightBlink, float browUp,
|
void updateFakeCoefficients(float leftBlink, float rightBlink, float browUp,
|
||||||
float jawOpen, std::vector<float>& coefficients) const;
|
float jawOpen, QVector<float>& coefficients) const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
||||||
|
@ -111,7 +109,7 @@ private:
|
||||||
float _eyeGazeRightPitch;
|
float _eyeGazeRightPitch;
|
||||||
float _eyeGazeRightYaw;
|
float _eyeGazeRightYaw;
|
||||||
|
|
||||||
std::vector<float> _blendshapeCoefficients;
|
QVector<float> _blendshapeCoefficients;
|
||||||
|
|
||||||
int _leftBlinkIndex;
|
int _leftBlinkIndex;
|
||||||
int _rightBlinkIndex;
|
int _rightBlinkIndex;
|
||||||
|
|
|
@ -9,8 +9,6 @@
|
||||||
#ifndef __interface__Visage__
|
#ifndef __interface__Visage__
|
||||||
#define __interface__Visage__
|
#define __interface__Visage__
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <QMultiHash>
|
#include <QMultiHash>
|
||||||
#include <QPair>
|
#include <QPair>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
|
@ -42,7 +40,7 @@ public:
|
||||||
float getEstimatedEyePitch() const { return _estimatedEyePitch; }
|
float getEstimatedEyePitch() const { return _estimatedEyePitch; }
|
||||||
float getEstimatedEyeYaw() const { return _estimatedEyeYaw; }
|
float getEstimatedEyeYaw() const { return _estimatedEyeYaw; }
|
||||||
|
|
||||||
const std::vector<float>& getBlendshapeCoefficients() const { return _blendshapeCoefficients; }
|
const QVector<float>& getBlendshapeCoefficients() const { return _blendshapeCoefficients; }
|
||||||
|
|
||||||
void update();
|
void update();
|
||||||
void reset();
|
void reset();
|
||||||
|
@ -71,7 +69,7 @@ private:
|
||||||
float _estimatedEyePitch;
|
float _estimatedEyePitch;
|
||||||
float _estimatedEyeYaw;
|
float _estimatedEyeYaw;
|
||||||
|
|
||||||
std::vector<float> _blendshapeCoefficients;
|
QVector<float> _blendshapeCoefficients;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* defined(__interface__Visage__) */
|
#endif /* defined(__interface__Visage__) */
|
||||||
|
|
|
@ -54,6 +54,15 @@ QStringList FBXGeometry::getJointNames() const {
|
||||||
return names;
|
return names;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool FBXGeometry::hasBlendedMeshes() const {
|
||||||
|
foreach (const FBXMesh& mesh, meshes) {
|
||||||
|
if (!mesh.blendshapes.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static int fbxGeometryMetaTypeId = qRegisterMetaType<FBXGeometry>();
|
static int fbxGeometryMetaTypeId = qRegisterMetaType<FBXGeometry>();
|
||||||
|
|
||||||
template<class T> QVariant readBinaryArray(QDataStream& in) {
|
template<class T> QVariant readBinaryArray(QDataStream& in) {
|
||||||
|
@ -1331,14 +1340,11 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
||||||
geometry.staticExtents.reset();
|
geometry.staticExtents.reset();
|
||||||
geometry.meshExtents.reset();
|
geometry.meshExtents.reset();
|
||||||
|
|
||||||
QVariantHash springs = mapping.value("spring").toHash();
|
|
||||||
QVariant defaultSpring = springs.value("default");
|
|
||||||
for (QHash<QString, ExtractedMesh>::iterator it = meshes.begin(); it != meshes.end(); it++) {
|
for (QHash<QString, ExtractedMesh>::iterator it = meshes.begin(); it != meshes.end(); it++) {
|
||||||
ExtractedMesh& extracted = it.value();
|
ExtractedMesh& extracted = it.value();
|
||||||
|
|
||||||
// accumulate local transforms
|
// accumulate local transforms
|
||||||
QString modelID = models.contains(it.key()) ? it.key() : parentMap.value(it.key());
|
QString modelID = models.contains(it.key()) ? it.key() : parentMap.value(it.key());
|
||||||
extracted.mesh.springiness = springs.value(models.value(modelID).name, defaultSpring).toFloat();
|
|
||||||
glm::mat4 modelTransform = getGlobalTransform(parentMap, models, modelID);
|
glm::mat4 modelTransform = getGlobalTransform(parentMap, models, modelID);
|
||||||
|
|
||||||
// compute the mesh extents from the transformed vertices
|
// compute the mesh extents from the transformed vertices
|
||||||
|
@ -1591,49 +1597,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
extracted.mesh.isEye = (maxJointIndex == geometry.leftEyeJointIndex || maxJointIndex == geometry.rightEyeJointIndex);
|
extracted.mesh.isEye = (maxJointIndex == geometry.leftEyeJointIndex || maxJointIndex == geometry.rightEyeJointIndex);
|
||||||
|
|
||||||
// extract spring edges, connections if springy
|
|
||||||
if (extracted.mesh.springiness > 0.0f) {
|
|
||||||
QSet<QPair<int, int> > edges;
|
|
||||||
|
|
||||||
extracted.mesh.vertexConnections.resize(extracted.mesh.vertices.size());
|
|
||||||
foreach (const FBXMeshPart& part, extracted.mesh.parts) {
|
|
||||||
for (int i = 0; i < part.quadIndices.size(); i += 4) {
|
|
||||||
int index0 = part.quadIndices.at(i);
|
|
||||||
int index1 = part.quadIndices.at(i + 1);
|
|
||||||
int index2 = part.quadIndices.at(i + 2);
|
|
||||||
int index3 = part.quadIndices.at(i + 3);
|
|
||||||
|
|
||||||
edges.insert(QPair<int, int>(qMin(index0, index1), qMax(index0, index1)));
|
|
||||||
edges.insert(QPair<int, int>(qMin(index1, index2), qMax(index1, index2)));
|
|
||||||
edges.insert(QPair<int, int>(qMin(index2, index3), qMax(index2, index3)));
|
|
||||||
edges.insert(QPair<int, int>(qMin(index3, index0), qMax(index3, index0)));
|
|
||||||
|
|
||||||
extracted.mesh.vertexConnections[index0].append(QPair<int, int>(index3, index1));
|
|
||||||
extracted.mesh.vertexConnections[index1].append(QPair<int, int>(index0, index2));
|
|
||||||
extracted.mesh.vertexConnections[index2].append(QPair<int, int>(index1, index3));
|
|
||||||
extracted.mesh.vertexConnections[index3].append(QPair<int, int>(index2, index0));
|
|
||||||
}
|
|
||||||
for (int i = 0; i < part.triangleIndices.size(); i += 3) {
|
|
||||||
int index0 = part.triangleIndices.at(i);
|
|
||||||
int index1 = part.triangleIndices.at(i + 1);
|
|
||||||
int index2 = part.triangleIndices.at(i + 2);
|
|
||||||
|
|
||||||
edges.insert(QPair<int, int>(qMin(index0, index1), qMax(index0, index1)));
|
|
||||||
edges.insert(QPair<int, int>(qMin(index1, index2), qMax(index1, index2)));
|
|
||||||
edges.insert(QPair<int, int>(qMin(index2, index0), qMax(index2, index0)));
|
|
||||||
|
|
||||||
extracted.mesh.vertexConnections[index0].append(QPair<int, int>(index2, index1));
|
|
||||||
extracted.mesh.vertexConnections[index1].append(QPair<int, int>(index0, index2));
|
|
||||||
extracted.mesh.vertexConnections[index2].append(QPair<int, int>(index1, index0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (QSet<QPair<int, int> >::const_iterator edge = edges.constBegin(); edge != edges.constEnd(); edge++) {
|
|
||||||
extracted.mesh.springEdges.append(*edge);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
geometry.meshes.append(extracted.mesh);
|
geometry.meshes.append(extracted.mesh);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1797,7 +1761,6 @@ FBXGeometry readSVO(const QByteArray& model) {
|
||||||
// and one mesh with one cluster and one part
|
// and one mesh with one cluster and one part
|
||||||
FBXMesh mesh;
|
FBXMesh mesh;
|
||||||
mesh.isEye = false;
|
mesh.isEye = false;
|
||||||
mesh.springiness = 0.0f;
|
|
||||||
|
|
||||||
FBXCluster cluster = { 0 };
|
FBXCluster cluster = { 0 };
|
||||||
mesh.clusters.append(cluster);
|
mesh.clusters.append(cluster);
|
||||||
|
|
|
@ -130,10 +130,6 @@ public:
|
||||||
bool isEye;
|
bool isEye;
|
||||||
|
|
||||||
QVector<FBXBlendshape> blendshapes;
|
QVector<FBXBlendshape> blendshapes;
|
||||||
|
|
||||||
float springiness;
|
|
||||||
QVector<QPair<int, int> > springEdges;
|
|
||||||
QVector<QVarLengthArray<QPair<int, int>, 4> > vertexConnections;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// An attachment to an FBX document.
|
/// An attachment to an FBX document.
|
||||||
|
@ -185,6 +181,8 @@ public:
|
||||||
|
|
||||||
int getJointIndex(const QString& name) const { return jointIndices.value(name) - 1; }
|
int getJointIndex(const QString& name) const { return jointIndices.value(name) - 1; }
|
||||||
QStringList getJointNames() const;
|
QStringList getJointNames() const;
|
||||||
|
|
||||||
|
bool hasBlendedMeshes() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(FBXGeometry)
|
Q_DECLARE_METATYPE(FBXGeometry)
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
#include "GeometryCache.h"
|
#include "GeometryCache.h"
|
||||||
|
#include "Model.h"
|
||||||
#include "world.h"
|
#include "world.h"
|
||||||
|
|
||||||
GeometryCache::~GeometryCache() {
|
GeometryCache::~GeometryCache() {
|
||||||
|
@ -291,6 +292,13 @@ QSharedPointer<NetworkGeometry> GeometryCache::getGeometry(const QUrl& url, cons
|
||||||
return getResource(url, fallback, delayLoad).staticCast<NetworkGeometry>();
|
return getResource(url, fallback, delayLoad).staticCast<NetworkGeometry>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GeometryCache::setBlendedVertices(const QPointer<Model>& model, const QWeakPointer<NetworkGeometry>& geometry,
|
||||||
|
const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals) {
|
||||||
|
if (!model.isNull() && model->getGeometry() == geometry) {
|
||||||
|
model->setBlendedVertices(vertices, normals);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QSharedPointer<Resource> GeometryCache::createResource(const QUrl& url,
|
QSharedPointer<Resource> GeometryCache::createResource(const QUrl& url,
|
||||||
const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra) {
|
const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra) {
|
||||||
|
|
||||||
|
@ -565,8 +573,8 @@ void NetworkGeometry::setGeometry(const FBXGeometry& geometry) {
|
||||||
networkMesh.vertexBuffer.bind();
|
networkMesh.vertexBuffer.bind();
|
||||||
networkMesh.vertexBuffer.setUsagePattern(QOpenGLBuffer::StaticDraw);
|
networkMesh.vertexBuffer.setUsagePattern(QOpenGLBuffer::StaticDraw);
|
||||||
|
|
||||||
// if we don't need to do any blending or springing, then the positions/normals can be static
|
// if we don't need to do any blending, the positions/normals can be static
|
||||||
if (mesh.blendshapes.isEmpty() && mesh.springiness == 0.0f) {
|
if (mesh.blendshapes.isEmpty()) {
|
||||||
int normalsOffset = mesh.vertices.size() * sizeof(glm::vec3);
|
int normalsOffset = mesh.vertices.size() * sizeof(glm::vec3);
|
||||||
int tangentsOffset = normalsOffset + mesh.normals.size() * sizeof(glm::vec3);
|
int tangentsOffset = normalsOffset + mesh.normals.size() * sizeof(glm::vec3);
|
||||||
int colorsOffset = tangentsOffset + mesh.tangents.size() * sizeof(glm::vec3);
|
int colorsOffset = tangentsOffset + mesh.tangents.size() * sizeof(glm::vec3);
|
||||||
|
@ -587,8 +595,8 @@ void NetworkGeometry::setGeometry(const FBXGeometry& geometry) {
|
||||||
networkMesh.vertexBuffer.write(clusterWeightsOffset, mesh.clusterWeights.constData(),
|
networkMesh.vertexBuffer.write(clusterWeightsOffset, mesh.clusterWeights.constData(),
|
||||||
mesh.clusterWeights.size() * sizeof(glm::vec4));
|
mesh.clusterWeights.size() * sizeof(glm::vec4));
|
||||||
|
|
||||||
// if there's no springiness, then the cluster indices/weights can be static
|
// otherwise, at least the cluster indices/weights can be static
|
||||||
} else if (mesh.springiness == 0.0f) {
|
} else {
|
||||||
int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3);
|
int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3);
|
||||||
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
|
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
|
||||||
int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
|
int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
|
||||||
|
@ -601,16 +609,7 @@ void NetworkGeometry::setGeometry(const FBXGeometry& geometry) {
|
||||||
networkMesh.vertexBuffer.write(clusterIndicesOffset, mesh.clusterIndices.constData(),
|
networkMesh.vertexBuffer.write(clusterIndicesOffset, mesh.clusterIndices.constData(),
|
||||||
mesh.clusterIndices.size() * sizeof(glm::vec4));
|
mesh.clusterIndices.size() * sizeof(glm::vec4));
|
||||||
networkMesh.vertexBuffer.write(clusterWeightsOffset, mesh.clusterWeights.constData(),
|
networkMesh.vertexBuffer.write(clusterWeightsOffset, mesh.clusterWeights.constData(),
|
||||||
mesh.clusterWeights.size() * sizeof(glm::vec4));
|
mesh.clusterWeights.size() * sizeof(glm::vec4));
|
||||||
|
|
||||||
} else {
|
|
||||||
int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3);
|
|
||||||
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
|
|
||||||
networkMesh.vertexBuffer.allocate(texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2));
|
|
||||||
networkMesh.vertexBuffer.write(0, mesh.tangents.constData(), mesh.tangents.size() * sizeof(glm::vec3));
|
|
||||||
networkMesh.vertexBuffer.write(colorsOffset, mesh.colors.constData(), mesh.colors.size() * sizeof(glm::vec3));
|
|
||||||
networkMesh.vertexBuffer.write(texCoordsOffset, mesh.texCoords.constData(),
|
|
||||||
mesh.texCoords.size() * sizeof(glm::vec2));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
networkMesh.vertexBuffer.release();
|
networkMesh.vertexBuffer.release();
|
||||||
|
|
|
@ -19,15 +19,18 @@
|
||||||
|
|
||||||
#include "FBXReader.h"
|
#include "FBXReader.h"
|
||||||
|
|
||||||
|
class Model;
|
||||||
class NetworkGeometry;
|
class NetworkGeometry;
|
||||||
class NetworkMesh;
|
class NetworkMesh;
|
||||||
class NetworkTexture;
|
class NetworkTexture;
|
||||||
|
|
||||||
/// Stores cached geometry.
|
/// Stores cached geometry.
|
||||||
class GeometryCache : public ResourceCache {
|
class GeometryCache : public ResourceCache {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
~GeometryCache();
|
virtual ~GeometryCache();
|
||||||
|
|
||||||
void renderHemisphere(int slices, int stacks);
|
void renderHemisphere(int slices, int stacks);
|
||||||
void renderSquare(int xDivisions, int yDivisions);
|
void renderSquare(int xDivisions, int yDivisions);
|
||||||
|
@ -38,7 +41,12 @@ public:
|
||||||
/// \param fallback a fallback URL to load if the desired one is unavailable
|
/// \param fallback a fallback URL to load if the desired one is unavailable
|
||||||
/// \param delayLoad if true, don't load the geometry immediately; wait until load is first requested
|
/// \param delayLoad if true, don't load the geometry immediately; wait until load is first requested
|
||||||
QSharedPointer<NetworkGeometry> getGeometry(const QUrl& url, const QUrl& fallback = QUrl(), bool delayLoad = false);
|
QSharedPointer<NetworkGeometry> getGeometry(const QUrl& url, const QUrl& fallback = QUrl(), bool delayLoad = false);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
|
||||||
|
void setBlendedVertices(const QPointer<Model>& model, const QWeakPointer<NetworkGeometry>& geometry,
|
||||||
|
const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
virtual QSharedPointer<Resource> createResource(const QUrl& url,
|
virtual QSharedPointer<Resource> createResource(const QUrl& url,
|
||||||
|
|
|
@ -6,6 +6,10 @@
|
||||||
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
|
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#include <QMetaType>
|
||||||
|
#include <QRunnable>
|
||||||
|
#include <QThreadPool>
|
||||||
|
|
||||||
#include <glm/gtx/transform.hpp>
|
#include <glm/gtx/transform.hpp>
|
||||||
|
|
||||||
#include <GeometryUtil.h>
|
#include <GeometryUtil.h>
|
||||||
|
@ -19,6 +23,10 @@
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
static int modelPointerTypeId = qRegisterMetaType<QPointer<Model> >();
|
||||||
|
static int weakNetworkGeometryPointerTypeId = qRegisterMetaType<QWeakPointer<NetworkGeometry> >();
|
||||||
|
static int vec3VectorTypeId = qRegisterMetaType<QVector<glm::vec3> >();
|
||||||
|
|
||||||
Model::Model(QObject* parent) :
|
Model::Model(QObject* parent) :
|
||||||
QObject(parent),
|
QObject(parent),
|
||||||
_scale(1.0f, 1.0f, 1.0f),
|
_scale(1.0f, 1.0f, 1.0f),
|
||||||
|
@ -104,8 +112,6 @@ void Model::init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Model::reset() {
|
void Model::reset() {
|
||||||
_resetStates = true;
|
|
||||||
|
|
||||||
foreach (Model* attachment, _attachments) {
|
foreach (Model* attachment, _attachments) {
|
||||||
attachment->reset();
|
attachment->reset();
|
||||||
}
|
}
|
||||||
|
@ -170,20 +176,10 @@ bool Model::render(float alpha) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// set up blended buffer ids on first render after load/simulate
|
// set up dilated textures on first render after load/simulate
|
||||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||||
if (_blendedVertexBufferIDs.isEmpty()) {
|
if (_dilatedTextures.isEmpty()) {
|
||||||
foreach (const FBXMesh& mesh, geometry.meshes) {
|
foreach (const FBXMesh& mesh, geometry.meshes) {
|
||||||
GLuint id = 0;
|
|
||||||
if (!mesh.blendshapes.isEmpty() || mesh.springiness > 0.0f) {
|
|
||||||
glGenBuffers(1, &id);
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, id);
|
|
||||||
glBufferData(GL_ARRAY_BUFFER, (mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3),
|
|
||||||
NULL, GL_DYNAMIC_DRAW);
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
||||||
}
|
|
||||||
_blendedVertexBufferIDs.append(id);
|
|
||||||
|
|
||||||
QVector<QSharedPointer<Texture> > dilated;
|
QVector<QSharedPointer<Texture> > dilated;
|
||||||
dilated.resize(mesh.parts.size());
|
dilated.resize(mesh.parts.size());
|
||||||
_dilatedTextures.append(dilated);
|
_dilatedTextures.append(dilated);
|
||||||
|
@ -478,6 +474,68 @@ QVector<Model::JointState> Model::updateGeometry() {
|
||||||
return newJointStates;
|
return newJointStates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Blender : public QRunnable {
|
||||||
|
public:
|
||||||
|
|
||||||
|
Blender(Model* model, const QWeakPointer<NetworkGeometry>& geometry,
|
||||||
|
const QVector<FBXMesh>& meshes, const QVector<float>& blendshapeCoefficients);
|
||||||
|
|
||||||
|
virtual void run();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
QPointer<Model> _model;
|
||||||
|
QWeakPointer<NetworkGeometry> _geometry;
|
||||||
|
QVector<FBXMesh> _meshes;
|
||||||
|
QVector<float> _blendshapeCoefficients;
|
||||||
|
};
|
||||||
|
|
||||||
|
Blender::Blender(Model* model, const QWeakPointer<NetworkGeometry>& geometry,
|
||||||
|
const QVector<FBXMesh>& meshes, const QVector<float>& blendshapeCoefficients) :
|
||||||
|
_model(model),
|
||||||
|
_geometry(geometry),
|
||||||
|
_meshes(meshes),
|
||||||
|
_blendshapeCoefficients(blendshapeCoefficients) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Blender::run() {
|
||||||
|
// make sure the model/geometry still exists
|
||||||
|
if (_model.isNull() || _geometry.isNull()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QVector<glm::vec3> vertices, normals;
|
||||||
|
int offset = 0;
|
||||||
|
foreach (const FBXMesh& mesh, _meshes) {
|
||||||
|
if (mesh.blendshapes.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
vertices += mesh.vertices;
|
||||||
|
normals += mesh.normals;
|
||||||
|
glm::vec3* meshVertices = vertices.data() + offset;
|
||||||
|
glm::vec3* meshNormals = normals.data() + offset;
|
||||||
|
offset += mesh.vertices.size();
|
||||||
|
const float NORMAL_COEFFICIENT_SCALE = 0.01f;
|
||||||
|
for (int i = 0, n = qMin(_blendshapeCoefficients.size(), mesh.blendshapes.size()); i < n; i++) {
|
||||||
|
float vertexCoefficient = _blendshapeCoefficients.at(i);
|
||||||
|
if (vertexCoefficient < EPSILON) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
float normalCoefficient = vertexCoefficient * NORMAL_COEFFICIENT_SCALE;
|
||||||
|
const FBXBlendshape& blendshape = mesh.blendshapes.at(i);
|
||||||
|
for (int j = 0; j < blendshape.indices.size(); j++) {
|
||||||
|
int index = blendshape.indices.at(j);
|
||||||
|
meshVertices[index] += blendshape.vertices.at(j) * vertexCoefficient;
|
||||||
|
meshNormals[index] += blendshape.normals.at(j) * normalCoefficient;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// post the result to the geometry cache, which will dispatch to the model if still alive
|
||||||
|
QMetaObject::invokeMethod(Application::getInstance()->getGeometryCache(), "setBlendedVertices",
|
||||||
|
Q_ARG(const QPointer<Model>&, _model), Q_ARG(const QWeakPointer<NetworkGeometry>&, _geometry),
|
||||||
|
Q_ARG(const QVector<glm::vec3>&, vertices), Q_ARG(const QVector<glm::vec3>&, normals));
|
||||||
|
}
|
||||||
|
|
||||||
void Model::simulate(float deltaTime, bool fullUpdate, const QVector<JointState>& newJointStates) {
|
void Model::simulate(float deltaTime, bool fullUpdate, const QVector<JointState>& newJointStates) {
|
||||||
if (!isActive()) {
|
if (!isActive()) {
|
||||||
return;
|
return;
|
||||||
|
@ -490,12 +548,20 @@ void Model::simulate(float deltaTime, bool fullUpdate, const QVector<JointState>
|
||||||
foreach (const FBXMesh& mesh, geometry.meshes) {
|
foreach (const FBXMesh& mesh, geometry.meshes) {
|
||||||
MeshState state;
|
MeshState state;
|
||||||
state.clusterMatrices.resize(mesh.clusters.size());
|
state.clusterMatrices.resize(mesh.clusters.size());
|
||||||
if (mesh.springiness > 0.0f) {
|
|
||||||
state.worldSpaceVertices.resize(mesh.vertices.size());
|
|
||||||
state.vertexVelocities.resize(mesh.vertices.size());
|
|
||||||
state.worldSpaceNormals.resize(mesh.vertices.size());
|
|
||||||
}
|
|
||||||
_meshStates.append(state);
|
_meshStates.append(state);
|
||||||
|
|
||||||
|
QOpenGLBuffer buffer;
|
||||||
|
if (!mesh.blendshapes.isEmpty()) {
|
||||||
|
buffer.setUsagePattern(QOpenGLBuffer::DynamicDraw);
|
||||||
|
buffer.create();
|
||||||
|
buffer.bind();
|
||||||
|
buffer.allocate((mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3));
|
||||||
|
buffer.write(0, mesh.vertices.constData(), mesh.vertices.size() * sizeof(glm::vec3));
|
||||||
|
buffer.write(mesh.vertices.size() * sizeof(glm::vec3), mesh.normals.constData(),
|
||||||
|
mesh.normals.size() * sizeof(glm::vec3));
|
||||||
|
buffer.release();
|
||||||
|
}
|
||||||
|
_blendedVertexBuffers.append(buffer);
|
||||||
}
|
}
|
||||||
foreach (const FBXAttachment& attachment, geometry.attachments) {
|
foreach (const FBXAttachment& attachment, geometry.attachments) {
|
||||||
Model* model = new Model(this);
|
Model* model = new Model(this);
|
||||||
|
@ -503,12 +569,12 @@ void Model::simulate(float deltaTime, bool fullUpdate, const QVector<JointState>
|
||||||
model->setURL(attachment.url);
|
model->setURL(attachment.url);
|
||||||
_attachments.append(model);
|
_attachments.append(model);
|
||||||
}
|
}
|
||||||
_resetStates = fullUpdate = true;
|
fullUpdate = true;
|
||||||
createCollisionShapes();
|
createCollisionShapes();
|
||||||
}
|
}
|
||||||
|
|
||||||
// exit early if we don't have to perform a full update
|
// exit early if we don't have to perform a full update
|
||||||
if (!(fullUpdate || _resetStates)) {
|
if (!fullUpdate) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -541,82 +607,12 @@ void Model::simulate(float deltaTime, bool fullUpdate, const QVector<JointState>
|
||||||
const FBXCluster& cluster = mesh.clusters.at(j);
|
const FBXCluster& cluster = mesh.clusters.at(j);
|
||||||
state.clusterMatrices[j] = _jointStates[cluster.jointIndex].transform * cluster.inverseBindMatrix;
|
state.clusterMatrices[j] = _jointStates[cluster.jointIndex].transform * cluster.inverseBindMatrix;
|
||||||
}
|
}
|
||||||
int vertexCount = state.worldSpaceVertices.size();
|
|
||||||
if (vertexCount == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
glm::vec3* destVertices = state.worldSpaceVertices.data();
|
|
||||||
glm::vec3* destVelocities = state.vertexVelocities.data();
|
|
||||||
glm::vec3* destNormals = state.worldSpaceNormals.data();
|
|
||||||
|
|
||||||
const glm::vec3* sourceVertices = mesh.vertices.constData();
|
|
||||||
if (!mesh.blendshapes.isEmpty()) {
|
|
||||||
_blendedVertices.resize(max(_blendedVertices.size(), vertexCount));
|
|
||||||
memcpy(_blendedVertices.data(), mesh.vertices.constData(), vertexCount * sizeof(glm::vec3));
|
|
||||||
|
|
||||||
// blend in each coefficient
|
|
||||||
for (unsigned int j = 0; j < _blendshapeCoefficients.size(); j++) {
|
|
||||||
float coefficient = _blendshapeCoefficients[j];
|
|
||||||
if (coefficient == 0.0f || j >= (unsigned int)mesh.blendshapes.size() || mesh.blendshapes[j].vertices.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const glm::vec3* vertex = mesh.blendshapes[j].vertices.constData();
|
|
||||||
for (const int* index = mesh.blendshapes[j].indices.constData(),
|
|
||||||
*end = index + mesh.blendshapes[j].indices.size(); index != end; index++, vertex++) {
|
|
||||||
_blendedVertices[*index] += *vertex * coefficient;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sourceVertices = _blendedVertices.constData();
|
|
||||||
}
|
|
||||||
glm::mat4 transform = glm::translate(_translation);
|
|
||||||
if (mesh.clusters.size() > 1) {
|
|
||||||
_blendedVertices.resize(max(_blendedVertices.size(), vertexCount));
|
|
||||||
|
|
||||||
// skin each vertex
|
|
||||||
const glm::vec4* clusterIndices = mesh.clusterIndices.constData();
|
|
||||||
const glm::vec4* clusterWeights = mesh.clusterWeights.constData();
|
|
||||||
for (int j = 0; j < vertexCount; j++) {
|
|
||||||
_blendedVertices[j] =
|
|
||||||
glm::vec3(state.clusterMatrices[clusterIndices[j][0]] *
|
|
||||||
glm::vec4(sourceVertices[j], 1.0f)) * clusterWeights[j][0] +
|
|
||||||
glm::vec3(state.clusterMatrices[clusterIndices[j][1]] *
|
|
||||||
glm::vec4(sourceVertices[j], 1.0f)) * clusterWeights[j][1] +
|
|
||||||
glm::vec3(state.clusterMatrices[clusterIndices[j][2]] *
|
|
||||||
glm::vec4(sourceVertices[j], 1.0f)) * clusterWeights[j][2] +
|
|
||||||
glm::vec3(state.clusterMatrices[clusterIndices[j][3]] *
|
|
||||||
glm::vec4(sourceVertices[j], 1.0f)) * clusterWeights[j][3];
|
|
||||||
}
|
|
||||||
sourceVertices = _blendedVertices.constData();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
transform = state.clusterMatrices[0];
|
|
||||||
}
|
|
||||||
if (_resetStates) {
|
|
||||||
for (int j = 0; j < vertexCount; j++) {
|
|
||||||
destVertices[j] = glm::vec3(transform * glm::vec4(sourceVertices[j], 1.0f));
|
|
||||||
destVelocities[j] = glm::vec3();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const float SPRINGINESS_MULTIPLIER = 200.0f;
|
|
||||||
const float DAMPING = 5.0f;
|
|
||||||
for (int j = 0; j < vertexCount; j++) {
|
|
||||||
destVelocities[j] += ((glm::vec3(transform * glm::vec4(sourceVertices[j], 1.0f)) - destVertices[j]) *
|
|
||||||
mesh.springiness * SPRINGINESS_MULTIPLIER - destVelocities[j] * DAMPING) * deltaTime;
|
|
||||||
destVertices[j] += destVelocities[j] * deltaTime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (int j = 0; j < vertexCount; j++) {
|
|
||||||
destNormals[j] = glm::vec3();
|
|
||||||
|
|
||||||
const glm::vec3& middle = destVertices[j];
|
|
||||||
for (QVarLengthArray<QPair<int, int>, 4>::const_iterator connection = mesh.vertexConnections.at(j).constBegin();
|
|
||||||
connection != mesh.vertexConnections.at(j).constEnd(); connection++) {
|
|
||||||
destNormals[j] += glm::normalize(glm::cross(destVertices[connection->second] - middle,
|
|
||||||
destVertices[connection->first] - middle));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_resetStates = false;
|
|
||||||
|
// post the blender
|
||||||
|
if (geometry.hasBlendedMeshes()) {
|
||||||
|
QThreadPool::globalInstance()->start(new Blender(this, _geometry, geometry.meshes, _blendshapeCoefficients));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Model::updateJointState(int index) {
|
void Model::updateJointState(int index) {
|
||||||
|
@ -915,6 +911,27 @@ void Model::applyCollision(CollisionInfo& collision) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Model::setBlendedVertices(const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals) {
|
||||||
|
if (_blendedVertexBuffers.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||||
|
int index = 0;
|
||||||
|
for (int i = 0; i < geometry.meshes.size(); i++) {
|
||||||
|
const FBXMesh& mesh = geometry.meshes.at(i);
|
||||||
|
if (mesh.blendshapes.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
QOpenGLBuffer& buffer = _blendedVertexBuffers[i];
|
||||||
|
buffer.bind();
|
||||||
|
buffer.write(0, vertices.constData() + index, mesh.vertices.size() * sizeof(glm::vec3));
|
||||||
|
buffer.write(mesh.vertices.size() * sizeof(glm::vec3), normals.constData() + index,
|
||||||
|
mesh.normals.size() * sizeof(glm::vec3));
|
||||||
|
buffer.release();
|
||||||
|
index += mesh.vertices.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Model::applyNextGeometry() {
|
void Model::applyNextGeometry() {
|
||||||
// delete our local geometry and custom textures
|
// delete our local geometry and custom textures
|
||||||
deleteGeometry();
|
deleteGeometry();
|
||||||
|
@ -933,10 +950,7 @@ void Model::deleteGeometry() {
|
||||||
delete attachment;
|
delete attachment;
|
||||||
}
|
}
|
||||||
_attachments.clear();
|
_attachments.clear();
|
||||||
foreach (GLuint id, _blendedVertexBufferIDs) {
|
_blendedVertexBuffers.clear();
|
||||||
glDeleteBuffers(1, &id);
|
|
||||||
}
|
|
||||||
_blendedVertexBufferIDs.clear();
|
|
||||||
_jointStates.clear();
|
_jointStates.clear();
|
||||||
_meshStates.clear();
|
_meshStates.clear();
|
||||||
clearShapes();
|
clearShapes();
|
||||||
|
@ -980,34 +994,30 @@ void Model::renderMeshes(float alpha, bool translucent) {
|
||||||
const MeshState& state = _meshStates.at(i);
|
const MeshState& state = _meshStates.at(i);
|
||||||
ProgramObject* activeProgram = program;
|
ProgramObject* activeProgram = program;
|
||||||
int tangentLocation = _normalMapTangentLocation;
|
int tangentLocation = _normalMapTangentLocation;
|
||||||
if (state.worldSpaceVertices.isEmpty()) {
|
glPushMatrix();
|
||||||
glPushMatrix();
|
Application::getInstance()->loadTranslatedViewMatrix(_translation);
|
||||||
Application::getInstance()->loadTranslatedViewMatrix(_translation);
|
|
||||||
|
if (state.clusterMatrices.size() > 1) {
|
||||||
if (state.clusterMatrices.size() > 1) {
|
skinProgram->bind();
|
||||||
skinProgram->bind();
|
glUniformMatrix4fvARB(skinLocations->clusterMatrices, state.clusterMatrices.size(), false,
|
||||||
glUniformMatrix4fvARB(skinLocations->clusterMatrices, state.clusterMatrices.size(), false,
|
(const float*)state.clusterMatrices.constData());
|
||||||
(const float*)state.clusterMatrices.constData());
|
int offset = (mesh.tangents.size() + mesh.colors.size()) * sizeof(glm::vec3) +
|
||||||
int offset = (mesh.tangents.size() + mesh.colors.size()) * sizeof(glm::vec3) +
|
mesh.texCoords.size() * sizeof(glm::vec2) +
|
||||||
mesh.texCoords.size() * sizeof(glm::vec2) +
|
(mesh.blendshapes.isEmpty() ? vertexCount * 2 * sizeof(glm::vec3) : 0);
|
||||||
(mesh.blendshapes.isEmpty() ? vertexCount * 2 * sizeof(glm::vec3) : 0);
|
skinProgram->setAttributeBuffer(skinLocations->clusterIndices, GL_FLOAT, offset, 4);
|
||||||
skinProgram->setAttributeBuffer(skinLocations->clusterIndices, GL_FLOAT, offset, 4);
|
skinProgram->setAttributeBuffer(skinLocations->clusterWeights, GL_FLOAT,
|
||||||
skinProgram->setAttributeBuffer(skinLocations->clusterWeights, GL_FLOAT,
|
offset + vertexCount * sizeof(glm::vec4), 4);
|
||||||
offset + vertexCount * sizeof(glm::vec4), 4);
|
skinProgram->enableAttributeArray(skinLocations->clusterIndices);
|
||||||
skinProgram->enableAttributeArray(skinLocations->clusterIndices);
|
skinProgram->enableAttributeArray(skinLocations->clusterWeights);
|
||||||
skinProgram->enableAttributeArray(skinLocations->clusterWeights);
|
activeProgram = skinProgram;
|
||||||
activeProgram = skinProgram;
|
tangentLocation = skinLocations->tangent;
|
||||||
tangentLocation = skinLocations->tangent;
|
|
||||||
|
} else {
|
||||||
} else {
|
glMultMatrixf((const GLfloat*)&state.clusterMatrices[0]);
|
||||||
glMultMatrixf((const GLfloat*)&state.clusterMatrices[0]);
|
|
||||||
program->bind();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
program->bind();
|
program->bind();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mesh.blendshapes.isEmpty() && mesh.springiness == 0.0f) {
|
if (mesh.blendshapes.isEmpty()) {
|
||||||
if (!mesh.tangents.isEmpty()) {
|
if (!mesh.tangents.isEmpty()) {
|
||||||
activeProgram->setAttributeBuffer(tangentLocation, GL_FLOAT, vertexCount * 2 * sizeof(glm::vec3), 3);
|
activeProgram->setAttributeBuffer(tangentLocation, GL_FLOAT, vertexCount * 2 * sizeof(glm::vec3), 3);
|
||||||
activeProgram->enableAttributeArray(tangentLocation);
|
activeProgram->enableAttributeArray(tangentLocation);
|
||||||
|
@ -1024,40 +1034,7 @@ void Model::renderMeshes(float alpha, bool translucent) {
|
||||||
}
|
}
|
||||||
glColorPointer(3, GL_FLOAT, 0, (void*)(mesh.tangents.size() * sizeof(glm::vec3)));
|
glColorPointer(3, GL_FLOAT, 0, (void*)(mesh.tangents.size() * sizeof(glm::vec3)));
|
||||||
glTexCoordPointer(2, GL_FLOAT, 0, (void*)((mesh.tangents.size() + mesh.colors.size()) * sizeof(glm::vec3)));
|
glTexCoordPointer(2, GL_FLOAT, 0, (void*)((mesh.tangents.size() + mesh.colors.size()) * sizeof(glm::vec3)));
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, _blendedVertexBufferIDs.at(i));
|
_blendedVertexBuffers[i].bind();
|
||||||
|
|
||||||
if (!state.worldSpaceVertices.isEmpty()) {
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * sizeof(glm::vec3), state.worldSpaceVertices.constData());
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, vertexCount * sizeof(glm::vec3),
|
|
||||||
vertexCount * sizeof(glm::vec3), state.worldSpaceNormals.constData());
|
|
||||||
|
|
||||||
} else {
|
|
||||||
_blendedVertices.resize(max(_blendedVertices.size(), vertexCount));
|
|
||||||
_blendedNormals.resize(_blendedVertices.size());
|
|
||||||
memcpy(_blendedVertices.data(), mesh.vertices.constData(), vertexCount * sizeof(glm::vec3));
|
|
||||||
memcpy(_blendedNormals.data(), mesh.normals.constData(), vertexCount * sizeof(glm::vec3));
|
|
||||||
|
|
||||||
// blend in each coefficient
|
|
||||||
for (unsigned int j = 0; j < _blendshapeCoefficients.size(); j++) {
|
|
||||||
float coefficient = _blendshapeCoefficients[j];
|
|
||||||
if (coefficient == 0.0f || j >= (unsigned int)mesh.blendshapes.size() || mesh.blendshapes[j].vertices.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const float NORMAL_COEFFICIENT_SCALE = 0.01f;
|
|
||||||
float normalCoefficient = coefficient * NORMAL_COEFFICIENT_SCALE;
|
|
||||||
const glm::vec3* vertex = mesh.blendshapes[j].vertices.constData();
|
|
||||||
const glm::vec3* normal = mesh.blendshapes[j].normals.constData();
|
|
||||||
for (const int* index = mesh.blendshapes[j].indices.constData(),
|
|
||||||
*end = index + mesh.blendshapes[j].indices.size(); index != end; index++, vertex++, normal++) {
|
|
||||||
_blendedVertices[*index] += *vertex * coefficient;
|
|
||||||
_blendedNormals[*index] += *normal * normalCoefficient;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * sizeof(glm::vec3), _blendedVertices.constData());
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, vertexCount * sizeof(glm::vec3),
|
|
||||||
vertexCount * sizeof(glm::vec3), _blendedNormals.constData());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
glVertexPointer(3, GL_FLOAT, 0, 0);
|
glVertexPointer(3, GL_FLOAT, 0, 0);
|
||||||
glNormalPointer(GL_FLOAT, 0, (void*)(vertexCount * sizeof(glm::vec3)));
|
glNormalPointer(GL_FLOAT, 0, (void*)(vertexCount * sizeof(glm::vec3)));
|
||||||
|
@ -1126,14 +1103,13 @@ void Model::renderMeshes(float alpha, bool translucent) {
|
||||||
|
|
||||||
activeProgram->disableAttributeArray(tangentLocation);
|
activeProgram->disableAttributeArray(tangentLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.worldSpaceVertices.isEmpty()) {
|
if (state.clusterMatrices.size() > 1) {
|
||||||
if (state.clusterMatrices.size() > 1) {
|
skinProgram->disableAttributeArray(skinLocations->clusterIndices);
|
||||||
skinProgram->disableAttributeArray(skinLocations->clusterIndices);
|
skinProgram->disableAttributeArray(skinLocations->clusterWeights);
|
||||||
skinProgram->disableAttributeArray(skinLocations->clusterWeights);
|
}
|
||||||
}
|
glPopMatrix();
|
||||||
glPopMatrix();
|
|
||||||
}
|
|
||||||
activeProgram->release();
|
activeProgram->release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,8 +43,8 @@ public:
|
||||||
void setPupilDilation(float dilation) { _pupilDilation = dilation; }
|
void setPupilDilation(float dilation) { _pupilDilation = dilation; }
|
||||||
float getPupilDilation() const { return _pupilDilation; }
|
float getPupilDilation() const { return _pupilDilation; }
|
||||||
|
|
||||||
void setBlendshapeCoefficients(const std::vector<float>& coefficients) { _blendshapeCoefficients = coefficients; }
|
void setBlendshapeCoefficients(const QVector<float>& coefficients) { _blendshapeCoefficients = coefficients; }
|
||||||
const std::vector<float>& getBlendshapeCoefficients() const { return _blendshapeCoefficients; }
|
const QVector<float>& getBlendshapeCoefficients() const { return _blendshapeCoefficients; }
|
||||||
|
|
||||||
bool isActive() const { return _geometry && _geometry->isLoaded(); }
|
bool isActive() const { return _geometry && _geometry->isLoaded(); }
|
||||||
|
|
||||||
|
@ -195,6 +195,9 @@ public:
|
||||||
/// Use the collision to affect the model
|
/// Use the collision to affect the model
|
||||||
void applyCollision(CollisionInfo& collision);
|
void applyCollision(CollisionInfo& collision);
|
||||||
|
|
||||||
|
/// Sets blended vertices computed in a separate thread.
|
||||||
|
void setBlendedVertices(const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
QSharedPointer<NetworkGeometry> _geometry;
|
QSharedPointer<NetworkGeometry> _geometry;
|
||||||
|
@ -219,9 +222,6 @@ protected:
|
||||||
class MeshState {
|
class MeshState {
|
||||||
public:
|
public:
|
||||||
QVector<glm::mat4> clusterMatrices;
|
QVector<glm::mat4> clusterMatrices;
|
||||||
QVector<glm::vec3> worldSpaceVertices;
|
|
||||||
QVector<glm::vec3> vertexVelocities;
|
|
||||||
QVector<glm::vec3> worldSpaceNormals;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
QVector<MeshState> _meshStates;
|
QVector<MeshState> _meshStates;
|
||||||
|
@ -271,16 +271,13 @@ private:
|
||||||
float _nextLODHysteresis;
|
float _nextLODHysteresis;
|
||||||
|
|
||||||
float _pupilDilation;
|
float _pupilDilation;
|
||||||
std::vector<float> _blendshapeCoefficients;
|
QVector<float> _blendshapeCoefficients;
|
||||||
|
|
||||||
QUrl _url;
|
QUrl _url;
|
||||||
|
|
||||||
QVector<GLuint> _blendedVertexBufferIDs;
|
QVector<QOpenGLBuffer> _blendedVertexBuffers;
|
||||||
QVector<QVector<QSharedPointer<Texture> > > _dilatedTextures;
|
|
||||||
bool _resetStates;
|
|
||||||
|
|
||||||
QVector<glm::vec3> _blendedVertices;
|
QVector<QVector<QSharedPointer<Texture> > > _dilatedTextures;
|
||||||
QVector<glm::vec3> _blendedNormals;
|
|
||||||
|
|
||||||
QVector<Model*> _attachments;
|
QVector<Model*> _attachments;
|
||||||
|
|
||||||
|
@ -306,4 +303,8 @@ private:
|
||||||
static QVector<JointState> createJointStates(const FBXGeometry& geometry);
|
static QVector<JointState> createJointStates(const FBXGeometry& geometry);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(QPointer<Model>)
|
||||||
|
Q_DECLARE_METATYPE(QWeakPointer<NetworkGeometry>)
|
||||||
|
Q_DECLARE_METATYPE(QVector<glm::vec3>)
|
||||||
|
|
||||||
#endif /* defined(__interface__Model__) */
|
#endif /* defined(__interface__Model__) */
|
||||||
|
|
|
@ -38,9 +38,6 @@ TextureCache::~TextureCache() {
|
||||||
if (_whiteTextureID != 0) {
|
if (_whiteTextureID != 0) {
|
||||||
glDeleteTextures(1, &_whiteTextureID);
|
glDeleteTextures(1, &_whiteTextureID);
|
||||||
}
|
}
|
||||||
foreach (GLuint id, _fileTextureIDs) {
|
|
||||||
glDeleteTextures(1, &id);
|
|
||||||
}
|
|
||||||
if (_primaryFramebufferObject) {
|
if (_primaryFramebufferObject) {
|
||||||
glDeleteTextures(1, &_primaryDepthTextureID);
|
glDeleteTextures(1, &_primaryDepthTextureID);
|
||||||
}
|
}
|
||||||
|
@ -104,23 +101,6 @@ GLuint TextureCache::getBlueTextureID() {
|
||||||
return _blueTextureID;
|
return _blueTextureID;
|
||||||
}
|
}
|
||||||
|
|
||||||
GLuint TextureCache::getFileTextureID(const QString& filename) {
|
|
||||||
GLuint id = _fileTextureIDs.value(filename);
|
|
||||||
if (id == 0) {
|
|
||||||
QImage image = QImage(filename).convertToFormat(QImage::Format_ARGB32);
|
|
||||||
|
|
||||||
glGenTextures(1, &id);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, id);
|
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 1,
|
|
||||||
GL_BGRA, GL_UNSIGNED_BYTE, image.constBits());
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
|
|
||||||
_fileTextureIDs.insert(filename, id);
|
|
||||||
}
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
QSharedPointer<NetworkTexture> TextureCache::getTexture(const QUrl& url, bool normalMap, bool dilatable) {
|
QSharedPointer<NetworkTexture> TextureCache::getTexture(const QUrl& url, bool normalMap, bool dilatable) {
|
||||||
if (!dilatable) {
|
if (!dilatable) {
|
||||||
return ResourceCache::getResource(url, QUrl(), false, &normalMap).staticCast<NetworkTexture>();
|
return ResourceCache::getResource(url, QUrl(), false, &normalMap).staticCast<NetworkTexture>();
|
||||||
|
@ -293,27 +273,50 @@ void ImageReader::run() {
|
||||||
_reply->deleteLater();
|
_reply->deleteLater();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
QUrl url = _reply->url();
|
||||||
QImage image = QImage::fromData(_reply->readAll());
|
QImage image = QImage::fromData(_reply->readAll());
|
||||||
|
_reply->deleteLater();
|
||||||
|
|
||||||
|
// enforce a fixed maximum
|
||||||
|
const int MAXIMUM_SIZE = 1024;
|
||||||
|
if (image.width() > MAXIMUM_SIZE || image.height() > MAXIMUM_SIZE) {
|
||||||
|
qDebug() << "Image greater than maximum size:" << url << image.width() << image.height();
|
||||||
|
image = image.scaled(MAXIMUM_SIZE, MAXIMUM_SIZE, Qt::KeepAspectRatio);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!image.hasAlphaChannel()) {
|
||||||
|
if (image.format() != QImage::Format_RGB888) {
|
||||||
|
image = image.convertToFormat(QImage::Format_RGB888);
|
||||||
|
}
|
||||||
|
QMetaObject::invokeMethod(texture.data(), "setImage", Q_ARG(const QImage&, image), Q_ARG(bool, false));
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (image.format() != QImage::Format_ARGB32) {
|
if (image.format() != QImage::Format_ARGB32) {
|
||||||
image = image.convertToFormat(QImage::Format_ARGB32);
|
image = image.convertToFormat(QImage::Format_ARGB32);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for translucency
|
// check for translucency/false transparency
|
||||||
|
int opaquePixels = 0;
|
||||||
int translucentPixels = 0;
|
int translucentPixels = 0;
|
||||||
const int EIGHT_BIT_MAXIMUM = 255;
|
const int EIGHT_BIT_MAXIMUM = 255;
|
||||||
const int RGB_BITS = 24;
|
const int RGB_BITS = 24;
|
||||||
for (int y = 0; y < image.height(); y++) {
|
for (int y = 0; y < image.height(); y++) {
|
||||||
for (int x = 0; x < image.width(); x++) {
|
for (int x = 0; x < image.width(); x++) {
|
||||||
int alpha = image.pixel(x, y) >> RGB_BITS;
|
int alpha = image.pixel(x, y) >> RGB_BITS;
|
||||||
if (alpha != 0 && alpha != EIGHT_BIT_MAXIMUM) {
|
if (alpha == EIGHT_BIT_MAXIMUM) {
|
||||||
|
opaquePixels++;
|
||||||
|
} else if (alpha != 0) {
|
||||||
translucentPixels++;
|
translucentPixels++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int imageArea = image.width() * image.height();
|
int imageArea = image.width() * image.height();
|
||||||
|
if (opaquePixels == imageArea) {
|
||||||
|
qDebug() << "Image with alpha channel is completely opaque:" << url;
|
||||||
|
image.convertToFormat(QImage::Format_RGB888);
|
||||||
|
}
|
||||||
QMetaObject::invokeMethod(texture.data(), "setImage", Q_ARG(const QImage&, image),
|
QMetaObject::invokeMethod(texture.data(), "setImage", Q_ARG(const QImage&, image),
|
||||||
Q_ARG(bool, translucentPixels >= imageArea / 2));
|
Q_ARG(bool, translucentPixels >= imageArea / 2));
|
||||||
_reply->deleteLater();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkTexture::downloadFinished(QNetworkReply* reply) {
|
void NetworkTexture::downloadFinished(QNetworkReply* reply) {
|
||||||
|
@ -327,8 +330,13 @@ void NetworkTexture::setImage(const QImage& image, bool translucent) {
|
||||||
finishedLoading(true);
|
finishedLoading(true);
|
||||||
imageLoaded(image);
|
imageLoaded(image);
|
||||||
glBindTexture(GL_TEXTURE_2D, getID());
|
glBindTexture(GL_TEXTURE_2D, getID());
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 1,
|
if (image.hasAlphaChannel()) {
|
||||||
GL_BGRA, GL_UNSIGNED_BYTE, image.constBits());
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 1,
|
||||||
|
GL_BGRA, GL_UNSIGNED_BYTE, image.constBits());
|
||||||
|
} else {
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.width(), image.height(), 1,
|
||||||
|
GL_RGB, GL_UNSIGNED_BYTE, image.constBits());
|
||||||
|
}
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
}
|
}
|
||||||
|
@ -360,8 +368,13 @@ QSharedPointer<Texture> DilatableNetworkTexture::getDilatedTexture(float dilatio
|
||||||
painter.end();
|
painter.end();
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, texture->getID());
|
glBindTexture(GL_TEXTURE_2D, texture->getID());
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dilatedImage.width(), dilatedImage.height(), 1,
|
if (dilatedImage.hasAlphaChannel()) {
|
||||||
GL_BGRA, GL_UNSIGNED_BYTE, dilatedImage.constBits());
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dilatedImage.width(), dilatedImage.height(), 1,
|
||||||
|
GL_BGRA, GL_UNSIGNED_BYTE, dilatedImage.constBits());
|
||||||
|
} else {
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, dilatedImage.width(), dilatedImage.height(), 1,
|
||||||
|
GL_RGB, GL_UNSIGNED_BYTE, dilatedImage.constBits());
|
||||||
|
}
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ class TextureCache : public ResourceCache {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
TextureCache();
|
TextureCache();
|
||||||
~TextureCache();
|
virtual ~TextureCache();
|
||||||
|
|
||||||
/// Returns the ID of the permutation/normal texture used for Perlin noise shader programs. This texture
|
/// Returns the ID of the permutation/normal texture used for Perlin noise shader programs. This texture
|
||||||
/// has two lines: the first, a set of random numbers in [0, 255] to be used as permutation offsets, and
|
/// has two lines: the first, a set of random numbers in [0, 255] to be used as permutation offsets, and
|
||||||
|
@ -39,9 +39,6 @@ public:
|
||||||
|
|
||||||
/// Returns the ID of a pale blue texture (useful for a normal map).
|
/// Returns the ID of a pale blue texture (useful for a normal map).
|
||||||
GLuint getBlueTextureID();
|
GLuint getBlueTextureID();
|
||||||
|
|
||||||
/// Returns the ID of a texture containing the contents of the specified file, loading it if necessary.
|
|
||||||
GLuint getFileTextureID(const QString& filename);
|
|
||||||
|
|
||||||
/// Loads a texture from the specified URL.
|
/// Loads a texture from the specified URL.
|
||||||
QSharedPointer<NetworkTexture> getTexture(const QUrl& url, bool normalMap = false, bool dilatable = false);
|
QSharedPointer<NetworkTexture> getTexture(const QUrl& url, bool normalMap = false, bool dilatable = false);
|
||||||
|
@ -84,8 +81,6 @@ private:
|
||||||
GLuint _whiteTextureID;
|
GLuint _whiteTextureID;
|
||||||
GLuint _blueTextureID;
|
GLuint _blueTextureID;
|
||||||
|
|
||||||
QHash<QString, GLuint> _fileTextureIDs;
|
|
||||||
|
|
||||||
QHash<QUrl, QWeakPointer<NetworkTexture> > _dilatableNetworkTextures;
|
QHash<QUrl, QWeakPointer<NetworkTexture> > _dilatableNetworkTextures;
|
||||||
|
|
||||||
GLuint _primaryDepthTextureID;
|
GLuint _primaryDepthTextureID;
|
||||||
|
|
|
@ -88,6 +88,17 @@ QByteArray AvatarData::toByteArray() {
|
||||||
// Body scale
|
// Body scale
|
||||||
destinationBuffer += packFloatRatioToTwoByte(destinationBuffer, _targetScale);
|
destinationBuffer += packFloatRatioToTwoByte(destinationBuffer, _targetScale);
|
||||||
|
|
||||||
|
// Head rotation (NOTE: This needs to become a quaternion to save two bytes)
|
||||||
|
destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _headData->getTweakedYaw());
|
||||||
|
destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _headData->getTweakedPitch());
|
||||||
|
destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _headData->getTweakedRoll());
|
||||||
|
|
||||||
|
// Head lean X,Z (head lateral and fwd/back motion relative to torso)
|
||||||
|
memcpy(destinationBuffer, &_headData->_leanSideways, sizeof(_headData->_leanSideways));
|
||||||
|
destinationBuffer += sizeof(_headData->_leanSideways);
|
||||||
|
memcpy(destinationBuffer, &_headData->_leanForward, sizeof(_headData->_leanForward));
|
||||||
|
destinationBuffer += sizeof(_headData->_leanForward);
|
||||||
|
|
||||||
// Lookat Position
|
// Lookat Position
|
||||||
memcpy(destinationBuffer, &_headData->_lookAtPosition, sizeof(_headData->_lookAtPosition));
|
memcpy(destinationBuffer, &_headData->_lookAtPosition, sizeof(_headData->_lookAtPosition));
|
||||||
destinationBuffer += sizeof(_headData->_lookAtPosition);
|
destinationBuffer += sizeof(_headData->_lookAtPosition);
|
||||||
|
@ -176,8 +187,8 @@ int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) {
|
||||||
_handData = new HandData(this);
|
_handData = new HandData(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
const unsigned char* startPosition = reinterpret_cast<const unsigned char*>(packet.data());
|
const unsigned char* startPosition = reinterpret_cast<const unsigned char*>(packet.data()) + offset;
|
||||||
const unsigned char* sourceBuffer = startPosition + offset;
|
const unsigned char* sourceBuffer = startPosition;
|
||||||
|
|
||||||
// Body world position
|
// Body world position
|
||||||
memcpy(&_position, sourceBuffer, sizeof(float) * 3);
|
memcpy(&_position, sourceBuffer, sizeof(float) * 3);
|
||||||
|
@ -191,6 +202,21 @@ int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) {
|
||||||
// Body scale
|
// Body scale
|
||||||
sourceBuffer += unpackFloatRatioFromTwoByte(sourceBuffer, _targetScale);
|
sourceBuffer += unpackFloatRatioFromTwoByte(sourceBuffer, _targetScale);
|
||||||
|
|
||||||
|
// Head rotation (NOTE: This needs to become a quaternion to save two bytes)
|
||||||
|
float headYaw, headPitch, headRoll;
|
||||||
|
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headYaw);
|
||||||
|
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headPitch);
|
||||||
|
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headRoll);
|
||||||
|
_headData->setYaw(headYaw);
|
||||||
|
_headData->setPitch(headPitch);
|
||||||
|
_headData->setRoll(headRoll);
|
||||||
|
|
||||||
|
// Head position relative to pelvis
|
||||||
|
memcpy(&_headData->_leanSideways, sourceBuffer, sizeof(_headData->_leanSideways));
|
||||||
|
sourceBuffer += sizeof(float);
|
||||||
|
memcpy(&_headData->_leanForward, sourceBuffer, sizeof(_headData->_leanForward));
|
||||||
|
sourceBuffer += sizeof(_headData->_leanForward);
|
||||||
|
|
||||||
// Lookat Position
|
// Lookat Position
|
||||||
memcpy(&_headData->_lookAtPosition, sourceBuffer, sizeof(_headData->_lookAtPosition));
|
memcpy(&_headData->_lookAtPosition, sourceBuffer, sizeof(_headData->_lookAtPosition));
|
||||||
sourceBuffer += sizeof(_headData->_lookAtPosition);
|
sourceBuffer += sizeof(_headData->_lookAtPosition);
|
||||||
|
|
|
@ -8,14 +8,11 @@
|
||||||
|
|
||||||
#include <QtCore/QDataStream>
|
#include <QtCore/QDataStream>
|
||||||
|
|
||||||
#include "HandData.h"
|
|
||||||
#include "AvatarData.h"
|
|
||||||
#include <SharedUtil.h>
|
|
||||||
#include <GeometryUtil.h>
|
#include <GeometryUtil.h>
|
||||||
|
#include <SharedUtil.h>
|
||||||
|
|
||||||
|
#include "AvatarData.h"
|
||||||
// When converting between fixed and float, use this as the radix.
|
#include "HandData.h"
|
||||||
const int fingerVectorRadix = 4;
|
|
||||||
|
|
||||||
HandData::HandData(AvatarData* owningAvatar) :
|
HandData::HandData(AvatarData* owningAvatar) :
|
||||||
_owningAvatarData(owningAvatar)
|
_owningAvatarData(owningAvatar)
|
||||||
|
|
|
@ -10,7 +10,8 @@
|
||||||
#define __hifi__HeadData__
|
#define __hifi__HeadData__
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <vector>
|
|
||||||
|
#include <QVector>
|
||||||
|
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
#include <glm/gtc/quaternion.hpp>
|
#include <glm/gtc/quaternion.hpp>
|
||||||
|
@ -54,7 +55,7 @@ public:
|
||||||
float getAudioAverageLoudness() const { return _audioAverageLoudness; }
|
float getAudioAverageLoudness() const { return _audioAverageLoudness; }
|
||||||
void setAudioAverageLoudness(float audioAverageLoudness) { _audioAverageLoudness = audioAverageLoudness; }
|
void setAudioAverageLoudness(float audioAverageLoudness) { _audioAverageLoudness = audioAverageLoudness; }
|
||||||
|
|
||||||
const std::vector<float>& getBlendshapeCoefficients() const { return _blendshapeCoefficients; }
|
const QVector<float>& getBlendshapeCoefficients() const { return _blendshapeCoefficients; }
|
||||||
|
|
||||||
float getPupilDilation() const { return _pupilDilation; }
|
float getPupilDilation() const { return _pupilDilation; }
|
||||||
void setPupilDilation(float pupilDilation) { _pupilDilation = pupilDilation; }
|
void setPupilDilation(float pupilDilation) { _pupilDilation = pupilDilation; }
|
||||||
|
@ -86,7 +87,7 @@ protected:
|
||||||
float _averageLoudness;
|
float _averageLoudness;
|
||||||
float _browAudioLift;
|
float _browAudioLift;
|
||||||
float _audioAverageLoudness;
|
float _audioAverageLoudness;
|
||||||
std::vector<float> _blendshapeCoefficients;
|
QVector<float> _blendshapeCoefficients;
|
||||||
float _pupilDilation;
|
float _pupilDilation;
|
||||||
AvatarData* _owningAvatar;
|
AvatarData* _owningAvatar;
|
||||||
|
|
||||||
|
|
|
@ -355,15 +355,7 @@ void Octree::deleteOctalCodeFromTree(const unsigned char* codeBuffer, bool colla
|
||||||
|
|
||||||
OctreeElement* node = _rootNode;
|
OctreeElement* node = _rootNode;
|
||||||
|
|
||||||
// We can't encode and delete nodes at the same time, so we guard against deleting any node that is actively
|
deleteOctalCodeFromTreeRecursion(node, &args);
|
||||||
// being encoded. And we stick that code on our pendingDelete list.
|
|
||||||
if (isEncoding(codeBuffer)) {
|
|
||||||
queueForLaterDelete(codeBuffer);
|
|
||||||
} else {
|
|
||||||
startDeleting(codeBuffer);
|
|
||||||
deleteOctalCodeFromTreeRecursion(node, &args);
|
|
||||||
doneDeleting(codeBuffer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Octree::deleteOctalCodeFromTreeRecursion(OctreeElement* node, void* extraData) {
|
void Octree::deleteOctalCodeFromTreeRecursion(OctreeElement* node, void* extraData) {
|
||||||
|
@ -796,11 +788,8 @@ int Octree::encodeTreeBitstream(OctreeElement* node,
|
||||||
return bytesWritten;
|
return bytesWritten;
|
||||||
}
|
}
|
||||||
|
|
||||||
startEncoding(node);
|
|
||||||
|
|
||||||
// If we're at a node that is out of view, then we can return, because no nodes below us will be in view!
|
// If we're at a node that is out of view, then we can return, because no nodes below us will be in view!
|
||||||
if (params.viewFrustum && !node->isInView(*params.viewFrustum)) {
|
if (params.viewFrustum && !node->isInView(*params.viewFrustum)) {
|
||||||
doneEncoding(node);
|
|
||||||
params.stopReason = EncodeBitstreamParams::OUT_OF_VIEW;
|
params.stopReason = EncodeBitstreamParams::OUT_OF_VIEW;
|
||||||
return bytesWritten;
|
return bytesWritten;
|
||||||
}
|
}
|
||||||
|
@ -825,7 +814,6 @@ int Octree::encodeTreeBitstream(OctreeElement* node,
|
||||||
|
|
||||||
// If the octalcode couldn't fit, then we can return, because no nodes below us will fit...
|
// If the octalcode couldn't fit, then we can return, because no nodes below us will fit...
|
||||||
if (!roomForOctalCode) {
|
if (!roomForOctalCode) {
|
||||||
doneEncoding(node);
|
|
||||||
bag.insert(node); // add the node back to the bag so it will eventually get included
|
bag.insert(node); // add the node back to the bag so it will eventually get included
|
||||||
params.stopReason = EncodeBitstreamParams::DIDNT_FIT;
|
params.stopReason = EncodeBitstreamParams::DIDNT_FIT;
|
||||||
return bytesWritten;
|
return bytesWritten;
|
||||||
|
@ -841,7 +829,10 @@ int Octree::encodeTreeBitstream(OctreeElement* node,
|
||||||
params.stats->traversed(node);
|
params.stats->traversed(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
int childBytesWritten = encodeTreeBitstreamRecursion(node, packetData, bag, params, currentEncodeLevel);
|
ViewFrustum::location parentLocationThisView = ViewFrustum::INTERSECT; // assume parent is in view, but not fully
|
||||||
|
|
||||||
|
int childBytesWritten = encodeTreeBitstreamRecursion(node, packetData, bag, params,
|
||||||
|
currentEncodeLevel, parentLocationThisView);
|
||||||
|
|
||||||
// if childBytesWritten == 1 then something went wrong... that's not possible
|
// if childBytesWritten == 1 then something went wrong... that's not possible
|
||||||
assert(childBytesWritten != 1);
|
assert(childBytesWritten != 1);
|
||||||
|
@ -868,14 +859,13 @@ int Octree::encodeTreeBitstream(OctreeElement* node,
|
||||||
packetData->endSubTree();
|
packetData->endSubTree();
|
||||||
}
|
}
|
||||||
|
|
||||||
doneEncoding(node);
|
|
||||||
|
|
||||||
return bytesWritten;
|
return bytesWritten;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Octree::encodeTreeBitstreamRecursion(OctreeElement* node,
|
int Octree::encodeTreeBitstreamRecursion(OctreeElement* node,
|
||||||
OctreePacketData* packetData, OctreeElementBag& bag,
|
OctreePacketData* packetData, OctreeElementBag& bag,
|
||||||
EncodeBitstreamParams& params, int& currentEncodeLevel) const {
|
EncodeBitstreamParams& params, int& currentEncodeLevel,
|
||||||
|
const ViewFrustum::location& parentLocationThisView) const {
|
||||||
// How many bytes have we written so far at this level;
|
// How many bytes have we written so far at this level;
|
||||||
int bytesAtThisLevel = 0;
|
int bytesAtThisLevel = 0;
|
||||||
|
|
||||||
|
@ -906,6 +896,8 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node,
|
||||||
return bytesAtThisLevel;
|
return bytesAtThisLevel;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ViewFrustum::location nodeLocationThisView = ViewFrustum::INSIDE; // assume we're inside
|
||||||
|
|
||||||
// caller can pass NULL as viewFrustum if they want everything
|
// caller can pass NULL as viewFrustum if they want everything
|
||||||
if (params.viewFrustum) {
|
if (params.viewFrustum) {
|
||||||
|
@ -922,10 +914,17 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node,
|
||||||
return bytesAtThisLevel;
|
return bytesAtThisLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if the parent isn't known to be INSIDE, then it must be INTERSECT, and we should double check to see
|
||||||
|
// if we are INSIDE, INTERSECT, or OUTSIDE
|
||||||
|
if (parentLocationThisView != ViewFrustum::INSIDE) {
|
||||||
|
assert(parentLocationThisView != ViewFrustum::OUTSIDE); // we shouldn't be here if our parent was OUTSIDE!
|
||||||
|
nodeLocationThisView = node->inFrustum(*params.viewFrustum);
|
||||||
|
}
|
||||||
|
|
||||||
// If we're at a node that is out of view, then we can return, because no nodes below us will be in view!
|
// If we're at a node that is out of view, then we can return, because no nodes below us will be in view!
|
||||||
// although technically, we really shouldn't ever be here, because our callers shouldn't be calling us if
|
// although technically, we really shouldn't ever be here, because our callers shouldn't be calling us if
|
||||||
// we're out of view
|
// we're out of view
|
||||||
if (!node->isInView(*params.viewFrustum)) {
|
if (nodeLocationThisView == ViewFrustum::OUTSIDE) {
|
||||||
if (params.stats) {
|
if (params.stats) {
|
||||||
params.stats->skippedOutOfView(node);
|
params.stats->skippedOutOfView(node);
|
||||||
}
|
}
|
||||||
|
@ -1079,7 +1078,11 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node,
|
||||||
OctreeElement* childNode = sortedChildren[i];
|
OctreeElement* childNode = sortedChildren[i];
|
||||||
int originalIndex = indexOfChildren[i];
|
int originalIndex = indexOfChildren[i];
|
||||||
|
|
||||||
bool childIsInView = (childNode && (!params.viewFrustum || childNode->isInView(*params.viewFrustum)));
|
bool childIsInView = (childNode &&
|
||||||
|
( !params.viewFrustum || // no view frustum was given, everything is assumed in view
|
||||||
|
(nodeLocationThisView == ViewFrustum::INSIDE) || // the parent was fully in view, we can assume ALL children are
|
||||||
|
(nodeLocationThisView == ViewFrustum::INTERSECT && childNode->isInView(*params.viewFrustum)) // the parent intersects and the child is in view
|
||||||
|
));
|
||||||
|
|
||||||
if (!childIsInView) {
|
if (!childIsInView) {
|
||||||
// must check childNode here, because it could be we got here because there was no childNode
|
// must check childNode here, because it could be we got here because there was no childNode
|
||||||
|
@ -1306,7 +1309,8 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node,
|
||||||
// This only applies in the view frustum case, in other cases, like file save and copy/past where
|
// This only applies in the view frustum case, in other cases, like file save and copy/past where
|
||||||
// no viewFrustum was requested, we still want to recurse the child tree.
|
// no viewFrustum was requested, we still want to recurse the child tree.
|
||||||
if (!params.viewFrustum || !oneAtBit(childrenColoredBits, originalIndex)) {
|
if (!params.viewFrustum || !oneAtBit(childrenColoredBits, originalIndex)) {
|
||||||
childTreeBytesOut = encodeTreeBitstreamRecursion(childNode, packetData, bag, params, thisLevel);
|
childTreeBytesOut = encodeTreeBitstreamRecursion(childNode, packetData, bag, params,
|
||||||
|
thisLevel, nodeLocationThisView);
|
||||||
}
|
}
|
||||||
|
|
||||||
// remember this for reshuffling
|
// remember this for reshuffling
|
||||||
|
@ -1621,56 +1625,6 @@ void dumpSetContents(const char* name, std::set<unsigned char*> set) {
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
void Octree::startEncoding(OctreeElement* node) {
|
|
||||||
_encodeSetLock.lock();
|
|
||||||
_codesBeingEncoded.insert(node->getOctalCode());
|
|
||||||
_encodeSetLock.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Octree::doneEncoding(OctreeElement* node) {
|
|
||||||
_encodeSetLock.lock();
|
|
||||||
_codesBeingEncoded.erase(node->getOctalCode());
|
|
||||||
_encodeSetLock.unlock();
|
|
||||||
|
|
||||||
// if we have any pending delete codes, then delete them now.
|
|
||||||
emptyDeleteQueue();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Octree::startDeleting(const unsigned char* code) {
|
|
||||||
_deleteSetLock.lock();
|
|
||||||
_codesBeingDeleted.insert(code);
|
|
||||||
_deleteSetLock.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Octree::doneDeleting(const unsigned char* code) {
|
|
||||||
_deleteSetLock.lock();
|
|
||||||
_codesBeingDeleted.erase(code);
|
|
||||||
_deleteSetLock.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Octree::isEncoding(const unsigned char* codeBuffer) {
|
|
||||||
_encodeSetLock.lock();
|
|
||||||
bool isEncoding = (_codesBeingEncoded.find(codeBuffer) != _codesBeingEncoded.end());
|
|
||||||
_encodeSetLock.unlock();
|
|
||||||
return isEncoding;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Octree::queueForLaterDelete(const unsigned char* codeBuffer) {
|
|
||||||
_deletePendingSetLock.lock();
|
|
||||||
_codesPendingDelete.insert(codeBuffer);
|
|
||||||
_deletePendingSetLock.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Octree::emptyDeleteQueue() {
|
|
||||||
_deletePendingSetLock.lock();
|
|
||||||
for (std::set<const unsigned char*>::iterator i = _codesPendingDelete.begin(); i != _codesPendingDelete.end(); ++i) {
|
|
||||||
const unsigned char* codeToDelete = *i;
|
|
||||||
_codesBeingDeleted.erase(codeToDelete);
|
|
||||||
deleteOctalCodeFromTree(codeToDelete, COLLAPSE_EMPTY_TREE);
|
|
||||||
}
|
|
||||||
_deletePendingSetLock.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Octree::cancelImport() {
|
void Octree::cancelImport() {
|
||||||
_stopImport = true;
|
_stopImport = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -287,7 +287,8 @@ protected:
|
||||||
|
|
||||||
int encodeTreeBitstreamRecursion(OctreeElement* node,
|
int encodeTreeBitstreamRecursion(OctreeElement* node,
|
||||||
OctreePacketData* packetData, OctreeElementBag& bag,
|
OctreePacketData* packetData, OctreeElementBag& bag,
|
||||||
EncodeBitstreamParams& params, int& currentEncodeLevel) const;
|
EncodeBitstreamParams& params, int& currentEncodeLevel,
|
||||||
|
const ViewFrustum::location& parentLocationThisView) const;
|
||||||
|
|
||||||
static bool countOctreeElementsOperation(OctreeElement* node, void* extraData);
|
static bool countOctreeElementsOperation(OctreeElement* node, void* extraData);
|
||||||
|
|
||||||
|
@ -302,40 +303,6 @@ protected:
|
||||||
bool _shouldReaverage;
|
bool _shouldReaverage;
|
||||||
bool _stopImport;
|
bool _stopImport;
|
||||||
|
|
||||||
/// Octal Codes of any subtrees currently being encoded. While any of these codes is being encoded, ancestors and
|
|
||||||
/// descendants of them can not be deleted.
|
|
||||||
std::set<const unsigned char*> _codesBeingEncoded;
|
|
||||||
/// mutex lock to protect the encoding set
|
|
||||||
QMutex _encodeSetLock;
|
|
||||||
|
|
||||||
/// Called to indicate that a OctreeElement is in the process of being encoded.
|
|
||||||
void startEncoding(OctreeElement* node);
|
|
||||||
/// Called to indicate that a OctreeElement is done being encoded.
|
|
||||||
void doneEncoding(OctreeElement* node);
|
|
||||||
/// Is the Octal Code currently being deleted?
|
|
||||||
bool isEncoding(const unsigned char* codeBuffer);
|
|
||||||
|
|
||||||
/// Octal Codes of any subtrees currently being deleted. While any of these codes is being deleted, ancestors and
|
|
||||||
/// descendants of them can not be encoded.
|
|
||||||
std::set<const unsigned char*> _codesBeingDeleted;
|
|
||||||
/// mutex lock to protect the deleting set
|
|
||||||
QMutex _deleteSetLock;
|
|
||||||
|
|
||||||
/// Called to indicate that an octal code is in the process of being deleted.
|
|
||||||
void startDeleting(const unsigned char* code);
|
|
||||||
/// Called to indicate that an octal code is done being deleted.
|
|
||||||
void doneDeleting(const unsigned char* code);
|
|
||||||
/// Octal Codes that were attempted to be deleted but couldn't be because they were actively being encoded, and were
|
|
||||||
/// instead queued for later delete
|
|
||||||
std::set<const unsigned char*> _codesPendingDelete;
|
|
||||||
/// mutex lock to protect the deleting set
|
|
||||||
QMutex _deletePendingSetLock;
|
|
||||||
|
|
||||||
/// Adds an Octal Code to the set of codes that needs to be deleted
|
|
||||||
void queueForLaterDelete(const unsigned char* codeBuffer);
|
|
||||||
/// flushes out any Octal Codes that had to be queued
|
|
||||||
void emptyDeleteQueue();
|
|
||||||
|
|
||||||
QReadWriteLock _lock;
|
QReadWriteLock _lock;
|
||||||
|
|
||||||
/// This tree is receiving inbound viewer datagrams.
|
/// This tree is receiving inbound viewer datagrams.
|
||||||
|
|
|
@ -1183,13 +1183,6 @@ float OctreeElement::getEnclosingRadius() const {
|
||||||
return getScale() * sqrtf(3.0f) / 2.0f;
|
return getScale() * sqrtf(3.0f) / 2.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OctreeElement::isInView(const ViewFrustum& viewFrustum) const {
|
|
||||||
AABox box = _box; // use temporary box so we can scale it
|
|
||||||
box.scale(TREE_SCALE);
|
|
||||||
bool inView = (ViewFrustum::OUTSIDE != viewFrustum.boxInFrustum(box));
|
|
||||||
return inView;
|
|
||||||
}
|
|
||||||
|
|
||||||
ViewFrustum::location OctreeElement::inFrustum(const ViewFrustum& viewFrustum) const {
|
ViewFrustum::location OctreeElement::inFrustum(const ViewFrustum& viewFrustum) const {
|
||||||
AABox box = _box; // use temporary box so we can scale it
|
AABox box = _box; // use temporary box so we can scale it
|
||||||
box.scale(TREE_SCALE);
|
box.scale(TREE_SCALE);
|
||||||
|
@ -1209,23 +1202,27 @@ bool OctreeElement::calculateShouldRender(const ViewFrustum* viewFrustum, float
|
||||||
bool shouldRender = false;
|
bool shouldRender = false;
|
||||||
if (hasContent()) {
|
if (hasContent()) {
|
||||||
float furthestDistance = furthestDistanceToCamera(*viewFrustum);
|
float furthestDistance = furthestDistanceToCamera(*viewFrustum);
|
||||||
float boundary = boundaryDistanceForRenderLevel(getLevel() + boundaryLevelAdjust, voxelScaleSize);
|
float childBoundary = boundaryDistanceForRenderLevel(getLevel() + 1 + boundaryLevelAdjust, voxelScaleSize);
|
||||||
float childBoundary = boundaryDistanceForRenderLevel(getLevel() + 1 + boundaryLevelAdjust, voxelScaleSize);
|
bool inChildBoundary = (furthestDistance <= childBoundary);
|
||||||
bool inBoundary = (furthestDistance <= boundary);
|
if (isLeaf() && inChildBoundary) {
|
||||||
bool inChildBoundary = (furthestDistance <= childBoundary);
|
shouldRender = true;
|
||||||
shouldRender = (isLeaf() && inChildBoundary) || (inBoundary && !inChildBoundary);
|
} else {
|
||||||
|
float boundary = childBoundary * 2.0f; // the boundary is always twice the distance of the child boundary
|
||||||
|
bool inBoundary = (furthestDistance <= boundary);
|
||||||
|
shouldRender = inBoundary && !inChildBoundary;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return shouldRender;
|
return shouldRender;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculates the distance to the furthest point of the voxel to the camera
|
// Calculates the distance to the furthest point of the voxel to the camera
|
||||||
|
// does as much math as possible in voxel scale and then scales up to TREE_SCALE at end
|
||||||
float OctreeElement::furthestDistanceToCamera(const ViewFrustum& viewFrustum) const {
|
float OctreeElement::furthestDistanceToCamera(const ViewFrustum& viewFrustum) const {
|
||||||
AABox box = getAABox();
|
glm::vec3 furthestPoint;
|
||||||
box.scale(TREE_SCALE);
|
viewFrustum.getFurthestPointFromCameraVoxelScale(getAABox(), furthestPoint);
|
||||||
glm::vec3 furthestPoint = viewFrustum.getFurthestPointFromCamera(box);
|
glm::vec3 temp = viewFrustum.getPositionVoxelScale() - furthestPoint;
|
||||||
glm::vec3 temp = viewFrustum.getPosition() - furthestPoint;
|
float distanceToFurthestPoint = sqrtf(glm::dot(temp, temp));
|
||||||
float distanceToVoxelCenter = sqrtf(glm::dot(temp, temp));
|
return distanceToFurthestPoint * (float)TREE_SCALE;
|
||||||
return distanceToVoxelCenter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
float OctreeElement::distanceToCamera(const ViewFrustum& viewFrustum) const {
|
float OctreeElement::distanceToCamera(const ViewFrustum& viewFrustum) const {
|
||||||
|
|
|
@ -112,9 +112,7 @@ public:
|
||||||
int getLevel() const { return numberOfThreeBitSectionsInCode(getOctalCode()) + 1; }
|
int getLevel() const { return numberOfThreeBitSectionsInCode(getOctalCode()) + 1; }
|
||||||
|
|
||||||
float getEnclosingRadius() const;
|
float getEnclosingRadius() const;
|
||||||
|
bool isInView(const ViewFrustum& viewFrustum) const { return inFrustum(viewFrustum) != ViewFrustum::OUTSIDE; }
|
||||||
|
|
||||||
bool isInView(const ViewFrustum& viewFrustum) const;
|
|
||||||
ViewFrustum::location inFrustum(const ViewFrustum& viewFrustum) const;
|
ViewFrustum::location inFrustum(const ViewFrustum& viewFrustum) const;
|
||||||
float distanceToCamera(const ViewFrustum& viewFrustum) const;
|
float distanceToCamera(const ViewFrustum& viewFrustum) const;
|
||||||
float furthestDistanceToCamera(const ViewFrustum& viewFrustum) const;
|
float furthestDistanceToCamera(const ViewFrustum& viewFrustum) const;
|
||||||
|
@ -126,7 +124,7 @@ public:
|
||||||
float distanceSquareToPoint(const glm::vec3& point) const; // when you don't need the actual distance, use this.
|
float distanceSquareToPoint(const glm::vec3& point) const; // when you don't need the actual distance, use this.
|
||||||
float distanceToPoint(const glm::vec3& point) const;
|
float distanceToPoint(const glm::vec3& point) const;
|
||||||
|
|
||||||
bool isLeaf() const { return getChildCount() == 0; }
|
bool isLeaf() const { return _childBitmask == 0; }
|
||||||
int getChildCount() const { return numberOfOnes(_childBitmask); }
|
int getChildCount() const { return numberOfOnes(_childBitmask); }
|
||||||
void printDebugDetails(const char* label) const;
|
void printDebugDetails(const char* label) const;
|
||||||
bool isDirty() const { return _isDirty; }
|
bool isDirty() const { return _isDirty; }
|
||||||
|
|
|
@ -10,9 +10,8 @@
|
||||||
#include <OctalCode.h>
|
#include <OctalCode.h>
|
||||||
|
|
||||||
OctreeElementBag::OctreeElementBag() :
|
OctreeElementBag::OctreeElementBag() :
|
||||||
_bagElements(NULL),
|
_bagElements()
|
||||||
_elementsInUse(0),
|
{
|
||||||
_sizeOfElementsArray(0) {
|
|
||||||
OctreeElement::addDeleteHook(this);
|
OctreeElement::addDeleteHook(this);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,114 +20,35 @@ OctreeElementBag::~OctreeElementBag() {
|
||||||
deleteAll();
|
deleteAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
void OctreeElementBag::deleteAll() {
|
|
||||||
if (_bagElements) {
|
|
||||||
delete[] _bagElements;
|
|
||||||
}
|
|
||||||
_bagElements = NULL;
|
|
||||||
_elementsInUse = 0;
|
|
||||||
_sizeOfElementsArray = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const int GROW_BAG_BY = 100;
|
|
||||||
|
|
||||||
// put a node into the bag
|
|
||||||
void OctreeElementBag::insert(OctreeElement* element) {
|
|
||||||
|
|
||||||
// Search for where we should live in the bag (sorted)
|
|
||||||
// Note: change this to binary search... instead of linear!
|
|
||||||
int insertAt = _elementsInUse;
|
|
||||||
for (int i = 0; i < _elementsInUse; i++) {
|
|
||||||
// just compare the pointers... that's good enough
|
|
||||||
if (_bagElements[i] == element) {
|
|
||||||
return; // exit early!!
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_bagElements[i] > element) {
|
|
||||||
insertAt = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// at this point, inserAt will be the location we want to insert at.
|
|
||||||
|
|
||||||
// If we don't have room in our bag, then grow the bag
|
|
||||||
if (_sizeOfElementsArray < _elementsInUse + 1) {
|
|
||||||
OctreeElement** oldBag = _bagElements;
|
|
||||||
_bagElements = new OctreeElement*[_sizeOfElementsArray + GROW_BAG_BY];
|
|
||||||
_sizeOfElementsArray += GROW_BAG_BY;
|
|
||||||
|
|
||||||
// If we had an old bag...
|
|
||||||
if (oldBag) {
|
|
||||||
// copy old elements into the new bag, but leave a space where we need to
|
|
||||||
// insert the new node
|
|
||||||
memcpy(_bagElements, oldBag, insertAt * sizeof(OctreeElement*));
|
|
||||||
memcpy(&_bagElements[insertAt + 1], &oldBag[insertAt], (_elementsInUse - insertAt) * sizeof(OctreeElement*));
|
|
||||||
delete[] oldBag;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// move existing elements further back in the bag array, leave a space where we need to
|
|
||||||
// insert the new node
|
|
||||||
memmove(&_bagElements[insertAt + 1], &_bagElements[insertAt], (_elementsInUse - insertAt) * sizeof(OctreeElement*));
|
|
||||||
}
|
|
||||||
_bagElements[insertAt] = element;
|
|
||||||
_elementsInUse++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// pull a node out of the bag (could come in any order)
|
|
||||||
OctreeElement* OctreeElementBag::extract() {
|
|
||||||
// pull the last node out, and shrink our list...
|
|
||||||
if (_elementsInUse) {
|
|
||||||
|
|
||||||
// get the last element
|
|
||||||
OctreeElement* element = _bagElements[_elementsInUse - 1];
|
|
||||||
|
|
||||||
// reduce the count
|
|
||||||
_elementsInUse--;
|
|
||||||
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OctreeElementBag::contains(OctreeElement* element) {
|
|
||||||
for (int i = 0; i < _elementsInUse; i++) {
|
|
||||||
// just compare the pointers... that's good enough
|
|
||||||
if (_bagElements[i] == element) {
|
|
||||||
return true; // exit early!!
|
|
||||||
}
|
|
||||||
// if we're past where it should be, then it's not here!
|
|
||||||
if (_bagElements[i] > element) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// if we made it through the entire bag, it's not here!
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OctreeElementBag::remove(OctreeElement* element) {
|
|
||||||
int foundAt = -1;
|
|
||||||
for (int i = 0; i < _elementsInUse; i++) {
|
|
||||||
// just compare the pointers... that's good enough
|
|
||||||
if (_bagElements[i] == element) {
|
|
||||||
foundAt = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// if we're past where it should be, then it's not here!
|
|
||||||
if (_bagElements[i] > element) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// if we found it, then we need to remove it....
|
|
||||||
if (foundAt != -1) {
|
|
||||||
memmove(&_bagElements[foundAt], &_bagElements[foundAt + 1], (_elementsInUse - foundAt) * sizeof(OctreeElement*));
|
|
||||||
_elementsInUse--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void OctreeElementBag::elementDeleted(OctreeElement* element) {
|
void OctreeElementBag::elementDeleted(OctreeElement* element) {
|
||||||
remove(element); // note: remove can safely handle nodes that aren't in it, so we don't need to check contains()
|
remove(element); // note: remove can safely handle nodes that aren't in it, so we don't need to check contains()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void OctreeElementBag::deleteAll() {
|
||||||
|
_bagElements.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void OctreeElementBag::insert(OctreeElement* element) {
|
||||||
|
_bagElements.insert(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
OctreeElement* OctreeElementBag::extract() {
|
||||||
|
OctreeElement* result = NULL;
|
||||||
|
|
||||||
|
if (_bagElements.size() > 0) {
|
||||||
|
QSet<OctreeElement*>::iterator front = _bagElements.begin();
|
||||||
|
result = *front;
|
||||||
|
_bagElements.erase(front);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OctreeElementBag::contains(OctreeElement* element) {
|
||||||
|
return _bagElements.contains(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OctreeElementBag::remove(OctreeElement* element) {
|
||||||
|
_bagElements.remove(element);
|
||||||
|
}
|
||||||
|
|
|
@ -27,18 +27,14 @@ public:
|
||||||
bool contains(OctreeElement* element); // is this element in the bag?
|
bool contains(OctreeElement* element); // is this element in the bag?
|
||||||
void remove(OctreeElement* element); // remove a specific element from the bag
|
void remove(OctreeElement* element); // remove a specific element from the bag
|
||||||
|
|
||||||
bool isEmpty() const { return (_elementsInUse == 0); }
|
bool isEmpty() const { return _bagElements.isEmpty(); }
|
||||||
int count() const { return _elementsInUse; }
|
int count() const { return _bagElements.size(); }
|
||||||
|
|
||||||
void deleteAll();
|
void deleteAll();
|
||||||
virtual void elementDeleted(OctreeElement* element);
|
virtual void elementDeleted(OctreeElement* element);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
QSet<OctreeElement*> _bagElements;
|
||||||
OctreeElement** _bagElements;
|
|
||||||
int _elementsInUse;
|
|
||||||
int _sizeOfElementsArray;
|
|
||||||
//int _hookID;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* defined(__hifi__OctreeElementBag__) */
|
#endif /* defined(__hifi__OctreeElementBag__) */
|
||||||
|
|
|
@ -25,6 +25,7 @@ using namespace std;
|
||||||
|
|
||||||
ViewFrustum::ViewFrustum() :
|
ViewFrustum::ViewFrustum() :
|
||||||
_position(0,0,0),
|
_position(0,0,0),
|
||||||
|
_positionVoxelScale(0,0,0),
|
||||||
_orientation(),
|
_orientation(),
|
||||||
_direction(IDENTITY_FRONT),
|
_direction(IDENTITY_FRONT),
|
||||||
_up(IDENTITY_UP),
|
_up(IDENTITY_UP),
|
||||||
|
@ -277,6 +278,10 @@ ViewFrustum::location ViewFrustum::boxInFrustum(const AABox& box) const {
|
||||||
return keyholeResult;
|
return keyholeResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: These calculations are expensive, taking up 80% of our time in this function.
|
||||||
|
// This appears to be expensive because we have to test the distance to each plane.
|
||||||
|
// One suggested optimization is to first check against the approximated cone. We might
|
||||||
|
// also be able to test against the cone to the bounding sphere of the box.
|
||||||
for(int i=0; i < 6; i++) {
|
for(int i=0; i < 6; i++) {
|
||||||
const glm::vec3& normal = _planes[i].getNormal();
|
const glm::vec3& normal = _planes[i].getNormal();
|
||||||
const glm::vec3& boxVertexP = box.getVertexP(normal);
|
const glm::vec3& boxVertexP = box.getVertexP(normal);
|
||||||
|
@ -691,39 +696,60 @@ OctreeProjectedPolygon ViewFrustum::getProjectedPolygon(const AABox& box) const
|
||||||
return projectedPolygon;
|
return projectedPolygon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Similar strategy to getProjectedPolygon() we use the knowledge of camera position relative to the
|
// Similar strategy to getProjectedPolygon() we use the knowledge of camera position relative to the
|
||||||
// axis-aligned voxels to determine which of the voxels vertices must be the furthest. No need for
|
// axis-aligned voxels to determine which of the voxels vertices must be the furthest. No need for
|
||||||
// squares and square-roots. Just compares.
|
// squares and square-roots. Just compares.
|
||||||
glm::vec3 ViewFrustum::getFurthestPointFromCamera(const AABox& box) const {
|
void ViewFrustum::getFurthestPointFromCamera(const AABox& box, glm::vec3& furthestPoint) const {
|
||||||
const glm::vec3& bottomNearRight = box.getCorner();
|
const glm::vec3& bottomNearRight = box.getCorner();
|
||||||
glm::vec3 center = box.calcCenter();
|
float scale = box.getScale();
|
||||||
glm::vec3 topFarLeft = box.calcTopFarLeft();
|
float halfScale = scale * 0.5f;
|
||||||
|
|
||||||
glm::vec3 furthestPoint;
|
if (_position.x < bottomNearRight.x + halfScale) {
|
||||||
if (_position.x < center.x) {
|
|
||||||
// we are to the right of the center, so the left edge is furthest
|
// we are to the right of the center, so the left edge is furthest
|
||||||
furthestPoint.x = topFarLeft.x;
|
furthestPoint.x = bottomNearRight.x + scale;
|
||||||
} else {
|
} else {
|
||||||
// we are to the left of the center, so the right edge is furthest (at center ok too)
|
|
||||||
furthestPoint.x = bottomNearRight.x;
|
furthestPoint.x = bottomNearRight.x;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_position.y < center.y) {
|
if (_position.y < bottomNearRight.y + halfScale) {
|
||||||
// we are below of the center, so the top edge is furthest
|
// we are below of the center, so the top edge is furthest
|
||||||
furthestPoint.y = topFarLeft.y;
|
furthestPoint.y = bottomNearRight.y + scale;
|
||||||
} else {
|
} else {
|
||||||
// we are above the center, so the lower edge is furthest (at center ok too)
|
|
||||||
furthestPoint.y = bottomNearRight.y;
|
furthestPoint.y = bottomNearRight.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_position.z < center.z) {
|
if (_position.z < bottomNearRight.z + halfScale) {
|
||||||
// we are to the near side of the center, so the far side edge is furthest
|
// we are to the near side of the center, so the far side edge is furthest
|
||||||
furthestPoint.z = topFarLeft.z;
|
furthestPoint.z = bottomNearRight.z + scale;
|
||||||
} else {
|
} else {
|
||||||
// we are to the far side of the center, so the near side edge is furthest (at center ok too)
|
|
||||||
furthestPoint.z = bottomNearRight.z;
|
furthestPoint.z = bottomNearRight.z;
|
||||||
}
|
}
|
||||||
|
|
||||||
return furthestPoint;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ViewFrustum::getFurthestPointFromCameraVoxelScale(const AABox& box, glm::vec3& furthestPoint) const {
|
||||||
|
const glm::vec3& bottomNearRight = box.getCorner();
|
||||||
|
float scale = box.getScale();
|
||||||
|
float halfScale = scale * 0.5f;
|
||||||
|
|
||||||
|
if (_positionVoxelScale.x < bottomNearRight.x + halfScale) {
|
||||||
|
// we are to the right of the center, so the left edge is furthest
|
||||||
|
furthestPoint.x = bottomNearRight.x + scale;
|
||||||
|
} else {
|
||||||
|
furthestPoint.x = bottomNearRight.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_positionVoxelScale.y < bottomNearRight.y + halfScale) {
|
||||||
|
// we are below of the center, so the top edge is furthest
|
||||||
|
furthestPoint.y = bottomNearRight.y + scale;
|
||||||
|
} else {
|
||||||
|
furthestPoint.y = bottomNearRight.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_positionVoxelScale.z < bottomNearRight.z + halfScale) {
|
||||||
|
// we are to the near side of the center, so the far side edge is furthest
|
||||||
|
furthestPoint.z = bottomNearRight.z + scale;
|
||||||
|
} else {
|
||||||
|
furthestPoint.z = bottomNearRight.z;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,11 +29,12 @@ const float DEFAULT_FAR_CLIP = 50.0f * TREE_SCALE;
|
||||||
class ViewFrustum {
|
class ViewFrustum {
|
||||||
public:
|
public:
|
||||||
// setters for camera attributes
|
// setters for camera attributes
|
||||||
void setPosition(const glm::vec3& p) { _position = p; }
|
void setPosition(const glm::vec3& p) { _position = p; _positionVoxelScale = (p / (float)TREE_SCALE); }
|
||||||
void setOrientation(const glm::quat& orientationAsQuaternion);
|
void setOrientation(const glm::quat& orientationAsQuaternion);
|
||||||
|
|
||||||
// getters for camera attributes
|
// getters for camera attributes
|
||||||
const glm::vec3& getPosition() const { return _position; }
|
const glm::vec3& getPosition() const { return _position; }
|
||||||
|
const glm::vec3& getPositionVoxelScale() const { return _positionVoxelScale; }
|
||||||
const glm::quat& getOrientation() const { return _orientation; }
|
const glm::quat& getOrientation() const { return _orientation; }
|
||||||
const glm::vec3& getDirection() const { return _direction; }
|
const glm::vec3& getDirection() const { return _direction; }
|
||||||
const glm::vec3& getUp() const { return _up; }
|
const glm::vec3& getUp() const { return _up; }
|
||||||
|
@ -102,8 +103,11 @@ public:
|
||||||
|
|
||||||
glm::vec2 projectPoint(glm::vec3 point, bool& pointInView) const;
|
glm::vec2 projectPoint(glm::vec3 point, bool& pointInView) const;
|
||||||
OctreeProjectedPolygon getProjectedPolygon(const AABox& box) const;
|
OctreeProjectedPolygon getProjectedPolygon(const AABox& box) const;
|
||||||
glm::vec3 getFurthestPointFromCamera(const AABox& box) const;
|
void getFurthestPointFromCamera(const AABox& box, glm::vec3& furthestPoint) const;
|
||||||
|
|
||||||
|
// assumes box is in voxel scale, not TREE_SCALE, will scale view frustum's position accordingly
|
||||||
|
void getFurthestPointFromCameraVoxelScale(const AABox& box, glm::vec3& furthestPoint) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Used for keyhole calculations
|
// Used for keyhole calculations
|
||||||
ViewFrustum::location pointInKeyhole(const glm::vec3& point) const;
|
ViewFrustum::location pointInKeyhole(const glm::vec3& point) const;
|
||||||
|
@ -111,7 +115,8 @@ private:
|
||||||
ViewFrustum::location boxInKeyhole(const AABox& box) const;
|
ViewFrustum::location boxInKeyhole(const AABox& box) const;
|
||||||
|
|
||||||
// camera location/orientation attributes
|
// camera location/orientation attributes
|
||||||
glm::vec3 _position;
|
glm::vec3 _position; // the position in TREE_SCALE
|
||||||
|
glm::vec3 _positionVoxelScale; // the position in voxel scale
|
||||||
glm::quat _orientation;
|
glm::quat _orientation;
|
||||||
|
|
||||||
// calculated for orientation
|
// calculated for orientation
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
#include <QtNetwork/QNetworkRequest>
|
#include <QtNetwork/QNetworkRequest>
|
||||||
#include <QtNetwork/QNetworkReply>
|
#include <QtNetwork/QNetworkReply>
|
||||||
|
|
||||||
|
#include <AudioRingBuffer.h>
|
||||||
#include <AvatarData.h>
|
#include <AvatarData.h>
|
||||||
#include <NodeList.h>
|
#include <NodeList.h>
|
||||||
#include <PacketHeaders.h>
|
#include <PacketHeaders.h>
|
||||||
|
@ -52,7 +53,9 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, bool wantMenuItems, co
|
||||||
_avatarIdentityTimer(NULL),
|
_avatarIdentityTimer(NULL),
|
||||||
_avatarBillboardTimer(NULL),
|
_avatarBillboardTimer(NULL),
|
||||||
_timerFunctionMap(),
|
_timerFunctionMap(),
|
||||||
_avatarAudioBuffer(NULL),
|
_isListeningToAudioStream(false),
|
||||||
|
_avatarSound(NULL),
|
||||||
|
_numAvatarSoundSentBytes(0),
|
||||||
_controllerScriptingInterface(controllerScriptingInterface),
|
_controllerScriptingInterface(controllerScriptingInterface),
|
||||||
_avatarData(NULL),
|
_avatarData(NULL),
|
||||||
_wantMenuItems(wantMenuItems),
|
_wantMenuItems(wantMenuItems),
|
||||||
|
@ -260,27 +263,55 @@ void ScriptEngine::run() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_isAvatar && _avatarData) {
|
if (_isAvatar && _avatarData) {
|
||||||
|
|
||||||
|
const int SCRIPT_AUDIO_BUFFER_SAMPLES = floor(((SCRIPT_DATA_CALLBACK_USECS * SAMPLE_RATE) / (1000 * 1000)) + 0.5);
|
||||||
|
const int SCRIPT_AUDIO_BUFFER_BYTES = SCRIPT_AUDIO_BUFFER_SAMPLES * sizeof(int16_t);
|
||||||
|
|
||||||
QByteArray avatarPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarData);
|
QByteArray avatarPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarData);
|
||||||
avatarPacket.append(_avatarData->toByteArray());
|
avatarPacket.append(_avatarData->toByteArray());
|
||||||
|
|
||||||
nodeList->broadcastToNodes(avatarPacket, NodeSet() << NodeType::AvatarMixer);
|
nodeList->broadcastToNodes(avatarPacket, NodeSet() << NodeType::AvatarMixer);
|
||||||
|
|
||||||
if (_avatarAudioBuffer && _numAvatarAudioBufferSamples > 0) {
|
if (_isListeningToAudioStream || _avatarSound) {
|
||||||
// if have an avatar audio stream then send it out to our audio-mixer
|
// if we have an avatar audio stream then send it out to our audio-mixer
|
||||||
|
|
||||||
bool silentFrame = true;
|
bool silentFrame = true;
|
||||||
|
|
||||||
// check if the all of the _numAvatarAudioBufferSamples to be sent are silence
|
int16_t numAvailableSamples = SCRIPT_AUDIO_BUFFER_SAMPLES;
|
||||||
for (int i = 0; i < _numAvatarAudioBufferSamples; ++i) {
|
const int16_t* nextSoundOutput = NULL;
|
||||||
if (_avatarAudioBuffer[i] != 0) {
|
|
||||||
silentFrame = false;
|
if (_avatarSound) {
|
||||||
break;
|
|
||||||
|
const QByteArray& soundByteArray = _avatarSound->getByteArray();
|
||||||
|
nextSoundOutput = reinterpret_cast<const int16_t*>(soundByteArray.data()
|
||||||
|
+ _numAvatarSoundSentBytes);
|
||||||
|
|
||||||
|
int numAvailableBytes = (soundByteArray.size() - _numAvatarSoundSentBytes) > SCRIPT_AUDIO_BUFFER_BYTES
|
||||||
|
? SCRIPT_AUDIO_BUFFER_BYTES
|
||||||
|
: soundByteArray.size() - _numAvatarSoundSentBytes;
|
||||||
|
numAvailableSamples = numAvailableBytes / sizeof(int16_t);
|
||||||
|
|
||||||
|
|
||||||
|
// check if the all of the _numAvatarAudioBufferSamples to be sent are silence
|
||||||
|
for (int i = 0; i < numAvailableSamples; ++i) {
|
||||||
|
if (nextSoundOutput[i] != 0) {
|
||||||
|
silentFrame = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_numAvatarSoundSentBytes += numAvailableBytes;
|
||||||
|
if (_numAvatarSoundSentBytes == soundByteArray.size()) {
|
||||||
|
// we're done with this sound object - so set our pointer back to NULL
|
||||||
|
// and our sent bytes back to zero
|
||||||
|
_avatarSound = NULL;
|
||||||
|
_numAvatarSoundSentBytes = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray audioPacket = byteArrayWithPopulatedHeader(silentFrame
|
QByteArray audioPacket = byteArrayWithPopulatedHeader(silentFrame
|
||||||
? PacketTypeSilentAudioFrame
|
? PacketTypeSilentAudioFrame
|
||||||
: PacketTypeMicrophoneAudioNoEcho);
|
: PacketTypeMicrophoneAudioNoEcho);
|
||||||
|
|
||||||
QDataStream packetStream(&audioPacket, QIODevice::Append);
|
QDataStream packetStream(&audioPacket, QIODevice::Append);
|
||||||
|
|
||||||
// use the orientation and position of this avatar for the source of this audio
|
// use the orientation and position of this avatar for the source of this audio
|
||||||
|
@ -289,13 +320,17 @@ void ScriptEngine::run() {
|
||||||
packetStream.writeRawData(reinterpret_cast<const char*>(&headOrientation), sizeof(glm::quat));
|
packetStream.writeRawData(reinterpret_cast<const char*>(&headOrientation), sizeof(glm::quat));
|
||||||
|
|
||||||
if (silentFrame) {
|
if (silentFrame) {
|
||||||
|
if (!_isListeningToAudioStream) {
|
||||||
|
// if we have a silent frame and we're not listening then just send nothing and break out of here
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// write the number of silent samples so the audio-mixer can uphold timing
|
// write the number of silent samples so the audio-mixer can uphold timing
|
||||||
int16_t numSilentSamples = _numAvatarAudioBufferSamples;
|
packetStream.writeRawData(reinterpret_cast<const char*>(&SCRIPT_AUDIO_BUFFER_SAMPLES), sizeof(int16_t));
|
||||||
packetStream.writeRawData(reinterpret_cast<const char*>(&numSilentSamples), sizeof(int16_t));
|
} else if (nextSoundOutput) {
|
||||||
} else {
|
|
||||||
// write the raw audio data
|
// write the raw audio data
|
||||||
packetStream.writeRawData(reinterpret_cast<const char*>(_avatarAudioBuffer),
|
packetStream.writeRawData(reinterpret_cast<const char*>(nextSoundOutput),
|
||||||
_numAvatarAudioBufferSamples * sizeof(int16_t));
|
numAvailableSamples * sizeof(int16_t));
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeList->broadcastToNodes(audioPacket, NodeSet() << NodeType::AudioMixer);
|
nodeList->broadcastToNodes(audioPacket, NodeSet() << NodeType::AudioMixer);
|
||||||
|
@ -303,7 +338,7 @@ void ScriptEngine::run() {
|
||||||
}
|
}
|
||||||
|
|
||||||
qint64 now = usecTimestampNow();
|
qint64 now = usecTimestampNow();
|
||||||
float deltaTime = (float)(now - lastUpdate)/(float)USECS_PER_SECOND;
|
float deltaTime = (float) (now - lastUpdate) / (float) USECS_PER_SECOND;
|
||||||
emit update(deltaTime);
|
emit update(deltaTime);
|
||||||
lastUpdate = now;
|
lastUpdate = now;
|
||||||
|
|
||||||
|
|
|
@ -56,10 +56,11 @@ public:
|
||||||
|
|
||||||
void setAvatarData(AvatarData* avatarData, const QString& objectName);
|
void setAvatarData(AvatarData* avatarData, const QString& objectName);
|
||||||
|
|
||||||
void setAvatarAudioBuffer(int16_t* avatarAudioBuffer) { _avatarAudioBuffer = avatarAudioBuffer; }
|
bool isListeningToAudioStream() const { return _isListeningToAudioStream; }
|
||||||
bool sendsAvatarAudioStream() const { return (bool) _avatarAudioBuffer; }
|
void setIsListeningToAudioStream(bool isListeningToAudioStream) { _isListeningToAudioStream = isListeningToAudioStream; }
|
||||||
void setNumAvatarAudioBufferSamples(int numAvatarAudioBufferSamples)
|
|
||||||
{ _numAvatarAudioBufferSamples = numAvatarAudioBufferSamples; }
|
void setAvatarSound(Sound* avatarSound) { _avatarSound = avatarSound; }
|
||||||
|
bool isPlayingAvatarSound() const { return _avatarSound != NULL; }
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
void run(); /// runs continuously until Agent.stop() is called
|
void run(); /// runs continuously until Agent.stop() is called
|
||||||
|
@ -91,8 +92,9 @@ protected:
|
||||||
QTimer* _avatarIdentityTimer;
|
QTimer* _avatarIdentityTimer;
|
||||||
QTimer* _avatarBillboardTimer;
|
QTimer* _avatarBillboardTimer;
|
||||||
QHash<QTimer*, QScriptValue> _timerFunctionMap;
|
QHash<QTimer*, QScriptValue> _timerFunctionMap;
|
||||||
int16_t* _avatarAudioBuffer;
|
bool _isListeningToAudioStream;
|
||||||
int _numAvatarAudioBufferSamples;
|
Sound* _avatarSound;
|
||||||
|
int _numAvatarSoundSentBytes;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void sendAvatarIdentityPacket();
|
void sendAvatarIdentityPacket();
|
||||||
|
|
|
@ -88,9 +88,16 @@ bool NodeList::packetVersionAndHashMatch(const QByteArray& packet) {
|
||||||
PacketType mismatchType = packetTypeForPacket(packet);
|
PacketType mismatchType = packetTypeForPacket(packet);
|
||||||
int numPacketTypeBytes = numBytesArithmeticCodingFromBuffer(packet.data());
|
int numPacketTypeBytes = numBytesArithmeticCodingFromBuffer(packet.data());
|
||||||
|
|
||||||
qDebug() << "Packet version mismatch on" << packetTypeForPacket(packet) << "- Sender"
|
static QMultiMap<QUuid, PacketType> versionDebugSuppressMap;
|
||||||
|
|
||||||
|
QUuid senderUUID = uuidFromPacketHeader(packet);
|
||||||
|
if (!versionDebugSuppressMap.contains(senderUUID, checkType)) {
|
||||||
|
qDebug() << "Packet version mismatch on" << packetTypeForPacket(packet) << "- Sender"
|
||||||
<< uuidFromPacketHeader(packet) << "sent" << qPrintable(QString::number(packet[numPacketTypeBytes])) << "but"
|
<< uuidFromPacketHeader(packet) << "sent" << qPrintable(QString::number(packet[numPacketTypeBytes])) << "but"
|
||||||
<< qPrintable(QString::number(versionForPacketType(mismatchType))) << "expected.";
|
<< qPrintable(QString::number(versionForPacketType(mismatchType))) << "expected.";
|
||||||
|
|
||||||
|
versionDebugSuppressMap.insert(senderUUID, checkType);
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ int packArithmeticallyCodedValue(int value, char* destination) {
|
||||||
PacketVersion versionForPacketType(PacketType type) {
|
PacketVersion versionForPacketType(PacketType type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case PacketTypeAvatarData:
|
case PacketTypeAvatarData:
|
||||||
return 2;
|
return 3;
|
||||||
case PacketTypeParticleData:
|
case PacketTypeParticleData:
|
||||||
return 1;
|
return 1;
|
||||||
case PacketTypeDomainList:
|
case PacketTypeDomainList:
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
|
|
||||||
/// Generalized threaded processor for handling received inbound packets.
|
/// Generalized threaded processor for handling received inbound packets.
|
||||||
class ReceivedPacketProcessor : public GenericThread {
|
class ReceivedPacketProcessor : public GenericThread {
|
||||||
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
ReceivedPacketProcessor() { }
|
ReceivedPacketProcessor() { }
|
||||||
|
|
||||||
|
|
|
@ -130,14 +130,22 @@ void outputBits(unsigned char byte, QDebug* continuedDebug) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int numberOfOnes(unsigned char byte) {
|
int numberOfOnes(unsigned char byte) {
|
||||||
return (byte >> 7)
|
|
||||||
+ ((byte >> 6) & 1)
|
static const int nbits[256] = {
|
||||||
+ ((byte >> 5) & 1)
|
0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,1,2,2,3,2,3,3,4,2,3,3,
|
||||||
+ ((byte >> 4) & 1)
|
4,3,4,4,5,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,
|
||||||
+ ((byte >> 3) & 1)
|
4,5,3,4,4,5,4,5,5,6,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,
|
||||||
+ ((byte >> 2) & 1)
|
3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,
|
||||||
+ ((byte >> 1) & 1)
|
4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,1,2,2,3,2,3,3,
|
||||||
+ (byte & 1);
|
4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,
|
||||||
|
3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,
|
||||||
|
6,6,7,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,
|
||||||
|
4,5,5,6,5,6,6,7,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,4,5,5,
|
||||||
|
6,5,6,6,7,5,6,6,7,6,7,7,8
|
||||||
|
};
|
||||||
|
|
||||||
|
return nbits[(unsigned char) byte];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool oneAtBit(unsigned char byte, int bitIndex) {
|
bool oneAtBit(unsigned char byte, int bitIndex) {
|
||||||
|
|
|
@ -42,13 +42,15 @@ int SimpleMovingAverage::updateAverage(float sample) {
|
||||||
|
|
||||||
void SimpleMovingAverage::reset() {
|
void SimpleMovingAverage::reset() {
|
||||||
_numSamples = 0;
|
_numSamples = 0;
|
||||||
|
_average = 0;
|
||||||
|
_eventDeltaAverage = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
float SimpleMovingAverage::getEventDeltaAverage() {
|
float SimpleMovingAverage::getEventDeltaAverage() const {
|
||||||
return (ONE_MINUS_WEIGHTING * _eventDeltaAverage) +
|
return (ONE_MINUS_WEIGHTING * _eventDeltaAverage) +
|
||||||
(WEIGHTING * ((usecTimestampNow() - _lastEventTimestamp) / 1000000.0f));
|
(WEIGHTING * ((usecTimestampNow() - _lastEventTimestamp) / 1000000.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
float SimpleMovingAverage::getAverageSampleValuePerSecond() {
|
float SimpleMovingAverage::getAverageSampleValuePerSecond() const {
|
||||||
return _average * (1 / getEventDeltaAverage());
|
return _average * (1 / getEventDeltaAverage());
|
||||||
}
|
}
|
|
@ -20,10 +20,10 @@ public:
|
||||||
int updateAverage(float sample);
|
int updateAverage(float sample);
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
int getSampleCount() { return _numSamples; };
|
int getSampleCount() const { return _numSamples; };
|
||||||
float getAverage() { return _average; };
|
float getAverage() const { return _average; };
|
||||||
float getEventDeltaAverage();
|
float getEventDeltaAverage() const;
|
||||||
float getAverageSampleValuePerSecond();
|
float getAverageSampleValuePerSecond() const;
|
||||||
private:
|
private:
|
||||||
int _numSamples;
|
int _numSamples;
|
||||||
uint64_t _lastEventTimestamp;
|
uint64_t _lastEventTimestamp;
|
||||||
|
|
|
@ -29,6 +29,7 @@ void ThreadedAssignment::setFinished(bool isFinished) {
|
||||||
_isFinished = isFinished;
|
_isFinished = isFinished;
|
||||||
|
|
||||||
if (_isFinished) {
|
if (_isFinished) {
|
||||||
|
aboutToFinish();
|
||||||
emit finished();
|
emit finished();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,18 +15,17 @@ class ThreadedAssignment : public Assignment {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
ThreadedAssignment(const QByteArray& packet);
|
ThreadedAssignment(const QByteArray& packet);
|
||||||
|
|
||||||
void setFinished(bool isFinished);
|
void setFinished(bool isFinished);
|
||||||
|
virtual void aboutToFinish() { };
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
/// threaded run of assignment
|
/// threaded run of assignment
|
||||||
virtual void run() = 0;
|
virtual void run() = 0;
|
||||||
|
|
||||||
virtual void deleteLater();
|
virtual void deleteLater();
|
||||||
|
|
||||||
virtual void readPendingDatagrams() = 0;
|
virtual void readPendingDatagrams() = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool readAvailableDatagram(QByteArray& destinationByteArray, HifiSockAddr& senderSockAddr);
|
bool readAvailableDatagram(QByteArray& destinationByteArray, HifiSockAddr& senderSockAddr);
|
||||||
|
|
||||||
void commonInit(const QString& targetName, NodeType_t nodeType);
|
void commonInit(const QString& targetName, NodeType_t nodeType);
|
||||||
bool _isFinished;
|
bool _isFinished;
|
||||||
private slots:
|
private slots:
|
||||||
|
|
Loading…
Reference in a new issue