diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index c56d255201..50517b706c 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -227,7 +227,7 @@ void MyAvatar::simulateAttachments(float deltaTime) { // don't update attachments here, do it in harvestResultsFromPhysicsSimulation() } -QByteArray MyAvatar::toByteArray(bool cullSmallChanges, bool sendAll) { +QByteArray MyAvatar::toByteArray(bool cullSmallChanges, bool sendAll, bool sendMinimum, bool compressed) { CameraMode mode = qApp->getCamera()->getMode(); _globalPosition = getPosition(); _globalBoundingBoxCorner.x = _characterController.getCapsuleRadius(); @@ -238,12 +238,12 @@ QByteArray MyAvatar::toByteArray(bool cullSmallChanges, bool sendAll) { // fake the avatar position that is sent up to the AvatarMixer glm::vec3 oldPosition = getPosition(); setPosition(getSkeletonPosition()); - QByteArray array = AvatarData::toByteArray(cullSmallChanges, sendAll); + QByteArray array = AvatarData::toByteArray(cullSmallChanges, sendAll, sendMinimum, compressed); // copy the correct position back setPosition(oldPosition); return array; } - return AvatarData::toByteArray(cullSmallChanges, sendAll); + return AvatarData::toByteArray(cullSmallChanges, sendAll, sendMinimum, compressed); } void MyAvatar::centerBody() { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index f081ec533b..609e93f616 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -333,7 +333,7 @@ private: glm::vec3 getWorldBodyPosition() const; glm::quat getWorldBodyOrientation() const; - QByteArray toByteArray(bool cullSmallChanges, bool sendAll) override; + QByteArray toByteArray(bool cullSmallChanges, bool sendAll, bool sendMinimum, bool compressed) override; void simulate(float deltaTime); void updateFromTrackers(float deltaTime); virtual void render(RenderArgs* renderArgs, const glm::vec3& cameraPositio) override; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index f81a1169af..ac378276fd 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -181,7 +181,7 @@ void AvatarData::setHandPosition(const glm::vec3& handPosition) { _handPosition = glm::inverse(getOrientation()) * (handPosition - getPosition()); } -QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { +QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll, bool sendMinimum, bool compressed) { // TODO: DRY this up to a shared method // that can pack any type given the number of bytes // and return the number of bytes to push the pointer @@ -199,208 +199,242 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { unsigned char* destinationBuffer = reinterpret_cast(avatarDataByteArray.data()); unsigned char* startPosition = destinationBuffer; - auto header = reinterpret_cast(destinationBuffer); - header->position[0] = getLocalPosition().x; - header->position[1] = getLocalPosition().y; - header->position[2] = getLocalPosition().z; - header->globalPosition[0] = _globalPosition.x; - header->globalPosition[1] = _globalPosition.y; - header->globalPosition[2] = _globalPosition.z; - header->globalBoundingBoxCorner[0] = getPosition().x - _globalBoundingBoxCorner.x; - header->globalBoundingBoxCorner[1] = getPosition().y - _globalBoundingBoxCorner.y; - header->globalBoundingBoxCorner[2] = getPosition().z - _globalBoundingBoxCorner.z; - - glm::vec3 bodyEulerAngles = glm::degrees(safeEulerAngles(getLocalOrientation())); - packFloatAngleToTwoByte((uint8_t*)(header->localOrientation + 0), bodyEulerAngles.y); - packFloatAngleToTwoByte((uint8_t*)(header->localOrientation + 1), bodyEulerAngles.x); - packFloatAngleToTwoByte((uint8_t*)(header->localOrientation + 2), bodyEulerAngles.z); - packFloatRatioToTwoByte((uint8_t*)(&header->scale), getDomainLimitedScale()); - header->lookAtPosition[0] = _headData->_lookAtPosition.x; - header->lookAtPosition[1] = _headData->_lookAtPosition.y; - header->lookAtPosition[2] = _headData->_lookAtPosition.z; - header->audioLoudness = _headData->_audioLoudness; - - glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix(); - packOrientationQuatToSixBytes(header->sensorToWorldQuat, glmExtractRotation(sensorToWorldMatrix)); - glm::vec3 scale = extractScale(sensorToWorldMatrix); - packFloatScalarToSignedTwoByteFixed((uint8_t*)&header->sensorToWorldScale, scale.x, SENSOR_TO_WORLD_SCALE_RADIX); - header->sensorToWorldTrans[0] = sensorToWorldMatrix[3][0]; - header->sensorToWorldTrans[1] = sensorToWorldMatrix[3][1]; - header->sensorToWorldTrans[2] = sensorToWorldMatrix[3][2]; - - setSemiNibbleAt(header->flags, KEY_STATE_START_BIT, _keyState); - // hand state - bool isFingerPointing = _handState & IS_FINGER_POINTING_FLAG; - setSemiNibbleAt(header->flags, HAND_STATE_START_BIT, _handState & ~IS_FINGER_POINTING_FLAG); - if (isFingerPointing) { - setAtBit(header->flags, HAND_STATE_FINGER_POINTING_BIT); - } - // faceshift state - if (_headData->_isFaceTrackerConnected) { - setAtBit(header->flags, IS_FACESHIFT_CONNECTED); - } - // eye tracker state - if (_headData->_isEyeTrackerConnected) { - setAtBit(header->flags, IS_EYE_TRACKER_CONNECTED); - } - // referential state - QUuid parentID = getParentID(); - if (!parentID.isNull()) { - setAtBit(header->flags, HAS_REFERENTIAL); - } - destinationBuffer += sizeof(AvatarDataPacket::Header); - - if (!parentID.isNull()) { - auto parentInfo = reinterpret_cast(destinationBuffer); - QByteArray referentialAsBytes = parentID.toRfc4122(); - memcpy(parentInfo->parentUUID, referentialAsBytes.data(), referentialAsBytes.size()); - parentInfo->parentJointIndex = _parentJointIndex; - destinationBuffer += sizeof(AvatarDataPacket::ParentInfo); + // Leading flags, to indicate how much data is actually included in the packet... + UINT8 packetStateFlags = 0; + if (sendMinimum) { + setAtBit(packetStateFlags, AVATARDATA_FLAGS_MINIMUM); } - // If it is connected, pack up the data - if (_headData->_isFaceTrackerConnected) { - auto faceTrackerInfo = reinterpret_cast(destinationBuffer); - - faceTrackerInfo->leftEyeBlink = _headData->_leftEyeBlink; - faceTrackerInfo->rightEyeBlink = _headData->_rightEyeBlink; - faceTrackerInfo->averageLoudness = _headData->_averageLoudness; - faceTrackerInfo->browAudioLift = _headData->_browAudioLift; - faceTrackerInfo->numBlendshapeCoefficients = _headData->_blendshapeCoefficients.size(); - destinationBuffer += sizeof(AvatarDataPacket::FaceTrackerInfo); - - // followed by a variable number of float coefficients - memcpy(destinationBuffer, _headData->_blendshapeCoefficients.data(), _headData->_blendshapeCoefficients.size() * sizeof(float)); - destinationBuffer += _headData->_blendshapeCoefficients.size() * sizeof(float); + // do we need this, or do we just always compress it? + if (compressed) { + setAtBit(packetStateFlags, AVATARDATA_FLAGS_COMPRESSED); } + memcpy(destinationBuffer, &packetStateFlags, sizeof(packetStateFlags)); + destinationBuffer += sizeof(packetStateFlags); - QReadLocker readLock(&_jointDataLock); + if (!sendMinimum) { + auto header = reinterpret_cast(destinationBuffer); + header->position[0] = getLocalPosition().x; + header->position[1] = getLocalPosition().y; + header->position[2] = getLocalPosition().z; + header->globalPosition[0] = _globalPosition.x; + header->globalPosition[1] = _globalPosition.y; + header->globalPosition[2] = _globalPosition.z; + header->globalBoundingBoxCorner[0] = getPosition().x - _globalBoundingBoxCorner.x; + header->globalBoundingBoxCorner[1] = getPosition().y - _globalBoundingBoxCorner.y; + header->globalBoundingBoxCorner[2] = getPosition().z - _globalBoundingBoxCorner.z; - // joint rotation data - *destinationBuffer++ = _jointData.size(); - unsigned char* validityPosition = destinationBuffer; - unsigned char validity = 0; - int validityBit = 0; + glm::vec3 bodyEulerAngles = glm::degrees(safeEulerAngles(getLocalOrientation())); + packFloatAngleToTwoByte((uint8_t*)(header->localOrientation + 0), bodyEulerAngles.y); + packFloatAngleToTwoByte((uint8_t*)(header->localOrientation + 1), bodyEulerAngles.x); + packFloatAngleToTwoByte((uint8_t*)(header->localOrientation + 2), bodyEulerAngles.z); + packFloatRatioToTwoByte((uint8_t*)(&header->scale), getDomainLimitedScale()); + header->lookAtPosition[0] = _headData->_lookAtPosition.x; + header->lookAtPosition[1] = _headData->_lookAtPosition.y; + header->lookAtPosition[2] = _headData->_lookAtPosition.z; + header->audioLoudness = _headData->_audioLoudness; - #ifdef WANT_DEBUG - int rotationSentCount = 0; - unsigned char* beforeRotations = destinationBuffer; - #endif + glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix(); + packOrientationQuatToSixBytes(header->sensorToWorldQuat, glmExtractRotation(sensorToWorldMatrix)); + glm::vec3 scale = extractScale(sensorToWorldMatrix); + packFloatScalarToSignedTwoByteFixed((uint8_t*)&header->sensorToWorldScale, scale.x, SENSOR_TO_WORLD_SCALE_RADIX); + header->sensorToWorldTrans[0] = sensorToWorldMatrix[3][0]; + header->sensorToWorldTrans[1] = sensorToWorldMatrix[3][1]; + header->sensorToWorldTrans[2] = sensorToWorldMatrix[3][2]; - _lastSentJointData.resize(_jointData.size()); + setSemiNibbleAt(header->flags, KEY_STATE_START_BIT, _keyState); + // hand state + bool isFingerPointing = _handState & IS_FINGER_POINTING_FLAG; + setSemiNibbleAt(header->flags, HAND_STATE_START_BIT, _handState & ~IS_FINGER_POINTING_FLAG); + if (isFingerPointing) { + setAtBit(header->flags, HAND_STATE_FINGER_POINTING_BIT); + } + // faceshift state + if (_headData->_isFaceTrackerConnected) { + setAtBit(header->flags, IS_FACESHIFT_CONNECTED); + } + // eye tracker state + if (_headData->_isEyeTrackerConnected) { + setAtBit(header->flags, IS_EYE_TRACKER_CONNECTED); + } + // referential state + QUuid parentID = getParentID(); + if (!parentID.isNull()) { + setAtBit(header->flags, HAS_REFERENTIAL); + } + destinationBuffer += sizeof(AvatarDataPacket::Header); - for (int i=0; i < _jointData.size(); i++) { - const JointData& data = _jointData[i]; - if (sendAll || _lastSentJointData[i].rotation != data.rotation) { - if (sendAll || - !cullSmallChanges || - fabsf(glm::dot(data.rotation, _lastSentJointData[i].rotation)) <= AVATAR_MIN_ROTATION_DOT) { - if (data.rotationSet) { - validity |= (1 << validityBit); - #ifdef WANT_DEBUG - rotationSentCount++; - #endif + if (!parentID.isNull()) { + auto parentInfo = reinterpret_cast(destinationBuffer); + QByteArray referentialAsBytes = parentID.toRfc4122(); + memcpy(parentInfo->parentUUID, referentialAsBytes.data(), referentialAsBytes.size()); + parentInfo->parentJointIndex = _parentJointIndex; + destinationBuffer += sizeof(AvatarDataPacket::ParentInfo); + } + + // If it is connected, pack up the data + if (_headData->_isFaceTrackerConnected) { + auto faceTrackerInfo = reinterpret_cast(destinationBuffer); + + faceTrackerInfo->leftEyeBlink = _headData->_leftEyeBlink; + faceTrackerInfo->rightEyeBlink = _headData->_rightEyeBlink; + faceTrackerInfo->averageLoudness = _headData->_averageLoudness; + faceTrackerInfo->browAudioLift = _headData->_browAudioLift; + faceTrackerInfo->numBlendshapeCoefficients = _headData->_blendshapeCoefficients.size(); + destinationBuffer += sizeof(AvatarDataPacket::FaceTrackerInfo); + + // followed by a variable number of float coefficients + memcpy(destinationBuffer, _headData->_blendshapeCoefficients.data(), _headData->_blendshapeCoefficients.size() * sizeof(float)); + destinationBuffer += _headData->_blendshapeCoefficients.size() * sizeof(float); + } + + QReadLocker readLock(&_jointDataLock); + + // joint rotation data + *destinationBuffer++ = _jointData.size(); + unsigned char* validityPosition = destinationBuffer; + unsigned char validity = 0; + int validityBit = 0; + + #ifdef WANT_DEBUG + int rotationSentCount = 0; + unsigned char* beforeRotations = destinationBuffer; + #endif + + _lastSentJointData.resize(_jointData.size()); + + for (int i=0; i < _jointData.size(); i++) { + const JointData& data = _jointData[i]; + if (sendAll || _lastSentJointData[i].rotation != data.rotation) { + if (sendAll || + !cullSmallChanges || + fabsf(glm::dot(data.rotation, _lastSentJointData[i].rotation)) <= AVATAR_MIN_ROTATION_DOT) { + if (data.rotationSet) { + validity |= (1 << validityBit); + #ifdef WANT_DEBUG + rotationSentCount++; + #endif + } } } - } - if (++validityBit == BITS_IN_BYTE) { - *destinationBuffer++ = validity; - validityBit = validity = 0; - } - } - if (validityBit != 0) { - *destinationBuffer++ = validity; - } - - validityBit = 0; - validity = *validityPosition++; - for (int i = 0; i < _jointData.size(); i ++) { - const JointData& data = _jointData[i]; - if (validity & (1 << validityBit)) { - destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, data.rotation); - } - if (++validityBit == BITS_IN_BYTE) { - validityBit = 0; - validity = *validityPosition++; - } - } - - - // joint translation data - validityPosition = destinationBuffer; - validity = 0; - validityBit = 0; - - #ifdef WANT_DEBUG - int translationSentCount = 0; - unsigned char* beforeTranslations = destinationBuffer; - #endif - - float maxTranslationDimension = 0.0; - for (int i=0; i < _jointData.size(); i++) { - const JointData& data = _jointData[i]; - if (sendAll || _lastSentJointData[i].translation != data.translation) { - if (sendAll || - !cullSmallChanges || - glm::distance(data.translation, _lastSentJointData[i].translation) > AVATAR_MIN_TRANSLATION) { - if (data.translationSet) { - validity |= (1 << validityBit); - #ifdef WANT_DEBUG - translationSentCount++; - #endif - maxTranslationDimension = glm::max(fabsf(data.translation.x), maxTranslationDimension); - maxTranslationDimension = glm::max(fabsf(data.translation.y), maxTranslationDimension); - maxTranslationDimension = glm::max(fabsf(data.translation.z), maxTranslationDimension); - } + if (++validityBit == BITS_IN_BYTE) { + *destinationBuffer++ = validity; + validityBit = validity = 0; } } - if (++validityBit == BITS_IN_BYTE) { + if (validityBit != 0) { *destinationBuffer++ = validity; - validityBit = validity = 0; } - } - if (validityBit != 0) { - *destinationBuffer++ = validity; - } - - validityBit = 0; - validity = *validityPosition++; - for (int i = 0; i < _jointData.size(); i ++) { - const JointData& data = _jointData[i]; - if (validity & (1 << validityBit)) { - destinationBuffer += - packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX); + validityBit = 0; + validity = *validityPosition++; + for (int i = 0; i < _jointData.size(); i ++) { + const JointData& data = _jointData[i]; + if (validity & (1 << validityBit)) { + destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, data.rotation); + } + if (++validityBit == BITS_IN_BYTE) { + validityBit = 0; + validity = *validityPosition++; + } } - if (++validityBit == BITS_IN_BYTE) { - validityBit = 0; - validity = *validityPosition++; + + + // joint translation data + validityPosition = destinationBuffer; + validity = 0; + validityBit = 0; + + #ifdef WANT_DEBUG + int translationSentCount = 0; + unsigned char* beforeTranslations = destinationBuffer; + #endif + + float maxTranslationDimension = 0.0; + for (int i=0; i < _jointData.size(); i++) { + const JointData& data = _jointData[i]; + if (sendAll || _lastSentJointData[i].translation != data.translation) { + if (sendAll || + !cullSmallChanges || + glm::distance(data.translation, _lastSentJointData[i].translation) > AVATAR_MIN_TRANSLATION) { + if (data.translationSet) { + validity |= (1 << validityBit); + #ifdef WANT_DEBUG + translationSentCount++; + #endif + maxTranslationDimension = glm::max(fabsf(data.translation.x), maxTranslationDimension); + maxTranslationDimension = glm::max(fabsf(data.translation.y), maxTranslationDimension); + maxTranslationDimension = glm::max(fabsf(data.translation.z), maxTranslationDimension); + } + } + } + if (++validityBit == BITS_IN_BYTE) { + *destinationBuffer++ = validity; + validityBit = validity = 0; + } } + + if (validityBit != 0) { + *destinationBuffer++ = validity; + } + + validityBit = 0; + validity = *validityPosition++; + for (int i = 0; i < _jointData.size(); i ++) { + const JointData& data = _jointData[i]; + if (validity & (1 << validityBit)) { + destinationBuffer += + packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX); + } + if (++validityBit == BITS_IN_BYTE) { + validityBit = 0; + validity = *validityPosition++; + } + } + + // faux joints + Transform controllerLeftHandTransform = Transform(getControllerLeftHandMatrix()); + destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, controllerLeftHandTransform.getRotation()); + destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerLeftHandTransform.getTranslation(), + TRANSLATION_COMPRESSION_RADIX); + Transform controllerRightHandTransform = Transform(getControllerRightHandMatrix()); + destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, controllerRightHandTransform.getRotation()); + destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerRightHandTransform.getTranslation(), + TRANSLATION_COMPRESSION_RADIX); + + #ifdef WANT_DEBUG + if (sendAll) { + qDebug() << "AvatarData::toByteArray" << cullSmallChanges << sendAll + << "rotations:" << rotationSentCount << "translations:" << translationSentCount + << "largest:" << maxTranslationDimension + << "size:" + << (beforeRotations - startPosition) << "+" + << (beforeTranslations - beforeRotations) << "+" + << (destinationBuffer - beforeTranslations) << "=" + << (destinationBuffer - startPosition); + } + #endif } - // faux joints - Transform controllerLeftHandTransform = Transform(getControllerLeftHandMatrix()); - destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, controllerLeftHandTransform.getRotation()); - destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerLeftHandTransform.getTranslation(), - TRANSLATION_COMPRESSION_RADIX); - Transform controllerRightHandTransform = Transform(getControllerRightHandMatrix()); - destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, controllerRightHandTransform.getRotation()); - destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerRightHandTransform.getTranslation(), - TRANSLATION_COMPRESSION_RADIX); + // NOTE: first byte of array should always be uncompressed - #ifdef WANT_DEBUG - if (sendAll) { - qDebug() << "AvatarData::toByteArray" << cullSmallChanges << sendAll - << "rotations:" << rotationSentCount << "translations:" << translationSentCount - << "largest:" << maxTranslationDimension - << "size:" - << (beforeRotations - startPosition) << "+" - << (beforeTranslations - beforeRotations) << "+" - << (destinationBuffer - beforeTranslations) << "=" - << (destinationBuffer - startPosition); + if (compressed) { + static const int SKIP_PACKET_FLAGS = 1; + static const int COMPRESSION_LEVEL = 9; + QByteArray uncompressedPortion = avatarDataByteArray.mid(SKIP_PACKET_FLAGS, + (destinationBuffer - startPosition) - SKIP_PACKET_FLAGS); + + QByteArray compressedPortion = qCompress(uncompressedPortion, COMPRESSION_LEVEL); + QByteArray flagsAndCompressed; + flagsAndCompressed.append(packetStateFlags); + flagsAndCompressed.append(compressedPortion); + + //qDebug() << __FUNCTION__ << "compressing data was:" << (uncompressedPortion.size() + SKIP_PACKET_FLAGS) << "now:" << flagsAndCompressed.size(); + + return flagsAndCompressed; } - #endif + // entire buffer is uncompressed return avatarDataByteArray.left(destinationBuffer - startPosition); } @@ -474,9 +508,30 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { _headData = new HeadData(this); } + UINT8 packetStateFlags = buffer.at(0); + bool minimumSent = oneAtBit(packetStateFlags, AVATARDATA_FLAGS_MINIMUM); + bool packetIsCompressed = oneAtBit(packetStateFlags, AVATARDATA_FLAGS_COMPRESSED); + + // if this is the minimum, then it only includes the flags + if (minimumSent) { + int numBytesRead = sizeof(packetStateFlags); + _averageBytesReceived.updateAverage(numBytesRead); + return numBytesRead; + } + + QByteArray uncompressBuffer; const unsigned char* startPosition = reinterpret_cast(buffer.data()); const unsigned char* endPosition = startPosition + buffer.size(); const unsigned char* sourceBuffer = startPosition; + + if (packetIsCompressed) { + uncompressBuffer = qUncompress(buffer.right(buffer.size() - sizeof(packetStateFlags))); + startPosition = reinterpret_cast(uncompressBuffer.data()); + endPosition = startPosition + uncompressBuffer.size(); + sourceBuffer = startPosition; + //qDebug() << __FUNCTION__ << "uncompressing compressed data was:" << buffer.size() << "now:" << uncompressBuffer.size(); + } + quint64 now = usecTimestampNow(); PACKET_READ_CHECK(Header, sizeof(AvatarDataPacket::Header)); @@ -679,8 +734,8 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { #ifdef WANT_DEBUG if (numValidJointRotations > 15) { qDebug() << "RECEIVING -- rotations:" << numValidJointRotations - << "translations:" << numValidJointTranslations - << "size:" << (int)(sourceBuffer - startPosition); + << "translations:" << numValidJointTranslations + << "size:" << (int)(sourceBuffer - startPosition); } #endif diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index b34b912f14..49f9336aeb 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -106,6 +106,13 @@ const char LEFT_HAND_POINTING_FLAG = 1; const char RIGHT_HAND_POINTING_FLAG = 2; const char IS_FINGER_POINTING_FLAG = 4; +// AvatarData state flags - we store the details about the packet encoding in the first byte, +// before the "header" structure +const char AVATARDATA_FLAGS_MINIMUM = 0; +const char AVATARDATA_FLAGS_COMPRESSED = 1; + + + static const float MAX_AVATAR_SCALE = 1000.0f; static const float MIN_AVATAR_SCALE = .005f; @@ -201,7 +208,7 @@ public: glm::vec3 getHandPosition() const; void setHandPosition(const glm::vec3& handPosition); - virtual QByteArray toByteArray(bool cullSmallChanges, bool sendAll); + virtual QByteArray toByteArray(bool cullSmallChanges, bool sendAll, bool sendMinimum = false, bool compressed = true); virtual void doneEncoding(bool cullSmallChanges); /// \return true if an error should be logged