mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-14 06:46:39 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into ess-pr
This commit is contained in:
commit
f540c94113
87 changed files with 2084 additions and 656 deletions
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
4
cmake/externals/quazip/CMakeLists.txt
vendored
4
cmake/externals/quazip/CMakeLists.txt
vendored
|
@ -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")
|
||||
|
|
4
cmake/externals/wasapi/CMakeLists.txt
vendored
4
cmake/externals/wasapi/CMakeLists.txt
vendored
|
@ -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 ""
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.";
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -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 {
|
||||
|
|
|
@ -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});
|
||||
}
|
||||
|
|
|
@ -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 "UNMUTE" 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));
|
||||
}
|
||||
|
|
|
@ -312,10 +312,11 @@ Item {
|
|||
readonly property string error: "="
|
||||
readonly property string settings: "@"
|
||||
readonly property string trash: "{"
|
||||
readonly property string objectGroup: ""
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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__);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
74
interface/src/avatar/CauterizedMeshPartPayload.cpp
Normal file
74
interface/src/avatar/CauterizedMeshPartPayload.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
29
interface/src/avatar/CauterizedMeshPartPayload.h
Normal file
29
interface/src/avatar/CauterizedMeshPartPayload.h
Normal 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
|
254
interface/src/avatar/CauterizedModel.cpp
Normal file
254
interface/src/avatar/CauterizedModel.cpp
Normal 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);
|
||||
}
|
53
interface/src/avatar/CauterizedModel.h
Normal file
53
interface/src/avatar/CauterizedModel.h
Normal 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
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
//
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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 = {});
|
||||
};
|
||||
|
|
|
@ -219,7 +219,8 @@ enum class AvatarMixerPacketVersion : PacketVersion {
|
|||
HandControllerJoints,
|
||||
HasKillAvatarReason,
|
||||
SessionDisplayName,
|
||||
Unignore
|
||||
Unignore,
|
||||
ImmediateSessionDisplayNameUpdates
|
||||
};
|
||||
|
||||
enum class DomainConnectRequestVersion : PacketVersion {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
59
libraries/render-utils/src/forward_model.slf
Normal file
59
libraries/render-utils/src/forward_model.slf
Normal 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);
|
||||
}
|
64
libraries/render-utils/src/forward_model_normal_map.slf
Normal file
64
libraries/render-utils/src/forward_model_normal_map.slf
Normal 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);
|
||||
}
|
|
@ -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);
|
||||
}
|
63
libraries/render-utils/src/forward_model_specular_map.slf
Normal file
63
libraries/render-utils/src/forward_model_specular_map.slf
Normal 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);
|
||||
}
|
45
libraries/render-utils/src/forward_model_unlit.slf
Normal file
45
libraries/render-utils/src/forward_model_unlit.slf
Normal 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());
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>;
|
||||
|
||||
|
|
|
@ -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 }});
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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; }
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
@ -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, ¤tProcessAffinity, ¤tSystemAffinity);
|
||||
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
|
||||
}
|
||||
|
|
|
@ -231,4 +231,8 @@ struct ProcessorInfo {
|
|||
|
||||
bool getProcessorInfo(ProcessorInfo& info);
|
||||
|
||||
const QString& getInterfaceSharedMemoryName();
|
||||
|
||||
void setMaxCores(uint8_t maxCores);
|
||||
|
||||
#endif // hifi_SharedUtil_h
|
||||
|
|
19
scripts/developer/tests/ambientSoundTest.js
Normal file
19
scripts/developer/tests/ambientSoundTest.js
Normal 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);
|
||||
});
|
|
@ -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(); });
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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++) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 ">
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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') {
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue