Merge branch 'master' into DOC-111

This commit is contained in:
David Rowe 2019-10-08 20:44:13 +13:00
commit f224832d27
162 changed files with 8822 additions and 4987 deletions

View file

@ -433,7 +433,7 @@ void Agent::executeScript() {
using namespace recording;
static const FrameType AUDIO_FRAME_TYPE = Frame::registerFrameType(AudioConstants::getAudioFrameName());
Frame::registerFrameHandler(AUDIO_FRAME_TYPE, [this, &scriptedAvatar](Frame::ConstPointer frame) {
Frame::registerFrameHandler(AUDIO_FRAME_TYPE, [this, &player, &scriptedAvatar](Frame::ConstPointer frame) {
if (_shouldMuteRecordingAudio) {
return;
}
@ -442,9 +442,18 @@ void Agent::executeScript() {
QByteArray audio(frame->data);
int16_t* samples = reinterpret_cast<int16_t*>(audio.data());
int numSamples = AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL;
auto volume = player->getVolume();
if (volume >= 0.0f && volume < 1.0f) {
int32_t fract = (int32_t)(volume * (float)(1 << 16)); // Q16
for (int i = 0; i < numSamples; i++) {
samples[i] = (fract * (int32_t)samples[i]) >> 16;
}
}
if (_isNoiseGateEnabled) {
int16_t* samples = reinterpret_cast<int16_t*>(audio.data());
int numSamples = AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL;
_audioGate.render(samples, samples, numSamples);
}

View file

@ -22,6 +22,7 @@
#include <AccountManager.h>
#include <AddressManager.h>
#include <Assignment.h>
#include <CrashAnnotations.h>
#include <LogHandler.h>
#include <LogUtils.h>
#include <LimitedNodeList.h>
@ -144,6 +145,7 @@ AssignmentClient::~AssignmentClient() {
}
void AssignmentClient::aboutToQuit() {
crash::annotations::setShutdownState(true);
stopAssignmentClient();
}
@ -173,6 +175,7 @@ void AssignmentClient::sendStatusPacketToACM() {
void AssignmentClient::sendAssignmentRequest() {
if (!_currentAssignment && !_isAssigned) {
crash::annotations::setShutdownState(false);
auto nodeList = DependencyManager::get<NodeList>();
@ -289,6 +292,8 @@ void AssignmentClient::handleAuthenticationRequest() {
}
void AssignmentClient::assignmentCompleted() {
crash::annotations::setShutdownState(true);
// we expect that to be here the previous assignment has completely cleaned up
assert(_currentAssignment.isNull());

View file

@ -240,6 +240,9 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) :
QThread::currentThread()->setObjectName("main thread");
LogHandler::getInstance().moveToThread(thread());
LogHandler::getInstance().setupRepeatedMessageFlusher();
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
DependencyManager::set<ScriptInitializers>();

View file

@ -38,7 +38,7 @@ MixerAvatar::MixerAvatar() {
_pendingEvent = false;
_verifyState = verificationFailed;
_needsIdentityUpdate = true;
qCDebug(avatars) << "Dynamic verification TIMED-OUT for " << getDisplayName() << getSessionUUID();
qCDebug(avatars) << "Dynamic verification TIMED-OUT for" << getDisplayName() << getSessionUUID();
} else {
qCDebug(avatars) << "Ignoring timeout of avatar challenge";
}
@ -287,7 +287,7 @@ void MixerAvatar::processCertifyEvents() {
<< ":" << _dynamicMarketResponse;
}
} else {
qCDebug(avatars) << "Get owner status failed for " << getDisplayName() << _marketplaceIdFromURL <<
qCDebug(avatars) << "Get owner status failed for" << getDisplayName() << _marketplaceIdFromURL <<
"message:" << responseJson["message"].toString();
_verifyState = error;
}
@ -356,7 +356,7 @@ void MixerAvatar::processChallengeResponse(ReceivedMessage& response) {
_verifyState = challengeResult ? verificationSucceeded : verificationFailed;
_needsIdentityUpdate = true;
if (_verifyState == verificationFailed) {
qCDebug(avatars) << "Dynamic verification FAILED for " << getDisplayName() << getSessionUUID();
qCDebug(avatars) << "Dynamic verification FAILED for" << getDisplayName() << getSessionUUID();
} else {
qCDebug(avatars) << "Dynamic verification SUCCEEDED for" << getDisplayName() << getSessionUUID();
}

View file

@ -24,6 +24,9 @@
class EntitySimulation;
/**jsdoc
* The <code>EntityViewer</code> API provides a headless viewer for assignment client scripts, so that they can "see" entities
* in order for them to be available in the {@link Entities} API.
*
* @namespace EntityViewer
*
* @hifi-assignment-client

View file

@ -28,6 +28,7 @@ public:
public slots:
/**jsdoc
* Updates the entities currently in view.
* @function EntityViewer.queryOctree
*/
void queryOctree();
@ -36,26 +37,30 @@ public slots:
// setters for camera attributes
/**jsdoc
* Sets the position of the view frustum.
* @function EntityViewer.setPosition
* @param {Vec3} position
* @param {Vec3} position - The position of the view frustum.
*/
void setPosition(const glm::vec3& position) { _hasViewFrustum = true; _viewFrustum.setPosition(position); }
/**jsdoc
* Sets the orientation of the view frustum.
* @function EntityViewer.setOrientation
* @param {Quat} orientation
* @param {Quat} orientation - The orientation of the view frustum.
*/
void setOrientation(const glm::quat& orientation) { _hasViewFrustum = true; _viewFrustum.setOrientation(orientation); }
/**jsdoc
* Sets the radius of the center "keyhole" in the view frustum.
* @function EntityViewer.setCenterRadius
* @param {number} radius
* @param {number} radius - The radius of the center "keyhole" in the view frustum.
*/
void setCenterRadius(float radius) { _hasViewFrustum = true; _viewFrustum.setCenterRadius(radius); }
/**jsdoc
* Sets the radius of the center "keyhole" in the view frustum.
* @function EntityViewer.setKeyholeRadius
* @param {number} radius
* @param {number} radius - The radius of the center "keyhole" in the view frustum.
* @deprecated This function is deprecated and will be removed. Use {@link EntityViewer.setCenterRadius|setCenterRadius}
* instead.
*/
@ -66,33 +71,38 @@ public slots:
/**jsdoc
* @function EntityViewer.setVoxelSizeScale
* @param {number} sizeScale
* @param {number} sizeScale - The voxel size scale.
* @deprecated This function is deprecated and will be removed.
*/
void setVoxelSizeScale(float sizeScale) { _octreeQuery.setOctreeSizeScale(sizeScale) ; }
/**jsdoc
* @function EntityViewer.setBoundaryLevelAdjust
* @param {number} boundaryLevelAdjust
* @param {number} boundaryLevelAdjust - The boundary level adjust factor.
* @deprecated This function is deprecated and will be removed.
*/
void setBoundaryLevelAdjust(int boundaryLevelAdjust) { _octreeQuery.setBoundaryLevelAdjust(boundaryLevelAdjust); }
/**jsdoc
* Sets the maximum number of entity packets to receive from the domain server per second.
* @function EntityViewer.setMaxPacketsPerSecond
* @param {number} maxPacketsPerSecond
* @param {number} maxPacketsPerSecond - The maximum number of entity packets to receive per second.
*/
void setMaxPacketsPerSecond(int maxPacketsPerSecond) { _octreeQuery.setMaxQueryPacketsPerSecond(maxPacketsPerSecond); }
// getters for camera attributes
/**jsdoc
* Gets the position of the view frustum.
* @function EntityViewer.getPosition
* @returns {Vec3}
* @returns {Vec3} The position of the view frustum.
*/
const glm::vec3& getPosition() const { return _viewFrustum.getPosition(); }
/**jsdoc
* Gets the orientation of the view frustum.
* @function EntityViewer.getOrientation
* @returns {Quat}
* @returns {Quat} The orientation of the view frustum.
*/
const glm::quat& getOrientation() const { return _viewFrustum.getOrientation(); }
@ -101,26 +111,30 @@ public slots:
/**jsdoc
* @function EntityViewer.getVoxelSizeScale
* @returns {number}
* @returns {number} The voxel size scale.
* @deprecated This function is deprecated and will be removed.
*/
float getVoxelSizeScale() const { return _octreeQuery.getOctreeSizeScale(); }
/**jsdoc
* @function EntityViewer.getBoundaryLevelAdjust
* @returns {number}
* @returns {number} The boundary level adjust factor.
* @deprecated This function is deprecated and will be removed.
*/
int getBoundaryLevelAdjust() const { return _octreeQuery.getBoundaryLevelAdjust(); }
/**jsdoc
* Gets the maximum number of entity packets to receive from the domain server per second.
* @function EntityViewer.getMaxPacketsPerSecond
* @returns {number}
* @returns {number} The maximum number of entity packets to receive per second.
*/
int getMaxPacketsPerSecond() const { return _octreeQuery.getMaxQueryPacketsPerSecond(); }
/**jsdoc
* Gets the number of nodes in the octree.
* @function EntityViewer.getOctreeElementsCount
* @returns {number}
* @returns {number} The number of nodes in the octree.
*/
unsigned getOctreeElementsCount() const { return _tree->getOctreeElementsCount(); }

View file

@ -556,7 +556,51 @@ void EntityScriptServer::checkAndCallPreload(const EntityItemID& entityID, bool
}
void EntityScriptServer::sendStatsPacket() {
QJsonObject statsObject;
QJsonObject octreeStats;
octreeStats["elementCount"] = (double)OctreeElement::getNodeCount();
octreeStats["internalElementCount"] = (double)OctreeElement::getInternalNodeCount();
octreeStats["leafElementCount"] = (double)OctreeElement::getLeafNodeCount();
statsObject["octree_stats"] = octreeStats;
QJsonObject scriptEngineStats;
int numberRunningScripts = 0;
const auto scriptEngine = _entitiesScriptEngine;
if (scriptEngine) {
numberRunningScripts = scriptEngine->getNumRunningEntityScripts();
}
scriptEngineStats["number_running_scripts"] = numberRunningScripts;
statsObject["script_engine_stats"] = scriptEngineStats;
auto nodeList = DependencyManager::get<NodeList>();
QJsonObject nodesObject;
nodeList->eachNode([&](const SharedNodePointer& node) {
QJsonObject clientStats;
const QString uuidString(uuidStringWithoutCurlyBraces(node->getUUID()));
clientStats["node_type"] = NodeType::getNodeTypeName(node->getType());
auto& nodeStats = node->getConnectionStats();
static const QString NODE_OUTBOUND_KBPS_STAT_KEY("outbound_kbit/s");
static const QString NODE_INBOUND_KBPS_STAT_KEY("inbound_kbit/s");
// add the key to ask the domain-server for a username replacement, if it has it
clientStats[USERNAME_UUID_REPLACEMENT_STATS_KEY] = uuidString;
clientStats[NODE_OUTBOUND_KBPS_STAT_KEY] = node->getOutboundKbps();
clientStats[NODE_INBOUND_KBPS_STAT_KEY] = node->getInboundKbps();
using namespace std::chrono;
const float statsPeriod = duration<float, seconds::period>(nodeStats.endTime - nodeStats.startTime).count();
clientStats["unreliable_packet/s"] = (nodeStats.sentUnreliablePackets + nodeStats.receivedUnreliablePackets) / statsPeriod;
clientStats["reliable_packet/s"] = (nodeStats.sentPackets + nodeStats.receivedPackets) / statsPeriod;
nodesObject[uuidString] = clientStats;
});
statsObject["nodes"] = nodesObject;
addPacketStatsAndSendStatsPacket(statsObject);
}
void EntityScriptServer::handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {

View file

@ -20,7 +20,7 @@ macro(SETUP_HIFI_LIBRARY)
foreach(SRC ${AVX_SRCS})
if (WIN32)
set_source_files_properties(${SRC} PROPERTIES COMPILE_FLAGS /arch:AVX)
elseif (APPLE OR UNIX)
elseif (APPLE OR (UNIX AND NOT ANDROID))
set_source_files_properties(${SRC} PROPERTIES COMPILE_FLAGS -mavx)
endif()
endforeach()
@ -30,7 +30,7 @@ macro(SETUP_HIFI_LIBRARY)
foreach(SRC ${AVX2_SRCS})
if (WIN32)
set_source_files_properties(${SRC} PROPERTIES COMPILE_FLAGS /arch:AVX2)
elseif (APPLE OR UNIX)
elseif (APPLE OR (UNIX AND NOT ANDROID))
set_source_files_properties(${SRC} PROPERTIES COMPILE_FLAGS "-mavx2 -mfma")
endif()
endforeach()
@ -44,7 +44,7 @@ macro(SETUP_HIFI_LIBRARY)
if (COMPILER_SUPPORTS_AVX512)
set_source_files_properties(${SRC} PROPERTIES COMPILE_FLAGS /arch:AVX512)
endif()
elseif (APPLE OR UNIX)
elseif (APPLE OR (UNIX AND NOT ANDROID))
check_cxx_compiler_flag("-mavx512f" COMPILER_SUPPORTS_AVX512)
if (COMPILER_SUPPORTS_AVX512)
set_source_files_properties(${SRC} PROPERTIES COMPILE_FLAGS -mavx512f)

View file

@ -250,14 +250,14 @@ $(document).ready(function(){
// set focus to the first input in the new row
$target.closest('table').find('tr.inputs input:first').focus();
}
} else {
var tableRows = sibling.parent();
var tableBody = tableRows.parent();
var tableRows = sibling.parent();
var tableBody = tableRows.parent();
// if theres no more siblings, we should jump to a new row
if (sibling.next().length == 0 && tableRows.nextAll().length == 1) {
tableBody.find("." + Settings.ADD_ROW_BUTTON_CLASS).click();
// if theres no more siblings, we should jump to a new row
if (sibling.next().length == 0 && tableRows.nextAll().length == 1) {
tableBody.find("." + Settings.ADD_ROW_BUTTON_CLASS).click();
}
}
}

View file

@ -136,6 +136,23 @@ function getCurrentDomainIDType() {
return DOMAIN_ID_TYPE_UNKNOWN;
}
function isCloudDomain() {
if (!domainIDIsSet()) {
return false;
}
if (typeof DomainInfo === 'undefined') {
return false;
}
if (DomainInfo === null) {
return false;
}
if (typeof DomainInfo.cloud_domain !== "boolean") {
return false;
}
return DomainInfo.cloud_domain;
}
function showLoadingDialog(msg) {
var message = '<div class="text-center">';
message += '<span class="glyphicon glyphicon-refresh glyphicon-refresh-animate"></span> ' + msg;

View file

@ -606,6 +606,9 @@ $(document).ready(function(){
var address = DomainInfo.network_address === null ? '' : DomainInfo.network_address;
var port = DomainInfo.network_port === null ? '' : DomainInfo.network_port;
var modal_body = "<div class='form-group'>";
if (isCloudDomain()) {
modal_body += '<div style="color:red;font-weight: bold">Changing the network settings may actually break your domain.</div>';
}
if (includeAddress) {
modal_body += "<label class='control-label'>Address</label>";
modal_body += "<input type='text' id='network-address-input' class='form-control' value='" + address + "'>";
@ -867,6 +870,10 @@ $(document).ready(function(){
}
}
if (getCurrentDomainIDType() === DOMAIN_ID_TYPE_TEMP) {
$(Settings.DOMAIN_ID_SELECTOR).siblings('span').append(" <b>This is a temporary domain and will not be visible in your domain list.</b>");
}
if (accessTokenIsSet()) {
appendAddButtonToPlacesTable();
}

View file

@ -32,6 +32,7 @@
#include <AccountManager.h>
#include <AssetClient.h>
#include <BuildInfo.h>
#include <CrashAnnotations.h>
#include <DependencyManager.h>
#include <HifiConfigVariantMap.h>
#include <HTTPConnection.h>
@ -174,6 +175,9 @@ DomainServer::DomainServer(int argc, char* argv[]) :
LogUtils::init();
LogHandler::getInstance().moveToThread(thread());
LogHandler::getInstance().setupRepeatedMessageFlusher();
qDebug() << "Setting up domain-server";
qDebug() << "[VERSION] Build sequence:" << qPrintable(applicationVersion());
qDebug() << "[VERSION] MODIFIED_ORGANIZATION:" << BuildInfo::MODIFIED_ORGANIZATION;
@ -182,6 +186,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
qDebug() << "[VERSION] BUILD_GLOBAL_SERVICES:" << BuildInfo::BUILD_GLOBAL_SERVICES;
qDebug() << "[VERSION] We will be using this name to find ICE servers:" << _iceServerAddr;
connect(this, &QCoreApplication::aboutToQuit, this, &DomainServer::aboutToQuit);
// make sure we have a fresh AccountManager instance
// (need this since domain-server can restart itself and maintain static variables)
@ -320,7 +325,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
connect(_contentManager.get(), &DomainContentBackupManager::recoveryCompleted, this, &DomainServer::restart);
static const int NODE_PING_MONITOR_INTERVAL_MSECS = 1 * MSECS_PER_SECOND;
static const int NODE_PING_MONITOR_INTERVAL_MSECS = 1 * MSECS_PER_SECOND;
_nodePingMonitorTimer = new QTimer{ this };
connect(_nodePingMonitorTimer, &QTimer::timeout, this, &DomainServer::nodePingMonitor);
_nodePingMonitorTimer->start(NODE_PING_MONITOR_INTERVAL_MSECS);
@ -380,7 +385,7 @@ void DomainServer::parseCommandLine(int argc, char* argv[]) {
}
if (_iceServerAddr.isEmpty()) {
qCWarning(domain_server_ice) << "Could not parse an IP address and port combination from" << hostnamePortString;
qCWarning(domain_server_ice) << "ALERT: Could not parse an IP address and port combination from" << hostnamePortString;
::exit(0);
}
}
@ -429,6 +434,10 @@ DomainServer::~DomainServer() {
DependencyManager::destroy<LimitedNodeList>();
}
void DomainServer::aboutToQuit() {
crash::annotations::setShutdownState(true);
}
void DomainServer::queuedQuit(QString quitMessage, int exitCode) {
if (!quitMessage.isEmpty()) {
qWarning() << qPrintable(quitMessage);
@ -867,7 +876,7 @@ void DomainServer::setupAutomaticNetworking() {
nodeList->startSTUNPublicSocketUpdate();
}
} else {
qDebug() << "Cannot enable domain-server automatic networking without a domain ID."
qCCritical(domain_server) << "PAGE: Cannot enable domain-server automatic networking without a domain ID."
<< "Please add an ID to your config file or via the web interface.";
return;
}
@ -1626,8 +1635,9 @@ void DomainServer::handleFailedICEServerAddressUpdate(QNetworkReply* requestRepl
} else {
const int ICE_SERVER_UPDATE_RETRY_MS = 2 * 1000;
qCWarning(domain_server_ice) << "Failed to update ice-server address (" << _iceServerSocket << ") with High Fidelity Metaverse - error was"
<< requestReply->errorString();
qCWarning(domain_server_ice) << "PAGE: Failed to update ice-server address (" << _iceServerSocket <<
") with Metaverse (" << requestReply->url() << ") (critical error for auto-networking) error:" <<
requestReply->errorString();
qCWarning(domain_server_ice) << "\tRe-attempting in" << ICE_SERVER_UPDATE_RETRY_MS / 1000 << "seconds";
QTimer::singleShot(ICE_SERVER_UPDATE_RETRY_MS, this, SLOT(sendICEServerAddressToMetaverseAPI()));
@ -3441,8 +3451,9 @@ void DomainServer::randomizeICEServerAddress(bool shouldTriggerHostLookup) {
// we ended up with an empty list since everything we've tried has failed
// so clear the set of failed addresses and start going through them again
qCWarning(domain_server_ice) << "All current ice-server addresses have failed - re-attempting all current addresses for"
<< _iceServerAddr;
qCWarning(domain_server_ice) <<
"PAGE: All current ice-server addresses have failed - re-attempting all current addresses for"
<< _iceServerAddr;
_failedIceServerAddresses.clear();
candidateICEAddresses = _iceServerAddresses;

View file

@ -136,6 +136,8 @@ private slots:
void tokenGrantFinished();
void profileRequestFinished();
void aboutToQuit();
signals:
void iceServerChanged();
void userConnected();

View file

@ -15,9 +15,10 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <BuildInfo.h>
#include <CrashAnnotations.h>
#include <LogHandler.h>
#include <SharedUtil.h>
#include <BuildInfo.h>
#include "DomainServer.h"
@ -32,6 +33,7 @@ int main(int argc, char* argv[]) {
// use a do-while to handle domain-server restart
do {
crash::annotations::setShutdownState(false);
DomainServer domainServer(argc, argv);
currentExitCode = domainServer.exec();
} while (currentExitCode == DomainServer::EXIT_CODE_REBOOT);
@ -39,4 +41,3 @@ int main(int argc, char* argv[]) {
qInfo() << "Quitting.";
return currentExitCode;
}

View file

@ -70,12 +70,16 @@ file(GLOB_RECURSE INTERFACE_SRCS "src/*.cpp" "src/*.h")
GroupSources("src")
list(APPEND INTERFACE_SRCS ${RESOURCES_RCC})
# grab the Objective-C sources on OS X
if (APPLE)
file(GLOB_RECURSE INTERFACE_OBJCPP_SRCS "src/*.m" "src/*.mm")
list(APPEND INTERFACE_SRCS ${INTERFACE_OBJCPP_SRCS})
endif ()
# Add SpeechRecognizer if on Windows or OS X, otherwise remove
if (WIN32)
# Use .cpp and .h files as is.
elseif (APPLE)
file(GLOB INTERFACE_OBJCPP_SRCS "src/SpeechRecognizer.mm")
set(INTERFACE_SRCS ${INTERFACE_SRCS} ${INTERFACE_OBJCPP_SRCS})
get_filename_component(SPEECHRECOGNIZER_CPP "src/SpeechRecognizer.cpp" ABSOLUTE)
list(REMOVE_ITEM INTERFACE_SRCS ${SPEECHRECOGNIZER_CPP})
else ()

File diff suppressed because it is too large Load diff

View file

@ -3,8 +3,10 @@
"channels": [
{ "from": "Keyboard.A", "when": ["Keyboard.RightMouseButton", "!Keyboard.Control"], "to": "Actions.LATERAL_LEFT" },
{ "from": "Keyboard.D", "when": ["Keyboard.RightMouseButton", "!Keyboard.Control"], "to": "Actions.LATERAL_RIGHT" },
{ "from": "Keyboard.E", "when": "!Keyboard.Control", "to": "Actions.LATERAL_RIGHT" },
{ "from": "Keyboard.Q", "when": "!Keyboard.Control", "to": "Actions.LATERAL_LEFT" },
{ "from": "Keyboard.E", "when": ["!Application.CameraSelfie", "!Keyboard.Control"], "to": "Actions.LATERAL_RIGHT" },
{ "from": "Keyboard.Q", "when": ["!Application.CameraSelfie", "!Keyboard.Control"], "to": "Actions.LATERAL_LEFT" },
{ "from": "Keyboard.Q", "when": ["Application.CameraSelfie", "!Keyboard.Control"], "to": "Actions.LATERAL_RIGHT" },
{ "from": "Keyboard.E", "when": ["Application.CameraSelfie", "!Keyboard.Control"], "to": "Actions.LATERAL_LEFT" },
{ "from": "Keyboard.T", "when": "!Keyboard.Control", "to": "Actions.TogglePushToTalk" },
{ "comment" : "Mouse turn need to be small continuous increments",
@ -39,7 +41,6 @@
]
},
{ "from": { "makeAxis" : [
["Keyboard.Left" ],
["Keyboard.Right"]
@ -85,6 +86,24 @@
"when": ["Application.CameraThirdPerson", "!Keyboard.Shift"],
"to": "Actions.Yaw"
},
{ "from": { "makeAxis" : [
["Keyboard.Left"],
["Keyboard.Right"]
]
},
"when": ["Application.CameraLookAt", "!Keyboard.Shift"],
"to": "Actions.Yaw"
},
{ "from": { "makeAxis" : [
["Keyboard.Left"],
["Keyboard.Right"]
]
},
"when": ["Application.CameraSelfie", "!Keyboard.Shift"],
"to": "Actions.Yaw"
},
{ "from": { "makeAxis" : [
["Keyboard.A"],
@ -104,6 +123,24 @@
"to": "Actions.Yaw"
},
{ "from": { "makeAxis" : [
["Keyboard.A"],
["Keyboard.D"]
]
},
"when": ["Application.CameraLookAt", "!Keyboard.Control"],
"to": "Actions.Yaw"
},
{ "from": { "makeAxis" : [
["Keyboard.A"],
["Keyboard.D"]
]
},
"when": ["Application.CameraSelfie", "!Keyboard.Control"],
"to": "Actions.Yaw"
},
{ "from": { "makeAxis" : [
["Keyboard.TouchpadLeft"],
["Keyboard.TouchpadRight"]
@ -122,6 +159,24 @@
"to": "Actions.Yaw"
},
{ "from": { "makeAxis" : [
["Keyboard.TouchpadLeft"],
["Keyboard.TouchpadRight"]
]
},
"when": "Application.CameraLookAt",
"to": "Actions.Yaw"
},
{ "from": { "makeAxis" : [
["Keyboard.TouchpadLeft"],
["Keyboard.TouchpadRight"]
]
},
"when": "Application.CameraSelfie",
"to": "Actions.Yaw"
},
{ "from": { "makeAxis" : ["Keyboard.MouseMoveLeft", "Keyboard.MouseMoveRight"] },
"when": "Keyboard.RightMouseButton",
"to": "Actions.DeltaYaw",
@ -132,7 +187,7 @@
},
{ "from": { "makeAxis" : ["Keyboard.MouseMoveUp", "Keyboard.MouseMoveDown"] },
"when": "Keyboard.RightMouseButton",
"when": ["!Application.CameraSelfie", "!Application.CameraLookAt", "Keyboard.RightMouseButton"],
"to": "Actions.DeltaPitch",
"filters":
[
@ -140,16 +195,40 @@
]
},
{ "from": "Keyboard.W", "when": "!Keyboard.Control", "to": "Actions.LONGITUDINAL_FORWARD" },
{ "from": "Keyboard.S", "when": "!Keyboard.Control", "to": "Actions.LONGITUDINAL_BACKWARD" },
{ "from": { "makeAxis" : ["Keyboard.MouseMoveUp", "Keyboard.MouseMoveDown"] },
"when": ["Application.CameraLookAt", "Keyboard.RightMouseButton"],
"to": "Actions.DeltaPitch",
"filters":
[
{ "type": "scale", "scale": 0.2 }
]
},
{ "from": { "makeAxis" : ["Keyboard.MouseMoveDown", "Keyboard.MouseMoveUp"] },
"when": ["Application.CameraSelfie", "Keyboard.RightMouseButton"],
"to": "Actions.DeltaPitch",
"filters":
[
{ "type": "scale", "scale": 0.2 }
]
},
{ "from": "Keyboard.W", "when": ["!Application.CameraSelfie", "!Keyboard.Control"], "to": "Actions.LONGITUDINAL_FORWARD" },
{ "from": "Keyboard.S", "when": ["!Application.CameraSelfie", "!Keyboard.Control"], "to": "Actions.LONGITUDINAL_BACKWARD" },
{ "from": "Keyboard.S", "when": ["Application.CameraSelfie", "!Keyboard.Control"], "to": "Actions.LONGITUDINAL_FORWARD" },
{ "from": "Keyboard.W", "when": ["Application.CameraSelfie", "!Keyboard.Control"], "to": "Actions.LONGITUDINAL_BACKWARD" },
{ "from": "Keyboard.Shift", "when": ["!Keyboard.Left", "!Keyboard.Right"], "to": "Actions.SPRINT" },
{ "from": "Keyboard.C", "when": "!Keyboard.Control", "to": "Actions.VERTICAL_DOWN" },
{ "from": "Keyboard.Left", "when": "Keyboard.Shift", "to": "Actions.LATERAL_LEFT" },
{ "from": "Keyboard.Right", "when": "Keyboard.Shift", "to": "Actions.LATERAL_RIGHT" },
{ "from": "Keyboard.Up", "when": "Application.CameraFirstPerson", "to": "Actions.LONGITUDINAL_FORWARD" },
{ "from": "Keyboard.Up", "when": "Application.CameraThirdPerson", "to": "Actions.LONGITUDINAL_FORWARD" },
{ "from": "Keyboard.Up", "when": "Application.CameraLookAt", "to": "Actions.LONGITUDINAL_FORWARD" },
{ "from": "Keyboard.Up", "when": "Application.CameraSelfie", "to": "Actions.LONGITUDINAL_BACKWARD" },
{ "from": "Keyboard.Down", "when": "Application.CameraFirstPerson", "to": "Actions.LONGITUDINAL_BACKWARD" },
{ "from": "Keyboard.Down", "when": "Application.CameraThirdPerson", "to": "Actions.LONGITUDINAL_BACKWARD" },
{ "from": "Keyboard.Down", "when": "Application.CameraLookAt", "to": "Actions.LONGITUDINAL_BACKWARD" },
{ "from": "Keyboard.Down", "when": "Application.CameraSelfie", "to": "Actions.LONGITUDINAL_FORWARD" },
{ "from": "Keyboard.PgDown", "to": "Actions.VERTICAL_DOWN" },
{ "from": "Keyboard.PgUp", "to": "Actions.VERTICAL_UP" },

View file

@ -699,8 +699,8 @@ Item {
spacing: controlsTableRoot.rowPadding
HifiStylesUit.GraphikRegular {
id: mirrorText
text: "Mirror Mode"
id: selfieText
text: "Selfie"
width: paintedWidth
height: parent.height
horizontalAlignment: Text.AlignLeft
@ -710,8 +710,8 @@ Item {
}
HifiStylesUit.GraphikRegular {
text: "See your own avatar"
width: parent.width - mirrorText.width - parent.spacing - controlsTableRoot.rowPadding
text: "Look at self"
width: parent.width - selfieText.width - parent.spacing - controlsTableRoot.rowPadding
height: parent.height
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter

View file

@ -234,9 +234,19 @@ Flickable {
SimplifiedControls.RadioButton {
id: thirdPerson
text: "Third Person View"
checked: Camera.mode === "third person"
checked: Camera.mode === "look at"
onClicked: {
Camera.mode = "third person"
Camera.mode = "look at"
}
}
SimplifiedControls.RadioButton {
id: selfie
text: "Selfie"
checked: Camera.mode === "selfie"
visible: true
onClicked: {
Camera.mode = "selfie"
}
}
@ -246,11 +256,21 @@ Flickable {
onModeUpdated: {
if (Camera.mode === "first person") {
firstPerson.checked = true
} else if (Camera.mode === "third person") {
} else if (Camera.mode === "look at") {
thirdPerson.checked = true
} else if (Camera.mode === "selfie" && HMD.active) {
selfie.checked = true
}
}
}
Connections {
target: HMD
onDisplayModeChanged: {
selfie.visible = isHMDMode ? false : true
}
}
}
}

View file

@ -40,6 +40,9 @@ Rectangle {
property bool inventoryFullyReceived: false
Component.onCompleted: {
var numTimesRun = Settings.getValue("simplifiedUI/SUIScriptExecutionCount", 0);
numTimesRun++;
Settings.setValue("simplifiedUI/SUIScriptExecutionCount", numTimesRun);
Commerce.getLoginStatus();
}
@ -52,7 +55,7 @@ Rectangle {
if ((MyAvatar.skeletonModelURL.indexOf("defaultAvatar") > -1 || MyAvatar.skeletonModelURL.indexOf("fst") === -1) &&
topBarInventoryModel.count > 0) {
Settings.setValue("simplifiedUI/alreadyAutoSelectedAvatar", true);
MyAvatar.skeletonModelURL = topBarInventoryModel.get(0).download_url;
MyAvatar.useFullAvatarURL = topBarInventoryModel.get(0).download_url;
}
}
}

View file

@ -1,6 +1,6 @@
import QtQuick 2.0
import QtWebEngine 1.5
import "../../controls" as Controls
import "../../../controls" as Controls
Controls.TabletWebView {
profile: WebEngineProfile { httpUserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36"}

View file

@ -19,6 +19,7 @@ StackView {
objectName: "stack"
property string title: "General Settings"
property alias gotoPreviousApp: root.gotoPreviousApp;
property alias gotoPreviousAppFromScript: root.gotoPreviousAppFromScript;
signal sendToScript(var message);
function pushSource(path) {
@ -30,6 +31,10 @@ StackView {
profileRoot.pop();
}
function emitSendToScript(message) {
profileRoot.sendToScript(message);
}
TabletPreferencesDialog {
id: root
objectName: "TabletGeneralPreferences"

View file

@ -104,6 +104,10 @@ Rectangle {
if (loader.item.hasOwnProperty("gotoPreviousApp")) {
loader.item.gotoPreviousApp = true;
}
if (loader.item.hasOwnProperty("gotoPreviousAppFromScript")) {
loader.item.gotoPreviousAppFromScript = true;
}
});
}
}
@ -276,7 +280,7 @@ Rectangle {
} else {
console.log("newSource is of unknown type!");
}
screenChanged(type, newSource);
});
}

View file

@ -30,6 +30,7 @@ Item {
property bool keyboardRaised: false
property bool punctuationMode: false
property bool gotoPreviousApp: false
property bool gotoPreviousAppFromScript: false
property var tablet;
@ -42,7 +43,9 @@ Item {
}
if (HMD.active) {
if (gotoPreviousApp) {
if (gotoPreviousAppFromScript) {
dialog.parent.sendToScript("returnToPreviousApp");
} else if (gotoPreviousApp) {
tablet.returnToPreviousApp();
} else {
tablet.popFromStack();
@ -59,7 +62,9 @@ Item {
}
if (HMD.active) {
if (gotoPreviousApp) {
if (gotoPreviousAppFromScript) {
dialog.parent.sendToScript("returnToPreviousApp");
} else if (gotoPreviousApp) {
tablet.returnToPreviousApp();
} else {
tablet.popFromStack();
@ -72,7 +77,9 @@ Item {
function closeDialog() {
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
if (gotoPreviousApp) {
if (gotoPreviousAppFromScript) {
dialog.parent.sendToScript("returnToPreviousApp");
} else if (gotoPreviousApp) {
tablet.returnToPreviousApp();
} else {
tablet.gotoHomeScreen();

View file

@ -0,0 +1,24 @@
//
// AppNapDisabler.h
// interface/src
//
// Copyright 2019 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_AppNapDisabler_h
#define hifi_AppNapDisabler_h
#import <objc/objc-runtime.h>
class AppNapDisabler {
public:
AppNapDisabler();
~AppNapDisabler();
private:
id _activity;
};
#endif // hifi_AppNapDisabler_h

View file

@ -0,0 +1,28 @@
//
// AppNapDisabler.mm
// interface/src
//
// Copyright 2019 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 <QtGlobal>
#ifdef Q_OS_MAC
#include "AppNapDisabler.h"
#import <AppKit/AppKit.h>
AppNapDisabler::AppNapDisabler() {
_activity = [[NSProcessInfo processInfo] beginActivityWithOptions:NSActivityUserInitiated reason:@"Audio is in use"];
[_activity retain];
}
AppNapDisabler::~AppNapDisabler() {
[[NSProcessInfo processInfo] endActivity:_activity];
[_activity release];
}
#endif // Q_OS_MAC

View file

@ -106,8 +106,8 @@
#include <MessagesClient.h>
#include <hfm/ModelFormatRegistry.h>
#include <model-networking/ModelCacheScriptingInterface.h>
#include <material-networking/MaterialCacheScriptingInterface.h>
#include <material-networking/TextureCacheScriptingInterface.h>
#include <material-networking/MaterialCache.h>
#include <ModelEntityItem.h>
#include <NetworkAccessManager.h>
#include <NetworkingConstants.h>
@ -265,6 +265,12 @@ extern "C" {
#include "AndroidHelper.h"
#endif
#if defined(Q_OS_MAC)
// On Mac OS, disable App Nap to prevent audio glitches while running in the background
#include "AppNapDisabler.h"
static AppNapDisabler appNapDisabler; // disabled, while in scope
#endif
#include "graphics/RenderEventHandler.h"
Q_LOGGING_CATEGORY(trace_app_input_mouse, "trace.app.input.mouse")
@ -709,6 +715,8 @@ static const QString STATE_CAMERA_FIRST_PERSON = "CameraFirstPerson";
static const QString STATE_CAMERA_THIRD_PERSON = "CameraThirdPerson";
static const QString STATE_CAMERA_ENTITY = "CameraEntity";
static const QString STATE_CAMERA_INDEPENDENT = "CameraIndependent";
static const QString STATE_CAMERA_LOOK_AT = "CameraLookAt";
static const QString STATE_CAMERA_SELFIE = "CameraSelfie";
static const QString STATE_SNAP_TURN = "SnapTurn";
static const QString STATE_ADVANCED_MOVEMENT_CONTROLS = "AdvancedMovement";
static const QString STATE_GROUNDED = "Grounded";
@ -854,9 +862,9 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
DependencyManager::set<VirtualPad::Manager>();
DependencyManager::set<DesktopPreviewProvider>();
#if defined(Q_OS_ANDROID)
DependencyManager::set<AccountManager>(); // use the default user agent getter
DependencyManager::set<AccountManager>(true); // use the default user agent getter
#else
DependencyManager::set<AccountManager>(std::bind(&Application::getUserAgent, qApp));
DependencyManager::set<AccountManager>(true, std::bind(&Application::getUserAgent, qApp));
#endif
DependencyManager::set<StatTracker>();
DependencyManager::set<ScriptEngines>(ScriptEngine::CLIENT_SCRIPT, defaultScriptsOverrideOption);
@ -884,6 +892,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
DependencyManager::set<TextureCache>();
DependencyManager::set<MaterialCache>();
DependencyManager::set<TextureCacheScriptingInterface>();
DependencyManager::set<MaterialCacheScriptingInterface>();
DependencyManager::set<FramebufferCache>();
DependencyManager::set<AnimationCache>();
DependencyManager::set<AnimationCacheScriptingInterface>();
@ -924,7 +933,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
DependencyManager::set<AudioInjectorManager>();
DependencyManager::set<MessagesClient>();
controller::StateController::setStateVariables({ { STATE_IN_HMD, STATE_CAMERA_FULL_SCREEN_MIRROR,
STATE_CAMERA_FIRST_PERSON, STATE_CAMERA_THIRD_PERSON, STATE_CAMERA_ENTITY, STATE_CAMERA_INDEPENDENT,
STATE_CAMERA_FIRST_PERSON, STATE_CAMERA_THIRD_PERSON, STATE_CAMERA_ENTITY, STATE_CAMERA_INDEPENDENT, STATE_CAMERA_LOOK_AT, STATE_CAMERA_SELFIE,
STATE_SNAP_TURN, STATE_ADVANCED_MOVEMENT_CONTROLS, STATE_GROUNDED, STATE_NAV_FOCUSED,
STATE_PLATFORM_WINDOWS, STATE_PLATFORM_MAC, STATE_PLATFORM_ANDROID, STATE_LEFT_HAND_DOMINANT, STATE_RIGHT_HAND_DOMINANT, STATE_STRAFE_ENABLED } });
DependencyManager::set<UserInputMapper>();
@ -1060,6 +1069,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
setProperty(hifi::properties::STEAM, (steamClient && steamClient->isRunning()));
setProperty(hifi::properties::CRASHED, _previousSessionCrashed);
LogHandler::getInstance().moveToThread(thread());
LogHandler::getInstance().setupRepeatedMessageFlusher();
{
const QStringList args = arguments();
@ -1871,6 +1883,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
_applicationStateDevice->setInputVariant(STATE_CAMERA_THIRD_PERSON, []() -> float {
return qApp->getCamera().getMode() == CAMERA_MODE_THIRD_PERSON ? 1 : 0;
});
_applicationStateDevice->setInputVariant(STATE_CAMERA_LOOK_AT, []() -> float {
return qApp->getCamera().getMode() == CAMERA_MODE_LOOK_AT ? 1 : 0;
});
_applicationStateDevice->setInputVariant(STATE_CAMERA_SELFIE, []() -> float {
return qApp->getCamera().getMode() == CAMERA_MODE_SELFIE ? 1 : 0;
});
_applicationStateDevice->setInputVariant(STATE_CAMERA_ENTITY, []() -> float {
return qApp->getCamera().getMode() == CAMERA_MODE_ENTITY ? 1 : 0;
});
@ -2895,6 +2913,7 @@ Application::~Application() {
DependencyManager::destroy<AnimationCacheScriptingInterface>();
DependencyManager::destroy<AnimationCache>();
DependencyManager::destroy<FramebufferCache>();
DependencyManager::destroy<MaterialCacheScriptingInterface>();
DependencyManager::destroy<MaterialCache>();
DependencyManager::destroy<TextureCacheScriptingInterface>();
DependencyManager::destroy<TextureCache>();
@ -3420,6 +3439,7 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) {
// Caches
surfaceContext->setContextProperty("AnimationCache", DependencyManager::get<AnimationCacheScriptingInterface>().data());
surfaceContext->setContextProperty("TextureCache", DependencyManager::get<TextureCacheScriptingInterface>().data());
surfaceContext->setContextProperty("MaterialCache", DependencyManager::get<MaterialCacheScriptingInterface>().data());
surfaceContext->setContextProperty("ModelCache", DependencyManager::get<ModelCacheScriptingInterface>().data());
surfaceContext->setContextProperty("SoundCache", DependencyManager::get<SoundCacheScriptingInterface>().data());
@ -3583,21 +3603,19 @@ void Application::updateCamera(RenderArgs& renderArgs, float deltaTime) {
// Always use the default eye position, not the actual head eye position.
// Using the latter will cause the camera to wobble with idle animations,
// or with changes from the face tracker
if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON) {
CameraMode mode = _myCamera.getMode();
if (mode == CAMERA_MODE_FIRST_PERSON) {
_thirdPersonHMDCameraBoomValid= false;
if (isHMDMode()) {
mat4 camMat = myAvatar->getSensorToWorldMatrix() * myAvatar->getHMDSensorMatrix();
_myCamera.setPosition(extractTranslation(camMat));
_myCamera.setOrientation(glmExtractRotation(camMat));
} else {
_myCamera.setPosition(myAvatar->getLookAtPivotPoint());
_myCamera.setOrientation(myAvatar->getLookAtRotation());
}
else {
_myCamera.setPosition(myAvatar->getDefaultEyePosition());
_myCamera.setOrientation(myAvatar->getMyHead()->getHeadOrientation());
}
}
else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) {
} else if (mode == CAMERA_MODE_THIRD_PERSON || mode == CAMERA_MODE_LOOK_AT || mode == CAMERA_MODE_SELFIE) {
if (isHMDMode()) {
if (!_thirdPersonHMDCameraBoomValid) {
const glm::vec3 CAMERA_OFFSET = glm::vec3(0.0f, 0.0f, 0.7f);
_thirdPersonHMDCameraBoom = cancelOutRollAndPitch(myAvatar->getHMDSensorOrientation()) * CAMERA_OFFSET;
@ -3612,22 +3630,28 @@ void Application::updateCamera(RenderArgs& renderArgs, float deltaTime) {
_myCamera.setOrientation(glm::normalize(glmExtractRotation(worldCameraMat)));
_myCamera.setPosition(extractTranslation(worldCameraMat));
}
else {
} else {
_thirdPersonHMDCameraBoomValid = false;
_myCamera.setOrientation(myAvatar->getHead()->getOrientation());
if (isOptionChecked(MenuOption::CenterPlayerInView)) {
_myCamera.setPosition(myAvatar->getDefaultEyePosition()
+ _myCamera.getOrientation() * boomOffset);
}
else {
_myCamera.setPosition(myAvatar->getDefaultEyePosition()
+ myAvatar->getWorldOrientation() * boomOffset);
if (mode == CAMERA_MODE_THIRD_PERSON) {
_myCamera.setOrientation(myAvatar->getHead()->getOrientation());
if (isOptionChecked(MenuOption::CenterPlayerInView)) {
_myCamera.setPosition(myAvatar->getDefaultEyePosition()
+ _myCamera.getOrientation() * boomOffset);
} else {
_myCamera.setPosition(myAvatar->getDefaultEyePosition()
+ myAvatar->getWorldOrientation() * boomOffset);
}
} else {
glm::quat lookAtRotation = myAvatar->getLookAtRotation();
if (mode == CAMERA_MODE_SELFIE) {
lookAtRotation = lookAtRotation * glm::angleAxis(PI, myAvatar->getWorldOrientation() * Vectors::UP);
}
_myCamera.setPosition(myAvatar->getLookAtPivotPoint()
+ lookAtRotation * boomOffset);
_myCamera.lookAt(myAvatar->getLookAtPivotPoint());
}
}
}
else if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
} else if (mode == CAMERA_MODE_MIRROR) {
_thirdPersonHMDCameraBoomValid= false;
if (isHMDMode()) {
@ -3652,8 +3676,7 @@ void Application::updateCamera(RenderArgs& renderArgs, float deltaTime) {
+ glm::vec3(0, _raiseMirror * myAvatar->getModelScale(), 0)
+ mirrorBodyOrientation * glm::vec3(0.0f, 0.0f, 1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror
+ mirrorBodyOrientation * hmdOffset);
}
else {
} else {
auto userInputMapper = DependencyManager::get<UserInputMapper>();
const float YAW_SPEED = TWO_PI / 5.0f;
float deltaYaw = userInputMapper->getActionState(controller::Action::YAW) * YAW_SPEED * deltaTime;
@ -3665,8 +3688,7 @@ void Application::updateCamera(RenderArgs& renderArgs, float deltaTime) {
glm::vec3(0.0f, 0.0f, -1.0f) * myAvatar->getBoomLength() * _scaleMirror);
}
renderArgs._renderMode = RenderArgs::MIRROR_RENDER_MODE;
}
else if (_myCamera.getMode() == CAMERA_MODE_ENTITY) {
} else if (mode == CAMERA_MODE_ENTITY) {
_thirdPersonHMDCameraBoomValid= false;
EntityItemPointer cameraEntity = _myCamera.getCameraEntityPointer();
if (cameraEntity != nullptr) {
@ -3675,8 +3697,7 @@ void Application::updateCamera(RenderArgs& renderArgs, float deltaTime) {
_myCamera.setOrientation(cameraEntity->getWorldOrientation() * hmdRotation);
glm::vec3 hmdOffset = extractTranslation(myAvatar->getHMDSensorMatrix());
_myCamera.setPosition(cameraEntity->getWorldPosition() + (hmdRotation * hmdOffset));
}
else {
} else {
_myCamera.setOrientation(cameraEntity->getWorldOrientation());
_myCamera.setPosition(cameraEntity->getWorldPosition());
}
@ -4384,12 +4405,12 @@ void Application::keyPressEvent(QKeyEvent* event) {
}
case Qt::Key_2: {
Menu* menu = Menu::getInstance();
menu->triggerOption(MenuOption::FullscreenMirror);
menu->triggerOption(MenuOption::SelfieCamera);
break;
}
case Qt::Key_3: {
Menu* menu = Menu::getInstance();
menu->triggerOption(MenuOption::ThirdPerson);
menu->triggerOption(MenuOption::LookAtCamera);
break;
}
case Qt::Key_4:
@ -5481,7 +5502,7 @@ void Application::loadSettings() {
// dictated that we should be in first person
Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, isFirstPerson);
Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, !isFirstPerson);
_myCamera.setMode((isFirstPerson) ? CAMERA_MODE_FIRST_PERSON : CAMERA_MODE_THIRD_PERSON);
_myCamera.setMode((isFirstPerson) ? CAMERA_MODE_FIRST_PERSON : CAMERA_MODE_LOOK_AT);
cameraMenuChanged();
auto inputs = pluginManager->getInputPlugins();
@ -5839,11 +5860,16 @@ void Application::cycleCamera() {
} else if (menu->isOptionChecked(MenuOption::FirstPerson)) {
menu->setIsOptionChecked(MenuOption::FirstPerson, false);
menu->setIsOptionChecked(MenuOption::ThirdPerson, true);
menu->setIsOptionChecked(MenuOption::LookAtCamera, true);
} else if (menu->isOptionChecked(MenuOption::ThirdPerson)) {
} else if (menu->isOptionChecked(MenuOption::LookAtCamera)) {
menu->setIsOptionChecked(MenuOption::ThirdPerson, false);
menu->setIsOptionChecked(MenuOption::LookAtCamera, false);
menu->setIsOptionChecked(MenuOption::SelfieCamera, true);
} else if (menu->isOptionChecked(MenuOption::SelfieCamera)) {
menu->setIsOptionChecked(MenuOption::SelfieCamera, false);
menu->setIsOptionChecked(MenuOption::FullscreenMirror, true);
}
@ -5855,11 +5881,11 @@ void Application::cameraModeChanged() {
case CAMERA_MODE_FIRST_PERSON:
Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, true);
break;
case CAMERA_MODE_THIRD_PERSON:
Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, true);
case CAMERA_MODE_LOOK_AT:
Menu::getInstance()->setIsOptionChecked(MenuOption::LookAtCamera, true);
break;
case CAMERA_MODE_MIRROR:
Menu::getInstance()->setIsOptionChecked(MenuOption::FullscreenMirror, true);
case CAMERA_MODE_SELFIE:
Menu::getInstance()->setIsOptionChecked(MenuOption::SelfieCamera, true);
break;
default:
// we don't have menu items for the others, so just leave it alone.
@ -5875,32 +5901,32 @@ void Application::changeViewAsNeeded(float boomLength) {
if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON && boomLengthGreaterThanMinimum) {
Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, false);
Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, true);
Menu::getInstance()->setIsOptionChecked(MenuOption::LookAtCamera, true);
cameraMenuChanged();
} else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON && !boomLengthGreaterThanMinimum) {
} else if (_myCamera.getMode() == CAMERA_MODE_LOOK_AT && !boomLengthGreaterThanMinimum) {
Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, true);
Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, false);
Menu::getInstance()->setIsOptionChecked(MenuOption::LookAtCamera, false);
cameraMenuChanged();
}
}
void Application::cameraMenuChanged() {
auto menu = Menu::getInstance();
if (menu->isOptionChecked(MenuOption::FullscreenMirror)) {
if (!isHMDMode() && _myCamera.getMode() != CAMERA_MODE_MIRROR) {
_mirrorYawOffset = 0.0f;
_myCamera.setMode(CAMERA_MODE_MIRROR);
getMyAvatar()->reset(false, false, false); // to reset any active MyAvatar::FollowHelpers
getMyAvatar()->setBoomLength(MyAvatar::ZOOM_DEFAULT);
}
} else if (menu->isOptionChecked(MenuOption::FirstPerson)) {
if (menu->isOptionChecked(MenuOption::FirstPerson)) {
if (_myCamera.getMode() != CAMERA_MODE_FIRST_PERSON) {
_myCamera.setMode(CAMERA_MODE_FIRST_PERSON);
getMyAvatar()->setBoomLength(MyAvatar::ZOOM_MIN);
}
} else if (menu->isOptionChecked(MenuOption::ThirdPerson)) {
if (_myCamera.getMode() != CAMERA_MODE_THIRD_PERSON) {
_myCamera.setMode(CAMERA_MODE_THIRD_PERSON);
} else if (menu->isOptionChecked(MenuOption::LookAtCamera)) {
if (_myCamera.getMode() != CAMERA_MODE_LOOK_AT) {
_myCamera.setMode(CAMERA_MODE_LOOK_AT);
if (getMyAvatar()->getBoomLength() == MyAvatar::ZOOM_MIN) {
getMyAvatar()->setBoomLength(MyAvatar::ZOOM_DEFAULT);
}
}
} else if (menu->isOptionChecked(MenuOption::SelfieCamera)) {
if (_myCamera.getMode() != CAMERA_MODE_SELFIE) {
_myCamera.setMode(CAMERA_MODE_SELFIE);
if (getMyAvatar()->getBoomLength() == MyAvatar::ZOOM_MIN) {
getMyAvatar()->setBoomLength(MyAvatar::ZOOM_DEFAULT);
}
@ -7441,6 +7467,7 @@ void Application::registerScriptEngineWithApplicationServices(const ScriptEngine
// Caches
scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get<AnimationCacheScriptingInterface>().data());
scriptEngine->registerGlobalObject("TextureCache", DependencyManager::get<TextureCacheScriptingInterface>().data());
scriptEngine->registerGlobalObject("MaterialCache", DependencyManager::get<MaterialCacheScriptingInterface>().data());
scriptEngine->registerGlobalObject("ModelCache", DependencyManager::get<ModelCacheScriptingInterface>().data());
scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get<SoundCacheScriptingInterface>().data());
@ -8474,6 +8501,8 @@ void Application::toggleLogDialog() {
Qt::WindowFlags flags = _logDialog->windowFlags() | Qt::Tool;
_logDialog->setWindowFlags(flags);
}
#else
Q_UNUSED(keepOnTop)
#endif
}
@ -8990,9 +9019,9 @@ void Application::setDisplayPlugin(DisplayPluginPointer newDisplayPlugin) {
cameraMenuChanged();
}
// Remove the mirror camera option from menu if in HMD mode
auto mirrorAction = menu->getActionForOption(MenuOption::FullscreenMirror);
mirrorAction->setVisible(!isHmd);
// Remove the selfie camera options from menu if in HMD mode
auto selfieAction = menu->getActionForOption(MenuOption::SelfieCamera);
selfieAction->setVisible(!isHmd);
}
Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin");

View file

@ -177,19 +177,19 @@ Menu::Menu() {
firstPersonAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
// View > Third Person
auto thirdPersonAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(
viewMenu, MenuOption::ThirdPerson, 0,
// View > Look At
auto lookAtAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(
viewMenu, MenuOption::LookAtCamera, 0,
false, qApp, SLOT(cameraMenuChanged())));
thirdPersonAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
lookAtAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
// View > Mirror
auto viewMirrorAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(
viewMenu, MenuOption::FullscreenMirror, 0,
false, qApp, SLOT(cameraMenuChanged())));
// View > Selfie
auto selfieAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(
viewMenu, MenuOption::SelfieCamera, 0,
false, qApp, SLOT(cameraMenuChanged())));
viewMirrorAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
selfieAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
viewMenu->addSeparator();

View file

@ -129,6 +129,7 @@ namespace MenuOption {
const QString Login = "Login/Sign Up";
const QString Log = "Log";
const QString LogExtraTimings = "Log Extra Timing Details";
const QString LookAtCamera = "Third Person";
const QString LowVelocityFilter = "Low Velocity Filter";
const QString MeshVisible = "Draw Mesh";
const QString MuteEnvironment = "Mute Environment";
@ -181,6 +182,7 @@ namespace MenuOption {
const QString RunTimingTests = "Run Timing Tests";
const QString ScriptedMotorControl = "Enable Scripted Motor Control";
const QString ShowTrackedObjects = "Show Tracked Objects";
const QString SelfieCamera = "Selfie";
const QString SendWrongDSConnectVersion = "Send wrong DS connect version";
const QString SendWrongProtocolVersion = "Send wrong protocol version";
const QString SetHomeLocation = "Set Home Location";
@ -201,7 +203,7 @@ namespace MenuOption {
const QString AnimStats = "Show Animation Stats";
const QString StopAllScripts = "Stop All Scripts";
const QString SuppressShortTimings = "Suppress Timings Less than 10ms";
const QString ThirdPerson = "Third Person";
const QString ThirdPerson = "Third Person Legacy";
const QString ThreePointCalibration = "3 Point Calibration";
const QString ThrottleFPSIfNotFocus = "Throttle FPS If Not Focus"; // FIXME - this value duplicated in Basic2DWindowOpenGLDisplayPlugin.cpp
const QString ToggleHipsFollowing = "Toggle Hips Following";

View file

@ -99,6 +99,16 @@ static const QString USER_RECENTER_MODEL_FORCE_STAND = QStringLiteral("ForceStan
static const QString USER_RECENTER_MODEL_AUTO = QStringLiteral("Auto");
static const QString USER_RECENTER_MODEL_DISABLE_HMD_LEAN = QStringLiteral("DisableHMDLean");
const QString HEAD_BLEND_DIRECTIONAL_ALPHA_NAME = "lookAroundAlpha";
const QString HEAD_BLEND_LINEAR_ALPHA_NAME = "lookBlendAlpha";
const float HEAD_ALPHA_BLENDING = 1.0f;
const QString POINT_REACTION_NAME = "point";
const QString POINT_BLEND_DIRECTIONAL_ALPHA_NAME = "pointAroundAlpha";
const QString POINT_BLEND_LINEAR_ALPHA_NAME = "pointBlendAlpha";
const QString POINT_REF_JOINT_NAME = "RightShoulder";
const float POINT_ALPHA_BLENDING = 1.0f;
MyAvatar::SitStandModelType stringToUserRecenterModel(const QString& str) {
if (str == USER_RECENTER_MODEL_FORCE_SIT) {
return MyAvatar::ForceSit;
@ -451,7 +461,7 @@ QByteArray MyAvatar::toByteArrayStateful(AvatarDataDetail dataDetail, bool dropF
_globalBoundingBoxDimensions.y = _characterController.getCapsuleHalfHeight();
_globalBoundingBoxDimensions.z = _characterController.getCapsuleRadius();
_globalBoundingBoxOffset = _characterController.getCapsuleLocalOffset();
if (mode == CAMERA_MODE_THIRD_PERSON || mode == CAMERA_MODE_INDEPENDENT) {
if (mode == CAMERA_MODE_THIRD_PERSON || mode == CAMERA_MODE_INDEPENDENT || mode == CAMERA_MODE_LOOK_AT || mode == CAMERA_MODE_SELFIE) {
// fake the avatar position that is sent up to the AvatarMixer
glm::vec3 oldPosition = getWorldPosition();
setWorldPosition(getSkeletonPosition());
@ -944,10 +954,20 @@ void MyAvatar::simulate(float deltaTime, bool inView) {
qCDebug(interfaceapp) << "MyAvatar::simulate headPosition is NaN";
headPosition = glm::vec3(0.0f);
}
head->setPosition(headPosition);
head->setScale(getModelScale());
head->simulate(deltaTime);
CameraMode mode = qApp->getCamera().getMode();
if (_scriptControlsHeadLookAt || mode == CAMERA_MODE_FIRST_PERSON || mode == CAMERA_MODE_LOOK_AT || mode == CAMERA_MODE_SELFIE) {
if (!_pointAtActive || !_isPointTargetValid) {
updateHeadLookAt(deltaTime);
} else {
resetHeadLookAt();
}
} else if (_headLookAtActive){
resetHeadLookAt();
_headLookAtActive = false;
}
}
// Record avatars movements.
@ -2610,7 +2630,7 @@ void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelN
glm::vec3 MyAvatar::getSkeletonPosition() const {
CameraMode mode = qApp->getCamera().getMode();
if (mode == CAMERA_MODE_THIRD_PERSON || mode == CAMERA_MODE_INDEPENDENT) {
if (mode == CAMERA_MODE_THIRD_PERSON || mode == CAMERA_MODE_INDEPENDENT || mode == CAMERA_MODE_LOOK_AT || mode == CAMERA_MODE_SELFIE) {
// The avatar is rotated PI about the yAxis, so we have to correct for it
// to get the skeleton offset contribution in the world-frame.
const glm::quat FLIP = glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f));
@ -2698,7 +2718,12 @@ void MyAvatar::updateMotors() {
if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) {
if (_characterController.getState() == CharacterController::State::Hover ||
_characterController.computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS) {
motorRotation = getMyHead()->getHeadOrientation();
CameraMode mode = qApp->getCamera().getMode();
if (mode == CAMERA_MODE_FIRST_PERSON || mode == CAMERA_MODE_LOOK_AT || mode == CAMERA_MODE_SELFIE) {
motorRotation = getLookAtRotation();
} else {
motorRotation = getMyHead()->getHeadOrientation();
}
} else {
// non-hovering = walking: follow camera twist about vertical but not lift
// we decompose camera's rotation and store the twist part in motorRotation
@ -3133,7 +3158,6 @@ void MyAvatar::initAnimGraph() {
}
emit animGraphUrlChanged(graphUrl);
_skeletonModel->getRig().initAnimGraph(graphUrl);
_currentAnimGraphUrl.set(graphUrl);
connect(&(_skeletonModel->getRig()), SIGNAL(onLoadComplete()), this, SLOT(animGraphLoaded()));
@ -3416,11 +3440,21 @@ void MyAvatar::setRotationThreshold(float angleRadians) {
}
void MyAvatar::updateOrientation(float deltaTime) {
// Smoothly rotate body with arrow keys
float targetSpeed = getDriveKey(YAW) * _yawSpeed;
CameraMode mode = qApp->getCamera().getMode();
bool computeLookAt = isReadyForPhysics() && !qApp->isHMDMode() &&
(mode == CAMERA_MODE_FIRST_PERSON || mode == CAMERA_MODE_LOOK_AT || mode == CAMERA_MODE_SELFIE);
bool smoothCameraYaw = computeLookAt && mode != CAMERA_MODE_FIRST_PERSON;
if (smoothCameraYaw) {
// For "Look At" and "Selfie" camera modes we also smooth the yaw rotation from right-click mouse movement.
float speedFromDeltaYaw = deltaTime > FLT_EPSILON ? getDriveKey(DELTA_YAW) / deltaTime : 0.0f;
speedFromDeltaYaw *= _yawSpeed / YAW_SPEED_DEFAULT;
targetSpeed += speedFromDeltaYaw;
}
if (targetSpeed != 0.0f) {
const float ROTATION_RAMP_TIMESCALE = 0.1f;
const float ROTATION_RAMP_TIMESCALE = 0.5f;
float blend = deltaTime / ROTATION_RAMP_TIMESCALE;
if (blend > 1.0f) {
blend = 1.0f;
@ -3441,10 +3475,10 @@ void MyAvatar::updateOrientation(float deltaTime) {
}
}
float totalBodyYaw = _bodyYawDelta * deltaTime;
// Rotate directly proportional to delta yaw and delta pitch from right-click mouse movement.
totalBodyYaw += getDriveKey(DELTA_YAW) * _yawSpeed / YAW_SPEED_DEFAULT;
if (!smoothCameraYaw) {
// Rotate directly proportional to delta yaw and delta pitch from right-click mouse movement.
totalBodyYaw += getDriveKey(DELTA_YAW) * _yawSpeed / YAW_SPEED_DEFAULT;
}
// Comfort Mode: If you press any of the left/right rotation drive keys or input, you'll
// get an instantaneous 15 degree turn. If you keep holding the key down you'll get another
// snap turn every half second.
@ -3453,7 +3487,6 @@ void MyAvatar::updateOrientation(float deltaTime) {
totalBodyYaw += getDriveKey(STEP_YAW);
snapTurn = true;
}
// Use head/HMD roll to turn while flying, but not when standing still.
if (qApp->isHMDMode() && getCharacterController()->getState() == CharacterController::State::Hover && _hmdRollControlEnabled && hasDriveInput()) {
@ -3488,7 +3521,68 @@ void MyAvatar::updateOrientation(float deltaTime) {
// update body orientation by movement inputs
glm::quat initialOrientation = getOrientationOutbound();
setWorldOrientation(getWorldOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, totalBodyYaw, 0.0f))));
glm::vec3 eyesPosition = getDefaultEyePosition();
const float FPS = 60.0f;
float timeScale = deltaTime * FPS;
bool faceForward = false;
bool isMovingFwdBwd = getDriveKey(TRANSLATE_Z) != 0.0f;
bool isMovingSideways = getDriveKey(TRANSLATE_X) != 0.0f;
bool isCameraYawing = getDriveKey(DELTA_YAW) + getDriveKey(STEP_YAW) + getDriveKey(YAW) != 0.0f;
bool isRotatingWhileSeated = !isCameraYawing && isMovingSideways && _characterController.getSeated();
glm::quat previousOrientation = getWorldOrientation();
if (!computeLookAt) {
setWorldOrientation(getWorldOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, totalBodyYaw, 0.0f))));
_lookAtCameraTarget = eyesPosition + getWorldOrientation() * Vectors::FRONT;
_lookAtYaw = getWorldOrientation();
_lookAtPitch = Quaternions::IDENTITY;
} else {
// Compute new look at vectors
if (totalBodyYaw != 0.0f) {
_lookAtYaw = _lookAtYaw * glm::quat(glm::radians(glm::vec3(0.0f, totalBodyYaw, 0.0f)));
}
float pitchIncrement = getDriveKey(PITCH) * _pitchSpeed * deltaTime
+ getDriveKey(DELTA_PITCH) * _pitchSpeed / PITCH_SPEED_DEFAULT;
if (pitchIncrement != 0.0f) {
glm::quat _previousLookAtPitch = _lookAtPitch;
_lookAtPitch = _lookAtPitch * glm::quat(glm::radians(glm::vec3(pitchIncrement, 0.0f, 0.0f)));
// Limit the camera horizontal pitch
float MAX_LOOK_AT_PITCH_DEGREES = 80.0f;
float pitchFromHorizont = glm::degrees(angleBetween(getLookAtRotation() * Vectors::FRONT, _lookAtYaw * Vectors::FRONT));
if (pitchFromHorizont > MAX_LOOK_AT_PITCH_DEGREES) {
_lookAtPitch = _previousLookAtPitch;
}
}
faceForward = isMovingFwdBwd || (isMovingSideways && !isRotatingWhileSeated);
// Blend the avatar orientation with the camera look at if moving forward.
if (faceForward || _shouldTurnToFaceCamera) {
const float REORIENT_FORWARD_BLEND = 0.25f;
const float REORIENT_TURN_BLEND = 0.03f;
const float DIAGONAL_TURN_BLEND = 0.02f;
float blend = (_shouldTurnToFaceCamera ? REORIENT_TURN_BLEND : REORIENT_FORWARD_BLEND) * timeScale;
if (blend > 1.0f) {
blend = 1.0f;
}
glm::quat faceRotation = _lookAtYaw;
if (isMovingFwdBwd) {
if (isMovingSideways) {
// Reorient avatar to face camera diagonal
blend = mode == CAMERA_MODE_FIRST_PERSON ? 1.0f : DIAGONAL_TURN_BLEND;
float turnSign = getDriveKey(TRANSLATE_Z) < 0.0f ? -1.0f : 1.0f;
turnSign = getDriveKey(TRANSLATE_X) > 0.0f ? -turnSign : turnSign;
faceRotation = _lookAtYaw * glm::angleAxis(turnSign * 0.25f * PI, Vectors::UP);
} else if (mode == CAMERA_MODE_FIRST_PERSON) {
blend = 1.0f;
}
}
setWorldOrientation(glm::slerp(getWorldOrientation(), faceRotation, blend));
} else if (isRotatingWhileSeated) {
float rotatingWhileSeatedYaw = -getDriveKey(TRANSLATE_X) * _yawSpeed * deltaTime;
setWorldOrientation(getWorldOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, rotatingWhileSeatedYaw, 0.0f))));
}
}
if (snapTurn) {
// Whether or not there is an existing smoothing going on, just reset the smoothing timer and set the starting position as the avatar's current position, then smooth to the new position.
@ -3509,9 +3603,105 @@ void MyAvatar::updateOrientation(float deltaTime) {
head->setBaseYaw(YAW(euler));
head->setBasePitch(PITCH(euler));
head->setBaseRoll(ROLL(euler));
} else if (computeLookAt) {
// Reset head orientation before applying the blending offset
head->setBaseYaw(0.0f);
head->setBasePitch(0.0f);
head->setBaseRoll(0.0f);
glm::vec3 cameraVector = (faceForward ? _lookAtPitch * getWorldOrientation() : getLookAtRotation()) * Vectors::FRONT;
glm::vec3 cameraYawVector = _lookAtYaw * Vectors::FRONT;
// Cap and attenuate head's lookat pitch angle
const float START_LOOKING_UP_DEGREES = 5.0f;
const float START_LOOKING_DOWN_DEGREES = 15.0f;
const float MAX_UP_DOWN_DEGREES = 90.0f;
glm::vec3 avatarVectorUp = getWorldOrientation() * Vectors::UP;
float upDownDot = glm::dot(cameraVector, avatarVectorUp);
float upDownDegrees = MAX_UP_DOWN_DEGREES - glm::degrees(acosf(abs(upDownDot)));
float lookAttenuation = 0.0f;
if (upDownDot <= 0.0f) {
if (upDownDegrees > START_LOOKING_DOWN_DEGREES) {
lookAttenuation = (upDownDegrees - START_LOOKING_DOWN_DEGREES) / (MAX_UP_DOWN_DEGREES - START_LOOKING_DOWN_DEGREES);
}
} else {
if (upDownDegrees > START_LOOKING_UP_DEGREES) {
lookAttenuation = (upDownDegrees - START_LOOKING_UP_DEGREES) / (MAX_UP_DOWN_DEGREES - START_LOOKING_UP_DEGREES);
}
}
glm::vec3 avatarVectorFront = getWorldOrientation() * Vectors::FRONT;
float frontBackDot = glm::dot(cameraYawVector, avatarVectorFront);
glm::vec3 avatarVectorRight = getWorldOrientation() * Vectors::RIGHT;
float leftRightDot = glm::dot(cameraYawVector, avatarVectorRight);
const float DEFAULT_REORIENT_ANGLE = 65.0f;
const float FIRST_PERSON_REORIENT_ANGLE = 95.0f;
const float TRIGGER_REORIENT_ANGLE = 45.0f;
const float FIRST_PERSON_TRIGGER_REORIENT_ANGLE = 65.0f;
glm::vec3 ajustedYawVector = cameraYawVector;
float limitAngle = 0.0f;
float triggerAngle = -glm::sin(glm::radians(TRIGGER_REORIENT_ANGLE));
if (mode == CAMERA_MODE_FIRST_PERSON) {
limitAngle = glm::sin(glm::radians(90.0f - FIRST_PERSON_TRIGGER_REORIENT_ANGLE));
triggerAngle = limitAngle;
}
float reorientAngle = mode == CAMERA_MODE_FIRST_PERSON ? FIRST_PERSON_REORIENT_ANGLE : DEFAULT_REORIENT_ANGLE;
if (frontBackDot < limitAngle) {
if (frontBackDot < 0.0f) {
ajustedYawVector = (leftRightDot < 0.0f ? -avatarVectorRight : avatarVectorRight);
cameraVector = (ajustedYawVector * _lookAtPitch) * Vectors::FRONT;
}
if (!isRotatingWhileSeated) {
if (frontBackDot < triggerAngle) {
_shouldTurnToFaceCamera = true;
_firstPersonSteadyHeadTimer = 0.0f;
}
} else {
setWorldOrientation(previousOrientation);
}
} else if (frontBackDot > glm::sin(glm::radians(reorientAngle))) {
_shouldTurnToFaceCamera = false;
}
cameraVector = glm::mix(cameraVector, ajustedYawVector, 1.0f - lookAttenuation);
// Calculate the camera target point.
glm::vec3 targetPoint = eyesPosition + glm::normalize(cameraVector);
const float LOOKAT_MIX_ALPHA = 0.25f;
if (!isFlying() || !hasDriveInput()) {
// Approximate the head's look at vector to the camera look at vector with some delay.
float mixAlpha = LOOKAT_MIX_ALPHA * timeScale;
if (mixAlpha > 1.0f) {
mixAlpha = 1.0f;
}
_lookAtCameraTarget = glm::mix(_lookAtCameraTarget, targetPoint, mixAlpha);
} else {
_lookAtCameraTarget = targetPoint;
}
_headLookAtActive = true;
const float FIRST_PERSON_RECENTER_SECONDS = 15.0f;
if (mode == CAMERA_MODE_FIRST_PERSON) {
if (getDriveKey(YAW) + getDriveKey(STEP_YAW) + getDriveKey(DELTA_YAW) == 0.0f) {
if (_firstPersonSteadyHeadTimer < FIRST_PERSON_RECENTER_SECONDS) {
if (_firstPersonSteadyHeadTimer > 0.0f) {
_firstPersonSteadyHeadTimer += deltaTime;
}
} else {
_shouldTurnToFaceCamera = true;
_firstPersonSteadyHeadTimer = 0.0f;
}
} else {
_firstPersonSteadyHeadTimer = deltaTime;
}
}
} else {
head->setBaseYaw(0.0f);
head->setBasePitch(getHead()->getBasePitch() + getDriveKey(PITCH) * _pitchSpeed * deltaTime
head->setBasePitch(getHead()->getBasePitch() + getDriveKey(PITCH) * _pitchSpeed * deltaTime
+ getDriveKey(DELTA_PITCH) * _pitchSpeed / PITCH_SPEED_DEFAULT);
head->setBaseRoll(0.0f);
}
@ -3543,7 +3733,7 @@ float MyAvatar::calculateGearedSpeed(const float driveKey) {
glm::vec3 MyAvatar::scaleMotorSpeed(const glm::vec3 forward, const glm::vec3 right) {
float stickFullOn = 0.85f;
auto zSpeed = getDriveKey(TRANSLATE_Z);
auto xSpeed = getDriveKey(TRANSLATE_X);
auto xSpeed = !_characterController.getSeated() ? getDriveKey(TRANSLATE_X) : 0.0f;
glm::vec3 direction;
if (!useAdvancedMovementControls() && qApp->isHMDMode()) {
// Walking disabled in settings.
@ -3581,6 +3771,11 @@ glm::vec3 MyAvatar::scaleMotorSpeed(const glm::vec3 forward, const glm::vec3 rig
} else {
// Desktop mode.
direction = (zSpeed * forward) + (xSpeed * right);
CameraMode mode = qApp->getCamera().getMode();
if ((mode == CAMERA_MODE_LOOK_AT || mode == CAMERA_MODE_FIRST_PERSON || mode == CAMERA_MODE_SELFIE) && zSpeed != 0.0f && xSpeed != 0.0f){
direction = (zSpeed * forward);
}
auto length = glm::length(direction);
if (length > EPSILON) {
direction /= length;
@ -4005,6 +4200,7 @@ void MyAvatar::goToLocation(const glm::vec3& newPosition,
_goToOrientation = quatOrientation;
}
resetLookAtRotation(_goToPosition, _goToOrientation);
emit transformChanged();
}
@ -4191,7 +4387,8 @@ bool MyAvatar::isFlying() {
bool MyAvatar::isInAir() {
// If Avatar is Hover, Falling, or Taking off, they are in Air.
return _characterController.getState() != CharacterController::State::Ground;
return _characterController.getState() != CharacterController::State::Ground &&
_characterController.getState() != CharacterController::State::Seated;
}
bool MyAvatar::getFlyingEnabled() {
@ -5236,9 +5433,13 @@ glm::quat MyAvatar::getOrientationForAudio() {
glm::quat result;
switch (_audioListenerMode) {
case AudioListenerMode::FROM_HEAD:
result = getHead()->getFinalOrientationInWorldFrame();
case AudioListenerMode::FROM_HEAD: {
// Using the camera's orientation instead, when the current mode is controlling the avatar's head.
CameraMode mode = qApp->getCamera().getMode();
bool headFollowsCamera = mode == CAMERA_MODE_FIRST_PERSON || mode == CAMERA_MODE_LOOK_AT || mode == CAMERA_MODE_SELFIE;
result = headFollowsCamera ? qApp->getCamera().getOrientation() : getHead()->getFinalOrientationInWorldFrame();
break;
}
case AudioListenerMode::FROM_CAMERA:
result = qApp->getCamera().getOrientation();
break;
@ -5853,6 +6054,7 @@ bool MyAvatar::pinJoint(int index, const glm::vec3& position, const glm::quat& o
}
slamPosition(position);
resetLookAtRotation(position, orientation);
setWorldOrientation(orientation);
auto it = std::find(_pinnedJoints.begin(), _pinnedJoints.end(), index);
@ -5957,6 +6159,10 @@ bool MyAvatar::beginReaction(QString reactionName) {
if (reactionIndex >= 0 && reactionIndex < (int)NUM_AVATAR_BEGIN_END_REACTIONS) {
std::lock_guard<std::mutex> guard(_reactionLock);
_reactionEnabledRefCounts[reactionIndex]++;
if (reactionName == POINT_REACTION_NAME) {
_pointAtActive = true;
_isPointTargetValid = true;
}
return true;
}
return false;
@ -5966,13 +6172,18 @@ bool MyAvatar::endReaction(QString reactionName) {
int reactionIndex = beginEndReactionNameToIndex(reactionName);
if (reactionIndex >= 0 && reactionIndex < (int)NUM_AVATAR_BEGIN_END_REACTIONS) {
std::lock_guard<std::mutex> guard(_reactionLock);
bool wasReactionActive = true;
if (_reactionEnabledRefCounts[reactionIndex] > 0) {
_reactionEnabledRefCounts[reactionIndex]--;
return true;
wasReactionActive = true;
} else {
_reactionEnabledRefCounts[reactionIndex] = 0;
return false;
wasReactionActive = false;
}
if (reactionName == POINT_REACTION_NAME) {
_pointAtActive = _reactionEnabledRefCounts[reactionIndex] > 0;
}
return wasReactionActive;
}
return false;
}
@ -5983,10 +6194,13 @@ void MyAvatar::updateRigControllerParameters(Rig::ControllerParameters& params)
for (int i = 0; i < TRIGGER_REACTION_NAMES.size(); i++) {
params.reactionTriggers[i] = _reactionTriggers[i];
}
int pointReactionIndex = beginEndReactionNameToIndex("point");
for (int i = 0; i < BEGIN_END_REACTION_NAMES.size(); i++) {
// copy current state into params.
params.reactionEnabledFlags[i] = _reactionEnabledRefCounts[i] > 0;
if (params.reactionEnabledFlags[i] && i == pointReactionIndex) {
params.reactionEnabledFlags[i] = _isPointTargetValid;
}
}
for (int i = 0; i < TRIGGER_REACTION_NAMES.size(); i++) {
@ -6334,7 +6548,6 @@ void MyAvatar::sendPacket(const QUuid& entityID) const {
void MyAvatar::setSitDriveKeysStatus(bool enabled) {
const std::vector<DriveKeys> DISABLED_DRIVE_KEYS_DURING_SIT = {
DriveKeys::TRANSLATE_X,
DriveKeys::TRANSLATE_Y,
DriveKeys::TRANSLATE_Z,
DriveKeys::STEP_TRANSLATE_X,
@ -6512,3 +6725,116 @@ void MyAvatar::updateLookAtPosition(FaceTracker* faceTracker, Camera& myCamera)
getHead()->setLookAtPosition(lookAtSpot);
}
glm::vec3 MyAvatar::aimToBlendValues(const glm::vec3& aimVector, const glm::quat& frameOrientation) {
// This method computes the values for the directional blending animation node
glm::vec3 uVector = glm::normalize(frameOrientation * Vectors::UNIT_X);
glm::vec3 vVector = glm::normalize(frameOrientation * Vectors::UNIT_Y);
glm::vec3 aimDirection;
if (glm::length(aimVector) > EPSILON) {
aimDirection = glm::normalize(aimVector);
} else {
// aim vector is zero
return glm::vec3();
}
float xDot = glm::dot(uVector, aimDirection);
float yDot = glm::dot(vVector, aimDirection);
// Make sure dot products are in range to avoid acosf returning NaN
xDot = glm::min(glm::max(xDot, -1.0f), 1.0f);
yDot = glm::min(glm::max(yDot, -1.0f), 1.0f);
float xAngle = acosf(xDot);
float yAngle = acosf(yDot);
// xBlend and yBlend are the values from -1.0 to 1.0 that set the directional blending.
// We compute them using the angles (0 to PI/2) => (1.0 to 0.0) and (PI/2 to PI) => (0.0 to -1.0)
float xBlend = -(xAngle - 0.5f * PI) / (0.5f * PI);
float yBlend = -(yAngle - 0.5f * PI) / (0.5f * PI);
glm::vec3 blendValues = glm::vec3(xBlend, yBlend, 0.0f);
return blendValues;
}
void MyAvatar::resetHeadLookAt() {
if (_skeletonModelLoaded) {
_skeletonModel->getRig().setDirectionalBlending(HEAD_BLEND_DIRECTIONAL_ALPHA_NAME, glm::vec3(),
HEAD_BLEND_LINEAR_ALPHA_NAME, HEAD_ALPHA_BLENDING);
}
}
void MyAvatar::resetLookAtRotation(const glm::vec3& avatarPosition, const glm::quat& avatarOrientation) {
// Align the look at values to the given avatar orientation
float yaw = safeEulerAngles(avatarOrientation).y;
_lookAtYaw = glm::angleAxis(yaw, avatarOrientation * Vectors::UP);
_lookAtPitch = Quaternions::IDENTITY;
_lookAtCameraTarget = avatarPosition + avatarOrientation * Vectors::FRONT;
resetHeadLookAt();
}
void MyAvatar::updateHeadLookAt(float deltaTime) {
if (_skeletonModelLoaded) {
glm::vec3 lookAtTarget = _scriptControlsHeadLookAt ? _lookAtScriptTarget : _lookAtCameraTarget;
glm::vec3 aimVector = lookAtTarget - getDefaultEyePosition();
glm::vec3 lookAtBlend = MyAvatar::aimToBlendValues(aimVector, getWorldOrientation());
_skeletonModel->getRig().setDirectionalBlending(HEAD_BLEND_DIRECTIONAL_ALPHA_NAME, lookAtBlend,
HEAD_BLEND_LINEAR_ALPHA_NAME, HEAD_ALPHA_BLENDING);
if (_scriptControlsHeadLookAt) {
_scriptHeadControlTimer += deltaTime;
if (_scriptHeadControlTimer > MAX_LOOK_AT_TIME_SCRIPT_CONTROL) {
_scriptHeadControlTimer = 0.0f;
_scriptControlsHeadLookAt = false;
_lookAtCameraTarget = _lookAtScriptTarget;
}
}
}
}
void MyAvatar::setHeadLookAt(const glm::vec3& lookAtTarget) {
if (QThread::currentThread() != thread()) {
BLOCKING_INVOKE_METHOD(this, "setHeadLookAt",
Q_ARG(const glm::vec3&, lookAtTarget));
return;
}
_headLookAtActive = true;
_scriptControlsHeadLookAt = true;
_scriptHeadControlTimer = 0.0f;
_lookAtScriptTarget = lookAtTarget;
}
glm::vec3 MyAvatar::getLookAtPivotPoint() {
glm::vec3 avatarUp = getWorldOrientation() * Vectors::UP;
glm::vec3 yAxisEyePosition = getWorldPosition() + avatarUp * glm::dot(avatarUp, _skeletonModel->getDefaultEyeModelPosition());
return yAxisEyePosition;
}
bool MyAvatar::setPointAt(const glm::vec3& pointAtTarget) {
if (QThread::currentThread() != thread()) {
bool result = false;
BLOCKING_INVOKE_METHOD(this, "setPointAt", Q_RETURN_ARG(bool, result),
Q_ARG(const glm::vec3&, pointAtTarget));
return result;
}
if (_skeletonModelLoaded && _pointAtActive) {
glm::vec3 aimVector = pointAtTarget - getJointPosition(POINT_REF_JOINT_NAME);
_isPointTargetValid = glm::dot(aimVector, getWorldOrientation() * Vectors::FRONT) > 0.0f;
if (_isPointTargetValid) {
glm::vec3 pointAtBlend = MyAvatar::aimToBlendValues(aimVector, getWorldOrientation());
_skeletonModel->getRig().setDirectionalBlending(POINT_BLEND_DIRECTIONAL_ALPHA_NAME, pointAtBlend,
POINT_BLEND_LINEAR_ALPHA_NAME, POINT_ALPHA_BLENDING);
}
return _isPointTargetValid;
}
return false;
}
void MyAvatar::resetPointAt() {
if (_skeletonModelLoaded) {
_skeletonModel->getRig().setDirectionalBlending(POINT_BLEND_DIRECTIONAL_ALPHA_NAME, glm::vec3(),
POINT_BLEND_LINEAR_ALPHA_NAME, POINT_ALPHA_BLENDING);
}
}

View file

@ -274,11 +274,12 @@ class MyAvatar : public Avatar {
* the value.</p>
* @property {number} analogPlusSprintSpeed - The sprint (run) speed of your avatar for the "AnalogPlus" control scheme.
* @property {MyAvatar.SitStandModelType} userRecenterModel - Controls avatar leaning and recentering behavior.
* @property {number} isInSittingState - <code>true</code> if your avatar is sitting (avatar leaning is disabled,
* recenntering is enabled), <code>false</code> if it is standing (avatar leaning is enabled, and avatar recenters if it
* leans too far). If <code>userRecenterModel == 2</code> (i.e., auto) the property value automatically updates as the
* user sits or stands, unless <code>isSitStandStateLocked == true</code>. Setting the property value overrides the
* current siting / standing state, which is updated when the user next sits or stands unless
* @property {number} isInSittingState - <code>true</code> if the user wearing the HMD is determined to be sitting
* (avatar leaning is disabled, recenntering is enabled), <code>false</code> if the user wearing the HMD is
* determined to be standing (avatar leaning is enabled, and avatar recenters if it leans too far).
* If <code>userRecenterModel == 2</code> (i.e., auto) the property value automatically updates as the user sits
* or stands, unless <code>isSitStandStateLocked == true</code>. Setting the property value overrides the current
* siting / standing state, which is updated when the user next sits or stands unless
* <code>isSitStandStateLocked == true</code>.
* @property {boolean} isSitStandStateLocked - <code>true</code> to lock the avatar sitting/standing state, i.e., use this
* to disable automatically changing state.
@ -1752,6 +1753,34 @@ public:
glm::vec3 getNextPosition() { return _goToPending ? _goToPosition : getWorldPosition(); }
void prepareAvatarEntityDataForReload();
/**jsdoc
* Turn the avatar's head until it faces the target point within the 90/-90 degree range.
* Once this method is called, API calls will have full control of the head for a limited time.
* If this method is not called for two seconds, the engine will regain control of the head.
* @function MyAvatar.setHeadLookAt
* @param {Vec3} lookAtTarget - The target point in world coordinates.
*/
Q_INVOKABLE void setHeadLookAt(const glm::vec3& lookAtTarget);
/**jsdoc
* Returns the current head look at target point in world coordinates.
* @function MyAvatar.getHeadLookAt
* @returns {Vec3} Default position between your avatar's eyes in world coordinates.
*/
Q_INVOKABLE glm::vec3 getHeadLookAt() { return _lookAtCameraTarget; }
/**jsdoc
* Aims the pointing directional blending towards the provided target point.
* The "point" reaction should be triggered before using this method.
* <code>MyAvatar.beginReaction("point")</code>
* Returns <code>true</code> if the target point lays in front of the avatar.
* @function MyAvatar.setPointAt
* @param {Vec3} pointAtTarget - The target point in world coordinates.
*/
Q_INVOKABLE bool setPointAt(const glm::vec3& pointAtTarget);
glm::quat getLookAtRotation() { return _lookAtYaw * _lookAtPitch; }
/**jsdoc
* Creates a new grab that grabs an entity.
* @function MyAvatar.grab
@ -1865,6 +1894,14 @@ public:
*/
Q_INVOKABLE void endSit(const glm::vec3& position, const glm::quat& rotation);
/**jsdoc
* Gets whether the avatar is in a seated pose. The seated pose is set by calling the
* MyAvatar::beginSit method.
* @function MyAvatar.isSeated
* @returns {boolean} <code>true</code> if the avatar is in a seated pose.
*/
Q_INVOKABLE bool isSeated() { return _characterController.getSeated(); }
int getOverrideJointCount() const;
bool getFlowActive() const;
bool getNetworkGraphActive() const;
@ -1881,6 +1918,7 @@ public:
void debugDrawPose(controller::Action action, const char* channelName, float size);
bool getIsJointOverridden(int jointIndex) const;
glm::vec3 getLookAtPivotPoint();
public slots:
@ -2629,6 +2667,19 @@ private:
glm::vec3 _trackedHeadPosition;
const float MAX_LOOK_AT_TIME_SCRIPT_CONTROL = 2.0f;
glm::quat _lookAtPitch;
glm::quat _lookAtYaw;
glm::vec3 _lookAtCameraTarget;
glm::vec3 _lookAtScriptTarget;
bool _headLookAtActive { false };
bool _shouldTurnToFaceCamera { false };
bool _scriptControlsHeadLookAt { false };
float _scriptHeadControlTimer { 0.0f };
float _firstPersonSteadyHeadTimer { 0.0f };
bool _pointAtActive { false };
bool _isPointTargetValid { true };
Setting::Handle<float> _realWorldFieldOfView;
Setting::Handle<bool> _useAdvancedMovementControls;
Setting::Handle<bool> _showPlayArea;
@ -2653,6 +2704,11 @@ private:
void initHeadBones();
void initAnimGraph();
void initFlowFromFST();
void updateHeadLookAt(float deltaTime);
void resetHeadLookAt();
void resetLookAtRotation(const glm::vec3& avatarPosition, const glm::quat& avatarOrientation);
void resetPointAt();
static glm::vec3 aimToBlendValues(const glm::vec3& aimVector, const glm::quat& frameOrientation);
// Avatar Preferences
QUrl _fullAvatarURLFromPreferences;

View file

@ -440,13 +440,13 @@ void Audio::handlePushedToTalk(bool enabled) {
}
}
void Audio::setInputDevice(const QAudioDeviceInfo& device, bool isHMD) {
void Audio::setInputDevice(const HifiAudioDeviceInfo& device, bool isHMD) {
withWriteLock([&] {
_devices.chooseInputDevice(device, isHMD);
});
}
void Audio::setOutputDevice(const QAudioDeviceInfo& device, bool isHMD) {
void Audio::setOutputDevice(const HifiAudioDeviceInfo& device, bool isHMD) {
withWriteLock([&] {
_devices.chooseOutputDevice(device, isHMD);
});

View file

@ -19,6 +19,7 @@
#include "SettingHandle.h"
#include "AudioFileWav.h"
#include <shared/ReadWriteLockable.h>
#include <HifiAudioDeviceInfo.h>
using MutedGetter = std::function<bool()>;
using MutedSetter = std::function<void(bool)>;
@ -158,7 +159,7 @@ public:
* @param {boolean} isHMD - Is HMD.
* @deprecated This function is deprecated and will be removed.
*/
Q_INVOKABLE void setInputDevice(const QAudioDeviceInfo& device, bool isHMD);
Q_INVOKABLE void setInputDevice(const HifiAudioDeviceInfo& device, bool isHMD);
/**jsdoc
* @function Audio.setOutputDevice
@ -166,7 +167,7 @@ public:
* @param {boolean} isHMD - Is HMD.
* @deprecated This function is deprecated and will be removed.
*/
Q_INVOKABLE void setOutputDevice(const QAudioDeviceInfo& device, bool isHMD);
Q_INVOKABLE void setOutputDevice(const HifiAudioDeviceInfo& device, bool isHMD);
/**jsdoc
* Enables or disables reverberation. Reverberation is done by the client on the post-mix audio. The reverberation options

View file

@ -29,6 +29,8 @@ static Setting::Handle<QString> desktopOutputDeviceSetting { QStringList { Audio
static Setting::Handle<QString> hmdInputDeviceSetting { QStringList { Audio::AUDIO, Audio::HMD, "INPUT" }};
static Setting::Handle<QString> hmdOutputDeviceSetting { QStringList { Audio::AUDIO, Audio::HMD, "OUTPUT" }};
Q_DECLARE_METATYPE(HifiAudioDeviceInfo);
Setting::Handle<QString>& getSetting(bool contextIsHMD, QAudio::Mode mode) {
if (mode == QAudio::AudioInput) {
return contextIsHMD ? hmdInputDeviceSetting : desktopInputDeviceSetting;
@ -64,6 +66,8 @@ static QString getTargetDevice(bool hmd, QAudio::Mode mode) {
} else { // if (_mode == QAudio::AudioOutput)
deviceName = qApp->getActiveDisplayPlugin()->getPreferredAudioOutDevice();
}
} else {
deviceName = HifiAudioDeviceInfo::DEFAULT_DEVICE_NAME;
}
return deviceName;
}
@ -139,7 +143,7 @@ QVariant AudioDeviceList::data(const QModelIndex& index, int role) const {
} else if (role == SelectedHMDRole) {
return _devices.at(index.row())->selectedHMD;
} else if (role == InfoRole) {
return QVariant::fromValue<QAudioDeviceInfo>(_devices.at(index.row())->info);
return QVariant::fromValue<HifiAudioDeviceInfo>(_devices.at(index.row())->info);
} else {
return QVariant();
}
@ -191,18 +195,13 @@ void AudioDeviceList::resetDevice(bool contextIsHMD) {
#endif
}
void AudioDeviceList::onDeviceChanged(const QAudioDeviceInfo& device, bool isHMD) {
QAudioDeviceInfo& selectedDevice = isHMD ? _selectedHMDDevice : _selectedDesktopDevice;
void AudioDeviceList::onDeviceChanged(const HifiAudioDeviceInfo& device, bool isHMD) {
HifiAudioDeviceInfo& selectedDevice = isHMD ? _selectedHMDDevice : _selectedDesktopDevice;
selectedDevice = device;
for (auto i = 0; i < _devices.size(); ++i) {
std::shared_ptr<AudioDevice> device = _devices[i];
bool& isSelected = isHMD ? device->selectedHMD : device->selectedDesktop;
if (isSelected && device->info != selectedDevice) {
isSelected = false;
} else if (device->info == selectedDevice) {
isSelected = true;
}
isSelected = device->info == selectedDevice;
}
emit deviceChanged(selectedDevice);
@ -259,37 +258,46 @@ std::shared_ptr<scripting::AudioDevice> getSimilarDevice(const QString& deviceNa
return devices[minDistanceIndex];
}
void AudioDeviceList::onDevicesChanged(const QList<QAudioDeviceInfo>& devices) {
void AudioDeviceList::onDevicesChanged(const QList<HifiAudioDeviceInfo>& devices) {
beginResetModel();
QList<std::shared_ptr<AudioDevice>> newDevices;
bool hmdIsSelected = false;
bool desktopIsSelected = false;
foreach(const QAudioDeviceInfo& deviceInfo, devices) {
foreach(const HifiAudioDeviceInfo& deviceInfo, devices) {
for (bool isHMD : {false, true}) {
auto& backupSelectedDeviceName = isHMD ? _backupSelectedHMDDeviceName : _backupSelectedDesktopDeviceName;
if (deviceInfo.deviceName() == backupSelectedDeviceName) {
QAudioDeviceInfo& selectedDevice = isHMD ? _selectedHMDDevice : _selectedDesktopDevice;
HifiAudioDeviceInfo& selectedDevice = isHMD ? _selectedHMDDevice : _selectedDesktopDevice;
selectedDevice = deviceInfo;
backupSelectedDeviceName.clear();
}
}
}
foreach(const QAudioDeviceInfo& deviceInfo, devices) {
foreach(const HifiAudioDeviceInfo& deviceInfo, devices) {
AudioDevice device;
device.info = deviceInfo;
device.display = device.info.deviceName()
.replace("High Definition", "HD")
.remove("Device")
.replace(" )", ")");
if (deviceInfo.isDefault()) {
if (deviceInfo.getMode() == QAudio::AudioInput) {
device.display = "Computer's default microphone (recommended)";
} else {
device.display = "Computer's default audio (recommended)";
}
} else {
device.display = device.info.deviceName()
.replace("High Definition", "HD")
.remove("Device")
.replace(" )", ")");
}
for (bool isHMD : {false, true}) {
QAudioDeviceInfo& selectedDevice = isHMD ? _selectedHMDDevice : _selectedDesktopDevice;
HifiAudioDeviceInfo& selectedDevice = isHMD ? _selectedHMDDevice : _selectedDesktopDevice;
bool& isSelected = isHMD ? device.selectedHMD : device.selectedDesktop;
if (!selectedDevice.isNull()) {
if (!selectedDevice.getDevice().isNull()) {
isSelected = (device.info == selectedDevice);
}
else {
@ -325,13 +333,13 @@ void AudioDeviceList::onDevicesChanged(const QList<QAudioDeviceInfo>& devices) {
if (!newDevices.isEmpty()) {
if (!hmdIsSelected) {
_backupSelectedHMDDeviceName = !_selectedHMDDevice.isNull() ? _selectedHMDDevice.deviceName() : _hmdSavedDeviceName;
_backupSelectedHMDDeviceName = !_selectedHMDDevice.getDevice().isNull() ? _selectedHMDDevice.deviceName() : _hmdSavedDeviceName;
auto device = getSimilarDevice(_backupSelectedHMDDeviceName, newDevices);
device->selectedHMD = true;
emit selectedDevicePlugged(device->info, true);
}
if (!desktopIsSelected) {
_backupSelectedDesktopDeviceName = !_selectedDesktopDevice.isNull() ? _selectedDesktopDevice.deviceName() : _desktopSavedDeviceName;
_backupSelectedDesktopDeviceName = !_selectedDesktopDevice.getDevice().isNull() ? _selectedDesktopDevice.deviceName() : _desktopSavedDeviceName;
auto device = getSimilarDevice(_backupSelectedDesktopDeviceName, newDevices);
device->selectedDesktop = true;
emit selectedDevicePlugged(device->info, false);
@ -382,8 +390,8 @@ AudioDevices::AudioDevices(bool& contextIsHMD) : _contextIsHMD(contextIsHMD) {
_outputs.onDeviceChanged(client->getActiveAudioDevice(QAudio::AudioOutput), contextIsHMD);
// connections are made after client is initialized, so we must also fetch the devices
const QList<QAudioDeviceInfo>& devicesInput = client->getAudioDevices(QAudio::AudioInput);
const QList<QAudioDeviceInfo>& devicesOutput = client->getAudioDevices(QAudio::AudioOutput);
const QList<HifiAudioDeviceInfo>& devicesInput = client->getAudioDevices(QAudio::AudioInput);
const QList<HifiAudioDeviceInfo>& devicesOutput = client->getAudioDevices(QAudio::AudioOutput);
//setup devices
_inputs.onDevicesChanged(devicesInput);
@ -397,9 +405,9 @@ void AudioDevices::onContextChanged(const QString& context) {
_outputs.resetDevice(_contextIsHMD);
}
void AudioDevices::onDeviceSelected(QAudio::Mode mode, const QAudioDeviceInfo& device,
const QAudioDeviceInfo& previousDevice, bool isHMD) {
QString deviceName = device.isNull() ? QString() : device.deviceName();
void AudioDevices::onDeviceSelected(QAudio::Mode mode, const HifiAudioDeviceInfo& device,
const HifiAudioDeviceInfo& previousDevice, bool isHMD) {
QString deviceName = device.deviceName();
auto& setting = getSetting(isHMD, mode);
@ -410,7 +418,7 @@ void AudioDevices::onDeviceSelected(QAudio::Mode mode, const QAudioDeviceInfo& d
setting.set(deviceName);
// log the selected device
if (!device.isNull()) {
if (!device.getDevice().isNull()) {
QJsonObject data;
const QString MODE = "audio_mode";
@ -434,13 +442,13 @@ void AudioDevices::onDeviceSelected(QAudio::Mode mode, const QAudioDeviceInfo& d
}
}
void AudioDevices::onDeviceChanged(QAudio::Mode mode, const QAudioDeviceInfo& device) {
void AudioDevices::onDeviceChanged(QAudio::Mode mode, const HifiAudioDeviceInfo& device) {
if (mode == QAudio::AudioInput) {
if (_requestedInputDevice == device) {
onDeviceSelected(QAudio::AudioInput, device,
_contextIsHMD ? _inputs._selectedHMDDevice : _inputs._selectedDesktopDevice,
_contextIsHMD);
_requestedInputDevice = QAudioDeviceInfo();
_requestedInputDevice = HifiAudioDeviceInfo();
}
_inputs.onDeviceChanged(device, _contextIsHMD);
} else { // if (mode == QAudio::AudioOutput)
@ -448,13 +456,13 @@ void AudioDevices::onDeviceChanged(QAudio::Mode mode, const QAudioDeviceInfo& de
onDeviceSelected(QAudio::AudioOutput, device,
_contextIsHMD ? _outputs._selectedHMDDevice : _outputs._selectedDesktopDevice,
_contextIsHMD);
_requestedOutputDevice = QAudioDeviceInfo();
_requestedOutputDevice = HifiAudioDeviceInfo();
}
_outputs.onDeviceChanged(device, _contextIsHMD);
}
}
void AudioDevices::onDevicesChanged(QAudio::Mode mode, const QList<QAudioDeviceInfo>& devices) {
void AudioDevices::onDevicesChanged(QAudio::Mode mode, const QList<HifiAudioDeviceInfo>& devices) {
static std::once_flag once;
std::call_once(once, [&] {
//readout settings
@ -503,14 +511,14 @@ void AudioDevices::onDevicesChanged(QAudio::Mode mode, const QList<QAudioDeviceI
}
void AudioDevices::chooseInputDevice(const QAudioDeviceInfo& device, bool isHMD) {
void AudioDevices::chooseInputDevice(const HifiAudioDeviceInfo& device, bool isHMD) {
//check if current context equals device to change
if (_contextIsHMD == isHMD) {
auto client = DependencyManager::get<AudioClient>().data();
_requestedInputDevice = device;
QMetaObject::invokeMethod(client, "switchAudioDevice",
Q_ARG(QAudio::Mode, QAudio::AudioInput),
Q_ARG(const QAudioDeviceInfo&, device));
Q_ARG(const HifiAudioDeviceInfo&, device));
} else {
//context is different. just save device in settings
onDeviceSelected(QAudio::AudioInput, device,
@ -520,14 +528,14 @@ void AudioDevices::chooseInputDevice(const QAudioDeviceInfo& device, bool isHMD)
}
}
void AudioDevices::chooseOutputDevice(const QAudioDeviceInfo& device, bool isHMD) {
void AudioDevices::chooseOutputDevice(const HifiAudioDeviceInfo& device, bool isHMD) {
//check if current context equals device to change
if (_contextIsHMD == isHMD) {
auto client = DependencyManager::get<AudioClient>().data();
_requestedOutputDevice = device;
QMetaObject::invokeMethod(client, "switchAudioDevice",
Q_ARG(QAudio::Mode, QAudio::AudioOutput),
Q_ARG(const QAudioDeviceInfo&, device));
Q_ARG(const HifiAudioDeviceInfo&, device));
} else {
//context is different. just save device in settings
onDeviceSelected(QAudio::AudioOutput, device,

View file

@ -19,11 +19,13 @@
#include <QAbstractListModel>
#include <QAudioDeviceInfo>
#include <HifiAudioDeviceInfo.h>
namespace scripting {
class AudioDevice {
public:
QAudioDeviceInfo info;
HifiAudioDeviceInfo info;
QString display;
bool selectedDesktop { false };
bool selectedHMD { false };
@ -50,12 +52,12 @@ public:
void resetDevice(bool contextIsHMD);
signals:
void deviceChanged(const QAudioDeviceInfo& device);
void selectedDevicePlugged(const QAudioDeviceInfo& device, bool isHMD);
void deviceChanged(const HifiAudioDeviceInfo& device);
void selectedDevicePlugged(const HifiAudioDeviceInfo& device, bool isHMD);
protected slots:
void onDeviceChanged(const QAudioDeviceInfo& device, bool isHMD);
void onDevicesChanged(const QList<QAudioDeviceInfo>& devices);
void onDeviceChanged(const HifiAudioDeviceInfo& device, bool isHMD);
void onDevicesChanged(const QList<HifiAudioDeviceInfo>& devices);
protected:
friend class AudioDevices;
@ -63,8 +65,8 @@ protected:
static QHash<int, QByteArray> _roles;
static Qt::ItemFlags _flags;
const QAudio::Mode _mode;
QAudioDeviceInfo _selectedDesktopDevice;
QAudioDeviceInfo _selectedHMDDevice;
HifiAudioDeviceInfo _selectedDesktopDevice;
HifiAudioDeviceInfo _selectedHMDDevice;
QString _backupSelectedDesktopDeviceName;
QString _backupSelectedHMDDeviceName;
QList<std::shared_ptr<AudioDevice>> _devices;
@ -124,14 +126,14 @@ signals:
void nop();
private slots:
void chooseInputDevice(const QAudioDeviceInfo& device, bool isHMD);
void chooseOutputDevice(const QAudioDeviceInfo& device, bool isHMD);
void chooseInputDevice(const HifiAudioDeviceInfo& device, bool isHMD);
void chooseOutputDevice(const HifiAudioDeviceInfo& device, bool isHMD);
void onContextChanged(const QString& context);
void onDeviceSelected(QAudio::Mode mode, const QAudioDeviceInfo& device,
const QAudioDeviceInfo& previousDevice, bool isHMD);
void onDeviceChanged(QAudio::Mode mode, const QAudioDeviceInfo& device);
void onDevicesChanged(QAudio::Mode mode, const QList<QAudioDeviceInfo>& devices);
void onDeviceSelected(QAudio::Mode mode, const HifiAudioDeviceInfo& device,
const HifiAudioDeviceInfo& previousDevice, bool isHMD);
void onDeviceChanged(QAudio::Mode mode, const HifiAudioDeviceInfo& device);
void onDevicesChanged(QAudio::Mode mode, const QList<HifiAudioDeviceInfo>& devices);
private:
friend class Audio;
@ -141,8 +143,8 @@ private:
AudioInputDeviceList _inputs;
AudioDeviceList _outputs { QAudio::AudioOutput };
QAudioDeviceInfo _requestedOutputDevice;
QAudioDeviceInfo _requestedInputDevice;
HifiAudioDeviceInfo _requestedOutputDevice;
HifiAudioDeviceInfo _requestedInputDevice;
const bool& _contextIsHMD;
};

View file

@ -6,5 +6,7 @@
<array>
<string>high-fidelity.hifi</string>
</array>
<key>com.apple.security.device.audio-input</key>
<true/>
</dict>
</plist>

View file

@ -1658,7 +1658,7 @@ void Rig::updateHead(bool headEnabled, bool hipsEnabled, const AnimPose& headPos
_animVars.set("splineIKEnabled", false);
_animVars.unset("headPosition");
_animVars.set("headRotation", headPose.rot());
_animVars.set("headType", (int)IKTarget::Type::RotationOnly);
_animVars.set("headType", (int)IKTarget::Type::Unknown);
}
}
}
@ -2645,3 +2645,8 @@ float Rig::getUnscaledEyeHeight() const {
return DEFAULT_AVATAR_EYE_HEIGHT;
}
}
void Rig::setDirectionalBlending(const QString& targetName, const glm::vec3& blendingTarget, const QString& alphaName, float alpha) {
_animVars.set(targetName, blendingTarget);
_animVars.set(alphaName, alpha);
}

View file

@ -254,6 +254,7 @@ public:
int getOverrideJointCount() const;
bool getFlowActive() const;
bool getNetworkGraphActive() const;
void setDirectionalBlending(const QString& targetName, const glm::vec3& blendingTarget, const QString& alphaName, float alpha);
signals:
void onLoadComplete();
@ -358,7 +359,7 @@ protected:
A,
B
};
NetworkAnimState() : clipNodeEnum(NetworkAnimState::None) {}
NetworkAnimState() : clipNodeEnum(NetworkAnimState::None), fps(30.0f), loop(false), firstFrame(0.0f), lastFrame(0.0f), blendTime(FLT_MAX) {}
NetworkAnimState(ClipNodeEnum clipNodeEnumIn, const QString& urlIn, float fpsIn, bool loopIn, float firstFrameIn, float lastFrameIn) :
clipNodeEnum(clipNodeEnumIn), url(urlIn), fps(fpsIn), loop(loopIn), firstFrame(firstFrameIn), lastFrame(lastFrameIn) {}

View file

@ -60,25 +60,13 @@ const int AudioClient::MIN_BUFFER_FRAMES = 1;
const int AudioClient::MAX_BUFFER_FRAMES = 20;
static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 100;
#if defined(Q_OS_ANDROID)
static const int CHECK_INPUT_READS_MSECS = 2000;
static const int MIN_READS_TO_CONSIDER_INPUT_ALIVE = 10;
#endif
static const auto DEFAULT_POSITION_GETTER = []{ return Vectors::ZERO; };
static const auto DEFAULT_ORIENTATION_GETTER = [] { return Quaternions::IDENTITY; };
static const int DEFAULT_BUFFER_FRAMES = 1;
// OUTPUT_CHANNEL_COUNT is audio pipeline output format, which is always 2 channel.
// _outputFormat.channelCount() is device output format, which may be 1 or multichannel.
static const int OUTPUT_CHANNEL_COUNT = 2;
static const bool DEFAULT_STARVE_DETECTION_ENABLED = true;
static const int STARVE_DETECTION_THRESHOLD = 3;
static const int STARVE_DETECTION_PERIOD = 10 * 1000; // 10 Seconds
const AudioClient::AudioPositionGetter AudioClient::DEFAULT_POSITION_GETTER = []{ return Vectors::ZERO; };
const AudioClient::AudioOrientationGetter AudioClient::DEFAULT_ORIENTATION_GETTER = [] { return Quaternions::IDENTITY; };
Setting::Handle<bool> dynamicJitterBufferEnabled("dynamicJitterBuffersEnabled",
InboundAudioStream::DEFAULT_DYNAMIC_JITTER_BUFFER_ENABLED);
@ -91,11 +79,23 @@ using Lock = std::unique_lock<Mutex>;
Mutex _deviceMutex;
Mutex _recordMutex;
HifiAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode);
// thread-safe
QList<QAudioDeviceInfo> getAvailableDevices(QAudio::Mode mode) {
QList<HifiAudioDeviceInfo> getAvailableDevices(QAudio::Mode mode) {
// NOTE: availableDevices() clobbers the Qt internal device list
Lock lock(_deviceMutex);
return QAudioDeviceInfo::availableDevices(mode);
auto devices = QAudioDeviceInfo::availableDevices(mode);
QList<HifiAudioDeviceInfo> newDevices;
for (auto& device : devices) {
newDevices.push_back(HifiAudioDeviceInfo(device, false, mode));
}
newDevices.push_front(defaultAudioDeviceForMode(mode));
return newDevices;
}
// now called from a background thread, to keep blocking operations off the audio thread
@ -109,6 +109,9 @@ void AudioClient::checkDevices() {
auto inputDevices = getAvailableDevices(QAudio::AudioInput);
auto outputDevices = getAvailableDevices(QAudio::AudioOutput);
QMetaObject::invokeMethod(this, "changeDefault", Q_ARG(HifiAudioDeviceInfo, inputDevices.first()), Q_ARG(QAudio::Mode, QAudio::AudioInput));
QMetaObject::invokeMethod(this, "changeDefault", Q_ARG(HifiAudioDeviceInfo, outputDevices.first()), Q_ARG(QAudio::Mode, QAudio::AudioOutput));
Lock lock(_deviceMutex);
if (inputDevices != _inputDevices) {
@ -122,22 +125,22 @@ void AudioClient::checkDevices() {
}
}
QAudioDeviceInfo AudioClient::getActiveAudioDevice(QAudio::Mode mode) const {
HifiAudioDeviceInfo AudioClient::getActiveAudioDevice(QAudio::Mode mode) const {
Lock lock(_deviceMutex);
if (mode == QAudio::AudioInput) {
return _inputDeviceInfo;
} else { // if (mode == QAudio::AudioOutput)
} else {
return _outputDeviceInfo;
}
}
QList<QAudioDeviceInfo> AudioClient::getAudioDevices(QAudio::Mode mode) const {
QList<HifiAudioDeviceInfo> AudioClient::getAudioDevices(QAudio::Mode mode) const {
Lock lock(_deviceMutex);
if (mode == QAudio::AudioInput) {
return _inputDevices;
} else { // if (mode == QAudio::AudioOutput)
} else {
return _outputDevices;
}
}
@ -226,7 +229,7 @@ static float computeLoudness(int16_t* samples, int numSamples) {
template <int NUM_CHANNELS>
static void applyGainSmoothing(float* buffer, int numFrames, float gain0, float gain1) {
// fast path for unity gain
if (gain0 == 1.0f && gain1 == 1.0f) {
return;
@ -241,7 +244,7 @@ static void applyGainSmoothing(float* buffer, int numFrames, float gain0, float
float tStep = 1.0f / numFrames;
for (int i = 0; i < numFrames; i++) {
// evaluate poly over t=[0,1)
float gain = (c3 * t + c2) * t * t + c0;
t += tStep;
@ -257,55 +260,7 @@ static inline float convertToFloat(int16_t sample) {
return (float)sample * (1 / 32768.0f);
}
AudioClient::AudioClient() :
AbstractAudioInterface(),
_gate(this),
_audioInput(NULL),
_dummyAudioInput(NULL),
_desiredInputFormat(),
_inputFormat(),
_numInputCallbackBytes(0),
_audioOutput(NULL),
_desiredOutputFormat(),
_outputFormat(),
_outputFrameSize(0),
_numOutputCallbackBytes(0),
_loopbackAudioOutput(NULL),
_loopbackOutputDevice(NULL),
_inputRingBuffer(0),
_localInjectorsStream(0, 1),
_receivedAudioStream(RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES),
_isStereoInput(false),
_outputStarveDetectionStartTimeMsec(0),
_outputStarveDetectionCount(0),
_outputBufferSizeFrames("audioOutputBufferFrames", DEFAULT_BUFFER_FRAMES),
_sessionOutputBufferSizeFrames(_outputBufferSizeFrames.get()),
_outputStarveDetectionEnabled("audioOutputStarveDetectionEnabled", DEFAULT_STARVE_DETECTION_ENABLED),
_lastRawInputLoudness(0.0f),
_lastSmoothedRawInputLoudness(0.0f),
_lastInputLoudness(0.0f),
_timeSinceLastClip(-1.0f),
_muted(false),
_shouldEchoLocally(false),
_shouldEchoToServer(false),
_isNoiseGateEnabled(true),
_isAECEnabled(true),
_reverb(false),
_reverbOptions(&_scriptReverbOptions),
_inputToNetworkResampler(NULL),
_networkToOutputResampler(NULL),
_localToOutputResampler(NULL),
_loopbackResampler(NULL),
_audioLimiter(AudioConstants::SAMPLE_RATE, OUTPUT_CHANNEL_COUNT),
_outgoingAvatarAudioSequenceNumber(0),
_audioOutputIODevice(_localInjectorsStream, _receivedAudioStream, this),
_stats(&_receivedAudioStream),
_positionGetter(DEFAULT_POSITION_GETTER),
#if defined(Q_OS_ANDROID)
_checkInputTimer(this),
_isHeadsetPluggedIn(false),
#endif
_orientationGetter(DEFAULT_ORIENTATION_GETTER) {
AudioClient::AudioClient() {
// avoid putting a lock in the device callback
assert(_localSamplesAvailable.is_lock_free());
@ -321,9 +276,9 @@ AudioClient::AudioClient() :
}
connect(&_receivedAudioStream, &MixedProcessedAudioStream::processSamples,
this, &AudioClient::processReceivedSamples, Qt::DirectConnection);
connect(this, &AudioClient::changeDevice, this, [=](const QAudioDeviceInfo& outputDeviceInfo) {
qCDebug(audioclient) << "got AudioClient::changeDevice signal, about to call switchOutputToAudioDevice() outputDeviceInfo: [" << outputDeviceInfo.deviceName() << "]";
this, &AudioClient::processReceivedSamples, Qt::DirectConnection);
connect(this, &AudioClient::changeDevice, this, [=](const HifiAudioDeviceInfo& outputDeviceInfo) {
qCDebug(audioclient)<< "got AudioClient::changeDevice signal, about to call switchOutputToAudioDevice() outputDeviceInfo: ["<< outputDeviceInfo.deviceName() << "]";
switchOutputToAudioDevice(outputDeviceInfo);
});
@ -373,7 +328,7 @@ AudioClient::AudioClient() :
packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat");
auto& domainHandler = nodeList->getDomainHandler();
connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, [this] {
connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, [this] {
_solo.reset();
});
connect(nodeList.data(), &NodeList::nodeActivated, this, [this](SharedNodePointer node) {
@ -431,15 +386,14 @@ void AudioClient::setAudioPaused(bool pause) {
}
}
QAudioDeviceInfo getNamedAudioDeviceForMode(QAudio::Mode mode, const QString& deviceName) {
QAudioDeviceInfo result;
foreach(QAudioDeviceInfo audioDevice, getAvailableDevices(mode)) {
HifiAudioDeviceInfo getNamedAudioDeviceForMode(QAudio::Mode mode, const QString& deviceName) {
HifiAudioDeviceInfo result;
foreach (HifiAudioDeviceInfo audioDevice, getAvailableDevices(mode)) {
if (audioDevice.deviceName().trimmed() == deviceName.trimmed()) {
result = audioDevice;
break;
}
}
return result;
}
@ -487,9 +441,11 @@ QString AudioClient::getWinDeviceName(wchar_t* guid) {
#endif
QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) {
HifiAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) {
QList<QAudioDeviceInfo> devices = QAudioDeviceInfo::availableDevices(mode);
#ifdef __APPLE__
if (getAvailableDevices(mode).size() > 1) {
if (devices.size() > 1) {
AudioDeviceID defaultDeviceID = 0;
uint32_t propertySize = sizeof(AudioDeviceID);
AudioObjectPropertyAddress propertyAddress = {
@ -519,9 +475,9 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) {
if (!getPropertyError && propertySize) {
// find a device in the list that matches the name we have and return it
foreach(QAudioDeviceInfo audioDevice, getAvailableDevices(mode)) {
foreach(QAudioDeviceInfo audioDevice, devices){
if (audioDevice.deviceName() == CFStringGetCStringPtr(deviceName, kCFStringEncodingMacRoman)) {
return audioDevice;
return HifiAudioDeviceInfo(audioDevice, true, mode);
}
}
}
@ -538,7 +494,9 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) {
waveInGetDevCaps(WAVE_MAPPER, &wic, sizeof(wic));
//Use the received manufacturer id to get the device's real name
waveInGetDevCaps(wic.wMid, &wic, sizeof(wic));
#if !defined(NDEBUG)
qCDebug(audioclient) << "input device:" << wic.szPname;
#endif
deviceName = wic.szPname;
} else {
WAVEOUTCAPS woc;
@ -546,7 +504,9 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) {
waveOutGetDevCaps(WAVE_MAPPER, &woc, sizeof(woc));
//Use the received manufacturer id to get the device's real name
waveOutGetDevCaps(woc.wMid, &woc, sizeof(woc));
#if !defined(NDEBUG)
qCDebug(audioclient) << "output device:" << woc.szPname;
#endif
deviceName = woc.szPname;
}
} else {
@ -569,10 +529,18 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) {
CoUninitialize();
}
qCDebug(audioclient) << "defaultAudioDeviceForMode mode: " << (mode == QAudio::AudioOutput ? "Output" : "Input")
<< " [" << deviceName << "] [" << getNamedAudioDeviceForMode(mode, deviceName).deviceName() << "]";
return getNamedAudioDeviceForMode(mode, deviceName);
HifiAudioDeviceInfo foundDevice;
foreach(QAudioDeviceInfo audioDevice, devices) {
if (audioDevice.deviceName().trimmed() == deviceName.trimmed()) {
foundDevice=HifiAudioDeviceInfo(audioDevice,true,mode);
break;
}
}
#if !defined(NDEBUG)
qCDebug(audioclient) << "defaultAudioDeviceForMode mode: " << (mode == QAudio::AudioOutput ? "Output" : "Input")
<< " [" << deviceName << "] [" << foundDevice.deviceName() << "]";
#endif
return foundDevice;
#endif
#if defined (Q_OS_ANDROID)
@ -580,18 +548,18 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) {
Setting::Handle<bool> enableAEC(SETTING_AEC_KEY, DEFAULT_AEC_ENABLED);
bool aecEnabled = enableAEC.get();
auto audioClient = DependencyManager::get<AudioClient>();
bool headsetOn = audioClient? audioClient->isHeadsetPluggedIn() : false;
auto inputDevices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput);
for (auto inputDevice : inputDevices) {
bool headsetOn = audioClient ? audioClient->isHeadsetPluggedIn() : false;
for (QAudioDeviceInfo inputDevice : devices) {
if (((headsetOn || !aecEnabled) && inputDevice.deviceName() == VOICE_RECOGNITION) ||
((!headsetOn && aecEnabled) && inputDevice.deviceName() == VOICE_COMMUNICATION)) {
return inputDevice;
((!headsetOn && aecEnabled) && inputDevice.deviceName() == VOICE_COMMUNICATION)) {
return HifiAudioDeviceInfo(inputDevice, false, QAudio::AudioInput);
}
}
}
#endif
// fallback for failed lookup is the default device
return (mode == QAudio::AudioInput) ? QAudioDeviceInfo::defaultInputDevice() : QAudioDeviceInfo::defaultOutputDevice();
return (mode == QAudio::AudioInput) ? HifiAudioDeviceInfo(QAudioDeviceInfo::defaultInputDevice(), true,mode) :
HifiAudioDeviceInfo(QAudioDeviceInfo::defaultOutputDevice(), true, mode);
}
bool AudioClient::getNamedAudioDeviceForModeExists(QAudio::Mode mode, const QString& deviceName) {
@ -643,29 +611,29 @@ bool adjustedFormatForAudioDevice(const QAudioDeviceInfo& audioDevice,
if (IsWindows8OrGreater()) {
// On Windows using WASAPI shared-mode, returns the internal mix format
return nativeFormatForAudioDevice(audioDevice, adjustedAudioFormat);
} // else enumerate formats
} // else enumerate formats
#endif
#if defined(Q_OS_MAC)
// Mac OSX returns the preferred CoreAudio format
return nativeFormatForAudioDevice(audioDevice, adjustedAudioFormat);
#endif
#if defined(Q_OS_ANDROID)
// As of Qt5.6, Android returns the native OpenSLES sample rate when possible, else 48000
if (nativeFormatForAudioDevice(audioDevice, adjustedAudioFormat)) {
return true;
} // else enumerate formats
} // else enumerate formats
#endif
adjustedAudioFormat = desiredAudioFormat;
//
// Attempt the device sample rate and channel count in decreasing order of preference.
//
const int sampleRates[] = { 48000, 44100, 32000, 24000, 16000, 96000, 192000, 88200, 176400 };
const int inputChannels[] = { 1, 2, 4, 6, 8 }; // prefer mono
const int outputChannels[] = { 2, 4, 6, 8, 1 }; // prefer stereo, downmix as last resort
const int inputChannels[] = { 1, 2, 4, 6, 8 }; // prefer mono
const int outputChannels[] = { 2, 4, 6, 8, 1 }; // prefer stereo, downmix as last resort
for (int channelCount : (desiredAudioFormat.channelCount() == 1 ? inputChannels : outputChannels)) {
for (int sampleRate : sampleRates) {
@ -758,22 +726,22 @@ void AudioClient::start() {
_desiredOutputFormat = _desiredInputFormat;
_desiredOutputFormat.setChannelCount(OUTPUT_CHANNEL_COUNT);
QAudioDeviceInfo inputDeviceInfo = defaultAudioDeviceForMode(QAudio::AudioInput);
HifiAudioDeviceInfo inputDeviceInfo = defaultAudioDeviceForMode(QAudio::AudioInput);
qCDebug(audioclient) << "The default audio input device is" << inputDeviceInfo.deviceName();
bool inputFormatSupported = switchInputToAudioDevice(inputDeviceInfo);
QAudioDeviceInfo outputDeviceInfo = defaultAudioDeviceForMode(QAudio::AudioOutput);
HifiAudioDeviceInfo outputDeviceInfo = defaultAudioDeviceForMode(QAudio::AudioOutput);
qCDebug(audioclient) << "The default audio output device is" << outputDeviceInfo.deviceName();
bool outputFormatSupported = switchOutputToAudioDevice(outputDeviceInfo);
if (!inputFormatSupported) {
qCDebug(audioclient) << "Unable to set up audio input because of a problem with input format.";
qCDebug(audioclient) << "The closest format available is" << inputDeviceInfo.nearestFormat(_desiredInputFormat);
qCDebug(audioclient) << "The closest format available is" << inputDeviceInfo.getDevice().nearestFormat(_desiredInputFormat);
}
if (!outputFormatSupported) {
qCDebug(audioclient) << "Unable to set up audio output because of a problem with output format.";
qCDebug(audioclient) << "The closest format available is" << outputDeviceInfo.nearestFormat(_desiredOutputFormat);
qCDebug(audioclient) << "The closest format available is" << outputDeviceInfo.getDevice().nearestFormat(_desiredOutputFormat);
}
#if defined(Q_OS_ANDROID)
connect(&_checkInputTimer, &QTimer::timeout, this, &AudioClient::checkInputTimeout);
@ -783,10 +751,10 @@ void AudioClient::start() {
void AudioClient::stop() {
qCDebug(audioclient) << "AudioClient::stop(), requesting switchInputToAudioDevice() to shut down";
switchInputToAudioDevice(QAudioDeviceInfo(), true);
switchInputToAudioDevice(HifiAudioDeviceInfo(), true);
qCDebug(audioclient) << "AudioClient::stop(), requesting switchOutputToAudioDevice() to shut down";
switchOutputToAudioDevice(QAudioDeviceInfo(), true);
switchOutputToAudioDevice(HifiAudioDeviceInfo(), true);
// Stop triggering the checks
QObject::disconnect(_checkPeakValuesTimer, &QTimer::timeout, nullptr, nullptr);
@ -978,16 +946,18 @@ void AudioClient::selectAudioFormat(const QString& selectedCodecName) {
}
bool AudioClient::switchAudioDevice(QAudio::Mode mode, const QAudioDeviceInfo& deviceInfo) {
auto device = deviceInfo;
if (device.isNull()) {
device = defaultAudioDeviceForMode(mode);
void AudioClient::changeDefault(HifiAudioDeviceInfo newDefault, QAudio::Mode mode) {
HifiAudioDeviceInfo currentDevice = mode == QAudio::AudioInput ? _inputDeviceInfo : _outputDeviceInfo;
if (currentDevice.isDefault() && currentDevice.getDevice() != newDefault.getDevice()) {
switchAudioDevice(mode, newDefault);
}
}
bool AudioClient::switchAudioDevice(QAudio::Mode mode, const HifiAudioDeviceInfo& deviceInfo) {
auto device = deviceInfo;
if (mode == QAudio::AudioInput) {
return switchInputToAudioDevice(device);
} else { // if (mode == QAudio::AudioOutput)
} else {
return switchOutputToAudioDevice(device);
}
}
@ -1030,7 +1000,7 @@ void AudioClient::configureReverb() {
p.wetDryMix = 100.0f;
p.preDelay = 0.0f;
p.earlyGain = -96.0f; // disable ER
p.lateGain += _reverbOptions->getWetDryMix() * (24.0f/100.0f) - 24.0f; // -0dB to -24dB, based on wetDryMix
p.lateGain += _reverbOptions->getWetDryMix() * (24.0f / 100.0f) - 24.0f; // -0dB to -24dB, based on wetDryMix
p.lateMixLeft = 0.0f;
p.lateMixRight = 0.0f;
@ -1414,7 +1384,7 @@ void AudioClient::handleMicAudioInput() {
} else if (_timeSinceLastClip >= 0.0f) {
_timeSinceLastClip += AudioConstants::NETWORK_FRAME_SECS;
}
isClipping = (_timeSinceLastClip >= 0.0f) && (_timeSinceLastClip < 2.0f); // 2 second hold time
isClipping = (_timeSinceLastClip >= 0.0f) && (_timeSinceLastClip < 2.0f); // 2 second hold time
#if defined(WEBRTC_ENABLED)
if (_isAECEnabled) {
@ -1453,7 +1423,7 @@ void AudioClient::handleDummyAudioInput() {
? AudioConstants::NETWORK_FRAME_BYTES_STEREO
: AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL;
QByteArray audioBuffer(numNetworkBytes, 0); // silent
QByteArray audioBuffer(numNetworkBytes, 0); // silent
handleAudioInput(audioBuffer);
}
@ -1598,7 +1568,7 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
// direct mix into mixBuffer
injector->getLocalHRTF().mixStereo(_localScratchBuffer, mixBuffer, gain,
AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
} else { // injector is mono
} else { // injector is mono
if (options.positionSet) {
@ -1736,7 +1706,7 @@ void AudioClient::setAcousticEchoCancellation(bool enable, bool emitSignal) {
bool AudioClient::setIsStereoInput(bool isStereoInput) {
bool stereoInputChanged = false;
if (isStereoInput != _isStereoInput && _inputDeviceInfo.supportedChannelCounts().contains(2)) {
if (isStereoInput != _isStereoInput && _inputDeviceInfo.getDevice().supportedChannelCounts().contains(2)) {
_isStereoInput = isStereoInput;
stereoInputChanged = true;
@ -1798,17 +1768,17 @@ void AudioClient::outputFormatChanged() {
_receivedAudioStream.outputFormatChanged(_outputFormat.sampleRate(), OUTPUT_CHANNEL_COUNT);
}
bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo inputDeviceInfo, bool isShutdownRequest) {
bool AudioClient::switchInputToAudioDevice(const HifiAudioDeviceInfo inputDeviceInfo, bool isShutdownRequest) {
Q_ASSERT_X(QThread::currentThread() == thread(), Q_FUNC_INFO, "Function invoked on wrong thread");
qCDebug(audioclient) << __FUNCTION__ << "inputDeviceInfo: [" << inputDeviceInfo.deviceName() << "]";
qCDebug(audioclient) << __FUNCTION__ << "inputDeviceInfo: [" << _inputDeviceInfo.deviceName() <<"----"<<inputDeviceInfo.getDevice().deviceName() << "]";
bool supportedFormat = false;
// NOTE: device start() uses the Qt internal device list
Lock lock(_deviceMutex);
#if defined(Q_OS_ANDROID)
_shouldRestartInputSetup = false; // avoid a double call to _audioInput->start() from audioInputStateChanged
_shouldRestartInputSetup = false; // avoid a double call to _audioInput->start() from audioInputStateChanged
#endif
// cleanup any previously initialized device
@ -1822,8 +1792,6 @@ bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo inputDeviceInf
_audioInput->deleteLater();
_audioInput = NULL;
_numInputCallbackBytes = 0;
_inputDeviceInfo = QAudioDeviceInfo();
}
if (_dummyAudioInput) {
@ -1854,12 +1822,16 @@ bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo inputDeviceInf
return true;
}
if (!inputDeviceInfo.isNull()) {
if (!inputDeviceInfo.getDevice().isNull()) {
qCDebug(audioclient) << "The audio input device " << inputDeviceInfo.deviceName() << "is available.";
bool doEmit = _inputDeviceInfo.deviceName() != inputDeviceInfo.deviceName();
_inputDeviceInfo = inputDeviceInfo;
emit deviceChanged(QAudio::AudioInput, inputDeviceInfo);
if (doEmit) {
emit deviceChanged(QAudio::AudioInput, _inputDeviceInfo);
}
if (adjustedFormatForAudioDevice(inputDeviceInfo, _desiredInputFormat, _inputFormat)) {
if (adjustedFormatForAudioDevice(_inputDeviceInfo.getDevice(), _desiredInputFormat, _inputFormat)) {
qCDebug(audioclient) << "The format to be used for audio input is" << _inputFormat;
// we've got the best we can get for input
@ -1884,7 +1856,7 @@ bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo inputDeviceInf
// if the user wants stereo but this device can't provide then bail
if (!_isStereoInput || _inputFormat.channelCount() == 2) {
_audioInput = new QAudioInput(inputDeviceInfo, _inputFormat, this);
_audioInput = new QAudioInput(_inputDeviceInfo.getDevice(), _inputFormat, this);
_numInputCallbackBytes = calculateNumberOfInputCallbackBytes(_inputFormat);
_audioInput->setBufferSize(_numInputCallbackBytes);
// different audio input devices may have different volumes
@ -1906,7 +1878,7 @@ bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo inputDeviceInf
connect(_inputDevice, SIGNAL(readyRead()), this, SLOT(handleMicAudioInput()));
supportedFormat = true;
} else {
qCDebug(audioclient) << "Error starting audio input -" << _audioInput->error();
qCDebug(audioclient) << "Error starting audio input -" << _audioInput->error();
_audioInput->deleteLater();
_audioInput = NULL;
}
@ -1919,7 +1891,7 @@ bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo inputDeviceInf
// This enables clients without a mic to still receive an audio stream from the mixer.
if (!_audioInput) {
qCDebug(audioclient) << "Audio input device is not available, using dummy input.";
_inputDeviceInfo = QAudioDeviceInfo();
_inputDeviceInfo.setDevice(QAudioDeviceInfo());
emit deviceChanged(QAudio::AudioInput, _inputDeviceInfo);
_inputFormat = _desiredInputFormat;
@ -1975,11 +1947,11 @@ void AudioClient::checkInputTimeout() {
void AudioClient::setHeadsetPluggedIn(bool pluggedIn) {
#if defined(Q_OS_ANDROID)
if (pluggedIn == !_isHeadsetPluggedIn && !_inputDeviceInfo.isNull()) {
QAndroidJniObject brand = QAndroidJniObject::getStaticObjectField<jstring>("android/os/Build", "BRAND");
if (pluggedIn == !_isHeadsetPluggedIn && !_inputDeviceInfo.getDevice().isNull()) {
QAndroidJniObject brand = QAndroidJniObject::getStaticObjectField<jstring>("android/os/Build", "BRAND");
// some samsung phones needs more time to shutdown the previous input device
if (brand.toString().contains("samsung", Qt::CaseInsensitive)) {
switchInputToAudioDevice(QAudioDeviceInfo(), true);
switchInputToAudioDevice(HifiAudioDeviceInfo(), true);
QThread::msleep(200);
}
@ -2026,7 +1998,7 @@ void AudioClient::outputNotify() {
}
}
bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo outputDeviceInfo, bool isShutdownRequest) {
bool AudioClient::switchOutputToAudioDevice(const HifiAudioDeviceInfo outputDeviceInfo, bool isShutdownRequest) {
Q_ASSERT_X(QThread::currentThread() == thread(), Q_FUNC_INFO, "Function invoked on wrong thread");
qCDebug(audioclient) << "AudioClient::switchOutputToAudioDevice() outputDeviceInfo: [" << outputDeviceInfo.deviceName() << "]";
@ -2061,8 +2033,6 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo outputDeviceI
delete[] _localOutputMixBuffer;
_localOutputMixBuffer = NULL;
_outputDeviceInfo = QAudioDeviceInfo();
}
// cleanup any resamplers
@ -2086,12 +2056,15 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo outputDeviceI
return true;
}
if (!outputDeviceInfo.isNull()) {
if (!outputDeviceInfo.getDevice().isNull()) {
qCDebug(audioclient) << "The audio output device " << outputDeviceInfo.deviceName() << "is available.";
bool doEmit = _outputDeviceInfo.deviceName() != outputDeviceInfo.deviceName();
_outputDeviceInfo = outputDeviceInfo;
emit deviceChanged(QAudio::AudioOutput, outputDeviceInfo);
if (doEmit) {
emit deviceChanged(QAudio::AudioOutput, _outputDeviceInfo);
}
if (adjustedFormatForAudioDevice(outputDeviceInfo, _desiredOutputFormat, _outputFormat)) {
if (adjustedFormatForAudioDevice(_outputDeviceInfo.getDevice(), _desiredOutputFormat, _outputFormat)) {
qCDebug(audioclient) << "The format to be used for audio output is" << _outputFormat;
// we've got the best we can get for input
@ -2113,7 +2086,7 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo outputDeviceI
outputFormatChanged();
// setup our general output device for audio-mixer audio
_audioOutput = new QAudioOutput(outputDeviceInfo, _outputFormat, this);
_audioOutput = new QAudioOutput(_outputDeviceInfo.getDevice(), _outputFormat, this);
int deviceChannelCount = _outputFormat.channelCount();
int frameSize = (AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL * deviceChannelCount * _outputFormat.sampleRate()) / _desiredOutputFormat.sampleRate();
@ -2165,7 +2138,7 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo outputDeviceI
localAudioLock.unlock();
// setup a loopback audio output device
_loopbackAudioOutput = new QAudioOutput(outputDeviceInfo, _outputFormat, this);
_loopbackAudioOutput = new QAudioOutput(outputDeviceInfo.getDevice(), _outputFormat, this);
_timeSinceLastReceived.start();
@ -2239,7 +2212,7 @@ float AudioClient::azimuthForSource(const glm::vec3& relativePosition) {
// produce an oriented angle about the y-axis
glm::vec3 direction = rotatedSourcePosition * (1.0f / fastSqrtf(rotatedSourcePositionLength2));
float angle = fastAcosf(glm::clamp(-direction.z, -1.0f, 1.0f)); // UNIT_NEG_Z is "forward"
float angle = fastAcosf(glm::clamp(-direction.z, -1.0f, 1.0f)); // UNIT_NEG_Z is "forward"
return (direction.x < 0.0f) ? -angle : angle;
} else {
@ -2423,4 +2396,4 @@ void AudioClient::setInputVolume(float volume, bool emitSignal) {
emit inputVolumeChanged(_audioInput->volume());
}
}
}
}

View file

@ -53,6 +53,7 @@
#include "AudioIOStats.h"
#include "AudioFileWav.h"
#include "HifiAudioDeviceInfo.h"
#ifdef _WIN32
#pragma warning( push )
@ -79,6 +80,9 @@ class QIODevice;
class Transform;
class NLPacket;
#define DEFAULT_STARVE_DETECTION_ENABLED true
#define DEFAULT_BUFFER_FRAMES 1
class AudioClient : public AbstractAudioInterface, public Dependency {
Q_OBJECT
SINGLETON_DEPENDENCY
@ -102,8 +106,8 @@ public:
_audio(audio), _unfulfilledReads(0) {}
void start() { open(QIODevice::ReadOnly | QIODevice::Unbuffered); }
qint64 readData(char * data, qint64 maxSize) override;
qint64 writeData(const char * data, qint64 maxSize) override { return 0; }
qint64 readData(char* data, qint64 maxSize) override;
qint64 writeData(const char* data, qint64 maxSize) override { return 0; }
int getRecentUnfulfilledReads() { int unfulfilledReads = _unfulfilledReads; _unfulfilledReads = 0; return unfulfilledReads; }
private:
LocalInjectorsStream& _localInjectorsStream;
@ -111,7 +115,7 @@ public:
AudioClient* _audio;
int _unfulfilledReads;
};
void startThread();
void negotiateAudioFormat();
void selectAudioFormat(const QString& selectedCodecName);
@ -152,12 +156,12 @@ public:
void setIsPlayingBackRecording(bool isPlayingBackRecording) { _isPlayingBackRecording = isPlayingBackRecording; }
Q_INVOKABLE void setAvatarBoundingBoxParameters(glm::vec3 corner, glm::vec3 scale);
bool outputLocalInjector(const AudioInjectorPointer& injector) override;
QAudioDeviceInfo getActiveAudioDevice(QAudio::Mode mode) const;
QList<QAudioDeviceInfo> getAudioDevices(QAudio::Mode mode) const;
HifiAudioDeviceInfo getActiveAudioDevice(QAudio::Mode mode) const;
QList<HifiAudioDeviceInfo> getAudioDevices(QAudio::Mode mode) const;
void enablePeakValues(bool enable) { _enablePeakValues = enable; }
bool peakValuesAvailable() const;
@ -233,11 +237,11 @@ public slots:
int setOutputBufferSize(int numFrames, bool persist = true);
bool shouldLoopbackInjectors() override { return _shouldEchoToServer; }
Q_INVOKABLE void changeDefault(HifiAudioDeviceInfo newDefault, QAudio::Mode mode);
// calling with a null QAudioDevice will use the system default
bool switchAudioDevice(QAudio::Mode mode, const QAudioDeviceInfo& deviceInfo = QAudioDeviceInfo());
bool switchAudioDevice(QAudio::Mode mode, const HifiAudioDeviceInfo& deviceInfo = HifiAudioDeviceInfo());
bool switchAudioDevice(QAudio::Mode mode, const QString& deviceName);
// Qt opensles plugin is not able to detect when the headset is plugged in
void setHeadsetPluggedIn(bool pluggedIn);
@ -269,10 +273,10 @@ signals:
void noiseGateOpened();
void noiseGateClosed();
void changeDevice(const QAudioDeviceInfo& outputDeviceInfo);
void changeDevice(const HifiAudioDeviceInfo& outputDeviceInfo);
void deviceChanged(QAudio::Mode mode, const QAudioDeviceInfo& device);
void devicesChanged(QAudio::Mode mode, const QList<QAudioDeviceInfo>& devices);
void deviceChanged(QAudio::Mode mode, const HifiAudioDeviceInfo& device);
void devicesChanged(QAudio::Mode mode, const QList<HifiAudioDeviceInfo>& devices);
void peakValueListChanged(const QList<float> peakValueList);
void receivedFirstPacket();
@ -291,6 +295,16 @@ protected:
virtual void customDeleter() override;
private:
static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES{ 100 };
// OUTPUT_CHANNEL_COUNT is audio pipeline output format, which is always 2 channel.
// _outputFormat.channelCount() is device output format, which may be 1 or multichannel.
static const int OUTPUT_CHANNEL_COUNT{ 2 };
static const int STARVE_DETECTION_THRESHOLD{ 3 };
static const int STARVE_DETECTION_PERIOD{ 10 * 1000 }; // 10 Seconds
static const AudioPositionGetter DEFAULT_POSITION_GETTER;
static const AudioOrientationGetter DEFAULT_ORIENTATION_GETTER;
friend class CheckDevicesThread;
friend class LocalInjectorsThread;
@ -306,9 +320,9 @@ private:
float gainForSource(float distance, float volume);
#ifdef Q_OS_ANDROID
QTimer _checkInputTimer;
QTimer _checkInputTimer{ this };
long _inputReadsSinceLastCheck = 0l;
bool _isHeadsetPluggedIn;
bool _isHeadsetPluggedIn { false };
#endif
class Gate {
@ -335,68 +349,68 @@ private:
bool _isSimulatingJitter{ false };
};
Gate _gate;
Gate _gate{ this };
Mutex _injectorsMutex;
QAudioInput* _audioInput;
QTimer* _dummyAudioInput;
QAudioInput* _audioInput{ nullptr };
QTimer* _dummyAudioInput{ nullptr };
QAudioFormat _desiredInputFormat;
QAudioFormat _inputFormat;
QIODevice* _inputDevice;
int _numInputCallbackBytes;
QAudioOutput* _audioOutput;
QIODevice* _inputDevice{ nullptr };
int _numInputCallbackBytes{ 0 };
QAudioOutput* _audioOutput{ nullptr };
std::atomic<bool> _audioOutputInitialized { false };
QAudioFormat _desiredOutputFormat;
QAudioFormat _outputFormat;
int _outputFrameSize;
int _numOutputCallbackBytes;
QAudioOutput* _loopbackAudioOutput;
QIODevice* _loopbackOutputDevice;
AudioRingBuffer _inputRingBuffer;
LocalInjectorsStream _localInjectorsStream;
int _outputFrameSize{ 0 };
int _numOutputCallbackBytes{ 0 };
QAudioOutput* _loopbackAudioOutput{ nullptr };
QIODevice* _loopbackOutputDevice{ nullptr };
AudioRingBuffer _inputRingBuffer{ 0 };
LocalInjectorsStream _localInjectorsStream{ 0 , 1 };
// In order to use _localInjectorsStream as a lock-free pipe,
// use it with a single producer/consumer, and track available samples and injectors
std::atomic<int> _localSamplesAvailable { 0 };
std::atomic<bool> _localInjectorsAvailable { false };
MixedProcessedAudioStream _receivedAudioStream;
bool _isStereoInput;
MixedProcessedAudioStream _receivedAudioStream{ RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES };
bool _isStereoInput{ false };
std::atomic<bool> _enablePeakValues { false };
quint64 _outputStarveDetectionStartTimeMsec;
int _outputStarveDetectionCount;
quint64 _outputStarveDetectionStartTimeMsec{ 0 };
int _outputStarveDetectionCount { 0 };
Setting::Handle<int> _outputBufferSizeFrames;
int _sessionOutputBufferSizeFrames;
Setting::Handle<bool> _outputStarveDetectionEnabled;
Setting::Handle<int> _outputBufferSizeFrames{"audioOutputBufferFrames", DEFAULT_BUFFER_FRAMES};
int _sessionOutputBufferSizeFrames{ _outputBufferSizeFrames.get() };
Setting::Handle<bool> _outputStarveDetectionEnabled{ "audioOutputStarveDetectionEnabled", DEFAULT_STARVE_DETECTION_ENABLED};
StDev _stdev;
QElapsedTimer _timeSinceLastReceived;
float _lastRawInputLoudness; // before mute/gate
float _lastSmoothedRawInputLoudness;
float _lastInputLoudness; // after mute/gate
float _timeSinceLastClip;
float _lastRawInputLoudness{ 0.0f }; // before mute/gate
float _lastSmoothedRawInputLoudness{ 0.0f };
float _lastInputLoudness{ 0.0f }; // after mute/gate
float _timeSinceLastClip{ -1.0f };
int _totalInputAudioSamples;
bool _muted;
bool _shouldEchoLocally;
bool _shouldEchoToServer;
bool _isNoiseGateEnabled;
bool _muted{ false };
bool _shouldEchoLocally{ false };
bool _shouldEchoToServer{ false };
bool _isNoiseGateEnabled{ true };
bool _warnWhenMuted;
bool _isAECEnabled;
bool _isAECEnabled{ true };
bool _reverb;
bool _reverb{ false };
AudioEffectOptions _scriptReverbOptions;
AudioEffectOptions _zoneReverbOptions;
AudioEffectOptions* _reverbOptions;
AudioEffectOptions* _reverbOptions{ &_scriptReverbOptions };
AudioReverb _sourceReverb { AudioConstants::SAMPLE_RATE };
AudioReverb _listenerReverb { AudioConstants::SAMPLE_RATE };
AudioReverb _localReverb { AudioConstants::SAMPLE_RATE };
// possible streams needed for resample
AudioSRC* _inputToNetworkResampler;
AudioSRC* _networkToOutputResampler;
AudioSRC* _localToOutputResampler;
AudioSRC* _loopbackResampler;
AudioSRC* _inputToNetworkResampler{ nullptr };
AudioSRC* _networkToOutputResampler{ nullptr };
AudioSRC* _localToOutputResampler{ nullptr };
AudioSRC* _loopbackResampler{ nullptr };
// for network audio (used by network audio thread)
int16_t _networkScratchBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_AMBISONIC];
@ -415,8 +429,8 @@ private:
int16_t _localScratchBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_AMBISONIC];
float* _localOutputMixBuffer { NULL };
Mutex _localAudioMutex;
AudioLimiter _audioLimiter;
AudioLimiter _audioLimiter{ AudioConstants::SAMPLE_RATE, OUTPUT_CHANNEL_COUNT };
// Adds Reverb
void configureReverb();
void updateReverbOptions();
@ -437,33 +451,33 @@ private:
void processWebrtcNearEnd(int16_t* samples, int numFrames, int numChannels, int sampleRate);
#endif
bool switchInputToAudioDevice(const QAudioDeviceInfo inputDeviceInfo, bool isShutdownRequest = false);
bool switchOutputToAudioDevice(const QAudioDeviceInfo outputDeviceInfo, bool isShutdownRequest = false);
bool switchInputToAudioDevice(const HifiAudioDeviceInfo inputDeviceInfo, bool isShutdownRequest = false);
bool switchOutputToAudioDevice(const HifiAudioDeviceInfo outputDeviceInfo, bool isShutdownRequest = false);
// Callback acceleration dependent calculations
int calculateNumberOfInputCallbackBytes(const QAudioFormat& format) const;
int calculateNumberOfFrameSamples(int numBytes) const;
quint16 _outgoingAvatarAudioSequenceNumber;
quint16 _outgoingAvatarAudioSequenceNumber{ 0 };
AudioOutputIODevice _audioOutputIODevice;
AudioOutputIODevice _audioOutputIODevice{ _localInjectorsStream, _receivedAudioStream, this };
AudioIOStats _stats;
AudioIOStats _stats{ &_receivedAudioStream };
AudioGate* _audioGate { nullptr };
bool _audioGateOpen { true };
AudioPositionGetter _positionGetter;
AudioOrientationGetter _orientationGetter;
AudioPositionGetter _positionGetter{ DEFAULT_POSITION_GETTER };
AudioOrientationGetter _orientationGetter{ DEFAULT_ORIENTATION_GETTER };
glm::vec3 avatarBoundingBoxCorner;
glm::vec3 avatarBoundingBoxScale;
QAudioDeviceInfo _inputDeviceInfo;
QAudioDeviceInfo _outputDeviceInfo;
HifiAudioDeviceInfo _inputDeviceInfo;
HifiAudioDeviceInfo _outputDeviceInfo;
QList<QAudioDeviceInfo> _inputDevices;
QList<QAudioDeviceInfo> _outputDevices;
QList<HifiAudioDeviceInfo> _inputDevices;
QList<HifiAudioDeviceInfo> _outputDevices;
AudioFileWav _audioFileWav;
@ -476,7 +490,7 @@ private:
CodecPluginPointer _codec;
QString _selectedCodecName;
Encoder* _encoder { nullptr }; // for outbound mic stream
Encoder* _encoder { nullptr }; // for outbound mic stream
RateCounter<> _silentOutbound;
RateCounter<> _audioOutbound;
@ -484,11 +498,11 @@ private:
RateCounter<> _audioInbound;
#if defined(Q_OS_ANDROID)
bool _shouldRestartInputSetup { true }; // Should we restart the input device because of an unintended stop?
bool _shouldRestartInputSetup { true }; // Should we restart the input device because of an unintended stop?
#endif
AudioSolo _solo;
Mutex _checkDevicesMutex;
QTimer* _checkDevicesTimer { nullptr };
Mutex _checkPeakValuesMutex;
@ -498,4 +512,4 @@ private:
};
#endif // hifi_AudioClient_h
#endif // hifi_AudioClient_h

View file

@ -0,0 +1,36 @@
//
// HifiAudioDeviceInfo.cpp
// libraries/audio-client/src
//
// Created by Amer Cerkic on 9/14/19.
// Copyright 2019 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 "HifiAudioDeviceInfo.h"
const QString HifiAudioDeviceInfo::DEFAULT_DEVICE_NAME = "default ";
void HifiAudioDeviceInfo::setDevice(QAudioDeviceInfo devInfo) {
_audioDeviceInfo = devInfo;
}
HifiAudioDeviceInfo& HifiAudioDeviceInfo::operator=(const HifiAudioDeviceInfo& other) {
_audioDeviceInfo = other.getDevice();
_mode = other.getMode();
_isDefault = other.isDefault();
return *this;
}
bool HifiAudioDeviceInfo::operator==(const HifiAudioDeviceInfo& rhs) const {
//Does the QAudioDeviceinfo match as well as is this the default device or
return getDevice() == rhs.getDevice() && isDefault() == rhs.isDefault();
}
bool HifiAudioDeviceInfo::operator!=(const HifiAudioDeviceInfo& rhs) const {
return getDevice() != rhs.getDevice() || isDefault() != rhs.isDefault();
}

View file

@ -0,0 +1,69 @@
//
// HifiAudioDeviceInfo.h
// libraries/audio-client/src
//
// Created by Amer Cerkic on 9/14/19.
// Copyright 2019 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
//
#pragma once
#ifndef hifi_audiodeviceinfo_h
#define hifi_audiodeviceinfo_h
#include <QObject>
#include <QAudioDeviceInfo>
#include <QAudio>
#include <QString>
class HifiAudioDeviceInfo : public QObject {
Q_OBJECT
public:
HifiAudioDeviceInfo() : QObject() {}
HifiAudioDeviceInfo(const HifiAudioDeviceInfo &deviceInfo) : QObject(){
_audioDeviceInfo = deviceInfo.getDevice();
_mode = deviceInfo.getMode();
_isDefault = deviceInfo.isDefault();
}
HifiAudioDeviceInfo(QAudioDeviceInfo deviceInfo, bool isDefault, QAudio::Mode mode) :
_audioDeviceInfo(deviceInfo),
_isDefault(isDefault),
_mode(mode){
}
void setMode(QAudio::Mode mode) { _mode = mode; }
void setIsDefault() { _isDefault = true; }
void setDevice(QAudioDeviceInfo devInfo);
QString deviceName() const {
#if defined(Q_OS_ANDROID)
return _audioDeviceInfo.deviceName();
#endif
if (_isDefault) {
return DEFAULT_DEVICE_NAME;
} else {
return _audioDeviceInfo.deviceName();
}
}
QAudioDeviceInfo getDevice() const { return _audioDeviceInfo; }
bool isDefault() const { return _isDefault; }
QAudio::Mode getMode() const { return _mode; }
HifiAudioDeviceInfo& operator=(const HifiAudioDeviceInfo& other);
bool operator==(const HifiAudioDeviceInfo& rhs) const;
bool operator!=(const HifiAudioDeviceInfo& rhs) const;
private:
QAudioDeviceInfo _audioDeviceInfo;
bool _isDefault { false };
QAudio::Mode _mode { QAudio::AudioInput };
public:
static const QString DEFAULT_DEVICE_NAME;
};
#endif

View file

@ -133,7 +133,21 @@ void Avatar::setShowNamesAboveHeads(bool show) {
showNamesAboveHeads = show;
}
static const char* avatarTransitStatusToStringMap[] = {
"IDLE",
"STARTED",
"PRE_TRANSIT",
"START_TRANSIT",
"TRANSITING",
"END_TRANSIT",
"POST_TRANSIT",
"ENDED",
"ABORT_TRANSIT"
};
AvatarTransit::Status AvatarTransit::update(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config) {
AvatarTransit::Status previousStatus = _status;
float oneFrameDistance = _isActive ? glm::length(avatarPosition - _endPosition) : glm::length(avatarPosition - _lastPosition);
if (oneFrameDistance > (config._minTriggerDistance * _scale)) {
if (oneFrameDistance < (config._maxTriggerDistance * _scale)) {
@ -150,6 +164,10 @@ AvatarTransit::Status AvatarTransit::update(float deltaTime, const glm::vec3& av
reset();
_status = Status::ENDED;
}
if (previousStatus != _status) {
qDebug(avatars_renderer) << "AvatarTransit " << avatarTransitStatusToStringMap[(int)previousStatus] << "->" << avatarTransitStatusToStringMap[_status];
}
return _status;
}

View file

@ -92,22 +92,23 @@ void Head::simulate(float deltaTime) {
} else if (_timeWithoutTalking - deltaTime < BLINK_AFTER_TALKING && _timeWithoutTalking >= BLINK_AFTER_TALKING) {
forceBlink = true;
}
if (_leftEyeBlinkVelocity == 0.0f && _rightEyeBlinkVelocity == 0.0f) {
// no blinking when brows are raised; blink less with increasing loudness
const float BASE_BLINK_RATE = 15.0f / 60.0f;
const float ROOT_LOUDNESS_TO_BLINK_INTERVAL = 0.25f;
if (forceBlink || (_browAudioLift < EPSILON && shouldDo(glm::max(1.0f, sqrt(fabs(_averageLoudness - _longTermAverageLoudness)) *
ROOT_LOUDNESS_TO_BLINK_INTERVAL) / BASE_BLINK_RATE, deltaTime))) {
_leftEyeBlinkVelocity = BLINK_SPEED + randFloat() * BLINK_SPEED_VARIABILITY;
_rightEyeBlinkVelocity = BLINK_SPEED + randFloat() * BLINK_SPEED_VARIABILITY;
float randSpeedVariability = randFloat();
float eyeBlinkVelocity = BLINK_SPEED + randSpeedVariability * BLINK_SPEED_VARIABILITY;
_leftEyeBlinkVelocity = eyeBlinkVelocity;
_rightEyeBlinkVelocity = eyeBlinkVelocity;
if (randFloat() < 0.5f) {
_leftEyeBlink = BLINK_START_VARIABILITY;
} else {
_rightEyeBlink = BLINK_START_VARIABILITY;
}
}
} else {
_leftEyeBlink = glm::clamp(_leftEyeBlink + _leftEyeBlinkVelocity * deltaTime, FULLY_OPEN, FULLY_CLOSED);
_rightEyeBlink = glm::clamp(_rightEyeBlink + _rightEyeBlinkVelocity * deltaTime, FULLY_OPEN, FULLY_CLOSED);

View file

@ -96,6 +96,21 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf
connect(entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity, this, handlePointerEvent);
connect(entityScriptingInterface, &EntityScriptingInterface::mouseMoveOnEntity, this, handlePointerEvent);
connect(entityScriptingInterface, &EntityScriptingInterface::mouseReleaseOnEntity, this, handlePointerEvent);
// Handle mouse-clicking or laser-clicking on entities with the `href` property set
connect(entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity, this, [&](const QUuid& entityID, const PointerEvent& event) {
if (!EntityTree::areEntityClicksCaptured() && (event.getButtons() & PointerEvent::PrimaryButton)) {
auto entity = getEntity(entityID);
if (!entity) {
return;
}
auto properties = entity->getProperties();
QString urlString = properties.getHref();
QUrl url = QUrl(urlString, QUrl::StrictMode);
if (url.isValid() && !url.isEmpty()) {
DependencyManager::get<AddressManager>()->handleLookupString(urlString);
}
}
});
connect(entityScriptingInterface, &EntityScriptingInterface::hoverEnterEntity, this, [&](const QUuid& entityID, const PointerEvent& event) {
std::shared_ptr<render::entities::WebEntityRenderer> thisEntity;
auto entity = getEntity(entityID);
@ -800,15 +815,6 @@ QUuid EntityTreeRenderer::mousePressEvent(QMouseEvent* event) {
RayToEntityIntersectionResult rayPickResult = _getPrevRayPickResultOperator(_mouseRayPickID);
EntityItemPointer entity;
if (rayPickResult.intersects && (entity = getTree()->findEntityByID(rayPickResult.entityID))) {
if (!EntityTree::areEntityClicksCaptured() && event->button() == Qt::MouseButton::LeftButton) {
auto properties = entity->getProperties();
QString urlString = properties.getHref();
QUrl url = QUrl(urlString, QUrl::StrictMode);
if (url.isValid() && !url.isEmpty()) {
DependencyManager::get<AddressManager>()->handleLookupString(urlString);
}
}
glm::vec2 pos2D = projectOntoEntityXYPlane(entity, ray, rayPickResult);
PointerEvent pointerEvent(PointerEvent::Press, PointerManager::MOUSE_POINTER_ID,
pos2D, rayPickResult.intersection,

View file

@ -108,15 +108,6 @@ protected:
virtual void setIsVisibleInSecondaryCamera(bool value) { _isVisibleInSecondaryCamera = value; }
virtual void setRenderLayer(RenderLayer value) { _renderLayer = value; }
virtual void setPrimitiveMode(PrimitiveMode value) { _primitiveMode = value; }
template <typename F, typename T>
T withReadLockResult(const std::function<T()>& f) {
T result;
withReadLock([&] {
result = f();
});
return result;
}
signals:
void requestRenderUpdate();

View file

@ -706,15 +706,11 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
*
* @property {Vec3} gravity=0,0,0 - The acceleration due to gravity in m/s<sup>2</sup> that the entity should move with, in
* world coordinates. Use a value of <code>{ x: 0, y: -9.8, z: 0 }</code> to simulate Earth's gravity. Gravity is applied
* to an entity's motion only if its <code>dynamic</code> property is <code>true</code>. The <code>gravity</code> value is
* applied in addition to the <code>acceleration</code> value.
* to an entity's motion only if its <code>dynamic</code> property is <code>true</code>.
* <p>If changing an entity's <code>gravity</code> from {@link Vec3(0)|Vec3.ZERO}, you need to give it a small
* <code>velocity</code> in order to kick off physics simulation.</p>
* @property {Vec3} acceleration=0,0,0 - A general acceleration in m/s<sup>2</sup> that the entity should move with, in world
* coordinates. The acceleration is applied to an entity's motion only if its <code>dynamic</code> property is
* <code>true</code>. The <code>acceleration</code> value is applied in addition to the <code>gravity</code> value.
* <p>If changing an entity's <code>acceleration</code> from {@link Vec3(0)|Vec3.ZERO}, you need to give it a small
* <code>velocity</code> in order to kick off physics simulation.<p>
* @property {Vec3} acceleration - The current, measured acceleration of the entity, in m/s<sup>2</sup>.
* <p class="important">Deprecated: This property is deprecated and will be removed.</p>
* @property {number} restitution=0.5 - The "bounciness" of an entity when it collides, range <code>0.0</code> &ndash;
* <code>0.99</code>. The higher the value, the more bouncy.
* @property {number} friction=0.5 - How much an entity slows down when it's moving against another, range <code>0.0</code>

View file

@ -106,8 +106,9 @@ public:
* "domain" entities, travel to different domains with a user as "avatar" entities, or be visible only to an individual user as
* "local" entities (a.k.a. "overlays").
*
* <p>Note: For Interface scripts, the entities available to scripts are those that Interface has displayed and so knows
* about.</p>
* <p>Note: For Interface, avatar, and client entity scripts, the entities available to scripts are those that Interface has
* displayed and so knows about. For assignment client scripts, the entities available are those that are "seen" by the
* {@link EntityViewer}. For entity server scripts, all entities are available.</p>
*
* <h3>Entity Methods</h3>
*
@ -728,7 +729,8 @@ public slots:
* Finds all domain and avatar entities whose axis-aligned boxes intersect a search frustum.
* @function Entities.findEntitiesInFrustum
* @param {ViewFrustum} frustum - The frustum to search in. The <code>position</code>, <code>orientation</code>,
* <code>projection</code>, and <code>centerRadius</code> properties must be specified.
* <code>projection</code>, and <code>centerRadius</code> properties must be specified. The <code>fieldOfView</code>
* and <code>aspectRatio</code> properties are not used; these values are specified by the <code>projection</code>.
* @returns {Uuid[]} An array of entity IDs whose axis-aligned boxes intersect the search frustum. The array is empty if no
* entities could be found.
* @example <caption>Report the number of entities in view.</caption>

View file

@ -21,8 +21,10 @@ const uint32_t MAX_RANGE_QUERY_DEPTH = 1;
static bool timeElapsed = true;
#else
const uint32_t MAX_RANGE_QUERY_DEPTH = 10000;
#if !defined(USE_GLES)
static bool timeElapsed = false;
#endif
#endif
#if defined(USE_GLES)
static bool hasTimerExtension() {

View file

@ -17,34 +17,6 @@
namespace gpu { namespace gles {
// returns the FOV from the projection matrix
static inline vec4 extractFov( const glm::mat4& m) {
static const std::array<vec4, 4> CLIPS{ {
{ 1, 0, 0, 1 },
{ -1, 0, 0, 1 },
{ 0, 1, 0, 1 },
{ 0, -1, 0, 1 }
} };
glm::mat4 mt = glm::transpose(m);
vec4 v, result;
// Left
v = mt * CLIPS[0];
result.x = -atanf(v.z / v.x);
// Right
v = mt * CLIPS[1];
result.y = atanf(v.z / v.x);
// Down
v = mt * CLIPS[2];
result.z = -atanf(v.z / v.y);
// Up
v = mt * CLIPS[3];
result.w = atanf(v.z / v.y);
return result;
}
class GLESFramebuffer : public gl::GLFramebuffer {
using Parent = gl::GLFramebuffer;
static GLuint allocate() {

View file

@ -50,6 +50,8 @@ namespace scriptable {
* @property {string} emissiveMap
* @property {string} albedoMap
* @property {string} opacityMap
* @property {string} opacityMapMode
* @property {number|string} opacityCutoff
* @property {string} metallicMap
* @property {string} specularMap
* @property {string} roughnessMap
@ -84,6 +86,8 @@ namespace scriptable {
QString emissiveMap;
QString albedoMap;
QString opacityMap;
QString opacityMapMode;
float opacityCutoff;
QString metallicMap;
QString specularMap;
QString roughnessMap;
@ -94,7 +98,6 @@ namespace scriptable {
QString lightMap;
QString scatteringMap;
std::array<glm::mat4, graphics::Material::NUM_TEXCOORD_TRANSFORMS> texCoordTransforms;
bool defaultFallthrough;
std::unordered_map<uint, bool> propertyFallthroughs; // not actually exposed to script

View file

@ -420,6 +420,18 @@ namespace scriptable {
obj.setProperty("opacityMap", material.opacityMap);
}
if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::MaterialKey::OPACITY_TRANSLUCENT_MAP_BIT | graphics::MaterialKey::OPACITY_MASK_MAP_BIT)) {
obj.setProperty("opacityMapMode", FALLTHROUGH);
} else if (material.key.getOpacityMapMode() != graphics::Material::DEFAULT_OPACITY_MAP_MODE) {
obj.setProperty("opacityMapMode", material.opacityMapMode);
}
if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::MaterialKey::OPACITY_CUTOFF_VAL_BIT)) {
obj.setProperty("opacityCutoff", FALLTHROUGH);
} else if (material.key.isOpacityCutoff()) {
obj.setProperty("opacityCutoff", material.opacityCutoff);
}
if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::MaterialKey::OCCLUSION_MAP_BIT)) {
obj.setProperty("occlusionMap", FALLTHROUGH);
} else if (!material.occlusionMap.isEmpty()) {

View file

@ -26,6 +26,7 @@ scriptable::ScriptableMaterial& scriptable::ScriptableMaterial::operator=(const
roughness = material.roughness;
metallic = material.metallic;
scattering = material.scattering;
opacityCutoff = material.opacityCutoff;
unlit = material.unlit;
emissive = material.emissive;
albedo = material.albedo;
@ -41,6 +42,8 @@ scriptable::ScriptableMaterial& scriptable::ScriptableMaterial::operator=(const
occlusionMap = material.occlusionMap;
lightMap = material.lightMap;
scatteringMap = material.scatteringMap;
opacityMapMode = material.opacityMapMode;
defaultFallthrough = material.defaultFallthrough;
propertyFallthroughs = material.propertyFallthroughs;
@ -55,9 +58,12 @@ scriptable::ScriptableMaterial::ScriptableMaterial(const graphics::MaterialPoint
name = material->getName().c_str();
model = material->getModel().c_str();
opacity = material->getOpacity();
opacityMapMode = QString(graphics::MaterialKey::getOpacityMapModeName(material->getOpacityMapMode()).c_str());
roughness = material->getRoughness();
metallic = material->getMetallic();
scattering = material->getScattering();
opacityCutoff = material->getOpacityCutoff();
unlit = material->isUnlit();
emissive = material->getEmissive();
albedo = material->getAlbedo();

View file

@ -14,6 +14,8 @@
#include <Transform.h>
#include "GraphicsLogging.h"
using namespace graphics;
using namespace gpu;
@ -22,7 +24,26 @@ const float Material::DEFAULT_OPACITY { 1.0f };
const float Material::DEFAULT_ALBEDO { 0.5f };
const float Material::DEFAULT_METALLIC { 0.0f };
const float Material::DEFAULT_ROUGHNESS { 1.0f };
const float Material::DEFAULT_SCATTERING { 0.0f };
const float Material::DEFAULT_SCATTERING{ 0.0f };
const MaterialKey::OpacityMapMode Material::DEFAULT_OPACITY_MAP_MODE{ MaterialKey::OPACITY_MAP_OPAQUE };
const float Material::DEFAULT_OPACITY_CUTOFF { 0.5f };
std::string MaterialKey::getOpacityMapModeName(OpacityMapMode mode) {
const std::string names[3] = { "OPACITY_MAP_OPAQUE", "OPACITY_MAP_MASK", "OPACITY_MAP_BLEND" };
return names[mode];
}
bool MaterialKey::getOpacityMapModeFromName(const std::string& modeName, MaterialKey::OpacityMapMode& mode) {
for (int i = OPACITY_MAP_OPAQUE; i <= OPACITY_MAP_BLEND; i++) {
mode = (MaterialKey::OpacityMapMode) i;
if (modeName == getOpacityMapModeName(mode)) {
return true;
}
}
return false;
}
Material::Material() {
for (int i = 0; i < NUM_TOTAL_FLAGS; i++) {
@ -40,6 +61,7 @@ Material::Material(const Material& material) :
_roughness(material._roughness),
_metallic(material._metallic),
_scattering(material._scattering),
_opacityCutoff(material._opacityCutoff),
_texcoordTransforms(material._texcoordTransforms),
_lightmapParams(material._lightmapParams),
_materialParams(material._materialParams),
@ -50,7 +72,7 @@ Material::Material(const Material& material) :
}
Material& Material::operator=(const Material& material) {
QMutexLocker locker(&_textureMapsMutex);
std::lock_guard<std::recursive_mutex> locker(_textureMapsMutex);
_name = material._name;
_model = material._model;
@ -61,6 +83,7 @@ Material& Material::operator=(const Material& material) {
_roughness = material._roughness;
_metallic = material._metallic;
_scattering = material._scattering;
_opacityCutoff = material._opacityCutoff;
_texcoordTransforms = material._texcoordTransforms;
_lightmapParams = material._lightmapParams;
_materialParams = material._materialParams;
@ -109,8 +132,22 @@ void Material::setScattering(float scattering) {
_scattering = scattering;
}
void Material::setOpacityCutoff(float opacityCutoff) {
opacityCutoff = glm::clamp(opacityCutoff, 0.0f, 1.0f);
_key.setOpacityCutoff(opacityCutoff != DEFAULT_OPACITY_CUTOFF);
_opacityCutoff = opacityCutoff;
}
void Material::setOpacityMapMode(MaterialKey::OpacityMapMode opacityMapMode) {
_key.setOpacityMapMode(opacityMapMode);
}
MaterialKey::OpacityMapMode Material::getOpacityMapMode() const {
return _key.getOpacityMapMode();
}
void Material::setTextureMap(MapChannel channel, const TextureMapPointer& textureMap) {
QMutexLocker locker(&_textureMapsMutex);
std::lock_guard<std::recursive_mutex> locker(_textureMapsMutex);
if (textureMap) {
_key.setMapChannel(channel, true);
@ -139,7 +176,14 @@ void Material::setTextureMap(MapChannel channel, const TextureMapPointer& textur
}
void Material::resetOpacityMap() const {
bool Material::resetOpacityMap() const {
// If OpacityMapMode explicit then nothing need to change here.
if (_key.isOpacityMapMode()) {
return false;
}
// Else, the legacy behavior is to interpret the albedo texture assigned to tune the opacity map mode value
auto previous = _key.getOpacityMapMode();
// Clear the previous flags
_key.setOpacityMaskMap(false);
_key.setTranslucentMap(false);
@ -163,10 +207,16 @@ void Material::resetOpacityMap() const {
}
}
}
auto newious = _key.getOpacityMapMode();
if (previous != newious) {
//opacity change detected for this material
return true;
}
return false;
}
const TextureMapPointer Material::getTextureMap(MapChannel channel) const {
QMutexLocker locker(&_textureMapsMutex);
std::lock_guard<std::recursive_mutex> locker(_textureMapsMutex);
auto result = _textureMaps.find(channel);
if (result != _textureMaps.end()) {

View file

@ -11,8 +11,7 @@
#ifndef hifi_model_Material_h
#define hifi_model_Material_h
#include <QMutex>
#include <mutex>
#include <bitset>
#include <map>
#include <unordered_map>
@ -44,6 +43,8 @@ public:
OPACITY_VAL_BIT,
OPACITY_MASK_MAP_BIT, // Opacity Map and Opacity MASK map are mutually exclusive
OPACITY_TRANSLUCENT_MAP_BIT,
OPACITY_MAP_MODE_BIT, // Opacity map mode bit is set if the value has set explicitely and not deduced from the textures assigned
OPACITY_CUTOFF_VAL_BIT,
SCATTERING_VAL_BIT,
// THe map bits must be in the same sequence as the enum names for the map channels
@ -73,6 +74,15 @@ public:
NUM_MAP_CHANNELS,
};
enum OpacityMapMode {
OPACITY_MAP_OPAQUE = 0,
OPACITY_MAP_MASK,
OPACITY_MAP_BLEND,
};
static std::string getOpacityMapModeName(OpacityMapMode mode);
// find the enum value from a string, return true if match found
static bool getOpacityMapModeFromName(const std::string& modeName, OpacityMapMode& mode);
// The signature is the Flags
Flags _flags;
@ -94,6 +104,27 @@ public:
Builder& withGlossy() { _flags.set(GLOSSY_VAL_BIT); return (*this); }
Builder& withTranslucentFactor() { _flags.set(OPACITY_VAL_BIT); return (*this); }
Builder& withTranslucentMap() { _flags.set(OPACITY_TRANSLUCENT_MAP_BIT); return (*this); }
Builder& withMaskMap() { _flags.set(OPACITY_MASK_MAP_BIT); return (*this); }
Builder& withOpacityMapMode(OpacityMapMode mode) {
switch (mode) {
case OPACITY_MAP_OPAQUE:
_flags.reset(OPACITY_TRANSLUCENT_MAP_BIT);
_flags.reset(OPACITY_MASK_MAP_BIT);
break;
case OPACITY_MAP_MASK:
_flags.reset(OPACITY_TRANSLUCENT_MAP_BIT);
_flags.set(OPACITY_MASK_MAP_BIT);
break;
case OPACITY_MAP_BLEND:
_flags.set(OPACITY_TRANSLUCENT_MAP_BIT);
_flags.reset(OPACITY_MASK_MAP_BIT);
break;
};
_flags.set(OPACITY_MAP_MODE_BIT); // Intentionally set the mode!
return (*this);
}
Builder& withOpacityCutoff() { _flags.set(OPACITY_CUTOFF_VAL_BIT); return (*this); }
Builder& withScattering() { _flags.set(SCATTERING_VAL_BIT); return (*this); }
@ -102,9 +133,6 @@ public:
Builder& withMetallicMap() { _flags.set(METALLIC_MAP_BIT); return (*this); }
Builder& withRoughnessMap() { _flags.set(ROUGHNESS_MAP_BIT); return (*this); }
Builder& withTranslucentMap() { _flags.set(OPACITY_TRANSLUCENT_MAP_BIT); return (*this); }
Builder& withMaskMap() { _flags.set(OPACITY_MASK_MAP_BIT); return (*this); }
Builder& withNormalMap() { _flags.set(NORMAL_MAP_BIT); return (*this); }
Builder& withOcclusionMap() { _flags.set(OCCLUSION_MAP_BIT); return (*this); }
Builder& withLightMap() { _flags.set(LIGHT_MAP_BIT); return (*this); }
@ -151,6 +179,9 @@ public:
void setOpacityMaskMap(bool value) { _flags.set(OPACITY_MASK_MAP_BIT, value); }
bool isOpacityMaskMap() const { return _flags[OPACITY_MASK_MAP_BIT]; }
void setOpacityCutoff(bool value) { _flags.set(OPACITY_CUTOFF_VAL_BIT, value); }
bool isOpacityCutoff() const { return _flags[OPACITY_CUTOFF_VAL_BIT]; }
void setNormalMap(bool value) { _flags.set(NORMAL_MAP_BIT, value); }
bool isNormalMap() const { return _flags[NORMAL_MAP_BIT]; }
@ -171,6 +202,26 @@ public:
// Translucency and Opacity Heuristics are combining several flags:
void setOpacityMapMode(OpacityMapMode mode) {
switch (mode) {
case OPACITY_MAP_OPAQUE:
_flags.reset(OPACITY_TRANSLUCENT_MAP_BIT);
_flags.reset(OPACITY_MASK_MAP_BIT);
break;
case OPACITY_MAP_MASK:
_flags.reset(OPACITY_TRANSLUCENT_MAP_BIT);
_flags.set(OPACITY_MASK_MAP_BIT);
break;
case OPACITY_MAP_BLEND:
_flags.set(OPACITY_TRANSLUCENT_MAP_BIT);
_flags.reset(OPACITY_MASK_MAP_BIT);
break;
};
_flags.set(OPACITY_MAP_MODE_BIT); // Intentionally set the mode!
}
bool isOpacityMapMode() const { return _flags[OPACITY_MAP_MODE_BIT]; }
OpacityMapMode getOpacityMapMode() const { return (isOpacityMaskMap() ? OPACITY_MAP_MASK : (isTranslucentMap() ? OPACITY_MAP_BLEND : OPACITY_MAP_OPAQUE)); }
bool isTranslucent() const { return isTranslucentFactor() || isTranslucentMap(); }
bool isOpaque() const { return !isTranslucent(); }
bool isSurfaceOpaque() const { return isOpaque() && !isOpacityMaskMap(); }
@ -229,6 +280,12 @@ public:
Builder& withoutMaskMap() { _value.reset(MaterialKey::OPACITY_MASK_MAP_BIT); _mask.set(MaterialKey::OPACITY_MASK_MAP_BIT); return (*this); }
Builder& withMaskMap() { _value.set(MaterialKey::OPACITY_MASK_MAP_BIT); _mask.set(MaterialKey::OPACITY_MASK_MAP_BIT); return (*this); }
Builder& withoutOpacityMapMode() { _value.reset(MaterialKey::OPACITY_MAP_MODE_BIT); _mask.set(MaterialKey::OPACITY_MAP_MODE_BIT); return (*this); }
Builder& withOpacityMapMode() { _value.set(MaterialKey::OPACITY_MAP_MODE_BIT); _mask.set(MaterialKey::OPACITY_MAP_MODE_BIT); return (*this); }
Builder& withoutOpacityCutoff() { _value.reset(MaterialKey::OPACITY_CUTOFF_VAL_BIT); _mask.set(MaterialKey::OPACITY_CUTOFF_VAL_BIT); return (*this); }
Builder& withOpacityCutoff() { _value.set(MaterialKey::OPACITY_CUTOFF_VAL_BIT); _mask.set(MaterialKey::OPACITY_CUTOFF_VAL_BIT); return (*this); }
Builder& withoutNormalMap() { _value.reset(MaterialKey::NORMAL_MAP_BIT); _mask.set(MaterialKey::NORMAL_MAP_BIT); return (*this); }
Builder& withNormalMap() { _value.set(MaterialKey::NORMAL_MAP_BIT); _mask.set(MaterialKey::NORMAL_MAP_BIT); return (*this); }
@ -283,6 +340,14 @@ public:
void setOpacity(float opacity);
float getOpacity() const { return _opacity; }
static const MaterialKey::OpacityMapMode DEFAULT_OPACITY_MAP_MODE;
void setOpacityMapMode(MaterialKey::OpacityMapMode opacityMapMode);
MaterialKey::OpacityMapMode getOpacityMapMode() const;
static const float DEFAULT_OPACITY_CUTOFF;
void setOpacityCutoff(float opacityCutoff);
float getOpacityCutoff() const { return _opacityCutoff; }
void setUnlit(bool value);
bool isUnlit() const { return _key.isUnlit(); }
@ -310,7 +375,8 @@ public:
// Albedo maps cannot have opacity detected until they are loaded
// This method allows const changing of the key/schemaBuffer without touching the map
void resetOpacityMap() const;
// return true if the opacity changed, flase otherwise
bool resetOpacityMap() const;
// conversion from legacy material properties to PBR equivalent
static float shininessToRoughness(float shininess) { return 1.0f - shininess / 100.0f; }
@ -357,6 +423,7 @@ private:
float _roughness { DEFAULT_ROUGHNESS };
float _metallic { DEFAULT_METALLIC };
float _scattering { DEFAULT_SCATTERING };
float _opacityCutoff { DEFAULT_OPACITY_CUTOFF };
std::array<glm::mat4, NUM_TEXCOORD_TRANSFORMS> _texcoordTransforms;
glm::vec2 _lightmapParams { 0.0, 1.0 };
glm::vec2 _materialParams { 0.0, 1.0 };
@ -365,7 +432,7 @@ private:
bool _defaultFallthrough { false };
std::unordered_map<uint, bool> _propertyFallthroughs { NUM_TOTAL_FLAGS };
mutable QMutex _textureMapsMutex { QMutex::Recursive };
mutable std::recursive_mutex _textureMapsMutex;
};
typedef std::shared_ptr<Material> MaterialPointer;
@ -425,18 +492,8 @@ public:
float _metallic { Material::DEFAULT_METALLIC }; // Not Metallic
float _scattering { Material::DEFAULT_SCATTERING }; // Scattering info
#if defined(__clang__)
__attribute__((unused))
#endif
glm::vec2 _spare { 0.0f }; // Padding
float _opacityCutoff { Material::DEFAULT_OPACITY_CUTOFF }; // Opacity cutoff applyed when using opacityMap as Mask
uint32_t _key { 0 }; // a copy of the materialKey
#if defined(__clang__)
__attribute__((unused))
#endif
glm::vec3 _spare2 { 0.0f };
// for alignment beauty, Material size == Mat4x4
// Texture Coord Transform Array
glm::mat4 _texcoordTransforms[Material::NUM_TEXCOORD_TRANSFORMS];

View file

@ -49,8 +49,7 @@ struct TexMapArray {
struct Material {
vec4 _emissiveOpacity;
vec4 _albedoRoughness;
vec4 _metallicScatteringSpare2;
vec4 _keySpare3;
vec4 _metallicScatteringOpacityCutoffKey;
};
LAYOUT_STD140(binding=GRAPHICS_BUFFER_MATERIAL) uniform materialBuffer {
@ -72,10 +71,11 @@ vec3 getMaterialAlbedo(Material m) { return m._albedoRoughness.rgb; }
float getMaterialRoughness(Material m) { return m._albedoRoughness.a; }
float getMaterialShininess(Material m) { return 1.0 - getMaterialRoughness(m); }
float getMaterialMetallic(Material m) { return m._metallicScatteringSpare2.x; }
float getMaterialScattering(Material m) { return m._metallicScatteringSpare2.y; }
float getMaterialMetallic(Material m) { return m._metallicScatteringOpacityCutoffKey.x; }
float getMaterialScattering(Material m) { return m._metallicScatteringOpacityCutoffKey.y; }
float getMaterialOpacityCutoff(Material m) { return m._metallicScatteringOpacityCutoffKey.z; }
BITFIELD getMaterialKey(Material m) { return floatBitsToInt(m._keySpare3.x); }
BITFIELD getMaterialKey(Material m) { return floatBitsToInt(m._metallicScatteringOpacityCutoffKey.w); }
const BITFIELD EMISSIVE_VAL_BIT = 0x00000001;
const BITFIELD UNLIT_VAL_BIT = 0x00000002;
@ -85,16 +85,18 @@ const BITFIELD GLOSSY_VAL_BIT = 0x00000010;
const BITFIELD OPACITY_VAL_BIT = 0x00000020;
const BITFIELD OPACITY_MASK_MAP_BIT = 0x00000040;
const BITFIELD OPACITY_TRANSLUCENT_MAP_BIT = 0x00000080;
const BITFIELD SCATTERING_VAL_BIT = 0x00000100;
const BITFIELD OPACITY_MAP_MODE_BIT = 0x00000100;
const BITFIELD OPACITY_CUTOFF_VAL_BIT = 0x00000200;
const BITFIELD SCATTERING_VAL_BIT = 0x00000400;
const BITFIELD EMISSIVE_MAP_BIT = 0x00000200;
const BITFIELD ALBEDO_MAP_BIT = 0x00000400;
const BITFIELD METALLIC_MAP_BIT = 0x00000800;
const BITFIELD ROUGHNESS_MAP_BIT = 0x00001000;
const BITFIELD NORMAL_MAP_BIT = 0x00002000;
const BITFIELD OCCLUSION_MAP_BIT = 0x00004000;
const BITFIELD LIGHTMAP_MAP_BIT = 0x00008000;
const BITFIELD SCATTERING_MAP_BIT = 0x00010000;
const BITFIELD EMISSIVE_MAP_BIT = 0x00000800;
const BITFIELD ALBEDO_MAP_BIT = 0x00001000;
const BITFIELD METALLIC_MAP_BIT = 0x00002000;
const BITFIELD ROUGHNESS_MAP_BIT = 0x00004000;
const BITFIELD NORMAL_MAP_BIT = 0x00008000;
const BITFIELD OCCLUSION_MAP_BIT = 0x00010000;
const BITFIELD LIGHTMAP_MAP_BIT = 0x00020000;
const BITFIELD SCATTERING_MAP_BIT = 0x00040000;
<@endif@>

View file

@ -214,14 +214,22 @@ vec3 fetchLightMap(vec2 uv) {
}
<@endfunc@>
<@func evalMaterialOpacity(fetchedOpacity, materialOpacity, matKey, opacity)@>
<@func evalMaterialOpacityMask(fetchedOpacity, materialOpacityCutoff, opacity)@>
{
const float OPACITY_MASK_THRESHOLD = 0.5;
<$opacity$> = mix(1.0,
mix(<$fetchedOpacity$>,
step(OPACITY_MASK_THRESHOLD, <$fetchedOpacity$>),
float((<$matKey$> & OPACITY_MASK_MAP_BIT) != 0)),
float((<$matKey$> & (OPACITY_TRANSLUCENT_MAP_BIT | OPACITY_MASK_MAP_BIT)) != 0)) * <$materialOpacity$>;
// This path only valid for opaque or texel opaque material
<$opacity$> = step(<$materialOpacityCutoff$>, <$fetchedOpacity$>);
}
<@endfunc@>
<@func evalMaterialOpacity(fetchedOpacity, materialOpacityCutoff, materialOpacity, matKey, opacity)@>
{
// This path only valid for transparent material
// Assert that float((<$matKey$> & (OPACITY_TRANSLUCENT_MAP_BIT | OPACITY_MASK_MAP_BIT)) != 0)) == 1.0
<$opacity$> = mix(<$fetchedOpacity$>,
step(<$materialOpacityCutoff$>, <$fetchedOpacity$>),
float((<$matKey$> & OPACITY_MASK_MAP_BIT) != 0))
* <$materialOpacity$>;
}
<@endfunc@>

View file

@ -139,6 +139,14 @@ NetworkMaterialResource::ParsedMaterials NetworkMaterialResource::parseJSONMater
* @property {string} opacityMap - The URL of the opacity texture image. Set the value the same as the <code>albedoMap</code>
* value for transparency.
* <code>"hifi_pbr"</code> model only.
* @property {number|string} opacityMapMode - The mode defining the interpretation of the opacity map. Values can be:
* <code>"OPACITY_MAP_OPAQUE"</code> for ignoring the opacity map information.
* <code>"OPACITY_MAP_MASK"</code> for using the opacity map as a mask, where only the texel greater than opacityCutoff are visible and rendered opaque.
* <code>"OPACITY_MAP_BLEND"</code> for using the opacity map for alpha blending the material surface with the background.
* Set to <code>"fallthrough"</code> to fall through to the material below. <code>"hifi_pbr"</code> model only.
* @property {number|string} opacityCutoff - The opacity cutoff threshold used to determine the opaque texels of the Opacity map
* when opacityMapMode is "OPACITY_MAP_MASK", range <code>0.0</code> &ndash; <code>1.0</code>.
* Set to <code>"fallthrough"</code> to fall through to the material below. <code>"hifi_pbr"</code> model only.
* @property {string} roughnessMap - The URL of the roughness texture image. You can use this or <code>glossMap</code>, but not
* both.
* Set to <code>"fallthrough"</code> to fall through to the material below. <code>"hifi_pbr"</code> model only.
@ -258,6 +266,24 @@ std::pair<std::string, std::shared_ptr<NetworkMaterial>> NetworkMaterialResource
} else if (value.isDouble()) {
material->setMetallic(value.toDouble());
}
} else if (key == "opacityMapMode") {
auto value = materialJSON.value(key);
auto valueString = (value.isString() ? value.toString() : "");
if (valueString == FALLTHROUGH) {
material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::OPACITY_MAP_MODE_BIT);
} else {
graphics::MaterialKey::OpacityMapMode mode;
if (graphics::MaterialKey::getOpacityMapModeFromName(valueString.toStdString(), mode)) {
material->setOpacityMapMode(mode);
}
}
} else if (key == "opacityCutoff") {
auto value = materialJSON.value(key);
if (value.isString() && value.toString() == FALLTHROUGH) {
material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::OPACITY_CUTOFF_VAL_BIT);
} else if (value.isDouble()) {
material->setOpacityCutoff(value.toDouble());
}
} else if (key == "scattering") {
auto value = materialJSON.value(key);
if (value.isString() && value.toString() == FALLTHROUGH) {
@ -748,13 +774,14 @@ bool NetworkMaterial::isMissingTexture() {
return false;
}
void NetworkMaterial::checkResetOpacityMap() {
bool NetworkMaterial::checkResetOpacityMap() {
// If material textures are loaded, check the material translucency
// FIXME: This should not be done here. The opacity map should already be reset in Material::setTextureMap.
// However, currently that code can be called before the albedo map is defined, so resetOpacityMap will fail.
// Geometry::areTexturesLoaded() is called repeatedly until it returns true, so we do the check here for now
const auto& albedoTexture = _textures[NetworkMaterial::MapChannel::ALBEDO_MAP];
if (albedoTexture.texture) {
resetOpacityMap();
return resetOpacityMap();
}
return false;
}

View file

@ -34,7 +34,7 @@ public:
void setLightMap(const QUrl& url);
bool isMissingTexture();
void checkResetOpacityMap();
bool checkResetOpacityMap();
class Texture {
public:

View file

@ -0,0 +1,17 @@
//
// MaterialCacheScriptingInterface.cpp
// libraries/mmodel-networking/src/model-networking
//
// Created by Sam Gateau on 17 September 2019.
// Copyright 2019 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 "MaterialCacheScriptingInterface.h"
MaterialCacheScriptingInterface::MaterialCacheScriptingInterface() :
ScriptableResourceCache::ScriptableResourceCache(DependencyManager::get<MaterialCache>())
{ }

View file

@ -0,0 +1,51 @@
//
// MaterialCacheScriptingInterface.h
// libraries/material-networking/src/material-networking
//
// Created by Sam Gateau on 17 September 2019.
// Copyright 2019 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
//
#pragma once
#ifndef hifi_MaterialCacheScriptingInterface_h
#define hifi_MaterialCacheScriptingInterface_h
#include <QObject>
#include <ResourceCache.h>
#include "MaterialCache.h"
class MaterialCacheScriptingInterface : public ScriptableResourceCache, public Dependency {
Q_OBJECT
// Properties are copied over from ResourceCache (see ResourceCache.h for reason).
/**jsdoc
* The <code>TextureCache</code> API manages texture cache resources.
*
* @namespace TextureCache
*
* @hifi-interface
* @hifi-client-entity
* @hifi-avatar
*
* @property {number} numTotal - Total number of total resources. <em>Read-only.</em>
* @property {number} numCached - Total number of cached resource. <em>Read-only.</em>
* @property {number} sizeTotal - Size in bytes of all resources. <em>Read-only.</em>
* @property {number} sizeCached - Size in bytes of all cached resources. <em>Read-only.</em>
*
* @borrows ResourceCache.getResourceList as getResourceList
* @borrows ResourceCache.updateTotalSize as updateTotalSize
* @borrows ResourceCache.prefetch as prefetch
* @borrows ResourceCache.dirty as dirty
*/
public:
MaterialCacheScriptingInterface();
};
#endif // hifi_MaterialCacheScriptingInterface_h

View file

@ -472,7 +472,10 @@ bool Geometry::areTexturesLoaded() const {
return false;
}
material->checkResetOpacityMap();
bool changed = material->checkResetOpacityMap();
if (changed) {
qCWarning(modelnetworking) << "Material list: opacity change detected for material " << material->getName().c_str();
}
}
for (auto& materialMapping : _materialMapping) {
@ -483,7 +486,10 @@ bool Geometry::areTexturesLoaded() const {
return false;
}
materialPair.second->checkResetOpacityMap();
bool changed = materialPair.second->checkResetOpacityMap();
if (changed) {
qCWarning(modelnetworking) << "Mapping list: opacity change detected for material " << materialPair.first.c_str();
}
}
}
}

View file

@ -47,6 +47,10 @@ Q_DECLARE_METATYPE(JSONCallbackParameters)
const QString ACCOUNTS_GROUP = "accounts";
const int POST_SETTINGS_INTERVAL = 10 * MSECS_PER_SECOND;
const int PULL_SETTINGS_RETRY_INTERVAL = 2 * MSECS_PER_SECOND;
const int MAX_PULL_RETRIES = 10;
JSONCallbackParameters::JSONCallbackParameters(QObject* callbackReceiver,
const QString& jsonCallbackMethod,
const QString& errorCallbackMethod) :
@ -70,9 +74,10 @@ QJsonObject AccountManager::dataObjectFromResponse(QNetworkReply* requestReply)
}
}
AccountManager::AccountManager(UserAgentGetter userAgentGetter) :
AccountManager::AccountManager(bool accountSettingsEnabled, UserAgentGetter userAgentGetter) :
_userAgentGetter(userAgentGetter),
_authURL()
_authURL(),
_accountSettingsEnabled(accountSettingsEnabled)
{
qRegisterMetaType<OAuthAccessToken>("OAuthAccessToken");
qRegisterMetaTypeStreamOperators<OAuthAccessToken>("OAuthAccessToken");
@ -87,12 +92,26 @@ AccountManager::AccountManager(UserAgentGetter userAgentGetter) :
qRegisterMetaType<AccountManagerAuth::Type>();
connect(this, &AccountManager::loginComplete, this, &AccountManager::uploadPublicKey);
connect(this, &AccountManager::loginComplete, this, &AccountManager::requestAccountSettings);
_pullSettingsRetryTimer = new QTimer(this);
_pullSettingsRetryTimer->setSingleShot(true);
_pullSettingsRetryTimer->setInterval(PULL_SETTINGS_RETRY_INTERVAL);
connect(_pullSettingsRetryTimer, &QTimer::timeout, this, &AccountManager::requestAccountSettings);
_postSettingsTimer = new QTimer(this);
_postSettingsTimer->setInterval(POST_SETTINGS_INTERVAL);
connect(this, SIGNAL(accountSettingsLoaded()), _postSettingsTimer, SLOT(start()));
connect(this, &AccountManager::logoutComplete, _postSettingsTimer, &QTimer::stop);
connect(_postSettingsTimer, &QTimer::timeout, this, &AccountManager::postAccountSettings);
connect(qApp, &QCoreApplication::aboutToQuit, this, &AccountManager::postAccountSettings);
}
const QString DOUBLE_SLASH_SUBSTITUTE = "slashslash";
const QString ACCOUNT_MANAGER_REQUESTED_SCOPE = "owner";
void AccountManager::logout() {
postAccountSettings();
_numPullRetries = 0;
// a logout means we want to delete the DataServerAccountInfo we currently have for this URL, in-memory and in file
_accountInfo = DataServerAccountInfo();
@ -104,6 +123,8 @@ void AccountManager::logout() {
emit logoutComplete();
// the username has changed to blank
emit usernameChanged(QString());
_settings.loggedOut();
}
QString accountFileDir() {
@ -160,33 +181,7 @@ void AccountManager::setAuthURL(const QUrl& authURL) {
qCDebug(networking) << "Found metaverse API account information for" << qPrintable(_authURL.toString());
} else {
// we didn't have a file - see if we can migrate old settings and store them in the new file
// check if there are existing access tokens to load from settings
Settings settings;
settings.beginGroup(ACCOUNTS_GROUP);
foreach(const QString& key, settings.allKeys()) {
// take a key copy to perform the double slash replacement
QString keyCopy(key);
QUrl keyURL(keyCopy.replace(DOUBLE_SLASH_SUBSTITUTE, "//"));
if (keyURL == _authURL) {
// pull out the stored access token and store it in memory
_accountInfo = settings.value(key).value<DataServerAccountInfo>();
qCDebug(networking) << "Migrated an access token for" << qPrintable(keyURL.toString())
<< "from previous settings file";
}
}
settings.endGroup();
if (_accountInfo.getAccessToken().token.isEmpty()) {
qCWarning(networking) << "Unable to load account file. No existing account settings will be loaded.";
} else {
// persist the migrated settings to file
persistAccountToFile();
}
qCWarning(networking) << "Unable to load account file. No existing account settings will be loaded.";
}
if (_isAgent && !_accountInfo.getAccessToken().token.isEmpty() && !_accountInfo.hasProfile()) {
@ -199,6 +194,10 @@ void AccountManager::setAuthURL(const QUrl& authURL) {
refreshAccessToken();
}
if (isLoggedIn()) {
emit loginComplete(_authURL);
}
// tell listeners that the auth endpoint has changed
emit authEndpointChanged();
}
@ -804,6 +803,116 @@ void AccountManager::requestProfileError(QNetworkReply::NetworkError error) {
qCDebug(networking) << "AccountManager requestProfileError - " << error;
}
void AccountManager::requestAccountSettings() {
if (!_accountSettingsEnabled) {
return;
}
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QUrl lockerURL = _authURL;
lockerURL.setPath("/api/v1/user/locker");
QNetworkRequest lockerRequest(lockerURL);
lockerRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
lockerRequest.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter());
lockerRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, _accountInfo.getAccessToken().authorizationHeaderValue());
QNetworkReply* lockerReply = networkAccessManager.get(lockerRequest);
connect(lockerReply, &QNetworkReply::finished, this, &AccountManager::requestAccountSettingsFinished);
connect(lockerReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestAccountSettingsError(QNetworkReply::NetworkError)));
_settings.startedLoading();
}
void AccountManager::requestAccountSettingsFinished() {
QNetworkReply* lockerReply = reinterpret_cast<QNetworkReply*>(sender());
QJsonDocument jsonResponse = QJsonDocument::fromJson(lockerReply->readAll());
const QJsonObject& rootObject = jsonResponse.object();
if (rootObject.contains("status") && rootObject["status"].toString() == "success") {
if (rootObject.contains("data") && rootObject["data"].isObject()) {
_settings.unpack(rootObject["data"].toObject());
emit accountSettingsLoaded();
} else {
qCDebug(networking) << "Error in response for account settings: no data object";
if (!_pullSettingsRetryTimer->isActive() && _numPullRetries < MAX_PULL_RETRIES) {
++_numPullRetries;
_pullSettingsRetryTimer->start();
}
}
} else {
qCDebug(networking) << "Error in response for account settings" << lockerReply->errorString();
if (!_pullSettingsRetryTimer->isActive() && _numPullRetries < MAX_PULL_RETRIES) {
++_numPullRetries;
_pullSettingsRetryTimer->start();
}
}
}
void AccountManager::requestAccountSettingsError(QNetworkReply::NetworkError error) {
qCWarning(networking) << "Account settings request encountered an error" << error;
if (!_pullSettingsRetryTimer->isActive() && _numPullRetries < MAX_PULL_RETRIES) {
++_numPullRetries;
_pullSettingsRetryTimer->start();
}
}
void AccountManager::postAccountSettings() {
if (!_accountSettingsEnabled) {
return;
}
if (_settings.lastChangeTimestamp() <= _lastSuccessfulSyncTimestamp && _lastSuccessfulSyncTimestamp != 0) {
// Nothing changed, skipping settings post
return;
}
if (!isLoggedIn()) {
qCWarning(networking) << "Can't post account settings: Not logged in";
return;
}
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QUrl lockerURL = _authURL;
lockerURL.setPath("/api/v1/user/locker");
QNetworkRequest lockerRequest(lockerURL);
lockerRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
lockerRequest.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter());
lockerRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
lockerRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, _accountInfo.getAccessToken().authorizationHeaderValue());
_currentSyncTimestamp = _settings.lastChangeTimestamp();
QJsonObject dataObj;
dataObj.insert("locker", _settings.pack());
auto postData = QJsonDocument(dataObj).toJson(QJsonDocument::Compact);
QNetworkReply* lockerReply = networkAccessManager.put(lockerRequest, postData);
connect(lockerReply, &QNetworkReply::finished, this, &AccountManager::postAccountSettingsFinished);
connect(lockerReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(postAccountSettingsError(QNetworkReply::NetworkError)));
}
void AccountManager::postAccountSettingsFinished() {
QNetworkReply* lockerReply = reinterpret_cast<QNetworkReply*>(sender());
QJsonDocument jsonResponse = QJsonDocument::fromJson(lockerReply->readAll());
const QJsonObject& rootObject = jsonResponse.object();
if (rootObject.contains("status") && rootObject["status"].toString() == "success") {
_lastSuccessfulSyncTimestamp = _currentSyncTimestamp;
} else {
qCDebug(networking) << "Error in response for account settings post" << lockerReply->errorString();
}
}
void AccountManager::postAccountSettingsError(QNetworkReply::NetworkError error) {
qCWarning(networking) << "Post encountered an error" << error;
}
void AccountManager::generateNewKeypair(bool isUserKeypair, const QUuid& domainID) {
if (thread() != QThread::currentThread()) {
@ -926,7 +1035,7 @@ void AccountManager::publicKeyUploadSucceeded(QNetworkReply* reply) {
void AccountManager::publicKeyUploadFailed(QNetworkReply* reply) {
// the public key upload has failed
qWarning() << "Public key upload failed from AccountManager" << reply->errorString();
qCritical() << "PAGE: Public key upload failed from AccountManager to" << reply->url() << reply->errorString();
// we aren't waiting for a response any longer
_isWaitingForKeypairResponse = false;

View file

@ -14,18 +14,19 @@
#include <QtCore/QByteArray>
#include <QtCore/QObject>
#include <QtCore/QTimer>
#include <QtCore/QUrl>
#include <QtNetwork/QNetworkReply>
#include <QUrlQuery>
#include <DependencyManager.h>
#include "AccountSettings.h"
#include "DataServerAccountInfo.h"
#include "NetworkingConstants.h"
#include "NetworkAccessManager.h"
#include "DataServerAccountInfo.h"
#include "SharedUtil.h"
#include <DependencyManager.h>
class JSONCallbackParameters {
public:
JSONCallbackParameters(QObject* callbackReceiver = nullptr,
@ -59,7 +60,7 @@ const auto DEFAULT_USER_AGENT_GETTER = []() -> QString { return HIGH_FIDELITY_US
class AccountManager : public QObject, public Dependency {
Q_OBJECT
public:
AccountManager(UserAgentGetter userAgentGetter = DEFAULT_USER_AGENT_GETTER);
AccountManager(bool accountSettingsEnabled = false, UserAgentGetter userAgentGetter = DEFAULT_USER_AGENT_GETTER);
QNetworkRequest createRequest(QString path, AccountManagerAuth::Type authType);
Q_INVOKABLE void sendRequest(const QString& path,
@ -107,6 +108,8 @@ public:
void setConfigFileURL(const QString& fileURL) { _configFileURL = fileURL; }
void saveLoginStatus(bool isLoggedIn);
AccountSettings& getAccountSettings() { return _settings; }
public slots:
void requestAccessToken(const QString& login, const QString& password);
void requestAccessTokenWithSteam(QByteArray authSessionTicket);
@ -136,6 +139,7 @@ signals:
void logoutComplete();
void newKeypair();
void limitedCommerceChanged();
void accountSettingsLoaded();
private slots:
void handleKeypairGenerationError();
@ -145,6 +149,13 @@ private slots:
void publicKeyUploadFailed(QNetworkReply* reply);
void generateNewKeypair(bool isUserKeypair = true, const QUuid& domainID = QUuid());
void requestAccountSettings();
void requestAccountSettingsFinished();
void requestAccountSettingsError(QNetworkReply::NetworkError error);
void postAccountSettings();
void postAccountSettingsFinished();
void postAccountSettingsError(QNetworkReply::NetworkError error);
private:
AccountManager(AccountManager const& other) = delete;
void operator=(AccountManager const& other) = delete;
@ -170,6 +181,14 @@ private:
bool _limitedCommerce { false };
QString _configFileURL;
bool _accountSettingsEnabled { false };
AccountSettings _settings;
quint64 _currentSyncTimestamp { 0 };
quint64 _lastSuccessfulSyncTimestamp { 0 };
int _numPullRetries { 0 };
QTimer* _pullSettingsRetryTimer { nullptr };
QTimer* _postSettingsTimer { nullptr };
};
#endif // hifi_AccountManager_h

View file

@ -0,0 +1,55 @@
//
// AccountSettings.cpp
// libraries/networking/src
//
// Created by Clement Brisset on 9/12/19.
// Copyright 2019 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 "AccountSettings.h"
#include <QJsonDocument>
#include <QJsonObject>
#include "NetworkLogging.h"
#include "SharedUtil.h"
static QString HOME_LOCATION_KEY { "home_location" };
QJsonObject AccountSettings::pack() {
QJsonObject data;
QReadLocker lock(&_settingsLock);
data.insert(HOME_LOCATION_KEY, _homeLocation);
return data;
}
void AccountSettings::unpack(QJsonObject data) {
QWriteLocker lock(&_settingsLock);
_lastChangeTimestamp = usecTimestampNow();
auto it = data.find(HOME_LOCATION_KEY);
_homeLocationState = it != data.end() && it->isString() ? Loaded : NotPresent;
_homeLocation = _homeLocationState == Loaded ? it->toString() : "";
}
void AccountSettings::setHomeLocation(QString homeLocation) {
QWriteLocker lock(&_settingsLock);
if (homeLocation != _homeLocation) {
_lastChangeTimestamp = usecTimestampNow();
}
_homeLocation = homeLocation;
}
void AccountSettings::startedLoading() {
_homeLocationState = Loading;
}
void AccountSettings::loggedOut() {
_homeLocationState = LoggedOut;
}

View file

@ -0,0 +1,47 @@
//
// AccountSettings.h
// libraries/networking/src
//
// Created by Clement Brisset on 9/12/19.
// Copyright 2019 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_AccountSettings_h
#define hifi_AccountSettings_h
#include <QJsonObject>
#include <QReadWriteLock>
#include <QString>
class AccountSettings {
public:
enum State {
LoggedOut,
Loading,
Loaded,
NotPresent
};
void loggedOut();
void startedLoading();
quint64 lastChangeTimestamp() const { return _lastChangeTimestamp; }
QJsonObject pack();
void unpack(QJsonObject data);
State homeLocationState() const { QReadLocker lock(&_settingsLock); return _homeLocationState; }
QString getHomeLocation() const { QReadLocker lock(&_settingsLock); return _homeLocation; }
void setHomeLocation(QString homeLocation);
private:
mutable QReadWriteLock _settingsLock;
quint64 _lastChangeTimestamp { 0 };
State _homeLocationState { LoggedOut };
QString _homeLocation;
};
#endif /* hifi_AccountSettings_h */

View file

@ -234,14 +234,38 @@ const JSONCallbackParameters& AddressManager::apiCallbackParameters() {
return callbackParams;
}
bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) {
bool AddressManager::handleUrl(const QUrl& lookupUrlIn, LookupTrigger trigger) {
static QString URL_TYPE_USER = "user";
static QString URL_TYPE_DOMAIN_ID = "domain_id";
static QString URL_TYPE_PLACE = "place";
static QString URL_TYPE_NETWORK_ADDRESS = "network_address";
if (lookupUrl.scheme() == URL_SCHEME_HIFI) {
qCDebug(networking) << "Trying to go to URL" << lookupUrl.toString();
QUrl lookupUrl = lookupUrlIn;
qCDebug(networking) << "Trying to go to URL" << lookupUrl.toString();
if (lookupUrl.scheme().isEmpty() && !lookupUrl.path().startsWith("/")) {
// 'urls' without schemes are taken as domain names, as opposed to
// simply a path portion of a url, so we need to set the scheme
lookupUrl.setScheme(URL_SCHEME_HIFI);
}
static const QRegExp PORT_REGEX = QRegExp("\\d{1,5}(\\/.*)?");
if(!lookupUrl.scheme().isEmpty() && lookupUrl.host().isEmpty() && PORT_REGEX.exactMatch(lookupUrl.path())) {
// this is in the form somewhere:<port>, convert it to hifi://somewhere:<port>
lookupUrl = QUrl(URL_SCHEME_HIFI + "://" + lookupUrl.toString());
}
// it should be noted that url's in the form
// somewhere:<port> are not valid, as that
// would indicate that the scheme is 'somewhere'
// use hifi://somewhere:<port> instead
if (lookupUrl.scheme() == URL_SCHEME_HIFI) {
if (lookupUrl.host().isEmpty()) {
// this was in the form hifi:/somewhere or hifi:somewhere. Fix it by making it hifi://somewhere
static const QRegExp HIFI_SCHEME_REGEX = QRegExp(URL_SCHEME_HIFI + ":\\/?", Qt::CaseInsensitive);
lookupUrl = QUrl(lookupUrl.toString().replace(HIFI_SCHEME_REGEX, URL_SCHEME_HIFI + "://"));
}
DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::LookupAddress);
@ -379,25 +403,11 @@ bool isPossiblePlaceName(QString possiblePlaceName) {
}
void AddressManager::handleLookupString(const QString& lookupString, bool fromSuggestions) {
if (!lookupString.isEmpty()) {
QString sanitizedString = lookupString.trimmed();
if (!sanitizedString.isEmpty()) {
// make this a valid hifi URL and handle it off to handleUrl
QString sanitizedString = lookupString.trimmed();
QUrl lookupURL;
if (!lookupString.startsWith('/')) {
// sometimes we need to handle lookupStrings like hifi:/somewhere
const QRegExp HIFI_SCHEME_REGEX = QRegExp(URL_SCHEME_HIFI + ":\\/{1,2}", Qt::CaseInsensitive);
sanitizedString = sanitizedString.remove(HIFI_SCHEME_REGEX);
lookupURL = QUrl(sanitizedString);
if (lookupURL.scheme().isEmpty() || lookupURL.scheme().toLower() == LOCALHOST) {
lookupURL = QUrl("hifi://" + sanitizedString);
}
} else {
lookupURL = QUrl(sanitizedString);
}
handleUrl(lookupURL, fromSuggestions ? Suggestions : UserInput);
handleUrl(sanitizedString, fromSuggestions ? Suggestions : UserInput);
}
}

View file

@ -52,7 +52,7 @@ LimitedNodeList::LimitedNodeList(int socketListenPort, int dtlsListenPort) :
_nodeSocket.bind(QHostAddress::AnyIPv4, port);
quint16 assignedPort = _nodeSocket.localPort();
if (socketListenPort != INVALID_PORT && socketListenPort != 0 && socketListenPort != assignedPort) {
qCCritical(networking) << "NodeList is unable to assign requested port of" << socketListenPort;
qCCritical(networking) << "PAGE: NodeList is unable to assign requested port of" << socketListenPort;
}
qCDebug(networking) << "NodeList socket is listening on" << assignedPort;
@ -1155,6 +1155,7 @@ void LimitedNodeList::startSTUNPublicSocketUpdate() {
void LimitedNodeList::possiblyTimeoutSTUNAddressLookup() {
if (_stunSockAddr.getAddress().isNull()) {
// our stun address is still NULL, but we've been waiting for long enough - time to force a fail
qCCritical(networking) << "PAGE: Failed to lookup address of STUN server" << STUN_SERVER_HOSTNAME;
stopInitialSTUNUpdate(false);
}
}
@ -1170,7 +1171,7 @@ void LimitedNodeList::stopInitialSTUNUpdate(bool success) {
if (!success) {
// if we're here this was the last failed STUN request
// use our DS as our stun server
qCDebug(networking, "Failed to lookup public address via STUN server at %s:%hu.",
qCWarning(networking, "PAGE: Failed to lookup public address via STUN server at %s:%hu (likely a critical error for auto-networking).",
STUN_SERVER_HOSTNAME, STUN_SERVER_PORT);
qCDebug(networking) << "LimitedNodeList public socket will be set with local port and null QHostAddress.";
@ -1212,7 +1213,8 @@ void LimitedNodeList::updateLocalSocket() {
QTcpSocket* localIPTestSocket = new QTcpSocket;
connect(localIPTestSocket, &QTcpSocket::connected, this, &LimitedNodeList::connectedForLocalSocketTest);
connect(localIPTestSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(errorTestingLocalSocket()));
connect(localIPTestSocket, static_cast<void(QTcpSocket::*)(QAbstractSocket::SocketError)>(&QTcpSocket::error),
this, &LimitedNodeList::errorTestingLocalSocket);
// attempt to connect to our reliable host
localIPTestSocket->connectToHost(RELIABLE_LOCAL_IP_CHECK_HOST, RELIABLE_LOCAL_IP_CHECK_PORT);
@ -1242,6 +1244,8 @@ void LimitedNodeList::errorTestingLocalSocket() {
// then use our possibly updated guessed local address as fallback
if (!_hasTCPCheckedLocalSocket) {
setLocalSocket(HifiSockAddr { getGuessedLocalAddress(), _nodeSocket.localPort() });
qCCritical(networking) << "PAGE: Can't connect to Google DNS service via TCP, falling back to guessed local address"
<< getLocalSockAddr();
}
localIPTestSocket->deleteLater();
@ -1325,7 +1329,7 @@ void LimitedNodeList::putLocalPortIntoSharedMemory(const QString key, QObject* p
qCDebug(networking) << "Wrote local listening port" << localPort << "to shared memory at key" << key;
} else {
qWarning() << "Failed to create and attach to shared memory to share local port with assignment-client children.";
qWarning() << "ALERT: Failed to create and attach to shared memory to share local port with assignment-client children.";
}
}

View file

@ -148,6 +148,8 @@ void ResourceCacheSharedItems::clear() {
ScriptableResourceCache::ScriptableResourceCache(QSharedPointer<ResourceCache> resourceCache) {
_resourceCache = resourceCache;
connect(&(*_resourceCache), &ResourceCache::dirty,
this, &ScriptableResourceCache::dirty, Qt::DirectConnection);
}
QVariantList ScriptableResourceCache::getResourceList() {
@ -323,7 +325,11 @@ QVariantList ResourceCache::getResourceList() {
BLOCKING_INVOKE_METHOD(this, "getResourceList",
Q_RETURN_ARG(QVariantList, list));
} else {
auto resources = _resources.uniqueKeys();
QList<QUrl> resources;
{
QReadLocker locker(&_resourcesLock);
resources = _resources.uniqueKeys();
}
list.reserve(resources.size());
for (auto& resource : resources) {
list << resource;
@ -510,7 +516,7 @@ void ResourceCache::updateTotalSize(const qint64& deltaSize) {
emit dirty();
}
QList<QSharedPointer<Resource>> ResourceCache::getLoadingRequests() {
return DependencyManager::get<ResourceCacheSharedItems>()->getLoadingRequests();
}

View file

@ -317,6 +317,13 @@ class ScriptableResourceCache : public QObject {
Q_PROPERTY(size_t sizeTotal READ getSizeTotalResources NOTIFY dirty)
Q_PROPERTY(size_t sizeCached READ getSizeCachedResources NOTIFY dirty)
/**jsdoc
* @property {number} numGlobalQueriesPending - Total number of global queries pending (across all resource managers). <em>Read-only.</em>
* @property {number} numGlobalQueriesLoading - Total number of global queries loading (across all resource managers). <em>Read-only.</em>
*/
Q_PROPERTY(size_t numGlobalQueriesPending READ getNumGlobalQueriesPending NOTIFY dirty)
Q_PROPERTY(size_t numGlobalQueriesLoading READ getNumGlobalQueriesLoading NOTIFY dirty)
public:
ScriptableResourceCache(QSharedPointer<ResourceCache> resourceCache);
@ -390,6 +397,9 @@ private:
size_t getSizeTotalResources() const { return _resourceCache->getSizeTotalResources(); }
size_t getNumCachedResources() const { return _resourceCache->getNumCachedResources(); }
size_t getSizeCachedResources() const { return _resourceCache->getSizeCachedResources(); }
size_t getNumGlobalQueriesPending() const { return ResourceCache::getPendingRequestCount(); }
size_t getNumGlobalQueriesLoading() const { return ResourceCache::getLoadingRequestCount(); }
};
/// Base class for resources.

View file

@ -269,7 +269,7 @@ bool Connection::processReceivedSequenceNumber(SequenceNumber sequenceNumber, in
bool wasDuplicate = false;
if (sequenceNumber > _lastReceivedSequenceNumber) {
// Update largest recieved sequence number
// Update largest received sequence number
_lastReceivedSequenceNumber = sequenceNumber;
} else {
// Otherwise, it could be a resend, try and remove it from the loss list
@ -312,9 +312,7 @@ void Connection::processControl(ControlPacketPointer controlPacket) {
// We're already in a state where we've received a handshake ack, so we are likely in a state
// where the other end expired our connection. Let's reset.
#ifdef UDT_CONNECTION_DEBUG
qCDebug(networking) << "Got HandshakeRequest from" << _destination << ", stopping SendQueue";
#endif
qCDebug(networking) << "Got HandshakeRequest from" << _destination << "while active, stopping SendQueue";
_hasReceivedHandshakeACK = false;
stopSendQueue();
}
@ -333,7 +331,7 @@ void Connection::processACK(ControlPacketPointer controlPacket) {
// validate that this isn't a BS ACK
if (ack > getSendQueue().getCurrentSequenceNumber()) {
// in UDT they specifically break the connection here - do we want to do anything?
Q_ASSERT_X(false, "Connection::processACK", "ACK recieved higher than largest sent sequence number");
Q_ASSERT_X(false, "Connection::processACK", "ACK received higher than largest sent sequence number");
return;
}

View file

@ -73,6 +73,7 @@ public:
void setMaxBandwidth(int maxBandwidth);
void sendHandshakeRequest();
bool hasReceivedHandshake() const { return _hasReceivedHandshake; }
void recordSentUnreliablePackets(int wireSize, int payloadSize);
void recordReceivedUnreliablePackets(int wireSize, int payloadSize);

Some files were not shown because too many files have changed in this diff Show more