diff --git a/assignment-client/src/AssignmentAction.cpp b/assignment-client/src/AssignmentAction.cpp new file mode 100644 index 0000000000..6cb3c06312 --- /dev/null +++ b/assignment-client/src/AssignmentAction.cpp @@ -0,0 +1,83 @@ +// +// AssignmentAction.cpp +// assignment-client/src/ +// +// Created by Seth Alves 2015-6-19 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "EntitySimulation.h" + +#include "AssignmentAction.h" + +AssignmentAction::AssignmentAction(EntityActionType type, QUuid id, EntityItemPointer ownerEntity) : + _id(id), + _type(type), + _data(QByteArray()), + _active(false), + _ownerEntity(ownerEntity) { +} + +AssignmentAction::~AssignmentAction() { +} + +void AssignmentAction::removeFromSimulation(EntitySimulation* simulation) const { + simulation->removeAction(_id); +} + +QByteArray AssignmentAction::serialize() { + return _data; +} + +void AssignmentAction::deserialize(QByteArray serializedArguments) { + _data = serializedArguments; +} + +bool AssignmentAction::updateArguments(QVariantMap arguments) { + qDebug() << "UNEXPECTED -- AssignmentAction::updateArguments called in assignment-client."; + return false; +} + +QVariantMap AssignmentAction::getArguments() { + qDebug() << "UNEXPECTED -- AssignmentAction::getArguments called in assignment-client."; + return QVariantMap(); +} + +glm::vec3 AssignmentAction::getPosition() { + qDebug() << "UNEXPECTED -- AssignmentAction::getPosition called in assignment-client."; + return glm::vec3(0.0f); +} + +void AssignmentAction::setPosition(glm::vec3 position) { + qDebug() << "UNEXPECTED -- AssignmentAction::setPosition called in assignment-client."; +} + +glm::quat AssignmentAction::getRotation() { + qDebug() << "UNEXPECTED -- AssignmentAction::getRotation called in assignment-client."; + return glm::quat(); +} + +void AssignmentAction::setRotation(glm::quat rotation) { + qDebug() << "UNEXPECTED -- AssignmentAction::setRotation called in assignment-client."; +} + +glm::vec3 AssignmentAction::getLinearVelocity() { + qDebug() << "UNEXPECTED -- AssignmentAction::getLinearVelocity called in assignment-client."; + return glm::vec3(0.0f); +} + +void AssignmentAction::setLinearVelocity(glm::vec3 linearVelocity) { + qDebug() << "UNEXPECTED -- AssignmentAction::setLinearVelocity called in assignment-client."; +} + +glm::vec3 AssignmentAction::getAngularVelocity() { + qDebug() << "UNEXPECTED -- AssignmentAction::getAngularVelocity called in assignment-client."; + return glm::vec3(0.0f); +} + +void AssignmentAction::setAngularVelocity(glm::vec3 angularVelocity) { + qDebug() << "UNEXPECTED -- AssignmentAction::setAngularVelocity called in assignment-client."; +} diff --git a/assignment-client/src/AssignmentAction.h b/assignment-client/src/AssignmentAction.h new file mode 100644 index 0000000000..cd72c1f277 --- /dev/null +++ b/assignment-client/src/AssignmentAction.h @@ -0,0 +1,57 @@ +// +// AssignmentAction.h +// assignment-client/src/ +// +// Created by Seth Alves 2015-6-19 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +// http://bulletphysics.org/Bullet/BulletFull/classbtActionInterface.html + +#ifndef hifi_AssignmentAction_h +#define hifi_AssignmentAction_h + +#include +#include + +#include "EntityActionInterface.h" + + +class AssignmentAction : public EntityActionInterface { +public: + AssignmentAction(EntityActionType type, QUuid id, EntityItemPointer ownerEntity); + virtual ~AssignmentAction(); + + const QUuid& getID() const { return _id; } + virtual EntityActionType getType() { return _type; } + virtual void removeFromSimulation(EntitySimulation* simulation) const; + virtual EntityItemWeakPointer getOwnerEntity() const { return _ownerEntity; } + virtual void setOwnerEntity(const EntityItemPointer ownerEntity) { _ownerEntity = ownerEntity; } + virtual bool updateArguments(QVariantMap arguments); + virtual QVariantMap getArguments(); + + virtual QByteArray serialize(); + virtual void deserialize(QByteArray serializedArguments); + +private: + QUuid _id; + EntityActionType _type; + QByteArray _data; + +protected: + virtual glm::vec3 getPosition(); + virtual void setPosition(glm::vec3 position); + virtual glm::quat getRotation(); + virtual void setRotation(glm::quat rotation); + virtual glm::vec3 getLinearVelocity(); + virtual void setLinearVelocity(glm::vec3 linearVelocity); + virtual glm::vec3 getAngularVelocity(); + virtual void setAngularVelocity(glm::vec3 angularVelocity); + + bool _active; + EntityItemWeakPointer _ownerEntity; +}; + +#endif // hifi_AssignmentAction_h diff --git a/assignment-client/src/AssignmentActionFactory.cpp b/assignment-client/src/AssignmentActionFactory.cpp new file mode 100644 index 0000000000..ba2692c611 --- /dev/null +++ b/assignment-client/src/AssignmentActionFactory.cpp @@ -0,0 +1,52 @@ +// +// AssignmentActionFactory.cpp +// assignment-client/src/ +// +// Created by Seth Alves on 2015-6-19 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "AssignmentActionFactory.h" + + +EntityActionPointer assignmentActionFactory(EntityActionType type, QUuid id, EntityItemPointer ownerEntity) { + return (EntityActionPointer) new AssignmentAction(type, id, ownerEntity); +} + + +EntityActionPointer AssignmentActionFactory::factory(EntitySimulation* simulation, + EntityActionType type, + QUuid id, + EntityItemPointer ownerEntity, + QVariantMap arguments) { + EntityActionPointer action = assignmentActionFactory(type, id, ownerEntity); + if (action) { + bool ok = action->updateArguments(arguments); + if (ok) { + return action; + } + } + return nullptr; +} + + +EntityActionPointer AssignmentActionFactory::factoryBA(EntitySimulation* simulation, + EntityItemPointer ownerEntity, + QByteArray data) { + QDataStream serializedActionDataStream(data); + EntityActionType type; + QUuid id; + + serializedActionDataStream >> type; + serializedActionDataStream >> id; + + EntityActionPointer action = assignmentActionFactory(type, id, ownerEntity); + + if (action) { + action->deserialize(data); + } + return action; +} diff --git a/assignment-client/src/AssignmentActionFactory.h b/assignment-client/src/AssignmentActionFactory.h new file mode 100644 index 0000000000..f71d22c0dd --- /dev/null +++ b/assignment-client/src/AssignmentActionFactory.h @@ -0,0 +1,32 @@ +// +// AssignmentActionFactory.cpp +// assignment-client/src/ +// +// Created by Seth Alves on 2015-6-19 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_AssignmentActionFactory_h +#define hifi_AssignmentActionFactory_h + +#include "EntityActionFactoryInterface.h" +#include "AssignmentAction.h" + +class AssignmentActionFactory : public EntityActionFactoryInterface { +public: + AssignmentActionFactory() : EntityActionFactoryInterface() { } + virtual ~AssignmentActionFactory() { } + virtual EntityActionPointer factory(EntitySimulation* simulation, + EntityActionType type, + QUuid id, + EntityItemPointer ownerEntity, + QVariantMap arguments); + virtual EntityActionPointer factoryBA(EntitySimulation* simulation, + EntityItemPointer ownerEntity, + QByteArray data); +}; + +#endif // hifi_AssignmentActionFactory_h diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index e125a44783..b503f59156 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -32,6 +32,7 @@ #include #include "AssignmentFactory.h" +#include "AssignmentActionFactory.h" #include "AssignmentClient.h" @@ -58,6 +59,9 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri auto avatarHashMap = DependencyManager::set(); auto entityScriptingInterface = DependencyManager::set(); + DependencyManager::registerInheritance(); + auto actionFactory = DependencyManager::set(); + // make up a uuid for this child so the parent can tell us apart. This id will be changed // when the domain server hands over an assignment. QUuid nodeUUID = QUuid::createUuid(); diff --git a/examples/debug-actions.js b/examples/debug-actions.js new file mode 100644 index 0000000000..132bd64362 --- /dev/null +++ b/examples/debug-actions.js @@ -0,0 +1,42 @@ +// +// +// + + +function printData(data) { + var str = ''; + for (var key in data) { + if (typeof data[key] == 'object') str += key + printData(data[key]) + ' '; + else str += key + ':' + data[key] + ' '; + } + return str; +}; + +function printActions(deltaTime) { + var printed = false; + var ids = Entities.findEntities(MyAvatar.position, 10); + for (var i = 0; i < ids.length; i++) { + var entityID = ids[i]; + var actionIDs = Entities.getActionIDs(entityID); + if (actionIDs.length > 0) { + var props = Entities.getEntityProperties(entityID); + var output = props.name + " "; + for (var actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) { + actionID = actionIDs[actionIndex]; + actionArguments = Entities.getActionArguments(entityID, actionID); + // output += actionArguments['type']; + output += "(" + printData(actionArguments) + ") "; + } + if (!printed) { + print("---------"); + printed = true; + } + print(output); + } + } +} + + +Script.setInterval(printActions, 5000); + +// Script.update.connect(printActions); diff --git a/examples/stick.js b/examples/stick.js index c9f6479d34..f581591957 100644 --- a/examples/stick.js +++ b/examples/stick.js @@ -16,41 +16,60 @@ var controllerID; var controllerActive; var stickID = null; var actionID = nullActionID; -// sometimes if this is run immediately the stick doesn't get created? use a timer. -Script.setTimeout(function() { - stickID = Entities.addEntity({ - type: "Model", - modelURL: "https://hifi-public.s3.amazonaws.com/eric/models/stick.fbx", - compoundShapeURL: "https://hifi-public.s3.amazonaws.com/eric/models/stick.obj", - dimensions: {x: .11, y: .11, z: 1.0}, - position: MyAvatar.getRightPalmPosition(), // initial position doesn't matter, as long as it's close - rotation: MyAvatar.orientation, - damping: .1, - collisionSoundURL: "http://public.highfidelity.io/sounds/Collisions-hitsandslaps/67LCollision07.wav", - restitution: 0.01, - collisionsWillMove: true - }); - actionID = Entities.addAction("hold", stickID, {relativePosition: {x: 0.0, y: 0.0, z: -0.9}, - hand: hand, - timeScale: 0.15}); -}, 3000); +var makingNewStick = false; + +function makeNewStick() { + if (makingNewStick) { + return; + } + makingNewStick = true; + cleanUp(); + // sometimes if this is run immediately the stick doesn't get created? use a timer. + Script.setTimeout(function() { + stickID = Entities.addEntity({ + type: "Model", + name: "stick", + modelURL: "https://hifi-public.s3.amazonaws.com/eric/models/stick.fbx", + compoundShapeURL: "https://hifi-public.s3.amazonaws.com/eric/models/stick.obj", + dimensions: {x: .11, y: .11, z: 1.0}, + position: MyAvatar.getRightPalmPosition(), // initial position doesn't matter, as long as it's close + rotation: MyAvatar.orientation, + damping: .1, + collisionSoundURL: "http://public.highfidelity.io/sounds/Collisions-hitsandslaps/67LCollision07.wav", + restitution: 0.01, + collisionsWillMove: true + }); + actionID = Entities.addAction("hold", stickID, {relativePosition: {x: 0.0, y: 0.0, z: -0.9}, + hand: hand, + timeScale: 0.15}); + if (actionID == nullActionID) { + cleanUp(); + } + makingNewStick = false; + }, 3000); +} function cleanUp() { - Entities.deleteEntity(stickID); + if (stickID) { + Entities.deleteEntity(stickID); + stickID = null; + } } function positionStick(stickOrientation) { var baseOffset = {x: 0.0, y: 0.0, z: -0.9}; var offset = Vec3.multiplyQbyV(stickOrientation, baseOffset); - Entities.updateAction(stickID, actionID, {relativePosition: offset, - relativeRotation: stickOrientation}); + if (!Entities.updateAction(stickID, actionID, {relativePosition: offset, relativeRotation: stickOrientation})) { + makeNewStick(); + } } function mouseMoveEvent(event) { if (!stickID || actionID == nullActionID) { + makeNewStick(); return; } var windowCenterX = Window.innerWidth / 2; @@ -89,6 +108,8 @@ function update(deltaTime){ } + Script.scriptEnding.connect(cleanUp); Controller.mouseMoveEvent.connect(mouseMoveEvent); Script.update.connect(update); +makeNewStick(); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index f6d9914afd..c1397beb28 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -91,6 +91,7 @@ #include #include #include +#include #include #include #include @@ -178,6 +179,7 @@ using namespace std; // Starfield information static unsigned STARFIELD_NUM_STARS = 50000; static unsigned STARFIELD_SEED = 1; +static uint8_t THROTTLED_IDLE_TIMER_DELAY = 10; const qint64 MAXIMUM_CACHE_SIZE = 10 * BYTES_PER_GIGABYTES; // 10GB @@ -1785,8 +1787,24 @@ void Application::checkFPS() { } void Application::idle() { - PerformanceTimer perfTimer("idle"); + static SimpleAverage interIdleDurations; + static uint64_t lastIdleEnd{ 0 }; + if (lastIdleEnd != 0) { + uint64_t now = usecTimestampNow(); + interIdleDurations.update(now - lastIdleEnd); + static uint64_t lastReportTime = now; + if ((now - lastReportTime) >= (USECS_PER_SECOND)) { + static QString LOGLINE("Average inter-idle time: %1 us for %2 samples"); + if (Menu::getInstance()->isOptionChecked(MenuOption::LogExtraTimings)) { + qCDebug(interfaceapp_timing) << LOGLINE.arg((int)interIdleDurations.getAverage()).arg(interIdleDurations.getCount()); + } + interIdleDurations.reset(); + lastReportTime = now; + } + } + + PerformanceTimer perfTimer("idle"); if (_aboutToQuit) { return; // bail early, nothing to do here. } @@ -1829,13 +1847,15 @@ void Application::idle() { _idleLoopStdev.reset(); } - // After finishing all of the above work, restart the idle timer, allowing 2ms to process events. - idleTimer->start(2); } - } + // After finishing all of the above work, ensure the idle timer is set to the proper interval, + // depending on whether we're throttling or not + idleTimer->start(_glWidget->isThrottleRendering() ? THROTTLED_IDLE_TIMER_DELAY : 0); + } // check for any requested background downloads. emit checkBackgroundDownloads(); + lastIdleEnd = usecTimestampNow(); } void Application::setFullscreen(bool fullscreen) { @@ -2208,6 +2228,7 @@ void Application::init() { // Make sure any new sounds are loaded as soon as know about them. connect(tree, &EntityTree::newCollisionSoundURL, DependencyManager::get().data(), &SoundCache::getSound); + connect(_myAvatar, &MyAvatar::newCollisionSoundURL, DependencyManager::get().data(), &SoundCache::getSound); } void Application::closeMirrorView() { diff --git a/interface/src/InterfaceActionFactory.cpp b/interface/src/InterfaceActionFactory.cpp index dfc22c5e7d..ccff5b4dc6 100644 --- a/interface/src/InterfaceActionFactory.cpp +++ b/interface/src/InterfaceActionFactory.cpp @@ -18,32 +18,53 @@ #include "InterfaceActionFactory.h" +EntityActionPointer interfaceActionFactory(EntityActionType type, QUuid id, EntityItemPointer ownerEntity) { + switch (type) { + case ACTION_TYPE_NONE: + return nullptr; + case ACTION_TYPE_OFFSET: + return (EntityActionPointer) new ObjectActionOffset(type, id, ownerEntity); + case ACTION_TYPE_SPRING: + return (EntityActionPointer) new ObjectActionSpring(type, id, ownerEntity); + case ACTION_TYPE_HOLD: + return (EntityActionPointer) new AvatarActionHold(type, id, ownerEntity); + } + + assert(false); + return nullptr; +} + + EntityActionPointer InterfaceActionFactory::factory(EntitySimulation* simulation, EntityActionType type, QUuid id, EntityItemPointer ownerEntity, QVariantMap arguments) { - EntityActionPointer action = nullptr; - switch (type) { - case ACTION_TYPE_NONE: - return nullptr; - case ACTION_TYPE_OFFSET: - action = (EntityActionPointer) new ObjectActionOffset(id, ownerEntity); - break; - case ACTION_TYPE_SPRING: - action = (EntityActionPointer) new ObjectActionSpring(id, ownerEntity); - break; - case ACTION_TYPE_HOLD: - action = (EntityActionPointer) new AvatarActionHold(id, ownerEntity); - break; + EntityActionPointer action = interfaceActionFactory(type, id, ownerEntity); + if (action) { + bool ok = action->updateArguments(arguments); + if (ok) { + return action; + } } + return nullptr; +} - bool ok = action->updateArguments(arguments); - if (ok) { - ownerEntity->addAction(simulation, action); - return action; + +EntityActionPointer InterfaceActionFactory::factoryBA(EntitySimulation* simulation, + EntityItemPointer ownerEntity, + QByteArray data) { + QDataStream serializedArgumentStream(data); + EntityActionType type; + QUuid id; + + serializedArgumentStream >> type; + serializedArgumentStream >> id; + + EntityActionPointer action = interfaceActionFactory(type, id, ownerEntity); + + if (action) { + action->deserialize(data); } - - action = nullptr; return action; } diff --git a/interface/src/InterfaceActionFactory.h b/interface/src/InterfaceActionFactory.h index 5848df4635..944e2fb753 100644 --- a/interface/src/InterfaceActionFactory.h +++ b/interface/src/InterfaceActionFactory.h @@ -23,6 +23,9 @@ public: QUuid id, EntityItemPointer ownerEntity, QVariantMap arguments); + virtual EntityActionPointer factoryBA(EntitySimulation* simulation, + EntityItemPointer ownerEntity, + QByteArray data); }; #endif // hifi_InterfaceActionFactory_h diff --git a/interface/src/InterfaceLogging.cpp b/interface/src/InterfaceLogging.cpp index 18bc4e58e8..0afcb30c27 100644 --- a/interface/src/InterfaceLogging.cpp +++ b/interface/src/InterfaceLogging.cpp @@ -12,3 +12,4 @@ #include "InterfaceLogging.h" Q_LOGGING_CATEGORY(interfaceapp, "hifi.interface") +Q_LOGGING_CATEGORY(interfaceapp_timing, "hifi.interface.timing") diff --git a/interface/src/InterfaceLogging.h b/interface/src/InterfaceLogging.h index d1d92aa93d..be2ee73fba 100644 --- a/interface/src/InterfaceLogging.h +++ b/interface/src/InterfaceLogging.h @@ -15,5 +15,6 @@ #include Q_DECLARE_LOGGING_CATEGORY(interfaceapp) +Q_DECLARE_LOGGING_CATEGORY(interfaceapp_timing) #endif // hifi_InterfaceLogging_h diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 20d88a59c0..c70d11b837 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -529,6 +529,7 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::FrameTimer); addActionToQMenuAndActionHash(timingMenu, MenuOption::RunTimingTests, 0, qApp, SLOT(runTests())); addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::PipelineWarnings); + addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::LogExtraTimings); addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::SuppressShortTimings); auto audioIO = DependencyManager::get(); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 0097437107..fae7092989 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -212,6 +212,7 @@ namespace MenuOption { const QString LodTools = "LOD Tools"; const QString Login = "Login"; const QString Log = "Log"; + const QString LogExtraTimings = "Log Extra Timing Details"; const QString LowVelocityFilter = "Low Velocity Filter"; const QString Mirror = "Mirror"; const QString MuteAudio = "Mute Microphone"; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 6ff8fb52df..748ef7c5a5 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -451,24 +451,24 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition, boo if (renderBounding && shouldRenderHead(renderArgs)) { _skeletonModel.renderBoundingCollisionShapes(*renderArgs->_batch, 0.7f); } + } - // If this is the avatar being looked at, render a little ball above their head - if (_isLookAtTarget && Menu::getInstance()->isOptionChecked(MenuOption::RenderFocusIndicator)) { - const float LOOK_AT_INDICATOR_RADIUS = 0.03f; - const float LOOK_AT_INDICATOR_OFFSET = 0.22f; - const glm::vec4 LOOK_AT_INDICATOR_COLOR = { 0.8f, 0.0f, 0.0f, 0.75f }; - glm::vec3 position; - if (_displayName.isEmpty() || _displayNameAlpha == 0.0f) { - position = glm::vec3(_position.x, getDisplayNamePosition().y, _position.z); - } else { - position = glm::vec3(_position.x, getDisplayNamePosition().y + LOOK_AT_INDICATOR_OFFSET, _position.z); - } - Transform transform; - transform.setTranslation(position); - batch.setModelTransform(transform); - DependencyManager::get()->renderSolidSphere(batch, LOOK_AT_INDICATOR_RADIUS - , 15, 15, LOOK_AT_INDICATOR_COLOR); + // If this is the avatar being looked at, render a little ball above their head + if (_isLookAtTarget && Menu::getInstance()->isOptionChecked(MenuOption::RenderFocusIndicator)) { + const float LOOK_AT_INDICATOR_RADIUS = 0.03f; + const float LOOK_AT_INDICATOR_OFFSET = 0.22f; + const glm::vec4 LOOK_AT_INDICATOR_COLOR = { 0.8f, 0.0f, 0.0f, 0.75f }; + glm::vec3 position; + if (_displayName.isEmpty() || _displayNameAlpha == 0.0f) { + position = glm::vec3(_position.x, getDisplayNamePosition().y, _position.z); + } else { + position = glm::vec3(_position.x, getDisplayNamePosition().y + LOOK_AT_INDICATOR_OFFSET, _position.z); } + Transform transform; + transform.setTranslation(position); + batch.setModelTransform(transform); + DependencyManager::get()->renderSolidSphere(batch, LOOK_AT_INDICATOR_RADIUS + , 15, 15, LOOK_AT_INDICATOR_COLOR); } // quick check before falling into the code below: @@ -694,9 +694,9 @@ Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& frustum, floa const float DESIRED_HIGHT_ON_SCREEN = 20; // In pixels (this is double on retinas) // Projected point are between -1.0f and 1.0f, hence 0.5f * windowSizeY - double pixelHeight = 0.5f * windowSizeY * glm::abs((p1.y / p1.w) - (p0.y / p0.w)); // + float pixelHeight = 0.5f * windowSizeY * glm::abs((p1.y / p1.w) - (p0.y / p0.w)); // // Handles pixel density (especially for macs retina displays) - double devicePixelRatio = qApp->getDevicePixelRatio() * qApp->getRenderResolutionScale(); // pixels / unit + float devicePixelRatio = (float)qApp->getDevicePixelRatio() * qApp->getRenderResolutionScale(); // pixels / unit // Compute correct scale to apply float scale = DESIRED_HIGHT_ON_SCREEN / (fontSize * pixelHeight) * devicePixelRatio; @@ -708,9 +708,10 @@ Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& frustum, floa glm::vec3 worldOffset = glm::vec3(screenOffset.x, screenOffset.y, 0.0f) / (float)pixelHeight; // Compute orientation - glm::vec3 eulerAngles = ::safeEulerAngles(frustum.getOrientation()); - eulerAngles.z = 0.0f; // Cancel roll - glm::quat orientation(eulerAngles); // back to quaternions + glm::vec3 dPosition = frustum.getPosition() - getPosition(); + // If x and z are 0, atan(x, z) is undefined, so default to 0 degrees + float yawRotation = dPosition.x == 0.0f && dPosition.z == 0.0f ? 0.0f : glm::atan(dPosition.x, dPosition.z); + glm::quat orientation = glm::quat(glm::vec3(0.0f, yawRotation, 0.0f)); // Set transform (The order IS important) result.setTranslation(textPosition); diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index 24ed9ba0e2..918521c0da 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -9,13 +9,21 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "QVariantGLM.h" #include "avatar/MyAvatar.h" #include "avatar/AvatarManager.h" #include "AvatarActionHold.h" -AvatarActionHold::AvatarActionHold(QUuid id, EntityItemPointer ownerEntity) : - ObjectActionSpring(id, ownerEntity) { +const uint16_t AvatarActionHold::holdVersion = 1; + +AvatarActionHold::AvatarActionHold(EntityActionType type, QUuid id, EntityItemPointer ownerEntity) : + ObjectActionSpring(type, id, ownerEntity), + _relativePosition(glm::vec3(0.0f)), + _relativeRotation(glm::quat()), + _hand("right"), + _mine(false) +{ #if WANT_DEBUG qDebug() << "AvatarActionHold::AvatarActionHold"; #endif @@ -28,6 +36,13 @@ AvatarActionHold::~AvatarActionHold() { } void AvatarActionHold::updateActionWorker(float deltaTimeStep) { + if (!_mine) { + // if a local script isn't updating this, then we are just getting spring-action data over the wire. + // let the super-class handle it. + ObjectActionSpring::updateActionWorker(deltaTimeStep); + return; + } + auto myAvatar = DependencyManager::get()->getMyAvatar(); if (!tryLockForRead()) { @@ -51,6 +66,22 @@ void AvatarActionHold::updateActionWorker(float deltaTimeStep) { if (!tryLockForWrite()) { return; } + + // check for NaNs + if (position.x != position.x || + position.y != position.y || + position.z != position.z) { + qDebug() << "AvatarActionHold::updateActionWorker -- target position includes NaN"; + return; + } + if (rotation.x != rotation.x || + rotation.y != rotation.y || + rotation.z != rotation.z || + rotation.w != rotation.w) { + qDebug() << "AvatarActionHold::updateActionWorker -- target rotation includes NaN"; + return; + } + _positionalTarget = position; _rotationalTarget = rotation; unlock(); @@ -76,22 +107,22 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) { lockForWrite(); if (rPOk) { _relativePosition = relativePosition; - } else if (!_parametersSet) { + } else { _relativePosition = glm::vec3(0.0f, 0.0f, 1.0f); } if (rROk) { _relativeRotation = relativeRotation; - } else if (!_parametersSet) { + } else { _relativeRotation = glm::quat(0.0f, 0.0f, 0.0f, 1.0f); } if (tSOk) { _linearTimeScale = timeScale; _angularTimeScale = timeScale; - } else if (!_parametersSet) { - _linearTimeScale = 0.2; - _angularTimeScale = 0.2; + } else { + _linearTimeScale = 0.2f; + _angularTimeScale = 0.2f; } if (hOk) { @@ -104,14 +135,39 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) { qDebug() << "hold action -- invalid hand argument:" << hand; _hand = "right"; } - } else if (!_parametersSet) { + } else { _hand = "right"; } - _parametersSet = true; + _mine = true; _positionalTargetSet = true; _rotationalTargetSet = true; _active = true; unlock(); return true; } + + +QVariantMap AvatarActionHold::getArguments() { + QVariantMap arguments; + lockForRead(); + if (_mine) { + arguments["relativePosition"] = glmToQMap(_relativePosition); + arguments["relativeRotation"] = glmToQMap(_relativeRotation); + arguments["timeScale"] = _linearTimeScale; + arguments["hand"] = _hand; + } else { + unlock(); + return ObjectActionSpring::getArguments(); + } + unlock(); + return arguments; +} + + +void AvatarActionHold::deserialize(QByteArray serializedArguments) { + if (_mine) { + return; + } + ObjectActionSpring::deserialize(serializedArguments); +} diff --git a/interface/src/avatar/AvatarActionHold.h b/interface/src/avatar/AvatarActionHold.h index 705c751029..a47f1ce05d 100644 --- a/interface/src/avatar/AvatarActionHold.h +++ b/interface/src/avatar/AvatarActionHold.h @@ -19,17 +19,25 @@ class AvatarActionHold : public ObjectActionSpring { public: - AvatarActionHold(QUuid id, EntityItemPointer ownerEntity); + AvatarActionHold(EntityActionType type, QUuid id, EntityItemPointer ownerEntity); virtual ~AvatarActionHold(); + virtual EntityActionType getType() { return ACTION_TYPE_HOLD; } + virtual bool updateArguments(QVariantMap arguments); + virtual QVariantMap getArguments(); + virtual void updateActionWorker(float deltaTimeStep); + virtual void deserialize(QByteArray serializedArguments); + private: + static const uint16_t holdVersion; + glm::vec3 _relativePosition; glm::quat _relativeRotation; QString _hand; - bool _parametersSet = false; + bool _mine = false; }; #endif // hifi_AvatarActionHold_h diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 25f768d037..dbd46cbfbd 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -257,6 +257,37 @@ void AvatarManager::handleOutgoingChanges(VectorOfMotionStates& motionStates) { void AvatarManager::handleCollisionEvents(CollisionEvents& collisionEvents) { // TODO: expose avatar collision events to JS + for (Collision collision : collisionEvents) { + // TODO: Current physics uses null idA or idB for non-entities. The plan is to handle MOTIONSTATE_TYPE_AVATAR, + // and then MOTIONSTATE_TYPE_MYAVATAR. As it is, this code only covers the case of my avatar (in which case one + // if the ids will be null), and the behavior for other avatars is not specified. This has to be fleshed + // out as soon as we use the new motionstates. + if (collision.idA.isNull() || collision.idB.isNull()) { + MyAvatar* myAvatar = getMyAvatar(); + const QString& collisionSoundURL = myAvatar->getCollisionSoundURL(); + if (!collisionSoundURL.isEmpty()) { + const float velocityChange = glm::length(collision.velocityChange); + const float MIN_AVATAR_COLLISION_ACCELERATION = 0.01; + const bool isSound = (collision.type == CONTACT_EVENT_TYPE_START) && (velocityChange > MIN_AVATAR_COLLISION_ACCELERATION); + + if (!isSound) { + // TODO: When the new motion states are used, we'll probably break from the whole loop as soon as we hit our own avatar + // (regardless of isSound), because other users should inject for their own avatars. + continue; + } + // Your avatar sound is personal to you, so let's say the "mass" part of the kinetic energy is already accounted for. + const float energy = velocityChange * velocityChange; + const float COLLISION_ENERGY_AT_FULL_VOLUME = 0.5f; + const float energyFactorOfFull = fmin(1.0f, energy / COLLISION_ENERGY_AT_FULL_VOLUME); + + // For general entity collisionSoundURL, playSound supports changing the pitch for the sound based on the size of the object, + // but most avatars are roughly the same size, so let's not be so fancy yet. + const float AVATAR_STRETCH_FACTOR = 1.0f; + + AudioInjector::playSound(collisionSoundURL, energyFactorOfFull, AVATAR_STRETCH_FACTOR, myAvatar->getPosition()); + } + } + } } void AvatarManager::updateAvatarPhysicsShape(const QUuid& id) { diff --git a/interface/src/avatar/AvatarMotionState.cpp b/interface/src/avatar/AvatarMotionState.cpp index 06da384a94..b106ee6398 100644 --- a/interface/src/avatar/AvatarMotionState.cpp +++ b/interface/src/avatar/AvatarMotionState.cpp @@ -148,10 +148,6 @@ QUuid AvatarMotionState::getSimulatorID() const { return _avatar->getSessionUUID(); } -// virtual -void AvatarMotionState::bump() { -} - // virtual int16_t AvatarMotionState::computeCollisionGroup() { return COLLISION_GROUP_OTHER_AVATAR; diff --git a/interface/src/avatar/AvatarMotionState.h b/interface/src/avatar/AvatarMotionState.h index 79a4d23179..ac813e764c 100644 --- a/interface/src/avatar/AvatarMotionState.h +++ b/interface/src/avatar/AvatarMotionState.h @@ -55,7 +55,6 @@ public: virtual const QUuid& getObjectID() const; virtual QUuid getSimulatorID() const; - virtual void bump(); void setBoundingBox(const glm::vec3& corner, const glm::vec3& diagonal); diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index 02d16a1ca4..ed7d84dd24 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -9,9 +9,11 @@ // #include +#include +#include #include -#include +#include #include #include "Application.h" @@ -293,10 +295,8 @@ void Head::relaxLean(float deltaTime) { } void Head::render(RenderArgs* renderArgs, float alpha, ViewFrustum* renderFrustum, bool postLighting) { - if (postLighting) { - if (_renderLookatVectors) { + if (_renderLookatVectors) { renderLookatVectors(renderArgs, _leftEyePosition, _rightEyePosition, getCorrectedLookAtPosition()); - } } } @@ -380,17 +380,19 @@ void Head::addLeanDeltas(float sideways, float forward) { } void Head::renderLookatVectors(RenderArgs* renderArgs, glm::vec3 leftEyePosition, glm::vec3 rightEyePosition, glm::vec3 lookatPosition) { + auto& batch = *renderArgs->_batch; + auto transform = Transform{}; + batch.setModelTransform(transform); + batch._glLineWidth(2.0f); + + auto deferredLighting = DependencyManager::get(); + deferredLighting->bindSimpleProgram(batch); + auto geometryCache = DependencyManager::get(); - DependencyManager::get()->begin(renderArgs); - - glLineWidth(2.0); - glm::vec4 startColor(0.2f, 0.2f, 0.2f, 1.0f); glm::vec4 endColor(1.0f, 1.0f, 1.0f, 0.0f); - geometryCache->renderLine(leftEyePosition, lookatPosition, startColor, endColor, _leftEyeLookAtID); - geometryCache->renderLine(rightEyePosition, lookatPosition, startColor, endColor, _rightEyeLookAtID); - - DependencyManager::get()->end(renderArgs); + geometryCache->renderLine(batch, leftEyePosition, lookatPosition, startColor, endColor, _leftEyeLookAtID); + geometryCache->renderLine(batch, rightEyePosition, lookatPosition, startColor, endColor, _rightEyeLookAtID); } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index c6a4ae39aa..594a8c198e 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -70,6 +70,7 @@ float DEFAULT_SCRIPTED_MOTOR_TIMESCALE = 1.0e6f; const int SCRIPTED_MOTOR_CAMERA_FRAME = 0; const int SCRIPTED_MOTOR_AVATAR_FRAME = 1; const int SCRIPTED_MOTOR_WORLD_FRAME = 2; +const QString& DEFAULT_AVATAR_COLLISION_SOUND_URL = "https://s3.amazonaws.com/hifi-public/sounds/Collisions-hitsandslaps/airhockey_hit1.wav"; const float MyAvatar::ZOOM_MIN = 0.5f; const float MyAvatar::ZOOM_MAX = 25.0f; @@ -90,6 +91,7 @@ MyAvatar::MyAvatar() : _scriptedMotorTimescale(DEFAULT_SCRIPTED_MOTOR_TIMESCALE), _scriptedMotorFrame(SCRIPTED_MOTOR_CAMERA_FRAME), _motionBehaviors(AVATAR_MOTION_DEFAULTS), + _collisionSoundURL(""), _characterController(this), _lookAtTargetAvatar(), _shouldRender(true), @@ -664,6 +666,7 @@ void MyAvatar::saveData() { settings.endArray(); settings.setValue("displayName", _displayName); + settings.setValue("collisionSoundURL", _collisionSoundURL); settings.endGroup(); } @@ -789,6 +792,7 @@ void MyAvatar::loadData() { settings.endArray(); setDisplayName(settings.value("displayName").toString()); + setCollisionSoundURL(settings.value("collisionSoundURL", DEFAULT_AVATAR_COLLISION_SOUND_URL).toString()); settings.endGroup(); } @@ -1183,6 +1187,13 @@ void MyAvatar::clearScriptableSettings() { _scriptedMotorTimescale = DEFAULT_SCRIPTED_MOTOR_TIMESCALE; } +void MyAvatar::setCollisionSoundURL(const QString& url) { + _collisionSoundURL = url; + if (!url.isEmpty() && (url != _collisionSoundURL)) { + emit newCollisionSoundURL(QUrl(url)); + } +} + void MyAvatar::attach(const QString& modelURL, const QString& jointName, const glm::vec3& translation, const glm::quat& rotation, float scale, bool allowDuplicates, bool useSaved) { if (QThread::currentThread() != thread()) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index ce5d2148a8..8410665ed5 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -25,6 +25,7 @@ class MyAvatar : public Avatar { Q_PROPERTY(glm::vec3 motorVelocity READ getScriptedMotorVelocity WRITE setScriptedMotorVelocity) Q_PROPERTY(float motorTimescale READ getScriptedMotorTimescale WRITE setScriptedMotorTimescale) Q_PROPERTY(QString motorReferenceFrame READ getScriptedMotorFrame WRITE setScriptedMotorFrame) + Q_PROPERTY(QString collisionSoundURL READ getCollisionSoundURL WRITE setCollisionSoundURL) //TODO: make gravity feature work Q_PROPERTY(glm::vec3 gravity READ getGravity WRITE setGravity) public: @@ -150,6 +151,9 @@ public: void setScriptedMotorTimescale(float timescale); void setScriptedMotorFrame(QString frame); + const QString& getCollisionSoundURL() {return _collisionSoundURL; } + void setCollisionSoundURL(const QString& url); + void clearScriptableSettings(); virtual void attach(const QString& modelURL, const QString& jointName = QString(), @@ -204,6 +208,7 @@ public slots: signals: void transformChanged(); + void newCollisionSoundURL(const QUrl& url); private: @@ -233,6 +238,7 @@ private: float _scriptedMotorTimescale; // timescale for avatar to achieve its target velocity int _scriptedMotorFrame; quint32 _motionBehaviors; + QString _collisionSoundURL; DynamicCharacterController _characterController; diff --git a/interface/src/devices/OculusManager.cpp b/interface/src/devices/OculusManager.cpp index 9d7146cbe7..d88e9e8011 100644 --- a/interface/src/devices/OculusManager.cpp +++ b/interface/src/devices/OculusManager.cpp @@ -249,7 +249,6 @@ static GlWindow* _outputWindow{ nullptr }; static bool _isConnected = false; static ovrHmd _ovrHmd; static ovrFovPort _eyeFov[ovrEye_Count]; -static ovrVector3f _eyeOffset[ovrEye_Count]; static ovrEyeRenderDesc _eyeRenderDesc[ovrEye_Count]; static ovrSizei _renderTargetSize; static glm::mat4 _eyeProjection[ovrEye_Count]; @@ -281,6 +280,7 @@ static ovrPosef _eyeRenderPoses[ovrEye_Count]; static ovrRecti _eyeViewports[ovrEye_Count]; static ovrVector3f _eyeOffsets[ovrEye_Count]; + glm::vec3 OculusManager::getLeftEyePosition() { return _eyePositions[ovrEye_Left]; } glm::vec3 OculusManager::getRightEyePosition() { return _eyePositions[ovrEye_Right]; } @@ -910,4 +910,4 @@ mat4 OculusManager::getEyePose(int eye) { mat4 OculusManager::getHeadPose() { ovrTrackingState ts = ovrHmd_GetTrackingState(_ovrHmd, ovr_GetTimeInSeconds()); return toGlm(ts.HeadPose.ThePose); -} \ No newline at end of file +} diff --git a/interface/src/devices/TV3DManager.cpp b/interface/src/devices/TV3DManager.cpp index 41e549a861..e945b5e79a 100644 --- a/interface/src/devices/TV3DManager.cpp +++ b/interface/src/devices/TV3DManager.cpp @@ -109,7 +109,6 @@ void TV3DManager::display(RenderArgs* renderArgs, Camera& whichCamera) { glScissor(portalX, portalY, portalW, portalH); glm::mat4 projection = glm::frustum(eye.left, eye.right, eye.bottom, eye.top, nearZ, farZ); - float fov = atan(1.0f / projection[1][1]); projection = glm::translate(projection, vec3(eye.modelTranslation, 0, 0)); eyeCamera.setProjection(projection); diff --git a/interface/src/ui/ApplicationCompositor.cpp b/interface/src/ui/ApplicationCompositor.cpp index 6cdf690d99..4c3a2a44c1 100644 --- a/interface/src/ui/ApplicationCompositor.cpp +++ b/interface/src/ui/ApplicationCompositor.cpp @@ -234,7 +234,6 @@ void ApplicationCompositor::displayOverlayTexture(RenderArgs* renderArgs) { model.setScale(vec3(mouseSize, 1.0f)); batch.setModelTransform(model); bindCursorTexture(batch); - vec4 reticleColor = { RETICLE_COLOR[0], RETICLE_COLOR[1], RETICLE_COLOR[2], 1.0f }; geometryCache->renderUnitQuad(batch, vec4(1)); renderArgs->_context->render(batch); } @@ -386,8 +385,8 @@ QPoint ApplicationCompositor::getPalmClickLocation(const PalmData *palm) const { ndcSpacePos = glm::vec3(clipSpacePos) / clipSpacePos.w; } - rv.setX(((ndcSpacePos.x + 1.0) / 2.0) * canvasSize.x); - rv.setY((1.0 - ((ndcSpacePos.y + 1.0) / 2.0)) * canvasSize.y); + rv.setX(((ndcSpacePos.x + 1.0f) / 2.0f) * canvasSize.x); + rv.setY((1.0f - ((ndcSpacePos.y + 1.0f) / 2.0f)) * canvasSize.y); } return rv; } @@ -505,7 +504,7 @@ void ApplicationCompositor::renderControllerPointers(gpu::Batch& batch) { // Get the angles, scaled between (-0.5,0.5) float xAngle = (atan2(direction.z, direction.x) + M_PI_2); - float yAngle = 0.5f - ((atan2(direction.z, direction.y) + M_PI_2)); + float yAngle = 0.5f - ((atan2f(direction.z, direction.y) + (float)M_PI_2)); // Get the pixel range over which the xAngle and yAngle are scaled float cursorRange = canvasSize.x * SixenseManager::getInstance().getCursorPixelRangeMult(); @@ -734,7 +733,7 @@ glm::vec2 ApplicationCompositor::screenToSpherical(const glm::vec2& screenPos) { glm::vec2 ApplicationCompositor::sphericalToScreen(const glm::vec2& sphericalPos) { glm::vec2 result = sphericalPos; - result.x *= -1.0; + result.x *= -1.0f; result /= MOUSE_RANGE; result += 0.5f; result *= qApp->getCanvasSize(); @@ -743,7 +742,7 @@ glm::vec2 ApplicationCompositor::sphericalToScreen(const glm::vec2& sphericalPos glm::vec2 ApplicationCompositor::sphericalToOverlay(const glm::vec2& sphericalPos) const { glm::vec2 result = sphericalPos; - result.x *= -1.0; + result.x *= -1.0f; result /= _textureFov; result.x /= _textureAspectRatio; result += 0.5f; diff --git a/interface/src/ui/AvatarInputs.cpp b/interface/src/ui/AvatarInputs.cpp index 11f744aaca..019deb9690 100644 --- a/interface/src/ui/AvatarInputs.cpp +++ b/interface/src/ui/AvatarInputs.cpp @@ -67,7 +67,6 @@ void AvatarInputs::update() { AI_UPDATE(cameraMuted, Menu::getInstance()->isOptionChecked(MenuOption::MuteFaceTracking)); auto audioIO = DependencyManager::get(); - const float CLIPPING_INDICATOR_TIME = 1.0f; const float AUDIO_METER_AVERAGING = 0.5; const float LOG2 = log(2.0f); const float METER_LOUDNESS_SCALE = 2.8f / 5.0f; @@ -85,7 +84,7 @@ void AvatarInputs::update() { } else { audioLevel = (log2loudness - (LOG2_LOUDNESS_FLOOR - 1.0f)) * METER_LOUDNESS_SCALE; } - if (audioLevel > 1.0) { + if (audioLevel > 1.0f) { audioLevel = 1.0; } AI_UPDATE_FLOAT(audioLevel, audioLevel, 0.01); diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 93b3ef8d07..35a049db3c 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -127,6 +127,8 @@ void PreferencesDialog::loadPreferences() { _displayNameString = myAvatar->getDisplayName(); ui.displayNameEdit->setText(_displayNameString); + ui.collisionSoundURLEdit->setText(myAvatar->getCollisionSoundURL()); + ui.sendDataCheckBox->setChecked(!menuInstance->isOptionChecked(MenuOption::DisableActivityLogger)); ui.snapshotLocationEdit->setText(Snapshot::snapshotsLocation.get()); @@ -204,6 +206,8 @@ void PreferencesDialog::savePreferences() { myAvatar->sendIdentityPacket(); } + myAvatar->setCollisionSoundURL(ui.collisionSoundURLEdit->text()); + if (!Menu::getInstance()->isOptionChecked(MenuOption::DisableActivityLogger) != ui.sendDataCheckBox->isChecked()) { Menu::getInstance()->triggerOption(MenuOption::DisableActivityLogger); diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index e588991f0b..49e93739a6 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -136,7 +136,6 @@ void Stats::updateStats() { unsigned long totalPingOctree = 0; int octreeServerCount = 0; int pingOctreeMax = 0; - int pingVoxel; nodeList->eachNode([&](const SharedNodePointer& node) { // TODO: this should also support entities if (node->getType() == NodeType::EntityServer) { @@ -147,19 +146,6 @@ void Stats::updateStats() { } } }); - - if (octreeServerCount) { - pingVoxel = totalPingOctree / octreeServerCount; - } - - //STAT_UPDATE(entitiesPing, pingVoxel); - //if (_expanded) { - // QString voxelMaxPing; - // if (pingVoxel >= 0) { // Average is only meaningful if pingVoxel is valid. - // voxelMaxPing = QString("Voxel max ping: %1").arg(pingOctreeMax); - // } else { - // voxelMaxPing = QString("Voxel max ping: --"); - // } } else { // -2 causes the QML to hide the ping column STAT_UPDATE(audioPing, -2); @@ -280,15 +266,15 @@ void Stats::updateStats() { } // Server Octree Elements - STAT_UPDATE(serverElements, totalNodes); - STAT_UPDATE(localElements, OctreeElement::getNodeCount()); + STAT_UPDATE(serverElements, (int)totalNodes); + STAT_UPDATE(localElements, (int)OctreeElement::getNodeCount()); if (_expanded) { - STAT_UPDATE(serverInternal, totalInternal); - STAT_UPDATE(serverLeaves, totalLeaves); + STAT_UPDATE(serverInternal, (int)totalInternal); + STAT_UPDATE(serverLeaves, (int)totalLeaves); // Local Voxels - STAT_UPDATE(localInternal, OctreeElement::getInternalNodeCount()); - STAT_UPDATE(localLeaves, OctreeElement::getLeafNodeCount()); + STAT_UPDATE(localInternal, (int)OctreeElement::getInternalNodeCount()); + STAT_UPDATE(localLeaves, (int)OctreeElement::getLeafNodeCount()); // LOD Details STAT_UPDATE(lodStatus, "You can see " + DependencyManager::get()->getLODFeedbackText()); } diff --git a/interface/ui/preferencesDialog.ui b/interface/ui/preferencesDialog.ui index e74b89075e..78f9f5bf09 100644 --- a/interface/ui/preferencesDialog.ui +++ b/interface/ui/preferencesDialog.ui @@ -189,6 +189,66 @@ + + + + + 0 + + + 7 + + + 7 + + + + + + Arial + + + + <html><head/><body><p>Avatar collision sound URL <span style=" color:#909090;">(optional)</span></p></body></html> + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + collisionSoundURLEdit + + + + + + + 4 + + + 5 + + + 4 + + + + + + Arial + + + + Qt::LeftToRight + + + Enter the URL of a sound to play when you bump into something + + + + + + + diff --git a/libraries/auto-updater/src/AutoUpdater.cpp b/libraries/auto-updater/src/AutoUpdater.cpp index 8c6aa5605d..ea4e43ff41 100644 --- a/libraries/auto-updater/src/AutoUpdater.cpp +++ b/libraries/auto-updater/src/AutoUpdater.cpp @@ -104,7 +104,9 @@ void AutoUpdater::parseLatestVersionData() { } void AutoUpdater::checkVersionAndNotify() { - if (QCoreApplication::applicationVersion() == "dev" || _builds.empty()) { + if (QCoreApplication::applicationVersion() == "dev" || + QCoreApplication::applicationVersion().contains("PR") || + _builds.empty()) { // No version checking is required in dev builds or when no build // data was found for the platform return; diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index 245cf00a3d..7603187e94 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -56,7 +56,8 @@ void RenderableTextEntityItem::render(RenderArgs* args) { batch.setModelTransform(transformToTopLeft); } - DependencyManager::get()->renderQuad(batch, minCorner, maxCorner, backgroundColor); + DependencyManager::get()->bindSimpleProgram(batch, false, false); + DependencyManager::get()->renderQuad(batch, minCorner, maxCorner, backgroundColor); float scale = _lineHeight / _textRenderer->getFontSize(); transformToTopLeft.setScale(scale); // Scale to have the correct line height diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 94f88b8390..cf602971c2 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -172,22 +172,20 @@ void RenderableWebEntityItem::render(RenderArgs* args) { Glower glow(0.0f); PerformanceTimer perfTimer("RenderableWebEntityItem::render"); Q_ASSERT(getType() == EntityTypes::Web); - static const glm::vec2 texMin(0.0f); - static const glm::vec2 texMax(1.0f); - glm::vec2 topLeft(-0.5f -0.5f); - glm::vec2 bottomRight(0.5f, 0.5f); + static const glm::vec2 texMin(0.0f), texMax(1.0f), topLeft(-0.5f), bottomRight(0.5f); Q_ASSERT(args->_batch); gpu::Batch& batch = *args->_batch; batch.setModelTransform(getTransformToCenter()); + bool textured = false, culled = false, emissive = false; if (_texture) { + batch._glActiveTexture(GL_TEXTURE0); batch._glBindTexture(GL_TEXTURE_2D, _texture); - batch._glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - batch._glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + textured = emissive = true; } - DependencyManager::get()->bindSimpleProgram(batch, true); + + DependencyManager::get()->bindSimpleProgram(batch, textured, culled, emissive); DependencyManager::get()->renderQuad(batch, topLeft, bottomRight, texMin, texMax, glm::vec4(1.0f)); - DependencyManager::get()->releaseSimpleProgram(batch); } void RenderableWebEntityItem::setSourceUrl(const QString& value) { diff --git a/libraries/entities/src/EntityActionFactoryInterface.h b/libraries/entities/src/EntityActionFactoryInterface.h index 9820313aae..5269405d55 100644 --- a/libraries/entities/src/EntityActionFactoryInterface.h +++ b/libraries/entities/src/EntityActionFactoryInterface.h @@ -28,6 +28,9 @@ class EntityActionFactoryInterface : public QObject, public Dependency { QUuid id, EntityItemPointer ownerEntity, QVariantMap arguments) { assert(false); return nullptr; } + virtual EntityActionPointer factoryBA(EntitySimulation* simulation, + EntityItemPointer ownerEntity, + QByteArray data) { assert(false); return nullptr; } }; #endif // hifi_EntityActionFactoryInterface_h diff --git a/libraries/entities/src/EntityActionInterface.cpp b/libraries/entities/src/EntityActionInterface.cpp index 54abdc4454..2b723d4e15 100644 --- a/libraries/entities/src/EntityActionInterface.cpp +++ b/libraries/entities/src/EntityActionInterface.cpp @@ -9,6 +9,78 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // + + +/* + + + + + +-----------------------+ +-------------------+ +------------------------------+ + | | | | | | + | EntityActionInterface | | btActionInterface | | EntityActionFactoryInterface | + | (entities) | | (bullet) | | (entities) | + +-----------------------+ +-------------------+ +------------------------------+ + | | | | | + +----+ +--+ +----------+ | | + | | | | | + +-------------------+ +--------------+ +------------------------+ +-------------------------+ + | | | | | | | | + | AssignmentAction | | ObjectAction | | InterfaceActionFactory | | AssignmentActionFactory | + |(assignment client)| | (physics) | | (interface) | | (assignment client) | + +-------------------+ +--------------+ +------------------------+ +-------------------------+ + | + | + | + +--------------------+ + | | + | ObjectActionSpring | + | (physics) | + +--------------------+ + + + + +An action is a callback which is registered with bullet. An action is called-back every physics +simulation step and can do whatever it wants with the various datastructures it has available. An +action, for example, can pull an EntityItem toward a point as if that EntityItem were connected to that +point by a spring. + +In this system, an action is a property of an EntityItem (rather, an EntityItem has a property which +encodes a list of actions). Each action has a type and some arguments. Actions can be created by a +script or when receiving information via an EntityTree data-stream (either over the network or from an +svo file). + +In the interface, if an EntityItem has actions, this EntityItem will have pointers to ObjectAction +subclass (like ObjectActionSpring) instantiations. Code in the entities library affects an action-object +via the EntityActionInterface (which knows nothing about bullet). When the ObjectAction subclass +instance is created, it is registered as an action with bullet. Bullet will call into code in this +instance with the btActionInterface every physics-simulation step. + +Because the action can exist next to the interface's EntityTree or the entity-server's EntityTree, +parallel versions of the factories and actions are needed. + +In an entity-server, any type of action is instantiated as an AssignmentAction. This action isn't called +by bullet (which isn't part of an assignment-client). It does nothing but remember its type and its +arguments. This may change as we try to make the entity-server's simple physics simulation better, but +right now the AssignmentAction class is a place-holder. + +The action-objects are instantiated by singleton (dependecy) subclasses of EntityActionFactoryInterface. +In the interface, the subclass is an InterfaceActionFactory and it will produce things like +ObjectActionSpring. In an entity-server the subclass is an AssignmentActionFactory and it always +produces AssignmentActions. + +Depending on the action's type, it will have various arguments. When a script changes an argument of an +action, the argument-holding member-variables of ObjectActionSpring (in this example) are updated and +also serialized into _actionData in the EntityItem. Each subclass of ObjectAction knows how to serialize +and deserialize its own arguments. _actionData is what gets sent over the wire or saved in an svo file. +When a packet-reader receives data for _actionData, it will save it in the EntityItem; this causes the +deserializer in the ObjectAction subclass to be called with the new data, thereby updating its argument +variables. These argument variables are used by the code which is run when bullet does a callback. + + + */ + #include "EntityItem.h" #include "EntityActionInterface.h" @@ -174,3 +246,16 @@ QString EntityActionInterface::extractStringArgument(QString objectName, QVarian QString v = vV.toString(); return v; } + +QDataStream& operator<<(QDataStream& stream, const EntityActionType& entityActionType) +{ + return stream << (quint16)entityActionType; +} + +QDataStream& operator>>(QDataStream& stream, EntityActionType& entityActionType) +{ + quint16 actionTypeAsInt; + stream >> actionTypeAsInt; + entityActionType = (EntityActionType)actionTypeAsInt; + return stream; +} diff --git a/libraries/entities/src/EntityActionInterface.h b/libraries/entities/src/EntityActionInterface.h index bd7a3217d5..5693e1fb6f 100644 --- a/libraries/entities/src/EntityActionInterface.h +++ b/libraries/entities/src/EntityActionInterface.h @@ -20,10 +20,10 @@ class EntitySimulation; enum EntityActionType { // keep these synchronized with actionTypeFromString and actionTypeToString - ACTION_TYPE_NONE, - ACTION_TYPE_OFFSET, - ACTION_TYPE_SPRING, - ACTION_TYPE_HOLD + ACTION_TYPE_NONE = 0, + ACTION_TYPE_OFFSET = 1000, + ACTION_TYPE_SPRING = 2000, + ACTION_TYPE_HOLD = 3000 }; @@ -32,10 +32,16 @@ public: EntityActionInterface() { } virtual ~EntityActionInterface() { } virtual const QUuid& getID() const = 0; + virtual EntityActionType getType() { assert(false); return ACTION_TYPE_NONE; } + virtual void removeFromSimulation(EntitySimulation* simulation) const = 0; - virtual const EntityItemPointer& getOwnerEntity() const = 0; + virtual EntityItemWeakPointer getOwnerEntity() const = 0; virtual void setOwnerEntity(const EntityItemPointer ownerEntity) = 0; virtual bool updateArguments(QVariantMap arguments) = 0; + virtual QVariantMap getArguments() = 0; + + virtual QByteArray serialize() = 0; + virtual void deserialize(QByteArray serializedArguments) = 0; static EntityActionType actionTypeFromString(QString actionTypeString); static QString actionTypeToString(EntityActionType actionType); @@ -67,4 +73,7 @@ protected: typedef std::shared_ptr EntityActionPointer; +QDataStream& operator<<(QDataStream& stream, const EntityActionType& entityActionType); +QDataStream& operator>>(QDataStream& stream, EntityActionType& entityActionType); + #endif // hifi_EntityActionInterface_h diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 74b7a36504..6559289e33 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "EntityItem.h" + #include #include @@ -22,12 +24,17 @@ #include #include "EntityScriptingInterface.h" -#include "EntityItem.h" #include "EntitiesLogging.h" #include "EntityTree.h" #include "EntitySimulation.h" +#include "EntityActionFactoryInterface.h" + + +const quint64 DEFAULT_SIMULATOR_CHANGE_LOCKOUT_PERIOD = (quint64)(0.2f * USECS_PER_SECOND); +const quint64 MAX_SIMULATOR_CHANGE_LOCKOUT_PERIOD = 2 * USECS_PER_SECOND; bool EntityItem::_sendPhysicsUpdates = true; +int EntityItem::_maxActionsDataSize = 800; EntityItem::EntityItem(const EntityItemID& entityItemID) : _type(EntityTypes::Unknown), @@ -64,8 +71,7 @@ EntityItem::EntityItem(const EntityItemID& entityItemID) : _collisionsWillMove(ENTITY_ITEM_DEFAULT_COLLISIONS_WILL_MOVE), _locked(ENTITY_ITEM_DEFAULT_LOCKED), _userData(ENTITY_ITEM_DEFAULT_USER_DATA), - _simulatorID(ENTITY_ITEM_DEFAULT_SIMULATOR_ID), - _simulatorIDChangedTime(0), + _simulationOwner(), _marketplaceID(ENTITY_ITEM_DEFAULT_MARKETPLACE_ID), _name(ENTITY_ITEM_DEFAULT_NAME), _href(""), @@ -86,6 +92,13 @@ EntityItem::EntityItem(const EntityItemID& entityItemID, const EntityItemPropert } EntityItem::~EntityItem() { + // clear out any left-over actions + EntityTree* entityTree = _element ? _element->getTree() : nullptr; + EntitySimulation* simulation = entityTree ? entityTree->getSimulation() : nullptr; + if (simulation) { + clearActions(simulation); + } + // these pointers MUST be correct at delete, else we probably have a dangling backpointer // to this EntityItem in the corresponding data structure. assert(!_simulated); @@ -96,13 +109,16 @@ EntityItem::~EntityItem() { EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties; + requestedProperties += PROP_SIMULATION_OWNER; requestedProperties += PROP_POSITION; - requestedProperties += PROP_DIMENSIONS; // NOTE: PROP_RADIUS obsolete requestedProperties += PROP_ROTATION; - requestedProperties += PROP_DENSITY; requestedProperties += PROP_VELOCITY; - requestedProperties += PROP_GRAVITY; + requestedProperties += PROP_ANGULAR_VELOCITY; requestedProperties += PROP_ACCELERATION; + + requestedProperties += PROP_DIMENSIONS; // NOTE: PROP_RADIUS obsolete + requestedProperties += PROP_DENSITY; + requestedProperties += PROP_GRAVITY; requestedProperties += PROP_DAMPING; requestedProperties += PROP_RESTITUTION; requestedProperties += PROP_FRICTION; @@ -111,7 +127,6 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_SCRIPT_TIMESTAMP; requestedProperties += PROP_COLLISION_SOUND_URL; requestedProperties += PROP_REGISTRATION_POINT; - requestedProperties += PROP_ANGULAR_VELOCITY; requestedProperties += PROP_ANGULAR_DAMPING; requestedProperties += PROP_VISIBLE; requestedProperties += PROP_IGNORE_FOR_COLLISIONS; @@ -120,10 +135,10 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_USER_DATA; requestedProperties += PROP_MARKETPLACE_ID; requestedProperties += PROP_NAME; - requestedProperties += PROP_SIMULATOR_ID; requestedProperties += PROP_HREF; requestedProperties += PROP_DESCRIPTION; - + requestedProperties += PROP_ACTION_DATA; + return requestedProperties; } @@ -228,13 +243,16 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet // PROP_PAGED_PROPERTY, // PROP_CUSTOM_PROPERTIES_INCLUDED, + APPEND_ENTITY_PROPERTY(PROP_SIMULATION_OWNER, _simulationOwner.toByteArray()); APPEND_ENTITY_PROPERTY(PROP_POSITION, getPosition()); - APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, getDimensions()); // NOTE: PROP_RADIUS obsolete APPEND_ENTITY_PROPERTY(PROP_ROTATION, getRotation()); - APPEND_ENTITY_PROPERTY(PROP_DENSITY, getDensity()); APPEND_ENTITY_PROPERTY(PROP_VELOCITY, getVelocity()); - APPEND_ENTITY_PROPERTY(PROP_GRAVITY, getGravity()); + APPEND_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, getAngularVelocity()); APPEND_ENTITY_PROPERTY(PROP_ACCELERATION, getAcceleration()); + + APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, getDimensions()); // NOTE: PROP_RADIUS obsolete + APPEND_ENTITY_PROPERTY(PROP_DENSITY, getDensity()); + APPEND_ENTITY_PROPERTY(PROP_GRAVITY, getGravity()); APPEND_ENTITY_PROPERTY(PROP_DAMPING, getDamping()); APPEND_ENTITY_PROPERTY(PROP_RESTITUTION, getRestitution()); APPEND_ENTITY_PROPERTY(PROP_FRICTION, getFriction()); @@ -242,19 +260,18 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet APPEND_ENTITY_PROPERTY(PROP_SCRIPT, getScript()); APPEND_ENTITY_PROPERTY(PROP_SCRIPT_TIMESTAMP, getScriptTimestamp()); APPEND_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, getRegistrationPoint()); - APPEND_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, getAngularVelocity()); APPEND_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, getAngularDamping()); APPEND_ENTITY_PROPERTY(PROP_VISIBLE, getVisible()); APPEND_ENTITY_PROPERTY(PROP_IGNORE_FOR_COLLISIONS, getIgnoreForCollisions()); APPEND_ENTITY_PROPERTY(PROP_COLLISIONS_WILL_MOVE, getCollisionsWillMove()); APPEND_ENTITY_PROPERTY(PROP_LOCKED, getLocked()); APPEND_ENTITY_PROPERTY(PROP_USER_DATA, getUserData()); - APPEND_ENTITY_PROPERTY(PROP_SIMULATOR_ID, getSimulatorID()); APPEND_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, getMarketplaceID()); APPEND_ENTITY_PROPERTY(PROP_NAME, getName()); APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, getCollisionSoundURL()); APPEND_ENTITY_PROPERTY(PROP_HREF, getHref()); APPEND_ENTITY_PROPERTY(PROP_DESCRIPTION, getDescription()); + APPEND_ENTITY_PROPERTY(PROP_ACTION_DATA, getActionData()); appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, @@ -318,8 +335,8 @@ int EntityItem::expectedBytes() { } +// clients use this method to unpack FULL updates from entity-server int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) { - if (args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_SPLIT_MTU) { // NOTE: This shouldn't happen. The only versions of the bit stream that didn't support split mtu buffers should @@ -343,14 +360,9 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef return 0; } - // if this bitstream indicates that this node is the simulation owner, ignore any physics-related updates. - glm::vec3 savePosition = getPosition(); - glm::quat saveRotation = getRotation(); - glm::vec3 saveVelocity = _velocity; - glm::vec3 saveAngularVelocity = _angularVelocity; - int originalLength = bytesLeftToRead; - QByteArray originalDataBuffer((const char*)data, originalLength); + // TODO: figure out a way to avoid the big deep copy below. + QByteArray originalDataBuffer((const char*)data, originalLength); // big deep copy! int clockSkew = args.sourceNode ? args.sourceNode->getClockSkewUsec() : 0; @@ -404,7 +416,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef qCDebug(entities) << " lastEdited =" << lastEdited; qCDebug(entities) << " ago=" << editedAgo << "seconds - " << agoAsString; #endif - + quint64 lastEditedFromBuffer = 0; quint64 lastEditedFromBufferAdjusted = 0; @@ -451,7 +463,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef ignoreServerPacket = true; } } - + if (ignoreServerPacket) { overwriteLocalData = false; #ifdef WANT_DEBUG @@ -468,8 +480,8 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef _lastEdited = lastEditedFromBufferAdjusted; _lastEditedFromRemote = now; _lastEditedFromRemoteInRemoteTime = lastEditedFromBuffer; - - // TODO: only send this notification if something ACTUALLY changed (hint, we haven't yet parsed + + // TODO: only send this notification if something ACTUALLY changed (hint, we haven't yet parsed // the properties out of the bitstream (see below)) somethingChangedNotification(); // notify derived classes that something has changed } @@ -489,7 +501,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef encodedUpdateDelta = updateDeltaCoder; // determine true length dataAt += encodedUpdateDelta.size(); bytesRead += encodedUpdateDelta.size(); - + // Newer bitstreams will have a last simulated and a last updated value quint64 lastSimulatedFromBufferAdjusted = now; if (args.bitstreamVersion >= VERSION_ENTITIES_HAS_LAST_SIMULATED_TIME) { @@ -512,7 +524,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef dataAt += encodedSimulatedDelta.size(); bytesRead += encodedSimulatedDelta.size(); } - + #ifdef WANT_DEBUG if (overwriteLocalData) { qCDebug(entities) << "EntityItem::readEntityDataFromBuffer()... changed entity:" << getEntityItemID(); @@ -521,47 +533,77 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef qCDebug(entities) << " getLastUpdated:" << debugTime(getLastUpdated(), now); } #endif - + // Property Flags QByteArray encodedPropertyFlags = originalDataBuffer.mid(bytesRead); // maximum possible size EntityPropertyFlags propertyFlags = encodedPropertyFlags; dataAt += propertyFlags.getEncodedLength(); bytesRead += propertyFlags.getEncodedLength(); - READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePosition); - // Old bitstreams had PROP_RADIUS, new bitstreams have PROP_DIMENSIONS - if (args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_DIMENSIONS) { - if (propertyFlags.getHasProperty(PROP_RADIUS)) { - float fromBuffer; - memcpy(&fromBuffer, dataAt, sizeof(fromBuffer)); - dataAt += sizeof(fromBuffer); - bytesRead += sizeof(fromBuffer); - if (overwriteLocalData) { - setRadius(fromBuffer); + if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_SIMULATION_OWNER_AND_ACTIONS_OVER_WIRE) { + // pack SimulationOwner and terse update properties near each other + + // NOTE: the server is authoritative for changes to simOwnerID so we always unpack ownership data + // even when we would otherwise ignore the rest of the packet. + + if (propertyFlags.getHasProperty(PROP_SIMULATION_OWNER)) { + QByteArray simOwnerData; + int bytes = OctreePacketData::unpackDataFromBytes(dataAt, simOwnerData); + SimulationOwner newSimOwner; + newSimOwner.fromByteArray(simOwnerData); + dataAt += bytes; + bytesRead += bytes; + + if (_simulationOwner.set(newSimOwner)) { + _dirtyFlags |= EntityItem::DIRTY_SIMULATOR_ID; } } - } else { + { // When we own the simulation we don't accept updates to the entity's transform/velocities + // but since we're using macros below we have to temporarily modify overwriteLocalData. + auto nodeList = DependencyManager::get(); + bool weOwnIt = _simulationOwner.matchesValidID(nodeList->getSessionUUID()); + bool oldOverwrite = overwriteLocalData; + overwriteLocalData = overwriteLocalData && !weOwnIt; + READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePosition); + READ_ENTITY_PROPERTY(PROP_ROTATION, glm::quat, updateRotation); + READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, updateVelocity); + READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocity); + READ_ENTITY_PROPERTY(PROP_ACCELERATION, glm::vec3, setAcceleration); + overwriteLocalData = oldOverwrite; + } + READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, updateDimensions); - } + READ_ENTITY_PROPERTY(PROP_DENSITY, float, updateDensity); + READ_ENTITY_PROPERTY(PROP_GRAVITY, glm::vec3, updateGravity); - READ_ENTITY_PROPERTY(PROP_ROTATION, glm::quat, updateRotation); - READ_ENTITY_PROPERTY(PROP_DENSITY, float, updateDensity); - READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, updateVelocity); - READ_ENTITY_PROPERTY(PROP_GRAVITY, glm::vec3, updateGravity); - if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_ACCELERATION) { + READ_ENTITY_PROPERTY(PROP_DAMPING, float, updateDamping); + READ_ENTITY_PROPERTY(PROP_RESTITUTION, float, updateRestitution); + READ_ENTITY_PROPERTY(PROP_FRICTION, float, updateFriction); + READ_ENTITY_PROPERTY(PROP_LIFETIME, float, updateLifetime); + READ_ENTITY_PROPERTY(PROP_SCRIPT, QString, setScript); + READ_ENTITY_PROPERTY(PROP_SCRIPT_TIMESTAMP, quint64, setScriptTimestamp); + READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, setRegistrationPoint); + } else { + // legacy order of packing here + READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePosition); + READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, updateDimensions); + READ_ENTITY_PROPERTY(PROP_ROTATION, glm::quat, updateRotation); + READ_ENTITY_PROPERTY(PROP_DENSITY, float, updateDensity); + READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, updateVelocity); + READ_ENTITY_PROPERTY(PROP_GRAVITY, glm::vec3, updateGravity); READ_ENTITY_PROPERTY(PROP_ACCELERATION, glm::vec3, setAcceleration); + + READ_ENTITY_PROPERTY(PROP_DAMPING, float, updateDamping); + READ_ENTITY_PROPERTY(PROP_RESTITUTION, float, updateRestitution); + READ_ENTITY_PROPERTY(PROP_FRICTION, float, updateFriction); + READ_ENTITY_PROPERTY(PROP_LIFETIME, float, updateLifetime); + READ_ENTITY_PROPERTY(PROP_SCRIPT, QString, setScript); + READ_ENTITY_PROPERTY(PROP_SCRIPT_TIMESTAMP, quint64, setScriptTimestamp); + READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, setRegistrationPoint); + READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocity); } - READ_ENTITY_PROPERTY(PROP_DAMPING, float, updateDamping); - READ_ENTITY_PROPERTY(PROP_RESTITUTION, float, updateRestitution); - READ_ENTITY_PROPERTY(PROP_FRICTION, float, updateFriction); - READ_ENTITY_PROPERTY(PROP_LIFETIME, float, updateLifetime); - READ_ENTITY_PROPERTY(PROP_SCRIPT, QString, setScript); - READ_ENTITY_PROPERTY(PROP_SCRIPT_TIMESTAMP, quint64, setScriptTimestamp); - READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, setRegistrationPoint); - READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocity); - //READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocityInDegrees); READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, updateAngularDamping); READ_ENTITY_PROPERTY(PROP_VISIBLE, bool, setVisible); READ_ENTITY_PROPERTY(PROP_IGNORE_FOR_COLLISIONS, bool, updateIgnoreForCollisions); @@ -569,12 +611,14 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef READ_ENTITY_PROPERTY(PROP_LOCKED, bool, setLocked); READ_ENTITY_PROPERTY(PROP_USER_DATA, QString, setUserData); - if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_ACCELERATION) { + if (args.bitstreamVersion < VERSION_ENTITIES_HAVE_SIMULATION_OWNER_AND_ACTIONS_OVER_WIRE) { + // this code for when there is only simulatorID and no simulation priority + // we always accept the server's notion of simulatorID, so we fake overwriteLocalData as true - // before we try to READ_ENTITY_PROPERTY it + // before we try to READ_ENTITY_PROPERTY it bool temp = overwriteLocalData; overwriteLocalData = true; - READ_ENTITY_PROPERTY(PROP_SIMULATOR_ID, QUuid, updateSimulatorID); + READ_ENTITY_PROPERTY(PROP_SIMULATION_OWNER, QUuid, updateSimulatorID); overwriteLocalData = temp; } @@ -587,12 +631,15 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef READ_ENTITY_PROPERTY(PROP_HREF, QString, setHref); READ_ENTITY_PROPERTY(PROP_DESCRIPTION, QString, setDescription); - bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData); + READ_ENTITY_PROPERTY(PROP_ACTION_DATA, QByteArray, setActionData); + + bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, + propertyFlags, overwriteLocalData); //////////////////////////////////// // WARNING: Do not add stream content here after the subclass. Always add it before the subclass // - // NOTE: we had a bad version of the stream that we added stream data after the subclass. We can attempt to recover + // NOTE: we had a bad version of the stream that we added stream data after the subclass. We can attempt to recover // by doing this parsing here... but it's not likely going to fully recover the content. // // TODO: Remove this conde once we've sufficiently migrated content past this damaged version @@ -614,7 +661,6 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef #ifdef WANT_DEBUG qCDebug(entities) << "skipTimeForward:" << skipTimeForward; #endif - // we want to extrapolate the motion forward to compensate for packet travel time, but // we don't want the side effect of flag setting. simulateKinematicMotion(skipTimeForward, false); @@ -624,15 +670,8 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef auto nodeList = DependencyManager::get(); const QUuid& myNodeID = nodeList->getSessionUUID(); if (overwriteLocalData) { - if (_simulatorID == myNodeID && !_simulatorID.isNull()) { - // we own the simulation, so we keep our transform+velocities and remove any related dirty flags - // rather than accept the values in the packet - setPosition(savePosition); - setRotation(saveRotation); - _velocity = saveVelocity; - _angularVelocity = saveAngularVelocity; - _dirtyFlags &= ~(EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES); - } else { + if (!_simulationOwner.matchesValidID(myNodeID)) { + _lastSimulated = now; } } @@ -759,6 +798,10 @@ void EntityItem::simulate(const quint64& now) { } void EntityItem::simulateKinematicMotion(float timeElapsed, bool setFlags) { + if (hasActions()) { + return; + } + if (hasAngularVelocity()) { // angular damping if (_angularDamping > 0.0f) { @@ -770,7 +813,7 @@ void EntityItem::simulateKinematicMotion(float timeElapsed, bool setFlags) { } float angularSpeed = glm::length(_angularVelocity); - + const float EPSILON_ANGULAR_VELOCITY_LENGTH = 0.0017453f; // 0.0017453 rad/sec = 0.1f degrees/sec if (angularSpeed < EPSILON_ANGULAR_VELOCITY_LENGTH) { if (setFlags && angularSpeed > 0.0f) { @@ -778,8 +821,8 @@ void EntityItem::simulateKinematicMotion(float timeElapsed, bool setFlags) { } _angularVelocity = ENTITY_ITEM_ZERO_VEC3; } else { - // for improved agreement with the way Bullet integrates rotations we use an approximation - // and break the integration into bullet-sized substeps + // for improved agreement with the way Bullet integrates rotations we use an approximation + // and break the integration into bullet-sized substeps glm::quat rotation = getRotation(); float dt = timeElapsed; while (dt > PHYSICS_ENGINE_FIXED_SUBSTEP) { @@ -892,6 +935,7 @@ EntityItemProperties EntityItem::getProperties() const { properties._type = getType(); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(simulationOwner, getSimulationOwner); COPY_ENTITY_PROPERTY_TO_PROPERTIES(position, getPosition); COPY_ENTITY_PROPERTY_TO_PROPERTIES(dimensions, getDimensions); // NOTE: radius is obsolete COPY_ENTITY_PROPERTY_TO_PROPERTIES(rotation, getRotation); @@ -917,11 +961,11 @@ EntityItemProperties EntityItem::getProperties() const { COPY_ENTITY_PROPERTY_TO_PROPERTIES(collisionsWillMove, getCollisionsWillMove); COPY_ENTITY_PROPERTY_TO_PROPERTIES(locked, getLocked); COPY_ENTITY_PROPERTY_TO_PROPERTIES(userData, getUserData); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(simulatorID, getSimulatorID); COPY_ENTITY_PROPERTY_TO_PROPERTIES(marketplaceID, getMarketplaceID); COPY_ENTITY_PROPERTY_TO_PROPERTIES(name, getName); COPY_ENTITY_PROPERTY_TO_PROPERTIES(href, getHref); COPY_ENTITY_PROPERTY_TO_PROPERTIES(description, getDescription); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(actionData, getActionData); properties._defaultSettings = false; @@ -947,6 +991,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { bool somethingChanged = false; // these affect TerseUpdate properties + SET_ENTITY_PROPERTY_FROM_PROPERTIES(simulationOwner, setSimulationOwner); SET_ENTITY_PROPERTY_FROM_PROPERTIES(position, updatePosition); SET_ENTITY_PROPERTY_FROM_PROPERTIES(rotation, updateRotation); SET_ENTITY_PROPERTY_FROM_PROPERTIES(velocity, updateVelocity); @@ -968,7 +1013,6 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionsWillMove, updateCollisionsWillMove); SET_ENTITY_PROPERTY_FROM_PROPERTIES(created, updateCreated); SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifetime, updateLifetime); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(simulatorID, updateSimulatorID); // non-simulation properties below SET_ENTITY_PROPERTY_FROM_PROPERTIES(script, setScript); @@ -983,6 +1027,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(name, setName); SET_ENTITY_PROPERTY_FROM_PROPERTIES(href, setHref); SET_ENTITY_PROPERTY_FROM_PROPERTIES(description, setDescription); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(actionData, setActionData); if (somethingChanged) { uint64_t now = usecTimestampNow(); @@ -1343,52 +1388,115 @@ void EntityItem::updateCreated(uint64_t value) { } } -void EntityItem::setSimulatorID(const QUuid& value) { - _simulatorID = value; - _simulatorIDChangedTime = usecTimestampNow(); +void EntityItem::setSimulationOwner(const QUuid& id, quint8 priority) { + _simulationOwner.set(id, priority); +} + +void EntityItem::setSimulationOwner(const SimulationOwner& owner) { + _simulationOwner.set(owner); } void EntityItem::updateSimulatorID(const QUuid& value) { - if (_simulatorID != value) { - _simulatorID = value; - _simulatorIDChangedTime = usecTimestampNow(); + if (_simulationOwner.setID(value)) { _dirtyFlags |= EntityItem::DIRTY_SIMULATOR_ID; } } +void EntityItem::clearSimulationOwnership() { + _simulationOwner.clear(); + // don't bother setting the DIRTY_SIMULATOR_ID flag because clearSimulationOwnership() + // is only ever called entity-server-side and the flags are only used client-side + //_dirtyFlags |= EntityItem::DIRTY_SIMULATOR_ID; + +} + bool EntityItem::addAction(EntitySimulation* simulation, EntityActionPointer action) { + checkWaitingToRemove(simulation); + if (!checkWaitingActionData(simulation)) { + return false; + } + + bool result = addActionInternal(simulation, action); + if (!result) { + removeAction(simulation, action->getID()); + } + return result; +} + +bool EntityItem::addActionInternal(EntitySimulation* simulation, EntityActionPointer action) { assert(action); + assert(simulation); + auto actionOwnerEntity = action->getOwnerEntity().lock(); + assert(actionOwnerEntity); + assert(actionOwnerEntity.get() == this); + const QUuid& actionID = action->getID(); assert(!_objectActions.contains(actionID) || _objectActions[actionID] == action); _objectActions[actionID] = action; - - assert(action->getOwnerEntity().get() == this); - simulation->addAction(action); - return false; + bool success; + QByteArray newDataCache = serializeActions(success); + if (success) { + _allActionsDataCache = newDataCache; + } + return success; } bool EntityItem::updateAction(EntitySimulation* simulation, const QUuid& actionID, const QVariantMap& arguments) { + checkWaitingToRemove(simulation); + if (!checkWaitingActionData(simulation)) { + return false; + } + if (!_objectActions.contains(actionID)) { return false; } EntityActionPointer action = _objectActions[actionID]; - return action->updateArguments(arguments); + bool success = action->updateArguments(arguments); + + if (success) { + _allActionsDataCache = serializeActions(success); + } else { + qDebug() << "EntityItem::updateAction failed"; + } + + return success; } bool EntityItem::removeAction(EntitySimulation* simulation, const QUuid& actionID) { + checkWaitingToRemove(simulation); + if (!checkWaitingActionData(simulation)) { + return false;; + } + + return removeActionInternal(actionID); +} + +bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulation* simulation) { if (_objectActions.contains(actionID)) { + if (!simulation) { + EntityTree* entityTree = _element ? _element->getTree() : nullptr; + simulation = entityTree ? entityTree->getSimulation() : nullptr; + } + EntityActionPointer action = _objectActions[actionID]; - _objectActions.remove(actionID); action->setOwnerEntity(nullptr); - action->removeFromSimulation(simulation); - return true; + _objectActions.remove(actionID); + + if (simulation) { + action->removeFromSimulation(simulation); + } + + bool success = true; + _allActionsDataCache = serializeActions(success); + return success; } return false; } -void EntityItem::clearActions(EntitySimulation* simulation) { +bool EntityItem::clearActions(EntitySimulation* simulation) { + _waitingActionData.clear(); QHash::iterator i = _objectActions.begin(); while (i != _objectActions.end()) { const QUuid id = i.key(); @@ -1397,4 +1505,150 @@ void EntityItem::clearActions(EntitySimulation* simulation) { action->setOwnerEntity(nullptr); action->removeFromSimulation(simulation); } + _actionsToRemove.clear(); + _allActionsDataCache.clear(); + return true; +} + +bool EntityItem::deserializeActions(QByteArray allActionsData, EntitySimulation* simulation) const { + bool success = true; + QVector serializedActions; + if (allActionsData.size() > 0) { + QDataStream serializedActionsStream(allActionsData); + serializedActionsStream >> serializedActions; + } + + // Keep track of which actions got added or updated by the new actionData + QSet updated; + + EntityTree* entityTree = _element ? _element->getTree() : nullptr; + if (!simulation) { + simulation = entityTree ? entityTree->getSimulation() : nullptr; + } + + if (simulation && entityTree) { + foreach(QByteArray serializedAction, serializedActions) { + QDataStream serializedActionStream(serializedAction); + EntityActionType actionType; + QUuid actionID; + serializedActionStream >> actionType; + serializedActionStream >> actionID; + updated << actionID; + + if (_objectActions.contains(actionID)) { + EntityActionPointer action = _objectActions[actionID]; + // TODO: make sure types match? there isn't currently a way to + // change the type of an existing action. + action->deserialize(serializedAction); + } else { + auto actionFactory = DependencyManager::get(); + if (simulation) { + EntityItemPointer entity = entityTree->findEntityByEntityItemID(_id); + EntityActionPointer action = actionFactory->factoryBA(simulation, entity, serializedAction); + if (action) { + entity->addActionInternal(simulation, action); + } + } else { + // we can't yet add the action. This method will be called later. + success = false; + } + } + } + + // remove any actions that weren't included in the new data. + QHash::const_iterator i = _objectActions.begin(); + while (i != _objectActions.end()) { + const QUuid id = i.key(); + if (!updated.contains(id)) { + _actionsToRemove << id; + } + i++; + } + } else { + // no simulation + success = false; + } + + return success; +} + +bool EntityItem::checkWaitingActionData(EntitySimulation* simulation) const { + if (_waitingActionData.size() == 0) { + return true; + } + bool success = deserializeActions(_waitingActionData, simulation); + if (success) { + _waitingActionData.clear(); + } + return success; +} + +void EntityItem::checkWaitingToRemove(EntitySimulation* simulation) { + foreach(QUuid actionID, _actionsToRemove) { + removeActionInternal(actionID, simulation); + } + _actionsToRemove.clear(); +} + +void EntityItem::setActionData(QByteArray actionData) { + checkWaitingToRemove(); + bool success = deserializeActions(actionData); + _allActionsDataCache = actionData; + if (success) { + _waitingActionData.clear(); + } else { + _waitingActionData = actionData; + } +} + +QByteArray EntityItem::serializeActions(bool& success) const { + QByteArray result; + if (!checkWaitingActionData()) { + return _waitingActionData; + } + + if (_objectActions.size() == 0) { + success = true; + return QByteArray(); + } + + QVector serializedActions; + QHash::const_iterator i = _objectActions.begin(); + while (i != _objectActions.end()) { + const QUuid id = i.key(); + EntityActionPointer action = _objectActions[id]; + QByteArray bytesForAction = action->serialize(); + serializedActions << bytesForAction; + i++; + } + + QDataStream serializedActionsStream(&result, QIODevice::WriteOnly); + serializedActionsStream << serializedActions; + + if (result.size() >= _maxActionsDataSize) { + success = false; + return result; + } + + success = true; + return result; +} + +const QByteArray EntityItem::getActionData() const { + return _allActionsDataCache; +} + +QVariantMap EntityItem::getActionArguments(const QUuid& actionID) const { + QVariantMap result; + + if (!checkWaitingActionData()) { + return result; + } + + if (_objectActions.contains(actionID)) { + EntityActionPointer action = _objectActions[actionID]; + result = action->getArguments(); + result["type"] = EntityActionInterface::actionTypeToString(action->getType()); + } + return result; } diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 3ce3a8e269..bc8901c6b1 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -29,6 +29,7 @@ #include "EntityItemProperties.h" #include "EntityItemPropertiesDefaults.h" #include "EntityTypes.h" +#include "SimulationOwner.h" class EntitySimulation; class EntityTreeElement; @@ -60,7 +61,6 @@ const float ACTIVATION_LINEAR_VELOCITY_DELTA = 0.01f; const float ACTIVATION_GRAVITY_DELTA = 0.1f; const float ACTIVATION_ANGULAR_VELOCITY_DELTA = 0.03f; - #define DONT_ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() = 0; #define ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() { }; @@ -68,7 +68,6 @@ const float ACTIVATION_ANGULAR_VELOCITY_DELTA = 0.03f; #define debugTimeOnly(T) qPrintable(QString("%1").arg(T, 16, 10)) #define debugTreeVector(V) V << "[" << V << " in meters ]" - /// EntityItem class this is the base class for all entity types. It handles the basic properties and functionality available /// to all other entity types. In particular: postion, size, rotation, age, lifetime, velocity, gravity. You can not instantiate /// one directly, instead you must only construct one of it's derived classes with additional features. @@ -92,8 +91,9 @@ public: DIRTY_LIFETIME = 0x0100, DIRTY_UPDATEABLE = 0x0200, DIRTY_MATERIAL = 0x00400, - DIRTY_PHYSICS_ACTIVATION = 0x0800, // we want to activate the object - DIRTY_SIMULATOR_ID = 0x1000, + DIRTY_PHYSICS_ACTIVATION = 0x0800, // should activate object in physics engine + DIRTY_SIMULATOR_OWNERSHIP = 0x1000, // should claim simulator ownership + DIRTY_SIMULATOR_ID = 0x2000, // the simulatorID has changed DIRTY_TRANSFORM = DIRTY_POSITION | DIRTY_ROTATION, DIRTY_VELOCITIES = DIRTY_LINEAR_VELOCITY | DIRTY_ANGULAR_VELOCITY }; @@ -317,11 +317,16 @@ public: const QString& getUserData() const { return _userData; } void setUserData(const QString& value) { _userData = value; } - - QUuid getSimulatorID() const { return _simulatorID; } - void setSimulatorID(const QUuid& value); + + const SimulationOwner& getSimulationOwner() const { return _simulationOwner; } + void setSimulationOwner(const QUuid& id, quint8 priority); + void setSimulationOwner(const SimulationOwner& owner); + void promoteSimulationPriority(quint8 priority); + + quint8 getSimulationPriority() const { return _simulationOwner.getPriority(); } + QUuid getSimulatorID() const { return _simulationOwner.getID(); } void updateSimulatorID(const QUuid& value); - quint64 getSimulatorIDChangedTime() const { return _simulatorIDChangedTime; } + void clearSimulationOwnership(); const QString& getMarketplaceID() const { return _marketplaceID; } void setMarketplaceID(const QString& value) { _marketplaceID = value; } @@ -358,7 +363,7 @@ public: virtual void updateShapeType(ShapeType type) { /* do nothing */ } uint32_t getDirtyFlags() const { return _dirtyFlags; } - void clearDirtyFlags(uint32_t mask = 0xffff) { _dirtyFlags &= ~mask; } + void clearDirtyFlags(uint32_t mask = 0xffffffff) { _dirtyFlags &= ~mask; } bool isMoving() const; @@ -379,10 +384,17 @@ public: void getAllTerseUpdateProperties(EntityItemProperties& properties) const; + void flagForOwnership() { _dirtyFlags |= DIRTY_SIMULATOR_OWNERSHIP; } + bool addAction(EntitySimulation* simulation, EntityActionPointer action); bool updateAction(EntitySimulation* simulation, const QUuid& actionID, const QVariantMap& arguments); bool removeAction(EntitySimulation* simulation, const QUuid& actionID); - void clearActions(EntitySimulation* simulation); + bool clearActions(EntitySimulation* simulation); + void setActionData(QByteArray actionData); + const QByteArray getActionData() const; + bool hasActions() { return !_objectActions.empty(); } + QList getActionIDs() { return _objectActions.keys(); } + QVariantMap getActionArguments(const QUuid& actionID) const; protected: @@ -426,8 +438,7 @@ protected: bool _collisionsWillMove; bool _locked; QString _userData; - QUuid _simulatorID; // id of Node which is currently responsible for simulating this Entity - quint64 _simulatorIDChangedTime; // when was _simulatorID last updated? + SimulationOwner _simulationOwner; QString _marketplaceID; QString _name; QString _href; //Hyperlink href @@ -457,7 +468,20 @@ protected: void* _physicsInfo = nullptr; // set by EntitySimulation bool _simulated; // set by EntitySimulation + bool addActionInternal(EntitySimulation* simulation, EntityActionPointer action); + bool removeActionInternal(const QUuid& actionID, EntitySimulation* simulation = nullptr); + bool deserializeActions(QByteArray allActionsData, EntitySimulation* simulation = nullptr) const; + QByteArray serializeActions(bool& success) const; QHash _objectActions; + static int _maxActionsDataSize; + mutable QByteArray _allActionsDataCache; + // when an entity-server starts up, EntityItem::setActionData is called before the entity-tree is + // ready. This means we can't find our EntityItemPointer or add the action to the simulation. These + // are used to keep track of and work around this situation. + bool checkWaitingActionData(EntitySimulation* simulation = nullptr) const; + void checkWaitingToRemove(EntitySimulation* simulation = nullptr); + mutable QByteArray _waitingActionData; + mutable QSet _actionsToRemove; }; #endif // hifi_EntityItem_h diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index f755cd49fb..9a1a5494b7 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -38,7 +38,7 @@ EntityPropertyList PROP_LAST_ITEM = (EntityPropertyList)(PROP_AFTER_LAST_ITEM - EntityItemProperties::EntityItemProperties() : CONSTRUCT_PROPERTY(visible, ENTITY_ITEM_DEFAULT_VISIBLE), -CONSTRUCT_PROPERTY(position, 0), +CONSTRUCT_PROPERTY(position, 0.0f), CONSTRUCT_PROPERTY(dimensions, ENTITY_ITEM_DEFAULT_DIMENSIONS), CONSTRUCT_PROPERTY(rotation, ENTITY_ITEM_DEFAULT_ROTATION), CONSTRUCT_PROPERTY(density, ENTITY_ITEM_DEFAULT_DENSITY), @@ -73,7 +73,7 @@ CONSTRUCT_PROPERTY(locked, ENTITY_ITEM_DEFAULT_LOCKED), CONSTRUCT_PROPERTY(textures, ""), CONSTRUCT_PROPERTY(animationSettings, ""), CONSTRUCT_PROPERTY(userData, ENTITY_ITEM_DEFAULT_USER_DATA), -CONSTRUCT_PROPERTY(simulatorID, ENTITY_ITEM_DEFAULT_SIMULATOR_ID), +CONSTRUCT_PROPERTY(simulationOwner, SimulationOwner()), CONSTRUCT_PROPERTY(text, TextEntityItem::DEFAULT_TEXT), CONSTRUCT_PROPERTY(lineHeight, TextEntityItem::DEFAULT_LINE_HEIGHT), CONSTRUCT_PROPERTY(textColor, TextEntityItem::DEFAULT_TEXT_COLOR), @@ -100,7 +100,7 @@ CONSTRUCT_PROPERTY(sourceUrl, ""), CONSTRUCT_PROPERTY(lineWidth, LineEntityItem::DEFAULT_LINE_WIDTH), CONSTRUCT_PROPERTY(linePoints, QVector()), CONSTRUCT_PROPERTY(faceCamera, TextEntityItem::DEFAULT_FACE_CAMERA), - +CONSTRUCT_PROPERTY(actionData, QByteArray()), _id(UNKNOWN_ENTITY_ID), _idSet(false), @@ -183,7 +183,6 @@ QString EntityItemProperties::getAnimationSettings() const { void EntityItemProperties::setCreated(QDateTime &v) { _created = v.toMSecsSinceEpoch() * 1000; // usec per msec - qDebug() << "EntityItemProperties::setCreated QDateTime" << v << _created; } void EntityItemProperties::debugDump() const { @@ -289,8 +288,8 @@ void EntityItemProperties::setBackgroundModeFromString(const QString& background EntityPropertyFlags EntityItemProperties::getChangedProperties() const { EntityPropertyFlags changedProperties; - CHECK_PROPERTY_CHANGE(PROP_DIMENSIONS, dimensions); CHECK_PROPERTY_CHANGE(PROP_POSITION, position); + CHECK_PROPERTY_CHANGE(PROP_DIMENSIONS, dimensions); CHECK_PROPERTY_CHANGE(PROP_ROTATION, rotation); CHECK_PROPERTY_CHANGE(PROP_DENSITY, density); CHECK_PROPERTY_CHANGE(PROP_VELOCITY, velocity); @@ -324,7 +323,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_LOCKED, locked); CHECK_PROPERTY_CHANGE(PROP_TEXTURES, textures); CHECK_PROPERTY_CHANGE(PROP_USER_DATA, userData); - CHECK_PROPERTY_CHANGE(PROP_SIMULATOR_ID, simulatorID); + CHECK_PROPERTY_CHANGE(PROP_SIMULATION_OWNER, simulationOwner); CHECK_PROPERTY_CHANGE(PROP_TEXT, text); CHECK_PROPERTY_CHANGE(PROP_LINE_HEIGHT, lineHeight); CHECK_PROPERTY_CHANGE(PROP_TEXT_COLOR, textColor); @@ -353,6 +352,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_HREF, href); CHECK_PROPERTY_CHANGE(PROP_DESCRIPTION, description); CHECK_PROPERTY_CHANGE(PROP_FACE_CAMERA, faceCamera); + CHECK_PROPERTY_CHANGE(PROP_ACTION_DATA, actionData); changedProperties += _stage.getChangedProperties(); changedProperties += _atmosphere.getChangedProperties(); @@ -419,7 +419,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(locked); COPY_PROPERTY_TO_QSCRIPTVALUE(textures); COPY_PROPERTY_TO_QSCRIPTVALUE(userData); - COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(simulatorID, getSimulatorIDAsString()); + //COPY_PROPERTY_TO_QSCRIPTVALUE(simulationOwner); // TODO: expose this for JSON saves? COPY_PROPERTY_TO_QSCRIPTVALUE(text); COPY_PROPERTY_TO_QSCRIPTVALUE(lineHeight); COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(textColor, getTextColor()); @@ -450,6 +450,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(href); COPY_PROPERTY_TO_QSCRIPTVALUE(description); COPY_PROPERTY_TO_QSCRIPTVALUE(faceCamera); + COPY_PROPERTY_TO_QSCRIPTVALUE(actionData); // Sitting properties support if (!skipDefaults) { @@ -563,6 +564,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(href, QString, setHref); COPY_PROPERTY_FROM_QSCRIPTVALUE(description, QString, setDescription); COPY_PROPERTY_FROM_QSCRIPTVALUE(faceCamera, bool, setFaceCamera); + COPY_PROPERTY_FROM_QSCRIPTVALUE(actionData, QByteArray, setActionData); if (!honorReadOnly) { // this is used by the json reader to set things that we don't want javascript to able to affect. @@ -570,7 +572,8 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool auto result = QDateTime::fromMSecsSinceEpoch(_created / 1000, Qt::UTC); // usec per msec return result; }); - COPY_PROPERTY_FROM_QSCRIPTVALUE(simulatorID, QUuid, setSimulatorID); + // TODO: expose this to QScriptValue for JSON saves? + //COPY_PROPERTY_FROM_QSCRIPTVALUE(simulationOwner, ???, setSimulatorPriority); } _stage.copyFromScriptValue(object, _defaultSettings); @@ -705,6 +708,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem // PROP_PAGED_PROPERTY, // PROP_CUSTOM_PROPERTIES_INCLUDED, + APPEND_ENTITY_PROPERTY(PROP_SIMULATION_OWNER, properties._simulationOwner.toByteArray()); APPEND_ENTITY_PROPERTY(PROP_POSITION, properties.getPosition()); APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, properties.getDimensions()); // NOTE: PROP_RADIUS obsolete APPEND_ENTITY_PROPERTY(PROP_ROTATION, properties.getRotation()); @@ -727,7 +731,6 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem APPEND_ENTITY_PROPERTY(PROP_COLLISIONS_WILL_MOVE, properties.getCollisionsWillMove()); APPEND_ENTITY_PROPERTY(PROP_LOCKED, properties.getLocked()); APPEND_ENTITY_PROPERTY(PROP_USER_DATA, properties.getUserData()); - APPEND_ENTITY_PROPERTY(PROP_SIMULATOR_ID, properties.getSimulatorID()); APPEND_ENTITY_PROPERTY(PROP_HREF, properties.getHref()); APPEND_ENTITY_PROPERTY(PROP_DESCRIPTION, properties.getDescription()); @@ -814,6 +817,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem APPEND_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, properties.getMarketplaceID()); APPEND_ENTITY_PROPERTY(PROP_NAME, properties.getName()); APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, properties.getCollisionSoundURL()); + APPEND_ENTITY_PROPERTY(PROP_ACTION_DATA, properties.getActionData()); } if (propertyCount > 0) { int endOfEntityItemData = packetData->getUncompressedByteOffset(); @@ -959,7 +963,8 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int EntityPropertyFlags propertyFlags = encodedPropertyFlags; dataAt += propertyFlags.getEncodedLength(); processedBytes += propertyFlags.getEncodedLength(); - + + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SIMULATION_OWNER, QByteArray, setSimulationOwner); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_POSITION, glm::vec3, setPosition); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DIMENSIONS, glm::vec3, setDimensions); // NOTE: PROP_RADIUS obsolete READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ROTATION, glm::quat, setRotation); @@ -982,7 +987,6 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLLISIONS_WILL_MOVE, bool, setCollisionsWillMove); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LOCKED, bool, setLocked); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_USER_DATA, QString, setUserData); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SIMULATOR_ID, QUuid, setSimulatorID); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_HREF, QString, setHref); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DESCRIPTION, QString, setDescription); @@ -1064,6 +1068,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MARKETPLACE_ID, QString, setMarketplaceID); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_NAME, QString, setName); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLLISION_SOUND_URL, QString, setCollisionSoundURL); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ACTION_DATA, QByteArray, setActionData); return valid; } @@ -1098,6 +1103,7 @@ bool EntityItemProperties::encodeEraseEntityMessage(const EntityItemID& entityIt } void EntityItemProperties::markAllChanged() { + _simulationOwnerChanged = true; _positionChanged = true; _dimensionsChanged = true; _rotationChanged = true; @@ -1110,7 +1116,6 @@ void EntityItemProperties::markAllChanged() { _frictionChanged = true; _lifetimeChanged = true; _userDataChanged = true; - _simulatorIDChanged = true; _scriptChanged = true; _scriptTimestampChanged = true; _collisionSoundURLChanged = true; @@ -1175,9 +1180,8 @@ void EntityItemProperties::markAllChanged() { _hrefChanged = true; _descriptionChanged = true; - _faceCameraChanged = true; - + _actionDataChanged = true; } /// The maximum bounding cube for the entity, independent of it's rotation. @@ -1227,3 +1231,27 @@ bool EntityItemProperties::hasTerseUpdateChanges() const { // a TerseUpdate includes the transform and its derivatives return _positionChanged || _velocityChanged || _rotationChanged || _angularVelocityChanged || _accelerationChanged; } + +bool EntityItemProperties::hasMiscPhysicsChanges() const { + return _gravityChanged || _dimensionsChanged || _densityChanged || _frictionChanged + || _restitutionChanged || _dampingChanged || _angularDampingChanged || _registrationPointChanged || + _compoundShapeURLChanged || _collisionsWillMoveChanged || _ignoreForCollisionsChanged; +} + +void EntityItemProperties::clearSimulationOwner() { + _simulationOwner.clear(); + _simulationOwnerChanged = true; +} + +void EntityItemProperties::setSimulationOwner(const QUuid& id, uint8_t priority) { + if (!_simulationOwner.matchesValidID(id) || _simulationOwner.getPriority() != priority) { + _simulationOwner.set(id, priority); + _simulationOwnerChanged = true; + } +} + +void EntityItemProperties::setSimulationOwner(const QByteArray& data) { + if (_simulationOwner.fromByteArray(data)) { + _simulationOwnerChanged = true; + } +} diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index fdf01c4761..b8200d025c 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -34,6 +34,7 @@ #include "EntityItemPropertiesMacros.h" #include "EntityTypes.h" #include "EntityPropertyFlags.h" +#include "SimulationOwner.h" #include "SkyboxPropertyGroup.h" #include "StagePropertyGroup.h" @@ -120,7 +121,7 @@ public: DEFINE_PROPERTY_REF(PROP_TEXTURES, Textures, textures, QString); DEFINE_PROPERTY_REF_WITH_SETTER_AND_GETTER(PROP_ANIMATION_SETTINGS, AnimationSettings, animationSettings, QString); DEFINE_PROPERTY_REF(PROP_USER_DATA, UserData, userData, QString); - DEFINE_PROPERTY_REF(PROP_SIMULATOR_ID, SimulatorID, simulatorID, QUuid); + DEFINE_PROPERTY_REF(PROP_SIMULATION_OWNER, SimulationOwner, simulationOwner, SimulationOwner); DEFINE_PROPERTY_REF(PROP_TEXT, Text, text, QString); DEFINE_PROPERTY(PROP_LINE_HEIGHT, LineHeight, lineHeight, float); DEFINE_PROPERTY_REF(PROP_TEXT_COLOR, TextColor, textColor, xColor); @@ -152,7 +153,8 @@ public: DEFINE_PROPERTY_REF(PROP_HREF, Href, href, QString); DEFINE_PROPERTY_REF(PROP_DESCRIPTION, Description, description, QString); DEFINE_PROPERTY(PROP_FACE_CAMERA, FaceCamera, faceCamera, bool); - + DEFINE_PROPERTY_REF(PROP_ACTION_DATA, ActionData, actionData, QByteArray); + static QString getBackgroundModeString(BackgroundMode mode); @@ -196,7 +198,7 @@ public: const QStringList& getTextureNames() const { return _textureNames; } void setTextureNames(const QStringList& value) { _textureNames = value; } - QString getSimulatorIDAsString() const { return _simulatorID.toString().mid(1,36).toUpper(); } + QString getSimulatorIDAsString() const { return _simulationOwner.getID().toString().mid(1,36).toUpper(); } void setVoxelDataDirty() { _voxelDataChanged = true; } @@ -205,6 +207,14 @@ public: void setCreated(QDateTime& v); bool hasTerseUpdateChanges() const; + bool hasMiscPhysicsChanges() const; + + void clearSimulationOwner(); + void setSimulationOwner(const QUuid& id, uint8_t priority); + void setSimulationOwner(const QByteArray& data); + void promoteSimulationPriority(quint8 priority) { _simulationOwner.promotePriority(priority); } + + void setActionDataDirty() { _actionDataChanged = true; } private: QUuid _id; @@ -284,7 +294,7 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, Locked, locked, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Textures, textures, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, UserData, userData, ""); - DEBUG_PROPERTY_IF_CHANGED(debug, properties, SimulatorID, simulatorID, QUuid()); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, SimulationOwner, simulationOwner, SimulationOwner()); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Text, text, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, LineHeight, lineHeight, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, TextColor, textColor, ""); @@ -304,7 +314,8 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, VoxelSurfaceStyle, voxelSurfaceStyle, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Href, href, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Description, description, ""); - + DEBUG_PROPERTY_IF_CHANGED(debug, properties, ActionData, actionData, ""); + properties.getStage().debugDump(); properties.getAtmosphere().debugDump(); properties.getSkybox().debugDump(); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 0a52897efd..be16683f39 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -86,7 +86,7 @@ enum EntityPropertyList { // available to all entities PROP_LOCKED, - + PROP_TEXTURES, // used by Model entities PROP_ANIMATION_SETTINGS, // used by Model entities PROP_USER_DATA, // all entities @@ -100,11 +100,11 @@ enum EntityPropertyList { PROP_EMIT_STRENGTH, PROP_LOCAL_GRAVITY, PROP_PARTICLE_RADIUS, - + PROP_COMPOUND_SHAPE_URL, // used by Model + zones entities PROP_MARKETPLACE_ID, // all entities PROP_ACCELERATION, // all entities - PROP_SIMULATOR_ID, // all entities + PROP_SIMULATION_OWNER, // formerly known as PROP_SIMULATOR_ID PROP_NAME, // all entities PROP_COLLISION_SOUND_URL, PROP_RESTITUTION, @@ -121,10 +121,11 @@ enum EntityPropertyList { // used by hyperlinks PROP_HREF, PROP_DESCRIPTION, - + PROP_FACE_CAMERA, PROP_SCRIPT_TIMESTAMP, - + PROP_ACTION_DATA, + //////////////////////////////////////////////////////////////////////////////////////////////////// // ATTENTION: add new properties to end of list just ABOVE this line PROP_AFTER_LAST_ITEM, diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index c9f7378bc8..7cc2c03dfc 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -9,18 +9,20 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "EntityScriptingInterface.h" + #include +#include "EntitiesLogging.h" +#include "EntityActionFactoryInterface.h" +#include "EntityActionInterface.h" +#include "EntitySimulation.h" #include "EntityTree.h" #include "LightEntityItem.h" #include "ModelEntityItem.h" +#include "SimulationOwner.h" #include "ZoneEntityItem.h" -#include "EntitiesLogging.h" -#include "EntitySimulation.h" -#include "EntityActionInterface.h" -#include "EntityActionFactoryInterface.h" -#include "EntityScriptingInterface.h" EntityScriptingInterface::EntityScriptingInterface() : _entityTree(NULL) @@ -61,16 +63,6 @@ void EntityScriptingInterface::setEntityTree(EntityTree* modelTree) { } } -void bidForSimulationOwnership(EntityItemProperties& properties) { - // We make a bid for simulation ownership by declaring our sessionID as simulation owner - // in the outgoing properties. The EntityServer may accept the bid or might not. - auto nodeList = DependencyManager::get(); - const QUuid myNodeID = nodeList->getSessionUUID(); - properties.setSimulatorID(myNodeID); -} - - - QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties) { EntityItemProperties propertiesWithSimID = properties; @@ -83,11 +75,15 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties _entityTree->lockForWrite(); EntityItemPointer entity = _entityTree->addEntity(id, propertiesWithSimID); if (entity) { - entity->setLastBroadcast(usecTimestampNow()); // This Node is creating a new object. If it's in motion, set this Node as the simulator. - bidForSimulationOwnership(propertiesWithSimID); + auto nodeList = DependencyManager::get(); + const QUuid myNodeID = nodeList->getSessionUUID(); + propertiesWithSimID.setSimulationOwner(myNodeID, SCRIPT_EDIT_SIMULATION_PRIORITY); + // and make note of it now, so we can act on it right away. - entity->setSimulatorID(propertiesWithSimID.getSimulatorID()); + entity->setSimulationOwner(myNodeID, SCRIPT_EDIT_SIMULATION_PRIORITY); + + entity->setLastBroadcast(usecTimestampNow()); } else { qCDebug(entities) << "script failed to add new Entity to local Octree"; success = false; @@ -113,7 +109,7 @@ EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identit if (entity) { results = entity->getProperties(); - // TODO: improve sitting points and naturalDimensions in the future, + // TODO: improve sitting points and naturalDimensions in the future, // for now we've included the old sitting points model behavior for entity types that are models // we've also added this hack for setting natural dimensions of models if (entity->getType() == EntityTypes::Model) { @@ -146,23 +142,35 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, EntityItemProperties proper if (entity) { // make sure the properties has a type, so that the encode can know which properties to include properties.setType(entity->getType()); - if (properties.hasTerseUpdateChanges()) { + bool hasTerseUpdateChanges = properties.hasTerseUpdateChanges(); + bool hasPhysicsChanges = properties.hasMiscPhysicsChanges() || hasTerseUpdateChanges; + if (hasPhysicsChanges) { auto nodeList = DependencyManager::get(); const QUuid myNodeID = nodeList->getSessionUUID(); - + if (entity->getSimulatorID() == myNodeID) { // we think we already own the simulation, so make sure to send ALL TerseUpdate properties - entity->getAllTerseUpdateProperties(properties); + if (hasTerseUpdateChanges) { + entity->getAllTerseUpdateProperties(properties); + } // TODO: if we knew that ONLY TerseUpdate properties have changed in properties AND the object // is dynamic AND it is active in the physics simulation then we could chose to NOT queue an update // and instead let the physics simulation decide when to send a terse update. This would remove // the "slide-no-rotate" glitch (and typical a double-update) that we see during the "poke rolling - // balls" test. However, even if we solve this problem we still need to provide a "slerp the visible + // balls" test. However, even if we solve this problem we still need to provide a "slerp the visible // proxy toward the true physical position" feature to hide the final glitches in the remote watcher's // simulation. + + if (entity->getSimulationPriority() < SCRIPT_EDIT_SIMULATION_PRIORITY) { + // we re-assert our simulation ownership at a higher priority + properties.setSimulationOwner(myNodeID, + glm::max(entity->getSimulationPriority(), SCRIPT_EDIT_SIMULATION_PRIORITY)); + } + } else { + // we make a bid for simulation ownership + properties.setSimulationOwner(myNodeID, SCRIPT_EDIT_SIMULATION_PRIORITY); + entity->flagForOwnership(); } - // we make a bid for (or assert existing) simulation ownership - properties.setSimulatorID(myNodeID); } entity->setLastBroadcast(usecTimestampNow()); } @@ -204,7 +212,7 @@ void EntityScriptingInterface::deleteEntity(QUuid id) { } QUuid EntityScriptingInterface::findClosestEntity(const glm::vec3& center, float radius) const { - EntityItemID result; + EntityItemID result; if (_entityTree) { _entityTree->lockForRead(); EntityItemPointer closestEntity = _entityTree->findClosestEntity(center, radius); @@ -264,8 +272,8 @@ RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionBlock return findRayIntersectionWorker(ray, Octree::Lock, precisionPicking); } -RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionWorker(const PickRay& ray, - Octree::lockType lockType, +RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionWorker(const PickRay& ray, + Octree::lockType lockType, bool precisionPicking) { @@ -273,8 +281,8 @@ RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionWorke if (_entityTree) { OctreeElement* element; EntityItemPointer intersectedEntity = NULL; - result.intersects = _entityTree->findRayIntersection(ray.origin, ray.direction, element, result.distance, result.face, - (void**)&intersectedEntity, lockType, &result.accurate, + result.intersects = _entityTree->findRayIntersection(ray.origin, ray.direction, element, result.distance, result.face, + (void**)&intersectedEntity, lockType, &result.accurate, precisionPicking); if (result.intersects && intersectedEntity) { result.entityID = intersectedEntity->getEntityItemID(); @@ -318,15 +326,15 @@ bool EntityScriptingInterface::getSendPhysicsUpdates() const { } -RayToEntityIntersectionResult::RayToEntityIntersectionResult() : - intersects(false), +RayToEntityIntersectionResult::RayToEntityIntersectionResult() : + intersects(false), accurate(true), // assume it's accurate entityID(), properties(), distance(0), face(), entity(NULL) -{ +{ } QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, const RayToEntityIntersectionResult& value) { @@ -341,7 +349,7 @@ QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, c obj.setProperty("distance", value.distance); - QString faceName = ""; + QString faceName = ""; // handle BoxFace switch (value.face) { case MIN_X_FACE: @@ -446,35 +454,35 @@ bool EntityScriptingInterface::setPoints(QUuid entityID, std::function(_entityTree->findEntityByEntityItemID(entityID)); if (!entity) { qCDebug(entities) << "EntityScriptingInterface::setPoints no entity with ID" << entityID; } - + EntityTypes::EntityType entityType = entity->getType(); - + if (entityType != EntityTypes::Line) { return false; } - + auto now = usecTimestampNow(); - + LineEntityItem* lineEntity = static_cast(entity.get()); _entityTree->lockForWrite(); bool success = actor(*lineEntity); entity->setLastEdited(now); entity->setLastBroadcast(now); _entityTree->unlock(); - + _entityTree->lockForRead(); EntityItemProperties properties = entity->getProperties(); _entityTree->unlock(); - + properties.setLinePointsDirty(); properties.setLastEdited(now); - - + + queueEntityMessage(PacketTypeEntityEdit, entityID, properties); return success; } @@ -509,7 +517,6 @@ bool EntityScriptingInterface::appendPoint(QUuid entityID, const glm::vec3& poin { return lineEntity.appendPoint(point); }); - } @@ -537,6 +544,16 @@ bool EntityScriptingInterface::actionWorker(const QUuid& entityID, bool success = actor(simulation, entity); _entityTree->unlock(); + + // transmit the change + _entityTree->lockForRead(); + EntityItemProperties properties = entity->getProperties(); + _entityTree->unlock(); + properties.setActionDataDirty(); + auto now = usecTimestampNow(); + properties.setLastEdited(now); + queueEntityMessage(PacketTypeEntityEdit, entityID, properties); + return success; } @@ -557,7 +574,14 @@ QUuid EntityScriptingInterface::addAction(const QString& actionTypeString, if (actionType == ACTION_TYPE_NONE) { return false; } - if (actionFactory->factory(simulation, actionType, actionID, entity, arguments)) { + EntityActionPointer action = actionFactory->factory(simulation, actionType, actionID, entity, arguments); + if (action) { + entity->addAction(simulation, action); + auto nodeList = DependencyManager::get(); + const QUuid myNodeID = nodeList->getSessionUUID(); + if (entity->getSimulatorID() != myNodeID) { + entity->flagForOwnership(); + } return true; } return false; @@ -571,13 +595,39 @@ QUuid EntityScriptingInterface::addAction(const QString& actionTypeString, bool EntityScriptingInterface::updateAction(const QUuid& entityID, const QUuid& actionID, const QVariantMap& arguments) { return actionWorker(entityID, [&](EntitySimulation* simulation, EntityItemPointer entity) { - return entity->updateAction(simulation, actionID, arguments); + bool success = entity->updateAction(simulation, actionID, arguments); + if (success) { + auto nodeList = DependencyManager::get(); + const QUuid myNodeID = nodeList->getSessionUUID(); + if (entity->getSimulatorID() != myNodeID) { + entity->flagForOwnership(); + } + } + return success; }); } - bool EntityScriptingInterface::deleteAction(const QUuid& entityID, const QUuid& actionID) { return actionWorker(entityID, [&](EntitySimulation* simulation, EntityItemPointer entity) { return entity->removeAction(simulation, actionID); }); } + +QVector EntityScriptingInterface::getActionIDs(const QUuid& entityID) { + QVector result; + actionWorker(entityID, [&](EntitySimulation* simulation, EntityItemPointer entity) { + QList actionIDs = entity->getActionIDs(); + result = QVector::fromList(actionIDs); + return true; + }); + return result; +} + +QVariantMap EntityScriptingInterface::getActionArguments(const QUuid& entityID, const QUuid& actionID) { + QVariantMap result; + actionWorker(entityID, [&](EntitySimulation* simulation, EntityItemPointer entity) { + result = entity->getActionArguments(actionID); + return true; + }); + return result; +} diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 12c8688816..5c1e4141a6 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -122,7 +122,7 @@ public slots: Q_INVOKABLE bool setVoxelSphere(QUuid entityID, const glm::vec3& center, float radius, int value); Q_INVOKABLE bool setVoxel(QUuid entityID, const glm::vec3& position, int value); Q_INVOKABLE bool setAllVoxels(QUuid entityID, int value); - + Q_INVOKABLE bool setAllPoints(QUuid entityID, const QVector& points); Q_INVOKABLE bool appendPoint(QUuid entityID, const glm::vec3& point); @@ -131,6 +131,8 @@ public slots: Q_INVOKABLE QUuid addAction(const QString& actionTypeString, const QUuid& entityID, const QVariantMap& arguments); Q_INVOKABLE bool updateAction(const QUuid& entityID, const QUuid& actionID, const QVariantMap& arguments); Q_INVOKABLE bool deleteAction(const QUuid& entityID, const QUuid& actionID); + Q_INVOKABLE QVector getActionIDs(const QUuid& entityID); + Q_INVOKABLE QVariantMap getActionArguments(const QUuid& entityID, const QUuid& actionID); signals: void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); @@ -165,7 +167,7 @@ private: void queueEntityMessage(PacketType packetType, EntityItemID entityID, const EntityItemProperties& properties); /// actually does the work of finding the ray intersection, can be called in locking mode or tryLock mode - RayToEntityIntersectionResult findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType, + RayToEntityIntersectionResult findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType, bool precisionPicking); EntityTree* _entityTree; diff --git a/libraries/entities/src/EntitySimulation.cpp b/libraries/entities/src/EntitySimulation.cpp index c13ea31063..a2d20fe5d5 100644 --- a/libraries/entities/src/EntitySimulation.cpp +++ b/libraries/entities/src/EntitySimulation.cpp @@ -260,3 +260,28 @@ void EntitySimulation::moveSimpleKinematics(const quint64& now) { } } } + +void EntitySimulation::addAction(EntityActionPointer action) { + lock(); + _actionsToAdd += action; + unlock(); +} + +void EntitySimulation::removeAction(const QUuid actionID) { + lock(); + _actionsToRemove += actionID; + unlock(); +} + +void EntitySimulation::removeActions(QList actionIDsToRemove) { + lock(); + _actionsToRemove += actionIDsToRemove; + unlock(); +} + +void EntitySimulation::applyActionChanges() { + lock(); + _actionsToAdd.clear(); + _actionsToRemove.clear(); + unlock(); +} diff --git a/libraries/entities/src/EntitySimulation.h b/libraries/entities/src/EntitySimulation.h index 7d244086e5..c1822abe77 100644 --- a/libraries/entities/src/EntitySimulation.h +++ b/libraries/entities/src/EntitySimulation.h @@ -57,10 +57,10 @@ public: friend class EntityTree; - virtual void addAction(EntityActionPointer action) { _actionsToAdd += action; } - virtual void removeAction(const QUuid actionID) { _actionsToRemove += actionID; } - virtual void removeActions(QList actionIDsToRemove) { _actionsToRemove += actionIDsToRemove; } - virtual void applyActionChanges() { _actionsToAdd.clear(); _actionsToRemove.clear(); } + virtual void addAction(EntityActionPointer action); + virtual void removeAction(const QUuid actionID); + virtual void removeActions(QList actionIDsToRemove); + virtual void applyActionChanges(); protected: // these only called by the EntityTree? /// \param entity pointer to EntityItem to be added diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 6a652d609b..d2b94b3267 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -23,6 +23,7 @@ #include "QVariantGLM.h" #include "EntitiesLogging.h" #include "RecurseOctreeToMapOperator.h" +#include "LogHandler.h" const quint64 SIMULATOR_CHANGE_LOCKOUT_PERIOD = (quint64)(0.2f * USECS_PER_SECOND); @@ -150,23 +151,34 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI } else { if (getIsServer()) { bool simulationBlocked = !entity->getSimulatorID().isNull(); - if (properties.simulatorIDChanged()) { - QUuid submittedID = properties.getSimulatorID(); + if (properties.simulationOwnerChanged()) { + QUuid submittedID = properties.getSimulationOwner().getID(); // a legit interface will only submit their own ID or NULL: if (submittedID.isNull()) { if (entity->getSimulatorID() == senderID) { // We only allow the simulation owner to clear their own simulationID's. simulationBlocked = false; + properties.clearSimulationOwner(); // clear everything } // else: We assume the sender really did believe it was the simulation owner when it sent } else if (submittedID == senderID) { // the sender is trying to take or continue ownership - if (entity->getSimulatorID().isNull() || entity->getSimulatorID() == senderID) { + if (entity->getSimulatorID().isNull()) { + // the sender it taking ownership + properties.promoteSimulationPriority(RECRUIT_SIMULATION_PRIORITY); + simulationBlocked = false; + } else if (entity->getSimulatorID() == senderID) { + // the sender is asserting ownership simulationBlocked = false; } else { // the sender is trying to steal ownership from another simulator - // so we apply the ownership change filter - if (usecTimestampNow() - entity->getSimulatorIDChangedTime() > SIMULATOR_CHANGE_LOCKOUT_PERIOD) { + // so we apply the rules for ownership change: + // (1) higher priority wins + // (2) equal priority wins if ownership filter has expired except... + uint8_t oldPriority = entity->getSimulationPriority(); + uint8_t newPriority = properties.getSimulationOwner().getPriority(); + if (newPriority > oldPriority || + (newPriority == oldPriority && properties.getSimulationOwner().hasExpired())) { simulationBlocked = false; } } @@ -174,12 +186,17 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI // the entire update is suspect --> ignore it return false; } + } else { + simulationBlocked = senderID != entity->getSimulatorID(); } if (simulationBlocked) { - // squash the physics-related changes. - properties.setSimulatorIDChanged(false); + // squash ownership and physics-related changes. + properties.setSimulationOwnerChanged(false); properties.setPositionChanged(false); properties.setRotationChanged(false); + properties.setVelocityChanged(false); + properties.setAngularVelocityChanged(false); + properties.setAccelerationChanged(false); } } // else client accepts what the server says @@ -636,6 +653,8 @@ int EntityTree::processEditPacketData(PacketType packetType, const unsigned char << "] attempted to add an entity."; } } else { + static QString repeatedMessage = + LogHandler::getInstance().addRepeatedMessageRegex("^Add or Edit failed.*"); qCDebug(entities) << "Add or Edit failed." << packetType << existingEntity.get(); } } diff --git a/libraries/entities/src/EntityTypes.h b/libraries/entities/src/EntityTypes.h index 323a4eb92b..013f1064bd 100644 --- a/libraries/entities/src/EntityTypes.h +++ b/libraries/entities/src/EntityTypes.h @@ -21,6 +21,7 @@ class EntityItem; typedef std::shared_ptr EntityItemPointer; +typedef std::weak_ptr EntityItemWeakPointer; inline uint qHash(const EntityItemPointer& a, uint seed) { return qHash(a.get(), seed); diff --git a/libraries/entities/src/LineEntityItem.cpp b/libraries/entities/src/LineEntityItem.cpp index fccf57c7c7..067b1d8fee 100644 --- a/libraries/entities/src/LineEntityItem.cpp +++ b/libraries/entities/src/LineEntityItem.cpp @@ -107,7 +107,7 @@ bool LineEntityItem::setLinePoints(const QVector& points) { } for (int i = 0; i < points.size(); i++) { glm::vec3 point = points.at(i); - glm::vec3 pos = getPosition(); + // glm::vec3 pos = getPosition(); glm::vec3 halfBox = getDimensions() * 0.5f; if ( (point.x < - halfBox.x || point.x > halfBox.x) || (point.y < -halfBox.y || point.y > halfBox.y) || (point.z < - halfBox.z || point.z > halfBox.z) ) { qDebug() << "Point is outside entity's bounding box"; diff --git a/libraries/entities/src/SimpleEntitySimulation.cpp b/libraries/entities/src/SimpleEntitySimulation.cpp index 8dedbd2162..81d9445f92 100644 --- a/libraries/entities/src/SimpleEntitySimulation.cpp +++ b/libraries/entities/src/SimpleEntitySimulation.cpp @@ -23,18 +23,19 @@ void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) { // has finished simulating it. auto nodeList = DependencyManager::get(); - SetOfEntities::iterator itemItr = _hasSimulationOwnerEntities.begin(); - while (itemItr != _hasSimulationOwnerEntities.end()) { + SetOfEntities::iterator itemItr = _entitiesWithSimulator.begin(); + while (itemItr != _entitiesWithSimulator.end()) { EntityItemPointer entity = *itemItr; if (entity->getSimulatorID().isNull()) { - itemItr = _hasSimulationOwnerEntities.erase(itemItr); + itemItr = _entitiesWithSimulator.erase(itemItr); } else if (now - entity->getLastChangedOnServer() >= AUTO_REMOVE_SIMULATION_OWNER_USEC) { SharedNodePointer ownerNode = nodeList->nodeWithUUID(entity->getSimulatorID()); if (ownerNode.isNull() || !ownerNode->isAlive()) { qCDebug(entities) << "auto-removing simulation owner" << entity->getSimulatorID(); - // TODO: zero velocities when we clear simulatorID? - entity->setSimulatorID(QUuid()); - itemItr = _hasSimulationOwnerEntities.erase(itemItr); + entity->clearSimulationOwnership(); + itemItr = _entitiesWithSimulator.erase(itemItr); + // zero the velocity on this entity so that it doesn't drift far away + entity->setVelocity(glm::vec3(0.0f)); } else { ++itemItr; } @@ -47,23 +48,23 @@ void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) { void SimpleEntitySimulation::addEntityInternal(EntityItemPointer entity) { EntitySimulation::addEntityInternal(entity); if (!entity->getSimulatorID().isNull()) { - _hasSimulationOwnerEntities.insert(entity); + _entitiesWithSimulator.insert(entity); } } void SimpleEntitySimulation::removeEntityInternal(EntityItemPointer entity) { - _hasSimulationOwnerEntities.remove(entity); + _entitiesWithSimulator.remove(entity); } void SimpleEntitySimulation::changeEntityInternal(EntityItemPointer entity) { EntitySimulation::changeEntityInternal(entity); if (!entity->getSimulatorID().isNull()) { - _hasSimulationOwnerEntities.insert(entity); + _entitiesWithSimulator.insert(entity); } entity->clearDirtyFlags(); } void SimpleEntitySimulation::clearEntitiesInternal() { - _hasSimulationOwnerEntities.clear(); + _entitiesWithSimulator.clear(); } diff --git a/libraries/entities/src/SimpleEntitySimulation.h b/libraries/entities/src/SimpleEntitySimulation.h index 6eb3980dd3..fff0659067 100644 --- a/libraries/entities/src/SimpleEntitySimulation.h +++ b/libraries/entities/src/SimpleEntitySimulation.h @@ -28,7 +28,7 @@ protected: virtual void changeEntityInternal(EntityItemPointer entity); virtual void clearEntitiesInternal(); - SetOfEntities _hasSimulationOwnerEntities; + SetOfEntities _entitiesWithSimulator; }; #endif // hifi_SimpleEntitySimulation_h diff --git a/libraries/entities/src/SimulationOwner.cpp b/libraries/entities/src/SimulationOwner.cpp new file mode 100644 index 0000000000..d6957873e2 --- /dev/null +++ b/libraries/entities/src/SimulationOwner.cpp @@ -0,0 +1,181 @@ +// +// SimulationOwner.cpp +// libraries/entities/src +// +// Created by Andrew Meadows on 2015.06.19 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "SimulationOwner.h" + +#include // included for tests +#include + +#include + +// static +const int SimulationOwner::NUM_BYTES_ENCODED = NUM_BYTES_RFC4122_UUID + 1; + + +SimulationOwner::SimulationOwner(const SimulationOwner& other) + : _id(other._id), _priority(other._priority), _expiry(other._expiry) { +} + +QByteArray SimulationOwner::toByteArray() const { + QByteArray data = _id.toRfc4122(); + data.append(_priority); + return data; +} + +bool SimulationOwner::fromByteArray(const QByteArray& data) { + if (data.size() == NUM_BYTES_ENCODED) { + QByteArray idBytes = data.left(NUM_BYTES_RFC4122_UUID); + _id = QUuid::fromRfc4122(idBytes); + _priority = data[NUM_BYTES_RFC4122_UUID]; + return true; + } + return false; +} + +void SimulationOwner::clear() { + _id = QUuid(); + _priority = 0; + _expiry = 0; +} + +void SimulationOwner::setPriority(quint8 priority) { + _priority = priority; + if (_priority == 0) { + // when priority is zero we clear everything + _expiry = 0; + _id = QUuid(); + } +} + +void SimulationOwner::promotePriority(quint8 priority) { + if (priority > _priority) { + _priority = priority; + updateExpiry(); + } +} + +bool SimulationOwner::setID(const QUuid& id) { + if (_id != id) { + _id = id; + updateExpiry(); + if (_id.isNull()) { + _priority = 0; + } + return true; + } + return false; +} + +bool SimulationOwner::set(const QUuid& id, quint8 priority) { + setPriority(priority); + return setID(id); +} + +bool SimulationOwner::set(const SimulationOwner& owner) { + setPriority(owner._priority); + return setID(owner._id); +} + +void SimulationOwner::updateExpiry() { + const quint64 OWNERSHIP_LOCKOUT_EXPIRY = USECS_PER_SECOND / 5; + _expiry = usecTimestampNow() + OWNERSHIP_LOCKOUT_EXPIRY; +} + +// NOTE: eventually this code will be moved into unit tests +// static debug +void SimulationOwner::test() { + { // test default constructor + SimulationOwner simOwner; + if (!simOwner.isNull()) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : SimulationOwner should be NULL" << std::endl; + } + + if (simOwner.getPriority() != 0) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : unexpeced SimulationOwner priority" << std::endl; + } + } + + { // test set constructor + QUuid id = QUuid::createUuid(); + quint8 priority = 128; + SimulationOwner simOwner(id, priority); + if (simOwner.isNull()) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : SimulationOwner should NOT be NULL" << std::endl; + } + + if (simOwner.getID() != id) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : SimulationOwner with unexpected id" << std::endl; + } + + if (simOwner.getPriority() != priority) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : unexpeced SimulationOwner priority" << std::endl; + } + + QUuid otherID = QUuid::createUuid(); + if (simOwner.getID() == otherID) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : SimulationOwner with unexpected id" << std::endl; + } + } + + { // test set() + QUuid id = QUuid::createUuid(); + quint8 priority = 1; + SimulationOwner simOwner; + simOwner.set(id, priority); + if (simOwner.isNull()) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : SimulationOwner should NOT be NULL" << std::endl; + } + + if (simOwner.getID() != id) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : SimulationOwner with unexpected id" << std::endl; + } + + if (simOwner.getPriority() != priority) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : unexpeced SimulationOwner priority" << std::endl; + } + } + + { // test encode/decode + SimulationOwner ownerA(QUuid::createUuid(), 1); + SimulationOwner ownerB(QUuid::createUuid(), 2); + + QByteArray data = ownerA.toByteArray(); + ownerB.fromByteArray(data); + + if (ownerA.getID() != ownerB.getID()) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : ownerA._id should be equal to ownerB._id" << std::endl; + } + } +} + +bool SimulationOwner::operator!=(const SimulationOwner& other) { + return (_id != other._id && _priority != other._priority); +} + +SimulationOwner& SimulationOwner::operator=(const SimulationOwner& other) { + _priority = other._priority; + if (_priority == 0) { + _id = QUuid(); + _expiry = 0; + } else { + if (_id != other._id) { + updateExpiry(); + } + _id = other._id; + } + return *this; +} + +QDebug& operator<<(QDebug& d, const SimulationOwner& simOwner) { + d << "{ id : " << simOwner._id << ", priority : " << (int)simOwner._priority << " }"; + return d; +} + diff --git a/libraries/entities/src/SimulationOwner.h b/libraries/entities/src/SimulationOwner.h new file mode 100644 index 0000000000..a848ad6e84 --- /dev/null +++ b/libraries/entities/src/SimulationOwner.h @@ -0,0 +1,88 @@ +// +// SimulationOwner.h +// libraries/entities/src +// +// Created by Andrew Meadows on 2015.06.19 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_SimulationOwner_h +#define hifi_SimulationOwner_h + +#include +#include + +#include +#include + +const quint8 NO_PRORITY = 0x00; + +// Simulation observers will bid to simulate unowned active objects at the lowest possible priority +// which is VOLUNTEER. If the server accepts a VOLUNTEER bid it will automatically bump it +// to RECRUIT priority so that other volunteers don't accidentally take over. +const quint8 VOLUNTEER_SIMULATION_PRIORITY = 0x01; +const quint8 RECRUIT_SIMULATION_PRIORITY = VOLUNTEER_SIMULATION_PRIORITY + 1; + +// When poking objects with scripts an observer will bid at SCRIPT_EDIT priority. +const quint8 SCRIPT_EDIT_SIMULATION_PRIORITY = 0x80; + +// PERSONAL priority (needs a better name) is the level at which a simulation observer will bid for +// objects that collide its MyAvatar. +const quint8 PERSONAL_SIMULATION_PRIORITY = SCRIPT_EDIT_SIMULATION_PRIORITY - 1; + + +class SimulationOwner { +public: + static const int NUM_BYTES_ENCODED; + + SimulationOwner() : _id(), _priority(0), _expiry(0) {} + SimulationOwner(const QUuid& id, quint8 priority) : _id(id), _priority(priority), _expiry(0) {} + SimulationOwner(const SimulationOwner& other); + + const QUuid& getID() const { return _id; } + quint8 getPriority() const { return _priority; } + const quint64& getExpiry() const { return _expiry; } + + QByteArray toByteArray() const; + bool fromByteArray(const QByteArray& data); + + void clear(); + + void setPriority(quint8 priority); + void promotePriority(quint8 priority); + + // return true if id is changed + bool setID(const QUuid& id); + bool set(const QUuid& id, quint8 priority); + bool set(const SimulationOwner& owner); + + bool isNull() const { return _id.isNull(); } + bool matchesValidID(const QUuid& id) const { return _id == id && !_id.isNull(); } + + void updateExpiry(); + + bool hasExpired() const { return usecTimestampNow() > _expiry; } + + bool operator>=(quint8 priority) const { return _priority >= priority; } + bool operator==(const SimulationOwner& other) { return (_id == other._id && _priority == other._priority); } + + bool operator!=(const SimulationOwner& other); + SimulationOwner& operator=(const SimulationOwner& other); + + friend QDebug& operator<<(QDebug& d, const SimulationOwner& simOwner); + + // debug + static void test(); + +private: + QUuid _id; + quint8 _priority; + quint64 _expiry; +}; + + + +#endif // hifi_SimulationOwner_h diff --git a/libraries/gpu/src/gpu/GLBackendPipeline.cpp b/libraries/gpu/src/gpu/GLBackendPipeline.cpp index e4dd90cac0..a770cf89b3 100755 --- a/libraries/gpu/src/gpu/GLBackendPipeline.cpp +++ b/libraries/gpu/src/gpu/GLBackendPipeline.cpp @@ -174,6 +174,10 @@ void GLBackend::do_setUniformBuffer(Batch& batch, uint32 paramOffset) { GLuint bo = getBufferID(*uniformBuffer); glBindBufferRange(GL_UNIFORM_BUFFER, slot, bo, rangeStart, rangeSize); #else + // because we rely on the program uniform mechanism we need to have + // the program bound, thank you MacOSX Legacy profile. + updatePipeline(); + GLfloat* data = (GLfloat*) (uniformBuffer->getData() + rangeStart); glUniform4fv(slot, rangeSize / sizeof(GLfloat[4]), data); diff --git a/libraries/gpu/src/gpu/State.h b/libraries/gpu/src/gpu/State.h index c9bd38efeb..dce9e50488 100755 --- a/libraries/gpu/src/gpu/State.h +++ b/libraries/gpu/src/gpu/State.h @@ -118,7 +118,7 @@ public: uint8 _function = LESS; uint8 _writeMask = true; uint8 _enabled = false; - uint8 _spare; + uint8 _spare = 0; public: DepthTest(bool enabled = false, bool writeMask = true, ComparisonFunction func = LESS) : _function(func), _writeMask(writeMask), _enabled(enabled) {} diff --git a/libraries/networking/src/JSONBreakableMarshal.cpp b/libraries/networking/src/JSONBreakableMarshal.cpp index ca56df52ad..d71d173947 100644 --- a/libraries/networking/src/JSONBreakableMarshal.cpp +++ b/libraries/networking/src/JSONBreakableMarshal.cpp @@ -1,5 +1,5 @@ // -// JSONBreakableMarshal.cpp +// JSONBreakableMarshal.cpp // libraries/networking/src // // Created by Stephen Birarda on 04/28/15. @@ -25,26 +25,26 @@ QStringList JSONBreakableMarshal::toStringList(const QJsonValue& jsonValue, cons if (jsonValue.isObject()) { QJsonObject jsonObject = jsonValue.toObject(); - + // enumerate the keys of the QJsonObject foreach(const QString& key, jsonObject.keys()) { QJsonValue childValue = jsonObject[key]; // setup the keypath for this key QString valueKeypath = (keypath.isEmpty() ? "" : keypath + ".") + key; - + if (childValue.isObject() || childValue.isArray()) { // recursion is required since the value is a QJsonObject or QJsonArray result << toStringList(childValue, valueKeypath); } else { // no recursion required, call our toString method to get the string representation // append the QStringList resulting from that to our QStringList - result << toString(childValue, valueKeypath); + result << toString(childValue, valueKeypath); } - } + } } else if (jsonValue.isArray()) { QJsonArray jsonArray = jsonValue.toArray(); - + // enumerate the elements in this QJsonArray for (int i = 0; i < jsonArray.size(); i++) { QJsonValue arrayValue = jsonArray[i]; @@ -77,8 +77,8 @@ const QString JSON_UNKNOWN_AS_STRING = "unknown"; QString JSONBreakableMarshal::toString(const QJsonValue& jsonValue, const QString& keypath) { // default the value as a string to unknown in case conversion fails QString valueAsString = JSON_UNKNOWN_AS_STRING; - - // as the QJsonValue what type it is and format its value as a string accordingly + + // ask the QJsonValue what type it is and format its value as a string accordingly if (jsonValue.isNull()) { valueAsString = JSON_NULL_AS_STRING; } else if (jsonValue.isBool()) { @@ -90,7 +90,7 @@ QString JSONBreakableMarshal::toString(const QJsonValue& jsonValue, const QStrin } else if (jsonValue.isUndefined()) { valueAsString = JSON_UNDEFINED_AS_STRING; } else if (jsonValue.isArray() || jsonValue.isObject()) { - qDebug() << "JSONBreakableMarshal::toString does not handle conversion of a QJsonObject or QJsonArray." + qDebug() << "JSONBreakableMarshal::toString does not handle conversion of a QJsonObject or QJsonArray." << "You should call JSONBreakableMarshal::toStringList instead."; } else { qDebug() << "Unrecognized QJsonValue - JSONBreakableMarshal cannot convert to string."; @@ -102,14 +102,14 @@ QString JSONBreakableMarshal::toString(const QJsonValue& jsonValue, const QStrin QVariant JSONBreakableMarshal::fromString(const QString& marshalValue) { // default the value to null QVariant result; - + // attempt to match the value with our expected strings if (marshalValue == JSON_NULL_AS_STRING) { // this is already our default, we don't need to do anything here } else if (marshalValue == JSON_TRUE_AS_STRING || marshalValue == JSON_FALSE_AS_STRING) { result = QVariant(marshalValue == JSON_TRUE_AS_STRING ? true : false); } else if (marshalValue == JSON_UNDEFINED_AS_STRING) { - result = JSON_UNDEFINED_AS_STRING; + result = JSON_UNDEFINED_AS_STRING; } else if (marshalValue == JSON_UNKNOWN_AS_STRING) { // we weren't able to marshal this value at the other end, set it as our unknown string result = JSON_UNKNOWN_AS_STRING; @@ -125,14 +125,15 @@ QVariant JSONBreakableMarshal::fromString(const QString& marshalValue) { // use a regex to look for surrounding quotes first const QString JSON_STRING_REGEX = "^\"([\\s\\S]*)\"$"; QRegExp stringRegex(JSON_STRING_REGEX); - + if (stringRegex.indexIn(marshalValue) != -1) { // set the result to the string value result = stringRegex.cap(1); } else { - // we failed to convert the value to anything, set the result to our unknown value - qDebug() << "Unrecognized output from JSONBreakableMarshal - could not convert" << marshalValue << "to QVariant."; - result = JSON_UNKNOWN_AS_STRING; + // we failed to convert the value to anything, set the result to our unknown value + qDebug() << "Unrecognized output from JSONBreakableMarshal - could not convert" + << marshalValue << "to QVariant."; + result = JSON_UNKNOWN_AS_STRING; } } } @@ -144,14 +145,14 @@ QVariantMap JSONBreakableMarshal::fromStringList(const QStringList& stringList) QVariant result = QVariantMap(); foreach(const QString& marshalString, stringList) { - + // find the equality operator int equalityIndex = marshalString.indexOf('='); - + // bail on parsing if we didn't find the equality operator if (equalityIndex != -1) { - - QVariant* currentValue = &result; + + QVariant* currentValue = &result; // pull the key (everything left of the equality sign) QString parentKeypath; @@ -160,7 +161,7 @@ QVariantMap JSONBreakableMarshal::fromStringList(const QStringList& stringList) // setup for array index checking const QString ARRAY_INDEX_REGEX_STRING = "\\[(\\d+)\\]"; QRegExp arrayRegex(ARRAY_INDEX_REGEX_STRING); - + // as long as we have a keypath we need to recurse downwards while (!keypath.isEmpty()) { parentKeypath = keypath; @@ -176,7 +177,7 @@ QVariantMap JSONBreakableMarshal::fromStringList(const QStringList& stringList) } QVariantList& currentList = *static_cast(currentValue->data()); - + // figure out what index we want to get the QJsonValue& for bool didConvert = false; int arrayIndex = arrayRegex.cap(1).toInt(&didConvert); @@ -187,7 +188,7 @@ QVariantMap JSONBreakableMarshal::fromStringList(const QStringList& stringList) for (int i = currentList.size(); i < arrayIndex + 1; i++) { // add the null QJsonValue at this array index to get the array to the right size - currentList.push_back(QJsonValue()); + currentList.push_back(QJsonValue()); } } @@ -196,7 +197,7 @@ QVariantMap JSONBreakableMarshal::fromStringList(const QStringList& stringList) // update the keypath by bumping past the array index keypath = keypath.mid(keypath.indexOf(']') + 1); - + // check if there is a key after the array index - if so push the keypath forward by a char if (keypath.startsWith(".")) { keypath = keypath.mid(1); @@ -209,7 +210,7 @@ QVariantMap JSONBreakableMarshal::fromStringList(const QStringList& stringList) } else { int keySeparatorIndex = keypath.indexOf('.'); - + // we need to figure out what the key to look at is QString subKey = keypath; @@ -219,20 +220,20 @@ QVariantMap JSONBreakableMarshal::fromStringList(const QStringList& stringList) if (arrayBracketIndex == -1 || (keySeparatorIndex != -1 && keySeparatorIndex < arrayBracketIndex)) { nextBreakIndex = keySeparatorIndex; - nextKeypathStartIndex = keySeparatorIndex + 1; - } else if (keySeparatorIndex == -1 || (arrayBracketIndex != -1 + nextKeypathStartIndex = keySeparatorIndex + 1; + } else if (keySeparatorIndex == -1 || (arrayBracketIndex != -1 && arrayBracketIndex < keySeparatorIndex)) { nextBreakIndex = arrayBracketIndex; nextKeypathStartIndex = arrayBracketIndex; } else { - qDebug() << "Unrecognized key format while trying to parse " << keypath << " - will not add" + qDebug() << "Unrecognized key format while trying to parse " << keypath << " - will not add" << "value to resulting QJsonObject."; break; } - + // set the current key from the determined index subKey = keypath.left(nextBreakIndex); - + // update the keypath being processed keypath = keypath.mid(nextKeypathStartIndex); @@ -244,11 +245,11 @@ QVariantMap JSONBreakableMarshal::fromStringList(const QStringList& stringList) // we're here becuase we know the current value should be an object // if it isn't then make it one - + if (!currentValue->canConvert(QMetaType::QVariantMap) || currentValue->isNull()) { *currentValue = QVariantMap(); } - + QVariantMap& currentMap = *static_cast(currentValue->data()); // is there a QJsonObject for this key yet? @@ -256,19 +257,19 @@ QVariantMap JSONBreakableMarshal::fromStringList(const QStringList& stringList) if (!currentMap.contains(subKey)) { currentMap[subKey] = QVariant(); } - + // change the currentValue to the QJsonValue for this key currentValue = ¤tMap[subKey]; } } *currentValue = fromString(marshalString.mid(equalityIndex + 1)); - + if (_interpolationMap.contains(parentKeypath)) { // we expect the currentValue here to be a string, that's the key we use for interpolation // bail if it isn't - if (currentValue->canConvert(QMetaType::QString) - && _interpolationMap[parentKeypath].canConvert(QMetaType::QVariantMap)) { + if (currentValue->canConvert(QMetaType::QString) + && _interpolationMap[parentKeypath].canConvert(QMetaType::QVariantMap)) { *currentValue = _interpolationMap[parentKeypath].toMap()[currentValue->toString()]; } } @@ -283,7 +284,7 @@ QVariantMap JSONBreakableMarshal::fromStringBuffer(const QByteArray& buffer) { QStringList packetList; int currentIndex = 0; int currentSeparator = buffer.indexOf('\0'); - + while (currentIndex < buffer.size() - 1) { packetList << QString::fromUtf8(buffer.mid(currentIndex, currentSeparator)); @@ -291,13 +292,13 @@ QVariantMap JSONBreakableMarshal::fromStringBuffer(const QByteArray& buffer) { // no more separators to be found, break out of here so we're not looping for nothing break; } - + // bump the currentIndex up to the last found separator currentIndex = currentSeparator + 1; // find the index of the next separator, assuming this one wasn't the last one in the packet if (currentSeparator < buffer.size() - 1) { - currentSeparator = buffer.indexOf('\0', currentIndex); + currentSeparator = buffer.indexOf('\0', currentIndex); } } @@ -305,30 +306,30 @@ QVariantMap JSONBreakableMarshal::fromStringBuffer(const QByteArray& buffer) { return fromStringList(packetList); } -void JSONBreakableMarshal::addInterpolationForKey(const QString& rootKey, const QString& interpolationKey, +void JSONBreakableMarshal::addInterpolationForKey(const QString& rootKey, const QString& interpolationKey, const QJsonValue& interpolationValue) { // if there is no map already beneath this key in our _interpolationMap create a QVariantMap there now - + if (!_interpolationMap.contains(rootKey)) { _interpolationMap.insert(rootKey, QVariantMap()); } - + if (_interpolationMap[rootKey].canConvert(QMetaType::QVariantMap)) { QVariantMap& mapForRootKey = *static_cast(_interpolationMap[rootKey].data()); - + mapForRootKey.insert(interpolationKey, QVariant(interpolationValue)); } else { - qDebug() << "JSONBreakableMarshal::addInterpolationForKey could not convert variant at key" << rootKey + qDebug() << "JSONBreakableMarshal::addInterpolationForKey could not convert variant at key" << rootKey << "to a QVariantMap. Can not add interpolation."; } } void JSONBreakableMarshal::removeInterpolationForKey(const QString& rootKey, const QString& interpolationKey) { // make sure the interpolation map contains this root key and that the value is a map - + if (_interpolationMap.contains(rootKey)) { QVariant& rootValue = _interpolationMap[rootKey]; - + if (!rootValue.isNull() && rootValue.canConvert(QMetaType::QVariantMap)) { // remove the value at the interpolationKey static_cast(rootValue.data())->remove(interpolationKey); diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 1d3a0a4397..0903f3613d 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -48,6 +48,8 @@ LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short _localSockAddr(), _publicSockAddr(), _stunSockAddr(STUN_SERVER_HOSTNAME, STUN_SERVER_PORT), + _numCollectedPackets(0), + _numCollectedBytes(0), _packetStatTimer(), _thisNodeCanAdjustLocks(false), _thisNodeCanRez(true) diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp index 5fe9bbbb99..42ee9f3025 100644 --- a/libraries/networking/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -73,7 +73,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketTypeEntityAdd: case PacketTypeEntityEdit: case PacketTypeEntityData: - return VERSION_ENTITIES_SCRIPT_TIMESTAMP_FIX; + return VERSION_ENTITIES_HAVE_SIMULATION_OWNER_AND_ACTIONS_OVER_WIRE; case PacketTypeEntityErase: return 2; case PacketTypeAudioStreamStats: diff --git a/libraries/networking/src/PacketHeaders.h b/libraries/networking/src/PacketHeaders.h index 789675ec00..0283ba3796 100644 --- a/libraries/networking/src/PacketHeaders.h +++ b/libraries/networking/src/PacketHeaders.h @@ -186,5 +186,6 @@ const PacketVersion VERSION_ENTITIES_LINE_POINTS = 29; const PacketVersion VERSION_ENTITIES_FACE_CAMERA = 30; const PacketVersion VERSION_ENTITIES_SCRIPT_TIMESTAMP = 31; const PacketVersion VERSION_ENTITIES_SCRIPT_TIMESTAMP_FIX = 32; +const PacketVersion VERSION_ENTITIES_HAVE_SIMULATION_OWNER_AND_ACTIONS_OVER_WIRE = 33; #endif // hifi_PacketHeaders_h diff --git a/libraries/octree/src/OctreePacketData.h b/libraries/octree/src/OctreePacketData.h index 9476fe024e..e6f86bb861 100644 --- a/libraries/octree/src/OctreePacketData.h +++ b/libraries/octree/src/OctreePacketData.h @@ -247,7 +247,6 @@ public: static int unpackDataFromBytes(const unsigned char* dataBytes, QVector& result); static int unpackDataFromBytes(const unsigned char* dataBytes, QByteArray& result); - private: /// appends raw bytes, might fail if byte would cause packet to be too large bool append(const unsigned char* data, int length); diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 5e9591a031..a975d21c4d 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -26,6 +26,8 @@ static const float ACCELERATION_EQUIVALENT_EPSILON_RATIO = 0.1f; static const quint8 STEPS_TO_DECIDE_BALLISTIC = 4; +const uint32_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 { @@ -58,8 +60,7 @@ bool entityTreeIsLocked() { EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer entity) : ObjectMotionState(shape), _entity(entity), - _sentActive(false), - _numNonMovingUpdates(0), + _sentInactive(true), _lastStep(0), _serverPosition(0.0f), _serverRotation(), @@ -67,9 +68,13 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer _serverAngularVelocity(0.0f), _serverGravity(0.0f), _serverAcceleration(0.0f), + _serverActionData(QByteArray()), + _lastMeasureStep(0), + _lastVelocity(glm::vec3(0.0f)), + _measuredAcceleration(glm::vec3(0.0f)), + _measuredDeltaTime(0.0f), _accelerationNearlyGravityCount(0), - _candidateForOwnership(false), - _loopsSinceOwnershipBid(0), + _nextOwnershipBid(0), _loopsWithoutOwner(0) { _type = MOTIONSTATE_TYPE_ENTITY; @@ -90,31 +95,39 @@ void EntityMotionState::updateServerPhysicsVariables() { _serverVelocity = _entity->getVelocity(); _serverAngularVelocity = _entity->getAngularVelocity(); _serverAcceleration = _entity->getAcceleration(); + _serverActionData = _entity->getActionData(); } // virtual -void EntityMotionState::handleEasyChanges(uint32_t flags) { +void EntityMotionState::handleEasyChanges(uint32_t flags, PhysicsEngine* engine) { assert(entityTreeIsLocked()); updateServerPhysicsVariables(); - ObjectMotionState::handleEasyChanges(flags); + ObjectMotionState::handleEasyChanges(flags, engine); + if (flags & EntityItem::DIRTY_SIMULATOR_ID) { _loopsWithoutOwner = 0; - _candidateForOwnership = 0; - if (_entity->getSimulatorID().isNull() - && !_entity->isMoving() - && _body->isActive()) { + if (_entity->getSimulatorID().isNull()) { + // simulation ownership is being removed // remove the ACTIVATION flag because this object is coming to rest // according to a remote simulation and we don't want to wake it up again flags &= ~EntityItem::DIRTY_PHYSICS_ACTIVATION; + // hint to Bullet that the object is deactivating _body->setActivationState(WANTS_DEACTIVATION); - } else { - auto nodeList = DependencyManager::get(); - const QUuid& sessionID = nodeList->getSessionUUID(); - if (_entity->getSimulatorID() != sessionID) { - _loopsSinceOwnershipBid = 0; + _outgoingPriority = NO_PRORITY; + } else { + _nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; + if (engine->getSessionID() == _entity->getSimulatorID() || _entity->getSimulationPriority() > _outgoingPriority) { + // we own the simulation or our priority looses to remote + _outgoingPriority = NO_PRORITY; } } } + if (flags & EntityItem::DIRTY_SIMULATOR_OWNERSHIP) { + // (DIRTY_SIMULATOR_OWNERSHIP really means "we should bid for ownership with SCRIPT priority") + // we're manipulating this object directly via script, so we artificially + // manipulate the logic to trigger an immediate bid for ownership + setOutgoingPriority(SCRIPT_EDIT_SIMULATION_PRIORITY); + } if ((flags & EntityItem::DIRTY_PHYSICS_ACTIVATION) && !_body->isActive()) { _body->activate(); } @@ -191,13 +204,10 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { if (_entity->getSimulatorID().isNull()) { _loopsWithoutOwner++; - const uint32_t OWNERSHIP_BID_DELAY = 50; - if (_loopsWithoutOwner > OWNERSHIP_BID_DELAY) { + if (_loopsWithoutOwner > LOOPS_FOR_SIMULATION_ORPHAN && usecTimestampNow() > _nextOwnershipBid) { //qDebug() << "Warning -- claiming something I saw moving." << getName(); - _candidateForOwnership = true; + setOutgoingPriority(VOLUNTEER_SIMULATION_PRIORITY); } - } else { - _loopsWithoutOwner = 0; } #ifdef WANT_DEBUG @@ -228,7 +238,7 @@ bool EntityMotionState::isCandidateForOwnership(const QUuid& sessionID) const { return false; } assert(entityTreeIsLocked()); - return _candidateForOwnership || sessionID == _entity->getSimulatorID(); + return _outgoingPriority != NO_PRORITY || sessionID == _entity->getSimulatorID(); } bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { @@ -241,7 +251,8 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { _serverVelocity = bulletToGLM(_body->getLinearVelocity()); _serverAngularVelocity = bulletToGLM(_body->getAngularVelocity()); _lastStep = simulationStep; - _sentActive = false; + _serverActionData = _entity->getActionData(); + _sentInactive = true; return false; } @@ -255,7 +266,7 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { float dt = (float)(numSteps) * PHYSICS_ENGINE_FIXED_SUBSTEP; const float INACTIVE_UPDATE_PERIOD = 0.5f; - if (!_sentActive) { + if (_sentInactive) { // we resend the inactive update every INACTIVE_UPDATE_PERIOD // until it is removed from the outgoing updates // (which happens when we don't own the simulation and it isn't touching our simulation) @@ -275,6 +286,10 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { _serverPosition += dt * _serverVelocity; } + if (_serverActionData != _entity->getActionData()) { + return true; + } + // 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. @@ -311,7 +326,8 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { // Bullet caps the effective rotation velocity inside its rotation integration step, therefore // we must integrate with the same algorithm and timestep in order achieve similar results. for (int i = 0; i < numSteps; ++i) { - _serverRotation = glm::normalize(computeBulletRotationStep(_serverAngularVelocity, PHYSICS_ENGINE_FIXED_SUBSTEP) * _serverRotation); + _serverRotation = glm::normalize(computeBulletRotationStep(_serverAngularVelocity, + PHYSICS_ENGINE_FIXED_SUBSTEP) * _serverRotation); } } const float MIN_ROTATION_DOT = 0.99999f; // This corresponds to about 0.5 degrees of rotation @@ -343,30 +359,20 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep, const QUuid& s assert(_body); assert(entityTreeIsLocked()); - if (!remoteSimulationOutOfSync(simulationStep)) { - _candidateForOwnership = false; + if (_entity->getSimulatorID() != sessionID) { + // we don't own the simulation, but maybe we should... + if (_outgoingPriority != NO_PRORITY) { + if (_outgoingPriority < _entity->getSimulationPriority()) { + // our priority looses to remote, so we don't bother to bid + _outgoingPriority = NO_PRORITY; + return false; + } + return usecTimestampNow() > _nextOwnershipBid; + } return false; } - if (_entity->getSimulatorID() == sessionID) { - // we own the simulation - _candidateForOwnership = false; - return true; - } - - const uint32_t FRAMES_BETWEEN_OWNERSHIP_CLAIMS = 30; - if (_candidateForOwnership) { - _candidateForOwnership = false; - ++_loopsSinceOwnershipBid; - if (_loopsSinceOwnershipBid > FRAMES_BETWEEN_OWNERSHIP_CLAIMS) { - // we don't own the simulation, but it's time to bid for it - _loopsSinceOwnershipBid = 0; - return true; - } - } - - _candidateForOwnership = false; - return false; + return remoteSimulationOutOfSync(simulationStep); } void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const QUuid& sessionID, uint32_t step) { @@ -380,7 +386,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q _entity->setVelocity(zero); _entity->setAngularVelocity(zero); _entity->setAcceleration(zero); - _sentActive = false; + _sentInactive = true; } else { float gravityLength = glm::length(_entity->getGravity()); float accVsGravity = glm::abs(glm::length(_measuredAcceleration) - gravityLength); @@ -406,9 +412,12 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q const float DYNAMIC_LINEAR_VELOCITY_THRESHOLD = 0.05f; // 5 cm/sec const float DYNAMIC_ANGULAR_VELOCITY_THRESHOLD = 0.087266f; // ~5 deg/sec - bool movingSlowly = glm::length2(_entity->getVelocity()) < (DYNAMIC_LINEAR_VELOCITY_THRESHOLD * DYNAMIC_LINEAR_VELOCITY_THRESHOLD) - && glm::length2(_entity->getAngularVelocity()) < (DYNAMIC_ANGULAR_VELOCITY_THRESHOLD * DYNAMIC_ANGULAR_VELOCITY_THRESHOLD) - && _entity->getAcceleration() == glm::vec3(0.0f); + + bool movingSlowlyLinear = + glm::length2(_entity->getVelocity()) < (DYNAMIC_LINEAR_VELOCITY_THRESHOLD * DYNAMIC_LINEAR_VELOCITY_THRESHOLD); + bool movingSlowlyAngular = glm::length2(_entity->getAngularVelocity()) < + (DYNAMIC_ANGULAR_VELOCITY_THRESHOLD * DYNAMIC_ANGULAR_VELOCITY_THRESHOLD); + bool movingSlowly = movingSlowlyLinear && movingSlowlyAngular && _entity->getAcceleration() == glm::vec3(0.0f); if (movingSlowly) { // velocities might not be zero, but we'll fake them as such, which will hopefully help convince @@ -417,7 +426,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q _entity->setVelocity(zero); _entity->setAngularVelocity(zero); } - _sentActive = true; + _sentInactive = false; } // remember properties for local server prediction @@ -426,8 +435,9 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q _serverVelocity = _entity->getVelocity(); _serverAcceleration = _entity->getAcceleration(); _serverAngularVelocity = _entity->getAngularVelocity(); + _serverActionData = _entity->getActionData(); - EntityItemProperties properties = _entity->getProperties(); + EntityItemProperties properties; // explicitly set the properties that changed so that they will be packed properties.setPosition(_serverPosition); @@ -435,6 +445,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q properties.setVelocity(_serverVelocity); properties.setAcceleration(_serverAcceleration); properties.setAngularVelocity(_serverAngularVelocity); + properties.setActionData(_serverActionData); // set the LastEdited of the properties but NOT the entity itself quint64 now = usecTimestampNow(); @@ -453,14 +464,14 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q if (!active) { // we own the simulation but the entity has stopped, so we tell the server that we're clearing simulatorID // but we remember that we do still own it... and rely on the server to tell us that we don't - properties.setSimulatorID(QUuid()); - } else { - // explicitly set the property's simulatorID so that it is flagged as changed and will be packed - properties.setSimulatorID(sessionID); + properties.clearSimulationOwner(); + _outgoingPriority = NO_PRORITY; } + // else the ownership is not changing so we don't bother to pack it } else { // we don't own the simulation for this entity yet, but we're sending a bid for it - properties.setSimulatorID(sessionID); + properties.setSimulationOwner(sessionID, glm::max(_outgoingPriority, VOLUNTEER_SIMULATION_PRIORITY)); + _nextOwnershipBid = now + USECS_BETWEEN_OWNERSHIP_BIDS; } if (EntityItem::getSendPhysicsUpdates()) { @@ -498,6 +509,13 @@ uint32_t EntityMotionState::getAndClearIncomingDirtyFlags() { return dirtyFlags; } +// virtual +quint8 EntityMotionState::getSimulationPriority() const { + if (_entity) { + return _entity->getSimulationPriority(); + } + return NO_PRORITY; +} // virtual QUuid EntityMotionState::getSimulatorID() const { @@ -508,10 +526,11 @@ QUuid EntityMotionState::getSimulatorID() const { return QUuid(); } - // virtual -void EntityMotionState::bump() { - _candidateForOwnership = true; +void EntityMotionState::bump(quint8 priority) { + if (_entity) { + setOutgoingPriority(glm::max(VOLUNTEER_SIMULATION_PRIORITY, --priority)); + } } void EntityMotionState::resetMeasuredBodyAcceleration() { @@ -539,9 +558,10 @@ void EntityMotionState::measureBodyAcceleration() { glm::vec3 velocity = bulletToGLM(_body->getLinearVelocity()); _measuredAcceleration = (velocity / powf(1.0f - _body->getLinearDamping(), dt) - _lastVelocity) * invDt; _lastVelocity = velocity; - if (numSubsteps > PHYSICS_ENGINE_MAX_NUM_SUBSTEPS && !_candidateForOwnership) { - _loopsSinceOwnershipBid = 0; + if (numSubsteps > PHYSICS_ENGINE_MAX_NUM_SUBSTEPS) { _loopsWithoutOwner = 0; + _lastStep = ObjectMotionState::getWorldSimulationStep(); + _sentInactive = false; } } } @@ -580,3 +600,7 @@ int16_t EntityMotionState::computeCollisionGroup() { } return COLLISION_GROUP_DEFAULT; } + +void EntityMotionState::setOutgoingPriority(quint8 priority) { + _outgoingPriority = glm::max(_outgoingPriority, priority); +} diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index 4f777a4575..009c931dba 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -29,7 +29,7 @@ public: virtual ~EntityMotionState(); void updateServerPhysicsVariables(); - virtual void handleEasyChanges(uint32_t flags); + virtual void handleEasyChanges(uint32_t flags, PhysicsEngine* engine); virtual void handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine); /// \return MOTION_TYPE_DYNAMIC or MOTION_TYPE_STATIC based on params set in EntityItem @@ -68,8 +68,9 @@ public: virtual const QUuid& getObjectID() const { return _entity->getID(); } + virtual quint8 getSimulationPriority() const; virtual QUuid getSimulatorID() const; - virtual void bump(); + virtual void bump(quint8 priority); EntityItemPointer getEntity() const { return _entity; } @@ -80,6 +81,9 @@ public: virtual int16_t computeCollisionGroup(); + // eternal logic can suggest a simuator priority bid for the next outgoing update + void setOutgoingPriority(quint8 priority); + friend class PhysicalEntitySimulation; protected: @@ -93,8 +97,7 @@ protected: EntityItemPointer _entity; - bool _sentActive; // true if body was active when we sent last update - int _numNonMovingUpdates; // RELIABLE_SEND_HACK for "not so reliable" resends of packets for non-moving objects + bool _sentInactive; // true if body was inactive when we sent last update // these are for the prediction of the remote server's simple extrapolation uint32_t _lastStep; // last step of server extrapolation @@ -104,6 +107,7 @@ protected: glm::vec3 _serverAngularVelocity; // radians per second glm::vec3 _serverGravity; glm::vec3 _serverAcceleration; + QByteArray _serverActionData; uint32_t _lastMeasureStep; glm::vec3 _lastVelocity; @@ -111,9 +115,9 @@ protected: float _measuredDeltaTime; quint8 _accelerationNearlyGravityCount; - bool _candidateForOwnership; - uint32_t _loopsSinceOwnershipBid; + quint64 _nextOwnershipBid = NO_PRORITY; uint32_t _loopsWithoutOwner; + quint8 _outgoingPriority = NO_PRORITY; }; #endif // hifi_EntityMotionState_h diff --git a/libraries/physics/src/ObjectAction.cpp b/libraries/physics/src/ObjectAction.cpp index 3759d04d80..ae29fe79d3 100644 --- a/libraries/physics/src/ObjectAction.cpp +++ b/libraries/physics/src/ObjectAction.cpp @@ -13,7 +13,7 @@ #include "ObjectAction.h" -ObjectAction::ObjectAction(QUuid id, EntityItemPointer ownerEntity) : +ObjectAction::ObjectAction(EntityActionType type, QUuid id, EntityItemPointer ownerEntity) : btActionInterface(), _id(id), _active(false), @@ -27,10 +27,13 @@ void ObjectAction::updateAction(btCollisionWorld* collisionWorld, btScalar delta if (!_active) { return; } - if (!_ownerEntity) { - qDebug() << "ObjectAction::updateAction no owner entity"; + if (_ownerEntity.expired()) { + qDebug() << "warning -- action with no entity removing self from btCollisionWorld."; + btDynamicsWorld* dynamicsWorld = static_cast(collisionWorld); + dynamicsWorld->removeAction(this); return; } + updateActionWorker(deltaTimeStep); } @@ -42,10 +45,11 @@ void ObjectAction::removeFromSimulation(EntitySimulation* simulation) const { } btRigidBody* ObjectAction::getRigidBody() { - if (!_ownerEntity) { + auto ownerEntity = _ownerEntity.lock(); + if (!ownerEntity) { return nullptr; } - void* physicsInfo = _ownerEntity->getPhysicsInfo(); + void* physicsInfo = ownerEntity->getPhysicsInfo(); if (!physicsInfo) { return nullptr; } @@ -124,3 +128,12 @@ void ObjectAction::setAngularVelocity(glm::vec3 angularVelocity) { rigidBody->setAngularVelocity(glmToBullet(angularVelocity)); rigidBody->activate(); } + +QByteArray ObjectAction::serialize() { + assert(false); + return QByteArray(); +} + +void ObjectAction::deserialize(QByteArray serializedArguments) { + assert(false); +} diff --git a/libraries/physics/src/ObjectAction.h b/libraries/physics/src/ObjectAction.h index f15a0ba872..0e982aaacf 100644 --- a/libraries/physics/src/ObjectAction.h +++ b/libraries/physics/src/ObjectAction.h @@ -26,14 +26,17 @@ class ObjectAction : public btActionInterface, public EntityActionInterface { public: - ObjectAction(QUuid id, EntityItemPointer ownerEntity); + ObjectAction(EntityActionType type, QUuid id, EntityItemPointer ownerEntity); virtual ~ObjectAction(); const QUuid& getID() const { return _id; } + virtual EntityActionType getType() { assert(false); return ACTION_TYPE_NONE; } virtual void removeFromSimulation(EntitySimulation* simulation) const; - virtual const EntityItemPointer& getOwnerEntity() const { return _ownerEntity; } + virtual EntityItemWeakPointer getOwnerEntity() const { return _ownerEntity; } virtual void setOwnerEntity(const EntityItemPointer ownerEntity) { _ownerEntity = ownerEntity; } + virtual bool updateArguments(QVariantMap arguments) { return false; } + virtual QVariantMap getArguments() { return QVariantMap(); } // this is called from updateAction and should be overridden by subclasses virtual void updateActionWorker(float deltaTimeStep) {} @@ -42,11 +45,15 @@ public: virtual void updateAction(btCollisionWorld* collisionWorld, btScalar deltaTimeStep); virtual void debugDraw(btIDebugDraw* debugDrawer); + virtual QByteArray serialize(); + virtual void deserialize(QByteArray serializedArguments); + private: QUuid _id; QReadWriteLock _lock; protected: + virtual btRigidBody* getRigidBody(); virtual glm::vec3 getPosition(); virtual void setPosition(glm::vec3 position); @@ -57,13 +64,14 @@ protected: virtual glm::vec3 getAngularVelocity(); virtual void setAngularVelocity(glm::vec3 angularVelocity); + void lockForRead() { _lock.lockForRead(); } bool tryLockForRead() { return _lock.tryLockForRead(); } void lockForWrite() { _lock.lockForWrite(); } bool tryLockForWrite() { return _lock.tryLockForWrite(); } void unlock() { _lock.unlock(); } bool _active; - EntityItemPointer _ownerEntity; + EntityItemWeakPointer _ownerEntity; }; #endif // hifi_ObjectAction_h diff --git a/libraries/physics/src/ObjectActionOffset.cpp b/libraries/physics/src/ObjectActionOffset.cpp index 1db43d1432..22c6b7e0d3 100644 --- a/libraries/physics/src/ObjectActionOffset.cpp +++ b/libraries/physics/src/ObjectActionOffset.cpp @@ -9,10 +9,14 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "QVariantGLM.h" + #include "ObjectActionOffset.h" -ObjectActionOffset::ObjectActionOffset(QUuid id, EntityItemPointer ownerEntity) : - ObjectAction(id, ownerEntity) { +const uint16_t ObjectActionOffset::offsetVersion = 1; + +ObjectActionOffset::ObjectActionOffset(EntityActionType type, QUuid id, EntityItemPointer ownerEntity) : + ObjectAction(type, id, ownerEntity) { #if WANT_DEBUG qDebug() << "ObjectActionOffset::ObjectActionOffset"; #endif @@ -30,7 +34,12 @@ void ObjectActionOffset::updateActionWorker(btScalar deltaTimeStep) { return; } - void* physicsInfo = _ownerEntity->getPhysicsInfo(); + auto ownerEntity = _ownerEntity.lock(); + if (!ownerEntity) { + return; + } + + void* physicsInfo = ownerEntity->getPhysicsInfo(); if (!physicsInfo) { unlock(); return; @@ -107,3 +116,52 @@ bool ObjectActionOffset::updateArguments(QVariantMap arguments) { unlock(); return true; } + +QVariantMap ObjectActionOffset::getArguments() { + QVariantMap arguments; + lockForRead(); + arguments["pointToOffsetFrom"] = glmToQMap(_pointToOffsetFrom); + arguments["linearTimeScale"] = _linearTimeScale; + arguments["linearDistance"] = _linearDistance; + unlock(); + return arguments; +} + +QByteArray ObjectActionOffset::serialize() { + QByteArray ba; + QDataStream dataStream(&ba, QIODevice::WriteOnly); + dataStream << getType(); + dataStream << getID(); + dataStream << ObjectActionOffset::offsetVersion; + + dataStream << _pointToOffsetFrom; + dataStream << _linearDistance; + dataStream << _linearTimeScale; + dataStream << _positionalTargetSet; + + return ba; +} + +void ObjectActionOffset::deserialize(QByteArray serializedArguments) { + QDataStream dataStream(serializedArguments); + + EntityActionType type; + QUuid id; + uint16_t serializationVersion; + + dataStream >> type; + assert(type == getType()); + dataStream >> id; + assert(id == getID()); + dataStream >> serializationVersion; + if (serializationVersion != ObjectActionOffset::offsetVersion) { + return; + } + + dataStream >> _pointToOffsetFrom; + dataStream >> _linearDistance; + dataStream >> _linearTimeScale; + dataStream >> _positionalTargetSet; + + _active = true; +} diff --git a/libraries/physics/src/ObjectActionOffset.h b/libraries/physics/src/ObjectActionOffset.h index 874b0a5477..28a08c2efe 100644 --- a/libraries/physics/src/ObjectActionOffset.h +++ b/libraries/physics/src/ObjectActionOffset.h @@ -19,13 +19,21 @@ class ObjectActionOffset : public ObjectAction { public: - ObjectActionOffset(QUuid id, EntityItemPointer ownerEntity); + ObjectActionOffset(EntityActionType type, QUuid id, EntityItemPointer ownerEntity); virtual ~ObjectActionOffset(); + virtual EntityActionType getType() { return ACTION_TYPE_OFFSET; } + virtual bool updateArguments(QVariantMap arguments); + virtual QVariantMap getArguments(); + virtual void updateActionWorker(float deltaTimeStep); -private: + virtual QByteArray serialize(); + virtual void deserialize(QByteArray serializedArguments); + + private: + static const uint16_t offsetVersion; glm::vec3 _pointToOffsetFrom; float _linearDistance; float _linearTimeScale; diff --git a/libraries/physics/src/ObjectActionSpring.cpp b/libraries/physics/src/ObjectActionSpring.cpp index 8eb4f7f652..fae593f3eb 100644 --- a/libraries/physics/src/ObjectActionSpring.cpp +++ b/libraries/physics/src/ObjectActionSpring.cpp @@ -9,10 +9,22 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "QVariantGLM.h" + #include "ObjectActionSpring.h" -ObjectActionSpring::ObjectActionSpring(QUuid id, EntityItemPointer ownerEntity) : - ObjectAction(id, ownerEntity) { +const float SPRING_MAX_SPEED = 10.0f; + +const uint16_t ObjectActionSpring::springVersion = 1; + +ObjectActionSpring::ObjectActionSpring(EntityActionType type, QUuid id, EntityItemPointer ownerEntity) : + ObjectAction(type, id, ownerEntity), + _positionalTarget(glm::vec3(0.0f)), + _linearTimeScale(0.2f), + _positionalTargetSet(false), + _rotationalTarget(glm::quat()), + _angularTimeScale(0.2f), + _rotationalTargetSet(false) { #if WANT_DEBUG qDebug() << "ObjectActionSpring::ObjectActionSpring"; #endif @@ -31,7 +43,12 @@ void ObjectActionSpring::updateActionWorker(btScalar deltaTimeStep) { return; } - void* physicsInfo = _ownerEntity->getPhysicsInfo(); + auto ownerEntity = _ownerEntity.lock(); + if (!ownerEntity) { + return; + } + + void* physicsInfo = ownerEntity->getPhysicsInfo(); if (!physicsInfo) { unlock(); return; @@ -46,10 +63,26 @@ void ObjectActionSpring::updateActionWorker(btScalar deltaTimeStep) { // handle the linear part if (_positionalTargetSet) { + // check for NaN + if (_positionalTarget.x != _positionalTarget.x || + _positionalTarget.y != _positionalTarget.y || + _positionalTarget.z != _positionalTarget.z) { + qDebug() << "ObjectActionSpring::updateActionWorker -- target position includes NaN"; + unlock(); + lockForWrite(); + _active = false; + unlock(); + return; + } glm::vec3 offset = _positionalTarget - bulletToGLM(rigidBody->getCenterOfMassPosition()); float offsetLength = glm::length(offset); float speed = offsetLength / _linearTimeScale; + // cap speed + if (speed > SPRING_MAX_SPEED) { + speed = SPRING_MAX_SPEED; + } + if (offsetLength > IGNORE_POSITION_DELTA) { glm::vec3 newVelocity = glm::normalize(offset) * speed; rigidBody->setLinearVelocity(glmToBullet(newVelocity)); @@ -61,6 +94,18 @@ void ObjectActionSpring::updateActionWorker(btScalar deltaTimeStep) { // handle rotation if (_rotationalTargetSet) { + if (_rotationalTarget.x != _rotationalTarget.x || + _rotationalTarget.y != _rotationalTarget.y || + _rotationalTarget.z != _rotationalTarget.z || + _rotationalTarget.w != _rotationalTarget.w) { + qDebug() << "AvatarActionHold::updateActionWorker -- target rotation includes NaN"; + unlock(); + lockForWrite(); + _active = false; + unlock(); + return; + } + glm::quat bodyRotation = bulletToGLM(rigidBody->getOrientation()); // if qZero and qOne are too close to each other, we can get NaN for angle. auto alignmentDot = glm::dot(bodyRotation, _rotationalTarget); @@ -108,7 +153,7 @@ bool ObjectActionSpring::updateArguments(QVariantMap arguments) { EntityActionInterface::extractFloatArgument("spring action", arguments, "angularTimeScale", rscOk, false); if (!ptOk && !rtOk) { - qDebug() << "spring action requires either targetPosition or targetRotation argument"; + qDebug() << "spring action requires at least one of targetPosition or targetRotation argument"; return false; } @@ -142,3 +187,67 @@ bool ObjectActionSpring::updateArguments(QVariantMap arguments) { unlock(); return true; } + +QVariantMap ObjectActionSpring::getArguments() { + QVariantMap arguments; + lockForRead(); + + if (_positionalTargetSet) { + arguments["linearTimeScale"] = _linearTimeScale; + arguments["targetPosition"] = glmToQMap(_positionalTarget); + } + + if (_rotationalTargetSet) { + arguments["targetRotation"] = glmToQMap(_rotationalTarget); + arguments["angularTimeScale"] = _angularTimeScale; + } + + unlock(); + return arguments; +} + +QByteArray ObjectActionSpring::serialize() { + QByteArray serializedActionArguments; + QDataStream dataStream(&serializedActionArguments, QIODevice::WriteOnly); + + dataStream << getType(); + dataStream << getID(); + dataStream << ObjectActionSpring::springVersion; + + dataStream << _positionalTarget; + dataStream << _linearTimeScale; + dataStream << _positionalTargetSet; + + dataStream << _rotationalTarget; + dataStream << _angularTimeScale; + dataStream << _rotationalTargetSet; + + return serializedActionArguments; +} + +void ObjectActionSpring::deserialize(QByteArray serializedArguments) { + QDataStream dataStream(serializedArguments); + + EntityActionType type; + QUuid id; + uint16_t serializationVersion; + + dataStream >> type; + assert(type == getType()); + dataStream >> id; + assert(id == getID()); + dataStream >> serializationVersion; + if (serializationVersion != ObjectActionSpring::springVersion) { + return; + } + + dataStream >> _positionalTarget; + dataStream >> _linearTimeScale; + dataStream >> _positionalTargetSet; + + dataStream >> _rotationalTarget; + dataStream >> _angularTimeScale; + dataStream >> _rotationalTargetSet; + + _active = true; +} diff --git a/libraries/physics/src/ObjectActionSpring.h b/libraries/physics/src/ObjectActionSpring.h index 9f3df0fdf8..c887a046bb 100644 --- a/libraries/physics/src/ObjectActionSpring.h +++ b/libraries/physics/src/ObjectActionSpring.h @@ -19,13 +19,21 @@ class ObjectActionSpring : public ObjectAction { public: - ObjectActionSpring(QUuid id, EntityItemPointer ownerEntity); + ObjectActionSpring(EntityActionType type, QUuid id, EntityItemPointer ownerEntity); virtual ~ObjectActionSpring(); + virtual EntityActionType getType() { return ACTION_TYPE_SPRING; } + virtual bool updateArguments(QVariantMap arguments); + virtual QVariantMap getArguments(); + virtual void updateActionWorker(float deltaTimeStep); + virtual QByteArray serialize(); + virtual void deserialize(QByteArray serializedArguments); + protected: + static const uint16_t springVersion; glm::vec3 _positionalTarget; float _linearTimeScale; diff --git a/libraries/physics/src/ObjectMotionState.cpp b/libraries/physics/src/ObjectMotionState.cpp index 85e67d72f7..7ff119fb79 100644 --- a/libraries/physics/src/ObjectMotionState.cpp +++ b/libraries/physics/src/ObjectMotionState.cpp @@ -114,7 +114,7 @@ void ObjectMotionState::setRigidBody(btRigidBody* body) { } } -void ObjectMotionState::handleEasyChanges(uint32_t flags) { +void ObjectMotionState::handleEasyChanges(uint32_t flags, PhysicsEngine* engine) { if (flags & EntityItem::DIRTY_POSITION) { btTransform worldTrans; if (flags & EntityItem::DIRTY_ROTATION) { @@ -159,7 +159,7 @@ void ObjectMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* if ((flags & HARD_DIRTY_PHYSICS_FLAGS) == 0) { // no HARD flags remain, so do any EASY changes if (flags & EASY_DIRTY_PHYSICS_FLAGS) { - handleEasyChanges(flags); + handleEasyChanges(flags, engine); } return; } @@ -174,7 +174,7 @@ void ObjectMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* } } if (flags & EASY_DIRTY_PHYSICS_FLAGS) { - handleEasyChanges(flags); + handleEasyChanges(flags, engine); } // it is possible that there are no HARD flags at this point (if DIRTY_SHAPE was removed) // so we check again befoe we reinsert: diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index 561ce02d62..30394ef5fc 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -40,7 +40,8 @@ enum MotionStateType { const uint32_t HARD_DIRTY_PHYSICS_FLAGS = (uint32_t)(EntityItem::DIRTY_MOTION_TYPE | EntityItem::DIRTY_SHAPE); const uint32_t EASY_DIRTY_PHYSICS_FLAGS = (uint32_t)(EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES | EntityItem::DIRTY_MASS | EntityItem::DIRTY_COLLISION_GROUP | - EntityItem::DIRTY_MATERIAL); + EntityItem::DIRTY_MATERIAL | EntityItem::DIRTY_SIMULATOR_ID | + EntityItem::DIRTY_SIMULATOR_OWNERSHIP); // These are the set of incoming flags that the PhysicsEngine needs to hear about: const uint32_t DIRTY_PHYSICS_FLAGS = (uint32_t)(HARD_DIRTY_PHYSICS_FLAGS | EASY_DIRTY_PHYSICS_FLAGS | @@ -70,7 +71,7 @@ public: ObjectMotionState(btCollisionShape* shape); ~ObjectMotionState(); - virtual void handleEasyChanges(uint32_t flags); + virtual void handleEasyChanges(uint32_t flags, PhysicsEngine* engine); virtual void handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine); void updateBodyMaterialProperties(); @@ -118,8 +119,9 @@ public: virtual const QUuid& getObjectID() const = 0; + virtual quint8 getSimulationPriority() const { return 0; } virtual QUuid getSimulatorID() const = 0; - virtual void bump() = 0; + virtual void bump(quint8 priority) {} virtual QString getName() { return ""; } diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index c68b993fe2..f6f02b8573 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -235,14 +235,32 @@ void PhysicalEntitySimulation::handleCollisionEvents(CollisionEvents& collisionE } } + +void PhysicalEntitySimulation::addAction(EntityActionPointer action) { + if (_physicsEngine) { + lock(); + const QUuid& actionID = action->getID(); + if (_physicsEngine->getActionByID(actionID)) { + qDebug() << "warning -- PhysicalEntitySimulation::addAction -- adding an action that was already in _physicsEngine"; + } + unlock(); + EntitySimulation::addAction(action); + } +} + void PhysicalEntitySimulation::applyActionChanges() { if (_physicsEngine) { - foreach (EntityActionPointer actionToAdd, _actionsToAdd) { - _physicsEngine->addAction(actionToAdd); - } + lock(); foreach (QUuid actionToRemove, _actionsToRemove) { _physicsEngine->removeAction(actionToRemove); } + _actionsToRemove.clear(); + foreach (EntityActionPointer actionToAdd, _actionsToAdd) { + if (!_actionsToRemove.contains(actionToAdd->getID())) { + _physicsEngine->addAction(actionToAdd); + } + } + _actionsToAdd.clear(); + unlock(); } - EntitySimulation::applyActionChanges(); } diff --git a/libraries/physics/src/PhysicalEntitySimulation.h b/libraries/physics/src/PhysicalEntitySimulation.h index 81ab9f5cce..9c439c53b2 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.h +++ b/libraries/physics/src/PhysicalEntitySimulation.h @@ -32,6 +32,7 @@ public: void init(EntityTree* tree, PhysicsEngine* engine, EntityEditPacketSender* packetSender); + virtual void addAction(EntityActionPointer action); virtual void applyActionChanges(); protected: // only called by EntitySimulation diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 55fc5e6295..022468633f 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -142,7 +142,7 @@ void PhysicsEngine::addObject(ObjectMotionState* motionState) { motionState->getAndClearIncomingDirtyFlags(); } - + void PhysicsEngine::removeObject(ObjectMotionState* object) { // wake up anything touching this object bump(object); @@ -172,7 +172,7 @@ void PhysicsEngine::deleteObjects(SetOfMotionStates& objects) { for (auto object : objects) { btRigidBody* body = object->getRigidBody(); removeObject(object); - + // NOTE: setRigidBody() modifies body->m_userPointer so we should clear the MotionState's body BEFORE deleting it. object->setRigidBody(nullptr); body->setMotionState(nullptr); @@ -194,7 +194,7 @@ void PhysicsEngine::changeObjects(VectorOfMotionStates& objects) { if (flags & HARD_DIRTY_PHYSICS_FLAGS) { object->handleHardAndEasyChanges(flags, this); } else if (flags & EASY_DIRTY_PHYSICS_FLAGS) { - object->handleEasyChanges(flags); + object->handleEasyChanges(flags, this); } } } @@ -260,26 +260,29 @@ void PhysicsEngine::stepSimulation() { void PhysicsEngine::doOwnershipInfection(const btCollisionObject* objectA, const btCollisionObject* objectB) { BT_PROFILE("ownershipInfection"); - if (_sessionID.isNull()) { - return; - } const btCollisionObject* characterObject = _characterController ? _characterController->getCollisionObject() : nullptr; - ObjectMotionState* a = static_cast(objectA->getUserPointer()); - ObjectMotionState* b = static_cast(objectB->getUserPointer()); + ObjectMotionState* motionStateA = static_cast(objectA->getUserPointer()); + ObjectMotionState* motionStateB = static_cast(objectB->getUserPointer()); - if (b && ((a && a->getSimulatorID() == _sessionID && !objectA->isStaticObject()) || (objectA == characterObject))) { - // NOTE: we might own the simulation of a kinematic object (A) + if (motionStateB && + ((motionStateA && motionStateA->getSimulatorID() == _sessionID && !objectA->isStaticObject()) || + (objectA == characterObject))) { + // NOTE: we might own the simulation of a kinematic object (A) // but we don't claim ownership of kinematic objects (B) based on collisions here. - if (!objectB->isStaticOrKinematicObject()) { - b->bump(); + if (!objectB->isStaticOrKinematicObject() && motionStateB->getSimulatorID() != _sessionID) { + quint8 priority = motionStateA ? motionStateA->getSimulationPriority() : PERSONAL_SIMULATION_PRIORITY; + motionStateB->bump(priority); } - } else if (a && ((b && b->getSimulatorID() == _sessionID && !objectB->isStaticObject()) || (objectB == characterObject))) { - // SIMILARLY: we might own the simulation of a kinematic object (B) + } else if (motionStateA && + ((motionStateB && motionStateB->getSimulatorID() == _sessionID && !objectB->isStaticObject()) || + (objectB == characterObject))) { + // SIMILARLY: we might own the simulation of a kinematic object (B) // but we don't claim ownership of kinematic objects (A) based on collisions here. - if (!objectA->isStaticOrKinematicObject()) { - a->bump(); + if (!objectA->isStaticOrKinematicObject() && motionStateA->getSimulatorID() != _sessionID) { + quint8 priority = motionStateB ? motionStateB->getSimulationPriority() : PERSONAL_SIMULATION_PRIORITY; + motionStateA->bump(priority); } } } @@ -293,13 +296,13 @@ void PhysicsEngine::updateContactMap() { for (int i = 0; i < numManifolds; ++i) { btPersistentManifold* contactManifold = _collisionDispatcher->getManifoldByIndexInternal(i); if (contactManifold->getNumContacts() > 0) { - // TODO: require scripts to register interest in callbacks for specific objects + // TODO: require scripts to register interest in callbacks for specific objects // so we can filter out most collision events right here. const btCollisionObject* objectA = static_cast(contactManifold->getBody0()); const btCollisionObject* objectB = static_cast(contactManifold->getBody1()); if (!(objectA->isActive() || objectB->isActive())) { - // both objects are inactive so stop tracking this contact, + // both objects are inactive so stop tracking this contact, // which will eventually trigger a CONTACT_EVENT_TYPE_END continue; } @@ -311,7 +314,9 @@ void PhysicsEngine::updateContactMap() { _contactMap[ContactKey(a, b)].update(_numContactFrames, contactManifold->getContactPoint(0)); } - doOwnershipInfection(objectA, objectB); + if (!_sessionID.isNull()) { + doOwnershipInfection(objectA, objectB); + } } } } @@ -327,24 +332,24 @@ CollisionEvents& PhysicsEngine::getCollisionEvents() { ContactInfo& contact = contactItr->second; ContactEventType type = contact.computeType(_numContactFrames); if(type != CONTACT_EVENT_TYPE_CONTINUE || _numSubsteps % CONTINUE_EVENT_FILTER_FREQUENCY == 0) { - ObjectMotionState* A = static_cast(contactItr->first._a); - ObjectMotionState* B = static_cast(contactItr->first._b); - glm::vec3 velocityChange = (A ? A->getObjectLinearVelocityChange() : glm::vec3(0.0f)) + - (B ? B->getObjectLinearVelocityChange() : glm::vec3(0.0f)); + ObjectMotionState* motionStateA = static_cast(contactItr->first._a); + ObjectMotionState* motionStateB = static_cast(contactItr->first._b); + glm::vec3 velocityChange = (motionStateA ? motionStateA->getObjectLinearVelocityChange() : glm::vec3(0.0f)) + + (motionStateB ? motionStateB->getObjectLinearVelocityChange() : glm::vec3(0.0f)); - if (A && A->getType() == MOTIONSTATE_TYPE_ENTITY) { - QUuid idA = A->getObjectID(); + if (motionStateA && motionStateA->getType() == MOTIONSTATE_TYPE_ENTITY) { + QUuid idA = motionStateA->getObjectID(); QUuid idB; - if (B && B->getType() == MOTIONSTATE_TYPE_ENTITY) { - idB = B->getObjectID(); + if (motionStateB && motionStateB->getType() == MOTIONSTATE_TYPE_ENTITY) { + idB = motionStateB->getObjectID(); } glm::vec3 position = bulletToGLM(contact.getPositionWorldOnB()) + _originOffset; glm::vec3 penetration = bulletToGLM(contact.distance * contact.normalWorldOnB); _collisionEvents.push_back(Collision(type, idA, idB, position, penetration, velocityChange)); - } else if (B && B->getType() == MOTIONSTATE_TYPE_ENTITY) { - QUuid idB = B->getObjectID(); + } else if (motionStateB && motionStateB->getType() == MOTIONSTATE_TYPE_ENTITY) { + QUuid idB = motionStateB->getObjectID(); glm::vec3 position = bulletToGLM(contact.getPositionWorldOnA()) + _originOffset; - // NOTE: we're flipping the order of A and B (so that the first objectID is never NULL) + // NOTE: we're flipping the order of A and B (so that the first objectID is never NULL) // hence we must negate the penetration. glm::vec3 penetration = - bulletToGLM(contact.distance * contact.normalWorldOnB); _collisionEvents.push_back(Collision(type, idB, QUuid(), position, penetration, velocityChange)); @@ -402,7 +407,7 @@ void PhysicsEngine::bump(ObjectMotionState* motionState) { if (!objectA->isStaticOrKinematicObject()) { ObjectMotionState* motionStateA = static_cast(objectA->getUserPointer()); if (motionStateA) { - motionStateA->bump(); + motionStateA->bump(VOLUNTEER_SIMULATION_PRIORITY); objectA->setActivationState(ACTIVE_TAG); } } @@ -410,7 +415,7 @@ void PhysicsEngine::bump(ObjectMotionState* motionState) { if (!objectB->isStaticOrKinematicObject()) { ObjectMotionState* motionStateB = static_cast(objectB->getUserPointer()); if (motionStateB) { - motionStateB->bump(); + motionStateB->bump(VOLUNTEER_SIMULATION_PRIORITY); objectB->setActivationState(ACTIVE_TAG); } } @@ -436,12 +441,21 @@ int16_t PhysicsEngine::getCollisionMask(int16_t group) const { return mask ? *mask : COLLISION_MASK_DEFAULT; } +EntityActionPointer PhysicsEngine::getActionByID(const QUuid& actionID) const { + if (_objectActions.contains(actionID)) { + return _objectActions[actionID]; + } + return nullptr; +} + void PhysicsEngine::addAction(EntityActionPointer action) { assert(action); const QUuid& actionID = action->getID(); if (_objectActions.contains(actionID)) { - assert(_objectActions[actionID] == action); - return; + if (_objectActions[actionID] == action) { + return; + } + removeAction(action->getID()); } _objectActions[actionID] = action; diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index 6bb6ef8305..a974a02e51 100644 --- a/libraries/physics/src/PhysicsEngine.h +++ b/libraries/physics/src/PhysicsEngine.h @@ -94,6 +94,7 @@ public: int16_t getCollisionMask(int16_t group) const; + EntityActionPointer getActionByID(const QUuid& actionID) const; void addAction(EntityActionPointer action); void removeAction(const QUuid actionID); diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index b49d1985bb..a44f1f053c 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -27,8 +27,8 @@ #include "gpu/GLBackend.h" #include "simple_vert.h" -#include "simple_frag.h" #include "simple_textured_frag.h" +#include "simple_textured_emisive_frag.h" #include "deferred_light_vert.h" #include "deferred_light_limited_vert.h" @@ -52,15 +52,15 @@ static const std::string glowIntensityShaderHandle = "glowIntensity"; void DeferredLightingEffect::init(AbstractViewStateInterface* viewState) { auto VS = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(simple_vert))); - auto PS = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(simple_frag))); - auto PSTextured = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(simple_textured_frag))); + auto PS = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(simple_textured_frag))); + auto PSEmissive = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(simple_textured_emisive_frag))); gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(VS, PS)); - gpu::ShaderPointer programTextured = gpu::ShaderPointer(gpu::Shader::createProgram(VS, PSTextured)); + gpu::ShaderPointer programEmissive = gpu::ShaderPointer(gpu::Shader::createProgram(VS, PSEmissive)); gpu::Shader::BindingSet slotBindings; gpu::Shader::makeProgram(*program, slotBindings); - gpu::Shader::makeProgram(*programTextured, slotBindings); + gpu::Shader::makeProgram(*programEmissive, slotBindings); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setCullMode(gpu::State::CULL_BACK); @@ -79,8 +79,8 @@ void DeferredLightingEffect::init(AbstractViewStateInterface* viewState) { _simpleProgram = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); _simpleProgramCullNone = gpu::PipelinePointer(gpu::Pipeline::create(program, stateCullNone)); - _simpleProgramTextured = gpu::PipelinePointer(gpu::Pipeline::create(programTextured, state)); - _simpleProgramTexturedCullNone = gpu::PipelinePointer(gpu::Pipeline::create(programTextured, stateCullNone)); + _simpleProgramEmissive = gpu::PipelinePointer(gpu::Pipeline::create(programEmissive, state)); + _simpleProgramEmissiveCullNone = gpu::PipelinePointer(gpu::Pipeline::create(programEmissive, stateCullNone)); _viewState = viewState; loadLightProgram(directional_light_frag, false, _directionalLight, _directionalLightLocations); @@ -117,14 +117,12 @@ void DeferredLightingEffect::init(AbstractViewStateInterface* viewState) { lp->setAmbientSpherePreset(gpu::SphericalHarmonics::Preset(_ambientLightMode % gpu::SphericalHarmonics::NUM_PRESET)); } -void DeferredLightingEffect::bindSimpleProgram(gpu::Batch& batch, bool textured, bool culled) { - // DependencyManager::get()->setPrimaryDrawBuffers(batch, true, true, true); - - if (textured) { +void DeferredLightingEffect::bindSimpleProgram(gpu::Batch& batch, bool textured, bool culled, bool emmisive) { + if (emmisive) { if (culled) { - batch.setPipeline(_simpleProgramTextured); + batch.setPipeline(_simpleProgramEmissive); } else { - batch.setPipeline(_simpleProgramTexturedCullNone); + batch.setPipeline(_simpleProgramEmissiveCullNone); } } else { if (culled) { @@ -133,48 +131,42 @@ void DeferredLightingEffect::bindSimpleProgram(gpu::Batch& batch, bool textured, batch.setPipeline(_simpleProgramCullNone); } } -} - -void DeferredLightingEffect::releaseSimpleProgram(gpu::Batch& batch) { - // DependencyManager::get()->setPrimaryDrawBuffers(batch, true, false, false); + if (!textured) { + // If it is not textured, bind white texture and keep using textured pipeline + batch.setUniformTexture(0, DependencyManager::get()->getWhiteTexture()); + } } void DeferredLightingEffect::renderSolidSphere(gpu::Batch& batch, float radius, int slices, int stacks, const glm::vec4& color) { bindSimpleProgram(batch); DependencyManager::get()->renderSphere(batch, radius, slices, stacks, color); - releaseSimpleProgram(batch); } void DeferredLightingEffect::renderWireSphere(gpu::Batch& batch, float radius, int slices, int stacks, const glm::vec4& color) { bindSimpleProgram(batch); DependencyManager::get()->renderSphere(batch, radius, slices, stacks, color, false); - releaseSimpleProgram(batch); } void DeferredLightingEffect::renderSolidCube(gpu::Batch& batch, float size, const glm::vec4& color) { bindSimpleProgram(batch); DependencyManager::get()->renderSolidCube(batch, size, color); - releaseSimpleProgram(batch); } void DeferredLightingEffect::renderWireCube(gpu::Batch& batch, float size, const glm::vec4& color) { bindSimpleProgram(batch); DependencyManager::get()->renderWireCube(batch, size, color); - releaseSimpleProgram(batch); } void DeferredLightingEffect::renderQuad(gpu::Batch& batch, const glm::vec3& minCorner, const glm::vec3& maxCorner, const glm::vec4& color) { bindSimpleProgram(batch); DependencyManager::get()->renderQuad(batch, minCorner, maxCorner, color); - releaseSimpleProgram(batch); } void DeferredLightingEffect::renderLine(gpu::Batch& batch, const glm::vec3& p1, const glm::vec3& p2, const glm::vec4& color1, const glm::vec4& color2) { bindSimpleProgram(batch); DependencyManager::get()->renderLine(batch, p1, p2, color1, color2); - releaseSimpleProgram(batch); } void DeferredLightingEffect::addPointLight(const glm::vec3& position, float radius, const glm::vec3& color, diff --git a/libraries/render-utils/src/DeferredLightingEffect.h b/libraries/render-utils/src/DeferredLightingEffect.h index 9d66bf08c0..d948f2c305 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.h +++ b/libraries/render-utils/src/DeferredLightingEffect.h @@ -34,10 +34,7 @@ public: void init(AbstractViewStateInterface* viewState); /// Sets up the state necessary to render static untextured geometry with the simple program. - void bindSimpleProgram(gpu::Batch& batch, bool textured = false, bool culled = true); - - /// Tears down the state necessary to render static untextured geometry with the simple program. - void releaseSimpleProgram(gpu::Batch& batch); + void bindSimpleProgram(gpu::Batch& batch, bool textured = false, bool culled = true, bool emmisive = false); //// Renders a solid sphere with the simple program. void renderSolidSphere(gpu::Batch& batch, float radius, int slices, int stacks, const glm::vec4& color); @@ -101,8 +98,8 @@ private: gpu::PipelinePointer _simpleProgram; gpu::PipelinePointer _simpleProgramCullNone; - gpu::PipelinePointer _simpleProgramTextured; - gpu::PipelinePointer _simpleProgramTexturedCullNone; + gpu::PipelinePointer _simpleProgramEmissive; + gpu::PipelinePointer _simpleProgramEmissiveCullNone; ProgramObject _directionalSkyboxLight; LightLocations _directionalSkyboxLightLocations; diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 3e7e9a0adf..b0e184d224 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -1058,13 +1058,13 @@ void GeometryCache::renderBevelCornersRect(gpu::Batch& batch, int x, int y, int int vertexPoint = 0; // Triangle strip points - // 3 ------ 5 - // / \ - // 1 7 - // | | - // 2 8 - // \ / - // 4 ------ 6 + // 3 ------ 5 // + // / \ // + // 1 7 // + // | | // + // 2 8 // + // \ / // + // 4 ------ 6 // // 1 vertexBuffer[vertexPoint++] = x; diff --git a/libraries/render-utils/src/sdf_text3D.slf b/libraries/render-utils/src/sdf_text3D.slf index 361f8454ab..d9972417ba 100644 --- a/libraries/render-utils/src/sdf_text3D.slf +++ b/libraries/render-utils/src/sdf_text3D.slf @@ -44,9 +44,9 @@ void main() { if (a < 0.01) { discard; } - + // final color gl_FragData[0] = vec4(Color.rgb, Color.a * a); - gl_FragData[1] = vec4(interpolatedNormal.xyz, 0.0) * 0.5 + vec4(0.5, 0.5, 0.5, 1.0); - gl_FragData[2] = vec4(0.0); + gl_FragData[1] = vec4(normalize(interpolatedNormal.xyz), 0.0) * 0.5 + vec4(0.5, 0.5, 0.5, 0.5); + gl_FragData[2] = vec4(Color.rgb, gl_FrontMaterial.shininess / 128.0); } \ No newline at end of file diff --git a/libraries/render-utils/src/simple_textured_emisive.slf b/libraries/render-utils/src/simple_textured_emisive.slf new file mode 100644 index 0000000000..643dcde190 --- /dev/null +++ b/libraries/render-utils/src/simple_textured_emisive.slf @@ -0,0 +1,33 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// simple.frag +// fragment shader +// +// Created by ClĂ©ment Brisset on 5/29/15. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +<@include DeferredBufferWrite.slh@> + +// the diffuse texture +uniform sampler2D originalTexture; + +// the interpolated normal +varying vec4 interpolatedNormal; + +void main(void) { + vec4 texel = texture2D(originalTexture, gl_TexCoord[0].st); + + packDeferredFragmentLightmap( + normalize(interpolatedNormal.xyz), + glowIntensity * texel.a, + gl_Color.rgb, + gl_FrontMaterial.specular.rgb, + gl_FrontMaterial.shininess, + texel.rgb); +} \ No newline at end of file diff --git a/libraries/shared/src/PhysicsHelpers.h b/libraries/shared/src/PhysicsHelpers.h index 0e58ae99f0..7ceafea915 100644 --- a/libraries/shared/src/PhysicsHelpers.h +++ b/libraries/shared/src/PhysicsHelpers.h @@ -15,7 +15,7 @@ #include #include -const int PHYSICS_ENGINE_MAX_NUM_SUBSTEPS = 4; +const int PHYSICS_ENGINE_MAX_NUM_SUBSTEPS = 6; // Bullet will start to "lose time" at 10 FPS. const float PHYSICS_ENGINE_FIXED_SUBSTEP = 1.0f / 60.0f; // return incremental rotation (Bullet-style) caused by angularVelocity over timeStep diff --git a/libraries/shared/src/QVariantGLM.cpp b/libraries/shared/src/QVariantGLM.cpp index aa8fa40593..7cebacee8e 100644 --- a/libraries/shared/src/QVariantGLM.cpp +++ b/libraries/shared/src/QVariantGLM.cpp @@ -24,6 +24,22 @@ QVariantList rgbColorToQList(rgbColor& v) { return QVariantList() << (int)(v[0]) << (int)(v[1]) << (int)(v[2]); } +QVariantMap glmToQMap(const glm::vec3& glmVector) { + QVariantMap vectorAsVariantMap; + vectorAsVariantMap["x"] = glmVector.x; + vectorAsVariantMap["y"] = glmVector.y; + vectorAsVariantMap["z"] = glmVector.z; + return vectorAsVariantMap; +} + +QVariantMap glmToQMap(const glm::quat& glmQuat) { + QVariantMap quatAsVariantMap; + quatAsVariantMap["x"] = glmQuat.x; + quatAsVariantMap["y"] = glmQuat.y; + quatAsVariantMap["z"] = glmQuat.z; + quatAsVariantMap["w"] = glmQuat.w; + return quatAsVariantMap; +} glm::vec3 qListToGlmVec3(const QVariant q) { diff --git a/libraries/shared/src/QVariantGLM.h b/libraries/shared/src/QVariantGLM.h index 4cc1d038a1..922aa7c4aa 100644 --- a/libraries/shared/src/QVariantGLM.h +++ b/libraries/shared/src/QVariantGLM.h @@ -21,6 +21,9 @@ QVariantList glmToQList(const glm::vec3& g); QVariantList glmToQList(const glm::quat& g); QVariantList rgbColorToQList(rgbColor& v); +QVariantMap glmToQMap(const glm::vec3& glmVector); +QVariantMap glmToQMap(const glm::quat& glmQuat); + glm::vec3 qListToGlmVec3(const QVariant q); glm::quat qListToGlmQuat(const QVariant q); void qListtoRgbColor(const QVariant q, rgbColor returnValue); diff --git a/libraries/shared/src/SimpleAverage.h b/libraries/shared/src/SimpleAverage.h new file mode 100644 index 0000000000..33ed9d84cc --- /dev/null +++ b/libraries/shared/src/SimpleAverage.h @@ -0,0 +1,33 @@ +// +// Created by Bradley Austin Davis on 2015/07/01. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once +#ifndef hifi_SimpleAverage_h +#define hifi_SimpleAverage_h + +template +class SimpleAverage { +public: + void update(T sample) { + _sum += sample; + ++_count; + } + void reset() { + _sum = 0; + _count = 0; + } + + int getCount() const { return _count; }; + T getAverage() const { return _sum / (T)_count; }; + +private: + int _count; + T _sum; +}; + +#endif diff --git a/libraries/shared/src/StreamUtils.cpp b/libraries/shared/src/StreamUtils.cpp index b2ea5d95a4..5726728f30 100644 --- a/libraries/shared/src/StreamUtils.cpp +++ b/libraries/shared/src/StreamUtils.cpp @@ -15,9 +15,9 @@ #include "StreamUtils.h" -const char* hex_digits = "0123456789abcdef"; void StreamUtil::dump(std::ostream& s, const QByteArray& buffer) { + const char* hex_digits = "0123456789abcdef"; int row_size = 32; int i = 0; while (i < buffer.size()) {