// // 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 #include #include #include #include // usecTimestampNow() #include #include // This is not ideal, but adding script-engine as a linked library, will cause a circular reference // I'm open to other potential solutions. Could we change cmake to allow libraries to reference each others // headers, but not link to each other, this is essentially what this construct is doing, but would be // better to add includes to the include path, but not link #include "../../script-engine/src/ScriptEngine.h" #include "EntityScriptingInterface.h" #include "EntityItem.h" #include "EntityTree.h" uint32_t EntityItem::_nextID = 0; // for locally created models std::map EntityItem::_tokenIDsToIDs; uint32_t EntityItem::_nextCreatorTokenID = 0; uint32_t EntityItem::getIDfromCreatorTokenID(uint32_t creatorTokenID) { if (_tokenIDsToIDs.find(creatorTokenID) != _tokenIDsToIDs.end()) { return _tokenIDsToIDs[creatorTokenID]; } return UNKNOWN_ENTITY_ID; } uint32_t EntityItem::getNextCreatorTokenID() { uint32_t creatorTokenID = _nextCreatorTokenID; _nextCreatorTokenID++; return creatorTokenID; } void EntityItem::handleAddEntityResponse(const QByteArray& packet) { const unsigned char* dataAt = reinterpret_cast(packet.data()); int numBytesPacketHeader = numBytesForPacketHeader(packet); dataAt += numBytesPacketHeader; uint32_t creatorTokenID; memcpy(&creatorTokenID, dataAt, sizeof(creatorTokenID)); dataAt += sizeof(creatorTokenID); uint32_t entityItemID; memcpy(&entityItemID, dataAt, sizeof(entityItemID)); dataAt += sizeof(entityItemID); // add our token to id mapping _tokenIDsToIDs[creatorTokenID] = entityItemID; } EntityItem::EntityItem() { _type = EntityTypes::Base; rgbColor noColor = { 0, 0, 0 }; init(glm::vec3(0,0,0), 0, noColor, NEW_ENTITY); } void EntityItem::initFromEntityItemID(const EntityItemID& entityItemID) { _id = entityItemID.id; _creatorTokenID = entityItemID.creatorTokenID; // init values with defaults before calling setProperties uint64_t now = usecTimestampNow(); _lastEdited = now; _lastUpdated = now; _position = glm::vec3(0,0,0); _radius = 0; _rotation = ENTITY_DEFAULT_ROTATION; _shouldBeDeleted = false; #ifdef HIDE_SUBCLASS_METHODS rgbColor noColor = { 0, 0, 0 }; memcpy(_color, noColor, sizeof(_color)); _modelURL = ENTITY_DEFAULT_MODEL_URL; // animation related _animationURL = ENTITY_DEFAULT_ANIMATION_URL; _animationIsPlaying = false; _animationFrameIndex = 0.0f; _animationFPS = ENTITY_DEFAULT_ANIMATION_FPS; _glowLevel = 0.0f; _jointMappingCompleted = false; _lastAnimated = now; #endif } EntityItem::EntityItem(const EntityItemID& entityItemID) { _type = EntityTypes::Base; initFromEntityItemID(entityItemID); } EntityItem::EntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) { _type = EntityTypes::Base; initFromEntityItemID(entityItemID); setProperties(properties, true); // force copy } EntityItem::~EntityItem() { } void EntityItem::init(glm::vec3 position, float radius, rgbColor color, uint32_t id) { if (id == NEW_ENTITY) { _id = _nextID; _nextID++; } else { _id = id; } quint64 now = usecTimestampNow(); _lastEdited = now; _lastUpdated = now; _position = position; _radius = radius; _rotation = ENTITY_DEFAULT_ROTATION; _shouldBeDeleted = false; #ifdef HIDE_SUBCLASS_METHODS memcpy(_color, color, sizeof(_color)); _modelURL = ENTITY_DEFAULT_MODEL_URL; // animation related _animationURL = ENTITY_DEFAULT_ANIMATION_URL; _animationIsPlaying = false; _animationFrameIndex = 0.0f; _animationFPS = ENTITY_DEFAULT_ANIMATION_FPS; _glowLevel = 0.0f; _jointMappingCompleted = false; _lastAnimated = now; #endif } OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packetData, EncodeBitstreamParams& params, EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData) 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 ByteCountCoded idCoder = getID(); QByteArray encodedID = idCoder; // encode our type as a byte count coded byte stream ByteCountCoded typeCoder = getType(); QByteArray encodedType = typeCoder; quint64 updateDelta = getLastUpdated() <= getLastEdited() ? 0 : getLastUpdated() - getLastEdited(); ByteCountCoded updateDeltaCoder = updateDelta; QByteArray encodedUpdateDelta = updateDeltaCoder; EntityPropertyFlags propertyFlags(PROP_LAST_ITEM); EntityPropertyFlags requestedProperties; requestedProperties += PROP_POSITION; requestedProperties += PROP_RADIUS; requestedProperties += PROP_MODEL_URL; requestedProperties += PROP_ROTATION; requestedProperties += PROP_COLOR; requestedProperties += PROP_ANIMATION_URL; requestedProperties += PROP_ANIMATION_FPS; requestedProperties += PROP_ANIMATION_FRAME_INDEX; requestedProperties += PROP_ANIMATION_PLAYING; requestedProperties += PROP_SHOULD_BE_DELETED; EntityPropertyFlags propertiesDidntFit = requestedProperties; // If we are being called for a subsequent pass at appendEntityData() that failed to completely encode this item, // then our modelTreeElementExtraEncodeData should include data about which properties we need to append. if (modelTreeElementExtraEncodeData && modelTreeElementExtraEncodeData->includedItems.contains(getEntityItemID())) { requestedProperties = modelTreeElementExtraEncodeData->includedItems.value(getEntityItemID()); } //qDebug() << "requestedProperties="; //requestedProperties.debugDumpBits(); LevelDetails modelLevel = packetData->startLevel(); bool successIDFits = packetData->appendValue(encodedID); bool successTypeFits = packetData->appendValue(encodedType); bool successLastEditedFits = packetData->appendValue(getLastEdited()); bool successLastUpdatedFits = packetData->appendValue(encodedUpdateDelta); int propertyFlagsOffset = packetData->getUncompressedByteOffset(); QByteArray encodedPropertyFlags = propertyFlags; int oldPropertyFlagsLength = encodedPropertyFlags.length(); bool successPropertyFlagsFits = packetData->appendValue(encodedPropertyFlags); int propertyCount = 0; bool headerFits = successIDFits && successTypeFits && 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, // PROP_VISIBLE, // PROP_POSITION if (requestedProperties.getHasProperty(PROP_POSITION)) { //qDebug() << "PROP_POSITION requested..."; LevelDetails propertyLevel = packetData->startLevel(); successPropertyFits = packetData->appendPosition(getPosition()); if (successPropertyFits) { propertyFlags |= PROP_POSITION; propertiesDidntFit -= PROP_POSITION; propertyCount++; packetData->endLevel(propertyLevel); } else { //qDebug() << "PROP_POSITION didn't fit..."; packetData->discardLevel(propertyLevel); appendState = OctreeElement::PARTIAL; } } else { //qDebug() << "PROP_POSITION NOT requested..."; propertiesDidntFit -= PROP_POSITION; } // PROP_RADIUS if (requestedProperties.getHasProperty(PROP_RADIUS)) { //qDebug() << "PROP_RADIUS requested..."; LevelDetails propertyLevel = packetData->startLevel(); successPropertyFits = packetData->appendValue(getRadius()); if (successPropertyFits) { propertyFlags |= PROP_RADIUS; propertiesDidntFit -= PROP_RADIUS; propertyCount++; packetData->endLevel(propertyLevel); } else { //qDebug() << "PROP_RADIUS didn't fit..."; packetData->discardLevel(propertyLevel); appendState = OctreeElement::PARTIAL; } } else { //qDebug() << "PROP_RADIUS NOT requested..."; propertiesDidntFit -= PROP_RADIUS; } // PROP_ROTATION if (requestedProperties.getHasProperty(PROP_ROTATION)) { //qDebug() << "PROP_ROTATION requested..."; LevelDetails propertyLevel = packetData->startLevel(); successPropertyFits = packetData->appendValue(getRotation()); if (successPropertyFits) { propertyFlags |= PROP_ROTATION; propertiesDidntFit -= PROP_ROTATION; propertyCount++; packetData->endLevel(propertyLevel); } else { //qDebug() << "PROP_ROTATION didn't fit..."; packetData->discardLevel(propertyLevel); appendState = OctreeElement::PARTIAL; } } else { //qDebug() << "PROP_ROTATION NOT requested..."; propertiesDidntFit -= PROP_ROTATION; } // PROP_SHOULD_BE_DELETED if (requestedProperties.getHasProperty(PROP_SHOULD_BE_DELETED)) { //qDebug() << "PROP_SHOULD_BE_DELETED requested..."; LevelDetails propertyLevel = packetData->startLevel(); successPropertyFits = packetData->appendValue(getShouldBeDeleted()); if (successPropertyFits) { propertyFlags |= PROP_SHOULD_BE_DELETED; propertiesDidntFit -= PROP_SHOULD_BE_DELETED; propertyCount++; packetData->endLevel(propertyLevel); } else { //qDebug() << "PROP_SHOULD_BE_DELETED didn't fit..."; packetData->discardLevel(propertyLevel); appendState = OctreeElement::PARTIAL; } } else { //qDebug() << "PROP_SHOULD_BE_DELETED NOT requested..."; propertiesDidntFit -= PROP_SHOULD_BE_DELETED; } // PROP_SCRIPT // script would go here... #ifdef HIDE_SUBCLASS_METHODS // PROP_COLOR if (requestedProperties.getHasProperty(PROP_COLOR)) { //qDebug() << "PROP_COLOR requested..."; LevelDetails propertyLevel = packetData->startLevel(); successPropertyFits = packetData->appendColor(getColor()); if (successPropertyFits) { propertyFlags |= PROP_COLOR; propertiesDidntFit -= PROP_COLOR; propertyCount++; packetData->endLevel(propertyLevel); } else { //qDebug() << "PROP_COLOR didn't fit..."; packetData->discardLevel(propertyLevel); appendState = OctreeElement::PARTIAL; } } else { //qDebug() << "PROP_COLOR NOT requested..."; propertiesDidntFit -= PROP_COLOR; } // PROP_MODEL_URL if (requestedProperties.getHasProperty(PROP_MODEL_URL)) { //qDebug() << "PROP_MODEL_URL requested..."; LevelDetails propertyLevel = packetData->startLevel(); successPropertyFits = packetData->appendValue(getModelURL()); if (successPropertyFits) { propertyFlags |= PROP_MODEL_URL; propertiesDidntFit -= PROP_MODEL_URL; propertyCount++; packetData->endLevel(propertyLevel); } else { //qDebug() << "PROP_MODEL_URL didn't fit..."; packetData->discardLevel(propertyLevel); appendState = OctreeElement::PARTIAL; } } else { //qDebug() << "PROP_MODEL_URL NOT requested..."; propertiesDidntFit -= PROP_MODEL_URL; } // PROP_ANIMATION_URL if (requestedProperties.getHasProperty(PROP_ANIMATION_URL)) { //qDebug() << "PROP_ANIMATION_URL requested..."; LevelDetails propertyLevel = packetData->startLevel(); successPropertyFits = packetData->appendValue(getAnimationURL()); if (successPropertyFits) { propertyFlags |= PROP_ANIMATION_URL; propertiesDidntFit -= PROP_ANIMATION_URL; propertyCount++; packetData->endLevel(propertyLevel); } else { //qDebug() << "PROP_ANIMATION_URL didn't fit..."; packetData->discardLevel(propertyLevel); appendState = OctreeElement::PARTIAL; } } else { //qDebug() << "PROP_ANIMATION_URL NOT requested..."; propertiesDidntFit -= PROP_ANIMATION_URL; } // PROP_ANIMATION_FPS if (requestedProperties.getHasProperty(PROP_ANIMATION_FPS)) { //qDebug() << "PROP_ANIMATION_FPS requested..."; LevelDetails propertyLevel = packetData->startLevel(); successPropertyFits = packetData->appendValue(getAnimationFPS()); if (successPropertyFits) { propertyFlags |= PROP_ANIMATION_FPS; propertiesDidntFit -= PROP_ANIMATION_FPS; propertyCount++; packetData->endLevel(propertyLevel); } else { //qDebug() << "PROP_ANIMATION_FPS didn't fit..."; packetData->discardLevel(propertyLevel); appendState = OctreeElement::PARTIAL; } } else { //qDebug() << "PROP_ANIMATION_FPS NOT requested..."; propertiesDidntFit -= PROP_ANIMATION_FPS; } // PROP_ANIMATION_FRAME_INDEX if (requestedProperties.getHasProperty(PROP_ANIMATION_FRAME_INDEX)) { //qDebug() << "PROP_ANIMATION_FRAME_INDEX requested..."; LevelDetails propertyLevel = packetData->startLevel(); successPropertyFits = packetData->appendValue(getAnimationFrameIndex()); if (successPropertyFits) { propertyFlags |= PROP_ANIMATION_FRAME_INDEX; propertiesDidntFit -= PROP_ANIMATION_FRAME_INDEX; propertyCount++; packetData->endLevel(propertyLevel); } else { //qDebug() << "PROP_ANIMATION_FRAME_INDEX didn't fit..."; packetData->discardLevel(propertyLevel); appendState = OctreeElement::PARTIAL; } } else { //qDebug() << "PROP_ANIMATION_FRAME_INDEX NOT requested..."; propertiesDidntFit -= PROP_ANIMATION_FRAME_INDEX; } // PROP_ANIMATION_PLAYING if (requestedProperties.getHasProperty(PROP_ANIMATION_PLAYING)) { //qDebug() << "PROP_ANIMATION_PLAYING requested..."; LevelDetails propertyLevel = packetData->startLevel(); successPropertyFits = packetData->appendValue(getAnimationIsPlaying()); if (successPropertyFits) { propertyFlags |= PROP_ANIMATION_PLAYING; propertiesDidntFit -= PROP_ANIMATION_PLAYING; propertyCount++; packetData->endLevel(propertyLevel); } else { //qDebug() << "PROP_ANIMATION_PLAYING didn't fit..."; packetData->discardLevel(propertyLevel); appendState = OctreeElement::PARTIAL; } } else { //qDebug() << "PROP_ANIMATION_PLAYING NOT requested..."; propertiesDidntFit -= PROP_ANIMATION_PLAYING; } #endif //def HIDE_SUBCLASS_METHODS } 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(modelLevel); } else { packetData->discardLevel(modelLevel); appendState = OctreeElement::NONE; // if we got here, then we didn't include the item } //qDebug() << "propertyFlags="; //propertyFlags.debugDumpBits(); //qDebug() << "propertiesDidntFit="; //propertiesDidntFit.debugDumpBits(); // 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 modelTreeElementExtraEncodeData->includedItems.insert(getEntityItemID(), propertiesDidntFit); } return appendState; } int EntityItem::expectedBytes() { int expectedBytes = sizeof(uint32_t) // id + sizeof(float) // age + sizeof(quint64) // last updated + sizeof(quint64) // lasted edited + sizeof(float) // radius + sizeof(glm::vec3) // position + sizeof(rgbColor); // color // potentially more... return expectedBytes; } int EntityItem::oldVersionReadEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) { int bytesRead = 0; if (bytesLeftToRead >= expectedBytes()) { int clockSkew = args.sourceNode ? args.sourceNode->getClockSkewUsec() : 0; const unsigned char* dataAt = data; // id memcpy(&_id, dataAt, sizeof(_id)); dataAt += sizeof(_id); bytesRead += sizeof(_id); // _lastUpdated memcpy(&_lastUpdated, dataAt, sizeof(_lastUpdated)); dataAt += sizeof(_lastUpdated); bytesRead += sizeof(_lastUpdated); _lastUpdated -= clockSkew; // _lastEdited memcpy(&_lastEdited, dataAt, sizeof(_lastEdited)); dataAt += sizeof(_lastEdited); bytesRead += sizeof(_lastEdited); _lastEdited -= clockSkew; // radius memcpy(&_radius, dataAt, sizeof(_radius)); dataAt += sizeof(_radius); bytesRead += sizeof(_radius); // position memcpy(&_position, dataAt, sizeof(_position)); dataAt += sizeof(_position); bytesRead += sizeof(_position); // color #ifdef HIDE_SUBCLASS_METHODS memcpy(_color, dataAt, sizeof(_color)); dataAt += sizeof(_color); bytesRead += sizeof(_color); #endif // shouldBeDeleted memcpy(&_shouldBeDeleted, dataAt, sizeof(_shouldBeDeleted)); dataAt += sizeof(_shouldBeDeleted); bytesRead += sizeof(_shouldBeDeleted); // modelURL uint16_t modelURLLength; memcpy(&modelURLLength, dataAt, sizeof(modelURLLength)); dataAt += sizeof(modelURLLength); bytesRead += sizeof(modelURLLength); QString modelURLString((const char*)dataAt); #ifdef HIDE_SUBCLASS_METHODS setModelURL(modelURLString); #endif dataAt += modelURLLength; bytesRead += modelURLLength; // rotation int bytes = unpackOrientationQuatFromBytes(dataAt, _rotation); dataAt += bytes; bytesRead += bytes; if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_ANIMATION) { // animationURL uint16_t animationURLLength; memcpy(&animationURLLength, dataAt, sizeof(animationURLLength)); dataAt += sizeof(animationURLLength); bytesRead += sizeof(animationURLLength); QString animationURLString((const char*)dataAt); #ifdef HIDE_SUBCLASS_METHODS setAnimationURL(animationURLString); #endif dataAt += animationURLLength; bytesRead += animationURLLength; #ifdef HIDE_SUBCLASS_METHODS // animationIsPlaying memcpy(&_animationIsPlaying, dataAt, sizeof(_animationIsPlaying)); dataAt += sizeof(_animationIsPlaying); bytesRead += sizeof(_animationIsPlaying); // animationFrameIndex memcpy(&_animationFrameIndex, dataAt, sizeof(_animationFrameIndex)); dataAt += sizeof(_animationFrameIndex); bytesRead += sizeof(_animationFrameIndex); // animationFPS memcpy(&_animationFPS, dataAt, sizeof(_animationFPS)); dataAt += sizeof(_animationFPS); bytesRead += sizeof(_animationFPS); #endif } } return bytesRead; } EntityItemID EntityItem::readEntityItemIDFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) { EntityItemID result; if (bytesLeftToRead >= sizeof(uint32_t)) { // id QByteArray encodedID((const char*)data, bytesLeftToRead); ByteCountCoded idCoder = encodedID; quint32 id = idCoder; result.id = id; result.isKnownID = true; result.creatorTokenID = UNKNOWN_ENTITY_TOKEN; } return result; } int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) { if (args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_SPLIT_MTU) { return oldVersionReadEntityDataFromBuffer(data, bytesLeftToRead, args); } // 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; // TODO: this is not correct, we don't yet have 16 byte IDs int bytesRead = 0; if (bytesLeftToRead >= MINIMUM_HEADER_BYTES) { int originalLength = bytesLeftToRead; QByteArray originalDataBuffer((const char*)data, originalLength); int clockSkew = args.sourceNode ? args.sourceNode->getClockSkewUsec() : 0; const unsigned char* dataAt = data; // id QByteArray encodedID = originalDataBuffer.mid(bytesRead); // maximum possible size ByteCountCoded idCoder = encodedID; encodedID = idCoder; // determine true length dataAt += encodedID.size(); bytesRead += encodedID.size(); _id = idCoder; _creatorTokenID = UNKNOWN_ENTITY_TOKEN; // if we know the id, then we don't care about the creator token _newlyCreated = false; // type 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; _type = (EntityTypes::EntityType_t)type; // _lastEdited memcpy(&_lastEdited, dataAt, sizeof(_lastEdited)); dataAt += sizeof(_lastEdited); bytesRead += sizeof(_lastEdited); _lastEdited -= clockSkew; // last updated is stored as ByteCountCoded delta from lastEdited QByteArray encodedUpdateDelta = originalDataBuffer.mid(bytesRead); // maximum possible size ByteCountCoded updateDeltaCoder = encodedUpdateDelta; quint64 updateDelta = updateDeltaCoder; _lastUpdated = _lastEdited + updateDelta; // don't adjust for clock skew since we already did that for _lastEdited encodedUpdateDelta = updateDeltaCoder; // determine true length dataAt += encodedUpdateDelta.size(); bytesRead += encodedUpdateDelta.size(); // Property Flags QByteArray encodedPropertyFlags = originalDataBuffer.mid(bytesRead); // maximum possible size EntityPropertyFlags propertyFlags = encodedPropertyFlags; dataAt += propertyFlags.getEncodedLength(); bytesRead += propertyFlags.getEncodedLength(); // PROP_POSITION if (propertyFlags.getHasProperty(PROP_POSITION)) { memcpy(&_position, dataAt, sizeof(_position)); dataAt += sizeof(_position); bytesRead += sizeof(_position); } // PROP_RADIUS if (propertyFlags.getHasProperty(PROP_RADIUS)) { memcpy(&_radius, dataAt, sizeof(_radius)); dataAt += sizeof(_radius); bytesRead += sizeof(_radius); } // PROP_ROTATION if (propertyFlags.getHasProperty(PROP_ROTATION)) { int bytes = unpackOrientationQuatFromBytes(dataAt, _rotation); dataAt += bytes; bytesRead += bytes; } // PROP_SHOULD_BE_DELETED if (propertyFlags.getHasProperty(PROP_SHOULD_BE_DELETED)) { memcpy(&_shouldBeDeleted, dataAt, sizeof(_shouldBeDeleted)); dataAt += sizeof(_shouldBeDeleted); bytesRead += sizeof(_shouldBeDeleted); } // PROP_SCRIPT // script would go here... #ifdef HIDE_SUBCLASS_METHODS // PROP_COLOR if (propertyFlags.getHasProperty(PROP_COLOR)) { memcpy(_color, dataAt, sizeof(_color)); dataAt += sizeof(_color); bytesRead += sizeof(_color); } // PROP_MODEL_URL if (propertyFlags.getHasProperty(PROP_MODEL_URL)) { // TODO: fix to new format... uint16_t modelURLLength; memcpy(&modelURLLength, dataAt, sizeof(modelURLLength)); dataAt += sizeof(modelURLLength); bytesRead += sizeof(modelURLLength); QString modelURLString((const char*)dataAt); setModelURL(modelURLString); dataAt += modelURLLength; bytesRead += modelURLLength; } // PROP_ANIMATION_URL if (propertyFlags.getHasProperty(PROP_ANIMATION_URL)) { // animationURL uint16_t animationURLLength; memcpy(&animationURLLength, dataAt, sizeof(animationURLLength)); dataAt += sizeof(animationURLLength); bytesRead += sizeof(animationURLLength); QString animationURLString((const char*)dataAt); setAnimationURL(animationURLString); dataAt += animationURLLength; bytesRead += animationURLLength; } // PROP_ANIMATION_FPS if (propertyFlags.getHasProperty(PROP_ANIMATION_FPS)) { memcpy(&_animationFPS, dataAt, sizeof(_animationFPS)); dataAt += sizeof(_animationFPS); bytesRead += sizeof(_animationFPS); } // PROP_ANIMATION_FRAME_INDEX if (propertyFlags.getHasProperty(PROP_ANIMATION_FRAME_INDEX)) { memcpy(&_animationFrameIndex, dataAt, sizeof(_animationFrameIndex)); dataAt += sizeof(_animationFrameIndex); bytesRead += sizeof(_animationFrameIndex); } // PROP_ANIMATION_PLAYING if (propertyFlags.getHasProperty(PROP_ANIMATION_PLAYING)) { memcpy(&_animationIsPlaying, dataAt, sizeof(_animationIsPlaying)); dataAt += sizeof(_animationIsPlaying); bytesRead += sizeof(_animationIsPlaying); } #endif } return bytesRead; } void EntityItem::debugDump() const { qDebug() << "EntityItem id:" << getEntityItemID(); qDebug(" edited ago:%f", getEditedAgo()); qDebug(" should die:%s", debug::valueOf(getShouldBeDeleted())); qDebug(" position:%f,%f,%f", _position.x, _position.y, _position.z); qDebug(" radius:%f", getRadius()); #ifdef HIDE_SUBCLASS_METHODS qDebug(" color:%d,%d,%d", _color[0], _color[1], _color[2]); if (!getModelURL().isEmpty()) { qDebug() << " modelURL:" << qPrintable(getModelURL()); } else { qDebug() << " modelURL: NONE"; } #endif } ////////////////////////////////////////////////////// ////////////////////////////////////////////////////// ////////////////////////////////////////////////////// //Change this to use property flags... //How do we also change this to support spanning multiple MTUs... //Need to output the encode structure like handling packets over the wire... ////////////////////////////////////////////////////// ////////////////////////////////////////////////////// ////////////////////////////////////////////////////// bool EntityItem::encodeEntityEditMessageDetails(PacketType command, EntityItemID id, const EntityItemProperties& properties, unsigned char* bufferOut, int sizeIn, int& sizeOut) { OctreePacketData packetData(false, sizeIn); // create a packetData object to add out packet details too. bool success = true; // assume the best OctreeElement::AppendState appendState = OctreeElement::COMPLETED; // assume the best sizeOut = 0; // TODO: We need to review how jurisdictions should be handled for entities. (The old Models and Particles code // didn't do anything special for jurisdictions, so we're keeping that same behavior here.) // // Always include the root octcode. This is only because the OctreeEditPacketSender will check these octcodes // to determine which server to send the changes to in the case of multiple jurisdictions. The root will be sent // to all servers. glm::vec3 rootPosition(0); float rootScale = 0.5f; unsigned char* octcode = pointToOctalCode(rootPosition.x, rootPosition.y, rootPosition.z, rootScale); success = packetData.startSubTree(octcode); delete[] octcode; // assuming we have rome to fit our octalCode, proceed... if (success) { // Now add our edit content details... bool isNewEntityItem = (id.id == NEW_ENTITY); // id // encode our ID as a byte count coded byte stream ByteCountCoded idCoder = id.id; QByteArray encodedID = idCoder; // encode our ID as a byte count coded byte stream ByteCountCoded tokenCoder; QByteArray encodedToken; // special case for handling "new" modelItems if (isNewEntityItem) { // encode our creator token as a byte count coded byte stream tokenCoder = id.creatorTokenID; encodedToken = tokenCoder; } // encode our type as a byte count coded byte stream ByteCountCoded typeCoder = (quint32)properties.getType(); QByteArray encodedType = typeCoder; quint64 updateDelta = 0; // this is an edit so by definition, it's update is in sync ByteCountCoded updateDeltaCoder = updateDelta; QByteArray encodedUpdateDelta = updateDeltaCoder; EntityPropertyFlags propertyFlags(PROP_LAST_ITEM); EntityPropertyFlags requestedProperties = properties.getChangedProperties(); EntityPropertyFlags propertiesDidntFit = requestedProperties; // TODO: we need to handle the multi-pass form of this, similar to how we handle entity data // // If we are being called for a subsequent pass at appendEntityData() that failed to completely encode this item, // then our modelTreeElementExtraEncodeData should include data about which properties we need to append. //if (modelTreeElementExtraEncodeData && modelTreeElementExtraEncodeData->includedItems.contains(getEntityItemID())) { // requestedProperties = modelTreeElementExtraEncodeData->includedItems.value(getEntityItemID()); //} //qDebug() << "requestedProperties="; //requestedProperties.debugDumpBits(); LevelDetails entityLevel = packetData.startLevel(); // Last Edited quint64 always first, before any other details, which allows us easy access to adjusting this // timestamp for clock skew bool successLastEditedFits = packetData.appendValue(properties.getLastEdited()); bool successIDFits = packetData.appendValue(encodedID); if (isNewEntityItem && successIDFits) { successIDFits = packetData.appendValue(encodedToken); } bool successTypeFits = packetData.appendValue(encodedType); // TODO: Should we get rid of this in this in edit packets, since this has to always be 0? bool successLastUpdatedFits = packetData.appendValue(encodedUpdateDelta); int propertyFlagsOffset = packetData.getUncompressedByteOffset(); QByteArray encodedPropertyFlags = propertyFlags; int oldPropertyFlagsLength = encodedPropertyFlags.length(); bool successPropertyFlagsFits = packetData.appendValue(encodedPropertyFlags); int propertyCount = 0; bool headerFits = successIDFits && successTypeFits && 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, // PROP_VISIBLE, // PROP_POSITION if (requestedProperties.getHasProperty(PROP_POSITION)) { //qDebug() << "PROP_POSITION requested..."; LevelDetails propertyLevel = packetData.startLevel(); successPropertyFits = packetData.appendPosition(properties.getPosition()); if (successPropertyFits) { propertyFlags |= PROP_POSITION; propertiesDidntFit -= PROP_POSITION; propertyCount++; packetData.endLevel(propertyLevel); } else { //qDebug() << "PROP_POSITION didn't fit..."; packetData.discardLevel(propertyLevel); appendState = OctreeElement::PARTIAL; } } else { //qDebug() << "PROP_POSITION NOT requested..."; propertiesDidntFit -= PROP_POSITION; } // PROP_RADIUS if (requestedProperties.getHasProperty(PROP_RADIUS)) { //qDebug() << "PROP_RADIUS requested..."; LevelDetails propertyLevel = packetData.startLevel(); successPropertyFits = packetData.appendValue(properties.getRadius()); if (successPropertyFits) { propertyFlags |= PROP_RADIUS; propertiesDidntFit -= PROP_RADIUS; propertyCount++; packetData.endLevel(propertyLevel); } else { //qDebug() << "PROP_RADIUS didn't fit..."; packetData.discardLevel(propertyLevel); appendState = OctreeElement::PARTIAL; } } else { //qDebug() << "PROP_RADIUS NOT requested..."; propertiesDidntFit -= PROP_RADIUS; } // PROP_ROTATION if (requestedProperties.getHasProperty(PROP_ROTATION)) { //qDebug() << "PROP_ROTATION requested..."; LevelDetails propertyLevel = packetData.startLevel(); successPropertyFits = packetData.appendValue(properties.getRotation()); if (successPropertyFits) { propertyFlags |= PROP_ROTATION; propertiesDidntFit -= PROP_ROTATION; propertyCount++; packetData.endLevel(propertyLevel); } else { //qDebug() << "PROP_ROTATION didn't fit..."; packetData.discardLevel(propertyLevel); appendState = OctreeElement::PARTIAL; } } else { //qDebug() << "PROP_ROTATION NOT requested..."; propertiesDidntFit -= PROP_ROTATION; } // PROP_SHOULD_BE_DELETED if (requestedProperties.getHasProperty(PROP_SHOULD_BE_DELETED)) { //qDebug() << "PROP_SHOULD_BE_DELETED requested..."; LevelDetails propertyLevel = packetData.startLevel(); successPropertyFits = packetData.appendValue(properties.getShouldBeDeleted()); if (successPropertyFits) { propertyFlags |= PROP_SHOULD_BE_DELETED; propertiesDidntFit -= PROP_SHOULD_BE_DELETED; propertyCount++; packetData.endLevel(propertyLevel); } else { //qDebug() << "PROP_SHOULD_BE_DELETED didn't fit..."; packetData.discardLevel(propertyLevel); appendState = OctreeElement::PARTIAL; } } else { //qDebug() << "PROP_SHOULD_BE_DELETED NOT requested..."; propertiesDidntFit -= PROP_SHOULD_BE_DELETED; } // PROP_SCRIPT // script would go here... #if 0 // def HIDE_SUBCLASS_METHODS // PROP_COLOR if (requestedProperties.getHasProperty(PROP_COLOR)) { //qDebug() << "PROP_COLOR requested..."; LevelDetails propertyLevel = packetData.startLevel(); successPropertyFits = packetData.appendColor(properties.getColor()); if (successPropertyFits) { propertyFlags |= PROP_COLOR; propertiesDidntFit -= PROP_COLOR; propertyCount++; packetData.endLevel(propertyLevel); } else { //qDebug() << "PROP_COLOR didn't fit..."; packetData.discardLevel(propertyLevel); appendState = OctreeElement::PARTIAL; } } else { //qDebug() << "PROP_COLOR NOT requested..."; propertiesDidntFit -= PROP_COLOR; } // PROP_MODEL_URL if (requestedProperties.getHasProperty(PROP_MODEL_URL)) { //qDebug() << "PROP_MODEL_URL requested..."; LevelDetails propertyLevel = packetData.startLevel(); successPropertyFits = packetData.appendValue(properties.getModelURL()); if (successPropertyFits) { propertyFlags |= PROP_MODEL_URL; propertiesDidntFit -= PROP_MODEL_URL; propertyCount++; packetData.endLevel(propertyLevel); } else { //qDebug() << "PROP_MODEL_URL didn't fit..."; packetData.discardLevel(propertyLevel); appendState = OctreeElement::PARTIAL; } } else { //qDebug() << "PROP_MODEL_URL NOT requested..."; propertiesDidntFit -= PROP_MODEL_URL; } // PROP_ANIMATION_URL if (requestedProperties.getHasProperty(PROP_ANIMATION_URL)) { //qDebug() << "PROP_ANIMATION_URL requested..."; LevelDetails propertyLevel = packetData.startLevel(); successPropertyFits = packetData.appendValue(properties.getAnimationURL()); if (successPropertyFits) { propertyFlags |= PROP_ANIMATION_URL; propertiesDidntFit -= PROP_ANIMATION_URL; propertyCount++; packetData.endLevel(propertyLevel); } else { //qDebug() << "PROP_ANIMATION_URL didn't fit..."; packetData.discardLevel(propertyLevel); appendState = OctreeElement::PARTIAL; } } else { //qDebug() << "PROP_ANIMATION_URL NOT requested..."; propertiesDidntFit -= PROP_ANIMATION_URL; } // PROP_ANIMATION_FPS if (requestedProperties.getHasProperty(PROP_ANIMATION_FPS)) { //qDebug() << "PROP_ANIMATION_FPS requested..."; LevelDetails propertyLevel = packetData.startLevel(); successPropertyFits = packetData.appendValue(properties.getAnimationFPS()); if (successPropertyFits) { propertyFlags |= PROP_ANIMATION_FPS; propertiesDidntFit -= PROP_ANIMATION_FPS; propertyCount++; packetData.endLevel(propertyLevel); } else { //qDebug() << "PROP_ANIMATION_FPS didn't fit..."; packetData.discardLevel(propertyLevel); appendState = OctreeElement::PARTIAL; } } else { //qDebug() << "PROP_ANIMATION_FPS NOT requested..."; propertiesDidntFit -= PROP_ANIMATION_FPS; } // PROP_ANIMATION_FRAME_INDEX if (requestedProperties.getHasProperty(PROP_ANIMATION_FRAME_INDEX)) { //qDebug() << "PROP_ANIMATION_FRAME_INDEX requested..."; LevelDetails propertyLevel = packetData.startLevel(); successPropertyFits = packetData.appendValue(properties.getAnimationFrameIndex()); if (successPropertyFits) { propertyFlags |= PROP_ANIMATION_FRAME_INDEX; propertiesDidntFit -= PROP_ANIMATION_FRAME_INDEX; propertyCount++; packetData.endLevel(propertyLevel); } else { //qDebug() << "PROP_ANIMATION_FRAME_INDEX didn't fit..."; packetData.discardLevel(propertyLevel); appendState = OctreeElement::PARTIAL; } } else { //qDebug() << "PROP_ANIMATION_FRAME_INDEX NOT requested..."; propertiesDidntFit -= PROP_ANIMATION_FRAME_INDEX; } // PROP_ANIMATION_PLAYING if (requestedProperties.getHasProperty(PROP_ANIMATION_PLAYING)) { //qDebug() << "PROP_ANIMATION_PLAYING requested..."; LevelDetails propertyLevel = packetData.startLevel(); successPropertyFits = packetData.appendValue(properties.getAnimationIsPlaying()); if (successPropertyFits) { propertyFlags |= PROP_ANIMATION_PLAYING; propertiesDidntFit -= PROP_ANIMATION_PLAYING; propertyCount++; packetData.endLevel(propertyLevel); } else { //qDebug() << "PROP_ANIMATION_PLAYING didn't fit..."; packetData.discardLevel(propertyLevel); appendState = OctreeElement::PARTIAL; } } else { //qDebug() << "PROP_ANIMATION_PLAYING NOT requested..."; propertiesDidntFit -= PROP_ANIMATION_PLAYING; } #endif //def HIDE_SUBCLASS_METHODS } 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 } //qDebug() << "propertyFlags="; //propertyFlags.debugDumpBits(); //qDebug() << "propertiesDidntFit="; //propertiesDidntFit.debugDumpBits(); // If any part of the model items didn't fit, then the element is considered partial if (appendState != OctreeElement::COMPLETED) { // TODO: handle mechanism for handling partial fitting data! // add this item into our list for the next appendElementData() pass //modelTreeElementExtraEncodeData->includedItems.insert(getEntityItemID(), propertiesDidntFit); // for now, if it's not complete, it's not successful success = false; } } if (success) { packetData.endSubTree(); const unsigned char* finalizedData = packetData.getFinalizedData(); int finalizedSize = packetData.getFinalizedSize(); memcpy(bufferOut, finalizedData, finalizedSize); sizeOut = finalizedSize; qDebug() << "encodeEntityEditMessageDetails().... "; outputBufferBits(finalizedData, finalizedSize); } else { packetData.discardSubTree(); sizeOut = 0; } return success; } // adjust any internal timestamps to fix clock skew for this server void EntityItem::adjustEditPacketForClockSkew(unsigned char* editPacketBuffer, ssize_t length, int clockSkew) { unsigned char* dataAt = editPacketBuffer; int octets = numberOfThreeBitSectionsInCode(dataAt); int lengthOfOctcode = bytesRequiredForCodeLength(octets); dataAt += lengthOfOctcode; // lastEdited quint64 lastEditedInLocalTime; memcpy(&lastEditedInLocalTime, dataAt, sizeof(lastEditedInLocalTime)); quint64 lastEditedInServerTime = lastEditedInLocalTime + clockSkew; memcpy(dataAt, &lastEditedInServerTime, sizeof(lastEditedInServerTime)); const bool wantDebug = false; if (wantDebug) { qDebug("EntityItem::adjustEditPacketForClockSkew()..."); qDebug() << " lastEditedInLocalTime: " << lastEditedInLocalTime; qDebug() << " clockSkew: " << clockSkew; qDebug() << " lastEditedInServerTime: " << lastEditedInServerTime; } } #ifdef HIDE_SUBCLASS_METHODS QMap EntityItem::_loadedAnimations; // TODO: improve cleanup by leveraging the AnimationPointer(s) AnimationCache EntityItem::_animationCache; // This class/instance will cleanup the animations once unloaded. class EntityAnimationsBookkeeper { public: ~EntityAnimationsBookkeeper() { EntityItem::cleanupLoadedAnimations(); } }; EntityAnimationsBookkeeper modelAnimationsBookkeeperInstance; void EntityItem::cleanupLoadedAnimations() { foreach(AnimationPointer animation, _loadedAnimations) { animation.clear(); } _loadedAnimations.clear(); } Animation* EntityItem::getAnimation(const QString& url) { AnimationPointer animation; // if we don't already have this model then create it and initialize it if (_loadedAnimations.find(url) == _loadedAnimations.end()) { animation = _animationCache.getAnimation(url); _loadedAnimations[url] = animation; } else { animation = _loadedAnimations[url]; } return animation.data(); } void EntityItem::mapJoints(const QStringList& modelJointNames) { // if we don't have animation, or we're already joint mapped then bail early if (!hasAnimation() || _jointMappingCompleted) { return; } Animation* myAnimation = getAnimation(_animationURL); if (!_jointMappingCompleted) { QStringList animationJointNames = myAnimation->getJointNames(); if (modelJointNames.size() > 0 && animationJointNames.size() > 0) { _jointMapping.resize(modelJointNames.size()); for (int i = 0; i < modelJointNames.size(); i++) { _jointMapping[i] = animationJointNames.indexOf(modelJointNames[i]); } _jointMappingCompleted = true; } } } QVector EntityItem::getAnimationFrame() { QVector frameData; if (hasAnimation() && _jointMappingCompleted) { Animation* myAnimation = getAnimation(_animationURL); QVector frames = myAnimation->getFrames(); int frameCount = frames.size(); if (frameCount > 0) { int animationFrameIndex = (int)glm::floor(_animationFrameIndex) % frameCount; QVector rotations = frames[animationFrameIndex].rotations; frameData.resize(_jointMapping.size()); for (int j = 0; j < _jointMapping.size(); j++) { int rotationIndex = _jointMapping[j]; if (rotationIndex != -1 && rotationIndex < rotations.size()) { frameData[j] = rotations[rotationIndex]; } } } } return frameData; } #endif void EntityItem::update(const quint64& updateTime) { _lastUpdated = updateTime; setShouldBeDeleted(getShouldBeDeleted()); quint64 now = usecTimestampNow(); // only advance the frame index if we're playing #ifdef HIDE_SUBCLASS_METHODS if (getAnimationIsPlaying()) { float deltaTime = (float)(now - _lastAnimated) / (float)USECS_PER_SECOND; const bool wantDebugging = false; if (wantDebugging) { qDebug() << "EntityItem::update() now=" << now; qDebug() << " updateTime=" << updateTime; qDebug() << " _lastAnimated=" << _lastAnimated; qDebug() << " deltaTime=" << deltaTime; } _lastAnimated = now; _animationFrameIndex += deltaTime * _animationFPS; if (wantDebugging) { qDebug() << " _animationFrameIndex=" << _animationFrameIndex; } } else { _lastAnimated = now; } #endif } void EntityItem::copyChangedProperties(const EntityItem& other) { *this = other; } EntityItemProperties EntityItem::getProperties() const { EntityItemProperties properties; //properties.copyFromEntityItem(*this); properties._id = getID(); properties._idSet = true; properties._type = getType(); properties._position = getPosition() * (float) TREE_SCALE; properties._radius = getRadius() * (float) TREE_SCALE; properties._rotation = getRotation(); properties._shouldBeDeleted = getShouldBeDeleted(); properties._positionChanged = false; properties._radiusChanged = false; properties._rotationChanged = false; properties._shouldBeDeletedChanged = false; #if 0 //def HIDE_SUBCLASS_METHODS properties._color = getXColor(); properties._modelURL = getModelURL(); properties._animationURL = getAnimationURL(); properties._animationIsPlaying = getAnimationIsPlaying(); properties._animationFrameIndex = getAnimationFrameIndex(); properties._animationFPS = getAnimationFPS(); properties._glowLevel = getGlowLevel(); properties._sittingPoints = getSittingPoints(); // sitting support properties._colorChanged = false; properties._modelURLChanged = false; properties._animationURLChanged = false; properties._animationIsPlayingChanged = false; properties._animationFrameIndexChanged = false; properties._animationFPSChanged = false; properties._glowLevelChanged = false; #endif properties._defaultSettings = false; return properties; } void EntityItem::setProperties(const EntityItemProperties& properties, bool forceCopy) { bool somethingChanged = false; if (properties._positionChanged || forceCopy) { setPosition(properties._position / (float) TREE_SCALE); somethingChanged = true; } if (properties._radiusChanged || forceCopy) { setRadius(properties._radius / (float) TREE_SCALE); somethingChanged = true; } if (properties._rotationChanged || forceCopy) { setRotation(properties._rotation); somethingChanged = true; } if (properties._shouldBeDeletedChanged || forceCopy) { setShouldBeDeleted(properties._shouldBeDeleted); somethingChanged = true; } #if 0 // def HIDE_SUBCLASS_METHODS if (properties._colorChanged || forceCopy) { setColor(properties._color); somethingChanged = true; } if (properties._modelURLChanged || forceCopy) { setModelURL(properties._modelURL); somethingChanged = true; } if (properties._animationURLChanged || forceCopy) { setAnimationURL(properties._animationURL); somethingChanged = true; } if (properties._animationIsPlayingChanged || forceCopy) { setAnimationIsPlaying(properties._animationIsPlaying); somethingChanged = true; } if (properties._animationFrameIndexChanged || forceCopy) { setAnimationFrameIndex(properties._animationFrameIndex); somethingChanged = true; } if (properties._animationFPSChanged || forceCopy) { setAnimationFPS(properties._animationFPS); somethingChanged = true; } if (properties._glowLevelChanged || forceCopy) { setGlowLevel(properties._glowLevel); somethingChanged = true; } #endif if (somethingChanged) { bool wantDebug = false; if (wantDebug) { uint64_t now = usecTimestampNow(); int elapsed = now - _lastEdited; qDebug() << "EntityItem::setProperties() AFTER update... edited AGO=" << elapsed << "now=" << now << " _lastEdited=" << _lastEdited; } setLastEdited(properties._lastEdited); } } EntityItemProperties::EntityItemProperties() : _id(UNKNOWN_ENTITY_ID), _idSet(false), _lastEdited(usecTimestampNow()), _type(EntityTypes::Base), _position(0), _radius(ENTITY_DEFAULT_RADIUS), _rotation(ENTITY_DEFAULT_ROTATION), _shouldBeDeleted(false), _positionChanged(false), _radiusChanged(false), _rotationChanged(false), _shouldBeDeletedChanged(false), #if 0 //def HIDE_SUBCLASS_METHODS _color(), _modelURL(""), _animationURL(""), _animationIsPlaying(false), _animationFrameIndex(0.0), _animationFPS(ENTITY_DEFAULT_ANIMATION_FPS), _glowLevel(0.0f), _colorChanged(false), _modelURLChanged(false), _animationURLChanged(false), _animationIsPlayingChanged(false), _animationFrameIndexChanged(false), _animationFPSChanged(false), _glowLevelChanged(false), #endif _defaultSettings(true) { } void EntityItemProperties::debugDump() const { qDebug() << "EntityItemProperties..."; qDebug() << " _id=" << _id; qDebug() << " _idSet=" << _idSet; qDebug() << " _position=" << _position.x << "," << _position.y << "," << _position.z; qDebug() << " _radius=" << _radius; } uint16_t EntityItemProperties::getChangedBits() const { uint16_t changedBits = 0; if (_radiusChanged) { changedBits += ENTITY_PACKET_CONTAINS_RADIUS; } if (_positionChanged) { changedBits += ENTITY_PACKET_CONTAINS_POSITION; } if (_rotationChanged) { changedBits += ENTITY_PACKET_CONTAINS_ROTATION; } if (_shouldBeDeletedChanged) { changedBits += ENTITY_PACKET_CONTAINS_SHOULDDIE; } #if 0 //def HIDE_SUBCLASS_METHODS if (_colorChanged) { changedBits += ENTITY_PACKET_CONTAINS_COLOR; } if (_modelURLChanged) { changedBits += ENTITY_PACKET_CONTAINS_MODEL_URL; } if (_animationURLChanged) { changedBits += ENTITY_PACKET_CONTAINS_ANIMATION_URL; } if (_animationIsPlayingChanged) { changedBits += ENTITY_PACKET_CONTAINS_ANIMATION_PLAYING; } if (_animationFrameIndexChanged) { changedBits += ENTITY_PACKET_CONTAINS_ANIMATION_FRAME; } if (_animationFPSChanged) { changedBits += ENTITY_PACKET_CONTAINS_ANIMATION_FPS; } #endif return changedBits; } EntityPropertyFlags EntityItemProperties::getChangedProperties() const { EntityPropertyFlags changedProperties; if (_radiusChanged) { changedProperties += PROP_RADIUS; } if (_positionChanged) { changedProperties += PROP_POSITION; } if (_rotationChanged) { changedProperties += PROP_ROTATION; } if (_shouldBeDeletedChanged) { changedProperties += PROP_SHOULD_BE_DELETED; } #if 0 //def HIDE_SUBCLASS_METHODS if (_colorChanged) { changedProperties += PROP_COLOR; } if (_modelURLChanged) { changedProperties += PROP_MODEL_URL; } if (_animationURLChanged) { changedProperties += PROP_ANIMATION_URL; } if (_animationIsPlayingChanged) { changedProperties += PROP_ANIMATION_PLAYING; } if (_animationFrameIndexChanged) { changedProperties += PROP_ANIMATION_FRAME_INDEX; } if (_animationFPSChanged) { changedProperties += PROP_ANIMATION_FPS; } #endif return changedProperties; } QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) const { QScriptValue properties = engine->newObject(); if (_idSet) { properties.setProperty("id", _id); bool isKnownID = (_id != UNKNOWN_ENTITY_ID); properties.setProperty("isKnownID", isKnownID); qDebug() << "EntityItemProperties::copyToScriptValue()... isKnownID=" << isKnownID << "id=" << _id; } QScriptValue position = vec3toScriptValue(engine, _position); properties.setProperty("position", position); properties.setProperty("radius", _radius); QScriptValue rotation = quatToScriptValue(engine, _rotation); properties.setProperty("rotation", rotation); properties.setProperty("shouldBeDeleted", _shouldBeDeleted); #if 0 // def HIDE_SUBCLASS_METHODS QScriptValue color = xColorToScriptValue(engine, _color); properties.setProperty("color", color); properties.setProperty("modelURL", _modelURL); properties.setProperty("animationURL", _animationURL); properties.setProperty("animationIsPlaying", _animationIsPlaying); properties.setProperty("animationFrameIndex", _animationFrameIndex); properties.setProperty("animationFPS", _animationFPS); properties.setProperty("glowLevel", _glowLevel); // Sitting properties support QScriptValue sittingPoints = engine->newObject(); for (int i = 0; i < _sittingPoints.size(); ++i) { QScriptValue sittingPoint = engine->newObject(); sittingPoint.setProperty("name", _sittingPoints[i].name); sittingPoint.setProperty("position", vec3toScriptValue(engine, _sittingPoints[i].position)); sittingPoint.setProperty("rotation", quatToScriptValue(engine, _sittingPoints[i].rotation)); sittingPoints.setProperty(i, sittingPoint); } sittingPoints.setProperty("length", _sittingPoints.size()); properties.setProperty("sittingPoints", sittingPoints); #endif // HIDE_SUBCLASS_METHODS return properties; } void EntityItemProperties::copyFromScriptValue(const QScriptValue& object) { QScriptValue typeScriptValue = object.property("type"); if (typeScriptValue.isValid()) { QString typeName; typeName = typeScriptValue.toVariant().toString(); _type = EntityTypes::getEntityTypeFromName(typeName); } QScriptValue position = object.property("position"); if (position.isValid()) { QScriptValue x = position.property("x"); QScriptValue y = position.property("y"); QScriptValue z = position.property("z"); if (x.isValid() && y.isValid() && z.isValid()) { glm::vec3 newPosition; newPosition.x = x.toVariant().toFloat(); newPosition.y = y.toVariant().toFloat(); newPosition.z = z.toVariant().toFloat(); if (_defaultSettings || newPosition != _position) { _position = newPosition; _positionChanged = true; } } } QScriptValue radius = object.property("radius"); if (radius.isValid()) { float newRadius; newRadius = radius.toVariant().toFloat(); if (_defaultSettings || newRadius != _radius) { _radius = newRadius; _radiusChanged = true; } } QScriptValue rotation = object.property("rotation"); if (rotation.isValid()) { QScriptValue x = rotation.property("x"); QScriptValue y = rotation.property("y"); QScriptValue z = rotation.property("z"); QScriptValue w = rotation.property("w"); if (x.isValid() && y.isValid() && z.isValid() && w.isValid()) { glm::quat newRotation; newRotation.x = x.toVariant().toFloat(); newRotation.y = y.toVariant().toFloat(); newRotation.z = z.toVariant().toFloat(); newRotation.w = w.toVariant().toFloat(); if (_defaultSettings || newRotation != _rotation) { _rotation = newRotation; _rotationChanged = true; } } } QScriptValue shouldBeDeleted = object.property("shouldBeDeleted"); if (shouldBeDeleted.isValid()) { bool newShouldBeDeleted; newShouldBeDeleted = shouldBeDeleted.toVariant().toBool(); if (_defaultSettings || newShouldBeDeleted != _shouldBeDeleted) { _shouldBeDeleted = newShouldBeDeleted; _shouldBeDeletedChanged = true; } } #if 0 //def HIDE_SUBCLASS_METHODS QScriptValue color = object.property("color"); if (color.isValid()) { QScriptValue red = color.property("red"); QScriptValue green = color.property("green"); QScriptValue blue = color.property("blue"); if (red.isValid() && green.isValid() && blue.isValid()) { xColor newColor; newColor.red = red.toVariant().toInt(); newColor.green = green.toVariant().toInt(); newColor.blue = blue.toVariant().toInt(); if (_defaultSettings || (newColor.red != _color.red || newColor.green != _color.green || newColor.blue != _color.blue)) { _color = newColor; _colorChanged = true; } } } QScriptValue modelURL = object.property("modelURL"); if (modelURL.isValid()) { QString newModelURL; newModelURL = modelURL.toVariant().toString(); if (_defaultSettings || newModelURL != _modelURL) { _modelURL = newModelURL; _modelURLChanged = true; } } QScriptValue animationURL = object.property("animationURL"); if (animationURL.isValid()) { QString newAnimationURL; newAnimationURL = animationURL.toVariant().toString(); if (_defaultSettings || newAnimationURL != _animationURL) { _animationURL = newAnimationURL; _animationURLChanged = true; } } QScriptValue animationIsPlaying = object.property("animationIsPlaying"); if (animationIsPlaying.isValid()) { bool newIsAnimationPlaying; newIsAnimationPlaying = animationIsPlaying.toVariant().toBool(); if (_defaultSettings || newIsAnimationPlaying != _animationIsPlaying) { _animationIsPlaying = newIsAnimationPlaying; _animationIsPlayingChanged = true; } } QScriptValue animationFrameIndex = object.property("animationFrameIndex"); if (animationFrameIndex.isValid()) { float newFrameIndex; newFrameIndex = animationFrameIndex.toVariant().toFloat(); if (_defaultSettings || newFrameIndex != _animationFrameIndex) { _animationFrameIndex = newFrameIndex; _animationFrameIndexChanged = true; } } QScriptValue animationFPS = object.property("animationFPS"); if (animationFPS.isValid()) { float newFPS; newFPS = animationFPS.toVariant().toFloat(); if (_defaultSettings || newFPS != _animationFPS) { _animationFPS = newFPS; _animationFPSChanged = true; } } QScriptValue glowLevel = object.property("glowLevel"); if (glowLevel.isValid()) { float newGlowLevel; newGlowLevel = glowLevel.toVariant().toFloat(); if (_defaultSettings || newGlowLevel != _glowLevel) { _glowLevel = newGlowLevel; _glowLevelChanged = true; } } #endif _lastEdited = usecTimestampNow(); } QScriptValue EntityItemPropertiesToScriptValue(QScriptEngine* engine, const EntityItemProperties& properties) { return properties.copyToScriptValue(engine); } void EntityItemPropertiesFromScriptValue(const QScriptValue &object, EntityItemProperties& properties) { properties.copyFromScriptValue(object); }