merge 'master' into 'workload'

This commit is contained in:
Andrew Meadows 2018-04-05 12:01:19 -07:00
commit 299f2a92e0
50 changed files with 1572 additions and 851 deletions

View file

@ -33,7 +33,7 @@ uint64_t AvatarMixerClientData::getLastOtherAvatarEncodeTime(QUuid otherAvatar)
return 0; 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); std::unordered_map<QUuid, uint64_t>::iterator itr = _lastOtherAvatarEncodeTime.find(otherAvatar);
if (itr != _lastOtherAvatarEncodeTime.end()) { if (itr != _lastOtherAvatarEncodeTime.end()) {
itr->second = time; itr->second = time;

View file

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

View file

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

View file

@ -24,6 +24,18 @@ Item {
HifiConstants { id: hifi; } HifiConstants { id: hifi; }
id: root; 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 // Username Text
RalewayRegular { RalewayRegular {

View file

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

View file

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

View file

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

View file

@ -25,18 +25,6 @@ Item {
id: root; id: root;
property alias currentIndex: securityImageGrid.currentIndex; 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 { SecurityImageModel {
id: gridModel; id: gridModel;

View file

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

View file

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

View file

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

View file

@ -4688,7 +4688,7 @@ void Application::init() {
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>(); auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
// connect the _entityCollisionSystem to our EntityTreeRenderer since that's what handles running entity scripts // 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); getEntities().data(), &EntityTreeRenderer::entityCollisionWithEntity);
// connect the _entities (EntityTreeRenderer) to our script engine's EntityScriptingInterface for firing // connect the _entities (EntityTreeRenderer) to our script engine's EntityScriptingInterface for firing
@ -5285,11 +5285,13 @@ void Application::update(float deltaTime) {
{ {
PROFILE_RANGE(simulation_physics, "PreStep"); PROFILE_RANGE(simulation_physics, "PreStep");
PerformanceTimer perfTimer("preStep)"); PerformanceTimer perfTimer("preStep)");
static VectorOfMotionStates motionStates; {
_entitySimulation->getObjectsToRemoveFromPhysics(motionStates); const VectorOfMotionStates& motionStates = _entitySimulation->getObjectsToRemoveFromPhysics();
_physicsEngine->removeObjects(motionStates); _physicsEngine->removeObjects(motionStates);
_entitySimulation->deleteObjectsRemovedFromPhysics(); _entitySimulation->deleteObjectsRemovedFromPhysics();
}
VectorOfMotionStates motionStates;
getEntities()->getTree()->withReadLock([&] { getEntities()->getTree()->withReadLock([&] {
_entitySimulation->getObjectsToAddToPhysics(motionStates); _entitySimulation->getObjectsToAddToPhysics(motionStates);
_physicsEngine->addObjects(motionStates); _physicsEngine->addObjects(motionStates);
@ -5303,7 +5305,7 @@ void Application::update(float deltaTime) {
_entitySimulation->applyDynamicChanges(); _entitySimulation->applyDynamicChanges();
avatarManager->getObjectsToRemoveFromPhysics(motionStates); avatarManager->getObjectsToRemoveFromPhysics(motionStates);
_physicsEngine->removeObjects(motionStates); _physicsEngine->removeObjects(motionStates);
avatarManager->getObjectsToAddToPhysics(motionStates); avatarManager->getObjectsToAddToPhysics(motionStates);
_physicsEngine->addObjects(motionStates); _physicsEngine->addObjects(motionStates);

View file

@ -86,10 +86,6 @@ void LODManager::autoAdjustLOD(float realTimeDelta) {
if (_octreeSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) { if (_octreeSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) {
_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(); emit LODDecreased();
// Assuming the LOD adjustment will work: we optimistically reset _avgRenderTime // 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 // 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) { if (_octreeSizeScale > ADJUST_LOD_MAX_SIZE_SCALE) {
_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(); emit LODIncreased();
// Assuming the LOD adjustment will work: we optimistically reset _avgRenderTime // 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 // to provide an FPS just below the increase threshold. It will drift close to its

View file

@ -110,7 +110,6 @@ bool SelectionScriptingInterface::enableListHighlight(const QString& listName, c
} }
if (!(*highlightStyle).isBoundToList()) { if (!(*highlightStyle).isBoundToList()) {
setupHandler(listName);
(*highlightStyle).setBoundToList(true); (*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) { template <class T> bool SelectionScriptingInterface::addToGameplayObjects(const QString& listName, T idToAdd) {
{ {
QWriteLocker lock(&_selectionListsLock); QWriteLocker lock(&_selectionListsLock);
@ -303,6 +314,15 @@ void SelectionScriptingInterface::setupHandler(const QString& selectionName) {
(*handler)->initialize(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) { void SelectionScriptingInterface::onSelectedItemsListChanged(const QString& listName) {
{ {
QWriteLocker lock(&_selectionHandlersLock); QWriteLocker lock(&_selectionHandlersLock);

View file

@ -160,13 +160,14 @@ public:
* If the Selection doesn't exist, it will be created. * 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. * 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. * The function can be called several times with different values in the style to modify it.
* *
* @function Selection.enableListHighlight * @function Selection.enableListHighlight
* @param listName {string} name of the selection * @param listName {string} name of the selection
* @param highlightStyle {jsObject} highlight style fields (see Selection.getListHighlightStyle for a detailed description of the highlightStyle). * @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. * @returns {bool} true if the selection was successfully enabled for highlight.
*/ */
Q_INVOKABLE bool enableListHighlight(const QString& listName, const QVariantMap& highlightStyle); Q_INVOKABLE bool enableListHighlight(const QString& listName, const QVariantMap& highlightStyle);
/**jsdoc /**jsdoc
* Disable highlighting for the named selection. * Disable highlighting for the named selection.
* If the Selection doesn't exist or wasn't enabled for highliting then nothing happens simply returning false. * 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 * @param listName {string} name of the selection
* @returns {bool} true if the selection was successfully disabled for highlight, false otherwise. * @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 /**jsdoc
* Query the highlight style values for the named selection. * 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. * 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); template <class T> bool removeFromGameplayObjects(const QString& listName, T idToRemove);
void setupHandler(const QString& selectionName); void setupHandler(const QString& selectionName);
void removeHandler(const QString& selectionName);
}; };

View file

@ -50,7 +50,9 @@ ContextOverlayInterface::ContextOverlayInterface() {
_entityPropertyFlags += PROP_OWNING_AVATAR_ID; _entityPropertyFlags += PROP_OWNING_AVATAR_ID;
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>().data(); 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::hoverEnterEntity, this, &ContextOverlayInterface::contextOverlays_hoverEnterEntity);
connect(entityScriptingInterface, &EntityScriptingInterface::hoverLeaveEntity, this, &ContextOverlayInterface::contextOverlays_hoverLeaveEntity); connect(entityScriptingInterface, &EntityScriptingInterface::hoverLeaveEntity, this, &ContextOverlayInterface::contextOverlays_hoverLeaveEntity);
connect(_tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"), &TabletProxy::tabletShownChanged, this, [&]() { connect(_tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"), &TabletProxy::tabletShownChanged, this, [&]() {
@ -97,6 +99,31 @@ void ContextOverlayInterface::setEnabled(bool enabled) {
_enabled = 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) { bool ContextOverlayInterface::createOrDestroyContextOverlay(const EntityItemID& entityItemID, const PointerEvent& event) {
if (_enabled && event.getButton() == PointerEvent::SecondaryButton) { if (_enabled && event.getButton() == PointerEvent::SecondaryButton) {
if (contextOverlayFilterPassed(entityItemID)) { if (contextOverlayFilterPassed(entityItemID)) {

View file

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

View file

@ -1911,7 +1911,7 @@ void EntityItem::computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask
} }
} }
void EntityItem::setSimulationOwner(const QUuid& id, quint8 priority) { void EntityItem::setSimulationOwner(const QUuid& id, uint8_t priority) {
if (wantTerseEditLogging() && (id != _simulationOwner.getID() || priority != _simulationOwner.getPriority())) { if (wantTerseEditLogging() && (id != _simulationOwner.getID() || priority != _simulationOwner.getPriority())) {
qCDebug(entities) << "sim ownership for" << getDebugName() << "is now" << id << priority; 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); _simulationOwner.setPendingPriority(priority, timestamp);
} }
@ -2967,13 +2967,6 @@ void EntityItem::retrieveMarketplacePublicKey() {
} }
void EntityItem::preDelete() { 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) { void EntityItem::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) {

View file

@ -304,14 +304,14 @@ public:
// FIXME not thread safe? // FIXME not thread safe?
const SimulationOwner& getSimulationOwner() const { return _simulationOwner; } 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 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(); } QUuid getSimulatorID() const { return _simulationOwner.getID(); }
void clearSimulationOwnership(); void clearSimulationOwnership();
void setPendingOwnershipPriority(quint8 priority, const quint64& timestamp); void setPendingOwnershipPriority(uint8_t priority, const quint64& timestamp);
uint8_t getPendingOwnershipPriority() const { return _simulationOwner.getPendingPriority(); } uint8_t getPendingOwnershipPriority() const { return _simulationOwner.getPendingPriority(); }
void rememberHasSimulationOwnershipBid() const; void rememberHasSimulationOwnershipBid() const;

View file

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

View file

@ -254,17 +254,6 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties
propertiesWithSimID = convertPropertiesFromScriptSemantics(propertiesWithSimID, scalesWithParent); propertiesWithSimID = convertPropertiesFromScriptSemantics(propertiesWithSimID, scalesWithParent);
propertiesWithSimID.setDimensionsInitialized(properties.dimensionsChanged()); 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()); EntityItemID id = EntityItemID(QUuid::createUuid());
// If we have a local entity tree set, then also update it. // 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 // queue the packet
if (success) { if (success) {
emit debitEnergySource(cost);
queueEntityMessage(PacketType::EntityAdd, id, propertiesWithSimID); queueEntityMessage(PacketType::EntityAdd, id, propertiesWithSimID);
return id; return id;
} else { } else {
return QUuid(); return QUuid();
@ -378,27 +365,9 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties&
EntityItemProperties properties = scriptSideProperties; 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); EntityItemID entityID(id);
if (!_entityTree) { if (!_entityTree) {
queueEntityMessage(PacketType::EntityEdit, entityID, properties); 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; return id;
} }
// If we have a local entity tree set, then also update it. // 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. // 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. // 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()) { if (!scriptSideProperties.parentIDChanged()) {
properties.setParentID(entity->getParentID()); properties.setParentID(entity->getParentID());
} }
@ -442,23 +408,11 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties&
properties.setClientOnly(entity->getClientOnly()); properties.setClientOnly(entity->getClientOnly());
properties.setOwningAvatarID(entity->getOwningAvatarID()); properties.setOwningAvatarID(entity->getOwningAvatarID());
properties = convertPropertiesFromScriptSemantics(properties, properties.getScalesWithParent()); properties = convertPropertiesFromScriptSemantics(properties, properties.getScalesWithParent());
updatedEntity = _entityTree->updateEntity(entityID, properties);
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);
}
}
}); });
// FIXME: We need to figure out a better way to handle this. Allowing these edits to go through potentially // 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 // 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. // 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; 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()) { if (entity->getLocked()) {
shouldDelete = false; shouldDelete = false;
} else { } 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... // TODO move this someplace that makes more sense...
bool EntityScriptingInterface::AABoxIntersectsCapsule(const glm::vec3& low, const glm::vec3& dimensions, bool EntityScriptingInterface::AABoxIntersectsCapsule(const glm::vec3& low, const glm::vec3& dimensions,
const glm::vec3& start, const glm::vec3& end, float radius) { const glm::vec3& start, const glm::vec3& end, float radius) {

View file

@ -94,8 +94,6 @@ void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, Ra
* Interface has displayed and so knows about. * Interface has displayed and so knows about.
* *
* @namespace Entities * @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. * @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 * 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. * clear keyboard focus.
@ -104,8 +102,6 @@ void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, Ra
class EntityScriptingInterface : public OctreeScriptingInterface, public Dependency { class EntityScriptingInterface : public OctreeScriptingInterface, public Dependency {
Q_OBJECT 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) Q_PROPERTY(QUuid keyboardFocusEntity READ getKeyboardFocusEntity WRITE setKeyboardFocusEntity)
friend EntityPropertyMetadataRequest; friend EntityPropertyMetadataRequest;
@ -126,7 +122,6 @@ public:
void setEntityTree(EntityTreePointer modelTree); void setEntityTree(EntityTreePointer modelTree);
EntityTreePointer getEntityTree() { return _entityTree; } EntityTreePointer getEntityTree() { return _entityTree; }
void setEntitiesScriptEngine(QSharedPointer<EntitiesScriptEngineProvider> engine); void setEntitiesScriptEngine(QSharedPointer<EntitiesScriptEngineProvider> engine);
float calculateCost(float mass, float oldVelocity, float newVelocity);
void resetActivityTracking(); void resetActivityTracking();
ActivityTracking getActivityTracking() const { return _activityTracking; } ActivityTracking getActivityTracking() const { return _activityTracking; }
@ -1834,14 +1829,6 @@ signals:
*/ */
void clearingEntities(); 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 /**jsdoc
* Triggered in when a script in a {@link Entities.EntityType|Web} entity's Web page script sends an event over the * 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>. * script's <code>EventBridge</code>.
@ -1882,14 +1869,8 @@ private:
QSharedPointer<EntitiesScriptEngineProvider> _entitiesScriptEngine; QSharedPointer<EntitiesScriptEngineProvider> _entitiesScriptEngine;
bool _bidOnSimulationOwnership { false }; bool _bidOnSimulationOwnership { false };
float _currentAvatarEnergy = { FLT_MAX };
float getCurrentAvatarEnergy() { return _currentAvatarEnergy; }
void setCurrentAvatarEnergy(float energy);
ActivityTracking _activityTracking; ActivityTracking _activityTracking;
float costMultiplier = { 0.01f };
float getCostMultiplier();
void setCostMultiplier(float value);
}; };
#endif // hifi_EntityScriptingInterface_h #endif // hifi_EntityScriptingInterface_h

View file

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

View file

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

View file

@ -94,7 +94,6 @@ OctreeElementPointer EntityTree::createNewElement(unsigned char* octalCode) {
void EntityTree::eraseAllOctreeElements(bool createNewRoot) { void EntityTree::eraseAllOctreeElements(bool createNewRoot) {
emit clearingEntities(); emit clearingEntities();
// this would be a good place to clean up our entities...
if (_simulation) { if (_simulation) {
_simulation->clearEntities(); _simulation->clearEntities();
} }
@ -455,12 +454,13 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti
uint32_t newFlags = entity->getDirtyFlags() & ~preFlags; uint32_t newFlags = entity->getDirtyFlags() & ~preFlags;
if (newFlags) { if (newFlags) {
if (_simulation) { if (entity->isSimulated()) {
assert((bool)_simulation);
if (newFlags & DIRTY_SIMULATION_FLAGS) { if (newFlags & DIRTY_SIMULATION_FLAGS) {
_simulation->changeEntity(entity); _simulation->changeEntity(entity);
} }
} else { } 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(); entity->clearDirtyFlags();
} }
} }
@ -471,7 +471,7 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti
if (entityScriptBefore != entityScriptAfter || reload) { if (entityScriptBefore != entityScriptAfter || reload) {
emitEntityScriptChanging(entity->getEntityItemID(), reload); // the entity script has changed emitEntityScriptChanging(entity->getEntityItemID(), reload); // the entity script has changed
} }
} }
// TODO: this final containingElement check should eventually be removed (or wrapped in an #ifdef DEBUG). // TODO: this final containingElement check should eventually be removed (or wrapped in an #ifdef DEBUG).
if (!entity->getElement()) { if (!entity->getElement()) {
@ -553,8 +553,6 @@ void EntityTree::setSimulation(EntitySimulationPointer simulation) {
assert(simulation->getEntityTree().get() == this); assert(simulation->getEntityTree().get() == this);
} }
if (_simulation && _simulation != simulation) { 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->clearEntities();
} }
_simulation = simulation; _simulation = simulation;
@ -652,7 +650,7 @@ void EntityTree::deleteEntities(QSet<EntityItemID> entityIDs, bool force, bool i
emit deletingEntityPointer(existingEntity.get()); emit deletingEntityPointer(existingEntity.get());
} }
if (theOperator.getEntities().size() > 0) { if (!theOperator.getEntities().empty()) {
recurseTreeWithOperator(&theOperator); recurseTreeWithOperator(&theOperator);
processRemovedEntities(theOperator); processRemovedEntities(theOperator);
_isDirty = true; _isDirty = true;
@ -694,7 +692,7 @@ void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator)
trackDeletedEntity(theEntity->getEntityItemID()); trackDeletedEntity(theEntity->getEntityItemID());
} }
if (_simulation) { if (theEntity->isSimulated()) {
_simulation->prepareEntityForDelete(theEntity); _simulation->prepareEntityForDelete(theEntity);
} }
@ -1696,7 +1694,7 @@ void EntityTree::releaseSceneEncodeData(OctreeElementExtraEncodeData* extraEncod
} }
void EntityTree::entityChanged(EntityItemPointer entity) { void EntityTree::entityChanged(EntityItemPointer entity) {
if (_simulation) { if (entity->isSimulated()) {
_simulation->changeEntity(entity); _simulation->changeEntity(entity);
} }
} }
@ -1810,13 +1808,13 @@ void EntityTree::update(bool simulate) {
_simulation->updateEntities(); _simulation->updateEntities();
{ {
PROFILE_RANGE(simulation_physics, "Deletes"); PROFILE_RANGE(simulation_physics, "Deletes");
VectorOfEntities pendingDeletes; SetOfEntities deadEntities;
_simulation->takeEntitiesToDelete(pendingDeletes); _simulation->takeDeadEntities(deadEntities);
if (pendingDeletes.size() > 0) { if (!deadEntities.empty()) {
// translate into list of ID's // translate into list of ID's
QSet<EntityItemID> idsToDelete; QSet<EntityItemID> idsToDelete;
for (auto entity : pendingDeletes) { for (auto entity : deadEntities) {
idsToDelete.insert(entity->getEntityItemID()); idsToDelete.insert(entity->getEntityItemID());
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -18,20 +18,88 @@
#include <SharedUtil.h> #include <SharedUtil.h>
#include <UUID.h> #include <UUID.h>
// Simulation observers will bid to simulate unowned active objects at the lowest possible priority // HighFidelity uses a distributed physics simulation where multiple "participants" simulate portions
// which is VOLUNTEER. If the server accepts a VOLUNTEER bid it will automatically bump it // of the same world. When portions overlap only one participant is allowed to be the authority for any
// to RECRUIT priority so that other volunteers don't accidentally take over. // particular object. For a simulated entity the authoritative participant is called the simulation "owner" and
const quint8 VOLUNTEER_SIMULATION_PRIORITY = 0x01; // their duty is to send transform/velocity updates for the entity to the central entity-server.
const quint8 RECRUIT_SIMULATION_PRIORITY = VOLUNTEER_SIMULATION_PRIORITY + 1; // 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. // When poking objects with scripts an observer will bid at SCRIPT_EDIT priority.
const quint8 SCRIPT_GRAB_SIMULATION_PRIORITY = 0x80; const uint8_t SCRIPT_GRAB_SIMULATION_PRIORITY = 128;
const quint8 SCRIPT_POKE_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY - 1; const uint8_t SCRIPT_POKE_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY - 1;
const quint8 AVATAR_ENTITY_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 // 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 // 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 { class SimulationOwner {
@ -39,25 +107,25 @@ public:
static const int NUM_BYTES_ENCODED; static const int NUM_BYTES_ENCODED;
SimulationOwner(); SimulationOwner();
SimulationOwner(const QUuid& id, quint8 priority); SimulationOwner(const QUuid& id, uint8_t priority);
const QUuid& getID() const { return _id; } const QUuid& getID() const { return _id; }
const quint64& getExpiry() const { return _expiry; } const uint64_t& getExpiry() const { return _expiry; }
quint8 getPriority() const { return _priority; } uint8_t getPriority() const { return _priority; }
QByteArray toByteArray() const; QByteArray toByteArray() const;
bool fromByteArray(const QByteArray& data); bool fromByteArray(const QByteArray& data);
void clear(); void clear();
void setPriority(quint8 priority); void setPriority(uint8_t priority);
void promotePriority(quint8 priority); void promotePriority(uint8_t priority);
// return true if id is changed // return true if id is changed
bool setID(const QUuid& id); 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); 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 isNull() const { return _id.isNull(); }
bool matchesValidID(const QUuid& id) const { return _id == id && !_id.isNull(); } bool matchesValidID(const QUuid& id) const { return _id == id && !_id.isNull(); }
@ -67,11 +135,11 @@ public:
bool hasExpired() const { return usecTimestampNow() > _expiry; } bool hasExpired() const { return usecTimestampNow() > _expiry; }
uint8_t getPendingPriority() const { return _pendingBidPriority; } uint8_t getPendingPriority() const { return _pendingBidPriority; }
bool pendingRelease(const quint64& timestamp); // return true if valid pending RELEASE bool pendingRelease(uint64_t timestamp); // return true if valid pending RELEASE
bool pendingTake(const quint64& timestamp); // return true if valid pending TAKE bool pendingTake(uint64_t timestamp); // return true if valid pending TAKE
void clearCurrentOwner(); 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) { return (_id == other._id && _priority == other._priority); }
bool operator!=(const SimulationOwner& other); bool operator!=(const SimulationOwner& other);
@ -84,11 +152,11 @@ public:
private: private:
QUuid _id; // owner QUuid _id; // owner
quint64 _expiry; // time when ownership can transition at equal priority uint64_t _expiry; // time when ownership can transition at equal priority
quint64 _pendingBidTimestamp; // time when pending bid was set uint64_t _pendingBidTimestamp; // time when pending bid was set
quint8 _priority; // priority of current owner uint8_t _priority; // priority of current owner
quint8 _pendingBidPriority; // priority at which we'd like to own it uint8_t _pendingBidPriority; // priority at which we'd like to own it
quint8 _pendingState; // NOTHING, TAKE, or RELEASE uint8_t _pendingState; // NOTHING, TAKE, or RELEASE
}; };

View file

@ -26,12 +26,7 @@
#ifdef WANT_DEBUG_ENTITY_TREE_LOCKS #ifdef WANT_DEBUG_ENTITY_TREE_LOCKS
#include "EntityTree.h" #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 { bool EntityMotionState::entityTreeIsLocked() const {
EntityTreeElementPointer element = _entity->getElement(); EntityTreeElementPointer element = _entity->getElement();
EntityTreePointer tree = element ? element->getTree() : nullptr; EntityTreePointer tree = element ? element->getTree() : nullptr;
@ -46,11 +41,13 @@ bool entityTreeIsLocked() {
} }
#endif #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) : EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer entity) :
ObjectMotionState(nullptr), ObjectMotionState(nullptr),
_entityPtr(entity), _entity(entity),
_entity(entity.get()),
_serverPosition(0.0f), _serverPosition(0.0f),
_serverRotation(), _serverRotation(),
_serverVelocity(0.0f), _serverVelocity(0.0f),
@ -60,7 +57,7 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer
_serverActionData(QByteArray()), _serverActionData(QByteArray()),
_lastVelocity(0.0f), _lastVelocity(0.0f),
_measuredAcceleration(0.0f), _measuredAcceleration(0.0f),
_nextOwnershipBid(0), _nextBidExpiry(0),
_measuredDeltaTime(0.0f), _measuredDeltaTime(0.0f),
_lastMeasureStep(0), _lastMeasureStep(0),
_lastStep(0), _lastStep(0),
@ -68,6 +65,12 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer
_accelerationNearlyGravityCount(0), _accelerationNearlyGravityCount(0),
_numInactiveUpdates(1) _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; _type = MOTIONSTATE_TYPE_ENTITY;
assert(_entity); assert(_entity);
assert(entityTreeIsLocked()); assert(entityTreeIsLocked());
@ -76,77 +79,89 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer
// rather than pass the legit shape pointer to the ObjectMotionState ctor above. // rather than pass the legit shape pointer to the ObjectMotionState ctor above.
setShape(shape); setShape(shape);
_outgoingPriority = _entity->getPendingOwnershipPriority(); _bidPriority = _entity->getPendingOwnershipPriority();
} if (_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID()) {
// client-only entities are always thus, so we cache this fact in _ownershipState
EntityMotionState::~EntityMotionState() { _ownershipState = EntityMotionState::OwnershipState::Unownable;
assert(_entity);
_entity = nullptr;
}
void EntityMotionState::updateServerPhysicsVariables() {
assert(entityTreeIsLocked());
if (isLocallyOwned()) {
// don't slam these values if we are the simulation owner
return;
} }
Transform localTransform; Transform localTransform;
_entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity); _entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity);
_serverVariablesSet = true;
_serverPosition = localTransform.getTranslation(); _serverPosition = localTransform.getTranslation();
_serverRotation = localTransform.getRotation(); _serverRotation = localTransform.getRotation();
_serverAcceleration = _entity->getAcceleration(); _serverAcceleration = _entity->getAcceleration();
_serverActionData = _entity->getDynamicData(); _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() { void EntityMotionState::handleDeactivation() {
if (_serverVariablesSet) { // copy _server data to entity
// copy _server data to entity Transform localTransform = _entity->getLocalTransform();
Transform localTransform = _entity->getLocalTransform(); localTransform.setTranslation(_serverPosition);
localTransform.setTranslation(_serverPosition); localTransform.setRotation(_serverRotation);
localTransform.setRotation(_serverRotation); _entity->setLocalTransformAndVelocities(localTransform, ENTITY_ITEM_ZERO_VEC3, ENTITY_ITEM_ZERO_VEC3);
_entity->setLocalTransformAndVelocities(localTransform, ENTITY_ITEM_ZERO_VEC3, ENTITY_ITEM_ZERO_VEC3); // and also to RigidBody
// and also to RigidBody btTransform worldTrans;
btTransform worldTrans; worldTrans.setOrigin(glmToBullet(_entity->getWorldPosition()));
worldTrans.setOrigin(glmToBullet(_entity->getWorldPosition())); worldTrans.setRotation(glmToBullet(_entity->getWorldOrientation()));
worldTrans.setRotation(glmToBullet(_entity->getWorldOrientation())); _body->setWorldTransform(worldTrans);
_body->setWorldTransform(worldTrans); // no need to update velocities... should already be zero
// no need to update velocities... should already be zero
}
} }
// virtual // virtual
void EntityMotionState::handleEasyChanges(uint32_t& flags) { void EntityMotionState::handleEasyChanges(uint32_t& flags) {
assert(_entity);
assert(entityTreeIsLocked()); assert(entityTreeIsLocked());
updateServerPhysicsVariables(); updateServerPhysicsVariables();
ObjectMotionState::handleEasyChanges(flags); ObjectMotionState::handleEasyChanges(flags);
if (flags & Simulation::DIRTY_SIMULATOR_ID) { if (flags & Simulation::DIRTY_SIMULATOR_ID) {
if (_entity->getSimulatorID().isNull()) { 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) { 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; flags &= ~Simulation::DIRTY_PHYSICS_ACTIVATION;
_body->setActivationState(WANTS_DEACTIVATION); _body->setActivationState(WANTS_DEACTIVATION);
_outgoingPriority = 0; _bidPriority = 0;
const float ACTIVATION_EXPIRY = 3.0f; // something larger than the 2.0 hard coded in Bullet const float ACTIVATION_EXPIRY = 3.0f; // something larger than the 2.0 hard coded in Bullet
_body->setDeactivationTime(ACTIVATION_EXPIRY); _body->setDeactivationTime(ACTIVATION_EXPIRY);
} else { } else {
// disowned object is still moving --> start timer for ownership bid // disowned object is still moving --> start timer for ownership bid
// TODO? put a delay in here proportional to distance from object? // TODO? put a delay in here proportional to distance from object?
upgradeOutgoingPriority(VOLUNTEER_SIMULATION_PRIORITY); upgradeBidPriority(VOLUNTEER_SIMULATION_PRIORITY);
_nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; _nextBidExpiry = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS;
} }
_loopsWithoutOwner = 0; _loopsWithoutOwner = 0;
_numInactiveUpdates = 0; _numInactiveUpdates = 0;
} else if (isLocallyOwned()) { } else if (isLocallyOwned()) {
// we just inherited ownership, make sure our desired priority matches what we have // we just inherited ownership, make sure our desired priority matches what we have
upgradeOutgoingPriority(_entity->getSimulationPriority()); upgradeBidPriority(_entity->getSimulationPriority());
} else { } else {
_outgoingPriority = 0; // the entity is owned by someone else, so we clear _bidPriority here
_nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; // 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; _numInactiveUpdates = 0;
} }
} }
@ -155,10 +170,10 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) {
// (1) we own it but may need to change the priority OR... // (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) // (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(); uint8_t newPriority = isLocallyOwned() ? _entity->getSimulationOwner().getPriority() : _entity->getSimulationOwner().getPendingPriority();
upgradeOutgoingPriority(newPriority); upgradeBidPriority(newPriority);
// reset bid expiry so that we bid ASAP // reset bid expiry so that we bid ASAP
_nextOwnershipBid = 0; _nextBidExpiry = 0;
} }
if ((flags & Simulation::DIRTY_PHYSICS_ACTIVATION) && !_body->isActive()) { if ((flags & Simulation::DIRTY_PHYSICS_ACTIVATION) && !_body->isActive()) {
if (_body->isKinematicObject()) { if (_body->isKinematicObject()) {
@ -175,7 +190,6 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) {
// virtual // virtual
bool EntityMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) { bool EntityMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) {
assert(_entity);
updateServerPhysicsVariables(); updateServerPhysicsVariables();
return ObjectMotionState::handleHardAndEasyChanges(flags, engine); 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... // This callback is invoked by the physics simulation at the end of each simulation step...
// iff the corresponding RigidBody is DYNAMIC and ACTIVE. // iff the corresponding RigidBody is DYNAMIC and ACTIVE.
void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { void EntityMotionState::setWorldTransform(const btTransform& worldTrans) {
assert(_entity);
assert(entityTreeIsLocked()); assert(entityTreeIsLocked());
measureBodyAcceleration(); measureBodyAcceleration();
@ -285,21 +298,10 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) {
if (_entity->getSimulatorID().isNull()) { if (_entity->getSimulatorID().isNull()) {
_loopsWithoutOwner++; _loopsWithoutOwner++;
if (_loopsWithoutOwner > LOOPS_FOR_SIMULATION_ORPHAN && usecTimestampNow() > _nextOwnershipBid) { if (_loopsWithoutOwner > LOOPS_FOR_SIMULATION_ORPHAN && usecTimestampNow() > _nextBidExpiry) {
upgradeOutgoingPriority(VOLUNTEER_SIMULATION_PRIORITY); 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) { bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
DETAILED_PROFILE_RANGE(simulation_physics, "CheckOutOfSync");
// NOTE: we only get here if we think we own the simulation // 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; bool parentTransformSuccess;
Transform localToWorld = _entity->getParentTransform(parentTransformSuccess); Transform localToWorld = _entity->getParentTransform(parentTransformSuccess);
@ -347,27 +343,6 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
worldVelocityToLocal.setTranslation(glm::vec3(0.0f)); 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; int numSteps = simulationStep - _lastStep;
float dt = (float)(numSteps) * PHYSICS_ENGINE_FIXED_SUBSTEP; float dt = (float)(numSteps) * PHYSICS_ENGINE_FIXED_SUBSTEP;
@ -378,9 +353,9 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
_entity->clearSimulationOwnership(); _entity->clearSimulationOwnership();
return false; return false;
} }
// we resend the inactive update every INACTIVE_UPDATE_PERIOD // we resend the inactive update with a growing delay: every INACTIVE_UPDATE_PERIOD * _numInactiveUpdates
// until it is removed from the outgoing updates // until it is removed from the owned list
// (which happens when we don't own the simulation and it isn't touching our simulation) // (which happens when we no longer own the simulation)
const float INACTIVE_UPDATE_PERIOD = 0.5f; const float INACTIVE_UPDATE_PERIOD = 0.5f;
return (dt > INACTIVE_UPDATE_PERIOD * (float)_numInactiveUpdates); return (dt > INACTIVE_UPDATE_PERIOD * (float)_numInactiveUpdates);
} }
@ -411,14 +386,10 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
if (_entity->dynamicDataNeedsTransmit()) { if (_entity->dynamicDataNeedsTransmit()) {
uint8_t priority = _entity->hasActions() ? SCRIPT_GRAB_SIMULATION_PRIORITY : SCRIPT_POKE_SIMULATION_PRIORITY; uint8_t priority = _entity->hasActions() ? SCRIPT_GRAB_SIMULATION_PRIORITY : SCRIPT_POKE_SIMULATION_PRIORITY;
upgradeOutgoingPriority(priority); upgradeBidPriority(priority);
return true; return true;
} }
if (_entity->shouldSuppressLocationEdits()) {
return false;
}
// Else we measure the error between current and extrapolated transform (according to expected behavior // 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. // 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_ERROR_RATIO_SQUARED = 0.0025f; // corresponds to 5% error in 1 second
const float MIN_SPEED_SQUARED = 1.0e-6f; // corresponds to 1mm/sec const float MIN_SPEED_SQUARED = 1.0e-6f; // corresponds to 1mm/sec
if (speed2 < MIN_SPEED_SQUARED || dx2 / speed2 > MIN_ERROR_RATIO_SQUARED) { 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; 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 const float MIN_ROTATION_DOT = 0.99999f; // This corresponds to about 0.5 degrees of rotation
glm::quat actualRotation = worldToLocal.getRotation() * bulletToGLM(worldTrans.getRotation()); 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); 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"); DETAILED_PROFILE_RANGE(simulation_physics, "ShouldSend");
// NOTE: we expect _entity and _body to be valid in this context, since shouldSendUpdate() is only called // 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. // after doesNotNeedToSendUpdate() returns false and that call should return 'true' if _entity or _body are NULL.
assert(_entity);
assert(_body);
assert(entityTreeIsLocked()); assert(entityTreeIsLocked());
if (_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID()) { // this case is prevented by setting _ownershipState to UNOWNABLE in EntityMotionState::ctor
// don't send updates for someone else's avatarEntities assert(!(_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID()));
return false;
}
if (_entity->dynamicDataNeedsTransmit() || _entity->queryAACubeNeedsUpdate()) { if (_entity->dynamicDataNeedsTransmit() || _entity->queryAACubeNeedsUpdate()) {
return true; return true;
@ -506,44 +450,19 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) {
return false; 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); return remoteSimulationOutOfSync(simulationStep);
} }
void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step) { void EntityMotionState::updateSendVelocities() {
DETAILED_PROFILE_RANGE(simulation_physics, "Send");
assert(_entity);
assert(entityTreeIsLocked());
if (!_body->isActive()) { if (!_body->isActive()) {
// make sure all derivatives are zero // make sure all derivatives are zero
zeroCleanObjectVelocities(); clearObjectVelocities();
_numInactiveUpdates++; _numInactiveUpdates = 1;
} else { } else {
glm::vec3 gravity = _entity->getGravity(); glm::vec3 gravity = _entity->getGravity();
// if this entity has been accelerated at close to gravity for a certain number of simulation-steps, let // if this entity has been accelerated at close to gravity for a certain number of simulation-steps
// the entity server's estimates include gravity. // let the entity server's estimates include gravity.
const uint8_t STEPS_TO_DECIDE_BALLISTIC = 4; const uint8_t STEPS_TO_DECIDE_BALLISTIC = 4;
if (_accelerationNearlyGravityCount >= STEPS_TO_DECIDE_BALLISTIC) { if (_accelerationNearlyGravityCount >= STEPS_TO_DECIDE_BALLISTIC) {
_entity->setAcceleration(gravity); _entity->setAcceleration(gravity);
@ -564,13 +483,82 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
if (movingSlowly) { if (movingSlowly) {
// velocities might not be zero, but we'll fake them as such, which will hopefully help convince // 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 // other simulating observers to deactivate their own copies
zeroCleanObjectVelocities(); clearObjectVelocities();
} }
} }
_numInactiveUpdates = 0; _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; Transform localTransform;
_entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity); _entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity);
_serverPosition = localTransform.getTranslation(); _serverPosition = localTransform.getTranslation();
@ -579,11 +567,8 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
_serverActionData = _entity->getDynamicData(); _serverActionData = _entity->getDynamicData();
EntityItemProperties properties; EntityItemProperties properties;
// explicitly set the properties that changed so that they will be packed
properties.setPosition(_entity->getLocalPosition()); properties.setPosition(_entity->getLocalPosition());
properties.setRotation(_entity->getLocalOrientation()); properties.setRotation(_entity->getLocalOrientation());
properties.setVelocity(_serverVelocity); properties.setVelocity(_serverVelocity);
properties.setAcceleration(_serverAcceleration); properties.setAcceleration(_serverAcceleration);
properties.setAngularVelocity(_serverAngularVelocity); properties.setAngularVelocity(_serverAngularVelocity);
@ -592,61 +577,35 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
properties.setActionData(_serverActionData); properties.setActionData(_serverActionData);
} }
if (properties.transformChanged()) { if (_entity->updateQueryAACube()) {
if (_entity->updateQueryAACube()) { // due to parenting, the server may not know where something is in world-space, so include the bounding cube.
// due to parenting, the server may not know where something is in world-space, so include the bounding cube. properties.setQueryAACube(_entity->getQueryAACube());
properties.setQueryAACube(_entity->getQueryAACube());
}
} }
// set the LastEdited of the properties but NOT the entity itself // set the LastEdited of the properties but NOT the entity itself
quint64 now = usecTimestampNow(); quint64 now = usecTimestampNow();
properties.setLastEdited(now); 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) { 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 // but we remember we do still own it... and rely on the server to tell us we don't
properties.clearSimulationOwner(); properties.clearSimulationOwner();
_outgoingPriority = 0; _bidPriority = 0;
_entity->setPendingOwnershipPriority(_outgoingPriority, now); _entity->setPendingOwnershipPriority(_bidPriority, now);
} else if (!isLocallyOwned()) { } else if (_bidPriority != _entity->getSimulationPriority()) {
// we don't own the simulation for this entity yet, but we're sending a bid for it // our desired priority has changed
quint8 bidPriority = glm::max<uint8_t>(_outgoingPriority, VOLUNTEER_SIMULATION_PRIORITY); if (_bidPriority == 0) {
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) {
// we should release ownership // we should release ownership
properties.clearSimulationOwner(); properties.clearSimulationOwner();
} else { } else {
// we just need to change the priority // 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()); EntityItemID id(_entity->getID());
EntityEditPacketSender* entityPacketSender = static_cast<EntityEditPacketSender*>(packetSender); EntityEditPacketSender* entityPacketSender = static_cast<EntityEditPacketSender*>(packetSender);
#ifdef WANT_DEBUG
qCDebug(physics) << "EntityMotionState::sendUpdate()... calling queueEditEntityMessage()...";
#endif
EntityTreeElementPointer element = _entity->getElement(); EntityTreeElementPointer element = _entity->getElement();
EntityTreePointer tree = element ? element->getTree() : nullptr; EntityTreePointer tree = element ? element->getTree() : nullptr;
@ -655,7 +614,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
properties.setOwningAvatarID(_entity->getOwningAvatarID()); properties.setOwningAvatarID(_entity->getOwningAvatarID());
entityPacketSender->queueEditEntityMessage(PacketType::EntityPhysics, tree, id, properties); 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 we've moved an entity with children, check/update the queryAACube of all descendents and tell the server
// if they've changed. // if they've changed.
@ -666,13 +625,12 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
EntityItemProperties newQueryCubeProperties; EntityItemProperties newQueryCubeProperties;
newQueryCubeProperties.setQueryAACube(descendant->getQueryAACube()); newQueryCubeProperties.setQueryAACube(descendant->getQueryAACube());
newQueryCubeProperties.setLastEdited(properties.getLastEdited()); newQueryCubeProperties.setLastEdited(properties.getLastEdited());
newQueryCubeProperties.setClientOnly(entityDescendant->getClientOnly()); newQueryCubeProperties.setClientOnly(entityDescendant->getClientOnly());
newQueryCubeProperties.setOwningAvatarID(entityDescendant->getOwningAvatarID()); newQueryCubeProperties.setOwningAvatarID(entityDescendant->getOwningAvatarID());
entityPacketSender->queueEditEntityMessage(PacketType::EntityPhysics, tree, entityPacketSender->queueEditEntityMessage(PacketType::EntityPhysics, tree,
descendant->getID(), newQueryCubeProperties); 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(); return _entity->getSimulationPriority();
} }
void EntityMotionState::slaveBidPriority() {
upgradeBidPriority(_entity->getSimulationPriority());
}
// virtual // virtual
QUuid EntityMotionState::getSimulatorID() const { QUuid EntityMotionState::getSimulatorID() const {
assert(entityTreeIsLocked()); assert(entityTreeIsLocked());
@ -731,7 +693,7 @@ QUuid EntityMotionState::getSimulatorID() const {
void EntityMotionState::bump(uint8_t priority) { void EntityMotionState::bump(uint8_t priority) {
assert(priority != 0); assert(priority != 0);
upgradeOutgoingPriority(glm::max(VOLUNTEER_SIMULATION_PRIORITY, --priority)); upgradeBidPriority(glm::max(VOLUNTEER_SIMULATION_PRIORITY, --priority));
} }
void EntityMotionState::resetMeasuredBodyAcceleration() { void EntityMotionState::resetMeasuredBodyAcceleration() {
@ -755,13 +717,14 @@ void EntityMotionState::measureBodyAcceleration() {
_lastMeasureStep = thisStep; _lastMeasureStep = thisStep;
_measuredDeltaTime = dt; _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 // hence the equation for acceleration is: a = (v1 / (1 - D)^dt - v0) / dt
glm::vec3 velocity = getBodyLinearVelocityGTSigma(); glm::vec3 velocity = getBodyLinearVelocityGTSigma();
_measuredAcceleration = (velocity / powf(1.0f - _body->getLinearDamping(), dt) - _lastVelocity) * invDt; _measuredAcceleration = (velocity / powf(1.0f - _body->getLinearDamping(), dt) - _lastVelocity) * invDt;
_lastVelocity = velocity; _lastVelocity = velocity;
if (numSubsteps > PHYSICS_ENGINE_MAX_NUM_SUBSTEPS) { if (numSubsteps > PHYSICS_ENGINE_MAX_NUM_SUBSTEPS) {
// we fall in here when _lastMeasureStep is old: the body has just become active
_loopsWithoutOwner = 0; _loopsWithoutOwner = 0;
_lastStep = ObjectMotionState::getWorldSimulationStep(); _lastStep = ObjectMotionState::getWorldSimulationStep();
_numInactiveUpdates = 0; _numInactiveUpdates = 0;
@ -805,24 +768,44 @@ QString EntityMotionState::getName() const {
// virtual // virtual
void EntityMotionState::computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const { void EntityMotionState::computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const {
assert(_entity);
_entity->computeCollisionGroupAndFinalMask(group, mask); _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 { bool EntityMotionState::isLocallyOwned() const {
return _entity->getSimulatorID() == Physics::getSessionUUID(); return _entity->getSimulatorID() == Physics::getSessionUUID();
} }
bool EntityMotionState::shouldBeLocallyOwned() const { bool EntityMotionState::isLocallyOwnedOrShouldBe() const {
return (_outgoingPriority > VOLUNTEER_SIMULATION_PRIORITY && _outgoingPriority > _entity->getSimulationPriority()) || return (_bidPriority > VOLUNTEER_SIMULATION_PRIORITY && _bidPriority > _entity->getSimulationPriority()) ||
_entity->getSimulatorID() == Physics::getSessionUUID(); _entity->getSimulatorID() == Physics::getSessionUUID();
} }
void EntityMotionState::upgradeOutgoingPriority(uint8_t priority) { void EntityMotionState::initForBid() {
_outgoingPriority = glm::max<uint8_t>(_outgoingPriority, priority); 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 // 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 // occured between the beginning and end of the stepSimulation() and we DON'T want to apply
// these physics simulation results. // these physics simulation results.

View file

@ -24,11 +24,17 @@
class EntityMotionState : public ObjectMotionState { class EntityMotionState : public ObjectMotionState {
public: public:
enum class OwnershipState {
NotLocallyOwned = 0,
PendingBid,
LocallyOwned,
Unownable
};
EntityMotionState() = delete;
EntityMotionState(btCollisionShape* shape, EntityItemPointer item); EntityMotionState(btCollisionShape* shape, EntityItemPointer item);
virtual ~EntityMotionState(); virtual ~EntityMotionState();
void updateServerPhysicsVariables();
void handleDeactivation(); void handleDeactivation();
virtual void handleEasyChanges(uint32_t& flags) override; virtual void handleEasyChanges(uint32_t& flags) override;
virtual bool handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) override; virtual bool handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) override;
@ -44,9 +50,8 @@ public:
// this relays outgoing position/rotation to the EntityItem // this relays outgoing position/rotation to the EntityItem
virtual void setWorldTransform(const btTransform& worldTrans) override; virtual void setWorldTransform(const btTransform& worldTrans) override;
bool isCandidateForOwnership() const;
bool remoteSimulationOutOfSync(uint32_t simulationStep);
bool shouldSendUpdate(uint32_t simulationStep); bool shouldSendUpdate(uint32_t simulationStep);
void sendBid(OctreeEditPacketSender* packetSender, uint32_t step);
void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step); void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step);
virtual uint32_t getIncomingDirtyFlags() override; virtual uint32_t getIncomingDirtyFlags() override;
@ -70,7 +75,9 @@ public:
virtual QUuid getSimulatorID() const override; virtual QUuid getSimulatorID() const override;
virtual void bump(uint8_t priority) 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 resetMeasuredBodyAcceleration();
void measureBodyAcceleration(); void measureBodyAcceleration();
@ -79,15 +86,29 @@ public:
virtual void computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const override; virtual void computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const override;
bool shouldSendBid();
bool isLocallyOwned() const override; bool isLocallyOwned() const override;
bool shouldBeLocallyOwned() const override; bool isLocallyOwnedOrShouldBe() const override; // aka shouldEmitCollisionEvents()
friend class PhysicalEntitySimulation; friend class PhysicalEntitySimulation;
OwnershipState getOwnershipState() const { return _ownershipState; }
protected: protected:
// changes _outgoingPriority only if priority is larger void updateSendVelocities();
void upgradeOutgoingPriority(uint8_t priority); uint64_t getNextBidExpiry() const { return _nextBidExpiry; }
void zeroCleanObjectVelocities() const; 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 #ifdef WANT_DEBUG_ENTITY_TREE_LOCKS
bool entityTreeIsLocked() const; bool entityTreeIsLocked() const;
@ -98,17 +119,21 @@ protected:
void setShape(const btCollisionShape* shape) override; void setShape(const btCollisionShape* shape) override;
void setMotionType(PhysicsMotionType motionType) override; void setMotionType(PhysicsMotionType motionType) override;
// In the glorious future (when entities lib depends on physics lib) the EntityMotionState will be // EntityMotionState keeps a SharedPointer to its EntityItem which is only set in the CTOR
// properly "owned" by the EntityItem and will be deleted by it in the dtor. In pursuit of that // and is only cleared in the DTOR
// state of affairs we can't keep a real EntityItemPointer as data member (it would produce a EntityItemPointer _entity;
// 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;
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::vec3 _serverPosition; // in simulation-frame (not world-frame)
glm::quat _serverRotation; glm::quat _serverRotation;
glm::vec3 _serverVelocity; glm::vec3 _serverVelocity;
@ -119,16 +144,18 @@ protected:
glm::vec3 _lastVelocity; glm::vec3 _lastVelocity;
glm::vec3 _measuredAcceleration; glm::vec3 _measuredAcceleration;
quint64 _nextOwnershipBid { 0 }; quint64 _nextBidExpiry { 0 };
float _measuredDeltaTime; float _measuredDeltaTime;
uint32_t _lastMeasureStep; uint32_t _lastMeasureStep;
uint32_t _lastStep; // last step of server extrapolation uint32_t _lastStep; // last step of server extrapolation
OwnershipState _ownershipState { OwnershipState::NotLocallyOwned };
uint8_t _loopsWithoutOwner; uint8_t _loopsWithoutOwner;
mutable uint8_t _accelerationNearlyGravityCount; mutable uint8_t _accelerationNearlyGravityCount;
uint8_t _numInactiveUpdates { 1 }; uint8_t _numInactiveUpdates { 1 };
uint8_t _outgoingPriority { 0 }; uint8_t _bidPriority { 0 };
bool _serverVariablesSet { false };
}; };
#endif // hifi_EntityMotionState_h #endif // hifi_EntityMotionState_h

View file

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

View file

@ -40,7 +40,7 @@ void PhysicalEntitySimulation::init(
} }
// begin EntitySimulation overrides // 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. // 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) { void PhysicalEntitySimulation::removeEntityInternal(EntityItemPointer entity) {
if (entity->isSimulated()) { if (entity->isSimulated()) {
EntitySimulation::removeEntityInternal(entity); EntitySimulation::removeEntityInternal(entity);
QMutexLocker lock(&_mutex);
_entitiesToAddToPhysics.remove(entity); _entitiesToAddToPhysics.remove(entity);
EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo()); EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo());
if (motionState) { if (motionState) {
_outgoingChanges.remove(motionState); removeOwnershipData(motionState);
_entitiesToRemoveFromPhysics.insert(entity); _entitiesToRemoveFromPhysics.insert(entity);
} else { } else if (entity->isDead() && entity->getElement()) {
_entitiesToDelete.insert(entity); _deadEntities.insert(entity);
} }
} }
} }
void PhysicalEntitySimulation::takeEntitiesToDelete(VectorOfEntities& entitiesToDelete) { void PhysicalEntitySimulation::removeOwnershipData(EntityMotionState* motionState) {
QMutexLocker lock(&_mutex); assert(motionState);
for (auto entity : _entitiesToDelete) { if (motionState->getOwnershipState() == EntityMotionState::OwnershipState::LocallyOwned) {
// this entity is still in its tree, so we insert into the external list for (uint32_t i = 0; i < _owned.size(); ++i) {
entitiesToDelete.push_back(entity); 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 void PhysicalEntitySimulation::clearOwnershipData() {
// rather than do it here 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()); EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo());
if (motionState) { if (motionState) {
_entitiesToRemoveFromPhysics.insert(entity); _entitiesToRemoveFromPhysics.insert(entity);
} }
} }
_entitiesToDelete.clear(); _deadEntities.swap(deadEntities);
_deadEntities.clear();
} }
void PhysicalEntitySimulation::changeEntityInternal(EntityItemPointer entity) { void PhysicalEntitySimulation::changeEntityInternal(EntityItemPointer entity) {
@ -98,15 +123,15 @@ void PhysicalEntitySimulation::changeEntityInternal(EntityItemPointer entity) {
if (motionState) { if (motionState) {
if (!entity->shouldBePhysical()) { if (!entity->shouldBePhysical()) {
// the entity should be removed from the physical simulation // the entity should be removed from the physical simulation
_pendingChanges.remove(motionState); _incomingChanges.remove(motionState);
_physicalObjects.remove(motionState); _physicalObjects.remove(motionState);
_outgoingChanges.remove(motionState); removeOwnershipData(motionState);
_entitiesToRemoveFromPhysics.insert(entity); _entitiesToRemoveFromPhysics.insert(entity);
if (entity->isMovingRelativeToParent()) { if (entity->isMovingRelativeToParent()) {
_simpleKinematicEntities.insert(entity); _simpleKinematicEntities.insert(entity);
} }
} else { } else {
_pendingChanges.insert(motionState); _incomingChanges.insert(motionState);
} }
} else if (entity->shouldBePhysical()) { } else if (entity->shouldBePhysical()) {
// The intent is for this object to be in the PhysicsEngine, but it has no MotionState yet. // 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 // 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... // anyway, so maybe the simulation was already properly shutdown? Cross our fingers...
// copy everything into _entitiesToDelete // remove the objects (aka MotionStates) from physics
for (auto stateItr : _physicalObjects) {
EntityMotionState* motionState = static_cast<EntityMotionState*>(&(*stateItr));
_entitiesToDelete.insert(motionState->getEntity());
}
// then remove the objects (aka MotionStates) from physics
_physicsEngine->removeSetOfObjects(_physicalObjects); _physicsEngine->removeSetOfObjects(_physicalObjects);
// delete the MotionStates // delete the MotionStates
// TODO: after we invert the entities/physics lib dependencies we will let EntityItem delete for (auto stateItr : _physicalObjects) {
// its own PhysicsInfo rather than do it here EntityMotionState* motionState = static_cast<EntityMotionState*>(&(*stateItr));
for (auto entity : _entitiesToDelete) { assert(motionState);
EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo()); EntityItemPointer entity = motionState->getEntity();
if (motionState) { entity->setPhysicsInfo(nullptr);
entity->setPhysicsInfo(nullptr); // TODO: someday when we invert the entities/physics lib dependencies we can let EntityItem delete its own PhysicsInfo
delete motionState; // until then we must do it here
} delete motionState;
} }
// finally clear all lists maintained by this class
_physicalObjects.clear(); _physicalObjects.clear();
// clear all other lists specific to this derived class
clearOwnershipData();
_entitiesToRemoveFromPhysics.clear(); _entitiesToRemoveFromPhysics.clear();
_entitiesToRelease.clear();
_entitiesToAddToPhysics.clear(); _entitiesToAddToPhysics.clear();
_pendingChanges.clear(); _incomingChanges.clear();
_outgoingChanges.clear();
} }
// virtual // virtual
void PhysicalEntitySimulation::prepareEntityForDelete(EntityItemPointer entity) { void PhysicalEntitySimulation::prepareEntityForDelete(EntityItemPointer entity) {
assert(entity); assert(entity);
assert(entity->isDead()); assert(entity->isDead());
QMutexLocker lock(&_mutex);
entity->clearActions(getThisPointer()); entity->clearActions(getThisPointer());
removeEntityInternal(entity); removeEntityInternal(entity);
} }
// end EntitySimulation overrides // end EntitySimulation overrides
void PhysicalEntitySimulation::getObjectsToRemoveFromPhysics(VectorOfMotionStates& result) { const VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToRemoveFromPhysics() {
result.clear();
QMutexLocker lock(&_mutex); QMutexLocker lock(&_mutex);
for (auto entity: _entitiesToRemoveFromPhysics) { for (auto entity: _entitiesToRemoveFromPhysics) {
// make sure it isn't on any side lists
_entitiesToAddToPhysics.remove(entity);
EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo()); EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo());
if (motionState) { assert(motionState);
_pendingChanges.remove(motionState);
_outgoingChanges.remove(motionState); _entitiesToAddToPhysics.remove(entity);
_physicalObjects.remove(motionState); if (entity->isDead() && entity->getElement()) {
result.push_back(motionState); _deadEntities.insert(entity);
_entitiesToRelease.insert(entity);
} }
if (entity->isDead()) { _incomingChanges.remove(motionState);
_entitiesToDelete.insert(entity); 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(); _entitiesToRemoveFromPhysics.clear();
return _objectsToDelete;
} }
void PhysicalEntitySimulation::deleteObjectsRemovedFromPhysics() { void PhysicalEntitySimulation::deleteObjectsRemovedFromPhysics() {
QMutexLocker lock(&_mutex); QMutexLocker lock(&_mutex);
for (auto entity: _entitiesToRelease) { for (auto motionState : _objectsToDelete) {
EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo()); // someday when we invert the entities/physics lib dependencies we can let EntityItem delete its own PhysicsInfo
assert(motionState); // until then we must do it here
entity->setPhysicsInfo(nullptr); // NOTE: a reference to the EntityItemPointer is released in the EntityMotionState::dtor
delete motionState; delete motionState;
if (entity->isDead()) {
_entitiesToDelete.insert(entity);
}
} }
_entitiesToRelease.clear(); _objectsToDelete.clear();
} }
void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& result) { void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& result) {
@ -248,18 +262,18 @@ void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& re
void PhysicalEntitySimulation::setObjectsToChange(const VectorOfMotionStates& objectsToChange) { void PhysicalEntitySimulation::setObjectsToChange(const VectorOfMotionStates& objectsToChange) {
QMutexLocker lock(&_mutex); QMutexLocker lock(&_mutex);
for (auto object : objectsToChange) { for (auto object : objectsToChange) {
_pendingChanges.insert(static_cast<EntityMotionState*>(object)); _incomingChanges.insert(static_cast<EntityMotionState*>(object));
} }
} }
void PhysicalEntitySimulation::getObjectsToChange(VectorOfMotionStates& result) { void PhysicalEntitySimulation::getObjectsToChange(VectorOfMotionStates& result) {
result.clear(); result.clear();
QMutexLocker lock(&_mutex); QMutexLocker lock(&_mutex);
for (auto stateItr : _pendingChanges) { for (auto stateItr : _incomingChanges) {
EntityMotionState* motionState = &(*stateItr); EntityMotionState* motionState = &(*stateItr);
result.push_back(motionState); result.push_back(motionState);
} }
_pendingChanges.clear(); _incomingChanges.clear();
} }
void PhysicalEntitySimulation::handleDeactivatedMotionStates(const VectorOfMotionStates& motionStates) { 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()); PROFILE_RANGE_EX(simulation_physics, "ChangedEntities", 0x00000000, (uint64_t)motionStates.size());
QMutexLocker lock(&_mutex); QMutexLocker lock(&_mutex);
// walk the motionStates looking for those that correspond to entities for (auto stateItr : motionStates) {
{ ObjectMotionState* state = &(*stateItr);
PROFILE_RANGE_EX(simulation_physics, "Filter", 0x00000000, (uint64_t)motionStates.size()); assert(state);
for (auto stateItr : motionStates) { if (state->getType() == MOTIONSTATE_TYPE_ENTITY) {
ObjectMotionState* state = &(*stateItr); EntityMotionState* entityState = static_cast<EntityMotionState*>(state);
assert(state); _entitiesToSort.insert(entityState->getEntity());
if (state->getType() == MOTIONSTATE_TYPE_ENTITY) { if (entityState->getOwnershipState() == EntityMotionState::OwnershipState::NotLocallyOwned) {
EntityMotionState* entityState = static_cast<EntityMotionState*>(state); // NOTE: entityState->getOwnershipState() reflects what ownership list (_bids or _owned) it is in
EntityItemPointer entity = entityState->getEntity(); // and is distinct from entityState->isLocallyOwned() which checks the simulation ownership
assert(entity.get()); // properties of the corresponding EntityItem. It is possible for the two states to be out
if (entityState->isCandidateForOwnership()) { // of sync. In fact, we're trying to put them back into sync here.
_outgoingChanges.insert(entityState); 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; _lastStepSendPackets = numSubsteps;
if (Physics::getSessionUUID().isNull()) { if (Physics::getSessionUUID().isNull()) {
// usually don't get here, but if so --> nothing to do // usually don't get here, but if so clear all ownership
_outgoingChanges.clear(); clearOwnershipData();
return;
} }
// 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 void PhysicalEntitySimulation::addOwnershipBid(EntityMotionState* motionState) {
PROFILE_RANGE_EX(simulation_physics, "Prune/Send", 0x00000000, (uint64_t)_outgoingChanges.size()); motionState->initForBid();
QSet<EntityMotionState*>::iterator stateItr = _outgoingChanges.begin(); motionState->sendBid(_entityPacketSender, _physicsEngine->getNumSubsteps());
while (stateItr != _outgoingChanges.end()) { _bids.push_back(motionState);
EntityMotionState* state = *stateItr; _nextBidExpiry = glm::min(_nextBidExpiry, motionState->getNextBidExpiry());
if (!state->isCandidateForOwnership()) { }
// prune
stateItr = _outgoingChanges.erase(stateItr); void PhysicalEntitySimulation::addOwnership(EntityMotionState* motionState) {
} else if (state->shouldSendUpdate(numSubsteps)) { motionState->initForOwned();
// update _owned.push_back(motionState);
state->sendUpdate(_entityPacketSender, numSubsteps); }
++stateItr;
} else { void PhysicalEntitySimulation::sendOwnershipBids(uint32_t numSubsteps) {
++stateItr; 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) { void PhysicalEntitySimulation::addDynamic(EntityDynamicPointer dynamic) {
if (_physicsEngine) { if (_physicsEngine) {
// FIXME put fine grain locking into _physicsEngine // FIXME put fine grain locking into _physicsEngine

View file

@ -27,7 +27,19 @@ class PhysicalEntitySimulation;
using PhysicalEntitySimulationPointer = std::shared_ptr<PhysicalEntitySimulation>; using PhysicalEntitySimulationPointer = std::shared_ptr<PhysicalEntitySimulation>;
using SetOfEntityMotionStates = QSet<EntityMotionState*>; 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 { class PhysicalEntitySimulation : public EntitySimulation {
Q_OBJECT
public: public:
PhysicalEntitySimulation(); PhysicalEntitySimulation();
~PhysicalEntitySimulation(); ~PhysicalEntitySimulation();
@ -37,21 +49,28 @@ public:
virtual void addDynamic(EntityDynamicPointer dynamic) override; virtual void addDynamic(EntityDynamicPointer dynamic) override;
virtual void applyDynamicChanges() 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 protected: // only called by EntitySimulation
// overrides for 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 addEntityInternal(EntityItemPointer entity) override;
virtual void removeEntityInternal(EntityItemPointer entity) override; virtual void removeEntityInternal(EntityItemPointer entity) override;
virtual void changeEntityInternal(EntityItemPointer entity) override; virtual void changeEntityInternal(EntityItemPointer entity) override;
virtual void clearEntitiesInternal() override; virtual void clearEntitiesInternal() override;
void removeOwnershipData(EntityMotionState* motionState);
void clearOwnershipData();
public: public:
virtual void prepareEntityForDelete(EntityItemPointer entity) override; virtual void prepareEntityForDelete(EntityItemPointer entity) override;
void getObjectsToRemoveFromPhysics(VectorOfMotionStates& result); const VectorOfMotionStates& getObjectsToRemoveFromPhysics();
void deleteObjectsRemovedFromPhysics(); void deleteObjectsRemovedFromPhysics();
void getObjectsToAddToPhysics(VectorOfMotionStates& result); void getObjectsToAddToPhysics(VectorOfMotionStates& result);
void setObjectsToChange(const VectorOfMotionStates& objectsToChange); void setObjectsToChange(const VectorOfMotionStates& objectsToChange);
void getObjectsToChange(VectorOfMotionStates& result); void getObjectsToChange(VectorOfMotionStates& result);
@ -62,19 +81,28 @@ public:
EntityEditPacketSender* getPacketSender() { return _entityPacketSender; } EntityEditPacketSender* getPacketSender() { return _entityPacketSender; }
private: void addOwnershipBid(EntityMotionState* motionState);
SetOfEntities _entitiesToRemoveFromPhysics; void addOwnership(EntityMotionState* motionState);
SetOfEntities _entitiesToRelease; void sendOwnershipBids(uint32_t numSubsteps);
SetOfEntities _entitiesToAddToPhysics; void sendOwnedUpdates(uint32_t numSubsteps);
SetOfEntityMotionStates _pendingChanges; // EntityMotionStates already in PhysicsEngine that need their physics changed private:
SetOfEntityMotionStates _outgoingChanges; // EntityMotionStates for which we may need to send updates to entity-server 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 SetOfMotionStates _physicalObjects; // MotionStates of entities in PhysicsEngine
PhysicsEnginePointer _physicsEngine = nullptr; PhysicsEnginePointer _physicsEngine = nullptr;
EntityEditPacketSender* _entityPacketSender = nullptr; EntityEditPacketSender* _entityPacketSender = nullptr;
VectorOfEntityMotionStates _owned;
VectorOfEntityMotionStates _bids;
uint64_t _nextBidExpiry;
uint32_t _lastStepSendPackets { 0 }; uint32_t _lastStepSendPackets { 0 };
}; };

View file

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

View file

@ -39,43 +39,52 @@ void FadeEditJob::run(const render::RenderContextPointer& renderContext, const F
auto scene = renderContext->_scene; auto scene = renderContext->_scene;
if (_isEditEnabled) { if (_isEditEnabled) {
float minIsectDistance = std::numeric_limits<float>::max(); static const std::string selectionName("TransitionEdit");
auto& itemBounds = inputs.get0(); auto scene = renderContext->_scene;
auto editedItem = findNearestItem(renderContext, itemBounds, minIsectDistance); if (!scene->isSelectionEmpty(selectionName)) {
render::Transaction transaction; auto selection = scene->getSelection(selectionName);
bool hasTransaction{ false }; auto editedItem = selection.getItems().front();
render::Transaction transaction;
bool hasTransaction{ false };
if (editedItem != _editedItem && render::Item::isValidID(_editedItem)) { if (editedItem != _editedItem && render::Item::isValidID(_editedItem)) {
// Remove transition from previously edited item as we've changed edited item // Remove transition from previously edited item as we've changed edited item
hasTransaction = true; 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); 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); scene->enqueueTransaction(transaction);
_editedItem = render::Item::INVALID_ITEM_ID;
} }
} }
else if (render::Item::isValidID(_editedItem)) { 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() FadeConfig::FadeConfig()
{ {
events[FADE_ELEMENT_ENTER_LEAVE_DOMAIN].noiseSize = glm::vec3{ 0.75f, 0.75f, 0.75f }; 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", "avatar_change",
}; };
void FadeConfig::save() const { void FadeConfig::save(const QString& configFilePath) const {
// Save will only work if the HIFI_USE_SOURCE_TREE_RESOURCES environment variable is set
assert(editedCategory < FADE_CATEGORY_COUNT); assert(editedCategory < FADE_CATEGORY_COUNT);
QJsonObject lProperties; QJsonObject lProperties;
const QString configFilePath = PathUtils::resourcesPath() + "config/" + eventNames[editedCategory] + ".json";
QFile file(configFilePath); QFile file(configFilePath);
if (!file.open(QFile::WriteOnly | QFile::Text)) { if (!file.open(QFile::WriteOnly | QFile::Text)) {
qWarning() << "Fade event configuration file " << configFilePath << " cannot be opened"; qWarning() << "Fade event configuration file " << configFilePath << " cannot be opened";
@ -382,8 +367,7 @@ void FadeConfig::save() const {
} }
} }
void FadeConfig::load() { void FadeConfig::load(const QString& configFilePath) {
const QString configFilePath = PathUtils::resourcesPath() + "config/" + eventNames[editedCategory] + ".json";
QFile file(configFilePath); QFile file(configFilePath);
if (!file.exists()) { if (!file.exists()) {
qWarning() << "Fade event configuration file " << configFilePath << " does not exist"; 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)) { if (update(*jobConfig, scene, transaction, state, deltaTime)) {
hasTransaction = true; hasTransaction = true;
} }
if (isFirstItem && jobConfig->manualFade && (state.threshold != jobConfig->threshold)) { if (isFirstItem && (state.threshold != jobConfig->threshold)) {
jobConfig->setProperty("threshold", state.threshold); jobConfig->setProperty("threshold", state.threshold);
isFirstItem = false; isFirstItem = false;
} }

View file

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

View file

@ -1,5 +1,3 @@
"use strict";
// //
// debugTransition.js // debugTransition.js
// developer/utilities/render // developer/utilities/render
@ -12,12 +10,17 @@
// //
(function() { (function() {
"use strict";
var TABLET_BUTTON_NAME = "Transition"; var TABLET_BUTTON_NAME = "Transition";
var QMLAPP_URL = Script.resolvePath("./transition.qml"); var QMLAPP_URL = Script.resolvePath("./transition.qml");
var ICON_URL = Script.resolvePath("../../../system/assets/images/transition-i.svg"); var ICON_URL = Script.resolvePath("../../../system/assets/images/transition-i.svg");
var ACTIVE_ICON_URL = Script.resolvePath("../../../system/assets/images/transition-a.svg"); var ACTIVE_ICON_URL = Script.resolvePath("../../../system/assets/images/transition-a.svg");
Script.include([
Script.resolvePath("../../../system/libraries/stringHelpers.js"),
]);
var onScreen = false; var onScreen = false;
function onClicked() { function onClicked() {
@ -37,6 +40,71 @@
var hasEventBridge = false; 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) { function wireEventBridge(on) {
if (!tablet) { if (!tablet) {
print("Warning in wireEventBridge(): 'tablet' undefined!"); print("Warning in wireEventBridge(): 'tablet' undefined!");
@ -46,11 +114,15 @@
if (!hasEventBridge) { if (!hasEventBridge) {
tablet.fromQml.connect(fromQml); tablet.fromQml.connect(fromQml);
hasEventBridge = true; hasEventBridge = true;
enablePointer();
Render.getConfig("RenderMainView.FadeEdit")["editFade"] = true
} }
} else { } else {
if (hasEventBridge) { if (hasEventBridge) {
tablet.fromQml.disconnect(fromQml); tablet.fromQml.disconnect(fromQml);
hasEventBridge = false; hasEventBridge = false;
disablePointer();
Render.getConfig("RenderMainView.FadeEdit")["editFade"] = false
} }
} }
} }
@ -66,12 +138,87 @@
wireEventBridge(onScreen); 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) { 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); button.clicked.connect(onClicked);
tablet.screenChanged.connect(onScreenChanged); tablet.screenChanged.connect(onScreenChanged);
Script.scriptEnding.connect(function () { Script.scriptEnding.connect(function () {
if (onScreen) { if (onScreen) {
tablet.gotoHomeScreen(); tablet.gotoHomeScreen();
@ -81,4 +228,28 @@
tablet.removeButton(button); tablet.removeButton(button);
}); });
var currentSelectionName = ""
var SelectionList = "TransitionEdit"
Selection.enableListToScene(SelectionList)
Selection.clearSelectedItemsList(SelectionList)
Entities.clickDownOnEntity.connect(function (id, event) {
if (selectedEntity) {
Selection.removeFromSelectedItemsList(SelectionList, "entity", selectedEntity)
}
selectedEntity = id
Selection.addToSelectedItemsList(SelectionList, "entity", selectedEntity)
update()
})
function cleanup() {
disablePointer();
Selection.removeListFromMap(SelectionList)
Selection.disableListToScene(SelectionList);
Overlays.deleteOverlay(noiseSphere);
Overlays.deleteOverlay(gradientSphere);
}
Script.scriptEnding.connect(cleanup);
}()); }());

View file

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

View file

@ -295,6 +295,7 @@ Script.include("/~/system/libraries/Xform.js");
this.actionID = null; this.actionID = null;
this.grabbedThingID = null; this.grabbedThingID = null;
this.targetObject = null; this.targetObject = null;
this.potentialEntityWithContextOverlay = false;
}; };
this.updateRecommendedArea = function() { this.updateRecommendedArea = function() {

View file

@ -1663,11 +1663,11 @@ SelectionDisplay = (function() {
mode: mode, mode: mode,
onBegin: function(event, pickRay, pickResult) { onBegin: function(event, pickRay, pickResult) {
if (direction === TRANSLATE_DIRECTION.X) { 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) { } 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) { } 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; var rotation = spaceMode === SPACE_LOCAL ? SelectionManager.localRotation : SelectionManager.worldRotation;
@ -1711,7 +1711,6 @@ SelectionDisplay = (function() {
onMove: function(event) { onMove: function(event) {
pickRay = generalComputePickRay(event.x, event.y); pickRay = generalComputePickRay(event.x, event.y);
// translate mode left/right based on view toward entity
var newIntersection = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, pickNormal); var newIntersection = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, pickNormal);
var vector = Vec3.subtract(newIntersection, lastPick); var vector = Vec3.subtract(newIntersection, lastPick);
@ -1729,7 +1728,7 @@ SelectionDisplay = (function() {
var dotVector = Vec3.dot(vector, projectionVector); var dotVector = Vec3.dot(vector, projectionVector);
vector = Vec3.multiply(dotVector, projectionVector); vector = Vec3.multiply(dotVector, projectionVector);
vector = grid.snapToGrid(vector); vector = grid.snapToGrid(vector);
var wantDebug = false; var wantDebug = false;
if (wantDebug) { if (wantDebug) {
print("translateUpDown... "); print("translateUpDown... ");
@ -2037,10 +2036,10 @@ SelectionDisplay = (function() {
vector = grid.snapToSpacing(vector); vector = grid.snapToSpacing(vector);
var changeInDimensions = Vec3.multiply(NEGATE_VECTOR, vec3Mult(localSigns, vector)); var changeInDimensions = Vec3.multiply(NEGATE_VECTOR, vec3Mult(localSigns, vector));
if (directionEnum === STRETCH_DIRECTION.ALL) { if (directionEnum === STRETCH_DIRECTION.ALL) {
var toCameraDistance = getDistanceToCamera(position); var toCameraDistance = getDistanceToCamera(position);
var dimensionsMultiple = toCameraDistance * STRETCH_DIRECTION_ALL_CAMERA_DISTANCE_MULTIPLE; var dimensionsMultiple = toCameraDistance * STRETCH_DIRECTION_ALL_CAMERA_DISTANCE_MULTIPLE;
changeInDimensions = Vec3.multiply(changeInDimensions, dimensionsMultiple); changeInDimensions = Vec3.multiply(changeInDimensions, dimensionsMultiple);
} }
var newDimensions; var newDimensions;

View file

@ -1,6 +1,6 @@
"use strict"; "use strict";
/*jslint vars:true, plusplus:true, forin:true*/ /*jslint vars:true, plusplus:true, forin:true*/
/*global Script, Settings, Window, Controller, Overlays, SoundArray, LODManager, MyAvatar, Tablet, Camera, HMD, Menu, Quat, Vec3*/ /*global Script, Settings, Window, Controller, Overlays, SoundArray, MyAvatar, Tablet, Camera, HMD, Menu, Quat, Vec3*/
// //
// notifications.js // notifications.js
// Version 0.801 // Version 0.801
@ -84,21 +84,18 @@
var NOTIFICATION_MENU_ITEM_POST = " Notifications"; var NOTIFICATION_MENU_ITEM_POST = " Notifications";
var PLAY_NOTIFICATION_SOUNDS_SETTING = "play_notification_sounds"; var PLAY_NOTIFICATION_SOUNDS_SETTING = "play_notification_sounds";
var PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE = "play_notification_sounds_type_"; var PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE = "play_notification_sounds_type_";
var lodTextID = false; var NOTIFICATIONS_MESSAGE_CHANNEL = "Hifi-Notifications";
var NOTIFICATIONS_MESSAGE_CHANNEL = "Hifi-Notifications"
var NotificationType = { var NotificationType = {
UNKNOWN: 0, UNKNOWN: 0,
SNAPSHOT: 1, SNAPSHOT: 1,
LOD_WARNING: 2, CONNECTION_REFUSED: 2,
CONNECTION_REFUSED: 3, EDIT_ERROR: 3,
EDIT_ERROR: 4, TABLET: 4,
TABLET: 5, CONNECTION: 5,
CONNECTION: 6, WALLET: 6,
WALLET: 7,
properties: [ properties: [
{ text: "Snapshot" }, { text: "Snapshot" },
{ text: "Level of Detail" },
{ text: "Connection Refused" }, { text: "Connection Refused" },
{ text: "Edit error" }, { text: "Edit error" },
{ text: "Tablet" }, { text: "Tablet" },
@ -153,10 +150,6 @@
// This handles the final dismissal of a notification after fading // This handles the final dismissal of a notification after fading
function dismiss(firstNoteOut, firstButOut, firstOut) { function dismiss(firstNoteOut, firstButOut, firstOut) {
if (firstNoteOut === lodTextID) {
lodTextID = false;
}
Overlays.deleteOverlay(firstNoteOut); Overlays.deleteOverlay(firstNoteOut);
Overlays.deleteOverlay(firstButOut); Overlays.deleteOverlay(firstButOut);
notifications.splice(firstOut, 1); notifications.splice(firstOut, 1);
@ -418,9 +411,6 @@
function deleteNotification(index) { function deleteNotification(index) {
var notificationTextID = notifications[index]; var notificationTextID = notifications[index];
if (notificationTextID === lodTextID) {
lodTextID = false;
}
Overlays.deleteOverlay(notificationTextID); Overlays.deleteOverlay(notificationTextID);
Overlays.deleteOverlay(buttons[index]); Overlays.deleteOverlay(buttons[index]);
notifications.splice(index, 1); notifications.splice(index, 1);
@ -674,20 +664,6 @@
} }
} }
LODManager.LODDecreased.connect(function () {
var warningText = "\n" +
"Due to the complexity of the content, the \n" +
"level of detail has been decreased. " +
"You can now see: \n" +
LODManager.getLODFeedbackText();
if (lodTextID === false) {
lodTextID = createNotification(warningText, NotificationType.LOD_WARNING);
} else {
Overlays.editOverlay(lodTextID, { text: warningText });
}
});
Controller.keyPressEvent.connect(keyPressEvent); Controller.keyPressEvent.connect(keyPressEvent);
Controller.mousePressEvent.connect(mousePressEvent); Controller.mousePressEvent.connect(mousePressEvent);
Controller.keyReleaseEvent.connect(keyReleaseEvent); Controller.keyReleaseEvent.connect(keyReleaseEvent);

View file

@ -24,15 +24,11 @@ extern AutoTester* autoTester;
#include <math.h> #include <math.h>
Test::Test() { Test::Test() {
QString regex(EXPECTED_IMAGE_PREFIX + QString("\\\\d").repeated(NUM_DIGITS) + ".png");
expectedImageFilenameFormat = QRegularExpression(regex);
mismatchWindow.setModal(true); mismatchWindow.setModal(true);
} }
bool Test::createTestResultsFolderPath(QString directory) { bool Test::createTestResultsFolderPath(QString directory) {
QDateTime now = QDateTime::currentDateTime(); QDateTime now = QDateTime::currentDateTime();
testResultsFolderPath = directory + "/" + TEST_RESULTS_FOLDER + "--" + now.toString(DATETIME_FORMAT); testResultsFolderPath = directory + "/" + TEST_RESULTS_FOLDER + "--" + now.toString(DATETIME_FORMAT);
QDir testResultsFolder(testResultsFolderPath); QDir testResultsFolder(testResultsFolderPath);
@ -76,7 +72,7 @@ bool Test::compareImageLists(bool isInteractiveMode, QProgressBar* progressBar)
QImage expectedImage(expectedImagesFullFilenames[i]); QImage expectedImage(expectedImagesFullFilenames[i]);
if (resultImage.width() != expectedImage.width() || resultImage.height() != expectedImage.height()) { if (resultImage.width() != expectedImage.width() || resultImage.height() != expectedImage.height()) {
messageBox.critical(0, "Internal error #1", "Images are not the same size"); messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Images are not the same size");
exit(-1); exit(-1);
} }
@ -84,7 +80,7 @@ bool Test::compareImageLists(bool isInteractiveMode, QProgressBar* progressBar)
try { try {
similarityIndex = imageComparer.compareImages(resultImage, expectedImage); similarityIndex = imageComparer.compareImages(resultImage, expectedImage);
} catch (...) { } catch (...) {
messageBox.critical(0, "Internal error #2", "Image not in expected format"); messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Image not in expected format");
exit(-1); exit(-1);
} }
@ -131,20 +127,20 @@ bool Test::compareImageLists(bool isInteractiveMode, QProgressBar* progressBar)
void Test::appendTestResultsToFile(QString testResultsFolderPath, TestFailure testFailure, QPixmap comparisonImage) { void Test::appendTestResultsToFile(QString testResultsFolderPath, TestFailure testFailure, QPixmap comparisonImage) {
if (!QDir().exists(testResultsFolderPath)) { if (!QDir().exists(testResultsFolderPath)) {
messageBox.critical(0, "Internal error #3", "Folder " + testResultsFolderPath + " not found"); messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Folder " + testResultsFolderPath + " not found");
exit(-1); exit(-1);
} }
QString failureFolderPath { testResultsFolderPath + "/" + "Failure_" + QString::number(index) }; QString failureFolderPath { testResultsFolderPath + "/" + "Failure_" + QString::number(index) };
if (!QDir().mkdir(failureFolderPath)) { if (!QDir().mkdir(failureFolderPath)) {
messageBox.critical(0, "Internal error #4", "Failed to create folder " + failureFolderPath); messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create folder " + failureFolderPath);
exit(-1); exit(-1);
} }
++index; ++index;
QFile descriptionFile(failureFolderPath + "/" + TEST_RESULTS_FILENAME); QFile descriptionFile(failureFolderPath + "/" + TEST_RESULTS_FILENAME);
if (!descriptionFile.open(QIODevice::ReadWrite)) { if (!descriptionFile.open(QIODevice::ReadWrite)) {
messageBox.critical(0, "Internal error #5", "Failed to create file " + TEST_RESULTS_FILENAME); messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create file " + TEST_RESULTS_FILENAME);
exit(-1); exit(-1);
} }
@ -164,14 +160,14 @@ void Test::appendTestResultsToFile(QString testResultsFolderPath, TestFailure te
sourceFile = testFailure._pathname + testFailure._expectedImageFilename; sourceFile = testFailure._pathname + testFailure._expectedImageFilename;
destinationFile = failureFolderPath + "/" + "Expected Image.jpg"; destinationFile = failureFolderPath + "/" + "Expected Image.jpg";
if (!QFile::copy(sourceFile, destinationFile)) { if (!QFile::copy(sourceFile, destinationFile)) {
messageBox.critical(0, "Internal error #6", "Failed to copy " + sourceFile + " to " + destinationFile); messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to copy " + sourceFile + " to " + destinationFile);
exit(-1); exit(-1);
} }
sourceFile = testFailure._pathname + testFailure._actualImageFilename; sourceFile = testFailure._pathname + testFailure._actualImageFilename;
destinationFile = failureFolderPath + "/" + "Actual Image.jpg"; destinationFile = failureFolderPath + "/" + "Actual Image.jpg";
if (!QFile::copy(sourceFile, destinationFile)) { if (!QFile::copy(sourceFile, destinationFile)) {
messageBox.critical(0, "Internal error #7", "Failed to copy " + sourceFile + " to " + destinationFile); messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to copy " + sourceFile + " to " + destinationFile);
exit(-1); exit(-1);
} }
@ -209,11 +205,6 @@ void Test::startTestsEvaluation() {
QStringList sortedTestResultsFilenames = createListOfAll_imagesInDirectory("png", pathToTestResultsDirectory); QStringList sortedTestResultsFilenames = createListOfAll_imagesInDirectory("png", pathToTestResultsDirectory);
QStringList expectedImagesURLs; QStringList expectedImagesURLs;
const QString URLPrefix("https://raw.githubusercontent.com");
const QString githubUser("NissimHadar");
const QString testsRepo("hifi_tests");
const QString branch("addRecursionToAutotester");
resultImagesFullFilenames.clear(); resultImagesFullFilenames.clear();
expectedImagesFilenames.clear(); expectedImagesFilenames.clear();
expectedImagesFullFilenames.clear(); expectedImagesFullFilenames.clear();
@ -226,16 +217,16 @@ void Test::startTestsEvaluation() {
QString expectedImagePartialSourceDirectory = getExpectedImagePartialSourceDirectory(currentFilename); QString expectedImagePartialSourceDirectory = getExpectedImagePartialSourceDirectory(currentFilename);
// Images are stored on GitHub as ExpectedImage_ddddd.png // Images are stored on GitHub as ExpectedImage_ddddd.png
// Extract the digits at the end of the filename (exluding the file extension) // Extract the digits at the end of the filename (excluding the file extension)
QString expectedImageFilenameTail = currentFilename.left(currentFilename.length() - 4).right(NUM_DIGITS); QString expectedImageFilenameTail = currentFilename.left(currentFilename.length() - 4).right(NUM_DIGITS);
QString expectedImageStoredFilename = EXPECTED_IMAGE_PREFIX + expectedImageFilenameTail + ".png"; QString expectedImageStoredFilename = EXPECTED_IMAGE_PREFIX + expectedImageFilenameTail + ".png";
QString imageURLString(URLPrefix + "/" + githubUser + "/" + testsRepo + "/" + branch + "/" + QString imageURLString("https://github.com/" + githubUser + "/hifi_tests/blob/" + gitHubBranch + "/" +
expectedImagePartialSourceDirectory + "/" + expectedImageStoredFilename); expectedImagePartialSourceDirectory + "/" + expectedImageStoredFilename + "?raw=true");
expectedImagesURLs << imageURLString; expectedImagesURLs << imageURLString;
// The image retrieved from Github needs a unique name // The image retrieved from GitHub needs a unique name
QString expectedImageFilename = currentFilename.replace("/", "_").replace(".", "_EI."); QString expectedImageFilename = currentFilename.replace("/", "_").replace(".", "_EI.");
expectedImagesFilenames << expectedImageFilename; expectedImagesFilenames << expectedImageFilename;
@ -273,25 +264,31 @@ bool Test::isAValidDirectory(QString pathname) {
return true; return true;
} }
void Test::importTest(QTextStream& textStream, const QString& testPathname) { QString Test::extractPathFromTestsDown(QString fullPath) {
// `testPathname` includes the full path to the test. We need the portion below (and including) `tests` // `fullPath` includes the full path to the test. We need the portion below (and including) `tests`
QStringList filenameParts = testPathname.split('/'); QStringList pathParts = fullPath.split('/');
int i{ 0 }; int i{ 0 };
while (i < filenameParts.length() && filenameParts[i] != "tests") { while (i < pathParts.length() && pathParts[i] != "tests") {
++i; ++i;
} }
if (i == filenameParts.length()) { if (i == pathParts.length()) {
messageBox.critical(0, "Internal error #10", "Bad testPathname"); messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Bad testPathname");
exit(-1); exit(-1);
} }
QString filename; QString partialPath;
for (int j = i; j < filenameParts.length(); ++j) { for (int j = i; j < pathParts.length(); ++j) {
filename += "/" + filenameParts[j]; partialPath += "/" + pathParts[j];
} }
textStream << "Script.include(\"" << "https://raw.githubusercontent.com/" << user << "/hifi_tests/" << branch << filename + "\");" << endl; return partialPath;
}
void Test::importTest(QTextStream& textStream, const QString& testPathname) {
QString partialPath = extractPathFromTestsDown(testPathname);
textStream << "Script.include(\"" << "https://github.com/" << githubUser
<< "/hifi_tests/blob/" << gitHubBranch << partialPath + "?raw=true\");" << endl;
} }
// Creates a single script in a user-selected folder. // Creates a single script in a user-selected folder.
@ -353,7 +350,7 @@ void Test::createRecursiveScript(QString topLevelDirectory, bool interactiveMode
QFile allTestsFilename(topLevelDirectory + "/" + recursiveTestsFilename); QFile allTestsFilename(topLevelDirectory + "/" + recursiveTestsFilename);
if (!allTestsFilename.open(QIODevice::WriteOnly | QIODevice::Text)) { if (!allTestsFilename.open(QIODevice::WriteOnly | QIODevice::Text)) {
messageBox.critical(0, messageBox.critical(0,
"Internal Error #8", "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
"Failed to create \"" + recursiveTestsFilename + "\" in directory \"" + topLevelDirectory + "\"" "Failed to create \"" + recursiveTestsFilename + "\" in directory \"" + topLevelDirectory + "\""
); );
@ -363,7 +360,9 @@ void Test::createRecursiveScript(QString topLevelDirectory, bool interactiveMode
QTextStream textStream(&allTestsFilename); QTextStream textStream(&allTestsFilename);
textStream << "// This is an automatically generated file, created by auto-tester" << endl << endl; textStream << "// This is an automatically generated file, created by auto-tester" << endl << endl;
textStream << "var autoTester = Script.require(\"https://raw.githubusercontent.com/" + user + "/hifi_tests/" + branch + "/tests/utils/autoTester.js\");" << endl; textStream << "var autoTester = Script.require(\"https://github.com/" + githubUser + "/hifi_tests/blob/"
+ gitHubBranch + "/tests/utils/autoTester.js?raw=true\");" << endl;
textStream << "autoTester.enableRecursive();" << endl << endl; textStream << "autoTester.enableRecursive();" << endl << endl;
QVector<QString> testPathnames; QVector<QString> testPathnames;
@ -454,6 +453,203 @@ void Test::createTest() {
messageBox.information(0, "Success", "Test images have been created"); messageBox.information(0, "Success", "Test images have been created");
} }
ExtractedText Test::getTestScriptLines(QString testFileName) {
ExtractedText relevantTextFromTest;
QFile inputFile(testFileName);
inputFile.open(QIODevice::ReadOnly);
if (!inputFile.isOpen()) {
messageBox.critical(0,
"Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
"Failed to open \"" + testFileName
);
}
QTextStream stream(&inputFile);
QString line = stream.readLine();
// Name of test is the string in the following line:
// autoTester.perform("Apply Material Entities to Avatars", Script.resolvePath("."), function(testType) {...
const QString ws("\\h*"); //white-space character
const QString functionPerformName(ws + "autoTester" + ws + "\\." + ws + "perform");
const QString quotedString("\\\".+\\\"");
const QString ownPath("Script" + ws + "\\." + ws + "resolvePath" + ws + "\\(" + ws + "\\\"\\.\\\"" + ws + "\\)");
const QString functionParameter("function" + ws + "\\(testType" + ws + "\\)");
QString regexTestTitle(ws + functionPerformName + "\\(" + quotedString + "\\," + ws + ownPath + "\\," + ws + functionParameter + ws + "{" + ".*");
QRegularExpression lineContainingTitle = QRegularExpression(regexTestTitle);
// Assert platform checks that test is running on the correct OS
const QString functionAssertPlatform(ws + "autoTester" + ws + "\\." + ws + "assertPlatform");
const QString regexAssertPlatform(ws + functionAssertPlatform + ws + "\\(" + ws + quotedString + ".*");
const QRegularExpression lineAssertPlatform = QRegularExpression(regexAssertPlatform);
// Assert display checks that test is running on the correct display
const QString functionAssertDisplay(ws + "autoTester" + ws + "\\." + ws + "assertDisplay");
const QString regexAssertDisplay(ws + functionAssertDisplay + ws + "\\(" + ws + quotedString + ".*");
const QRegularExpression lineAssertDisplay = QRegularExpression(regexAssertDisplay);
// Assert CPU checks that test is running on the correct type of CPU
const QString functionAssertCPU(ws + "autoTester" + ws + "\\." + ws + "assertCPU");
const QString regexAssertCPU(ws + functionAssertCPU + ws + "\\(" + ws + quotedString + ".*");
const QRegularExpression lineAssertCPU = QRegularExpression(regexAssertCPU);
// Assert GPU checks that test is running on the correct type of GPU
const QString functionAssertGPU(ws + "autoTester" + ws + "\\." + ws + "assertGPU");
const QString regexAssertGPU(ws + functionAssertGPU + ws + "\\(" + ws + quotedString + ".*");
const QRegularExpression lineAssertGPU = QRegularExpression(regexAssertGPU);
// Each step is either of the following forms:
// autoTester.addStepSnapshot("Take snapshot"...
// autoTester.addStep("Clean up after test"...
const QString functionAddStepSnapshotName(ws + "autoTester" + ws + "\\." + ws + "addStepSnapshot");
const QString regexStepSnapshot(ws + functionAddStepSnapshotName + ws + "\\(" + ws + quotedString + ".*");
const QRegularExpression lineStepSnapshot = QRegularExpression(regexStepSnapshot);
const QString functionAddStepName(ws + "autoTester" + ws + "\\." + ws + "addStep");
const QString regexStep(ws + functionAddStepName + ws + "\\(" + ws + quotedString + ws + "\\)" + ".*");
const QRegularExpression lineStep = QRegularExpression(regexStep);
while (!line.isNull()) {
line = stream.readLine();
if (lineContainingTitle.match(line).hasMatch()) {
QStringList tokens = line.split('"');
relevantTextFromTest.title = tokens[1];
} else if (lineAssertPlatform.match(line).hasMatch()) {
QStringList platforms = line.split('"');
relevantTextFromTest.platform = platforms[1];
} else if (lineAssertDisplay.match(line).hasMatch()) {
QStringList displays = line.split('"');
relevantTextFromTest.display = displays[1];
} else if (lineAssertCPU.match(line).hasMatch()) {
QStringList cpus = line.split('"');
relevantTextFromTest.cpu = cpus[1];
} else if (lineAssertGPU.match(line).hasMatch()) {
QStringList gpus = line.split('"');
relevantTextFromTest.gpu = gpus[1];
} else if (lineStepSnapshot.match(line).hasMatch()) {
QStringList tokens = line.split('"');
QString nameOfStep = tokens[1];
Step *step = new Step();
step->text = nameOfStep;
step->takeSnapshot = true;
relevantTextFromTest.stepList.emplace_back(step);
} else if (lineStep.match(line).hasMatch()) {
QStringList tokens = line.split('"');
QString nameOfStep = tokens[1];
Step *step = new Step();
step->text = nameOfStep;
step->takeSnapshot = false;
relevantTextFromTest.stepList.emplace_back(step);
}
}
inputFile.close();
return relevantTextFromTest;
}
// Create an MD file for a user-selected test.
// The folder selected must contain a script named "test.js", the file produced is named "test.md"
void Test::createMDFile() {
// Folder selection
QString testDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test", ".", QFileDialog::ShowDirsOnly);
if (testDirectory == "") {
return;
}
// Verify folder contains test.js file
QString testFileName(testDirectory + "/" + TEST_FILENAME);
QFileInfo testFileInfo(testFileName);
if (!testFileInfo.exists()) {
messageBox.critical(0, "Error", "Could not find file: " + TEST_FILENAME);
return;
}
ExtractedText testScriptLines = getTestScriptLines(testFileName);
QString mdFilename(testDirectory + "/" + "test.md");
QFile mdFile(mdFilename);
if (!mdFile.open(QIODevice::WriteOnly)) {
messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create file " + mdFilename);
exit(-1);
}
QTextStream stream(&mdFile);
//Test title
QString testName = testScriptLines.title;
stream << "# " << testName << "\n";
// Find the relevant part of the path to the test (i.e. from "tests" down
QString partialPath = extractPathFromTestsDown(testDirectory);
stream << "## Run this script URL: [Manual](./test.js?raw=true) [Auto](./testAuto.js?raw=true)(from menu/Edit/Open and Run scripts from URL...)." << "\n\n";
stream << "## Preconditions" << "\n";
stream << "- In an empty region of a domain with editing rights." << "\n\n";
// Platform
QStringList platforms = testScriptLines.platform.split(" ");;
stream << "## Platforms\n";
stream << "Run the test on each of the following platforms\n";
for (int i = 0; i < platforms.size(); ++i) {
// Note that the platforms parameter may include extra spaces, these appear as empty strings in the list
if (platforms[i] != QString()) {
stream << " - " << platforms[i] << "\n";
}
}
// Display
QStringList displays = testScriptLines.display.split(" ");
stream << "## Displays\n";
stream << "Run the test on each of the following displays\n";
for (int i = 0; i < displays.size(); ++i) {
// Note that the displays parameter may include extra spaces, these appear as empty strings in the list
if (displays[i] != QString()) {
stream << " - " << displays[i] << "\n";
}
}
// CPU
QStringList cpus = testScriptLines.cpu.split(" ");
stream << "## Processors\n";
stream << "Run the test on each of the following processors\n";
for (int i = 0; i < cpus.size(); ++i) {
// Note that the cpus parameter may include extra spaces, these appear as empty strings in the list
if (cpus[i] != QString()) {
stream << " - " << cpus[i] << "\n";
}
}
// GPU
QStringList gpus = testScriptLines.gpu.split(" ");
stream << "## Graphics Cards\n";
stream << "Run the test on graphics cards from each of the following vendors\n";
for (int i = 0; i < gpus.size(); ++i) {
// Note that the gpus parameter may include extra spaces, these appear as empty strings in the list
if (gpus[i] != QString()) {
stream << " - " << gpus[i] << "\n";
}
}
stream << "## Steps\n";
stream << "Press space bar to advance step by step\n\n";
int snapShotIndex { 0 };
for (size_t i = 0; i < testScriptLines.stepList.size(); ++i) {
stream << "### Step " << QString::number(i) << "\n";
stream << "- " << testScriptLines.stepList[i + 1]->text << "\n";
if (testScriptLines.stepList[i]->takeSnapshot) {
stream << "- ![](./ExpectedImage_" << QString::number(snapShotIndex).rightJustified(5, '0') << ".png)\n";
++snapShotIndex;
}
}
mdFile.close();
}
void Test::copyJPGtoPNG(QString sourceJPGFullFilename, QString destinationPNGFullFilename) { void Test::copyJPGtoPNG(QString sourceJPGFullFilename, QString destinationPNGFullFilename) {
QFile::remove(destinationPNGFullFilename); QFile::remove(destinationPNGFullFilename);
@ -526,7 +722,7 @@ QString Test::getExpectedImagePartialSourceDirectory(QString filename) {
} }
if (i < 0) { if (i < 0) {
messageBox.critical(0, "Internal error #9", "Bad filename"); messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Bad filename");
exit(-1); exit(-1);
} }

View file

@ -19,6 +19,24 @@
#include "ImageComparer.h" #include "ImageComparer.h"
#include "ui/MismatchWindow.h" #include "ui/MismatchWindow.h"
class Step {
public:
QString text;
bool takeSnapshot;
};
using StepList = std::vector<Step*>;
class ExtractedText {
public:
QString title;
QString platform;
QString display;
QString cpu;
QString gpu;
StepList stepList;
};
class Test { class Test {
public: public:
Test(); Test();
@ -31,7 +49,7 @@ public:
void createRecursiveScript(QString topLevelDirectory, bool interactiveMode); void createRecursiveScript(QString topLevelDirectory, bool interactiveMode);
void createTest(); void createTest();
void deleteOldSnapshots(); void createMDFile();
bool compareImageLists(bool isInteractiveMode, QProgressBar* progressBar); bool compareImageLists(bool isInteractiveMode, QProgressBar* progressBar);
@ -47,7 +65,7 @@ public:
void zipAndDeleteTestResultsFolder(); void zipAndDeleteTestResultsFolder();
bool isAValidDirectory(QString pathname); bool isAValidDirectory(QString pathname);
QString extractPathFromTestsDown(QString fullPath);
QString getExpectedImageDestinationDirectory(QString filename); QString getExpectedImageDestinationDirectory(QString filename);
QString getExpectedImagePartialSourceDirectory(QString filename); QString getExpectedImagePartialSourceDirectory(QString filename);
@ -62,8 +80,6 @@ private:
QDir imageDirectory; QDir imageDirectory;
QRegularExpression expectedImageFilenameFormat;
MismatchWindow mismatchWindow; MismatchWindow mismatchWindow;
ImageComparer imageComparer; ImageComparer imageComparer;
@ -81,9 +97,11 @@ private:
QStringList resultImagesFullFilenames; QStringList resultImagesFullFilenames;
// Used for accessing GitHub // Used for accessing GitHub
const QString user { "NissimHadar" }; const QString githubUser{ "highfidelity" };
const QString branch { "addRecursionToAutotester" }; const QString gitHubBranch { "master" };
const QString DATETIME_FORMAT { "yyyy-MM-dd_hh-mm-ss" }; const QString DATETIME_FORMAT { "yyyy-MM-dd_hh-mm-ss" };
ExtractedText getTestScriptLines(QString testFileName);
}; };
#endif // hifi_test_h #endif // hifi_test_h

View file

@ -33,7 +33,11 @@ void AutoTester::on_createRecursiveScriptsRecursivelyButton_clicked() {
} }
void AutoTester::on_createTestButton_clicked() { void AutoTester::on_createTestButton_clicked() {
test->createTest(); test->createTest();
}
void AutoTester::on_createMDFileButton_clicked() {
test->createMDFile();
} }
void AutoTester::on_closeButton_clicked() { void AutoTester::on_closeButton_clicked() {

View file

@ -29,8 +29,9 @@ private slots:
void on_evaluateTestsButton_clicked(); void on_evaluateTestsButton_clicked();
void on_createRecursiveScriptButton_clicked(); void on_createRecursiveScriptButton_clicked();
void on_createRecursiveScriptsRecursivelyButton_clicked(); void on_createRecursiveScriptsRecursivelyButton_clicked();
void on_createTestButton_clicked(); void on_createTestButton_clicked();
void on_closeButton_clicked(); void on_createMDFileButton_clicked();
void on_closeButton_clicked();
void saveImage(int index); void saveImage(int index);

View file

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>607</width> <width>607</width>
<height>395</height> <height>514</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -18,7 +18,7 @@
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>20</x> <x>20</x>
<y>300</y> <y>420</y>
<width>220</width> <width>220</width>
<height>40</height> <height>40</height>
</rect> </rect>
@ -44,7 +44,7 @@
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>20</x> <x>20</x>
<y>135</y> <y>255</y>
<width>220</width> <width>220</width>
<height>40</height> <height>40</height>
</rect> </rect>
@ -70,7 +70,7 @@
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>23</x> <x>23</x>
<y>100</y> <y>220</y>
<width>131</width> <width>131</width>
<height>20</height> <height>20</height>
</rect> </rect>
@ -86,7 +86,7 @@
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>20</x> <x>20</x>
<y>190</y> <y>310</y>
<width>255</width> <width>255</width>
<height>23</height> <height>23</height>
</rect> </rect>
@ -108,6 +108,19 @@
<string>Create Recursive Scripts Recursively</string> <string>Create Recursive Scripts Recursively</string>
</property> </property>
</widget> </widget>
<widget class="QPushButton" name="createMDFileButton">
<property name="geometry">
<rect>
<x>20</x>
<y>90</y>
<width>220</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>Create MD file</string>
</property>
</widget>
</widget> </widget>
<widget class="QMenuBar" name="menuBar"> <widget class="QMenuBar" name="menuBar">
<property name="geometry"> <property name="geometry">

91
tools/bake-tools/bake.py Normal file
View file

@ -0,0 +1,91 @@
import os, json, sys, shutil, subprocess, shlex, time
EXE = os.environ['HIFI_OVEN']
def listFiles(directory, extension):
items = os.listdir(directory)
fileList = []
for f in items:
if f.endswith('.' + extension):
fileList.append(f)
return fileList
def camelCaseString(string):
string = string.replace('-', ' ')
return ''.join(x for x in string.title() if not x.isspace())
def groupFiles(originalDirectory, newDirectory, files):
for file in files:
newPath = os.sep.join([newDirectory, file])
originalPath = os.sep.join([originalDirectory, file])
shutil.move(originalPath, newPath)
def groupKTXFiles(directory, filePath):
baseFile = os.path.basename(filePath)
filename = os.path.splitext(baseFile)[0]
camelCaseFileName = camelCaseString(filename)
path = os.sep.join([directory, camelCaseFileName])
files = listFiles(directory, 'ktx')
if len(files) > 0:
createDirectory(path)
groupFiles(directory, path, files)
newFilePath = os.sep.join([path, baseFile+'.baked.fbx'])
originalFilePath = os.sep.join([directory, baseFile+'.baked.fbx'])
originalFilePath.strip()
shutil.move(originalFilePath, newFilePath)
def bakeFile(filePath, outputDirectory):
createDirectory(outputDirectory)
cmd = EXE + ' -i ' + filePath + ' -o ' + outputDirectory + ' -t fbx'
args = shlex.split(cmd)
process = subprocess.Popen(cmd, stdout=False, stderr=False)
process.wait()
bakedFile = os.path.splitext(filePath)[0]
groupKTXFiles(outputDirectory, bakedFile)
def bakeFilesInDirectory(directory, outputDirectory):
for root, subFolders, filenames in os.walk(directory):
for filename in filenames:
if filename.endswith('.fbx'):
filePath = os.sep.join([root, filename])
absFilePath = os.path.abspath(filePath)
outputFolder = os.path.join(outputDirectory, os.path.relpath(root))
print "Baking file: " + filename
bakeFile(absFilePath, outputFolder)
else:
filePath = os.sep.join([root, filename])
absFilePath = os.path.abspath(filePath)
outputFolder = os.path.join(outputDirectory, os.path.relpath(root))
newFilePath = os.sep.join([outputFolder, filename])
createDirectory(outputFolder)
print "moving file: " + filename + " to: " + outputFolder
shutil.copy(absFilePath, newFilePath)
def createDirectory(directory):
if not os.path.exists(directory):
os.makedirs(directory)
def checkIfExeExists():
if not os.path.isfile(EXE) and os.access(EXE, os.X_OK):
print 'HIFI_OVEN evironment variable is not set'
sys.exit()
def handleOptions():
option = sys.argv[1]
if option == '--help' or option == '-h':
print 'Usage: bake.py INPUT_DIRECTORY[directory to bake] OUTPUT_DIRECTORY[directory to place backed files]'
print 'Note: Output directory will be created if directory does not exist'
sys.exit()
def main():
argsLength = len(sys.argv)
if argsLength == 3:
checkIfExeExists()
rootDirectory = sys.argv[1]
outputDirectory = os.path.abspath(sys.argv[2])
createDirectory(outputDirectory)
bakeFilesInDirectory(rootDirectory, outputDirectory)
elif argsLength == 2:
handleOptions()
main()

View file

@ -0,0 +1,82 @@
import json, os, sys, gzip
prefix = 'file:///~/'
MAP = {}
def createAssetMapping(assetDirectory):
baseDirectory = os.path.basename(os.path.normpath(assetDirectory))
for root, subfolder, filenames in os.walk(assetDirectory):
for filename in filenames:
if not filename.endswith('.ktx'):
substring = os.path.commonprefix([assetDirectory, root])
newPath = root.replace(substring, '');
filePath = os.sep.join([newPath, filename])
if filePath[0] == '\\':
filePath = filePath[1:]
finalPath = prefix + baseDirectory + '/' + filePath
finalPath = finalPath.replace('\\', '/')
file = os.path.splitext(filename)[0]
file = os.path.splitext(file)[0]
MAP[file] = finalPath
def hasURL(prop):
if "URL" in prop:
return True
return False
def handleURL(url):
newUrl = url
if "atp:" in url:
baseFilename = os.path.basename(url)
filename = os.path.splitext(baseFilename)[0]
newUrl = MAP[filename]
print newUrl
return newUrl
def handleOptions():
option = sys.argv[1]
if option == '--help' or option == '-h':
print 'Usage: convertToRelativePaths.py INPUT[json file you want to update the urls] INPUT[directory that the baked files are located in]'
sys.exit()
def main():
argsLength = len(sys.argv)
if argsLength == 3:
jsonFile = sys.argv[1]
gzipFile = jsonFile + '.gz'
assetDirectory = sys.argv[2]
createAssetMapping(assetDirectory)
f = open(jsonFile)
data = json.load(f)
f.close()
for entity in data['Entities']:
for prop in entity:
value = entity[prop]
if hasURL(prop):
value = handleURL(value)
if prop == "script":
value = handleURL(value)
if prop == "textures":
try:
tmp = json.loads(value)
for index in tmp:
tmp[index] = handleURL(tmp[index])
value = json.dumps(tmp)
except:
value = handleURL(value)
if prop == "serverScripts":
value = handleURL(value)
entity[prop] = value
jsonString = json.dumps(data)
jsonBytes= jsonString.encode('utf-8')
with gzip.GzipFile(gzipFile, 'w') as fout: # 4. gzip
fout.write(jsonBytes)
elif argsLength == 2:
handleOptions()
main()