mirror of
https://github.com/overte-org/overte.git
synced 2025-08-09 17:24:24 +02:00
Merge branch 'master' of https://github.com/worklist/hifi into bugfixes
This commit is contained in:
commit
f1704e9d79
24 changed files with 641 additions and 183 deletions
|
@ -3396,20 +3396,91 @@ void Application::displayStats() {
|
||||||
sprintf(avatarStats, "Avatar: pos %.3f, %.3f, %.3f, vel %.1f, yaw = %.2f", avatarPos.x, avatarPos.y, avatarPos.z, glm::length(_myAvatar.getVelocity()), _myAvatar.getBodyYaw());
|
sprintf(avatarStats, "Avatar: pos %.3f, %.3f, %.3f, vel %.1f, yaw = %.2f", avatarPos.x, avatarPos.y, avatarPos.z, glm::length(_myAvatar.getVelocity()), _myAvatar.getBodyYaw());
|
||||||
drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, avatarStats);
|
drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, avatarStats);
|
||||||
|
|
||||||
|
Node* avatarMixer = NodeList::getInstance()->soloNodeOfType(NODE_TYPE_AVATAR_MIXER);
|
||||||
QLocale locale(QLocale::English);
|
char avatarMixerStats[200];
|
||||||
|
if (avatarMixer) {
|
||||||
|
sprintf(avatarMixerStats, "Avatar Mixer: %.f kbps, %.f pps",
|
||||||
|
roundf(avatarMixer->getAverageKilobitsPerSecond()),
|
||||||
|
roundf(avatarMixer->getAveragePacketsPerSecond()));
|
||||||
|
} else {
|
||||||
|
sprintf(avatarMixerStats, "No Avatar Mixer");
|
||||||
|
}
|
||||||
|
statsVerticalOffset += PELS_PER_LINE;
|
||||||
|
drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, avatarMixerStats);
|
||||||
|
|
||||||
|
|
||||||
|
// Used for formatting voxel stats details
|
||||||
|
statsVerticalOffset += PELS_PER_LINE; // skip a line for voxels
|
||||||
|
QLocale locale(QLocale::English);
|
||||||
std::stringstream voxelStats;
|
std::stringstream voxelStats;
|
||||||
voxelStats.precision(4);
|
|
||||||
voxelStats << "Voxels " <<
|
// iterate all the current voxel stats, and list their sending modes, and total voxel counts
|
||||||
"Max: " << _voxels.getMaxVoxels() / 1000.f << "K " <<
|
std::stringstream sendingMode("");
|
||||||
"Rendered: " << _voxels.getVoxelsRendered() / 1000.f << "K " <<
|
sendingMode << "Voxel Sending Mode: [";
|
||||||
"Written: " << _voxels.getVoxelsWritten() / 1000.f << "K " <<
|
int serverCount = 0;
|
||||||
"Abandoned: " << _voxels.getAbandonedVoxels() / 1000.f << "K " <<
|
int movingServerCount = 0;
|
||||||
"Updated: " << _voxels.getVoxelsUpdated() / 1000.f << "K ";
|
unsigned long totalNodes = 0;
|
||||||
|
unsigned long totalInternal = 0;
|
||||||
|
unsigned long totalLeaves = 0;
|
||||||
|
for(NodeToVoxelSceneStatsIterator i = _voxelServerSceneStats.begin(); i != _voxelServerSceneStats.end(); i++) {
|
||||||
|
//const QUuid& uuid = i->first;
|
||||||
|
VoxelSceneStats& stats = i->second;
|
||||||
|
serverCount++;
|
||||||
|
if (serverCount > 1) {
|
||||||
|
sendingMode << ",";
|
||||||
|
}
|
||||||
|
if (stats.isMoving()) {
|
||||||
|
sendingMode << "M";
|
||||||
|
movingServerCount++;
|
||||||
|
} else {
|
||||||
|
sendingMode << "S";
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate server node totals
|
||||||
|
totalNodes += stats.getTotalVoxels();
|
||||||
|
totalInternal += stats.getTotalInternal();
|
||||||
|
totalLeaves += stats.getTotalLeaves();
|
||||||
|
}
|
||||||
|
if (serverCount == 0) {
|
||||||
|
sendingMode << "---";
|
||||||
|
}
|
||||||
|
sendingMode << "] " << serverCount << " servers";
|
||||||
|
if (movingServerCount > 0) {
|
||||||
|
sendingMode << " <SCENE NOT STABLE>";
|
||||||
|
} else {
|
||||||
|
sendingMode << " <SCENE STABLE>";
|
||||||
|
}
|
||||||
|
|
||||||
|
QString serversTotalString = locale.toString((uint)totalNodes); // consider adding: .rightJustified(10, ' ');
|
||||||
|
QString serversInternalString = locale.toString((uint)totalInternal);
|
||||||
|
QString serversLeavesString = locale.toString((uint)totalLeaves);
|
||||||
|
|
||||||
|
// Server Voxels
|
||||||
|
voxelStats.str("");
|
||||||
|
voxelStats <<
|
||||||
|
"Server Voxels Total: " << serversTotalString.toLocal8Bit().constData() << " / " <<
|
||||||
|
"Internal: " << serversInternalString.toLocal8Bit().constData() << " / " <<
|
||||||
|
"Leaves: " << serversLeavesString.toLocal8Bit().constData() << "";
|
||||||
statsVerticalOffset += PELS_PER_LINE;
|
statsVerticalOffset += PELS_PER_LINE;
|
||||||
drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char*)voxelStats.str().c_str());
|
drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char*)voxelStats.str().c_str());
|
||||||
|
|
||||||
|
unsigned long localTotal = VoxelNode::getNodeCount();
|
||||||
|
unsigned long localInternal = VoxelNode::getInternalNodeCount();
|
||||||
|
unsigned long localLeaves = VoxelNode::getLeafNodeCount();
|
||||||
|
QString localTotalString = locale.toString((uint)localTotal); // consider adding: .rightJustified(10, ' ');
|
||||||
|
QString localInternalString = locale.toString((uint)localInternal);
|
||||||
|
QString localLeavesString = locale.toString((uint)localLeaves);
|
||||||
|
|
||||||
|
// Local Voxels
|
||||||
|
voxelStats.str("");
|
||||||
|
voxelStats <<
|
||||||
|
"Local Voxels Total: " << localTotalString.toLocal8Bit().constData() << " / " <<
|
||||||
|
"Internal: " << localInternalString.toLocal8Bit().constData() << " / " <<
|
||||||
|
"Leaves: " << localLeavesString.toLocal8Bit().constData() << "";
|
||||||
|
statsVerticalOffset += PELS_PER_LINE;
|
||||||
|
drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char*)voxelStats.str().c_str());
|
||||||
|
|
||||||
|
// Local Voxel Memory Usage
|
||||||
voxelStats.str("");
|
voxelStats.str("");
|
||||||
voxelStats <<
|
voxelStats <<
|
||||||
"Voxels Memory Nodes: " << VoxelNode::getTotalMemoryUsage() / 1000000.f << "MB "
|
"Voxels Memory Nodes: " << VoxelNode::getTotalMemoryUsage() / 1000000.f << "MB "
|
||||||
|
@ -3421,51 +3492,25 @@ void Application::displayStats() {
|
||||||
statsVerticalOffset += PELS_PER_LINE;
|
statsVerticalOffset += PELS_PER_LINE;
|
||||||
drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char*)voxelStats.str().c_str());
|
drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char*)voxelStats.str().c_str());
|
||||||
|
|
||||||
unsigned long localTotal = VoxelNode::getNodeCount();
|
// Voxel Rendering
|
||||||
unsigned long localInternal = VoxelNode::getInternalNodeCount();
|
|
||||||
unsigned long localLeaves = VoxelNode::getLeafNodeCount();
|
|
||||||
QString localTotalString = locale.toString((uint)localTotal); // consider adding: .rightJustified(10, ' ');
|
|
||||||
QString localInternalString = locale.toString((uint)localInternal);
|
|
||||||
QString localLeavesString = locale.toString((uint)localLeaves);
|
|
||||||
|
|
||||||
|
|
||||||
voxelStats.str("");
|
voxelStats.str("");
|
||||||
voxelStats <<
|
voxelStats.precision(4);
|
||||||
"Local Voxels Total: " << localTotalString.toLocal8Bit().constData() << " / " <<
|
voxelStats << "Voxel Rendering Slots" <<
|
||||||
"Internal: " << localInternalString.toLocal8Bit().constData() << " / " <<
|
"Max: " << _voxels.getMaxVoxels() / 1000.f << "K " <<
|
||||||
"Leaves: " << localLeavesString.toLocal8Bit().constData() << "";
|
"Drawn: " << _voxels.getVoxelsWritten() / 1000.f << "K " <<
|
||||||
|
"Abandoned: " << _voxels.getAbandonedVoxels() / 1000.f << "K ";
|
||||||
statsVerticalOffset += PELS_PER_LINE;
|
statsVerticalOffset += PELS_PER_LINE;
|
||||||
drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char*)voxelStats.str().c_str());
|
drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char*)voxelStats.str().c_str());
|
||||||
|
|
||||||
voxelStats.str("");
|
// draw Sending mode AFTER server node stats
|
||||||
char* voxelDetails = _voxelSceneStats.getItemValue(VoxelSceneStats::ITEM_VOXELS);
|
|
||||||
voxelStats << "Voxels Sent from Server: " << voxelDetails;
|
|
||||||
statsVerticalOffset += PELS_PER_LINE;
|
statsVerticalOffset += PELS_PER_LINE;
|
||||||
drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char*)voxelStats.str().c_str());
|
drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char*)sendingMode.str().c_str());
|
||||||
|
|
||||||
voxelStats.str("");
|
|
||||||
voxelDetails = _voxelSceneStats.getItemValue(VoxelSceneStats::ITEM_ELAPSED);
|
|
||||||
voxelStats << "Scene Send Time from Server: " << voxelDetails;
|
|
||||||
statsVerticalOffset += PELS_PER_LINE;
|
|
||||||
drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char*)voxelStats.str().c_str());
|
|
||||||
|
|
||||||
voxelStats.str("");
|
|
||||||
voxelDetails = _voxelSceneStats.getItemValue(VoxelSceneStats::ITEM_ENCODE);
|
|
||||||
voxelStats << "Encode Time on Server: " << voxelDetails;
|
|
||||||
statsVerticalOffset += PELS_PER_LINE;
|
|
||||||
drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char*)voxelStats.str().c_str());
|
|
||||||
|
|
||||||
voxelStats.str("");
|
|
||||||
voxelDetails = _voxelSceneStats.getItemValue(VoxelSceneStats::ITEM_MODE);
|
|
||||||
voxelStats << "Sending Mode: " << voxelDetails;
|
|
||||||
statsVerticalOffset += PELS_PER_LINE;
|
|
||||||
drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char*)voxelStats.str().c_str());
|
|
||||||
|
|
||||||
|
// Incoming packets
|
||||||
voxelStats.str("");
|
voxelStats.str("");
|
||||||
int voxelPacketsToProcess = _voxelProcessor.packetsToProcessCount();
|
int voxelPacketsToProcess = _voxelProcessor.packetsToProcessCount();
|
||||||
QString packetsString = locale.toString((int)voxelPacketsToProcess);
|
QString packetsString = locale.toString((int)voxelPacketsToProcess);
|
||||||
QString maxString = locale.toString((int)_recentMaxPackets);
|
QString maxString = locale.toString((int)_recentMaxPackets);
|
||||||
|
|
||||||
voxelStats << "Voxel Packets to Process: " << packetsString.toLocal8Bit().constData()
|
voxelStats << "Voxel Packets to Process: " << packetsString.toLocal8Bit().constData()
|
||||||
<< " [Recent Max: " << maxString.toLocal8Bit().constData() << "]";
|
<< " [Recent Max: " << maxString.toLocal8Bit().constData() << "]";
|
||||||
|
|
||||||
|
@ -3483,19 +3528,8 @@ void Application::displayStats() {
|
||||||
statsVerticalOffset += PELS_PER_LINE;
|
statsVerticalOffset += PELS_PER_LINE;
|
||||||
drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char*)voxelStats.str().c_str());
|
drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char*)voxelStats.str().c_str());
|
||||||
|
|
||||||
|
|
||||||
Node *avatarMixer = NodeList::getInstance()->soloNodeOfType(NODE_TYPE_AVATAR_MIXER);
|
// Leap data
|
||||||
char avatarMixerStats[200];
|
|
||||||
|
|
||||||
if (avatarMixer) {
|
|
||||||
sprintf(avatarMixerStats, "Avatar Mixer: %.f kbps, %.f pps",
|
|
||||||
roundf(avatarMixer->getAverageKilobitsPerSecond()),
|
|
||||||
roundf(avatarMixer->getAveragePacketsPerSecond()));
|
|
||||||
} else {
|
|
||||||
sprintf(avatarMixerStats, "No Avatar Mixer");
|
|
||||||
}
|
|
||||||
statsVerticalOffset += PELS_PER_LINE;
|
|
||||||
drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, avatarMixerStats);
|
|
||||||
statsVerticalOffset += PELS_PER_LINE;
|
statsVerticalOffset += PELS_PER_LINE;
|
||||||
drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char*)LeapManager::statusString().c_str());
|
drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char*)LeapManager::statusString().c_str());
|
||||||
|
|
||||||
|
@ -4075,6 +4109,10 @@ void Application::domainChanged(QString domain) {
|
||||||
|
|
||||||
// reset the environment so that we don't erroneously end up with multiple
|
// reset the environment so that we don't erroneously end up with multiple
|
||||||
_environment.resetToDefault();
|
_environment.resetToDefault();
|
||||||
|
|
||||||
|
// reset our node to stats and node to jurisdiction maps... since these must be changing...
|
||||||
|
_voxelServerJurisdictions.clear();
|
||||||
|
_voxelServerSceneStats.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::nodeAdded(Node* node) {
|
void Application::nodeAdded(Node* node) {
|
||||||
|
@ -4101,6 +4139,14 @@ void Application::nodeKilled(Node* node) {
|
||||||
fade.voxelDetails.s = fade.voxelDetails.s * slightly_smaller;
|
fade.voxelDetails.s = fade.voxelDetails.s * slightly_smaller;
|
||||||
_voxelFades.push_back(fade);
|
_voxelFades.push_back(fade);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the voxel server is going away, remove it from our jurisdiction map so we don't send voxels to a dead server
|
||||||
|
_voxelServerJurisdictions.erase(nodeUUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// also clean up scene stats for that server
|
||||||
|
if (_voxelServerSceneStats.find(nodeUUID) != _voxelServerSceneStats.end()) {
|
||||||
|
_voxelServerSceneStats.erase(nodeUUID);
|
||||||
}
|
}
|
||||||
} else if (node->getLinkedData() == _lookatTargetAvatar) {
|
} else if (node->getLinkedData() == _lookatTargetAvatar) {
|
||||||
_lookatTargetAvatar = NULL;
|
_lookatTargetAvatar = NULL;
|
||||||
|
@ -4109,19 +4155,43 @@ void Application::nodeKilled(Node* node) {
|
||||||
|
|
||||||
int Application::parseVoxelStats(unsigned char* messageData, ssize_t messageLength, sockaddr senderAddress) {
|
int Application::parseVoxelStats(unsigned char* messageData, ssize_t messageLength, sockaddr senderAddress) {
|
||||||
|
|
||||||
// parse the incoming stats data, and stick it into our averaging stats object for now... even though this
|
|
||||||
// means mixing in stats from potentially multiple servers.
|
|
||||||
int statsMessageLength = _voxelSceneStats.unpackFromMessage(messageData, messageLength);
|
|
||||||
|
|
||||||
// But, also identify the sender, and keep track of the contained jurisdiction root for this server
|
// But, also identify the sender, and keep track of the contained jurisdiction root for this server
|
||||||
Node* voxelServer = NodeList::getInstance()->nodeWithAddress(&senderAddress);
|
Node* voxelServer = NodeList::getInstance()->nodeWithAddress(&senderAddress);
|
||||||
|
|
||||||
|
// parse the incoming stats datas stick it in a temporary object for now, while we
|
||||||
|
// determine which server it belongs to
|
||||||
|
VoxelSceneStats temp;
|
||||||
|
int statsMessageLength = temp.unpackFromMessage(messageData, messageLength);
|
||||||
|
|
||||||
// quick fix for crash... why would voxelServer be NULL?
|
// quick fix for crash... why would voxelServer be NULL?
|
||||||
if (voxelServer) {
|
if (voxelServer) {
|
||||||
QUuid nodeUUID = voxelServer->getUUID();
|
QUuid nodeUUID = voxelServer->getUUID();
|
||||||
|
|
||||||
|
// now that we know the node ID, let's add these stats to the stats for that node...
|
||||||
|
if (_voxelServerSceneStats.find(nodeUUID) != _voxelServerSceneStats.end()) {
|
||||||
|
VoxelSceneStats& oldStats = _voxelServerSceneStats[nodeUUID];
|
||||||
|
|
||||||
|
// this if construct is a little strange because we aren't quite using it yet. But
|
||||||
|
// we want to keep this logic in here for now because we plan to use it soon to determine
|
||||||
|
// additional network optimization states and better rate control
|
||||||
|
if (!oldStats.isMoving() && temp.isMoving()) {
|
||||||
|
// we think we are starting to move
|
||||||
|
_voxelServerSceneStats[nodeUUID].unpackFromMessage(messageData, messageLength);
|
||||||
|
} else if (oldStats.isMoving() && !temp.isMoving()) {
|
||||||
|
// we think we are done moving
|
||||||
|
_voxelServerSceneStats[nodeUUID].unpackFromMessage(messageData, messageLength);
|
||||||
|
} else if (!oldStats.isMoving() && !temp.isMoving()) {
|
||||||
|
// we think we are still not moving
|
||||||
|
} else {
|
||||||
|
// we think we are still moving
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
_voxelServerSceneStats[nodeUUID] = temp;
|
||||||
|
}
|
||||||
|
|
||||||
VoxelPositionSize rootDetails;
|
VoxelPositionSize rootDetails;
|
||||||
voxelDetailsForCode(_voxelSceneStats.getJurisdictionRoot(), rootDetails);
|
voxelDetailsForCode(temp.getJurisdictionRoot(), rootDetails);
|
||||||
|
|
||||||
// see if this is the first we've heard of this node...
|
// see if this is the first we've heard of this node...
|
||||||
if (_voxelServerJurisdictions.find(nodeUUID) == _voxelServerJurisdictions.end()) {
|
if (_voxelServerJurisdictions.find(nodeUUID) == _voxelServerJurisdictions.end()) {
|
||||||
|
@ -4142,7 +4212,7 @@ int Application::parseVoxelStats(unsigned char* messageData, ssize_t messageLeng
|
||||||
// but VoxelSceneStats thinks it's just returning a reference to it's contents. So we need to make a copy of the
|
// but VoxelSceneStats thinks it's just returning a reference to it's contents. So we need to make a copy of the
|
||||||
// details from the VoxelSceneStats to construct the JurisdictionMap
|
// details from the VoxelSceneStats to construct the JurisdictionMap
|
||||||
JurisdictionMap jurisdictionMap;
|
JurisdictionMap jurisdictionMap;
|
||||||
jurisdictionMap.copyContents(_voxelSceneStats.getJurisdictionRoot(), _voxelSceneStats.getJurisdictionEndNodes());
|
jurisdictionMap.copyContents(temp.getJurisdictionRoot(), temp.getJurisdictionEndNodes());
|
||||||
_voxelServerJurisdictions[nodeUUID] = jurisdictionMap;
|
_voxelServerJurisdictions[nodeUUID] = jurisdictionMap;
|
||||||
}
|
}
|
||||||
return statsMessageLength;
|
return statsMessageLength;
|
||||||
|
|
|
@ -135,7 +135,7 @@ public:
|
||||||
QSettings* getSettings() { return _settings; }
|
QSettings* getSettings() { return _settings; }
|
||||||
Swatch* getSwatch() { return &_swatch; }
|
Swatch* getSwatch() { return &_swatch; }
|
||||||
QMainWindow* getWindow() { return _window; }
|
QMainWindow* getWindow() { return _window; }
|
||||||
VoxelSceneStats* getVoxelSceneStats() { return &_voxelSceneStats; }
|
NodeToVoxelSceneStats* getVoxelSceneStats() { return &_voxelServerSceneStats; }
|
||||||
|
|
||||||
QNetworkAccessManager* getNetworkAccessManager() { return _networkAccessManager; }
|
QNetworkAccessManager* getNetworkAccessManager() { return _networkAccessManager; }
|
||||||
GeometryCache* getGeometryCache() { return &_geometryCache; }
|
GeometryCache* getGeometryCache() { return &_geometryCache; }
|
||||||
|
@ -448,10 +448,10 @@ private:
|
||||||
|
|
||||||
PieMenu _pieMenu;
|
PieMenu _pieMenu;
|
||||||
|
|
||||||
VoxelSceneStats _voxelSceneStats;
|
|
||||||
int parseVoxelStats(unsigned char* messageData, ssize_t messageLength, sockaddr senderAddress);
|
int parseVoxelStats(unsigned char* messageData, ssize_t messageLength, sockaddr senderAddress);
|
||||||
|
|
||||||
NodeToJurisdictionMap _voxelServerJurisdictions;
|
NodeToJurisdictionMap _voxelServerJurisdictions;
|
||||||
|
NodeToVoxelSceneStats _voxelServerSceneStats;
|
||||||
|
|
||||||
std::vector<VoxelFade> _voxelFades;
|
std::vector<VoxelFade> _voxelFades;
|
||||||
};
|
};
|
||||||
|
|
|
@ -355,6 +355,7 @@ Menu::Menu() :
|
||||||
QMenu* raveGloveOptionsMenu = developerMenu->addMenu("Rave Glove Options");
|
QMenu* raveGloveOptionsMenu = developerMenu->addMenu("Rave Glove Options");
|
||||||
|
|
||||||
addCheckableActionToQMenuAndActionHash(raveGloveOptionsMenu, MenuOption::SimulateLeapHand);
|
addCheckableActionToQMenuAndActionHash(raveGloveOptionsMenu, MenuOption::SimulateLeapHand);
|
||||||
|
addCheckableActionToQMenuAndActionHash(raveGloveOptionsMenu, MenuOption::DisplayLeapHands, 0, true);
|
||||||
addCheckableActionToQMenuAndActionHash(raveGloveOptionsMenu, MenuOption::TestRaveGlove);
|
addCheckableActionToQMenuAndActionHash(raveGloveOptionsMenu, MenuOption::TestRaveGlove);
|
||||||
|
|
||||||
QMenu* trackingOptionsMenu = developerMenu->addMenu("Tracking Options");
|
QMenu* trackingOptionsMenu = developerMenu->addMenu("Tracking Options");
|
||||||
|
|
|
@ -160,6 +160,7 @@ namespace MenuOption {
|
||||||
const QString DisableConstantCulling = "Disable Constant Culling";
|
const QString DisableConstantCulling = "Disable Constant Culling";
|
||||||
const QString DisableFastVoxelPipeline = "Disable Fast Voxel Pipeline";
|
const QString DisableFastVoxelPipeline = "Disable Fast Voxel Pipeline";
|
||||||
const QString DisplayFrustum = "Display Frustum";
|
const QString DisplayFrustum = "Display Frustum";
|
||||||
|
const QString DisplayLeapHands = "Display Leap Hands";
|
||||||
const QString DontRenderVoxels = "Don't call _voxels.render()";
|
const QString DontRenderVoxels = "Don't call _voxels.render()";
|
||||||
const QString DontCallOpenGLForVoxels = "Don't call glDrawRangeElementsEXT() for Voxels";
|
const QString DontCallOpenGLForVoxels = "Don't call glDrawRangeElementsEXT() for Voxels";
|
||||||
const QString EchoAudio = "Echo Audio";
|
const QString EchoAudio = "Echo Audio";
|
||||||
|
|
|
@ -162,6 +162,12 @@ glm::vec3 extractTranslation(const glm::mat4& matrix) {
|
||||||
return glm::vec3(matrix[3][0], matrix[3][1], matrix[3][2]);
|
return glm::vec3(matrix[3][0], matrix[3][1], matrix[3][2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setTranslation(glm::mat4& matrix, const glm::vec3& translation) {
|
||||||
|
matrix[3][0] = translation.x;
|
||||||
|
matrix[3][1] = translation.y;
|
||||||
|
matrix[3][2] = translation.z;
|
||||||
|
}
|
||||||
|
|
||||||
glm::quat extractRotation(const glm::mat4& matrix, bool assumeOrthogonal) {
|
glm::quat extractRotation(const glm::mat4& matrix, bool assumeOrthogonal) {
|
||||||
// uses the iterative polar decomposition algorithm described by Ken Shoemake at
|
// uses the iterative polar decomposition algorithm described by Ken Shoemake at
|
||||||
// http://www.cs.wisc.edu/graphics/Courses/838-s2002/Papers/polar-decomp.pdf
|
// http://www.cs.wisc.edu/graphics/Courses/838-s2002/Papers/polar-decomp.pdf
|
||||||
|
|
|
@ -57,6 +57,8 @@ glm::quat safeMix(const glm::quat& q1, const glm::quat& q2, float alpha);
|
||||||
|
|
||||||
glm::vec3 extractTranslation(const glm::mat4& matrix);
|
glm::vec3 extractTranslation(const glm::mat4& matrix);
|
||||||
|
|
||||||
|
void setTranslation(glm::mat4& matrix, const glm::vec3& translation);
|
||||||
|
|
||||||
glm::quat extractRotation(const glm::mat4& matrix, bool assumeOrthogonal = false);
|
glm::quat extractRotation(const glm::mat4& matrix, bool assumeOrthogonal = false);
|
||||||
|
|
||||||
double diffclock(timeval *clock1,timeval *clock2);
|
double diffclock(timeval *clock1,timeval *clock2);
|
||||||
|
|
|
@ -665,12 +665,14 @@ void Avatar::updateArmIKAndConstraints(float deltaTime, AvatarJointID fingerTipJ
|
||||||
float distance = glm::length(armVector);
|
float distance = glm::length(armVector);
|
||||||
|
|
||||||
// don't let right hand get dragged beyond maximum arm length...
|
// don't let right hand get dragged beyond maximum arm length...
|
||||||
if (distance > _maxArmLength) {
|
const float ARM_RETRACTION = 0.75f;
|
||||||
|
float armLength = _maxArmLength * ARM_RETRACTION;
|
||||||
|
if (distance > armLength) {
|
||||||
// reset right hand to be constrained to maximum arm length
|
// reset right hand to be constrained to maximum arm length
|
||||||
fingerJoint.position = shoulderJoint.position;
|
fingerJoint.position = shoulderJoint.position;
|
||||||
glm::vec3 armNormal = armVector / distance;
|
glm::vec3 armNormal = armVector / distance;
|
||||||
armVector = armNormal * _maxArmLength;
|
armVector = armNormal * armLength;
|
||||||
distance = _maxArmLength;
|
distance = armLength;
|
||||||
glm::vec3 constrainedPosition = shoulderJoint.position;
|
glm::vec3 constrainedPosition = shoulderJoint.position;
|
||||||
constrainedPosition += armVector;
|
constrainedPosition += armVector;
|
||||||
fingerJoint.position = constrainedPosition;
|
fingerJoint.position = constrainedPosition;
|
||||||
|
|
|
@ -11,11 +11,10 @@
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
#include "Avatar.h"
|
#include "Avatar.h"
|
||||||
#include "Hand.h"
|
#include "Hand.h"
|
||||||
|
#include "Menu.h"
|
||||||
#include "Util.h"
|
#include "Util.h"
|
||||||
#include "renderer/ProgramObject.h"
|
#include "renderer/ProgramObject.h"
|
||||||
|
|
||||||
const bool SHOW_LEAP_HAND = true;
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
Hand::Hand(Avatar* owningAvatar) :
|
Hand::Hand(Avatar* owningAvatar) :
|
||||||
|
@ -139,7 +138,7 @@ void Hand::render(bool lookingInMirror) {
|
||||||
|
|
||||||
calculateGeometry();
|
calculateGeometry();
|
||||||
|
|
||||||
if ( SHOW_LEAP_HAND ) {
|
if (Menu::getInstance()->isOptionChecked(MenuOption::DisplayLeapHands)) {
|
||||||
if (!isRaveGloveActive()) {
|
if (!isRaveGloveActive()) {
|
||||||
renderLeapFingerTrails();
|
renderLeapFingerTrails();
|
||||||
}
|
}
|
||||||
|
|
|
@ -603,13 +603,6 @@ float MyAvatar::getBallRenderAlpha(int ball, bool lookingInMirror) const {
|
||||||
|
|
||||||
void MyAvatar::renderBody(bool lookingInMirror, bool renderAvatarBalls) {
|
void MyAvatar::renderBody(bool lookingInMirror, bool renderAvatarBalls) {
|
||||||
|
|
||||||
if (Application::getInstance()->getCamera()->getMode() == CAMERA_MODE_FIRST_PERSON && !lookingInMirror) {
|
|
||||||
// Dont display body, only the hand
|
|
||||||
_hand.render(lookingInMirror);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_head.getVideoFace().isFullFrame()) {
|
if (_head.getVideoFace().isFullFrame()) {
|
||||||
// Render the full-frame video
|
// Render the full-frame video
|
||||||
float alpha = getBallRenderAlpha(BODY_BALL_HEAD_BASE, lookingInMirror);
|
float alpha = getBallRenderAlpha(BODY_BALL_HEAD_BASE, lookingInMirror);
|
||||||
|
@ -684,11 +677,11 @@ void MyAvatar::renderBody(bool lookingInMirror, bool renderAvatarBalls) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Render the body's voxels and head
|
// Render the body's voxels and head
|
||||||
|
if (!_skeletonModel.render(1.0f)) {
|
||||||
|
_voxels.render(false);
|
||||||
|
}
|
||||||
float alpha = getBallRenderAlpha(BODY_BALL_HEAD_BASE, lookingInMirror);
|
float alpha = getBallRenderAlpha(BODY_BALL_HEAD_BASE, lookingInMirror);
|
||||||
if (alpha > 0.0f) {
|
if (alpha > 0.0f) {
|
||||||
if (!_skeletonModel.render(alpha)) {
|
|
||||||
_voxels.render(false);
|
|
||||||
}
|
|
||||||
_head.render(alpha, true);
|
_head.render(alpha, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,12 +27,50 @@ void SkeletonModel::simulate(float deltaTime) {
|
||||||
setScale(glm::vec3(1.0f, 1.0f, 1.0f) * _owningAvatar->getScale() * MODEL_SCALE);
|
setScale(glm::vec3(1.0f, 1.0f, 1.0f) * _owningAvatar->getScale() * MODEL_SCALE);
|
||||||
|
|
||||||
Model::simulate(deltaTime);
|
Model::simulate(deltaTime);
|
||||||
|
|
||||||
|
// find the left and rightmost active Leap palms
|
||||||
|
HandData& hand = _owningAvatar->getHand();
|
||||||
|
int leftPalmIndex = -1;
|
||||||
|
float leftPalmX = FLT_MAX;
|
||||||
|
int rightPalmIndex = -1;
|
||||||
|
float rightPalmX = -FLT_MAX;
|
||||||
|
for (int i = 0; i < hand.getNumPalms(); i++) {
|
||||||
|
if (hand.getPalms()[i].isActive()) {
|
||||||
|
float x = hand.getPalms()[i].getRawPosition().x;
|
||||||
|
if (x < leftPalmX) {
|
||||||
|
leftPalmIndex = i;
|
||||||
|
leftPalmX = x;
|
||||||
|
}
|
||||||
|
if (x > rightPalmX) {
|
||||||
|
rightPalmIndex = i;
|
||||||
|
rightPalmX = x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (_owningAvatar->getHandState() == HAND_STATE_NULL) {
|
const float HAND_RESTORATION_RATE = 0.25f;
|
||||||
const float HAND_RESTORATION_RATE = 0.25f;
|
|
||||||
restoreRightHandPosition(HAND_RESTORATION_RATE);
|
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||||
|
if (leftPalmIndex == -1) {
|
||||||
|
// no Leap data; set hands from mouse
|
||||||
|
if (_owningAvatar->getHandState() == HAND_STATE_NULL) {
|
||||||
|
restoreRightHandPosition(HAND_RESTORATION_RATE);
|
||||||
|
} else {
|
||||||
|
setRightHandPosition(_owningAvatar->getHandPosition());
|
||||||
|
}
|
||||||
|
restoreLeftHandPosition(HAND_RESTORATION_RATE);
|
||||||
|
|
||||||
|
} else if (leftPalmIndex == rightPalmIndex) {
|
||||||
|
// right hand only
|
||||||
|
applyPalmData(geometry.rightHandJointIndex, geometry.rightFingerJointIndices, geometry.rightFingertipJointIndices,
|
||||||
|
hand.getPalms()[leftPalmIndex]);
|
||||||
|
restoreLeftHandPosition(HAND_RESTORATION_RATE);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
setRightHandPosition(_owningAvatar->getHandPosition());
|
applyPalmData(geometry.leftHandJointIndex, geometry.leftFingerJointIndices, geometry.leftFingertipJointIndices,
|
||||||
|
hand.getPalms()[leftPalmIndex]);
|
||||||
|
applyPalmData(geometry.rightHandJointIndex, geometry.rightFingerJointIndices, geometry.rightFingertipJointIndices,
|
||||||
|
hand.getPalms()[rightPalmIndex]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,6 +124,67 @@ bool SkeletonModel::render(float alpha) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class IndexValue {
|
||||||
|
public:
|
||||||
|
int index;
|
||||||
|
float value;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool operator<(const IndexValue& firstIndex, const IndexValue& secondIndex) {
|
||||||
|
return firstIndex.value < secondIndex.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkeletonModel::applyPalmData(int jointIndex, const QVector<int>& fingerJointIndices,
|
||||||
|
const QVector<int>& fingertipJointIndices, PalmData& palm) {
|
||||||
|
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||||
|
setJointPosition(jointIndex, palm.getPosition());
|
||||||
|
float sign = (jointIndex == geometry.rightHandJointIndex) ? 1.0f : -1.0f;
|
||||||
|
glm::quat palmRotation = rotationBetween(_rotation * IDENTITY_UP, -palm.getNormal()) * _rotation *
|
||||||
|
glm::angleAxis(90.0f, 0.0f, sign, 0.0f); // ninety degree rotation to face fingers forward from bind pose
|
||||||
|
|
||||||
|
// sort the finger indices by raw x, get the average direction
|
||||||
|
QVector<IndexValue> fingerIndices;
|
||||||
|
glm::vec3 direction;
|
||||||
|
for (int i = 0; i < palm.getNumFingers(); i++) {
|
||||||
|
glm::vec3 fingerVector = palm.getFingers()[i].getTipPosition() - palm.getPosition();
|
||||||
|
float length = glm::length(fingerVector);
|
||||||
|
if (length > EPSILON) {
|
||||||
|
direction += fingerVector / length;
|
||||||
|
}
|
||||||
|
fingerVector = glm::inverse(palmRotation) * fingerVector * -sign;
|
||||||
|
IndexValue indexValue = { i, atan2f(fingerVector.z, fingerVector.x) };
|
||||||
|
fingerIndices.append(indexValue);
|
||||||
|
}
|
||||||
|
qSort(fingerIndices.begin(), fingerIndices.end());
|
||||||
|
|
||||||
|
// rotate palm according to average finger direction
|
||||||
|
float directionLength = glm::length(direction);
|
||||||
|
if (directionLength > EPSILON) {
|
||||||
|
palmRotation = rotationBetween(palmRotation * glm::vec3(-sign, 0.0f, 0.0f), direction) * palmRotation;
|
||||||
|
}
|
||||||
|
setJointRotation(jointIndex, palmRotation, true);
|
||||||
|
|
||||||
|
// no point in continuing if there are no fingers
|
||||||
|
if (palm.getNumFingers() == 0 || fingerJointIndices.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// match them up as best we can
|
||||||
|
float proportion = fingerIndices.size() / (float)fingerJointIndices.size();
|
||||||
|
for (int i = 0; i < fingerJointIndices.size(); i++) {
|
||||||
|
int fingerIndex = fingerIndices.at(roundf(i * proportion)).index;
|
||||||
|
glm::vec3 fingerVector = palm.getFingers()[fingerIndex].getTipPosition() -
|
||||||
|
palm.getFingers()[fingerIndex].getRootPosition();
|
||||||
|
|
||||||
|
int fingerJointIndex = fingerJointIndices.at(i);
|
||||||
|
int fingertipJointIndex = fingertipJointIndices.at(i);
|
||||||
|
glm::vec3 jointVector = extractTranslation(geometry.joints.at(fingertipJointIndex).bindTransform) -
|
||||||
|
extractTranslation(geometry.joints.at(fingerJointIndex).bindTransform);
|
||||||
|
|
||||||
|
setJointRotation(fingerJointIndex, rotationBetween(palmRotation * jointVector, fingerVector) * palmRotation, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void SkeletonModel::updateJointState(int index) {
|
void SkeletonModel::updateJointState(int index) {
|
||||||
Model::updateJointState(index);
|
Model::updateJointState(index);
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,8 @@
|
||||||
#ifndef __interface__SkeletonModel__
|
#ifndef __interface__SkeletonModel__
|
||||||
#define __interface__SkeletonModel__
|
#define __interface__SkeletonModel__
|
||||||
|
|
||||||
|
#include <HandData.h>
|
||||||
|
|
||||||
#include "renderer/Model.h"
|
#include "renderer/Model.h"
|
||||||
|
|
||||||
class Avatar;
|
class Avatar;
|
||||||
|
@ -26,6 +28,9 @@ public:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
|
void applyPalmData(int jointIndex, const QVector<int>& fingerJointIndices,
|
||||||
|
const QVector<int>& fingertipJointIndices, PalmData& palm);
|
||||||
|
|
||||||
/// Updates the state of the joint at the specified index.
|
/// Updates the state of the joint at the specified index.
|
||||||
virtual void updateJointState(int index);
|
virtual void updateJointState(int index);
|
||||||
|
|
||||||
|
|
|
@ -198,8 +198,8 @@ void LeapManager::nextFrame(Avatar& avatar) {
|
||||||
// There's no real Leap data and we need to fake it.
|
// There's no real Leap data and we need to fake it.
|
||||||
for (size_t i = 0; i < hand.getNumPalms(); ++i) {
|
for (size_t i = 0; i < hand.getNumPalms(); ++i) {
|
||||||
static const glm::vec3 fakeHandOffsets[] = {
|
static const glm::vec3 fakeHandOffsets[] = {
|
||||||
glm::vec3( -500.0f, 50.0f, 50.0f),
|
glm::vec3( -250.0f, 50.0f, 50.0f),
|
||||||
glm::vec3( 0.0f, 50.0f, 50.0f)
|
glm::vec3( 250.0f, 50.0f, 50.0f)
|
||||||
};
|
};
|
||||||
static const glm::vec3 fakeHandFingerMirrors[] = {
|
static const glm::vec3 fakeHandFingerMirrors[] = {
|
||||||
glm::vec3( -1.0f, 1.0f, 1.0f),
|
glm::vec3( -1.0f, 1.0f, 1.0f),
|
||||||
|
@ -218,7 +218,7 @@ void LeapManager::nextFrame(Avatar& avatar) {
|
||||||
// Simulated data
|
// Simulated data
|
||||||
|
|
||||||
palm.setRawPosition(glm::vec3( 0.0f, 0.0f, 0.0f) + fakeHandOffsets[i]);
|
palm.setRawPosition(glm::vec3( 0.0f, 0.0f, 0.0f) + fakeHandOffsets[i]);
|
||||||
palm.setRawNormal(glm::vec3(0.0f, 1.0f, 0.0f));
|
palm.setRawNormal(glm::vec3(0.0f, -1.0f, 0.0f));
|
||||||
|
|
||||||
for (size_t f = 0; f < palm.getNumFingers(); ++f) {
|
for (size_t f = 0; f < palm.getNumFingers(); ++f) {
|
||||||
FingerData& finger = palm.getFingers()[f];
|
FingerData& finger = palm.getFingers()[f];
|
||||||
|
|
|
@ -726,6 +726,17 @@ void setTangents(FBXMesh& mesh, int firstIndex, int secondIndex) {
|
||||||
-glm::degrees(atan2f(-texCoordDelta.t, texCoordDelta.s)), normal) * glm::normalize(bitangent), normal);
|
-glm::degrees(atan2f(-texCoordDelta.t, texCoordDelta.s)), normal) * glm::normalize(bitangent), normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QVector<int> getIndices(const QVector<QString> ids, QVector<QString> modelIDs) {
|
||||||
|
QVector<int> indices;
|
||||||
|
foreach (const QString& id, ids) {
|
||||||
|
int index = modelIDs.indexOf(id);
|
||||||
|
if (index != -1) {
|
||||||
|
indices.append(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return indices;
|
||||||
|
}
|
||||||
|
|
||||||
FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) {
|
FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) {
|
||||||
QHash<QString, ExtractedMesh> meshes;
|
QHash<QString, ExtractedMesh> meshes;
|
||||||
QVector<ExtractedBlendshape> blendshapes;
|
QVector<ExtractedBlendshape> blendshapes;
|
||||||
|
@ -747,6 +758,10 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
||||||
QString jointHeadName = processID(joints.value("jointHead", "jointHead").toString());
|
QString jointHeadName = processID(joints.value("jointHead", "jointHead").toString());
|
||||||
QString jointLeftHandName = processID(joints.value("jointLeftHand", "jointLeftHand").toString());
|
QString jointLeftHandName = processID(joints.value("jointLeftHand", "jointLeftHand").toString());
|
||||||
QString jointRightHandName = processID(joints.value("jointRightHand", "jointRightHand").toString());
|
QString jointRightHandName = processID(joints.value("jointRightHand", "jointRightHand").toString());
|
||||||
|
QVariantList jointLeftFingerNames = joints.values("jointLeftFinger");
|
||||||
|
QVariantList jointRightFingerNames = joints.values("jointRightFinger");
|
||||||
|
QVariantList jointLeftFingertipNames = joints.values("jointLeftFingertip");
|
||||||
|
QVariantList jointRightFingertipNames = joints.values("jointRightFingertip");
|
||||||
QString jointEyeLeftID;
|
QString jointEyeLeftID;
|
||||||
QString jointEyeRightID;
|
QString jointEyeRightID;
|
||||||
QString jointNeckID;
|
QString jointNeckID;
|
||||||
|
@ -755,6 +770,10 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
||||||
QString jointHeadID;
|
QString jointHeadID;
|
||||||
QString jointLeftHandID;
|
QString jointLeftHandID;
|
||||||
QString jointRightHandID;
|
QString jointRightHandID;
|
||||||
|
QVector<QString> jointLeftFingerIDs(jointLeftFingerNames.size());
|
||||||
|
QVector<QString> jointRightFingerIDs(jointRightFingerNames.size());
|
||||||
|
QVector<QString> jointLeftFingertipIDs(jointLeftFingertipNames.size());
|
||||||
|
QVector<QString> jointRightFingertipIDs(jointRightFingertipNames.size());
|
||||||
|
|
||||||
QVariantHash blendshapeMappings = mapping.value("bs").toHash();
|
QVariantHash blendshapeMappings = mapping.value("bs").toHash();
|
||||||
QHash<QByteArray, QPair<int, float> > blendshapeIndices;
|
QHash<QByteArray, QPair<int, float> > blendshapeIndices;
|
||||||
|
@ -811,6 +830,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
||||||
} else {
|
} else {
|
||||||
name = getID(object.properties);
|
name = getID(object.properties);
|
||||||
}
|
}
|
||||||
|
int index;
|
||||||
if (name == jointEyeLeftName || name == "EyeL" || name == "joint_Leye") {
|
if (name == jointEyeLeftName || name == "EyeL" || name == "joint_Leye") {
|
||||||
jointEyeLeftID = getID(object.properties);
|
jointEyeLeftID = getID(object.properties);
|
||||||
|
|
||||||
|
@ -834,6 +854,18 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
||||||
|
|
||||||
} else if (name == jointRightHandName) {
|
} else if (name == jointRightHandName) {
|
||||||
jointRightHandID = getID(object.properties);
|
jointRightHandID = getID(object.properties);
|
||||||
|
|
||||||
|
} else if ((index = jointLeftFingerNames.indexOf(name)) != -1) {
|
||||||
|
jointLeftFingerIDs[index] = getID(object.properties);
|
||||||
|
|
||||||
|
} else if ((index = jointRightFingerNames.indexOf(name)) != -1) {
|
||||||
|
jointRightFingerIDs[index] = getID(object.properties);
|
||||||
|
|
||||||
|
} else if ((index = jointLeftFingertipNames.indexOf(name)) != -1) {
|
||||||
|
jointLeftFingertipIDs[index] = getID(object.properties);
|
||||||
|
|
||||||
|
} else if ((index = jointRightFingertipNames.indexOf(name)) != -1) {
|
||||||
|
jointRightFingertipIDs[index] = getID(object.properties);
|
||||||
}
|
}
|
||||||
glm::vec3 translation;
|
glm::vec3 translation;
|
||||||
glm::vec3 rotationOffset;
|
glm::vec3 rotationOffset;
|
||||||
|
@ -1063,14 +1095,18 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
||||||
glm::quat combinedRotation = model.preRotation * model.rotation * model.postRotation;
|
glm::quat combinedRotation = model.preRotation * model.rotation * model.postRotation;
|
||||||
if (joint.parentIndex == -1) {
|
if (joint.parentIndex == -1) {
|
||||||
joint.transform = geometry.offset * model.preTransform * glm::mat4_cast(combinedRotation) * model.postTransform;
|
joint.transform = geometry.offset * model.preTransform * glm::mat4_cast(combinedRotation) * model.postTransform;
|
||||||
joint.inverseBindRotation = glm::inverse(combinedRotation);
|
joint.inverseDefaultRotation = glm::inverse(combinedRotation);
|
||||||
|
joint.distanceToParent = 0.0f;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
const FBXJoint& parentJoint = geometry.joints.at(joint.parentIndex);
|
const FBXJoint& parentJoint = geometry.joints.at(joint.parentIndex);
|
||||||
joint.transform = parentJoint.transform *
|
joint.transform = parentJoint.transform *
|
||||||
model.preTransform * glm::mat4_cast(combinedRotation) * model.postTransform;
|
model.preTransform * glm::mat4_cast(combinedRotation) * model.postTransform;
|
||||||
joint.inverseBindRotation = glm::inverse(combinedRotation) * parentJoint.inverseBindRotation;
|
joint.inverseDefaultRotation = glm::inverse(combinedRotation) * parentJoint.inverseDefaultRotation;
|
||||||
|
joint.distanceToParent = glm::distance(extractTranslation(parentJoint.transform),
|
||||||
|
extractTranslation(joint.transform));
|
||||||
}
|
}
|
||||||
|
joint.inverseBindRotation = joint.inverseDefaultRotation;
|
||||||
geometry.joints.append(joint);
|
geometry.joints.append(joint);
|
||||||
geometry.jointIndices.insert(model.name, geometry.joints.size() - 1);
|
geometry.jointIndices.insert(model.name, geometry.joints.size() - 1);
|
||||||
}
|
}
|
||||||
|
@ -1084,6 +1120,10 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
||||||
geometry.headJointIndex = modelIDs.indexOf(jointHeadID);
|
geometry.headJointIndex = modelIDs.indexOf(jointHeadID);
|
||||||
geometry.leftHandJointIndex = modelIDs.indexOf(jointLeftHandID);
|
geometry.leftHandJointIndex = modelIDs.indexOf(jointLeftHandID);
|
||||||
geometry.rightHandJointIndex = modelIDs.indexOf(jointRightHandID);
|
geometry.rightHandJointIndex = modelIDs.indexOf(jointRightHandID);
|
||||||
|
geometry.leftFingerJointIndices = getIndices(jointLeftFingerIDs, modelIDs);
|
||||||
|
geometry.rightFingerJointIndices = getIndices(jointRightFingerIDs, modelIDs);
|
||||||
|
geometry.leftFingertipJointIndices = getIndices(jointLeftFingertipIDs, modelIDs);
|
||||||
|
geometry.rightFingertipJointIndices = getIndices(jointRightFingertipIDs, modelIDs);
|
||||||
|
|
||||||
// extract the translation component of the neck transform
|
// extract the translation component of the neck transform
|
||||||
if (geometry.neckJointIndex != -1) {
|
if (geometry.neckJointIndex != -1) {
|
||||||
|
@ -1182,6 +1222,11 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
||||||
}
|
}
|
||||||
fbxCluster.inverseBindMatrix = glm::inverse(cluster.transformLink) * modelTransform;
|
fbxCluster.inverseBindMatrix = glm::inverse(cluster.transformLink) * modelTransform;
|
||||||
extracted.mesh.clusters.append(fbxCluster);
|
extracted.mesh.clusters.append(fbxCluster);
|
||||||
|
|
||||||
|
// override the bind rotation with the transform link
|
||||||
|
FBXJoint& joint = geometry.joints[fbxCluster.jointIndex];
|
||||||
|
joint.inverseBindRotation = glm::inverse(extractRotation(cluster.transformLink));
|
||||||
|
joint.bindTransform = cluster.transformLink;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,13 +46,16 @@ public:
|
||||||
bool isFree;
|
bool isFree;
|
||||||
QVector<int> freeLineage;
|
QVector<int> freeLineage;
|
||||||
int parentIndex;
|
int parentIndex;
|
||||||
|
float distanceToParent;
|
||||||
glm::mat4 preTransform;
|
glm::mat4 preTransform;
|
||||||
glm::quat preRotation;
|
glm::quat preRotation;
|
||||||
glm::quat rotation;
|
glm::quat rotation;
|
||||||
glm::quat postRotation;
|
glm::quat postRotation;
|
||||||
glm::mat4 postTransform;
|
glm::mat4 postTransform;
|
||||||
glm::mat4 transform;
|
glm::mat4 transform;
|
||||||
|
glm::quat inverseDefaultRotation;
|
||||||
glm::quat inverseBindRotation;
|
glm::quat inverseBindRotation;
|
||||||
|
glm::mat4 bindTransform;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A single binding to a joint in an FBX document.
|
/// A single binding to a joint in an FBX document.
|
||||||
|
@ -134,6 +137,12 @@ public:
|
||||||
int leftHandJointIndex;
|
int leftHandJointIndex;
|
||||||
int rightHandJointIndex;
|
int rightHandJointIndex;
|
||||||
|
|
||||||
|
QVector<int> leftFingerJointIndices;
|
||||||
|
QVector<int> rightFingerJointIndices;
|
||||||
|
|
||||||
|
QVector<int> leftFingertipJointIndices;
|
||||||
|
QVector<int> rightFingertipJointIndices;
|
||||||
|
|
||||||
glm::vec3 neckPivot;
|
glm::vec3 neckPivot;
|
||||||
|
|
||||||
QVector<FBXAttachment> attachments;
|
QVector<FBXAttachment> attachments;
|
||||||
|
|
|
@ -485,6 +485,10 @@ bool Model::setLeftHandRotation(const glm::quat& rotation) {
|
||||||
return isActive() && setJointRotation(_geometry->getFBXGeometry().leftHandJointIndex, rotation);
|
return isActive() && setJointRotation(_geometry->getFBXGeometry().leftHandJointIndex, rotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float Model::getLeftArmLength() const {
|
||||||
|
return isActive() ? getLimbLength(_geometry->getFBXGeometry().leftHandJointIndex) : 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
bool Model::setRightHandPosition(const glm::vec3& position) {
|
bool Model::setRightHandPosition(const glm::vec3& position) {
|
||||||
return isActive() && setJointPosition(_geometry->getFBXGeometry().rightHandJointIndex, position);
|
return isActive() && setJointPosition(_geometry->getFBXGeometry().rightHandJointIndex, position);
|
||||||
}
|
}
|
||||||
|
@ -497,6 +501,10 @@ bool Model::setRightHandRotation(const glm::quat& rotation) {
|
||||||
return isActive() && setJointRotation(_geometry->getFBXGeometry().rightHandJointIndex, rotation);
|
return isActive() && setJointRotation(_geometry->getFBXGeometry().rightHandJointIndex, rotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float Model::getRightArmLength() const {
|
||||||
|
return isActive() ? getLimbLength(_geometry->getFBXGeometry().rightHandJointIndex) : 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
void Model::setURL(const QUrl& url) {
|
void Model::setURL(const QUrl& url) {
|
||||||
// don't recreate the geometry if it's the same URL
|
// don't recreate the geometry if it's the same URL
|
||||||
if (_url == url) {
|
if (_url == url) {
|
||||||
|
@ -566,47 +574,96 @@ bool Model::getJointPosition(int jointIndex, glm::vec3& position) const {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Model::getJointRotation(int jointIndex, glm::quat& rotation) const {
|
bool Model::getJointRotation(int jointIndex, glm::quat& rotation, bool fromBind) const {
|
||||||
if (jointIndex == -1 || _jointStates.isEmpty()) {
|
if (jointIndex == -1 || _jointStates.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
rotation = _jointStates[jointIndex].combinedRotation *
|
rotation = _jointStates[jointIndex].combinedRotation *
|
||||||
_geometry->getFBXGeometry().joints[jointIndex].inverseBindRotation;
|
(fromBind ? _geometry->getFBXGeometry().joints[jointIndex].inverseBindRotation :
|
||||||
|
_geometry->getFBXGeometry().joints[jointIndex].inverseDefaultRotation);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Model::setJointPosition(int jointIndex, const glm::vec3& position) {
|
void Model::setJointTranslation(int jointIndex, int parentIndex, int childIndex, const glm::vec3& translation) {
|
||||||
|
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||||
|
JointState& state = _jointStates[jointIndex];
|
||||||
|
if (childIndex != -1 && geometry.joints.at(jointIndex).isFree) {
|
||||||
|
// if there's a child, then I must adjust *my* rotation
|
||||||
|
glm::vec3 childTranslation = extractTranslation(_jointStates.at(childIndex).transform);
|
||||||
|
glm::quat delta = rotationBetween(childTranslation - extractTranslation(state.transform),
|
||||||
|
childTranslation - translation);
|
||||||
|
state.rotation = state.rotation * glm::inverse(state.combinedRotation) * delta * state.combinedRotation;
|
||||||
|
state.combinedRotation = delta * state.combinedRotation;
|
||||||
|
}
|
||||||
|
if (parentIndex != -1 && geometry.joints.at(parentIndex).isFree) {
|
||||||
|
// if there's a parent, then I must adjust *its* rotation
|
||||||
|
JointState& parent = _jointStates[parentIndex];
|
||||||
|
glm::vec3 parentTranslation = extractTranslation(parent.transform);
|
||||||
|
glm::quat delta = rotationBetween(extractTranslation(state.transform) - parentTranslation,
|
||||||
|
translation - parentTranslation);
|
||||||
|
parent.rotation = parent.rotation * glm::inverse(parent.combinedRotation) * delta * parent.combinedRotation;
|
||||||
|
parent.combinedRotation = delta * parent.combinedRotation;
|
||||||
|
}
|
||||||
|
::setTranslation(state.transform, translation);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Model::setJointPosition(int jointIndex, const glm::vec3& position, int lastFreeIndex) {
|
||||||
if (jointIndex == -1 || _jointStates.isEmpty()) {
|
if (jointIndex == -1 || _jointStates.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
glm::vec3 relativePosition = position - _translation;
|
glm::vec3 relativePosition = position - _translation;
|
||||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||||
const QVector<int>& freeLineage = geometry.joints.at(jointIndex).freeLineage;
|
const QVector<int>& freeLineage = geometry.joints.at(jointIndex).freeLineage;
|
||||||
|
if (lastFreeIndex == -1) {
|
||||||
|
lastFreeIndex = freeLineage.last();
|
||||||
|
}
|
||||||
|
|
||||||
// this is a cyclic coordinate descent algorithm: see
|
// this is a constraint relaxation algorithm: see
|
||||||
// http://www.ryanjuckett.com/programming/animation/21-cyclic-coordinate-descent-in-2d
|
// http://www.ryanjuckett.com/programming/animation/22-constraint-relaxation-ik-in-2d
|
||||||
const int ITERATION_COUNT = 1;
|
|
||||||
|
// the influence of gravity; lowers the potential energy of our configurations
|
||||||
|
glm::vec3 gravity = _rotation * IDENTITY_UP * -0.01f;
|
||||||
|
|
||||||
|
// over one or more iterations, apply the length constraints and update the rotations accordingly
|
||||||
|
float uniformScale = (_scale.x + _scale.y + _scale.z) / 3.0f;
|
||||||
|
const int ITERATION_COUNT = 3;
|
||||||
for (int i = 0; i < ITERATION_COUNT; i++) {
|
for (int i = 0; i < ITERATION_COUNT; i++) {
|
||||||
// first, we go from the joint upwards, rotating the end as close as possible to the target
|
// start by optimistically setting the position of the end joint to our target
|
||||||
glm::vec3 endPosition = extractTranslation(_jointStates[jointIndex].transform);
|
setJointTranslation(jointIndex, freeLineage.at(1), -1, relativePosition);
|
||||||
for (int j = 1; j < freeLineage.size(); j++) {
|
|
||||||
int index = freeLineage.at(j);
|
for (int j = 1; freeLineage.at(j - 1) != lastFreeIndex; j++) {
|
||||||
if (glm::distance(endPosition, relativePosition) < EPSILON) {
|
int sourceIndex = freeLineage.at(j);
|
||||||
return true; // close enough to target position
|
int destIndex = freeLineage.at(j - 1);
|
||||||
}
|
JointState& sourceState = _jointStates[sourceIndex];
|
||||||
const FBXJoint& joint = geometry.joints.at(index);
|
JointState& destState = _jointStates[destIndex];
|
||||||
if (!joint.isFree) {
|
glm::vec3 sourceTranslation = extractTranslation(sourceState.transform);
|
||||||
|
glm::vec3 destTranslation = extractTranslation(destState.transform);
|
||||||
|
glm::vec3 boneVector = destTranslation - sourceTranslation;
|
||||||
|
float boneLength = glm::length(boneVector);
|
||||||
|
if (boneLength < EPSILON) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
JointState& state = _jointStates[index];
|
float extension = geometry.joints.at(destIndex).distanceToParent * uniformScale / boneLength - 1.0f;
|
||||||
glm::vec3 jointPosition = extractTranslation(state.transform);
|
if (fabs(extension) < EPSILON) {
|
||||||
glm::vec3 jointVector = endPosition - jointPosition;
|
continue;
|
||||||
glm::quat deltaRotation = rotationBetween(jointVector, relativePosition - jointPosition);
|
}
|
||||||
state.rotation = state.rotation * glm::inverse(state.combinedRotation) * deltaRotation * state.combinedRotation;
|
if (j == 1) {
|
||||||
endPosition = deltaRotation * jointVector + jointPosition;
|
setJointTranslation(sourceIndex, freeLineage.at(j + 1), -1,
|
||||||
|
sourceTranslation - boneVector * extension + gravity);
|
||||||
|
|
||||||
|
} else if (sourceIndex == lastFreeIndex) {
|
||||||
|
setJointTranslation(destIndex, -1, freeLineage.at(j - 2),
|
||||||
|
destTranslation + boneVector * extension + gravity);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
setJointTranslation(sourceIndex, freeLineage.at(j + 1), -1,
|
||||||
|
sourceTranslation - boneVector * extension * 0.5f + gravity);
|
||||||
|
setJointTranslation(destIndex, -1, freeLineage.at(j - 2),
|
||||||
|
destTranslation + boneVector * extension * 0.5f + gravity);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// then, from the first free joint downwards, update the transforms again
|
// now update the joint states from the top
|
||||||
for (int j = freeLineage.size() - 1; j >= 0; j--) {
|
for (int j = freeLineage.size() - 1; j >= 0; j--) {
|
||||||
updateJointState(freeLineage.at(j));
|
updateJointState(freeLineage.at(j));
|
||||||
}
|
}
|
||||||
|
@ -615,13 +672,14 @@ bool Model::setJointPosition(int jointIndex, const glm::vec3& position) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Model::setJointRotation(int jointIndex, const glm::quat& rotation) {
|
bool Model::setJointRotation(int jointIndex, const glm::quat& rotation, bool fromBind) {
|
||||||
if (jointIndex == -1 || _jointStates.isEmpty()) {
|
if (jointIndex == -1 || _jointStates.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
JointState& state = _jointStates[jointIndex];
|
JointState& state = _jointStates[jointIndex];
|
||||||
state.rotation = state.rotation * glm::inverse(state.combinedRotation) * rotation *
|
state.rotation = state.rotation * glm::inverse(state.combinedRotation) * rotation *
|
||||||
glm::inverse(_geometry->getFBXGeometry().joints.at(jointIndex).inverseBindRotation);
|
glm::inverse(fromBind ? _geometry->getFBXGeometry().joints.at(jointIndex).inverseBindRotation :
|
||||||
|
_geometry->getFBXGeometry().joints.at(jointIndex).inverseDefaultRotation);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -638,6 +696,19 @@ bool Model::restoreJointPosition(int jointIndex, float percent) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float Model::getLimbLength(int jointIndex) const {
|
||||||
|
if (jointIndex == -1 || _jointStates.isEmpty()) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||||
|
const QVector<int>& freeLineage = geometry.joints.at(jointIndex).freeLineage;
|
||||||
|
int length = 0.0f;
|
||||||
|
for (int i = freeLineage.size() - 2; i >= 0; i--) {
|
||||||
|
length += geometry.joints.at(freeLineage.at(i)).distanceToParent;
|
||||||
|
}
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
void Model::deleteGeometry() {
|
void Model::deleteGeometry() {
|
||||||
foreach (Model* attachment, _attachments) {
|
foreach (Model* attachment, _attachments) {
|
||||||
delete attachment;
|
delete attachment;
|
||||||
|
|
|
@ -83,6 +83,9 @@ public:
|
||||||
/// \return whether or not the left hand joint was found
|
/// \return whether or not the left hand joint was found
|
||||||
bool setLeftHandRotation(const glm::quat& rotation);
|
bool setLeftHandRotation(const glm::quat& rotation);
|
||||||
|
|
||||||
|
/// Returns the extended length from the left hand to its first free ancestor.
|
||||||
|
float getLeftArmLength() const;
|
||||||
|
|
||||||
/// Sets the position of the right hand using inverse kinematics.
|
/// Sets the position of the right hand using inverse kinematics.
|
||||||
/// \return whether or not the right hand joint was found
|
/// \return whether or not the right hand joint was found
|
||||||
bool setRightHandPosition(const glm::vec3& position);
|
bool setRightHandPosition(const glm::vec3& position);
|
||||||
|
@ -96,6 +99,9 @@ public:
|
||||||
/// \return whether or not the right hand joint was found
|
/// \return whether or not the right hand joint was found
|
||||||
bool setRightHandRotation(const glm::quat& rotation);
|
bool setRightHandRotation(const glm::quat& rotation);
|
||||||
|
|
||||||
|
/// Returns the extended length from the right hand to its first free ancestor.
|
||||||
|
float getRightArmLength() const;
|
||||||
|
|
||||||
/// Returns the average color of all meshes in the geometry.
|
/// Returns the average color of all meshes in the geometry.
|
||||||
glm::vec4 computeAverageColor() const;
|
glm::vec4 computeAverageColor() const;
|
||||||
|
|
||||||
|
@ -135,10 +141,10 @@ protected:
|
||||||
virtual void maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state);
|
virtual void maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state);
|
||||||
|
|
||||||
bool getJointPosition(int jointIndex, glm::vec3& position) const;
|
bool getJointPosition(int jointIndex, glm::vec3& position) const;
|
||||||
bool getJointRotation(int jointIndex, glm::quat& rotation) const;
|
bool getJointRotation(int jointIndex, glm::quat& rotation, bool fromBind = false) const;
|
||||||
|
|
||||||
bool setJointPosition(int jointIndex, const glm::vec3& position);
|
bool setJointPosition(int jointIndex, const glm::vec3& position, int lastFreeIndex = -1);
|
||||||
bool setJointRotation(int jointIndex, const glm::quat& rotation);
|
bool setJointRotation(int jointIndex, const glm::quat& rotation, bool fromBind = false);
|
||||||
|
|
||||||
/// Restores the indexed joint to its default position.
|
/// Restores the indexed joint to its default position.
|
||||||
/// \param percent the percentage of the default position to apply (i.e., 0.25f to slerp one fourth of the way to
|
/// \param percent the percentage of the default position to apply (i.e., 0.25f to slerp one fourth of the way to
|
||||||
|
@ -146,8 +152,14 @@ protected:
|
||||||
/// \return true if the joint was found
|
/// \return true if the joint was found
|
||||||
bool restoreJointPosition(int jointIndex, float percent = 1.0f);
|
bool restoreJointPosition(int jointIndex, float percent = 1.0f);
|
||||||
|
|
||||||
|
/// Computes and returns the extended length of the limb terminating at the specified joint and starting at the joint's
|
||||||
|
/// first free ancestor.
|
||||||
|
float getLimbLength(int jointIndex) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
void setJointTranslation(int jointIndex, int parentIndex, int childIndex, const glm::vec3& translation);
|
||||||
|
|
||||||
void deleteGeometry();
|
void deleteGeometry();
|
||||||
|
|
||||||
float _pupilDilation;
|
float _pupilDilation;
|
||||||
|
|
|
@ -13,8 +13,6 @@
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QSlider>
|
#include <QSlider>
|
||||||
|
|
||||||
#include <VoxelSceneStats.h>
|
|
||||||
|
|
||||||
class LodToolsDialog : public QDialog {
|
class LodToolsDialog : public QDialog {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -14,46 +14,72 @@
|
||||||
|
|
||||||
#include <VoxelSceneStats.h>
|
#include <VoxelSceneStats.h>
|
||||||
|
|
||||||
|
#include "Application.h"
|
||||||
|
|
||||||
#include "ui/VoxelStatsDialog.h"
|
#include "ui/VoxelStatsDialog.h"
|
||||||
|
|
||||||
|
VoxelStatsDialog::VoxelStatsDialog(QWidget* parent, NodeToVoxelSceneStats* model) :
|
||||||
VoxelStatsDialog::VoxelStatsDialog(QWidget* parent, VoxelSceneStats* model) :
|
|
||||||
QDialog(parent, Qt::Window | Qt::WindowCloseButtonHint | Qt::WindowStaysOnTopHint),
|
QDialog(parent, Qt::Window | Qt::WindowCloseButtonHint | Qt::WindowStaysOnTopHint),
|
||||||
_model(model) {
|
_model(model) {
|
||||||
|
|
||||||
|
_statCount = 0;
|
||||||
|
|
||||||
char strBuf[64];
|
for (int i = 0; i < MAX_STATS; i++) {
|
||||||
|
_labels[i] = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
this->setWindowTitle("Voxel Statistics");
|
this->setWindowTitle("Voxel Statistics");
|
||||||
|
|
||||||
// Create layouter
|
// Create layouter
|
||||||
QFormLayout* form = new QFormLayout();
|
_form = new QFormLayout();
|
||||||
this->QDialog::setLayout(form);
|
this->QDialog::setLayout(_form);
|
||||||
|
|
||||||
// Setup labels
|
// Setup stat items
|
||||||
for (int i = 0; i < VoxelSceneStats::ITEM_COUNT; i++) {
|
_serverVoxels = AddStatItem("Voxels on Servers", GREENISH);
|
||||||
VoxelSceneStats::Item item = (VoxelSceneStats::Item)(i);
|
_localVoxels = AddStatItem("Local Voxels", YELLOWISH);
|
||||||
VoxelSceneStats::ItemInfo& itemInfo = _model->getItemInfo(item);
|
_localVoxelsMemory = AddStatItem("Voxels Memory", GREYISH);
|
||||||
QLabel* label = _labels[item] = new QLabel();
|
_voxelsRendered = AddStatItem("Voxels Rendered", GREENISH);
|
||||||
|
_sendingMode = AddStatItem("Sending Mode", YELLOWISH);
|
||||||
|
|
||||||
// Set foreground color to 62.5% brightness of the meter (otherwise will be hard to read on the bright background)
|
/** NOT YET READY
|
||||||
QPalette palette = label->palette();
|
VoxelSceneStats temp;
|
||||||
unsigned rgb = itemInfo.colorRGBA >> 8;
|
for (int i = 0; i < VoxelSceneStats::ITEM_COUNT; i++) {
|
||||||
const unsigned colorpart1 = 0xfefefeu;
|
VoxelSceneStats::Item item = (VoxelSceneStats::Item)(i);
|
||||||
const unsigned colorpart2 = 0xf8f8f8;
|
VoxelSceneStats::ItemInfo& itemInfo = temp.getItemInfo(item);
|
||||||
rgb = ((rgb & colorpart1) >> 1) + ((rgb & colorpart2) >> 3);
|
AddStatItem(itemInfo.caption, itemInfo.colorRGBA);
|
||||||
palette.setColor(QPalette::WindowText, QColor::fromRgb(rgb));
|
}
|
||||||
label->setPalette(palette);
|
**/
|
||||||
|
}
|
||||||
const int STATS_LABEL_WIDTH = 550;
|
|
||||||
label->setFixedWidth(STATS_LABEL_WIDTH);
|
|
||||||
|
|
||||||
snprintf(strBuf, sizeof(strBuf), " %s:", itemInfo.caption);
|
int VoxelStatsDialog::AddStatItem(const char* caption, unsigned colorRGBA) {
|
||||||
form->addRow(strBuf, label);
|
char strBuf[64];
|
||||||
}
|
const int STATS_LABEL_WIDTH = 550;
|
||||||
|
|
||||||
|
_statCount++; // increment our current stat count
|
||||||
|
|
||||||
|
QLabel* label = _labels[_statCount] = new QLabel();
|
||||||
|
|
||||||
|
// Set foreground color to 62.5% brightness of the meter (otherwise will be hard to read on the bright background)
|
||||||
|
QPalette palette = label->palette();
|
||||||
|
|
||||||
|
// This goofiness came from the bandwidth meter code, it basically stores a color in an unsigned and extracts it
|
||||||
|
unsigned rgb = colorRGBA >> 8;
|
||||||
|
const unsigned colorpart1 = 0xfefefeu;
|
||||||
|
const unsigned colorpart2 = 0xf8f8f8;
|
||||||
|
rgb = ((rgb & colorpart1) >> 1) + ((rgb & colorpart2) >> 3);
|
||||||
|
palette.setColor(QPalette::WindowText, QColor::fromRgb(rgb));
|
||||||
|
label->setPalette(palette);
|
||||||
|
|
||||||
|
label->setFixedWidth(STATS_LABEL_WIDTH);
|
||||||
|
|
||||||
|
snprintf(strBuf, sizeof(strBuf), " %s:", caption);
|
||||||
|
_form->addRow(strBuf, label);
|
||||||
|
|
||||||
|
return _statCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
VoxelStatsDialog::~VoxelStatsDialog() {
|
VoxelStatsDialog::~VoxelStatsDialog() {
|
||||||
for (int i = 0; i < VoxelSceneStats::ITEM_COUNT; ++i) {
|
for (int i = 0; i < _statCount; i++) {
|
||||||
delete _labels[i];
|
delete _labels[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,16 +87,104 @@ VoxelStatsDialog::~VoxelStatsDialog() {
|
||||||
void VoxelStatsDialog::paintEvent(QPaintEvent* event) {
|
void VoxelStatsDialog::paintEvent(QPaintEvent* event) {
|
||||||
|
|
||||||
// Update labels
|
// Update labels
|
||||||
char strBuf[256];
|
|
||||||
for (int i = 0; i < VoxelSceneStats::ITEM_COUNT; i++) {
|
VoxelSystem* voxels = Application::getInstance()->getVoxels();
|
||||||
VoxelSceneStats::Item item = (VoxelSceneStats::Item)(i);
|
QLabel* label;
|
||||||
QLabel* label = _labels[item];
|
QLocale locale(QLocale::English);
|
||||||
snprintf(strBuf, sizeof(strBuf), "%s", _model->getItemValue(item));
|
std::stringstream statsValue;
|
||||||
label->setText(strBuf);
|
statsValue.precision(4);
|
||||||
|
|
||||||
|
// Voxels Rendered
|
||||||
|
label = _labels[_voxelsRendered];
|
||||||
|
statsValue << "Max: " << voxels->getMaxVoxels() / 1000.f << "K " <<
|
||||||
|
"Drawn: " << voxels->getVoxelsWritten() / 1000.f << "K " <<
|
||||||
|
"Abandoned: " << voxels->getAbandonedVoxels() / 1000.f << "K " <<
|
||||||
|
"ReadBuffer: " << voxels->getVoxelsRendered() / 1000.f << "K " <<
|
||||||
|
"Changed: " << voxels->getVoxelsUpdated() / 1000.f << "K ";
|
||||||
|
label->setText(statsValue.str().c_str());
|
||||||
|
|
||||||
|
// Voxels Memory Usage
|
||||||
|
label = _labels[_localVoxelsMemory];
|
||||||
|
statsValue.str("");
|
||||||
|
statsValue <<
|
||||||
|
"Nodes RAM: " << VoxelNode::getTotalMemoryUsage() / 1000000.f << "MB "
|
||||||
|
"Geometry RAM: " << voxels->getVoxelMemoryUsageRAM() / 1000000.f << "MB " <<
|
||||||
|
"VBO: " << voxels->getVoxelMemoryUsageVBO() / 1000000.f << "MB ";
|
||||||
|
if (voxels->hasVoxelMemoryUsageGPU()) {
|
||||||
|
statsValue << "GPU: " << voxels->getVoxelMemoryUsageGPU() / 1000000.f << "MB ";
|
||||||
|
}
|
||||||
|
label->setText(statsValue.str().c_str());
|
||||||
|
|
||||||
|
// Local Voxels
|
||||||
|
label = _labels[_localVoxels];
|
||||||
|
unsigned long localTotal = VoxelNode::getNodeCount();
|
||||||
|
unsigned long localInternal = VoxelNode::getInternalNodeCount();
|
||||||
|
unsigned long localLeaves = VoxelNode::getLeafNodeCount();
|
||||||
|
QString localTotalString = locale.toString((uint)localTotal); // consider adding: .rightJustified(10, ' ');
|
||||||
|
QString localInternalString = locale.toString((uint)localInternal);
|
||||||
|
QString localLeavesString = locale.toString((uint)localLeaves);
|
||||||
|
|
||||||
|
statsValue.str("");
|
||||||
|
statsValue <<
|
||||||
|
"Total: " << localTotalString.toLocal8Bit().constData() << " / " <<
|
||||||
|
"Internal: " << localInternalString.toLocal8Bit().constData() << " / " <<
|
||||||
|
"Leaves: " << localLeavesString.toLocal8Bit().constData() << "";
|
||||||
|
label->setText(statsValue.str().c_str());
|
||||||
|
|
||||||
|
// iterate all the current voxel stats, and list their sending modes, total their voxels, etc...
|
||||||
|
std::stringstream sendingMode("");
|
||||||
|
|
||||||
|
int serverCount = 0;
|
||||||
|
int movingServerCount = 0;
|
||||||
|
unsigned long totalNodes = 0;
|
||||||
|
unsigned long totalInternal = 0;
|
||||||
|
unsigned long totalLeaves = 0;
|
||||||
|
NodeToVoxelSceneStats* sceneStats = Application::getInstance()->getVoxelSceneStats();
|
||||||
|
for(NodeToVoxelSceneStatsIterator i = sceneStats->begin(); i != sceneStats->end(); i++) {
|
||||||
|
//const QUuid& uuid = i->first;
|
||||||
|
VoxelSceneStats& stats = i->second;
|
||||||
|
serverCount++;
|
||||||
|
|
||||||
|
// calculate server node totals
|
||||||
|
totalNodes += stats.getTotalVoxels();
|
||||||
|
totalInternal += stats.getTotalInternal();
|
||||||
|
totalLeaves += stats.getTotalLeaves();
|
||||||
|
|
||||||
|
// Sending mode
|
||||||
|
if (serverCount > 1) {
|
||||||
|
sendingMode << ",";
|
||||||
|
}
|
||||||
|
if (stats.isMoving()) {
|
||||||
|
sendingMode << "M";
|
||||||
|
movingServerCount++;
|
||||||
|
} else {
|
||||||
|
sendingMode << "S";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sendingMode << " - " << serverCount << " servers";
|
||||||
|
if (movingServerCount > 0) {
|
||||||
|
sendingMode << " <SCENE NOT STABLE>";
|
||||||
|
} else {
|
||||||
|
sendingMode << " <SCENE STABLE>";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
label = _labels[_sendingMode];
|
||||||
|
label->setText(sendingMode.str().c_str());
|
||||||
|
|
||||||
|
// Server Voxels
|
||||||
|
QString serversTotalString = locale.toString((uint)totalNodes); // consider adding: .rightJustified(10, ' ');
|
||||||
|
QString serversInternalString = locale.toString((uint)totalInternal);
|
||||||
|
QString serversLeavesString = locale.toString((uint)totalLeaves);
|
||||||
|
label = _labels[_serverVoxels];
|
||||||
|
statsValue.str("");
|
||||||
|
statsValue <<
|
||||||
|
"Total: " << serversTotalString.toLocal8Bit().constData() << " / " <<
|
||||||
|
"Internal: " << serversInternalString.toLocal8Bit().constData() << " / " <<
|
||||||
|
"Leaves: " << serversLeavesString.toLocal8Bit().constData() << "";
|
||||||
|
label->setText(statsValue.str().c_str());
|
||||||
|
|
||||||
this->QDialog::paintEvent(event);
|
this->QDialog::paintEvent(event);
|
||||||
this->setFixedSize(this->width(), this->height());
|
//this->setFixedSize(this->width(), this->height());
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoxelStatsDialog::reject() {
|
void VoxelStatsDialog::reject() {
|
||||||
|
|
|
@ -10,15 +10,18 @@
|
||||||
#define __hifi__VoxelStatsDialog__
|
#define __hifi__VoxelStatsDialog__
|
||||||
|
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
|
#include <QFormLayout>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
|
|
||||||
#include <VoxelSceneStats.h>
|
#include <VoxelSceneStats.h>
|
||||||
|
|
||||||
|
#define MAX_STATS 40
|
||||||
|
|
||||||
class VoxelStatsDialog : public QDialog {
|
class VoxelStatsDialog : public QDialog {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
// Sets up the UI
|
// Sets up the UI
|
||||||
VoxelStatsDialog(QWidget* parent, VoxelSceneStats* model);
|
VoxelStatsDialog(QWidget* parent, NodeToVoxelSceneStats* model);
|
||||||
~VoxelStatsDialog();
|
~VoxelStatsDialog();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
@ -34,9 +37,19 @@ protected:
|
||||||
// Emits a 'closed' signal when this dialog is closed.
|
// Emits a 'closed' signal when this dialog is closed.
|
||||||
void closeEvent(QCloseEvent*);
|
void closeEvent(QCloseEvent*);
|
||||||
|
|
||||||
|
int AddStatItem(const char* caption, unsigned colorRGBA);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QLabel* _labels[VoxelSceneStats::ITEM_COUNT];
|
QFormLayout* _form;
|
||||||
VoxelSceneStats* _model;
|
QLabel* _labels[MAX_STATS];
|
||||||
|
NodeToVoxelSceneStats* _model;
|
||||||
|
int _statCount;
|
||||||
|
|
||||||
|
int _sendingMode;
|
||||||
|
int _serverVoxels;
|
||||||
|
int _localVoxels;
|
||||||
|
int _localVoxelsMemory;
|
||||||
|
int _voxelsRendered;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* defined(__interface__VoxelStatsDialog__) */
|
#endif /* defined(__interface__VoxelStatsDialog__) */
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
class SimpleMovingAverage {
|
class SimpleMovingAverage {
|
||||||
public:
|
public:
|
||||||
SimpleMovingAverage(int numSamplesToAverage);
|
SimpleMovingAverage(int numSamplesToAverage = 100);
|
||||||
|
|
||||||
int updateAverage(float sample);
|
int updateAverage(float sample);
|
||||||
void reset();
|
void reset();
|
||||||
|
@ -30,8 +30,8 @@ private:
|
||||||
float _average;
|
float _average;
|
||||||
float _eventDeltaAverage;
|
float _eventDeltaAverage;
|
||||||
|
|
||||||
const float WEIGHTING;
|
float WEIGHTING;
|
||||||
const float ONE_MINUS_WEIGHTING;
|
float ONE_MINUS_WEIGHTING;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* defined(__hifi__Stats__) */
|
#endif /* defined(__hifi__Stats__) */
|
||||||
|
|
|
@ -103,6 +103,7 @@ void VoxelNodeData::resetVoxelPacket() {
|
||||||
// the clients requested color state.
|
// the clients requested color state.
|
||||||
_currentPacketIsColor = (LOW_RES_MONO && getWantLowResMoving() && _viewFrustumChanging) ? false : getWantColor();
|
_currentPacketIsColor = (LOW_RES_MONO && getWantLowResMoving() && _viewFrustumChanging) ? false : getWantColor();
|
||||||
PACKET_TYPE voxelPacketType = _currentPacketIsColor ? PACKET_TYPE_VOXEL_DATA : PACKET_TYPE_VOXEL_DATA_MONOCHROME;
|
PACKET_TYPE voxelPacketType = _currentPacketIsColor ? PACKET_TYPE_VOXEL_DATA : PACKET_TYPE_VOXEL_DATA_MONOCHROME;
|
||||||
|
|
||||||
int numBytesPacketHeader = populateTypeAndVersion(_voxelPacket, voxelPacketType);
|
int numBytesPacketHeader = populateTypeAndVersion(_voxelPacket, voxelPacketType);
|
||||||
_voxelPacketAt = _voxelPacket + numBytesPacketHeader;
|
_voxelPacketAt = _voxelPacket + numBytesPacketHeader;
|
||||||
_voxelPacketAvailableBytes = MAX_VOXEL_PACKET_SIZE - numBytesPacketHeader;
|
_voxelPacketAvailableBytes = MAX_VOXEL_PACKET_SIZE - numBytesPacketHeader;
|
||||||
|
|
|
@ -107,6 +107,7 @@ int VoxelSendThread::handlePacketSend(Node* node, VoxelNodeData* nodeData, int&
|
||||||
NodeList::getInstance()->getNodeSocket()->send(node->getActiveSocket(),
|
NodeList::getInstance()->getNodeSocket()->send(node->getActiveSocket(),
|
||||||
nodeData->getPacket(), nodeData->getPacketLength());
|
nodeData->getPacket(), nodeData->getPacketLength());
|
||||||
}
|
}
|
||||||
|
nodeData->stats.markAsSent();
|
||||||
} else {
|
} else {
|
||||||
// just send the voxel packet
|
// just send the voxel packet
|
||||||
NodeList::getInstance()->getNodeSocket()->send(node->getActiveSocket(),
|
NodeList::getInstance()->getNodeSocket()->send(node->getActiveSocket(),
|
||||||
|
|
|
@ -7,6 +7,9 @@
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
#include <PacketHeaders.h>
|
#include <PacketHeaders.h>
|
||||||
#include <SharedUtil.h>
|
#include <SharedUtil.h>
|
||||||
|
|
||||||
|
@ -521,30 +524,26 @@ void VoxelSceneStats::printDebugDetails() {
|
||||||
qDebug(" trees removed : %lu\n", _treesRemoved );
|
qDebug(" trees removed : %lu\n", _treesRemoved );
|
||||||
}
|
}
|
||||||
|
|
||||||
const unsigned greenish = 0x40ff40d0;
|
|
||||||
const unsigned yellowish = 0xffef40c0;
|
|
||||||
const unsigned greyish = 0xd0d0d0a0;
|
|
||||||
|
|
||||||
VoxelSceneStats::ItemInfo VoxelSceneStats::_ITEMS[] = {
|
VoxelSceneStats::ItemInfo VoxelSceneStats::_ITEMS[] = {
|
||||||
{ "Elapsed" , greenish },
|
{ "Elapsed" , GREENISH , 2 , "Elapsed,fps" },
|
||||||
{ "Encode" , yellowish },
|
{ "Encode" , YELLOWISH , 2 , "Time,fps" },
|
||||||
{ "Network" , greyish },
|
{ "Network" , GREYISH , 3 , "Packets,Bytes,KBPS" },
|
||||||
{ "Voxels on Server" , greenish },
|
{ "Voxels on Server" , GREENISH , 3 , "Total,Internal,Leaves" },
|
||||||
{ "Voxels Sent" , yellowish },
|
{ "Voxels Sent" , YELLOWISH , 5 , "Total,Bits/Voxel,Avg Bits/Voxel,Internal,Leaves" },
|
||||||
{ "Colors Sent" , greyish },
|
{ "Colors Sent" , GREYISH , 3 , "Total,Internal,Leaves" },
|
||||||
{ "Bitmasks Sent" , greenish },
|
{ "Bitmasks Sent" , GREENISH , 3 , "Colors,Exists,In Packets" },
|
||||||
{ "Traversed" , yellowish },
|
{ "Traversed" , YELLOWISH , 3 , "Total,Internal,Leaves" },
|
||||||
{ "Skipped - Total" , greyish },
|
{ "Skipped - Total" , GREYISH , 3 , "Total,Internal,Leaves" },
|
||||||
{ "Skipped - Distance" , greenish },
|
{ "Skipped - Distance" , GREENISH , 3 , "Total,Internal,Leaves" },
|
||||||
{ "Skipped - Out of View", yellowish },
|
{ "Skipped - Out of View", YELLOWISH , 3 , "Total,Internal,Leaves" },
|
||||||
{ "Skipped - Was in View", greyish },
|
{ "Skipped - Was in View", GREYISH , 3 , "Total,Internal,Leaves" },
|
||||||
{ "Skipped - No Change" , greenish },
|
{ "Skipped - No Change" , GREENISH , 3 , "Total,Internal,Leaves" },
|
||||||
{ "Skipped - Occluded" , yellowish },
|
{ "Skipped - Occluded" , YELLOWISH , 3 , "Total,Internal,Leaves" },
|
||||||
{ "Didn't fit in packet" , greyish },
|
{ "Didn't fit in packet" , GREYISH , 4 , "Total,Internal,Leaves,Removed" },
|
||||||
{ "Mode" , greenish },
|
{ "Mode" , GREENISH , 4 , "Moving,Stationary,Partial,Full" },
|
||||||
};
|
};
|
||||||
|
|
||||||
char* VoxelSceneStats::getItemValue(Item item) {
|
const char* VoxelSceneStats::getItemValue(Item item) {
|
||||||
const uint64_t USECS_PER_SECOND = 1000 * 1000;
|
const uint64_t USECS_PER_SECOND = 1000 * 1000;
|
||||||
int calcFPS, calcAverageFPS, calculatedKBPS;
|
int calcFPS, calcAverageFPS, calculatedKBPS;
|
||||||
switch(item) {
|
switch(item) {
|
||||||
|
@ -651,3 +650,4 @@ char* VoxelSceneStats::getItemValue(Item item) {
|
||||||
return _itemValueBuffer;
|
return _itemValueBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,10 @@
|
||||||
#include <NodeList.h>
|
#include <NodeList.h>
|
||||||
#include "JurisdictionMap.h"
|
#include "JurisdictionMap.h"
|
||||||
|
|
||||||
|
#define GREENISH 0x40ff40d0
|
||||||
|
#define YELLOWISH 0xffef40c0
|
||||||
|
#define GREYISH 0xd0d0d0a0
|
||||||
|
|
||||||
class VoxelNode;
|
class VoxelNode;
|
||||||
|
|
||||||
/// Collects statistics for calculating and sending a scene from a voxel server to an interface client
|
/// Collects statistics for calculating and sending a scene from a voxel server to an interface client
|
||||||
|
@ -114,8 +118,10 @@ public:
|
||||||
|
|
||||||
/// Meta information about each stats item
|
/// Meta information about each stats item
|
||||||
struct ItemInfo {
|
struct ItemInfo {
|
||||||
char const* const caption;
|
char const* const caption;
|
||||||
unsigned colorRGBA;
|
unsigned colorRGBA;
|
||||||
|
int detailsCount;
|
||||||
|
const char* detailsLabels;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Returns details about items tracked by VoxelSceneStats
|
/// Returns details about items tracked by VoxelSceneStats
|
||||||
|
@ -124,14 +130,19 @@ public:
|
||||||
|
|
||||||
/// Returns a UI formatted value of an item tracked by VoxelSceneStats
|
/// Returns a UI formatted value of an item tracked by VoxelSceneStats
|
||||||
/// \param Item item The item from the stats you're interested in.
|
/// \param Item item The item from the stats you're interested in.
|
||||||
char* getItemValue(Item item);
|
const char* getItemValue(Item item);
|
||||||
|
|
||||||
/// Returns OctCode for root node of the jurisdiction of this particular voxel server
|
/// Returns OctCode for root node of the jurisdiction of this particular voxel server
|
||||||
unsigned char* getJurisdictionRoot() const { return _jurisdictionRoot; }
|
unsigned char* getJurisdictionRoot() const { return _jurisdictionRoot; }
|
||||||
|
|
||||||
/// Returns list of OctCodes for end nodes of the jurisdiction of this particular voxel server
|
/// Returns list of OctCodes for end nodes of the jurisdiction of this particular voxel server
|
||||||
const std::vector<unsigned char*>& getJurisdictionEndNodes() const { return _jurisdictionEndNodes; }
|
const std::vector<unsigned char*>& getJurisdictionEndNodes() const { return _jurisdictionEndNodes; }
|
||||||
|
|
||||||
|
bool isMoving() const { return _isMoving; };
|
||||||
|
unsigned long getTotalVoxels() const { return _totalVoxels; }
|
||||||
|
unsigned long getTotalInternal() const { return _totalInternal; }
|
||||||
|
unsigned long getTotalLeaves() const { return _totalLeaves; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool _isReadyToSend;
|
bool _isReadyToSend;
|
||||||
unsigned char _statsMessage[MAX_PACKET_SIZE];
|
unsigned char _statsMessage[MAX_PACKET_SIZE];
|
||||||
|
@ -224,4 +235,9 @@ private:
|
||||||
std::vector<unsigned char*> _jurisdictionEndNodes;
|
std::vector<unsigned char*> _jurisdictionEndNodes;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Map between node IDs and their reported VoxelSceneStats. Typically used by classes that need to know which nodes sent
|
||||||
|
/// which voxel stats
|
||||||
|
typedef std::map<QUuid, VoxelSceneStats> NodeToVoxelSceneStats;
|
||||||
|
typedef std::map<QUuid, VoxelSceneStats>::iterator NodeToVoxelSceneStatsIterator;
|
||||||
|
|
||||||
#endif /* defined(__hifi__VoxelSceneStats__) */
|
#endif /* defined(__hifi__VoxelSceneStats__) */
|
||||||
|
|
Loading…
Reference in a new issue