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

This commit is contained in:
Zach Fox 2018-04-06 10:58:32 -07:00
commit af5a68b282
109 changed files with 2633 additions and 1102 deletions

View file

@ -33,7 +33,7 @@ uint64_t AvatarMixerClientData::getLastOtherAvatarEncodeTime(QUuid otherAvatar)
return 0;
}
void AvatarMixerClientData::setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, const uint64_t& time) {
void AvatarMixerClientData::setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, uint64_t time) {
std::unordered_map<QUuid, uint64_t>::iterator itr = _lastOtherAvatarEncodeTime.find(otherAvatar);
if (itr != _lastOtherAvatarEncodeTime.end()) {
itr->second = time;

View file

@ -113,7 +113,7 @@ public:
ViewFrustum getViewFrustum() const { return _currentViewFrustum; }
uint64_t getLastOtherAvatarEncodeTime(QUuid otherAvatar) const;
void setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, const uint64_t& time);
void setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, uint64_t time);
QVector<JointData>& getLastOtherAvatarSentJoints(QUuid otherAvatar) {
auto& lastOtherAvatarSentJoints = _lastOtherAvatarSentJoints[otherAvatar];

View file

@ -470,7 +470,6 @@ void EntityServer::startDynamicDomainVerification() {
// Delete the entity if it doesn't pass static certificate verification
tree->deleteEntity(i.value(), true);
} else {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest networkRequest;
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
@ -490,9 +489,13 @@ void EntityServer::startDynamicDomainVerification() {
if (networkReply->error() == QNetworkReply::NoError) {
if (jsonObject["domain_id"].toString() != thisDomainID) {
qCDebug(entities) << "Entity's cert's domain ID" << jsonObject["domain_id"].toString()
<< "doesn't match the current Domain ID" << thisDomainID << "; deleting entity" << i.value();
tree->deleteEntity(i.value(), true);
if (entity->getAge() > (_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS/MSECS_PER_SECOND)) {
qCDebug(entities) << "Entity's cert's domain ID" << jsonObject["domain_id"].toString()
<< "doesn't match the current Domain ID" << thisDomainID << "; deleting entity" << i.value();
tree->deleteEntity(i.value(), true);
} else {
qCDebug(entities) << "Entity failed dynamic domain verification, but was created too recently to necessitate deletion:" << i.value();
}
} else {
qCDebug(entities) << "Entity passed dynamic domain verification:" << i.value();
}

View file

@ -105,8 +105,6 @@ EntityScriptServer::~EntityScriptServer() {
static const QString ENTITY_SCRIPT_SERVER_LOGGING_NAME = "entity-script-server";
void EntityScriptServer::handleReloadEntityServerScriptPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
// These are temporary checks until we can ensure that nodes eventually disconnect if the Domain Server stops telling them
// about each other.
if (senderNode->getCanRez() || senderNode->getCanRezTmp() || senderNode->getCanRezCertified() || senderNode->getCanRezTmpCertified()) {
auto entityID = QUuid::fromRfc4122(message->read(NUM_BYTES_RFC4122_UUID));
@ -119,8 +117,6 @@ void EntityScriptServer::handleReloadEntityServerScriptPacket(QSharedPointer<Rec
}
void EntityScriptServer::handleEntityScriptGetStatusPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
// These are temporary checks until we can ensure that nodes eventually disconnect if the Domain Server stops telling them
// about each other.
if (senderNode->getCanRez() || senderNode->getCanRezTmp() || senderNode->getCanRezCertified() || senderNode->getCanRezTmpCertified()) {
MessageID messageID;
message->readPrimitive(&messageID);
@ -190,15 +186,14 @@ void EntityScriptServer::updateEntityPPS() {
}
void EntityScriptServer::handleEntityServerScriptLogPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
// These are temporary checks until we can ensure that nodes eventually disconnect if the Domain Server stops telling them
// about each other.
bool canRezAny = senderNode->getCanRez() || senderNode->getCanRezTmp() || senderNode->getCanRezCertified() || senderNode->getCanRezTmpCertified();
bool enable = false;
message->readPrimitive(&enable);
auto senderUUID = senderNode->getUUID();
auto it = _logListeners.find(senderUUID);
if (enable && senderNode->getCanRez()) {
if (enable && canRezAny) {
if (it == std::end(_logListeners)) {
_logListeners.insert(senderUUID);
qCInfo(entity_script_server) << "Node" << senderUUID << "subscribed to log stream";

View file

@ -4,8 +4,8 @@ set(EXTERNAL_NAME serverless-content)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC66.zip
URL_MD5 91edfde96e06efc847ca327ab97f4c74
URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC66-v2.zip
URL_MD5 d76bdb3e2bf7ae5d20115bd97b0c44a8
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""

View file

@ -1042,41 +1042,7 @@ void DomainServer::processListRequestPacket(QSharedPointer<ReceivedMessage> mess
bool DomainServer::isInInterestSet(const SharedNodePointer& nodeA, const SharedNodePointer& nodeB) {
auto nodeAData = static_cast<DomainServerNodeData*>(nodeA->getLinkedData());
auto nodeBData = static_cast<DomainServerNodeData*>(nodeB->getLinkedData());
// if we have no linked data for node A then B can't possibly be in the interest set
if (!nodeAData) {
return false;
}
// first check if the general interest set A contains the type for B
if (nodeAData->getNodeInterestSet().contains(nodeB->getType())) {
// given that there is a match in the general interest set, do any special checks
// (1/19/17) Agents only need to connect to Entity Script Servers to perform administrative tasks
// related to entity server scripts. Only agents with rez permissions should be doing that, so
// if the agent does not have those permissions, we do not want them and the server to incur the
// overhead of connecting to one another. Additionally we exclude agents that do not care about the
// Entity Script Server and won't attempt to connect to it.
bool isAgentWithoutRights = nodeA->getType() == NodeType::Agent
&& nodeB->getType() == NodeType::EntityScriptServer
&& !nodeA->getCanRez() && !nodeA->getCanRezTmp()
&& !nodeA->getCanRezCertified() && !nodeA->getCanRezTmpCertified();
if (isAgentWithoutRights) {
return false;
}
bool isScriptServerForIneffectiveAgent =
(nodeA->getType() == NodeType::EntityScriptServer && nodeB->getType() == NodeType::Agent)
&& ((nodeBData && !nodeBData->getNodeInterestSet().contains(NodeType::EntityScriptServer))
|| (!nodeB->getCanRez() && !nodeB->getCanRezTmp() && !nodeB->getCanRezCertified() && !nodeB->getCanRezTmpCertified()));
return !isScriptServerForIneffectiveAgent;
} else {
return false;
}
return nodeAData && nodeAData->getNodeInterestSet().contains(nodeB->getType());
}
unsigned int DomainServer::countConnectedUsers() {
@ -3476,4 +3442,4 @@ void DomainServer::handleOctreeFileReplacementRequest(QSharedPointer<ReceivedMes
if (node->getCanReplaceContent()) {
handleOctreeFileReplacement(message->readAll());
}
}
}

View file

@ -114,7 +114,7 @@
{ "from": "Keyboard.W", "when": "!Keyboard.Control", "to": "Actions.LONGITUDINAL_FORWARD" },
{ "from": "Keyboard.S", "when": "!Keyboard.Control", "to": "Actions.LONGITUDINAL_BACKWARD" },
{ "from": "Keyboard.Shift", "when": ["!Keyboard.Left", "!Keyboard.Right"], "to": "Actions.SPRINT" },
{ "from": "Keyboard.Control", "to": "Actions.VERTICAL_DOWN" },
{ "from": "Keyboard.C", "to": "Actions.VERTICAL_DOWN" },
{ "from": "Keyboard.Left", "when": "Keyboard.Shift", "to": "Actions.LATERAL_LEFT" },
{ "from": "Keyboard.Right", "when": "Keyboard.Shift", "to": "Actions.LATERAL_RIGHT" },
{ "from": "Keyboard.Up", "when": "Application.CameraFirstPerson", "to": "Actions.LONGITUDINAL_FORWARD" },

View file

@ -163,10 +163,18 @@ TextField {
text: textField.label
colorScheme: textField.colorScheme
anchors.left: parent.left
anchors.right: parent.right
Binding on anchors.right {
when: parent.right
value: parent.right
}
Binding on wrapMode {
when: parent.right
value: Text.WordWrap
}
anchors.bottom: parent.top
anchors.bottomMargin: 3
wrapMode: Text.WordWrap
visible: label != ""
}
}

View file

@ -122,9 +122,21 @@ Item {
newViewRequestedCallback(request)
}
// Prior to 5.10, the WebEngineView loading property is true during initial page loading and then stays false
// as in-page javascript adds more html content. However, in 5.10 there is a bug such that adding html turns
// loading true, and never turns it false again. safeLoading provides a workaround, but it should be removed
// when QT fixes this.
property bool safeLoading: false
property bool loadingLatched: false
property var loadingRequest: null
onLoadingChanged: {
flick.onLoadingChanged(loadRequest)
loadingChangedCallback(loadRequest)
webViewCore.loadingRequest = loadRequest;
webViewCore.safeLoading = webViewCore.loading && !loadingLatched;
webViewCore.loadingLatched |= webViewCore.loading;
}
onSafeLoadingChanged: {
flick.onLoadingChanged(webViewCore.loadingRequest)
loadingChangedCallback(webViewCore.loadingRequest)
}
}
@ -133,7 +145,7 @@ Item {
x: flick.width/2 - width/2
y: flick.height/2 - height/2
source: "../../icons/loader-snake-64-w.gif"
visible: webViewCore.loading && /^(http.*|)$/i.test(webViewCore.url.toString())
visible: webViewCore.safeLoading && /^(http.*|)$/i.test(webViewCore.url.toString())
playing: visible
z: 10000
}

View file

@ -39,6 +39,7 @@ Windows.ScrollingWindow {
property var assetMappingsModel: Assets.mappingModel;
property var currentDirectory;
property var selectedItemCount: treeView.selection.selectedIndexes.length;
property int updatesCount: 0; // this is used for notifying model-dependent bindings about model updates
Settings {
category: "Overlay.AssetServer"
@ -51,6 +52,9 @@ Windows.ScrollingWindow {
ApplicationInterface.uploadRequest.connect(uploadClicked);
assetMappingsModel.errorGettingMappings.connect(handleGetMappingsError);
assetMappingsModel.autoRefreshEnabled = true;
assetMappingsModel.updated.connect(function() {
++updatesCount;
});
reload();
}
@ -852,12 +856,17 @@ Windows.ScrollingWindow {
checked = Qt.binding(isChecked);
}
function getStatus() {
// kind of hack for ensuring getStatus() will be re-evaluated on updatesCount changes
return updatesCount, assetProxyModel.data(treeView.selection.currentIndex, 0x105);
}
function isEnabled() {
if (!treeView.selection.hasSelection) {
return false;
}
var status = assetProxyModel.data(treeView.selection.currentIndex, 0x105);
var status = getStatus();
if (status === "--") {
return false;
}
@ -882,9 +891,9 @@ Windows.ScrollingWindow {
return false;
}
var status = assetProxyModel.data(treeView.selection.currentIndex, 0x105);
return isEnabled() && status !== "Not Baked";
}
var status = getStatus();
return isEnabled() && status !== "Not Baked";
}
}
Item {

View file

@ -96,6 +96,7 @@ Rectangle {
root.activeView = "checkoutFailure";
UserActivityLogger.commercePurchaseFailure(root.itemId, root.itemAuthor, root.itemPrice, !root.alreadyOwned, result.message);
} else {
root.certificateId = result.data.certificate_id;
root.itemHref = result.data.download_url;
if (result.data.categories.indexOf("Wearables") > -1) {
root.itemType = "wearable";
@ -188,7 +189,7 @@ Rectangle {
onItemHrefChanged: {
if (root.itemHref.indexOf(".fst") > -1) {
root.itemType = "avatar";
} else if (root.itemHref.indexOf('.json.gz') > -1) {
} else if (root.itemHref.indexOf('.json.gz') > -1 || root.itemHref.indexOf('.content.zip') > -1) {
root.itemType = "contentSet";
} else if (root.itemHref.indexOf('.app.json') > -1) {
root.itemType = "app";
@ -772,7 +773,7 @@ Rectangle {
lightboxPopup.button1text = "CANCEL";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.button2text = "CONFIRM";
lightboxPopup.button2method = "Commerce.replaceContentSet('" + root.itemHref + "');" +
lightboxPopup.button2method = "Commerce.replaceContentSet('" + root.itemHref + "', '" + root.certificateId + "');" +
"root.visible = false;rezzedNotifContainer.visible = true; rezzedNotifContainerTimer.start();" +
"UserActivityLogger.commerceEntityRezzed('" + root.itemId + "', 'checkout', '" + root.itemType + "');";
lightboxPopup.visible = true;

View file

@ -564,7 +564,7 @@ Item {
onClicked: {
Tablet.playSound(TabletEnums.ButtonClick);
if (root.itemType === "contentSet") {
sendToPurchases({method: 'showReplaceContentLightbox', itemHref: root.itemHref});
sendToPurchases({method: 'showReplaceContentLightbox', itemHref: root.itemHref, certID: root.certificateId});
} else if (root.itemType === "avatar") {
sendToPurchases({method: 'showChangeAvatarLightbox', itemName: root.itemName, itemHref: root.itemHref});
} else if (root.itemType === "app") {

View file

@ -486,7 +486,7 @@ Rectangle {
lightboxPopup.button1text = "CANCEL";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.button2text = "CONFIRM";
lightboxPopup.button2method = "Commerce.replaceContentSet('" + msg.itemHref + "'); root.visible = false;";
lightboxPopup.button2method = "Commerce.replaceContentSet('" + msg.itemHref + "', '" + msg.certID + "'); root.visible = false;";
lightboxPopup.visible = true;
} else if (msg.method === "showChangeAvatarLightbox") {
lightboxPopup.titleText = "Change Avatar";
@ -792,7 +792,7 @@ Rectangle {
currentItemType = "avatar";
} else if (currentCategories.indexOf("Wearables") > -1) {
currentItemType = "wearable";
} else if (currentRootFileUrl.endsWith('.json.gz')) {
} else if (currentRootFileUrl.endsWith('.json.gz') || currentRootFileUrl.endsWith('.content.zip')) {
currentItemType = "contentSet";
} else if (currentRootFileUrl.endsWith('.app.json')) {
currentItemType = "app";

View file

@ -24,6 +24,18 @@ Item {
HifiConstants { id: hifi; }
id: root;
// This will cause a bug -- if you bring up passphrase selection in HUD mode while
// in HMD while having HMD preview enabled, then move, then finish passphrase selection,
// HMD preview will stay off.
// TODO: Fix this unlikely bug
onVisibleChanged: {
if (visible) {
sendSignalToWallet({method: 'disableHmdPreview'});
} else {
sendSignalToWallet({method: 'maybeEnableHmdPreview'});
}
}
// Username Text
RalewayRegular {

View file

@ -68,10 +68,6 @@ Item {
propagateComposedEvents: false;
hoverEnabled: true;
}
Component.onDestruction: {
sendSignalToParent({method: 'maybeEnableHmdPreview'});
}
// This will cause a bug -- if you bring up passphrase selection in HUD mode while
// in HMD while having HMD preview enabled, then move, then finish passphrase selection,

View file

@ -61,9 +61,6 @@ Item {
if (root.shouldImmediatelyFocus) {
focusFirstTextField();
}
sendMessageToLightbox({method: 'disableHmdPreview'});
} else {
sendMessageToLightbox({method: 'maybeEnableHmdPreview'});
}
}

View file

@ -44,6 +44,17 @@ Item {
}
}
// This will cause a bug -- if you bring up security image selection in HUD mode while
// in HMD while having HMD preview enabled, then move, then finish passphrase selection,
// HMD preview will stay off.
// TODO: Fix this unlikely bug
onVisibleChanged: {
if (visible) {
sendSignalToWallet({method: 'disableHmdPreview'});
} else {
sendSignalToWallet({method: 'maybeEnableHmdPreview'});
}
}
// Security Image
Item {

View file

@ -25,18 +25,6 @@ Item {
id: root;
property alias currentIndex: securityImageGrid.currentIndex;
// This will cause a bug -- if you bring up security image selection in HUD mode while
// in HMD while having HMD preview enabled, then move, then finish passphrase selection,
// HMD preview will stay off.
// TODO: Fix this unlikely bug
onVisibleChanged: {
if (visible) {
sendSignalToWallet({method: 'disableHmdPreview'});
} else {
sendSignalToWallet({method: 'maybeEnableHmdPreview'});
}
}
SecurityImageModel {
id: gridModel;

View file

@ -237,7 +237,7 @@ Rectangle {
} else {
sendToScript(msg);
}
} else if (msg.method === 'maybeEnableHmdPreview') {
} else {
sendToScript(msg);
}
}

View file

@ -76,6 +76,12 @@ Item {
var currentStepNumber = root.activeView.substring(5);
UserActivityLogger.commerceWalletSetupProgress(timestamp, root.setupAttemptID,
Math.round((timestamp - root.startingTimestamp)/1000), currentStepNumber, root.setupStepNames[currentStepNumber - 1]);
if (root.activeView === "step_2" || root.activeView === "step_3") {
sendSignalToWallet({method: 'disableHmdPreview'});
} else {
sendSignalToWallet({method: 'maybeEnableHmdPreview'});
}
}
//
@ -441,7 +447,7 @@ Item {
}
Item {
id: choosePassphraseContainer;
visible: root.hasShownSecurityImageTip && root.activeView === "step_3";
visible: root.activeView === "step_3";
// Anchors
anchors.top: titleBarContainer.bottom;
anchors.topMargin: 30;
@ -451,10 +457,7 @@ Item {
onVisibleChanged: {
if (visible) {
sendSignalToWallet({method: 'disableHmdPreview'});
Commerce.getWalletAuthenticatedStatus();
} else {
sendSignalToWallet({method: 'maybeEnableHmdPreview'});
}
}

View file

@ -40,6 +40,7 @@ Rectangle {
property var assetMappingsModel: Assets.mappingModel;
property var currentDirectory;
property var selectedItemCount: treeView.selection.selectedIndexes.length;
property int updatesCount: 0; // this is used for notifying model-dependent bindings about model updates
Settings {
category: "Overlay.AssetServer"
@ -51,6 +52,9 @@ Rectangle {
ApplicationInterface.uploadRequest.connect(uploadClicked);
assetMappingsModel.errorGettingMappings.connect(handleGetMappingsError);
assetMappingsModel.autoRefreshEnabled = true;
assetMappingsModel.updated.connect(function() {
++updatesCount;
});
reload();
}
@ -850,12 +854,17 @@ Rectangle {
checked = Qt.binding(isChecked);
}
function getStatus() {
// kind of hack for ensuring getStatus() will be re-evaluated on updatesCount changes
return updatesCount, assetProxyModel.data(treeView.selection.currentIndex, 0x105);
}
function isEnabled() {
if (!treeView.selection.hasSelection) {
return false;
}
var status = assetProxyModel.data(treeView.selection.currentIndex, 0x105);
var status = getStatus();
if (status === "--") {
return false;
}
@ -880,7 +889,7 @@ Rectangle {
return false;
}
var status = assetProxyModel.data(treeView.selection.currentIndex, 0x105);
var status = getStatus();
return isEnabled() && status !== "Not Baked";
}
}

View file

@ -3046,7 +3046,6 @@ void Application::handleSandboxStatus(QNetworkReply* reply) {
static const QString SENT_TO_PREVIOUS_LOCATION = "previous_location";
static const QString SENT_TO_ENTRY = "entry";
static const QString SENT_TO_SANDBOX = "sandbox";
QString sentTo;
@ -3055,15 +3054,8 @@ void Application::handleSandboxStatus(QNetworkReply* reply) {
#if !defined(Q_OS_ANDROID)
showHelp();
#endif
if (sandboxIsRunning) {
qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home.";
DependencyManager::get<AddressManager>()->goToLocalSandbox();
sentTo = SENT_TO_SANDBOX;
} else {
qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry.";
DependencyManager::get<AddressManager>()->goToEntry();
sentTo = SENT_TO_ENTRY;
}
DependencyManager::get<AddressManager>()->goToEntry();
sentTo = SENT_TO_ENTRY;
firstRun.set(false);
} else {
@ -4679,7 +4671,7 @@ void Application::init() {
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
// connect the _entityCollisionSystem to our EntityTreeRenderer since that's what handles running entity scripts
connect(_entitySimulation.get(), &EntitySimulation::entityCollisionWithEntity,
connect(_entitySimulation.get(), &PhysicalEntitySimulation::entityCollisionWithEntity,
getEntities().data(), &EntityTreeRenderer::entityCollisionWithEntity);
// connect the _entities (EntityTreeRenderer) to our script engine's EntityScriptingInterface for firing
@ -5274,11 +5266,13 @@ void Application::update(float deltaTime) {
{
PROFILE_RANGE(simulation_physics, "PreStep");
PerformanceTimer perfTimer("preStep)");
static VectorOfMotionStates motionStates;
_entitySimulation->getObjectsToRemoveFromPhysics(motionStates);
_physicsEngine->removeObjects(motionStates);
_entitySimulation->deleteObjectsRemovedFromPhysics();
{
const VectorOfMotionStates& motionStates = _entitySimulation->getObjectsToRemoveFromPhysics();
_physicsEngine->removeObjects(motionStates);
_entitySimulation->deleteObjectsRemovedFromPhysics();
}
VectorOfMotionStates motionStates;
getEntities()->getTree()->withReadLock([&] {
_entitySimulation->getObjectsToAddToPhysics(motionStates);
_physicsEngine->addObjects(motionStates);
@ -5292,7 +5286,7 @@ void Application::update(float deltaTime) {
_entitySimulation->applyDynamicChanges();
avatarManager->getObjectsToRemoveFromPhysics(motionStates);
avatarManager->getObjectsToRemoveFromPhysics(motionStates);
_physicsEngine->removeObjects(motionStates);
avatarManager->getObjectsToAddToPhysics(motionStates);
_physicsEngine->addObjects(motionStates);
@ -6244,8 +6238,9 @@ bool Application::canAcceptURL(const QString& urlString) const {
bool Application::acceptURL(const QString& urlString, bool defaultUpload) {
QUrl url(urlString);
if (isDomainURL(url)) {
// this is a URL for a domain, either hifi:// or serverless - have the AddressManager handle it
if (url.scheme() == URL_SCHEME_HIFI) {
// this is a hifi URL - have the AddressManager handle it
QMetaObject::invokeMethod(DependencyManager::get<AddressManager>().data(), "handleLookupString",
Qt::AutoConnection, Q_ARG(const QString&, urlString));
return true;

View file

@ -129,7 +129,7 @@ void DiscoverabilityManager::updateLocation() {
// Update Steam
if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
steamClient->updateLocation(domainHandler.getHostname(), addressManager->currentFacingShareableAddress());
steamClient->updateLocation(domainHandler.getHostname(), addressManager->currentFacingPublicAddress());
}
}

View file

@ -86,10 +86,6 @@ void LODManager::autoAdjustLOD(float realTimeDelta) {
if (_octreeSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) {
_octreeSizeScale = ADJUST_LOD_MIN_SIZE_SCALE;
}
qCDebug(interfaceapp) << "adjusting LOD DOWN"
<< "fps =" << currentFPS
<< "targetFPS =" << getLODDecreaseFPS()
<< "octreeSizeScale =" << _octreeSizeScale;
emit LODDecreased();
// Assuming the LOD adjustment will work: we optimistically reset _avgRenderTime
// to provide an FPS just above the decrease threshold. It will drift close to its
@ -111,10 +107,6 @@ void LODManager::autoAdjustLOD(float realTimeDelta) {
if (_octreeSizeScale > ADJUST_LOD_MAX_SIZE_SCALE) {
_octreeSizeScale = ADJUST_LOD_MAX_SIZE_SCALE;
}
qCDebug(interfaceapp) << "adjusting LOD UP"
<< "fps =" << currentFPS
<< "targetFPS =" << getLODDecreaseFPS()
<< "octreeSizeScale =" << _octreeSizeScale;
emit LODIncreased();
// Assuming the LOD adjustment will work: we optimistically reset _avgRenderTime
// to provide an FPS just below the increase threshold. It will drift close to its

View file

@ -67,8 +67,8 @@ using namespace std;
const float DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES = 30.0f;
const float YAW_SPEED_DEFAULT = 100.0f; // degrees/sec
const float PITCH_SPEED_DEFAULT = 75.0f; // degrees/sec
const float YAW_SPEED_DEFAULT = 75.0f; // degrees/sec
const float PITCH_SPEED_DEFAULT = 50.0f; // degrees/sec
const float MAX_BOOST_SPEED = 0.5f * DEFAULT_AVATAR_MAX_WALKING_SPEED; // action motor gets additive boost below this speed
const float MIN_AVATAR_SPEED = 0.05f;
@ -2227,7 +2227,7 @@ void MyAvatar::updateActionMotor(float deltaTime) {
}
float boomChange = getDriveKey(ZOOM);
_boomLength += 2.0f * _boomLength * boomChange + boomChange * boomChange;
_boomLength += 4.0f * _boomLength * boomChange + boomChange * boomChange;
_boomLength = glm::clamp<float>(_boomLength, ZOOM_MIN, ZOOM_MAX);
}
@ -2760,6 +2760,18 @@ bool MyAvatar::isDriveKeyDisabled(DriveKeys key) const {
}
}
void MyAvatar::triggerVerticalRecenter() {
_follow.setForceActivateVertical(true);
}
void MyAvatar::triggerHorizontalRecenter() {
_follow.setForceActivateHorizontal(true);
}
void MyAvatar::triggerRotationRecenter() {
_follow.setForceActivateRotation(true);
}
// old school meat hook style
glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const {
glm::vec3 headPosition;
@ -2957,7 +2969,9 @@ void MyAvatar::FollowHelper::decrementTimeRemaining(float dt) {
bool MyAvatar::FollowHelper::shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const {
const float FOLLOW_ROTATION_THRESHOLD = cosf(PI / 6.0f); // 30 degrees
glm::vec2 bodyFacing = getFacingDir2D(currentBodyMatrix);
return glm::dot(-myAvatar.getHeadControllerFacingMovingAverage(), bodyFacing) < FOLLOW_ROTATION_THRESHOLD;
}
bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const {
@ -2974,6 +2988,7 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar,
const float MAX_FORWARD_LEAN = 0.15f;
const float MAX_BACKWARD_LEAN = 0.1f;
if (forwardLeanAmount > 0 && forwardLeanAmount > MAX_FORWARD_LEAN) {
return true;
} else if (forwardLeanAmount < 0 && forwardLeanAmount < -MAX_BACKWARD_LEAN) {
@ -2981,6 +2996,7 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar,
}
return fabs(lateralLeanAmount) > MAX_LATERAL_LEAN;
}
bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const {
@ -2988,6 +3004,7 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, co
const float CYLINDER_BOTTOM = -1.5f;
glm::vec3 offset = extractTranslation(desiredBodyMatrix) - extractTranslation(currentBodyMatrix);
return (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM);
}
@ -3005,6 +3022,19 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat
if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) {
activate(Vertical);
}
} else {
if (!isActive(Rotation) && getForceActivateRotation()) {
activate(Rotation);
setForceActivateRotation(false);
}
if (!isActive(Horizontal) && getForceActivateHorizontal()) {
activate(Horizontal);
setForceActivateHorizontal(false);
}
if (!isActive(Vertical) && getForceActivateVertical()) {
activate(Vertical);
setForceActivateVertical(false);
}
}
glm::mat4 desiredWorldMatrix = myAvatar.getSensorToWorldMatrix() * desiredBodyMatrix;
@ -3054,6 +3084,30 @@ glm::mat4 MyAvatar::FollowHelper::postPhysicsUpdate(const MyAvatar& myAvatar, co
}
}
bool MyAvatar::FollowHelper::getForceActivateRotation() const {
return _forceActivateRotation;
}
void MyAvatar::FollowHelper::setForceActivateRotation(bool val) {
_forceActivateRotation = val;
}
bool MyAvatar::FollowHelper::getForceActivateVertical() const {
return _forceActivateVertical;
}
void MyAvatar::FollowHelper::setForceActivateVertical(bool val) {
_forceActivateVertical = val;
}
bool MyAvatar::FollowHelper::getForceActivateHorizontal() const {
return _forceActivateHorizontal;
}
void MyAvatar::FollowHelper::setForceActivateHorizontal(bool val) {
_forceActivateHorizontal = val;
}
float MyAvatar::getAccelerationEnergy() {
glm::vec3 velocity = getWorldVelocity();
int changeInVelocity = abs(velocity.length() - priorVelocity.length());

View file

@ -404,6 +404,32 @@ public:
Q_INVOKABLE void enableDriveKey(DriveKeys key);
Q_INVOKABLE bool isDriveKeyDisabled(DriveKeys key) const;
/**jsdoc
*The triggerVerticalRecenter function activates one time the recentering
*behaviour in the vertical direction. This call is only takes effect when the property
*MyAvatar.hmdLeanRecenterEnabled is set to false.
*@function MyAvatar.triggerVerticalRecenter
*
*/
/**jsdoc
*The triggerHorizontalRecenter function activates one time the recentering behaviour
*in the horizontal direction. This call is only takes effect when the property
*MyAvatar.hmdLeanRecenterEnabled is set to false.
*@function MyAvatar.triggerHorizontalRecenter
*/
/**jsdoc
*The triggerRotationRecenter function activates one time the recentering behaviour
*in the rotation of the root of the avatar. This call is only takes effect when the property
*MyAvatar.hmdLeanRecenterEnabled is set to false.
*@function MyAvatar.triggerRotationRecenter
*/
Q_INVOKABLE void triggerVerticalRecenter();
Q_INVOKABLE void triggerHorizontalRecenter();
Q_INVOKABLE void triggerRotationRecenter();
eyeContactTarget getEyeContactTarget();
const MyHead* getMyHead() const;
@ -803,6 +829,15 @@ private:
bool shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const;
void prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& bodySensorMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput);
glm::mat4 postPhysicsUpdate(const MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix);
bool getForceActivateRotation() const;
void setForceActivateRotation(bool val);
bool getForceActivateVertical() const;
void setForceActivateVertical(bool val);
bool getForceActivateHorizontal() const;
void setForceActivateHorizontal(bool val);
std::atomic<bool> _forceActivateRotation{ false };
std::atomic<bool> _forceActivateVertical{ false };
std::atomic<bool> _forceActivateHorizontal{ false };
};
FollowHelper _follow;
@ -839,6 +874,7 @@ private:
bool _hmdLeanRecenterEnabled { true };
bool _sprint { false };
AnimPose _prePhysicsRoomPose;
std::mutex _holdActionsMutex;
std::vector<AvatarActionHold*> _holdActions;

View file

@ -293,7 +293,7 @@ void Ledger::account() {
// The api/failResponse is called just for the side effect of logging.
void Ledger::updateLocationSuccess(QNetworkReply& reply) { apiResponse("updateLocation", reply); }
void Ledger::updateLocationFailure(QNetworkReply& reply) { failResponse("updateLocation", reply); }
void Ledger::updateLocation(const QString& asset_id, const QString location, const bool controlledFailure) {
void Ledger::updateLocation(const QString& asset_id, const QString& location, const bool& alsoUpdateSiblings, const bool controlledFailure) {
auto wallet = DependencyManager::get<Wallet>();
auto walletScriptingInterface = DependencyManager::get<WalletScriptingInterface>();
uint walletStatus = walletScriptingInterface->getWalletStatus();
@ -308,6 +308,9 @@ void Ledger::updateLocation(const QString& asset_id, const QString location, con
QJsonObject transaction;
transaction["certificate_id"] = asset_id;
transaction["place_name"] = location;
if (alsoUpdateSiblings) {
transaction["also_update_siblings"] = true;
}
QJsonDocument transactionDoc{ transaction };
auto transactionString = transactionDoc.toJson(QJsonDocument::Compact);
signedSend("transaction", transactionString, key, "location", "updateLocationSuccess", "updateLocationFailure", controlledFailure);

View file

@ -31,7 +31,7 @@ public:
void inventory(const QStringList& keys);
void history(const QStringList& keys, const int& pageNumber);
void account();
void updateLocation(const QString& asset_id, const QString location, const bool controlledFailure = false);
void updateLocation(const QString& asset_id, const QString& location, const bool& alsoUpdateSiblings = false, const bool controlledFailure = false);
void certificateInfo(const QString& certificateId);
void transferHfcToNode(const QString& hfc_key, const QString& nodeID, const int& amount, const QString& optionalMessage);
void transferHfcToUsername(const QString& hfc_key, const QString& username, const int& amount, const QString& optionalMessage);

View file

@ -190,7 +190,9 @@ void QmlCommerce::transferHfcToUsername(const QString& username, const int& amou
ledger->transferHfcToUsername(key, username, amount, optionalMessage);
}
void QmlCommerce::replaceContentSet(const QString& itemHref) {
void QmlCommerce::replaceContentSet(const QString& itemHref, const QString& certificateID) {
auto ledger = DependencyManager::get<Ledger>();
ledger->updateLocation(certificateID, DependencyManager::get<AddressManager>()->getPlaceName(), true);
qApp->replaceDomainContent(itemHref);
QJsonObject messageProperties = {
{ "status", "SuccessfulRequestToReplaceContent" },

View file

@ -84,7 +84,7 @@ protected:
Q_INVOKABLE void transferHfcToNode(const QString& nodeID, const int& amount, const QString& optionalMessage);
Q_INVOKABLE void transferHfcToUsername(const QString& username, const int& amount, const QString& optionalMessage);
Q_INVOKABLE void replaceContentSet(const QString& itemHref);
Q_INVOKABLE void replaceContentSet(const QString& itemHref, const QString& certificateID);
Q_INVOKABLE QString getInstalledApps();
Q_INVOKABLE bool installApp(const QString& appHref);

View file

@ -81,26 +81,14 @@ void LaserPointer::editRenderState(const std::string& state, const QVariant& sta
});
}
void LaserPointer::updateRenderStateOverlay(const OverlayID& id, const QVariant& props) {
if (!id.isNull() && props.isValid()) {
QVariantMap propMap = props.toMap();
propMap.remove("visible");
qApp->getOverlays().editOverlay(id, propMap);
}
}
PickResultPointer LaserPointer::getVisualPickResult(const PickResultPointer& pickResult) {
PickResultPointer visualPickResult = pickResult;
auto rayPickResult = std::static_pointer_cast<RayPickResult>(visualPickResult);
IntersectionType type = rayPickResult ? rayPickResult->type : IntersectionType::NONE;
void LaserPointer::updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const QUuid& objectID, const PickRay& pickRay, bool defaultState) {
if (!renderState.getStartID().isNull()) {
QVariantMap startProps;
startProps.insert("position", vec3toVariant(pickRay.origin));
startProps.insert("visible", true);
startProps.insert("ignoreRayIntersection", renderState.doesStartIgnoreRays());
qApp->getOverlays().editOverlay(renderState.getStartID(), startProps);
}
glm::vec3 endVec;
if (((defaultState || !_lockEnd) && _lockEndObject.id.isNull()) || type == IntersectionType::HUD) {
endVec = pickRay.origin + pickRay.direction * distance;
} else {
if (type != IntersectionType::HUD) {
glm::vec3 endVec;
PickRay pickRay = rayPickResult ? PickRay(rayPickResult->pickVariant) : PickRay();
if (!_lockEndObject.id.isNull()) {
glm::vec3 pos;
glm::quat rot;
@ -122,17 +110,54 @@ void LaserPointer::updateRenderState(const RenderState& renderState, const Inter
}
const glm::vec3 DEFAULT_REGISTRATION_POINT = glm::vec3(0.5f);
endVec = pos + rot * (dim * (DEFAULT_REGISTRATION_POINT - registrationPoint));
} else {
glm::vec3 direction = endVec - pickRay.origin;
float distance = glm::distance(pickRay.origin, endVec);
glm::vec3 normalizedDirection = glm::normalize(direction);
rayPickResult->type = _lockEndObject.isOverlay ? IntersectionType::OVERLAY : IntersectionType::ENTITY;
rayPickResult->objectID = _lockEndObject.id;
rayPickResult->intersection = endVec;
rayPickResult->distance = distance;
rayPickResult->surfaceNormal = -normalizedDirection;
rayPickResult->pickVariant["direction"] = vec3toVariant(normalizedDirection);
} else if (type != IntersectionType::NONE && _lockEnd) {
if (type == IntersectionType::ENTITY) {
endVec = DependencyManager::get<EntityScriptingInterface>()->getEntityTransform(objectID)[3];
endVec = DependencyManager::get<EntityScriptingInterface>()->getEntityTransform(rayPickResult->objectID)[3];
} else if (type == IntersectionType::OVERLAY) {
endVec = vec3FromVariant(qApp->getOverlays().getProperty(objectID, "position").value);
endVec = vec3FromVariant(qApp->getOverlays().getProperty(rayPickResult->objectID, "position").value);
} else if (type == IntersectionType::AVATAR) {
endVec = DependencyManager::get<AvatarHashMap>()->getAvatar(objectID)->getPosition();
endVec = DependencyManager::get<AvatarHashMap>()->getAvatar(rayPickResult->objectID)->getPosition();
}
glm::vec3 direction = endVec - pickRay.origin;
float distance = glm::distance(pickRay.origin, endVec);
glm::vec3 normalizedDirection = glm::normalize(direction);
rayPickResult->intersection = endVec;
rayPickResult->distance = distance;
rayPickResult->surfaceNormal = -normalizedDirection;
rayPickResult->pickVariant["direction"] = vec3toVariant(normalizedDirection);
}
}
return visualPickResult;
}
void LaserPointer::updateRenderStateOverlay(const OverlayID& id, const QVariant& props) {
if (!id.isNull() && props.isValid()) {
QVariantMap propMap = props.toMap();
propMap.remove("visible");
qApp->getOverlays().editOverlay(id, propMap);
}
}
void LaserPointer::updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const QUuid& objectID, const PickRay& pickRay) {
if (!renderState.getStartID().isNull()) {
QVariantMap startProps;
startProps.insert("position", vec3toVariant(pickRay.origin));
startProps.insert("visible", true);
startProps.insert("ignoreRayIntersection", renderState.doesStartIgnoreRays());
qApp->getOverlays().editOverlay(renderState.getStartID(), startProps);
}
glm::vec3 endVec = pickRay.origin + pickRay.direction * distance;
QVariant end = vec3toVariant(endVec);
if (!renderState.getPathID().isNull()) {
QVariantMap pathProps;
@ -195,15 +220,15 @@ void LaserPointer::updateVisuals(const PickResultPointer& pickResult) {
IntersectionType type = rayPickResult ? rayPickResult->type : IntersectionType::NONE;
if (_enabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() &&
(type != IntersectionType::NONE || _laserLength > 0.0f || !_lockEndObject.id.isNull())) {
PickRay pickRay(rayPickResult->pickVariant);
PickRay pickRay = rayPickResult ? PickRay(rayPickResult->pickVariant): PickRay();
QUuid uid = rayPickResult->objectID;
float distance = _laserLength > 0.0f ? _laserLength : rayPickResult->distance;
updateRenderState(_renderStates[_currentRenderState], type, distance, uid, pickRay, false);
updateRenderState(_renderStates[_currentRenderState], type, distance, uid, pickRay);
disableRenderState(_defaultRenderStates[_currentRenderState].second);
} else if (_enabled && !_currentRenderState.empty() && _defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) {
disableRenderState(_renderStates[_currentRenderState]);
PickRay pickRay = rayPickResult ? PickRay(rayPickResult->pickVariant) : PickRay();
updateRenderState(_defaultRenderStates[_currentRenderState].second, IntersectionType::NONE, _defaultRenderStates[_currentRenderState].first, QUuid(), pickRay, true);
updateRenderState(_defaultRenderStates[_currentRenderState].second, IntersectionType::NONE, _defaultRenderStates[_currentRenderState].first, QUuid(), pickRay);
} else if (!_currentRenderState.empty()) {
disableRenderState(_renderStates[_currentRenderState]);
disableRenderState(_defaultRenderStates[_currentRenderState].second);
@ -386,4 +411,4 @@ glm::vec2 LaserPointer::findPos2D(const PickedObject& pickedObject, const glm::v
default:
return glm::vec2(NAN);
}
}
}

View file

@ -82,6 +82,7 @@ public:
protected:
PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button = "", bool hover = true) override;
PickResultPointer getVisualPickResult(const PickResultPointer& pickResult) override;
PickedObject getHoveredObject(const PickResultPointer& pickResult) override;
Pointer::Buttons getPressedButtons(const PickResultPointer& pickResult) override;
@ -102,7 +103,7 @@ private:
LockEndObject _lockEndObject;
void updateRenderStateOverlay(const OverlayID& id, const QVariant& props);
void updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const QUuid& objectID, const PickRay& pickRay, bool defaultState);
void updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const QUuid& objectID, const PickRay& pickRay);
void disableRenderState(const RenderState& renderState);
struct TriggerState {

View file

@ -80,7 +80,7 @@ void AssetMappingsScriptingInterface::uploadFile(QString path, QString mapping,
auto result = offscreenUi->inputDialog(OffscreenUi::ICON_INFORMATION, "Specify Asset Path",
dropEvent ? dropHelpText : helpText, mapping);
if (!result.isValid()) {
if (!result.isValid() || result.toString() == "") {
completedCallback.call({ -1 });
return;
}

View file

@ -110,7 +110,6 @@ bool SelectionScriptingInterface::enableListHighlight(const QString& listName, c
}
if (!(*highlightStyle).isBoundToList()) {
setupHandler(listName);
(*highlightStyle).setBoundToList(true);
}
@ -172,6 +171,18 @@ render::HighlightStyle SelectionScriptingInterface::getHighlightStyle(const QStr
}
}
bool SelectionScriptingInterface::enableListToScene(const QString& listName) {
setupHandler(listName);
return true;
}
bool SelectionScriptingInterface::disableListToScene(const QString& listName) {
removeHandler(listName);
return true;
}
template <class T> bool SelectionScriptingInterface::addToGameplayObjects(const QString& listName, T idToAdd) {
{
QWriteLocker lock(&_selectionListsLock);
@ -303,6 +314,15 @@ void SelectionScriptingInterface::setupHandler(const QString& selectionName) {
(*handler)->initialize(selectionName);
}
void SelectionScriptingInterface::removeHandler(const QString& selectionName) {
QWriteLocker lock(&_selectionHandlersLock);
auto handler = _handlerMap.find(selectionName);
if (handler != _handlerMap.end()) {
delete handler.value();
_handlerMap.erase(handler);
}
}
void SelectionScriptingInterface::onSelectedItemsListChanged(const QString& listName) {
{
QWriteLocker lock(&_selectionHandlersLock);

View file

@ -160,13 +160,14 @@ public:
* If the Selection doesn't exist, it will be created.
* All objects in the list will be displayed with the highlight effect as specified from the highlightStyle.
* The function can be called several times with different values in the style to modify it.
*
*
* @function Selection.enableListHighlight
* @param listName {string} name of the selection
* @param highlightStyle {jsObject} highlight style fields (see Selection.getListHighlightStyle for a detailed description of the highlightStyle).
* @returns {bool} true if the selection was successfully enabled for highlight.
*/
Q_INVOKABLE bool enableListHighlight(const QString& listName, const QVariantMap& highlightStyle);
/**jsdoc
* Disable highlighting for the named selection.
* If the Selection doesn't exist or wasn't enabled for highliting then nothing happens simply returning false.
@ -175,7 +176,27 @@ public:
* @param listName {string} name of the selection
* @returns {bool} true if the selection was successfully disabled for highlight, false otherwise.
*/
Q_INVOKABLE bool disableListHighlight(const QString& listName);
Q_INVOKABLE bool disableListHighlight(const QString& listName);
/**jsdoc
* Enable scene selection for the named selection.
* If the Selection doesn't exist, it will be created.
* All objects in the list will be sent to a scene selection.
*
* @function Selection.enableListToScene
* @param listName {string} name of the selection
* @returns {bool} true if the selection was successfully enabled on the scene.
*/
Q_INVOKABLE bool enableListToScene(const QString& listName);
/**jsdoc
* Disable scene selection for the named selection.
* If the Selection doesn't exist or wasn't enabled on the scene then nothing happens simply returning false.
*
* @function Selection.disableListToScene
* @param listName {string} name of the selection
* @returns {bool} true if the selection was successfully disabled on the scene, false otherwise.
*/
Q_INVOKABLE bool disableListToScene(const QString& listName);
/**jsdoc
* Query the highlight style values for the named selection.
* If the Selection doesn't exist or hasn't been highlight enabled yet, it will return an empty object.
@ -223,7 +244,7 @@ private:
template <class T> bool removeFromGameplayObjects(const QString& listName, T idToRemove);
void setupHandler(const QString& selectionName);
void removeHandler(const QString& selectionName);
};

View file

@ -95,7 +95,7 @@ QTemporaryFile* Snapshot::saveTempSnapshot(QImage image) {
QFile* Snapshot::savedFileForSnapshot(QImage & shot, bool isTemporary, const QString& userSelectedFilename) {
// adding URL to snapshot
QUrl currentURL = DependencyManager::get<AddressManager>()->currentShareableAddress();
QUrl currentURL = DependencyManager::get<AddressManager>()->currentPublicAddress();
shot.setText(URL, currentURL.toString());
QString username = DependencyManager::get<AccountManager>()->getAccountInfo().getUsername();

View file

@ -65,7 +65,7 @@ void SnapshotUploader::uploadSuccess(QNetworkReply& reply) {
} else {
emit DependencyManager::get<WindowScriptingInterface>()->snapshotShared(true, contents);
delete this;
this->deleteLater();
}
}
@ -75,23 +75,27 @@ void SnapshotUploader::uploadFailure(QNetworkReply& reply) {
if (replyString.size() == 0) {
replyString = reply.errorString();
}
replyString = replyString.left(1000); // Only print first 1000 characters of error
qDebug() << "Snapshot upload reply error (truncated):" << replyString;
emit DependencyManager::get<WindowScriptingInterface>()->snapshotShared(true, replyString); // maybe someday include _inWorldLocation, _filename?
delete this;
this->deleteLater();
}
void SnapshotUploader::createStorySuccess(QNetworkReply& reply) {
QString replyString = reply.readAll();
emit DependencyManager::get<WindowScriptingInterface>()->snapshotShared(false, replyString);
delete this;
this->deleteLater();
}
void SnapshotUploader::createStoryFailure(QNetworkReply& reply) {
QString replyString = reply.readAll();
qDebug() << "Error " << reply.errorString() << " uploading snapshot " << _pathname << " from " << _inWorldLocation;
qDebug() << "Error " << reply.errorString() << " uploading snapshot story " << _pathname << " from " << _inWorldLocation;
if (replyString.size() == 0) {
replyString = reply.errorString();
}
replyString = replyString.left(1000); // Only print first 1000 characters of error
qDebug() << "Snapshot story upload reply error (truncated):" << replyString;
emit DependencyManager::get<WindowScriptingInterface>()->snapshotShared(true, replyString);
delete this;
this->deleteLater();
}

View file

@ -50,7 +50,9 @@ ContextOverlayInterface::ContextOverlayInterface() {
_entityPropertyFlags += PROP_OWNING_AVATAR_ID;
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>().data();
connect(entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity, this, &ContextOverlayInterface::createOrDestroyContextOverlay);
connect(entityScriptingInterface, &EntityScriptingInterface::clickDownOnEntity, this, &ContextOverlayInterface::clickDownOnEntity);
connect(entityScriptingInterface, &EntityScriptingInterface::holdingClickOnEntity, this, &ContextOverlayInterface::holdingClickOnEntity);
connect(entityScriptingInterface, &EntityScriptingInterface::mouseReleaseOnEntity, this, &ContextOverlayInterface::mouseReleaseOnEntity);
connect(entityScriptingInterface, &EntityScriptingInterface::hoverEnterEntity, this, &ContextOverlayInterface::contextOverlays_hoverEnterEntity);
connect(entityScriptingInterface, &EntityScriptingInterface::hoverLeaveEntity, this, &ContextOverlayInterface::contextOverlays_hoverLeaveEntity);
connect(_tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"), &TabletProxy::tabletShownChanged, this, [&]() {
@ -97,6 +99,31 @@ void ContextOverlayInterface::setEnabled(bool enabled) {
_enabled = enabled;
}
void ContextOverlayInterface::clickDownOnEntity(const EntityItemID& entityItemID, const PointerEvent& event) {
if (_enabled && event.getButton() == PointerEvent::SecondaryButton && contextOverlayFilterPassed(entityItemID)) {
_mouseDownEntity = entityItemID;
_mouseDownEntityTimestamp = usecTimestampNow();
} else {
if (!_currentEntityWithContextOverlay.isNull()) {
disableEntityHighlight(_currentEntityWithContextOverlay);
destroyContextOverlay(_currentEntityWithContextOverlay, event);
}
}
}
static const float CONTEXT_OVERLAY_CLICK_HOLD_TIME_MSEC = 400.0f;
void ContextOverlayInterface::holdingClickOnEntity(const EntityItemID& entityItemID, const PointerEvent& event) {
if (!_mouseDownEntity.isNull() && ((usecTimestampNow() - _mouseDownEntityTimestamp) > (CONTEXT_OVERLAY_CLICK_HOLD_TIME_MSEC * USECS_PER_MSEC))) {
_mouseDownEntity = EntityItemID();
}
}
void ContextOverlayInterface::mouseReleaseOnEntity(const EntityItemID& entityItemID, const PointerEvent& event) {
if (_enabled && event.getButton() == PointerEvent::SecondaryButton && contextOverlayFilterPassed(entityItemID) && _mouseDownEntity == entityItemID) {
createOrDestroyContextOverlay(entityItemID, event);
}
}
bool ContextOverlayInterface::createOrDestroyContextOverlay(const EntityItemID& entityItemID, const PointerEvent& event) {
if (_enabled && event.getButton() == PointerEvent::SecondaryButton) {
if (contextOverlayFilterPassed(entityItemID)) {

View file

@ -64,6 +64,10 @@ signals:
void contextOverlayClicked(const QUuid& currentEntityWithContextOverlay);
public slots:
void clickDownOnEntity(const EntityItemID& entityItemID, const PointerEvent& event);
void holdingClickOnEntity(const EntityItemID& entityItemID, const PointerEvent& event);
void mouseReleaseOnEntity(const EntityItemID& entityItemID, const PointerEvent& event);
bool createOrDestroyContextOverlay(const EntityItemID& entityItemID, const PointerEvent& event);
bool destroyContextOverlay(const EntityItemID& entityItemID, const PointerEvent& event);
bool destroyContextOverlay(const EntityItemID& entityItemID);
@ -84,6 +88,8 @@ private:
};
bool _verboseLogging{ true };
bool _enabled { true };
EntityItemID _mouseDownEntity{};
quint64 _mouseDownEntityTimestamp;
EntityItemID _currentEntityWithContextOverlay{};
EntityItemID _lastInspectedEntity{};
QString _entityMarketplaceID;

View file

@ -6,10 +6,43 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// NOTE: we don't need to include this header unless/until we add additional symbols.
// By removing this header we prevent these warnings on windows:
//
// warning LNK4221: This object file does not define any previously undefined public symbols,
// so it will not be used by any link operation that consumes this library
//
//#include "JSEndpoint.h"
#include "JSEndpoint.h"
#include "../../Logging.h"
using namespace controller;
QString formatException(const QJSValue& exception) {
QString note { "UncaughtException" };
QString result;
const auto message = exception.toString();
const auto fileName = exception.property("fileName").toString();
const auto lineNumber = exception.property("lineNumber").toString();
const auto stacktrace = exception.property("stack").toString();
const QString SCRIPT_EXCEPTION_FORMAT = "[%0] %1 in %2:%3";
const QString SCRIPT_BACKTRACE_SEP = "\n ";
result = QString(SCRIPT_EXCEPTION_FORMAT).arg(note, message, fileName, lineNumber);
if (!stacktrace.isEmpty()) {
result += QString("\n[Backtrace]%1%2").arg(SCRIPT_BACKTRACE_SEP).arg(stacktrace);
}
return result;
}
float JSEndpoint::peek() const {
QJSValue result = _callable.call();
if (result.isError()) {
qCDebug(controllers).noquote() << formatException(result);
return 0.0f;
} else {
return (float)result.toNumber();
}
}
void JSEndpoint::apply(float newValue, const Pointer& source) {
QJSValue result = _callable.call(QJSValueList({ QJSValue(newValue) }));
if (result.isError()) {
qCDebug(controllers).noquote() << formatException(result);
}
}

View file

@ -24,16 +24,11 @@ public:
: Endpoint(Input::INVALID_INPUT), _callable(callable) {
}
virtual float peek() const override {
return (float)const_cast<JSEndpoint*>(this)->_callable.call().toNumber();
}
virtual void apply(float newValue, const Pointer& source) override {
_callable.call(QJSValueList({ QJSValue(newValue) }));
}
virtual float peek() const override;
virtual void apply(float newValue, const Pointer& source) override;
private:
QJSValue _callable;
mutable QJSValue _callable;
};
}

View file

@ -7,6 +7,7 @@
//
#include "ScriptEndpoint.h"
#include "../../Logging.h"
#include <QtCore/QThread>
@ -14,6 +15,25 @@
using namespace controller;
QString formatException(const QScriptValue& exception) {
QString note { "UncaughtException" };
QString result;
const auto message = exception.toString();
const auto fileName = exception.property("fileName").toString();
const auto lineNumber = exception.property("lineNumber").toString();
const auto stacktrace = exception.property("stack").toString();
const QString SCRIPT_EXCEPTION_FORMAT = "[%0] %1 in %2:%3";
const QString SCRIPT_BACKTRACE_SEP = "\n ";
result = QString(SCRIPT_EXCEPTION_FORMAT).arg(note, message, fileName, lineNumber);
if (!stacktrace.isEmpty()) {
result += QString("\n[Backtrace]%1%2").arg(SCRIPT_BACKTRACE_SEP).arg(stacktrace);
}
return result;
}
float ScriptEndpoint::peek() const {
const_cast<ScriptEndpoint*>(this)->updateValue();
return _lastValueRead;
@ -26,10 +46,11 @@ void ScriptEndpoint::updateValue() {
}
QScriptValue result = _callable.call();
// If the callable ever returns a non-number, we assume it's a pose
// and start reporting ourselves as a pose.
if (result.isNumber()) {
if (result.isError()) {
// print JavaScript exception
qCDebug(controllers).noquote() << formatException(result);
_lastValueRead = 0.0f;
} else if (result.isNumber()) {
_lastValueRead = (float)_callable.call().toNumber();
} else {
Pose::fromScriptValue(result, _lastPoseRead);
@ -52,8 +73,12 @@ void ScriptEndpoint::internalApply(float value, int sourceID) {
Q_ARG(int, sourceID));
return;
}
_callable.call(QScriptValue(),
QScriptValue result = _callable.call(QScriptValue(),
QScriptValueList({ QScriptValue(value), QScriptValue(sourceID) }));
if (result.isError()) {
// print JavaScript exception
qCDebug(controllers).noquote() << formatException(result);
}
}
Pose ScriptEndpoint::peekPose() const {
@ -67,6 +92,10 @@ void ScriptEndpoint::updatePose() {
return;
}
QScriptValue result = _callable.call();
if (result.isError()) {
// print JavaScript exception
qCDebug(controllers).noquote() << formatException(result);
}
Pose::fromScriptValue(result, _lastPoseRead);
}
@ -85,6 +114,10 @@ void ScriptEndpoint::internalApply(const Pose& newPose, int sourceID) {
Q_ARG(int, sourceID));
return;
}
_callable.call(QScriptValue(),
QScriptValue result = _callable.call(QScriptValue(),
QScriptValueList({ Pose::toScriptValue(_callable.engine(), newPose), QScriptValue(sourceID) }));
if (result.isError()) {
// print JavaScript exception
qCDebug(controllers).noquote() << formatException(result);
}
}

View file

@ -131,6 +131,8 @@ ItemKey ShapeEntityRenderer::getKey() {
withReadLock([&] {
if (isTransparent()) {
builder.withTransparent();
} else if (_canCastShadow) {
builder.withShadowCaster();
}
});

View file

@ -1911,7 +1911,7 @@ void EntityItem::computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask
}
}
void EntityItem::setSimulationOwner(const QUuid& id, quint8 priority) {
void EntityItem::setSimulationOwner(const QUuid& id, uint8_t priority) {
if (wantTerseEditLogging() && (id != _simulationOwner.getID() || priority != _simulationOwner.getPriority())) {
qCDebug(entities) << "sim ownership for" << getDebugName() << "is now" << id << priority;
}
@ -1942,7 +1942,7 @@ void EntityItem::clearSimulationOwnership() {
}
void EntityItem::setPendingOwnershipPriority(quint8 priority, const quint64& timestamp) {
void EntityItem::setPendingOwnershipPriority(uint8_t priority, const quint64& timestamp) {
_simulationOwner.setPendingPriority(priority, timestamp);
}
@ -2962,13 +2962,6 @@ void EntityItem::retrieveMarketplacePublicKey() {
}
void EntityItem::preDelete() {
// clear out any left-over actions
EntityTreeElementPointer element = _element; // use local copy of _element for logic below
EntityTreePointer entityTree = element ? element->getTree() : nullptr;
EntitySimulationPointer simulation = entityTree ? entityTree->getSimulation() : nullptr;
if (simulation) {
clearActions(simulation);
}
}
void EntityItem::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) {

View file

@ -304,14 +304,14 @@ public:
// FIXME not thread safe?
const SimulationOwner& getSimulationOwner() const { return _simulationOwner; }
void setSimulationOwner(const QUuid& id, quint8 priority);
void setSimulationOwner(const QUuid& id, uint8_t priority);
void setSimulationOwner(const SimulationOwner& owner);
void promoteSimulationPriority(quint8 priority);
void promoteSimulationPriority(uint8_t priority);
quint8 getSimulationPriority() const { return _simulationOwner.getPriority(); }
uint8_t getSimulationPriority() const { return _simulationOwner.getPriority(); }
QUuid getSimulatorID() const { return _simulationOwner.getID(); }
void clearSimulationOwnership();
void setPendingOwnershipPriority(quint8 priority, const quint64& timestamp);
void setPendingOwnershipPriority(uint8_t priority, const quint64& timestamp);
uint8_t getPendingOwnershipPriority() const { return _simulationOwner.getPendingPriority(); }
void rememberHasSimulationOwnershipBid() const;

View file

@ -326,7 +326,7 @@ public:
void clearSimulationOwner();
void setSimulationOwner(const QUuid& id, uint8_t priority);
void setSimulationOwner(const QByteArray& data);
void promoteSimulationPriority(quint8 priority) { _simulationOwner.promotePriority(priority); }
void promoteSimulationPriority(uint8_t priority) { _simulationOwner.promotePriority(priority); }
void setActionDataDirty() { _actionDataChanged = true; }

View file

@ -254,17 +254,6 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties
propertiesWithSimID = convertPropertiesFromScriptSemantics(propertiesWithSimID, scalesWithParent);
propertiesWithSimID.setDimensionsInitialized(properties.dimensionsChanged());
auto dimensions = propertiesWithSimID.getDimensions();
float volume = dimensions.x * dimensions.y * dimensions.z;
auto density = propertiesWithSimID.getDensity();
auto newVelocity = propertiesWithSimID.getVelocity().length();
float cost = calculateCost(density * volume, 0, newVelocity);
cost *= costMultiplier;
if (cost > _currentAvatarEnergy) {
return QUuid();
}
EntityItemID id = EntityItemID(QUuid::createUuid());
// If we have a local entity tree set, then also update it.
@ -295,9 +284,7 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties
// queue the packet
if (success) {
emit debitEnergySource(cost);
queueEntityMessage(PacketType::EntityAdd, id, propertiesWithSimID);
return id;
} else {
return QUuid();
@ -378,27 +365,9 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties&
EntityItemProperties properties = scriptSideProperties;
auto dimensions = properties.getDimensions();
float volume = dimensions.x * dimensions.y * dimensions.z;
auto density = properties.getDensity();
auto newVelocity = properties.getVelocity().length();
float oldVelocity = { 0.0f };
EntityItemID entityID(id);
if (!_entityTree) {
queueEntityMessage(PacketType::EntityEdit, entityID, properties);
//if there is no local entity entity tree, no existing velocity, use 0.
float cost = calculateCost(density * volume, oldVelocity, newVelocity);
cost *= costMultiplier;
if (cost > _currentAvatarEnergy) {
return QUuid();
} else {
//debit the avatar energy and continue
emit debitEnergySource(cost);
}
return id;
}
// If we have a local entity tree set, then also update it.
@ -420,9 +389,6 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties&
// All of parentID, parentJointIndex, position, rotation are needed to make sense of any of them.
// If any of these changed, pull any missing properties from the entity.
//existing entity, retrieve old velocity for check down below
oldVelocity = entity->getWorldVelocity().length();
if (!scriptSideProperties.parentIDChanged()) {
properties.setParentID(entity->getParentID());
}
@ -442,23 +408,11 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties&
properties.setClientOnly(entity->getClientOnly());
properties.setOwningAvatarID(entity->getOwningAvatarID());
properties = convertPropertiesFromScriptSemantics(properties, properties.getScalesWithParent());
float cost = calculateCost(density * volume, oldVelocity, newVelocity);
cost *= costMultiplier;
if (cost > _currentAvatarEnergy) {
updatedEntity = false;
} else {
//debit the avatar energy and continue
updatedEntity = _entityTree->updateEntity(entityID, properties);
if (updatedEntity) {
emit debitEnergySource(cost);
}
}
updatedEntity = _entityTree->updateEntity(entityID, properties);
});
// FIXME: We need to figure out a better way to handle this. Allowing these edits to go through potentially
// breaks avatar energy and entities that are parented.
// breaks entities that are parented.
//
// To handle cases where a script needs to edit an entity with a _known_ entity id but doesn't exist
// in the local entity tree, we need to allow those edits to go through to the server.
@ -577,21 +531,6 @@ void EntityScriptingInterface::deleteEntity(QUuid id) {
return;
}
auto dimensions = entity->getScaledDimensions();
float volume = dimensions.x * dimensions.y * dimensions.z;
auto density = entity->getDensity();
auto velocity = entity->getWorldVelocity().length();
float cost = calculateCost(density * volume, velocity, 0);
cost *= costMultiplier;
if (cost > _currentAvatarEnergy) {
shouldDelete = false;
return;
} else {
//debit the avatar energy and continue
emit debitEnergySource(cost);
}
if (entity->getLocked()) {
shouldDelete = false;
} else {
@ -1812,23 +1751,6 @@ void EntityScriptingInterface::emitScriptEvent(const EntityItemID& entityID, con
}
}
float EntityScriptingInterface::calculateCost(float mass, float oldVelocity, float newVelocity) {
return std::abs(mass * (newVelocity - oldVelocity));
}
void EntityScriptingInterface::setCurrentAvatarEnergy(float energy) {
// qCDebug(entities) << "NEW AVATAR ENERGY IN ENTITY SCRIPTING INTERFACE: " << energy;
_currentAvatarEnergy = energy;
}
float EntityScriptingInterface::getCostMultiplier() {
return costMultiplier;
}
void EntityScriptingInterface::setCostMultiplier(float value) {
costMultiplier = value;
}
// TODO move this someplace that makes more sense...
bool EntityScriptingInterface::AABoxIntersectsCapsule(const glm::vec3& low, const glm::vec3& dimensions,
const glm::vec3& start, const glm::vec3& end, float radius) {

View file

@ -94,8 +94,6 @@ void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, Ra
* Interface has displayed and so knows about.
*
* @namespace Entities
* @property {number} currentAvatarEnergy - <strong>Deprecated</strong>
* @property {number} costMultiplier - <strong>Deprecated</strong>
* @property {Uuid} keyboardFocusEntity - Get or set the {@link Entities.EntityType|Web} entity that has keyboard focus.
* If no entity has keyboard focus, get returns <code>null</code>; set to <code>null</code> or {@link Uuid|Uuid.NULL} to
* clear keyboard focus.
@ -104,8 +102,6 @@ void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, Ra
class EntityScriptingInterface : public OctreeScriptingInterface, public Dependency {
Q_OBJECT
Q_PROPERTY(float currentAvatarEnergy READ getCurrentAvatarEnergy WRITE setCurrentAvatarEnergy)
Q_PROPERTY(float costMultiplier READ getCostMultiplier WRITE setCostMultiplier)
Q_PROPERTY(QUuid keyboardFocusEntity READ getKeyboardFocusEntity WRITE setKeyboardFocusEntity)
friend EntityPropertyMetadataRequest;
@ -126,7 +122,6 @@ public:
void setEntityTree(EntityTreePointer modelTree);
EntityTreePointer getEntityTree() { return _entityTree; }
void setEntitiesScriptEngine(QSharedPointer<EntitiesScriptEngineProvider> engine);
float calculateCost(float mass, float oldVelocity, float newVelocity);
void resetActivityTracking();
ActivityTracking getActivityTracking() const { return _activityTracking; }
@ -1834,14 +1829,6 @@ signals:
*/
void clearingEntities();
/**jsdoc
* @function Entities.debitEnergySource
* @param {number} value - The amount to debit.
* @returns {Signal}
* @deprecated This function is deprecated and will soon be removed.
*/
void debitEnergySource(float value);
/**jsdoc
* Triggered in when a script in a {@link Entities.EntityType|Web} entity's Web page script sends an event over the
* script's <code>EventBridge</code>.
@ -1882,14 +1869,8 @@ private:
QSharedPointer<EntitiesScriptEngineProvider> _entitiesScriptEngine;
bool _bidOnSimulationOwnership { false };
float _currentAvatarEnergy = { FLT_MAX };
float getCurrentAvatarEnergy() { return _currentAvatarEnergy; }
void setCurrentAvatarEnergy(float energy);
ActivityTracking _activityTracking;
float costMultiplier = { 0.01f };
float getCostMultiplier();
void setCostMultiplier(float value);
};
#endif // hifi_EntityScriptingInterface_h

View file

@ -20,7 +20,7 @@
void EntitySimulation::setEntityTree(EntityTreePointer tree) {
if (_entityTree && _entityTree != tree) {
_mortalEntities.clear();
_nextExpiry = quint64(-1);
_nextExpiry = std::numeric_limits<uint64_t>::max();
_entitiesToUpdate.clear();
_entitiesToSort.clear();
_simpleKinematicEntities.clear();
@ -30,7 +30,7 @@ void EntitySimulation::setEntityTree(EntityTreePointer tree) {
void EntitySimulation::updateEntities() {
QMutexLocker lock(&_mutex);
quint64 now = usecTimestampNow();
uint64_t now = usecTimestampNow();
// these methods may accumulate entries in _entitiesToBeDeleted
expireMortalEntities(now);
@ -40,18 +40,14 @@ void EntitySimulation::updateEntities() {
sortEntitiesThatMoved();
}
void EntitySimulation::takeEntitiesToDelete(VectorOfEntities& entitiesToDelete) {
void EntitySimulation::takeDeadEntities(SetOfEntities& entitiesToDelete) {
QMutexLocker lock(&_mutex);
for (auto entity : _entitiesToDelete) {
// push this entity onto the external list
entitiesToDelete.push_back(entity);
}
_entitiesToDelete.clear();
entitiesToDelete.swap(_deadEntities);
_deadEntities.clear();
}
void EntitySimulation::removeEntityInternal(EntityItemPointer entity) {
QMutexLocker lock(&_mutex);
// remove from all internal lists except _entitiesToDelete
// remove from all internal lists except _deadEntities
_mortalEntities.remove(entity);
_entitiesToUpdate.remove(entity);
_entitiesToSort.remove(entity);
@ -67,42 +63,23 @@ void EntitySimulation::prepareEntityForDelete(EntityItemPointer entity) {
QMutexLocker lock(&_mutex);
entity->clearActions(getThisPointer());
removeEntityInternal(entity);
_entitiesToDelete.insert(entity);
}
}
void EntitySimulation::addEntityInternal(EntityItemPointer entity) {
if (entity->isMovingRelativeToParent() && !entity->getPhysicsInfo()) {
QMutexLocker lock(&_mutex);
_simpleKinematicEntities.insert(entity);
entity->setLastSimulated(usecTimestampNow());
}
}
void EntitySimulation::changeEntityInternal(EntityItemPointer entity) {
QMutexLocker lock(&_mutex);
if (entity->isMovingRelativeToParent() && !entity->getPhysicsInfo()) {
int numKinematicEntities = _simpleKinematicEntities.size();
_simpleKinematicEntities.insert(entity);
if (numKinematicEntities != _simpleKinematicEntities.size()) {
entity->setLastSimulated(usecTimestampNow());
if (entity->getElement()) {
_deadEntities.insert(entity);
}
} else {
_simpleKinematicEntities.remove(entity);
}
}
// protected
void EntitySimulation::expireMortalEntities(const quint64& now) {
void EntitySimulation::expireMortalEntities(uint64_t now) {
if (now > _nextExpiry) {
PROFILE_RANGE_EX(simulation_physics, "ExpireMortals", 0xffff00ff, (uint64_t)_mortalEntities.size());
// only search for expired entities if we expect to find one
_nextExpiry = quint64(-1);
_nextExpiry = std::numeric_limits<uint64_t>::max();
QMutexLocker lock(&_mutex);
SetOfEntities::iterator itemItr = _mortalEntities.begin();
while (itemItr != _mortalEntities.end()) {
EntityItemPointer entity = *itemItr;
quint64 expiry = entity->getExpiry();
uint64_t expiry = entity->getExpiry();
if (expiry < now) {
itemItr = _mortalEntities.erase(itemItr);
entity->die();
@ -122,7 +99,7 @@ void EntitySimulation::expireMortalEntities(const quint64& now) {
}
// protected
void EntitySimulation::callUpdateOnEntitiesThatNeedIt(const quint64& now) {
void EntitySimulation::callUpdateOnEntitiesThatNeedIt(uint64_t now) {
PerformanceTimer perfTimer("updatingEntities");
QMutexLocker lock(&_mutex);
SetOfEntities::iterator itemItr = _entitiesToUpdate.begin();
@ -176,7 +153,7 @@ void EntitySimulation::addEntity(EntityItemPointer entity) {
entity->deserializeActions();
if (entity->isMortal()) {
_mortalEntities.insert(entity);
quint64 expiry = entity->getExpiry();
uint64_t expiry = entity->getExpiry();
if (expiry < _nextExpiry) {
_nextExpiry = expiry;
}
@ -207,7 +184,6 @@ void EntitySimulation::changeEntity(EntityItemPointer 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.
bool wasRemoved = false;
uint32_t dirtyFlags = entity->getDirtyFlags();
if (dirtyFlags & Simulation::DIRTY_POSITION) {
AACube domainBounds(glm::vec3((float)-HALF_TREE_SCALE), (float)TREE_SCALE);
@ -217,50 +193,45 @@ void EntitySimulation::changeEntity(EntityItemPointer entity) {
qCDebug(entities) << "Entity " << entity->getEntityItemID() << " moved out of domain bounds.";
entity->die();
prepareEntityForDelete(entity);
wasRemoved = true;
return;
}
}
if (!wasRemoved) {
if (dirtyFlags & Simulation::DIRTY_LIFETIME) {
if (entity->isMortal()) {
_mortalEntities.insert(entity);
quint64 expiry = entity->getExpiry();
if (expiry < _nextExpiry) {
_nextExpiry = expiry;
}
} else {
_mortalEntities.remove(entity);
if (dirtyFlags & Simulation::DIRTY_LIFETIME) {
if (entity->isMortal()) {
_mortalEntities.insert(entity);
uint64_t expiry = entity->getExpiry();
if (expiry < _nextExpiry) {
_nextExpiry = expiry;
}
entity->clearDirtyFlags(Simulation::DIRTY_LIFETIME);
}
if (entity->needsToCallUpdate()) {
_entitiesToUpdate.insert(entity);
} else {
_entitiesToUpdate.remove(entity);
_mortalEntities.remove(entity);
}
changeEntityInternal(entity);
entity->clearDirtyFlags(Simulation::DIRTY_LIFETIME);
}
if (entity->needsToCallUpdate()) {
_entitiesToUpdate.insert(entity);
} else {
_entitiesToUpdate.remove(entity);
}
changeEntityInternal(entity);
}
void EntitySimulation::clearEntities() {
QMutexLocker lock(&_mutex);
_mortalEntities.clear();
_nextExpiry = quint64(-1);
_nextExpiry = std::numeric_limits<uint64_t>::max();
_entitiesToUpdate.clear();
_entitiesToSort.clear();
_simpleKinematicEntities.clear();
clearEntitiesInternal();
for (auto entity : _allEntities) {
entity->setSimulated(false);
entity->die();
}
_allEntities.clear();
_entitiesToDelete.clear();
_deadEntities.clear();
}
void EntitySimulation::moveSimpleKinematics(const quint64& now) {
void EntitySimulation::moveSimpleKinematics(uint64_t now) {
PROFILE_RANGE_EX(simulation_physics, "MoveSimples", 0xffff00ff, (uint64_t)_simpleKinematicEntities.size());
SetOfEntities::iterator itemItr = _simpleKinematicEntities.begin();
while (itemItr != _simpleKinematicEntities.end()) {

View file

@ -12,6 +12,8 @@
#ifndef hifi_EntitySimulation_h
#define hifi_EntitySimulation_h
#include <limits>
#include <QtCore/QObject>
#include <QSet>
#include <QVector>
@ -43,9 +45,8 @@ const int DIRTY_SIMULATION_FLAGS =
Simulation::DIRTY_SIMULATOR_ID;
class EntitySimulation : public QObject, public std::enable_shared_from_this<EntitySimulation> {
Q_OBJECT
public:
EntitySimulation() : _mutex(QMutex::Recursive), _entityTree(NULL), _nextExpiry(quint64(-1)) { }
EntitySimulation() : _mutex(QMutex::Recursive), _entityTree(NULL), _nextExpiry(std::numeric_limits<uint64_t>::max()) { }
virtual ~EntitySimulation() { setEntityTree(NULL); }
inline EntitySimulationPointer getThisPointer() const {
@ -57,8 +58,6 @@ public:
void updateEntities();
// friend class EntityTree;
virtual void addDynamic(EntityDynamicPointer dynamic);
virtual void removeDynamic(const QUuid dynamicID);
virtual void removeDynamics(QList<QUuid> dynamicIDsToRemove);
@ -74,29 +73,26 @@ public:
void clearEntities();
void moveSimpleKinematics(const quint64& now);
void moveSimpleKinematics(uint64_t now);
EntityTreePointer getEntityTree() { return _entityTree; }
virtual void takeEntitiesToDelete(VectorOfEntities& entitiesToDelete);
virtual void takeDeadEntities(SetOfEntities& entitiesToDelete);
/// \param entity pointer to EntityItem that needs to be put on the entitiesToDelete list and removed from others.
virtual void prepareEntityForDelete(EntityItemPointer entity);
signals:
void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
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(const quint64& now) = 0;
virtual void addEntityInternal(EntityItemPointer entity);
virtual void removeEntityInternal(EntityItemPointer entity) = 0;
virtual void changeEntityInternal(EntityItemPointer entity);
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 clearEntitiesInternal() = 0;
void expireMortalEntities(const quint64& now);
void callUpdateOnEntitiesThatNeedIt(const quint64& now);
void expireMortalEntities(uint64_t now);
void callUpdateOnEntitiesThatNeedIt(uint64_t now);
virtual void sortEntitiesThatMoved();
QMutex _mutex{ QMutex::Recursive };
@ -108,7 +104,7 @@ protected:
QMutex _dynamicsMutex { QMutex::Recursive };
protected:
SetOfEntities _entitiesToDelete; // entities simulation decided needed to be deleted (EntityTree will actually delete)
SetOfEntities _deadEntities;
private:
void moveSimpleKinematics();
@ -120,11 +116,10 @@ private:
// An entity may be in more than one list.
SetOfEntities _allEntities; // tracks all entities added the simulation
SetOfEntities _mortalEntities; // entities that have an expiry
quint64 _nextExpiry;
uint64_t _nextExpiry;
SetOfEntities _entitiesToUpdate; // entities that need to call EntityItem::update()
};
#endif // hifi_EntitySimulation_h

View file

@ -94,7 +94,6 @@ OctreeElementPointer EntityTree::createNewElement(unsigned char* octalCode) {
void EntityTree::eraseAllOctreeElements(bool createNewRoot) {
emit clearingEntities();
// this would be a good place to clean up our entities...
if (_simulation) {
_simulation->clearEntities();
}
@ -260,7 +259,7 @@ void EntityTree::postAddEntity(EntityItemPointer entity) {
return;
}
}
// check to see if we need to simulate this entity..
if (_simulation) {
_simulation->addEntity(entity);
@ -425,8 +424,8 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti
if (!childEntity) {
continue;
}
EntityTreeElementPointer containingElement = childEntity->getElement();
if (!containingElement) {
EntityTreeElementPointer childContainingElement = childEntity->getElement();
if (!childContainingElement) {
continue;
}
@ -440,7 +439,7 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti
addToNeedsParentFixupList(childEntity);
}
UpdateEntityOperator theChildOperator(getThisPointer(), containingElement, childEntity, queryCube);
UpdateEntityOperator theChildOperator(getThisPointer(), childContainingElement, childEntity, queryCube);
recurseTreeWithOperator(&theChildOperator);
foreach (SpatiallyNestablePointer childChild, childEntity->getChildren()) {
if (childChild && childChild->getNestableType() == NestableType::Entity) {
@ -453,12 +452,13 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti
uint32_t newFlags = entity->getDirtyFlags() & ~preFlags;
if (newFlags) {
if (_simulation) {
if (entity->isSimulated()) {
assert((bool)_simulation);
if (newFlags & DIRTY_SIMULATION_FLAGS) {
_simulation->changeEntity(entity);
}
} else {
// normally the _simulation clears ALL updateFlags, but since there is none we do it explicitly
// normally the _simulation clears ALL dirtyFlags, but when not possible we do it explicitly
entity->clearDirtyFlags();
}
}
@ -469,7 +469,7 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti
if (entityScriptBefore != entityScriptAfter || reload) {
emitEntityScriptChanging(entity->getEntityItemID(), reload); // the entity script has changed
}
}
}
// TODO: this final containingElement check should eventually be removed (or wrapped in an #ifdef DEBUG).
if (!entity->getElement()) {
@ -551,8 +551,6 @@ void EntityTree::setSimulation(EntitySimulationPointer simulation) {
assert(simulation->getEntityTree().get() == this);
}
if (_simulation && _simulation != simulation) {
// It's important to clearEntities() on the simulation since taht will update each
// EntityItem::_simulationState correctly so as to not confuse the next _simulation.
_simulation->clearEntities();
}
_simulation = simulation;
@ -650,7 +648,7 @@ void EntityTree::deleteEntities(QSet<EntityItemID> entityIDs, bool force, bool i
emit deletingEntityPointer(existingEntity.get());
}
if (theOperator.getEntities().size() > 0) {
if (!theOperator.getEntities().empty()) {
recurseTreeWithOperator(&theOperator);
processRemovedEntities(theOperator);
_isDirty = true;
@ -692,7 +690,7 @@ void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator)
trackDeletedEntity(theEntity->getEntityItemID());
}
if (_simulation) {
if (theEntity->isSimulated()) {
_simulation->prepareEntityForDelete(theEntity);
}
}
@ -1688,7 +1686,7 @@ void EntityTree::releaseSceneEncodeData(OctreeElementExtraEncodeData* extraEncod
}
void EntityTree::entityChanged(EntityItemPointer entity) {
if (_simulation) {
if (entity->isSimulated()) {
_simulation->changeEntity(entity);
}
}
@ -1802,13 +1800,13 @@ void EntityTree::update(bool simulate) {
_simulation->updateEntities();
{
PROFILE_RANGE(simulation_physics, "Deletes");
VectorOfEntities pendingDeletes;
_simulation->takeEntitiesToDelete(pendingDeletes);
if (pendingDeletes.size() > 0) {
SetOfEntities deadEntities;
_simulation->takeDeadEntities(deadEntities);
if (!deadEntities.empty()) {
// translate into list of ID's
QSet<EntityItemID> idsToDelete;
for (auto entity : pendingDeletes) {
for (auto entity : deadEntities) {
idsToDelete.insert(entity->getEntityItemID());
}

View file

@ -267,8 +267,11 @@ void MaterialEntityItem::setOwningAvatarID(const QUuid& owningAvatarID) {
void MaterialEntityItem::removeMaterial() {
graphics::MaterialPointer material = getMaterial();
if (!material) {
return;
}
QUuid parentID = getClientOnly() ? getOwningAvatarID() : getParentID();
if (!material || parentID.isNull()) {
if (parentID.isNull()) {
return;
}
@ -336,4 +339,4 @@ void MaterialEntityItem::update(const quint64& now) {
}
EntityItem::update(now);
}
}

View file

@ -18,7 +18,7 @@
#include "EntityItem.h"
#include "EntitiesLogging.h"
const quint64 MAX_OWNERLESS_PERIOD = 2 * USECS_PER_SECOND;
const uint64_t MAX_OWNERLESS_PERIOD = 2 * USECS_PER_SECOND;
void SimpleEntitySimulation::clearOwnership(const QUuid& ownerID) {
QMutexLocker lock(&_mutex);
@ -33,7 +33,7 @@ void SimpleEntitySimulation::clearOwnership(const QUuid& ownerID) {
if (entity->getDynamic() && entity->hasLocalVelocity()) {
// it is still moving dynamically --> add to orphaned list
_entitiesThatNeedSimulationOwner.insert(entity);
quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD;
uint64_t expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD;
if (expiry < _nextOwnerlessExpiry) {
_nextOwnerlessExpiry = expiry;
}
@ -50,15 +50,15 @@ void SimpleEntitySimulation::clearOwnership(const QUuid& ownerID) {
}
}
void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) {
void SimpleEntitySimulation::updateEntitiesInternal(uint64_t now) {
if (now > _nextOwnerlessExpiry) {
// search for ownerless objects that have expired
QMutexLocker lock(&_mutex);
_nextOwnerlessExpiry = -1;
_nextOwnerlessExpiry = std::numeric_limits<uint64_t>::max();
SetOfEntities::iterator itemItr = _entitiesThatNeedSimulationOwner.begin();
while (itemItr != _entitiesThatNeedSimulationOwner.end()) {
EntityItemPointer entity = *itemItr;
quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD;
uint64_t expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD;
if (expiry < now) {
// no simulators have volunteered ownership --> remove from list
itemItr = _entitiesThatNeedSimulationOwner.erase(itemItr);
@ -85,14 +85,18 @@ void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) {
}
void SimpleEntitySimulation::addEntityInternal(EntityItemPointer entity) {
EntitySimulation::addEntityInternal(entity);
if (entity->isMovingRelativeToParent() && !entity->getPhysicsInfo()) {
QMutexLocker lock(&_mutex);
_simpleKinematicEntities.insert(entity);
entity->setLastSimulated(usecTimestampNow());
}
if (!entity->getSimulatorID().isNull()) {
QMutexLocker lock(&_mutex);
_entitiesWithSimulationOwner.insert(entity);
} else if (entity->getDynamic() && entity->hasLocalVelocity()) {
QMutexLocker lock(&_mutex);
_entitiesThatNeedSimulationOwner.insert(entity);
quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD;
uint64_t expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD;
if (expiry < _nextOwnerlessExpiry) {
_nextOwnerlessExpiry = expiry;
}
@ -101,19 +105,29 @@ void SimpleEntitySimulation::addEntityInternal(EntityItemPointer entity) {
void SimpleEntitySimulation::removeEntityInternal(EntityItemPointer entity) {
EntitySimulation::removeEntityInternal(entity);
QMutexLocker lock(&_mutex);
_entitiesWithSimulationOwner.remove(entity);
_entitiesThatNeedSimulationOwner.remove(entity);
}
void SimpleEntitySimulation::changeEntityInternal(EntityItemPointer entity) {
EntitySimulation::changeEntityInternal(entity);
{
QMutexLocker lock(&_mutex);
if (entity->isMovingRelativeToParent() && !entity->getPhysicsInfo()) {
int numKinematicEntities = _simpleKinematicEntities.size();
_simpleKinematicEntities.insert(entity);
if (numKinematicEntities != _simpleKinematicEntities.size()) {
entity->setLastSimulated(usecTimestampNow());
}
} else {
_simpleKinematicEntities.remove(entity);
}
}
if (entity->getSimulatorID().isNull()) {
QMutexLocker lock(&_mutex);
_entitiesWithSimulationOwner.remove(entity);
if (entity->getDynamic() && entity->hasLocalVelocity()) {
_entitiesThatNeedSimulationOwner.insert(entity);
quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD;
uint64_t expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD;
if (expiry < _nextOwnerlessExpiry) {
_nextOwnerlessExpiry = expiry;
}

View file

@ -28,7 +28,7 @@ public:
void clearOwnership(const QUuid& ownerID);
protected:
virtual void updateEntitiesInternal(const quint64& now) override;
virtual void updateEntitiesInternal(uint64_t now) override;
virtual void addEntityInternal(EntityItemPointer entity) override;
virtual void removeEntityInternal(EntityItemPointer entity) override;
virtual void changeEntityInternal(EntityItemPointer entity) override;
@ -38,7 +38,7 @@ protected:
SetOfEntities _entitiesWithSimulationOwner;
SetOfEntities _entitiesThatNeedSimulationOwner;
quint64 _nextOwnerlessExpiry { 0 };
uint64_t _nextOwnerlessExpiry { 0 };
};
#endif // hifi_SimpleEntitySimulation_h

View file

@ -16,9 +16,9 @@
#include <NumericalConstants.h>
const quint8 PENDING_STATE_NOTHING = 0;
const quint8 PENDING_STATE_TAKE = 1;
const quint8 PENDING_STATE_RELEASE = 2;
const uint8_t PENDING_STATE_NOTHING = 0;
const uint8_t PENDING_STATE_TAKE = 1;
const uint8_t PENDING_STATE_RELEASE = 2;
// static
const int SimulationOwner::NUM_BYTES_ENCODED = NUM_BYTES_RFC4122_UUID + 1;
@ -33,7 +33,7 @@ SimulationOwner::SimulationOwner() :
{
}
SimulationOwner::SimulationOwner(const QUuid& id, quint8 priority) :
SimulationOwner::SimulationOwner(const QUuid& id, uint8_t priority) :
_id(id),
_expiry(0),
_pendingBidTimestamp(0),
@ -67,11 +67,11 @@ void SimulationOwner::clear() {
_pendingState = PENDING_STATE_NOTHING;
}
void SimulationOwner::setPriority(quint8 priority) {
void SimulationOwner::setPriority(uint8_t priority) {
_priority = priority;
}
void SimulationOwner::promotePriority(quint8 priority) {
void SimulationOwner::promotePriority(uint8_t priority) {
if (priority > _priority) {
_priority = priority;
}
@ -89,7 +89,7 @@ bool SimulationOwner::setID(const QUuid& id) {
return false;
}
bool SimulationOwner::set(const QUuid& id, quint8 priority) {
bool SimulationOwner::set(const QUuid& id, uint8_t priority) {
uint8_t oldPriority = _priority;
setPriority(priority);
return setID(id) || oldPriority != _priority;
@ -101,22 +101,22 @@ bool SimulationOwner::set(const SimulationOwner& owner) {
return setID(owner._id) || oldPriority != _priority;
}
void SimulationOwner::setPendingPriority(quint8 priority, const quint64& timestamp) {
void SimulationOwner::setPendingPriority(uint8_t priority, uint64_t timestamp) {
_pendingBidPriority = priority;
_pendingBidTimestamp = timestamp;
_pendingState = (_pendingBidPriority == 0) ? PENDING_STATE_RELEASE : PENDING_STATE_TAKE;
}
void SimulationOwner::updateExpiry() {
const quint64 OWNERSHIP_LOCKOUT_EXPIRY = USECS_PER_SECOND / 5;
const uint64_t OWNERSHIP_LOCKOUT_EXPIRY = 200 * USECS_PER_MSEC;
_expiry = usecTimestampNow() + OWNERSHIP_LOCKOUT_EXPIRY;
}
bool SimulationOwner::pendingRelease(const quint64& timestamp) {
bool SimulationOwner::pendingRelease(uint64_t timestamp) {
return _pendingBidPriority == 0 && _pendingState == PENDING_STATE_RELEASE && _pendingBidTimestamp >= timestamp;
}
bool SimulationOwner::pendingTake(const quint64& timestamp) {
bool SimulationOwner::pendingTake(uint64_t timestamp) {
return _pendingBidPriority > 0 && _pendingState == PENDING_STATE_TAKE && _pendingBidTimestamp >= timestamp;
}
@ -142,7 +142,7 @@ void SimulationOwner::test() {
{ // test set constructor
QUuid id = QUuid::createUuid();
quint8 priority = 128;
uint8_t priority = 128;
SimulationOwner simOwner(id, priority);
if (simOwner.isNull()) {
std::cout << __FILE__ << ":" << __LINE__ << " ERROR : SimulationOwner should NOT be NULL" << std::endl;
@ -164,7 +164,7 @@ void SimulationOwner::test() {
{ // test set()
QUuid id = QUuid::createUuid();
quint8 priority = 1;
uint8_t priority = 1;
SimulationOwner simOwner;
simOwner.set(id, priority);
if (simOwner.isNull()) {

View file

@ -18,20 +18,88 @@
#include <SharedUtil.h>
#include <UUID.h>
// Simulation observers will bid to simulate unowned active objects at the lowest possible priority
// which is VOLUNTEER. If the server accepts a VOLUNTEER bid it will automatically bump it
// to RECRUIT priority so that other volunteers don't accidentally take over.
const quint8 VOLUNTEER_SIMULATION_PRIORITY = 0x01;
const quint8 RECRUIT_SIMULATION_PRIORITY = VOLUNTEER_SIMULATION_PRIORITY + 1;
// HighFidelity uses a distributed physics simulation where multiple "participants" simulate portions
// of the same world. When portions overlap only one participant is allowed to be the authority for any
// particular object. For a simulated entity the authoritative participant is called the simulation "owner" and
// their duty is to send transform/velocity updates for the entity to the central entity-server.
// The entity-server relays updates to other participants who apply them as "state synchronization"
// to their own simulation.
//
// Participants acquire ownership by sending a "bid" to the entity-server. The bid is a properties update:
// {
// "simulationOwner": { "ownerID" : sessionID, "priority" : priority },
// transform/velocity properties
// }
//
// The entity-server is the authority as to who owns what and may reject a bid.
// The rules for handling a bid are as follows:
//
// (1) A bid may be refused for special ownership restrictions, but otherwise...
//
// (2) A bid at higher priority is accepted
//
// (3) A bid at equal priority is rejected if receieved within a grace-period (200msec)
// of the last ownership transition, otherwise it is accepted
//
// (4) The current owner is the only participant allowed to clear ownership (entity-server can override).
//
// (5) The current owner is the only participant allowed to adjust priority (entity-server can override).
//
// (6) If an owner does not update the transform or velocities of an owned entity within some period
// (5 seconds) then ownership is cleared and the entity's velocities are zeroed. This to handle
// the case when an owner drops off the network.
//
// The priority of a participant's bid depends on how "interested" it is in the entity's motion. The rules
// for bidding are as follows:
//
// (7) A participant (almost) never assumes that a bid is accepted by the entity-server. It packs the
// simulation owner and priority as if they really did change but doesn't actually modify them
// locally. Thus, if the bid packet is lost the participant will re-send after some period.
// The participant only updates its knowledge of who owns what when it recieves an update from the
// entity-server. An exception is when the participant creates a moving entity: it assumes it starts
// off owning any moving entities it creates.
//
// (8) When an unowned entity becomes active in the physics simulation the participant will
// start a timer and if the entity is still unowned after some period (0.5 seconds)
// it will bid at priority = VOLUNTEER (=2). The entity-server never grants ownership at VOLUNTEER
// priority: when a VOLUNTEER bid is accepted the entity-server always promotes the priority to
// RECRUIT (VOLUNTEER + 1); this to avoid a race-condition which might rapidly transition ownership
// when multiple participants (with variable ping-times to the server) bid simultaneously for a
// recently activated entity.
//
// (9) When a participant changes an entity's transform/velocity it will bid at priority = POKE (=127)
//
// (10) When an entity touches MyAvatar the participant it will bid at priority = POKE.
//
// (11) When a participant grabs an entity it will bid at priority = GRAB (=128).
//
// (12) When entityA, locally owned at priority = N, collides with an unowned entityB the owner will
// also bid for entityB at priority = N-1 (or VOLUNTEER, whichever is larger).
//
// (13) When an entity comes to rest and is deactivated in the physics simulation the owner will
// send an update to: clear their ownerhsip, set priority to zero, and set the object's
// velocities to be zero. As per a normal bid, the owner does NOT assume that its ownership
// has been cleared until it hears from the entity-server. This, if the packet is lost the
// owner will re-send after some period.
//
// (14) When an entity's ownership priority drops below VOLUNTEER other participants may bid for it
// immediately at priority = VOLUNTEER.
//
// (15) When an entity is still active but the owner no longer wants to own it, it will drop its priority
// to YIELD (=1, less than VOLUNTEER) thereby signalling to other participants to bid for it.
//
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;
// When poking objects with scripts an observer will bid at SCRIPT_EDIT priority.
const quint8 SCRIPT_GRAB_SIMULATION_PRIORITY = 0x80;
const quint8 SCRIPT_POKE_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY - 1;
const quint8 AVATAR_ENTITY_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY + 1;
const uint8_t SCRIPT_GRAB_SIMULATION_PRIORITY = 128;
const uint8_t SCRIPT_POKE_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY - 1;
const uint8_t AVATAR_ENTITY_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY + 1;
// PERSONAL priority (needs a better name) is the level at which a simulation observer owns its own avatar
// which really just means: things that collide with it will be bid at a priority level one lower
const quint8 PERSONAL_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY;
const uint8_t PERSONAL_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY;
class SimulationOwner {
@ -39,25 +107,25 @@ public:
static const int NUM_BYTES_ENCODED;
SimulationOwner();
SimulationOwner(const QUuid& id, quint8 priority);
SimulationOwner(const QUuid& id, uint8_t priority);
const QUuid& getID() const { return _id; }
const quint64& getExpiry() const { return _expiry; }
quint8 getPriority() const { return _priority; }
const uint64_t& getExpiry() const { return _expiry; }
uint8_t getPriority() const { return _priority; }
QByteArray toByteArray() const;
bool fromByteArray(const QByteArray& data);
void clear();
void setPriority(quint8 priority);
void promotePriority(quint8 priority);
void setPriority(uint8_t priority);
void promotePriority(uint8_t priority);
// return true if id is changed
bool setID(const QUuid& id);
bool set(const QUuid& id, quint8 priority);
bool set(const QUuid& id, uint8_t priority);
bool set(const SimulationOwner& owner);
void setPendingPriority(quint8 priority, const quint64& timestamp);
void setPendingPriority(uint8_t priority, uint64_t timestamp);
bool isNull() const { return _id.isNull(); }
bool matchesValidID(const QUuid& id) const { return _id == id && !_id.isNull(); }
@ -67,11 +135,11 @@ public:
bool hasExpired() const { return usecTimestampNow() > _expiry; }
uint8_t getPendingPriority() const { return _pendingBidPriority; }
bool pendingRelease(const quint64& timestamp); // return true if valid pending RELEASE
bool pendingTake(const quint64& timestamp); // return true if valid pending TAKE
bool pendingRelease(uint64_t timestamp); // return true if valid pending RELEASE
bool pendingTake(uint64_t timestamp); // return true if valid pending TAKE
void clearCurrentOwner();
bool operator>=(quint8 priority) const { return _priority >= priority; }
bool operator>=(uint8_t priority) const { return _priority >= priority; }
bool operator==(const SimulationOwner& other) { return (_id == other._id && _priority == other._priority); }
bool operator!=(const SimulationOwner& other);
@ -84,11 +152,11 @@ public:
private:
QUuid _id; // owner
quint64 _expiry; // time when ownership can transition at equal priority
quint64 _pendingBidTimestamp; // time when pending bid was set
quint8 _priority; // priority of current owner
quint8 _pendingBidPriority; // priority at which we'd like to own it
quint8 _pendingState; // NOTHING, TAKE, or RELEASE
uint64_t _expiry; // time when ownership can transition at equal priority
uint64_t _pendingBidTimestamp; // time when pending bid was set
uint8_t _priority; // priority of current owner
uint8_t _pendingBidPriority; // priority at which we'd like to own it
uint8_t _pendingState; // NOTHING, TAKE, or RELEASE
};

View file

@ -288,7 +288,7 @@ OctreeElementPointer UpdateEntityOperator::possiblyCreateChildAt(const OctreeEle
int indexOfChildContainingNewEntity = element->getMyChildContaining(_newEntityBox);
if (childIndex == indexOfChildContainingNewEntity) {
return element->addChildAtIndex(childIndex);;
return element->addChildAtIndex(childIndex);
}
}
}

View file

@ -55,6 +55,7 @@ GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] =
(&::gpu::gl::GLBackend::do_setUniformBuffer),
(&::gpu::gl::GLBackend::do_setResourceBuffer),
(&::gpu::gl::GLBackend::do_setResourceTexture),
(&::gpu::gl::GLBackend::do_setResourceTextureTable),
(&::gpu::gl::GLBackend::do_setResourceFramebufferSwapChainTexture),
(&::gpu::gl::GLBackend::do_setFramebuffer),

View file

@ -40,7 +40,6 @@
#endif
// Let these be configured by the one define picked above
#ifdef GPU_STEREO_TECHNIQUE_DOUBLED_SIMPLE
#define GPU_STEREO_DRAWCALL_DOUBLED
@ -102,6 +101,12 @@ public:
static const int MAX_NUM_RESOURCE_TEXTURES = 16;
size_t getMaxNumResourceTextures() const { return MAX_NUM_RESOURCE_TEXTURES; }
// Texture Tables offers 2 dedicated slot (taken from the ubo slots)
static const int MAX_NUM_RESOURCE_TABLE_TEXTURES = 2;
static const int RESOURCE_TABLE_TEXTURE_SLOT_OFFSET = TRANSFORM_CAMERA_SLOT + 1;
size_t getMaxNumResourceTextureTables() const { return MAX_NUM_RESOURCE_TABLE_TEXTURES; }
// Draw Stage
virtual void do_draw(const Batch& batch, size_t paramOffset) = 0;
virtual void do_drawIndexed(const Batch& batch, size_t paramOffset) = 0;
@ -130,6 +135,7 @@ public:
// Resource Stage
virtual void do_setResourceBuffer(const Batch& batch, size_t paramOffset) final;
virtual void do_setResourceTexture(const Batch& batch, size_t paramOffset) final;
virtual void do_setResourceTextureTable(const Batch& batch, size_t paramOffset);
virtual void do_setResourceFramebufferSwapChainTexture(const Batch& batch, size_t paramOffset) final;
// Pipeline Stage
@ -230,6 +236,10 @@ protected:
void recycle() const override;
// FIXME instead of a single flag, create a features struct similar to
// https://www.khronos.org/registry/vulkan/specs/1.0/man/html/VkPhysicalDeviceFeatures.html
virtual bool supportsBindless() const { return false; }
static const size_t INVALID_OFFSET = (size_t)-1;
bool _inRenderTransferPass { false };
int32_t _uboAlignment { 0 };
@ -389,6 +399,10 @@ protected:
virtual bool bindResourceBuffer(uint32_t slot, BufferPointer& buffer) = 0;
virtual void releaseResourceBuffer(uint32_t slot) = 0;
// Helper function that provides common code used by do_setResourceTexture and
// do_setResourceTextureTable (in non-bindless mode)
void bindResourceTexture(uint32_t slot, const TexturePointer& texture);
// update resource cache and do the gl unbind call with the current gpu::Texture cached at slot s
void releaseResourceTexture(uint32_t slot);

View file

@ -1,4 +1,4 @@
//
//
// GLBackendPipeline.cpp
// libraries/gpu/src/gpu
//
@ -9,6 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "GLBackend.h"
#include <gpu/TextureTable.h>
#include "GLShared.h"
#include "GLPipeline.h"
#include "GLShader.h"
@ -247,8 +249,11 @@ void GLBackend::do_setResourceTexture(const Batch& batch, size_t paramOffset) {
return;
}
TexturePointer resourceTexture = batch._textures.get(batch._params[paramOffset + 0]._uint);
const auto& resourceTexture = batch._textures.get(batch._params[paramOffset + 0]._uint);
bindResourceTexture(slot, resourceTexture);
}
void GLBackend::bindResourceTexture(uint32_t slot, const TexturePointer& resourceTexture) {
if (!resourceTexture) {
releaseResourceTexture(slot);
return;
@ -306,6 +311,19 @@ void GLBackend::setResourceTexture(unsigned int slot, const TexturePointer& reso
}
}
void GLBackend::do_setResourceTextureTable(const Batch& batch, size_t paramOffset) {
const auto& textureTablePointer = batch._textureTables.get(batch._params[paramOffset]._uint);
if (!textureTablePointer) {
return;
}
const auto& textureTable = *textureTablePointer;
const auto& textures = textureTable.getTextures();
for (GLuint slot = 0; slot < textures.size(); ++slot) {
bindResourceTexture(slot, textures[slot]);
}
}
int GLBackend::ResourceStageState::findEmptyTextureSlot() const {
// start from the end of the slots, try to find an empty one that can be used
for (auto i = MAX_NUM_RESOURCE_TEXTURES - 1; i > 0; i--) {
@ -315,4 +333,3 @@ int GLBackend::ResourceStageState::findEmptyTextureSlot() const {
}
return -1;
}

View file

@ -78,6 +78,11 @@ R"SHADER(
#endif
};
// TextureTable specific defines
static const std::string textureTableVersion {
"#extension GL_ARB_bindless_texture : require\n#define GPU_TEXTURE_TABLE_BINDLESS\n"
};
// Versions specific of the shader
static const std::array<std::string, GLShader::NumVersions> VERSION_DEFINES { {
"",
@ -96,9 +101,9 @@ GLShader* GLBackend::compileBackendShader(const Shader& shader, const Shader::Co
auto& shaderObject = shaderObjects[version];
std::string shaderDefines = getBackendShaderHeader() + "\n"
+ (supportsBindless() ? textureTableVersion : "\n")
+ DOMAIN_DEFINES[shader.getType()] + "\n"
+ VERSION_DEFINES[version];
if (handler) {
bool retest = true;
std::string currentSrc = shaderSource;
@ -120,7 +125,7 @@ GLShader* GLBackend::compileBackendShader(const Shader& shader, const Shader::Co
compilationLogs[version].compiled = ::gl::compileShader(shaderDomain, shaderSource, shaderDefines, shaderObject.glshader, compilationLogs[version].message);
}
if (!compilationLogs[version].compiled) {
if (!compilationLogs[version].compiled) {
qCWarning(gpugllogging) << "GLBackend::compileBackendProgram - Shader didn't compile:\n" << compilationLogs[version].message.c_str();
shader.setCompilationLogs(compilationLogs);
return nullptr;

View file

@ -167,7 +167,6 @@ void GL41Texture::syncSampler() const {
glTexParameteri(_target, GL_TEXTURE_WRAP_R, WRAP_MODES[sampler.getWrapModeW()]);
glTexParameterfv(_target, GL_TEXTURE_BORDER_COLOR, (const float*)&sampler.getBorderColor());
glTexParameteri(_target, GL_TEXTURE_BASE_LEVEL, (uint16)sampler.getMipOffset());
glTexParameterf(_target, GL_TEXTURE_MIN_LOD, (float)sampler.getMinMip());
glTexParameterf(_target, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.f : sampler.getMaxMip()));
@ -206,9 +205,6 @@ void GL41FixedAllocationTexture::allocateStorage() const {
void GL41FixedAllocationTexture::syncSampler() const {
Parent::syncSampler();
const Sampler& sampler = _gpuObject.getSampler();
auto baseMip = std::max<uint16_t>(sampler.getMipOffset(), sampler.getMinMip());
glTexParameteri(_target, GL_TEXTURE_BASE_LEVEL, baseMip);
glTexParameterf(_target, GL_TEXTURE_MIN_LOD, (float)sampler.getMinMip());
glTexParameterf(_target, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.0f : sampler.getMaxMip()));
}
@ -610,4 +606,3 @@ GL41ResourceTexture::GL41ResourceTexture(const std::weak_ptr<GLBackend>& backend
GL41ResourceTexture::~GL41ResourceTexture() {
}

View file

@ -16,9 +16,11 @@
#include <gpu/gl/GLTexture.h>
#include <thread>
#include <gpu/TextureTable.h>
#define INCREMENTAL_TRANSFER 0
#define GPU_SSBO_TRANSFORM_OBJECT 1
#define GPU_BINDLESS_TEXTURES 0
namespace gpu { namespace gl45 {
@ -31,6 +33,9 @@ class GL45Backend : public GLBackend {
friend class Context;
public:
#if GPU_BINDLESS_TEXTURES
virtual bool supportsBindless() const override { return true; }
#endif
#ifdef GPU_SSBO_TRANSFORM_OBJECT
static const GLint TRANSFORM_OBJECT_SLOT { 14 }; // SSBO binding slot
@ -58,8 +63,62 @@ public:
void generateMips() const override;
Size copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum internalFormat, GLenum format, GLenum type, Size sourceSize, const void* sourcePointer) const override;
void syncSampler() const override;
#if GPU_BINDLESS_TEXTURES
bool isBindless() const {
return _bindless.operator bool();
}
struct Bindless {
uint64_t handle{ 0 };
uint32_t minMip{ 0 };
uint32_t sampler{ 0 };
bool operator==(const Bindless& other) const {
return handle == other.handle && minMip == other.minMip && sampler == other.sampler;
}
bool operator!=(const Bindless& other) const {
return !(*this == other);
}
operator bool() const {
return handle != 0;
}
};
virtual const Bindless& getBindless() const;
void releaseBindless() const;
void recreateBindless() const;
private:
mutable Bindless _bindless;
#endif
static Sampler getInvalidSampler();
// This stores the texture handle (64 bits) in xy, the min mip available in z, and the sampler ID in w
mutable Sampler _cachedSampler{ getInvalidSampler() };
};
#if GPU_BINDLESS_TEXTURES
class GL45TextureTable : public GLObject<TextureTable> {
static GLuint allocate();
using Parent = GLObject<TextureTable>;
public:
using BindlessArray = std::array<GL45Texture::Bindless, TextureTable::COUNT>;
GL45TextureTable(const std::weak_ptr<GLBackend>& backend, const TextureTable& texture);
~GL45TextureTable();
void update(const BindlessArray& newHandles);
// FIXME instead of making a buffer for each table, there should be a global buffer of all materials
// and we should store an offset into that buffer
BindlessArray _handles;
};
#endif
//
// Textures that have fixed allocation sizes and cannot be managed at runtime
//
@ -74,6 +133,7 @@ public:
protected:
Size size() const override { return _size; }
void allocateStorage() const;
void syncSampler() const override;
const Size _size { 0 };
@ -104,7 +164,6 @@ public:
friend class GL45Backend;
using PromoteLambda = std::function<void()>;
protected:
GL45VariableAllocationTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture);
~GL45VariableAllocationTexture();
@ -114,6 +173,9 @@ public:
Size copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum internalFormat, GLenum format, GLenum type, Size sourceSize, const void* sourcePointer) const override;
void copyTextureMipsInGPUMem(GLuint srcId, GLuint destId, uint16_t srcMipOffset, uint16_t destMipOffset, uint16_t populatedMips) override;
#if GPU_BINDLESS_TEXTURES
virtual const Bindless& getBindless() const override;
#endif
};
class GL45ResourceTexture : public GL45VariableAllocationTexture {
@ -182,6 +244,7 @@ protected:
GLuint getQueryID(const QueryPointer& query) override;
GLQuery* syncGPUObject(const Query& query) override;
// Draw Stage
void do_draw(const Batch& batch, size_t paramOffset) override;
void do_drawIndexed(const Batch& batch, size_t paramOffset) override;
@ -213,6 +276,12 @@ protected:
// Texture Management Stage
void initTextureManagementStage() override;
#if GPU_BINDLESS_TEXTURES
GL45TextureTable* syncGPUObject(const TextureTablePointer& textureTable);
// Resource stage
void do_setResourceTextureTable(const Batch& batch, size_t paramOffset) override;
#endif
};
} }

View file

@ -165,6 +165,11 @@ void GL45Backend::makeProgramBindings(ShaderObject& shaderObject) {
shaderObject.transformCameraSlot = gpu::TRANSFORM_CAMERA_SLOT;
}
loc = glGetUniformBlockIndex(glprogram, "gpu_resourceTextureTable0");
if (loc >= 0) {
glUniformBlockBinding(glprogram, loc, RESOURCE_TABLE_TEXTURE_SLOT_OFFSET);
}
(void)CHECK_GL_ERROR();
}

View file

@ -20,14 +20,17 @@
#include <QtCore/QDebug>
#include <NumericalConstants.h>
#include <gl/Context.h>
#include <gpu/TextureTable.h>
#include <gpu/gl/GLTexelFormat.h>
using namespace gpu;
using namespace gpu::gl;
using namespace gpu::gl45;
#define SPARSE_PAGE_SIZE_OVERHEAD_ESTIMATE 1.3f
#define MAX_RESOURCE_TEXTURES_PER_FRAME 2
#define FORCE_STRICT_TEXTURE 0
#define ENABLE_SPARSE_TEXTURE 0
GLTexture* GL45Backend::syncGPUObject(const TexturePointer& texturePointer) {
if (!texturePointer) {
@ -51,14 +54,18 @@ GLTexture* GL45Backend::syncGPUObject(const TexturePointer& texturePointer) {
object = new GL45AttachmentTexture(shared_from_this(), texture);
break;
#if FORCE_STRICT_TEXTURE
case TextureUsageType::RESOURCE:
#endif
case TextureUsageType::STRICT_RESOURCE:
qCDebug(gpugllogging) << "Strict texture " << texture.source().c_str();
object = new GL45StrictResourceTexture(shared_from_this(), texture);
break;
#if !FORCE_STRICT_TEXTURE
case TextureUsageType::RESOURCE: {
if (GL45VariableAllocationTexture::_frameTexturesCreated < MAX_RESOURCE_TEXTURES_PER_FRAME) {
#if 0
#if ENABLE_SPARSE_TEXTURE
if (isTextureManagementSparseEnabled() && GL45Texture::isSparseEligible(texture)) {
object = new GL45SparseResourceTexture(shared_from_this(), texture);
} else {
@ -76,7 +83,7 @@ GLTexture* GL45Backend::syncGPUObject(const TexturePointer& texturePointer) {
}
break;
}
#endif
default:
Q_UNREACHABLE();
}
@ -114,6 +121,61 @@ void GL45Backend::initTextureManagementStage() {
using GL45Texture = GL45Backend::GL45Texture;
class GLSamplerCache {
public:
GLuint getGLSampler(const Sampler& sampler) {
if (0 == _samplerCache.count(sampler)) {
GLuint result = 0;
glGenSamplers(1, &result);
const auto& fm = GLTexture::FILTER_MODES[sampler.getFilter()];
glSamplerParameteri(result, GL_TEXTURE_MIN_FILTER, fm.minFilter);
glSamplerParameteri(result, GL_TEXTURE_MAG_FILTER, fm.magFilter);
if (sampler.doComparison()) {
glSamplerParameteri(result, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE_ARB);
glSamplerParameteri(result, GL_TEXTURE_COMPARE_FUNC, COMPARISON_TO_GL[sampler.getComparisonFunction()]);
} else {
glSamplerParameteri(result, GL_TEXTURE_COMPARE_MODE, GL_NONE);
}
glSamplerParameteri(result, GL_TEXTURE_WRAP_S, GLTexture::WRAP_MODES[sampler.getWrapModeU()]);
glSamplerParameteri(result, GL_TEXTURE_WRAP_T, GLTexture::WRAP_MODES[sampler.getWrapModeV()]);
glSamplerParameteri(result, GL_TEXTURE_WRAP_R, GLTexture::WRAP_MODES[sampler.getWrapModeW()]);
glSamplerParameterf(result, GL_TEXTURE_MAX_ANISOTROPY_EXT, sampler.getMaxAnisotropy());
glSamplerParameterfv(result, GL_TEXTURE_BORDER_COLOR, (const float*)&sampler.getBorderColor());
glSamplerParameterf(result, GL_TEXTURE_MIN_LOD, sampler.getMinMip());
glSamplerParameterf(result, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.f : sampler.getMaxMip()));
_samplerCache[sampler] = result;
return result;
}
return _samplerCache[sampler];
}
void releaseGLSampler(GLuint sampler) {
// NO OP
}
private:
std::unordered_map<Sampler, GLuint> _samplerCache;
};
static GLSamplerCache SAMPLER_CACHE;
Sampler GL45Texture::getInvalidSampler() {
static Sampler INVALID_SAMPLER;
static std::once_flag once;
std::call_once(once, [] {
Sampler::Desc invalidDesc;
invalidDesc._borderColor = vec4(-1.0f);
INVALID_SAMPLER = Sampler(invalidDesc);
});
return INVALID_SAMPLER;
}
GL45Texture::GL45Texture(const std::weak_ptr<GLBackend>& backend, const Texture& texture)
: GLTexture(backend, texture, allocate(texture)) {
}
@ -121,10 +183,10 @@ GL45Texture::GL45Texture(const std::weak_ptr<GLBackend>& backend, const Texture&
GLuint GL45Texture::allocate(const Texture& texture) {
GLuint result;
glCreateTextures(getGLTextureType(texture), 1, &result);
#ifdef DEBUG
auto source = texture.source();
glObjectLabel(GL_TEXTURE, result, (GLsizei)source.length(), source.data());
#endif
if (::gl::Context::enableDebugLogger()) {
auto source = texture.source();
glObjectLabel(GL_TEXTURE, result, (GLsizei)source.length(), source.data());
}
return result;
}
@ -189,8 +251,50 @@ Size GL45Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const
return amountCopied;
}
#if GPU_BINDLESS_TEXTURES
void GL45Texture::releaseBindless() const {
// Release the old handler
SAMPLER_CACHE.releaseGLSampler(_bindless.sampler);
glMakeTextureHandleNonResidentARB(_bindless.handle);
_bindless = Bindless();
}
void GL45Texture::recreateBindless() const {
if (isBindless()) {
releaseBindless();
} else {
// Once a texture is about to become bindless, it's base mip level MUST be set to 0
glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, 0);
}
_bindless.sampler = SAMPLER_CACHE.getGLSampler(_cachedSampler);
_bindless.handle = glGetTextureSamplerHandleARB(_id, _bindless.sampler);
glMakeTextureHandleResidentARB(_bindless.handle);
}
const GL45Texture::Bindless& GL45Texture::getBindless() const {
if (!_bindless) {
recreateBindless();
}
return _bindless;
}
#endif
void GL45Texture::syncSampler() const {
const Sampler& sampler = _gpuObject.getSampler();
if (_cachedSampler == sampler) {
return;
}
_cachedSampler = sampler;
#if GPU_BINDLESS_TEXTURES
if (isBindless()) {
recreateBindless();
return;
}
#endif
const auto& fm = FILTER_MODES[sampler.getFilter()];
glTextureParameteri(_id, GL_TEXTURE_MIN_FILTER, fm.minFilter);
@ -214,6 +318,8 @@ void GL45Texture::syncSampler() const {
glTextureParameterf(_id, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.f : sampler.getMaxMip()));
}
// Fixed allocation textures, used for strict resources & framebuffer attachments
using GL45FixedAllocationTexture = GL45Backend::GL45FixedAllocationTexture;
GL45FixedAllocationTexture::GL45FixedAllocationTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture) : GL45Texture(backend, texture), _size(texture.evalTotalSize()) {
@ -238,8 +344,6 @@ void GL45FixedAllocationTexture::allocateStorage() const {
void GL45FixedAllocationTexture::syncSampler() const {
Parent::syncSampler();
const Sampler& sampler = _gpuObject.getSampler();
auto baseMip = std::max<uint16_t>(sampler.getMipOffset(), sampler.getMinMip());
glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, baseMip);
glTextureParameterf(_id, GL_TEXTURE_MIN_LOD, (float)sampler.getMinMip());
glTextureParameterf(_id, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.f : sampler.getMaxMip()));
}
@ -275,6 +379,9 @@ GL45StrictResourceTexture::GL45StrictResourceTexture(const std::weak_ptr<GLBacke
if (texture.isAutogenerateMips()) {
generateMips();
}
// Re-sync the sampler to force access to the new mip level
syncSampler();
}
GL45StrictResourceTexture::~GL45StrictResourceTexture() {
@ -282,3 +389,87 @@ GL45StrictResourceTexture::~GL45StrictResourceTexture() {
Backend::textureResidentGPUMemSize.update(size(), 0);
}
// Encapsulate bindless textures
#if GPU_BINDLESS_TEXTURES
using GL45TextureTable = GL45Backend::GL45TextureTable;
GLuint GL45TextureTable::allocate() {
GLuint result;
glCreateBuffers(1, &result);
return result;
}
GL45TextureTable::GL45TextureTable(const std::weak_ptr<GLBackend>& backend, const TextureTable& textureTable)
: Parent(backend, textureTable, allocate()){
Backend::setGPUObject(textureTable, this);
// FIXME include these in overall buffer storage reporting
glNamedBufferStorage(_id, sizeof(uvec4) * TextureTable::COUNT, nullptr, GL_DYNAMIC_STORAGE_BIT);
}
void GL45TextureTable::update(const BindlessArray& handles) {
if (_handles != handles) {
_handles = handles;
// FIXME include these in overall buffer storage reporting
// FIXME use a single shared buffer for bindless data
glNamedBufferSubData(_id, 0, sizeof(GL45Texture::Bindless) * TextureTable::COUNT, &_handles[0]);
}
}
GL45TextureTable::~GL45TextureTable() {
if (_id) {
auto backend = _backend.lock();
if (backend) {
// FIXME include these in overall buffer storage reporting
backend->releaseBuffer(_id, 0);
}
}
}
GL45TextureTable* GL45Backend::syncGPUObject(const TextureTablePointer& textureTablePointer) {
const auto& textureTable = *textureTablePointer;
// Find the target handles
auto defaultTextures = gpu::TextureTable::getDefault()->getTextures();
auto textures = textureTable.getTextures();
GL45TextureTable::BindlessArray handles{};
for (size_t i = 0; i < textures.size(); ++i) {
auto texture = textures[i];
if (!texture) {
texture = defaultTextures[i];
}
if (!texture) {
continue;
}
// FIXME what if we have a non-transferrable texture here?
auto gltexture = (GL45Texture*)syncGPUObject(texture);
if (!gltexture) {
continue;
}
handles[i] = gltexture->getBindless();
}
// If the object hasn't been created, or the object definition is out of date, drop and re-create
GL45TextureTable* object = Backend::getGPUObject<GL45TextureTable>(textureTable);
if (!object) {
object = new GL45TextureTable(shared_from_this(), textureTable);
}
object->update(handles);
return object;
}
void GL45Backend::do_setResourceTextureTable(const Batch& batch, size_t paramOffset) {
auto textureTablePointer = batch._textureTables.get(batch._params[paramOffset]._uint);
if (!textureTablePointer) {
return;
}
auto slot = batch._params[paramOffset + 1]._uint;
GL45TextureTable* glTextureTable = syncGPUObject(textureTablePointer);
if (glTextureTable) {
glBindBufferBase(GL_UNIFORM_BUFFER, slot + GLBackend::RESOURCE_TABLE_TEXTURE_SLOT_OFFSET, glTextureTable->_id);
}
}
#endif

View file

@ -27,6 +27,7 @@ using namespace gpu;
using namespace gpu::gl;
using namespace gpu::gl45;
using GL45Texture = GL45Backend::GL45Texture;
using GL45VariableAllocationTexture = GL45Backend::GL45VariableAllocationTexture;
GL45VariableAllocationTexture::GL45VariableAllocationTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture) : GL45Texture(backend, texture) {
@ -40,6 +41,17 @@ GL45VariableAllocationTexture::~GL45VariableAllocationTexture() {
Backend::textureResourcePopulatedGPUMemSize.update(_populatedSize, 0);
}
#if GPU_BINDLESS_TEXTURES
const GL45Texture::Bindless& GL45VariableAllocationTexture::getBindless() const {
// The parent call may re-initialize the _bindless member, so we need to call it first
const auto& result = Parent::getBindless();
// Make sure the referenced structure has the correct minimum available mip value
_bindless.minMip = _populatedMip - _allocatedMip;
// Now return the result
return result;
}
#endif
Size GL45VariableAllocationTexture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum internalFormat, GLenum format, GLenum type, Size sourceSize, const void* sourcePointer) const {
Size amountCopied = 0;
amountCopied = Parent::copyMipFaceLinesFromTexture(mip, face, size, yOffset, internalFormat, format, type, sourceSize, sourcePointer);
@ -127,7 +139,13 @@ Size GL45ResourceTexture::copyMipsFromTexture() {
void GL45ResourceTexture::syncSampler() const {
Parent::syncSampler();
#if GPU_BINDLESS_TEXTURES
if (!isBindless()) {
glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, _populatedMip - _allocatedMip);
}
#else
glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, _populatedMip - _allocatedMip);
#endif
}
void GL45ResourceTexture::promote() {
@ -137,6 +155,13 @@ void GL45ResourceTexture::promote() {
uint16_t targetAllocatedMip = _allocatedMip - std::min<uint16_t>(_allocatedMip, 2);
targetAllocatedMip = std::max<uint16_t>(_minAllocatedMip, targetAllocatedMip);
#if GPU_BINDLESS_TEXTURES
bool bindless = isBindless();
if (bindless) {
releaseBindless();
}
#endif
GLuint oldId = _id;
auto oldSize = _size;
uint16_t oldAllocatedMip = _allocatedMip;
@ -150,10 +175,17 @@ void GL45ResourceTexture::promote() {
// copy pre-existing mips
copyTextureMipsInGPUMem(oldId, _id, oldAllocatedMip, _allocatedMip, _populatedMip);
#if GPU_BINDLESS_TEXTURES
if (bindless) {
getBindless();
}
#endif
// destroy the old texture
glDeleteTextures(1, &oldId);
// Update sampler
_cachedSampler = getInvalidSampler();
syncSampler();
// update the memory usage
@ -170,6 +202,13 @@ void GL45ResourceTexture::demote() {
auto oldSize = _size;
auto oldPopulatedMip = _populatedMip;
#if GPU_BINDLESS_TEXTURES
bool bindless = isBindless();
if (bindless) {
releaseBindless();
}
#endif
// allocate new texture
const_cast<GLuint&>(_id) = allocate(_gpuObject);
uint16_t oldAllocatedMip = _allocatedMip;
@ -179,10 +218,17 @@ void GL45ResourceTexture::demote() {
// copy pre-existing mips
copyTextureMipsInGPUMem(oldId, _id, oldAllocatedMip, _allocatedMip, _populatedMip);
#if GPU_BINDLESS_TEXTURES
if (bindless) {
getBindless();
}
#endif
// destroy the old texture
glDeleteTextures(1, &oldId);
// Update sampler
_cachedSampler = getInvalidSampler();
syncSampler();
// update the memory usage

View file

@ -75,6 +75,7 @@ Batch::Batch(const Batch& batch_) {
_buffers._items.swap(batch._buffers._items);
_textures._items.swap(batch._textures._items);
_textureTables._items.swap(batch._textureTables._items);
_streamFormats._items.swap(batch._streamFormats._items);
_transforms._items.swap(batch._transforms._items);
_pipelines._items.swap(batch._pipelines._items);
@ -113,6 +114,7 @@ void Batch::clear() {
_data.clear();
_buffers.clear();
_textures.clear();
_textureTables.clear();
_streamFormats.clear();
_transforms.clear();
_pipelines.clear();
@ -337,6 +339,12 @@ void Batch::setResourceTexture(uint32 slot, const TextureView& view) {
setResourceTexture(slot, view._texture);
}
void Batch::setResourceTextureTable(const TextureTablePointer& textureTable, uint32 slot) {
ADD_COMMAND(setResourceTextureTable);
_params.emplace_back(_textureTables.cache(textureTable));
_params.emplace_back(slot);
}
void Batch::setResourceFramebufferSwapChainTexture(uint32 slot, const FramebufferSwapChainPointer& framebuffer, unsigned int swapChainIndex, unsigned int renderBufferSlot) {
ADD_COMMAND(setResourceFramebufferSwapChainTexture);

View file

@ -187,6 +187,7 @@ public:
void setResourceTexture(uint32 slot, const TexturePointer& texture);
void setResourceTexture(uint32 slot, const TextureView& view); // not a command, just a shortcut from a TextureView
void setResourceTextureTable(const TextureTablePointer& table, uint32 slot = 0);
void setResourceFramebufferSwapChainTexture(uint32 slot, const FramebufferSwapChainPointer& framebuffer, unsigned int swpaChainIndex, unsigned int renderBufferSlot = 0U); // not a command, just a shortcut from a TextureView
// Ouput Stage
@ -302,6 +303,7 @@ public:
COMMAND_setUniformBuffer,
COMMAND_setResourceBuffer,
COMMAND_setResourceTexture,
COMMAND_setResourceTextureTable,
COMMAND_setResourceFramebufferSwapChainTexture,
COMMAND_setFramebuffer,
@ -409,9 +411,10 @@ public:
return offset;
}
Data get(uint32 offset) const {
const Data& get(uint32 offset) const {
if (offset >= _items.size()) {
return Data();
static const Data EMPTY;
return EMPTY;
}
return (_items.data() + offset)->_data;
}
@ -424,6 +427,7 @@ public:
typedef Cache<BufferPointer>::Vector BufferCaches;
typedef Cache<TexturePointer>::Vector TextureCaches;
typedef Cache<TextureTablePointer>::Vector TextureTableCaches;
typedef Cache<Stream::FormatPointer>::Vector StreamFormatCaches;
typedef Cache<Transform>::Vector TransformCaches;
typedef Cache<PipelinePointer>::Vector PipelineCaches;
@ -479,6 +483,7 @@ public:
BufferCaches _buffers;
TextureCaches _textures;
TextureTableCaches _textureTables;
StreamFormatCaches _streamFormats;
TransformCaches _transforms;
PipelineCaches _pipelines;

View file

@ -91,6 +91,8 @@ namespace gpu {
using Textures = std::vector<TexturePointer>;
class TextureView;
using TextureViews = std::vector<TextureView>;
class TextureTable;
using TextureTablePointer = std::shared_ptr<TextureTable>;
struct StereoState {
bool isStereo() const {

View file

@ -17,8 +17,10 @@
#include <QMetaType>
#include <QUrl>
#include <functional>
#include <shared/Storage.h>
#include <shared/FileCache.h>
#include <RegisteredMetaTypes.h>
#include "Forward.h"
#include "Resource.h"
#include "Metric.h"
@ -126,12 +128,23 @@ public:
uint8 _wrapModeV = WRAP_REPEAT;
uint8 _wrapModeW = WRAP_REPEAT;
uint8 _mipOffset = 0;
uint8 _minMip = 0;
uint8 _maxMip = MAX_MIP_LEVEL;
Desc() {}
Desc(const Filter filter, const WrapMode wrap = WRAP_REPEAT) : _filter(filter), _wrapModeU(wrap), _wrapModeV(wrap), _wrapModeW(wrap) {}
bool operator==(const Desc& other) const {
return _borderColor == other._borderColor &&
_maxAnisotropy == other._maxAnisotropy &&
_filter == other._filter &&
_comparisonFunc == other._comparisonFunc &&
_wrapModeU == other._wrapModeU &&
_wrapModeV == other._wrapModeV &&
_wrapModeW == other._wrapModeW &&
_minMip == other._minMip &&
_maxMip == other._maxMip;
}
};
Sampler() {}
@ -151,11 +164,17 @@ public:
ComparisonFunction getComparisonFunction() const { return ComparisonFunction(_desc._comparisonFunc); }
bool doComparison() const { return getComparisonFunction() != ALWAYS; }
uint8 getMipOffset() const { return _desc._mipOffset; }
uint8 getMinMip() const { return _desc._minMip; }
uint8 getMaxMip() const { return _desc._maxMip; }
const Desc& getDesc() const { return _desc; }
bool operator==(const Sampler& other) const {
return _desc == other._desc;
}
bool operator!=(const Sampler& other) const {
return !(*this == other);
}
protected:
Desc _desc;
};
@ -666,6 +685,17 @@ typedef std::shared_ptr< TextureSource > TextureSourcePointer;
};
namespace std {
template<> struct hash<gpu::Sampler> {
size_t operator()(const gpu::Sampler& sampler) const noexcept {
size_t result = 0;
const auto& desc = sampler.getDesc();
hash_combine(result, desc._comparisonFunc, desc._filter, desc._maxAnisotropy, desc._maxMip, desc._minMip, desc._wrapModeU, desc._wrapModeV, desc._wrapModeW);
return result;
}
};
}
Q_DECLARE_METATYPE(gpu::TexturePointer)
#endif

View file

@ -0,0 +1,54 @@
//
// Created by Bradley Austin Davis on 2017/01/25
// Copyright 2013-2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "TextureTable.h"
#include "Texture.h"
#include <shared/GlobalAppProperties.h>
using namespace gpu;
const size_t TextureTable::COUNT{ TEXTURE_TABLE_COUNT };
TextureTable::TextureTable() { }
TextureTable::TextureTable(const std::initializer_list<TexturePointer>& textures) {
auto max = std::min<size_t>(COUNT, textures.size());
auto itr = textures.begin();
size_t index = 0;
while (itr != textures.end() && index < max) {
setTexture(index, *itr);
++index;
}
}
TextureTable::TextureTable(const Array& textures) : _textures(textures) {
}
void TextureTable::setTexture(size_t index, const TexturePointer& texturePointer) {
if (index >= COUNT || _textures[index] == texturePointer) {
return;
}
{
Lock lock(_mutex);
++_stamp;
_textures[index] = texturePointer;
}
}
void TextureTable::setTexture(size_t index, const TextureView& textureView) {
setTexture(index, textureView._texture);
}
TextureTable::Array TextureTable::getTextures() const {
Array result;
{
Lock lock(_mutex);
result = _textures;
}
return result;
}

View file

@ -0,0 +1,44 @@
//
// Created by Bradley Austin Davis on 2017/01/25
// Copyright 2013-2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_gpu_TextureTable_h
#define hifi_gpu_TextureTable_h
#include "Forward.h"
#include <array>
#define TEXTURE_TABLE_COUNT 8
namespace gpu {
class TextureTable {
public:
static const size_t COUNT;
using Array = std::array<TexturePointer, TEXTURE_TABLE_COUNT>;
TextureTable();
TextureTable(const std::initializer_list<TexturePointer>& textures);
TextureTable(const Array& textures);
// Only for gpu::Context
const GPUObjectPointer gpuObject{};
void setTexture(size_t index, const TexturePointer& texturePointer);
void setTexture(size_t index, const TextureView& texturePointer);
Array getTextures() const;
Stamp getStamp() const { return _stamp; }
private:
mutable Mutex _mutex;
Array _textures;
Stamp _stamp{ 1 };
};
}
#endif

View file

@ -0,0 +1,38 @@
<!
// gpu/TextureTable.slh
//
// Created by Sam Gateau on 1/25/17.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
!>
<@if not GPU_TEXTURE_TABLE_SLH@>
<@def GPU_TEXTURE_TABLE_SLH@>
#ifdef GPU_TEXTURE_TABLE_BINDLESS
#define GPU_TEXTURE_TABLE_MAX_NUM_TEXTURES 8
struct GPUTextureTable {
uvec4 _textures[GPU_TEXTURE_TABLE_MAX_NUM_TEXTURES];
};
#define TextureTable(index, name) layout (std140) uniform gpu_resourceTextureTable##index { GPUTextureTable name; }
#define tableTex(name, slot) sampler2D(name._textures[slot].xy)
#define tableTexMinLod(name, slot) float(name._textures[slot].z)
#define tableTexValue(name, slot, uv) \
tableTexValueLod(tableTex(matTex, albedoMap), tableTexMinLod(matTex, albedoMap), uv)
vec4 tableTexValueLod(sampler2D sampler, float minLod, vec2 uv) {
float queryLod = textureQueryLod(sampler, uv).x;
queryLod = max(minLod, queryLod);
return textureLod(sampler, uv, queryLod);
}
#else
#endif
<@endif@>

View file

@ -504,6 +504,8 @@ TexturePointer Texture::build(const ktx::KTXDescriptor& descriptor) {
gpuktxKeyValue._usage = Texture::Usage::Builder().withColor().withAlpha().build();
}
auto samplerDesc = gpuktxKeyValue._samplerDesc;
samplerDesc._maxMip = gpu::Sampler::MAX_MIP_LEVEL;
auto texture = create(gpuktxKeyValue._usageType,
type,
texelFormat,
@ -513,7 +515,7 @@ TexturePointer Texture::build(const ktx::KTXDescriptor& descriptor) {
1, // num Samples
header.getNumberOfSlices(),
header.getNumberOfLevels(),
gpuktxKeyValue._samplerDesc);
samplerDesc);
texture->setUsage(gpuktxKeyValue._usage);
// Assing the mips availables

View file

@ -20,6 +20,7 @@
#include <ColorUtils.h>
#include <gpu/Resource.h>
#include <gpu/TextureTable.h>
class Transform;
@ -361,6 +362,8 @@ public:
const std::string& getModel() const { return _model; }
void setModel(const std::string& model) { _model = model; }
const gpu::TextureTablePointer& getTextureTable() const { return _textureTable; }
protected:
std::string _name { "" };
@ -368,6 +371,7 @@ private:
mutable MaterialKey _key;
mutable UniformBufferView _schemaBuffer;
mutable UniformBufferView _texMapArrayBuffer;
mutable gpu::TextureTablePointer _textureTable{ std::make_shared<gpu::TextureTable>() };
TextureMaps _textureMaps;

View file

@ -77,6 +77,17 @@ QUrl AddressManager::currentShareableAddress(bool domainOnly) const {
}
}
QUrl AddressManager::currentPublicAddress(bool domainOnly) const {
// return an address that can be used by others to visit this client's current location. If
// in a serverless domain (which can't be visited) return an empty URL.
QUrl shareableAddress = currentShareableAddress(domainOnly);
if (shareableAddress.scheme() != URL_SCHEME_HIFI) {
return QUrl(); // file: urls aren't public
}
return shareableAddress;
}
QUrl AddressManager::currentFacingShareableAddress() const {
auto hifiURL = currentShareableAddress();
if (hifiURL.scheme() == URL_SCHEME_HIFI) {
@ -86,6 +97,17 @@ QUrl AddressManager::currentFacingShareableAddress() const {
return hifiURL;
}
QUrl AddressManager::currentFacingPublicAddress() const {
// return an address that can be used by others to visit this client's current location. If
// in a serverless domain (which can't be visited) return an empty URL.
QUrl shareableAddress = currentFacingShareableAddress();
if (shareableAddress.scheme() != URL_SCHEME_HIFI) {
return QUrl(); // file: urls aren't public
}
return shareableAddress;
}
void AddressManager::loadSettings(const QString& lookupString) {
#if defined(USE_GLES) && defined(Q_OS_WIN)
handleUrl(QUrl("hifi://127.0.0.0"), LookupTrigger::StartupFromSettings);
@ -288,6 +310,7 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) {
// lookupUrl.scheme() == URL_SCHEME_HTTP ||
// lookupUrl.scheme() == URL_SCHEME_HTTPS ||
_previousLookup.clear();
_shareablePlaceName.clear();
QUrl domainURL = PathUtils::expandToLocalDataAbsolutePath(lookupUrl);
setDomainInfo(domainURL, trigger);
emit lookupResultsFinished();

View file

@ -150,7 +150,9 @@ public:
QUrl currentAddress(bool domainOnly = false) const;
QUrl currentFacingAddress() const;
QUrl currentShareableAddress(bool domainOnly = false) const;
QUrl currentPublicAddress(bool domainOnly = false) const;
QUrl currentFacingShareableAddress() const;
QUrl currentFacingPublicAddress() const;
QString currentPath(bool withOrientation = true) const;
QString currentFacingPath() const;

View file

@ -192,17 +192,12 @@ bool OctreePersistThread::process() {
QString lockFileName = _filename + ".lock";
std::ifstream lockFile(qPrintable(lockFileName), std::ios::in | std::ios::binary | std::ios::ate);
if (lockFile.is_open()) {
qCDebug(octree) << "WARNING: Octree lock file detected at startup:" << lockFileName
<< "-- Attempting to restore from previous backup file.";
// This is where we should attempt to find the most recent backup and restore from
// that file as our persist file.
restoreFromMostRecentBackup();
qCDebug(octree) << "WARNING: Octree lock file detected at startup:" << lockFileName;
lockFile.close();
qCDebug(octree) << "Loading Octree... lock file closed:" << lockFileName;
qCDebug(octree) << "Removing lock file:" << lockFileName;
remove(qPrintable(lockFileName));
qCDebug(octree) << "Loading Octree... lock file removed:" << lockFileName;
qCDebug(octree) << "Lock file removed:" << lockFileName;
}
persistentFileRead = _tree->readFromFile(qPrintable(_filename.toLocal8Bit()));

View file

@ -26,12 +26,7 @@
#ifdef WANT_DEBUG_ENTITY_TREE_LOCKS
#include "EntityTree.h"
#endif
const uint8_t LOOPS_FOR_SIMULATION_ORPHAN = 50;
const quint64 USECS_BETWEEN_OWNERSHIP_BIDS = USECS_PER_SECOND / 5;
#ifdef WANT_DEBUG_ENTITY_TREE_LOCKS
bool EntityMotionState::entityTreeIsLocked() const {
EntityTreeElementPointer element = _entity->getElement();
EntityTreePointer tree = element ? element->getTree() : nullptr;
@ -46,11 +41,13 @@ bool entityTreeIsLocked() {
}
#endif
const uint8_t LOOPS_FOR_SIMULATION_ORPHAN = 50;
const quint64 USECS_BETWEEN_OWNERSHIP_BIDS = USECS_PER_SECOND / 5;
EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer entity) :
ObjectMotionState(nullptr),
_entityPtr(entity),
_entity(entity.get()),
_entity(entity),
_serverPosition(0.0f),
_serverRotation(),
_serverVelocity(0.0f),
@ -60,7 +57,7 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer
_serverActionData(QByteArray()),
_lastVelocity(0.0f),
_measuredAcceleration(0.0f),
_nextOwnershipBid(0),
_nextBidExpiry(0),
_measuredDeltaTime(0.0f),
_lastMeasureStep(0),
_lastStep(0),
@ -68,6 +65,12 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer
_accelerationNearlyGravityCount(0),
_numInactiveUpdates(1)
{
// Why is _numInactiveUpdates initialied to 1?
// Because: when an entity is first created by a LOCAL operatioin its local simulation ownership is assumed,
// which causes it to be immediately placed on the 'owned' list, but in this case an "update" already just
// went out for the object's creation and there is no need to send another. By initializing _numInactiveUpdates
// to 1 here we trick remoteSimulationOutOfSync() to return "false" the first time through for this case.
_type = MOTIONSTATE_TYPE_ENTITY;
assert(_entity);
assert(entityTreeIsLocked());
@ -76,77 +79,89 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer
// rather than pass the legit shape pointer to the ObjectMotionState ctor above.
setShape(shape);
_outgoingPriority = _entity->getPendingOwnershipPriority();
}
EntityMotionState::~EntityMotionState() {
assert(_entity);
_entity = nullptr;
}
void EntityMotionState::updateServerPhysicsVariables() {
assert(entityTreeIsLocked());
if (isLocallyOwned()) {
// don't slam these values if we are the simulation owner
return;
_bidPriority = _entity->getPendingOwnershipPriority();
if (_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID()) {
// client-only entities are always thus, so we cache this fact in _ownershipState
_ownershipState = EntityMotionState::OwnershipState::Unownable;
}
Transform localTransform;
_entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity);
_serverVariablesSet = true;
_serverPosition = localTransform.getTranslation();
_serverRotation = localTransform.getRotation();
_serverAcceleration = _entity->getAcceleration();
_serverActionData = _entity->getDynamicData();
}
EntityMotionState::~EntityMotionState() {
if (_entity) {
assert(_entity->getPhysicsInfo() == this);
_entity->setPhysicsInfo(nullptr);
_entity.reset();
}
}
void EntityMotionState::updateServerPhysicsVariables() {
if (_ownershipState != EntityMotionState::OwnershipState::LocallyOwned) {
// only slam these values if we are NOT the simulation owner
Transform localTransform;
_entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity);
_serverPosition = localTransform.getTranslation();
_serverRotation = localTransform.getRotation();
_serverAcceleration = _entity->getAcceleration();
_serverActionData = _entity->getDynamicData();
_lastStep = ObjectMotionState::getWorldSimulationStep();
}
}
void EntityMotionState::handleDeactivation() {
if (_serverVariablesSet) {
// copy _server data to entity
Transform localTransform = _entity->getLocalTransform();
localTransform.setTranslation(_serverPosition);
localTransform.setRotation(_serverRotation);
_entity->setLocalTransformAndVelocities(localTransform, ENTITY_ITEM_ZERO_VEC3, ENTITY_ITEM_ZERO_VEC3);
// and also to RigidBody
btTransform worldTrans;
worldTrans.setOrigin(glmToBullet(_entity->getWorldPosition()));
worldTrans.setRotation(glmToBullet(_entity->getWorldOrientation()));
_body->setWorldTransform(worldTrans);
// no need to update velocities... should already be zero
}
// copy _server data to entity
Transform localTransform = _entity->getLocalTransform();
localTransform.setTranslation(_serverPosition);
localTransform.setRotation(_serverRotation);
_entity->setLocalTransformAndVelocities(localTransform, ENTITY_ITEM_ZERO_VEC3, ENTITY_ITEM_ZERO_VEC3);
// and also to RigidBody
btTransform worldTrans;
worldTrans.setOrigin(glmToBullet(_entity->getWorldPosition()));
worldTrans.setRotation(glmToBullet(_entity->getWorldOrientation()));
_body->setWorldTransform(worldTrans);
// no need to update velocities... should already be zero
}
// virtual
void EntityMotionState::handleEasyChanges(uint32_t& flags) {
assert(_entity);
assert(entityTreeIsLocked());
updateServerPhysicsVariables();
ObjectMotionState::handleEasyChanges(flags);
if (flags & Simulation::DIRTY_SIMULATOR_ID) {
if (_entity->getSimulatorID().isNull()) {
// simulation ownership has been removed by an external simulator
// simulation ownership has been removed
if (glm::length2(_entity->getWorldVelocity()) == 0.0f) {
// this object is coming to rest --> clear the ACTIVATION flag and _outgoingPriority
// this object is coming to rest --> clear the ACTIVATION flag and _bidPriority
flags &= ~Simulation::DIRTY_PHYSICS_ACTIVATION;
_body->setActivationState(WANTS_DEACTIVATION);
_outgoingPriority = 0;
_bidPriority = 0;
const float ACTIVATION_EXPIRY = 3.0f; // something larger than the 2.0 hard coded in Bullet
_body->setDeactivationTime(ACTIVATION_EXPIRY);
} else {
// disowned object is still moving --> start timer for ownership bid
// TODO? put a delay in here proportional to distance from object?
upgradeOutgoingPriority(VOLUNTEER_SIMULATION_PRIORITY);
_nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS;
upgradeBidPriority(VOLUNTEER_SIMULATION_PRIORITY);
_nextBidExpiry = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS;
}
_loopsWithoutOwner = 0;
_numInactiveUpdates = 0;
} else if (isLocallyOwned()) {
// we just inherited ownership, make sure our desired priority matches what we have
upgradeOutgoingPriority(_entity->getSimulationPriority());
upgradeBidPriority(_entity->getSimulationPriority());
} else {
_outgoingPriority = 0;
_nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS;
// the entity is owned by someone else, so we clear _bidPriority here
// but _bidPriority may be updated to non-zero value if this object interacts with locally owned simulation
// in which case we may try to bid again
_bidPriority = 0;
_nextBidExpiry = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS;
_numInactiveUpdates = 0;
}
}
@ -155,10 +170,10 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) {
// (1) we own it but may need to change the priority OR...
// (2) we don't own it but should bid (because a local script has been changing physics properties)
uint8_t newPriority = isLocallyOwned() ? _entity->getSimulationOwner().getPriority() : _entity->getSimulationOwner().getPendingPriority();
upgradeOutgoingPriority(newPriority);
upgradeBidPriority(newPriority);
// reset bid expiry so that we bid ASAP
_nextOwnershipBid = 0;
_nextBidExpiry = 0;
}
if ((flags & Simulation::DIRTY_PHYSICS_ACTIVATION) && !_body->isActive()) {
if (_body->isKinematicObject()) {
@ -175,7 +190,6 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) {
// virtual
bool EntityMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) {
assert(_entity);
updateServerPhysicsVariables();
return ObjectMotionState::handleHardAndEasyChanges(flags, engine);
}
@ -253,7 +267,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(_entity);
assert(entityTreeIsLocked());
measureBodyAcceleration();
@ -285,21 +298,10 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) {
if (_entity->getSimulatorID().isNull()) {
_loopsWithoutOwner++;
if (_loopsWithoutOwner > LOOPS_FOR_SIMULATION_ORPHAN && usecTimestampNow() > _nextOwnershipBid) {
upgradeOutgoingPriority(VOLUNTEER_SIMULATION_PRIORITY);
if (_loopsWithoutOwner > LOOPS_FOR_SIMULATION_ORPHAN && usecTimestampNow() > _nextBidExpiry) {
upgradeBidPriority(VOLUNTEER_SIMULATION_PRIORITY);
}
}
#ifdef WANT_DEBUG
quint64 now = usecTimestampNow();
qCDebug(physics) << "EntityMotionState::setWorldTransform()... changed entity:" << _entity->getEntityItemID();
qCDebug(physics) << " last edited:" << _entity->getLastEdited()
<< formatUsecTime(now - _entity->getLastEdited()) << "ago";
qCDebug(physics) << " last simulated:" << _entity->getLastSimulated()
<< formatUsecTime(now - _entity->getLastSimulated()) << "ago";
qCDebug(physics) << " last updated:" << _entity->getLastUpdated()
<< formatUsecTime(now - _entity->getLastUpdated()) << "ago";
#endif
}
@ -323,19 +325,13 @@ void EntityMotionState::setShape(const btCollisionShape* shape) {
}
}
bool EntityMotionState::isCandidateForOwnership() const {
assert(_body);
assert(_entity);
assert(entityTreeIsLocked());
return _outgoingPriority != 0
|| isLocallyOwned()
|| _entity->dynamicDataNeedsTransmit();
}
bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
DETAILED_PROFILE_RANGE(simulation_physics, "CheckOutOfSync");
// NOTE: we only get here if we think we own the simulation
assert(_body);
DETAILED_PROFILE_RANGE(simulation_physics, "CheckOutOfSync");
// Since we own the simulation: make sure _bidPriority is not less than current owned priority
// because: an _bidPriority of zero indicates that we should drop ownership when we have it.
upgradeBidPriority(_entity->getSimulationPriority());
bool parentTransformSuccess;
Transform localToWorld = _entity->getParentTransform(parentTransformSuccess);
@ -347,27 +343,6 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
worldVelocityToLocal.setTranslation(glm::vec3(0.0f));
}
// if we've never checked before, our _lastStep will be 0, and we need to initialize our state
if (_lastStep == 0) {
btTransform xform = _body->getWorldTransform();
_serverVariablesSet = true;
_serverPosition = worldToLocal.transform(bulletToGLM(xform.getOrigin()));
_serverRotation = worldToLocal.getRotation() * bulletToGLM(xform.getRotation());
_serverVelocity = worldVelocityToLocal.transform(getBodyLinearVelocityGTSigma());
_serverAcceleration = Vectors::ZERO;
_serverAngularVelocity = worldVelocityToLocal.transform(bulletToGLM(_body->getAngularVelocity()));
_lastStep = simulationStep;
_serverActionData = _entity->getDynamicData();
_numInactiveUpdates = 1;
return false;
}
#ifdef WANT_DEBUG
glm::vec3 wasPosition = _serverPosition;
glm::quat wasRotation = _serverRotation;
glm::vec3 wasAngularVelocity = _serverAngularVelocity;
#endif
int numSteps = simulationStep - _lastStep;
float dt = (float)(numSteps) * PHYSICS_ENGINE_FIXED_SUBSTEP;
@ -378,9 +353,9 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
_entity->clearSimulationOwnership();
return false;
}
// we resend the inactive update every INACTIVE_UPDATE_PERIOD
// until it is removed from the outgoing updates
// (which happens when we don't own the simulation and it isn't touching our simulation)
// we resend the inactive update with a growing delay: every INACTIVE_UPDATE_PERIOD * _numInactiveUpdates
// until it is removed from the owned list
// (which happens when we no longer own the simulation)
const float INACTIVE_UPDATE_PERIOD = 0.5f;
return (dt > INACTIVE_UPDATE_PERIOD * (float)_numInactiveUpdates);
}
@ -411,14 +386,10 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
if (_entity->dynamicDataNeedsTransmit()) {
uint8_t priority = _entity->hasActions() ? SCRIPT_GRAB_SIMULATION_PRIORITY : SCRIPT_POKE_SIMULATION_PRIORITY;
upgradeOutgoingPriority(priority);
upgradeBidPriority(priority);
return true;
}
if (_entity->shouldSuppressLocationEdits()) {
return false;
}
// Else we measure the error between current and extrapolated transform (according to expected behavior
// of remote EntitySimulation) and return true if the error is significant.
@ -440,13 +411,6 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
const float MIN_ERROR_RATIO_SQUARED = 0.0025f; // corresponds to 5% error in 1 second
const float MIN_SPEED_SQUARED = 1.0e-6f; // corresponds to 1mm/sec
if (speed2 < MIN_SPEED_SQUARED || dx2 / speed2 > MIN_ERROR_RATIO_SQUARED) {
#ifdef WANT_DEBUG
qCDebug(physics) << ".... (dx2 > MAX_POSITION_ERROR_SQUARED) ....";
qCDebug(physics) << "wasPosition:" << wasPosition;
qCDebug(physics) << "bullet position:" << position;
qCDebug(physics) << "_serverPosition:" << _serverPosition;
qCDebug(physics) << "dx2:" << dx2;
#endif
return true;
}
}
@ -466,22 +430,6 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
const float MIN_ROTATION_DOT = 0.99999f; // This corresponds to about 0.5 degrees of rotation
glm::quat actualRotation = worldToLocal.getRotation() * bulletToGLM(worldTrans.getRotation());
#ifdef WANT_DEBUG
if ((fabsf(glm::dot(actualRotation, _serverRotation)) < MIN_ROTATION_DOT)) {
qCDebug(physics) << ".... ((fabsf(glm::dot(actualRotation, _serverRotation)) < MIN_ROTATION_DOT)) ....";
qCDebug(physics) << "wasAngularVelocity:" << wasAngularVelocity;
qCDebug(physics) << "_serverAngularVelocity:" << _serverAngularVelocity;
qCDebug(physics) << "length wasAngularVelocity:" << glm::length(wasAngularVelocity);
qCDebug(physics) << "length _serverAngularVelocity:" << glm::length(_serverAngularVelocity);
qCDebug(physics) << "wasRotation:" << wasRotation;
qCDebug(physics) << "bullet actualRotation:" << actualRotation;
qCDebug(physics) << "_serverRotation:" << _serverRotation;
}
#endif
return (fabsf(glm::dot(actualRotation, _serverRotation)) < MIN_ROTATION_DOT);
}
@ -489,14 +437,10 @@ 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(_entity);
assert(_body);
assert(entityTreeIsLocked());
if (_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID()) {
// don't send updates for someone else's avatarEntities
return false;
}
// this case is prevented by setting _ownershipState to UNOWNABLE in EntityMotionState::ctor
assert(!(_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID()));
if (_entity->dynamicDataNeedsTransmit() || _entity->queryAACubeNeedsUpdate()) {
return true;
@ -506,44 +450,19 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) {
return false;
}
if (!isLocallyOwned()) {
// we don't own the simulation
// NOTE: we do not volunteer to own kinematic or static objects
uint8_t volunteerPriority = _body->isStaticOrKinematicObject() ? VOLUNTEER_SIMULATION_PRIORITY : 0;
bool shouldBid = _outgoingPriority > volunteerPriority && // but we would like to own it AND
usecTimestampNow() > _nextOwnershipBid; // it is time to bid again
if (shouldBid && _outgoingPriority < _entity->getSimulationPriority()) {
// we are insufficiently interested so clear _outgoingPriority
// and reset the bid expiry
_outgoingPriority = 0;
_nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS;
}
return shouldBid;
} else {
// When we own the simulation: make sure _outgoingPriority is not less than current owned priority
// because: an _outgoingPriority of zero indicates that we should drop ownership when we have it.
upgradeOutgoingPriority(_entity->getSimulationPriority());
}
return remoteSimulationOutOfSync(simulationStep);
}
void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step) {
DETAILED_PROFILE_RANGE(simulation_physics, "Send");
assert(_entity);
assert(entityTreeIsLocked());
void EntityMotionState::updateSendVelocities() {
if (!_body->isActive()) {
// make sure all derivatives are zero
zeroCleanObjectVelocities();
_numInactiveUpdates++;
clearObjectVelocities();
_numInactiveUpdates = 1;
} else {
glm::vec3 gravity = _entity->getGravity();
// if this entity has been accelerated at close to gravity for a certain number of simulation-steps, let
// the entity server's estimates include gravity.
// if this entity has been accelerated at close to gravity for a certain number of simulation-steps
// let the entity server's estimates include gravity.
const uint8_t STEPS_TO_DECIDE_BALLISTIC = 4;
if (_accelerationNearlyGravityCount >= STEPS_TO_DECIDE_BALLISTIC) {
_entity->setAcceleration(gravity);
@ -564,13 +483,82 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
if (movingSlowly) {
// velocities might not be zero, but we'll fake them as such, which will hopefully help convince
// other simulating observers to deactivate their own copies
zeroCleanObjectVelocities();
clearObjectVelocities();
}
}
_numInactiveUpdates = 0;
}
}
// remember properties for local server prediction
void EntityMotionState::sendBid(OctreeEditPacketSender* packetSender, uint32_t step) {
DETAILED_PROFILE_RANGE(simulation_physics, "Bid");
assert(entityTreeIsLocked());
updateSendVelocities();
EntityItemProperties properties;
Transform localTransform;
glm::vec3 linearVelocity;
glm::vec3 angularVelocity;
_entity->getLocalTransformAndVelocities(localTransform, linearVelocity, angularVelocity);
properties.setPosition(localTransform.getTranslation());
properties.setRotation(localTransform.getRotation());
properties.setVelocity(linearVelocity);
properties.setAcceleration(_entity->getAcceleration());
properties.setAngularVelocity(angularVelocity);
if (_entity->dynamicDataNeedsTransmit()) {
_entity->setDynamicDataNeedsTransmit(false);
properties.setActionData(_entity->getDynamicData());
}
if (_entity->updateQueryAACube()) {
// due to parenting, the server may not know where something is in world-space, so include the bounding cube.
properties.setQueryAACube(_entity->getQueryAACube());
}
// set the LastEdited of the properties but NOT the entity itself
quint64 now = usecTimestampNow();
properties.setLastEdited(now);
// we don't own the simulation for this entity yet, but we're sending a bid for it
uint8_t bidPriority = glm::max<uint8_t>(_bidPriority, VOLUNTEER_SIMULATION_PRIORITY);
properties.setSimulationOwner(Physics::getSessionUUID(), bidPriority);
// copy _bidPriority into pendingPriority...
_entity->setPendingOwnershipPriority(_bidPriority, now);
// don't forget to remember that we have made a bid
_entity->rememberHasSimulationOwnershipBid();
EntityTreeElementPointer element = _entity->getElement();
EntityTreePointer tree = element ? element->getTree() : nullptr;
properties.setClientOnly(_entity->getClientOnly());
properties.setOwningAvatarID(_entity->getOwningAvatarID());
EntityItemID id(_entity->getID());
EntityEditPacketSender* entityPacketSender = static_cast<EntityEditPacketSender*>(packetSender);
entityPacketSender->queueEditEntityMessage(PacketType::EntityPhysics, tree, id, properties);
_entity->setLastBroadcast(now); // for debug/physics status icons
// NOTE: we don't descend to children for ownership bid. Instead, if we win ownership of the parent
// then in sendUpdate() we'll walk descendents and send updates for their QueryAACubes if necessary.
_lastStep = step;
_nextBidExpiry = now + USECS_BETWEEN_OWNERSHIP_BIDS;
// finally: clear _bidPriority
// which will may get promoted before next bid
// or maybe we'll win simulation ownership
_bidPriority = 0;
}
void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step) {
DETAILED_PROFILE_RANGE(simulation_physics, "Send");
assert(entityTreeIsLocked());
assert(isLocallyOwned());
updateSendVelocities();
// remember _serverFoo data for local prediction of server state
Transform localTransform;
_entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity);
_serverPosition = localTransform.getTranslation();
@ -579,11 +567,8 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
_serverActionData = _entity->getDynamicData();
EntityItemProperties properties;
// explicitly set the properties that changed so that they will be packed
properties.setPosition(_entity->getLocalPosition());
properties.setRotation(_entity->getLocalOrientation());
properties.setVelocity(_serverVelocity);
properties.setAcceleration(_serverAcceleration);
properties.setAngularVelocity(_serverAngularVelocity);
@ -592,61 +577,35 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
properties.setActionData(_serverActionData);
}
if (properties.transformChanged()) {
if (_entity->updateQueryAACube()) {
// due to parenting, the server may not know where something is in world-space, so include the bounding cube.
properties.setQueryAACube(_entity->getQueryAACube());
}
if (_entity->updateQueryAACube()) {
// due to parenting, the server may not know where something is in world-space, so include the bounding cube.
properties.setQueryAACube(_entity->getQueryAACube());
}
// set the LastEdited of the properties but NOT the entity itself
quint64 now = usecTimestampNow();
properties.setLastEdited(now);
#ifdef WANT_DEBUG
quint64 lastSimulated = _entity->getLastSimulated();
qCDebug(physics) << "EntityMotionState::sendUpdate()";
qCDebug(physics) << " EntityItemId:" << _entity->getEntityItemID()
<< "---------------------------------------------";
qCDebug(physics) << " lastSimulated:" << debugTime(lastSimulated, now);
#endif //def WANT_DEBUG
if (_numInactiveUpdates > 0) {
// we own the simulation but the entity has stopped so we tell the server we're clearing simulatorID
// the entity is stopped and inactive so we tell the server we're clearing simulatorID
// but we remember we do still own it... and rely on the server to tell us we don't
properties.clearSimulationOwner();
_outgoingPriority = 0;
_entity->setPendingOwnershipPriority(_outgoingPriority, now);
} else if (!isLocallyOwned()) {
// we don't own the simulation for this entity yet, but we're sending a bid for it
quint8 bidPriority = glm::max<uint8_t>(_outgoingPriority, VOLUNTEER_SIMULATION_PRIORITY);
properties.setSimulationOwner(Physics::getSessionUUID(), bidPriority);
_nextOwnershipBid = now + USECS_BETWEEN_OWNERSHIP_BIDS;
// copy _outgoingPriority into pendingPriority...
_entity->setPendingOwnershipPriority(_outgoingPriority, now);
// don't forget to remember that we have made a bid
_entity->rememberHasSimulationOwnershipBid();
// ...then reset _outgoingPriority
_outgoingPriority = 0;
// _outgoingPrioriuty will be re-computed before next bid,
// or will be set to agree with ownership priority should we win the bid
} else if (_outgoingPriority != _entity->getSimulationPriority()) {
// we own the simulation but our desired priority has changed
if (_outgoingPriority == 0) {
_bidPriority = 0;
_entity->setPendingOwnershipPriority(_bidPriority, now);
} else if (_bidPriority != _entity->getSimulationPriority()) {
// our desired priority has changed
if (_bidPriority == 0) {
// we should release ownership
properties.clearSimulationOwner();
} else {
// we just need to change the priority
properties.setSimulationOwner(Physics::getSessionUUID(), _outgoingPriority);
properties.setSimulationOwner(Physics::getSessionUUID(), _bidPriority);
}
_entity->setPendingOwnershipPriority(_outgoingPriority, now);
_entity->setPendingOwnershipPriority(_bidPriority, now);
}
EntityItemID id(_entity->getID());
EntityEditPacketSender* entityPacketSender = static_cast<EntityEditPacketSender*>(packetSender);
#ifdef WANT_DEBUG
qCDebug(physics) << "EntityMotionState::sendUpdate()... calling queueEditEntityMessage()...";
#endif
EntityTreeElementPointer element = _entity->getElement();
EntityTreePointer tree = element ? element->getTree() : nullptr;
@ -655,7 +614,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
properties.setOwningAvatarID(_entity->getOwningAvatarID());
entityPacketSender->queueEditEntityMessage(PacketType::EntityPhysics, tree, id, properties);
_entity->setLastBroadcast(now);
_entity->setLastBroadcast(now); // for debug/physics status icons
// if we've moved an entity with children, check/update the queryAACube of all descendents and tell the server
// if they've changed.
@ -666,13 +625,12 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
EntityItemProperties newQueryCubeProperties;
newQueryCubeProperties.setQueryAACube(descendant->getQueryAACube());
newQueryCubeProperties.setLastEdited(properties.getLastEdited());
newQueryCubeProperties.setClientOnly(entityDescendant->getClientOnly());
newQueryCubeProperties.setOwningAvatarID(entityDescendant->getOwningAvatarID());
entityPacketSender->queueEditEntityMessage(PacketType::EntityPhysics, tree,
descendant->getID(), newQueryCubeProperties);
entityDescendant->setLastBroadcast(now);
entityDescendant->setLastBroadcast(now); // for debug/physics status icons
}
}
});
@ -723,6 +681,10 @@ uint8_t EntityMotionState::getSimulationPriority() const {
return _entity->getSimulationPriority();
}
void EntityMotionState::slaveBidPriority() {
upgradeBidPriority(_entity->getSimulationPriority());
}
// virtual
QUuid EntityMotionState::getSimulatorID() const {
assert(entityTreeIsLocked());
@ -731,7 +693,7 @@ QUuid EntityMotionState::getSimulatorID() const {
void EntityMotionState::bump(uint8_t priority) {
assert(priority != 0);
upgradeOutgoingPriority(glm::max(VOLUNTEER_SIMULATION_PRIORITY, --priority));
upgradeBidPriority(glm::max(VOLUNTEER_SIMULATION_PRIORITY, --priority));
}
void EntityMotionState::resetMeasuredBodyAcceleration() {
@ -755,13 +717,14 @@ void EntityMotionState::measureBodyAcceleration() {
_lastMeasureStep = thisStep;
_measuredDeltaTime = dt;
// Note: the integration equation for velocity uses damping: v1 = (v0 + a * dt) * (1 - D)^dt
// Note: the integration equation for velocity uses damping (D): v1 = (v0 + a * dt) * (1 - D)^dt
// hence the equation for acceleration is: a = (v1 / (1 - D)^dt - v0) / dt
glm::vec3 velocity = getBodyLinearVelocityGTSigma();
_measuredAcceleration = (velocity / powf(1.0f - _body->getLinearDamping(), dt) - _lastVelocity) * invDt;
_lastVelocity = velocity;
if (numSubsteps > PHYSICS_ENGINE_MAX_NUM_SUBSTEPS) {
// we fall in here when _lastMeasureStep is old: the body has just become active
_loopsWithoutOwner = 0;
_lastStep = ObjectMotionState::getWorldSimulationStep();
_numInactiveUpdates = 0;
@ -805,24 +768,44 @@ QString EntityMotionState::getName() const {
// virtual
void EntityMotionState::computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const {
assert(_entity);
_entity->computeCollisionGroupAndFinalMask(group, mask);
}
bool EntityMotionState::shouldSendBid() {
if (_bidPriority >= glm::max(_entity->getSimulationPriority(), VOLUNTEER_SIMULATION_PRIORITY)) {
return true;
} else {
// NOTE: this 'else' case has a side-effect: it clears _bidPriority
// which may be updated next simulation step (via collision or script event)
_bidPriority = 0;
return false;
}
}
bool EntityMotionState::isLocallyOwned() const {
return _entity->getSimulatorID() == Physics::getSessionUUID();
}
bool EntityMotionState::shouldBeLocallyOwned() const {
return (_outgoingPriority > VOLUNTEER_SIMULATION_PRIORITY && _outgoingPriority > _entity->getSimulationPriority()) ||
bool EntityMotionState::isLocallyOwnedOrShouldBe() const {
return (_bidPriority > VOLUNTEER_SIMULATION_PRIORITY && _bidPriority > _entity->getSimulationPriority()) ||
_entity->getSimulatorID() == Physics::getSessionUUID();
}
void EntityMotionState::upgradeOutgoingPriority(uint8_t priority) {
_outgoingPriority = glm::max<uint8_t>(_outgoingPriority, priority);
void EntityMotionState::initForBid() {
assert(_ownershipState != EntityMotionState::OwnershipState::Unownable);
_ownershipState = EntityMotionState::OwnershipState::PendingBid;
}
void EntityMotionState::zeroCleanObjectVelocities() const {
void EntityMotionState::initForOwned() {
assert(_ownershipState != EntityMotionState::OwnershipState::Unownable);
_ownershipState = EntityMotionState::OwnershipState::LocallyOwned;
}
void EntityMotionState::upgradeBidPriority(uint8_t priority) {
_bidPriority = glm::max<uint8_t>(_bidPriority, priority);
}
void EntityMotionState::clearObjectVelocities() const {
// If transform or velocities are flagged as dirty it means a network or scripted change
// occured between the beginning and end of the stepSimulation() and we DON'T want to apply
// these physics simulation results.

View file

@ -24,11 +24,17 @@
class EntityMotionState : public ObjectMotionState {
public:
enum class OwnershipState {
NotLocallyOwned = 0,
PendingBid,
LocallyOwned,
Unownable
};
EntityMotionState() = delete;
EntityMotionState(btCollisionShape* shape, EntityItemPointer item);
virtual ~EntityMotionState();
void updateServerPhysicsVariables();
void handleDeactivation();
virtual void handleEasyChanges(uint32_t& flags) override;
virtual bool handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) override;
@ -44,9 +50,8 @@ public:
// this relays outgoing position/rotation to the EntityItem
virtual void setWorldTransform(const btTransform& worldTrans) override;
bool isCandidateForOwnership() const;
bool remoteSimulationOutOfSync(uint32_t simulationStep);
bool shouldSendUpdate(uint32_t simulationStep);
void sendBid(OctreeEditPacketSender* packetSender, uint32_t step);
void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step);
virtual uint32_t getIncomingDirtyFlags() override;
@ -70,7 +75,9 @@ public:
virtual QUuid getSimulatorID() const override;
virtual void bump(uint8_t priority) override;
EntityItemPointer getEntity() const { return _entityPtr.lock(); }
// getEntity() returns a smart-pointer by reference because it is only ever used
// to insert into lists of smart pointers, and the lists will make their own copies
const EntityItemPointer& getEntity() const { return _entity; }
void resetMeasuredBodyAcceleration();
void measureBodyAcceleration();
@ -79,15 +86,29 @@ public:
virtual void computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const override;
bool shouldSendBid();
bool isLocallyOwned() const override;
bool shouldBeLocallyOwned() const override;
bool isLocallyOwnedOrShouldBe() const override; // aka shouldEmitCollisionEvents()
friend class PhysicalEntitySimulation;
OwnershipState getOwnershipState() const { return _ownershipState; }
protected:
// changes _outgoingPriority only if priority is larger
void upgradeOutgoingPriority(uint8_t priority);
void zeroCleanObjectVelocities() const;
void updateSendVelocities();
uint64_t getNextBidExpiry() const { return _nextBidExpiry; }
void initForBid();
void initForOwned();
void clearOwnershipState() { _ownershipState = OwnershipState::NotLocallyOwned; }
void updateServerPhysicsVariables();
bool remoteSimulationOutOfSync(uint32_t simulationStep);
// changes _bidPriority only if priority is larger
void upgradeBidPriority(uint8_t priority);
// upgradeBidPriority to value stored in _entity
void slaveBidPriority();
void clearObjectVelocities() const;
#ifdef WANT_DEBUG_ENTITY_TREE_LOCKS
bool entityTreeIsLocked() const;
@ -98,17 +119,21 @@ protected:
void setShape(const btCollisionShape* shape) override;
void setMotionType(PhysicsMotionType motionType) override;
// In the glorious future (when entities lib depends on physics lib) the EntityMotionState will be
// properly "owned" by the EntityItem and will be deleted by it in the dtor. In pursuit of that
// state of affairs we can't keep a real EntityItemPointer as data member (it would produce a
// recursive dependency). Instead we keep a EntityItemWeakPointer to break that dependency while
// still granting us the capability to generate EntityItemPointers as necessary (for external data
// structures that use the MotionState to get to the EntityItem).
EntityItemWeakPointer _entityPtr;
// Meanwhile we also keep a raw EntityItem* for internal stuff where the pointer is guaranteed valid.
EntityItem* _entity;
// EntityMotionState keeps a SharedPointer to its EntityItem which is only set in the CTOR
// and is only cleared in the DTOR
EntityItemPointer _entity;
bool _serverVariablesSet { false };
// These "_serverFoo" variables represent what we think the server knows.
// They are used in two different modes:
//
// (1) For remotely owned simulation: we store the last values recieved from the server.
// When the body comes to rest and goes inactive we slam its final transforms to agree with the last server
// update. This to reduce state synchronization errors when the local simulation deviated from remote.
//
// (2) For locally owned simulation: we store the last values sent to the server, integrated forward over time
// according to how we think the server doing it. We calculate the error between the true local transform
// and the remote to decide when to send another update.
//
glm::vec3 _serverPosition; // in simulation-frame (not world-frame)
glm::quat _serverRotation;
glm::vec3 _serverVelocity;
@ -119,16 +144,18 @@ protected:
glm::vec3 _lastVelocity;
glm::vec3 _measuredAcceleration;
quint64 _nextOwnershipBid { 0 };
quint64 _nextBidExpiry { 0 };
float _measuredDeltaTime;
uint32_t _lastMeasureStep;
uint32_t _lastStep; // last step of server extrapolation
OwnershipState _ownershipState { OwnershipState::NotLocallyOwned };
uint8_t _loopsWithoutOwner;
mutable uint8_t _accelerationNearlyGravityCount;
uint8_t _numInactiveUpdates { 1 };
uint8_t _outgoingPriority { 0 };
uint8_t _bidPriority { 0 };
bool _serverVariablesSet { false };
};
#endif // hifi_EntityMotionState_h

View file

@ -148,9 +148,9 @@ public:
virtual const QUuid getObjectID() const = 0;
virtual quint8 getSimulationPriority() const { return 0; }
virtual uint8_t getSimulationPriority() const { return 0; }
virtual QUuid getSimulatorID() const = 0;
virtual void bump(quint8 priority) {}
virtual void bump(uint8_t priority) {}
virtual QString getName() const { return ""; }
@ -164,7 +164,7 @@ public:
void clearInternalKinematicChanges() { _hasInternalKinematicChanges = false; }
virtual bool isLocallyOwned() const { return false; }
virtual bool shouldBeLocallyOwned() const { return false; }
virtual bool isLocallyOwnedOrShouldBe() const { return false; } // aka shouldEmitCollisionEvents()
friend class PhysicsEngine;

View file

@ -40,7 +40,7 @@ void PhysicalEntitySimulation::init(
}
// begin EntitySimulation overrides
void PhysicalEntitySimulation::updateEntitiesInternal(const quint64& now) {
void PhysicalEntitySimulation::updateEntitiesInternal(uint64_t now) {
// Do nothing here because the "internal" update the PhysicsEngine::stepSimualtion() which is done elsewhere.
}
@ -61,33 +61,58 @@ void PhysicalEntitySimulation::addEntityInternal(EntityItemPointer entity) {
void PhysicalEntitySimulation::removeEntityInternal(EntityItemPointer entity) {
if (entity->isSimulated()) {
EntitySimulation::removeEntityInternal(entity);
QMutexLocker lock(&_mutex);
_entitiesToAddToPhysics.remove(entity);
EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo());
if (motionState) {
_outgoingChanges.remove(motionState);
removeOwnershipData(motionState);
_entitiesToRemoveFromPhysics.insert(entity);
} else {
_entitiesToDelete.insert(entity);
} else if (entity->isDead() && entity->getElement()) {
_deadEntities.insert(entity);
}
}
}
void PhysicalEntitySimulation::takeEntitiesToDelete(VectorOfEntities& entitiesToDelete) {
QMutexLocker lock(&_mutex);
for (auto entity : _entitiesToDelete) {
// this entity is still in its tree, so we insert into the external list
entitiesToDelete.push_back(entity);
void PhysicalEntitySimulation::removeOwnershipData(EntityMotionState* motionState) {
assert(motionState);
if (motionState->getOwnershipState() == EntityMotionState::OwnershipState::LocallyOwned) {
for (uint32_t i = 0; i < _owned.size(); ++i) {
if (_owned[i] == motionState) {
_owned[i]->clearOwnershipState();
_owned.remove(i);
}
}
} else if (motionState->getOwnershipState() == EntityMotionState::OwnershipState::PendingBid) {
for (uint32_t i = 0; i < _bids.size(); ++i) {
if (_bids[i] == motionState) {
_bids[i]->clearOwnershipState();
_bids.remove(i);
}
}
}
}
// Someday when we invert the entities/physics lib dependencies we can let EntityItem delete its own PhysicsInfo
// rather than do it here
void PhysicalEntitySimulation::clearOwnershipData() {
for (uint32_t i = 0; i < _owned.size(); ++i) {
_owned[i]->clearOwnershipState();
}
_owned.clear();
for (uint32_t i = 0; i < _bids.size(); ++i) {
_bids[i]->clearOwnershipState();
}
_bids.clear();
}
void PhysicalEntitySimulation::takeDeadEntities(SetOfEntities& deadEntities) {
QMutexLocker lock(&_mutex);
for (auto entity : _deadEntities) {
EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo());
if (motionState) {
_entitiesToRemoveFromPhysics.insert(entity);
}
}
_entitiesToDelete.clear();
_deadEntities.swap(deadEntities);
_deadEntities.clear();
}
void PhysicalEntitySimulation::changeEntityInternal(EntityItemPointer entity) {
@ -98,15 +123,15 @@ void PhysicalEntitySimulation::changeEntityInternal(EntityItemPointer entity) {
if (motionState) {
if (!entity->shouldBePhysical()) {
// the entity should be removed from the physical simulation
_pendingChanges.remove(motionState);
_incomingChanges.remove(motionState);
_physicalObjects.remove(motionState);
_outgoingChanges.remove(motionState);
removeOwnershipData(motionState);
_entitiesToRemoveFromPhysics.insert(entity);
if (entity->isMovingRelativeToParent()) {
_simpleKinematicEntities.insert(entity);
}
} else {
_pendingChanges.insert(motionState);
_incomingChanges.insert(motionState);
}
} else if (entity->shouldBePhysical()) {
// The intent is for this object to be in the PhysicsEngine, but it has no MotionState yet.
@ -125,80 +150,69 @@ void PhysicalEntitySimulation::clearEntitiesInternal() {
// while it is in the middle of a simulation step. As it is, we're probably in shutdown mode
// anyway, so maybe the simulation was already properly shutdown? Cross our fingers...
// copy everything into _entitiesToDelete
for (auto stateItr : _physicalObjects) {
EntityMotionState* motionState = static_cast<EntityMotionState*>(&(*stateItr));
_entitiesToDelete.insert(motionState->getEntity());
}
// then remove the objects (aka MotionStates) from physics
// remove the objects (aka MotionStates) from physics
_physicsEngine->removeSetOfObjects(_physicalObjects);
// delete the MotionStates
// TODO: after we invert the entities/physics lib dependencies we will let EntityItem delete
// its own PhysicsInfo rather than do it here
for (auto entity : _entitiesToDelete) {
EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo());
if (motionState) {
entity->setPhysicsInfo(nullptr);
delete motionState;
}
for (auto stateItr : _physicalObjects) {
EntityMotionState* motionState = static_cast<EntityMotionState*>(&(*stateItr));
assert(motionState);
EntityItemPointer entity = motionState->getEntity();
entity->setPhysicsInfo(nullptr);
// TODO: someday when we invert the entities/physics lib dependencies we can let EntityItem delete its own PhysicsInfo
// until then we must do it here
delete motionState;
}
// finally clear all lists maintained by this class
_physicalObjects.clear();
// clear all other lists specific to this derived class
clearOwnershipData();
_entitiesToRemoveFromPhysics.clear();
_entitiesToRelease.clear();
_entitiesToAddToPhysics.clear();
_pendingChanges.clear();
_outgoingChanges.clear();
_incomingChanges.clear();
}
// virtual
void PhysicalEntitySimulation::prepareEntityForDelete(EntityItemPointer entity) {
assert(entity);
assert(entity->isDead());
QMutexLocker lock(&_mutex);
entity->clearActions(getThisPointer());
removeEntityInternal(entity);
}
// end EntitySimulation overrides
void PhysicalEntitySimulation::getObjectsToRemoveFromPhysics(VectorOfMotionStates& result) {
result.clear();
const VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToRemoveFromPhysics() {
QMutexLocker lock(&_mutex);
for (auto entity: _entitiesToRemoveFromPhysics) {
// make sure it isn't on any side lists
_entitiesToAddToPhysics.remove(entity);
EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo());
if (motionState) {
_pendingChanges.remove(motionState);
_outgoingChanges.remove(motionState);
_physicalObjects.remove(motionState);
result.push_back(motionState);
_entitiesToRelease.insert(entity);
assert(motionState);
_entitiesToAddToPhysics.remove(entity);
if (entity->isDead() && entity->getElement()) {
_deadEntities.insert(entity);
}
if (entity->isDead()) {
_entitiesToDelete.insert(entity);
}
_incomingChanges.remove(motionState);
removeOwnershipData(motionState);
_physicalObjects.remove(motionState);
// remember this motionState and delete it later (after removing its RigidBody from the PhysicsEngine)
_objectsToDelete.push_back(motionState);
}
_entitiesToRemoveFromPhysics.clear();
return _objectsToDelete;
}
void PhysicalEntitySimulation::deleteObjectsRemovedFromPhysics() {
QMutexLocker lock(&_mutex);
for (auto entity: _entitiesToRelease) {
EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo());
assert(motionState);
entity->setPhysicsInfo(nullptr);
for (auto motionState : _objectsToDelete) {
// someday when we invert the entities/physics lib dependencies we can let EntityItem delete its own PhysicsInfo
// until then we must do it here
// NOTE: a reference to the EntityItemPointer is released in the EntityMotionState::dtor
delete motionState;
if (entity->isDead()) {
_entitiesToDelete.insert(entity);
}
}
_entitiesToRelease.clear();
_objectsToDelete.clear();
}
void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& result) {
@ -248,18 +262,18 @@ void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& re
void PhysicalEntitySimulation::setObjectsToChange(const VectorOfMotionStates& objectsToChange) {
QMutexLocker lock(&_mutex);
for (auto object : objectsToChange) {
_pendingChanges.insert(static_cast<EntityMotionState*>(object));
_incomingChanges.insert(static_cast<EntityMotionState*>(object));
}
}
void PhysicalEntitySimulation::getObjectsToChange(VectorOfMotionStates& result) {
result.clear();
QMutexLocker lock(&_mutex);
for (auto stateItr : _pendingChanges) {
for (auto stateItr : _incomingChanges) {
EntityMotionState* motionState = &(*stateItr);
result.push_back(motionState);
}
_pendingChanges.clear();
_incomingChanges.clear();
}
void PhysicalEntitySimulation::handleDeactivatedMotionStates(const VectorOfMotionStates& motionStates) {
@ -279,20 +293,22 @@ void PhysicalEntitySimulation::handleChangedMotionStates(const VectorOfMotionSta
PROFILE_RANGE_EX(simulation_physics, "ChangedEntities", 0x00000000, (uint64_t)motionStates.size());
QMutexLocker lock(&_mutex);
// walk the motionStates looking for those that correspond to entities
{
PROFILE_RANGE_EX(simulation_physics, "Filter", 0x00000000, (uint64_t)motionStates.size());
for (auto stateItr : motionStates) {
ObjectMotionState* state = &(*stateItr);
assert(state);
if (state->getType() == MOTIONSTATE_TYPE_ENTITY) {
EntityMotionState* entityState = static_cast<EntityMotionState*>(state);
EntityItemPointer entity = entityState->getEntity();
assert(entity.get());
if (entityState->isCandidateForOwnership()) {
_outgoingChanges.insert(entityState);
for (auto stateItr : motionStates) {
ObjectMotionState* state = &(*stateItr);
assert(state);
if (state->getType() == MOTIONSTATE_TYPE_ENTITY) {
EntityMotionState* entityState = static_cast<EntityMotionState*>(state);
_entitiesToSort.insert(entityState->getEntity());
if (entityState->getOwnershipState() == EntityMotionState::OwnershipState::NotLocallyOwned) {
// NOTE: entityState->getOwnershipState() reflects what ownership list (_bids or _owned) it is in
// and is distinct from entityState->isLocallyOwned() which checks the simulation ownership
// properties of the corresponding EntityItem. It is possible for the two states to be out
// of sync. In fact, we're trying to put them back into sync here.
if (entityState->isLocallyOwned()) {
addOwnership(entityState);
} else if (entityState->shouldSendBid()) {
addOwnershipBid(entityState);
}
_entitiesToSort.insert(entity);
}
}
}
@ -302,26 +318,78 @@ void PhysicalEntitySimulation::handleChangedMotionStates(const VectorOfMotionSta
_lastStepSendPackets = numSubsteps;
if (Physics::getSessionUUID().isNull()) {
// usually don't get here, but if so --> nothing to do
_outgoingChanges.clear();
return;
// usually don't get here, but if so clear all ownership
clearOwnershipData();
}
// send updates before bids, because this simplifies the logic thasuccessful bids will immediately send an update when added to the 'owned' list
sendOwnedUpdates(numSubsteps);
sendOwnershipBids(numSubsteps);
}
}
// look for entities to prune or update
PROFILE_RANGE_EX(simulation_physics, "Prune/Send", 0x00000000, (uint64_t)_outgoingChanges.size());
QSet<EntityMotionState*>::iterator stateItr = _outgoingChanges.begin();
while (stateItr != _outgoingChanges.end()) {
EntityMotionState* state = *stateItr;
if (!state->isCandidateForOwnership()) {
// prune
stateItr = _outgoingChanges.erase(stateItr);
} else if (state->shouldSendUpdate(numSubsteps)) {
// update
state->sendUpdate(_entityPacketSender, numSubsteps);
++stateItr;
} else {
++stateItr;
void PhysicalEntitySimulation::addOwnershipBid(EntityMotionState* motionState) {
motionState->initForBid();
motionState->sendBid(_entityPacketSender, _physicsEngine->getNumSubsteps());
_bids.push_back(motionState);
_nextBidExpiry = glm::min(_nextBidExpiry, motionState->getNextBidExpiry());
}
void PhysicalEntitySimulation::addOwnership(EntityMotionState* motionState) {
motionState->initForOwned();
_owned.push_back(motionState);
}
void PhysicalEntitySimulation::sendOwnershipBids(uint32_t numSubsteps) {
uint64_t now = usecTimestampNow();
if (now > _nextBidExpiry) {
PROFILE_RANGE_EX(simulation_physics, "Bid", 0x00000000, (uint64_t)_bids.size());
_nextBidExpiry = std::numeric_limits<uint64_t>::max();
uint32_t i = 0;
while (i < _bids.size()) {
bool removeBid = false;
if (_bids[i]->isLocallyOwned()) {
// when an object transitions from 'bid' to 'owned' we are changing the "mode" of data stored
// in the EntityMotionState::_serverFoo variables (please see comments in EntityMotionState.h)
// therefore we need to immediately send an update so that the values stored are what we're
// "telling" the server rather than what we've been "hearing" from the server.
_bids[i]->slaveBidPriority();
_bids[i]->sendUpdate(_entityPacketSender, numSubsteps);
addOwnership(_bids[i]);
removeBid = true;
} else if (!_bids[i]->shouldSendBid()) {
removeBid = true;
_bids[i]->clearOwnershipState();
}
if (removeBid) {
_bids.remove(i);
} else {
if (now > _bids[i]->getNextBidExpiry()) {
_bids[i]->sendBid(_entityPacketSender, numSubsteps);
_nextBidExpiry = glm::min(_nextBidExpiry, _bids[i]->getNextBidExpiry());
}
++i;
}
}
}
}
void PhysicalEntitySimulation::sendOwnedUpdates(uint32_t numSubsteps) {
PROFILE_RANGE_EX(simulation_physics, "Update", 0x00000000, (uint64_t)_owned.size());
uint32_t i = 0;
while (i < _owned.size()) {
if (!_owned[i]->isLocallyOwned()) {
if (_owned[i]->shouldSendBid()) {
addOwnershipBid(_owned[i]);
} else {
_owned[i]->clearOwnershipState();
}
_owned.remove(i);
} else {
if (_owned[i]->shouldSendUpdate(numSubsteps)) {
_owned[i]->sendUpdate(_entityPacketSender, numSubsteps);
}
++i;
}
}
}
@ -336,7 +404,6 @@ void PhysicalEntitySimulation::handleCollisionEvents(const CollisionEvents& coll
}
}
void PhysicalEntitySimulation::addDynamic(EntityDynamicPointer dynamic) {
if (_physicsEngine) {
// FIXME put fine grain locking into _physicsEngine

View file

@ -27,7 +27,19 @@ class PhysicalEntitySimulation;
using PhysicalEntitySimulationPointer = std::shared_ptr<PhysicalEntitySimulation>;
using SetOfEntityMotionStates = QSet<EntityMotionState*>;
class VectorOfEntityMotionStates: public std::vector<EntityMotionState*> {
public:
void remove(uint32_t index) {
assert(index < size());
if (index < size() - 1) {
(*this)[index] = back();
}
pop_back();
}
};
class PhysicalEntitySimulation : public EntitySimulation {
Q_OBJECT
public:
PhysicalEntitySimulation();
~PhysicalEntitySimulation();
@ -37,21 +49,28 @@ public:
virtual void addDynamic(EntityDynamicPointer dynamic) override;
virtual void applyDynamicChanges() override;
virtual void takeEntitiesToDelete(VectorOfEntities& entitiesToDelete) override;
virtual void takeDeadEntities(SetOfEntities& deadEntities) override;
signals:
void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
protected: // only called by EntitySimulation
// overrides for EntitySimulation
virtual void updateEntitiesInternal(const quint64& now) override;
virtual void updateEntitiesInternal(uint64_t now) override;
virtual void addEntityInternal(EntityItemPointer entity) override;
virtual void removeEntityInternal(EntityItemPointer entity) override;
virtual void changeEntityInternal(EntityItemPointer entity) override;
virtual void clearEntitiesInternal() override;
void removeOwnershipData(EntityMotionState* motionState);
void clearOwnershipData();
public:
virtual void prepareEntityForDelete(EntityItemPointer entity) override;
void getObjectsToRemoveFromPhysics(VectorOfMotionStates& result);
const VectorOfMotionStates& getObjectsToRemoveFromPhysics();
void deleteObjectsRemovedFromPhysics();
void getObjectsToAddToPhysics(VectorOfMotionStates& result);
void setObjectsToChange(const VectorOfMotionStates& objectsToChange);
void getObjectsToChange(VectorOfMotionStates& result);
@ -62,19 +81,28 @@ public:
EntityEditPacketSender* getPacketSender() { return _entityPacketSender; }
private:
SetOfEntities _entitiesToRemoveFromPhysics;
SetOfEntities _entitiesToRelease;
SetOfEntities _entitiesToAddToPhysics;
void addOwnershipBid(EntityMotionState* motionState);
void addOwnership(EntityMotionState* motionState);
void sendOwnershipBids(uint32_t numSubsteps);
void sendOwnedUpdates(uint32_t numSubsteps);
SetOfEntityMotionStates _pendingChanges; // EntityMotionStates already in PhysicsEngine that need their physics changed
SetOfEntityMotionStates _outgoingChanges; // EntityMotionStates for which we may need to send updates to entity-server
private:
SetOfEntities _entitiesToAddToPhysics;
SetOfEntities _entitiesToRemoveFromPhysics;
VectorOfMotionStates _objectsToDelete;
SetOfEntityMotionStates _incomingChanges; // EntityMotionStates that have changed from external sources
// and need their RigidBodies updated
SetOfMotionStates _physicalObjects; // MotionStates of entities in PhysicsEngine
PhysicsEnginePointer _physicsEngine = nullptr;
EntityEditPacketSender* _entityPacketSender = nullptr;
VectorOfEntityMotionStates _owned;
VectorOfEntityMotionStates _bids;
uint64_t _nextBidExpiry;
uint32_t _lastStepSendPackets { 0 };
};

View file

@ -571,7 +571,7 @@ const CollisionEvents& PhysicsEngine::getCollisionEvents() {
// modify the logic below.
//
// We only create events when at least one of the objects is (or should be) owned in the local simulation.
if (motionStateA && (motionStateA->shouldBeLocallyOwned())) {
if (motionStateA && (motionStateA->isLocallyOwnedOrShouldBe())) {
QUuid idA = motionStateA->getObjectID();
QUuid idB;
if (motionStateB) {
@ -582,7 +582,7 @@ const CollisionEvents& PhysicsEngine::getCollisionEvents() {
(motionStateB ? motionStateB->getObjectLinearVelocityChange() : glm::vec3(0.0f));
glm::vec3 penetration = bulletToGLM(contact.distance * contact.normalWorldOnB);
_collisionEvents.push_back(Collision(type, idA, idB, position, penetration, velocityChange));
} else if (motionStateB && (motionStateB->shouldBeLocallyOwned())) {
} else if (motionStateB && (motionStateB->isLocallyOwnedOrShouldBe())) {
QUuid idB = motionStateB->getObjectID();
QUuid idA;
if (motionStateA) {

View file

@ -68,8 +68,9 @@ void Pointer::update(unsigned int pointerID) {
// This only needs to be a read lock because update won't change any of the properties that can be modified from scripts
withReadLock([&] {
auto pickResult = getPrevPickResult();
updateVisuals(pickResult);
generatePointerEvents(pointerID, pickResult);
auto visualPickResult = getVisualPickResult(pickResult);
updateVisuals(visualPickResult);
generatePointerEvents(pointerID, visualPickResult);
});
}

View file

@ -89,6 +89,7 @@ protected:
virtual bool shouldHover(const PickResultPointer& pickResult) { return true; }
virtual bool shouldTrigger(const PickResultPointer& pickResult) { return true; }
virtual PickResultPointer getVisualPickResult(const PickResultPointer& pickResult) { return pickResult; };
static const float POINTER_MOVE_DELAY;
static const float TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED;

View file

@ -380,6 +380,8 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const
batch.setResourceTexture(AntialiasingPass_VelocityMapSlot, nullptr);
batch.setResourceTexture(AntialiasingPass_NextMapSlot, nullptr);
});
args->popViewFrustum();
}
@ -520,7 +522,7 @@ void JitterSample::run(const render::RenderContextPointer& renderContext) {
viewFrustum.setProjection(projMat);
viewFrustum.calculate();
args->setViewFrustum(viewFrustum);
args->pushViewFrustum(viewFrustum);
} else {
mat4 projMats[2];
args->_context->getStereoProjections(projMats);
@ -538,4 +540,4 @@ void JitterSample::run(const render::RenderContextPointer& renderContext) {
}
#endif
#endif

View file

@ -39,43 +39,52 @@ void FadeEditJob::run(const render::RenderContextPointer& renderContext, const F
auto scene = renderContext->_scene;
if (_isEditEnabled) {
float minIsectDistance = std::numeric_limits<float>::max();
auto& itemBounds = inputs.get0();
auto editedItem = findNearestItem(renderContext, itemBounds, minIsectDistance);
render::Transaction transaction;
bool hasTransaction{ false };
static const std::string selectionName("TransitionEdit");
auto scene = renderContext->_scene;
if (!scene->isSelectionEmpty(selectionName)) {
auto selection = scene->getSelection(selectionName);
auto editedItem = selection.getItems().front();
render::Transaction transaction;
bool hasTransaction{ false };
if (editedItem != _editedItem && render::Item::isValidID(_editedItem)) {
// Remove transition from previously edited item as we've changed edited item
hasTransaction = true;
if (editedItem != _editedItem && render::Item::isValidID(_editedItem)) {
// Remove transition from previously edited item as we've changed edited item
hasTransaction = true;
transaction.removeTransitionFromItem(_editedItem);
}
_editedItem = editedItem;
if (render::Item::isValidID(_editedItem)) {
static const render::Transition::Type categoryToTransition[FADE_CATEGORY_COUNT] = {
render::Transition::ELEMENT_ENTER_DOMAIN,
render::Transition::BUBBLE_ISECT_OWNER,
render::Transition::BUBBLE_ISECT_TRESPASSER,
render::Transition::USER_ENTER_DOMAIN,
render::Transition::AVATAR_CHANGE
};
auto transitionType = categoryToTransition[inputs.get1()];
transaction.queryTransitionOnItem(_editedItem, [transitionType, scene](render::ItemID id, const render::Transition* transition) {
if (transition == nullptr || transition->isFinished || transition->eventType != transitionType) {
// Relaunch transition
render::Transaction transaction;
transaction.addTransitionToItem(id, transitionType);
scene->enqueueTransaction(transaction);
}
});
hasTransaction = true;
}
if (hasTransaction) {
scene->enqueueTransaction(transaction);
}
} else if (render::Item::isValidID(_editedItem)) {
// Remove transition from previously edited item as we've disabled fade edition
render::Transaction transaction;
transaction.removeTransitionFromItem(_editedItem);
}
_editedItem = editedItem;
if (render::Item::isValidID(_editedItem)) {
static const render::Transition::Type categoryToTransition[FADE_CATEGORY_COUNT] = {
render::Transition::ELEMENT_ENTER_DOMAIN,
render::Transition::BUBBLE_ISECT_OWNER,
render::Transition::BUBBLE_ISECT_TRESPASSER,
render::Transition::USER_ENTER_DOMAIN,
render::Transition::AVATAR_CHANGE
};
auto transitionType = categoryToTransition[inputs.get1()];
transaction.queryTransitionOnItem(_editedItem, [transitionType, scene](render::ItemID id, const render::Transition* transition) {
if (transition == nullptr || transition->isFinished || transition->eventType!=transitionType) {
// Relaunch transition
render::Transaction transaction;
transaction.addTransitionToItem(id, transitionType);
scene->enqueueTransaction(transaction);
}
});
hasTransaction = true;
}
if (hasTransaction) {
scene->enqueueTransaction(transaction);
_editedItem = render::Item::INVALID_ITEM_ID;
}
}
else if (render::Item::isValidID(_editedItem)) {
@ -87,28 +96,6 @@ void FadeEditJob::run(const render::RenderContextPointer& renderContext, const F
}
}
render::ItemID FadeEditJob::findNearestItem(const render::RenderContextPointer& renderContext, const render::ItemBounds& inputs, float& minIsectDistance) const {
const glm::vec3 rayOrigin = renderContext->args->getViewFrustum().getPosition();
const glm::vec3 rayDirection = renderContext->args->getViewFrustum().getDirection();
BoxFace face;
glm::vec3 normal;
float isectDistance;
render::ItemID nearestItem = render::Item::INVALID_ITEM_ID;
const float minDistance = 1.f;
const float maxDistance = 50.f;
for (const auto& itemBound : inputs) {
if (!itemBound.bound.contains(rayOrigin) && itemBound.bound.findRayIntersection(rayOrigin, rayDirection, isectDistance, face, normal)) {
auto& item = renderContext->_scene->getItem(itemBound.id);
if (item.getKey().isWorldSpace() && isectDistance>minDistance && isectDistance < minIsectDistance && isectDistance<maxDistance) {
nearestItem = itemBound.id;
minIsectDistance = isectDistance;
}
}
}
return nearestItem;
}
FadeConfig::FadeConfig()
{
events[FADE_ELEMENT_ENTER_LEAVE_DOMAIN].noiseSize = glm::vec3{ 0.75f, 0.75f, 0.75f };
@ -353,11 +340,9 @@ QString FadeConfig::eventNames[FADE_CATEGORY_COUNT] = {
"avatar_change",
};
void FadeConfig::save() const {
// Save will only work if the HIFI_USE_SOURCE_TREE_RESOURCES environment variable is set
void FadeConfig::save(const QString& configFilePath) const {
assert(editedCategory < FADE_CATEGORY_COUNT);
QJsonObject lProperties;
const QString configFilePath = PathUtils::resourcesPath() + "config/" + eventNames[editedCategory] + ".json";
QFile file(configFilePath);
if (!file.open(QFile::WriteOnly | QFile::Text)) {
qWarning() << "Fade event configuration file " << configFilePath << " cannot be opened";
@ -382,8 +367,7 @@ void FadeConfig::save() const {
}
}
void FadeConfig::load() {
const QString configFilePath = PathUtils::resourcesPath() + "config/" + eventNames[editedCategory] + ".json";
void FadeConfig::load(const QString& configFilePath) {
QFile file(configFilePath);
if (!file.exists()) {
qWarning() << "Fade event configuration file " << configFilePath << " does not exist";
@ -594,7 +578,7 @@ void FadeJob::run(const render::RenderContextPointer& renderContext, FadeJob::Ou
if (update(*jobConfig, scene, transaction, state, deltaTime)) {
hasTransaction = true;
}
if (isFirstItem && jobConfig->manualFade && (state.threshold != jobConfig->threshold)) {
if (isFirstItem && (state.threshold != jobConfig->threshold)) {
jobConfig->setProperty("threshold", state.threshold);
isFirstItem = false;
}

View file

@ -160,8 +160,8 @@ public:
float manualThreshold{ 0.f };
bool manualFade{ false };
Q_INVOKABLE void save() const;
Q_INVOKABLE void load();
Q_INVOKABLE void save(const QString& filePath) const;
Q_INVOKABLE void load(const QString& filePath);
static QString eventNames[FADE_CATEGORY_COUNT];
@ -190,7 +190,6 @@ private:
bool _isEditEnabled{ false };
render::ItemID _editedItem{ render::Item::INVALID_ITEM_ID };
render::ItemID findNearestItem(const render::RenderContextPointer& renderContext, const render::ItemBounds& inputs, float& minIsectDistance) const;
};
class FadeJob {

View file

@ -48,6 +48,74 @@ TexMapArray getTexMapArray() {
<@func declareMaterialTextures(withAlbedo, withRoughness, withNormal, withMetallic, withEmissive, withOcclusion, withScattering)@>
<@include gpu/TextureTable.slh@>
#ifdef GPU_TEXTURE_TABLE_BINDLESS
TextureTable(0, matTex);
<!
ALBEDO = 0,
NORMAL, 1
METALLIC, 2
EMISSIVE_LIGHTMAP, 3
ROUGHNESS, 4
OCCLUSION, 5
SCATTERING, 6
!>
<@if withAlbedo@>
#define albedoMap 0
vec4 fetchAlbedoMap(vec2 uv) {
return tableTexValue(matTex, albedoMap, uv);
}
<@endif@>
<@if withRoughness@>
#define roughnessMap 4
float fetchRoughnessMap(vec2 uv) {
return tableTexValue(matTex, roughnessMap, uv).r;
}
<@endif@>
<@if withNormal@>
#define normalMap 1
vec3 fetchNormalMap(vec2 uv) {
return tableTexValue(matTex, normalMap, uv).xyz;
}
<@endif@>
<@if withMetallic@>
#define metallicMap 2
float fetchMetallicMap(vec2 uv) {
return tableTexValue(matTex, metallicMap, uv).r;
}
<@endif@>
<@if withEmissive@>
#define emissiveMap 3
vec3 fetchEmissiveMap(vec2 uv) {
return tableTexValue(matTex, emissiveMap, uv).rgb;
}
<@endif@>
<@if withOcclusion@>
#define occlusionMap 5
float fetchOcclusionMap(vec2 uv) {
return tableTexValue(matTex, occlusionMap, uv).r;
}
<@endif@>
<@if withScattering@>
#define scatteringMap 6
float fetchScatteringMap(vec2 uv) {
float scattering = texture(tableTex(matTex, scatteringMap), uv).r; // boolean scattering for now
return max(((scattering - 0.1) / 0.9), 0.0);
return tableTexValue(matTex, scatteringMap, uv).r; // boolean scattering for now
}
<@endif@>
#else
<@if withAlbedo@>
uniform sampler2D albedoMap;
vec4 fetchAlbedoMap(vec2 uv) {
@ -102,6 +170,8 @@ float fetchScatteringMap(vec2 uv) {
}
<@endif@>
#endif
<@endfunc@>

View file

@ -152,7 +152,7 @@ void MeshPartPayload::bindMesh(gpu::Batch& batch) {
batch.setInputStream(0, _drawMesh->getVertexStream());
}
void MeshPartPayload::bindTransform(gpu::Batch& batch, RenderArgs::RenderMode renderMode) const {
void MeshPartPayload::bindTransform(gpu::Batch& batch, RenderArgs::RenderMode renderMode) const {
batch.setModelTransform(_drawTransform);
}

View file

@ -32,6 +32,7 @@
#include "model_lightmap_fade_vert.h"
#include "model_lightmap_normal_map_fade_vert.h"
#include "model_translucent_vert.h"
#include "model_translucent_normal_map_vert.h"
#include "skin_model_fade_vert.h"
#include "skin_model_normal_map_fade_vert.h"
#include "skin_model_fade_dq_vert.h"
@ -68,11 +69,13 @@
#include "model_lightmap_normal_map_frag.h"
#include "model_translucent_frag.h"
#include "model_translucent_unlit_frag.h"
#include "model_translucent_normal_map_frag.h"
#include "model_lightmap_fade_frag.h"
#include "model_lightmap_normal_map_fade_frag.h"
#include "model_translucent_fade_frag.h"
#include "model_translucent_unlit_fade_frag.h"
#include "model_translucent_normal_map_fade_frag.h"
#include "overlay3D_vert.h"
#include "overlay3D_frag.h"
@ -187,6 +190,7 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip
auto modelLightmapVertex = model_lightmap_vert::getShader();
auto modelLightmapNormalMapVertex = model_lightmap_normal_map_vert::getShader();
auto modelTranslucentVertex = model_translucent_vert::getShader();
auto modelTranslucentNormalMapVertex = model_translucent_normal_map_vert::getShader();
auto modelShadowVertex = model_shadow_vert::getShader();
auto modelLightmapFadeVertex = model_lightmap_fade_vert::getShader();
@ -227,6 +231,7 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip
auto modelNormalMapPixel = model_normal_map_frag::getShader();
auto modelTranslucentPixel = model_translucent_frag::getShader();
auto modelTranslucentUnlitPixel = model_translucent_unlit_frag::getShader();
auto modelTranslucentNormalMapPixel = model_translucent_normal_map_frag::getShader();
auto modelShadowPixel = model_shadow_frag::getShader();
auto modelLightmapPixel = model_lightmap_frag::getShader();
auto modelLightmapNormalMapPixel = model_lightmap_normal_map_frag::getShader();
@ -239,6 +244,7 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip
auto modelShadowFadePixel = model_shadow_fade_frag::getShader();
auto modelTranslucentFadePixel = model_translucent_fade_frag::getShader();
auto modelTranslucentUnlitFadePixel = model_translucent_unlit_fade_frag::getShader();
auto modelTranslucentNormalMapFadePixel = model_translucent_normal_map_fade_frag::getShader();
auto simpleFadePixel = simple_textured_fade_frag::getShader();
auto simpleUnlitFadePixel = simple_textured_unlit_fade_frag::getShader();
auto simpleTranslucentFadePixel = simple_transparent_textured_fade_frag::getShader();
@ -296,7 +302,7 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip
simpleVertex, simpleTranslucentUnlitPixel, nullptr, nullptr);
addPipeline(
Key::Builder().withMaterial().withTranslucent().withTangents(),
modelTranslucentVertex, modelTranslucentPixel, nullptr, nullptr);
modelTranslucentNormalMapVertex, modelTranslucentNormalMapPixel, nullptr, nullptr);
addPipeline(
// FIXME: Ignore lightmap for translucents meshpart
Key::Builder().withMaterial().withTranslucent().withLightmap(),
@ -316,7 +322,7 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip
simpleFadeVertex, simpleTranslucentUnlitFadePixel, batchSetter, itemSetter);
addPipeline(
Key::Builder().withMaterial().withTranslucent().withTangents().withFade(),
modelNormalMapFadeVertex, modelTranslucentFadePixel, batchSetter, itemSetter);
modelTranslucentNormalMapVertex, modelTranslucentNormalMapFadePixel, batchSetter, itemSetter);
addPipeline(
// FIXME: Ignore lightmap for translucents meshpart
Key::Builder().withMaterial().withTranslucent().withLightmap().withFade(),
@ -358,14 +364,14 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip
skinModelTranslucentVertex, modelTranslucentPixel, nullptr, nullptr);
addPipeline(
Key::Builder().withMaterial().withSkinned().withTranslucent().withTangents(),
skinModelNormalMapTranslucentVertex, modelTranslucentPixel, nullptr, nullptr);
skinModelNormalMapTranslucentVertex, modelTranslucentNormalMapPixel, nullptr, nullptr);
// Same thing but with Fade on
addPipeline(
Key::Builder().withMaterial().withSkinned().withTranslucent().withFade(),
skinModelFadeVertex, modelTranslucentFadePixel, batchSetter, itemSetter);
addPipeline(
Key::Builder().withMaterial().withSkinned().withTranslucent().withTangents().withFade(),
skinModelNormalMapFadeVertex, modelTranslucentFadePixel, batchSetter, itemSetter);
skinModelNormalMapFadeVertex, modelTranslucentNormalMapFadePixel, batchSetter, itemSetter);
// dual quaternion skinned
addPipeline(
@ -388,14 +394,14 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip
skinModelTranslucentDualQuatVertex, modelTranslucentPixel, nullptr, nullptr);
addPipeline(
Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTranslucent().withTangents(),
skinModelNormalMapTranslucentDualQuatVertex, modelTranslucentPixel, nullptr, nullptr);
skinModelNormalMapTranslucentDualQuatVertex, modelTranslucentNormalMapPixel, nullptr, nullptr);
// Same thing but with Fade on
addPipeline(
Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTranslucent().withFade(),
skinModelFadeDualQuatVertex, modelTranslucentFadePixel, batchSetter, itemSetter);
addPipeline(
Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTranslucent().withTangents().withFade(),
skinModelNormalMapFadeDualQuatVertex, modelTranslucentFadePixel, batchSetter, itemSetter);
skinModelNormalMapFadeDualQuatVertex, modelTranslucentNormalMapFadePixel, batchSetter, itemSetter);
// Depth-only
addPipeline(
@ -612,7 +618,8 @@ void initZPassPipelines(ShapePlumber& shapePlumber, gpu::StatePointer state) {
#include "RenderPipelines.h"
#include <model-networking/TextureCache.h>
void RenderPipelines::bindMaterial(graphics::MaterialPointer material, gpu::Batch& batch, bool enableTextures) {
// FIXME find a better way to setup the default textures
void RenderPipelines::bindMaterial(const graphics::MaterialPointer& material, gpu::Batch& batch, bool enableTextures) {
if (!material) {
return;
}
@ -630,84 +637,65 @@ void RenderPipelines::bindMaterial(graphics::MaterialPointer material, gpu::Batc
numUnlit++;
}
if (!enableTextures) {
batch.setResourceTexture(ShapePipeline::Slot::ALBEDO, textureCache->getWhiteTexture());
batch.setResourceTexture(ShapePipeline::Slot::MAP::ROUGHNESS, textureCache->getWhiteTexture());
batch.setResourceTexture(ShapePipeline::Slot::MAP::NORMAL, textureCache->getBlueTexture());
batch.setResourceTexture(ShapePipeline::Slot::MAP::METALLIC, textureCache->getBlackTexture());
batch.setResourceTexture(ShapePipeline::Slot::MAP::OCCLUSION, textureCache->getWhiteTexture());
batch.setResourceTexture(ShapePipeline::Slot::MAP::SCATTERING, textureCache->getWhiteTexture());
batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, textureCache->getBlackTexture());
return;
}
const auto& drawMaterialTextures = material->getTextureTable();
// Albedo
if (materialKey.isAlbedoMap()) {
auto itr = textureMaps.find(graphics::MaterialKey::ALBEDO_MAP);
if (itr != textureMaps.end() && itr->second->isDefined()) {
batch.setResourceTexture(ShapePipeline::Slot::ALBEDO, itr->second->getTextureView());
if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) {
drawMaterialTextures->setTexture(ShapePipeline::Slot::ALBEDO, itr->second->getTextureView());
} else {
batch.setResourceTexture(ShapePipeline::Slot::ALBEDO, textureCache->getGrayTexture());
drawMaterialTextures->setTexture(ShapePipeline::Slot::ALBEDO, textureCache->getWhiteTexture());
}
}
// Roughness map
if (materialKey.isRoughnessMap()) {
auto itr = textureMaps.find(graphics::MaterialKey::ROUGHNESS_MAP);
if (itr != textureMaps.end() && itr->second->isDefined()) {
batch.setResourceTexture(ShapePipeline::Slot::MAP::ROUGHNESS, itr->second->getTextureView());
// texcoord are assumed to be the same has albedo
if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) {
drawMaterialTextures->setTexture(ShapePipeline::Slot::ROUGHNESS, itr->second->getTextureView());
} else {
batch.setResourceTexture(ShapePipeline::Slot::MAP::ROUGHNESS, textureCache->getWhiteTexture());
drawMaterialTextures->setTexture(ShapePipeline::Slot::ROUGHNESS, textureCache->getWhiteTexture());
}
}
// Normal map
if (materialKey.isNormalMap()) {
auto itr = textureMaps.find(graphics::MaterialKey::NORMAL_MAP);
if (itr != textureMaps.end() && itr->second->isDefined()) {
batch.setResourceTexture(ShapePipeline::Slot::MAP::NORMAL, itr->second->getTextureView());
// texcoord are assumed to be the same has albedo
if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) {
drawMaterialTextures->setTexture(ShapePipeline::Slot::NORMAL, itr->second->getTextureView());
} else {
batch.setResourceTexture(ShapePipeline::Slot::MAP::NORMAL, textureCache->getBlueTexture());
drawMaterialTextures->setTexture(ShapePipeline::Slot::NORMAL, textureCache->getBlueTexture());
}
}
// Metallic map
if (materialKey.isMetallicMap()) {
auto itr = textureMaps.find(graphics::MaterialKey::METALLIC_MAP);
if (itr != textureMaps.end() && itr->second->isDefined()) {
batch.setResourceTexture(ShapePipeline::Slot::MAP::METALLIC, itr->second->getTextureView());
// texcoord are assumed to be the same has albedo
if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) {
drawMaterialTextures->setTexture(ShapePipeline::Slot::METALLIC, itr->second->getTextureView());
} else {
batch.setResourceTexture(ShapePipeline::Slot::MAP::METALLIC, textureCache->getBlackTexture());
drawMaterialTextures->setTexture(ShapePipeline::Slot::METALLIC, textureCache->getBlackTexture());
}
}
// Occlusion map
if (materialKey.isOcclusionMap()) {
auto itr = textureMaps.find(graphics::MaterialKey::OCCLUSION_MAP);
if (itr != textureMaps.end() && itr->second->isDefined()) {
batch.setResourceTexture(ShapePipeline::Slot::MAP::OCCLUSION, itr->second->getTextureView());
// texcoord are assumed to be the same has albedo
if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) {
drawMaterialTextures->setTexture(ShapePipeline::Slot::OCCLUSION, itr->second->getTextureView());
} else {
batch.setResourceTexture(ShapePipeline::Slot::MAP::OCCLUSION, textureCache->getWhiteTexture());
drawMaterialTextures->setTexture(ShapePipeline::Slot::OCCLUSION, textureCache->getWhiteTexture());
}
}
// Scattering map
if (materialKey.isScatteringMap()) {
auto itr = textureMaps.find(graphics::MaterialKey::SCATTERING_MAP);
if (itr != textureMaps.end() && itr->second->isDefined()) {
batch.setResourceTexture(ShapePipeline::Slot::MAP::SCATTERING, itr->second->getTextureView());
// texcoord are assumed to be the same has albedo
if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) {
drawMaterialTextures->setTexture(ShapePipeline::Slot::SCATTERING, itr->second->getTextureView());
} else {
batch.setResourceTexture(ShapePipeline::Slot::MAP::SCATTERING, textureCache->getWhiteTexture());
drawMaterialTextures->setTexture(ShapePipeline::Slot::SCATTERING, textureCache->getWhiteTexture());
}
}
@ -715,18 +703,19 @@ void RenderPipelines::bindMaterial(graphics::MaterialPointer material, gpu::Batc
if (materialKey.isLightmapMap()) {
auto itr = textureMaps.find(graphics::MaterialKey::LIGHTMAP_MAP);
if (itr != textureMaps.end() && itr->second->isDefined()) {
batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, itr->second->getTextureView());
if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) {
drawMaterialTextures->setTexture(ShapePipeline::Slot::EMISSIVE_LIGHTMAP, itr->second->getTextureView());
} else {
batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, textureCache->getGrayTexture());
drawMaterialTextures->setTexture(ShapePipeline::Slot::EMISSIVE_LIGHTMAP, textureCache->getGrayTexture());
}
} else if (materialKey.isEmissiveMap()) {
auto itr = textureMaps.find(graphics::MaterialKey::EMISSIVE_MAP);
if (itr != textureMaps.end() && itr->second->isDefined()) {
batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, itr->second->getTextureView());
if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) {
drawMaterialTextures->setTexture(ShapePipeline::Slot::EMISSIVE_LIGHTMAP, itr->second->getTextureView());
} else {
batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, textureCache->getBlackTexture());
drawMaterialTextures->setTexture(ShapePipeline::Slot::EMISSIVE_LIGHTMAP, textureCache->getBlackTexture());
}
}
batch.setResourceTextureTable(material->getTextureTable());
}

View file

@ -15,7 +15,7 @@
class RenderPipelines {
public:
static void bindMaterial(graphics::MaterialPointer material, gpu::Batch& batch, bool enableTextures);
static void bindMaterial(const graphics::MaterialPointer& material, gpu::Batch& batch, bool enableTextures);
};

View file

@ -46,7 +46,7 @@ void printOctalCode(const unsigned char* octalCode) {
}
char sectionValue(const unsigned char* startByte, char startIndexInByte) {
char rightShift = 8 - startIndexInByte - 3;
int8_t rightShift = 8 - startIndexInByte - 3;
if (rightShift < 0) {
return ((startByte[0] << -rightShift) & 7) + (startByte[1] >> (8 + rightShift));
@ -73,7 +73,7 @@ int branchIndexWithDescendant(const unsigned char* ancestorOctalCode, const unsi
return sectionValue(descendantOctalCode + 1 + (branchStartBit / 8), branchStartBit % 8);
}
unsigned char* childOctalCode(const unsigned char* parentOctalCode, char childNumber) {
unsigned char* childOctalCode(const unsigned char* parentOctalCode, int childNumber) {
// find the length (in number of three bit code sequences)
// in the parent
@ -111,7 +111,7 @@ unsigned char* childOctalCode(const unsigned char* parentOctalCode, char childNu
// calculate the amount of left shift required
// this will be -1 or -2 if there's wrap
char leftShift = 8 - (startBit % 8) - 3;
int8_t leftShift = 8 - (startBit % 8) - 3;
if (leftShift < 0) {
// we have a wrap-around to accomodate

View file

@ -30,7 +30,7 @@ using OctalCodePtrList = std::vector<OctalCodePtr>;
void printOctalCode(const unsigned char* octalCode);
size_t bytesRequiredForCodeLength(unsigned char threeBitCodes);
int branchIndexWithDescendant(const unsigned char* ancestorOctalCode, const unsigned char* descendantOctalCode);
unsigned char* childOctalCode(const unsigned char* parentOctalCode, char childNumber);
unsigned char* childOctalCode(const unsigned char* parentOctalCode, int childNumber);
const int OVERFLOWED_OCTCODE_BUFFER = -1;
const int UNKNOWN_OCTCODE_LENGTH = -2;

View file

@ -1,5 +1,3 @@
"use strict";
//
// debugTransition.js
// developer/utilities/render
@ -12,12 +10,17 @@
//
(function() {
"use strict";
var TABLET_BUTTON_NAME = "Transition";
var QMLAPP_URL = Script.resolvePath("./transition.qml");
var ICON_URL = Script.resolvePath("../../../system/assets/images/transition-i.svg");
var ACTIVE_ICON_URL = Script.resolvePath("../../../system/assets/images/transition-a.svg");
Script.include([
Script.resolvePath("../../../system/libraries/stringHelpers.js"),
]);
var onScreen = false;
function onClicked() {
@ -37,6 +40,71 @@
var hasEventBridge = false;
function enableSphereVisualization() {
if (gradientSphere==undefined) {
gradientSphere = Overlays.addOverlay("sphere", {
position: MyAvatar.position,
rotation: Quat.fromPitchYawRollDegrees(0.0, 0.0, 0.0),
dimensions: { x: 1.0, y: 1.0, z: 1.0 },
color: { red: 100, green: 150, blue: 255},
alpha: 0.2,
solid: false
});
}
if (noiseSphere==undefined) {
noiseSphere = Overlays.addOverlay("sphere", {
position: MyAvatar.position,
rotation: Quat.fromPitchYawRollDegrees(0.0, 0.0, 0.0),
dimensions: { x: 1.0, y: 1.0, z: 1.0 },
color: { red: 255, green: 150, blue: 100},
alpha: 0.2,
solid: false
});
}
}
function disableSphereVisualization() {
Overlays.deleteOverlay(noiseSphere);
Overlays.deleteOverlay(gradientSphere);
noiseSphere = undefined
gradientSphere = undefined
}
// Create a Laser pointer used to pick and add objects to selections
var END_DIMENSIONS = { x: 0.05, y: 0.05, z: 0.05 };
var COLOR1 = {red: 255, green: 0, blue: 0};
var COLOR2 = {red: 0, green: 255, blue: 0};
var end1 = {
type: "sphere",
dimensions: END_DIMENSIONS,
color: COLOR1,
ignoreRayIntersection: true
}
var end2 = {
type: "sphere",
dimensions: END_DIMENSIONS,
color: COLOR2,
ignoreRayIntersection: true
}
var laser
function enablePointer() {
laser = Pointers.createPointer(PickType.Ray, {
joint: "Mouse",
filter: Picks.PICK_ENTITIES,
renderStates: [{name: "one", end: end1}],
defaultRenderStates: [{name: "one", end: end2, distance: 2.0}],
enabled: true
});
Pointers.setRenderState(laser, "one");
Pointers.enablePointer(laser)
}
function disablePointer() {
Pointers.disablePointer(laser)
Pointers.removePointer(laser);
}
function wireEventBridge(on) {
if (!tablet) {
print("Warning in wireEventBridge(): 'tablet' undefined!");
@ -46,11 +114,15 @@
if (!hasEventBridge) {
tablet.fromQml.connect(fromQml);
hasEventBridge = true;
enablePointer();
Render.getConfig("RenderMainView.FadeEdit")["editFade"] = true
}
} else {
if (hasEventBridge) {
tablet.fromQml.disconnect(fromQml);
hasEventBridge = false;
disablePointer();
Render.getConfig("RenderMainView.FadeEdit")["editFade"] = false
}
}
}
@ -66,12 +138,87 @@
wireEventBridge(onScreen);
}
var isEditEnabled = false
var noiseSphere
var gradientSphere
var selectedEntity
var editedCategory
var FADE_MIN_SCALE = 0.001
var FADE_MAX_SCALE = 10000.0
function parameterToValuePow(parameter, minValue, maxOverMinValue) {
return minValue * Math.pow(maxOverMinValue, parameter);
//return parameter
}
function update(dt) {
var gradientProperties = Entities.getEntityProperties(selectedEntity, ["position", "dimensions"]);
if (gradientProperties!=undefined) {
var pos = gradientProperties.position
if (pos!=undefined) {
var config = Render.getConfig("RenderMainView.Fade")
//print("Center at "+pos.x+" "+pos.y+" "+pos.z)
var dim = {x:config.baseSizeX, y:config.baseSizeY, z:config.baseSizeZ}
dim.x = parameterToValuePow(dim.x, FADE_MIN_SCALE, FADE_MAX_SCALE/ FADE_MIN_SCALE)
dim.y = parameterToValuePow(dim.y, FADE_MIN_SCALE, FADE_MAX_SCALE/ FADE_MIN_SCALE)
dim.z = parameterToValuePow(dim.z, FADE_MIN_SCALE, FADE_MAX_SCALE/ FADE_MIN_SCALE)
if (editedCategory==4 || editedCategory==5) {
dim.y = gradientProperties.dimensions.y
pos.y = pos.y - gradientProperties.dimensions.y/2
}
Overlays.editOverlay(gradientSphere, { position: pos, dimensions: dim, alpha: config.baseLevel })
dim.x = parameterToValuePow(config.noiseSizeX, FADE_MIN_SCALE, FADE_MAX_SCALE/ FADE_MIN_SCALE)
dim.y = parameterToValuePow(config.noiseSizeY, FADE_MIN_SCALE, FADE_MAX_SCALE/ FADE_MIN_SCALE)
dim.z = parameterToValuePow(config.noiseSizeZ, FADE_MIN_SCALE, FADE_MAX_SCALE/ FADE_MIN_SCALE)
Overlays.editOverlay(noiseSphere, { position: pos, dimensions: dim, alpha: config.noiseLevel })
}
}
}
Script.update.connect(update);
function loadConfiguration(fileUrl) {
var config = Render.getConfig("RenderMainView.Fade")
config.load(fileUrl)
}
function saveConfiguration(fileUrl) {
var config = Render.getConfig("RenderMainView.Fade")
config.save(fileUrl)
}
function fromQml(message) {
tokens = message.split('*')
//print("Received '"+message+"' from transition.qml")
command = tokens[0].toLowerCase()
if (command=="category") {
editedCategory = parseInt(tokens[1])
} else if (command=="save") {
var filePath = tokens[1]
print("Raw token = "+filePath)
if (filePath.startsWith("file:///")) {
filePath = filePath.substr(8)
print("Saving configuration to "+filePath)
saveConfiguration(filePath)
} else {
print("Configurations can only be saved to local files")
}
} else if (command=="load") {
var filePath = tokens[1]
if (filePath.startsWith("file:///")) {
filePath = filePath.substr(8)
print("Loading configuration from "+filePath)
loadConfiguration(filePath)
} else {
print("Configurations can only be loaded from local files")
}
}
}
button.clicked.connect(onClicked);
tablet.screenChanged.connect(onScreenChanged);
Script.scriptEnding.connect(function () {
if (onScreen) {
tablet.gotoHomeScreen();
@ -81,4 +228,28 @@
tablet.removeButton(button);
});
var currentSelectionName = ""
var SelectionList = "TransitionEdit"
Selection.enableListToScene(SelectionList)
Selection.clearSelectedItemsList(SelectionList)
Entities.clickDownOnEntity.connect(function (id, event) {
if (selectedEntity) {
Selection.removeFromSelectedItemsList(SelectionList, "entity", selectedEntity)
}
selectedEntity = id
Selection.addToSelectedItemsList(SelectionList, "entity", selectedEntity)
update()
})
function cleanup() {
disablePointer();
Selection.removeListFromMap(SelectionList)
Selection.disableListToScene(SelectionList);
Overlays.deleteOverlay(noiseSphere);
Overlays.deleteOverlay(gradientSphere);
}
Script.scriptEnding.connect(cleanup);
}());

View file

@ -11,6 +11,7 @@
import QtQuick 2.7
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.0
import "qrc:///qml/styles-uit"
import "qrc:///qml/controls-uit" as HifiControls
@ -22,11 +23,31 @@ Rectangle {
id: root
anchors.margins: hifi.dimensions.contentMargin.x
signal sendToScript(var message);
color: hifi.colors.baseGray;
property var config: Render.getConfig("RenderMainView.Fade");
property var configEdit: Render.getConfig("RenderMainView.FadeEdit");
FileDialog {
id: fileDialog
title: "Please choose a file"
folder: shortcuts.documents
nameFilters: [ "JSON files (*.json)", "All files (*)" ]
onAccepted: {
root.sendToScript(title.split(" ")[0]+"*"+fileUrl.toString())
// This is a hack to be sure the widgets below properly reflect the change of category: delete the Component
// by setting the loader source to Null and then recreate it 500ms later
paramWidgetLoader.sourceComponent = undefined;
postpone.interval = 500
postpone.start()
}
onRejected: {
}
Component.onCompleted: visible = false
}
ColumnLayout {
spacing: 3
anchors.left: parent.left
@ -37,23 +58,14 @@ Rectangle {
}
RowLayout {
spacing: 20
spacing: 8
Layout.fillWidth: true
id: root_col
HifiControls.CheckBox {
anchors.verticalCenter: parent.verticalCenter
boxSize: 20
text: "Edit"
checked: root.configEdit["editFade"]
onCheckedChanged: {
root.configEdit["editFade"] = checked;
Render.getConfig("RenderMainView.DrawFadedOpaqueBounds").enabled = checked;
}
}
HifiControls.ComboBox {
anchors.verticalCenter: parent.verticalCenter
Layout.fillWidth: true
anchors.left : parent.left
width: 300
id: categoryBox
model: ["Elements enter/leave domain", "Bubble isect. - Owner POV", "Bubble isect. - Trespasser POV", "Another user leaves/arrives", "Changing an avatar"]
Timer {
@ -61,17 +73,48 @@ Rectangle {
interval: 100; running: false; repeat: false
onTriggered: {
paramWidgetLoader.sourceComponent = paramWidgets
var isTimeBased = categoryBox.currentIndex==0 || categoryBox.currentIndex==3
paramWidgetLoader.item.isTimeBased = isTimeBased
}
}
onCurrentIndexChanged: {
var descriptions = [
"Time based threshold, gradients centered on object",
"Fixed threshold, gradients centered on owner avatar",
"Position based threshold (increases when trespasser moves closer to avatar), gradients centered on trespasser avatar",
"Time based threshold, gradients centered on bottom of object",
"UNSUPPORTED"
]
description.text = descriptions[currentIndex]
root.config["editedCategory"] = currentIndex;
// This is a hack to be sure the widgets below properly reflect the change of category: delete the Component
// by setting the loader source to Null and then recreate it 100ms later
paramWidgetLoader.sourceComponent = undefined;
postpone.interval = 100
postpone.start()
root.sendToScript("category*"+currentIndex)
}
}
HifiControls.Button {
action: saveAction
Layout.fillWidth: true
anchors.top: parent.top
anchors.bottom: parent.bottom
}
HifiControls.Button {
action: loadAction
Layout.fillWidth: true
anchors.top: parent.top
anchors.bottom: parent.bottom
}
}
HifiControls.Label {
id: description
text: "..."
Layout.fillWidth: true
wrapMode: Text.WordWrap
}
RowLayout {
@ -104,19 +147,18 @@ Rectangle {
id: saveAction
text: "Save"
onTriggered: {
root.config.save()
fileDialog.title = "Save configuration..."
fileDialog.selectExisting = false
fileDialog.open()
}
}
Action {
id: loadAction
text: "Load"
onTriggered: {
root.config.load()
// This is a hack to be sure the widgets below properly reflect the change of category: delete the Component
// by setting the loader source to Null and then recreate it 500ms later
paramWidgetLoader.sourceComponent = undefined;
postpone.interval = 500
postpone.start()
fileDialog.title = "Load configuration..."
fileDialog.selectExisting = true
fileDialog.open()
}
}
@ -128,13 +170,8 @@ Rectangle {
ColumnLayout {
spacing: 3
width: root_col.width
property bool isTimeBased
HifiControls.CheckBox {
text: "Invert"
boxSize: 20
checked: root.config["isInverted"]
onCheckedChanged: { root.config["isInverted"] = checked }
}
RowLayout {
Layout.fillWidth: true
@ -196,16 +233,32 @@ Rectangle {
}
}
ConfigSlider {
RowLayout {
spacing: 20
height: 36
label: "Edge Width"
integral: false
config: root.config
property: "edgeWidth"
max: 1.0
min: 0.0
HifiControls.CheckBox {
text: "Invert gradient"
anchors.verticalCenter: parent.verticalCenter
boxSize: 20
checked: root.config["isInverted"]
onCheckedChanged: { root.config["isInverted"] = checked }
}
ConfigSlider {
anchors.left: undefined
anchors.verticalCenter: parent.verticalCenter
height: 36
width: 300
label: "Edge Width"
integral: false
config: root.config
property: "edgeWidth"
max: 1.0
min: 0.0
}
}
RowLayout {
Layout.fillWidth: true
@ -278,6 +331,8 @@ Rectangle {
Layout.fillWidth: true
ConfigSlider {
enabled: isTimeBased
opacity: isTimeBased ? 1.0 : 0.0
anchors.left: undefined
anchors.right: undefined
Layout.fillWidth: true
@ -290,6 +345,8 @@ Rectangle {
min: 0.1
}
HifiControls.ComboBox {
enabled: isTimeBased
opacity: isTimeBased ? 1.0 : 0.0
Layout.fillWidth: true
model: ["Linear", "Ease In", "Ease Out", "Ease In / Out"]
currentIndex: root.config["timing"]
@ -364,20 +421,6 @@ Rectangle {
id: paramWidgetLoader
sourceComponent: paramWidgets
}
Row {
anchors.left: parent.left
anchors.right: parent.right
Button {
action: saveAction
}
Button {
action: loadAction
}
}
}
}

View file

@ -14,7 +14,7 @@
PICK_MAX_DISTANCE, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD,
DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, ZERO_VEC, ensureDynamic,
getControllerWorldLocation, projectOntoEntityXYPlane, ContextOverlay, HMD, Reticle, Overlays, isPointingAtUI
Picks, makeLaserLockInfo Xform, makeLaserParams, AddressManager, getEntityParents
Picks, makeLaserLockInfo Xform, makeLaserParams, AddressManager, getEntityParents, Selection
*/
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
@ -295,6 +295,7 @@ Script.include("/~/system/libraries/Xform.js");
this.actionID = null;
this.grabbedThingID = null;
this.targetObject = null;
this.potentialEntityWithContextOverlay = false;
};
this.updateRecommendedArea = function() {
@ -467,8 +468,10 @@ Script.include("/~/system/libraries/Xform.js");
Script.clearTimeout(this.contextOverlayTimer);
}
this.contextOverlayTimer = false;
if (entityID !== this.entityWithContextOverlay) {
if (entityID === this.entityWithContextOverlay) {
this.destroyContextOverlay();
} else {
Selection.removeFromSelectedItemsList("contextOverlayHighlightList", "entity", entityID);
}
var targetEntity = this.targetObject.getTargetEntity();

View file

@ -40,7 +40,7 @@
}
function onScreenChanged(type, url) {
onHelpScreen = type === "Web" && url.startsWith(HELP_URL);
onHelpScreen = type === "Web" && (url.indexOf(HELP_URL) === 0);
button.editProperties({ isActive: onHelpScreen });
}

View file

@ -1618,8 +1618,18 @@ SelectionDisplay = (function() {
grid.snapToGrid(Vec3.sum(cornerPosition, vector), constrainMajorOnly),
cornerPosition);
for (var i = 0; i < SelectionManager.selections.length; i++) {
var properties = SelectionManager.savedProperties[SelectionManager.selections[i]];
// editing a parent will cause all the children to automatically follow along, so don't
// edit any entity who has an ancestor in SelectionManager.selections
var toMove = SelectionManager.selections.filter(function (selection) {
if (SelectionManager.selections.indexOf(SelectionManager.savedProperties[selection].parentID) >= 0) {
return false; // a parent is also being moved, so don't issue an edit for this entity
} else {
return true;
}
});
for (var i = 0; i < toMove.length; i++) {
var properties = SelectionManager.savedProperties[toMove[i]];
if (!properties) {
continue;
}
@ -1628,7 +1638,7 @@ SelectionDisplay = (function() {
y: 0,
z: vector.z
});
Entities.editEntity(SelectionManager.selections[i], {
Entities.editEntity(toMove[i], {
position: newPosition
});
@ -1653,11 +1663,11 @@ SelectionDisplay = (function() {
mode: mode,
onBegin: function(event, pickRay, pickResult) {
if (direction === TRANSLATE_DIRECTION.X) {
pickNormal = { x:0, y:0, z:1 };
pickNormal = { x:0, y:1, z:1 };
} else if (direction === TRANSLATE_DIRECTION.Y) {
pickNormal = { x:1, y:0, z:0 };
pickNormal = { x:1, y:0, z:1 };
} else if (direction === TRANSLATE_DIRECTION.Z) {
pickNormal = { x:0, y:1, z:0 };
pickNormal = { x:1, y:1, z:0 };
}
var rotation = spaceMode === SPACE_LOCAL ? SelectionManager.localRotation : SelectionManager.worldRotation;
@ -1701,7 +1711,6 @@ SelectionDisplay = (function() {
onMove: function(event) {
pickRay = generalComputePickRay(event.x, event.y);
// translate mode left/right based on view toward entity
var newIntersection = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, pickNormal);
var vector = Vec3.subtract(newIntersection, lastPick);
@ -1719,7 +1728,7 @@ SelectionDisplay = (function() {
var dotVector = Vec3.dot(vector, projectionVector);
vector = Vec3.multiply(dotVector, projectionVector);
vector = grid.snapToGrid(vector);
var wantDebug = false;
if (wantDebug) {
print("translateUpDown... ");
@ -1727,9 +1736,19 @@ SelectionDisplay = (function() {
Vec3.print(" newIntersection:", newIntersection);
Vec3.print(" vector:", vector);
}
for (var i = 0; i < SelectionManager.selections.length; i++) {
var id = SelectionManager.selections[i];
// editing a parent will cause all the children to automatically follow along, so don't
// edit any entity who has an ancestor in SelectionManager.selections
var toMove = SelectionManager.selections.filter(function (selection) {
if (SelectionManager.selections.indexOf(SelectionManager.savedProperties[selection].parentID) >= 0) {
return false; // a parent is also being moved, so don't issue an edit for this entity
} else {
return true;
}
});
for (var i = 0; i < toMove.length; i++) {
var id = toMove[i];
var properties = SelectionManager.savedProperties[id];
var newPosition = Vec3.sum(properties.position, vector);
Entities.editEntity(id, { position: newPosition });
@ -2017,10 +2036,10 @@ SelectionDisplay = (function() {
vector = grid.snapToSpacing(vector);
var changeInDimensions = Vec3.multiply(NEGATE_VECTOR, vec3Mult(localSigns, vector));
if (directionEnum === STRETCH_DIRECTION.ALL) {
var toCameraDistance = getDistanceToCamera(position);
var dimensionsMultiple = toCameraDistance * STRETCH_DIRECTION_ALL_CAMERA_DISTANCE_MULTIPLE;
changeInDimensions = Vec3.multiply(changeInDimensions, dimensionsMultiple);
if (directionEnum === STRETCH_DIRECTION.ALL) {
var toCameraDistance = getDistanceToCamera(position);
var dimensionsMultiple = toCameraDistance * STRETCH_DIRECTION_ALL_CAMERA_DISTANCE_MULTIPLE;
changeInDimensions = Vec3.multiply(changeInDimensions, dimensionsMultiple);
}
var newDimensions;
@ -2166,8 +2185,19 @@ SelectionDisplay = (function() {
// the selections center point. Otherwise, the rotation will be around the entities
// registration point which does not need repositioning.
var reposition = (SelectionManager.selections.length > 1);
for (var i = 0; i < SelectionManager.selections.length; i++) {
var entityID = SelectionManager.selections[i];
// editing a parent will cause all the children to automatically follow along, so don't
// edit any entity who has an ancestor in SelectionManager.selections
var toRotate = SelectionManager.selections.filter(function (selection) {
if (SelectionManager.selections.indexOf(SelectionManager.savedProperties[selection].parentID) >= 0) {
return false; // a parent is also being moved, so don't issue an edit for this entity
} else {
return true;
}
});
for (var i = 0; i < toRotate.length; i++) {
var entityID = toRotate[i];
var initialProperties = SelectionManager.savedProperties[entityID];
var newProperties = {

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