overte-JulianGro/libraries/entities/src/EntityItem.cpp
2018-05-24 11:28:52 -07:00

3127 lines
117 KiB
C++

//
// EntityItem.cpp
// libraries/models/src
//
// Created by Brad Hefta-Gaub on 12/4/13.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "EntityItem.h"
#include <QtCore/QObject>
#include <QtEndian>
#include <QJsonDocument>
#include <NetworkingConstants.h>
#include <NetworkAccessManager.h>
#include <QtNetwork/QNetworkReply>
#include <QtNetwork/QNetworkRequest>
#include <glm/gtx/transform.hpp>
#include <BufferParser.h>
#include <ByteCountCoding.h>
#include <GLMHelpers.h>
#include <Octree.h>
#include <PhysicsHelpers.h>
#include <Profile.h>
#include <RegisteredMetaTypes.h>
#include <SharedUtil.h> // usecTimestampNow()
#include <LogHandler.h>
#include <Extents.h>
#include "EntityScriptingInterface.h"
#include "EntitiesLogging.h"
#include "EntityTree.h"
#include "EntitySimulation.h"
#include "EntityDynamicFactoryInterface.h"
Q_DECLARE_METATYPE(EntityItemPointer);
int entityItemPointernMetaTypeId = qRegisterMetaType<EntityItemPointer>();
int EntityItem::_maxActionsDataSize = 800;
quint64 EntityItem::_rememberDeletedActionTime = 20 * USECS_PER_SECOND;
QString EntityItem::_marketplacePublicKey;
EntityItem::EntityItem(const EntityItemID& entityItemID) :
SpatiallyNestable(NestableType::Entity, entityItemID)
{
setLocalVelocity(ENTITY_ITEM_DEFAULT_VELOCITY);
setLocalAngularVelocity(ENTITY_ITEM_DEFAULT_ANGULAR_VELOCITY);
setUnscaledDimensions(ENTITY_ITEM_DEFAULT_DIMENSIONS);
// explicitly set transform parts to set dirty flags used by batch rendering
locationChanged();
dimensionsChanged();
quint64 now = usecTimestampNow();
_lastSimulated = now;
_lastUpdated = now;
}
EntityItem::~EntityItem() {
// these pointers MUST be correct at delete, else we probably have a dangling backpointer
// to this EntityItem in the corresponding data structure.
assert(!_simulated || (!_element && !_physicsInfo));
assert(!_element);
assert(!_physicsInfo);
}
EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties;
requestedProperties += PROP_SIMULATION_OWNER;
requestedProperties += PROP_POSITION;
requestedProperties += PROP_ROTATION;
requestedProperties += PROP_VELOCITY;
requestedProperties += PROP_ANGULAR_VELOCITY;
requestedProperties += PROP_ACCELERATION;
requestedProperties += PROP_DIMENSIONS;
requestedProperties += PROP_DENSITY;
requestedProperties += PROP_GRAVITY;
requestedProperties += PROP_DAMPING;
requestedProperties += PROP_RESTITUTION;
requestedProperties += PROP_FRICTION;
requestedProperties += PROP_LIFETIME;
requestedProperties += PROP_SCRIPT;
requestedProperties += PROP_SCRIPT_TIMESTAMP;
requestedProperties += PROP_SERVER_SCRIPTS;
requestedProperties += PROP_COLLISION_SOUND_URL;
requestedProperties += PROP_REGISTRATION_POINT;
requestedProperties += PROP_ANGULAR_DAMPING;
requestedProperties += PROP_VISIBLE;
requestedProperties += PROP_CAN_CAST_SHADOW;
requestedProperties += PROP_COLLISIONLESS;
requestedProperties += PROP_COLLISION_MASK;
requestedProperties += PROP_DYNAMIC;
requestedProperties += PROP_LOCKED;
requestedProperties += PROP_USER_DATA;
// Certifiable properties
requestedProperties += PROP_ITEM_NAME;
requestedProperties += PROP_ITEM_DESCRIPTION;
requestedProperties += PROP_ITEM_CATEGORIES;
requestedProperties += PROP_ITEM_ARTIST;
requestedProperties += PROP_ITEM_LICENSE;
requestedProperties += PROP_LIMITED_RUN;
requestedProperties += PROP_MARKETPLACE_ID;
requestedProperties += PROP_EDITION_NUMBER;
requestedProperties += PROP_ENTITY_INSTANCE_NUMBER;
requestedProperties += PROP_CERTIFICATE_ID;
requestedProperties += PROP_STATIC_CERTIFICATE_VERSION;
requestedProperties += PROP_NAME;
requestedProperties += PROP_HREF;
requestedProperties += PROP_DESCRIPTION;
requestedProperties += PROP_ACTION_DATA;
requestedProperties += PROP_PARENT_ID;
requestedProperties += PROP_PARENT_JOINT_INDEX;
requestedProperties += PROP_QUERY_AA_CUBE;
requestedProperties += PROP_CLIENT_ONLY;
requestedProperties += PROP_OWNING_AVATAR_ID;
requestedProperties += PROP_LAST_EDITED_BY;
requestedProperties += PROP_CLONEABLE;
requestedProperties += PROP_CLONE_LIFETIME;
requestedProperties += PROP_CLONE_LIMIT;
requestedProperties += PROP_CLONE_DYNAMIC;
requestedProperties += PROP_CLONE_AVATAR_ENTITY;
requestedProperties += PROP_CLONE_ORIGIN_ID;
return requestedProperties;
}
OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packetData, EncodeBitstreamParams& params,
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData) const {
// ALL this fits...
// object ID [16 bytes]
// ByteCountCoded(type code) [~1 byte]
// last edited [8 bytes]
// ByteCountCoded(last_edited to last_updated delta) [~1-8 bytes]
// PropertyFlags<>( everything ) [1-2 bytes]
// ~27-35 bytes...
OctreeElement::AppendState appendState = OctreeElement::COMPLETED; // assume the best
// encode our ID as a byte count coded byte stream
QByteArray encodedID = getID().toRfc4122();
// encode our type as a byte count coded byte stream
ByteCountCoded<quint32> typeCoder = getType();
QByteArray encodedType = typeCoder;
// last updated (animations, non-physics changes)
quint64 updateDelta = getLastUpdated() <= getLastEdited() ? 0 : getLastUpdated() - getLastEdited();
ByteCountCoded<quint64> updateDeltaCoder = updateDelta;
QByteArray encodedUpdateDelta = updateDeltaCoder;
// last simulated (velocity, angular velocity, physics changes)
quint64 simulatedDelta = getLastSimulated() <= getLastEdited() ? 0 : getLastSimulated() - getLastEdited();
ByteCountCoded<quint64> simulatedDeltaCoder = simulatedDelta;
QByteArray encodedSimulatedDelta = simulatedDeltaCoder;
EntityPropertyFlags propertyFlags(PROP_LAST_ITEM);
EntityPropertyFlags requestedProperties = getEntityProperties(params);
// If we are being called for a subsequent pass at appendEntityData() that failed to completely encode this item,
// then our entityTreeElementExtraEncodeData should include data about which properties we need to append.
if (entityTreeElementExtraEncodeData && entityTreeElementExtraEncodeData->entities.contains(getEntityItemID())) {
requestedProperties = entityTreeElementExtraEncodeData->entities.value(getEntityItemID());
}
EntityPropertyFlags propertiesDidntFit = requestedProperties;
LevelDetails entityLevel = packetData->startLevel();
quint64 lastEdited = getLastEdited();
#ifdef WANT_DEBUG
float editedAgo = getEditedAgo();
QString agoAsString = formatSecondsElapsed(editedAgo);
qCDebug(entities) << "Writing entity " << getEntityItemID() << " to buffer, lastEdited =" << lastEdited
<< " ago=" << editedAgo << "seconds - " << agoAsString;
#endif
bool successIDFits = false;
bool successTypeFits = false;
bool successCreatedFits = false;
bool successLastEditedFits = false;
bool successLastUpdatedFits = false;
bool successLastSimulatedFits = false;
bool successPropertyFlagsFits = false;
int propertyFlagsOffset = 0;
int oldPropertyFlagsLength = 0;
QByteArray encodedPropertyFlags;
int propertyCount = 0;
successIDFits = packetData->appendRawData(encodedID);
if (successIDFits) {
successTypeFits = packetData->appendRawData(encodedType);
}
if (successTypeFits) {
successCreatedFits = packetData->appendValue(_created);
}
if (successCreatedFits) {
successLastEditedFits = packetData->appendValue(lastEdited);
}
if (successLastEditedFits) {
successLastUpdatedFits = packetData->appendRawData(encodedUpdateDelta);
}
if (successLastUpdatedFits) {
successLastSimulatedFits = packetData->appendRawData(encodedSimulatedDelta);
}
if (successLastSimulatedFits) {
propertyFlagsOffset = packetData->getUncompressedByteOffset();
encodedPropertyFlags = propertyFlags;
oldPropertyFlagsLength = encodedPropertyFlags.length();
successPropertyFlagsFits = packetData->appendRawData(encodedPropertyFlags);
}
bool headerFits = successIDFits && successTypeFits && successCreatedFits && successLastEditedFits
&& successLastUpdatedFits && successPropertyFlagsFits;
int startOfEntityItemData = packetData->getUncompressedByteOffset();
if (headerFits) {
bool successPropertyFits;
propertyFlags -= PROP_LAST_ITEM; // clear the last item for now, we may or may not set it as the actual item
// These items would go here once supported....
// PROP_PAGED_PROPERTY,
// PROP_CUSTOM_PROPERTIES_INCLUDED,
APPEND_ENTITY_PROPERTY(PROP_SIMULATION_OWNER, _simulationOwner.toByteArray());
APPEND_ENTITY_PROPERTY(PROP_POSITION, getLocalPosition());
APPEND_ENTITY_PROPERTY(PROP_ROTATION, getLocalOrientation());
APPEND_ENTITY_PROPERTY(PROP_VELOCITY, getLocalVelocity());
APPEND_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, getLocalAngularVelocity());
APPEND_ENTITY_PROPERTY(PROP_ACCELERATION, getAcceleration());
APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, getUnscaledDimensions());
APPEND_ENTITY_PROPERTY(PROP_DENSITY, getDensity());
APPEND_ENTITY_PROPERTY(PROP_GRAVITY, getGravity());
APPEND_ENTITY_PROPERTY(PROP_DAMPING, getDamping());
APPEND_ENTITY_PROPERTY(PROP_RESTITUTION, getRestitution());
APPEND_ENTITY_PROPERTY(PROP_FRICTION, getFriction());
APPEND_ENTITY_PROPERTY(PROP_LIFETIME, getLifetime());
APPEND_ENTITY_PROPERTY(PROP_SCRIPT, getScript());
APPEND_ENTITY_PROPERTY(PROP_SCRIPT_TIMESTAMP, getScriptTimestamp());
APPEND_ENTITY_PROPERTY(PROP_SERVER_SCRIPTS, getServerScripts());
APPEND_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, getRegistrationPoint());
APPEND_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, getAngularDamping());
APPEND_ENTITY_PROPERTY(PROP_VISIBLE, getVisible());
APPEND_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, getCanCastShadow());
APPEND_ENTITY_PROPERTY(PROP_COLLISIONLESS, getCollisionless());
APPEND_ENTITY_PROPERTY(PROP_COLLISION_MASK, getCollisionMask());
APPEND_ENTITY_PROPERTY(PROP_DYNAMIC, getDynamic());
APPEND_ENTITY_PROPERTY(PROP_LOCKED, getLocked());
APPEND_ENTITY_PROPERTY(PROP_USER_DATA, getUserData());
// Certifiable Properties
APPEND_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, getMarketplaceID());
APPEND_ENTITY_PROPERTY(PROP_ITEM_NAME, getItemName());
APPEND_ENTITY_PROPERTY(PROP_ITEM_DESCRIPTION, getItemDescription());
APPEND_ENTITY_PROPERTY(PROP_ITEM_CATEGORIES, getItemCategories());
APPEND_ENTITY_PROPERTY(PROP_ITEM_ARTIST, getItemArtist());
APPEND_ENTITY_PROPERTY(PROP_ITEM_LICENSE, getItemLicense());
APPEND_ENTITY_PROPERTY(PROP_LIMITED_RUN, getLimitedRun());
APPEND_ENTITY_PROPERTY(PROP_EDITION_NUMBER, getEditionNumber());
APPEND_ENTITY_PROPERTY(PROP_ENTITY_INSTANCE_NUMBER, getEntityInstanceNumber());
APPEND_ENTITY_PROPERTY(PROP_CERTIFICATE_ID, getCertificateID());
APPEND_ENTITY_PROPERTY(PROP_STATIC_CERTIFICATE_VERSION, getStaticCertificateVersion());
APPEND_ENTITY_PROPERTY(PROP_NAME, getName());
APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, getCollisionSoundURL());
APPEND_ENTITY_PROPERTY(PROP_HREF, getHref());
APPEND_ENTITY_PROPERTY(PROP_DESCRIPTION, getDescription());
APPEND_ENTITY_PROPERTY(PROP_ACTION_DATA, getDynamicData());
// convert AVATAR_SELF_ID to actual sessionUUID.
QUuid actualParentID = getParentID();
if (actualParentID == AVATAR_SELF_ID) {
auto nodeList = DependencyManager::get<NodeList>();
actualParentID = nodeList->getSessionUUID();
}
APPEND_ENTITY_PROPERTY(PROP_PARENT_ID, actualParentID);
APPEND_ENTITY_PROPERTY(PROP_PARENT_JOINT_INDEX, getParentJointIndex());
APPEND_ENTITY_PROPERTY(PROP_QUERY_AA_CUBE, getQueryAACube());
APPEND_ENTITY_PROPERTY(PROP_LAST_EDITED_BY, getLastEditedBy());
APPEND_ENTITY_PROPERTY(PROP_CLONEABLE, getCloneable());
APPEND_ENTITY_PROPERTY(PROP_CLONE_LIFETIME, getCloneLifetime());
APPEND_ENTITY_PROPERTY(PROP_CLONE_LIMIT, getCloneLimit());
APPEND_ENTITY_PROPERTY(PROP_CLONE_DYNAMIC, getCloneDynamic());
APPEND_ENTITY_PROPERTY(PROP_CLONE_AVATAR_ENTITY, getCloneAvatarEntity());
APPEND_ENTITY_PROPERTY(PROP_CLONE_ORIGIN_ID, getCloneOriginID());
appendSubclassData(packetData, params, entityTreeElementExtraEncodeData,
requestedProperties,
propertyFlags,
propertiesDidntFit,
propertyCount,
appendState);
}
if (propertyCount > 0) {
int endOfEntityItemData = packetData->getUncompressedByteOffset();
encodedPropertyFlags = propertyFlags;
int newPropertyFlagsLength = encodedPropertyFlags.length();
packetData->updatePriorBytes(propertyFlagsOffset,
(const unsigned char*)encodedPropertyFlags.constData(), encodedPropertyFlags.length());
// if the size of the PropertyFlags shrunk, we need to shift everything down to front of packet.
if (newPropertyFlagsLength < oldPropertyFlagsLength) {
int oldSize = packetData->getUncompressedSize();
const unsigned char* modelItemData = packetData->getUncompressedData(propertyFlagsOffset + oldPropertyFlagsLength);
int modelItemDataLength = endOfEntityItemData - startOfEntityItemData;
int newEntityItemDataStart = propertyFlagsOffset + newPropertyFlagsLength;
packetData->updatePriorBytes(newEntityItemDataStart, modelItemData, modelItemDataLength);
int newSize = oldSize - (oldPropertyFlagsLength - newPropertyFlagsLength);
packetData->setUncompressedSize(newSize);
} else {
assert(newPropertyFlagsLength == oldPropertyFlagsLength); // should not have grown
}
packetData->endLevel(entityLevel);
} else {
packetData->discardLevel(entityLevel);
appendState = OctreeElement::NONE; // if we got here, then we didn't include the item
}
// If any part of the model items didn't fit, then the element is considered partial
if (appendState != OctreeElement::COMPLETED) {
// add this item into our list for the next appendElementData() pass
entityTreeElementExtraEncodeData->entities.insert(getEntityItemID(), propertiesDidntFit);
}
// if any part of our entity was sent, call trackSend
if (appendState != OctreeElement::NONE) {
params.trackSend(getID(), getLastEdited());
}
return appendState;
}
// TODO: My goal is to get rid of this concept completely. The old code (and some of the current code) used this
// result to calculate if a packet being sent to it was potentially bad or corrupt. I've adjusted this to now
// only consider the minimum header bytes as being required. But it would be preferable to completely eliminate
// this logic from the callers.
int EntityItem::expectedBytes() {
// Header bytes
// object ID [16 bytes]
// ByteCountCoded(type code) [~1 byte]
// last edited [8 bytes]
// ByteCountCoded(last_edited to last_updated delta) [~1-8 bytes]
// PropertyFlags<>( everything ) [1-2 bytes]
// ~27-35 bytes...
const int MINIMUM_HEADER_BYTES = 27;
return MINIMUM_HEADER_BYTES;
}
// clients use this method to unpack FULL updates from entity-server
int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) {
setSourceUUID(args.sourceUUID);
args.entitiesPerPacket++;
// Header bytes
// object ID [16 bytes]
// ByteCountCoded(type code) [~1 byte]
// last edited [8 bytes]
// ByteCountCoded(last_edited to last_updated delta) [~1-8 bytes]
// PropertyFlags<>( everything ) [1-2 bytes]
// ~27-35 bytes...
const int MINIMUM_HEADER_BYTES = 27;
if (bytesLeftToRead < MINIMUM_HEADER_BYTES) {
return 0;
}
int64_t clockSkew = 0;
uint64_t maxPingRoundTrip = 33333; // two frames periods at 60 fps
if (args.sourceNode) {
clockSkew = args.sourceNode->getClockSkewUsec();
const float MSECS_PER_USEC = 1000;
maxPingRoundTrip += args.sourceNode->getPingMs() * MSECS_PER_USEC;
}
BufferParser parser(data, bytesLeftToRead);
#ifdef DEBUG
#define VALIDATE_ENTITY_ITEM_PARSER 1
#endif
#ifdef VALIDATE_ENTITY_ITEM_PARSER
int bytesRead = 0;
int originalLength = bytesLeftToRead;
// TODO: figure out a way to avoid the big deep copy below.
QByteArray originalDataBuffer((const char*)data, originalLength); // big deep copy!
const unsigned char* dataAt = data;
#endif
// id
parser.readUuid(_id);
#ifdef VALIDATE_ENTITY_ITEM_PARSER
{
QByteArray encodedID = originalDataBuffer.mid(bytesRead, NUM_BYTES_RFC4122_UUID); // maximum possible size
QUuid id = QUuid::fromRfc4122(encodedID);
dataAt += encodedID.size();
bytesRead += encodedID.size();
Q_ASSERT(id == _id);
Q_ASSERT(parser.offset() == (unsigned int) bytesRead);
}
#endif
// type
parser.readCompressedCount<quint32>((quint32&)_type);
#ifdef VALIDATE_ENTITY_ITEM_PARSER
QByteArray encodedType = originalDataBuffer.mid(bytesRead); // maximum possible size
ByteCountCoded<quint32> typeCoder = encodedType;
encodedType = typeCoder; // determine true length
dataAt += encodedType.size();
bytesRead += encodedType.size();
quint32 type = typeCoder;
EntityTypes::EntityType oldType = (EntityTypes::EntityType)type;
Q_ASSERT(oldType == _type);
Q_ASSERT(parser.offset() == (unsigned int) bytesRead);
#endif
bool overwriteLocalData = true; // assume the new content overwrites our local data
quint64 now = usecTimestampNow();
bool somethingChanged = false;
// _created
{
quint64 createdFromBuffer = 0;
parser.readValue(createdFromBuffer);
#ifdef VALIDATE_ENTITY_ITEM_PARSER
{
quint64 createdFromBuffer2 = 0;
memcpy(&createdFromBuffer2, dataAt, sizeof(createdFromBuffer2));
dataAt += sizeof(createdFromBuffer2);
bytesRead += sizeof(createdFromBuffer2);
Q_ASSERT(createdFromBuffer2 == createdFromBuffer);
Q_ASSERT(parser.offset() == (unsigned int) bytesRead);
}
#endif
if (_created == UNKNOWN_CREATED_TIME) {
// we don't yet have a _created timestamp, so we accept this one
createdFromBuffer -= clockSkew;
if (createdFromBuffer > now || createdFromBuffer == UNKNOWN_CREATED_TIME) {
createdFromBuffer = now;
}
_created = createdFromBuffer;
}
}
#ifdef WANT_DEBUG
quint64 lastEdited = getLastEdited();
float editedAgo = getEditedAgo();
QString agoAsString = formatSecondsElapsed(editedAgo);
QString ageAsString = formatSecondsElapsed(getAge());
qCDebug(entities) << "------------------------------------------";
qCDebug(entities) << "Loading entity " << getEntityItemID() << " from buffer...";
qCDebug(entities) << "------------------------------------------";
debugDump();
qCDebug(entities) << "------------------------------------------";
qCDebug(entities) << " _created =" << _created;
qCDebug(entities) << " age=" << getAge() << "seconds - " << ageAsString;
qCDebug(entities) << " lastEdited =" << lastEdited;
qCDebug(entities) << " ago=" << editedAgo << "seconds - " << agoAsString;
#endif
quint64 lastEditedFromBuffer = 0;
// TODO: we could make this encoded as a delta from _created
// _lastEdited
parser.readValue(lastEditedFromBuffer);
#ifdef VALIDATE_ENTITY_ITEM_PARSER
{
quint64 lastEditedFromBuffer2 = 0;
memcpy(&lastEditedFromBuffer2, dataAt, sizeof(lastEditedFromBuffer2));
dataAt += sizeof(lastEditedFromBuffer2);
bytesRead += sizeof(lastEditedFromBuffer2);
Q_ASSERT(lastEditedFromBuffer2 == lastEditedFromBuffer);
Q_ASSERT(parser.offset() == (unsigned int) bytesRead);
}
#endif
quint64 lastEditedFromBufferAdjusted = lastEditedFromBuffer == 0 ? 0 : lastEditedFromBuffer - clockSkew;
if (lastEditedFromBufferAdjusted > now) {
lastEditedFromBufferAdjusted = now;
}
bool fromSameServerEdit = (lastEditedFromBuffer == _lastEditedFromRemoteInRemoteTime);
#ifdef WANT_DEBUG
qCDebug(entities) << "data from server **************** ";
qCDebug(entities) << " entityItemID:" << getEntityItemID();
qCDebug(entities) << " now:" << now;
qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now);
qCDebug(entities) << " lastEditedFromBuffer:" << debugTime(lastEditedFromBuffer, now);
qCDebug(entities) << " clockSkew:" << clockSkew;
qCDebug(entities) << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now);
qCDebug(entities) << " _lastEditedFromRemote:" << debugTime(_lastEditedFromRemote, now);
qCDebug(entities) << " _lastEditedFromRemoteInRemoteTime:" << debugTime(_lastEditedFromRemoteInRemoteTime, now);
qCDebug(entities) << " fromSameServerEdit:" << fromSameServerEdit;
#endif
bool ignoreServerPacket = false; // assume we'll use this server packet
// If this packet is from the same server edit as the last packet we accepted from the server
// we probably want to use it.
if (fromSameServerEdit) {
// If this is from the same sever packet, then check against any local changes since we got
// the most recent packet from this server time
if (_lastEdited > _lastEditedFromRemote) {
ignoreServerPacket = true;
}
} else {
// If this isn't from the same sever packet, then honor our skew adjusted times...
// If we've changed our local tree more recently than the new data from this packet
// then we will not be changing our values, instead we just read and skip the data
if (_lastEdited > lastEditedFromBufferAdjusted) {
ignoreServerPacket = true;
}
}
// before proceeding, check to see if this is an entity that we know has been deleted, which
// might happen in the case of out-of-order and/or recorvered packets, if we've deleted the entity
// we can confidently ignore this packet
EntityTreePointer tree = getTree();
if (tree && tree->isDeletedEntity(_id)) {
#ifdef WANT_DEBUG
qCDebug(entities) << "Received packet for previously deleted entity [" << _id << "] ignoring. "
"(inside " << __FUNCTION__ << ")";
#endif
ignoreServerPacket = true;
}
if (ignoreServerPacket) {
overwriteLocalData = false;
#ifdef WANT_DEBUG
qCDebug(entities) << "IGNORING old data from server!!! ****************";
debugDump();
#endif
} else {
#ifdef WANT_DEBUG
qCDebug(entities) << "USING NEW data from server!!! ****************";
debugDump();
#endif
// don't allow _lastEdited to be in the future
_lastEdited = lastEditedFromBufferAdjusted;
_lastEditedFromRemote = now;
_lastEditedFromRemoteInRemoteTime = lastEditedFromBuffer;
// TODO: only send this notification if something ACTUALLY changed (hint, we haven't yet parsed
// the properties out of the bitstream (see below))
somethingChangedNotification(); // notify derived classes that something has changed
}
// last updated is stored as ByteCountCoded delta from lastEdited
quint64 updateDelta;
parser.readCompressedCount(updateDelta);
#ifdef VALIDATE_ENTITY_ITEM_PARSER
{
QByteArray encodedUpdateDelta = originalDataBuffer.mid(bytesRead); // maximum possible size
ByteCountCoded<quint64> updateDeltaCoder = encodedUpdateDelta;
quint64 updateDelta2 = updateDeltaCoder;
Q_ASSERT(updateDelta == updateDelta2);
encodedUpdateDelta = updateDeltaCoder; // determine true length
dataAt += encodedUpdateDelta.size();
bytesRead += encodedUpdateDelta.size();
Q_ASSERT(parser.offset() == (unsigned int) bytesRead);
}
#endif
if (overwriteLocalData) {
_lastUpdated = lastEditedFromBufferAdjusted + updateDelta; // don't adjust for clock skew since we already did that
#ifdef WANT_DEBUG
qCDebug(entities) << " _lastUpdated:" << debugTime(_lastUpdated, now);
qCDebug(entities) << " _lastEdited:" << debugTime(_lastEdited, now);
qCDebug(entities) << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now);
#endif
}
// Newer bitstreams will have a last simulated and a last updated value
quint64 lastSimulatedFromBufferAdjusted = now;
// last simulated is stored as ByteCountCoded delta from lastEdited
quint64 simulatedDelta;
parser.readCompressedCount(simulatedDelta);
#ifdef VALIDATE_ENTITY_ITEM_PARSER
{
QByteArray encodedSimulatedDelta = originalDataBuffer.mid(bytesRead); // maximum possible size
ByteCountCoded<quint64> simulatedDeltaCoder = encodedSimulatedDelta;
quint64 simulatedDelta2 = simulatedDeltaCoder;
Q_ASSERT(simulatedDelta2 == simulatedDelta);
encodedSimulatedDelta = simulatedDeltaCoder; // determine true length
dataAt += encodedSimulatedDelta.size();
bytesRead += encodedSimulatedDelta.size();
Q_ASSERT(parser.offset() == (unsigned int) bytesRead);
}
#endif
if (overwriteLocalData) {
lastSimulatedFromBufferAdjusted = lastEditedFromBufferAdjusted + simulatedDelta; // don't adjust for clock skew since we already did that
if (lastSimulatedFromBufferAdjusted > now) {
lastSimulatedFromBufferAdjusted = now;
}
#ifdef WANT_DEBUG
qCDebug(entities) << " _lastEdited:" << debugTime(_lastEdited, now);
qCDebug(entities) << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now);
qCDebug(entities) << " lastSimulatedFromBufferAdjusted:" << debugTime(lastSimulatedFromBufferAdjusted, now);
#endif
}
#ifdef WANT_DEBUG
if (overwriteLocalData) {
qCDebug(entities) << "EntityItem::readEntityDataFromBuffer()... changed entity:" << getEntityItemID();
qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now);
qCDebug(entities) << " getLastSimulated:" << debugTime(getLastSimulated(), now);
qCDebug(entities) << " getLastUpdated:" << debugTime(getLastUpdated(), now);
}
#endif
// Property Flags
EntityPropertyFlags propertyFlags;
parser.readFlags(propertyFlags);
#ifdef VALIDATE_ENTITY_ITEM_PARSER
{
QByteArray encodedPropertyFlags = originalDataBuffer.mid(bytesRead); // maximum possible size
EntityPropertyFlags propertyFlags2 = encodedPropertyFlags;
dataAt += propertyFlags.getEncodedLength();
bytesRead += propertyFlags.getEncodedLength();
Q_ASSERT(propertyFlags2 == propertyFlags);
Q_ASSERT(parser.offset() == (unsigned int)bytesRead);
}
#endif
#ifdef VALIDATE_ENTITY_ITEM_PARSER
Q_ASSERT(parser.data() + parser.offset() == dataAt);
#else
const unsigned char* dataAt = parser.data() + parser.offset();
int bytesRead = (int)parser.offset();
#endif
auto nodeList = DependencyManager::get<NodeList>();
const QUuid& myNodeID = nodeList->getSessionUUID();
bool weOwnSimulation = _simulationOwner.matchesValidID(myNodeID);
// pack SimulationOwner and terse update properties near each other
// NOTE: the server is authoritative for changes to simOwnerID so we always unpack ownership data
// even when we would otherwise ignore the rest of the packet.
bool filterRejection = false;
if (propertyFlags.getHasProperty(PROP_SIMULATION_OWNER)) {
QByteArray simOwnerData;
int bytes = OctreePacketData::unpackDataFromBytes(dataAt, simOwnerData);
SimulationOwner newSimOwner;
newSimOwner.fromByteArray(simOwnerData);
dataAt += bytes;
bytesRead += bytes;
if (wantTerseEditLogging() && _simulationOwner != newSimOwner) {
qCDebug(entities) << "sim ownership for" << getDebugName() << "is now" << newSimOwner;
}
// This is used in the custom physics setters, below. When an entity-server filter alters
// or rejects a set of properties, it clears this. In such cases, we don't want those custom
// setters to ignore what the server says.
filterRejection = newSimOwner.getID().isNull();
if (weOwnSimulation) {
if (newSimOwner.getID().isNull() && !_simulationOwner.pendingRelease(lastEditedFromBufferAdjusted)) {
// entity-server is trying to clear our ownership (probably at our own request)
// but we actually want to own it, therefore we ignore this clear event
// and pretend that we own it (e.g. we assume we'll receive ownership soon)
// However, for now, when the server uses a newer time than what we sent, listen to what we're told.
if (overwriteLocalData) {
weOwnSimulation = false;
}
} else if (_simulationOwner.set(newSimOwner)) {
markDirtyFlags(Simulation::DIRTY_SIMULATOR_ID);
somethingChanged = true;
// recompute weOwnSimulation for later
weOwnSimulation = _simulationOwner.matchesValidID(myNodeID);
}
} else if (_simulationOwner.pendingTake(now - maxPingRoundTrip)) {
// we sent a bid already but maybe before this packet was sent from the server
weOwnSimulation = true;
if (newSimOwner.getID().isNull()) {
// the entity-server is trying to clear someone else's ownership
if (!_simulationOwner.isNull()) {
markDirtyFlags(Simulation::DIRTY_SIMULATOR_ID);
somethingChanged = true;
_simulationOwner.clearCurrentOwner();
}
} else if (newSimOwner.getID() == myNodeID) {
// the entity-server is awarding us ownership which is what we want
_simulationOwner.set(newSimOwner);
}
} else if (newSimOwner.matchesValidID(myNodeID) && !_simulationOwner.pendingTake(now)) {
// entity-server tells us that we have simulation ownership while we never requested this for this EntityItem,
// this could happen when the user reloads the cache and entity tree.
markDirtyFlags(Simulation::DIRTY_SIMULATOR_ID);
somethingChanged = true;
_simulationOwner.clearCurrentOwner();
weOwnSimulation = false;
} else if (_simulationOwner.set(newSimOwner)) {
markDirtyFlags(Simulation::DIRTY_SIMULATOR_ID);
somethingChanged = true;
// recompute weOwnSimulation for later
weOwnSimulation = _simulationOwner.matchesValidID(myNodeID);
}
}
auto lastEdited = lastEditedFromBufferAdjusted;
bool otherOverwrites = overwriteLocalData && !weOwnSimulation;
auto shouldUpdate = [lastEdited, otherOverwrites, filterRejection](quint64 updatedTimestamp, bool valueChanged) {
bool simulationChanged = lastEdited > updatedTimestamp;
return otherOverwrites && simulationChanged && (valueChanged || filterRejection);
};
{ // When we own the simulation we don't accept updates to the entity's transform/velocities
// we also want to ignore any duplicate packets that have the same "recently updated" values
// as a packet we've already recieved. This is because we want multiple edits of the same
// information to be idempotent, but if we applied new physics properties we'd resimulation
// with small differences in results.
// Because the regular streaming property "setters" only have access to the new value, we've
// made these lambdas that can access other details about the previous updates to suppress
// any duplicates.
// Note: duplicate packets are expected and not wrong. They may be sent for any number of
// reasons and the contract is that the client handles them in an idempotent manner.
auto customUpdatePositionFromNetwork = [this, shouldUpdate, lastEdited](glm::vec3 value){
if (shouldUpdate(_lastUpdatedPositionTimestamp, value != _lastUpdatedPositionValue)) {
setPosition(value);
_lastUpdatedPositionTimestamp = lastEdited;
_lastUpdatedPositionValue = value;
}
};
auto customUpdateRotationFromNetwork = [this, shouldUpdate, lastEdited](glm::quat value){
if (shouldUpdate(_lastUpdatedRotationTimestamp, value != _lastUpdatedRotationValue)) {
setRotation(value);
_lastUpdatedRotationTimestamp = lastEdited;
_lastUpdatedRotationValue = value;
}
};
auto customUpdateVelocityFromNetwork = [this, shouldUpdate, lastEdited](glm::vec3 value){
if (shouldUpdate(_lastUpdatedVelocityTimestamp, value != _lastUpdatedVelocityValue)) {
setVelocity(value);
_lastUpdatedVelocityTimestamp = lastEdited;
_lastUpdatedVelocityValue = value;
}
};
auto customUpdateAngularVelocityFromNetwork = [this, shouldUpdate, lastEdited](glm::vec3 value){
if (shouldUpdate(_lastUpdatedAngularVelocityTimestamp, value != _lastUpdatedAngularVelocityValue)) {
setAngularVelocity(value);
_lastUpdatedAngularVelocityTimestamp = lastEdited;
_lastUpdatedAngularVelocityValue = value;
}
};
auto customSetAcceleration = [this, shouldUpdate, lastEdited](glm::vec3 value){
if (shouldUpdate(_lastUpdatedAccelerationTimestamp, value != _lastUpdatedAccelerationValue)) {
setAcceleration(value);
_lastUpdatedAccelerationTimestamp = lastEdited;
_lastUpdatedAccelerationValue = value;
}
};
READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, customUpdatePositionFromNetwork);
READ_ENTITY_PROPERTY(PROP_ROTATION, glm::quat, customUpdateRotationFromNetwork);
READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, customUpdateVelocityFromNetwork);
READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, customUpdateAngularVelocityFromNetwork);
READ_ENTITY_PROPERTY(PROP_ACCELERATION, glm::vec3, customSetAcceleration);
}
READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, setUnscaledDimensions);
READ_ENTITY_PROPERTY(PROP_DENSITY, float, setDensity);
READ_ENTITY_PROPERTY(PROP_GRAVITY, glm::vec3, setGravity);
READ_ENTITY_PROPERTY(PROP_DAMPING, float, setDamping);
READ_ENTITY_PROPERTY(PROP_RESTITUTION, float, setRestitution);
READ_ENTITY_PROPERTY(PROP_FRICTION, float, setFriction);
READ_ENTITY_PROPERTY(PROP_LIFETIME, float, setLifetime);
READ_ENTITY_PROPERTY(PROP_SCRIPT, QString, setScript);
READ_ENTITY_PROPERTY(PROP_SCRIPT_TIMESTAMP, quint64, setScriptTimestamp);
{
// We use this scope to work around an issue stopping server script changes
// from being received by an entity script server running a script that continously updates an entity.
// Basically, we'll allow recent changes to the server scripts even if there are local changes to other properties
// that have been made more recently.
bool overwriteLocalData = !ignoreServerPacket || (lastEditedFromBufferAdjusted > _serverScriptsChangedTimestamp);
READ_ENTITY_PROPERTY(PROP_SERVER_SCRIPTS, QString, setServerScripts);
}
READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, setRegistrationPoint);
READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, setAngularDamping);
READ_ENTITY_PROPERTY(PROP_VISIBLE, bool, setVisible);
READ_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, bool, setCanCastShadow);
READ_ENTITY_PROPERTY(PROP_COLLISIONLESS, bool, setCollisionless);
READ_ENTITY_PROPERTY(PROP_COLLISION_MASK, uint16_t, setCollisionMask);
READ_ENTITY_PROPERTY(PROP_DYNAMIC, bool, setDynamic);
READ_ENTITY_PROPERTY(PROP_LOCKED, bool, setLocked);
READ_ENTITY_PROPERTY(PROP_USER_DATA, QString, setUserData);
READ_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, QString, setMarketplaceID);
READ_ENTITY_PROPERTY(PROP_ITEM_NAME, QString, setItemName);
READ_ENTITY_PROPERTY(PROP_ITEM_DESCRIPTION, QString, setItemDescription);
READ_ENTITY_PROPERTY(PROP_ITEM_CATEGORIES, QString, setItemCategories);
READ_ENTITY_PROPERTY(PROP_ITEM_ARTIST, QString, setItemArtist);
READ_ENTITY_PROPERTY(PROP_ITEM_LICENSE, QString, setItemLicense);
READ_ENTITY_PROPERTY(PROP_LIMITED_RUN, quint32, setLimitedRun);
READ_ENTITY_PROPERTY(PROP_EDITION_NUMBER, quint32, setEditionNumber);
READ_ENTITY_PROPERTY(PROP_ENTITY_INSTANCE_NUMBER, quint32, setEntityInstanceNumber);
READ_ENTITY_PROPERTY(PROP_CERTIFICATE_ID, QString, setCertificateID);
READ_ENTITY_PROPERTY(PROP_STATIC_CERTIFICATE_VERSION, quint32, setStaticCertificateVersion);
READ_ENTITY_PROPERTY(PROP_NAME, QString, setName);
READ_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, QString, setCollisionSoundURL);
READ_ENTITY_PROPERTY(PROP_HREF, QString, setHref);
READ_ENTITY_PROPERTY(PROP_DESCRIPTION, QString, setDescription);
READ_ENTITY_PROPERTY(PROP_ACTION_DATA, QByteArray, setDynamicData);
{ // parentID and parentJointIndex are also protected by simulation ownership
bool oldOverwrite = overwriteLocalData;
overwriteLocalData = overwriteLocalData && !weOwnSimulation;
READ_ENTITY_PROPERTY(PROP_PARENT_ID, QUuid, setParentID);
READ_ENTITY_PROPERTY(PROP_PARENT_JOINT_INDEX, quint16, setParentJointIndex);
overwriteLocalData = oldOverwrite;
}
{
auto customUpdateQueryAACubeFromNetwork = [this, shouldUpdate, lastEdited](AACube value){
if (shouldUpdate(_lastUpdatedQueryAACubeTimestamp, value != _lastUpdatedQueryAACubeValue)) {
setQueryAACube(value);
_lastUpdatedQueryAACubeTimestamp = lastEdited;
_lastUpdatedQueryAACubeValue = value;
}
};
READ_ENTITY_PROPERTY(PROP_QUERY_AA_CUBE, AACube, customUpdateQueryAACubeFromNetwork);
}
READ_ENTITY_PROPERTY(PROP_LAST_EDITED_BY, QUuid, setLastEditedBy);
READ_ENTITY_PROPERTY(PROP_CLONEABLE, bool, setCloneable);
READ_ENTITY_PROPERTY(PROP_CLONE_LIFETIME, float, setCloneLifetime);
READ_ENTITY_PROPERTY(PROP_CLONE_LIMIT, float, setCloneLimit);
READ_ENTITY_PROPERTY(PROP_CLONE_DYNAMIC, bool, setCloneDynamic);
READ_ENTITY_PROPERTY(PROP_CLONE_AVATAR_ENTITY, bool, setCloneAvatarEntity);
READ_ENTITY_PROPERTY(PROP_CLONE_ORIGIN_ID, QUuid, setCloneOriginID);
bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args,
propertyFlags, overwriteLocalData, somethingChanged);
////////////////////////////////////
// WARNING: Do not add stream content here after the subclass. Always add it before the subclass
//
// NOTE: we had a bad version of the stream that we added stream data after the subclass. We can attempt to recover
// by doing this parsing here... but it's not likely going to fully recover the content.
//
if (overwriteLocalData && (getDirtyFlags() & (Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES))) {
// NOTE: This code is attempting to "repair" the old data we just got from the server to make it more
// closely match where the entities should be if they'd stepped forward in time to "now". The server
// is sending us data with a known "last simulated" time. That time is likely in the past, and therefore
// this "new" data is actually slightly out of date. We calculate the time we need to skip forward and
// use our simulation helper routine to get a best estimate of where the entity should be.
float skipTimeForward = (float)(now - lastSimulatedFromBufferAdjusted) / (float)(USECS_PER_SECOND);
// we want to extrapolate the motion forward to compensate for packet travel time, but
// we don't want the side effect of flag setting.
stepKinematicMotion(skipTimeForward);
}
if (overwriteLocalData) {
if (!_simulationOwner.matchesValidID(myNodeID)) {
_lastSimulated = now;
}
}
// Tracking for editing roundtrips here. We will tell our EntityTree that we just got incoming data about
// and entity that was edited at some time in the past. The tree will determine how it wants to track this
// information.
EntityTreeElementPointer element = _element; // use local copy of _element for logic below
if (element && element->getTree()) {
element->getTree()->trackIncomingEntityLastEdited(lastEditedFromBufferAdjusted, bytesRead);
}
return bytesRead;
}
void EntityItem::debugDump() const {
auto position = getWorldPosition();
qCDebug(entities) << "EntityItem id:" << getEntityItemID();
qCDebug(entities, " edited ago:%f", (double)getEditedAgo());
qCDebug(entities, " position:%f,%f,%f", (double)position.x, (double)position.y, (double)position.z);
qCDebug(entities) << " dimensions:" << getScaledDimensions();
}
// adjust any internal timestamps to fix clock skew for this server
void EntityItem::adjustEditPacketForClockSkew(QByteArray& buffer, qint64 clockSkew) {
unsigned char* dataAt = reinterpret_cast<unsigned char*>(buffer.data());
int octets = numberOfThreeBitSectionsInCode(dataAt);
int lengthOfOctcode = (int)bytesRequiredForCodeLength(octets);
dataAt += lengthOfOctcode;
// lastEdited
quint64 lastEditedInLocalTime;
memcpy(&lastEditedInLocalTime, dataAt, sizeof(lastEditedInLocalTime));
quint64 lastEditedInServerTime = lastEditedInLocalTime > 0 ? lastEditedInLocalTime + clockSkew : 0;
memcpy(dataAt, &lastEditedInServerTime, sizeof(lastEditedInServerTime));
#ifdef WANT_DEBUG
qCDebug(entities, "EntityItem::adjustEditPacketForClockSkew()...");
qCDebug(entities) << " lastEditedInLocalTime: " << lastEditedInLocalTime;
qCDebug(entities) << " clockSkew: " << clockSkew;
qCDebug(entities) << " lastEditedInServerTime: " << lastEditedInServerTime;
#endif
//assert(lastEditedInLocalTime > (quint64)0);
}
float EntityItem::computeMass() const {
glm::vec3 dimensions = getScaledDimensions();
return getDensity() * _volumeMultiplier * dimensions.x * dimensions.y * dimensions.z;
}
void EntityItem::setDensity(float density) {
float clampedDensity = glm::max(glm::min(density, ENTITY_ITEM_MAX_DENSITY), ENTITY_ITEM_MIN_DENSITY);
withWriteLock([&] {
if (_density != clampedDensity) {
_density = clampedDensity;
_flags |= Simulation::DIRTY_MASS;
}
});
}
void EntityItem::setMass(float mass) {
// Setting the mass actually changes the _density (at fixed volume), however
// we must protect the density range to help maintain stability of physics simulation
// therefore this method might not accept the mass that is supplied.
glm::vec3 dimensions = getScaledDimensions();
float volume = _volumeMultiplier * dimensions.x * dimensions.y * dimensions.z;
// compute new density
float newDensity = 1.0f;
if (volume < ENTITY_ITEM_MIN_VOLUME) {
// avoid divide by zero
newDensity = glm::min(mass / ENTITY_ITEM_MIN_VOLUME, ENTITY_ITEM_MAX_DENSITY);
} else {
newDensity = glm::max(glm::min(mass / volume, ENTITY_ITEM_MAX_DENSITY), ENTITY_ITEM_MIN_DENSITY);
}
withWriteLock([&] {
if (_density != newDensity) {
_density = newDensity;
_flags |= Simulation::DIRTY_MASS;
}
});
}
void EntityItem::setHref(QString value) {
auto href = value.toLower();
// If the string has something and doesn't start with with "hifi://" it shouldn't be set
// We allow the string to be empty, because that's the initial state of this property
if (!value.isEmpty() &&
!(value.toLower().startsWith("hifi://")) &&
!(value.toLower().startsWith("file://"))
// TODO: serverless-domains will eventually support http and https also
) {
return;
}
withWriteLock([&] {
_href = value;
});
}
void EntityItem::setCollisionSoundURL(const QString& value) {
bool modified = false;
withWriteLock([&] {
if (_collisionSoundURL != value) {
_collisionSoundURL = value;
modified = true;
}
});
if (modified) {
if (auto myTree = getTree()) {
myTree->notifyNewCollisionSoundURL(value, getEntityItemID());
}
}
}
void EntityItem::simulate(const quint64& now) {
DETAILED_PROFILE_RANGE(simulation_physics, "Simulate");
if (getLastSimulated() == 0) {
setLastSimulated(now);
}
float timeElapsed = (float)(now - getLastSimulated()) / (float)(USECS_PER_SECOND);
#ifdef WANT_DEBUG
qCDebug(entities) << "********** EntityItem::simulate()";
qCDebug(entities) << " entity ID=" << getEntityItemID();
qCDebug(entities) << " simulator ID=" << getSimulatorID();
qCDebug(entities) << " now=" << now;
qCDebug(entities) << " _lastSimulated=" << _lastSimulated;
qCDebug(entities) << " timeElapsed=" << timeElapsed;
qCDebug(entities) << " hasVelocity=" << hasVelocity();
qCDebug(entities) << " hasGravity=" << hasGravity();
qCDebug(entities) << " hasAcceleration=" << hasAcceleration();
qCDebug(entities) << " hasAngularVelocity=" << hasAngularVelocity();
qCDebug(entities) << " getAngularVelocity=" << getAngularVelocity();
qCDebug(entities) << " isMortal=" << isMortal();
qCDebug(entities) << " getAge()=" << getAge();
qCDebug(entities) << " getLifetime()=" << getLifetime();
if (hasVelocity() || hasGravity()) {
qCDebug(entities) << " MOVING...=";
qCDebug(entities) << " hasVelocity=" << hasVelocity();
qCDebug(entities) << " hasGravity=" << hasGravity();
qCDebug(entities) << " hasAcceleration=" << hasAcceleration();
qCDebug(entities) << " hasAngularVelocity=" << hasAngularVelocity();
qCDebug(entities) << " getAngularVelocity=" << getAngularVelocity();
}
if (hasAngularVelocity()) {
qCDebug(entities) << " CHANGING...=";
qCDebug(entities) << " hasAngularVelocity=" << hasAngularVelocity();
qCDebug(entities) << " getAngularVelocity=" << getAngularVelocity();
}
if (isMortal()) {
qCDebug(entities) << " MORTAL...=";
qCDebug(entities) << " isMortal=" << isMortal();
qCDebug(entities) << " getAge()=" << getAge();
qCDebug(entities) << " getLifetime()=" << getLifetime();
}
qCDebug(entities) << " ********** EntityItem::simulate() .... SETTING _lastSimulated=" << _lastSimulated;
#endif
if (!stepKinematicMotion(timeElapsed)) {
// this entity is no longer moving
// flag it to transition from KINEMATIC to STATIC
markDirtyFlags(Simulation::DIRTY_MOTION_TYPE);
setAcceleration(Vectors::ZERO);
}
setLastSimulated(now);
}
bool EntityItem::stepKinematicMotion(float timeElapsed) {
DETAILED_PROFILE_RANGE(simulation_physics, "StepKinematicMotion");
// get all the data
Transform transform;
glm::vec3 linearVelocity;
glm::vec3 angularVelocity;
getLocalTransformAndVelocities(transform, linearVelocity, angularVelocity);
// find out if it is moving
bool isSpinning = (glm::length2(angularVelocity) > 0.0f);
float linearSpeedSquared = glm::length2(linearVelocity);
bool isTranslating = linearSpeedSquared > 0.0f;
bool moving = isTranslating || isSpinning;
if (!moving) {
return false;
}
if (timeElapsed <= 0.0f) {
// someone gave us a useless time value so bail early
// but return 'true' because it is moving
return true;
}
const float MAX_TIME_ELAPSED = 1.0f; // seconds
if (timeElapsed > MAX_TIME_ELAPSED) {
qCDebug(entities) << "kinematic timestep = " << timeElapsed << " truncated to " << MAX_TIME_ELAPSED;
}
timeElapsed = glm::min(timeElapsed, MAX_TIME_ELAPSED);
if (isSpinning) {
float angularDamping = getAngularDamping();
// angular damping
if (angularDamping > 0.0f) {
angularVelocity *= powf(1.0f - angularDamping, timeElapsed);
}
const float MIN_KINEMATIC_ANGULAR_SPEED_SQUARED =
KINEMATIC_ANGULAR_SPEED_THRESHOLD * KINEMATIC_ANGULAR_SPEED_THRESHOLD;
if (glm::length2(angularVelocity) < MIN_KINEMATIC_ANGULAR_SPEED_SQUARED) {
angularVelocity = Vectors::ZERO;
} else {
// for improved agreement with the way Bullet integrates rotations we use an approximation
// and break the integration into bullet-sized substeps
glm::quat rotation = transform.getRotation();
float dt = timeElapsed;
while (dt > 0.0f) {
glm::quat dQ = computeBulletRotationStep(angularVelocity, glm::min(dt, PHYSICS_ENGINE_FIXED_SUBSTEP));
rotation = glm::normalize(dQ * rotation);
dt -= PHYSICS_ENGINE_FIXED_SUBSTEP;
}
transform.setRotation(rotation);
}
}
glm::vec3 position = transform.getTranslation();
const float MIN_KINEMATIC_LINEAR_SPEED_SQUARED =
KINEMATIC_LINEAR_SPEED_THRESHOLD * KINEMATIC_LINEAR_SPEED_THRESHOLD;
if (isTranslating) {
glm::vec3 deltaVelocity = Vectors::ZERO;
// linear damping
float damping = getDamping();
if (damping > 0.0f) {
deltaVelocity = (powf(1.0f - damping, timeElapsed) - 1.0f) * linearVelocity;
}
const float MIN_KINEMATIC_LINEAR_ACCELERATION_SQUARED = 1.0e-4f; // 0.01 m/sec^2
vec3 acceleration = getAcceleration();
if (glm::length2(acceleration) > MIN_KINEMATIC_LINEAR_ACCELERATION_SQUARED) {
// yes acceleration
// acceleration is in world-frame but we need it in local-frame
glm::vec3 linearAcceleration = acceleration;
bool success;
Transform parentTransform = getParentTransform(success);
if (success) {
linearAcceleration = glm::inverse(parentTransform.getRotation()) * linearAcceleration;
}
deltaVelocity += linearAcceleration * timeElapsed;
if (linearSpeedSquared < MIN_KINEMATIC_LINEAR_SPEED_SQUARED
&& glm::length2(deltaVelocity) < MIN_KINEMATIC_LINEAR_SPEED_SQUARED
&& glm::length2(linearVelocity + deltaVelocity) < MIN_KINEMATIC_LINEAR_SPEED_SQUARED) {
linearVelocity = Vectors::ZERO;
} else {
// NOTE: we do NOT include the second-order acceleration term (0.5 * a * dt^2)
// when computing the displacement because Bullet also ignores that term. Yes,
// this is an approximation and it works best when dt is small.
position += timeElapsed * linearVelocity;
linearVelocity += deltaVelocity;
}
} else {
// no acceleration
if (linearSpeedSquared < MIN_KINEMATIC_LINEAR_SPEED_SQUARED) {
linearVelocity = Vectors::ZERO;
} else {
// NOTE: we don't use second-order acceleration term for linear displacement
// because Bullet doesn't use it.
position += timeElapsed * linearVelocity;
linearVelocity += deltaVelocity;
}
}
}
transform.setTranslation(position);
setLocalTransformAndVelocities(transform, linearVelocity, angularVelocity);
return true;
}
bool EntityItem::isMoving() const {
return hasVelocity() || hasAngularVelocity();
}
bool EntityItem::isMovingRelativeToParent() const {
return hasLocalVelocity() || hasLocalAngularVelocity();
}
EntityTreePointer EntityItem::getTree() const {
EntityTreeElementPointer containingElement = getElement();
EntityTreePointer tree = containingElement ? containingElement->getTree() : nullptr;
return tree;
}
SpatialParentTree* EntityItem::getParentTree() const {
return getTree().get();
}
bool EntityItem::wantTerseEditLogging() const {
EntityTreePointer tree = getTree();
return tree ? tree->wantTerseEditLogging() : false;
}
glm::mat4 EntityItem::getEntityToWorldMatrix() const {
glm::mat4 translation = glm::translate(getWorldPosition());
glm::mat4 rotation = glm::mat4_cast(getWorldOrientation());
glm::mat4 scale = glm::scale(getScaledDimensions());
glm::mat4 registration = glm::translate(ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint());
return translation * rotation * scale * registration;
}
glm::mat4 EntityItem::getWorldToEntityMatrix() const {
return glm::inverse(getEntityToWorldMatrix());
}
glm::vec3 EntityItem::entityToWorld(const glm::vec3& point) const {
return glm::vec3(getEntityToWorldMatrix() * glm::vec4(point, 1.0f));
}
glm::vec3 EntityItem::worldToEntity(const glm::vec3& point) const {
return glm::vec3(getWorldToEntityMatrix() * glm::vec4(point, 1.0f));
}
bool EntityItem::lifetimeHasExpired() const {
return isMortal() && (getAge() > getLifetime());
}
quint64 EntityItem::getExpiry() const {
return getCreated() + (quint64)(getLifetime() * (float)USECS_PER_SECOND);
}
EntityItemProperties EntityItem::getProperties(EntityPropertyFlags desiredProperties) const {
EncodeBitstreamParams params; // unknown
EntityPropertyFlags propertyFlags = desiredProperties.isEmpty() ? getEntityProperties(params) : desiredProperties;
EntityItemProperties properties(propertyFlags);
properties._id = getID();
properties._idSet = true;
properties._created = getCreated();
properties._lastEdited = getLastEdited();
properties.setClientOnly(getClientOnly());
properties.setOwningAvatarID(getOwningAvatarID());
properties._type = getType();
COPY_ENTITY_PROPERTY_TO_PROPERTIES(simulationOwner, getSimulationOwner);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(position, getLocalPosition);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(dimensions, getUnscaledDimensions);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(rotation, getLocalOrientation);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(density, getDensity);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(velocity, getLocalVelocity);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(gravity, getGravity);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(acceleration, getAcceleration);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(damping, getDamping);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(restitution, getRestitution);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(friction, getFriction);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(created, getCreated);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(lifetime, getLifetime);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(script, getScript);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(scriptTimestamp, getScriptTimestamp);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(serverScripts, getServerScripts);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(collisionSoundURL, getCollisionSoundURL);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(registrationPoint, getRegistrationPoint);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(angularVelocity, getLocalAngularVelocity);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(angularDamping, getAngularDamping);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(localRenderAlpha, getLocalRenderAlpha);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(visible, getVisible);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(canCastShadow, getCanCastShadow);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(collisionless, getCollisionless);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(collisionMask, getCollisionMask);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(dynamic, getDynamic);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(locked, getLocked);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(userData, getUserData);
// Certifiable Properties
COPY_ENTITY_PROPERTY_TO_PROPERTIES(itemName, getItemName);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(itemDescription, getItemDescription);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(itemCategories, getItemCategories);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(itemArtist, getItemArtist);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(itemLicense, getItemLicense);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(limitedRun, getLimitedRun);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(marketplaceID, getMarketplaceID);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(editionNumber, getEditionNumber);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(entityInstanceNumber, getEntityInstanceNumber);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(certificateID, getCertificateID);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(staticCertificateVersion, getStaticCertificateVersion);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(name, getName);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(href, getHref);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(description, getDescription);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(actionData, getDynamicData);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(parentID, getParentID);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(parentJointIndex, getParentJointIndex);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(queryAACube, getQueryAACube);
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);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(lastEditedBy, getLastEditedBy);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneable, getCloneable);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneLifetime, getCloneLifetime);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneLimit, getCloneLimit);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneDynamic, getCloneDynamic);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneAvatarEntity, getCloneAvatarEntity);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneOriginID, getCloneOriginID);
properties._defaultSettings = false;
return properties;
}
void EntityItem::getAllTerseUpdateProperties(EntityItemProperties& properties) const {
// a TerseUpdate includes the transform and its derivatives
if (!properties._positionChanged) {
properties._position = getLocalPosition();
}
if (!properties._velocityChanged) {
properties._velocity = getLocalVelocity();
}
if (!properties._rotationChanged) {
properties._rotation = getLocalOrientation();
}
if (!properties._angularVelocityChanged) {
properties._angularVelocity = getLocalAngularVelocity();
}
if (!properties._accelerationChanged) {
properties._acceleration = getAcceleration();
}
properties._positionChanged = true;
properties._velocityChanged = true;
properties._rotationChanged = true;
properties._angularVelocityChanged = true;
properties._accelerationChanged = true;
}
void EntityItem::flagForOwnershipBid(uint8_t priority) {
markDirtyFlags(Simulation::DIRTY_SIMULATION_OWNERSHIP_PRIORITY);
auto nodeList = DependencyManager::get<NodeList>();
if (_simulationOwner.matchesValidID(nodeList->getSessionUUID())) {
// we already own it
_simulationOwner.promotePriority(priority);
} else {
// we don't own it yet
_simulationOwner.setPendingPriority(priority, usecTimestampNow());
}
}
bool EntityItem::setProperties(const EntityItemProperties& properties) {
bool somethingChanged = false;
// these affect TerseUpdate properties
SET_ENTITY_PROPERTY_FROM_PROPERTIES(simulationOwner, setSimulationOwner);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(position, setPosition);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(rotation, setRotation);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(velocity, setVelocity);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(angularVelocity, setAngularVelocity);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(acceleration, setAcceleration);
// these (along with "position" above) affect tree structure
SET_ENTITY_PROPERTY_FROM_PROPERTIES(dimensions, setUnscaledDimensions);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(registrationPoint, setRegistrationPoint);
// these (along with all properties above) affect the simulation
SET_ENTITY_PROPERTY_FROM_PROPERTIES(density, setDensity);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(gravity, setGravity);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(damping, setDamping);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(angularDamping, setAngularDamping);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(restitution, setRestitution);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(friction, setFriction);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionless, setCollisionless);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionMask, setCollisionMask);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(dynamic, setDynamic);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(created, setCreated);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifetime, setLifetime);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(locked, setLocked);
// non-simulation properties below
SET_ENTITY_PROPERTY_FROM_PROPERTIES(script, setScript);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(scriptTimestamp, setScriptTimestamp);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(serverScripts, setServerScripts);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionSoundURL, setCollisionSoundURL);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(localRenderAlpha, setLocalRenderAlpha);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(visible, setVisible);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(canCastShadow, setCanCastShadow);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(userData, setUserData);
// Certifiable Properties
SET_ENTITY_PROPERTY_FROM_PROPERTIES(itemName, setItemName);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(itemDescription, setItemDescription);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(itemCategories, setItemCategories);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(itemArtist, setItemArtist);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(itemLicense, setItemLicense);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(limitedRun, setLimitedRun);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(marketplaceID, setMarketplaceID);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(editionNumber, setEditionNumber);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(entityInstanceNumber, setEntityInstanceNumber);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(certificateID, setCertificateID);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(staticCertificateVersion, setStaticCertificateVersion);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(name, setName);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(href, setHref);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(description, setDescription);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(actionData, setDynamicData);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(parentID, setParentID);
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);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(lastEditedBy, setLastEditedBy);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneable, setCloneable);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneLifetime, setCloneLifetime);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneLimit, setCloneLimit);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneDynamic, setCloneDynamic);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneAvatarEntity, setCloneAvatarEntity);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneOriginID, setCloneOriginID);
if (updateQueryAACube()) {
somethingChanged = true;
}
// Now check the sub classes
somethingChanged |= setSubClassProperties(properties);
// Finally notify if change detected
if (somethingChanged) {
uint64_t now = usecTimestampNow();
#ifdef WANT_DEBUG
int elapsed = now - getLastEdited();
qCDebug(entities) << "EntityItem::setProperties() AFTER update... edited AGO=" << elapsed <<
"now=" << now << " getLastEdited()=" << getLastEdited();
#endif
setLastEdited(now);
somethingChangedNotification(); // notify derived classes that something has changed
if (getDirtyFlags() & (Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES)) {
// anything that sets the transform or velocity must update _lastSimulated which is used
// for kinematic extrapolation (e.g. we want to extrapolate forward from this moment
// when position and/or velocity was changed).
_lastSimulated = now;
}
}
// timestamps
quint64 timestamp = properties.getCreated();
if (_created == UNKNOWN_CREATED_TIME && timestamp != UNKNOWN_CREATED_TIME) {
quint64 now = usecTimestampNow();
if (timestamp > now) {
timestamp = now;
}
_created = timestamp;
}
return somethingChanged;
}
void EntityItem::recordCreationTime() {
if (_created == UNKNOWN_CREATED_TIME) {
_created = usecTimestampNow();
}
auto now = usecTimestampNow();
_lastEdited = _created;
_lastUpdated = now;
_lastSimulated = now;
}
const Transform EntityItem::getTransformToCenter(bool& success) const {
Transform result = getTransform(success);
if (getRegistrationPoint() != ENTITY_ITEM_HALF_VEC3) { // If it is not already centered, translate to center
result.postTranslate((ENTITY_ITEM_HALF_VEC3 - getRegistrationPoint()) * getScaledDimensions()); // Position to center
}
return result;
}
/// The maximum bounding cube for the entity, independent of it's rotation.
/// This accounts for the registration point (upon which rotation occurs around).
///
AACube EntityItem::getMaximumAACube(bool& success) const {
if (_recalcMaxAACube) {
glm::vec3 centerOfRotation = getWorldPosition(success); // also where _registration point is
if (success) {
_recalcMaxAACube = false;
// we want to compute the furthestExtent that an entity can extend out from its "position"
// to do this we compute the max of these two vec3s: registration and 1-registration
// and then scale by dimensions
glm::vec3 maxExtents = getScaledDimensions() * glm::max(_registrationPoint, glm::vec3(1.0f) - _registrationPoint);
// there exists a sphere that contains maxExtents for all rotations
float radius = glm::length(maxExtents);
// put a cube around the sphere
// TODO? replace _maxAACube with _boundingSphereRadius
glm::vec3 minimumCorner = centerOfRotation - glm::vec3(radius, radius, radius);
_maxAACube = AACube(minimumCorner, radius * 2.0f);
}
} else {
success = true;
}
return _maxAACube;
}
/// The minimum bounding cube for the entity accounting for it's rotation.
/// This accounts for the registration point (upon which rotation occurs around).
///
AACube EntityItem::getMinimumAACube(bool& success) const {
if (_recalcMinAACube) {
// position represents the position of the registration point.
glm::vec3 position = getWorldPosition(success);
if (success) {
_recalcMinAACube = false;
glm::vec3 dimensions = getScaledDimensions();
glm::vec3 unrotatedMinRelativeToEntity = - (dimensions * _registrationPoint);
glm::vec3 unrotatedMaxRelativeToEntity = dimensions * (glm::vec3(1.0f, 1.0f, 1.0f) - _registrationPoint);
Extents extents = { unrotatedMinRelativeToEntity, unrotatedMaxRelativeToEntity };
extents.rotate(getWorldOrientation());
// shift the extents to be relative to the position/registration point
extents.shiftBy(position);
// the cube that best encompasses extents is...
AABox box(extents);
glm::vec3 centerOfBox = box.calcCenter();
float longestSide = box.getLargestDimension();
float halfLongestSide = longestSide / 2.0f;
glm::vec3 cornerOfCube = centerOfBox - glm::vec3(halfLongestSide, halfLongestSide, halfLongestSide);
_minAACube = AACube(cornerOfCube, longestSide);
}
} else {
success = true;
}
return _minAACube;
}
AABox EntityItem::getAABox(bool& success) const {
if (_recalcAABox) {
// position represents the position of the registration point.
glm::vec3 position = getWorldPosition(success);
if (success) {
_recalcAABox = false;
glm::vec3 dimensions = getScaledDimensions();
glm::vec3 unrotatedMinRelativeToEntity = - (dimensions * _registrationPoint);
glm::vec3 unrotatedMaxRelativeToEntity = dimensions * (glm::vec3(1.0f, 1.0f, 1.0f) - _registrationPoint);
Extents extents = { unrotatedMinRelativeToEntity, unrotatedMaxRelativeToEntity };
extents.rotate(getWorldOrientation());
// shift the extents to be relative to the position/registration point
extents.shiftBy(position);
_cachedAABox = AABox(extents);
}
} else {
success = true;
}
return _cachedAABox;
}
AACube EntityItem::getQueryAACube(bool& success) const {
AACube result = SpatiallyNestable::getQueryAACube(success);
if (success) {
return result;
}
// this is for when we've loaded an older json file that didn't have queryAACube properties.
result = getMaximumAACube(success);
if (success) {
_queryAACube = result;
_queryAACubeSet = true;
}
return result;
}
bool EntityItem::shouldPuffQueryAACube() const {
return hasActions() || isChildOfMyAvatar() || isMovingRelativeToParent();
}
// TODO: get rid of all users of this function...
// ... radius = cornerToCornerLength / 2.0f
// ... cornerToCornerLength = sqrt(3 x maxDimension ^ 2)
// ... radius = sqrt(3 x maxDimension ^ 2) / 2.0f;
float EntityItem::getRadius() const {
return 0.5f * glm::length(getScaledDimensions());
}
void EntityItem::adjustShapeInfoByRegistration(ShapeInfo& info) const {
if (_registrationPoint != ENTITY_ITEM_DEFAULT_REGISTRATION_POINT) {
glm::mat4 scale = glm::scale(getScaledDimensions());
glm::mat4 registration = scale * glm::translate(ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint());
glm::vec3 regTransVec = glm::vec3(registration[3]); // extract position component from matrix
info.setOffset(regTransVec);
}
}
bool EntityItem::contains(const glm::vec3& point) const {
if (getShapeType() == SHAPE_TYPE_COMPOUND) {
bool success;
bool result = getAABox(success).contains(point);
return result && success;
} else {
ShapeInfo info;
info.setParams(getShapeType(), glm::vec3(0.5f));
adjustShapeInfoByRegistration(info);
return info.contains(worldToEntity(point));
}
}
void EntityItem::computeShapeInfo(ShapeInfo& info) {
info.setParams(getShapeType(), 0.5f * getScaledDimensions());
adjustShapeInfoByRegistration(info);
}
float EntityItem::getVolumeEstimate() const {
glm::vec3 dimensions = getScaledDimensions();
return dimensions.x * dimensions.y * dimensions.z;
}
void EntityItem::setRegistrationPoint(const glm::vec3& value) {
if (value != _registrationPoint) {
withWriteLock([&] {
_registrationPoint = glm::clamp(value, 0.0f, 1.0f);
});
dimensionsChanged(); // Registration Point affects the bounding box
markDirtyFlags(Simulation::DIRTY_SHAPE);
}
}
void EntityItem::setPosition(const glm::vec3& value) {
if (getLocalPosition() != value) {
setLocalPosition(value);
EntityTreePointer tree = getTree();
markDirtyFlags(Simulation::DIRTY_POSITION);
if (tree) {
tree->entityChanged(getThisPointer());
}
forEachDescendant([&](SpatiallyNestablePointer object) {
if (object->getNestableType() == NestableType::Entity) {
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(object);
entity->markDirtyFlags(Simulation::DIRTY_POSITION);
if (tree) {
tree->entityChanged(entity);
}
}
});
}
}
void EntityItem::setParentID(const QUuid& value) {
QUuid oldParentID = getParentID();
if (oldParentID != value) {
EntityTreePointer tree = getTree();
if (tree && !oldParentID.isNull()) {
tree->removeFromChildrenOfAvatars(getThisPointer());
}
uint32_t oldParentNoBootstrapping = 0;
uint32_t newParentNoBootstrapping = 0;
if (!value.isNull() && tree) {
EntityItemPointer entity = tree->findEntityByEntityItemID(value);
if (entity) {
newParentNoBootstrapping = entity->getSpecialFlags() & Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING;
}
}
if (!oldParentID.isNull() && tree) {
EntityItemPointer entity = tree->findEntityByEntityItemID(oldParentID);
if (entity) {
oldParentNoBootstrapping = entity->getDirtyFlags() & Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING;
}
}
if (!value.isNull() && (value == Physics::getSessionUUID() || value == AVATAR_SELF_ID)) {
newParentNoBootstrapping |= Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING;
}
if (!oldParentID.isNull() && (oldParentID == Physics::getSessionUUID() || oldParentID == AVATAR_SELF_ID)) {
oldParentNoBootstrapping |= Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING;
}
if ((bool)(oldParentNoBootstrapping ^ newParentNoBootstrapping)) {
if ((bool)(newParentNoBootstrapping & Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING)) {
markSpecialFlags(Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING);
forEachDescendant([&](SpatiallyNestablePointer object) {
if (object->getNestableType() == NestableType::Entity) {
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(object);
entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP);
entity->markSpecialFlags(Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING);
}
});
} else {
clearSpecialFlags(Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING);
forEachDescendant([&](SpatiallyNestablePointer object) {
if (object->getNestableType() == NestableType::Entity) {
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(object);
entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP);
entity->clearSpecialFlags(Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING);
}
});
}
}
SpatiallyNestable::setParentID(value);
// children are forced to be kinematic
// may need to not collide with own avatar
markDirtyFlags(Simulation::DIRTY_MOTION_TYPE | Simulation::DIRTY_COLLISION_GROUP);
if (tree) {
tree->addToNeedsParentFixupList(getThisPointer());
}
updateQueryAACube();
}
}
glm::vec3 EntityItem::getScaledDimensions() const {
glm::vec3 scale = getSNScale();
return _unscaledDimensions * scale;
}
void EntityItem::setScaledDimensions(const glm::vec3& value) {
glm::vec3 parentScale = getSNScale();
setUnscaledDimensions(value * parentScale);
}
void EntityItem::setUnscaledDimensions(const glm::vec3& value) {
glm::vec3 newDimensions = glm::max(value, glm::vec3(ENTITY_ITEM_MIN_DIMENSION));
if (getUnscaledDimensions() != newDimensions) {
withWriteLock([&] {
_unscaledDimensions = newDimensions;
});
locationChanged();
dimensionsChanged();
withWriteLock([&] {
_flags |= (Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS);
_queryAACubeSet = false;
});
}
}
void EntityItem::setRotation(glm::quat rotation) {
if (getLocalOrientation() != rotation) {
setLocalOrientation(rotation);
_flags |= Simulation::DIRTY_ROTATION;
forEachDescendant([&](SpatiallyNestablePointer object) {
if (object->getNestableType() == NestableType::Entity) {
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(object);
entity->markDirtyFlags(Simulation::DIRTY_ROTATION | Simulation::DIRTY_POSITION);
}
});
}
}
void EntityItem::setVelocity(const glm::vec3& value) {
glm::vec3 velocity = getLocalVelocity();
if (velocity != value) {
if (getShapeType() == SHAPE_TYPE_STATIC_MESH) {
if (velocity != Vectors::ZERO) {
setLocalVelocity(Vectors::ZERO);
}
} else {
float speed = glm::length(value);
if (!glm::isnan(speed)) {
const float MIN_LINEAR_SPEED = 0.001f;
const float MAX_LINEAR_SPEED = 270.0f; // 3m per step at 90Hz
if (speed < MIN_LINEAR_SPEED) {
velocity = ENTITY_ITEM_ZERO_VEC3;
} else if (speed > MAX_LINEAR_SPEED) {
velocity = (MAX_LINEAR_SPEED / speed) * value;
} else {
velocity = value;
}
setLocalVelocity(velocity);
_flags |= Simulation::DIRTY_LINEAR_VELOCITY;
}
}
}
}
void EntityItem::setDamping(float value) {
auto clampedDamping = glm::clamp(value, 0.0f, 1.0f);
withWriteLock([&] {
if (_damping != clampedDamping) {
_damping = clampedDamping;
_flags |= Simulation::DIRTY_MATERIAL;
}
});
}
void EntityItem::setGravity(const glm::vec3& value) {
withWriteLock([&] {
if (_gravity != value) {
if (getShapeType() == SHAPE_TYPE_STATIC_MESH) {
_gravity = Vectors::ZERO;
} else {
float magnitude = glm::length(value);
if (!glm::isnan(magnitude)) {
const float MAX_ACCELERATION_OF_GRAVITY = 10.0f * 9.8f; // 10g
if (magnitude > MAX_ACCELERATION_OF_GRAVITY) {
_gravity = (MAX_ACCELERATION_OF_GRAVITY / magnitude) * value;
} else {
_gravity = value;
}
_flags |= Simulation::DIRTY_LINEAR_VELOCITY;
}
}
}
});
}
void EntityItem::setAngularVelocity(const glm::vec3& value) {
glm::vec3 angularVelocity = getLocalAngularVelocity();
if (angularVelocity != value) {
if (getShapeType() == SHAPE_TYPE_STATIC_MESH) {
setLocalAngularVelocity(Vectors::ZERO);
} else {
float speed = glm::length(value);
if (!glm::isnan(speed)) {
const float MIN_ANGULAR_SPEED = 0.0002f;
const float MAX_ANGULAR_SPEED = 9.0f * TWO_PI; // 1/10 rotation per step at 90Hz
if (speed < MIN_ANGULAR_SPEED) {
angularVelocity = ENTITY_ITEM_ZERO_VEC3;
} else if (speed > MAX_ANGULAR_SPEED) {
angularVelocity = (MAX_ANGULAR_SPEED / speed) * value;
} else {
angularVelocity = value;
}
setLocalAngularVelocity(angularVelocity);
_flags |= Simulation::DIRTY_ANGULAR_VELOCITY;
}
}
}
}
void EntityItem::setAngularDamping(float value) {
auto clampedDamping = glm::clamp(value, 0.0f, 1.0f);
withWriteLock([&] {
if (_angularDamping != clampedDamping) {
_angularDamping = clampedDamping;
_flags |= Simulation::DIRTY_MATERIAL;
}
});
}
void EntityItem::setCollisionless(bool value) {
withWriteLock([&] {
if (_collisionless != value) {
_collisionless = value;
_flags |= Simulation::DIRTY_COLLISION_GROUP;
}
});
}
void EntityItem::setCollisionMask(uint16_t value) {
withWriteLock([&] {
if ((_collisionMask & ENTITY_COLLISION_MASK_DEFAULT) != (value & ENTITY_COLLISION_MASK_DEFAULT)) {
_collisionMask = (value & ENTITY_COLLISION_MASK_DEFAULT);
_flags |= Simulation::DIRTY_COLLISION_GROUP;
}
});
}
void EntityItem::setDynamic(bool value) {
if (getDynamic() != value) {
withWriteLock([&] {
// dynamic and STATIC_MESH are incompatible so we check for that case
if (value && getShapeType() == SHAPE_TYPE_STATIC_MESH) {
if (_dynamic) {
_dynamic = false;
_flags |= Simulation::DIRTY_MOTION_TYPE;
}
} else {
_dynamic = value;
_flags |= Simulation::DIRTY_MOTION_TYPE;
}
});
}
}
void EntityItem::setRestitution(float value) {
float clampedValue = glm::max(glm::min(ENTITY_ITEM_MAX_RESTITUTION, value), ENTITY_ITEM_MIN_RESTITUTION);
withWriteLock([&] {
if (_restitution != clampedValue) {
_restitution = clampedValue;
_flags |= Simulation::DIRTY_MATERIAL;
}
});
}
void EntityItem::setFriction(float value) {
float clampedValue = glm::max(glm::min(ENTITY_ITEM_MAX_FRICTION, value), ENTITY_ITEM_MIN_FRICTION);
withWriteLock([&] {
if (_friction != clampedValue) {
_friction = clampedValue;
_flags |= Simulation::DIRTY_MATERIAL;
}
});
}
void EntityItem::setLifetime(float value) {
withWriteLock([&] {
if (_lifetime != value) {
_lifetime = value;
_flags |= Simulation::DIRTY_LIFETIME;
}
});
}
void EntityItem::setCreated(quint64 value) {
withWriteLock([&] {
if (_created != value) {
_created = value;
_flags |= Simulation::DIRTY_LIFETIME;
}
});
}
void EntityItem::computeCollisionGroupAndFinalMask(int32_t& group, int32_t& mask) const {
if (_collisionless) {
group = BULLET_COLLISION_GROUP_COLLISIONLESS;
mask = 0;
} else {
if (getDynamic()) {
group = BULLET_COLLISION_GROUP_DYNAMIC;
} else if (isMovingRelativeToParent() || hasActions()) {
group = BULLET_COLLISION_GROUP_KINEMATIC;
} else {
group = BULLET_COLLISION_GROUP_STATIC;
}
uint16_t userMask = getCollisionMask();
if ((bool)(userMask & USER_COLLISION_GROUP_MY_AVATAR) !=
(bool)(userMask & USER_COLLISION_GROUP_OTHER_AVATAR)) {
// asymmetric avatar collision mask bits
if (!getSimulatorID().isNull() && getSimulatorID() != Physics::getSessionUUID()) {
// someone else owns the simulation, so we toggle the avatar bits (swap interpretation)
userMask ^= USER_COLLISION_MASK_AVATARS | ~userMask;
}
}
if ((bool)(_flags & Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING)) {
userMask &= ~USER_COLLISION_GROUP_MY_AVATAR;
}
mask = Physics::getDefaultCollisionMask(group) & (int32_t)(userMask);
}
}
void EntityItem::setSimulationOwner(const QUuid& id, uint8_t priority) {
if (wantTerseEditLogging() && (id != _simulationOwner.getID() || priority != _simulationOwner.getPriority())) {
qCDebug(entities) << "sim ownership for" << getDebugName() << "is now" << id << priority;
}
_simulationOwner.set(id, priority);
}
void EntityItem::setSimulationOwner(const SimulationOwner& owner) {
// NOTE: this method only used by EntityServer. The Interface uses special code in readEntityDataFromBuffer().
if (wantTerseEditLogging() && _simulationOwner != owner) {
qCDebug(entities) << "sim ownership for" << getDebugName() << "is now" << owner;
}
if (_simulationOwner.set(owner)) {
markDirtyFlags(Simulation::DIRTY_SIMULATOR_ID);
}
}
void EntityItem::clearSimulationOwnership() {
if (wantTerseEditLogging() && !_simulationOwner.isNull()) {
qCDebug(entities) << "sim ownership for" << getDebugName() << "is now null";
}
_simulationOwner.clear();
// don't bother setting the DIRTY_SIMULATOR_ID flag because:
// (a) when entity-server calls clearSimulationOwnership() the dirty-flags are meaningless (only used by interface)
// (b) the interface only calls clearSimulationOwnership() in a context that already knows best about dirty flags
//markDirtyFlags(Simulation::DIRTY_SIMULATOR_ID);
}
void EntityItem::setPendingOwnershipPriority(uint8_t priority, const quint64& timestamp) {
_simulationOwner.setPendingPriority(priority, timestamp);
}
QString EntityItem::actionsToDebugString() {
QString result;
QVector<QByteArray> serializedActions;
QHash<QUuid, EntityDynamicPointer>::const_iterator i = _objectActions.begin();
while (i != _objectActions.end()) {
const QUuid id = i.key();
EntityDynamicPointer action = _objectActions[id];
EntityDynamicType actionType = action->getType();
result += QString("") + actionType + ":" + action->getID().toString() + " ";
i++;
}
return result;
}
bool EntityItem::addAction(EntitySimulationPointer simulation, EntityDynamicPointer action) {
bool result;
withWriteLock([&] {
checkWaitingToRemove(simulation);
result = addActionInternal(simulation, action);
if (result) {
action->setIsMine(true);
_dynamicDataDirty = true;
} else {
removeActionInternal(action->getID());
}
});
updateQueryAACube();
return result;
}
bool EntityItem::addActionInternal(EntitySimulationPointer simulation, EntityDynamicPointer action) {
assert(action);
assert(simulation);
auto actionOwnerEntity = action->getOwnerEntity().lock();
assert(actionOwnerEntity);
assert(actionOwnerEntity.get() == this);
const QUuid& actionID = action->getID();
assert(!_objectActions.contains(actionID) || _objectActions[actionID] == action);
_objectActions[actionID] = action;
simulation->addDynamic(action);
bool success;
QByteArray newDataCache;
serializeActions(success, newDataCache);
if (success) {
_allActionsDataCache = newDataCache;
_flags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
auto actionType = action->getType();
if (actionType == DYNAMIC_TYPE_HOLD || actionType == DYNAMIC_TYPE_FAR_GRAB) {
if (!(bool)(_flags & Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING)) {
_flags |= Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING;
_flags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar
forEachDescendant([&](SpatiallyNestablePointer child) {
if (child->getNestableType() == NestableType::Entity) {
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(child);
entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP);
entity->markSpecialFlags(Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING);
}
});
}
}
} else {
qCDebug(entities) << "EntityItem::addActionInternal -- serializeActions failed";
}
return success;
}
bool EntityItem::updateAction(EntitySimulationPointer simulation, const QUuid& actionID, const QVariantMap& arguments) {
bool success = false;
withWriteLock([&] {
checkWaitingToRemove(simulation);
if (!_objectActions.contains(actionID)) {
return;
}
EntityDynamicPointer action = _objectActions[actionID];
success = action->updateArguments(arguments);
if (success) {
action->setIsMine(true);
serializeActions(success, _allActionsDataCache);
_flags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
} else {
qCDebug(entities) << "EntityItem::updateAction failed";
}
});
return success;
}
bool EntityItem::removeAction(EntitySimulationPointer simulation, const QUuid& actionID) {
bool success = false;
withWriteLock([&] {
checkWaitingToRemove(simulation);
success = removeActionInternal(actionID);
});
updateQueryAACube();
return success;
}
bool EntityItem::stillHasGrabActions() const {
QList<EntityDynamicPointer> holdActions = getActionsOfType(DYNAMIC_TYPE_HOLD);
QList<EntityDynamicPointer>::const_iterator i = holdActions.begin();
while (i != holdActions.end()) {
EntityDynamicPointer action = *i;
if (action->isMine()) {
return true;
}
i++;
}
QList<EntityDynamicPointer> farGrabActions = getActionsOfType(DYNAMIC_TYPE_FAR_GRAB);
i = farGrabActions.begin();
while (i != farGrabActions.end()) {
EntityDynamicPointer action = *i;
if (action->isMine()) {
return true;
}
i++;
}
return false;
}
bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPointer simulation) {
_previouslyDeletedActions.insert(actionID, usecTimestampNow());
if (_objectActions.contains(actionID)) {
if (!simulation) {
EntityTreeElementPointer element = _element; // use local copy of _element for logic below
EntityTreePointer entityTree = element ? element->getTree() : nullptr;
simulation = entityTree ? entityTree->getSimulation() : nullptr;
}
EntityDynamicPointer action = _objectActions[actionID];
auto removedActionType = action->getType();
action->setOwnerEntity(nullptr);
action->setIsMine(false);
_objectActions.remove(actionID);
if ((removedActionType == DYNAMIC_TYPE_HOLD || removedActionType == DYNAMIC_TYPE_FAR_GRAB) && !stillHasGrabActions()) {
_flags &= ~Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING;
_flags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar
forEachDescendant([&](SpatiallyNestablePointer child) {
if (child->getNestableType() == NestableType::Entity) {
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(child);
entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP);
entity->clearSpecialFlags(Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING);
}
});
} else {
// NO-OP: we assume SPECIAL_FLAGS_NO_BOOTSTRAPPING bits and collision group are correct
// because they should have been set correctly when the action was added
// and/or when children were linked
}
if (simulation) {
action->removeFromSimulation(simulation);
}
bool success = true;
serializeActions(success, _allActionsDataCache);
_flags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
setDynamicDataNeedsTransmit(true);
return success;
}
return false;
}
bool EntityItem::clearActions(EntitySimulationPointer simulation) {
withWriteLock([&] {
QHash<QUuid, EntityDynamicPointer>::iterator i = _objectActions.begin();
while (i != _objectActions.end()) {
const QUuid id = i.key();
EntityDynamicPointer action = _objectActions[id];
i = _objectActions.erase(i);
action->setOwnerEntity(nullptr);
action->removeFromSimulation(simulation);
}
// empty _serializedActions means no actions for the EntityItem
_actionsToRemove.clear();
_allActionsDataCache.clear();
_flags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
_flags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar
});
return true;
}
void EntityItem::deserializeActions() {
withWriteLock([&] {
deserializeActionsInternal();
});
}
void EntityItem::deserializeActionsInternal() {
quint64 now = usecTimestampNow();
if (!_element) {
qCDebug(entities) << "EntityItem::deserializeActionsInternal -- no _element";
return;
}
EntityTreePointer entityTree = getTree();
assert(entityTree);
EntitySimulationPointer simulation = entityTree ? entityTree->getSimulation() : nullptr;
assert(simulation);
QVector<QByteArray> serializedActions;
if (_allActionsDataCache.size() > 0) {
QDataStream serializedActionsStream(_allActionsDataCache);
serializedActionsStream >> serializedActions;
}
// Keep track of which actions got added or updated by the new dynamicData
QSet<QUuid> updated;
foreach(QByteArray serializedAction, serializedActions) {
QDataStream serializedActionStream(serializedAction);
EntityDynamicType actionType;
QUuid actionID;
serializedActionStream >> actionType;
serializedActionStream >> actionID;
if (_previouslyDeletedActions.contains(actionID)) {
continue;
}
if (_objectActions.contains(actionID)) {
EntityDynamicPointer action = _objectActions[actionID];
// TODO: make sure types match? there isn't currently a way to
// change the type of an existing action.
if (!action->isMine()) {
action->deserialize(serializedAction);
}
updated << actionID;
} else {
auto actionFactory = DependencyManager::get<EntityDynamicFactoryInterface>();
EntityItemPointer entity = getThisPointer();
EntityDynamicPointer action = actionFactory->factoryBA(entity, serializedAction);
if (action) {
entity->addActionInternal(simulation, action);
updated << actionID;
} else {
HIFI_FCDEBUG(entities(), "EntityItem::deserializeActionsInternal -- action creation failed for"
<< getID() << _name); // getName();
removeActionInternal(actionID, nullptr);
}
}
}
// remove any actions that weren't included in the new data.
QHash<QUuid, EntityDynamicPointer>::const_iterator i = _objectActions.begin();
while (i != _objectActions.end()) {
QUuid id = i.key();
if (!updated.contains(id)) {
EntityDynamicPointer action = i.value();
if (action->isMine()) {
// we just received an update that didn't include one of our actions. tell the server about it (again).
setDynamicDataNeedsTransmit(true);
} else {
// don't let someone else delete my action.
_actionsToRemove << id;
_previouslyDeletedActions.insert(id, now);
}
}
i++;
}
// trim down _previouslyDeletedActions
QMutableHashIterator<QUuid, quint64> _previouslyDeletedIter(_previouslyDeletedActions);
while (_previouslyDeletedIter.hasNext()) {
_previouslyDeletedIter.next();
if (now - _previouslyDeletedIter.value() > _rememberDeletedActionTime) {
_previouslyDeletedActions.remove(_previouslyDeletedIter.key());
}
}
_dynamicDataDirty = true;
return;
}
void EntityItem::checkWaitingToRemove(EntitySimulationPointer simulation) {
foreach(QUuid actionID, _actionsToRemove) {
removeActionInternal(actionID, simulation);
}
_actionsToRemove.clear();
}
void EntityItem::setDynamicData(QByteArray dynamicData) {
withWriteLock([&] {
setDynamicDataInternal(dynamicData);
});
}
void EntityItem::setDynamicDataInternal(QByteArray dynamicData) {
if (_allActionsDataCache != dynamicData) {
_allActionsDataCache = dynamicData;
deserializeActionsInternal();
}
checkWaitingToRemove();
}
void EntityItem::serializeActions(bool& success, QByteArray& result) const {
if (_objectActions.size() == 0) {
success = true;
result.clear();
return;
}
QVector<QByteArray> serializedActions;
QHash<QUuid, EntityDynamicPointer>::const_iterator i = _objectActions.begin();
while (i != _objectActions.end()) {
const QUuid id = i.key();
EntityDynamicPointer action = _objectActions[id];
QByteArray bytesForAction = action->serialize();
serializedActions << bytesForAction;
i++;
}
QDataStream serializedActionsStream(&result, QIODevice::WriteOnly);
serializedActionsStream << serializedActions;
if (result.size() >= _maxActionsDataSize) {
qCDebug(entities) << "EntityItem::serializeActions size is too large -- "
<< result.size() << ">=" << _maxActionsDataSize;
success = false;
return;
}
success = true;
return;
}
const QByteArray EntityItem::getDynamicDataInternal() const {
if (_dynamicDataDirty) {
bool success;
serializeActions(success, _allActionsDataCache);
if (success) {
_dynamicDataDirty = false;
}
}
return _allActionsDataCache;
}
const QByteArray EntityItem::getDynamicData() const {
QByteArray result;
if (_dynamicDataDirty) {
withWriteLock([&] {
getDynamicDataInternal();
result = _allActionsDataCache;
});
} else {
withReadLock([&] {
result = _allActionsDataCache;
});
}
return result;
}
QVariantMap EntityItem::getActionArguments(const QUuid& actionID) const {
QVariantMap result;
withReadLock([&] {
if (_objectActions.contains(actionID)) {
EntityDynamicPointer action = _objectActions[actionID];
result = action->getArguments();
result["type"] = EntityDynamicInterface::dynamicTypeToString(action->getType());
}
});
return result;
}
bool EntityItem::shouldSuppressLocationEdits() const {
// if any of the actions indicate they'd like suppression, suppress
QHash<QUuid, EntityDynamicPointer>::const_iterator i = _objectActions.begin();
while (i != _objectActions.end()) {
if (i.value()->shouldSuppressLocationEdits()) {
return true;
}
i++;
}
// if any of the ancestors are MyAvatar, suppress
if (isChildOfMyAvatar()) {
return true;
}
return false;
}
QList<EntityDynamicPointer> EntityItem::getActionsOfType(EntityDynamicType typeToGet) const {
QList<EntityDynamicPointer> result;
QHash<QUuid, EntityDynamicPointer>::const_iterator i = _objectActions.begin();
while (i != _objectActions.end()) {
EntityDynamicPointer action = i.value();
if (action->getType() == typeToGet && action->isActive()) {
result += action;
}
i++;
}
return result;
}
void EntityItem::locationChanged(bool tellPhysics) {
requiresRecalcBoxes();
if (tellPhysics) {
_flags |= Simulation::DIRTY_TRANSFORM;
EntityTreePointer tree = getTree();
if (tree) {
tree->entityChanged(getThisPointer());
}
}
SpatiallyNestable::locationChanged(tellPhysics); // tell all the children, also
somethingChangedNotification();
}
void EntityItem::dimensionsChanged() {
requiresRecalcBoxes();
SpatiallyNestable::dimensionsChanged(); // Do what you have to do
somethingChangedNotification();
}
bool EntityItem::getScalesWithParent() const {
// keep this logic the same as in EntityItemProperties::getScalesWithParent
if (getClientOnly()) {
QUuid ancestorID = findAncestorOfType(NestableType::Avatar);
return !ancestorID.isNull();
} else {
return false;
}
}
void EntityItem::globalizeProperties(EntityItemProperties& properties, const QString& messageTemplate, const glm::vec3& offset) const {
// TODO -- combine this with convertLocationToScriptSemantics
bool success;
auto globalPosition = getWorldPosition(success);
if (success) {
properties.setPosition(globalPosition + offset);
properties.setRotation(getWorldOrientation());
properties.setDimensions(getScaledDimensions());
// Should we do velocities and accelerations, too? This could end up being quite involved, which is why the method exists.
} else {
properties.setPosition(getQueryAACube().calcCenter() + offset); // best we can do
}
if (!messageTemplate.isEmpty()) {
QString name = properties.getName();
if (name.isEmpty()) {
name = EntityTypes::getEntityTypeName(properties.getType());
}
qCWarning(entities) << messageTemplate.arg(getEntityItemID().toString()).arg(name).arg(properties.getParentID().toString());
}
QUuid empty;
properties.setParentID(empty);
}
bool EntityItem::matchesJSONFilters(const QJsonObject& jsonFilters) const {
// The intention for the query JSON filter and this method is to be flexible to handle a variety of filters for
// ALL entity properties. Some work will need to be done to the property system so that it can be more flexible
// (to grab the value and default value of a property given the string representation of that property, for example)
// currently the only property filter we handle is '+' for serverScripts
// which means that we only handle a filtered query asking for entities where the serverScripts property is non-default
static const QString SERVER_SCRIPTS_PROPERTY = "serverScripts";
foreach(const auto& property, jsonFilters.keys()) {
if (property == SERVER_SCRIPTS_PROPERTY && jsonFilters[property] == EntityQueryFilterSymbol::NonDefault) {
// check if this entity has a non-default value for serverScripts
if (_serverScripts != ENTITY_ITEM_DEFAULT_SERVER_SCRIPTS) {
return true;
} else {
return false;
}
}
}
// the json filter syntax did not match what we expected, return a match
return true;
}
quint64 EntityItem::getLastSimulated() const {
quint64 result;
withReadLock([&] {
result = _lastSimulated;
});
return result;
}
void EntityItem::setLastSimulated(quint64 now) {
withWriteLock([&] {
_lastSimulated = now;
});
}
quint64 EntityItem::getLastEdited() const {
quint64 result;
withReadLock([&] {
result = _lastEdited;
});
return result;
}
void EntityItem::setLastEdited(quint64 lastEdited) {
withWriteLock([&] {
_lastEdited = _lastUpdated = lastEdited;
_changedOnServer = glm::max(lastEdited, _changedOnServer);
});
}
quint64 EntityItem::getLastBroadcast() const {
quint64 result;
withReadLock([&] {
result = _lastBroadcast;
});
return result;
}
void EntityItem::setLastBroadcast(quint64 lastBroadcast) {
withWriteLock([&] {
_lastBroadcast = lastBroadcast;
});
}
void EntityItem::markAsChangedOnServer() {
withWriteLock([&] {
_changedOnServer = usecTimestampNow();
});
}
quint64 EntityItem::getLastChangedOnServer() const {
quint64 result;
withReadLock([&] {
result = _changedOnServer;
});
return result;
}
void EntityItem::update(const quint64& now) {
withWriteLock([&] {
_lastUpdated = now;
});
}
quint64 EntityItem::getLastUpdated() const {
quint64 result;
withReadLock([&] {
result = _lastUpdated;
});
return result;
}
void EntityItem::requiresRecalcBoxes() {
withWriteLock([&] {
_recalcAABox = true;
_recalcMinAACube = true;
_recalcMaxAACube = true;
});
}
QString EntityItem::getHref() const {
QString result;
withReadLock([&] {
result = _href;
});
return result;
}
QString EntityItem::getDescription() const {
QString result;
withReadLock([&] {
result = _description;
});
return result;
}
void EntityItem::setDescription(const QString& value) {
withWriteLock([&] {
_description = value;
});
}
float EntityItem::getLocalRenderAlpha() const {
float result;
withReadLock([&] {
result = _localRenderAlpha;
});
return result;
}
void EntityItem::setLocalRenderAlpha(float localRenderAlpha) {
withWriteLock([&] {
_localRenderAlpha = localRenderAlpha;
});
}
glm::vec3 EntityItem::getGravity() const {
glm::vec3 result;
withReadLock([&] {
result = _gravity;
});
return result;
}
glm::vec3 EntityItem::getAcceleration() const {
glm::vec3 result;
withReadLock([&] {
result = _acceleration;
});
return result;
}
void EntityItem::setAcceleration(const glm::vec3& value) {
withWriteLock([&] {
_acceleration = value;
});
}
float EntityItem::getDamping() const {
float result;
withReadLock([&] {
result = _damping;
});
return result;
}
float EntityItem::getRestitution() const {
float result;
withReadLock([&] {
result = _restitution;
});
return result;
}
float EntityItem::getFriction() const {
float result;
withReadLock([&] {
result = _friction;
});
return result;
}
// lifetime related properties.
float EntityItem::getLifetime() const {
float result;
withReadLock([&] {
result = _lifetime;
});
return result;
}
quint64 EntityItem::getCreated() const {
quint64 result;
withReadLock([&] {
result = _created;
});
return result;
}
QString EntityItem::getScript() const {
QString result;
withReadLock([&] {
result = _script;
});
return result;
}
void EntityItem::setScript(const QString& value) {
withWriteLock([&] {
_script = value;
});
}
quint64 EntityItem::getScriptTimestamp() const {
quint64 result;
withReadLock([&] {
result = _scriptTimestamp;
});
return result;
}
void EntityItem::setScriptTimestamp(const quint64 value) {
withWriteLock([&] {
_scriptTimestamp = value;
});
}
QString EntityItem::getServerScripts() const {
QString result;
withReadLock([&] {
result = _serverScripts;
});
return result;
}
void EntityItem::setServerScripts(const QString& serverScripts) {
withWriteLock([&] {
_serverScripts = serverScripts;
_serverScriptsChangedTimestamp = usecTimestampNow();
});
}
QString EntityItem::getCollisionSoundURL() const {
QString result;
withReadLock([&] {
result = _collisionSoundURL;
});
return result;
}
glm::vec3 EntityItem::getRegistrationPoint() const {
glm::vec3 result;
withReadLock([&] {
result = _registrationPoint;
});
return result;
}
float EntityItem::getAngularDamping() const {
float result;
withReadLock([&] {
result = _angularDamping;
});
return result;
}
QString EntityItem::getName() const {
QString result;
withReadLock([&] {
result = _name;
});
return result;
}
void EntityItem::setName(const QString& value) {
withWriteLock([&] {
_name = value;
});
}
QString EntityItem::getDebugName() {
QString result = getName();
if (result.isEmpty()) {
result = getID().toString();
}
return result;
}
bool EntityItem::getVisible() const {
bool result;
withReadLock([&] {
result = _visible;
});
return result;
}
void EntityItem::setVisible(bool value) {
bool changed = false;
withWriteLock([&] {
if (_visible != value) {
changed = true;
_visible = value;
}
});
if (changed) {
emit requestRenderUpdate();
}
}
bool EntityItem::getCanCastShadow() const {
bool result;
withReadLock([&] {
result = _canCastShadow;
});
return result;
}
void EntityItem::setCanCastShadow(bool value) {
bool changed = false;
withWriteLock([&] {
if (_canCastShadow != value) {
changed = true;
_canCastShadow = value;
}
});
if (changed) {
emit requestRenderUpdate();
}
}
bool EntityItem::isChildOfMyAvatar() const {
QUuid ancestorID = findAncestorOfType(NestableType::Avatar);
return !ancestorID.isNull() && (ancestorID == Physics::getSessionUUID() || ancestorID == AVATAR_SELF_ID);
}
bool EntityItem::getCollisionless() const {
bool result;
withReadLock([&] {
result = _collisionless;
});
return result;
}
uint16_t EntityItem::getCollisionMask() const {
uint16_t result;
withReadLock([&] {
result = _collisionMask;
});
return result;
}
bool EntityItem::getDynamic() const {
if (SHAPE_TYPE_STATIC_MESH == getShapeType()) {
return false;
}
bool result;
withReadLock([&] {
result = _dynamic;
});
return result;
}
bool EntityItem::getLocked() const {
bool result;
withReadLock([&] {
result = _locked;
});
return result;
}
void EntityItem::setLocked(bool value) {
bool changed { false };
withWriteLock([&] {
if (_locked != value) {
_locked = value;
changed = true;
}
});
if (changed) {
markDirtyFlags(Simulation::DIRTY_MOTION_TYPE);
EntityTreePointer tree = getTree();
if (tree) {
tree->entityChanged(getThisPointer());
}
}
}
QString EntityItem::getUserData() const {
QString result;
withReadLock([&] {
result = _userData;
});
return result;
}
void EntityItem::setUserData(const QString& value) {
withWriteLock([&] {
_userData = value;
});
}
// Certifiable Properties
#define DEFINE_PROPERTY_GETTER(type, accessor, var) \
type EntityItem::get##accessor() const { \
type result; \
withReadLock([&] { \
result = _##var; \
}); \
return result; \
}
#define DEFINE_PROPERTY_SETTER(type, accessor, var) \
void EntityItem::set##accessor(const type & value) { \
withWriteLock([&] { \
_##var = value; \
}); \
}
#define DEFINE_PROPERTY_ACCESSOR(type, accessor, var) DEFINE_PROPERTY_GETTER(type, accessor, var) DEFINE_PROPERTY_SETTER(type, accessor, var)
DEFINE_PROPERTY_ACCESSOR(QString, ItemName, itemName)
DEFINE_PROPERTY_ACCESSOR(QString, ItemDescription, itemDescription)
DEFINE_PROPERTY_ACCESSOR(QString, ItemCategories, itemCategories)
DEFINE_PROPERTY_ACCESSOR(QString, ItemArtist, itemArtist)
DEFINE_PROPERTY_ACCESSOR(QString, ItemLicense, itemLicense)
DEFINE_PROPERTY_ACCESSOR(quint32, LimitedRun, limitedRun)
DEFINE_PROPERTY_ACCESSOR(QString, MarketplaceID, marketplaceID)
DEFINE_PROPERTY_ACCESSOR(quint32, EditionNumber, editionNumber)
DEFINE_PROPERTY_ACCESSOR(quint32, EntityInstanceNumber, entityInstanceNumber)
DEFINE_PROPERTY_ACCESSOR(QString, CertificateID, certificateID)
DEFINE_PROPERTY_ACCESSOR(quint32, StaticCertificateVersion, staticCertificateVersion)
uint32_t EntityItem::getDirtyFlags() const {
uint32_t result;
withReadLock([&] {
result = _flags & Simulation::DIRTY_FLAGS;
});
return result;
}
void EntityItem::markDirtyFlags(uint32_t mask) {
withWriteLock([&] {
mask &= Simulation::DIRTY_FLAGS;
_flags |= mask;
});
}
void EntityItem::clearDirtyFlags(uint32_t mask) {
withWriteLock([&] {
mask &= Simulation::DIRTY_FLAGS;
_flags &= ~mask;
});
}
uint32_t EntityItem::getSpecialFlags() const {
uint32_t result;
withReadLock([&] {
result = _flags & Simulation::SPECIAL_FLAGS;
});
return result;
}
void EntityItem::markSpecialFlags(uint32_t mask) {
withWriteLock([&] {
mask &= Simulation::SPECIAL_FLAGS;
_flags |= mask;
});
}
void EntityItem::clearSpecialFlags(uint32_t mask) {
withWriteLock([&] {
mask &= Simulation::SPECIAL_FLAGS;
_flags &= ~mask;
});
}
float EntityItem::getDensity() const {
float result;
withReadLock([&] {
result = _density;
});
return result;
}
EntityItem::ChangeHandlerId EntityItem::registerChangeHandler(const ChangeHandlerCallback& handler) {
ChangeHandlerId result = QUuid::createUuid();
withWriteLock([&] {
_changeHandlers[result] = handler;
});
return result;
}
void EntityItem::deregisterChangeHandler(const ChangeHandlerId& changeHandlerId) {
withWriteLock([&] {
_changeHandlers.remove(changeHandlerId);
});
}
void EntityItem::somethingChangedNotification() {
auto id = getEntityItemID();
withReadLock([&] {
for (const auto& handler : _changeHandlers.values()) {
handler(id);
}
});
}
void EntityItem::retrieveMarketplacePublicKey() {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest networkRequest;
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL();
requestURL.setPath("/api/v1/commerce/marketplace_key");
QJsonObject request;
networkRequest.setUrl(requestURL);
QNetworkReply* networkReply = NULL;
networkReply = networkAccessManager.get(networkRequest);
connect(networkReply, &QNetworkReply::finished, [=]() {
QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object();
jsonObject = jsonObject["data"].toObject();
if (networkReply->error() == QNetworkReply::NoError) {
if (!jsonObject["public_key"].toString().isEmpty()) {
EntityItem::_marketplacePublicKey = jsonObject["public_key"].toString();
qCWarning(entities) << "Marketplace public key has been set to" << _marketplacePublicKey;
} else {
qCWarning(entities) << "Marketplace public key is empty!";
}
} else {
qCWarning(entities) << "Call to" << networkRequest.url() << "failed! Error:" << networkReply->error();
}
networkReply->deleteLater();
});
}
void EntityItem::preDelete() {
}
void EntityItem::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) {
std::lock_guard<std::mutex> lock(_materialsLock);
_materials[parentMaterialName].push(material);
}
void EntityItem::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) {
std::lock_guard<std::mutex> lock(_materialsLock);
_materials[parentMaterialName].remove(material);
}
std::unordered_map<std::string, graphics::MultiMaterial> EntityItem::getMaterials() {
std::unordered_map<std::string, graphics::MultiMaterial> toReturn;
{
std::lock_guard<std::mutex> lock(_materialsLock);
toReturn = _materials;
}
return toReturn;
}
bool EntityItem::getCloneable() const {
bool result;
withReadLock([&] {
result = _cloneable;
});
return result;
}
void EntityItem::setCloneable(bool value) {
withWriteLock([&] {
_cloneable = value;
});
}
float EntityItem::getCloneLifetime() const {
float result;
withReadLock([&] {
result = _cloneLifetime;
});
return result;
}
void EntityItem::setCloneLifetime(float value) {
withWriteLock([&] {
_cloneLifetime = value;
});
}
float EntityItem::getCloneLimit() const {
float result;
withReadLock([&] {
result = _cloneLimit;
});
return result;
}
void EntityItem::setCloneLimit(float value) {
withWriteLock([&] {
_cloneLimit = value;
});
}
bool EntityItem::getCloneDynamic() const {
bool result;
withReadLock([&] {
result = _cloneDynamic;
});
return result;
}
void EntityItem::setCloneDynamic(bool value) {
withWriteLock([&] {
_cloneDynamic = value;
});
}
bool EntityItem::getCloneAvatarEntity() const {
bool result;
withReadLock([&] {
result = _cloneAvatarEntity;
});
return result;
}
void EntityItem::setCloneAvatarEntity(bool value) {
withWriteLock([&] {
_cloneAvatarEntity = value;
});
}
const QUuid EntityItem::getCloneOriginID() const {
QUuid result;
withReadLock([&] {
result = _cloneOriginID;
});
return result;
}
void EntityItem::setCloneOriginID(const QUuid& value) {
withWriteLock([&] {
_cloneOriginID = value;
});
}
void EntityItem::addCloneID(const QUuid& cloneID) {
withWriteLock([&] {
if (!_cloneIDs.contains(cloneID)) {
_cloneIDs.append(cloneID);
}
});
}
void EntityItem::removeCloneID(const QUuid& cloneID) {
withWriteLock([&] {
int index = _cloneIDs.indexOf(cloneID);
if (index >= 0) {
_cloneIDs.removeAt(index);
}
});
}
const QVector<QUuid> EntityItem::getCloneIDs() const {
QVector<QUuid> result;
withReadLock([&] {
result = _cloneIDs;
});
return result;
}
void EntityItem::setCloneIDs(const QVector<QUuid>& cloneIDs) {
withWriteLock([&] {
_cloneIDs = cloneIDs;
});
}