diff --git a/interface/resources/qml/hifi/MenuOption.qml b/interface/resources/qml/hifi/MenuOption.qml deleted file mode 100644 index 46cf5d9662..0000000000 --- a/interface/resources/qml/hifi/MenuOption.qml +++ /dev/null @@ -1,165 +0,0 @@ -import QtQuick 2.5 - -QtObject { - readonly property string aboutApp: "About Interface"; - readonly property string addRemoveFriends: "Add/Remove Friends..."; - readonly property string addressBar: "Show Address Bar"; - readonly property string animations: "Animations..."; - readonly property string animDebugDrawAnimPose: "Debug Draw Animation"; - readonly property string animDebugDrawDefaultPose: "Debug Draw Default Pose"; - readonly property string animDebugDrawPosition: "Debug Draw Position"; - readonly property string antialiasing: "Antialiasing"; - readonly property string assetMigration: "ATP Asset Migration"; - readonly property string assetServer: "Asset Server"; - readonly property string atmosphere: "Atmosphere"; - readonly property string attachments: "Attachments..."; - readonly property string audioNetworkStats: "Audio Network Stats"; - readonly property string audioNoiseReduction: "Audio Noise Reduction"; - readonly property string audioScope: "Show Scope"; - readonly property string audioScopeFiftyFrames: "Fifty"; - readonly property string audioScopeFiveFrames: "Five"; - readonly property string audioScopeFrames: "Display Frames"; - readonly property string audioScopePause: "Pause Scope"; - readonly property string audioScopeTwentyFrames: "Twenty"; - readonly property string audioStatsShowInjectedStreams: "Audio Stats Show Injected Streams"; - readonly property string audioTools: "Show Level Meter"; - readonly property string autoMuteAudio: "Auto Mute Microphone"; - readonly property string avatarReceiveStats: "Show Receive Stats"; - readonly property string back: "Back"; - readonly property string bandwidthDetails: "Bandwidth Details"; - readonly property string binaryEyelidControl: "Binary Eyelid Control"; - readonly property string bookmarkLocation: "Bookmark Location"; - readonly property string bookmarks: "Bookmarks"; - readonly property string cachesSize: "RAM Caches Size"; - readonly property string calibrateCamera: "Calibrate Camera"; - readonly property string cameraEntityMode: "Entity Mode"; - readonly property string centerPlayerInView: "Center Player In View"; - readonly property string chat: "Chat..."; - readonly property string collisions: "Collisions"; - readonly property string connexion: "Activate 3D Connexion Devices"; - readonly property string console_: "Console..."; - readonly property string controlWithSpeech: "Control With Speech"; - readonly property string copyAddress: "Copy Address to Clipboard"; - readonly property string copyPath: "Copy Path to Clipboard"; - readonly property string coupleEyelids: "Couple Eyelids"; - readonly property string crashInterface: "Crash Interface"; - readonly property string debugAmbientOcclusion: "Debug Ambient Occlusion"; - readonly property string decreaseAvatarSize: "Decrease Avatar Size"; - readonly property string deleteBookmark: "Delete Bookmark..."; - readonly property string disableActivityLogger: "Disable Activity Logger"; - readonly property string disableEyelidAdjustment: "Disable Eyelid Adjustment"; - readonly property string disableLightEntities: "Disable Light Entities"; - readonly property string disableNackPackets: "Disable Entity NACK Packets"; - readonly property string diskCacheEditor: "Disk Cache Editor"; - readonly property string displayCrashOptions: "Display Crash Options"; - readonly property string displayHandTargets: "Show Hand Targets"; - readonly property string displayModelBounds: "Display Model Bounds"; - readonly property string displayModelTriangles: "Display Model Triangles"; - readonly property string displayModelElementChildProxies: "Display Model Element Children"; - readonly property string displayModelElementProxy: "Display Model Element Bounds"; - readonly property string displayDebugTimingDetails: "Display Timing Details"; - readonly property string dontDoPrecisionPicking: "Don't Do Precision Picking"; - readonly property string dontRenderEntitiesAsScene: "Don't Render Entities as Scene"; - readonly property string echoLocalAudio: "Echo Local Audio"; - readonly property string echoServerAudio: "Echo Server Audio"; - readonly property string enable3DTVMode: "Enable 3DTV Mode"; - readonly property string enableCharacterController: "Enable avatar collisions"; - readonly property string expandMyAvatarSimulateTiming: "Expand /myAvatar/simulation"; - readonly property string expandMyAvatarTiming: "Expand /myAvatar"; - readonly property string expandOtherAvatarTiming: "Expand /otherAvatar"; - readonly property string expandPaintGLTiming: "Expand /paintGL"; - readonly property string expandUpdateTiming: "Expand /update"; - readonly property string faceshift: "Faceshift"; - readonly property string firstPerson: "First Person"; - readonly property string fivePointCalibration: "5 Point Calibration"; - readonly property string fixGaze: "Fix Gaze (no saccade)"; - readonly property string forward: "Forward"; - readonly property string frameTimer: "Show Timer"; - readonly property string fullscreenMirror: "Mirror"; - readonly property string help: "Help..."; - readonly property string increaseAvatarSize: "Increase Avatar Size"; - readonly property string independentMode: "Independent Mode"; - readonly property string inputMenu: "Avatar>Input Devices"; - readonly property string keyboardMotorControl: "Enable Keyboard Motor Control"; - readonly property string leapMotionOnHMD: "Leap Motion on HMD"; - readonly property string loadScript: "Open and Run Script File..."; - readonly property string loadScriptURL: "Open and Run Script from URL..."; - readonly property string lodTools: "LOD Tools"; - readonly property string login: "Login"; - readonly property string log: "Log"; - readonly property string logExtraTimings: "Log Extra Timing Details"; - readonly property string lowVelocityFilter: "Low Velocity Filter"; - readonly property string meshVisible: "Draw Mesh"; - readonly property string miniMirror: "Mini Mirror"; - readonly property string muteAudio: "Mute Microphone"; - readonly property string muteEnvironment: "Mute Environment"; - readonly property string muteFaceTracking: "Mute Face Tracking"; - readonly property string namesAboveHeads: "Names Above Heads"; - readonly property string noFaceTracking: "None"; - readonly property string octreeStats: "Entity Statistics"; - readonly property string onePointCalibration: "1 Point Calibration"; - readonly property string onlyDisplayTopTen: "Only Display Top Ten"; - readonly property string outputMenu: "Display"; - readonly property string packageModel: "Package Model..."; - readonly property string pair: "Pair"; - readonly property string physicsShowOwned: "Highlight Simulation Ownership"; - readonly property string physicsShowHulls: "Draw Collision Hulls"; - readonly property string pipelineWarnings: "Log Render Pipeline Warnings"; - readonly property string preferences: "General..."; - readonly property string quit: "Quit"; - readonly property string reloadAllScripts: "Reload All Scripts"; - readonly property string reloadContent: "Reload Content (Clears all caches)"; - readonly property string renderBoundingCollisionShapes: "Show Bounding Collision Shapes"; - readonly property string renderFocusIndicator: "Show Eye Focus"; - readonly property string renderLookAtTargets: "Show Look-at Targets"; - readonly property string renderLookAtVectors: "Show Look-at Vectors"; - readonly property string renderResolution: "Scale Resolution"; - readonly property string renderResolutionOne: "1"; - readonly property string renderResolutionTwoThird: "2/3"; - readonly property string renderResolutionHalf: "1/2"; - readonly property string renderResolutionThird: "1/3"; - readonly property string renderResolutionQuarter: "1/4"; - readonly property string renderAmbientLight: "Ambient Light"; - readonly property string renderAmbientLightGlobal: "Global"; - readonly property string renderAmbientLight0: "OLD_TOWN_SQUARE"; - readonly property string renderAmbientLight1: "GRACE_CATHEDRAL"; - readonly property string renderAmbientLight2: "EUCALYPTUS_GROVE"; - readonly property string renderAmbientLight3: "ST_PETERS_BASILICA"; - readonly property string renderAmbientLight4: "UFFIZI_GALLERY"; - readonly property string renderAmbientLight5: "GALILEOS_TOMB"; - readonly property string renderAmbientLight6: "VINE_STREET_KITCHEN"; - readonly property string renderAmbientLight7: "BREEZEWAY"; - readonly property string renderAmbientLight8: "CAMPUS_SUNSET"; - readonly property string renderAmbientLight9: "FUNSTON_BEACH_SUNSET"; - readonly property string resetAvatarSize: "Reset Avatar Size"; - readonly property string resetSensors: "Reset Sensors"; - readonly property string runningScripts: "Running Scripts..."; - readonly property string runTimingTests: "Run Timing Tests"; - readonly property string scriptEditor: "Script Editor..."; - readonly property string scriptedMotorControl: "Enable Scripted Motor Control"; - readonly property string showDSConnectTable: "Show Domain Connection Timing"; - readonly property string showBordersEntityNodes: "Show Entity Nodes"; - readonly property string showRealtimeEntityStats: "Show Realtime Entity Stats"; - readonly property string showWhosLookingAtMe: "Show Who's Looking at Me"; - readonly property string standingHMDSensorMode: "Standing HMD Sensor Mode"; - readonly property string simulateEyeTracking: "Simulate"; - readonly property string sMIEyeTracking: "SMI Eye Tracking"; - readonly property string stars: "Stars"; - readonly property string stats: "Stats"; - readonly property string stopAllScripts: "Stop All Scripts"; - readonly property string suppressShortTimings: "Suppress Timings Less than 10ms"; - readonly property string thirdPerson: "Third Person"; - readonly property string threePointCalibration: "3 Point Calibration"; - readonly property string throttleFPSIfNotFocus: "Throttle FPS If Not Focus"; // FIXME - this value duplicated in Basic2DWindowOpenGLDisplayPlugin.cpp - readonly property string toolWindow: "Tool Window"; - readonly property string transmitterDrive: "Transmitter Drive"; - readonly property string turnWithHead: "Turn using Head"; - readonly property string useAudioForMouth: "Use Audio for Mouth"; - readonly property string useCamera: "Use Camera"; - readonly property string velocityFilter: "Velocity Filter"; - readonly property string visibleToEveryone: "Everyone"; - readonly property string visibleToFriends: "Friends"; - readonly property string visibleToNoOne: "No one"; - readonly property string worldAxes: "World Axes"; -} - diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 8ebb9a1f9d..b0e4880011 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -762,6 +762,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : // Tell our entity edit sender about our known jurisdictions _entityEditSender.setServerJurisdictions(&_entityServerJurisdictions); + _entityEditSender.setMyAvatar(getMyAvatar()); // For now we're going to set the PPS for outbound packets to be super high, this is // probably not the right long term solution. But for now, we're going to do this to @@ -1050,6 +1051,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : // Make sure we don't time out during slow operations at startup updateHeartbeat(); + OctreeEditPacketSender* packetSender = entityScriptingInterface->getPacketSender(); + EntityEditPacketSender* entityPacketSender = static_cast(packetSender); + entityPacketSender->setMyAvatar(getMyAvatar()); + connect(this, &Application::applicationStateChanged, this, &Application::activeChanged); qCDebug(interfaceapp, "Startup time: %4.2f seconds.", (double)startupTimer.elapsed() / 1000.0); @@ -3589,10 +3594,6 @@ void Application::update(float deltaTime) { int Application::sendNackPackets() { - if (Menu::getInstance()->isOptionChecked(MenuOption::DisableNackPackets)) { - return 0; - } - // iterates through all nodes in NodeList auto nodeList = DependencyManager::get(); @@ -4194,6 +4195,7 @@ void Application::clearDomainOctreeDetails() { qCDebug(interfaceapp) << "Clearing domain octree details..."; resetPhysicsReadyInformation(); + getMyAvatar()->setAvatarEntityDataChanged(true); // to recreate worn entities // reset our node to stats and node to jurisdiction maps... since these must be changing... _entityServerJurisdictions.withWriteLock([&] { diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index f946553b55..538410a47d 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -530,9 +530,6 @@ Menu::Menu() { // Developer > Network >>> MenuWrapper* networkMenu = developerMenu->addMenu("Network"); addActionToQMenuAndActionHash(networkMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches())); - addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::DisableNackPackets, 0, false, - qApp->getEntityEditPacketSender(), - SLOT(toggleNackPackets())); addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::DisableActivityLogger, 0, diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 7230ac2157..36d285e2cf 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -84,7 +84,6 @@ namespace MenuOption { const QString DisableActivityLogger = "Disable Activity Logger"; const QString DisableEyelidAdjustment = "Disable Eyelid Adjustment"; const QString DisableLightEntities = "Disable Light Entities"; - const QString DisableNackPackets = "Disable Entity NACK Packets"; const QString DiskCacheEditor = "Disk Cache Editor"; const QString DisplayCrashOptions = "Display Crash Options"; const QString DisplayHandTargets = "Show Hand Targets"; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 9ae636af36..d12306a122 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include "Application.h" @@ -102,6 +103,18 @@ Avatar::Avatar(RigPointer rig) : Avatar::~Avatar() { assert(isDead()); // mark dead before calling the dtor + + EntityTreeRenderer* treeRenderer = qApp->getEntities(); + EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; + if (entityTree) { + entityTree->withWriteLock([&] { + AvatarEntityMap avatarEntities = getAvatarEntityData(); + for (auto entityID : avatarEntities.keys()) { + entityTree->deleteEntity(entityID, true, true); + } + }); + } + if (_motionState) { delete _motionState; _motionState = nullptr; @@ -157,6 +170,90 @@ void Avatar::animateScaleChanges(float deltaTime) { } } +void Avatar::updateAvatarEntities() { + // - if queueEditEntityMessage sees clientOnly flag it does _myAvatar->updateAvatarEntity() + // - updateAvatarEntity saves the bytes and sets _avatarEntityDataLocallyEdited + // - MyAvatar::update notices _avatarEntityDataLocallyEdited and calls sendIdentityPacket + // - sendIdentityPacket sends the entity bytes to the server which relays them to other interfaces + // - AvatarHashMap::processAvatarIdentityPacket on other interfaces call avatar->setAvatarEntityData() + // - setAvatarEntityData saves the bytes and sets _avatarEntityDataChanged = true + // - (My)Avatar::simulate notices _avatarEntityDataChanged and here we are... + + if (!_avatarEntityDataChanged) { + return; + } + + if (getID() == QUuid()) { + return; // wait until MyAvatar gets an ID before doing this. + } + + EntityTreeRenderer* treeRenderer = qApp->getEntities(); + EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; + if (!entityTree) { + return; + } + + bool success = true; + QScriptEngine scriptEngine; + entityTree->withWriteLock([&] { + AvatarEntityMap avatarEntities = getAvatarEntityData(); + for (auto entityID : avatarEntities.keys()) { + // see EntityEditPacketSender::queueEditEntityMessage for the other end of this. unpack properties + // and either add or update the entity. + QByteArray jsonByteArray = avatarEntities.value(entityID); + QJsonDocument jsonProperties = QJsonDocument::fromBinaryData(jsonByteArray); + if (!jsonProperties.isObject()) { + qCDebug(interfaceapp) << "got bad avatarEntity json" << QString(jsonByteArray.toHex()); + continue; + } + + QVariant variantProperties = jsonProperties.toVariant(); + QVariantMap asMap = variantProperties.toMap(); + QScriptValue scriptProperties = variantMapToScriptValue(asMap, scriptEngine); + EntityItemProperties properties; + EntityItemPropertiesFromScriptValueHonorReadOnly(scriptProperties, properties); + properties.setClientOnly(true); + properties.setOwningAvatarID(getID()); + + // there's no entity-server to tell us we're the simulation owner, so always set the + // simulationOwner to the owningAvatarID and a high priority. + properties.setSimulationOwner(getID(), AVATAR_ENTITY_SIMULATION_PRIORITY); + + if (properties.getParentID() == AVATAR_SELF_ID) { + properties.setParentID(getID()); + } + + EntityItemPointer entity = entityTree->findEntityByEntityItemID(EntityItemID(entityID)); + + if (entity) { + if (entityTree->updateEntity(entityID, properties)) { + entity->updateLastEditedFromRemote(); + } else { + success = false; + } + } else { + entity = entityTree->addEntity(entityID, properties); + if (!entity) { + success = false; + } + } + } + + AvatarEntityIDs recentlyDettachedAvatarEntities = getAndClearRecentlyDetachedIDs(); + foreach (auto entityID, recentlyDettachedAvatarEntities) { + if (!_avatarEntityData.contains(entityID)) { + entityTree->deleteEntity(entityID, true, true); + } + } + }); + + if (success) { + setAvatarEntityDataChanged(false); + } +} + + + void Avatar::simulate(float deltaTime) { PerformanceTimer perfTimer("simulate"); @@ -229,6 +326,7 @@ void Avatar::simulate(float deltaTime) { simulateAttachments(deltaTime); updatePalms(); + updateAvatarEntities(); } bool Avatar::isLookingAtMe(AvatarSharedPointer avatar) const { @@ -1088,7 +1186,7 @@ void Avatar::setParentID(const QUuid& parentID) { if (success) { setTransform(beforeChangeTransform, success); if (!success) { - qDebug() << "Avatar::setParentID failed to reset avatar's location."; + qCDebug(interfaceapp) << "Avatar::setParentID failed to reset avatar's location."; } } } @@ -1103,7 +1201,7 @@ void Avatar::setParentJointIndex(quint16 parentJointIndex) { if (success) { setTransform(beforeChangeTransform, success); if (!success) { - qDebug() << "Avatar::setParentJointIndex failed to reset avatar's location."; + qCDebug(interfaceapp) << "Avatar::setParentJointIndex failed to reset avatar's location."; } } } diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 2580ac1d37..7072ce3847 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -64,6 +64,7 @@ public: typedef std::shared_ptr PayloadPointer; void init(); + void updateAvatarEntities(); void simulate(float deltaTime); virtual void simulateAttachments(float deltaTime); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 740caf7a7a..2366d078bd 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -309,6 +309,10 @@ void MyAvatar::update(float deltaTime) { head->setAudioLoudness(audio->getLastInputLoudness()); head->setAudioAverageLoudness(audio->getAudioAverageInputLoudness()); + if (_avatarEntityDataLocallyEdited) { + sendIdentityPacket(); + } + simulate(deltaTime); currentEnergy += energyChargeRate; @@ -448,7 +452,8 @@ void MyAvatar::simulate(float deltaTime) { EntityItemProperties properties = entity->getProperties(); properties.setQueryAACubeDirty(); properties.setLastEdited(now); - packetSender->queueEditEntityMessage(PacketType::EntityEdit, entity->getID(), properties); + + packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, entity->getID(), properties); entity->setLastBroadcast(usecTimestampNow()); } } @@ -464,6 +469,8 @@ void MyAvatar::simulate(float deltaTime) { _characterController.setEnabled(true); } } + + updateAvatarEntities(); } // thread-safe @@ -711,6 +718,16 @@ void MyAvatar::saveData() { } settings.endArray(); + settings.beginWriteArray("avatarEntityData"); + int avatarEntityIndex = 0; + for (auto entityID : _avatarEntityData.keys()) { + settings.setArrayIndex(avatarEntityIndex); + settings.setValue("id", entityID); + settings.setValue("properties", _avatarEntityData.value(entityID)); + avatarEntityIndex++; + } + settings.endArray(); + settings.setValue("displayName", _displayName); settings.setValue("collisionSoundURL", _collisionSoundURL); settings.setValue("useSnapTurn", _useSnapTurn); @@ -822,6 +839,17 @@ void MyAvatar::loadData() { settings.endArray(); setAttachmentData(attachmentData); + int avatarEntityCount = settings.beginReadArray("avatarEntityData"); + for (int i = 0; i < avatarEntityCount; i++) { + settings.setArrayIndex(i); + QUuid entityID = settings.value("id").toUuid(); + // QUuid entityID = QUuid::createUuid(); // generate a new ID + QByteArray properties = settings.value("properties").toByteArray(); + updateAvatarEntity(entityID, properties); + } + settings.endArray(); + setAvatarEntityDataChanged(true); + setDisplayName(settings.value("displayName").toString()); setCollisionSoundURL(settings.value("collisionSoundURL", DEFAULT_AVATAR_COLLISION_SOUND_URL).toString()); setSnapTurn(settings.value("useSnapTurn", _useSnapTurn).toBool()); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 9c556dc42b..b26cecbc9e 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -955,14 +955,16 @@ void AvatarData::clearJointsData() { } bool AvatarData::hasIdentityChangedAfterParsing(const QByteArray& data) { + // this is used by the avatar-mixer QDataStream packetStream(data); QUuid avatarUUID; QUrl unusedModelURL; // legacy faceModel support QUrl skeletonModelURL; QVector attachmentData; + AvatarEntityMap avatarEntityData; QString displayName; - packetStream >> avatarUUID >> unusedModelURL >> skeletonModelURL >> attachmentData >> displayName; + packetStream >> avatarUUID >> unusedModelURL >> skeletonModelURL >> attachmentData >> displayName >> avatarEntityData; bool hasIdentityChanged = false; @@ -982,6 +984,11 @@ bool AvatarData::hasIdentityChangedAfterParsing(const QByteArray& data) { hasIdentityChanged = true; } + if (avatarEntityData != _avatarEntityData) { + setAvatarEntityData(avatarEntityData); + hasIdentityChanged = true; + } + return hasIdentityChanged; } @@ -993,7 +1000,7 @@ QByteArray AvatarData::identityByteArray() { QUrl unusedModelURL; // legacy faceModel support - identityStream << QUuid() << unusedModelURL << urlToSend << _attachmentData << _displayName; + identityStream << QUuid() << unusedModelURL << urlToSend << _attachmentData << _displayName << _avatarEntityData; return identityData; } @@ -1167,6 +1174,8 @@ void AvatarData::sendIdentityPacket() { [&](const SharedNodePointer& node) { nodeList->sendPacketList(std::move(packetList), *node); }); + + _avatarEntityDataLocallyEdited = false; } void AvatarData::updateJointMappings() { @@ -1339,6 +1348,7 @@ static const QString JSON_AVATAR_HEAD_MODEL = QStringLiteral("headModel"); static const QString JSON_AVATAR_BODY_MODEL = QStringLiteral("bodyModel"); static const QString JSON_AVATAR_DISPLAY_NAME = QStringLiteral("displayName"); static const QString JSON_AVATAR_ATTACHEMENTS = QStringLiteral("attachments"); +static const QString JSON_AVATAR_ENTITIES = QStringLiteral("attachedEntities"); static const QString JSON_AVATAR_SCALE = QStringLiteral("scale"); QJsonValue toJsonValue(const JointData& joint) { @@ -1377,6 +1387,17 @@ QJsonObject AvatarData::toJson() const { root[JSON_AVATAR_ATTACHEMENTS] = attachmentsJson; } + if (!_avatarEntityData.empty()) { + QJsonArray avatarEntityJson; + for (auto entityID : _avatarEntityData.keys()) { + QVariantMap entityData; + entityData.insert("id", entityID); + entityData.insert("properties", _avatarEntityData.value(entityID)); + avatarEntityJson.push_back(QVariant(entityData).toJsonObject()); + } + root[JSON_AVATAR_ENTITIES] = avatarEntityJson; + } + auto recordingBasis = getRecordingBasis(); bool success; Transform avatarTransform = getTransform(success); @@ -1476,6 +1497,13 @@ void AvatarData::fromJson(const QJsonObject& json) { setAttachmentData(attachments); } + // if (json.contains(JSON_AVATAR_ENTITIES) && json[JSON_AVATAR_ENTITIES].isArray()) { + // QJsonArray attachmentsJson = json[JSON_AVATAR_ATTACHEMENTS].toArray(); + // for (auto attachmentJson : attachmentsJson) { + // // TODO -- something + // } + // } + // Joint rotations are relative to the avatar, so they require no basis correction if (json.contains(JSON_AVATAR_JOINT_ARRAY)) { QVector jointArray; @@ -1628,9 +1656,69 @@ void AvatarData::setAttachmentsVariant(const QVariantList& variant) { QVector newAttachments; newAttachments.reserve(variant.size()); for (const auto& attachmentVar : variant) { - AttachmentData attachment; + AttachmentData attachment; attachment.fromVariant(attachmentVar); newAttachments.append(attachment); } setAttachmentData(newAttachments); } + +void AvatarData::updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "updateAvatarEntity", Q_ARG(const QUuid&, entityID), Q_ARG(QByteArray, entityData)); + return; + } + _avatarEntityData.insert(entityID, entityData); + _avatarEntityDataLocallyEdited = true; +} + +void AvatarData::clearAvatarEntity(const QUuid& entityID) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "clearAvatarEntity", Q_ARG(const QUuid&, entityID)); + return; + } + _avatarEntityData.remove(entityID); + _avatarEntityDataLocallyEdited = true; +} + +AvatarEntityMap AvatarData::getAvatarEntityData() const { + if (QThread::currentThread() != thread()) { + AvatarEntityMap result; + QMetaObject::invokeMethod(const_cast(this), "getAvatarEntityData", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(AvatarEntityMap, result)); + return result; + } + return _avatarEntityData; +} + +void AvatarData::setAvatarEntityData(const AvatarEntityMap& avatarEntityData) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setAvatarEntityData", Q_ARG(const AvatarEntityMap&, avatarEntityData)); + return; + } + if (_avatarEntityData != avatarEntityData) { + // keep track of entities that were attached to this avatar but no longer are + AvatarEntityIDs previousAvatarEntityIDs = QSet::fromList(_avatarEntityData.keys()); + + _avatarEntityData = avatarEntityData; + setAvatarEntityDataChanged(true); + + foreach (auto entityID, previousAvatarEntityIDs) { + if (!_avatarEntityData.contains(entityID)) { + _avatarEntityDetached.insert(entityID); + } + } + } +} + +AvatarEntityIDs AvatarData::getAndClearRecentlyDetachedIDs() { + if (QThread::currentThread() != thread()) { + AvatarEntityIDs result; + QMetaObject::invokeMethod(const_cast(this), "getRecentlyDetachedIDs", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(AvatarEntityIDs, result)); + return result; + } + AvatarEntityIDs result = _avatarEntityDetached; + _avatarEntityDetached.clear(); + return result; +} diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index e9dc100a7c..2402a052c6 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -61,6 +61,8 @@ typedef unsigned long long quint64; using AvatarSharedPointer = std::shared_ptr; using AvatarWeakPointer = std::weak_ptr; using AvatarHash = QHash; +using AvatarEntityMap = QMap; +using AvatarEntityIDs = QSet; using AvatarDataSequenceNumber = uint16_t; @@ -134,6 +136,10 @@ class AttachmentData; class Transform; using TransformPointer = std::shared_ptr; +// When writing out avatarEntities to a QByteArray, if the parentID is the ID of MyAvatar, use this ID instead. This allows +// the value to be reset when the sessionID changes. +const QUuid AVATAR_SELF_ID = QUuid("{00000000-0000-0000-0000-000000000001}"); + class AvatarData : public QObject, public SpatiallyNestable { Q_OBJECT @@ -272,6 +278,9 @@ public: Q_INVOKABLE QVariantList getAttachmentsVariant() const; Q_INVOKABLE void setAttachmentsVariant(const QVariantList& variant); + Q_INVOKABLE void updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData); + Q_INVOKABLE void clearAvatarEntity(const QUuid& entityID); + void setForceFaceTrackerConnected(bool connected) { _forceFaceTrackerConnected = connected; } // key state @@ -323,6 +332,11 @@ public: glm::vec3 getClientGlobalPosition() { return _globalPosition; } + Q_INVOKABLE AvatarEntityMap getAvatarEntityData() const; + Q_INVOKABLE void setAvatarEntityData(const AvatarEntityMap& avatarEntityData); + void setAvatarEntityDataChanged(bool value) { _avatarEntityDataChanged = value; } + AvatarEntityIDs getAndClearRecentlyDetachedIDs(); + public slots: void sendAvatarDataPacket(); void sendIdentityPacket(); @@ -390,6 +404,11 @@ protected: // updates about one avatar to another. glm::vec3 _globalPosition; + AvatarEntityIDs _avatarEntityDetached; // recently detached from this avatar + AvatarEntityMap _avatarEntityData; + bool _avatarEntityDataLocallyEdited { false }; + bool _avatarEntityDataChanged { false }; + private: friend void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar); static QUrl _defaultFullAvatarModelUrl; diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index f14e2b0ad3..bd43560ae8 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -107,21 +107,23 @@ void AvatarHashMap::processAvatarDataPacket(QSharedPointer mess } void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer message, SharedNodePointer sendingNode) { + // this is used by clients // setup a data stream to parse the packet QDataStream identityStream(message->getMessage()); QUuid sessionUUID; - + while (!identityStream.atEnd()) { QUrl faceMeshURL, skeletonURL; QVector attachmentData; + AvatarEntityMap avatarEntityData; QString displayName; - identityStream >> sessionUUID >> faceMeshURL >> skeletonURL >> attachmentData >> displayName; + identityStream >> sessionUUID >> faceMeshURL >> skeletonURL >> attachmentData >> displayName >> avatarEntityData; // mesh URL for a UUID, find avatar in our list auto avatar = newOrExistingAvatar(sessionUUID, sendingNode); - + if (avatar->getSkeletonModelURL().isEmpty() || (avatar->getSkeletonModelURL() != skeletonURL)) { avatar->setSkeletonModelURL(skeletonURL); // Will expand "" to default and so will not continuously fire } @@ -130,6 +132,8 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer avatar->setAttachmentData(attachmentData); } + avatar->setAvatarEntityData(avatarEntityData); + if (avatar->getDisplayName() != displayName) { avatar->setDisplayName(displayName); } diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index d148145dde..011675fc82 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -47,6 +47,9 @@ namespace render { } void makeEntityItemStatusGetters(EntityItemPointer entity, render::Item::Status::Getters& statusGetters) { + auto nodeList = DependencyManager::get(); + const QUuid& myNodeID = nodeList->getSessionUUID(); + statusGetters.push_back([entity] () -> render::Item::Status::Value { quint64 delta = usecTimestampNow() - entity->getLastEditedFromRemote(); const float WAIT_THRESHOLD_INV = 1.0f / (0.2f * USECS_PER_SECOND); @@ -81,9 +84,7 @@ void makeEntityItemStatusGetters(EntityItemPointer entity, render::Item::Status: (unsigned char)RenderItemStatusIcon::ACTIVE_IN_BULLET); }); - statusGetters.push_back([entity] () -> render::Item::Status::Value { - auto nodeList = DependencyManager::get(); - const QUuid& myNodeID = nodeList->getSessionUUID(); + statusGetters.push_back([entity, myNodeID] () -> render::Item::Status::Value { bool weOwnSimulation = entity->getSimulationOwner().matchesValidID(myNodeID); bool otherOwnSimulation = !weOwnSimulation && !entity->getSimulationOwner().isNull(); @@ -106,4 +107,18 @@ void makeEntityItemStatusGetters(EntityItemPointer entity, render::Item::Status: return render::Item::Status::Value(0.0f, render::Item::Status::Value::GREEN, (unsigned char)RenderItemStatusIcon::HAS_ACTIONS); }); + + statusGetters.push_back([entity, myNodeID] () -> render::Item::Status::Value { + if (entity->getClientOnly()) { + if (entity->getOwningAvatarID() == myNodeID) { + return render::Item::Status::Value(1.0f, render::Item::Status::Value::GREEN, + (unsigned char)RenderItemStatusIcon::CLIENT_ONLY); + } else { + return render::Item::Status::Value(1.0f, render::Item::Status::Value::RED, + (unsigned char)RenderItemStatusIcon::CLIENT_ONLY); + } + } + return render::Item::Status::Value(0.0f, render::Item::Status::Value::GREEN, + (unsigned char)RenderItemStatusIcon::CLIENT_ONLY); + }); } diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index 09451e87d4..9840bf3150 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -26,6 +26,7 @@ enum class RenderItemStatusIcon { SIMULATION_OWNER = 3, HAS_ACTIONS = 4, OTHER_SIMULATION_OWNER = 5, + CLIENT_ONLY = 6, NONE = 255 }; diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 1d5570b8b7..ad35a1a00c 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -981,7 +981,7 @@ void RenderablePolyVoxEntityItem::compressVolumeDataAndSendEditPacket() { PhysicalEntitySimulationPointer peSimulation = std::static_pointer_cast(simulation); EntityEditPacketSender* packetSender = peSimulation ? peSimulation->getPacketSender() : nullptr; if (packetSender) { - packetSender->queueEditEntityMessage(PacketType::EntityEdit, entity->getID(), properties); + packetSender->queueEditEntityMessage(PacketType::EntityEdit, tree, entity->getID(), properties); } }); }); diff --git a/libraries/entities/src/EntityEditPacketSender.cpp b/libraries/entities/src/EntityEditPacketSender.cpp index 1e38c32964..b96911c8c2 100644 --- a/libraries/entities/src/EntityEditPacketSender.cpp +++ b/libraries/entities/src/EntityEditPacketSender.cpp @@ -10,6 +10,7 @@ // #include +#include #include #include #include @@ -24,9 +25,7 @@ EntityEditPacketSender::EntityEditPacketSender() { } void EntityEditPacketSender::processEntityEditNackPacket(QSharedPointer message, SharedNodePointer sendingNode) { - if (_shouldProcessNack) { - processNackPacket(*message, sendingNode); - } + processNackPacket(*message, sendingNode); } void EntityEditPacketSender::adjustEditPacketForClockSkew(PacketType type, QByteArray& buffer, qint64 clockSkew) { @@ -35,18 +34,76 @@ void EntityEditPacketSender::adjustEditPacketForClockSkew(PacketType type, QByte } } -void EntityEditPacketSender::queueEditEntityMessage(PacketType type, EntityItemID modelID, - const EntityItemProperties& properties) { +void EntityEditPacketSender::queueEditAvatarEntityMessage(PacketType type, + EntityTreePointer entityTree, + EntityItemID entityItemID, + const EntityItemProperties& properties) { if (!_shouldSend) { return; // bail early } + if (properties.getOwningAvatarID() != _myAvatar->getID()) { + return; // don't send updates for someone else's avatarEntity + } + + assert(properties.getClientOnly()); + + // this is an avatar-based entity. update our avatar-data rather than sending to the entity-server + assert(_myAvatar); + + if (!entityTree) { + qDebug() << "EntityEditPacketSender::queueEditEntityMessage null entityTree."; + return; + } + EntityItemPointer entity = entityTree->findEntityByEntityItemID(entityItemID); + if (!entity) { + qDebug() << "EntityEditPacketSender::queueEditEntityMessage can't find entity."; + return; + } + + // the properties that get serialized into the avatar identity packet should be the entire set + // rather than just the ones being edited. + entity->setProperties(properties); + EntityItemProperties entityProperties = entity->getProperties(); + + QScriptValue scriptProperties = EntityItemNonDefaultPropertiesToScriptValue(&_scriptEngine, entityProperties); + QVariant variantProperties = scriptProperties.toVariant(); + QJsonDocument jsonProperties = QJsonDocument::fromVariant(variantProperties); + + // the ID of the parent/avatar changes from session to session. use a special UUID to indicate the avatar + QJsonObject jsonObject = jsonProperties.object(); + if (QUuid(jsonObject["parentID"].toString()) == _myAvatar->getID()) { + jsonObject["parentID"] = AVATAR_SELF_ID.toString(); + } + jsonProperties = QJsonDocument(jsonObject); + + QByteArray binaryProperties = jsonProperties.toBinaryData(); + _myAvatar->updateAvatarEntity(entityItemID, binaryProperties); + + entity->setLastBroadcast(usecTimestampNow()); + return; +} + + +void EntityEditPacketSender::queueEditEntityMessage(PacketType type, + EntityTreePointer entityTree, + EntityItemID entityItemID, + const EntityItemProperties& properties) { + if (!_shouldSend) { + return; // bail early + } + + if (properties.getClientOnly()) { + queueEditAvatarEntityMessage(type, entityTree, entityItemID, properties); + return; + } + QByteArray bufferOut(NLPacket::maxPayloadSize(type), 0); - if (EntityItemProperties::encodeEntityEditPacket(type, modelID, properties, bufferOut)) { + if (EntityItemProperties::encodeEntityEditPacket(type, entityItemID, properties, bufferOut)) { #ifdef WANT_DEBUG qCDebug(entities) << "calling queueOctreeEditMessage()..."; - qCDebug(entities) << " id:" << modelID; + qCDebug(entities) << " id:" << entityItemID; qCDebug(entities) << " properties:" << properties; #endif queueOctreeEditMessage(type, bufferOut); @@ -58,6 +115,10 @@ void EntityEditPacketSender::queueEraseEntityMessage(const EntityItemID& entityI return; // bail early } + // in case this was a clientOnly entity: + assert(_myAvatar); + _myAvatar->clearAvatarEntity(entityItemID); + QByteArray bufferOut(NLPacket::maxPayloadSize(PacketType::EntityErase), 0); if (EntityItemProperties::encodeEraseEntityMessage(entityItemID, bufferOut)) { diff --git a/libraries/entities/src/EntityEditPacketSender.h b/libraries/entities/src/EntityEditPacketSender.h index 26e4dd83ff..1991142f3f 100644 --- a/libraries/entities/src/EntityEditPacketSender.h +++ b/libraries/entities/src/EntityEditPacketSender.h @@ -15,6 +15,7 @@ #include #include "EntityItem.h" +#include "AvatarData.h" /// Utility for processing, packing, queueing and sending of outbound edit voxel messages. class EntityEditPacketSender : public OctreeEditPacketSender { @@ -22,11 +23,21 @@ class EntityEditPacketSender : public OctreeEditPacketSender { public: EntityEditPacketSender(); + void setMyAvatar(AvatarData* myAvatar) { _myAvatar = myAvatar; } + AvatarData* getMyAvatar() { return _myAvatar; } + void clearAvatarEntity(QUuid entityID) { assert(_myAvatar); _myAvatar->clearAvatarEntity(entityID); } + + void queueEditAvatarEntityMessage(PacketType type, EntityTreePointer entityTree, + EntityItemID entityItemID, const EntityItemProperties& properties); + + /// Queues an array of several voxel edit messages. Will potentially send a pending multi-command packet. Determines /// which voxel-server node or nodes the packet should be sent to. Can be called even before voxel servers are known, in /// which case up to MaxPendingMessages will be buffered and processed when voxel servers are known. /// NOTE: EntityItemProperties assumes that all distances are in meter units - void queueEditEntityMessage(PacketType type, EntityItemID modelID, const EntityItemProperties& properties); + void queueEditEntityMessage(PacketType type, EntityTreePointer entityTree, + EntityItemID entityItemID, const EntityItemProperties& properties); + void queueEraseEntityMessage(const EntityItemID& entityItemID); @@ -36,9 +47,9 @@ public: public slots: void processEntityEditNackPacket(QSharedPointer message, SharedNodePointer sendingNode); - void toggleNackPackets() { _shouldProcessNack = !_shouldProcessNack; } private: - bool _shouldProcessNack = true; + AvatarData* _myAvatar { nullptr }; + QScriptEngine _scriptEngine; }; #endif // hifi_EntityEditPacketSender_h diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index bd4292f75d..dc5fbf7d8e 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -137,6 +137,9 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_PARENT_JOINT_INDEX; requestedProperties += PROP_QUERY_AA_CUBE; + requestedProperties += PROP_CLIENT_ONLY; + requestedProperties += PROP_OWNING_AVATAR_ID; + return requestedProperties; } @@ -1093,6 +1096,8 @@ EntityItemProperties EntityItem::getProperties(EntityPropertyFlags desiredProper properties._id = getID(); properties._idSet = true; properties._created = _created; + properties.setClientOnly(_clientOnly); + properties.setOwningAvatarID(_owningAvatarID); properties._type = getType(); @@ -1133,6 +1138,9 @@ EntityItemProperties EntityItem::getProperties(EntityPropertyFlags desiredProper COPY_ENTITY_PROPERTY_TO_PROPERTIES(localPosition, getLocalPosition); COPY_ENTITY_PROPERTY_TO_PROPERTIES(localRotation, getLocalOrientation); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(clientOnly, getClientOnly); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(owningAvatarID, getOwningAvatarID); + properties._defaultSettings = false; return properties; @@ -1222,6 +1230,9 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(parentJointIndex, setParentJointIndex); SET_ENTITY_PROPERTY_FROM_PROPERTIES(queryAACube, setQueryAACube); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(clientOnly, setClientOnly); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(owningAvatarID, setOwningAvatarID); + AACube saveQueryAACube = _queryAACube; checkAndAdjustQueryAACube(); if (saveQueryAACube != _queryAACube) { diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 4d3d93e40f..2c6dc9b74d 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -372,6 +372,7 @@ public: glm::vec3 entityToWorld(const glm::vec3& point) const; quint64 getLastEditedFromRemote() const { return _lastEditedFromRemote; } + void updateLastEditedFromRemote() { _lastEditedFromRemote = usecTimestampNow(); } void getAllTerseUpdateProperties(EntityItemProperties& properties) const; @@ -422,12 +423,19 @@ public: /// entity to definitively state if the preload signal should be sent. /// /// We only want to preload if: - /// there is some script, and either the script value or the scriptTimestamp + /// there is some script, and either the script value or the scriptTimestamp /// value have changed since our last preload - bool shouldPreloadScript() const { return !_script.isEmpty() && + bool shouldPreloadScript() const { return !_script.isEmpty() && ((_loadedScript != _script) || (_loadedScriptTimestamp != _scriptTimestamp)); } void scriptHasPreloaded() { _loadedScript = _script; _loadedScriptTimestamp = _scriptTimestamp; } + bool getClientOnly() const { return _clientOnly; } + void setClientOnly(bool clientOnly) { _clientOnly = clientOnly; } + // if this entity is client-only, which avatar is it associated with? + QUuid getOwningAvatarID() const { return _owningAvatarID; } + void setOwningAvatarID(const QUuid& owningAvatarID) { _owningAvatarID = owningAvatarID; } + + protected: void setSimulated(bool simulated) { _simulated = simulated; } @@ -539,6 +547,9 @@ protected: mutable QHash _previouslyDeletedActions; QUuid _sourceUUID; /// the server node UUID we came from + + bool _clientOnly { false }; + QUuid _owningAvatarID; }; #endif // hifi_EntityItem_h diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 2655f59f56..99285b4986 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -307,9 +307,13 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_QUERY_AA_CUBE, queryAACube); CHECK_PROPERTY_CHANGE(PROP_LOCAL_POSITION, localPosition); CHECK_PROPERTY_CHANGE(PROP_LOCAL_ROTATION, localRotation); + CHECK_PROPERTY_CHANGE(PROP_FLYING_ALLOWED, flyingAllowed); CHECK_PROPERTY_CHANGE(PROP_GHOSTING_ALLOWED, ghostingAllowed); + CHECK_PROPERTY_CHANGE(PROP_CLIENT_ONLY, clientOnly); + CHECK_PROPERTY_CHANGE(PROP_OWNING_AVATAR_ID, owningAvatarID); + changedProperties += _animation.getChangedProperties(); changedProperties += _keyLight.getChangedProperties(); changedProperties += _skybox.getChangedProperties(); @@ -544,6 +548,12 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_POSITION, localPosition); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_ROTATION, localRotation); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLIENT_ONLY, clientOnly); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_OWNING_AVATAR_ID, owningAvatarID); + + properties.setProperty("clientOnly", convertScriptValue(engine, getClientOnly())); + properties.setProperty("owningAvatarID", convertScriptValue(engine, getOwningAvatarID())); + // FIXME - I don't think these properties are supported any more //COPY_PROPERTY_TO_QSCRIPTVALUE(localRenderAlpha); @@ -683,6 +693,9 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(flyingAllowed, bool, setFlyingAllowed); COPY_PROPERTY_FROM_QSCRIPTVALUE(ghostingAllowed, bool, setGhostingAllowed); + COPY_PROPERTY_FROM_QSCRIPTVALUE(clientOnly, bool, setClientOnly); + COPY_PROPERTY_FROM_QSCRIPTVALUE(owningAvatarID, QUuid, setOwningAvatarID); + _lastEdited = usecTimestampNow(); } @@ -1579,6 +1592,9 @@ void EntityItemProperties::markAllChanged() { _flyingAllowedChanged = true; _ghostingAllowedChanged = true; + + _clientOnlyChanged = true; + _owningAvatarIDChanged = true; } // The minimum bounding box for the entity. @@ -1899,6 +1915,12 @@ QList EntityItemProperties::listChangedProperties() { if (queryAACubeChanged()) { out += "queryAACube"; } + if (clientOnlyChanged()) { + out += "clientOnly"; + } + if (owningAvatarIDChanged()) { + out += "owningAvatarID"; + } if (flyingAllowedChanged()) { out += "flyingAllowed"; diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 622646fd5f..59749cfef5 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -208,6 +208,9 @@ public: DEFINE_PROPERTY(PROP_FLYING_ALLOWED, FlyingAllowed, flyingAllowed, bool, ZoneEntityItem::DEFAULT_FLYING_ALLOWED); DEFINE_PROPERTY(PROP_GHOSTING_ALLOWED, GhostingAllowed, ghostingAllowed, bool, ZoneEntityItem::DEFAULT_GHOSTING_ALLOWED); + DEFINE_PROPERTY(PROP_CLIENT_ONLY, ClientOnly, clientOnly, bool, false); + DEFINE_PROPERTY_REF(PROP_OWNING_AVATAR_ID, OwningAvatarID, owningAvatarID, QUuid, UNKNOWN_ENTITY_ID); + static QString getBackgroundModeString(BackgroundMode mode); @@ -422,6 +425,9 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, FlyingAllowed, flyingAllowed, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, GhostingAllowed, ghostingAllowed, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, ClientOnly, clientOnly, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, OwningAvatarID, owningAvatarID, ""); + properties.getAnimation().debugDump(); properties.getSkybox().debugDump(); properties.getStage().debugDump(); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 19b47da8e7..20c451eaae 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -172,6 +172,9 @@ enum EntityPropertyList { PROP_FLYING_ALLOWED, // can avatars in a zone fly? PROP_GHOSTING_ALLOWED, // can avatars in a zone turn off physics? + PROP_CLIENT_ONLY, // doesn't go over wire + PROP_OWNING_AVATAR_ID, // doesn't go over wire + //////////////////////////////////////////////////////////////////////////////////////////////////// // 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 01f444491d..15c2bffd80 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -36,7 +36,7 @@ EntityScriptingInterface::EntityScriptingInterface(bool bidOnSimulationOwnership void EntityScriptingInterface::queueEntityMessage(PacketType packetType, EntityItemID entityID, const EntityItemProperties& properties) { - getEntityPacketSender()->queueEditEntityMessage(packetType, entityID, properties); + getEntityPacketSender()->queueEditEntityMessage(packetType, _entityTree, entityID, properties); } bool EntityScriptingInterface::canAdjustLocks() { @@ -123,10 +123,17 @@ EntityItemProperties convertLocationFromScriptSemantics(const EntityItemProperti } -QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties) { +QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties, bool clientOnly) { EntityItemProperties propertiesWithSimID = convertLocationFromScriptSemantics(properties); propertiesWithSimID.setDimensionsInitialized(properties.dimensionsChanged()); + if (clientOnly) { + auto nodeList = DependencyManager::get(); + const QUuid myNodeID = nodeList->getSessionUUID(); + propertiesWithSimID.setClientOnly(clientOnly); + propertiesWithSimID.setOwningAvatarID(myNodeID); + } + auto dimensions = propertiesWithSimID.getDimensions(); float volume = dimensions.x * dimensions.y * dimensions.z; auto density = propertiesWithSimID.getDensity(); @@ -272,13 +279,21 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& bool updatedEntity = false; _entityTree->withWriteLock([&] { + EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); + if (!entity) { + return; + } + + auto nodeList = DependencyManager::get(); + if (entity->getClientOnly() && entity->getOwningAvatarID() != nodeList->getSessionUUID()) { + // don't edit other avatar's avatarEntities + return; + } + if (scriptSideProperties.parentRelatedPropertyChanged()) { // All of parentID, parentJointIndex, position, rotation are needed to make sense of any of them. // If any of these changed, pull any missing properties from the entity. - EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); - if (!entity) { - return; - } + //existing entity, retrieve old velocity for check down below oldVelocity = entity->getVelocity().length(); @@ -296,6 +311,8 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& } } properties = convertLocationFromScriptSemantics(properties); + properties.setClientOnly(entity->getClientOnly()); + properties.setOwningAvatarID(entity->getOwningAvatarID()); float cost = calculateCost(density * volume, oldVelocity, newVelocity); cost *= costMultiplier; @@ -384,6 +401,14 @@ void EntityScriptingInterface::deleteEntity(QUuid id) { EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); if (entity) { + auto nodeList = DependencyManager::get(); + const QUuid myNodeID = nodeList->getSessionUUID(); + if (entity->getClientOnly() && entity->getOwningAvatarID() != myNodeID) { + // don't delete other avatar's avatarEntities + shouldDelete = false; + return; + } + auto dimensions = entity->getDimensions(); float volume = dimensions.x * dimensions.y * dimensions.z; auto density = entity->getDensity(); @@ -771,6 +796,11 @@ bool EntityScriptingInterface::actionWorker(const QUuid& entityID, return false; } + auto nodeList = DependencyManager::get(); + const QUuid myNodeID = nodeList->getSessionUUID(); + + EntityItemProperties properties; + EntityItemPointer entity; bool doTransmit = false; _entityTree->withWriteLock([&] { @@ -786,15 +816,20 @@ bool EntityScriptingInterface::actionWorker(const QUuid& entityID, return; } + if (entity->getClientOnly() && entity->getOwningAvatarID() != myNodeID) { + return; + } + doTransmit = actor(simulation, entity); if (doTransmit) { + properties.setClientOnly(entity->getClientOnly()); + properties.setOwningAvatarID(entity->getOwningAvatarID()); _entityTree->entityChanged(entity); } }); // transmit the change if (doTransmit) { - EntityItemProperties properties; _entityTree->withReadLock([&] { properties = entity->getProperties(); }); diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 5f80b7abb2..2f5446874b 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -82,7 +82,7 @@ public slots: Q_INVOKABLE bool canRez(); /// adds a model with the specific properties - Q_INVOKABLE QUuid addEntity(const EntityItemProperties& properties); + Q_INVOKABLE QUuid addEntity(const EntityItemProperties& properties, bool clientOnly = false); /// temporary method until addEntity can be used from QJSEngine Q_INVOKABLE QUuid addModelEntity(const QString& name, const QString& modelUrl, const glm::vec3& position); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 97d4e91acd..b85ee1dcf9 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -311,7 +311,9 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer result = NULL; - if (getIsClient()) { + bool clientOnly = properties.getClientOnly(); + + if (!clientOnly && getIsClient()) { // if our Node isn't allowed to create entities in this domain, don't try. auto nodeList = DependencyManager::get(); if (nodeList && !nodeList->getThisNodeCanRez()) { @@ -1382,8 +1384,11 @@ bool EntityTree::sendEntitiesOperation(OctreeElementPointer element, void* extra properties.markAllChanged(); // so the entire property set is considered new, since we're making a new entity + EntityTreeElementPointer entityTreeElement = std::static_pointer_cast(element); + EntityTreePointer tree = entityTreeElement->getTree(); + // queue the packet to send to the server - args->packetSender->queueEditEntityMessage(PacketType::EntityAdd, newID, properties); + args->packetSender->queueEditEntityMessage(PacketType::EntityAdd, tree, newID, properties); // also update the local tree instantly (note: this is not our tree, but an alternate tree) if (args->otherTree) { diff --git a/libraries/entities/src/SimulationOwner.h b/libraries/entities/src/SimulationOwner.h index 1afec426d7..5f940bbe25 100644 --- a/libraries/entities/src/SimulationOwner.h +++ b/libraries/entities/src/SimulationOwner.h @@ -27,6 +27,7 @@ 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_GRAB_SIMULATION_PRIORITY = 0x80; const quint8 SCRIPT_POKE_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY - 1; +const quint8 AVATAR_ENTITY_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY + 1; // PERSONAL priority (needs a better name) is the level at which a simulation observer owns its own avatar // which really just means: things that collide with it will be bid at a priority level one lower diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 9f23d3834b..b04d582d6d 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -50,7 +50,7 @@ PacketVersion versionForPacketType(PacketType packetType) { return VERSION_ENTITIES_NO_FLY_ZONES; case PacketType::AvatarData: case PacketType::BulkAvatarData: - return static_cast(AvatarMixerPacketVersion::SoftAttachmentSupport); + return static_cast(AvatarMixerPacketVersion::AvatarEntities); case PacketType::ICEServerHeartbeat: return 18; // ICE Server Heartbeat signing case PacketType::AssetGetInfo: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 42e5fff420..030b4af8c9 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -175,7 +175,8 @@ const PacketVersion VERSION_ENTITIES_NO_FLY_ZONES = 58; enum class AvatarMixerPacketVersion : PacketVersion { TranslationSupport = 17, - SoftAttachmentSupport + SoftAttachmentSupport, + AvatarEntities }; #endif // hifi_PacketHeaders_h diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index f0539110d3..053bfcbd85 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -404,6 +404,11 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) { assert(_body); assert(entityTreeIsLocked()); + if (_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID()) { + // don't send updates for someone else's avatarEntities + return false; + } + if (_entity->actionDataNeedsTransmit()) { return true; } @@ -547,8 +552,14 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ qCDebug(physics) << "EntityMotionState::sendUpdate()... calling queueEditEntityMessage()..."; #endif - entityPacketSender->queueEditEntityMessage(PacketType::EntityEdit, id, properties); - _entity->setLastBroadcast(usecTimestampNow()); + EntityTreeElementPointer element = _entity->getElement(); + EntityTreePointer tree = element ? element->getTree() : nullptr; + + properties.setClientOnly(_entity->getClientOnly()); + properties.setOwningAvatarID(_entity->getOwningAvatarID()); + + entityPacketSender->queueEditEntityMessage(PacketType::EntityEdit, tree, id, properties); + _entity->setLastBroadcast(now); // if we've moved an entity with children, check/update the queryAACube of all descendents and tell the server // if they've changed. @@ -559,8 +570,13 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ EntityItemProperties newQueryCubeProperties; newQueryCubeProperties.setQueryAACube(descendant->getQueryAACube()); newQueryCubeProperties.setLastEdited(properties.getLastEdited()); - entityPacketSender->queueEditEntityMessage(PacketType::EntityEdit, descendant->getID(), newQueryCubeProperties); - entityDescendant->setLastBroadcast(usecTimestampNow()); + + newQueryCubeProperties.setClientOnly(entityDescendant->getClientOnly()); + newQueryCubeProperties.setOwningAvatarID(entityDescendant->getOwningAvatarID()); + + entityPacketSender->queueEditEntityMessage(PacketType::EntityEdit, tree, + descendant->getID(), newQueryCubeProperties); + entityDescendant->setLastBroadcast(now); } } }); diff --git a/scripts/system/assets/images/lock.svg b/scripts/system/assets/images/lock.svg new file mode 100644 index 0000000000..bb9658de00 --- /dev/null +++ b/scripts/system/assets/images/lock.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/scripts/system/assets/images/unlock.svg b/scripts/system/assets/images/unlock.svg new file mode 100644 index 0000000000..789a8b0ed5 --- /dev/null +++ b/scripts/system/assets/images/unlock.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/scripts/system/attachedEntitiesManager.js b/scripts/system/attachedEntitiesManager.js index 9ddb040297..79c5973a6e 100644 --- a/scripts/system/attachedEntitiesManager.js +++ b/scripts/system/attachedEntitiesManager.js @@ -22,42 +22,28 @@ var MINIMUM_DROP_DISTANCE_FROM_JOINT = 0.8; var ATTACHED_ENTITY_SEARCH_DISTANCE = 10.0; var ATTACHED_ENTITIES_SETTINGS_KEY = "ATTACHED_ENTITIES"; var DRESSING_ROOM_DISTANCE = 2.0; -var SHOW_TOOL_BAR = false; +var SHOW_TOOL_BAR = true; // tool bar if (SHOW_TOOL_BAR) { - var BUTTON_SIZE = 32; - var PADDING = 3; + var BUTTON_SIZE = 64; + var PADDING = 6; Script.include(["libraries/toolBars.js"]); - var toolBar = new ToolBar(0, 0, ToolBar.VERTICAL, "highfidelity.attachedEntities.toolbar", function(screenSize) { - return { - x: (BUTTON_SIZE + PADDING), - y: (screenSize.y / 2 - BUTTON_SIZE * 2 + PADDING) - }; - }); - var saveButton = toolBar.addOverlay("image", { + + var toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.attachedEntities.toolbar"); + var lockButton = toolBar.addTool({ width: BUTTON_SIZE, height: BUTTON_SIZE, - imageURL: ".../save.png", + imageURL: Script.resolvePath("assets/images/lock.svg"), color: { red: 255, green: 255, blue: 255 }, - alpha: 1 - }); - var loadButton = toolBar.addOverlay("image", { - width: BUTTON_SIZE, - height: BUTTON_SIZE, - imageURL: ".../load.png", - color: { - red: 255, - green: 255, - blue: 255 - }, - alpha: 1 - }); + alpha: 1, + visible: true + }, false); } @@ -67,10 +53,8 @@ function mousePressEvent(event) { y: event.y }); - if (clickedOverlay == saveButton) { - manager.saveAttachedEntities(); - } else if (clickedOverlay == loadButton) { - manager.loadAttachedEntities(); + if (lockButton === toolBar.clicked(clickedOverlay)) { + manager.toggleLocked(); } } @@ -92,6 +76,8 @@ Script.scriptEnding.connect(scriptEnding); function AttachedEntitiesManager() { + var clothingLocked = true; + this.subscribeToMessages = function() { Messages.subscribe('Hifi-Object-Manipulation'); Messages.messageReceived.connect(this.handleWearableMessages); @@ -128,26 +114,14 @@ function AttachedEntitiesManager() { } } - this.avatarIsInDressingRoom = function() { - // return true if MyAvatar is near the dressing room - var possibleDressingRoom = Entities.findEntities(MyAvatar.position, DRESSING_ROOM_DISTANCE); - for (i = 0; i < possibleDressingRoom.length; i++) { - var entityID = possibleDressingRoom[i]; - var props = Entities.getEntityProperties(entityID); - if (props.name == 'Hifi-Dressing-Room-Base') { - return true; - } - } - return false; - } - this.handleEntityRelease = function(grabbedEntity, releasedFromJoint) { // if this is still equipped, just rewrite the position information. var grabData = getEntityCustomData('grabKey', grabbedEntity, {}); - if ("refCount" in grabData && grabData.refCount > 0) { - manager.updateRelativeOffsets(grabbedEntity); - return; - } + // if ("refCount" in grabData && grabData.refCount > 0) { + // // for adjusting things in your other hand + // manager.updateRelativeOffsets(grabbedEntity); + // return; + // } var allowedJoints = getEntityCustomData('wearable', grabbedEntity, DEFAULT_WEARABLE_DATA).joints; @@ -179,21 +153,27 @@ function AttachedEntitiesManager() { } if (bestJointIndex != -1) { - var wearProps = { - parentID: MyAvatar.sessionUUID, - parentJointIndex: bestJointIndex - }; + var wearProps = Entities.getEntityProperties(grabbedEntity); + wearProps.parentID = MyAvatar.sessionUUID; + wearProps.parentJointIndex = bestJointIndex; + var updatePresets = false; if (bestJointOffset && bestJointOffset.constructor === Array) { - if (this.avatarIsInDressingRoom() || bestJointOffset.length < 2) { - this.updateRelativeOffsets(grabbedEntity); + if (!clothingLocked || bestJointOffset.length < 2) { + // we're unlocked or this thing didn't have a preset position, so update it + updatePresets = true; } else { - // don't snap the entity to the preferred position if the avatar is in the dressing room. + // don't snap the entity to the preferred position if unlocked wearProps.localPosition = bestJointOffset[0]; wearProps.localRotation = bestJointOffset[1]; } } - Entities.editEntity(grabbedEntity, wearProps); + + Entities.deleteEntity(grabbedEntity); + grabbedEntity = Entities.addEntity(wearProps, true); + if (updatePresets) { + this.updateRelativeOffsets(grabbedEntity); + } } else if (props.parentID != NULL_UUID) { // drop the entity and set it to have no parent (not on the avatar), unless it's being equipped in a hand. if (props.parentID === MyAvatar.sessionUUID && @@ -201,7 +181,26 @@ function AttachedEntitiesManager() { props.parentJointIndex == MyAvatar.getJointIndex("LeftHand"))) { // this is equipped on a hand -- don't clear the parent. } else { - Entities.editEntity(grabbedEntity, { parentID: NULL_UUID }); + var wearProps = Entities.getEntityProperties(grabbedEntity); + wearProps.parentID = NULL_UUID; + wearProps.parentJointIndex = -1; + + delete wearProps.id; + delete wearProps.created; + delete wearProps.age; + delete wearProps.ageAsText; + delete wearProps.naturalDimensions; + delete wearProps.naturalPosition; + delete wearProps.actionData; + delete wearProps.sittingPoints; + delete wearProps.boundingBox; + delete wearProps.clientOnly; + delete wearProps.owningAvatarID; + delete wearProps.localPosition; + delete wearProps.localRotation; + + Entities.deleteEntity(grabbedEntity); + Entities.addEntity(wearProps); } } } @@ -221,21 +220,32 @@ function AttachedEntitiesManager() { return false; } - this.saveAttachedEntities = function() { - print("--- saving attached entities ---"); - saveData = []; - var nearbyEntities = Entities.findEntities(MyAvatar.position, ATTACHED_ENTITY_SEARCH_DISTANCE); - for (i = 0; i < nearbyEntities.length; i++) { - var entityID = nearbyEntities[i]; - if (this.updateRelativeOffsets(entityID)) { - var props = Entities.getEntityProperties(entityID); // refresh, because updateRelativeOffsets changed them - this.scrubProperties(props); - saveData.push(props); - } + this.toggleLocked = function() { + print("toggleLocked"); + if (clothingLocked) { + clothingLocked = false; + toolBar.setImageURL(Script.resolvePath("assets/images/unlock.svg"), lockButton); + } else { + clothingLocked = true; + toolBar.setImageURL(Script.resolvePath("assets/images/lock.svg"), lockButton); } - Settings.setValue(ATTACHED_ENTITIES_SETTINGS_KEY, JSON.stringify(saveData)); } + // this.saveAttachedEntities = function() { + // print("--- saving attached entities ---"); + // saveData = []; + // var nearbyEntities = Entities.findEntities(MyAvatar.position, ATTACHED_ENTITY_SEARCH_DISTANCE); + // for (i = 0; i < nearbyEntities.length; i++) { + // var entityID = nearbyEntities[i]; + // if (this.updateRelativeOffsets(entityID)) { + // var props = Entities.getEntityProperties(entityID); // refresh, because updateRelativeOffsets changed them + // this.scrubProperties(props); + // saveData.push(props); + // } + // } + // Settings.setValue(ATTACHED_ENTITIES_SETTINGS_KEY, JSON.stringify(saveData)); + // } + this.scrubProperties = function(props) { var toScrub = ["queryAACube", "position", "rotation", "created", "ageAsText", "naturalDimensions", @@ -258,37 +268,37 @@ function AttachedEntitiesManager() { } } - this.loadAttachedEntities = function(grabbedEntity) { - print("--- loading attached entities ---"); - jsonAttachmentData = Settings.getValue(ATTACHED_ENTITIES_SETTINGS_KEY); - var loadData = []; - try { - loadData = JSON.parse(jsonAttachmentData); - } catch (e) { - print('error parsing saved attachment data'); - return; - } + // this.loadAttachedEntities = function(grabbedEntity) { + // print("--- loading attached entities ---"); + // jsonAttachmentData = Settings.getValue(ATTACHED_ENTITIES_SETTINGS_KEY); + // var loadData = []; + // try { + // loadData = JSON.parse(jsonAttachmentData); + // } catch (e) { + // print('error parsing saved attachment data'); + // return; + // } - for (i = 0; i < loadData.length; i ++) { - var savedProps = loadData[ i ]; - var currentProps = Entities.getEntityProperties(savedProps.id); - if (currentProps.id == savedProps.id && - // TODO -- also check that parentJointIndex matches? - currentProps.parentID == MyAvatar.sessionUUID) { - // entity is already in-world. TODO -- patch it up? - continue; - } - this.scrubProperties(savedProps); - delete savedProps["id"]; - savedProps.parentID = MyAvatar.sessionUUID; // this will change between sessions - var loadedEntityID = Entities.addEntity(savedProps); + // for (i = 0; i < loadData.length; i ++) { + // var savedProps = loadData[ i ]; + // var currentProps = Entities.getEntityProperties(savedProps.id); + // if (currentProps.id == savedProps.id && + // // TODO -- also check that parentJointIndex matches? + // currentProps.parentID == MyAvatar.sessionUUID) { + // // entity is already in-world. TODO -- patch it up? + // continue; + // } + // this.scrubProperties(savedProps); + // delete savedProps["id"]; + // savedProps.parentID = MyAvatar.sessionUUID; // this will change between sessions + // var loadedEntityID = Entities.addEntity(savedProps, true); - Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ - action: 'loaded', - grabbedEntity: loadedEntityID - })); - } - } + // Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ + // action: 'loaded', + // grabbedEntity: loadedEntityID + // })); + // } + // } } var manager = new AttachedEntitiesManager(); diff --git a/scripts/system/libraries/toolBars.js b/scripts/system/libraries/toolBars.js index d97575d349..9efe533457 100644 --- a/scripts/system/libraries/toolBars.js +++ b/scripts/system/libraries/toolBars.js @@ -56,6 +56,10 @@ Overlay2D = function(properties, overlay) { // overlay is an optional variable properties.alpha = alpha; Overlays.editOverlay(overlay, { alpha: alpha }); } + this.setImageURL = function(imageURL) { + properties.imageURL = imageURL; + Overlays.editOverlay(overlay, { imageURL: imageURL }); + } this.show = function(doShow) { properties.visible = doShow; Overlays.editOverlay(overlay, { visible: doShow }); @@ -254,7 +258,7 @@ ToolBar = function(x, y, direction, optionalPersistenceKey, optionalInitialPosit } this.save(); } - + this.setAlpha = function(alpha, tool) { if(typeof(tool) === 'undefined') { for(var tool in this.tools) { @@ -268,7 +272,11 @@ ToolBar = function(x, y, direction, optionalPersistenceKey, optionalInitialPosit this.tools[tool].setAlpha(alpha); } } - + + this.setImageURL = function(imageURL, tool) { + this.tools[tool].setImageURL(imageURL); + } + this.setBack = function(color, alpha) { if (color == null) { Overlays.editOverlay(this.back, { visible: false }); @@ -478,4 +486,4 @@ ToolBar = function(x, y, direction, optionalPersistenceKey, optionalInitialPosit } ToolBar.SPACING = 6; ToolBar.VERTICAL = 0; -ToolBar.HORIZONTAL = 1; \ No newline at end of file +ToolBar.HORIZONTAL = 1; diff --git a/scripts/system/users.js b/scripts/system/users.js index d935dd23ca..c010b7ea24 100644 --- a/scripts/system/users.js +++ b/scripts/system/users.js @@ -716,7 +716,7 @@ var usersWindow = (function () { if (clickedOverlay === windowPane) { - overlayX = event.x - WINDOW_MARGIN; + overlayX = event.x - windowPosition.x - WINDOW_MARGIN; overlayY = event.y - windowPosition.y + windowHeight - WINDOW_MARGIN - windowLineHeight; numLinesBefore = Math.round(overlayY / windowLineHeight);