mirror of
https://thingvellir.net/git/overte
synced 2025-03-27 23:52:03 +01:00
Merge pull request #7840 from sethalves/avatar-entities-3
Avatar entities
This commit is contained in:
commit
5b98b062f8
28 changed files with 779 additions and 134 deletions
|
@ -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<EntityEditPacketSender*>(packetSender);
|
||||
entityPacketSender->setMyAvatar(getMyAvatar());
|
||||
|
||||
connect(this, &Application::applicationStateChanged, this, &Application::activeChanged);
|
||||
qCDebug(interfaceapp, "Startup time: %4.2f seconds.", (double)startupTimer.elapsed() / 1000.0);
|
||||
|
||||
|
@ -4194,6 +4199,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([&] {
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include <SharedUtil.h>
|
||||
#include <TextRenderer3D.h>
|
||||
#include <TextureCache.h>
|
||||
#include <VariantMapToScriptValue.h>
|
||||
#include <DebugDraw.h>
|
||||
|
||||
#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.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,6 +64,7 @@ public:
|
|||
typedef std::shared_ptr<render::Item::PayloadInterface> PayloadPointer;
|
||||
|
||||
void init();
|
||||
void updateAvatarEntities();
|
||||
void simulate(float deltaTime);
|
||||
virtual void simulateAttachments(float deltaTime);
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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> 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<JointData> jointArray;
|
||||
|
@ -1628,9 +1656,69 @@ void AvatarData::setAttachmentsVariant(const QVariantList& variant) {
|
|||
QVector<AttachmentData> 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<AvatarData*>(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<QUuid>::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<AvatarData*>(this), "getRecentlyDetachedIDs", Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(AvatarEntityIDs, result));
|
||||
return result;
|
||||
}
|
||||
AvatarEntityIDs result = _avatarEntityDetached;
|
||||
_avatarEntityDetached.clear();
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -61,6 +61,8 @@ typedef unsigned long long quint64;
|
|||
using AvatarSharedPointer = std::shared_ptr<AvatarData>;
|
||||
using AvatarWeakPointer = std::weak_ptr<AvatarData>;
|
||||
using AvatarHash = QHash<QUuid, AvatarSharedPointer>;
|
||||
using AvatarEntityMap = QMap<QUuid, QByteArray>;
|
||||
using AvatarEntityIDs = QSet<QUuid>;
|
||||
|
||||
using AvatarDataSequenceNumber = uint16_t;
|
||||
|
||||
|
@ -134,6 +136,10 @@ class AttachmentData;
|
|||
class Transform;
|
||||
using TransformPointer = std::shared_ptr<Transform>;
|
||||
|
||||
// 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;
|
||||
|
|
|
@ -107,21 +107,23 @@ void AvatarHashMap::processAvatarDataPacket(QSharedPointer<ReceivedMessage> mess
|
|||
}
|
||||
|
||||
void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer<ReceivedMessage> 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> 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<ReceivedMessage>
|
|||
avatar->setAttachmentData(attachmentData);
|
||||
}
|
||||
|
||||
avatar->setAvatarEntityData(avatarEntityData);
|
||||
|
||||
if (avatar->getDisplayName() != displayName) {
|
||||
avatar->setDisplayName(displayName);
|
||||
}
|
||||
|
|
|
@ -47,6 +47,9 @@ namespace render {
|
|||
}
|
||||
|
||||
void makeEntityItemStatusGetters(EntityItemPointer entity, render::Item::Status::Getters& statusGetters) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
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<NodeList>();
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ enum class RenderItemStatusIcon {
|
|||
SIMULATION_OWNER = 3,
|
||||
HAS_ACTIONS = 4,
|
||||
OTHER_SIMULATION_OWNER = 5,
|
||||
CLIENT_ONLY = 6,
|
||||
NONE = 255
|
||||
};
|
||||
|
||||
|
|
|
@ -981,7 +981,7 @@ void RenderablePolyVoxEntityItem::compressVolumeDataAndSendEditPacket() {
|
|||
PhysicalEntitySimulationPointer peSimulation = std::static_pointer_cast<PhysicalEntitySimulation>(simulation);
|
||||
EntityEditPacketSender* packetSender = peSimulation ? peSimulation->getPacketSender() : nullptr;
|
||||
if (packetSender) {
|
||||
packetSender->queueEditEntityMessage(PacketType::EntityEdit, entity->getID(), properties);
|
||||
packetSender->queueEditEntityMessage(PacketType::EntityEdit, tree, entity->getID(), properties);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
//
|
||||
|
||||
#include <assert.h>
|
||||
#include <QJsonDocument>
|
||||
#include <PerfStat.h>
|
||||
#include <OctalCode.h>
|
||||
#include <udt/PacketHeaders.h>
|
||||
|
@ -35,18 +36,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 +117,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)) {
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <OctreeEditPacketSender.h>
|
||||
|
||||
#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);
|
||||
|
||||
|
@ -40,5 +51,7 @@ public slots:
|
|||
|
||||
private:
|
||||
bool _shouldProcessNack = true;
|
||||
AvatarData* _myAvatar { nullptr };
|
||||
QScriptEngine _scriptEngine;
|
||||
};
|
||||
#endif // hifi_EntityEditPacketSender_h
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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<QUuid, quint64> _previouslyDeletedActions;
|
||||
|
||||
QUuid _sourceUUID; /// the server node UUID we came from
|
||||
|
||||
bool _clientOnly { false };
|
||||
QUuid _owningAvatarID;
|
||||
};
|
||||
|
||||
#endif // hifi_EntityItem_h
|
||||
|
|
|
@ -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<QString> EntityItemProperties::listChangedProperties() {
|
|||
if (queryAACubeChanged()) {
|
||||
out += "queryAACube";
|
||||
}
|
||||
if (clientOnlyChanged()) {
|
||||
out += "clientOnly";
|
||||
}
|
||||
if (owningAvatarIDChanged()) {
|
||||
out += "owningAvatarID";
|
||||
}
|
||||
|
||||
if (flyingAllowedChanged()) {
|
||||
out += "flyingAllowed";
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<NodeList>();
|
||||
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<NodeList>();
|
||||
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<NodeList>();
|
||||
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<NodeList>();
|
||||
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();
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<NodeList>();
|
||||
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<EntityTreeElement>(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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -50,7 +50,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
|||
return VERSION_ENTITIES_NO_FLY_ZONES;
|
||||
case PacketType::AvatarData:
|
||||
case PacketType::BulkAvatarData:
|
||||
return static_cast<PacketVersion>(AvatarMixerPacketVersion::SoftAttachmentSupport);
|
||||
return static_cast<PacketVersion>(AvatarMixerPacketVersion::AvatarEntities);
|
||||
case PacketType::ICEServerHeartbeat:
|
||||
return 18; // ICE Server Heartbeat signing
|
||||
case PacketType::AssetGetInfo:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
90
scripts/system/assets/images/lock.svg
Normal file
90
scripts/system/assets/images/lock.svg
Normal file
|
@ -0,0 +1,90 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="64mm"
|
||||
height="64mm"
|
||||
viewBox="0 0 226.77165 226.77165"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="lock.svg">
|
||||
<defs
|
||||
id="defs4">
|
||||
<linearGradient
|
||||
id="linearGradient5587"
|
||||
osb:paint="solid">
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5589" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient5587"
|
||||
id="linearGradient5591"
|
||||
x1="63.214287"
|
||||
y1="970.93364"
|
||||
x2="165.35715"
|
||||
y2="970.93364"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1.979899"
|
||||
inkscape:cx="-342.3833"
|
||||
inkscape:cy="122.87157"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="2782"
|
||||
inkscape:window-height="1764"
|
||||
inkscape:window-x="98"
|
||||
inkscape:window-y="36"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-825.59055)">
|
||||
<rect
|
||||
style="fill:url(#linearGradient5591);fill-opacity:1;stroke:#000000;stroke-linejoin:round;stroke-opacity:1;stroke-width:5;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
id="rect4138"
|
||||
width="97.14286"
|
||||
height="82.85714"
|
||||
x="65.714287"
|
||||
y="929.50507" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#a7abdf;stroke-width:9.00350285;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 78.319686,927.59356 c -2.221784,0.87707 -0.135759,-31.35492 36.848474,-30.3048 34.29837,0.97388 35.8249,30.70886 35.8249,30.70886"
|
||||
id="path4148"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="csc" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.9 KiB |
90
scripts/system/assets/images/unlock.svg
Normal file
90
scripts/system/assets/images/unlock.svg
Normal file
|
@ -0,0 +1,90 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="64mm"
|
||||
height="64mm"
|
||||
viewBox="0 0 226.77165 226.77165"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="unlock.svg">
|
||||
<defs
|
||||
id="defs4">
|
||||
<linearGradient
|
||||
id="linearGradient4135"
|
||||
osb:paint="solid">
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop4137" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4135"
|
||||
id="linearGradient4139"
|
||||
x1="63.214287"
|
||||
y1="970.93364"
|
||||
x2="165.35715"
|
||||
y2="970.93364"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1.979899"
|
||||
inkscape:cx="-280.07997"
|
||||
inkscape:cy="122.87157"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="2782"
|
||||
inkscape:window-height="1764"
|
||||
inkscape:window-x="98"
|
||||
inkscape:window-y="36"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-825.59055)">
|
||||
<rect
|
||||
style="fill:url(#linearGradient4139);fill-opacity:1;stroke:#000000;stroke-linejoin:round;stroke-opacity:1;stroke-width:5;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
id="rect4138"
|
||||
width="97.14286"
|
||||
height="82.85714"
|
||||
x="65.714287"
|
||||
y="929.50507" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#abaac5;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 142.93659,923.06268 c -2.19267,1.09633 -0.13398,-39.19338 36.36549,-37.88073 33.84882,1.21733 35.35534,38.3858 35.35534,38.3858"
|
||||
id="path4148"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="csc" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.8 KiB |
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
ToolBar.HORIZONTAL = 1;
|
||||
|
|
Loading…
Reference in a new issue