mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-05-27 21:31:34 +02:00
Merge branch 'master' of github.com:highfidelity/hifi into commerce_fixCertificateInfoError
This commit is contained in:
commit
af5a68b282
109 changed files with 2633 additions and 1102 deletions
|
@ -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;
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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 ""
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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" },
|
||||
|
|
|
@ -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 != ""
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -61,9 +61,6 @@ Item {
|
|||
if (root.shouldImmediatelyFocus) {
|
||||
focusFirstTextField();
|
||||
}
|
||||
sendMessageToLightbox({method: 'disableHmdPreview'});
|
||||
} else {
|
||||
sendMessageToLightbox({method: 'maybeEnableHmdPreview'});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -237,7 +237,7 @@ Rectangle {
|
|||
} else {
|
||||
sendToScript(msg);
|
||||
}
|
||||
} else if (msg.method === 'maybeEnableHmdPreview') {
|
||||
} else {
|
||||
sendToScript(msg);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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" },
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -131,6 +131,8 @@ ItemKey ShapeEntityRenderer::getKey() {
|
|||
withReadLock([&] {
|
||||
if (isTransparent()) {
|
||||
builder.withTransparent();
|
||||
} else if (_canCastShadow) {
|
||||
builder.withShadowCaster();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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; }
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
} }
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
54
libraries/gpu/src/gpu/TextureTable.cpp
Normal file
54
libraries/gpu/src/gpu/TextureTable.cpp
Normal 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;
|
||||
}
|
44
libraries/gpu/src/gpu/TextureTable.h
Normal file
44
libraries/gpu/src/gpu/TextureTable.h
Normal 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
|
38
libraries/gpu/src/gpu/TextureTable.slh
Normal file
38
libraries/gpu/src/gpu/TextureTable.slh
Normal 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@>
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 };
|
||||
};
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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@>
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}());
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue