Merge branch 'master' of github.com:highfidelity/hifi into privateUserData

This commit is contained in:
Zach Fox 2019-05-13 14:13:34 -07:00
commit e87381f431
172 changed files with 2981 additions and 1498 deletions

View file

@ -10,7 +10,14 @@ endif()
include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/macros/TargetPython.cmake")
target_python()
if (HIFI_ANDROID )
# set our OS X deployment target
# (needs to be set before first project() call and before prebuild.py)
# Will affect VCPKG dependencies
if (APPLE)
set(ENV{MACOSX_DEPLOYMENT_TARGET} 10.9)
endif()
if (HIFI_ANDROID)
execute_process(
COMMAND ${HIFI_PYTHON_EXEC} ${CMAKE_CURRENT_SOURCE_DIR}/prebuild.py --android ${HIFI_ANDROID_APP} --build-root ${CMAKE_BINARY_DIR}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
@ -55,7 +62,7 @@ endif()
# Use default time server if none defined in environment
set_from_env(TIMESERVER_URL TIMESERVER_URL "http://sha256timestamp.ws.symantec.com/sha256/timestamp")
set(HIFI_USE_OPTIMIZED_IK OFF)
set(HIFI_USE_OPTIMIZED_IK_OPTION OFF)
set(BUILD_CLIENT_OPTION ON)
set(BUILD_SERVER_OPTION ON)
set(BUILD_TESTS_OPTION OFF)
@ -119,7 +126,7 @@ if (USE_GLES AND (NOT ANDROID))
set(DISABLE_QML_OPTION ON)
endif()
option(HIFI_USE_OPTIMIZED_IK "USE OPTIMIZED IK" ${HIFI_USE_OPTIMIZED_IK_OPTION})
option(HIFI_USE_OPTIMIZED_IK "Use optimized IK" ${HIFI_USE_OPTIMIZED_IK_OPTION})
option(BUILD_CLIENT "Build client components" ${BUILD_CLIENT_OPTION})
option(BUILD_SERVER "Build server components" ${BUILD_SERVER_OPTION})
option(BUILD_TESTS "Build tests" ${BUILD_TESTS_OPTION})
@ -150,7 +157,7 @@ foreach(PLATFORM_QT_COMPONENT ${PLATFORM_QT_COMPONENTS})
list(APPEND PLATFORM_QT_LIBRARIES "Qt5::${PLATFORM_QT_COMPONENT}")
endforeach()
MESSAGE(STATUS "USE OPTIMIZED IK: " ${HIFI_USE_OPTIMIZED_IK})
MESSAGE(STATUS "Use optimized IK: " ${HIFI_USE_OPTIMIZED_IK})
MESSAGE(STATUS "Build server: " ${BUILD_SERVER})
MESSAGE(STATUS "Build client: " ${BUILD_CLIENT})
MESSAGE(STATUS "Build tests: " ${BUILD_TESTS})

View file

@ -307,7 +307,7 @@ void AssignmentClient::assignmentCompleted() {
// reset our NodeList by switching back to unassigned and clearing the list
nodeList->setOwnerType(NodeType::Unassigned);
nodeList->reset();
nodeList->reset("Assignment completed");
nodeList->resetNodeInterestSet();
_isAssigned = false;

View file

@ -45,6 +45,7 @@ int AvatarAudioStream::parseStreamProperties(PacketType type, const QByteArray&
: AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
// restart the codec
if (_codec) {
QMutexLocker lock(&_decoderMutex);
if (_decoder) {
_codec->releaseDecoder(_decoder);
}

View file

@ -37,6 +37,7 @@ void EntityTreeHeadlessViewer::update() {
if (_tree) {
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);
tree->withTryWriteLock([&] {
tree->preUpdate();
tree->update();
});
}

View file

@ -88,12 +88,10 @@ if (APPLE)
exec_program(sw_vers ARGS -productVersion OUTPUT_VARIABLE OSX_VERSION)
string(REGEX MATCH "^[0-9]+\\.[0-9]+" OSX_VERSION ${OSX_VERSION})
message(STATUS "Detected OS X version = ${OSX_VERSION}")
message(STATUS "OS X deployment target = ${CMAKE_OSX_DEPLOYMENT_TARGET}")
set(OSX_SDK "${OSX_VERSION}" CACHE STRING "OS X SDK version to look for inside Xcode bundle or at OSX_SDK_PATH")
# set our OS X deployment target
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.9)
# find the SDK path for the desired SDK
find_path(
_OSX_DESIRED_SDK_PATH

View file

@ -813,26 +813,23 @@ void DomainGatekeeper::processICEPeerInformationPacket(QSharedPointer<ReceivedMe
// any peer we don't have we add to the hash, otherwise we update
QDataStream iceResponseStream(message->getMessage());
NetworkPeer* receivedPeer = new NetworkPeer;
auto receivedPeer = SharedNetworkPeer::create();
iceResponseStream >> *receivedPeer;
if (!_icePeers.contains(receivedPeer->getUUID())) {
qDebug() << "New peer requesting ICE connection being added to hash -" << *receivedPeer;
SharedNetworkPeer newPeer = SharedNetworkPeer(receivedPeer);
_icePeers[receivedPeer->getUUID()] = newPeer;
qCDebug(domain_server_ice) << "New peer requesting ICE connection being added to hash -" << *receivedPeer;
_icePeers[receivedPeer->getUUID()] = receivedPeer;
// make sure we know when we should ping this peer
connect(newPeer.data(), &NetworkPeer::pingTimerTimeout, this, &DomainGatekeeper::handlePeerPingTimeout);
connect(receivedPeer.data(), &NetworkPeer::pingTimerTimeout, this, &DomainGatekeeper::handlePeerPingTimeout);
// immediately ping the new peer, and start a timer to continue pinging it until we connect to it
newPeer->startPingTimer();
receivedPeer->startPingTimer();
qDebug() << "Sending ping packets to establish connectivity with ICE peer with ID"
<< newPeer->getUUID();
qCDebug(domain_server_ice) << "Sending ping packets to establish connectivity with ICE peer with ID"
<< receivedPeer->getUUID();
pingPunchForConnectingPeer(newPeer);
} else {
delete receivedPeer;
pingPunchForConnectingPeer(receivedPeer);
}
}

View file

@ -58,6 +58,7 @@
#include <OctreeDataUtils.h>
Q_LOGGING_CATEGORY(domain_server, "hifi.domain_server")
Q_LOGGING_CATEGORY(domain_server_ice, "hifi.domain_server.ice")
const QString ACCESS_TOKEN_KEY_PATH = "metaverse.access_token";
const QString DomainServer::REPLACEMENT_FILE_EXTENSION = ".replace";
@ -374,7 +375,7 @@ void DomainServer::parseCommandLine(int argc, char* argv[]) {
}
if (_iceServerAddr.isEmpty()) {
qWarning() << "Could not parse an IP address and port combination from" << hostnamePortString;
qCWarning(domain_server_ice) << "Could not parse an IP address and port combination from" << hostnamePortString;
::exit(0);
}
}
@ -1570,12 +1571,8 @@ void DomainServer::sendICEServerAddressToMetaverseAPI() {
callbackParameters.errorCallbackMethod = "handleFailedICEServerAddressUpdate";
callbackParameters.jsonCallbackMethod = "handleSuccessfulICEServerAddressUpdate";
static bool printedIceServerMessage = false;
if (!printedIceServerMessage) {
printedIceServerMessage = true;
qDebug() << "Updating ice-server address in High Fidelity Metaverse API to"
<< (_iceServerSocket.isNull() ? "" : _iceServerSocket.getAddress().toString());
}
qCDebug(domain_server_ice) << "Updating ice-server address in High Fidelity Metaverse API to"
<< (_iceServerSocket.isNull() ? "" : _iceServerSocket.getAddress().toString());
static const QString DOMAIN_ICE_ADDRESS_UPDATE = "/api/v1/domains/%1/ice_server_address";
@ -1589,11 +1586,11 @@ void DomainServer::sendICEServerAddressToMetaverseAPI() {
void DomainServer::handleSuccessfulICEServerAddressUpdate(QNetworkReply* requestReply) {
_sendICEServerAddressToMetaverseAPIInProgress = false;
if (_sendICEServerAddressToMetaverseAPIRedo) {
qDebug() << "ice-server address updated with metaverse, but has since changed. redoing update...";
qCDebug(domain_server_ice) << "ice-server address (" << _iceServerSocket << ") updated with metaverse, but has since changed. redoing update...";
_sendICEServerAddressToMetaverseAPIRedo = false;
sendICEServerAddressToMetaverseAPI();
} else {
qDebug() << "ice-server address updated with metaverse.";
qCDebug(domain_server_ice) << "ice-server address (" << _iceServerSocket << ") updated with metaverse.";
}
}
@ -1606,9 +1603,9 @@ void DomainServer::handleFailedICEServerAddressUpdate(QNetworkReply* requestRepl
} else {
const int ICE_SERVER_UPDATE_RETRY_MS = 2 * 1000;
qWarning() << "Failed to update ice-server address with High Fidelity Metaverse - error was"
qCWarning(domain_server_ice) << "Failed to update ice-server address (" << _iceServerSocket << ") with High Fidelity Metaverse - error was"
<< requestReply->errorString();
qWarning() << "\tRe-attempting in" << ICE_SERVER_UPDATE_RETRY_MS / 1000 << "seconds";
qCWarning(domain_server_ice) << "\tRe-attempting in" << ICE_SERVER_UPDATE_RETRY_MS / 1000 << "seconds";
QTimer::singleShot(ICE_SERVER_UPDATE_RETRY_MS, this, SLOT(sendICEServerAddressToMetaverseAPI()));
}
@ -1621,26 +1618,26 @@ void DomainServer::sendHeartbeatToIceServer() {
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
if (!accountManager->getAccountInfo().hasPrivateKey()) {
qWarning() << "Cannot send an ice-server heartbeat without a private key for signature.";
qWarning() << "Waiting for keypair generation to complete before sending ICE heartbeat.";
qCWarning(domain_server_ice) << "Cannot send an ice-server heartbeat without a private key for signature.";
qCWarning(domain_server_ice) << "Waiting for keypair generation to complete before sending ICE heartbeat.";
if (!limitedNodeList->getSessionUUID().isNull()) {
accountManager->generateNewDomainKeypair(limitedNodeList->getSessionUUID());
} else {
qWarning() << "Attempting to send ICE server heartbeat with no domain ID. This is not supported";
qCWarning(domain_server_ice) << "Attempting to send ICE server heartbeat with no domain ID. This is not supported";
}
return;
}
const int FAILOVER_NO_REPLY_ICE_HEARTBEATS { 3 };
const int FAILOVER_NO_REPLY_ICE_HEARTBEATS { 6 };
// increase the count of no reply ICE heartbeats and check the current value
++_noReplyICEHeartbeats;
if (_noReplyICEHeartbeats > FAILOVER_NO_REPLY_ICE_HEARTBEATS) {
qWarning() << "There have been" << _noReplyICEHeartbeats - 1 << "heartbeats sent with no reply from the ice-server";
qWarning() << "Clearing the current ice-server socket and selecting a new candidate ice-server";
qCWarning(domain_server_ice) << "There have been" << _noReplyICEHeartbeats - 1 << "heartbeats sent with no reply from the ice-server";
qCWarning(domain_server_ice) << "Clearing the current ice-server socket and selecting a new candidate ice-server";
// add the current address to our list of failed addresses
_failedIceServerAddresses << _iceServerSocket.getAddress();
@ -1713,8 +1710,8 @@ void DomainServer::sendHeartbeatToIceServer() {
limitedNodeList->sendUnreliablePacket(*_iceServerHeartbeatPacket, _iceServerSocket);
} else {
qDebug() << "Not sending ice-server heartbeat since there is no selected ice-server.";
qDebug() << "Waiting for" << _iceServerAddr << "host lookup response";
qCDebug(domain_server_ice) << "Not sending ice-server heartbeat since there is no selected ice-server.";
qCDebug(domain_server_ice) << "Waiting for" << _iceServerAddr << "host lookup response";
}
}
@ -3294,7 +3291,7 @@ void DomainServer::processICEServerHeartbeatDenialPacket(QSharedPointer<Received
static const int NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN = 3;
if (++_numHeartbeatDenials > NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN) {
qDebug() << "Received" << NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN << "heartbeat denials from ice-server"
qCDebug(domain_server_ice) << "Received" << NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN << "heartbeat denials from ice-server"
<< "- re-generating keypair now";
// we've hit our threshold of heartbeat denials, trigger a keypair re-generation
@ -3316,7 +3313,7 @@ void DomainServer::processICEServerHeartbeatACK(QSharedPointer<ReceivedMessage>
if (!_connectedToICEServer) {
_connectedToICEServer = true;
sendICEServerAddressToMetaverseAPI();
qInfo() << "Connected to ice-server at" << _iceServerSocket;
qCInfo(domain_server_ice) << "Connected to ice-server at" << _iceServerSocket;
}
}
@ -3347,7 +3344,7 @@ void DomainServer::handleICEHostInfo(const QHostInfo& hostInfo) {
}
if (hostInfo.error() != QHostInfo::NoError || sanitizedAddresses.empty()) {
qWarning() << "IP address lookup failed for" << _iceServerAddr << ":" << hostInfo.errorString();
qCWarning(domain_server_ice) << "IP address lookup failed for" << _iceServerAddr << ":" << hostInfo.errorString();
// if we don't have an ICE server to use yet, trigger a retry
if (_iceServerSocket.isNull()) {
@ -3362,7 +3359,7 @@ void DomainServer::handleICEHostInfo(const QHostInfo& hostInfo) {
_iceServerAddresses = sanitizedAddresses;
if (countBefore == 0) {
qInfo() << "Found" << _iceServerAddresses.count() << "ice-server IP addresses for" << _iceServerAddr;
qCInfo(domain_server_ice) << "Found" << _iceServerAddresses.count() << "ice-server IP addresses for" << _iceServerAddr;
}
if (_iceServerSocket.isNull()) {
@ -3396,7 +3393,7 @@ 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
qWarning() << "All current ice-server addresses have failed - re-attempting all current addresses for"
qCWarning(domain_server_ice) << "All current ice-server addresses have failed - re-attempting all current addresses for"
<< _iceServerAddr;
_failedIceServerAddresses.clear();
@ -3416,7 +3413,7 @@ void DomainServer::randomizeICEServerAddress(bool shouldTriggerHostLookup) {
}
_iceServerSocket = HifiSockAddr { candidateICEAddresses[indexToTry], ICE_SERVER_DEFAULT_PORT };
qInfo() << "Set candidate ice-server socket to" << _iceServerSocket;
qCInfo(domain_server_ice) << "Set candidate ice-server socket to" << _iceServerSocket;
// clear our number of hearbeat denials, this should be re-set on ice-server change
_numHeartbeatDenials = 0;

View file

@ -40,6 +40,7 @@
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(domain_server)
Q_DECLARE_LOGGING_CATEGORY(domain_server_ice)
typedef QSharedPointer<Assignment> SharedAssignmentPointer;
typedef QMultiHash<QUuid, WalletTransaction*> TransactionHash;

View file

@ -1,3 +1,5 @@
import json
import logging
import os
import platform
import time
@ -7,6 +9,15 @@ try:
except ImportError:
fcntl = None
try:
import msvcrt
except ImportError:
msvcrt = None
logger = logging.getLogger(__name__)
# Used to ensure only one instance of the script runs at a time
class Singleton:
def __init__(self, path):
@ -33,7 +44,10 @@ class Singleton:
else:
self.fh.close()
self.fh = None
print("Couldn't aquire lock, retrying in 10 seconds")
# print is horked here so write directly to stdout.
with open(1, mode="w", closefd=False) as _stdout:
_stdout.write("Couldn't aquire lock, retrying in 10 seconds\n")
_stdout.flush()
time.sleep(10)
return self
@ -43,4 +57,104 @@ class Singleton:
else:
fcntl.lockf(self.fh, fcntl.LOCK_UN)
self.fh.close()
os.unlink(self.path)
os.unlink(self.path)
class FLock:
"""
File locking context manager
>> with FLock("/tmp/foo.lock"):
>> do_something_that_must_be_synced()
The lock file must stick around forever. The author is not aware of a no cross platform way to clean it up w/o introducting race conditions.
"""
def __init__(self, path):
self.fh = os.open(path, os.O_CREAT | os.O_RDWR)
self.path = path
def _lock_posix(self):
try:
fcntl.lockf(self.fh, fcntl.LOCK_EX | fcntl.LOCK_NB)
except BlockingIOError:
# Windows sleeps for 10 seconds before giving up on a lock.
# Lets mimic that behavior.
time.sleep(10)
return False
else:
return True
def _lock_windows(self):
try:
msvcrt.locking(self.fh, msvcrt.LK_LOCK, 1)
except OSError:
return False
else:
return True
if fcntl is not None:
_lock = _lock_posix
elif msvcrt is not None:
_lock = _lock_windows
else:
raise RuntimeError("No locking library found")
def read_stats(self):
data = {}
with open(self.fh, mode="r", closefd=False) as stats_file:
stats_file.seek(0)
try:
data = json.loads(stats_file.read())
except json.decoder.JSONDecodeError:
logger.warning("couldn't decode json in lock file")
except PermissionError:
# Can't read a locked file on Windows :(
pass
lock_age = time.time() - os.fstat(self.fh).st_mtime
if lock_age > 0:
data["Age"] = "%0.2f" % lock_age
with open(1, mode="w", closefd=False) as _stdout:
_stdout.write("Lock stats:\n")
for key, value in sorted(data.items()):
_stdout.write("* %s: %s\n" % (key, value))
_stdout.flush()
def write_stats(self):
stats = {
"Owner PID": os.getpid(),
}
flock_env_vars = os.getenv("FLOCK_ENV_VARS")
if flock_env_vars:
for env_var_name in flock_env_vars.split(":"):
stats[env_var_name] = os.getenv(env_var_name)
with open(self.fh, mode="w", closefd=False) as stats_file:
stats_file.truncate()
return stats_file.write(json.dumps(stats, indent=2))
def __enter__(self):
while not self._lock():
try:
self.read_stats()
except (IOError, ValueError) as exc:
logger.exception("couldn't read stats")
time.sleep(3.33) # don't hammer the file
self.write_stats()
return self
def __exit__(self, type, value, traceback):
os.close(self.fh)
# WARNING: `os.close` gives up the lock on `fh` then we attempt the `os.unlink`. On posix platforms this can lead to us deleting a lock file that another process owns. This step is required to maintain compatablity with Singleton. When and if FLock is completely rolled out to the build fleet this unlink should be removed.
try:
os.unlink(self.path)
except (FileNotFoundError, PermissionError):
logger.exception("couldn't unlink lock file")
if os.getenv("USE_FLOCK_CLS") is not None:
logger.warning("Using FLock locker")
Singleton = FLock

View file

@ -24,6 +24,20 @@ Rectangle {
property var pushingToTalk: AudioScriptingInterface.pushingToTalk;
readonly property var userSpeakingLevel: 0.4;
property bool gated: false;
Timer {
// used to hold the muted warning.
id: mutedTimer
interval: 2000;
running: false;
repeat: false;
property bool isRunning: false;
onTriggered: {
isRunning = false;
}
}
Component.onCompleted: {
AudioScriptingInterface.noiseGateOpened.connect(function() { gated = false; });
AudioScriptingInterface.noiseGateClosed.connect(function() { gated = true; });
@ -54,7 +68,17 @@ Rectangle {
opacity: 0.7;
onLevelChanged: {
var rectOpacity = (muted && (level >= userSpeakingLevel)) ? 1.0 : 0.7;
var mutedAndSpeaking = (muted && (level >= userSpeakingLevel));
if (!mutedTimer.isRunning && !pushToTalk) {
if (mutedAndSpeaking) {
mutedTimer.start();
mutedTimer.isRunning = true;
statusText.text = "MUTED";
} else {
statusText.text = "";
}
}
var rectOpacity = mutedAndSpeaking ? 1.0 : 0.7;
if (pushToTalk && !pushingToTalk) {
rectOpacity = (mouseArea.containsMouse) ? 1.0 : 0.7;
} else if (mouseArea.containsMouse && rectOpacity != 1.0) {
@ -63,6 +87,10 @@ Rectangle {
micBar.opacity = rectOpacity;
}
onPushToTalkChanged: {
statusText.text = pushToTalk ? HMD.active ? "PTT" : "PTT-(T)" : "";
}
color: "#00000000";
border {
width: mouseArea.containsMouse || mouseArea.containsPress ? 2 : 0;
@ -190,7 +218,6 @@ Rectangle {
color: pushToTalk ? (pushingToTalk ? colors.unmutedColor : colors.mutedColor) : (level >= userSpeakingLevel && muted) ? colors.mutedColor : colors.unmutedColor;
font.bold: true
text: pushToTalk ? (HMD.active ? "PTT" : "PTT-(T)") : (muted ? "MUTED" : "MUTE");
size: 12;
}
}

View file

@ -136,7 +136,6 @@
#include <SoundCacheScriptingInterface.h>
#include <ui/TabletScriptingInterface.h>
#include <ui/ToolbarScriptingInterface.h>
#include <InteractiveWindow.h>
#include <Tooltip.h>
#include <udt/PacketHeaders.h>
#include <UserActivityLogger.h>
@ -213,6 +212,7 @@
#include "ui/UpdateDialog.h"
#include "ui/DomainConnectionModel.h"
#include "ui/Keyboard.h"
#include "ui/InteractiveWindow.h"
#include "Util.h"
#include "InterfaceParentFinder.h"
#include "ui/OctreeStatsProvider.h"
@ -245,11 +245,8 @@
#include "webbrowser/WebBrowserSuggestionsEngine.h"
#include <DesktopPreviewProvider.h>
#include "AboutUtil.h"
#include <DisableDeferred.h>
#if defined(Q_OS_WIN)
#include <VersionHelpers.h>
@ -331,6 +328,7 @@ static const unsigned int THROTTLED_SIM_FRAMERATE = 15;
static const int THROTTLED_SIM_FRAME_PERIOD_MS = MSECS_PER_SECOND / THROTTLED_SIM_FRAMERATE;
static const int ENTITY_SERVER_ADDED_TIMEOUT = 5000;
static const int ENTITY_SERVER_CONNECTION_TIMEOUT = 5000;
static const int WATCHDOG_TIMER_TIMEOUT = 100;
static const float INITIAL_QUERY_RADIUS = 10.0f; // priority radius for entities before physics enabled
@ -377,7 +375,12 @@ const std::vector<std::pair<QString, Application::AcceptURLMethod>> Application:
class DeadlockWatchdogThread : public QThread {
public:
static const unsigned long HEARTBEAT_UPDATE_INTERVAL_SECS = 1;
// TODO: go back to 2 min across the board, after figuring out the issues with mac
#if defined(Q_OS_MAC)
static const unsigned long MAX_HEARTBEAT_AGE_USECS = 600 * USECS_PER_SECOND; // 10 mins with no checkin probably a deadlock, right now, on MAC
#else
static const unsigned long MAX_HEARTBEAT_AGE_USECS = 120 * USECS_PER_SECOND; // 2 mins with no checkin probably a deadlock
#endif
static const int WARNING_ELAPSED_HEARTBEAT = 500 * USECS_PER_MSEC; // warn if elapsed heartbeat average is large
static const int HEARTBEAT_SAMPLES = 100000; // ~5 seconds worth of samples
@ -557,7 +560,10 @@ public:
return true;
}
}
// Attempting to close MIDI interfaces of a hot-unplugged device can result in audio-driver deadlock.
// Detecting MIDI devices that have been added/removed after starting Inteface has been disabled.
// https://support.microsoft.com/en-us/help/4460006/midi-device-app-hangs-when-former-midi-api-is-used
#if 0
if (message->message == WM_DEVICECHANGE) {
const float MIN_DELTA_SECONDS = 2.0f; // de-bounce signal
static float lastTriggerTime = 0.0f;
@ -567,6 +573,7 @@ public:
Midi::USBchanged(); // re-scan the MIDI bus
}
}
#endif
}
return false;
}
@ -978,6 +985,7 @@ const bool DEFAULT_PREFER_STYLUS_OVER_LASER = false;
const bool DEFAULT_PREFER_AVATAR_FINGER_OVER_STYLUS = false;
const QString DEFAULT_CURSOR_NAME = "DEFAULT";
const bool DEFAULT_MINI_TABLET_ENABLED = true;
const bool DEFAULT_AWAY_STATE_WHEN_FOCUS_LOST_IN_VR_ENABLED = true;
QSharedPointer<OffscreenUi> getOffscreenUI() {
#if !defined(DISABLE_QML)
@ -1008,6 +1016,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
_preferStylusOverLaserSetting("preferStylusOverLaser", DEFAULT_PREFER_STYLUS_OVER_LASER),
_preferAvatarFingerOverStylusSetting("preferAvatarFingerOverStylus", DEFAULT_PREFER_AVATAR_FINGER_OVER_STYLUS),
_constrainToolbarPosition("toolbar/constrainToolbarToCenterX", true),
_awayStateWhenFocusLostInVREnabled("awayStateWhenFocusLostInVREnabled", DEFAULT_AWAY_STATE_WHEN_FOCUS_LOST_IN_VR_ENABLED),
_preferredCursor("preferredCursor", DEFAULT_CURSOR_NAME),
_miniTabletEnabledSetting("miniTabletEnabled", DEFAULT_MINI_TABLET_ENABLED),
_scaleMirror(1.0f),
@ -1060,6 +1069,14 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
}
}
{
// identify gpu as early as possible to help identify OpenGL initialization errors.
auto gpuIdent = GPUIdent::getInstance();
setCrashAnnotation("gpu_name", gpuIdent->getName().toStdString());
setCrashAnnotation("gpu_driver", gpuIdent->getDriver().toStdString());
setCrashAnnotation("gpu_memory", std::to_string(gpuIdent->getMemory()));
}
// make sure the debug draw singleton is initialized on the main thread.
DebugDraw::getInstance().removeMarker("");
@ -1121,6 +1138,18 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
auto deadlockWatchdogThread = new DeadlockWatchdogThread();
deadlockWatchdogThread->setMainThreadID(QThread::currentThreadId());
deadlockWatchdogThread->start();
// Main thread timer to keep the watchdog updated
QTimer* watchdogUpdateTimer = new QTimer(this);
connect(watchdogUpdateTimer, &QTimer::timeout, [this] { updateHeartbeat(); });
connect(this, &QCoreApplication::aboutToQuit, [watchdogUpdateTimer] {
watchdogUpdateTimer->stop();
watchdogUpdateTimer->deleteLater();
});
watchdogUpdateTimer->setSingleShot(false);
watchdogUpdateTimer->setInterval(WATCHDOG_TIMER_TIMEOUT); // 100ms, Qt::CoarseTimer acceptable
watchdogUpdateTimer->start();
}
// Set File Logger Session UUID
@ -2679,7 +2708,7 @@ void Application::cleanupBeforeQuit() {
auto nodeList = DependencyManager::get<NodeList>();
// send the domain a disconnect packet, force stoppage of domain-server check-ins
nodeList->getDomainHandler().disconnect();
nodeList->getDomainHandler().disconnect("Quitting");
nodeList->setIsShuttingDown(true);
// tell the packet receiver we're shutting down, so it can drop packets
@ -2773,21 +2802,15 @@ Application::~Application() {
// remove avatars from physics engine
auto avatarManager = DependencyManager::get<AvatarManager>();
avatarManager->clearOtherAvatars();
auto myCharacterController = getMyAvatar()->getCharacterController();
myCharacterController->clearDetailedMotionStates();
PhysicsEngine::Transaction transaction;
avatarManager->buildPhysicsTransaction(transaction);
_physicsEngine->processTransaction(transaction);
avatarManager->handleProcessedPhysicsTransaction(transaction);
avatarManager->deleteAllAvatars();
auto myCharacterController = getMyAvatar()->getCharacterController();
myCharacterController->clearDetailedMotionStates();
myCharacterController->buildPhysicsTransaction(transaction);
_physicsEngine->processTransaction(transaction);
myCharacterController->handleProcessedPhysicsTransaction(transaction);
_physicsEngine->setCharacterController(nullptr);
// the _shapeManager should have zero references
@ -2919,7 +2942,7 @@ void Application::initializeGL() {
#if !defined(DISABLE_QML)
QStringList chromiumFlags;
// Bug 21993: disable microphone and camera input
// Bug 21993: disable microphone and camera input
chromiumFlags << "--use-fake-device-for-media-stream";
// Disable signed distance field font rendering on ATI/AMD GPUs, due to
// https://highfidelity.manuscript.com/f/cases/13677/Text-showing-up-white-on-Marketplace-app
@ -3006,7 +3029,7 @@ void Application::initializeDisplayPlugins() {
void Application::initializeRenderEngine() {
// FIXME: on low end systems os the shaders take up to 1 minute to compile, so we pause the deadlock watchdog thread.
DeadlockWatchdogThread::withPause([&] {
_graphicsEngine.initializeRender(DISABLE_DEFERRED);
_graphicsEngine.initializeRender();
DependencyManager::get<Keyboard>()->registerKeyboardHighlighting();
});
}
@ -3621,6 +3644,11 @@ void Application::setSettingConstrainToolbarPosition(bool setting) {
getOffscreenUI()->setConstrainToolbarToCenterX(setting);
}
void Application::setAwayStateWhenFocusLostInVREnabled(bool enabled) {
_awayStateWhenFocusLostInVREnabled.set(enabled);
emit awayStateWhenFocusLostInVRChanged(enabled);
}
void Application::setMiniTabletEnabled(bool enabled) {
_miniTabletEnabledSetting.set(enabled);
emit miniTabletEnabledChanged(enabled);
@ -4016,8 +4044,8 @@ bool Application::notify(QObject * object, QEvent * event) {
if (thread() == QThread::currentThread()) {
PROFILE_RANGE_IF_LONGER(app, "notify", 2)
return QApplication::notify(object, event);
}
}
return QApplication::notify(object, event);
}
@ -4979,9 +5007,6 @@ void setupCpuMonitorThread() {
void Application::idle() {
PerformanceTimer perfTimer("idle");
// Update the deadlock watchdog
updateHeartbeat();
#if !defined(DISABLE_QML)
auto offscreenUi = getOffscreenUI();
@ -5528,7 +5553,7 @@ void Application::pauseUntilLoginDetermined() {
cameraModeChanged();
// disconnect domain handler.
nodeList->getDomainHandler().disconnect();
nodeList->getDomainHandler().disconnect("Pause until login determined");
// From now on, it's permissible to call resumeAfterLoginDialogActionTaken()
_resumeAfterLoginDialogActionTaken_SafeToRun = true;
@ -5824,7 +5849,8 @@ void Application::cameraModeChanged() {
Menu::getInstance()->setIsOptionChecked(MenuOption::FullscreenMirror, true);
break;
default:
break;
// we don't have menu items for the others, so just leave it alone.
return;
}
cameraMenuChanged();
}
@ -5907,7 +5933,7 @@ void Application::reloadResourceCaches() {
DependencyManager::get<TextureCache>()->refreshAll();
DependencyManager::get<recording::ClipCache>()->refreshAll();
DependencyManager::get<NodeList>()->reset(); // Force redownload of .fst models
DependencyManager::get<NodeList>()->reset("Reloading resources"); // Force redownload of .fst models
DependencyManager::get<ScriptEngines>()->reloadAllScripts();
getOffscreenUI()->clearCache();
@ -6396,64 +6422,42 @@ void Application::update(float deltaTime) {
PROFILE_RANGE(simulation_physics, "Simulation");
PerformanceTimer perfTimer("simulation");
if (_physicsEnabled) {
auto t0 = std::chrono::high_resolution_clock::now();
auto t1 = t0;
getEntities()->preUpdate();
auto t0 = std::chrono::high_resolution_clock::now();
auto t1 = t0;
{
PROFILE_RANGE(simulation_physics, "PrePhysics");
PerformanceTimer perfTimer("prePhysics)");
{
PROFILE_RANGE(simulation_physics, "PrePhysics");
PerformanceTimer perfTimer("prePhysics)");
{
PROFILE_RANGE(simulation_physics, "RemoveEntities");
const VectorOfMotionStates& motionStates = _entitySimulation->getObjectsToRemoveFromPhysics();
{
PROFILE_RANGE_EX(simulation_physics, "NumObjs", 0xffff0000, (uint64_t)motionStates.size());
_physicsEngine->removeObjects(motionStates);
}
_entitySimulation->deleteObjectsRemovedFromPhysics();
}
PROFILE_RANGE(simulation_physics, "Entities");
PhysicsEngine::Transaction transaction;
_entitySimulation->buildPhysicsTransaction(transaction);
_physicsEngine->processTransaction(transaction);
_entitySimulation->handleProcessedPhysicsTransaction(transaction);
}
{
PROFILE_RANGE(simulation_physics, "AddEntities");
VectorOfMotionStates motionStates;
getEntities()->getTree()->withReadLock([&] {
_entitySimulation->getObjectsToAddToPhysics(motionStates);
PROFILE_RANGE_EX(simulation_physics, "NumObjs", 0xffff0000, (uint64_t)motionStates.size());
_physicsEngine->addObjects(motionStates);
});
}
{
VectorOfMotionStates motionStates;
PROFILE_RANGE(simulation_physics, "ChangeEntities");
getEntities()->getTree()->withReadLock([&] {
_entitySimulation->getObjectsToChange(motionStates);
VectorOfMotionStates stillNeedChange = _physicsEngine->changeObjects(motionStates);
_entitySimulation->setObjectsToChange(stillNeedChange);
});
}
t1 = std::chrono::high_resolution_clock::now();
{
PROFILE_RANGE(simulation_physics, "Avatars");
PhysicsEngine::Transaction transaction;
avatarManager->buildPhysicsTransaction(transaction);
_physicsEngine->processTransaction(transaction);
avatarManager->handleProcessedPhysicsTransaction(transaction);
myAvatar->prepareForPhysicsSimulation();
_physicsEngine->enableGlobalContactAddedCallback(myAvatar->isFlying());
}
}
if (_physicsEnabled) {
{
PROFILE_RANGE(simulation_physics, "PrepareActions");
_entitySimulation->applyDynamicChanges();
t1 = std::chrono::high_resolution_clock::now();
{
PROFILE_RANGE(simulation_physics, "Avatars");
PhysicsEngine::Transaction transaction;
avatarManager->buildPhysicsTransaction(transaction);
_physicsEngine->processTransaction(transaction);
avatarManager->handleProcessedPhysicsTransaction(transaction);
myAvatar->getCharacterController()->buildPhysicsTransaction(transaction);
_physicsEngine->processTransaction(transaction);
myAvatar->getCharacterController()->handleProcessedPhysicsTransaction(transaction);
myAvatar->prepareForPhysicsSimulation();
_physicsEngine->enableGlobalContactAddedCallback(myAvatar->isFlying());
}
{
PROFILE_RANGE(simulation_physics, "PrepareActions");
_physicsEngine->forEachDynamic([&](EntityDynamicPointer dynamic) {
dynamic->prepareForPhysicsSimulation();
});
}
_physicsEngine->forEachDynamic([&](EntityDynamicPointer dynamic) {
dynamic->prepareForPhysicsSimulation();
});
}
auto t2 = std::chrono::high_resolution_clock::now();
{
@ -6707,7 +6711,7 @@ void Application::updateRenderArgs(float deltaTime) {
}
appRenderArgs._renderArgs = RenderArgs(_graphicsEngine.getGPUContext(), lodManager->getOctreeSizeScale(),
lodManager->getBoundaryLevelAdjust(), lodManager->getLODAngleHalfTan(), RenderArgs::DEFAULT_RENDER_MODE,
RenderArgs::MONO, RenderArgs::RENDER_DEBUG_NONE);
RenderArgs::MONO, RenderArgs::DEFERRED, RenderArgs::RENDER_DEBUG_NONE);
appRenderArgs._renderArgs._scene = getMain3DScene();
{
@ -9380,7 +9384,7 @@ void Application::showUrlHandler(const QUrl& url) {
void Application::beforeEnterBackground() {
auto nodeList = DependencyManager::get<NodeList>();
nodeList->setSendDomainServerCheckInEnabled(false);
nodeList->reset(true);
nodeList->reset("Entering background", true);
clearDomainOctreeDetails();
}

View file

@ -211,6 +211,8 @@ public:
float getNumCollisionObjects() const;
float getTargetRenderFrameRate() const; // frames/second
static void setupQmlSurface(QQmlContext* surfaceContext, bool setAdditionalContextProperties);
float getFieldOfView() { return _fieldOfView.get(); }
void setFieldOfView(float fov);
@ -239,6 +241,9 @@ public:
float getSettingConstrainToolbarPosition() { return _constrainToolbarPosition.get(); }
void setSettingConstrainToolbarPosition(bool setting);
float getAwayStateWhenFocusLostInVREnabled() { return _awayStateWhenFocusLostInVREnabled.get(); }
void setAwayStateWhenFocusLostInVREnabled(bool setting);
Q_INVOKABLE void setMinimumGPUTextureMemStabilityCount(int stabilityCount) { _minimumGPUTextureMemSizeStabilityCount = stabilityCount; }
NodeToOctreeSceneStats* getOcteeSceneStats() { return &_octreeServerSceneStats; }
@ -369,6 +374,7 @@ signals:
void loginDialogFocusDisabled();
void miniTabletEnabledChanged(bool enabled);
void awayStateWhenFocusLostInVRChanged(bool enabled);
public slots:
QVector<EntityItemID> pasteEntities(float x, float y, float z);
@ -604,7 +610,6 @@ private:
void maybeToggleMenuVisible(QMouseEvent* event) const;
void toggleTabletUI(bool shouldOpen = false) const;
static void setupQmlSurface(QQmlContext* surfaceContext, bool setAdditionalContextProperties);
void userKickConfirmation(const QUuid& nodeID);
MainWindow* _window;
@ -672,6 +677,7 @@ private:
Setting::Handle<bool> _preferStylusOverLaserSetting;
Setting::Handle<bool> _preferAvatarFingerOverStylusSetting;
Setting::Handle<bool> _constrainToolbarPosition;
Setting::Handle<bool> _awayStateWhenFocusLostInVREnabled;
Setting::Handle<QString> _preferredCursor;
Setting::Handle<bool> _miniTabletEnabledSetting;
Setting::Handle<bool> _keepLogWindowOnTop { "keepLogWindowOnTop", false };

View file

@ -272,10 +272,10 @@ public:
}
};
void SecondaryCameraRenderTask::build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, bool isDeferred) {
void SecondaryCameraRenderTask::build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor) {
const auto cachedArg = task.addJob<SecondaryCameraJob>("SecondaryCamera");
task.addJob<RenderViewTask>("RenderSecondView", cullFunctor, isDeferred, render::ItemKey::TAG_BITS_1, render::ItemKey::TAG_BITS_1);
task.addJob<RenderViewTask>("RenderSecondView", cullFunctor, render::ItemKey::TAG_BITS_1, render::ItemKey::TAG_BITS_1);
task.addJob<EndSecondaryCameraFrame>("EndSecondaryCamera", cachedArg);
}

View file

@ -65,7 +65,7 @@ public:
using JobModel = render::Task::Model<SecondaryCameraRenderTask, Config>;
SecondaryCameraRenderTask() {}
void configure(const Config& config) {}
void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, bool isDeferred = true);
void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor);
};
#endif

View file

@ -101,7 +101,7 @@ AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWe
}
AvatarManager::~AvatarManager() {
assert(_avatarsToChangeInPhysics.empty());
assert(_otherAvatarsToChangeInPhysics.empty());
}
void AvatarManager::init() {
@ -295,7 +295,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
render::Transaction renderTransaction;
workload::Transaction workloadTransaction;
for (int p = kHero; p < NumVariants; p++) {
auto& priorityQueue = avatarPriorityQueues[p];
// Sorting the current queue HERE as part of the measured timing.
@ -314,7 +314,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
// remove the orb if it is there
avatar->removeOrb();
if (avatar->needsPhysicsUpdate()) {
_avatarsToChangeInPhysics.insert(avatar);
_otherAvatarsToChangeInPhysics.insert(avatar);
}
} else {
avatar->updateOrbPosition();
@ -419,69 +419,111 @@ AvatarSharedPointer AvatarManager::newSharedAvatar(const QUuid& sessionUUID) {
}
void AvatarManager::queuePhysicsChange(const OtherAvatarPointer& avatar) {
_avatarsToChangeInPhysics.insert(avatar);
_otherAvatarsToChangeInPhysics.insert(avatar);
}
DetailedMotionState* AvatarManager::createDetailedMotionState(OtherAvatarPointer avatar, int32_t jointIndex) {
bool isBound = false;
std::vector<int32_t> boundJoints;
const btCollisionShape* shape = avatar->createCollisionShape(jointIndex, isBound, boundJoints);
if (shape) {
DetailedMotionState* motionState = new DetailedMotionState(avatar, shape, jointIndex);
motionState->setMass(0.0f); // DetailedMotionState has KINEMATIC MotionType, so zero mass is ok
motionState->setIsBound(isBound, boundJoints);
return motionState;
}
return nullptr;
}
void AvatarManager::rebuildAvatarPhysics(PhysicsEngine::Transaction& transaction, OtherAvatarPointer avatar) {
if (!avatar->_motionState) {
avatar->_motionState = new AvatarMotionState(avatar, nullptr);
}
AvatarMotionState* motionState = avatar->_motionState;
ShapeInfo shapeInfo;
avatar->computeShapeInfo(shapeInfo);
const btCollisionShape* shape = ObjectMotionState::getShapeManager()->getShape(shapeInfo);
assert(shape);
motionState->setShape(shape);
motionState->setMass(avatar->computeMass());
if (motionState->getRigidBody()) {
transaction.objectsToReinsert.push_back(motionState);
} else {
transaction.objectsToAdd.push_back(motionState);
}
motionState->clearIncomingDirtyFlags();
// Rather than reconcile numbers of joints after change to model or LOD
// we blow away old detailedMotionStates and create anew all around.
// delete old detailedMotionStates
auto& detailedMotionStates = avatar->getDetailedMotionStates();
if (detailedMotionStates.size() != 0) {
for (auto& detailedMotionState : detailedMotionStates) {
transaction.objectsToRemove.push_back(detailedMotionState);
}
avatar->resetDetailedMotionStates();
}
// build new detailedMotionStates
OtherAvatar::BodyLOD lod = avatar->getBodyLOD();
if (lod == OtherAvatar::BodyLOD::Sphere) {
auto dMotionState = createDetailedMotionState(avatar, -1);
if (dMotionState) {
detailedMotionStates.push_back(dMotionState);
transaction.objectsToAdd.push_back(dMotionState);
}
} else {
int32_t numJoints = avatar->getJointCount();
for (int32_t i = 0; i < numJoints; i++) {
auto dMotionState = createDetailedMotionState(avatar, i);
if (dMotionState) {
detailedMotionStates.push_back(dMotionState);
transaction.objectsToAdd.push_back(dMotionState);
}
}
}
avatar->_needsReinsertion = false;
}
void AvatarManager::buildPhysicsTransaction(PhysicsEngine::Transaction& transaction) {
SetOfOtherAvatars failedShapeBuilds;
for (auto avatar : _avatarsToChangeInPhysics) {
_myAvatar->getCharacterController()->buildPhysicsTransaction(transaction);
for (auto avatar : _otherAvatarsToChangeInPhysics) {
bool isInPhysics = avatar->isInPhysicsSimulation();
if (isInPhysics != avatar->shouldBeInPhysicsSimulation()) {
if (isInPhysics) {
transaction.objectsToRemove.push_back(avatar->_motionState);
avatar->_motionState = nullptr;
auto& detailedMotionStates = avatar->getDetailedMotionStates();
for (auto& mState : detailedMotionStates) {
transaction.objectsToRemove.push_back(mState);
for (auto& motionState : detailedMotionStates) {
transaction.objectsToRemove.push_back(motionState);
}
avatar->resetDetailedMotionStates();
} else {
if (avatar->getDetailedMotionStates().size() == 0) {
avatar->createDetailedMotionStates(avatar);
for (auto dMotionState : avatar->getDetailedMotionStates()) {
transaction.objectsToAdd.push_back(dMotionState);
}
}
if (avatar->getDetailedMotionStates().size() > 0) {
ShapeInfo shapeInfo;
avatar->computeShapeInfo(shapeInfo);
btCollisionShape* shape = const_cast<btCollisionShape*>(ObjectMotionState::getShapeManager()->getShape(shapeInfo));
if (shape) {
AvatarMotionState* motionState = new AvatarMotionState(avatar, shape);
motionState->setMass(avatar->computeMass());
avatar->_motionState = motionState;
transaction.objectsToAdd.push_back(motionState);
} else {
failedShapeBuilds.insert(avatar);
}
} else {
failedShapeBuilds.insert(avatar);
}
rebuildAvatarPhysics(transaction, avatar);
}
} else if (isInPhysics) {
transaction.objectsToChange.push_back(avatar->_motionState);
auto& detailedMotionStates = avatar->getDetailedMotionStates();
for (auto& mState : detailedMotionStates) {
if (mState) {
transaction.objectsToChange.push_back(mState);
}
AvatarMotionState* motionState = avatar->_motionState;
uint32_t flags = motionState->getIncomingDirtyFlags();
if (flags & EASY_DIRTY_PHYSICS_FLAGS) {
motionState->handleEasyChanges(flags);
}
// NOTE: we don't call detailedMotionState->handleEasyChanges() here because they are KINEMATIC
// and Bullet will automagically call DetailedMotionState::getWorldTransform() on all that are active.
if (motionState->needsNewShape()) {
rebuildAvatarPhysics(transaction, avatar);
} else {
if (flags & (Simulation::DIRTY_MOTION_TYPE | Simulation::DIRTY_COLLISION_GROUP)) {
transaction.objectsToReinsert.push_back(motionState);
}
motionState->clearIncomingDirtyFlags();
}
}
}
_avatarsToChangeInPhysics.swap(failedShapeBuilds);
}
void AvatarManager::handleProcessedPhysicsTransaction(PhysicsEngine::Transaction& transaction) {
// things on objectsToChange correspond to failed changes
// so we push them back onto _avatarsToChangeInPhysics
for (auto object : transaction.objectsToChange) {
AvatarMotionState* motionState = static_cast<AvatarMotionState*>(object);
assert(motionState);
assert(motionState->_avatar);
_avatarsToChangeInPhysics.insert(motionState->_avatar);
}
// things on objectsToRemove are ready for delete
for (auto object : transaction.objectsToRemove) {
delete object;
@ -536,14 +578,20 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar
workload::SpacePointer space = _space;
transaction.transitionFinishedOperator(avatar->getRenderItemID(), [space, avatar]() {
const render::ScenePointer& scene = qApp->getMain3DScene();
render::Transaction transaction;
avatar->removeFromScene(avatar, scene, transaction);
scene->enqueueTransaction(transaction);
if (avatar->getLastFadeRequested() != render::Transition::Type::USER_LEAVE_DOMAIN) {
// The avatar is using another transition besides the fade-out transition, which means it is still in use.
// Deleting the avatar now could cause state issues, so abort deletion and show message.
qCWarning(interfaceapp) << "An ending fade-out transition wants to delete an avatar, but the avatar is still in use. Avatar deletion has aborted. (avatar ID: " << avatar->getSessionUUID() << ")";
} else {
const render::ScenePointer& scene = qApp->getMain3DScene();
render::Transaction transaction;
avatar->removeFromScene(avatar, scene, transaction);
scene->enqueueTransaction(transaction);
workload::Transaction workloadTransaction;
workloadTransaction.remove(avatar->getSpaceIndex());
space->enqueueTransaction(workloadTransaction);
workload::Transaction workloadTransaction;
workloadTransaction.remove(avatar->getSpaceIndex());
space->enqueueTransaction(workloadTransaction);
}
});
scene->enqueueTransaction(transaction);
}
@ -570,7 +618,7 @@ void AvatarManager::clearOtherAvatars() {
++avatarIterator;
}
}
}
}
for (auto& av : removedAvatars) {
handleRemovedAvatar(av);
@ -578,7 +626,7 @@ void AvatarManager::clearOtherAvatars() {
}
void AvatarManager::deleteAllAvatars() {
assert(_avatarsToChangeInPhysics.empty());
assert(_otherAvatarsToChangeInPhysics.empty());
QReadLocker locker(&_hashLock);
AvatarHash::iterator avatarIterator = _avatarHash.begin();
while (avatarIterator != _avatarHash.end()) {
@ -588,7 +636,7 @@ void AvatarManager::deleteAllAvatars() {
if (avatar != _myAvatar) {
auto otherAvatar = std::static_pointer_cast<OtherAvatar>(avatar);
assert(!otherAvatar->_motionState);
assert(otherAvatar->getDetailedMotionStates().size() == 0);
assert(otherAvatar->getDetailedMotionStates().size() == 0);
}
}
}

View file

@ -273,6 +273,8 @@ public slots:
protected:
AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer) override;
DetailedMotionState* createDetailedMotionState(OtherAvatarPointer avatar, int32_t jointIndex);
void rebuildAvatarPhysics(PhysicsEngine::Transaction& transaction, OtherAvatarPointer avatar);
private:
explicit AvatarManager(QObject* parent = 0);
@ -288,7 +290,7 @@ private:
void handleTransitAnimations(AvatarTransit::Status status);
using SetOfOtherAvatars = std::set<OtherAvatarPointer>;
SetOfOtherAvatars _avatarsToChangeInPhysics;
SetOfOtherAvatars _otherAvatarsToChangeInPhysics;
std::shared_ptr<MyAvatar> _myAvatar;
quint64 _lastSendAvatarDataTime = 0; // Controls MyAvatar send data rate.

View file

@ -29,23 +29,19 @@ void AvatarMotionState::handleEasyChanges(uint32_t& flags) {
}
}
bool AvatarMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) {
return ObjectMotionState::handleHardAndEasyChanges(flags, engine);
}
AvatarMotionState::~AvatarMotionState() {
assert(_avatar);
_avatar = nullptr;
}
// virtual
uint32_t AvatarMotionState::getIncomingDirtyFlags() {
uint32_t AvatarMotionState::getIncomingDirtyFlags() const {
return _body ? _dirtyFlags : 0;
}
void AvatarMotionState::clearIncomingDirtyFlags() {
void AvatarMotionState::clearIncomingDirtyFlags(uint32_t mask) {
if (_body) {
_dirtyFlags = 0;
_dirtyFlags &= ~mask;
}
}
@ -54,13 +50,6 @@ PhysicsMotionType AvatarMotionState::computePhysicsMotionType() const {
return MOTION_TYPE_DYNAMIC;
}
// virtual and protected
const btCollisionShape* AvatarMotionState::computeNewShape() {
ShapeInfo shapeInfo;
_avatar->computeShapeInfo(shapeInfo);
return getShapeManager()->getShape(shapeInfo);
}
// virtual
bool AvatarMotionState::isMoving() const {
return false;

View file

@ -23,44 +23,44 @@ class AvatarMotionState : public ObjectMotionState {
public:
AvatarMotionState(OtherAvatarPointer avatar, const btCollisionShape* shape);
virtual void handleEasyChanges(uint32_t& flags) override;
virtual bool handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) override;
void handleEasyChanges(uint32_t& flags) override;
virtual PhysicsMotionType getMotionType() const override { return _motionType; }
PhysicsMotionType getMotionType() const override { return _motionType; }
virtual uint32_t getIncomingDirtyFlags() override;
virtual void clearIncomingDirtyFlags() override;
uint32_t getIncomingDirtyFlags() const override;
void clearIncomingDirtyFlags(uint32_t mask = DIRTY_PHYSICS_FLAGS) override;
virtual PhysicsMotionType computePhysicsMotionType() const override;
PhysicsMotionType computePhysicsMotionType() const override;
virtual bool isMoving() const override;
bool isMoving() const override;
// this relays incoming position/rotation to the RigidBody
virtual void getWorldTransform(btTransform& worldTrans) const override;
void getWorldTransform(btTransform& worldTrans) const override;
// this relays outgoing position/rotation to the EntityItem
virtual void setWorldTransform(const btTransform& worldTrans) override;
void setWorldTransform(const btTransform& worldTrans) override;
// These pure virtual methods must be implemented for each MotionState type
// and make it possible to implement more complicated methods in this base class.
// pure virtual overrides from ObjectMotionState
virtual float getObjectRestitution() const override;
virtual float getObjectFriction() const override;
virtual float getObjectLinearDamping() const override;
virtual float getObjectAngularDamping() const override;
float getObjectRestitution() const override;
float getObjectFriction() const override;
float getObjectLinearDamping() const override;
float getObjectAngularDamping() const override;
virtual glm::vec3 getObjectPosition() const override;
virtual glm::quat getObjectRotation() const override;
virtual glm::vec3 getObjectLinearVelocity() const override;
virtual glm::vec3 getObjectAngularVelocity() const override;
virtual glm::vec3 getObjectGravity() const override;
glm::vec3 getObjectPosition() const override;
glm::quat getObjectRotation() const override;
glm::vec3 getObjectLinearVelocity() const override;
glm::vec3 getObjectAngularVelocity() const override;
glm::vec3 getObjectGravity() const override;
virtual const QUuid getObjectID() const override;
const QUuid getObjectID() const override;
virtual QString getName() const override;
virtual QUuid getSimulatorID() const override;
QString getName() const override;
ShapeType getShapeType() const override { return SHAPE_TYPE_CAPSULE_Y; }
QUuid getSimulatorID() const override;
void setBoundingBox(const glm::vec3& corner, const glm::vec3& diagonal);
@ -69,9 +69,9 @@ public:
void setCollisionGroup(int32_t group) { _collisionGroup = group; }
int32_t getCollisionGroup() { return _collisionGroup; }
virtual void computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const override;
void computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const override;
virtual float getMass() const override;
float getMass() const override;
friend class AvatarManager;
friend class Avatar;
@ -85,9 +85,6 @@ protected:
// ever called by the Avatar class dtor.
~AvatarMotionState();
virtual bool isReadyToComputeShape() const override { return true; }
virtual const btCollisionShape* computeNewShape() override;
OtherAvatarPointer _avatar;
float _diameter { 0.0f };
int32_t _collisionGroup;

View file

@ -17,7 +17,7 @@
#include "MyAvatar.h"
DetailedMotionState::DetailedMotionState(AvatarPointer avatar, const btCollisionShape* shape, int jointIndex) :
DetailedMotionState::DetailedMotionState(AvatarPointer avatar, const btCollisionShape* shape, int32_t jointIndex) :
ObjectMotionState(shape), _avatar(avatar), _jointIndex(jointIndex) {
assert(_avatar);
if (!_avatar->isMyAvatar()) {
@ -33,47 +33,26 @@ void DetailedMotionState::handleEasyChanges(uint32_t& flags) {
}
}
bool DetailedMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) {
return ObjectMotionState::handleHardAndEasyChanges(flags, engine);
}
DetailedMotionState::~DetailedMotionState() {
assert(_avatar);
_avatar = nullptr;
}
// virtual
uint32_t DetailedMotionState::getIncomingDirtyFlags() {
uint32_t DetailedMotionState::getIncomingDirtyFlags() const {
return _body ? _dirtyFlags : 0;
}
void DetailedMotionState::clearIncomingDirtyFlags() {
void DetailedMotionState::clearIncomingDirtyFlags(uint32_t mask) {
if (_body) {
_dirtyFlags = 0;
_dirtyFlags &= ~mask;
}
}
PhysicsMotionType DetailedMotionState::computePhysicsMotionType() const {
// TODO?: support non-DYNAMIC motion for avatars? (e.g. when sitting)
return MOTION_TYPE_KINEMATIC;
}
// virtual and protected
const btCollisionShape* DetailedMotionState::computeNewShape() {
btCollisionShape* shape = nullptr;
if (!_avatar->isMyAvatar()) {
if (_otherAvatar != nullptr) {
shape = _otherAvatar->createCollisionShape(_jointIndex, _isBound, _boundJoints);
}
} else {
std::shared_ptr<MyAvatar> myAvatar = std::static_pointer_cast<MyAvatar>(_avatar);
if (myAvatar) {
shape = myAvatar->getCharacterController()->createDetailedCollisionShapeForJoint(_jointIndex);
}
}
return shape;
}
// virtual
bool DetailedMotionState::isMoving() const {
return false;
@ -178,11 +157,23 @@ void DetailedMotionState::setRigidBody(btRigidBody* body) {
}
void DetailedMotionState::setShape(const btCollisionShape* shape) {
ObjectMotionState::setShape(shape);
if (_shape != shape) {
if (_shape) {
getShapeManager()->releaseShape(_shape);
}
_shape = shape;
if (_body) {
assert(_shape);
_body->setCollisionShape(const_cast<btCollisionShape*>(_shape));
}
} else if (shape) {
// we need to release unused reference to shape
getShapeManager()->releaseShape(shape);
}
}
void DetailedMotionState::forceActive() {
if (_body && !_body->isActive()) {
_body->setActivationState(ACTIVE_TAG);
}
}
}

View file

@ -23,55 +23,55 @@ class DetailedMotionState : public ObjectMotionState {
public:
DetailedMotionState(AvatarPointer avatar, const btCollisionShape* shape, int jointIndex);
virtual void handleEasyChanges(uint32_t& flags) override;
virtual bool handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) override;
void handleEasyChanges(uint32_t& flags) override;
virtual PhysicsMotionType getMotionType() const override { return _motionType; }
PhysicsMotionType getMotionType() const override { return _motionType; }
virtual uint32_t getIncomingDirtyFlags() override;
virtual void clearIncomingDirtyFlags() override;
uint32_t getIncomingDirtyFlags() const override;
void clearIncomingDirtyFlags(uint32_t mask = DIRTY_PHYSICS_FLAGS) override;
virtual PhysicsMotionType computePhysicsMotionType() const override;
PhysicsMotionType computePhysicsMotionType() const override;
virtual bool isMoving() const override;
bool isMoving() const override;
// this relays incoming position/rotation to the RigidBody
virtual void getWorldTransform(btTransform& worldTrans) const override;
void getWorldTransform(btTransform& worldTrans) const override;
// this relays outgoing position/rotation to the EntityItem
virtual void setWorldTransform(const btTransform& worldTrans) override;
void setWorldTransform(const btTransform& worldTrans) override;
// These pure virtual methods must be implemented for each MotionState type
// and make it possible to implement more complicated methods in this base class.
// pure virtual overrides from ObjectMotionState
virtual float getObjectRestitution() const override;
virtual float getObjectFriction() const override;
virtual float getObjectLinearDamping() const override;
virtual float getObjectAngularDamping() const override;
float getObjectRestitution() const override;
float getObjectFriction() const override;
float getObjectLinearDamping() const override;
float getObjectAngularDamping() const override;
virtual glm::vec3 getObjectPosition() const override;
virtual glm::quat getObjectRotation() const override;
virtual glm::vec3 getObjectLinearVelocity() const override;
virtual glm::vec3 getObjectAngularVelocity() const override;
virtual glm::vec3 getObjectGravity() const override;
glm::vec3 getObjectPosition() const override;
glm::quat getObjectRotation() const override;
glm::vec3 getObjectLinearVelocity() const override;
glm::vec3 getObjectAngularVelocity() const override;
glm::vec3 getObjectGravity() const override;
virtual const QUuid getObjectID() const override;
const QUuid getObjectID() const override;
virtual QString getName() const override;
virtual QUuid getSimulatorID() const override;
QString getName() const override;
ShapeType getShapeType() const override { return SHAPE_TYPE_HULL; }
QUuid getSimulatorID() const override;
void addDirtyFlags(uint32_t flags) { _dirtyFlags |= flags; }
virtual void computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const override;
void computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const override;
virtual float getMass() const override;
float getMass() const override;
void forceActive();
QUuid getAvatarID() const { return _avatar->getID(); }
int getJointIndex() const { return _jointIndex; }
void setIsBound(bool isBound, std::vector<int> boundJoints) { _isBound = isBound; _boundJoints = boundJoints; }
bool getIsBound(std::vector<int>& boundJoints) const { boundJoints = _boundJoints; return _isBound; }
int32_t getJointIndex() const { return _jointIndex; }
void setIsBound(bool isBound, const std::vector<int32_t>& boundJoints) { _isBound = isBound; _boundJoints = boundJoints; }
bool getIsBound(std::vector<int32_t>& boundJoints) const { boundJoints = _boundJoints; return _isBound; }
friend class AvatarManager;
friend class Avatar;
@ -84,17 +84,14 @@ protected:
// ever called by the Avatar class dtor.
~DetailedMotionState();
virtual bool isReadyToComputeShape() const override { return true; }
virtual const btCollisionShape* computeNewShape() override;
AvatarPointer _avatar;
float _diameter { 0.0f };
uint32_t _dirtyFlags;
int _jointIndex { -1 };
int32_t _jointIndex { -1 };
OtherAvatarPointer _otherAvatar { nullptr };
bool _isBound { false };
std::vector<int> _boundJoints;
std::vector<int32_t> _boundJoints;
};
#endif // hifi_DetailedMotionState_h

View file

@ -3755,6 +3755,7 @@ void MyAvatar::restrictScaleFromDomainSettings(const QJsonObject& domainSettings
void MyAvatar::leaveDomain() {
clearScaleRestriction();
saveAvatarScale();
prepareResetTraitInstances();
}
void MyAvatar::saveAvatarScale() {

View file

@ -377,21 +377,18 @@ void MyCharacterController::updateMassProperties() {
_rigidBody->setMassProps(mass, inertia);
}
btCollisionShape* MyCharacterController::createDetailedCollisionShapeForJoint(int jointIndex) {
const btCollisionShape* MyCharacterController::createDetailedCollisionShapeForJoint(int32_t jointIndex) {
ShapeInfo shapeInfo;
_avatar->computeDetailedShapeInfo(shapeInfo, jointIndex);
if (shapeInfo.getType() != SHAPE_TYPE_NONE) {
btCollisionShape* shape = const_cast<btCollisionShape*>(ObjectMotionState::getShapeManager()->getShape(shapeInfo));
if (shape) {
shape->setMargin(0.001f);
}
const btCollisionShape* shape = ObjectMotionState::getShapeManager()->getShape(shapeInfo);
return shape;
}
return nullptr;
}
DetailedMotionState* MyCharacterController::createDetailedMotionStateForJoint(int jointIndex) {
auto shape = createDetailedCollisionShapeForJoint(jointIndex);
DetailedMotionState* MyCharacterController::createDetailedMotionStateForJoint(int32_t jointIndex) {
const btCollisionShape* shape = createDetailedCollisionShapeForJoint(jointIndex);
if (shape) {
DetailedMotionState* motionState = new DetailedMotionState(_avatar, shape, jointIndex);
motionState->setMass(_avatar->computeMass());
@ -423,25 +420,16 @@ void MyCharacterController::buildPhysicsTransaction(PhysicsEngine::Transaction&
}
if (_pendingFlags & PENDING_FLAG_ADD_DETAILED_TO_SIMULATION) {
_pendingFlags &= ~PENDING_FLAG_ADD_DETAILED_TO_SIMULATION;
for (int i = 0; i < _avatar->getJointCount(); i++) {
for (int32_t i = 0; i < _avatar->getJointCount(); i++) {
auto dMotionState = createDetailedMotionStateForJoint(i);
if (dMotionState) {
_detailedMotionStates.push_back(dMotionState);
transaction.objectsToAdd.push_back(dMotionState);
}
}
}
}
void MyCharacterController::handleProcessedPhysicsTransaction(PhysicsEngine::Transaction& transaction) {
// things on objectsToRemove are ready for delete
for (auto object : transaction.objectsToRemove) {
delete object;
}
transaction.clear();
}
class DetailedRayResultCallback : public btCollisionWorld::AllHitsRayResultCallback {
public:
DetailedRayResultCallback()
@ -467,7 +455,7 @@ std::vector<MyCharacterController::RayAvatarResult> MyCharacterController::rayTe
_dynamicsWorld->rayTest(origin, end, rayCallback);
if (rayCallback.m_hitFractions.size() > 0) {
foundAvatars.reserve(rayCallback.m_hitFractions.size());
for (int i = 0; i < rayCallback.m_hitFractions.size(); i++) {
for (int32_t i = 0; i < rayCallback.m_hitFractions.size(); i++) {
auto object = rayCallback.m_collisionObjects[i];
ObjectMotionState* motionState = static_cast<ObjectMotionState*>(object->getUserPointer());
if (motionState && motionState->getType() == MOTIONSTATE_TYPE_DETAILED) {
@ -493,4 +481,4 @@ std::vector<MyCharacterController::RayAvatarResult> MyCharacterController::rayTe
}
}
return foundAvatars;
}
}

View file

@ -44,27 +44,25 @@ public:
void setDensity(btScalar density) { _density = density; }
btCollisionShape* createDetailedCollisionShapeForJoint(int jointIndex);
DetailedMotionState* createDetailedMotionStateForJoint(int jointIndex);
const btCollisionShape* createDetailedCollisionShapeForJoint(int32_t jointIndex);
DetailedMotionState* createDetailedMotionStateForJoint(int32_t jointIndex);
std::vector<DetailedMotionState*>& getDetailedMotionStates() { return _detailedMotionStates; }
void clearDetailedMotionStates();
void resetDetailedMotionStates();
void buildPhysicsTransaction(PhysicsEngine::Transaction& transaction);
void handleProcessedPhysicsTransaction(PhysicsEngine::Transaction& transaction);
struct RayAvatarResult {
bool _intersect { false };
bool _isBound { false };
QUuid _intersectWithAvatar;
int _intersectWithJoint { -1 };
int32_t _intersectWithJoint { -1 };
float _distance { 0.0f };
float _maxDistance { 0.0f };
QVariantMap _extraInfo;
glm::vec3 _intersectionPoint;
glm::vec3 _intersectionNormal;
std::vector<int> _boundJoints;
std::vector<int32_t> _boundJoints;
};
std::vector<RayAvatarResult> rayTest(const btVector3& origin, const btVector3& direction, const btScalar& length,
const QVector<uint>& jointsToExclude) const;

View file

@ -116,6 +116,8 @@ void OtherAvatar::updateSpaceProxy(workload::Transaction& transaction) const {
int OtherAvatar::parseDataFromBuffer(const QByteArray& buffer) {
int32_t bytesRead = Avatar::parseDataFromBuffer(buffer);
for (size_t i = 0; i < _detailedMotionStates.size(); i++) {
// NOTE: we activate _detailedMotionStates is because they are KINEMATIC
// and Bullet will automagically call DetailedMotionState::getWorldTransform() when active.
_detailedMotionStates[i]->forceActive();
}
if (_moving && _motionState) {
@ -124,11 +126,11 @@ int OtherAvatar::parseDataFromBuffer(const QByteArray& buffer) {
return bytesRead;
}
btCollisionShape* OtherAvatar::createCollisionShape(int jointIndex, bool& isBound, std::vector<int>& boundJoints) {
const btCollisionShape* OtherAvatar::createCollisionShape(int32_t jointIndex, bool& isBound, std::vector<int32_t>& boundJoints) {
ShapeInfo shapeInfo;
isBound = false;
QString jointName = "";
if (jointIndex > -1 && jointIndex < (int)_multiSphereShapes.size()) {
QString jointName = "";
if (jointIndex > -1 && jointIndex < (int32_t)_multiSphereShapes.size()) {
jointName = _multiSphereShapes[jointIndex].getJointName();
}
switch (_bodyLOD) {
@ -163,39 +165,21 @@ btCollisionShape* OtherAvatar::createCollisionShape(int jointIndex, bool& isBoun
}
break;
}
// Note: MultiSphereLow case really means: "skip fingers and use spheres for hands,
// else fall through to MultiSphereHigh case"
case BodyLOD::MultiSphereHigh:
computeDetailedShapeInfo(shapeInfo, jointIndex);
break;
default:
assert(false); // should never reach here
break;
}
if (shapeInfo.getType() != SHAPE_TYPE_NONE) {
auto shape = const_cast<btCollisionShape*>(ObjectMotionState::getShapeManager()->getShape(shapeInfo));
if (shape) {
shape->setMargin(0.001f);
}
return shape;
}
return nullptr;
}
DetailedMotionState* OtherAvatar::createMotionState(std::shared_ptr<OtherAvatar> avatar, int jointIndex) {
bool isBound = false;
std::vector<int> boundJoints;
btCollisionShape* shape = createCollisionShape(jointIndex, isBound, boundJoints);
if (shape) {
DetailedMotionState* motionState = new DetailedMotionState(avatar, shape, jointIndex);
motionState->setMass(computeMass());
motionState->setIsBound(isBound, boundJoints);
return motionState;
}
return nullptr;
return ObjectMotionState::getShapeManager()->getShape(shapeInfo);
}
void OtherAvatar::resetDetailedMotionStates() {
for (size_t i = 0; i < _detailedMotionStates.size(); i++) {
_detailedMotionStates[i] = nullptr;
}
// NOTE: the DetailedMotionStates are deleted after being added to PhysicsEngine::Transaction::_objectsToRemove
// See AvatarManager::handleProcessedPhysicsTransaction()
_detailedMotionStates.clear();
}
@ -231,11 +215,11 @@ void OtherAvatar::computeShapeLOD() {
}
bool OtherAvatar::isInPhysicsSimulation() const {
return _motionState != nullptr && _detailedMotionStates.size() > 0;
return _motionState && _motionState->getRigidBody();
}
bool OtherAvatar::shouldBeInPhysicsSimulation() const {
return !isDead() && !(isInPhysicsSimulation() && _needsReinsertion);
return !isDead() && _workloadRegion < workload::Region::R3;
}
bool OtherAvatar::needsPhysicsUpdate() const {
@ -245,12 +229,9 @@ bool OtherAvatar::needsPhysicsUpdate() const {
void OtherAvatar::rebuildCollisionShape() {
if (_motionState) {
// do not actually rebuild here, instead flag for later
_motionState->addDirtyFlags(Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS);
}
for (size_t i = 0; i < _detailedMotionStates.size(); i++) {
if (_detailedMotionStates[i]) {
_detailedMotionStates[i]->addDirtyFlags(Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS);
}
_needsReinsertion = true;
}
}
@ -260,25 +241,6 @@ void OtherAvatar::setCollisionWithOtherAvatarsFlags() {
}
}
void OtherAvatar::createDetailedMotionStates(const std::shared_ptr<OtherAvatar>& avatar) {
auto& detailedMotionStates = getDetailedMotionStates();
assert(detailedMotionStates.empty());
if (_bodyLOD == BodyLOD::Sphere) {
auto dMotionState = createMotionState(avatar, -1);
if (dMotionState) {
detailedMotionStates.push_back(dMotionState);
}
} else {
for (int i = 0; i < getJointCount(); i++) {
auto dMotionState = createMotionState(avatar, i);
if (dMotionState) {
detailedMotionStates.push_back(dMotionState);
}
}
}
_needsReinsertion = false;
}
void OtherAvatar::simulate(float deltaTime, bool inView) {
PROFILE_RANGE(simulation, "simulate");

View file

@ -52,9 +52,7 @@ public:
bool shouldBeInPhysicsSimulation() const;
bool needsPhysicsUpdate() const;
btCollisionShape* createCollisionShape(int jointIndex, bool& isBound, std::vector<int>& boundJoints);
DetailedMotionState* createMotionState(std::shared_ptr<OtherAvatar> avatar, int jointIndex);
void createDetailedMotionStates(const std::shared_ptr<OtherAvatar>& avatar);
const btCollisionShape* createCollisionShape(int32_t jointIndex, bool& isBound, std::vector<int32_t>& boundJoints);
std::vector<DetailedMotionState*>& getDetailedMotionStates() { return _detailedMotionStates; }
void resetDetailedMotionStates();
BodyLOD getBodyLOD() { return _bodyLOD; }

View file

@ -65,15 +65,15 @@ void GraphicsEngine::initializeGPU(GLWidget* glwidget) {
DependencyManager::get<TextureCache>()->setGPUContext(_gpuContext);
}
void GraphicsEngine::initializeRender(bool disableDeferred) {
void GraphicsEngine::initializeRender() {
// Set up the render engine
render::CullFunctor cullFunctor = LODManager::shouldRender;
_renderEngine->addJob<UpdateSceneTask>("UpdateScene");
#ifndef Q_OS_ANDROID
_renderEngine->addJob<SecondaryCameraRenderTask>("SecondaryCameraJob", cullFunctor, !disableDeferred);
_renderEngine->addJob<SecondaryCameraRenderTask>("SecondaryCameraJob", cullFunctor);
#endif
_renderEngine->addJob<RenderViewTask>("RenderMainView", cullFunctor, !disableDeferred, render::ItemKey::TAG_BITS_0, render::ItemKey::TAG_BITS_0);
_renderEngine->addJob<RenderViewTask>("RenderMainView", cullFunctor, render::ItemKey::TAG_BITS_0, render::ItemKey::TAG_BITS_0);
_renderEngine->load();
_renderEngine->registerScene(_renderScene);

View file

@ -44,7 +44,7 @@ public:
~GraphicsEngine();
void initializeGPU(GLWidget*);
void initializeRender(bool disableDeferred);
void initializeRender();
void startup();
void shutdown();

View file

@ -22,7 +22,7 @@ namespace render {
PerformanceTimer perfTimer("worldBox");
auto& batch = *args->_batch;
DependencyManager::get<GeometryCache>()->bindSimpleProgram(batch);
DependencyManager::get<GeometryCache>()->bindSimpleProgram(batch, false, false, true, false, false, true, args->_renderMethod == Args::RenderMethod::FORWARD);
WorldBoxRenderData::renderWorldBox(args, batch);
}
}

View file

@ -53,6 +53,15 @@ int main(int argc, const char* argv[]) {
// https://i.kym-cdn.com/entries/icons/original/000/008/342/ihave.jpg
QSurfaceFormat::setDefaultFormat(format);
#endif
#if defined(Q_OS_WIN)
// Check the minimum version of
if (gl::getAvailableVersion() < gl::getRequiredVersion()) {
MessageBoxA(nullptr, "Interface requires OpenGL 4.1 or higher", "Unsupported", MB_OK);
return -1;
}
#endif
setupHifiApplication(BuildInfo::INTERFACE_NAME);
QStringList arguments;

View file

@ -168,7 +168,7 @@ bool isEntityPhysicsReady(const EntityItemPointer& entity) {
bool hasAABox;
entity->getAABox(hasAABox);
if (hasAABox && downloadedCollisionTypes.count(modelEntity->getShapeType()) != 0) {
return (!entity->shouldBePhysical() || entity->isReadyToComputeShape() || modelEntity->computeShapeFailedToLoad());
return (!entity->shouldBePhysical() || entity->isInPhysicsSimulation() || modelEntity->computeShapeFailedToLoad());
}
}
}

View file

@ -20,8 +20,7 @@ const float ParabolaPointer::RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_W
const bool ParabolaPointer::RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA { false };
const bool ParabolaPointer::RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_DRAWINFRONT { false };
gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::_parabolaPipeline { nullptr };
gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::_transparentParabolaPipeline { nullptr };
std::map<std::pair<bool, bool>, gpu::PipelinePointer> ParabolaPointer::RenderState::ParabolaRenderItem::_parabolaPipelines;
ParabolaPointer::ParabolaPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover,
const PointerTriggers& triggers, bool faceAvatar, bool followNormal, float followNormalStrength, bool centerEndY, bool lockEnd, bool distanceScaleEnd,
@ -401,33 +400,34 @@ void ParabolaPointer::RenderState::ParabolaRenderItem::updateBounds() {
_bound = AABox(min, max - min);
}
const gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::getParabolaPipeline() {
if (!_parabolaPipeline || !_transparentParabolaPipeline) {
{
gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::parabola);
auto state = std::make_shared<gpu::State>();
gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::getParabolaPipeline(bool forward) const {
if (_parabolaPipelines.empty()) {
using namespace shader::render_utils::program;
static const std::vector<std::tuple<bool, bool, uint32_t>> keys = {
std::make_tuple(false, false, parabola), std::make_tuple(false, true, forward_parabola), std::make_tuple(true, false, parabola_translucent)/*, std::make_tuple(true, true, forward_parabola_translucent)*/
};
for (auto& key : keys) {
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
state->setDepthTest(true, true, gpu::LESS_EQUAL);
state->setBlendFunction(false,
if (std::get<0>(key)) {
PrepareStencil::testMask(*state);
} else {
PrepareStencil::testMaskDrawShape(*state);
}
state->setBlendFunction(std::get<0>(key),
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
PrepareStencil::testMaskDrawShape(*state);
state->setCullMode(gpu::State::CULL_NONE);
_parabolaPipeline = gpu::Pipeline::create(program, state);
_parabolaPipelines[{std::get<0>(key), std::get<1>(key)}] = gpu::Pipeline::create(gpu::Shader::createProgram(std::get<2>(key)), state);
}
{
gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::parabola_translucent);
auto state = std::make_shared<gpu::State>();
state->setDepthTest(true, true, gpu::LESS_EQUAL);
state->setBlendFunction(true,
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
PrepareStencil::testMask(*state);
state->setCullMode(gpu::State::CULL_NONE);
_transparentParabolaPipeline = gpu::Pipeline::create(program, state);
}
// The forward opaque/translucent pipelines are the same for now
_parabolaPipelines[{ true, true }] = _parabolaPipelines[{ false, true}];
}
return (_parabolaData.color.a < 1.0f ? _transparentParabolaPipeline : _parabolaPipeline);
return _parabolaPipelines[{ _parabolaData.color.a < 1.0f, forward }];
}
void ParabolaPointer::RenderState::ParabolaRenderItem::render(RenderArgs* args) {
@ -441,7 +441,7 @@ void ParabolaPointer::RenderState::ParabolaRenderItem::render(RenderArgs* args)
transform.setTranslation(_origin);
batch.setModelTransform(transform);
batch.setPipeline(getParabolaPipeline());
batch.setPipeline(getParabolaPipeline(args->_renderMethod == render::Args::RenderMethod::FORWARD));
const int MAX_SECTIONS = 100;
if (glm::length2(_parabolaData.acceleration) < EPSILON) {

View file

@ -26,9 +26,8 @@ public:
bool isVisibleInSecondaryCamera, bool drawInFront, bool enabled);
~ParabolaRenderItem() {}
static gpu::PipelinePointer _parabolaPipeline;
static gpu::PipelinePointer _transparentParabolaPipeline;
const gpu::PipelinePointer getParabolaPipeline();
static std::map<std::pair<bool, bool>, gpu::PipelinePointer> _parabolaPipelines;
gpu::PipelinePointer getParabolaPipeline(bool forward) const;
void render(RenderArgs* args);
render::Item::Bound& editBound() { return _bound; }

View file

@ -366,8 +366,10 @@ void Audio::onContextChanged() {
void Audio::handlePushedToTalk(bool enabled) {
if (getPTT()) {
if (enabled) {
DependencyManager::get<AudioClient>()->setOutputGain(0.1f); // duck the output by 20dB
setMuted(false);
} else {
DependencyManager::get<AudioClient>()->setOutputGain(1.0f);
setMuted(true);
}
}

View file

@ -22,6 +22,13 @@
#include <DependencyManager.h>
#include <OffscreenUi.h>
static const QVariantMap DOCK_AREA {
{ "TOP", DockArea::TOP },
{ "BOTTOM", DockArea::BOTTOM },
{ "LEFT", DockArea::LEFT },
{ "RIGHT", DockArea::RIGHT }
};
int DesktopScriptingInterface::getWidth() {
QSize size = qApp->getWindow()->windowHandle()->screen()->virtualSize();
return size.width();
@ -39,6 +46,10 @@ QVariantMap DesktopScriptingInterface::getPresentationMode() {
return presentationModes;
}
QVariantMap DesktopScriptingInterface::getDockArea() {
return DOCK_AREA;
}
void DesktopScriptingInterface::setHUDAlpha(float alpha) {
qApp->getApplicationCompositor().setAlpha(alpha);
}

View file

@ -17,7 +17,7 @@
#include <DependencyManager.h>
#include "InteractiveWindow.h"
#include "ui/InteractiveWindow.h"
/**jsdoc
* @namespace Desktop
@ -37,10 +37,11 @@ class DesktopScriptingInterface : public QObject, public Dependency {
Q_PROPERTY(int height READ getHeight) // Physical height of screen(s) including task bars and system menus
Q_PROPERTY(QVariantMap PresentationMode READ getPresentationMode CONSTANT FINAL)
Q_PROPERTY(QVariantMap DockArea READ getDockArea CONSTANT FINAL)
Q_PROPERTY(int ALWAYS_ON_TOP READ flagAlwaysOnTop CONSTANT FINAL)
Q_PROPERTY(int CLOSE_BUTTON_HIDES READ flagCloseButtonHides CONSTANT FINAL)
public:
public:
Q_INVOKABLE void setHUDAlpha(float alpha);
Q_INVOKABLE void show(const QString& path, const QString& title);
@ -54,6 +55,8 @@ private:
static int flagAlwaysOnTop() { return AlwaysOnTop; }
static int flagCloseButtonHides() { return CloseButtonHides; }
static QVariantMap getDockArea();
Q_INVOKABLE static QVariantMap getPresentationMode();
};

View file

@ -30,6 +30,9 @@ HMDScriptingInterface::HMDScriptingInterface() {
connect(qApp, &Application::miniTabletEnabledChanged, [this](bool enabled) {
emit miniTabletEnabledChanged(enabled);
});
connect(qApp, &Application::awayStateWhenFocusLostInVRChanged, [this](bool enabled) {
emit awayStateWhenFocusLostInVRChanged(enabled);
});
}
glm::vec3 HMDScriptingInterface::calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction) const {
@ -137,6 +140,14 @@ bool HMDScriptingInterface::getMiniTabletEnabled() {
return qApp->getMiniTabletEnabled();
}
void HMDScriptingInterface::setAwayStateWhenFocusLostInVREnabled(bool enabled) {
qApp->setAwayStateWhenFocusLostInVREnabled(enabled);
}
bool HMDScriptingInterface::getAwayStateWhenFocusLostInVREnabled() {
return qApp->getAwayStateWhenFocusLostInVREnabled();
}
QScriptValue HMDScriptingInterface::getHUDLookAtPosition2D(QScriptContext* context, QScriptEngine* engine) {
glm::vec3 hudIntersection;

View file

@ -375,6 +375,14 @@ signals:
*/
bool miniTabletEnabledChanged(bool enabled);
/**jsdoc
* Triggered when the altering the mode for going into an away state when the interface focus is lost in VR.
* @function HMD.awayStateWhenFocusLostInVRChanged
* @param {boolean} enabled - <code>true</code> if the setting to go into an away state in VR when the interface focus is lost is enabled, otherwise <code>false</code>.
* @returns {Signal}
*/
bool awayStateWhenFocusLostInVRChanged(bool enabled);
public:
HMDScriptingInterface();
@ -423,6 +431,9 @@ public:
void setMiniTabletEnabled(bool enabled);
bool getMiniTabletEnabled();
void setAwayStateWhenFocusLostInVREnabled(bool enabled);
bool getAwayStateWhenFocusLostInVREnabled();
QVariant getPlayAreaRect();
QVector<glm::vec3> getSensorPositions();

View file

@ -11,12 +11,15 @@
#include "InteractiveWindow.h"
#include "Application.h"
#include <QtQml/QQmlContext>
#include <QtCore/QThread>
#include <QtGui/QGuiApplication>
#include <QtQuick/QQuickWindow>
#include <QQuickView>
#include <DependencyManager.h>
#include <DockWidget.h>
#include <RegisteredMetaTypes.h>
#include "OffscreenUi.h"
@ -40,9 +43,17 @@ static const char* const VISIBLE_PROPERTY = "visible";
static const char* const INTERACTIVE_WINDOW_VISIBLE_PROPERTY = "interactiveWindowVisible";
static const char* const EVENT_BRIDGE_PROPERTY = "eventBridge";
static const char* const PRESENTATION_MODE_PROPERTY = "presentationMode";
static const char* const DOCKED_PROPERTY = "presentationWindowInfo";
static const char* const DOCK_AREA_PROPERTY = "dockArea";
static const QStringList KNOWN_SCHEMES = QStringList() << "http" << "https" << "file" << "about" << "atp" << "qrc";
static const int DEFAULT_HEIGHT = 60;
static void dockWidgetDeleter(DockWidget* dockWidget) {
dockWidget->deleteLater();
}
void registerInteractiveWindowMetaType(QScriptEngine* engine) {
qScriptRegisterMetaType(engine, interactiveWindowPointerToScriptValue, interactiveWindowPointerFromScriptValue);
}
@ -58,55 +69,117 @@ void interactiveWindowPointerFromScriptValue(const QScriptValue& object, Interac
}
InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap& properties) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
bool docked = false;
InteractiveWindowPresentationMode presentationMode = InteractiveWindowPresentationMode::Native;
if (properties.contains(PRESENTATION_MODE_PROPERTY)) {
presentationMode = (InteractiveWindowPresentationMode) properties[PRESENTATION_MODE_PROPERTY].toInt();
}
if (properties.contains(DOCKED_PROPERTY) && presentationMode == InteractiveWindowPresentationMode::Native) {
QVariantMap nativeWindowInfo = properties[DOCKED_PROPERTY].toMap();
Qt::DockWidgetArea dockArea = Qt::TopDockWidgetArea;
QString title;
QSize windowSize(DEFAULT_HEIGHT, DEFAULT_HEIGHT);
// Build the event bridge and wrapper on the main thread
offscreenUi->loadInNewContext(CONTENT_WINDOW_QML, [&](QQmlContext* context, QObject* object) {
_qmlWindow = object;
context->setContextProperty(EVENT_BRIDGE_PROPERTY, this);
if (properties.contains(FLAGS_PROPERTY)) {
object->setProperty(FLAGS_PROPERTY, properties[FLAGS_PROPERTY].toUInt());
}
if (properties.contains(PRESENTATION_MODE_PROPERTY)) {
object->setProperty(PRESENTATION_MODE_PROPERTY, properties[PRESENTATION_MODE_PROPERTY].toInt());
}
if (properties.contains(TITLE_PROPERTY)) {
object->setProperty(TITLE_PROPERTY, properties[TITLE_PROPERTY].toString());
title = properties[TITLE_PROPERTY].toString();
}
if (properties.contains(SIZE_PROPERTY)) {
const auto size = vec2FromVariant(properties[SIZE_PROPERTY]);
object->setProperty(INTERACTIVE_WINDOW_SIZE_PROPERTY, QSize(size.x, size.y));
}
if (properties.contains(POSITION_PROPERTY)) {
const auto position = vec2FromVariant(properties[POSITION_PROPERTY]);
object->setProperty(INTERACTIVE_WINDOW_POSITION_PROPERTY, QPointF(position.x, position.y));
}
if (properties.contains(VISIBLE_PROPERTY)) {
object->setProperty(VISIBLE_PROPERTY, properties[INTERACTIVE_WINDOW_VISIBLE_PROPERTY].toBool());
windowSize = QSize(size.x, size.y);
}
connect(object, SIGNAL(sendToScript(QVariant)), this, SLOT(qmlToScript(const QVariant&)), Qt::QueuedConnection);
connect(object, SIGNAL(interactiveWindowPositionChanged()), this, SIGNAL(positionChanged()), Qt::QueuedConnection);
connect(object, SIGNAL(interactiveWindowSizeChanged()), this, SIGNAL(sizeChanged()), Qt::QueuedConnection);
connect(object, SIGNAL(interactiveWindowVisibleChanged()), this, SIGNAL(visibleChanged()), Qt::QueuedConnection);
connect(object, SIGNAL(presentationModeChanged()), this, SIGNAL(presentationModeChanged()), Qt::QueuedConnection);
connect(object, SIGNAL(titleChanged()), this, SIGNAL(titleChanged()), Qt::QueuedConnection);
connect(object, SIGNAL(windowClosed()), this, SIGNAL(closed()), Qt::QueuedConnection);
connect(object, SIGNAL(selfDestruct()), this, SLOT(close()), Qt::QueuedConnection);
auto mainWindow = qApp->getWindow();
_dockWidget = std::shared_ptr<DockWidget>(new DockWidget(title, mainWindow), dockWidgetDeleter);
auto quickView = _dockWidget->getQuickView();
Application::setupQmlSurface(quickView->rootContext(), true);
if (nativeWindowInfo.contains(DOCK_AREA_PROPERTY)) {
DockArea dockedArea = (DockArea) nativeWindowInfo[DOCK_AREA_PROPERTY].toInt();
switch (dockedArea) {
case DockArea::TOP:
dockArea = Qt::TopDockWidgetArea;
_dockWidget->setFixedHeight(windowSize.height());
break;
case DockArea::BOTTOM:
dockArea = Qt::BottomDockWidgetArea;
_dockWidget->setFixedHeight(windowSize.height());
break;
case DockArea::LEFT:
dockArea = Qt::LeftDockWidgetArea;
_dockWidget->setFixedWidth(windowSize.width());
break;
case DockArea::RIGHT:
dockArea = Qt::RightDockWidgetArea;
_dockWidget->setFixedWidth(windowSize.width());
break;
default:
_dockWidget->setFixedHeight(DEFAULT_HEIGHT);
break;
}
}
QObject::connect(quickView.get(), &QQuickView::statusChanged, [&, this] (QQuickView::Status status) {
if (status == QQuickView::Ready) {
QQuickItem* rootItem = _dockWidget->getRootItem();
QObject::connect(rootItem, SIGNAL(sendToScript(QVariant)), this, SLOT(qmlToScript(const QVariant&)), Qt::QueuedConnection);
}
});
_dockWidget->setSource(QUrl(sourceUrl));
mainWindow->addDockWidget(dockArea, _dockWidget.get());
_dockedWindow = docked;
} else {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
// Build the event bridge and wrapper on the main thread
offscreenUi->loadInNewContext(CONTENT_WINDOW_QML, [&](QQmlContext* context, QObject* object) {
_qmlWindow = object;
context->setContextProperty(EVENT_BRIDGE_PROPERTY, this);
if (properties.contains(FLAGS_PROPERTY)) {
object->setProperty(FLAGS_PROPERTY, properties[FLAGS_PROPERTY].toUInt());
}
if (properties.contains(PRESENTATION_MODE_PROPERTY)) {
object->setProperty(PRESENTATION_MODE_PROPERTY, properties[PRESENTATION_MODE_PROPERTY].toInt());
}
if (properties.contains(TITLE_PROPERTY)) {
object->setProperty(TITLE_PROPERTY, properties[TITLE_PROPERTY].toString());
}
if (properties.contains(SIZE_PROPERTY)) {
const auto size = vec2FromVariant(properties[SIZE_PROPERTY]);
object->setProperty(INTERACTIVE_WINDOW_SIZE_PROPERTY, QSize(size.x, size.y));
}
if (properties.contains(POSITION_PROPERTY)) {
const auto position = vec2FromVariant(properties[POSITION_PROPERTY]);
object->setProperty(INTERACTIVE_WINDOW_POSITION_PROPERTY, QPointF(position.x, position.y));
}
if (properties.contains(VISIBLE_PROPERTY)) {
object->setProperty(VISIBLE_PROPERTY, properties[INTERACTIVE_WINDOW_VISIBLE_PROPERTY].toBool());
}
connect(object, SIGNAL(sendToScript(QVariant)), this, SLOT(qmlToScript(const QVariant&)), Qt::QueuedConnection);
connect(object, SIGNAL(interactiveWindowPositionChanged()), this, SIGNAL(positionChanged()), Qt::QueuedConnection);
connect(object, SIGNAL(interactiveWindowSizeChanged()), this, SIGNAL(sizeChanged()), Qt::QueuedConnection);
connect(object, SIGNAL(interactiveWindowVisibleChanged()), this, SIGNAL(visibleChanged()), Qt::QueuedConnection);
connect(object, SIGNAL(presentationModeChanged()), this, SIGNAL(presentationModeChanged()), Qt::QueuedConnection);
connect(object, SIGNAL(titleChanged()), this, SIGNAL(titleChanged()), Qt::QueuedConnection);
connect(object, SIGNAL(windowClosed()), this, SIGNAL(closed()), Qt::QueuedConnection);
connect(object, SIGNAL(selfDestruct()), this, SLOT(close()), Qt::QueuedConnection);
#ifdef Q_OS_WIN
connect(object, SIGNAL(nativeWindowChanged()), this, SLOT(parentNativeWindowToMainWindow()), Qt::QueuedConnection);
connect(object, SIGNAL(interactiveWindowVisibleChanged()), this, SLOT(parentNativeWindowToMainWindow()), Qt::QueuedConnection);
connect(object, SIGNAL(presentationModeChanged()), this, SLOT(parentNativeWindowToMainWindow()), Qt::QueuedConnection);
connect(object, SIGNAL(nativeWindowChanged()), this, SLOT(parentNativeWindowToMainWindow()), Qt::QueuedConnection);
connect(object, SIGNAL(interactiveWindowVisibleChanged()), this, SLOT(parentNativeWindowToMainWindow()), Qt::QueuedConnection);
connect(object, SIGNAL(presentationModeChanged()), this, SLOT(parentNativeWindowToMainWindow()), Qt::QueuedConnection);
#endif
QUrl sourceURL{ sourceUrl };
// If the passed URL doesn't correspond to a known scheme, assume it's a local file path
if (!KNOWN_SCHEMES.contains(sourceURL.scheme(), Qt::CaseInsensitive)) {
sourceURL = QUrl::fromLocalFile(sourceURL.toString()).toString();
}
object->setProperty(SOURCE_PROPERTY, sourceURL);
});
QUrl sourceURL{ sourceUrl };
// If the passed URL doesn't correspond to a known scheme, assume it's a local file path
if (!KNOWN_SCHEMES.contains(sourceURL.scheme(), Qt::CaseInsensitive)) {
sourceURL = QUrl::fromLocalFile(sourceURL.toString()).toString();
}
object->setProperty(SOURCE_PROPERTY, sourceURL);
});
}
}
InteractiveWindow::~InteractiveWindow() {
@ -115,7 +188,14 @@ InteractiveWindow::~InteractiveWindow() {
void InteractiveWindow::sendToQml(const QVariant& message) {
// Forward messages received from the script on to QML
QMetaObject::invokeMethod(_qmlWindow, "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message));
if (_dockedWindow) {
QQuickItem* rootItem = _dockWidget->getRootItem();
if (rootItem) {
QMetaObject::invokeMethod(_qmlWindow, "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message));
}
} else {
QMetaObject::invokeMethod(_qmlWindow, "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message));
}
}
void InteractiveWindow::emitScriptEvent(const QVariant& scriptMessage) {
@ -143,6 +223,9 @@ void InteractiveWindow::close() {
if (_qmlWindow) {
_qmlWindow->deleteLater();
}
qApp->getWindow()->removeDockWidget(_dockWidget.get());
_dockWidget = nullptr;
_qmlWindow = nullptr;
}

View file

@ -36,6 +36,14 @@ namespace InteractiveWindowEnums {
Native
};
Q_ENUM_NS(InteractiveWindowPresentationMode);
enum DockArea {
TOP,
BOTTOM,
LEFT,
RIGHT
};
Q_ENUM_NS(DockArea);
}
using namespace InteractiveWindowEnums;
@ -52,8 +60,10 @@ using namespace InteractiveWindowEnums;
* @property {Vec2} size
* @property {boolean} visible
* @property {Desktop.PresentationMode} presentationMode
*
*
*/
class DockWidget;
class InteractiveWindow : public QObject {
Q_OBJECT
@ -65,7 +75,6 @@ class InteractiveWindow : public QObject {
public:
InteractiveWindow(const QString& sourceUrl, const QVariantMap& properties);
~InteractiveWindow();
private:
@ -194,7 +203,9 @@ protected slots:
void qmlToScript(const QVariant& message);
private:
bool _dockedWindow { false };
QPointer<QObject> _qmlWindow;
std::shared_ptr<DockWidget> _dockWidget { nullptr };
};
typedef InteractiveWindow* InteractiveWindowPointer;

View file

@ -111,6 +111,12 @@ void setupPreferences() {
auto setter = [](bool value) { qApp->setSettingConstrainToolbarPosition(value); };
preferences->addPreference(new CheckPreference(UI_CATEGORY, "Constrain Toolbar Position to Horizontal Center", getter, setter));
}
{
auto getter = []()->bool { return qApp->getAwayStateWhenFocusLostInVREnabled(); };
auto setter = [](bool value) { qApp->setAwayStateWhenFocusLostInVREnabled(value); };
preferences->addPreference(new CheckPreference(UI_CATEGORY, "Go into away state when interface window loses focus in VR", getter, setter));
}
{
auto getter = []()->float { return qApp->getDesktopTabletScale(); };

View file

@ -145,6 +145,7 @@ void GameWorkloadRenderItem::setAllViews(const workload::Views& views) {
}
const gpu::PipelinePointer GameWorkloadRenderItem::getProxiesPipeline() {
// FIXME: this needs a forward pipeline, or to only write to one output
if (!_drawAllProxiesPipeline) {
gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::drawWorkloadProxy);
auto state = std::make_shared<gpu::State>();
@ -162,6 +163,7 @@ const gpu::PipelinePointer GameWorkloadRenderItem::getProxiesPipeline() {
const gpu::PipelinePointer GameWorkloadRenderItem::getViewsPipeline() {
// FIXME: this needs a forward pipeline, or to only write to one output
if (!_drawAllViewsPipeline) {
gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::drawWorkloadView);
auto state = std::make_shared<gpu::State>();

View file

@ -221,6 +221,35 @@ static float computeLoudness(int16_t* samples, int numSamples, int numChannels,
return (float)loudness * scale;
}
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;
}
// cubic poly from gain0 to gain1
float c3 = -2.0f * (gain1 - gain0);
float c2 = 3.0f * (gain1 - gain0);
float c0 = gain0;
float t = 0.0f;
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;
// apply gain to all channels
for (int ch = 0; ch < NUM_CHANNELS; ch++) {
buffer[NUM_CHANNELS*i + ch] *= gain;
}
}
}
static inline float convertToFloat(int16_t sample) {
return (float)sample * (1 / 32768.0f);
}
@ -2109,6 +2138,14 @@ qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) {
int framesPopped = samplesPopped / AudioConstants::STEREO;
int bytesWritten;
if (samplesPopped > 0) {
// apply output gain
float newGain = _audio->_outputGain.load(std::memory_order_acquire);
float oldGain = _audio->_lastOutputGain;
_audio->_lastOutputGain = newGain;
applyGainSmoothing<OUTPUT_CHANNEL_COUNT>(mixBuffer, framesPopped, oldGain, newGain);
if (deviceChannelCount == OUTPUT_CHANNEL_COUNT) {
// limit the audio
_audio->_audioLimiter.render(mixBuffer, (int16_t*)data, framesPopped);

View file

@ -241,8 +241,10 @@ public slots:
void setInputVolume(float volume, bool emitSignal = true);
void setReverb(bool reverb);
void setReverbOptions(const AudioEffectOptions* options);
void setLocalInjectorGain(float gain) { _localInjectorGain = gain; };
void setSystemInjectorGain(float gain) { _systemInjectorGain = gain; };
void setOutputGain(float gain) { _outputGain = gain; };
void outputNotify();
@ -395,6 +397,8 @@ private:
int _outputPeriod { 0 };
float* _outputMixBuffer { NULL };
int16_t* _outputScratchBuffer { NULL };
std::atomic<float> _outputGain { 1.0f };
float _lastOutputGain { 1.0f };
// for local audio (used by audio injectors thread)
std::atomic<float> _localInjectorGain { 1.0f };

View file

@ -10,6 +10,7 @@
//
#include "InboundAudioStream.h"
#include "TryLocker.h"
#include <glm/glm.hpp>
@ -215,7 +216,7 @@ int InboundAudioStream::parseData(ReceivedMessage& message) {
if (framesAvailable > _desiredJitterBufferFrames + MAX_FRAMES_OVER_DESIRED) {
int framesToDrop = framesAvailable - (_desiredJitterBufferFrames + DESIRED_JITTER_BUFFER_FRAMES_PADDING);
_ringBuffer.shiftReadPosition(framesToDrop * _ringBuffer.getNumFrameSamples());
_framesAvailableStat.reset();
_currentJitterBufferFrames = 0;
@ -247,10 +248,18 @@ int InboundAudioStream::lostAudioData(int numPackets) {
QByteArray decodedBuffer;
while (numPackets--) {
MutexTryLocker lock(_decoderMutex);
if (!lock.isLocked()) {
// an incoming packet is being processed,
// and will likely be on the ring buffer shortly,
// so don't bother generating more data
qCInfo(audiostream, "Packet currently being unpacked or lost frame already being generated. Not generating lost frame.");
return 0;
}
if (_decoder) {
_decoder->lostFrame(decodedBuffer);
} else {
decodedBuffer.resize(AudioConstants::NETWORK_FRAME_BYTES_STEREO);
decodedBuffer.resize(AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL * _numChannels);
memset(decodedBuffer.data(), 0, decodedBuffer.size());
}
_ringBuffer.writeData(decodedBuffer.data(), decodedBuffer.size());
@ -260,6 +269,12 @@ int InboundAudioStream::lostAudioData(int numPackets) {
int InboundAudioStream::parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties) {
QByteArray decodedBuffer;
// may block on the real-time thread, which is acceptible as
// parseAudioData is only called by the packet processing
// thread which, while high performance, is not as sensitive to
// delays as the real-time thread.
QMutexLocker lock(&_decoderMutex);
if (_decoder) {
_decoder->decode(packetAfterStreamProperties, decodedBuffer);
} else {
@ -278,16 +293,23 @@ int InboundAudioStream::writeDroppableSilentFrames(int silentFrames) {
// case we will call the decoder's lostFrame() method, which indicates
// that it should interpolate from its last known state down toward
// silence.
if (_decoder) {
// FIXME - We could potentially use the output from the codec, in which
// case we might get a cleaner fade toward silence. NOTE: The below logic
// attempts to catch up in the event that the jitter buffers have grown.
// The better long term fix is to use the output from the decode, detect
// when it actually reaches silence, and then delete the silent portions
// of the jitter buffers. Or petentially do a cross fade from the decode
// output to silence.
QByteArray decodedBuffer;
_decoder->lostFrame(decodedBuffer);
{
// may block on the real-time thread, which is acceptible as
// writeDroppableSilentFrames is only called by the packet processing
// thread which, while high performance, is not as sensitive to
// delays as the real-time thread.
QMutexLocker lock(&_decoderMutex);
if (_decoder) {
// FIXME - We could potentially use the output from the codec, in which
// case we might get a cleaner fade toward silence. NOTE: The below logic
// attempts to catch up in the event that the jitter buffers have grown.
// The better long term fix is to use the output from the decode, detect
// when it actually reaches silence, and then delete the silent portions
// of the jitter buffers. Or petentially do a cross fade from the decode
// output to silence.
QByteArray decodedBuffer;
_decoder->lostFrame(decodedBuffer);
}
}
// calculate how many silent frames we should drop.
@ -338,10 +360,23 @@ int InboundAudioStream::popSamples(int maxSamples, bool allOrNothing) {
popSamplesNoCheck(samplesAvailable);
samplesPopped = samplesAvailable;
} else {
// we can't pop any samples, set this stream to starved
// we can't pop any samples, set this stream to starved for jitter
// buffer calculations.
setToStarved();
_consecutiveNotMixedCount++;
_lastPopSucceeded = false;
//Kick PLC to generate a filler frame, reducing 'click'
lostAudioData(allOrNothing ? (maxSamples - samplesAvailable) / _ringBuffer.getNumFrameSamples() : 1);
samplesPopped = _ringBuffer.samplesAvailable();
if (samplesPopped) {
popSamplesNoCheck(samplesPopped);
} else {
// No samples available means a packet is currently being
// processed, so we don't generate lost audio data, and instead
// just wait for the packet to come in. This prevents locking
// the real-time audio thread at the cost of a potential (but rare)
// 'click'
_lastPopSucceeded = false;
}
}
}
return samplesPopped;
@ -528,6 +563,7 @@ void InboundAudioStream::setupCodec(CodecPluginPointer codec, const QString& cod
_codec = codec;
_selectedCodecName = codecName;
if (_codec) {
QMutexLocker lock(&_decoderMutex);
_decoder = codec->createDecoder(AudioConstants::SAMPLE_RATE, numChannels);
}
}
@ -535,6 +571,7 @@ void InboundAudioStream::setupCodec(CodecPluginPointer codec, const QString& cod
void InboundAudioStream::cleanupCodec() {
// release any old codec encoder/decoder first...
if (_codec) {
QMutexLocker lock(&_decoderMutex);
if (_decoder) {
_codec->releaseDecoder(_decoder);
_decoder = nullptr;

View file

@ -187,6 +187,7 @@ protected:
CodecPluginPointer _codec;
QString _selectedCodecName;
QMutex _decoderMutex;
Decoder* _decoder { nullptr };
int _mismatchedAudioCodecCount { 0 };
};

View file

@ -11,6 +11,7 @@
#include "MixedProcessedAudioStream.h"
#include "AudioLogging.h"
#include "TryLocker.h"
MixedProcessedAudioStream::MixedProcessedAudioStream(int numFramesCapacity, int numStaticJitterFrames)
: InboundAudioStream(AudioConstants::STEREO, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL,
@ -36,13 +37,20 @@ int MixedProcessedAudioStream::lostAudioData(int numPackets) {
QByteArray outputBuffer;
while (numPackets--) {
MutexTryLocker lock(_decoderMutex);
if (!lock.isLocked()) {
// an incoming packet is being processed,
// and will likely be on the ring buffer shortly,
// so don't bother generating more data
qCInfo(audiostream, "Packet currently being unpacked or lost frame already being generated. Not generating lost frame.");
return 0;
}
if (_decoder) {
_decoder->lostFrame(decodedBuffer);
} else {
decodedBuffer.resize(AudioConstants::NETWORK_FRAME_BYTES_STEREO);
memset(decodedBuffer.data(), 0, decodedBuffer.size());
}
emit addedStereoSamples(decodedBuffer);
emit processSamples(decodedBuffer, outputBuffer);
@ -55,6 +63,12 @@ int MixedProcessedAudioStream::lostAudioData(int numPackets) {
int MixedProcessedAudioStream::parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties) {
QByteArray decodedBuffer;
// may block on the real-time thread, which is acceptible as
// parseAudioData is only called by the packet processing
// thread which, while high performance, is not as sensitive to
// delays as the real-time thread.
QMutexLocker lock(&_decoderMutex);
if (_decoder) {
_decoder->decode(packetAfterStreamProperties, decodedBuffer);
} else {

View file

@ -690,6 +690,11 @@ void Avatar::fade(render::Transaction& transaction, render::Transition::Type typ
transaction.addTransitionToItem(itemId, type, _renderItemID);
}
}
_lastFadeRequested = type;
}
render::Transition::Type Avatar::getLastFadeRequested() const {
return _lastFadeRequested;
}
void Avatar::removeFromScene(AvatarSharedPointer self, const render::ScenePointer& scene, render::Transaction& transaction) {
@ -779,7 +784,7 @@ void Avatar::render(RenderArgs* renderArgs) {
pointerTransform.setTranslation(position);
pointerTransform.setRotation(rotation);
batch.setModelTransform(pointerTransform);
geometryCache->bindSimpleProgram(batch);
geometryCache->bindSimpleProgram(batch, false, false, true, false, false, true, renderArgs->_renderMethod == render::Args::FORWARD);
geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, laserLength, 0.0f), laserColor, _leftPointerGeometryID);
}
}
@ -803,7 +808,7 @@ void Avatar::render(RenderArgs* renderArgs) {
pointerTransform.setTranslation(position);
pointerTransform.setRotation(rotation);
batch.setModelTransform(pointerTransform);
geometryCache->bindSimpleProgram(batch);
geometryCache->bindSimpleProgram(batch, false, false, true, false, false, true, renderArgs->_renderMethod == render::Args::FORWARD);
geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, laserLength, 0.0f), laserColor, _rightPointerGeometryID);
}
}
@ -829,7 +834,7 @@ void Avatar::render(RenderArgs* renderArgs) {
auto& frustum = renderArgs->getViewFrustum();
auto textPosition = getDisplayNamePosition();
if (frustum.pointIntersectsFrustum(textPosition)) {
renderDisplayName(batch, frustum, textPosition);
renderDisplayName(batch, frustum, textPosition, renderArgs->_renderMethod == render::Args::FORWARD);
}
}
}
@ -1034,7 +1039,7 @@ Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& view, const g
return result;
}
void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& view, const glm::vec3& textPosition) const {
void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& view, const glm::vec3& textPosition, bool forward) const {
PROFILE_RANGE_BATCH(batch, __FUNCTION__);
bool shouldShowReceiveStats = showReceiveStats && !isMyAvatar();
@ -1090,7 +1095,7 @@ void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& view, const
{
PROFILE_RANGE_BATCH(batch, __FUNCTION__":renderBevelCornersRect");
DependencyManager::get<GeometryCache>()->bindSimpleProgram(batch, false, false, true, true, true);
DependencyManager::get<GeometryCache>()->bindSimpleProgram(batch, false, false, true, true, true, true, forward);
DependencyManager::get<GeometryCache>()->renderBevelCornersRect(batch, left, bottom, width, height,
bevelDistance, backgroundColor, _nameRectGeometryID);
}
@ -1103,7 +1108,7 @@ void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& view, const
batch.setModelTransform(textTransform);
{
PROFILE_RANGE_BATCH(batch, __FUNCTION__":renderText");
renderer->draw(batch, text_x, -text_y, nameUTF8.data(), textColor);
renderer->draw(batch, text_x, -text_y, nameUTF8.data(), textColor, glm::vec2(-1.0f), forward);
}
}
}

View file

@ -523,6 +523,7 @@ public:
void fadeIn(render::ScenePointer scene);
void fadeOut(render::Transaction& transaction, KillAvatarReason reason);
render::Transition::Type getLastFadeRequested() const;
// JSDoc is in AvatarData.h.
Q_INVOKABLE virtual float getEyeHeight() const override;
@ -694,13 +695,14 @@ protected:
glm::vec3 getDisplayNamePosition() const;
Transform calculateDisplayNameTransform(const ViewFrustum& view, const glm::vec3& textPosition) const;
void renderDisplayName(gpu::Batch& batch, const ViewFrustum& view, const glm::vec3& textPosition) const;
void renderDisplayName(gpu::Batch& batch, const ViewFrustum& view, const glm::vec3& textPosition, bool forward) const;
virtual bool shouldRenderHead(const RenderArgs* renderArgs) const;
virtual void fixupModelsInScene(const render::ScenePointer& scene);
virtual void updatePalms();
render::ItemID _renderItemID{ render::Item::INVALID_ITEM_ID };
render::Transition::Type _lastFadeRequested { render::Transition::Type::NONE }; // Used for sanity checking
ThreadSafeValueCache<glm::vec3> _leftPalmPositionCache { glm::vec3() };
ThreadSafeValueCache<glm::quat> _leftPalmRotationCache { glm::quat() };

View file

@ -220,6 +220,7 @@ void EntityTreeRenderer::stopDomainAndNonOwnedEntities() {
void EntityTreeRenderer::clearDomainAndNonOwnedEntities() {
stopDomainAndNonOwnedEntities();
auto sessionUUID = getTree()->getMyAvatarSessionUUID();
std::unordered_map<EntityItemID, EntityRendererPointer> savedEntities;
// remove all entities from the scene
auto scene = _viewState->getMain3DScene();
@ -227,7 +228,7 @@ void EntityTreeRenderer::clearDomainAndNonOwnedEntities() {
for (const auto& entry : _entitiesInScene) {
const auto& renderer = entry.second;
const EntityItemPointer& entityItem = renderer->getEntity();
if (!(entityItem->isLocalEntity() || (entityItem->isAvatarEntity() && entityItem->getOwningAvatarID() == getTree()->getMyAvatarSessionUUID()))) {
if (!(entityItem->isLocalEntity() || (entityItem->isAvatarEntity() && entityItem->getOwningAvatarID() == sessionUUID))) {
fadeOutRenderable(renderer);
} else {
savedEntities[entry.first] = entry.second;
@ -238,7 +239,9 @@ void EntityTreeRenderer::clearDomainAndNonOwnedEntities() {
_renderablesToUpdate = savedEntities;
_entitiesInScene = savedEntities;
_layeredZones.clearNonLocalLayeredZones();
if (_layeredZones.clearDomainAndNonOwnedZones(sessionUUID)) {
applyLayeredZones();
}
OctreeProcessor::clearDomainAndNonOwnedEntities();
}
@ -271,6 +274,9 @@ void EntityTreeRenderer::clear() {
// reset the zone to the default (while we load the next scene)
_layeredZones.clear();
if (!_shuttingDown) {
applyLayeredZones();
}
OctreeProcessor::clear();
}
@ -363,6 +369,7 @@ void EntityTreeRenderer::addPendingEntities(const render::ScenePointer& scene, r
for (const auto& processedId : processedIds) {
_entitiesToAdd.erase(processedId);
}
forceRecheckEntities();
}
}
}
@ -474,6 +481,12 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene
}
}
void EntityTreeRenderer::preUpdate() {
if (_tree && !_shuttingDown) {
_tree->preUpdate();
}
}
void EntityTreeRenderer::update(bool simulate) {
PROFILE_RANGE(simulation_physics, "ETR::update");
PerformanceTimer perfTimer("ETRupdate");
@ -537,8 +550,7 @@ void EntityTreeRenderer::handleSpaceUpdate(std::pair<int32_t, glm::vec4> proxyUp
_spaceUpdates.emplace_back(proxyUpdate.first, proxyUpdate.second);
}
bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(QSet<EntityItemID>& entitiesContainingAvatar) {
bool didUpdate = false;
void EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(QSet<EntityItemID>& entitiesContainingAvatar) {
float radius = 0.01f; // for now, assume 0.01 meter radius, because we actually check the point inside later
QVector<QUuid> entityIDs;
@ -550,7 +562,7 @@ bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(QSet<EntityItemI
// FIXME - if EntityTree had a findEntitiesContainingPoint() this could theoretically be a little faster
entityTree->evalEntitiesInSphere(_avatarPosition, radius, PickFilter(), entityIDs);
LayeredZones oldLayeredZones(std::move(_layeredZones));
LayeredZones oldLayeredZones(_layeredZones);
_layeredZones.clear();
// create a list of entities that actually contain the avatar's position
@ -578,8 +590,8 @@ bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(QSet<EntityItemI
if (contains) {
// if this entity is a zone and visible, add it to our layered zones
if (isZone && entity->getVisible() && renderableForEntity(entity)) {
_layeredZones.insert(std::dynamic_pointer_cast<ZoneEntityItem>(entity));
if (isZone && entity->getVisible() && renderableIdForEntity(entity) != render::Item::INVALID_ITEM_ID) {
_layeredZones.emplace_back(std::dynamic_pointer_cast<ZoneEntityItem>(entity));
}
if ((!hasScript && isZone) || scriptHasLoaded) {
@ -588,24 +600,17 @@ bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(QSet<EntityItemI
}
}
// check if our layered zones have changed
if ((_layeredZones.empty() && oldLayeredZones.empty()) || (!oldLayeredZones.empty() && _layeredZones.contains(oldLayeredZones))) {
return;
_layeredZones.sort();
if (!_layeredZones.equals(oldLayeredZones)) {
applyLayeredZones();
}
applyLayeredZones();
didUpdate = true;
});
return didUpdate;
}
bool EntityTreeRenderer::checkEnterLeaveEntities() {
void EntityTreeRenderer::checkEnterLeaveEntities() {
PROFILE_RANGE(simulation_physics, "EnterLeave");
PerformanceTimer perfTimer("enterLeave");
auto now = usecTimestampNow();
bool didUpdate = false;
if (_tree && !_shuttingDown) {
glm::vec3 avatarPosition = _viewState->getAvatarPosition();
@ -623,7 +628,7 @@ bool EntityTreeRenderer::checkEnterLeaveEntities() {
_forceRecheckEntities = false;
QSet<EntityItemID> entitiesContainingAvatar;
didUpdate = findBestZoneAndMaybeContainingEntities(entitiesContainingAvatar);
findBestZoneAndMaybeContainingEntities(entitiesContainingAvatar);
// Note: at this point we don't need to worry about the tree being locked, because we only deal with
// EntityItemIDs from here. The callEntityScriptMethod() method is robust against attempting to call scripts
@ -649,7 +654,6 @@ bool EntityTreeRenderer::checkEnterLeaveEntities() {
}
}
}
return didUpdate;
}
void EntityTreeRenderer::leaveDomainAndNonOwnedEntities() {
@ -696,18 +700,12 @@ bool EntityTreeRenderer::applyLayeredZones() {
// in the expected layered order and update the scene with it
auto scene = _viewState->getMain3DScene();
if (scene) {
render::Transaction transaction;
render::ItemIDs list;
for (auto& zone : _layeredZones) {
auto id = renderableIdForEntity(zone.zone);
// The zone may not have been rendered yet.
if (id != render::Item::INVALID_ITEM_ID) {
list.push_back(id);
}
}
render::Selection selection("RankedZones", list);
transaction.resetSelection(selection);
_layeredZones.appendRenderIDs(list, this);
render::Selection selection("RankedZones", list);
render::Transaction transaction;
transaction.resetSelection(selection);
scene->enqueueTransaction(transaction);
} else {
qCWarning(entitiesrenderer) << "EntityTreeRenderer::applyLayeredZones(), Unexpected null scene, possibly during application shutdown";
@ -1018,7 +1016,6 @@ void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) {
}
void EntityTreeRenderer::addingEntity(const EntityItemID& entityID) {
forceRecheckEntities(); // reset our state to force checking our inside/outsideness of entities
checkAndCallPreload(entityID);
auto entity = std::static_pointer_cast<EntityTree>(_tree)->findEntityByID(entityID);
if (entity) {
@ -1190,107 +1187,103 @@ void EntityTreeRenderer::updateEntityRenderStatus(bool shouldRenderEntities) {
}
void EntityTreeRenderer::updateZone(const EntityItemID& id) {
// Get in the zone!
auto zone = std::dynamic_pointer_cast<ZoneEntityItem>(getTree()->findEntityByEntityItemID(id));
if (zone && zone->contains(_avatarPosition)) {
_layeredZones.update(zone);
if (auto zone = std::dynamic_pointer_cast<ZoneEntityItem>(getTree()->findEntityByEntityItemID(id))) {
if (_layeredZones.update(zone, _avatarPosition, this)) {
applyLayeredZones();
}
}
}
EntityTreeRenderer::LayeredZones::LayeredZones(LayeredZones&& other) {
// In a swap:
// > All iterators and references remain valid. The past-the-end iterator is invalidated.
bool isSkyboxLayerValid = (other._skyboxLayer != other.end());
bool EntityTreeRenderer::LayeredZones::clearDomainAndNonOwnedZones(const QUuid& sessionUUID) {
bool zonesChanged = false;
swap(other);
_map.swap(other._map);
_skyboxLayer = other._skyboxLayer;
if (!isSkyboxLayerValid) {
_skyboxLayer = end();
auto it = begin();
while (it != end()) {
auto zone = it->zone.lock();
if (!zone || !(zone->isLocalEntity() || (zone->isAvatarEntity() && zone->getOwningAvatarID() == sessionUUID))) {
zonesChanged = true;
it = erase(it);
} else {
it++;
}
}
if (zonesChanged) {
sort();
}
return zonesChanged;
}
void EntityTreeRenderer::LayeredZones::clearNonLocalLayeredZones() {
std::set<LayeredZone> localLayeredZones;
std::map<QUuid, iterator> newMap;
std::pair<bool, bool> EntityTreeRenderer::LayeredZones::getZoneInteractionProperties() const {
for (auto it = cbegin(); it != cend(); it++) {
auto zone = it->zone.lock();
if (zone && zone->isDomainEntity()) {
return { zone->getFlyingAllowed(), zone->getGhostingAllowed() };
}
}
return { true, true };
}
for (auto iter = begin(); iter != end(); iter++) {
LayeredZone layeredZone = *iter;
bool EntityTreeRenderer::LayeredZones::update(std::shared_ptr<ZoneEntityItem> zone, const glm::vec3& position, EntityTreeRenderer* entityTreeRenderer) {
// When a zone's position or visibility changes, we call this method
// In order to resort our zones, we first remove the changed zone, and then re-insert it if necessary
if (layeredZone.zone->isLocalEntity()) {
bool success;
iterator it;
std::tie(it, success) = localLayeredZones.insert(layeredZone);
bool needsResort = false;
if (success) {
newMap.emplace(layeredZone.id, it);
{
auto it = begin();
while (it != end()) {
if (it->zone.lock() == zone) {
break;
}
it++;
}
if (it != end()) {
erase(it);
needsResort = true;
}
}
// Only call contains if the zone is rendering
if (zone->isVisible() && entityTreeRenderer->renderableIdForEntity(zone) != render::Item::INVALID_ITEM_ID && zone->contains(position)) {
emplace_back(zone);
needsResort = true;
}
if (needsResort) {
sort();
}
return needsResort;
}
bool EntityTreeRenderer::LayeredZones::equals(const LayeredZones& other) const {
if (size() != other.size()) {
return false;
}
auto it = cbegin();
auto otherIt = other.cbegin();
while (it != cend()) {
if (*it != *otherIt) {
return false;
}
it++;
otherIt++;
}
return true;
}
void EntityTreeRenderer::LayeredZones::appendRenderIDs(render::ItemIDs& list, EntityTreeRenderer* entityTreeRenderer) const {
for (auto it = cbegin(); it != cend(); it++) {
if (it->zone.lock()) {
auto id = entityTreeRenderer->renderableIdForEntityId(it->id);
if (id != render::Item::INVALID_ITEM_ID) {
list.push_back(id);
}
}
}
std::set<LayeredZone>::operator=(localLayeredZones);
_map = newMap;
_skyboxLayer = empty() ? end() : begin();
}
void EntityTreeRenderer::LayeredZones::clear() {
std::set<LayeredZone>::clear();
_map.clear();
_skyboxLayer = end();
}
std::pair<EntityTreeRenderer::LayeredZones::iterator, bool> EntityTreeRenderer::LayeredZones::insert(const LayeredZone& layer) {
iterator it;
bool success;
std::tie(it, success) = std::set<LayeredZone>::insert(layer);
if (success) {
_map.emplace(it->id, it);
}
return { it, success };
}
void EntityTreeRenderer::LayeredZones::update(std::shared_ptr<ZoneEntityItem> zone) {
bool isVisible = zone->isVisible();
if (empty() && isVisible) {
// there are no zones: set this one
insert(zone);
return;
} else {
LayeredZone zoneLayer(zone);
// find this zone's layer, if it exists
iterator layer = end();
auto it = _map.find(zoneLayer.id);
if (it != _map.end()) {
layer = it->second;
// if the volume changed, we need to resort the layer (reinsertion)
// if the visibility changed, we need to erase the layer
if (zoneLayer.volume != layer->volume || !isVisible) {
erase(layer);
_map.erase(it);
layer = end();
}
}
// (re)insert this zone's layer if necessary
if (layer == end() && isVisible) {
std::tie(layer, std::ignore) = insert(zoneLayer);
_map.emplace(layer->id, layer);
}
}
}
bool EntityTreeRenderer::LayeredZones::contains(const LayeredZones& other) {
bool result = std::equal(other.begin(), other._skyboxLayer, begin());
if (result) {
// if valid, set the _skyboxLayer from the other LayeredZones
_skyboxLayer = std::next(begin(), std::distance(other.begin(), other._skyboxLayer));
}
return result;
}
CalculateEntityLoadingPriority EntityTreeRenderer::_calculateEntityLoadingPriorityFunc = [](const EntityItem& item) -> float {
@ -1298,14 +1291,7 @@ CalculateEntityLoadingPriority EntityTreeRenderer::_calculateEntityLoadingPriori
};
std::pair<bool, bool> EntityTreeRenderer::getZoneInteractionProperties() {
for (auto& zone : _layeredZones) {
// Only domain entities control flying allowed and ghosting allowed
if (zone.zone && zone.zone->isDomainEntity()) {
return { zone.zone->getFlyingAllowed(), zone.zone->getGhostingAllowed() };
}
}
return { true, true };
return _layeredZones.getZoneInteractionProperties();
}
bool EntityTreeRenderer::wantsKeyboardFocus(const EntityItemID& id) const {

View file

@ -78,6 +78,7 @@ public:
void setSetPrecisionPickingOperator(std::function<void(unsigned int, bool)> setPrecisionPickingOperator) { _setPrecisionPickingOperator = setPrecisionPickingOperator; }
void shutdown();
void preUpdate();
void update(bool simulate);
EntityTreePointer getTree() { return std::static_pointer_cast<EntityTree>(_tree); }
@ -169,7 +170,7 @@ private:
void resetEntitiesScriptEngine();
bool findBestZoneAndMaybeContainingEntities(QSet<EntityItemID>& entitiesContainingAvatar);
void findBestZoneAndMaybeContainingEntities(QSet<EntityItemID>& entitiesContainingAvatar);
bool applyLayeredZones();
void stopDomainAndNonOwnedEntities();
@ -180,7 +181,7 @@ private:
EntityItemID _currentClickingOnEntityID;
QScriptValueList createEntityArgs(const EntityItemID& entityID);
bool checkEnterLeaveEntities();
void checkEnterLeaveEntities();
void leaveDomainAndNonOwnedEntities();
void leaveAllEntities();
void forceRecheckEntities();
@ -210,48 +211,38 @@ private:
class LayeredZone {
public:
LayeredZone(std::shared_ptr<ZoneEntityItem> zone, QUuid id, float volume) : zone(zone), id(id), volume(volume) {}
LayeredZone(std::shared_ptr<ZoneEntityItem> zone) : LayeredZone(zone, zone->getID(), zone->getVolumeEstimate()) {}
LayeredZone(std::shared_ptr<ZoneEntityItem> zone) : zone(zone), id(zone->getID()), volume(zone->getVolumeEstimate()) {}
bool operator<(const LayeredZone& r) const { return std::tie(volume, id) < std::tie(r.volume, r.id); }
bool operator==(const LayeredZone& r) const { return id == r.id; }
// We need to sort on volume AND id so that different clients sort zones with identical volumes the same way
bool operator<(const LayeredZone& r) const { return volume < r.volume || (volume == r.volume && id < r.id); }
bool operator==(const LayeredZone& r) const { return zone.lock() && zone.lock() == r.zone.lock(); }
bool operator!=(const LayeredZone& r) const { return !(*this == r); }
bool operator<=(const LayeredZone& r) const { return (*this < r) || (*this == r); }
std::shared_ptr<ZoneEntityItem> zone;
std::weak_ptr<ZoneEntityItem> zone;
QUuid id;
float volume;
};
class LayeredZones : public std::set<LayeredZone> {
class LayeredZones : public std::vector<LayeredZone> {
public:
LayeredZones() {};
LayeredZones(LayeredZones&& other);
bool clearDomainAndNonOwnedZones(const QUuid& sessionUUID);
// avoid accidental misconstruction
LayeredZones(const LayeredZones&) = delete;
LayeredZones& operator=(const LayeredZones&) = delete;
LayeredZones& operator=(LayeredZones&&) = delete;
void sort() { std::sort(begin(), end(), std::less<LayeredZone>()); }
bool equals(const LayeredZones& other) const;
bool update(std::shared_ptr<ZoneEntityItem> zone, const glm::vec3& position, EntityTreeRenderer* entityTreeRenderer);
void clear();
void clearNonLocalLayeredZones();
std::pair<iterator, bool> insert(const LayeredZone& layer);
void update(std::shared_ptr<ZoneEntityItem> zone);
bool contains(const LayeredZones& other);
std::shared_ptr<ZoneEntityItem> getZone() { return empty() ? nullptr : begin()->zone; }
private:
std::map<QUuid, iterator> _map;
iterator _skyboxLayer { end() };
void appendRenderIDs(render::ItemIDs& list, EntityTreeRenderer* entityTreeRenderer) const;
std::pair<bool, bool> getZoneInteractionProperties() const;
};
LayeredZones _layeredZones;
float _avgRenderableUpdateCost { 0.0f };
uint64_t _lastZoneCheck { 0 };
const uint64_t ZONE_CHECK_INTERVAL = USECS_PER_MSEC * 100; // ~10hz
const float ZONE_CHECK_DISTANCE = 0.001f;
float _avgRenderableUpdateCost { 0.0f };
ReadWriteLockable _changedEntitiesGuard;
std::unordered_set<EntityItemID> _changedEntities;

View file

@ -261,15 +261,17 @@ void GizmoEntityRenderer::doRender(RenderArgs* args) {
Transform transform;
bool hasTickMarks;
glm::vec4 tickProperties;
bool forward;
withReadLock([&] {
transform = _renderTransform;
hasTickMarks = _ringProperties.getHasTickMarks();
tickProperties = glm::vec4(_ringProperties.getMajorTickMarksAngle(), _ringProperties.getMajorTickMarksLength(),
_ringProperties.getMinorTickMarksAngle(), _ringProperties.getMinorTickMarksLength());
forward = _renderLayer != RenderLayer::WORLD || args->_renderMethod == Args::RenderMethod::FORWARD;
});
bool wireframe = render::ShapeKey(args->_globalShapeKey).isWireframe() || _primitiveMode == PrimitiveMode::LINES;
geometryCache->bindSimpleProgram(batch, false, isTransparent(), false, wireframe, true, true, _renderLayer != RenderLayer::WORLD);
geometryCache->bindSimpleProgram(batch, false, isTransparent(), false, wireframe, true, true, forward);
batch.setModelTransform(transform);

View file

@ -113,11 +113,13 @@ void GridEntityRenderer::doRender(RenderArgs* args) {
glm::vec4 color;
glm::vec3 dimensions;
Transform renderTransform;
bool forward;
withReadLock([&] {
color = glm::vec4(toGlm(_color), _alpha);
color = EntityRenderer::calculatePulseColor(color, _pulseProperties, _created);
dimensions = _dimensions;
renderTransform = _renderTransform;
forward = _renderLayer != RenderLayer::WORLD || args->_renderMethod == Args::RenderMethod::FORWARD;
});
if (!_visible) {
@ -153,5 +155,5 @@ void GridEntityRenderer::doRender(RenderArgs* args) {
DependencyManager::get<GeometryCache>()->renderGrid(*batch, minCorner, maxCorner,
minorGridRowDivisions, minorGridColDivisions, MINOR_GRID_EDGE,
majorGridRowDivisions, majorGridColDivisions, MAJOR_GRID_EDGE,
color, _geometryId);
color, forward, _geometryId);
}

View file

@ -55,7 +55,8 @@ void LineEntityRenderer::doRender(RenderArgs* args) {
transform.setRotation(modelTransform.getRotation());
batch.setModelTransform(transform);
if (_linePoints.size() > 1) {
DependencyManager::get<GeometryCache>()->bindSimpleProgram(batch);
DependencyManager::get<GeometryCache>()->bindSimpleProgram(batch, false, false, true, false, false, true,
_renderLayer != RenderLayer::WORLD || args->_renderMethod == Args::RenderMethod::FORWARD);
DependencyManager::get<GeometryCache>()->renderVertices(batch, gpu::LINE_STRIP, _lineVerticesID);
}
}

View file

@ -735,13 +735,15 @@ bool RenderableModelEntityItem::shouldBePhysical() const {
auto model = getModel();
// If we have a model, make sure it hasn't failed to download.
// If it has, we'll report back that we shouldn't be physical so that physics aren't held waiting for us to be ready.
if (model && (getShapeType() == SHAPE_TYPE_COMPOUND || getShapeType() == SHAPE_TYPE_SIMPLE_COMPOUND) && model->didCollisionGeometryRequestFail()) {
return false;
} else if (model && getShapeType() != SHAPE_TYPE_NONE && model->didVisualGeometryRequestFail()) {
return false;
} else {
return ModelEntityItem::shouldBePhysical();
ShapeType shapeType = getShapeType();
if (model) {
if ((shapeType == SHAPE_TYPE_COMPOUND || shapeType == SHAPE_TYPE_SIMPLE_COMPOUND) && model->didCollisionGeometryRequestFail()) {
return false;
} else if (shapeType != SHAPE_TYPE_NONE && model->didVisualGeometryRequestFail()) {
return false;
}
}
return !isDead() && shapeType != SHAPE_TYPE_NONE && QUrl(_modelURL).isValid();
}
int RenderableModelEntityItem::getJointParent(int index) const {
@ -1520,7 +1522,7 @@ void ModelEntityRenderer::doRender(RenderArgs* args) {
model = _model;
});
if (model) {
model->renderDebugMeshBoxes(batch);
model->renderDebugMeshBoxes(batch, args->_renderMethod == Args::RenderMethod::FORWARD);
}
#endif
}

View file

@ -19,15 +19,12 @@
#include <PerfStat.h>
#include <shaders/Shaders.h>
#include <DisableDeferred.h>
#include "paintStroke_Shared.slh"
using namespace render;
using namespace render::entities;
gpu::PipelinePointer PolyLineEntityRenderer::_pipeline = nullptr;
gpu::PipelinePointer PolyLineEntityRenderer::_glowPipeline = nullptr;
std::map<std::pair<render::Args::RenderMethod, bool>, gpu::PipelinePointer> PolyLineEntityRenderer::_pipelines;
static const QUrl DEFAULT_POLYLINE_TEXTURE = PathUtils::resourcesUrl("images/paintStroke.png");
@ -44,29 +41,24 @@ PolyLineEntityRenderer::PolyLineEntityRenderer(const EntityItemPointer& entity)
}
}
void PolyLineEntityRenderer::buildPipeline() {
// FIXME: opaque pipeline
gpu::ShaderPointer program = gpu::Shader::createProgram(DISABLE_DEFERRED ? shader::entities_renderer::program::paintStroke_forward : shader::entities_renderer::program::paintStroke);
void PolyLineEntityRenderer::buildPipelines() {
// FIXME: opaque pipelines
static const std::vector<std::pair<render::Args::RenderMethod, bool>> keys = {
{ render::Args::DEFERRED, false }, { render::Args::DEFERRED, true }, { render::Args::FORWARD, false }, { render::Args::FORWARD, true },
};
for (auto& key : keys) {
gpu::ShaderPointer program = gpu::Shader::createProgram(key.first == render::Args::DEFERRED ? shader::entities_renderer::program::paintStroke : shader::entities_renderer::program::paintStroke_forward);
{
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
state->setCullMode(gpu::State::CullMode::CULL_NONE);
state->setDepthTest(true, true, gpu::LESS_EQUAL);
state->setDepthTest(true, !key.second, gpu::LESS_EQUAL);
PrepareStencil::testMask(*state);
state->setBlendFunction(true,
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
_pipeline = gpu::Pipeline::create(program, state);
}
{
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
state->setCullMode(gpu::State::CullMode::CULL_NONE);
state->setDepthTest(true, false, gpu::LESS_EQUAL);
PrepareStencil::testMask(*state);
state->setBlendFunction(true,
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
_glowPipeline = gpu::Pipeline::create(program, state);
_pipelines[key] = gpu::Pipeline::create(program, state);
}
}
@ -299,11 +291,11 @@ void PolyLineEntityRenderer::doRender(RenderArgs* args) {
return;
}
if (!_pipeline) {
buildPipeline();
if (_pipelines.empty()) {
buildPipelines();
}
batch.setPipeline(_glow ? _glowPipeline : _pipeline);
batch.setPipeline(_pipelines[{args->_renderMethod, _glow}]);
batch.setModelTransform(transform);
batch.setResourceTexture(0, texture);
batch.draw(gpu::TRIANGLE_STRIP, (gpu::uint32)(2 * numVertices), 0);

View file

@ -37,7 +37,7 @@ protected:
virtual ShapeKey getShapeKey() override;
virtual void doRender(RenderArgs* args) override;
void buildPipeline();
static void buildPipelines();
void updateGeometry();
void updateData();
@ -58,8 +58,7 @@ protected:
size_t _numVertices;
gpu::BufferPointer _polylineDataBuffer;
gpu::BufferPointer _polylineGeometryBuffer;
static gpu::PipelinePointer _pipeline;
static gpu::PipelinePointer _glowPipeline;
static std::map<std::pair<render::Args::RenderMethod, bool>, gpu::PipelinePointer> _pipelines;
};
} } // namespace

View file

@ -72,7 +72,6 @@ public:
glm::mat4 localToVoxelMatrix() const;
virtual ShapeType getShapeType() const override;
virtual bool shouldBePhysical() const override { return !isDead(); }
virtual bool isReadyToComputeShape() const override;
virtual void computeShapeInfo(ShapeInfo& info) override;

View file

@ -280,7 +280,7 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) {
// FIXME, support instanced multi-shape rendering using multidraw indirect
outColor.a *= _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f;
render::ShapePipelinePointer pipeline;
if (renderLayer == RenderLayer::WORLD) {
if (renderLayer == RenderLayer::WORLD && args->_renderMethod != Args::RenderMethod::FORWARD) {
pipeline = outColor.a < 1.0f ? geometryCache->getTransparentShapePipeline() : geometryCache->getOpaqueShapePipeline();
} else {
pipeline = outColor.a < 1.0f ? geometryCache->getForwardTransparentShapePipeline() : geometryCache->getForwardOpaqueShapePipeline();

View file

@ -163,7 +163,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) {
Transform modelTransform;
glm::vec3 dimensions;
BillboardMode billboardMode;
bool layered;
bool forward;
withReadLock([&] {
modelTransform = _renderTransform;
dimensions = _dimensions;
@ -174,7 +174,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) {
textColor = EntityRenderer::calculatePulseColor(textColor, _pulseProperties, _created);
backgroundColor = glm::vec4(_backgroundColor, fadeRatio * _backgroundAlpha);
backgroundColor = EntityRenderer::calculatePulseColor(backgroundColor, _pulseProperties, _created);
layered = _renderLayer != RenderLayer::WORLD;
forward = _renderLayer != RenderLayer::WORLD || args->_renderMethod == render::Args::FORWARD;
});
// Render background
@ -187,7 +187,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) {
gpu::Batch& batch = *args->_batch;
// FIXME: we need to find a better way of rendering text so we don't have to do this
if (layered) {
if (forward) {
DependencyManager::get<DeferredLightingEffect>()->setupKeyLightBatch(args, batch);
}
@ -199,7 +199,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) {
if (backgroundColor.a > 0.0f) {
batch.setModelTransform(transformToTopLeft);
auto geometryCache = DependencyManager::get<GeometryCache>();
geometryCache->bindSimpleProgram(batch, false, backgroundColor.a < 1.0f, false, false, false, true, layered);
geometryCache->bindSimpleProgram(batch, false, backgroundColor.a < 1.0f, false, false, false, true, forward);
geometryCache->renderQuad(batch, minCorner, maxCorner, backgroundColor, _geometryID);
}
@ -210,7 +210,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) {
batch.setModelTransform(transformToTopLeft);
glm::vec2 bounds = glm::vec2(dimensions.x - (_leftMargin + _rightMargin), dimensions.y - (_topMargin + _bottomMargin));
_textRenderer->draw(batch, _leftMargin / scale, -_topMargin / scale, _text, textColor, bounds / scale, layered);
_textRenderer->draw(batch, _leftMargin / scale, -_topMargin / scale, _text, textColor, bounds / scale, forward);
}
}

View file

@ -92,9 +92,7 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) {
}
{ // Sun
// Need an update ?
if (_needSunUpdate) {
// Do we need to allocate the light in the stage ?
if (LightStage::isIndexInvalid(_sunIndex)) {
_sunIndex = _stage->addLight(_sunLight);
} else {
@ -107,9 +105,7 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) {
{ // Ambient
updateAmbientMap();
// Need an update ?
if (_needAmbientUpdate) {
// Do we need to allocate the light in the stage ?
if (LightStage::isIndexInvalid(_ambientIndex)) {
_ambientIndex = _stage->addLight(_ambientLight);
} else {
@ -123,7 +119,7 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) {
updateSkyboxMap();
if (_needBackgroundUpdate) {
if (_skyboxMode == COMPONENT_MODE_ENABLED && BackgroundStage::isIndexInvalid(_backgroundIndex)) {
if (BackgroundStage::isIndexInvalid(_backgroundIndex)) {
_backgroundIndex = _backgroundStage->addBackground(_background);
}
_needBackgroundUpdate = false;
@ -186,24 +182,19 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) {
}
}
void ZoneEntityRenderer::removeFromScene(const ScenePointer& scene, Transaction& transaction) {
#if 0
if (_model) {
_model->removeFromScene(scene, transaction);
}
#endif
Parent::removeFromScene(scene, transaction);
}
void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) {
DependencyManager::get<EntityTreeRenderer>()->updateZone(entity->getID());
auto position = entity->getWorldPosition();
auto rotation = entity->getWorldOrientation();
auto dimensions = entity->getScaledDimensions();
bool rotationChanged = rotation != _lastRotation;
bool transformChanged = rotationChanged || position != _lastPosition || dimensions != _lastDimensions;
auto visible = entity->getVisible();
if (transformChanged || visible != _lastVisible) {
_lastVisible = visible;
DependencyManager::get<EntityTreeRenderer>()->updateZone(entity->getID());
}
auto proceduralUserData = entity->getUserData();
bool proceduralUserDataChanged = _proceduralUserData != proceduralUserData;
@ -226,25 +217,6 @@ void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen
_proceduralUserData = entity->getUserData();
}
#if 0
if (_lastShapeURL != _typedEntity->getCompoundShapeURL()) {
_lastShapeURL = _typedEntity->getCompoundShapeURL();
_model.reset();
_model = std::make_shared<Model>();
_model->setIsWireframe(true);
_model->init();
_model->setURL(_lastShapeURL);
}
if (_model && _model->isActive()) {
_model->setScaleToFit(true, _lastDimensions);
_model->setSnapModelToRegistrationPoint(true, _entity->getRegistrationPoint());
_model->setRotation(_lastRotation);
_model->setTranslation(_lastPosition);
_model->simulate(0.0f);
}
#endif
updateKeyZoneItemFromEntity(entity);
if (keyLightChanged) {
@ -296,6 +268,10 @@ ItemKey ZoneEntityRenderer::getKey() {
}
bool ZoneEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const {
if (entity->getVisible() != _lastVisible) {
return true;
}
if (entity->keyLightPropertiesChanged() ||
entity->ambientLightPropertiesChanged() ||
entity->hazePropertiesChanged() ||
@ -323,25 +299,7 @@ bool ZoneEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoint
return true;
}
#if 0
if (_typedEntity->getCompoundShapeURL() != _lastShapeURL) {
return true;
}
if (_model) {
if (!_model->needsFixupInScene() && (!ZoneEntityItem::getDrawZoneBoundaries() || _entity->getShapeType() != SHAPE_TYPE_COMPOUND)) {
return true;
}
if (_model->needsFixupInScene() && (ZoneEntityItem::getDrawZoneBoundaries() || _entity->getShapeType() == SHAPE_TYPE_COMPOUND)) {
return true;
}
if (_lastModelActive != _model->isActive()) {
return true;
}
}
#endif
// FIXME: do we need to trigger an update when shapeType changes? see doRenderUpdateAsynchronousTyped
return false;
}
@ -450,7 +408,6 @@ void ZoneEntityRenderer::updateKeyZoneItemFromEntity(const TypedEntityPointer& e
}
void ZoneEntityRenderer::setAmbientURL(const QString& ambientUrl) {
// nothing change if nothing change
if (_ambientTextureURL == ambientUrl) {
return;
}
@ -466,8 +423,6 @@ void ZoneEntityRenderer::setAmbientURL(const QString& ambientUrl) {
_pendingAmbientTexture = true;
auto textureCache = DependencyManager::get<TextureCache>();
_ambientTexture = textureCache->getTexture(_ambientTextureURL, image::TextureUsage::AMBIENT_TEXTURE);
// keep whatever is assigned on the ambient map/sphere until texture is loaded
}
}
@ -492,7 +447,6 @@ void ZoneEntityRenderer::updateAmbientMap() {
}
void ZoneEntityRenderer::setSkyboxURL(const QString& skyboxUrl) {
// nothing change if nothing change
if (_skyboxTextureURL == skyboxUrl) {
return;
}

View file

@ -24,9 +24,6 @@
#include "RenderableEntityItem.h"
#include <ComponentMode.h>
#if 0
#include <Model.h>
#endif
namespace render { namespace entities {
class ZoneEntityRenderer : public TypedEntityRenderer<ZoneEntityItem> {
@ -40,7 +37,6 @@ protected:
virtual void onRemoveFromSceneTyped(const TypedEntityPointer& entity) override;
virtual ItemKey getKey() override;
virtual void doRender(RenderArgs* args) override;
virtual void removeFromScene(const ScenePointer& scene, Transaction& transaction) override;
virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override;
virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override;
virtual void doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) override;
@ -76,14 +72,7 @@ private:
glm::vec3 _lastPosition;
glm::vec3 _lastDimensions;
glm::quat _lastRotation;
// FIXME compount shapes are currently broken
// FIXME draw zone boundaries are currently broken (also broken in master)
#if 0
ModelPointer _model;
bool _lastModelActive { false };
QString _lastShapeURL;
#endif
bool _lastVisible;
LightStagePointer _stage;
const graphics::LightPointer _sunLight { std::make_shared<graphics::Light>() };
@ -137,25 +126,4 @@ private:
} } // namespace
#if 0
class NetworkGeometry;
class KeyLightPayload;
class RenderableZoneEntityItemMeta;
class RenderableZoneEntityItem : public ZoneEntityItem, public RenderableEntityInterface {
public:
virtual bool contains(const glm::vec3& point) const override;
virtual bool addToScene(const EntityItemPointer& self, const render::ScenePointer& scene, render::Transaction& transaction) override;
virtual void removeFromScene(const EntityItemPointer& self, const render::ScenePointer& scene, render::Transaction& transaction) override;
private:
virtual void locationChanged(bool tellPhysics = true, bool tellChildren = true) override { EntityItem::locationChanged(tellPhysics, tellChildren); notifyBoundChanged(); }
virtual void dimensionsChanged() override { EntityItem::dimensionsChanged(); notifyBoundChanged(); }
void notifyBoundChanged();
void notifyChangedRenderItem();
void sceneUpdateRenderItemFromEntity(render::Transaction& transaction);
};
#endif
#endif // hifi_RenderableZoneEntityItem_h

View file

@ -1835,42 +1835,42 @@ void EntityItem::setParentID(const QUuid& value) {
if (!value.isNull() && tree) {
EntityItemPointer entity = tree->findEntityByEntityItemID(value);
if (entity) {
newParentNoBootstrapping = entity->getSpecialFlags() & Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING;
newParentNoBootstrapping = entity->getSpecialFlags() & Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING;
}
}
if (!oldParentID.isNull() && tree) {
EntityItemPointer entity = tree->findEntityByEntityItemID(oldParentID);
if (entity) {
oldParentNoBootstrapping = entity->getDirtyFlags() & Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING;
oldParentNoBootstrapping = entity->getDirtyFlags() & Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING;
}
}
if (!value.isNull() && (value == Physics::getSessionUUID() || value == AVATAR_SELF_ID)) {
newParentNoBootstrapping |= Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING;
newParentNoBootstrapping |= Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING;
}
if (!oldParentID.isNull() && (oldParentID == Physics::getSessionUUID() || oldParentID == AVATAR_SELF_ID)) {
oldParentNoBootstrapping |= Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING;
oldParentNoBootstrapping |= Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING;
}
if ((bool)(oldParentNoBootstrapping ^ newParentNoBootstrapping)) {
if ((bool)(newParentNoBootstrapping & Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING)) {
markSpecialFlags(Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING);
if ((bool)(newParentNoBootstrapping & Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING)) {
markSpecialFlags(Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING);
forEachDescendant([&](SpatiallyNestablePointer object) {
if (object->getNestableType() == NestableType::Entity) {
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(object);
entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP);
entity->markSpecialFlags(Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING);
entity->markSpecialFlags(Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING);
}
});
} else {
clearSpecialFlags(Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING);
clearSpecialFlags(Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING);
forEachDescendant([&](SpatiallyNestablePointer object) {
if (object->getNestableType() == NestableType::Entity) {
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(object);
entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP);
entity->clearSpecialFlags(Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING);
entity->clearSpecialFlags(Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING);
}
});
}
@ -2030,9 +2030,10 @@ void EntityItem::setCollisionMask(uint16_t value) {
void EntityItem::setDynamic(bool value) {
if (getDynamic() != value) {
auto shapeType = getShapeType();
withWriteLock([&] {
// dynamic and STATIC_MESH are incompatible so we check for that case
if (value && getShapeType() == SHAPE_TYPE_STATIC_MESH) {
if (value && shapeType == SHAPE_TYPE_STATIC_MESH) {
if (_dynamic) {
_dynamic = false;
_flags |= Simulation::DIRTY_MOTION_TYPE;
@ -2108,7 +2109,7 @@ void EntityItem::computeCollisionGroupAndFinalMask(int32_t& group, int32_t& mask
}
}
if ((bool)(_flags & Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING)) {
if ((bool)(_flags & Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING)) {
userMask &= ~USER_COLLISION_GROUP_MY_AVATAR;
}
mask = Physics::getDefaultCollisionMask(group) & (int32_t)(userMask);
@ -2179,8 +2180,8 @@ bool EntityItem::addAction(EntitySimulationPointer simulation, EntityDynamicPoin
}
void EntityItem::enableNoBootstrap() {
if (!(bool)(_flags & Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING)) {
_flags |= Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING;
if (!(bool)(_flags & Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING)) {
_flags |= Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING;
_flags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar
// NOTE: unlike disableNoBootstrap() below, we do not call simulation->changeEntity() here
@ -2192,7 +2193,7 @@ void EntityItem::enableNoBootstrap() {
if (child->getNestableType() == NestableType::Entity) {
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(child);
entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP);
entity->markSpecialFlags(Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING);
entity->markSpecialFlags(Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING);
}
});
}
@ -2200,7 +2201,7 @@ void EntityItem::enableNoBootstrap() {
void EntityItem::disableNoBootstrap() {
if (!stillHasMyGrabAction()) {
_flags &= ~Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING;
_flags &= ~Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING;
_flags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar
EntityTreePointer entityTree = getTree();
@ -2213,7 +2214,7 @@ void EntityItem::disableNoBootstrap() {
if (child->getNestableType() == NestableType::Entity) {
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(child);
entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP);
entity->clearSpecialFlags(Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING);
entity->clearSpecialFlags(Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING);
simulation->changeEntity(entity);
}
});
@ -2332,7 +2333,7 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPoi
if (removedActionType == DYNAMIC_TYPE_HOLD || removedActionType == DYNAMIC_TYPE_FAR_GRAB) {
disableNoBootstrap();
} else {
// NO-OP: we assume SPECIAL_FLAGS_NO_BOOTSTRAPPING bits and collision group are correct
// NO-OP: we assume SPECIAL_FLAG_NO_BOOTSTRAPPING bits and collision group are correct
// because they should have been set correctly when the action was added
// and/or when children were linked
}
@ -3174,21 +3175,21 @@ DEFINE_PROPERTY_ACCESSOR(quint32, StaticCertificateVersion, staticCertificateVer
uint32_t EntityItem::getDirtyFlags() const {
uint32_t result;
withReadLock([&] {
result = _flags & Simulation::DIRTY_FLAGS;
result = _flags & Simulation::DIRTY_FLAGS_MASK;
});
return result;
}
void EntityItem::markDirtyFlags(uint32_t mask) {
withWriteLock([&] {
mask &= Simulation::DIRTY_FLAGS;
mask &= Simulation::DIRTY_FLAGS_MASK;
_flags |= mask;
});
}
void EntityItem::clearDirtyFlags(uint32_t mask) {
withWriteLock([&] {
mask &= Simulation::DIRTY_FLAGS;
mask &= Simulation::DIRTY_FLAGS_MASK;
_flags &= ~mask;
});
}
@ -3196,21 +3197,21 @@ void EntityItem::clearDirtyFlags(uint32_t mask) {
uint32_t EntityItem::getSpecialFlags() const {
uint32_t result;
withReadLock([&] {
result = _flags & Simulation::SPECIAL_FLAGS;
result = _flags & Simulation::SPECIAL_FLAGS_MASK;
});
return result;
}
void EntityItem::markSpecialFlags(uint32_t mask) {
withWriteLock([&] {
mask &= Simulation::SPECIAL_FLAGS;
mask &= Simulation::SPECIAL_FLAGS_MASK;
_flags |= mask;
});
}
void EntityItem::clearSpecialFlags(uint32_t mask) {
withWriteLock([&] {
mask &= Simulation::SPECIAL_FLAGS;
mask &= Simulation::SPECIAL_FLAGS_MASK;
_flags &= ~mask;
});
}

View file

@ -23,6 +23,7 @@
#include <OctreeElement.h> // for OctreeElement::AppendState
#include <OctreePacketData.h>
#include <PhysicsCollisionGroups.h>
#include <SimulationFlags.h>
#include <ShapeInfo.h>
#include <Transform.h>
#include <SpatiallyNestable.h>
@ -33,11 +34,11 @@
#include "EntityPropertyFlags.h"
#include "EntityTypes.h"
#include "SimulationOwner.h"
#include "SimulationFlags.h"
#include "EntityDynamicInterface.h"
#include "GrabPropertyGroup.h"
class EntitySimulation;
using EntitySimulationPointer = std::shared_ptr<EntitySimulation>;
class EntityTreeElement;
class EntityTreeElementExtraEncodeData;
class EntityDynamicInterface;
@ -322,7 +323,7 @@ public:
bool getDynamic() const;
void setDynamic(bool value);
virtual bool shouldBePhysical() const { return false; }
virtual bool shouldBePhysical() const { return !isDead() && getShapeType() != SHAPE_TYPE_NONE; }
bool isVisuallyReady() const { return _visuallyReady; }
bool getLocked() const;
@ -426,8 +427,9 @@ public:
bool isSimulated() const { return _simulated; }
void* getPhysicsInfo() const { return _physicsInfo; }
bool isInPhysicsSimulation() const { return (bool)(_flags & Simulation::SPECIAL_FLAG_IN_PHYSICS_SIMULATION); }
void* getPhysicsInfo() const { return _physicsInfo; }
void setPhysicsInfo(void* data) { _physicsInfo = data; }
EntityTreeElementPointer getElement() const { return _element; }

View file

@ -176,47 +176,44 @@ void EntitySimulation::addEntity(EntityItemPointer entity) {
void EntitySimulation::changeEntity(EntityItemPointer entity) {
QMutexLocker lock(&_mutex);
assert(entity);
if (!entity->isSimulated()) {
// This entity was either never added to the simulation or has been removed
// (probably for pending delete), so we don't want to keep a pointer to it
// on any internal lists.
return;
}
_changedEntities.insert(entity);
}
// Although it is not the responsibility of the EntitySimulation to sort the tree for EXTERNAL changes
// it IS responsibile for triggering deletes for entities that leave the bounds of the domain, hence
// we must check for that case here, however we rely on the change event to have set DIRTY_POSITION flag.
void EntitySimulation::processChangedEntities() {
QMutexLocker lock(&_mutex);
PROFILE_RANGE_EX(simulation_physics, "processChangedEntities", 0xffff00ff, (uint64_t)_changedEntities.size());
for (auto& entity : _changedEntities) {
if (entity->isSimulated()) {
processChangedEntity(entity);
}
}
_changedEntities.clear();
}
void EntitySimulation::processChangedEntity(const EntityItemPointer& entity) {
uint32_t dirtyFlags = entity->getDirtyFlags();
if (dirtyFlags & Simulation::DIRTY_POSITION) {
AACube domainBounds(glm::vec3((float)-HALF_TREE_SCALE), (float)TREE_SCALE);
bool success;
AACube newCube = entity->getQueryAACube(success);
if (success && !domainBounds.touches(newCube)) {
qCDebug(entities) << "Entity " << entity->getEntityItemID() << " moved out of domain bounds.";
entity->die();
prepareEntityForDelete(entity);
return;
}
}
if (dirtyFlags & Simulation::DIRTY_LIFETIME) {
if (entity->isMortal()) {
_mortalEntities.insert(entity);
uint64_t expiry = entity->getExpiry();
if (expiry < _nextExpiry) {
_nextExpiry = expiry;
if (dirtyFlags & (Simulation::DIRTY_LIFETIME | Simulation::DIRTY_UPDATEABLE)) {
if (dirtyFlags & Simulation::DIRTY_LIFETIME) {
if (entity->isMortal()) {
_mortalEntities.insert(entity);
uint64_t expiry = entity->getExpiry();
if (expiry < _nextExpiry) {
_nextExpiry = expiry;
}
} else {
_mortalEntities.remove(entity);
}
} else {
_mortalEntities.remove(entity);
}
entity->clearDirtyFlags(Simulation::DIRTY_LIFETIME);
if (dirtyFlags & Simulation::DIRTY_UPDATEABLE) {
if (entity->needsToCallUpdate()) {
_entitiesToUpdate.insert(entity);
} else {
_entitiesToUpdate.remove(entity);
}
}
entity->clearDirtyFlags(Simulation::DIRTY_LIFETIME | Simulation::DIRTY_UPDATEABLE);
}
if (entity->needsToCallUpdate()) {
_entitiesToUpdate.insert(entity);
} else {
_entitiesToUpdate.remove(entity);
}
changeEntityInternal(entity);
}
void EntitySimulation::clearEntities() {

View file

@ -13,6 +13,7 @@
#define hifi_EntitySimulation_h
#include <limits>
#include <unordered_set>
#include <QtCore/QObject>
#include <QSet>
@ -82,13 +83,15 @@ public:
/// \param entity pointer to EntityItem that needs to be put on the entitiesToDelete list and removed from others.
virtual void prepareEntityForDelete(EntityItemPointer entity);
void processChangedEntities();
protected:
// These pure virtual methods are protected because they are not to be called will-nilly. The base class
// calls them in the right places.
virtual void updateEntitiesInternal(uint64_t now) = 0;
virtual void addEntityInternal(EntityItemPointer entity) = 0;
virtual void removeEntityInternal(EntityItemPointer entity);
virtual void changeEntityInternal(EntityItemPointer entity) = 0;
virtual void processChangedEntity(const EntityItemPointer& entity);
virtual void clearEntitiesInternal() = 0;
void expireMortalEntities(uint64_t now);
@ -114,11 +117,11 @@ private:
// We maintain multiple lists, each for its distinct purpose.
// An entity may be in more than one list.
std::unordered_set<EntityItemPointer> _changedEntities; // all changes this frame
SetOfEntities _allEntities; // tracks all entities added the simulation
SetOfEntities _mortalEntities; // entities that have an expiry
uint64_t _nextExpiry;
SetOfEntities _entitiesToUpdate; // entities that need to call EntityItem::update()
};

View file

@ -2108,7 +2108,6 @@ void EntityTree::entityChanged(EntityItemPointer entity) {
}
void EntityTree::fixupNeedsParentFixups() {
PROFILE_RANGE(simulation_physics, "FixupParents");
MovingEntitiesOperator moveOperator;
QVector<EntityItemWeakPointer> entitiesToFixup;
{
@ -2218,11 +2217,19 @@ void EntityTree::addToNeedsParentFixupList(EntityItemPointer entity) {
_needsParentFixup.append(entity);
}
void EntityTree::preUpdate() {
withWriteLock([&] {
fixupNeedsParentFixups();
if (_simulation) {
_simulation->processChangedEntities();
}
});
}
void EntityTree::update(bool simulate) {
PROFILE_RANGE(simulation_physics, "UpdateTree");
PerformanceTimer perfTimer("updateTree");
withWriteLock([&] {
fixupNeedsParentFixups();
if (simulate && _simulation) {
_simulation->updateEntities();
{

View file

@ -109,9 +109,10 @@ public:
virtual void releaseSceneEncodeData(OctreeElementExtraEncodeData* extraEncodeData) const override;
virtual void update() override { update(true); }
void update(bool simulate);
// Why preUpdate() and update()?
// Because sometimes we need to do stuff between the two.
void preUpdate() override;
void update(bool simulate = true) override;
// The newer API...
void postAddEntity(EntityItemPointer entityItem);

View file

@ -421,11 +421,6 @@ void ModelEntityItem::setAnimationFPS(float value) {
});
}
// virtual
bool ModelEntityItem::shouldBePhysical() const {
return !isDead() && getShapeType() != SHAPE_TYPE_NONE && QUrl(_modelURL).isValid();
}
void ModelEntityItem::resizeJointArrays(int newSize) {
if (newSize < 0) {
return;

View file

@ -118,8 +118,6 @@ public:
const QString getTextures() const;
void setTextures(const QString& textures);
virtual bool shouldBePhysical() const override;
virtual void setJointRotations(const QVector<glm::quat>& rotations);
virtual void setJointRotationsSet(const QVector<bool>& rotationsSet);
virtual void setJointTranslations(const QVector<glm::vec3>& translations);

View file

@ -231,6 +231,8 @@ public:
EntityPropertyFlags& propertyFlags, bool overwriteLocalData,
bool& somethingChanged) override;
bool shouldBePhysical() const override { return false; }
void setColor(const glm::u8vec3& value);
glm::u8vec3 getColor() const { return _particleProperties.color.gradient.target; }

View file

@ -164,7 +164,6 @@ class PolyVoxEntityItem : public EntityItem {
glm::vec3 getSurfacePositionAdjustment() const;
virtual ShapeType getShapeType() const override;
virtual bool shouldBePhysical() const override { return !isDead(); }
bool isEdged() const;

View file

@ -84,8 +84,6 @@ public:
void setUnscaledDimensions(const glm::vec3& value) override;
bool shouldBePhysical() const override { return !isDead(); }
bool supportsDetailedIntersection() const override;
bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
OctreeElementPointer& element, float& distance,

View file

@ -85,7 +85,9 @@ void SimpleEntitySimulation::removeEntityInternal(EntityItemPointer entity) {
_entitiesThatNeedSimulationOwner.remove(entity);
}
void SimpleEntitySimulation::changeEntityInternal(EntityItemPointer entity) {
void SimpleEntitySimulation::processChangedEntity(const EntityItemPointer& entity) {
EntitySimulation::processChangedEntity(entity);
uint32_t flags = entity->getDirtyFlags();
if ((flags & Simulation::DIRTY_SIMULATOR_ID) || (flags & Simulation::DIRTY_VELOCITIES)) {
if (entity->getSimulatorID().isNull()) {

View file

@ -31,7 +31,7 @@ protected:
void updateEntitiesInternal(uint64_t now) override;
void addEntityInternal(EntityItemPointer entity) override;
void removeEntityInternal(EntityItemPointer entity) override;
void changeEntityInternal(EntityItemPointer entity) override;
void processChangedEntity(const EntityItemPointer& entity) override;
void clearEntitiesInternal() override;
void sortEntitiesThatMoved() override;

View file

@ -89,6 +89,8 @@
// (14) When an entity's ownership priority drops to YIELD (=1, below VOLUNTEER) other participants may
// bid for it immediately at VOLUNTEER.
//
/* These declarations temporarily moved to SimulationFlags.h while we unravel some spaghetti dependencies.
* The intent is to move them back here once the dust settles.
const uint8_t YIELD_SIMULATION_PRIORITY = 1;
const uint8_t VOLUNTEER_SIMULATION_PRIORITY = YIELD_SIMULATION_PRIORITY + 1;
const uint8_t RECRUIT_SIMULATION_PRIORITY = VOLUNTEER_SIMULATION_PRIORITY + 1;
@ -101,6 +103,7 @@ const uint8_t SCRIPT_POKE_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY
// which really just means: things that collide with it will be bid at a priority level one lower
const uint8_t PERSONAL_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY;
const uint8_t AVATAR_ENTITY_SIMULATION_PRIORITY = PERSONAL_SIMULATION_PRIORITY;
*/
class SimulationOwner {

View file

@ -62,6 +62,7 @@ public:
virtual bool isReadyToComputeShape() const override { return false; }
virtual void setShapeType(ShapeType type) override;
virtual ShapeType getShapeType() const override;
bool shouldBePhysical() const override { return false; }
QString getCompoundShapeURL() const;
virtual void setCompoundShapeURL(const QString& url);

View file

@ -874,6 +874,7 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash&
joint.isSkeletonJoint = false;
hfmModel.joints.push_back(joint);
}
hfmModel.shapeVertices.resize(hfmModel.joints.size());
// Build skeleton
@ -1243,6 +1244,13 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash&
}
}
for (int clusterIndex = 0; clusterIndex < mesh.clusters.size() - 1; clusterIndex++) {
ShapeVertices& points = hfmModel.shapeVertices.at(clusterIndex);
for (glm::vec3 vertex : mesh.vertices) {
points.push_back(vertex);
}
}
mesh.meshExtents.reset();
foreach(const glm::vec3& vertex, mesh.vertices) {
mesh.meshExtents.addPoint(vertex);

View file

@ -71,19 +71,159 @@ void gl::globalRelease(bool finish) {}
#endif
void gl::getTargetVersion(int& major, int& minor) {
uint16_t gl::getTargetVersion() {
uint8_t major = 0, minor = 0;
#if defined(USE_GLES)
major = 3;
minor = 2;
#else
#if defined(Q_OS_MAC)
#elif defined(Q_OS_MAC)
major = 4;
minor = 1;
#else
major = 4;
minor = disableGl45() ? 1 : 5;
#endif
return GL_MAKE_VERSION(major, minor);
}
uint16_t gl::getRequiredVersion() {
uint8_t major = 0, minor = 0;
#if defined(USE_GLES)
major = 3;
minor = 2;
#else
major = 4;
minor = 1;
#endif
return GL_MAKE_VERSION(major, minor);
}
#if defined(Q_OS_WIN)
typedef BOOL(APIENTRYP PFNWGLCHOOSEPIXELFORMATARBPROC)(HDC hdc, const int *piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats);
typedef HGLRC(APIENTRYP PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC hDC, HGLRC hShareContext, const int *attribList);
GLAPI PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB;
GLAPI PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB;
static bool setupPixelFormatSimple(HDC hdc) {
// FIXME build the PFD based on the
static const PIXELFORMATDESCRIPTOR pfd = // pfd Tells Windows How We Want Things To Be
{
sizeof(PIXELFORMATDESCRIPTOR), // Size Of This Pixel Format Descriptor
1, // Version Number
PFD_DRAW_TO_WINDOW | // Format Must Support Window
PFD_SUPPORT_OPENGL | // Format Must Support OpenGL
PFD_DOUBLEBUFFER, // Must Support Double Buffering
PFD_TYPE_RGBA, // Request An RGBA Format
24, // Select Our Color Depth
0, 0, 0, 0, 0, 0, // Color Bits Ignored
1, // Alpha Buffer
0, // Shift Bit Ignored
0, // No Accumulation Buffer
0, 0, 0, 0, // Accumulation Bits Ignored
24, // 24 Bit Z-Buffer (Depth Buffer)
8, // 8 Bit Stencil Buffer
0, // No Auxiliary Buffer
PFD_MAIN_PLANE, // Main Drawing Layer
0, // Reserved
0, 0, 0 // Layer Masks Ignored
};
auto pixelFormat = ChoosePixelFormat(hdc, &pfd);
if (pixelFormat == 0) {
return false;
}
if (SetPixelFormat(hdc, pixelFormat, &pfd) == FALSE) {
return false;
}
return true;
}
#endif
uint16_t gl::getAvailableVersion() {
static uint8_t major = 0, minor = 0;
static std::once_flag once;
std::call_once(once, [&] {
#if defined(USE_GLES)
// FIXME do runtime detection of the available GL version
major = 3;
minor = 2;
#elif defined(Q_OS_MAC)
// Damn it Apple.
major = 4;
minor = 1;
#elif defined(Q_OS_WIN)
//
HINSTANCE hInstance = GetModuleHandle(nullptr);
const auto windowClassName = "OpenGLVersionCheck";
WNDCLASS wc = { };
wc.lpfnWndProc = DefWindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = windowClassName;
RegisterClass(&wc);
using Handle = std::shared_ptr<void>;
HWND rawHwnd = CreateWindowEx(
WS_EX_APPWINDOW, // extended style
windowClassName, // class name
windowClassName, // title
WS_CLIPSIBLINGS | WS_CLIPCHILDREN | CS_OWNDC | WS_POPUP, // style
0, 0, 10, 10, // position and size
NULL, NULL, hInstance, NULL);
auto WindowDestroyer = [](void* handle) {
DestroyWindow((HWND)handle);
};
Handle hWnd = Handle(rawHwnd, WindowDestroyer);
if (!hWnd) {
return;
}
HDC rawDC = GetDC(rawHwnd);
auto DCDestroyer = [=](void* handle) {
ReleaseDC(rawHwnd, (HDC)handle);
};
if (!rawDC) {
return;
}
Handle hDC = Handle(rawDC, DCDestroyer);
if (!setupPixelFormatSimple(rawDC)) {
return;
}
auto GLRCDestroyer = [](void* handle) {
wglDeleteContext((HGLRC)handle);
};
auto rawglrc = wglCreateContext(rawDC);
if (!rawglrc) {
return;
}
Handle hGLRC = Handle(rawglrc, GLRCDestroyer);
if (!wglMakeCurrent(rawDC, rawglrc)) {
return;
}
gl::initModuleGl();
wglMakeCurrent(0, 0);
hGLRC.reset();
if (!wglChoosePixelFormatARB || !wglCreateContextAttribsARB) {
return;
}
// The only two versions we care about on Windows
// are 4.5 and 4.1
if (GLAD_GL_VERSION_4_5) {
major = 4;
minor = disableGl45() ? 1 : 5;
} else if (GLAD_GL_VERSION_4_1) {
major = 4;
minor = 1;
}
#else
// FIXME do runtime detection of GL version on non-Mac/Windows/Mobile platforms
major = 4;
minor = disableGl45() ? 1 : 5;
#endif
});
return GL_MAKE_VERSION(major, minor);
}
const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() {
@ -105,10 +245,9 @@ const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() {
// Qt Quick may need a depth and stencil buffer. Always make sure these are available.
format.setDepthBufferSize(DEFAULT_GL_DEPTH_BUFFER_BITS);
format.setStencilBufferSize(DEFAULT_GL_STENCIL_BUFFER_BITS);
int major, minor;
::gl::getTargetVersion(major, minor);
format.setMajorVersion(major);
format.setMinorVersion(minor);
auto glversion = ::gl::getTargetVersion();
format.setMajorVersion(GL_GET_MAJOR_VERSION(glversion));
format.setMinorVersion(GL_GET_MINOR_VERSION(glversion));
});
return format;
}

View file

@ -34,6 +34,10 @@ int glVersionToInteger(QString glVersion);
bool isRenderThread();
#define GL_MAKE_VERSION(major, minor) ((major << 8) | minor)
#define GL_GET_MINOR_VERSION(glversion) (glversion & 0x00FF)
#define GL_GET_MAJOR_VERSION(glversion) ((glversion & 0xFF00) >> 8)
namespace gl {
void globalLock();
void globalRelease(bool finish = true);
@ -52,7 +56,11 @@ namespace gl {
bool disableGl45();
void getTargetVersion(int& major, int& minor);
uint16_t getTargetVersion();
uint16_t getAvailableVersion();
uint16_t getRequiredVersion();
} // namespace gl
#define CHECK_GL_ERROR() ::gl::checkGLErrorDebug(__FUNCTION__)

View file

@ -187,38 +187,43 @@ void Midi::MidiSetup() {
MIDIINCAPS incaps;
for (unsigned int i = 0; i < midiInGetNumDevs(); i++) {
midiInGetDevCaps(i, &incaps, sizeof(MIDIINCAPS));
if (MMSYSERR_NOERROR == midiInGetDevCaps(i, &incaps, sizeof(MIDIINCAPS))) {
bool found = false;
for (int j = 0; j < midiInExclude.size(); j++) {
if (midiInExclude[j].toStdString().compare(incaps.szPname) == 0) {
found = true;
break;
bool found = false;
for (int j = 0; j < midiInExclude.size(); j++) {
if (midiInExclude[j].toStdString().compare(incaps.szPname) == 0) {
found = true;
break;
}
}
if (!found) { // EXCLUDE AN INPUT BY NAME
HMIDIIN tmphin;
if (MMSYSERR_NOERROR == midiInOpen(&tmphin, i, (DWORD_PTR)MidiInProc, NULL, CALLBACK_FUNCTION)) {
if (MMSYSERR_NOERROR == midiInStart(tmphin)) {
midihin.push_back(tmphin);
}
}
}
}
if (!found) { // EXCLUDE AN INPUT BY NAME
HMIDIIN tmphin;
midiInOpen(&tmphin, i, (DWORD_PTR)MidiInProc, NULL, CALLBACK_FUNCTION);
midiInStart(tmphin);
midihin.push_back(tmphin);
}
}
MIDIOUTCAPS outcaps;
for (unsigned int i = 0; i < midiOutGetNumDevs(); i++) {
midiOutGetDevCaps(i, &outcaps, sizeof(MIDIINCAPS));
if (MMSYSERR_NOERROR == midiOutGetDevCaps(i, &outcaps, sizeof(MIDIOUTCAPS))) {
bool found = false;
for (int j = 0; j < midiOutExclude.size(); j++) {
if (midiOutExclude[j].toStdString().compare(outcaps.szPname) == 0) {
found = true;
break;
bool found = false;
for (int j = 0; j < midiOutExclude.size(); j++) {
if (midiOutExclude[j].toStdString().compare(outcaps.szPname) == 0) {
found = true;
break;
}
}
if (!found) { // EXCLUDE AN OUTPUT BY NAME
HMIDIOUT tmphout;
if (MMSYSERR_NOERROR == midiOutOpen(&tmphout, i, (DWORD_PTR)MidiOutProc, NULL, CALLBACK_FUNCTION)) {
midihout.push_back(tmphout);
}
}
}
if (!found) { // EXCLUDE AN OUTPUT BY NAME
HMIDIOUT tmphout;
midiOutOpen(&tmphout, i, (DWORD_PTR)MidiOutProc, NULL, CALLBACK_FUNCTION);
midihout.push_back(tmphout);
}
}

View file

@ -61,7 +61,7 @@ void CalculateBlendshapeNormalsTask::run(const baker::BakeContextPointer& contex
outVertex = blendshape.vertices[lookupIndex];
} else {
// Index isn't in the blendshape, so return vertex from mesh
outVertex = mesh.vertices[lookupIndex];
outVertex = baker::safeGet(mesh.vertices, lookupIndex);
}
});
}

View file

@ -32,7 +32,7 @@ void CalculateMeshNormalsTask::run(const baker::BakeContextPointer& context, con
return &normalsOut[normalIndex];
},
[&mesh](int vertexIndex, glm::vec3& outVertex) /* VertexSetter */ {
outVertex = mesh.vertices[vertexIndex];
outVertex = baker::safeGet(mesh.vertices, vertexIndex);
}
);
}

View file

@ -25,6 +25,17 @@ namespace baker {
}
}
template<typename T>
const T& safeGet(const QVector<T>& data, int i) {
static T t;
if (i >= 0 && data.size() > i) {
return data[i];
} else {
return t;
}
}
// Returns a reference to the normal at the specified index, or nullptr if it cannot be accessed
using NormalAccessor = std::function<glm::vec3*(int index)>;

View file

@ -542,5 +542,3 @@ void GeometryResourceWatcher::resourceRefreshed() {
// FIXME: Model is not set up to handle a refresh
// _instance.reset();
}
#include "ModelCache.moc"

View file

@ -482,7 +482,7 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const
} else {
QString iceServerAddress = domainObject[DOMAIN_ICE_SERVER_ADDRESS_KEY].toString();
qCDebug(networking) << "Possible domain change required to connect to domain with ID" << domainID
qCDebug(networking_ice) << "Possible domain change required to connect to domain with ID" << domainID
<< "via ice-server at" << iceServerAddress;
emit possibleDomainChangeRequiredViaICEForID(iceServerAddress, domainID);

View file

@ -64,7 +64,7 @@ DomainHandler::DomainHandler(QObject* parent) :
connect(this, &DomainHandler::redirectToErrorDomainURL, &_apiRefreshTimer, &QTimer::stop);
}
void DomainHandler::disconnect() {
void DomainHandler::disconnect(QString reason) {
// if we're currently connected to a domain, send a disconnect packet on our way out
if (_isConnected) {
sendDisconnectPacket();
@ -81,6 +81,8 @@ void DomainHandler::disconnect() {
_sockAddr.clear();
}
qCDebug(networking_ice) << "Disconnecting from domain server.";
qCDebug(networking_ice) << "REASON:" << reason;
setIsConnected(false);
}
@ -100,9 +102,9 @@ void DomainHandler::clearSettings() {
_settingsObject = QJsonObject();
}
void DomainHandler::softReset() {
void DomainHandler::softReset(QString reason) {
qCDebug(networking) << "Resetting current domain connection information.";
disconnect();
disconnect(reason);
clearSettings();
@ -118,10 +120,10 @@ void DomainHandler::softReset() {
}
}
void DomainHandler::hardReset() {
void DomainHandler::hardReset(QString reason) {
emit resetting();
softReset();
softReset(reason);
_isInErrorState = false;
emit redirectErrorStateChanged(_isInErrorState);
@ -166,7 +168,7 @@ void DomainHandler::setErrorDomainURL(const QUrl& url) {
void DomainHandler::setSockAddr(const HifiSockAddr& sockAddr, const QString& hostname) {
if (_sockAddr != sockAddr) {
// we should reset on a sockAddr change
hardReset();
hardReset("Changing domain sockAddr");
// change the sockAddr
_sockAddr = sockAddr;
}
@ -209,7 +211,7 @@ void DomainHandler::setURLAndID(QUrl domainURL, QUuid domainID) {
// if it's in the error state, reset and try again.
if ((_domainURL != domainURL || _sockAddr.getPort() != domainPort) || _isInErrorState) {
// re-set the domain info so that auth information is reloaded
hardReset();
hardReset("Changing domain URL");
QString previousHost = _domainURL.host();
_domainURL = domainURL;
@ -242,10 +244,24 @@ void DomainHandler::setURLAndID(QUrl domainURL, QUuid domainID) {
void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id) {
auto newIceServer = _iceServerSockAddr.getAddress().toString() != iceServerHostname;
auto newDomainID = id != _pendingDomainID;
// if it's in the error state, reset and try again.
if ((_iceServerSockAddr.getAddress().toString() != iceServerHostname || id != _pendingDomainID) || _isInErrorState) {
if (newIceServer || newDomainID || _isInErrorState) {
QString reason;
if (newIceServer) {
reason += "New ICE server;";
}
if (newDomainID) {
reason += "New domain ID;";
}
if (_isInErrorState) {
reason += "Domain in error state;";
}
// re-set the domain info to connect to new domain
hardReset();
hardReset(reason);
// refresh our ICE client UUID to something new
_iceClientID = QUuid::createUuid();
@ -268,7 +284,7 @@ void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname,
completedIceServerHostnameLookup();
}
qCDebug(networking) << "ICE required to connect to domain via ice server at" << iceServerHostname;
qCDebug(networking_ice) << "ICE required to connect to domain via ice server at" << iceServerHostname;
}
}
@ -322,7 +338,7 @@ void DomainHandler::completedHostnameLookup(const QHostInfo& hostInfo) {
}
void DomainHandler::completedIceServerHostnameLookup() {
qCDebug(networking) << "ICE server socket is at" << _iceServerSockAddr;
qCDebug(networking_ice) << "ICE server socket is at" << _iceServerSockAddr;
DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetICEServerSocket);
@ -409,7 +425,7 @@ void DomainHandler::processSettingsPacketList(QSharedPointer<ReceivedMessage> pa
void DomainHandler::processICEPingReplyPacket(QSharedPointer<ReceivedMessage> message) {
const HifiSockAddr& senderSockAddr = message->getSenderSockAddr();
qCDebug(networking) << "Received reply from domain-server on" << senderSockAddr;
qCDebug(networking_ice) << "Received reply from domain-server on" << senderSockAddr;
if (getIP().isNull()) {
// we're hearing back from this domain-server, no need to refresh API information
@ -417,13 +433,13 @@ void DomainHandler::processICEPingReplyPacket(QSharedPointer<ReceivedMessage> me
// for now we're unsafely assuming this came back from the domain
if (senderSockAddr == _icePeer.getLocalSocket()) {
qCDebug(networking) << "Connecting to domain using local socket";
qCDebug(networking_ice) << "Connecting to domain using local socket";
activateICELocalSocket();
} else if (senderSockAddr == _icePeer.getPublicSocket()) {
qCDebug(networking) << "Conecting to domain using public socket";
qCDebug(networking_ice) << "Conecting to domain using public socket";
activateICEPublicSocket();
} else {
qCDebug(networking) << "Reply does not match either local or public socket for domain. Will not connect.";
qCDebug(networking_ice) << "Reply does not match either local or public socket for domain. Will not connect.";
}
}
}
@ -442,7 +458,7 @@ void DomainHandler::processDTLSRequirementPacket(QSharedPointer<ReceivedMessage>
void DomainHandler::processICEResponsePacket(QSharedPointer<ReceivedMessage> message) {
if (_icePeer.hasSockets()) {
qCDebug(networking) << "Received an ICE peer packet for domain-server but we already have sockets. Not processing.";
qCDebug(networking_ice) << "Received an ICE peer packet for domain-server but we already have sockets. Not processing.";
// bail on processing this packet if our ice peer already has sockets
return;
}
@ -457,10 +473,10 @@ void DomainHandler::processICEResponsePacket(QSharedPointer<ReceivedMessage> mes
DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveDSPeerInformation);
if (_icePeer.getUUID() != _pendingDomainID) {
qCDebug(networking) << "Received a network peer with ID that does not match current domain. Will not attempt connection.";
qCDebug(networking_ice) << "Received a network peer with ID that does not match current domain. Will not attempt connection.";
_icePeer.reset();
} else {
qCDebug(networking) << "Received network peer object for domain -" << _icePeer;
qCDebug(networking_ice) << "Received network peer object for domain -" << _icePeer;
// ask the peer object to start its ping timer
_icePeer.startPingTimer();
@ -546,10 +562,14 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer<Rec
bool DomainHandler::checkInPacketTimeout() {
++_checkInPacketsSinceLastReply;
if (_checkInPacketsSinceLastReply > 1) {
qCDebug(networking_ice) << "Silent domain checkins:" << _checkInPacketsSinceLastReply;
}
if (_checkInPacketsSinceLastReply > MAX_SILENT_DOMAIN_SERVER_CHECK_INS) {
// we haven't heard back from DS in MAX_SILENT_DOMAIN_SERVER_CHECK_INS
// so emit our signal that says that
qCDebug(networking) << "Limit of silent domain checkins reached";
qCDebug(networking_ice) << "Limit of silent domain checkins reached";
emit limitOfSilentDomainCheckInsReached();
return true;
} else {

View file

@ -42,7 +42,7 @@ class DomainHandler : public QObject {
public:
DomainHandler(QObject* parent = 0);
void disconnect();
void disconnect(QString reason);
void clearSettings();
const QUuid& getUUID() const { return _uuid; }
@ -105,7 +105,7 @@ public:
bool isSocketKnown() const { return !_sockAddr.getAddress().isNull(); }
void softReset();
void softReset(QString reason);
int getCheckInPacketsSinceLastReply() const { return _checkInPacketsSinceLastReply; }
bool checkInPacketTimeout();
@ -210,7 +210,7 @@ signals:
private:
bool reasonSuggestsLogin(ConnectionRefusedReason reasonCode);
void sendDisconnectPacket();
void hardReset();
void hardReset(QString reason);
bool isHardRefusal(int reasonCode);

View file

@ -889,6 +889,7 @@ void LimitedNodeList::removeSilentNodes() {
});
foreach(const SharedNodePointer& killedNode, killedNodes) {
qCDebug(networking_ice) << "Removing silent node" << killedNode;
handleNodeKill(killedNode);
}
}
@ -1269,7 +1270,7 @@ void LimitedNodeList::sendPacketToIceServer(PacketType packetType, const HifiSoc
iceDataStream << peerID;
qCDebug(networking) << "Sending packet to ICE server to request connection info for peer with ID"
qCDebug(networking_ice) << "Sending packet to ICE server to request connection info for peer with ID"
<< uuidStringWithoutCurlyBraces(peerID);
}

View file

@ -12,6 +12,7 @@
#include "NetworkLogging.h"
Q_LOGGING_CATEGORY(networking, "hifi.networking")
Q_LOGGING_CATEGORY(networking_ice, "hifi.networking.ice")
Q_LOGGING_CATEGORY(resourceLog, "hifi.networking.resource")
Q_LOGGING_CATEGORY(asset_client, "hifi.networking.asset_client")
Q_LOGGING_CATEGORY(messages_client, "hifi.networking.messages_client")

View file

@ -16,6 +16,7 @@
Q_DECLARE_LOGGING_CATEGORY(resourceLog)
Q_DECLARE_LOGGING_CATEGORY(networking)
Q_DECLARE_LOGGING_CATEGORY(networking_ice)
Q_DECLARE_LOGGING_CATEGORY(asset_client)
Q_DECLARE_LOGGING_CATEGORY(messages_client)

View file

@ -23,7 +23,7 @@
const QString ICE_SERVER_HOSTNAME = "localhost";
const quint16 ICE_SERVER_DEFAULT_PORT = 7337;
const int ICE_HEARBEAT_INTERVAL_MSECS = 2 * 1000;
const int ICE_HEARBEAT_INTERVAL_MSECS = 1 * 1000;
const int MAX_ICE_CONNECTION_ATTEMPTS = 5;
const int UDP_PUNCH_PING_INTERVAL_MS = 250;

View file

@ -243,9 +243,11 @@ void NodeList::processICEPingPacket(QSharedPointer<ReceivedMessage> message) {
sendPacket(std::move(replyPacket), message->getSenderSockAddr());
}
void NodeList::reset(bool skipDomainHandlerReset) {
void NodeList::reset(QString reason, bool skipDomainHandlerReset) {
if (thread() != QThread::currentThread()) {
QMetaObject::invokeMethod(this, "reset", Q_ARG(bool, skipDomainHandlerReset));
QMetaObject::invokeMethod(this, "reset",
Q_ARG(QString, reason),
Q_ARG(bool, skipDomainHandlerReset));
return;
}
@ -267,7 +269,7 @@ void NodeList::reset(bool skipDomainHandlerReset) {
if (!skipDomainHandlerReset) {
// clear the domain connection information, unless they're the ones that asked us to reset
_domainHandler.softReset();
_domainHandler.softReset(reason);
}
// refresh the owner UUID to the NULL UUID
@ -297,12 +299,12 @@ void NodeList::sendDomainServerCheckIn() {
// may be called by multiple threads.
if (!_sendDomainServerCheckInEnabled) {
qCDebug(networking) << "Refusing to send a domain-server check in while it is disabled.";
qCDebug(networking_ice) << "Refusing to send a domain-server check in while it is disabled.";
return;
}
if (_isShuttingDown) {
qCDebug(networking) << "Refusing to send a domain-server check in while shutting down.";
qCDebug(networking_ice) << "Refusing to send a domain-server check in while shutting down.";
return;
}
@ -311,9 +313,9 @@ void NodeList::sendDomainServerCheckIn() {
if (publicSockAddr.isNull()) {
// we don't know our public socket and we need to send it to the domain server
qCDebug(networking) << "Waiting for inital public socket from STUN. Will not send domain-server check in.";
qCDebug(networking_ice) << "Waiting for inital public socket from STUN. Will not send domain-server check in.";
} else if (domainHandlerIp.isNull() && _domainHandler.requiresICE()) {
qCDebug(networking) << "Waiting for ICE discovered domain-server socket. Will not send domain-server check in.";
qCDebug(networking_ice) << "Waiting for ICE discovered domain-server socket. Will not send domain-server check in.";
handleICEConnectionToDomainServer();
// let the domain handler know we are due to send a checkin packet
} else if (!domainHandlerIp.isNull() && !_domainHandler.checkInPacketTimeout()) {
@ -324,7 +326,7 @@ void NodeList::sendDomainServerCheckIn() {
if (!domainIsConnected) {
auto hostname = _domainHandler.getHostname();
qCDebug(networking) << "Sending connect request to domain-server at" << hostname;
qCDebug(networking_ice) << "Sending connect request to domain-server at" << hostname;
// is this our localhost domain-server?
// if so we need to make sure we have an up-to-date local port in case it restarted
@ -334,7 +336,7 @@ void NodeList::sendDomainServerCheckIn() {
quint16 domainPort = DEFAULT_DOMAIN_SERVER_PORT;
getLocalServerPortFromSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY, domainPort);
qCDebug(networking) << "Local domain-server port read from shared memory (or default) is" << domainPort;
qCDebug(networking_ice) << "Local domain-server port read from shared memory (or default) is" << domainPort;
_domainHandler.setPort(domainPort);
}
}
@ -346,7 +348,7 @@ void NodeList::sendDomainServerCheckIn() {
bool requiresUsernameSignature = !domainIsConnected && !connectionToken.isNull();
if (requiresUsernameSignature && !accountManager->getAccountInfo().hasPrivateKey()) {
qWarning() << "A keypair is required to present a username signature to the domain-server"
qCWarning(networking_ice) << "A keypair is required to present a username signature to the domain-server"
<< "but no keypair is present. Waiting for keypair generation to complete.";
accountManager->generateNewUserKeypair();
@ -574,12 +576,12 @@ void NodeList::pingPunchForDomainServer() {
const int NUM_DOMAIN_SERVER_PINGS_BEFORE_RESET = 2000 / UDP_PUNCH_PING_INTERVAL_MS;
if (_domainHandler.getICEPeer().getConnectionAttempts() == 0) {
qCDebug(networking) << "Sending ping packets to establish connectivity with domain-server with ID"
qCDebug(networking_ice) << "Sending ping packets to establish connectivity with domain-server with ID"
<< uuidStringWithoutCurlyBraces(_domainHandler.getPendingDomainID());
} else {
if (_domainHandler.getICEPeer().getConnectionAttempts() % NUM_DOMAIN_SERVER_PINGS_BEFORE_RESET == 0) {
// if we have then nullify the domain handler's network peer and send a fresh ICE heartbeat
qCDebug(networking) << "No ping replies received from domain-server with ID"
qCDebug(networking_ice) << "No ping replies received from domain-server with ID"
<< uuidStringWithoutCurlyBraces(_domainHandler.getICEClientID()) << "-" << "re-sending ICE query.";
_domainHandler.getICEPeer().softReset();
@ -657,14 +659,14 @@ void NodeList::processDomainServerList(QSharedPointer<ReceivedMessage> message)
if (_domainHandler.isConnected() &&
((currentLocalID != Node::NULL_LOCAL_ID && newLocalID != currentLocalID) ||
(!currentSessionID.isNull() && newUUID != currentSessionID))) {
qCDebug(networking) << "Local ID or Session ID changed while connected to domain - forcing NodeList reset";
// reset the nodelist, but don't do a domain handler reset since we're about to process a good domain list
reset(true);
reset("Local ID or Session ID changed while connected to domain - forcing NodeList reset", true);
// tell the domain handler that we're no longer connected so that below
// it can re-perform actions as if we just connected
_domainHandler.setIsConnected(false);
// Clear any reliable connections using old ID.
_nodeSocket.clearConnections();
}
setSessionLocalID(newLocalID);

View file

@ -103,8 +103,8 @@ public:
virtual HifiSockAddr getDomainSockAddr() const override { return _domainHandler.getSockAddr(); }
public slots:
void reset(bool skipDomainHandlerReset = false);
void resetFromDomainHandler() { reset(true); }
void reset(QString reason, bool skipDomainHandlerReset = false);
void resetFromDomainHandler() { reset("Reset from Domain Handler", true); }
void sendDomainServerCheckIn();
void handleDSPathQuery(const QString& newPath);

View file

@ -58,7 +58,7 @@ void ThreadedAssignment::setFinished(bool isFinished) {
packetReceiver.setShouldDropPackets(true);
// send a disconnect packet to the domain
nodeList->getDomainHandler().disconnect();
nodeList->getDomainHandler().disconnect("Finished");
// stop our owned timers
_domainServerTimer.stop();

View file

@ -269,6 +269,7 @@ enum class EntityVersion : PacketVersion {
CertificateTypeProperty,
DisableWebMedia,
ParticleShapeType,
ParticleShapeTypeDeadlockFix,
PrivateUserData,
// Add new versions above here

View file

@ -145,7 +145,10 @@ public:
virtual bool rootElementHasData() const { return false; }
virtual void releaseSceneEncodeData(OctreeElementExtraEncodeData* extraEncodeData) const { }
virtual void update() { } // nothing to do by default
// Why preUpdate() and update()?
// Because EntityTree needs them.
virtual void preUpdate() { }
virtual void update(bool simulate = true) { }
OctreeElementPointer getRoot() { return _rootElement; }

View file

@ -242,6 +242,7 @@ bool OctreePersistThread::backupCurrentFile() {
}
void OctreePersistThread::process() {
_tree->preUpdate();
_tree->update();
auto now = std::chrono::steady_clock::now();

View file

@ -25,23 +25,6 @@
#include "PhysicsHelpers.h"
#include "PhysicsLogging.h"
#ifdef WANT_DEBUG_ENTITY_TREE_LOCKS
#include "EntityTree.h"
bool EntityMotionState::entityTreeIsLocked() const {
EntityTreeElementPointer element = _entity->getElement();
EntityTreePointer tree = element ? element->getTree() : nullptr;
if (!tree) {
return true;
}
return true;
}
#else
bool entityTreeIsLocked() {
return true;
}
#endif
const uint8_t LOOPS_FOR_SIMULATION_ORPHAN = 50;
const quint64 USECS_BETWEEN_OWNERSHIP_BIDS = USECS_PER_SECOND / 5;
@ -74,7 +57,6 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer
_type = MOTIONSTATE_TYPE_ENTITY;
assert(_entity);
assert(entityTreeIsLocked());
setMass(_entity->computeMass());
// we need the side-effects of EntityMotionState::setShape() so we call it explicitly here
// rather than pass the legit shape pointer to the ObjectMotionState ctor above.
@ -143,7 +125,6 @@ void EntityMotionState::handleDeactivation() {
// virtual
void EntityMotionState::handleEasyChanges(uint32_t& flags) {
assert(entityTreeIsLocked());
updateServerPhysicsVariables();
ObjectMotionState::handleEasyChanges(flags);
@ -191,17 +172,10 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) {
}
// virtual
bool EntityMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) {
updateServerPhysicsVariables();
return ObjectMotionState::handleHardAndEasyChanges(flags, engine);
}
PhysicsMotionType EntityMotionState::computePhysicsMotionType() const {
if (!_entity) {
return MOTION_TYPE_STATIC;
}
assert(entityTreeIsLocked());
if (_entity->getLocked()) {
if (_entity->isMoving()) {
@ -226,7 +200,6 @@ PhysicsMotionType EntityMotionState::computePhysicsMotionType() const {
}
bool EntityMotionState::isMoving() const {
assert(entityTreeIsLocked());
return _entity && _entity->isMovingRelativeToParent();
}
@ -240,7 +213,6 @@ void EntityMotionState::getWorldTransform(btTransform& worldTrans) const {
if (!_entity) {
return;
}
assert(entityTreeIsLocked());
if (_motionType == MOTION_TYPE_KINEMATIC) {
BT_PROFILE("kinematicIntegration");
uint32_t thisStep = ObjectMotionState::getWorldSimulationStep();
@ -271,7 +243,6 @@ void EntityMotionState::getWorldTransform(btTransform& worldTrans) const {
// This callback is invoked by the physics simulation at the end of each simulation step...
// iff the corresponding RigidBody is DYNAMIC and ACTIVE.
void EntityMotionState::setWorldTransform(const btTransform& worldTrans) {
assert(entityTreeIsLocked());
measureBodyAcceleration();
// If transform or velocities are flagged as dirty it means a network or scripted change
@ -309,19 +280,6 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) {
}
// virtual and protected
bool EntityMotionState::isReadyToComputeShape() const {
return _entity->isReadyToComputeShape();
}
// virtual and protected
const btCollisionShape* EntityMotionState::computeNewShape() {
ShapeInfo shapeInfo;
assert(entityTreeIsLocked());
_entity->computeShapeInfo(shapeInfo);
return getShapeManager()->getShape(shapeInfo);
}
const uint8_t MAX_NUM_INACTIVE_UPDATES = 20;
bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
@ -439,7 +397,6 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) {
DETAILED_PROFILE_RANGE(simulation_physics, "ShouldSend");
// NOTE: we expect _entity and _body to be valid in this context, since shouldSendUpdate() is only called
// after doesNotNeedToSendUpdate() returns false and that call should return 'true' if _entity or _body are NULL.
assert(entityTreeIsLocked());
// this case is prevented by setting _ownershipState to UNOWNABLE in EntityMotionState::ctor
assert(!(_entity->isAvatarEntity() && _entity->getOwningAvatarID() != Physics::getSessionUUID()));
@ -505,7 +462,6 @@ void EntityMotionState::updateSendVelocities() {
void EntityMotionState::sendBid(OctreeEditPacketSender* packetSender, uint32_t step) {
DETAILED_PROFILE_RANGE(simulation_physics, "Bid");
assert(entityTreeIsLocked());
updateSendVelocities();
@ -546,7 +502,6 @@ void EntityMotionState::sendBid(OctreeEditPacketSender* packetSender, uint32_t s
void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step) {
DETAILED_PROFILE_RANGE(simulation_physics, "Send");
assert(entityTreeIsLocked());
assert(isLocallyOwned());
updateSendVelocities();
@ -645,8 +600,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
_bumpedPriority = 0;
}
uint32_t EntityMotionState::getIncomingDirtyFlags() {
assert(entityTreeIsLocked());
uint32_t EntityMotionState::getIncomingDirtyFlags() const {
uint32_t dirtyFlags = 0;
if (_body && _entity) {
dirtyFlags = _entity->getDirtyFlags();
@ -676,10 +630,9 @@ uint32_t EntityMotionState::getIncomingDirtyFlags() {
return dirtyFlags;
}
void EntityMotionState::clearIncomingDirtyFlags() {
assert(entityTreeIsLocked());
void EntityMotionState::clearIncomingDirtyFlags(uint32_t mask) {
if (_body && _entity) {
_entity->clearDirtyFlags(DIRTY_PHYSICS_FLAGS);
_entity->clearDirtyFlags(mask);
}
}
@ -694,7 +647,6 @@ void EntityMotionState::slaveBidPriority() {
// virtual
QUuid EntityMotionState::getSimulatorID() const {
assert(entityTreeIsLocked());
return _entity->getSimulatorID();
}
@ -762,6 +714,10 @@ glm::vec3 EntityMotionState::getObjectLinearVelocityChange() const {
return _measuredAcceleration * _measuredDeltaTime;
}
bool EntityMotionState::shouldBeInPhysicsSimulation() const {
return _region < workload::Region::R3 && _entity->shouldBePhysical();
}
// virtual
void EntityMotionState::setMotionType(PhysicsMotionType motionType) {
ObjectMotionState::setMotionType(motionType);
@ -770,7 +726,6 @@ void EntityMotionState::setMotionType(PhysicsMotionType motionType) {
// virtual
QString EntityMotionState::getName() const {
assert(entityTreeIsLocked());
return _entity->getName();
}
@ -788,6 +743,15 @@ bool EntityMotionState::shouldSendBid() const {
&& !_entity->getLocked();
}
void EntityMotionState::setRigidBody(btRigidBody* body) {
ObjectMotionState::setRigidBody(body);
if (_body) {
_entity->markSpecialFlags(Simulation::SPECIAL_FLAG_IN_PHYSICS_SIMULATION);
} else {
_entity->clearSpecialFlags(Simulation::SPECIAL_FLAG_IN_PHYSICS_SIMULATION);
}
}
uint8_t EntityMotionState::computeFinalBidPriority() const {
return (_region == workload::Region::R1) ?
glm::max(glm::max(VOLUNTEER_SIMULATION_PRIORITY, _bumpedPriority), _entity->getScriptSimulationPriority()) : 0;

View file

@ -12,6 +12,7 @@
#ifndef hifi_EntityMotionState_h
#define hifi_EntityMotionState_h
#include <EntityItem.h>
#include <EntityTypes.h>
#include <AACube.h>
#include <workload/Region.h>
@ -38,7 +39,6 @@ public:
void handleDeactivation();
virtual void handleEasyChanges(uint32_t& flags) override;
virtual bool handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) override;
/// \return PhysicsMotionType based on params set in EntityItem
virtual PhysicsMotionType computePhysicsMotionType() const override;
@ -55,8 +55,8 @@ public:
void sendBid(OctreeEditPacketSender* packetSender, uint32_t step);
void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step);
virtual uint32_t getIncomingDirtyFlags() override;
virtual void clearIncomingDirtyFlags() override;
virtual uint32_t getIncomingDirtyFlags() const override;
virtual void clearIncomingDirtyFlags(uint32_t mask = DIRTY_PHYSICS_FLAGS) override;
virtual float getObjectRestitution() const override { return _entity->getRestitution(); }
virtual float getObjectFriction() const override { return _entity->getFriction(); }
@ -84,6 +84,7 @@ public:
void measureBodyAcceleration();
virtual QString getName() const override;
ShapeType getShapeType() const override { return _entity->getShapeType(); }
virtual void computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const override;
@ -99,6 +100,8 @@ public:
void saveKinematicState(btScalar timeStep) override;
protected:
void setRigidBody(btRigidBody* body) override;
uint8_t computeFinalBidPriority() const;
void updateSendVelocities();
uint64_t getNextBidExpiry() const { return _nextBidExpiry; }
@ -112,12 +115,8 @@ protected:
void clearObjectVelocities() const;
#ifdef WANT_DEBUG_ENTITY_TREE_LOCKS
bool entityTreeIsLocked() const;
#endif
bool isReadyToComputeShape() const override;
const btCollisionShape* computeNewShape() override;
bool isInPhysicsSimulation() const { return _body != nullptr; }
bool shouldBeInPhysicsSimulation() const;
void setMotionType(PhysicsMotionType motionType) override;
// EntityMotionState keeps a SharedPointer to its EntityItem which is only set in the CTOR

View file

@ -11,7 +11,8 @@
#include "ObjectActionTractor.h"
#include "QVariantGLM.h"
#include <EntityItem.h>
#include <QVariantGLM.h>
#include "PhysicsLogging.h"

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