Merge branch 'master' of https://github.com/highfidelity/hifi into ess-pr

This commit is contained in:
Atlante45 2017-01-25 13:06:49 -08:00
commit f540c94113
87 changed files with 2084 additions and 656 deletions

View file

@ -12,6 +12,8 @@
#include "AssetServer.h"
#include <thread>
#include <QtCore/QCoreApplication>
#include <QtCore/QCryptographicHash>
#include <QtCore/QDateTime>
@ -21,6 +23,7 @@
#include <QtCore/QJsonDocument>
#include <QtCore/QString>
#include <SharedUtil.h>
#include <ServerPathUtils.h>
#include "NetworkLogging.h"
@ -29,8 +32,44 @@
#include "UploadAssetTask.h"
#include <ClientServerUtils.h>
static const uint8_t MIN_CORES_FOR_MULTICORE = 4;
static const uint8_t CPU_AFFINITY_COUNT_HIGH = 2;
static const uint8_t CPU_AFFINITY_COUNT_LOW = 1;
static const int INTERFACE_RUNNING_CHECK_FREQUENCY_MS = 1000;
const QString ASSET_SERVER_LOGGING_TARGET_NAME = "asset-server";
bool interfaceRunning() {
bool result = false;
#ifdef Q_OS_WIN
QSharedMemory sharedMemory { getInterfaceSharedMemoryName() };
result = sharedMemory.attach(QSharedMemory::ReadOnly);
if (result) {
sharedMemory.detach();
}
#endif
return result;
}
void updateConsumedCores() {
static bool wasInterfaceRunning = false;
bool isInterfaceRunning = interfaceRunning();
// If state is unchanged, return early
if (isInterfaceRunning == wasInterfaceRunning) {
return;
}
wasInterfaceRunning = isInterfaceRunning;
auto coreCount = std::thread::hardware_concurrency();
if (isInterfaceRunning) {
coreCount = coreCount > MIN_CORES_FOR_MULTICORE ? CPU_AFFINITY_COUNT_HIGH : CPU_AFFINITY_COUNT_LOW;
}
qDebug() << "Setting max consumed cores to " << coreCount;
setMaxCores(coreCount);
}
AssetServer::AssetServer(ReceivedMessage& message) :
ThreadedAssignment(message),
_taskPool(this)
@ -46,6 +85,20 @@ AssetServer::AssetServer(ReceivedMessage& message) :
packetReceiver.registerListener(PacketType::AssetGetInfo, this, "handleAssetGetInfo");
packetReceiver.registerListener(PacketType::AssetUpload, this, "handleAssetUpload");
packetReceiver.registerListener(PacketType::AssetMappingOperation, this, "handleAssetMappingOperation");
#ifdef Q_OS_WIN
updateConsumedCores();
QTimer* timer = new QTimer(this);
auto timerConnection = connect(timer, &QTimer::timeout, [] {
updateConsumedCores();
});
connect(qApp, &QCoreApplication::aboutToQuit, [this, timerConnection] {
disconnect(timerConnection);
});
timer->setInterval(INTERFACE_RUNNING_CHECK_FREQUENCY_MS);
timer->setTimerType(Qt::CoarseTimer);
timer->start();
#endif
}
void AssetServer::run() {

View file

@ -131,7 +131,7 @@ void AudioMixerSlavePool::setNumThreads(int numThreads) {
}
void AudioMixerSlavePool::resize(int numThreads) {
assert(_numThreads == _slaves.size());
assert(_numThreads == (int)_slaves.size());
#ifdef AUDIO_SINGLE_THREADED
qDebug("%s: running single threaded", __FUNCTION__, numThreads);
@ -182,6 +182,6 @@ void AudioMixerSlavePool::resize(int numThreads) {
}
_numThreads = _numStarted = _numFinished = numThreads;
assert(_numThreads == _slaves.size());
assert(_numThreads == (int)_slaves.size());
#endif
}

View file

@ -262,8 +262,12 @@ void AvatarMixer::broadcastAvatarData() {
// setup a PacketList for the avatarPackets
auto avatarPacketList = NLPacketList::create(PacketType::BulkAvatarData);
if (avatar.getSessionDisplayName().isEmpty() && // We haven't set it yet...
nodeData->getReceivedIdentity()) { // ... but we have processed identity (with possible displayName).
if (nodeData->getAvatarSessionDisplayNameMustChange()) {
const QString& existingBaseDisplayName = nodeData->getBaseDisplayName();
if (--_sessionDisplayNames[existingBaseDisplayName].second <= 0) {
_sessionDisplayNames.remove(existingBaseDisplayName);
}
QString baseName = avatar.getDisplayName().trimmed();
const QRegularExpression curses{ "fuck|shit|damn|cock|cunt" }; // POC. We may eventually want something much more elaborate (subscription?).
baseName = baseName.replace(curses, "*"); // Replace rather than remove, so that people have a clue that the person's a jerk.
@ -276,11 +280,14 @@ void AvatarMixer::broadcastAvatarData() {
QPair<int, int>& soFar = _sessionDisplayNames[baseName]; // Inserts and answers 0, 0 if not already present, which is what we want.
int& highWater = soFar.first;
nodeData->setBaseDisplayName(baseName);
avatar.setSessionDisplayName((highWater > 0) ? baseName + "_" + QString::number(highWater) : baseName);
QString sessionDisplayName = (highWater > 0) ? baseName + "_" + QString::number(highWater) : baseName;
avatar.setSessionDisplayName(sessionDisplayName);
highWater++;
soFar.second++; // refcount
nodeData->flagIdentityChange();
sendIdentityPacket(nodeData, node); // Tell new node about its sessionUUID. Others will find out below.
nodeData->setAvatarSessionDisplayNameMustChange(false);
sendIdentityPacket(nodeData, node); // Tell node whose name changed about its new session display name. Others will find out below.
qDebug() << "Giving session display name" << sessionDisplayName << "to node with ID" << node->getUUID();
}
// this is an AGENT we have received head data from
@ -584,7 +591,7 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer<ReceivedMessage> mes
if (avatar.processAvatarIdentity(identity)) {
QMutexLocker nodeDataLocker(&nodeData->getMutex());
nodeData->flagIdentityChange();
nodeData->setReceivedIdentity();
nodeData->setAvatarSessionDisplayNameMustChange(true);
}
}
}

View file

@ -53,8 +53,8 @@ public:
HRCTime getIdentityChangeTimestamp() const { return _identityChangeTimestamp; }
void flagIdentityChange() { _identityChangeTimestamp = p_high_resolution_clock::now(); }
bool getReceivedIdentity() const { return _gotIdentity; }
void setReceivedIdentity() { _gotIdentity = true; }
bool getAvatarSessionDisplayNameMustChange() const { return _avatarSessionDisplayNameMustChange; }
void setAvatarSessionDisplayNameMustChange(bool set = true) { _avatarSessionDisplayNameMustChange = set; }
void setFullRateDistance(float fullRateDistance) { _fullRateDistance = fullRateDistance; }
float getFullRateDistance() const { return _fullRateDistance; }
@ -112,7 +112,7 @@ private:
std::unordered_set<QUuid> _hasReceivedFirstPacketsFrom;
HRCTime _identityChangeTimestamp;
bool _gotIdentity { false };
bool _avatarSessionDisplayNameMustChange{ false };
float _fullRateDistance = FLT_MAX;
float _maxAvatarDistance = FLT_MAX;

View file

@ -38,10 +38,10 @@ set(${EXTERNAL_NAME_UPPER}_DLL_PATH ${INSTALL_DIR}/lib CACHE FILEPATH "Location
if (APPLE)
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libquazip5.1.0.0.dylib CACHE FILEPATH "Location of QuaZip release library")
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/libquazip5.1.0.0.dylib CACHE FILEPATH "Location of QuaZip release library")
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/libquazip5d.1.0.0.dylib CACHE FILEPATH "Location of QuaZip release library")
elseif (WIN32)
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/quazip5.lib CACHE FILEPATH "Location of QuaZip release library")
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/quazip5.lib CACHE FILEPATH "Location of QuaZip release library")
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/quazip5d.lib CACHE FILEPATH "Location of QuaZip release library")
else ()
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libquazip5.so CACHE FILEPATH "Location of QuaZip release library")
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/libquazip5.so CACHE FILEPATH "Location of QuaZip release library")

View file

@ -6,8 +6,8 @@ if (WIN32)
include(ExternalProject)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi5.zip
URL_MD5 0530753e855ffc00232cc969bf1c84a8
URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi6.zip
URL_MD5 fcac808c1ba0b0f5b44ea06e2612ebab
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""

View file

@ -1285,7 +1285,7 @@
{
"name": "entityScriptSourceWhitelist",
"label": "Entity Scripts Allowed from:",
"help": "The domains that entity scripts are allowed from. A comma separated list of domains that entity scripts are allowed from, if someone attempts to create and entity or edit an entity to have a different domain, it will be rejected. If left blank, any domain is allowed.",
"help": "Comma separated list of URLs (with optional paths) that entity scripts are allowed from. If someone attempts to create and entity or edit an entity to have a different domain, it will be rejected. If left blank, any domain is allowed.",
"placeholder": "",
"default": "",
"advanced": true

View file

@ -12,7 +12,7 @@
<div id="setup-sidebar" class="hidden-xs" data-spy="affix" data-offset-top="55" data-clampedwidth="#setup-sidebar-col">
<script id="list-group-template" type="text/template">
<% _.each(descriptions, function(group){ %>
<% panelID = group.name ? group.name : group.label %>
<% panelID = group.name ? group.name : group.html_id %>
<li>
<a href="#<%- panelID %>" class="list-group-item">
<span class="badge"></span>

View file

@ -106,7 +106,7 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessag
if (node) {
// set the sending sock addr and node interest set on this node
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
nodeData->setSendingSockAddr(message->getSenderSockAddr());
// guard against patched agents asking to hear about other agents
@ -127,12 +127,12 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessag
emit connectedNode(node);
} else {
qDebug() << "Refusing connection from node at" << message->getSenderSockAddr()
<< "with hardware address" << nodeConnection.hardwareAddress
<< "with hardware address" << nodeConnection.hardwareAddress
<< "and machine fingerprint" << nodeConnection.machineFingerprint;
}
}
NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QString verifiedUsername, const QHostAddress& senderAddress,
NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QString verifiedUsername, const QHostAddress& senderAddress,
const QString& hardwareAddress, const QUuid& machineFingerprint) {
NodePermissions userPerms;
@ -282,7 +282,7 @@ void DomainGatekeeper::updateNodePermissions() {
QString hardwareAddress;
QUuid machineFingerprint;
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
if (nodeData) {
hardwareAddress = nodeData->getHardwareAddress();
machineFingerprint = nodeData->getMachineFingerprint();
@ -335,7 +335,7 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo
// add the new node
SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection);
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(newNode->getLinkedData());
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(newNode->getLinkedData());
// set assignment related data on the linked data for this node
nodeData->setAssignmentUUID(matchingQueuedAssignment->getUUID());
@ -457,7 +457,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
newNode->setPermissions(userPerms);
// grab the linked data for our new node so we can set the username
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(newNode->getLinkedData());
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(newNode->getLinkedData());
// if we have a username from the connect request, set it on the DomainServerNodeData
nodeData->setUsername(username);

View file

@ -107,7 +107,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
qRegisterMetaType<DomainServerWebSessionData>("DomainServerWebSessionData");
qRegisterMetaTypeStreamOperators<DomainServerWebSessionData>("DomainServerWebSessionData");
// make sure we hear about newly connected nodes from our gatekeeper
connect(&_gatekeeper, &DomainGatekeeper::connectedNode, this, &DomainServer::handleConnectedNode);
@ -281,7 +281,7 @@ bool DomainServer::optionallyReadX509KeyAndCertificate() {
QString keyPassphraseString = QProcessEnvironment::systemEnvironment().value(X509_KEY_PASSPHRASE_ENV);
qDebug() << "Reading certificate file at" << certPath << "for HTTPS.";
qDebug() << "Reading key file at" << keyPath << "for HTTPS.";
qDebug() << "Reading key file at" << keyPath << "for HTTPS.";
QFile certFile(certPath);
certFile.open(QIODevice::ReadOnly);
@ -528,12 +528,12 @@ void DomainServer::setupNodeListAndAssignments() {
packetReceiver.registerListener(PacketType::DomainServerPathQuery, this, "processPathQueryPacket");
packetReceiver.registerListener(PacketType::NodeJsonStats, this, "processNodeJSONStatsPacket");
packetReceiver.registerListener(PacketType::DomainDisconnectRequest, this, "processNodeDisconnectRequestPacket");
// NodeList won't be available to the settings manager when it is created, so call registerListener here
packetReceiver.registerListener(PacketType::DomainSettingsRequest, &_settingsManager, "processSettingsRequestPacket");
packetReceiver.registerListener(PacketType::NodeKickRequest, &_settingsManager, "processNodeKickRequestPacket");
packetReceiver.registerListener(PacketType::UsernameFromIDRequest, &_settingsManager, "processUsernameFromIDRequestPacket");
// register the gatekeeper for the packets it needs to receive
packetReceiver.registerListener(PacketType::DomainConnectRequest, &_gatekeeper, "processConnectRequestPacket");
packetReceiver.registerListener(PacketType::ICEPing, &_gatekeeper, "processICEPingPacket");
@ -542,7 +542,7 @@ void DomainServer::setupNodeListAndAssignments() {
packetReceiver.registerListener(PacketType::ICEServerHeartbeatDenied, this, "processICEServerHeartbeatDenialPacket");
packetReceiver.registerListener(PacketType::ICEServerHeartbeatACK, this, "processICEServerHeartbeatACK");
// add whatever static assignments that have been parsed to the queue
addStaticAssignmentsToQueue();
@ -809,18 +809,18 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet<Ass
defaultedType != Assignment::AllTypes;
defaultedType = static_cast<Assignment::Type>(static_cast<int>(defaultedType) + 1)) {
if (!excludedTypes.contains(defaultedType) && defaultedType != Assignment::AgentType) {
if (defaultedType == Assignment::AssetServerType) {
// Make sure the asset-server is enabled before adding it here.
// Initially we do not assign it by default so we can test it in HF domains first
static const QString ASSET_SERVER_ENABLED_KEYPATH = "asset_server.enabled";
if (!_settingsManager.valueOrDefaultValueForKeyPath(ASSET_SERVER_ENABLED_KEYPATH).toBool()) {
// skip to the next iteration if asset-server isn't enabled
continue;
}
}
// type has not been set from a command line or config file config, use the default
// by clearing whatever exists and writing a single default assignment with no payload
Assignment* newAssignment = new Assignment(Assignment::CreateCommand, (Assignment::Type) defaultedType);
@ -837,9 +837,9 @@ void DomainServer::processListRequestPacket(QSharedPointer<ReceivedMessage> mess
// update this node's sockets in case they have changed
sendingNode->setPublicSocket(nodeRequestData.publicSockAddr);
sendingNode->setLocalSocket(nodeRequestData.localSockAddr);
// update the NodeInterestSet in case there have been any changes
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(sendingNode->getLinkedData());
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(sendingNode->getLinkedData());
// guard against patched agents asking to hear about other agents
auto safeInterestSet = nodeRequestData.interestList.toSet();
@ -964,14 +964,14 @@ void DomainServer::handleConnectedNode(SharedNodePointer newNode) {
void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr &senderSockAddr) {
const int NUM_DOMAIN_LIST_EXTENDED_HEADER_BYTES = NUM_BYTES_RFC4122_UUID + NUM_BYTES_RFC4122_UUID + 2;
// setup the extended header for the domain list packets
// this data is at the beginning of each of the domain list packets
QByteArray extendedHeader(NUM_DOMAIN_LIST_EXTENDED_HEADER_BYTES, 0);
QDataStream extendedHeaderStream(&extendedHeader, QIODevice::WriteOnly);
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
extendedHeaderStream << limitedNodeList->getSessionUUID();
extendedHeaderStream << node->getUUID();
extendedHeaderStream << node->getPermissions();
@ -981,7 +981,7 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif
// always send the node their own UUID back
QDataStream domainListStream(domainListPackets.get());
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
// store the nodeInterestSet on this DomainServerNodeData, in case it has changed
auto& nodeInterestSet = nodeData->getNodeInterestSet();
@ -1008,7 +1008,7 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif
});
}
}
// send an empty list to the node, in case there were no other nodes
domainListPackets->closeCurrentPacket(true);
@ -1017,8 +1017,8 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif
}
QUuid DomainServer::connectionSecretForNodes(const SharedNodePointer& nodeA, const SharedNodePointer& nodeB) {
DomainServerNodeData* nodeAData = dynamic_cast<DomainServerNodeData*>(nodeA->getLinkedData());
DomainServerNodeData* nodeBData = dynamic_cast<DomainServerNodeData*>(nodeB->getLinkedData());
DomainServerNodeData* nodeAData = static_cast<DomainServerNodeData*>(nodeA->getLinkedData());
DomainServerNodeData* nodeBData = static_cast<DomainServerNodeData*>(nodeB->getLinkedData());
if (nodeAData && nodeBData) {
QUuid& secretUUID = nodeAData->getSessionSecretHash()[nodeB->getUUID()];
@ -1028,7 +1028,7 @@ QUuid DomainServer::connectionSecretForNodes(const SharedNodePointer& nodeA, con
secretUUID = QUuid::createUuid();
// set it on the other Node's sessionSecretHash
reinterpret_cast<DomainServerNodeData*>(nodeBData)->getSessionSecretHash().insert(nodeA->getUUID(), secretUUID);
static_cast<DomainServerNodeData*>(nodeBData)->getSessionSecretHash().insert(nodeA->getUUID(), secretUUID);
}
return secretUUID;
@ -1157,7 +1157,7 @@ void DomainServer::processRequestAssignmentPacket(QSharedPointer<ReceivedMessage
void DomainServer::setupPendingAssignmentCredits() {
// enumerate the NodeList to find the assigned nodes
DependencyManager::get<LimitedNodeList>()->eachNode([&](const SharedNodePointer& node){
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
if (!nodeData->getAssignmentUUID().isNull() && !nodeData->getWalletUUID().isNull()) {
// check if we have a non-finalized transaction for this node to add this amount to
@ -1543,7 +1543,7 @@ void DomainServer::sendHeartbeatToIceServer() {
}
void DomainServer::processNodeJSONStatsPacket(QSharedPointer<ReceivedMessage> packetList, SharedNodePointer sendingNode) {
auto nodeData = dynamic_cast<DomainServerNodeData*>(sendingNode->getLinkedData());
auto nodeData = static_cast<DomainServerNodeData*>(sendingNode->getLinkedData());
if (nodeData) {
nodeData->updateJSONStats(packetList->getMessage());
}
@ -1589,7 +1589,7 @@ QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) {
nodeJson[JSON_KEY_UPTIME] = QString::number(double(QDateTime::currentMSecsSinceEpoch() - node->getWakeTimestamp()) / 1000.0);
// if the node has pool information, add it
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
// add the node username, if it exists
nodeJson[JSON_KEY_USERNAME] = nodeData->getUsername();
@ -1657,23 +1657,23 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
if (connection->requestOperation() == QNetworkAccessManager::GetOperation
&& assignmentRegex.indexIn(url.path()) != -1) {
QUuid nodeUUID = QUuid(assignmentRegex.cap(1));
auto matchingNode = nodeList->nodeWithUUID(nodeUUID);
// don't handle if we don't have a matching node
if (!matchingNode) {
return false;
}
auto nodeData = dynamic_cast<DomainServerNodeData*>(matchingNode->getLinkedData());
auto nodeData = static_cast<DomainServerNodeData*>(matchingNode->getLinkedData());
// don't handle if we don't have node data for this node
if (!nodeData) {
return false;
}
SharedAssignmentPointer matchingAssignment = _allAssignments.value(nodeData->getAssignmentUUID());
// check if we have an assignment that matches this temp UUID, and it is a scripted assignment
if (matchingAssignment && matchingAssignment->getType() == Assignment::AgentType) {
// we have a matching assignment and it is for the right type, have the HTTP manager handle it
@ -1688,7 +1688,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
return true;
}
// request not handled
return false;
}
@ -1720,7 +1720,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
// enumerate the NodeList to find the assigned nodes
nodeList->eachNode([this, &assignedNodesJSON](const SharedNodePointer& node){
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
if (!nodeData->getAssignmentUUID().isNull()) {
// add the node using the UUID as the key
@ -1808,7 +1808,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
if (matchingNode) {
// create a QJsonDocument with the stats QJsonObject
QJsonObject statsObject =
reinterpret_cast<DomainServerNodeData*>(matchingNode->getLinkedData())->getStatsJSONObject();
static_cast<DomainServerNodeData*>(matchingNode->getLinkedData())->getStatsJSONObject();
// add the node type to the JSON data for output purposes
statsObject["node_type"] = NodeType::getNodeTypeName(matchingNode->getType()).toLower().replace(' ', '-');
@ -2280,7 +2280,7 @@ void DomainServer::addStaticAssignmentsToQueue() {
// if the domain-server has just restarted,
// check if there are static assignments that we need to throw into the assignment queue
auto sharedAssignments = _allAssignments.values();
// sort the assignments to put the server/mixer assignments first
qSort(sharedAssignments.begin(), sharedAssignments.end(), [](SharedAssignmentPointer a, SharedAssignmentPointer b){
if (a->getType() == b->getType()) {
@ -2291,9 +2291,9 @@ void DomainServer::addStaticAssignmentsToQueue() {
return a->getType() != Assignment::AgentType;
}
});
auto staticAssignment = sharedAssignments.begin();
while (staticAssignment != sharedAssignments.end()) {
// add any of the un-matched static assignments to the queue

View file

@ -725,7 +725,7 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointer<Re
}
// potentially remove connect permissions for the MAC address and machine fingerprint
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(matchingNode->getLinkedData());
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(matchingNode->getLinkedData());
if (nodeData) {
// mac address first
NodePermissionsKey macAddressKey(nodeData->getHardwareAddress(), 0);
@ -782,41 +782,48 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointer<Re
void DomainServerSettingsManager::processUsernameFromIDRequestPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
// From the packet, pull the UUID we're identifying
QUuid nodeUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
if (!nodeUUID.isNull()) {
// Before we do any processing on this packet, make sure it comes from a node that is allowed to kick (is an admin)
// OR from a node whose UUID matches the one in the packet
if (sendingNode->getCanKick() || nodeUUID == sendingNode->getUUID()) {
// First, make sure we actually have a node with this UUID
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
auto matchingNode = limitedNodeList->nodeWithUUID(nodeUUID);
// First, make sure we actually have a node with this UUID
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
auto matchingNode = limitedNodeList->nodeWithUUID(nodeUUID);
// If we do have a matching node...
if (matchingNode) {
// If we do have a matching node...
if (matchingNode) {
// Setup the packet
auto usernameFromIDReplyPacket = NLPacket::create(PacketType::UsernameFromIDReply);
QString verifiedUsername;
QUuid machineFingerprint;
// Write the UUID to the packet
usernameFromIDReplyPacket->write(nodeUUID.toRfc4122());
// Check if the sending node has permission to kick (is an admin)
// OR if the message is from a node whose UUID matches the one in the packet
if (sendingNode->getCanKick() || nodeUUID == sendingNode->getUUID()) {
// It's time to figure out the username
QString verifiedUsername = matchingNode->getPermissions().getVerifiedUserName();
// Setup the packet
auto usernameFromIDReplyPacket = NLPacket::create(PacketType::UsernameFromIDReply);
usernameFromIDReplyPacket->write(nodeUUID.toRfc4122());
verifiedUsername = matchingNode->getPermissions().getVerifiedUserName();
usernameFromIDReplyPacket->writeString(verifiedUsername);
// now put in the machine fingerprint
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(matchingNode->getLinkedData());
QUuid machineFingerprint = nodeData ? nodeData->getMachineFingerprint() : QUuid();
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(matchingNode->getLinkedData());
machineFingerprint = nodeData ? nodeData->getMachineFingerprint() : QUuid();
usernameFromIDReplyPacket->write(machineFingerprint.toRfc4122());
qDebug() << "Sending username" << verifiedUsername << "and machine fingerprint" << machineFingerprint << "associated with node" << nodeUUID;
// Ship it!
limitedNodeList->sendPacket(std::move(usernameFromIDReplyPacket), *sendingNode);
} else {
qWarning() << "Node username request received for unknown node. Refusing to process.";
usernameFromIDReplyPacket->writeString(verifiedUsername);
usernameFromIDReplyPacket->write(machineFingerprint.toRfc4122());
}
} else {
qWarning() << "Refusing to process a username request packet from node" << uuidStringWithoutCurlyBraces(sendingNode->getUUID())
<< ". Either node doesn't have kick permissions or is requesting a username not from their UUID.";
}
// Write whether or not the user is an admin
bool isAdmin = matchingNode->getCanKick();
usernameFromIDReplyPacket->writePrimitive(isAdmin);
qDebug() << "Sending username" << verifiedUsername << "and machine fingerprint" << machineFingerprint << "associated with node" << nodeUUID << ". Node admin status: " << isAdmin;
// Ship it!
limitedNodeList->sendPacket(std::move(usernameFromIDReplyPacket), *sendingNode);
} else {
qWarning() << "Node username request received for unknown node. Refusing to process.";
}
} else {
qWarning() << "Node username request received for invalid node ID. Refusing to process.";
}

View file

@ -15,7 +15,13 @@ import "../styles-uit"
Item {
property alias text: popupText.text
property alias headerGlyph: headerGlyph.text
property alias headerText: headerText.text
property real popupRadius: hifi.dimensions.borderRadius
property real headerTextPixelSize: 22
property real popupTextPixelSize: 16
FontLoader { id: ralewayRegular; source: "../../fonts/Raleway-Regular.ttf"; }
FontLoader { id: ralewaySemiBold; source: "../../fonts/Raleway-SemiBold.ttf"; }
visible: false
id: letterbox
anchors.fill: parent
@ -27,19 +33,79 @@ Item {
}
Rectangle {
width: Math.max(parent.width * 0.75, 400)
height: popupText.contentHeight*1.5
height: contentContainer.height + 50
anchors.centerIn: parent
radius: popupRadius
color: "white"
FiraSansSemiBold {
id: popupText
size: hifi.fontSizes.textFieldInput
color: hifi.colors.darkGray
horizontalAlignment: Text.AlignHCenter
anchors.fill: parent
anchors.leftMargin: 15
anchors.rightMargin: 15
wrapMode: Text.WordWrap
Item {
id: contentContainer
width: parent.width - 60
height: childrenRect.height
anchors.centerIn: parent
Item {
id: popupHeaderContainer
visible: headerText.text !== "" || headerGlyph.text !== ""
height: 30
// Anchors
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
// Header Glyph
HiFiGlyphs {
id: headerGlyph
visible: headerGlyph.text !== ""
// Size
height: parent.height
// Anchors
anchors.left: parent.left
anchors.leftMargin: -15
// Text Size
size: headerTextPixelSize*2.5
// Style
horizontalAlignment: Text.AlignHLeft
verticalAlignment: Text.AlignVCenter
color: hifi.colors.darkGray
}
// Header Text
Text {
id: headerText
visible: headerText.text !== ""
// Size
height: parent.height
// Anchors
anchors.left: headerGlyph.right
anchors.leftMargin: -5
// Text Size
font.pixelSize: headerTextPixelSize
// Style
font.family: ralewaySemiBold.name
color: hifi.colors.darkGray
horizontalAlignment: Text.AlignHLeft
verticalAlignment: Text.AlignVCenter
wrapMode: Text.WordWrap
textFormat: Text.StyledText
}
}
// Popup Text
Text {
id: popupText
// Size
width: parent.width
// Anchors
anchors.top: popupHeaderContainer.visible ? popupHeaderContainer.bottom : parent.top
anchors.topMargin: popupHeaderContainer.visible ? 15 : 0
anchors.left: parent.left
anchors.right: parent.right
// Text alignment
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHLeft
// Style
font.pixelSize: popupTextPixelSize
font.family: ralewayRegular.name
color: hifi.colors.darkGray
wrapMode: Text.WordWrap
textFormat: Text.StyledText
}
}
}
MouseArea {

View file

@ -28,11 +28,12 @@ Item {
property string uuid: ""
property string displayName: ""
property string userName: ""
property int displayTextHeight: 18
property real displayNameTextPixelSize: 18
property int usernameTextHeight: 12
property real audioLevel: 0.0
property bool isMyCard: false
property bool selected: false
property bool isAdmin: false
/* User image commented out for now - will probably be re-introduced later.
Column {
@ -55,22 +56,178 @@ Item {
width: parent.width - /*avatarImage.width - parent.spacing - */parent.anchors.leftMargin - parent.anchors.rightMargin
height: childrenRect.height
anchors.verticalCenter: parent.verticalCenter
// DisplayName Text
FiraSansSemiBold {
id: displayNameText
// Properties
text: thisNameCard.displayName
elide: Text.ElideRight
// DisplayName field for my card
Rectangle {
id: myDisplayName
visible: isMyCard
// Size
width: parent.width
width: parent.width + 70
height: 35
// Anchors
anchors.top: parent.top
// Text Size
size: thisNameCard.displayTextHeight
// Text Positioning
verticalAlignment: Text.AlignVCenter
anchors.left: parent.left
anchors.leftMargin: -10
// Style
color: hifi.colors.darkGray
color: hifi.colors.textFieldLightBackground
border.color: hifi.colors.blueHighlight
border.width: 0
TextInput {
id: myDisplayNameText
// Properties
text: thisNameCard.displayName
maximumLength: 256
clip: true
// Size
width: parent.width
height: parent.height
// Anchors
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 10
anchors.right: parent.right
anchors.rightMargin: editGlyph.width + editGlyph.anchors.rightMargin
// Style
color: hifi.colors.darkGray
FontLoader { id: firaSansSemiBold; source: "../../fonts/FiraSans-SemiBold.ttf"; }
font.family: firaSansSemiBold.name
font.pixelSize: displayNameTextPixelSize
selectionColor: hifi.colors.blueHighlight
selectedTextColor: "black"
// Text Positioning
verticalAlignment: TextInput.AlignVCenter
horizontalAlignment: TextInput.AlignLeft
// Signals
onEditingFinished: {
pal.sendToScript({method: 'displayNameUpdate', params: text})
cursorPosition = 0
focus = false
myDisplayName.border.width = 0
color = hifi.colors.darkGray
}
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
hoverEnabled: true
onClicked: {
myDisplayName.border.width = 1
myDisplayNameText.focus ? myDisplayNameText.cursorPosition = myDisplayNameText.positionAt(mouseX, mouseY, TextInput.CursorOnCharacter) : myDisplayNameText.selectAll();
myDisplayNameText.focus = true
myDisplayNameText.color = "black"
}
onDoubleClicked: {
myDisplayNameText.selectAll();
myDisplayNameText.focus = true;
}
onEntered: myDisplayName.color = hifi.colors.lightGrayText
onExited: myDisplayName.color = hifi.colors.textFieldLightBackground
}
// Edit pencil glyph
HiFiGlyphs {
id: editGlyph
text: hifi.glyphs.editPencil
// Text Size
size: displayNameTextPixelSize*1.5
// Anchors
anchors.right: parent.right
anchors.rightMargin: 5
anchors.verticalCenter: parent.verticalCenter
// Style
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
color: hifi.colors.baseGray
}
}
// Spacer for DisplayName for my card
Item {
id: myDisplayNameSpacer
width: 1
height: 4
// Anchors
anchors.top: myDisplayName.bottom
}
// DisplayName container for others' cards
Item {
id: displayNameContainer
visible: !isMyCard
// Size
width: parent.width
height: displayNameTextPixelSize + 4
// Anchors
anchors.top: parent.top
anchors.left: parent.left
// DisplayName Text for others' cards
FiraSansSemiBold {
id: displayNameText
// Properties
text: thisNameCard.displayName
elide: Text.ElideRight
// Size
width: isAdmin ? Math.min(displayNameTextMetrics.tightBoundingRect.width + 8, parent.width - adminLabelText.width - adminLabelQuestionMark.width + 8) : parent.width
// Anchors
anchors.top: parent.top
anchors.left: parent.left
// Text Size
size: displayNameTextPixelSize
// Text Positioning
verticalAlignment: Text.AlignVCenter
// Style
color: hifi.colors.darkGray
}
TextMetrics {
id: displayNameTextMetrics
font: displayNameText.font
text: displayNameText.text
}
// "ADMIN" label for other users' cards
RalewaySemiBold {
id: adminLabelText
visible: isAdmin
text: "ADMIN"
// Text size
size: displayNameText.size - 4
// Anchors
anchors.verticalCenter: parent.verticalCenter
anchors.left: displayNameText.right
// Style
font.capitalization: Font.AllUppercase
color: hifi.colors.redHighlight
// Alignment
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignTop
}
// This Rectangle refers to the [?] popup button next to "ADMIN"
Item {
id: adminLabelQuestionMark
visible: isAdmin
// Size
width: 20
height: displayNameText.height
// Anchors
anchors.verticalCenter: parent.verticalCenter
anchors.left: adminLabelText.right
RalewayRegular {
id: adminLabelQuestionMarkText
text: "[?]"
size: adminLabelText.size
font.capitalization: Font.AllUppercase
color: hifi.colors.redHighlight
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
anchors.fill: parent
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
hoverEnabled: true
onClicked: letterbox(hifi.glyphs.question,
"Domain Admin",
"This user is an admin on this domain. Admins can <b>Silence</b> and <b>Ban</b> other users at their discretion - so be extra nice!")
onEntered: adminLabelQuestionMarkText.color = "#94132e"
onExited: adminLabelQuestionMarkText.color = hifi.colors.redHighlight
}
}
}
// UserName Text
@ -83,7 +240,7 @@ Item {
// Size
width: parent.width
// Anchors
anchors.top: displayNameText.bottom
anchors.top: isMyCard ? myDisplayNameSpacer.bottom : displayNameContainer.bottom
// Text Size
size: thisNameCard.usernameTextHeight
// Text Positioning
@ -94,7 +251,7 @@ Item {
// Spacer
Item {
id: spacer
id: userNameSpacer
height: 4
width: parent.width
// Anchors
@ -105,10 +262,10 @@ Item {
Rectangle {
id: nameCardVUMeter
// Size
width: ((gainSlider.value - gainSlider.minimumValue)/(gainSlider.maximumValue - gainSlider.minimumValue)) * parent.width
width: isMyCard ? myDisplayName.width - 70 : ((gainSlider.value - gainSlider.minimumValue)/(gainSlider.maximumValue - gainSlider.minimumValue)) * parent.width
height: 8
// Anchors
anchors.top: spacer.bottom
anchors.top: userNameSpacer.bottom
// Style
radius: 4
color: "#c5c5c5"
@ -189,7 +346,12 @@ Item {
maximumValue: 20.0
stepSize: 5
updateValueWhileDragging: true
onValueChanged: updateGainFromQML(uuid, value)
onValueChanged: updateGainFromQML(uuid, value, false)
onPressedChanged: {
if (!pressed) {
updateGainFromQML(uuid, value, true)
}
}
MouseArea {
anchors.fill: parent
onWheel: {
@ -203,7 +365,8 @@ Item {
mouse.accepted = false
}
onReleased: {
// Pass through to Slider
// the above mouse.accepted seems to make this
// never get called, nonetheless...
mouse.accepted = false
}
}
@ -225,12 +388,13 @@ Item {
}
}
function updateGainFromQML(avatarUuid, sliderValue) {
if (pal.gainSliderValueDB[avatarUuid] !== sliderValue) {
function updateGainFromQML(avatarUuid, sliderValue, isReleased) {
if (isReleased || pal.gainSliderValueDB[avatarUuid] !== sliderValue) {
pal.gainSliderValueDB[avatarUuid] = sliderValue;
var data = {
sessionId: avatarUuid,
gain: sliderValue
gain: sliderValue,
isReleased: isReleased
};
pal.sendToScript({method: 'updateGain', params: data});
}

View file

@ -28,13 +28,26 @@ Rectangle {
property int rowHeight: 70
property int actionButtonWidth: 75
property int nameCardWidth: palContainer.width - actionButtonWidth*(iAmAdmin ? 4 : 2) - 4 - hifi.dimensions.scrollbarBackgroundWidth
property var myData: ({displayName: "", userName: "", audioLevel: 0.0}) // valid dummy until set
property var myData: ({displayName: "", userName: "", audioLevel: 0.0, admin: true}) // valid dummy until set
property var ignored: ({}); // Keep a local list of ignored avatars & their data. Necessary because HashMap is slow to respond after ignoring.
property var userModelData: [] // This simple list is essentially a mirror of the userModel listModel without all the extra complexities.
property bool iAmAdmin: false
// Keep a local list of per-avatar gainSliderValueDBs. Far faster than fetching this data from the server.
// NOTE: if another script modifies the per-avatar gain, this value won't be accurate!
property var gainSliderValueDB: ({});
// The letterbox used for popup messages
LetterboxMessage {
id: letterboxMessage
z: 999 // Force the popup on top of everything else
}
function letterbox(headerGlyph, headerText, message) {
letterboxMessage.headerGlyph = headerGlyph
letterboxMessage.headerText = headerText
letterboxMessage.text = message
letterboxMessage.visible = true
letterboxMessage.popupRadius = 0
}
// This is the container for the PAL
Rectangle {
@ -176,8 +189,6 @@ Rectangle {
TableViewColumn {
visible: iAmAdmin
role: "kick"
// The hacky spaces used to center text over the button, since I don't know how to apply a margin
// to column header text.
title: "BAN"
width: actionButtonWidth
movable: false
@ -207,11 +218,12 @@ Rectangle {
id: nameCard
// Properties
displayName: styleData.value
userName: model && model.userName
audioLevel: model && model.audioLevel
userName: model ? model.userName : ""
audioLevel: model ? model.audioLevel : 0.0
visible: !isCheckBox && !isButton
uuid: model && model.sessionId
uuid: model ? model.sessionId : ""
selected: styleData.selected
isAdmin: model && model.admin
// Size
width: nameCardWidth
height: parent.height
@ -229,15 +241,16 @@ Rectangle {
id: actionCheckBox
visible: isCheckBox
anchors.centerIn: parent
checked: model[styleData.role]
checked: model ? model[styleData.role] : false
// If this is a "Personal Mute" checkbox, disable the checkbox if the "Ignore" checkbox is checked.
enabled: !(styleData.role === "personalMute" && model["ignore"])
enabled: !(styleData.role === "personalMute" && (model ? model["ignore"] : true))
boxSize: 24
onClicked: {
var newValue = !model[styleData.role]
userModel.setProperty(model.userIndex, styleData.role, newValue)
userModelData[model.userIndex][styleData.role] = newValue // Defensive programming
Users[styleData.role](model.sessionId, newValue)
UserActivityLogger["palAction"](newValue ? styleData.role : "un-" + styleData.role, model.sessionId)
if (styleData.role === "ignore") {
userModel.setProperty(model.userIndex, "personalMute", newValue)
userModelData[model.userIndex]["personalMute"] = newValue // Defensive programming
@ -264,6 +277,7 @@ Rectangle {
height: 24
onClicked: {
Users[styleData.role](model.sessionId)
UserActivityLogger["palAction"](styleData.role, model.sessionId)
if (styleData.role === "kick") {
// Just for now, while we cannot undo "Ban":
userModel.remove(model.userIndex)
@ -337,11 +351,6 @@ Rectangle {
visible: iAmAdmin
color: hifi.colors.lightGrayText
}
function letterbox(message) {
letterboxMessage.text = message;
letterboxMessage.visible = true
}
// This Rectangle refers to the [?] popup button next to "NAMES"
Rectangle {
color: hifi.colors.tableBackgroundLight
@ -365,9 +374,11 @@ Rectangle {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
hoverEnabled: true
onClicked: letterbox("Bold names in the list are Avatar Display Names.\n" +
"If a Display Name isn't set, a unique Session Display Name is assigned." +
"\n\nAdministrators of this domain can also see the Username or Machine ID associated with each avatar present.")
onClicked: letterbox(hifi.glyphs.question,
"Display Names",
"Bold names in the list are <b>avatar display names</b>.<br>" +
"If a display name isn't set, a unique <b>session display name</b> is assigned." +
"<br><br>Administrators of this domain can also see the <b>username</b> or <b>machine ID</b> associated with each avatar present.")
onEntered: helpText.color = hifi.colors.baseGrayHighlight
onExited: helpText.color = hifi.colors.darkGray
}
@ -396,15 +407,30 @@ Rectangle {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
hoverEnabled: true
onClicked: letterbox('Silencing a user mutes their microphone. Silenced users can unmute themselves by clicking the "UNMUTE" button on their HUD.\n\n' +
"Banning a user will remove them from this domain and prevent them from returning. You can un-ban users from your domain's settings page.)")
onClicked: letterbox(hifi.glyphs.question,
"Admin Actions",
"<b>Silence</b> mutes a user's microphone. Silenced users can unmute themselves by clicking &quot;UNMUTE&quot; on their toolbar.<br><br>" +
"<b>Ban</b> removes a user from this domain and prevents them from returning. Admins can un-ban users from the Sandbox Domain Settings page.")
onEntered: adminHelpText.color = "#94132e"
onExited: adminHelpText.color = hifi.colors.redHighlight
}
}
LetterboxMessage {
id: letterboxMessage
}
// Timer used when selecting table rows that aren't yet present in the model
// (i.e. when selecting avatars using edit.js or sphere overlays)
Timer {
property bool selected // Selected or deselected?
property int userIndex // The userIndex of the avatar we want to select
id: selectionTimer
onTriggered: {
if (selected) {
table.selection.clear(); // for now, no multi-select
table.selection.select(userIndex);
table.positionViewAtRow(userIndex, ListView.Beginning);
} else {
table.selection.deselect(userIndex);
}
}
}
function findSessionIndex(sessionId, optionalData) { // no findIndex in .qml
@ -443,19 +469,30 @@ Rectangle {
case 'select':
var sessionIds = message.params[0];
var selected = message.params[1];
var alreadyRefreshed = message.params[2];
var userIndex = findSessionIndex(sessionIds[0]);
if (sessionIds.length > 1) {
letterbox('Only one user can be selected at a time.');
letterbox("", "", 'Only one user can be selected at a time.');
} else if (userIndex < 0) {
letterbox('The last editor is not among this list of users.');
} else {
if (selected) {
table.selection.clear(); // for now, no multi-select
table.selection.select(userIndex);
table.positionViewAtRow(userIndex, ListView.Visible);
// If we've already refreshed the PAL and the avatar still isn't present in the model...
if (alreadyRefreshed === true) {
letterbox('', '', 'The last editor of this object is either you or not among this list of users.');
} else {
table.selection.deselect(userIndex);
pal.sendToScript({method: 'refresh', params: message.params});
}
} else {
// If we've already refreshed the PAL and found the avatar in the model
if (alreadyRefreshed === true) {
// Wait a little bit before trying to actually select the avatar in the table
selectionTimer.interval = 250;
} else {
// If we've found the avatar in the model and didn't need to refresh,
// select the avatar in the table immediately
selectionTimer.interval = 0;
}
selectionTimer.selected = selected;
selectionTimer.userIndex = userIndex;
selectionTimer.start();
}
break;
// Received an "updateUsername()" request from the JS
@ -464,6 +501,7 @@ Rectangle {
var userId = message.params[0];
// The text that goes in the userName field is the second parameter in the message.
var userName = message.params[1];
var admin = message.params[2];
// If the userId is empty, we're updating "myData".
if (!userId) {
myData.userName = userName;
@ -475,8 +513,9 @@ Rectangle {
// Set the userName appropriately
userModel.setProperty(userIndex, "userName", userName);
userModelData[userIndex].userName = userName; // Defensive programming
} else {
console.log("updateUsername() called with unknown UUID: ", userId);
// Set the admin status appropriately
userModel.setProperty(userIndex, "admin", admin);
userModelData[userIndex].admin = admin; // Defensive programming
}
}
break;
@ -492,8 +531,6 @@ Rectangle {
if (userIndex != -1) {
userModel.setProperty(userIndex, "audioLevel", audioLevel);
userModelData[userIndex].audioLevel = audioLevel; // Defensive programming
} else {
console.log("updateUsername() called with unknown UUID: ", userId);
}
}
}
@ -502,6 +539,10 @@ Rectangle {
ignored = {};
gainSliderValueDB = {};
break;
case 'avatarDisconnected':
var sessionID = message.params[0];
delete ignored[sessionID];
break;
default:
console.log('Unrecognized message:', JSON.stringify(message));
}

View file

@ -312,10 +312,11 @@ Item {
readonly property string error: "="
readonly property string settings: "@"
readonly property string trash: "{"
readonly property string objectGroup: "&#xe000;"
readonly property string objectGroup: "\ue000"
readonly property string cm: "}"
readonly property string msvg79: "~"
readonly property string deg: "\\"
readonly property string px: "|"
readonly property string editPencil: "\ue00d"
}
}

View file

@ -1939,6 +1939,8 @@ void Application::initializeUi() {
rootContext->setContextProperty("AvatarList", DependencyManager::get<AvatarManager>().data());
rootContext->setContextProperty("Users", DependencyManager::get<UsersScriptingInterface>().data());
rootContext->setContextProperty("UserActivityLogger", DependencyManager::get<UserActivityLoggerScriptingInterface>().data());
rootContext->setContextProperty("Camera", &_myCamera);
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
@ -3400,6 +3402,8 @@ void Application::idle(float nsecsElapsed) {
PROFILE_COUNTER(app, "cpuSystem", { { "system", kernelUserAndSystem.z } });
#endif
auto displayPlugin = getActiveDisplayPlugin();
if (displayPlugin) {
PROFILE_COUNTER_IF_CHANGED(app, "present", float, displayPlugin->presentRate());
@ -3409,9 +3413,15 @@ void Application::idle(float nsecsElapsed) {
PROFILE_COUNTER_IF_CHANGED(app, "pendingDownloads", int, ResourceCache::getPendingRequestCount());
PROFILE_COUNTER_IF_CHANGED(app, "currentProcessing", int, DependencyManager::get<StatTracker>()->getStat("Processing").toInt());
PROFILE_COUNTER_IF_CHANGED(app, "pendingProcessing", int, DependencyManager::get<StatTracker>()->getStat("PendingProcessing").toInt());
auto renderConfig = _renderEngine->getConfiguration();
PROFILE_COUNTER_IF_CHANGED(render, "gpuTime", float, (float)_gpuContext->getFrameTimerGPUAverage());
PROFILE_COUNTER(render_detail, "gpuTimes", {
{ "OpaqueRangeTimer", renderConfig->getConfig("OpaqueRangeTimer")->property("gpuRunTime") },
{ "LinearDepth", renderConfig->getConfig("LinearDepth")->property("gpuRunTime") },
{ "SurfaceGeometry", renderConfig->getConfig("SurfaceGeometry")->property("gpuRunTime") },
{ "RenderDeferred", renderConfig->getConfig("RenderDeferred")->property("gpuRunTime") },
{ "ToneAndPostRangeTimer", renderConfig->getConfig("ToneAndPostRangeTimer")->property("gpuRunTime") }
});
PROFILE_RANGE(app, __FUNCTION__);

View file

@ -62,7 +62,7 @@ const glm::vec3 HAND_TO_PALM_OFFSET(0.0f, 0.12f, 0.08f);
namespace render {
template <> const ItemKey payloadGetKey(const AvatarSharedPointer& avatar) {
return ItemKey::Builder::opaqueShape();
return ItemKey::Builder::opaqueShape().withTypeMeta();
}
template <> const Item::Bound payloadGetBound(const AvatarSharedPointer& avatar) {
return static_pointer_cast<Avatar>(avatar)->getBounds();
@ -74,6 +74,15 @@ namespace render {
avatarPtr->render(args, qApp->getCamera()->getPosition());
}
}
template <> uint32_t metaFetchMetaSubItems(const AvatarSharedPointer& avatar, ItemIDs& subItems) {
auto avatarPtr = static_pointer_cast<Avatar>(avatar);
if (avatarPtr->getSkeletonModel()) {
auto metaSubItems = avatarPtr->getSkeletonModel()->fetchRenderItemIDs();
subItems.insert(subItems.end(), metaSubItems.begin(), metaSubItems.end());
return (uint32_t) metaSubItems.size();
}
return 0;
}
}
static uint64_t timeProcessingJoints = 0;
@ -1035,10 +1044,14 @@ void Avatar::setModelURLFinished(bool success) {
// create new model, can return an instance of a SoftAttachmentModel rather then Model
static std::shared_ptr<Model> allocateAttachmentModel(bool isSoft, RigPointer rigOverride) {
static std::shared_ptr<Model> allocateAttachmentModel(bool isSoft, RigPointer rigOverride, bool isCauterized) {
if (isSoft) {
// cast to std::shared_ptr<Model>
return std::dynamic_pointer_cast<Model>(std::make_shared<SoftAttachmentModel>(std::make_shared<Rig>(), nullptr, rigOverride));
std::shared_ptr<SoftAttachmentModel> softModel = std::make_shared<SoftAttachmentModel>(std::make_shared<Rig>(), nullptr, rigOverride);
if (isCauterized) {
softModel->flagAsCauterized();
}
return std::dynamic_pointer_cast<Model>(softModel);
} else {
return std::make_shared<Model>(std::make_shared<Rig>());
}
@ -1064,12 +1077,12 @@ void Avatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
for (int i = 0; i < attachmentData.size(); i++) {
if (i == (int)_attachmentModels.size()) {
// if number of attachments has been increased, we need to allocate a new model
_attachmentModels.push_back(allocateAttachmentModel(attachmentData[i].isSoft, _skeletonModel->getRig()));
_attachmentModels.push_back(allocateAttachmentModel(attachmentData[i].isSoft, _skeletonModel->getRig(), isMyAvatar()));
}
else if (i < oldAttachmentData.size() && oldAttachmentData[i].isSoft != attachmentData[i].isSoft) {
// if the attachment has changed type, we need to re-allocate a new one.
_attachmentsToRemove.push_back(_attachmentModels[i]);
_attachmentModels[i] = allocateAttachmentModel(attachmentData[i].isSoft, _skeletonModel->getRig());
_attachmentModels[i] = allocateAttachmentModel(attachmentData[i].isSoft, _skeletonModel->getRig(), isMyAvatar());
}
_attachmentModels[i]->setURL(attachmentData[i].modelURL);
}
@ -1354,4 +1367,4 @@ void Avatar::ensureInScene(AvatarSharedPointer self) {
if (!_inScene) {
addToScene(self);
}
}
}

View file

@ -32,6 +32,7 @@ namespace render {
template <> const ItemKey payloadGetKey(const AvatarSharedPointer& avatar);
template <> const Item::Bound payloadGetBound(const AvatarSharedPointer& avatar);
template <> void payloadRender(const AvatarSharedPointer& avatar, RenderArgs* args);
template <> uint32_t metaFetchMetaSubItems(const AvatarSharedPointer& avatar, ItemIDs& subItems);
}
static const float SCALING_RATIO = .05f;

View file

@ -260,6 +260,10 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar
}
if (removalReason == KillAvatarReason::TheirAvatarEnteredYourBubble || removalReason == YourAvatarEnteredTheirBubble) {
DependencyManager::get<NodeList>()->radiusIgnoreNodeBySessionID(avatar->getSessionUUID(), true);
} else if (removalReason == KillAvatarReason::AvatarDisconnected) {
// remove from node sets, if present
DependencyManager::get<NodeList>()->removeFromIgnoreMuteSets(avatar->getSessionUUID());
DependencyManager::get<UsersScriptingInterface>()->avatarDisconnected(avatar->getSessionUUID());
}
_avatarFades.push_back(removedAvatar);
}

View file

@ -0,0 +1,74 @@
//
// CauterizedMeshPartPayload.cpp
// interface/src/renderer
//
// Created by Andrew Meadows 2017.01.17
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "CauterizedMeshPartPayload.h"
#include <PerfStat.h>
#include "SkeletonModel.h"
using namespace render;
CauterizedMeshPartPayload::CauterizedMeshPartPayload(Model* model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform)
: ModelMeshPartPayload(model, meshIndex, partIndex, shapeIndex, transform, offsetTransform) {}
void CauterizedMeshPartPayload::updateTransformForSkinnedCauterizedMesh(const Transform& transform,
const QVector<glm::mat4>& clusterMatrices,
const QVector<glm::mat4>& cauterizedClusterMatrices) {
_transform = transform;
_cauterizedTransform = transform;
if (clusterMatrices.size() > 0) {
_worldBound = AABox();
for (auto& clusterMatrix : clusterMatrices) {
AABox clusterBound = _localBound;
clusterBound.transform(clusterMatrix);
_worldBound += clusterBound;
}
_worldBound.transform(transform);
if (clusterMatrices.size() == 1) {
_transform = _transform.worldTransform(Transform(clusterMatrices[0]));
if (cauterizedClusterMatrices.size() != 0) {
_cauterizedTransform = _cauterizedTransform.worldTransform(Transform(cauterizedClusterMatrices[0]));
} else {
_cauterizedTransform = _transform;
}
}
} else {
_worldBound = _localBound;
_worldBound.transform(_drawTransform);
}
}
void CauterizedMeshPartPayload::bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const {
// Still relying on the raw data from the model
const Model::MeshState& state = _model->getMeshState(_meshIndex);
SkeletonModel* skeleton = static_cast<SkeletonModel*>(_model);
bool useCauterizedMesh = (renderMode != RenderArgs::RenderMode::SHADOW_RENDER_MODE) && skeleton->getEnableCauterization();
if (state.clusterBuffer) {
if (useCauterizedMesh) {
const Model::MeshState& cState = skeleton->getCauterizeMeshState(_meshIndex);
batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, cState.clusterBuffer);
} else {
batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, state.clusterBuffer);
}
batch.setModelTransform(_transform);
} else {
if (useCauterizedMesh) {
batch.setModelTransform(_cauterizedTransform);
} else {
batch.setModelTransform(_transform);
}
}
}

View file

@ -0,0 +1,29 @@
//
// CauterizedModelMeshPartPayload.h
// interface/src/avatar
//
// Created by AndrewMeadows 2017.01.17
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_CauterizedMeshPartPayload_h
#define hifi_CauterizedMeshPartPayload_h
#include <MeshPartPayload.h>
class CauterizedMeshPartPayload : public ModelMeshPartPayload {
public:
CauterizedMeshPartPayload(Model* model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform);
void updateTransformForSkinnedCauterizedMesh(const Transform& transform,
const QVector<glm::mat4>& clusterMatrices,
const QVector<glm::mat4>& cauterizedClusterMatrices);
void bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const override;
private:
Transform _cauterizedTransform;
};
#endif // hifi_CauterizedMeshPartPayload_h

View file

@ -0,0 +1,254 @@
//
// CauterizedModel.cpp
// interface/src/avatar
//
// Created by Andrew Meadows 2017.01.17
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "CauterizedModel.h"
#include <AbstractViewStateInterface.h>
#include <MeshPartPayload.h>
#include <PerfStat.h>
#include "CauterizedMeshPartPayload.h"
CauterizedModel::CauterizedModel(RigPointer rig, QObject* parent) :
Model(rig, parent) {
}
CauterizedModel::~CauterizedModel() {
}
void CauterizedModel::deleteGeometry() {
Model::deleteGeometry();
_cauterizeMeshStates.clear();
}
bool CauterizedModel::updateGeometry() {
bool needsFullUpdate = Model::updateGeometry();
if (_isCauterized && needsFullUpdate) {
assert(_cauterizeMeshStates.empty());
const FBXGeometry& fbxGeometry = getFBXGeometry();
foreach (const FBXMesh& mesh, fbxGeometry.meshes) {
Model::MeshState state;
state.clusterMatrices.resize(mesh.clusters.size());
_cauterizeMeshStates.append(state);
}
}
return needsFullUpdate;
}
void CauterizedModel::createVisibleRenderItemSet() {
if (_isCauterized) {
assert(isLoaded());
const auto& meshes = _renderGeometry->getMeshes();
// all of our mesh vectors must match in size
if ((int)meshes.size() != _meshStates.size()) {
qCDebug(renderlogging) << "WARNING!!!! Mesh Sizes don't match! We will not segregate mesh groups yet.";
return;
}
// We should not have any existing renderItems if we enter this section of code
Q_ASSERT(_modelMeshRenderItemsSet.isEmpty());
_modelMeshRenderItemsSet.clear();
Transform transform;
transform.setTranslation(_translation);
transform.setRotation(_rotation);
Transform offset;
offset.setScale(_scale);
offset.postTranslate(_offset);
// Run through all of the meshes, and place them into their segregated, but unsorted buckets
int shapeID = 0;
uint32_t numMeshes = (uint32_t)meshes.size();
for (uint32_t i = 0; i < numMeshes; i++) {
const auto& mesh = meshes.at(i);
if (!mesh) {
continue;
}
// Create the render payloads
int numParts = (int)mesh->getNumParts();
for (int partIndex = 0; partIndex < numParts; partIndex++) {
auto ptr = std::make_shared<CauterizedMeshPartPayload>(this, i, partIndex, shapeID, transform, offset);
_modelMeshRenderItemsSet << std::static_pointer_cast<ModelMeshPartPayload>(ptr);
shapeID++;
}
}
} else {
Model::createVisibleRenderItemSet();
}
}
void CauterizedModel::createCollisionRenderItemSet() {
// Temporary HACK: use base class method for now
Model::createCollisionRenderItemSet();
}
// Called within Model::simulate call, below.
void CauterizedModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
Model::updateRig(deltaTime, parentTransform);
_needsUpdateClusterMatrices = true;
}
void CauterizedModel::updateClusterMatrices() {
PerformanceTimer perfTimer("CauterizedModel::updateClusterMatrices");
if (!_needsUpdateClusterMatrices || !isLoaded()) {
return;
}
_needsUpdateClusterMatrices = false;
const FBXGeometry& geometry = getFBXGeometry();
for (int i = 0; i < _meshStates.size(); i++) {
Model::MeshState& state = _meshStates[i];
const FBXMesh& mesh = geometry.meshes.at(i);
for (int j = 0; j < mesh.clusters.size(); j++) {
const FBXCluster& cluster = mesh.clusters.at(j);
auto jointMatrix = _rig->getJointTransform(cluster.jointIndex);
#if GLM_ARCH & GLM_ARCH_SSE2
glm::mat4 out, inverseBindMatrix = cluster.inverseBindMatrix;
glm_mat4_mul((glm_vec4*)&jointMatrix, (glm_vec4*)&inverseBindMatrix, (glm_vec4*)&out);
state.clusterMatrices[j] = out;
#else
state.clusterMatrices[j] = jointMatrix * cluster.inverseBindMatrix;
#endif
}
// Once computed the cluster matrices, update the buffer(s)
if (mesh.clusters.size() > 1) {
if (!state.clusterBuffer) {
state.clusterBuffer = std::make_shared<gpu::Buffer>(state.clusterMatrices.size() * sizeof(glm::mat4),
(const gpu::Byte*) state.clusterMatrices.constData());
} else {
state.clusterBuffer->setSubData(0, state.clusterMatrices.size() * sizeof(glm::mat4),
(const gpu::Byte*) state.clusterMatrices.constData());
}
}
}
// as an optimization, don't build cautrizedClusterMatrices if the boneSet is empty.
if (!_cauterizeBoneSet.empty()) {
static const glm::mat4 zeroScale(
glm::vec4(0.0f, 0.0f, 0.0f, 0.0f),
glm::vec4(0.0f, 0.0f, 0.0f, 0.0f),
glm::vec4(0.0f, 0.0f, 0.0f, 0.0f),
glm::vec4(0.0f, 0.0f, 0.0f, 1.0f));
auto cauterizeMatrix = _rig->getJointTransform(geometry.neckJointIndex) * zeroScale;
for (int i = 0; i < _cauterizeMeshStates.size(); i++) {
Model::MeshState& state = _cauterizeMeshStates[i];
const FBXMesh& mesh = geometry.meshes.at(i);
for (int j = 0; j < mesh.clusters.size(); j++) {
const FBXCluster& cluster = mesh.clusters.at(j);
auto jointMatrix = _rig->getJointTransform(cluster.jointIndex);
if (_cauterizeBoneSet.find(cluster.jointIndex) != _cauterizeBoneSet.end()) {
jointMatrix = cauterizeMatrix;
}
#if GLM_ARCH & GLM_ARCH_SSE2
glm::mat4 out, inverseBindMatrix = cluster.inverseBindMatrix;
glm_mat4_mul((glm_vec4*)&jointMatrix, (glm_vec4*)&inverseBindMatrix, (glm_vec4*)&out);
state.clusterMatrices[j] = out;
#else
state.clusterMatrices[j] = jointMatrix * cluster.inverseBindMatrix;
#endif
}
if (!_cauterizeBoneSet.empty() && (state.clusterMatrices.size() > 1)) {
if (!state.clusterBuffer) {
state.clusterBuffer =
std::make_shared<gpu::Buffer>(state.clusterMatrices.size() * sizeof(glm::mat4),
(const gpu::Byte*) state.clusterMatrices.constData());
} else {
state.clusterBuffer->setSubData(0, state.clusterMatrices.size() * sizeof(glm::mat4),
(const gpu::Byte*) state.clusterMatrices.constData());
}
}
}
}
// post the blender if we're not currently waiting for one to finish
if (geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) {
_blendedBlendshapeCoefficients = _blendshapeCoefficients;
DependencyManager::get<ModelBlender>()->noteRequiresBlend(getThisPointer());
}
}
void CauterizedModel::updateRenderItems() {
if (_isCauterized) {
if (!_addedToScene) {
return;
}
glm::vec3 scale = getScale();
if (_collisionGeometry) {
// _collisionGeometry is already scaled
scale = glm::vec3(1.0f);
}
_needsUpdateClusterMatrices = true;
_renderItemsNeedUpdate = false;
// queue up this work for later processing, at the end of update and just before rendering.
// the application will ensure only the last lambda is actually invoked.
void* key = (void*)this;
std::weak_ptr<Model> weakSelf = shared_from_this();
AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [weakSelf, scale]() {
// do nothing, if the model has already been destroyed.
auto self = weakSelf.lock();
if (!self) {
return;
}
render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();
Transform modelTransform;
modelTransform.setTranslation(self->getTranslation());
modelTransform.setRotation(self->getRotation());
Transform scaledModelTransform(modelTransform);
scaledModelTransform.setScale(scale);
uint32_t deleteGeometryCounter = self->getGeometryCounter();
render::PendingChanges pendingChanges;
QList<render::ItemID> keys = self->getRenderItems().keys();
foreach (auto itemID, keys) {
pendingChanges.updateItem<CauterizedMeshPartPayload>(itemID, [modelTransform, deleteGeometryCounter](CauterizedMeshPartPayload& data) {
if (data._model && data._model->isLoaded()) {
// Ensure the model geometry was not reset between frames
if (deleteGeometryCounter == data._model->getGeometryCounter()) {
// lazy update of cluster matrices used for rendering. We need to update them here, so we can correctly update the bounding box.
data._model->updateClusterMatrices();
// update the model transform and bounding box for this render item.
const Model::MeshState& state = data._model->getMeshState(data._meshIndex);
CauterizedModel* cModel = static_cast<CauterizedModel*>(data._model);
assert(data._meshIndex < cModel->_cauterizeMeshStates.size());
const Model::MeshState& cState = cModel->_cauterizeMeshStates.at(data._meshIndex);
data.updateTransformForSkinnedCauterizedMesh(modelTransform, state.clusterMatrices, cState.clusterMatrices);
}
}
});
}
scene->enqueuePendingChanges(pendingChanges);
});
} else {
Model::updateRenderItems();
}
}
const Model::MeshState& CauterizedModel::getCauterizeMeshState(int index) const {
assert(index < _meshStates.size());
return _cauterizeMeshStates.at(index);
}

View file

@ -0,0 +1,53 @@
//
// CauterizeableModel.h
// interface/src/avatar
//
// Created by Andrew Meadows 2016.01.17
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_CauterizedModel_h
#define hifi_CauterizedModel_h
#include <Model.h>
class CauterizedModel : public Model {
Q_OBJECT
public:
CauterizedModel(RigPointer rig, QObject* parent);
virtual ~CauterizedModel();
void flagAsCauterized() { _isCauterized = true; }
bool getIsCauterized() const { return _isCauterized; }
void setEnableCauterization(bool flag) { _enableCauterization = flag; }
bool getEnableCauterization() const { return _enableCauterization; }
const std::unordered_set<int>& getCauterizeBoneSet() const { return _cauterizeBoneSet; }
void setCauterizeBoneSet(const std::unordered_set<int>& boneSet) { _cauterizeBoneSet = boneSet; }
void deleteGeometry() override;
bool updateGeometry() override;
void createVisibleRenderItemSet() override;
void createCollisionRenderItemSet() override;
virtual void updateRig(float deltaTime, glm::mat4 parentTransform) override;
virtual void updateClusterMatrices() override;
void updateRenderItems() override;
const Model::MeshState& getCauterizeMeshState(int index) const;
protected:
std::unordered_set<int> _cauterizeBoneSet;
QVector<Model::MeshState> _cauterizeMeshStates;
bool _isCauterized { false };
bool _enableCauterization { false };
};
#endif // hifi_CauterizedModel_h

View file

@ -116,12 +116,12 @@ MyAvatar::MyAvatar(RigPointer rig) :
_hmdAtRestDetector(glm::vec3(0), glm::quat())
{
using namespace recording;
_skeletonModel->flagAsCauterized();
for (int i = 0; i < MAX_DRIVE_KEYS; i++) {
_driveKeys[i] = 0.0f;
}
// Necessary to select the correct slot
using SlotType = void(MyAvatar::*)(const glm::vec3&, bool, const glm::quat&, bool);
@ -1592,7 +1592,7 @@ void MyAvatar::preDisplaySide(RenderArgs* renderArgs) {
// toggle using the cauterizedBones depending on where the camera is and the rendering pass type.
const bool shouldDrawHead = shouldRenderHead(renderArgs);
if (shouldDrawHead != _prevShouldDrawHead) {
_skeletonModel->setCauterizeBones(!shouldDrawHead);
_skeletonModel->setEnableCauterization(!shouldDrawHead);
}
_prevShouldDrawHead = shouldDrawHead;
}

View file

@ -24,7 +24,7 @@
#include "AnimDebugDraw.h"
SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent, RigPointer rig) :
Model(rig, parent),
CauterizedModel(rig, parent),
_owningAvatar(owningAvatar),
_boundingCapsuleLocalOffset(0.0f),
_boundingCapsuleRadius(0.0f),
@ -166,7 +166,7 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
_rig->computeMotionAnimationState(deltaTime, position, velocity, orientation, ccState);
// evaluate AnimGraph animation and update jointStates.
Model::updateRig(deltaTime, parentTransform);
CauterizedModel::updateRig(deltaTime, parentTransform);
Rig::EyeParameters eyeParams;
eyeParams.worldHeadOrientation = headParams.worldHeadOrientation;
@ -178,10 +178,8 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
eyeParams.rightEyeJointIndex = geometry.rightEyeJointIndex;
_rig->updateFromEyeParameters(eyeParams);
} else {
Model::updateRig(deltaTime, parentTransform);
CauterizedModel::updateRig(deltaTime, parentTransform);
// This is a little more work than we really want.
//

View file

@ -12,8 +12,7 @@
#ifndef hifi_SkeletonModel_h
#define hifi_SkeletonModel_h
#include <Model.h>
#include "CauterizedModel.h"
class Avatar;
class MuscleConstraint;
@ -23,7 +22,7 @@ using SkeletonModelPointer = std::shared_ptr<SkeletonModel>;
using SkeletonModelWeakPointer = std::weak_ptr<SkeletonModel>;
/// A skeleton loaded from a model.
class SkeletonModel : public Model {
class SkeletonModel : public CauterizedModel {
Q_OBJECT
public:
@ -31,10 +30,10 @@ public:
SkeletonModel(Avatar* owningAvatar, QObject* parent = nullptr, RigPointer rig = nullptr);
~SkeletonModel();
virtual void initJointStates() override;
void initJointStates() override;
virtual void simulate(float deltaTime, bool fullUpdate = true) override;
virtual void updateRig(float deltaTime, glm::mat4 parentTransform) override;
void simulate(float deltaTime, bool fullUpdate = true) override;
void updateRig(float deltaTime, glm::mat4 parentTransform) override;
void updateAttitude();
/// Returns the index of the left hand joint, or -1 if not found.
@ -105,7 +104,7 @@ public:
float getHeadClipDistance() const { return _headClipDistance; }
virtual void onInvalidate() override;
void onInvalidate() override;
signals:

View file

@ -13,7 +13,7 @@
#include "InterfaceLogging.h"
SoftAttachmentModel::SoftAttachmentModel(RigPointer rig, QObject* parent, RigPointer rigOverride) :
Model(rig, parent),
CauterizedModel(rig, parent),
_rigOverride(rigOverride) {
assert(_rig);
assert(_rigOverride);

View file

@ -12,7 +12,7 @@
#ifndef hifi_SoftAttachmentModel_h
#define hifi_SoftAttachmentModel_h
#include <Model.h>
#include "CauterizedModel.h"
// A model that allows the creator to specify a secondary rig instance.
// When the cluster matrices are created for rendering, the
@ -22,16 +22,15 @@
// This is used by Avatar instances to wear clothing that follows the same
// animated pose as the SkeletonModel.
class SoftAttachmentModel : public Model {
class SoftAttachmentModel : public CauterizedModel {
Q_OBJECT
public:
SoftAttachmentModel(RigPointer rig, QObject* parent, RigPointer rigOverride);
~SoftAttachmentModel();
virtual void updateRig(float deltaTime, glm::mat4 parentTransform) override;
virtual void updateClusterMatrices() override;
void updateRig(float deltaTime, glm::mat4 parentTransform) override;
void updateClusterMatrices() override;
protected:
int getJointIndexOverride(int i) const;

View file

@ -56,7 +56,7 @@ int main(int argc, const char* argv[]) {
QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN);
QCoreApplication::setApplicationVersion(BuildInfo::VERSION);
QString applicationName = "High Fidelity Interface - " + qgetenv("USERNAME");
const QString& applicationName = getInterfaceSharedMemoryName();
bool instanceMightBeRunning = true;

View file

@ -9,9 +9,13 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "AudioInjectorOptions.h"
#include <QScriptValueIterator>
#include <RegisteredMetaTypes.h>
#include "AudioInjectorOptions.h"
#include "AudioLogging.h"
AudioInjectorOptions::AudioInjectorOptions() :
position(0.0f, 0.0f, 0.0f),
@ -22,7 +26,7 @@ AudioInjectorOptions::AudioInjectorOptions() :
ambisonic(false),
ignorePenumbra(false),
localOnly(false),
secondOffset(0.0)
secondOffset(0.0f)
{
}
@ -40,31 +44,51 @@ QScriptValue injectorOptionsToScriptValue(QScriptEngine* engine, const AudioInje
}
void injectorOptionsFromScriptValue(const QScriptValue& object, AudioInjectorOptions& injectorOptions) {
if (object.property("position").isValid()) {
vec3FromScriptValue(object.property("position"), injectorOptions.position);
if (!object.isObject()) {
qWarning() << "Audio injector options is not an object.";
return;
}
if (object.property("volume").isValid()) {
injectorOptions.volume = object.property("volume").toNumber();
}
if (object.property("loop").isValid()) {
injectorOptions.loop = object.property("loop").toBool();
}
if (object.property("orientation").isValid()) {
quatFromScriptValue(object.property("orientation"), injectorOptions.orientation);
}
if (object.property("ignorePenumbra").isValid()) {
injectorOptions.ignorePenumbra = object.property("ignorePenumbra").toBool();
}
if (object.property("localOnly").isValid()) {
injectorOptions.localOnly = object.property("localOnly").toBool();
}
if (object.property("secondOffset").isValid()) {
injectorOptions.secondOffset = object.property("secondOffset").toNumber();
QScriptValueIterator it(object);
while (it.hasNext()) {
it.next();
if (it.name() == "position") {
vec3FromScriptValue(object.property("position"), injectorOptions.position);
} else if (it.name() == "orientation") {
quatFromScriptValue(object.property("orientation"), injectorOptions.orientation);
} else if (it.name() == "volume") {
if (it.value().isNumber()) {
injectorOptions.volume = it.value().toNumber();
} else {
qCWarning(audio) << "Audio injector options: volume is not a number";
}
} else if (it.name() == "loop") {
if (it.value().isBool()) {
injectorOptions.loop = it.value().toBool();
} else {
qCWarning(audio) << "Audio injector options: loop is not a boolean";
}
} else if (it.name() == "ignorePenumbra") {
if (it.value().isBool()) {
injectorOptions.ignorePenumbra = it.value().toBool();
} else {
qCWarning(audio) << "Audio injector options: ignorePenumbra is not a boolean";
}
} else if (it.name() == "localOnly") {
if (it.value().isBool()) {
injectorOptions.localOnly = it.value().toBool();
} else {
qCWarning(audio) << "Audio injector options: localOnly is not a boolean";
}
} else if (it.name() == "secondOffset") {
if (it.value().isNumber()) {
injectorOptions.secondOffset = it.value().toNumber();
} else {
qCWarning(audio) << "Audio injector options: secondOffset is not a number";
}
} else {
qCWarning(audio) << "Unknown audio injector option:" << it.name();
}
}
}

View file

@ -1047,7 +1047,7 @@ bool AvatarData::processAvatarIdentity(const Identity& identity) {
}
if (identity.displayName != _displayName) {
setDisplayName(identity.displayName);
_displayName = identity.displayName;
hasIdentityChanged = true;
}
maybeUpdateSessionDisplayNameFromTransport(identity.sessionDisplayName);
@ -1094,6 +1094,9 @@ void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) {
void AvatarData::setDisplayName(const QString& displayName) {
_displayName = displayName;
_sessionDisplayName = "";
sendIdentityPacket();
qCDebug(avatars) << "Changing display name for avatar to" << displayName;
}

View file

@ -482,6 +482,7 @@ void OpenGLDisplayPlugin::submitFrame(const gpu::FramePointer& newFrame) {
}
void OpenGLDisplayPlugin::updateFrameData() {
PROFILE_RANGE(render, __FUNCTION__)
if (_lockCurrentTexture) {
return;
}
@ -596,12 +597,16 @@ void OpenGLDisplayPlugin::internalPresent() {
}
void OpenGLDisplayPlugin::present() {
PROFILE_RANGE_EX(render, __FUNCTION__, 0xffffff00, (uint64_t)presentCount())
updateFrameData();
auto frameId = (uint64_t)presentCount();
PROFILE_RANGE_EX(render, __FUNCTION__, 0xffffff00, frameId)
{
PROFILE_RANGE_EX(render, "updateFrameData", 0xff00ff00, frameId)
updateFrameData();
}
incrementPresentCount();
{
PROFILE_RANGE_EX(render, "recycle", 0xff00ff00, (uint64_t)presentCount())
PROFILE_RANGE_EX(render, "recycle", 0xff00ff00, frameId)
_gpuContext->recycle();
}
@ -615,19 +620,19 @@ void OpenGLDisplayPlugin::present() {
_lastFrame = _currentFrame.get();
});
// Execute the frame rendering commands
PROFILE_RANGE_EX(render, "execute", 0xff00ff00, (uint64_t)presentCount())
PROFILE_RANGE_EX(render, "execute", 0xff00ff00, frameId)
_gpuContext->executeFrame(_currentFrame);
}
// Write all layers to a local framebuffer
{
PROFILE_RANGE_EX(render, "composite", 0xff00ffff, (uint64_t)presentCount())
PROFILE_RANGE_EX(render, "composite", 0xff00ffff, frameId)
compositeLayers();
}
// Take the composite framebuffer and send it to the output device
{
PROFILE_RANGE_EX(render, "internalPresent", 0xff00ffff, (uint64_t)presentCount())
PROFILE_RANGE_EX(render, "internalPresent", 0xff00ffff, frameId)
internalPresent();
}

View file

@ -15,21 +15,21 @@
namespace render {
template <> const ItemKey payloadGetKey(const RenderableEntityItemProxy::Pointer& payload) {
if (payload && payload->entity) {
if (payload->entity->getType() == EntityTypes::Light) {
return ItemKey::Builder::light();
if (payload && payload->_entity) {
if (payload->_entity->getType() == EntityTypes::Light) {
return ItemKey::Builder::light().withTypeMeta();
}
if (payload && payload->entity->isTransparent()) {
return ItemKey::Builder::transparentShape();
if (payload && payload->_entity->isTransparent()) {
return ItemKey::Builder::transparentShape().withTypeMeta();
}
}
return ItemKey::Builder::opaqueShape();
return ItemKey::Builder::opaqueShape().withTypeMeta();
}
template <> const Item::Bound payloadGetBound(const RenderableEntityItemProxy::Pointer& payload) {
if (payload && payload->entity) {
if (payload && payload->_entity) {
bool success;
auto result = payload->entity->getAABox(success);
auto result = payload->_entity->getAABox(success);
if (!success) {
return render::Item::Bound();
}
@ -39,11 +39,19 @@ namespace render {
}
template <> void payloadRender(const RenderableEntityItemProxy::Pointer& payload, RenderArgs* args) {
if (args) {
if (payload && payload->entity && payload->entity->getVisible()) {
payload->entity->render(args);
if (payload && payload->_entity && payload->_entity->getVisible()) {
payload->_entity->render(args);
}
}
}
template <> uint32_t metaFetchMetaSubItems(const RenderableEntityItemProxy::Pointer& payload, ItemIDs& subItems) {
auto metaID = payload->_metaID;
if (Item::isValidID(metaID)) {
subItems.emplace_back(metaID);
return 1;
}
return 0;
}
}
void makeEntityItemStatusGetters(EntityItemPointer entity, render::Item::Status::Getters& statusGetters) {

View file

@ -36,17 +36,19 @@ void makeEntityItemStatusGetters(EntityItemPointer entity, render::Item::Status:
class RenderableEntityItemProxy {
public:
RenderableEntityItemProxy(EntityItemPointer entity) : entity(entity) { }
RenderableEntityItemProxy(EntityItemPointer entity, render::ItemID metaID) : _entity(entity), _metaID(metaID) { }
typedef render::Payload<RenderableEntityItemProxy> Payload;
typedef Payload::DataPointer Pointer;
EntityItemPointer entity;
EntityItemPointer _entity;
render::ItemID _metaID;
};
namespace render {
template <> const ItemKey payloadGetKey(const RenderableEntityItemProxy::Pointer& payload);
template <> const Item::Bound payloadGetBound(const RenderableEntityItemProxy::Pointer& payload);
template <> void payloadRender(const RenderableEntityItemProxy::Pointer& payload, RenderArgs* args);
template <> uint32_t metaFetchMetaSubItems(const RenderableEntityItemProxy::Pointer& payload, ItemIDs& subItems);
}
// Mixin class for implementing basic single item rendering
@ -55,7 +57,7 @@ public:
bool addToScene(EntityItemPointer self, std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges) {
_myItem = scene->allocateID();
auto renderData = std::make_shared<RenderableEntityItemProxy>(self);
auto renderData = std::make_shared<RenderableEntityItemProxy>(self, _myItem);
auto renderPayload = std::make_shared<RenderableEntityItemProxy::Payload>(renderData);
render::Item::Status::Getters statusGetters;

View file

@ -194,7 +194,7 @@ public:
namespace render {
template <> const ItemKey payloadGetKey(const RenderableModelEntityItemMeta::Pointer& payload) {
return ItemKey::Builder::opaqueShape();
return ItemKey::Builder::opaqueShape().withTypeMeta();
}
template <> const Item::Bound payloadGetBound(const RenderableModelEntityItemMeta::Pointer& payload) {
@ -216,6 +216,15 @@ namespace render {
}
}
}
template <> uint32_t metaFetchMetaSubItems(const RenderableModelEntityItemMeta::Pointer& payload, ItemIDs& subItems) {
auto modelEntity = std::static_pointer_cast<RenderableModelEntityItem>(payload->entity);
if (modelEntity->hasModel()) {
auto metaSubItems = modelEntity->getModelNotSafe()->fetchRenderItemIDs();
subItems.insert(subItems.end(), metaSubItems.begin(), metaSubItems.end());
return (uint32_t) metaSubItems.size();
}
return 0;
}
}
bool RenderableModelEntityItem::addToScene(EntityItemPointer self, std::shared_ptr<render::Scene> scene,
@ -473,6 +482,10 @@ void RenderableModelEntityItem::render(RenderArgs* args) {
}
}
ModelPointer RenderableModelEntityItem::getModelNotSafe() {
return _model;
}
ModelPointer RenderableModelEntityItem::getModel(QSharedPointer<EntityTreeRenderer> renderer) {
if (!renderer) {
return nullptr;

View file

@ -52,6 +52,7 @@ public:
BoxFace& face, glm::vec3& surfaceNormal,
void** intersectedObject, bool precisionPicking) const override;
ModelPointer getModel(QSharedPointer<EntityTreeRenderer> renderer);
ModelPointer getModelNotSafe();
virtual bool needsToCallUpdate() const override;
virtual void update(const quint64& now) override;

View file

@ -246,14 +246,16 @@ void RenderableWebEntityItem::render(RenderArgs* args) {
}
void RenderableWebEntityItem::setSourceUrl(const QString& value) {
if (_sourceUrl != value) {
qCDebug(entities) << "Setting web entity source URL to " << value;
_sourceUrl = value;
if (_webSurface) {
AbstractViewStateInterface::instance()->postLambdaEvent([this] {
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
});
}
auto valueBeforeSuperclassSet = _sourceUrl;
WebEntityItem::setSourceUrl(value);
if (_sourceUrl != valueBeforeSuperclassSet && _webSurface) {
qCDebug(entities) << "Changing web entity source URL to " << _sourceUrl;
AbstractViewStateInterface::instance()->postLambdaEvent([this] {
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
});
}
}

View file

@ -962,9 +962,16 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
if (validEditPacket && !_entityScriptSourceWhitelist.isEmpty() && !properties.getScript().isEmpty()) {
bool passedWhiteList = false;
auto entityScript = properties.getScript();
// grab a URL representation of the entity script so we can check the host for this script
auto entityScriptURL = QUrl::fromUserInput(properties.getScript());
for (const auto& whiteListedPrefix : _entityScriptSourceWhitelist) {
if (entityScript.startsWith(whiteListedPrefix, Qt::CaseInsensitive)) {
auto whiteListURL = QUrl::fromUserInput(whiteListedPrefix);
// check if this script URL matches the whitelist domain and, optionally, is beneath the path
if (entityScriptURL.host().compare(whiteListURL.host(), Qt::CaseInsensitive) == 0 &&
entityScriptURL.path().startsWith(whiteListURL.path(), Qt::CaseInsensitive)) {
passedWhiteList = true;
break;
}

View file

@ -125,7 +125,13 @@ bool WebEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const g
void WebEntityItem::setSourceUrl(const QString& value) {
if (_sourceUrl != value) {
_sourceUrl = value;
auto newURL = QUrl::fromUserInput(value);
if (newURL.isValid()) {
_sourceUrl = newURL.toDisplayString();
} else {
qCDebug(entities) << "Clearing web entity source URL since" << value << "cannot be parsed to a valid URL.";
}
}
}

View file

@ -847,6 +847,16 @@ void NodeList::ignoreNodeBySessionID(const QUuid& nodeID, bool ignoreEnabled) {
}
}
void NodeList::removeFromIgnoreMuteSets(const QUuid& nodeID) {
// don't remove yourself, or nobody
if (!nodeID.isNull() && _sessionUUID != nodeID) {
QWriteLocker ignoredSetLocker{ &_ignoredSetLock };
QWriteLocker personalMutedSetLocker{ &_personalMutedSetLock };
_ignoredNodeIDs.unsafe_erase(nodeID);
_personalMutedNodeIDs.unsafe_erase(nodeID);
}
}
bool NodeList::isIgnoringNode(const QUuid& nodeID) const {
QReadLocker ignoredSetLocker{ &_ignoredSetLock };
return _ignoredNodeIDs.find(nodeID) != _ignoredNodeIDs.cend();
@ -1030,25 +1040,20 @@ void NodeList::muteNodeBySessionID(const QUuid& nodeID) {
}
void NodeList::requestUsernameFromSessionID(const QUuid& nodeID) {
// send a request to domain-server to get the username associated with the given session ID
if (getThisNodeCanKick() || nodeID.isNull()) {
// setup the packet
auto usernameFromIDRequestPacket = NLPacket::create(PacketType::UsernameFromIDRequest, NUM_BYTES_RFC4122_UUID, true);
// send a request to domain-server to get the username/machine fingerprint/admin status associated with the given session ID
// If the requesting user isn't an admin, the username and machine fingerprint will return "".
auto usernameFromIDRequestPacket = NLPacket::create(PacketType::UsernameFromIDRequest, NUM_BYTES_RFC4122_UUID, true);
// write the node ID to the packet
if (nodeID.isNull()) {
usernameFromIDRequestPacket->write(getSessionUUID().toRfc4122());
} else {
usernameFromIDRequestPacket->write(nodeID.toRfc4122());
}
qCDebug(networking) << "Sending packet to get username of node" << uuidStringWithoutCurlyBraces(nodeID);
sendPacket(std::move(usernameFromIDRequestPacket), _domainHandler.getSockAddr());
// write the node ID to the packet
if (nodeID.isNull()) {
usernameFromIDRequestPacket->write(getSessionUUID().toRfc4122());
} else {
qWarning() << "You do not have permissions to kick in this domain."
<< "Request to get the username of node" << uuidStringWithoutCurlyBraces(nodeID) << "will not be sent";
usernameFromIDRequestPacket->write(nodeID.toRfc4122());
}
qCDebug(networking) << "Sending packet to get username/fingerprint/admin status of node" << uuidStringWithoutCurlyBraces(nodeID);
sendPacket(std::move(usernameFromIDRequestPacket), _domainHandler.getSockAddr());
}
void NodeList::processUsernameFromIDReply(QSharedPointer<ReceivedMessage> message) {
@ -1058,10 +1063,13 @@ void NodeList::processUsernameFromIDReply(QSharedPointer<ReceivedMessage> messag
QString username = message->readString();
// read the machine fingerprint from the packet
QString machineFingerprintString = (QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID))).toString();
bool isAdmin;
message->readPrimitive(&isAdmin);
qCDebug(networking) << "Got username" << username << "and machine fingerprint" << machineFingerprintString << "for node" << nodeUUIDString;
qCDebug(networking) << "Got username" << username << "and machine fingerprint"
<< machineFingerprintString << "for node" << nodeUUIDString << ". isAdmin:" << isAdmin;
emit usernameFromIDReply(nodeUUIDString, username, machineFingerprintString);
emit usernameFromIDReply(nodeUUIDString, username, machineFingerprintString, isAdmin);
}
void NodeList::setRequestsDomainListData(bool isRequesting) {

View file

@ -90,6 +90,8 @@ public:
bool getRequestsDomainListData() { return _requestsDomainListData; }
void setRequestsDomainListData(bool isRequesting);
void removeFromIgnoreMuteSets(const QUuid& nodeID);
public slots:
void reset();
void sendDomainServerCheckIn();
@ -118,7 +120,7 @@ signals:
void receivedDomainServerList();
void ignoredNode(const QUuid& nodeID, bool enabled);
void ignoreRadiusEnabledChanged(bool isIgnored);
void usernameFromIDReply(const QString& nodeID, const QString& username, const QString& machineFingerprint);
void usernameFromIDReply(const QString& nodeID, const QString& username, const QString& machineFingerprint, bool isAdmin);
private slots:
void stopKeepalivePingTimer();

View file

@ -38,6 +38,21 @@ void UserActivityLoggerScriptingInterface::tutorialProgress( QString stepName, i
}
void UserActivityLoggerScriptingInterface::palAction(QString action, QString target) {
QJsonObject payload;
payload["action"] = action;
if (target.length() > 0) {
payload["target"] = target;
}
logAction("pal_activity", payload);
}
void UserActivityLoggerScriptingInterface::palOpened(float secondsOpened) {
logAction("pal_opened", {
{ "seconds_opened", secondsOpened }
});
}
void UserActivityLoggerScriptingInterface::logAction(QString action, QJsonObject details) {
QMetaObject::invokeMethod(&UserActivityLogger::getInstance(), "logAction",
Q_ARG(QString, action),

View file

@ -25,7 +25,8 @@ public:
Q_INVOKABLE void toggledAway(bool isAway);
Q_INVOKABLE void tutorialProgress(QString stepName, int stepNumber, float secondsToComplete,
float tutorialElapsedTime, QString tutorialRunID = "", int tutorialVersion = 0, QString controllerType = "");
Q_INVOKABLE void palAction(QString action, QString target);
Q_INVOKABLE void palOpened(float secondsOpen);
private:
void logAction(QString action, QJsonObject details = {});
};

View file

@ -219,7 +219,8 @@ enum class AvatarMixerPacketVersion : PacketVersion {
HandControllerJoints,
HasKillAvatarReason,
SessionDisplayName,
Unignore
Unignore,
ImmediateSessionDisplayNameUpdates
};
enum class DomainConnectRequestVersion : PacketVersion {

View file

@ -43,14 +43,14 @@ public:
class SteamScriptingInterface : public QObject {
Q_OBJECT
Q_PROPERTY(bool isRunning READ isRunning)
Q_PROPERTY(bool running READ isRunning)
public:
SteamScriptingInterface(QObject* parent, SteamClientPlugin* plugin) : QObject(parent) {}
SteamScriptingInterface(QObject* parent, SteamClientPlugin* plugin) : QObject(parent), _plugin(plugin) {}
public slots:
bool isRunning() const { return _plugin->isRunning(); }
void openInviteOverlay() const { _plugin->openInviteOverlay(); }
public slots:
bool isRunning() const { return _plugin && _plugin->isRunning(); }
void openInviteOverlay() const { if (_plugin) { _plugin->openInviteOverlay(); } }
private:
SteamClientPlugin* _plugin;

View file

@ -251,7 +251,7 @@ void MeshPartPayload::bindMaterial(gpu::Batch& batch, const ShapePipeline::Locat
}
}
void MeshPartPayload::bindTransform(gpu::Batch& batch, const ShapePipeline::LocationsPointer locations, bool canCauterize) const {
void MeshPartPayload::bindTransform(gpu::Batch& batch, const ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const {
batch.setModelTransform(_drawTransform);
}
@ -265,7 +265,7 @@ void MeshPartPayload::render(RenderArgs* args) const {
assert(locations);
// Bind the model transform and the skinCLusterMatrices if needed
bindTransform(batch, locations);
bindTransform(batch, locations, args->_renderMode);
//Bind the index buffer and vertex buffer and Blend shapes if needed
bindMesh(batch);
@ -359,11 +359,8 @@ void ModelMeshPartPayload::notifyLocationChanged() {
}
void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& transform,
const QVector<glm::mat4>& clusterMatrices,
const QVector<glm::mat4>& cauterizedClusterMatrices) {
void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& transform, const QVector<glm::mat4>& clusterMatrices) {
_transform = transform;
_cauterizedTransform = transform;
if (clusterMatrices.size() > 0) {
_worldBound = AABox();
@ -372,16 +369,13 @@ void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& transf
clusterBound.transform(clusterMatrix);
_worldBound += clusterBound;
}
_worldBound.transform(transform);
_worldBound.transform(_transform);
if (clusterMatrices.size() == 1) {
_transform = _transform.worldTransform(Transform(clusterMatrices[0]));
if (cauterizedClusterMatrices.size() != 0) {
_cauterizedTransform = _cauterizedTransform.worldTransform(Transform(cauterizedClusterMatrices[0]));
} else {
_cauterizedTransform = _transform;
}
}
} else {
_worldBound = _localBound;
_worldBound.transform(_transform);
}
}
@ -408,7 +402,7 @@ ItemKey ModelMeshPartPayload::getKey() const {
}
}
if (!_hasFinishedFade) {
if (_fadeState != FADE_COMPLETE) {
builder.withTransparent();
}
@ -478,7 +472,7 @@ ShapeKey ModelMeshPartPayload::getShapeKey() const {
}
ShapeKey::Builder builder;
if (isTranslucent || !_hasFinishedFade) {
if (isTranslucent || _fadeState != FADE_COMPLETE) {
builder.withTranslucent();
}
if (hasTangents) {
@ -519,43 +513,39 @@ void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) const {
batch.setInputStream(2, _drawMesh->getVertexStream().makeRangedStream(2));
}
float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f;
if (!_hasColorAttrib || fadeRatio < 1.0f) {
batch._glColor4f(1.0f, 1.0f, 1.0f, fadeRatio);
if (_fadeState != FADE_COMPLETE) {
batch._glColor4f(1.0f, 1.0f, 1.0f, computeFadeAlpha());
} else if (!_hasColorAttrib) {
batch._glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
}
}
void ModelMeshPartPayload::bindTransform(gpu::Batch& batch, const ShapePipeline::LocationsPointer locations, bool canCauterize) const {
void ModelMeshPartPayload::bindTransform(gpu::Batch& batch, const ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const {
// Still relying on the raw data from the model
const Model::MeshState& state = _model->_meshStates.at(_meshIndex);
const Model::MeshState& state = _model->getMeshState(_meshIndex);
if (state.clusterBuffer) {
if (canCauterize && _model->getCauterizeBones()) {
batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, state.cauterizedClusterBuffer);
} else {
batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, state.clusterBuffer);
}
batch.setModelTransform(_transform);
} else {
if (canCauterize && _model->getCauterizeBones()) {
batch.setModelTransform(_cauterizedTransform);
} else {
batch.setModelTransform(_transform);
}
batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, state.clusterBuffer);
}
batch.setModelTransform(_transform);
}
void ModelMeshPartPayload::startFade() {
bool shouldFade = EntityItem::getEntitiesShouldFadeFunction()();
if (shouldFade) {
_fadeStartTime = usecTimestampNow();
_hasStartedFade = true;
_hasFinishedFade = false;
} else {
_isFading = true;
_hasStartedFade = true;
_hasFinishedFade = true;
float ModelMeshPartPayload::computeFadeAlpha() const {
if (_fadeState == FADE_WAITING_TO_START) {
return 0.0f;
}
float fadeAlpha = 1.0f;
const float INV_FADE_PERIOD = 1.0f / (float)(1 * USECS_PER_SECOND);
float fraction = (float)(usecTimestampNow() - _fadeStartTime) * INV_FADE_PERIOD;
if (fraction < 1.0f) {
fadeAlpha = Interpolate::simpleNonLinearBlend(fraction);
}
if (fadeAlpha >= 1.0f) {
_fadeState = FADE_COMPLETE;
// when fade-in completes we flag model for one last "render item update"
_model->setRenderItemsNeedUpdate();
return 1.0f;
}
return Interpolate::simpleNonLinearBlend(fadeAlpha);
}
void ModelMeshPartPayload::render(RenderArgs* args) const {
@ -565,40 +555,34 @@ void ModelMeshPartPayload::render(RenderArgs* args) const {
return; // bail asap
}
// If we didn't start the fade in, check if we are ready to now....
if (!_hasStartedFade && _model->isLoaded() && _model->getGeometry()->areTexturesLoaded()) {
const_cast<ModelMeshPartPayload&>(*this).startFade();
if (_fadeState == FADE_WAITING_TO_START) {
if (_model->isLoaded() && _model->getGeometry()->areTexturesLoaded()) {
if (EntityItem::getEntitiesShouldFadeFunction()()) {
_fadeStartTime = usecTimestampNow();
_fadeState = FADE_IN_PROGRESS;
} else {
_fadeState = FADE_COMPLETE;
}
_model->setRenderItemsNeedUpdate();
} else {
return;
}
}
// If we still didn't start the fade in, bail
if (!_hasStartedFade) {
if (!args) {
return;
}
// When an individual mesh parts like this finishes its fade, we will mark the Model as
// having render items that need updating
bool nextIsFading = _isFading ? isStillFading() : false;
bool startFading = !_isFading && !_hasFinishedFade && _hasStartedFade;
bool endFading = _isFading && !nextIsFading;
if (startFading || endFading) {
_isFading = startFading;
_hasFinishedFade = endFading;
_model->setRenderItemsNeedUpdate();
}
gpu::Batch& batch = *(args->_batch);
if (!getShapeKey().isValid()) {
return;
}
gpu::Batch& batch = *(args->_batch);
auto locations = args->_pipeline->locations;
assert(locations);
// Bind the model transform and the skinCLusterMatrices if needed
bool canCauterize = args->_renderMode != RenderArgs::SHADOW_RENDER_MODE;
_model->updateClusterMatrices();
bindTransform(batch, locations, canCauterize);
bindTransform(batch, locations, args->_renderMode);
//Bind the index buffer and vertex buffer and Blend shapes if needed
bindMesh(batch);
@ -606,9 +590,7 @@ void ModelMeshPartPayload::render(RenderArgs* args) const {
// apply material properties
bindMaterial(batch, locations);
if (args) {
args->_details._materialSwitches++;
}
args->_details._materialSwitches++;
// Draw!
{
@ -616,9 +598,6 @@ void ModelMeshPartPayload::render(RenderArgs* args) const {
drawCall(batch);
}
if (args) {
const int INDICES_PER_TRIANGLE = 3;
args->_details._trianglesRendered += _drawPart._numIndices / INDICES_PER_TRIANGLE;
}
const int INDICES_PER_TRIANGLE = 3;
args->_details._trianglesRendered += _drawPart._numIndices / INDICES_PER_TRIANGLE;
}

View file

@ -21,6 +21,10 @@
#include <model/Geometry.h>
const uint8_t FADE_WAITING_TO_START = 0;
const uint8_t FADE_IN_PROGRESS = 1;
const uint8_t FADE_COMPLETE = 2;
class Model;
class MeshPartPayload {
@ -48,21 +52,20 @@ public:
void drawCall(gpu::Batch& batch) const;
virtual void bindMesh(gpu::Batch& batch) const;
virtual void bindMaterial(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations) const;
virtual void bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, bool canCauterize = true) const;
virtual void bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const;
// Payload resource cached values
std::shared_ptr<const model::Mesh> _drawMesh;
int _partIndex = 0;
model::Mesh::Part _drawPart;
std::shared_ptr<const model::Material> _drawMaterial;
model::Box _localBound;
Transform _drawTransform;
Transform _transform;
mutable model::Box _worldBound;
int _partIndex = 0;
bool _hasColorAttrib { false };
bool _hasColorAttrib = false;
model::Box _localBound;
mutable model::Box _worldBound;
std::shared_ptr<const model::Mesh> _drawMesh;
std::shared_ptr<const model::Material> _drawMaterial;
model::Mesh::Part _drawPart;
size_t getVerticesCount() const { return _drawMesh ? _drawMesh->getNumVertices() : 0; }
size_t getMaterialTextureSize() { return _drawMaterial ? _drawMaterial->getTextureSize() : 0; }
@ -86,13 +89,9 @@ public:
void notifyLocationChanged() override;
void updateTransformForSkinnedMesh(const Transform& transform,
const QVector<glm::mat4>& clusterMatrices,
const QVector<glm::mat4>& cauterizedClusterMatrices);
const QVector<glm::mat4>& clusterMatrices);
// Entity fade in
void startFade();
bool hasStartedFade() { return _hasStartedFade; }
bool isStillFading() const { return Interpolate::calculateFadeRatio(_fadeStartTime) < 1.0f; }
float computeFadeAlpha() const;
// Render Item interface
render::ItemKey getKey() const override;
@ -102,13 +101,12 @@ public:
// ModelMeshPartPayload functions to perform render
void bindMesh(gpu::Batch& batch) const override;
void bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, bool canCauterize = true) const override;
void bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const override;
void initCache();
Model* _model;
Transform _cauterizedTransform;
int _meshIndex;
int _shapeID;
@ -116,10 +114,8 @@ public:
bool _isBlendShaped{ false };
private:
quint64 _fadeStartTime { 0 };
bool _hasStartedFade { false };
mutable bool _hasFinishedFade { false };
mutable bool _isFading { false };
mutable quint64 _fadeStartTime { 0 };
mutable uint8_t _fadeState { FADE_WAITING_TO_START };
};
namespace render {

View file

@ -91,7 +91,6 @@ Model::Model(RigPointer rig, QObject* parent) :
_scaledToFit(false),
_snapModelToRegistrationPoint(false),
_snappedToRegistrationPoint(false),
_cauterizeBones(false),
_url(HTTP_INVALID_COM),
_isVisible(true),
_blendNumber(0),
@ -228,9 +227,6 @@ void Model::updateRenderItems() {
foreach (auto itemID, self->_modelMeshRenderItems.keys()) {
pendingChanges.updateItem<ModelMeshPartPayload>(itemID, [modelTransform, deleteGeometryCounter](ModelMeshPartPayload& data) {
if (data._model && data._model->isLoaded()) {
if (!data.hasStartedFade() && data._model->getGeometry()->areTexturesLoaded()) {
data.startFade();
}
// Ensure the model geometry was not reset between frames
if (deleteGeometryCounter == data._model->_deleteGeometryCounter) {
// lazy update of cluster matrices used for rendering. We need to update them here, so we can correctly update the bounding box.
@ -238,7 +234,7 @@ void Model::updateRenderItems() {
// update the model transform and bounding box for this render item.
const Model::MeshState& state = data._model->_meshStates.at(data._meshIndex);
data.updateTransformForSkinnedMesh(modelTransform, state.clusterMatrices, state.cauterizedClusterMatrices);
data.updateTransformForSkinnedMesh(modelTransform, state.clusterMatrices);
}
}
});
@ -294,8 +290,6 @@ bool Model::updateGeometry() {
foreach (const FBXMesh& mesh, fbxGeometry.meshes) {
MeshState state;
state.clusterMatrices.resize(mesh.clusters.size());
state.cauterizedClusterMatrices.resize(mesh.clusters.size());
_meshStates.append(state);
// Note: we add empty buffers for meshes that lack blendshapes so we can access the buffers by index
@ -673,6 +667,7 @@ bool Model::addToScene(std::shared_ptr<render::Scene> scene,
hasTransparent = hasTransparent || renderItem.get()->getShapeKey().isTranslucent();
verticesCount += renderItem.get()->getVerticesCount();
_modelMeshRenderItems.insert(item, renderPayload);
_modelMeshRenderItemIDs.emplace_back(item);
}
somethingAdded = !_modelMeshRenderItems.empty();
@ -695,6 +690,7 @@ void Model::removeFromScene(std::shared_ptr<render::Scene> scene, render::Pendin
foreach (auto item, _modelMeshRenderItems.keys()) {
pendingChanges.removeItem(item);
}
_modelMeshRenderItemIDs.clear();
_modelMeshRenderItems.clear();
_modelMeshRenderItemsSet.clear();
@ -1157,13 +1153,6 @@ void Model::updateClusterMatrices() {
}
_needsUpdateClusterMatrices = false;
const FBXGeometry& geometry = getFBXGeometry();
static const glm::mat4 zeroScale(
glm::vec4(0.0f, 0.0f, 0.0f, 0.0f),
glm::vec4(0.0f, 0.0f, 0.0f, 0.0f),
glm::vec4(0.0f, 0.0f, 0.0f, 0.0f),
glm::vec4(0.0f, 0.0f, 0.0f, 1.0f));
auto cauterizeMatrix = _rig->getJointTransform(geometry.neckJointIndex) * zeroScale;
for (int i = 0; i < _meshStates.size(); i++) {
MeshState& state = _meshStates[i];
const FBXMesh& mesh = geometry.meshes.at(i);
@ -1177,20 +1166,6 @@ void Model::updateClusterMatrices() {
#else
state.clusterMatrices[j] = jointMatrix * cluster.inverseBindMatrix;
#endif
// as an optimization, don't build cautrizedClusterMatrices if the boneSet is empty.
if (!_cauterizeBoneSet.empty()) {
if (_cauterizeBoneSet.find(cluster.jointIndex) != _cauterizeBoneSet.end()) {
jointMatrix = cauterizeMatrix;
}
#if GLM_ARCH & GLM_ARCH_SSE2
glm::mat4 out, inverseBindMatrix = cluster.inverseBindMatrix;
glm_mat4_mul((glm_vec4*)&jointMatrix, (glm_vec4*)&inverseBindMatrix, (glm_vec4*)&out);
state.cauterizedClusterMatrices[j] = out;
#else
state.cauterizedClusterMatrices[j] = jointMatrix * cluster.inverseBindMatrix;
#endif
}
}
// Once computed the cluster matrices, update the buffer(s)
@ -1202,17 +1177,6 @@ void Model::updateClusterMatrices() {
state.clusterBuffer->setSubData(0, state.clusterMatrices.size() * sizeof(glm::mat4),
(const gpu::Byte*) state.clusterMatrices.constData());
}
if (!_cauterizeBoneSet.empty() && (state.cauterizedClusterMatrices.size() > 1)) {
if (!state.cauterizedClusterBuffer) {
state.cauterizedClusterBuffer =
std::make_shared<gpu::Buffer>(state.cauterizedClusterMatrices.size() * sizeof(glm::mat4),
(const gpu::Byte*) state.cauterizedClusterMatrices.constData());
} else {
state.cauterizedClusterBuffer->setSubData(0, state.cauterizedClusterMatrices.size() * sizeof(glm::mat4),
(const gpu::Byte*) state.cauterizedClusterMatrices.constData());
}
}
}
}
@ -1301,6 +1265,10 @@ AABox Model::getRenderableMeshBound() const {
}
}
const render::ItemIDs& Model::fetchRenderItemIDs() const {
return _modelMeshRenderItemIDs;
}
void Model::createRenderItemSet() {
if (_collisionGeometry) {
if (_collisionRenderItemsSet.empty()) {

View file

@ -101,10 +101,11 @@ public:
bool isLayeredInFront() const { return _isLayeredInFront; }
void updateRenderItems();
virtual void updateRenderItems();
void setRenderItemsNeedUpdate() { _renderItemsNeedUpdate = true; }
bool getRenderItemsNeedUpdate() { return _renderItemsNeedUpdate; }
AABox getRenderableMeshBound() const;
const render::ItemIDs& fetchRenderItemIDs() const;
bool maybeStartBlender();
@ -214,12 +215,6 @@ public:
bool getIsScaledToFit() const { return _scaledToFit; } /// is model scaled to fit
glm::vec3 getScaleToFitDimensions() const; /// the dimensions model is scaled to, including inferred y/z
void setCauterizeBones(bool flag) { _cauterizeBones = flag; }
bool getCauterizeBones() const { return _cauterizeBones; }
const std::unordered_set<int>& getCauterizeBoneSet() const { return _cauterizeBoneSet; }
void setCauterizeBoneSet(const std::unordered_set<int>& boneSet) { _cauterizeBoneSet = boneSet; }
int getBlendshapeCoefficientsNum() const { return _blendshapeCoefficients.size(); }
float getBlendshapeCoefficient(int index) const {
return ((index < 0) && (index >= _blendshapeCoefficients.size())) ? 0.0f : _blendshapeCoefficients.at(index);
@ -230,7 +225,7 @@ public:
const glm::vec3& getRegistrationPoint() const { return _registrationPoint; }
// returns 'true' if needs fullUpdate after geometry change
bool updateGeometry();
virtual bool updateGeometry();
void setCollisionMesh(model::MeshPointer mesh);
void setLoadingPriority(float priority) { _loadingPriority = priority; }
@ -241,6 +236,18 @@ public:
int getRenderInfoDrawCalls() const { return _renderInfoDrawCalls; }
bool getRenderInfoHasTransparent() const { return _renderInfoHasTransparent; }
class MeshState {
public:
QVector<glm::mat4> clusterMatrices;
gpu::BufferPointer clusterBuffer;
};
const MeshState& getMeshState(int index) { return _meshStates.at(index); }
uint32_t getGeometryCounter() const { return _deleteGeometryCounter; }
const QMap<render::ItemID, render::PayloadPointer>& getRenderItems() const { return _modelMeshRenderItems; }
public slots:
void loadURLFinished(bool success);
@ -297,18 +304,7 @@ protected:
bool _snappedToRegistrationPoint; /// are we currently snapped to a registration point
glm::vec3 _registrationPoint = glm::vec3(0.5f); /// the point in model space our center is snapped to
class MeshState {
public:
QVector<glm::mat4> clusterMatrices;
QVector<glm::mat4> cauterizedClusterMatrices;
gpu::BufferPointer clusterBuffer;
gpu::BufferPointer cauterizedClusterBuffer;
};
QVector<MeshState> _meshStates;
std::unordered_set<int> _cauterizeBoneSet;
bool _cauterizeBones;
virtual void initJointStates();
@ -341,7 +337,7 @@ protected:
protected:
void deleteGeometry();
virtual void deleteGeometry();
void initJointTransforms();
QVector<float> _blendshapeCoefficients;
@ -370,12 +366,11 @@ protected:
void recalculateMeshBoxes(bool pickAgainstTriangles = false);
void createRenderItemSet();
void createVisibleRenderItemSet();
void createCollisionRenderItemSet();
virtual void createVisibleRenderItemSet();
virtual void createCollisionRenderItemSet();
bool _isWireframe;
// debug rendering support
void renderDebugMeshBoxes(gpu::Batch& batch);
int _debugMeshBoxesID = GeometryCache::UNKNOWN_ID;
@ -389,6 +384,8 @@ protected:
QSet<std::shared_ptr<ModelMeshPartPayload>> _modelMeshRenderItemsSet;
QMap<render::ItemID, render::PayloadPointer> _modelMeshRenderItems;
render::ItemIDs _modelMeshRenderItemIDs;
bool _addedToScene { false }; // has been added to scene
bool _needsFixupInScene { true }; // needs to be removed/re-added to scene
bool _needsReload { true };

View file

@ -53,14 +53,15 @@ RenderDeferredTask::RenderDeferredTask(RenderFetchCullSortTask::Output items) {
ShapePlumberPointer shapePlumber = std::make_shared<ShapePlumber>();
initDeferredPipelines(*shapePlumber);
// Extract opaques / transparents / lights / overlays
const auto opaques = items[0];
const auto transparents = items[1];
const auto lights = items[2];
const auto overlayOpaques = items[3];
const auto overlayTransparents = items[4];
const auto background = items[5];
const auto spatialSelection = items[6];
// Extract opaques / transparents / lights / metas / overlays / background
const auto opaques = items[RenderFetchCullSortTask::OPAQUE_SHAPE];
const auto transparents = items[RenderFetchCullSortTask::TRANSPARENT_SHAPE];
const auto lights = items[RenderFetchCullSortTask::LIGHT];
const auto metas = items[RenderFetchCullSortTask::META];
const auto overlayOpaques = items[RenderFetchCullSortTask::OVERLAY_OPAQUE_SHAPE];
const auto overlayTransparents = items[RenderFetchCullSortTask::OVERLAY_TRANSPARENT_SHAPE];
const auto background = items[RenderFetchCullSortTask::BACKGROUND];
const auto spatialSelection = items[RenderFetchCullSortTask::SPATIAL_SELECTION];
// Prepare deferred, generate the shared Deferred Frame Transform
const auto deferredFrameTransform = addJob<GenerateDeferredFrameTransform>("DeferredFrameTransform");
@ -158,6 +159,11 @@ RenderDeferredTask::RenderDeferredTask(RenderFetchCullSortTask::Output items) {
// Debugging stages
{
// Bounds do not draw on stencil buffer, so they must come last
addJob<DrawBounds>("DrawMetaBounds", metas);
// Debugging Deferred buffer job
const auto debugFramebuffers = render::Varying(DebugDeferredBuffer::Inputs(deferredFramebuffer, linearDepthTarget, surfaceGeometryFramebuffer, ambientOcclusionFramebuffer));
addJob<DebugDeferredBuffer>("DebugDeferredBuffer", debugFramebuffers);

View file

@ -24,8 +24,6 @@
#include <gpu/StandardShaderLib.h>
#include <render/drawItemBounds_vert.h>
#include <render/drawItemBounds_frag.h>
#include "nop_frag.h"
using namespace render;
@ -36,13 +34,15 @@ RenderForwardTask::RenderForwardTask(RenderFetchCullSortTask::Output items) {
ShapePlumberPointer shapePlumber = std::make_shared<ShapePlumber>();
initForwardPipelines(*shapePlumber);
// Extract opaques / transparents / lights / overlays
const auto opaques = items[0];
const auto transparents = items[1];
const auto lights = items[2];
const auto overlayOpaques = items[3];
const auto overlayTransparents = items[4];
const auto background = items[5];
// Extract opaques / transparents / lights / metas / overlays / background
const auto opaques = items[RenderFetchCullSortTask::OPAQUE_SHAPE];
const auto transparents = items[RenderFetchCullSortTask::TRANSPARENT_SHAPE];
const auto lights = items[RenderFetchCullSortTask::LIGHT];
const auto metas = items[RenderFetchCullSortTask::META];
const auto overlayOpaques = items[RenderFetchCullSortTask::OVERLAY_OPAQUE_SHAPE];
const auto overlayTransparents = items[RenderFetchCullSortTask::OVERLAY_TRANSPARENT_SHAPE];
const auto background = items[RenderFetchCullSortTask::BACKGROUND];
const auto spatialSelection = items[RenderFetchCullSortTask::SPATIAL_SELECTION];
const auto framebuffer = addJob<PrepareFramebuffer>("PrepareFramebuffer");
@ -180,57 +180,4 @@ void DrawBackground::run(const SceneContextPointer& sceneContext, const RenderCo
args->_batch = nullptr;
}
const gpu::PipelinePointer DrawBounds::getPipeline() {
if (!_boundsPipeline) {
auto vs = gpu::Shader::createVertex(std::string(drawItemBounds_vert));
auto ps = gpu::Shader::createPixel(std::string(drawItemBounds_frag));
gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
gpu::Shader::BindingSet slotBindings;
gpu::Shader::makeProgram(*program, slotBindings);
_cornerLocation = program->getUniforms().findLocation("inBoundPos");
_scaleLocation = program->getUniforms().findLocation("inBoundDim");
auto state = std::make_shared<gpu::State>();
state->setDepthTest(true, false, gpu::LESS_EQUAL);
state->setBlendFunction(true,
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
gpu::State::DEST_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ZERO);
_boundsPipeline = gpu::Pipeline::create(program, state);
}
return _boundsPipeline;
}
void DrawBounds::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext,
const Inputs& items) {
RenderArgs* args = renderContext->args;
gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
args->_batch = &batch;
// Setup projection
glm::mat4 projMat;
Transform viewMat;
args->getViewFrustum().evalProjectionMatrix(projMat);
args->getViewFrustum().evalViewTransform(viewMat);
batch.setProjectionTransform(projMat);
batch.setViewTransform(viewMat);
batch.setModelTransform(Transform());
// Bind program
batch.setPipeline(getPipeline());
assert(_cornerLocation >= 0);
assert(_scaleLocation >= 0);
// Render bounds
for (const auto& item : items) {
batch._glUniform3fv(_cornerLocation, 1, (const float*)(&item.bound.getCorner()));
batch._glUniform3fv(_scaleLocation, 1, (const float*)(&item.bound.getScale()));
static const int NUM_VERTICES_PER_CUBE = 24;
batch.draw(gpu::LINES, NUM_VERTICES_PER_CUBE, 0);
}
});
}

View file

@ -68,25 +68,4 @@ public:
const Inputs& background);
};
class DrawBounds {
public:
class Config : public render::JobConfig {
public:
Config() : JobConfig(false) {}
};
using Inputs = render::ItemBounds;
using JobModel = render::Job::ModelI<DrawBounds, Inputs, Config>;
void configure(const Config& configuration) {}
void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext,
const Inputs& items);
private:
const gpu::PipelinePointer getPipeline();
gpu::PipelinePointer _boundsPipeline;
int _cornerLocation { -1 };
int _scaleLocation { -1 };
};
#endif // hifi_RenderForwardTask_h

View file

@ -34,6 +34,13 @@
#include "model_normal_map_frag.h"
#include "model_normal_specular_map_frag.h"
#include "model_specular_map_frag.h"
#include "forward_model_frag.h"
#include "forward_model_unlit_frag.h"
#include "forward_model_normal_map_frag.h"
#include "forward_model_normal_specular_map_frag.h"
#include "forward_model_specular_map_frag.h"
#include "model_lightmap_frag.h"
#include "model_lightmap_normal_map_frag.h"
#include "model_lightmap_normal_specular_map_frag.h"
@ -227,11 +234,11 @@ void initForwardPipelines(render::ShapePlumber& plumber) {
auto skinModelNormalMapVertex = gpu::Shader::createVertex(std::string(skin_model_normal_map_vert));
// Pixel shaders
auto modelPixel = gpu::Shader::createPixel(std::string(model_frag));
auto modelUnlitPixel = gpu::Shader::createPixel(std::string(model_unlit_frag));
auto modelNormalMapPixel = gpu::Shader::createPixel(std::string(model_normal_map_frag));
auto modelSpecularMapPixel = gpu::Shader::createPixel(std::string(model_specular_map_frag));
auto modelNormalSpecularMapPixel = gpu::Shader::createPixel(std::string(model_normal_specular_map_frag));
auto modelPixel = gpu::Shader::createPixel(std::string(forward_model_frag));
auto modelUnlitPixel = gpu::Shader::createPixel(std::string(forward_model_unlit_frag));
auto modelNormalMapPixel = gpu::Shader::createPixel(std::string(forward_model_normal_map_frag));
auto modelSpecularMapPixel = gpu::Shader::createPixel(std::string(forward_model_specular_map_frag));
auto modelNormalSpecularMapPixel = gpu::Shader::createPixel(std::string(forward_model_normal_specular_map_frag));
using Key = render::ShapeKey;
auto addPipeline = std::bind(&addPlumberPipeline, std::ref(plumber), _1, _2, _3);

View file

@ -0,0 +1,59 @@
<@include gpu/Config.slh@>
<$VERSION_HEADER$>
// Generated on <$_SCRIBE_DATE$>
// model.frag
// fragment shader
//
// Created by Andrzej Kapolka on 10/14/13.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
<@include DeferredBufferWrite.slh@>
<@include model/Material.slh@>
<@include MaterialTextures.slh@>
<$declareMaterialTextures(ALBEDO, ROUGHNESS, _SCRIBE_NULL, _SCRIBE_NULL, EMISSIVE, OCCLUSION)$>
in vec4 _position;
in vec3 _normal;
in vec3 _color;
in vec2 _texCoord0;
in vec2 _texCoord1;
void main(void) {
Material mat = getMaterial();
int matKey = getMaterialKey(mat);
<$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, _SCRIBE_NULL, emissiveTex)$>
<$fetchMaterialTexturesCoord1(matKey, _texCoord1, occlusionTex)$>
float opacity = 1.0;
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>;
<$discardTransparent(opacity)$>;
vec3 albedo = getMaterialAlbedo(mat);
<$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>;
albedo *= _color;
float roughness = getMaterialRoughness(mat);
<$evalMaterialRoughness(roughnessTex, roughness, matKey, roughness)$>;
vec3 emissive = getMaterialEmissive(mat);
<$evalMaterialEmissive(emissiveTex, emissive, matKey, emissive)$>;
float scattering = getMaterialScattering(mat);
packDeferredFragment(
normalize(_normal.xyz),
opacity,
albedo,
roughness,
getMaterialMetallic(mat),
emissive,
occlusionTex,
scattering);
}

View file

@ -0,0 +1,64 @@
<@include gpu/Config.slh@>
<$VERSION_HEADER$>
// Generated on <$_SCRIBE_DATE$>
//
// model_normal_map.frag
// fragment shader
//
// Created by Andrzej Kapolka on 10/29/13.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
<@include DeferredBufferWrite.slh@>
<@include model/Material.slh@>
<@include MaterialTextures.slh@>
<$declareMaterialTextures(ALBEDO, ROUGHNESS, NORMAL, _SCRIBE_NULL, EMISSIVE, OCCLUSION, SCATTERING)$>
in vec4 _position;
in vec2 _texCoord0;
in vec2 _texCoord1;
in vec3 _normal;
in vec3 _tangent;
in vec3 _color;
void main(void) {
Material mat = getMaterial();
int matKey = getMaterialKey(mat);
<$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex, roughnessTex, normalTex, _SCRIBE_NULL, emissiveTex, scatteringTex)$>
<$fetchMaterialTexturesCoord1(matKey, _texCoord1, occlusionTex)$>
float opacity = 1.0;
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>;
<$discardTransparent(opacity)$>;
vec3 albedo = getMaterialAlbedo(mat);
<$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>;
albedo *= _color;
float roughness = getMaterialRoughness(mat);
<$evalMaterialRoughness(roughnessTex, roughness, matKey, roughness)$>;
vec3 emissive = getMaterialEmissive(mat);
<$evalMaterialEmissive(emissiveTex, emissive, matKey, emissive)$>;
vec3 viewNormal;
<$tangentToViewSpace(normalTex, _normal, _tangent, viewNormal)$>
float scattering = getMaterialScattering(mat);
<$evalMaterialScattering(scatteringTex, scattering, matKey, scattering)$>;
packDeferredFragment(
viewNormal,
opacity,
albedo,
roughness,
getMaterialMetallic(mat),
emissive,
occlusionTex,
scattering);
}

View file

@ -0,0 +1,66 @@
<@include gpu/Config.slh@>
<$VERSION_HEADER$>
// Generated on <$_SCRIBE_DATE$>
//
// model_normal_specular_map.frag
// fragment shader
//
// Created by Andrzej Kapolka on 5/6/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
<@include DeferredBufferWrite.slh@>
<@include model/Material.slh@>
<@include MaterialTextures.slh@>
<$declareMaterialTextures(ALBEDO, ROUGHNESS, NORMAL, METALLIC, EMISSIVE, OCCLUSION)$>
in vec4 _position;
in vec2 _texCoord0;
in vec2 _texCoord1;
in vec3 _normal;
in vec3 _tangent;
in vec3 _color;
void main(void) {
Material mat = getMaterial();
int matKey = getMaterialKey(mat);
<$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex, roughnessTex, normalTex, metallicTex, emissiveTex)$>
<$fetchMaterialTexturesCoord1(matKey, _texCoord1, occlusionTex)$>
float opacity = 1.0;
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)&>;
<$discardTransparent(opacity)$>;
vec3 albedo = getMaterialAlbedo(mat);
<$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>;
albedo *= _color;
float roughness = getMaterialRoughness(mat);
<$evalMaterialRoughness(roughnessTex, roughness, matKey, roughness)$>;
vec3 emissive = getMaterialEmissive(mat);
<$evalMaterialEmissive(emissiveTex, emissive, matKey, emissive)$>;
vec3 viewNormal;
<$tangentToViewSpace(normalTex, _normal, _tangent, viewNormal)$>
float metallic = getMaterialMetallic(mat);
<$evalMaterialMetallic(metallicTex, metallic, matKey, metallic)$>;
float scattering = getMaterialScattering(mat);
packDeferredFragment(
normalize(viewNormal.xyz),
opacity,
albedo,
roughness,
metallic,
emissive,
occlusionTex,
scattering);
}

View file

@ -0,0 +1,63 @@
<@include gpu/Config.slh@>
<$VERSION_HEADER$>
// Generated on <$_SCRIBE_DATE$>
//
// model_specular_map.frag
// fragment shader
//
// Created by Andrzej Kapolka on 5/6/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
<@include DeferredBufferWrite.slh@>
<@include model/Material.slh@>
<@include MaterialTextures.slh@>
<$declareMaterialTextures(ALBEDO, ROUGHNESS, _SCRIBE_NULL, METALLIC, EMISSIVE, OCCLUSION)$>
in vec4 _position;
in vec2 _texCoord0;
in vec2 _texCoord1;
in vec3 _normal;
in vec3 _color;
void main(void) {
Material mat = getMaterial();
int matKey = getMaterialKey(mat);
<$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, metallicTex, emissiveTex)$>
<$fetchMaterialTexturesCoord1(matKey, _texCoord1, occlusionTex)$>
float opacity = 1.0;
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>;
<$discardTransparent(opacity)$>;
vec3 albedo = getMaterialAlbedo(mat);
<$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>;
albedo *= _color;
float roughness = getMaterialRoughness(mat);
<$evalMaterialRoughness(roughnessTex, roughness, matKey, roughness)$>;
vec3 emissive = getMaterialEmissive(mat);
<$evalMaterialEmissive(emissiveTex, emissive, matKey, emissive)$>;
float metallic = getMaterialMetallic(mat);
<$evalMaterialMetallic(metallicTex, metallic, matKey, metallic)$>;
float scattering = getMaterialScattering(mat);
packDeferredFragment(
normalize(_normal),
opacity,
albedo,
roughness,
metallic,
emissive,
occlusionTex,
scattering);
}

View file

@ -0,0 +1,45 @@
<@include gpu/Config.slh@>
<$VERSION_HEADER$>
// Generated on <$_SCRIBE_DATE$>
//
// material_opaque_unlit.frag
// fragment shader
//
// Created by Sam Gateau on 5/5/2016.
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
<@include DeferredBufferWrite.slh@>
<@include LightingModel.slh@>
<@include model/Material.slh@>
<@include MaterialTextures.slh@>
<$declareMaterialTextures(ALBEDO)$>
in vec2 _texCoord0;
in vec3 _normal;
in vec3 _color;
in float _alpha;
void main(void) {
Material mat = getMaterial();
int matKey = getMaterialKey(mat);
<$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex)$>
float opacity = 1.0;
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>;
<$discardTransparent(opacity)$>;
vec3 albedo = getMaterialAlbedo(mat);
<$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>;
albedo *= _color;
packDeferredFragmentUnlit(
normalize(_normal),
opacity,
albedo * isUnlitEnabled());
}

View file

@ -170,7 +170,6 @@ namespace render {
for (size_t i = 0; i < NUM_FILTERS; i++) {
if (_filters[i].test(itemKey)) {
outItems[i].template edit<ItemBounds>().emplace_back(itemBound);
break;
}
}
}

View file

@ -19,6 +19,10 @@
#include <ViewFrustum.h>
#include <gpu/Context.h>
#include <drawItemBounds_vert.h>
#include <drawItemBounds_frag.h>
using namespace render;
void render::renderItems(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems, int maxDrawnItems) {
@ -134,3 +138,59 @@ void DrawLight::run(const SceneContextPointer& sceneContext, const RenderContext
auto config = std::static_pointer_cast<Config>(renderContext->jobConfig);
config->setNumDrawn((int)inLights.size());
}
const gpu::PipelinePointer DrawBounds::getPipeline() {
if (!_boundsPipeline) {
auto vs = gpu::Shader::createVertex(std::string(drawItemBounds_vert));
auto ps = gpu::Shader::createPixel(std::string(drawItemBounds_frag));
gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
gpu::Shader::BindingSet slotBindings;
gpu::Shader::makeProgram(*program, slotBindings);
_cornerLocation = program->getUniforms().findLocation("inBoundPos");
_scaleLocation = program->getUniforms().findLocation("inBoundDim");
auto state = std::make_shared<gpu::State>();
state->setDepthTest(true, false, gpu::LESS_EQUAL);
state->setBlendFunction(true,
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
gpu::State::DEST_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ZERO);
_boundsPipeline = gpu::Pipeline::create(program, state);
}
return _boundsPipeline;
}
void DrawBounds::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext,
const Inputs& items) {
RenderArgs* args = renderContext->args;
gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
args->_batch = &batch;
// Setup projection
glm::mat4 projMat;
Transform viewMat;
args->getViewFrustum().evalProjectionMatrix(projMat);
args->getViewFrustum().evalViewTransform(viewMat);
batch.setProjectionTransform(projMat);
batch.setViewTransform(viewMat);
batch.setModelTransform(Transform());
// Bind program
batch.setPipeline(getPipeline());
assert(_cornerLocation >= 0);
assert(_scaleLocation >= 0);
// Render bounds
for (const auto& item : items) {
batch._glUniform3fv(_cornerLocation, 1, (const float*)(&item.bound.getCorner()));
batch._glUniform3fv(_scaleLocation, 1, (const float*)(&item.bound.getScale()));
static const int NUM_VERTICES_PER_CUBE = 24;
batch.draw(gpu::LINES, NUM_VERTICES_PER_CUBE, 0);
}
});
}

View file

@ -50,6 +50,27 @@ protected:
int _maxDrawn; // initialized by Config
};
class DrawBounds {
public:
class Config : public render::JobConfig {
public:
Config() : JobConfig(false) {}
};
using Inputs = render::ItemBounds;
using JobModel = render::Job::ModelI<DrawBounds, Inputs, Config>;
void configure(const Config& configuration) {}
void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext,
const Inputs& items);
private:
const gpu::PipelinePointer getPipeline();
gpu::PipelinePointer _boundsPipeline;
int _cornerLocation { -1 };
int _scaleLocation { -1 };
};
}
#endif // hifi_render_DrawTask_h

View file

@ -38,6 +38,7 @@ public:
enum FlagBit {
TYPE_SHAPE = 0, // Item is a Shape
TYPE_LIGHT, // Item is a Light
TYPE_META, // Item is a Meta: meanning it s used to represent a higher level object, potentially represented by other render items
TRANSLUCENT, // Transparent and not opaque, for some odd reason TRANSPARENCY doesn't work...
VIEW_SPACE, // Transformed in view space, and not in world space
DYNAMIC, // Dynamic and bound will change unlike static item
@ -72,6 +73,7 @@ public:
Builder& withTypeShape() { _flags.set(TYPE_SHAPE); return (*this); }
Builder& withTypeLight() { _flags.set(TYPE_LIGHT); return (*this); }
Builder& withTypeMeta() { _flags.set(TYPE_META); return (*this); }
Builder& withTransparent() { _flags.set(TRANSLUCENT); return (*this); }
Builder& withViewSpace() { _flags.set(VIEW_SPACE); return (*this); }
Builder& withDynamic() { _flags.set(DYNAMIC); return (*this); }
@ -91,6 +93,7 @@ public:
bool isShape() const { return _flags[TYPE_SHAPE]; }
bool isLight() const { return _flags[TYPE_LIGHT]; }
bool isMeta() const { return _flags[TYPE_META]; }
bool isOpaque() const { return !_flags[TRANSLUCENT]; }
bool isTransparent() const { return _flags[TRANSLUCENT]; }
@ -150,6 +153,7 @@ public:
Builder& withTypeShape() { _value.set(ItemKey::TYPE_SHAPE); _mask.set(ItemKey::TYPE_SHAPE); return (*this); }
Builder& withTypeLight() { _value.set(ItemKey::TYPE_LIGHT); _mask.set(ItemKey::TYPE_LIGHT); return (*this); }
Builder& withTypeMeta() { _value.set(ItemKey::TYPE_META); _mask.set(ItemKey::TYPE_META); return (*this); }
Builder& withOpaque() { _value.reset(ItemKey::TRANSLUCENT); _mask.set(ItemKey::TRANSLUCENT); return (*this); }
Builder& withTransparent() { _value.set(ItemKey::TRANSLUCENT); _mask.set(ItemKey::TRANSLUCENT); return (*this); }
@ -179,6 +183,7 @@ public:
static Builder opaqueShape() { return Builder().withTypeShape().withOpaque().withWorldSpace(); }
static Builder transparentShape() { return Builder().withTypeShape().withTransparent().withWorldSpace(); }
static Builder light() { return Builder().withTypeLight(); }
static Builder meta() { return Builder().withTypeMeta(); }
static Builder background() { return Builder().withViewSpace().withLayered(); }
static Builder opaqueShapeLayered() { return Builder().withTypeShape().withOpaque().withWorldSpace().withLayered(); }
static Builder transparentShapeLayered() { return Builder().withTypeShape().withTransparent().withWorldSpace().withLayered(); }
@ -210,6 +215,25 @@ inline QDebug operator<<(QDebug debug, const ItemFilter& me) {
using ItemID = uint32_t;
using ItemCell = int32_t;
// A few typedefs for standard containers of ItemIDs
using ItemIDs = std::vector<ItemID>;
using ItemIDSet = std::set<ItemID>;
// Handy type to just pass the ID and the bound of an item
class ItemBound {
public:
ItemBound(ItemID id) : id(id) { }
ItemBound(ItemID id, const AABox& bound) : id(id), bound(bound) { }
ItemID id;
AABox bound;
};
// many Item Bounds in a vector
using ItemBounds = std::vector<ItemBound>;
// Item is the proxy to a bounded "object" in the scene
// An item is described by its Key
class Item {
public:
typedef std::vector<Item> Vector;
@ -295,6 +319,8 @@ public:
virtual const ShapeKey getShapeKey() const = 0;
virtual uint32_t fetchMetaSubItems(ItemIDs& subItems) const = 0;
~PayloadInterface() {}
// Status interface is local to the base class
@ -313,6 +339,9 @@ public:
Item() {}
~Item() {}
// Item exists if it has a valid payload
bool exist() const { return (bool)(_payload); }
// Main scene / item managment interface reset/update/kill
void resetPayload(const PayloadPointer& payload);
void resetCell(ItemCell cell = INVALID_CELL, bool _small = false) { _cell = cell; _key.setSmaller(_small); }
@ -339,6 +368,9 @@ public:
// Shape Type Interface
const ShapeKey getShapeKey() const { return _payload->getShapeKey(); }
// Meta Type Interface
uint32_t fetchMetaSubItems(ItemIDs& subItems) const { return _payload->fetchMetaSubItems(subItems); }
// Access the status
const StatusPointer& getStatus() const { return _payload->getStatus(); }
@ -370,10 +402,7 @@ inline QDebug operator<<(QDebug debug, const Item& item) {
return debug;
}
// THe Payload class is the real Payload to be used
// THis allow anything to be turned into a Payload as long as the required interface functions are available
// When creating a new kind of payload from a new "stuff" class then you need to create specialized version for "stuff"
// of the Payload interface
// Item shared interface supported by the payload
template <class T> const ItemKey payloadGetKey(const std::shared_ptr<T>& payloadData) { return ItemKey(); }
template <class T> const Item::Bound payloadGetBound(const std::shared_ptr<T>& payloadData) { return Item::Bound(); }
template <class T> int payloadGetLayer(const std::shared_ptr<T>& payloadData) { return 0; }
@ -385,6 +414,14 @@ template <class T> void payloadRender(const std::shared_ptr<T>& payloadData, Ren
// implying that the shape will setup its own pipeline without the use of the ShapeKey.
template <class T> const ShapeKey shapeGetShapeKey(const std::shared_ptr<T>& payloadData) { return ShapeKey::Builder::ownPipeline(); }
// Meta Type Interface
// Meta items act as the grouping object for several sub items (typically shapes).
template <class T> uint32_t metaFetchMetaSubItems(const std::shared_ptr<T>& payloadData, ItemIDs& subItems) { return 0; }
// THe Payload class is the real Payload to be used
// THis allow anything to be turned into a Payload as long as the required interface functions are available
// When creating a new kind of payload from a new "stuff" class then you need to create specialized version for "stuff"
// of the Payload interface
template <class T> class Payload : public Item::PayloadInterface {
public:
typedef std::shared_ptr<T> DataPointer;
@ -403,6 +440,9 @@ public:
// Shape Type interface
virtual const ShapeKey getShapeKey() const override { return shapeGetShapeKey<T>(_data); }
// Meta Type Interface
virtual uint32_t fetchMetaSubItems(ItemIDs& subItems) const override { return metaFetchMetaSubItems<T>(_data, subItems); }
protected:
DataPointer _data;
@ -450,22 +490,6 @@ template <> const Item::Bound payloadGetBound(const FooPointer& foo) {
typedef Item::PayloadPointer PayloadPointer;
typedef std::vector< PayloadPointer > Payloads;
// A few typedefs for standard containers of ItemIDs
using ItemIDs = std::vector<ItemID>;
using ItemIDSet = std::set<ItemID>;
// Handy type to just pass the ID and the bound of an item
class ItemBound {
public:
ItemBound(ItemID id) : id(id) { }
ItemBound(ItemID id, const AABox& bound) : id(id), bound(bound) { }
ItemID id;
AABox bound;
};
// many Item Bounds in a vector
using ItemBounds = std::vector<ItemBound>;
// A map of items by ShapeKey to optimize rendering pipeline assignments
using ShapeBounds = std::unordered_map<ShapeKey, ItemBounds, ShapeKey::Hash, ShapeKey::KeyEqual>;

View file

@ -29,33 +29,41 @@ RenderFetchCullSortTask::RenderFetchCullSortTask(CullFunctor cullFunctor) {
const auto nonspatialSelection = addJob<FetchNonspatialItems>("FetchOverlaySelection");
// Multi filter visible items into different buckets
const int NUM_FILTERS = 3;
const int NUM_SPATIAL_FILTERS = 4;
const int NUM_NON_SPATIAL_FILTERS = 3;
const int OPAQUE_SHAPE_BUCKET = 0;
const int TRANSPARENT_SHAPE_BUCKET = 1;
const int LIGHT_BUCKET = 2;
const int META_BUCKET = 3;
const int BACKGROUND_BUCKET = 2;
MultiFilterItem<NUM_FILTERS>::ItemFilterArray spatialFilters = { {
MultiFilterItem<NUM_SPATIAL_FILTERS>::ItemFilterArray spatialFilters = { {
ItemFilter::Builder::opaqueShape(),
ItemFilter::Builder::transparentShape(),
ItemFilter::Builder::light()
ItemFilter::Builder::light(),
ItemFilter::Builder::meta()
} };
MultiFilterItem<NUM_FILTERS>::ItemFilterArray nonspatialFilters = { {
MultiFilterItem<NUM_NON_SPATIAL_FILTERS>::ItemFilterArray nonspatialFilters = { {
ItemFilter::Builder::opaqueShape(),
ItemFilter::Builder::transparentShape(),
ItemFilter::Builder::background()
} };
const auto filteredSpatialBuckets = addJob<MultiFilterItem<NUM_FILTERS>>("FilterSceneSelection", culledSpatialSelection, spatialFilters).get<MultiFilterItem<NUM_FILTERS>::ItemBoundsArray>();
const auto filteredNonspatialBuckets = addJob<MultiFilterItem<NUM_FILTERS>>("FilterOverlaySelection", nonspatialSelection, nonspatialFilters).get<MultiFilterItem<NUM_FILTERS>::ItemBoundsArray>();
const auto filteredSpatialBuckets =
addJob<MultiFilterItem<NUM_SPATIAL_FILTERS>>("FilterSceneSelection", culledSpatialSelection, spatialFilters)
.get<MultiFilterItem<NUM_SPATIAL_FILTERS>::ItemBoundsArray>();
const auto filteredNonspatialBuckets =
addJob<MultiFilterItem<NUM_NON_SPATIAL_FILTERS>>("FilterOverlaySelection", nonspatialSelection, nonspatialFilters)
.get<MultiFilterItem<NUM_NON_SPATIAL_FILTERS>::ItemBoundsArray>();
// Extract opaques / transparents / lights / overlays
const auto opaques = addJob<DepthSortItems>("DepthSortOpaque", filteredSpatialBuckets[OPAQUE_SHAPE_BUCKET]);
const auto transparents = addJob<DepthSortItems>("DepthSortTransparent", filteredSpatialBuckets[TRANSPARENT_SHAPE_BUCKET], DepthSortItems(false));
const auto lights = filteredSpatialBuckets[LIGHT_BUCKET];
const auto metas = filteredSpatialBuckets[META_BUCKET];
const auto overlayOpaques = addJob<DepthSortItems>("DepthSortOverlayOpaque", filteredNonspatialBuckets[OPAQUE_SHAPE_BUCKET]);
const auto overlayTransparents = addJob<DepthSortItems>("DepthSortOverlayTransparent", filteredNonspatialBuckets[TRANSPARENT_SHAPE_BUCKET], DepthSortItems(false));
const auto background = filteredNonspatialBuckets[BACKGROUND_BUCKET];
setOutput(Output{{
opaques, transparents, lights, overlayOpaques, overlayTransparents, background, spatialSelection }});
opaques, transparents, lights, metas, overlayOpaques, overlayTransparents, background, spatialSelection }});
}

View file

@ -19,7 +19,21 @@
class RenderFetchCullSortTask : public render::Task {
public:
using Output = std::array<render::Varying, 7>;
enum Buckets {
OPAQUE_SHAPE = 0,
TRANSPARENT_SHAPE,
LIGHT,
META,
OVERLAY_OPAQUE_SHAPE,
OVERLAY_TRANSPARENT_SHAPE,
BACKGROUND,
SPATIAL_SELECTION,
NUM_BUCKETS
};
using Output = std::array<render::Varying, Buckets::NUM_BUCKETS>;
using JobModel = ModelO<RenderFetchCullSortTask>;
RenderFetchCullSortTask(render::CullFunctor cullFunctor);

View file

@ -58,7 +58,7 @@ ItemID Scene::allocateID() {
return _IDAllocator.fetch_add(1);
}
bool Scene::isAllocatedID(const ItemID& id) {
bool Scene::isAllocatedID(const ItemID& id) const {
return Item::isValidID(id) && (id < _numAllocatedItems.load());
}

View file

@ -61,7 +61,7 @@ public:
ItemID allocateID();
// Check that the ID is valid and allocated for this scene, this a threadsafe call
bool isAllocatedID(const ItemID& id);
bool isAllocatedID(const ItemID& id) const;
// THis is the total number of allocated items, this a threadsafe call
size_t getNumItems() const { return _numAllocatedItems.load(); }
@ -78,6 +78,9 @@ public:
// WARNING, There is No check on the validity of the ID, so this could return a bad Item
const Item& getItem(const ItemID& id) const { return _items[id]; }
// Same as getItem, checking if the id is valid
const Item getItemSafe(const ItemID& id) const { if (isAllocatedID(id)) { return _items[id]; } else { return Item(); } }
// Access the spatialized items
const ItemSpatialTree& getSpatialTree() const { return _masterSpatialTree; }

View file

@ -32,7 +32,7 @@ void injectorFromScriptValue(const QScriptValue& object, ScriptAudioInjector*& o
ScriptAudioInjector::ScriptAudioInjector(AudioInjector* injector) :
_injector(injector)
{
QObject::connect(injector, &AudioInjector::finished, this, &ScriptAudioInjector::finished);
}
ScriptAudioInjector::~ScriptAudioInjector() {

View file

@ -135,9 +135,17 @@ signals:
/**jsdoc
* Notifies scripts of the username and machine fingerprint associated with a UUID.
* Username and machineFingerprint will be their default constructor output if the requesting user isn't an admin.
* @function Users.usernameFromIDReply
*/
void usernameFromIDReply(const QString& nodeID, const QString& username, const QString& machineFingerprint);
void usernameFromIDReply(const QString& nodeID, const QString& username, const QString& machineFingerprint, bool isAdmin);
/**jsdoc
* Notifies scripts that a user has disconnected from the domain
* @function Users.avatar.avatarDisconnected
* @param {nodeID} NodeID The session ID of the avatar that has disconnected
*/
void avatarDisconnected(const QUuid& nodeID);
private:
bool getRequestsDomainListData();

View file

@ -61,6 +61,13 @@ float Interpolate::interpolate3Points(float y1, float y2, float y3, float u) {
}
}
float Interpolate::simpleNonLinearBlend(float fraction) {
// uses arctan() to map a linear distribution in domain [0,1] to a non-linear blend (slow out, slow in) in range [0,1]
const float WIDTH = 20.0f;
const float INV_ARCTAN_WIDTH = 0.339875327433f; // 1 / (2 * atan(WIDTH/2))
return 0.5f + atanf(WIDTH * (fraction - 0.5f)) * INV_ARCTAN_WIDTH;
}
float Interpolate::calculateFadeRatio(quint64 start) {
const float FADE_TIME = 1.0f;
float t = 2.0f * std::min(((float)(usecTimestampNow() - start)) / ((float)(FADE_TIME * USECS_PER_SECOND)), 1.0f);
@ -69,4 +76,4 @@ float Interpolate::calculateFadeRatio(quint64 start) {
// The easing function isn't exactly 1 at t = 2, so we need to scale the whole function up slightly
const float EASING_SCALE = 1.001f;
return std::min(EASING_SCALE * fadeRatio, 1.0f);
}
}

View file

@ -25,6 +25,10 @@ public:
// pass through all three y values. Return value lies wholly within the range of y values passed in.
static float interpolate3Points(float y1, float y2, float y3, float u);
// returns smooth in and out blend between 0 and 1
// DANGER: assumes fraction is properly inside range [0, 1]
static float simpleNonLinearBlend(float fraction);
static float calculateFadeRatio(quint64 start);
};

View file

@ -18,6 +18,8 @@
#include <cctype>
#include <time.h>
#include <mutex>
#include <thread>
#include <set>
#include <glm/glm.hpp>
@ -1022,4 +1024,54 @@ bool getProcessorInfo(ProcessorInfo& info) {
#endif
return false;
}
}
const QString& getInterfaceSharedMemoryName() {
static const QString applicationName = "High Fidelity Interface - " + qgetenv("USERNAME");
return applicationName;
}
const std::vector<uint8_t>& getAvailableCores() {
static std::vector<uint8_t> availableCores;
#ifdef Q_OS_WIN
static std::once_flag once;
std::call_once(once, [&] {
DWORD_PTR defaultProcessAffinity = 0, defaultSystemAffinity = 0;
HANDLE process = GetCurrentProcess();
GetProcessAffinityMask(process, &defaultProcessAffinity, &defaultSystemAffinity);
for (uint64_t i = 0; i < sizeof(DWORD_PTR) * BITS_IN_BYTE; ++i) {
DWORD_PTR coreMask = 1;
coreMask <<= i;
if (0 != (defaultSystemAffinity & coreMask)) {
availableCores.push_back(i);
}
}
});
#endif
return availableCores;
}
void setMaxCores(uint8_t maxCores) {
#ifdef Q_OS_WIN
HANDLE process = GetCurrentProcess();
auto availableCores = getAvailableCores();
if (availableCores.size() <= maxCores) {
DWORD_PTR currentProcessAffinity = 0, currentSystemAffinity = 0;
GetProcessAffinityMask(process, &currentProcessAffinity, &currentSystemAffinity);
SetProcessAffinityMask(GetCurrentProcess(), currentSystemAffinity);
return;
}
DWORD_PTR newProcessAffinity = 0;
while (maxCores) {
int index = randIntInRange(0, (int)availableCores.size() - 1);
DWORD_PTR coreMask = 1;
coreMask <<= availableCores[index];
newProcessAffinity |= coreMask;
availableCores.erase(availableCores.begin() + index);
maxCores--;
}
SetProcessAffinityMask(process, newProcessAffinity);
#endif
}

View file

@ -231,4 +231,8 @@ struct ProcessorInfo {
bool getProcessorInfo(ProcessorInfo& info);
const QString& getInterfaceSharedMemoryName();
void setMaxCores(uint8_t maxCores);
#endif // hifi_SharedUtil_h

View file

@ -0,0 +1,19 @@
var WAVE = 'http://cdn.rawgit.com/ambisonictoolkit/atk-sounds/aa31005c/stereo/Aurora_Surgit-Lux_Aeterna.wav';
var uuid = Entities.addEntity({
type: "Shape",
shape: "Icosahedron",
dimensions: Vec3.HALF,
script: Script.resolvePath('../../tutorials/entity_scripts/ambientSound.js'),
position: Vec3.sum(Vec3.multiply(5, Quat.getFront(MyAvatar.orientation)), MyAvatar.position),
userData: JSON.stringify({
soundURL: WAVE,
maxVolume: 0.1,
range: 25,
disabled: true,
grabbableKey: { wantsTrigger: true },
}),
lifetime: 600,
});
Script.scriptEnding.connect(function() {
Entities.deleteEntity(uuid);
});

View file

@ -13,7 +13,7 @@ var qml = Script.resolvePath('deferredLighting.qml');
var window = new OverlayWindow({
title: 'Lighting',
source: qml,
width: 400, height:220,
width: 400, height:280,
});
window.setPosition(Window.innerWidth - 420, 50);
window.closed.connect(function() { Script.stop(); });

View file

@ -74,7 +74,7 @@ Column {
Column {
spacing: 10
Repeater {
model: [ "Tone Mapping exposure:ToneMapping:exposure:5.0:-5.0"
model: [ "Tone Mapping Exposure:ToneMapping:exposure:5.0:-5.0"
]
ConfigSlider {
label: qsTr(modelData.split(":")[0])
@ -88,7 +88,7 @@ Column {
Row {
Label {
text: "Debug Framebuffer"
text: "Tone Mapping Curve"
anchors.left: root.left
}
@ -109,6 +109,7 @@ Column {
}
Row {
id: framebuffer
spacing: 10
Label {
text: "Debug Framebuffer"
@ -156,5 +157,14 @@ Column {
onCurrentIndexChanged: { framebuffer.setDebugMode(currentIndex) }
}
}
Row {
id: metas
CheckBox {
text: "Draw Meta Bounds"
checked: Render.getConfig("DrawMetaBounds")["enabled"]
onCheckedChanged: { Render.getConfig("DrawMetaBounds")["enabled"] = checked }
}
}
}

View file

@ -45,8 +45,6 @@ var DEFAULT_GRABBABLE_DATA = {
var ACTION_TTL = 10; // seconds
var enabled = true;
function getTag() {
return "grab-" + MyAvatar.sessionUUID;
}
@ -321,7 +319,7 @@ Grabber.prototype.computeNewGrabPlane = function() {
}
Grabber.prototype.pressEvent = function(event) {
if (!enabled) {
if (isInEditMode()) {
return;
}
@ -633,31 +631,10 @@ function keyReleaseEvent(event) {
grabber.keyReleaseEvent(event);
}
function editEvent(channel, message, sender, localOnly) {
if (channel != "edit-events") {
return;
}
if (sender != MyAvatar.sessionUUID) {
return;
}
if (!localOnly) {
return;
}
try {
var data = JSON.parse(message);
if ("enabled" in data) {
enabled = !data["enabled"];
}
} catch(e) {
}
}
Controller.mousePressEvent.connect(pressEvent);
Controller.mouseMoveEvent.connect(moveEvent);
Controller.mouseReleaseEvent.connect(releaseEvent);
Controller.keyPressEvent.connect(keyPressEvent);
Controller.keyReleaseEvent.connect(keyReleaseEvent);
Messages.subscribe("edit-events");
Messages.messageReceived.connect(editEvent);
}()); // END LOCAL_SCOPE

View file

@ -493,11 +493,9 @@ function removeMyAvatarFromCollidesWith(origCollidesWith) {
// If another script is managing the reticle (as is done by HandControllerPointer), we should not be setting it here,
// and we should not be showing lasers when someone else is using the Reticle to indicate a 2D minor mode.
var EXTERNALLY_MANAGED_2D_MINOR_MODE = true;
var EDIT_SETTING = "io.highfidelity.isEditting";
function isEditing() {
var actualSettingValue = Settings.getValue(EDIT_SETTING) === "false" ? false : !!Settings.getValue(EDIT_SETTING);
return EXTERNALLY_MANAGED_2D_MINOR_MODE && actualSettingValue;
return EXTERNALLY_MANAGED_2D_MINOR_MODE && isInEditMode();
}
function isIn2DMode() {
@ -849,6 +847,9 @@ function MyController(hand) {
};
this.setState = function(newState, reason) {
if (isInEditMode() && newState !== STATE_OFF && newState !== STATE_SEARCHING) {
return;
}
setGrabCommunications((newState === STATE_DISTANCE_HOLDING) || (newState === STATE_NEAR_GRABBING));
if (WANT_DEBUG || WANT_DEBUG_STATE) {
var oldStateName = stateToName(this.state);

View file

@ -429,7 +429,6 @@ var toolBar = (function () {
});
});
that.setActive(false);
}
@ -446,6 +445,7 @@ var toolBar = (function () {
};
that.setActive = function (active) {
Settings.setValue(EDIT_SETTING, active);
if (active === isActive) {
return;
}
@ -457,7 +457,6 @@ var toolBar = (function () {
enabled: active
}));
isActive = active;
Settings.setValue(EDIT_SETTING, active);
if (!isActive) {
entityListTool.setVisible(false);
gridTool.setVisible(false);
@ -1625,7 +1624,7 @@ var PropertiesTool = function (opts) {
Camera.cameraEntity = selectionManager.selections[0];
}
} else if (data.action === "rescaleDimensions") {
var multiplier = data.percentage / 100;
var multiplier = data.percentage / 100.0;
if (selectionManager.hasSelection()) {
selectionManager.saveProperties();
for (i = 0; i < selectionManager.selections.length; i++) {

View file

@ -593,7 +593,7 @@ hr {
.dropdown dl {
font-family: FiraSans-SemiBold;
font-size: 15px;
width: 172px;
width: 292px;
height: 28px;
padding: 0 28px 0 12px;
color: #afafaf;
@ -645,7 +645,7 @@ hr {
.dropdown li {
list-style-type: none;
padding: 3px 0 1px 12px;
width: 200px;
width: 320px;
height: auto;
font-family: FiraSans-SemiBold;
font-size: 15px;

View file

@ -340,13 +340,13 @@
<div class="model-group model-section zone-section property dropdown">
<label>Collision shape type</label>
<select name="SelectShapeType" id="property-shape-type">
<option value="none">None</option>
<option value="none">No Collision</option>
<option value="box">Box</option>
<option value="sphere">Sphere</option>
<option value="compound">Compound</option>
<option value="simple-hull">One Hull</option>
<option value="simple-compound">Hull Per Submesh</option>
<option value="static-mesh">Static Mesh</option>
<option value="simple-hull">Basic - Whole model</option>
<option value="simple-compound">Good - Sub-meshes</option>
<option value="static-mesh">Exact - All polygons (non-dynamic only)</option>
</select>
</div>
<div class="model-group model-section zone-section property url ">

View file

@ -1418,7 +1418,7 @@ function loaded() {
EventBridge.emitWebEvent(JSON.stringify({
type: "action",
action: "rescaleDimensions",
percentage: parseInt(elRescaleDimensionsPct.value),
percentage: parseFloat(elRescaleDimensionsPct.value),
}));
});
elReloadScriptsButton.addEventListener("click", function() {

View file

@ -148,7 +148,6 @@
if (!canWriteAssets) {
console.log("ERROR: Clara.io FBX: File download cancelled because no permissions to write to Asset Server");
EventBridge.emitWebEvent(WARN_USER_NO_PERMISSIONS);
event.stopPropagation();
return;
}

View file

@ -134,7 +134,7 @@ EntityListTool = function(opts) {
Window.alert('There were no recent users of the ' + selectionManager.selections.length + ' selected objects.');
} else {
// No need to subscribe if we're just sending.
Messages.sendMessage('com.highfidelity.pal', JSON.stringify({method: 'select', params: [dedupped, true]}), 'local');
Messages.sendMessage('com.highfidelity.pal', JSON.stringify({method: 'select', params: [dedupped, true, false]}), 'local');
}
} else if (data.type == "delete") {
deleteSelectedEntities();

View file

@ -6,6 +6,12 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// note: this constant is currently duplicated in edit.js
EDIT_SETTING = "io.highfidelity.isEditting";
isInEditMode = function isInEditMode() {
return Settings.getValue(EDIT_SETTING) === "false" ? false : !!Settings.getValue(EDIT_SETTING);
};
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== 'function') {

View file

@ -103,6 +103,8 @@ ExtendedOverlay.prototype.select = function (selected) {
return;
}
UserActivityLogger.palAction(selected ? "avatar_selected" : "avatar_deselected", this.key);
this.editOverlay({color: color(selected, this.hovering, this.audioLevel)});
if (this.model) {
this.model.editOverlay({textures: textures(selected)});
@ -231,11 +233,26 @@ pal.fromQml.connect(function (message) { // messages are {method, params}, like
break;
case 'refresh':
removeOverlays();
populateUserList();
populateUserList(message.params);
UserActivityLogger.palAction("refresh", "");
break;
case 'updateGain':
data = message.params;
Users.setAvatarGain(data['sessionId'], data['gain']);
if (data['isReleased']) {
// isReleased=true happens once at the end of a cycle of dragging
// the slider about, but with same gain as last isReleased=false so
// we don't set the gain in that case, and only here do we want to
// send an analytic event.
UserActivityLogger.palAction("avatar_gain_changed", data['sessionId']);
} else {
Users.setAvatarGain(data['sessionId'], data['gain']);
}
break;
case 'displayNameUpdate':
if (MyAvatar.displayName != message.params) {
MyAvatar.displayName = message.params;
UserActivityLogger.palAction("display_name_change", "");
}
break;
default:
print('Unrecognized message from Pal.qml:', JSON.stringify(message));
@ -254,7 +271,7 @@ function addAvatarNode(id) {
color: color(selected, false, 0.0),
ignoreRayIntersection: false}, selected, true);
}
function populateUserList() {
function populateUserList(selectData) {
var data = [];
AvatarList.getAvatarIdentifiers().sort().forEach(function (id) { // sorting the identifiers is just an aid for debugging
var avatar = AvatarList.getAvatar(id);
@ -262,14 +279,12 @@ function populateUserList() {
displayName: avatar.sessionDisplayName,
userName: '',
sessionId: id || '',
audioLevel: 0.0
audioLevel: 0.0,
admin: false
};
// If the current user is an admin OR
// they're requesting their own username ("id" is blank)...
if (Users.canKick || !id) {
// Request the username from the given UUID
Users.requestUsernameFromID(id);
}
// Request the username, fingerprint, and admin status from the given UUID
// Username and fingerprint returns default constructor output if the requesting user isn't an admin
Users.requestUsernameFromID(id);
// Request personal mute status and ignore status
// from NodeList (as long as we're not requesting it for our own ID)
if (id) {
@ -280,20 +295,27 @@ function populateUserList() {
data.push(avatarPalDatum);
print('PAL data:', JSON.stringify(avatarPalDatum));
});
pal.sendToQml({method: 'users', params: data});
pal.sendToQml({ method: 'users', params: data });
if (selectData) {
selectData[2] = true;
pal.sendToQml({ method: 'select', params: selectData });
}
}
// The function that handles the reply from the server
function usernameFromIDReply(id, username, machineFingerprint) {
function usernameFromIDReply(id, username, machineFingerprint, isAdmin) {
var data;
// If the ID we've received is our ID...
if (MyAvatar.sessionUUID === id) {
// Set the data to contain specific strings.
data = ['', username];
} else {
data = ['', username, isAdmin];
} else if (Users.canKick) {
// Set the data to contain the ID and the username (if we have one)
// or fingerprint (if we don't have a username) string.
data = [id, username || machineFingerprint];
data = [id, username || machineFingerprint, isAdmin];
} else {
// Set the data to contain specific strings.
data = [id, '', isAdmin];
}
print('Username Data:', JSON.stringify(data));
// Ship the data off to QML
@ -370,7 +392,7 @@ function removeOverlays() {
function handleClick(pickRay) {
ExtendedOverlay.applyPickRay(pickRay, function (overlay) {
// Don't select directly. Tell qml, who will give us back a list of ids.
var message = {method: 'select', params: [[overlay.key], !overlay.selected]};
var message = {method: 'select', params: [[overlay.key], !overlay.selected, false]};
pal.sendToQml(message);
return true;
});
@ -493,7 +515,6 @@ function getAudioLevel(id) {
var audioLevel = 0.0;
var data = id ? ExtendedOverlay.get(id) : myData;
if (!data) {
print('no data for', id);
return audioLevel;
}
@ -547,7 +568,10 @@ var button = toolBar.addButton({
buttonState: 1,
alpha: 0.9
});
var isWired = false;
var palOpenedAt;
function off() {
if (isWired) { // It is not ok to disconnect these twice, hence guard.
Script.update.disconnect(updateOverlays);
@ -559,6 +583,11 @@ function off() {
triggerPressMapping.disable(); // see above
removeOverlays();
Users.requestsDomainListData = false;
if (palOpenedAt) {
var duration = new Date().getTime() - palOpenedAt;
UserActivityLogger.palOpened(duration / 1000.0);
palOpenedAt = 0; // just a falsy number is good enough.
}
if (audioInterval) {
Script.clearInterval(audioInterval);
}
@ -575,12 +604,16 @@ function onClicked() {
triggerMapping.enable();
triggerPressMapping.enable();
createAudioInterval();
palOpenedAt = new Date().getTime();
} else {
off();
}
pal.setVisible(!pal.visible);
}
function avatarDisconnected(nodeID) {
// remove from the pal list
pal.sendToQml({method: 'avatarDisconnected', params: [nodeID]});
}
//
// Button state.
//
@ -593,6 +626,8 @@ button.clicked.connect(onClicked);
pal.visibleChanged.connect(onVisibleChanged);
pal.closed.connect(off);
Users.usernameFromIDReply.connect(usernameFromIDReply);
Users.avatarDisconnected.connect(avatarDisconnected);
function clearLocalQMLDataAndClosePAL() {
pal.sendToQml({ method: 'clearLocalQMLData' });
if (pal.visible) {
@ -615,6 +650,7 @@ Script.scriptEnding.connect(function () {
Window.domainConnectionRefused.disconnect(clearLocalQMLDataAndClosePAL);
Messages.unsubscribe(CHANNEL);
Messages.messageReceived.disconnect(receiveMessage);
Users.avatarDisconnected.disconnect(avatarDisconnected);
off();
});

View file

@ -1,34 +1,48 @@
// ambientSound.js
//
// This entity script will allow you to create an ambient sound that loops when a person is within a given
// range of this entity. Great way to add one or more ambisonic soundfields to your environment.
// range of this entity. Great way to add one or more ambisonic soundfields to your environment.
//
// In the userData section for the entity, add/edit three values:
// userData.soundURL should be a string giving the URL to the sound file. Defaults to 100 meters if not set.
// In the userData section for the entity, add/edit three values:
// userData.soundURL should be a string giving the URL to the sound file. Defaults to 100 meters if not set.
// userData.range should be an integer for the max distance away from the entity where the sound will be audible.
// userData.volume is the max volume at which the clip should play. Defaults to 1.0 full volume)
// userData.maxVolume is the max volume at which the clip should play. Defaults to 1.0 full volume.
// userData.disabled is an optionanl boolean flag which can be used to disable the ambient sound. Defaults to false.
//
// The rotation of the entity is copied to the ambisonic field, so by rotating the entity you will rotate the
// direction in-which a certain sound comes from.
//
// The rotation of the entity is copied to the ambisonic field, so by rotating the entity you will rotate the
// direction in-which a certain sound comes from.
//
// Remember that the entity has to be visible to the user for the sound to play at all, so make sure the entity is
// large enough to be loaded at the range you set, particularly for large ranges.
//
// large enough to be loaded at the range you set, particularly for large ranges.
//
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function(){
(function(){
// This sample clip and range will be used if you don't add userData to the entity (see above)
var DEFAULT_RANGE = 100;
var DEFAULT_URL = "http://hifi-content.s3.amazonaws.com/ken/samples/forest_ambiX.wav";
var DEFAULT_VOLUME = 1.0;
var DEFAULT_USERDATA = {
soundURL: DEFAULT_URL,
range: DEFAULT_RANGE,
maxVolume: DEFAULT_VOLUME,
disabled: true,
grabbableKey: { wantsTrigger: true },
};
var soundURL = "";
var soundOptions = {
loop: true,
localOnly: true,
};
var range = DEFAULT_RANGE;
var maxVolume = DEFAULT_VOLUME;
var disabled = false;
var UPDATE_INTERVAL_MSECS = 100;
var rotation;
@ -39,14 +53,13 @@
var checkTimer = false;
var _this;
var WANT_COLOR_CHANGE = false;
var COLOR_OFF = { red: 128, green: 128, blue: 128 };
var COLOR_ON = { red: 255, green: 0, blue: 0 };
var WANT_DEBUG = false;
function debugPrint(string) {
if (WANT_DEBUG) {
print(string);
print("ambientSound | " + string);
}
}
@ -55,84 +68,201 @@
var oldSoundURL = soundURL;
var props = Entities.getEntityProperties(entity, [ "userData" ]);
if (props.userData) {
var data = JSON.parse(props.userData);
try {
var data = JSON.parse(props.userData);
} catch(e) {
debugPrint("unable to parse userData JSON string: " + props.userData);
this.cleanup();
return;
}
if (data.soundURL && !(soundURL === data.soundURL)) {
soundURL = data.soundURL;
debugPrint("Read ambient sound URL: " + soundURL);
} else if (!data.soundURL) {
soundURL = DEFAULT_URL;
debugPrint("Read ambient sound URL: " + soundURL);
}
if (data.range && !(range === data.range)) {
range = data.range;
debugPrint("Read ambient sound range: " + range);
}
if (data.volume && !(maxVolume === data.volume)) {
maxVolume = data.volume;
// Check known aliases for the "volume" setting (which allows for inplace upgrade of existing marketplace entities)
data.maxVolume = data.maxVolume || data.soundVolume || data.volume;
if (data.maxVolume && !(maxVolume === data.maxVolume)) {
maxVolume = data.maxVolume;
debugPrint("Read ambient sound volume: " + maxVolume);
}
if ("disabled" in data && !(disabled === data.disabled)) {
disabled = data.disabled;
debugPrint("Read ambient disabled state: " + disabled);
this._updateColor(disabled);
}
}
if (disabled) {
this.cleanup();
soundURL = "";
return;
} else if (!checkTimer) {
checkTimer = Script.setInterval(_this.maybeUpdate, UPDATE_INTERVAL_MSECS);
}
if (!(soundURL === oldSoundURL) || (soundURL === "")) {
debugPrint("Loading ambient sound into cache");
ambientSound = SoundCache.getSound(soundURL);
if (soundURL) {
debugPrint("Loading ambient sound into cache");
// Use prefetch to detect URL loading errors
var resource = SoundCache.prefetch(soundURL);
function onStateChanged() {
if (resource.state === Resource.State.FINISHED) {
resource.stateChanged.disconnect(onStateChanged);
ambientSound = SoundCache.getSound(soundURL);
} else if (resource.state === Resource.State.FAILED) {
resource.stateChanged.disconnect(onStateChanged);
debugPrint("Failed to download ambient sound: " + soundURL);
}
}
resource.stateChanged.connect(onStateChanged);
onStateChanged(resource.state);
}
if (soundPlaying && soundPlaying.playing) {
debugPrint("URL changed, stopping current ambient sound");
soundPlaying.stop();
soundPlaying = false;
if (WANT_COLOR_CHANGE) {
Entities.editEntity(entity, { color: COLOR_OFF });
}
debugPrint("Restarting ambient sound");
}
}
}
}
this.preload = function(entityID) {
this.clickDownOnEntity = function(entityID, mouseEvent) {
if (mouseEvent.isPrimaryButton) {
this._toggle("primary click");
}
};
this.startFarTrigger = function() {
this._toggle("far click");
};
this._toggle = function(hint) {
// Toggle between ON/OFF state, but only if not in edit mode
if (Settings.getValue("io.highfidelity.isEditting")) {
return;
}
var props = Entities.getEntityProperties(entity, [ "userData" ]);
if (!props.userData) {
debugPrint("userData is empty; ignoring " + hint);
this.cleanup();
return;
}
var data = JSON.parse(props.userData);
data.disabled = !data.disabled;
debugPrint(hint + " -- triggering ambient sound " + (data.disabled ? "OFF" : "ON") + " (" + data.soundURL + ")");
this.cleanup();
// Save the userData and notify nearby listeners of the change
Entities.editEntity(entity, {
userData: JSON.stringify(data)
});
Messages.sendMessage(entity, "toggled");
};
this._updateColor = function(disabled) {
// Update Shape or Text Entity color based on ON/OFF status
var props = Entities.getEntityProperties(entity, [ "color", "textColor" ]);
var targetColor = disabled ? COLOR_OFF : COLOR_ON;
var currentColor = props.textColor || props.color;
var newProps = props.textColor ? { textColor: targetColor } : { color: targetColor };
if (currentColor.red !== targetColor.red ||
currentColor.green !== targetColor.green ||
currentColor.blue !== targetColor.blue) {
Entities.editEntity(entity, newProps);
}
};
this.preload = function(entityID) {
// Load the sound and range from the entity userData fields, and note the position of the entity.
debugPrint("Ambient sound preload");
debugPrint("Ambient sound preload " + entityID);
entity = entityID;
_this = this;
checkTimer = Script.setInterval(this.maybeUpdate, UPDATE_INTERVAL_MSECS);
};
var props = Entities.getEntityProperties(entity, [ "userData" ]);
var data = {};
if (props.userData) {
data = JSON.parse(props.userData);
}
var changed = false;
for(var p in DEFAULT_USERDATA) {
if (!(p in data)) {
data[p] = DEFAULT_USERDATA[p];
changed = true;
}
}
if (!data.grabbableKey.wantsTrigger) {
data.grabbableKey.wantsTrigger = true;
changed = true;
}
if (changed) {
debugPrint("applying default values to userData");
Entities.editEntity(entity, { userData: JSON.stringify(data) });
}
this._updateColor(data.disabled);
this.updateSettings();
// Subscribe to toggle notifications using entity ID as a channel name
Messages.subscribe(entity);
Messages.messageReceived.connect(this, "_onMessageReceived");
};
this._onMessageReceived = function(channel, message, sender, local) {
// Handle incoming toggle notifications
if (channel === entity && message === "toggled") {
debugPrint("received " + message + " from " + sender);
this.updateSettings();
}
};
this.maybeUpdate = function() {
// Every UPDATE_INTERVAL_MSECS, update the volume of the ambient sound based on distance from my avatar
_this.updateSettings();
var HYSTERESIS_FRACTION = 0.1;
var props = Entities.getEntityProperties(entity, [ "position", "rotation" ]);
if (disabled || !props.position) {
_this.cleanup();
return;
}
center = props.position;
rotation = props.rotation;
var distance = Vec3.length(Vec3.subtract(MyAvatar.position, center));
if (distance <= range) {
var volume = (1.0 - distance / range) * maxVolume;
if (!soundPlaying && ambientSound.downloaded) {
soundPlaying = Audio.playSound(ambientSound, { loop: true,
localOnly: true,
orientation: rotation,
volume: volume });
debugPrint("Starting ambient sound, volume: " + volume);
if (WANT_COLOR_CHANGE) {
Entities.editEntity(entity, { color: COLOR_ON });
}
soundOptions.orientation = rotation;
soundOptions.volume = volume;
if (!soundPlaying && ambientSound && ambientSound.downloaded) {
debugPrint("Starting ambient sound: " + soundURL + " (duration: " + ambientSound.duration + ")");
soundPlaying = Audio.playSound(ambientSound, soundOptions);
} else if (soundPlaying && soundPlaying.playing) {
soundPlaying.setOptions( { volume: volume, orientation: rotation } );
soundPlaying.setOptions(soundOptions);
}
} else if (soundPlaying && soundPlaying.playing && (distance > range * HYSTERESIS_FRACTION)) {
soundPlaying.stop();
soundPlaying = false;
Entities.editEntity(entity, { color: { red: 128, green: 128, blue: 128 }});
debugPrint("Out of range, stopping ambient sound");
debugPrint("Out of range, stopping ambient sound: " + soundURL);
}
}
};
this.unload = function(entityID) {
this.unload = function(entityID) {
debugPrint("Ambient sound unload");
this.cleanup();
Messages.unsubscribe(entity);
Messages.messageReceived.disconnect(this, "_onMessageReceived");
};
this.cleanup = function() {
if (checkTimer) {
Script.clearInterval(checkTimer);
checkTimer = false;
}
if (soundPlaying && soundPlaying.playing) {
soundPlaying.stop();
soundPlaying = false;
}
};
};
})
})