// // 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // usecTimestampNow() #include #include #include "EntityScriptingInterface.h" #include "EntitiesLogging.h" #include "EntityTree.h" #include "EntitySimulation.h" #include "EntityDynamicFactoryInterface.h" Q_DECLARE_METATYPE(EntityItemPointer); int entityItemPointernMetaTypeId = qRegisterMetaType(); 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 typeCoder = getType(); QByteArray encodedType = typeCoder; // last updated (animations, non-physics changes) quint64 updateDelta = getLastUpdated() <= getLastEdited() ? 0 : getLastUpdated() - getLastEdited(); ByteCountCoded updateDeltaCoder = updateDelta; QByteArray encodedUpdateDelta = updateDeltaCoder; // last simulated (velocity, angular velocity, physics changes) quint64 simulatedDelta = getLastSimulated() <= getLastEdited() ? 0 : getLastSimulated() - getLastEdited(); ByteCountCoded 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(); 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&)_type); #ifdef VALIDATE_ENTITY_ITEM_PARSER QByteArray encodedType = originalDataBuffer.mid(bytesRead); // maximum possible size ByteCountCoded 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 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 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(); 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(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(); 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(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(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(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(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 serializedActions; QHash::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(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 holdActions = getActionsOfType(DYNAMIC_TYPE_HOLD); QList::const_iterator i = holdActions.begin(); while (i != holdActions.end()) { EntityDynamicPointer action = *i; if (action->isMine()) { return true; } i++; } QList 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(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::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 serializedActions; if (_allActionsDataCache.size() > 0) { QDataStream serializedActionsStream(_allActionsDataCache); serializedActionsStream >> serializedActions; } // Keep track of which actions got added or updated by the new dynamicData QSet 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(); 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::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 _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 serializedActions; QHash::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::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 EntityItem::getActionsOfType(EntityDynamicType typeToGet) const { QList result; QHash::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 lock(_materialsLock); _materials[parentMaterialName].push(material); } void EntityItem::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) { std::lock_guard lock(_materialsLock); _materials[parentMaterialName].remove(material); } std::unordered_map EntityItem::getMaterials() { std::unordered_map toReturn; { std::lock_guard 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 EntityItem::getCloneIDs() const { QVector result; withReadLock([&] { result = _cloneIDs; }); return result; } void EntityItem::setCloneIDs(const QVector& cloneIDs) { withWriteLock([&] { _cloneIDs = cloneIDs; }); }