mirror of
https://github.com/overte-org/overte.git
synced 2025-07-23 11:44:09 +02:00
Merge branch 'master' of github.com:highfidelity/hifi into tablet-ui
This commit is contained in:
commit
fa61bbe2a6
31 changed files with 1431 additions and 383 deletions
|
@ -502,8 +502,8 @@ void Agent::processAgentAvatar() {
|
||||||
if (!_scriptEngine->isFinished() && _isAvatar) {
|
if (!_scriptEngine->isFinished() && _isAvatar) {
|
||||||
auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
|
auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
|
||||||
|
|
||||||
QByteArray avatarByteArray = scriptedAvatar->toByteArray((randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO)
|
AvatarData::AvatarDataDetail dataDetail = (randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO) ? AvatarData::SendAllData : AvatarData::CullSmallData;
|
||||||
? AvatarData::SendAllData : AvatarData::CullSmallData);
|
QByteArray avatarByteArray = scriptedAvatar->toByteArray(dataDetail, 0, scriptedAvatar->getLastSentJointData());
|
||||||
scriptedAvatar->doneEncoding(true);
|
scriptedAvatar->doneEncoding(true);
|
||||||
|
|
||||||
static AvatarDataSequenceNumber sequenceNumber = 0;
|
static AvatarDataSequenceNumber sequenceNumber = 0;
|
||||||
|
|
|
@ -35,7 +35,9 @@
|
||||||
static const uint8_t MIN_CORES_FOR_MULTICORE = 4;
|
static const uint8_t MIN_CORES_FOR_MULTICORE = 4;
|
||||||
static const uint8_t CPU_AFFINITY_COUNT_HIGH = 2;
|
static const uint8_t CPU_AFFINITY_COUNT_HIGH = 2;
|
||||||
static const uint8_t CPU_AFFINITY_COUNT_LOW = 1;
|
static const uint8_t CPU_AFFINITY_COUNT_LOW = 1;
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
static const int INTERFACE_RUNNING_CHECK_FREQUENCY_MS = 1000;
|
static const int INTERFACE_RUNNING_CHECK_FREQUENCY_MS = 1000;
|
||||||
|
#endif
|
||||||
|
|
||||||
const QString ASSET_SERVER_LOGGING_TARGET_NAME = "asset-server";
|
const QString ASSET_SERVER_LOGGING_TARGET_NAME = "asset-server";
|
||||||
|
|
||||||
|
|
|
@ -423,12 +423,17 @@ void AvatarMixer::broadcastAvatarData() {
|
||||||
nodeData->incrementAvatarOutOfView();
|
nodeData->incrementAvatarOutOfView();
|
||||||
} else {
|
} else {
|
||||||
detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO
|
detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO
|
||||||
? AvatarData::SendAllData : AvatarData::IncludeSmallData;
|
? AvatarData::SendAllData : AvatarData::CullSmallData;
|
||||||
nodeData->incrementAvatarInView();
|
nodeData->incrementAvatarInView();
|
||||||
}
|
}
|
||||||
|
|
||||||
numAvatarDataBytes += avatarPacketList->write(otherNode->getUUID().toRfc4122());
|
numAvatarDataBytes += avatarPacketList->write(otherNode->getUUID().toRfc4122());
|
||||||
numAvatarDataBytes += avatarPacketList->write(otherAvatar.toByteArray(detail));
|
auto lastEncodeForOther = nodeData->getLastOtherAvatarEncodeTime(otherNode->getUUID());
|
||||||
|
QVector<JointData>& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getUUID());
|
||||||
|
bool distanceAdjust = true;
|
||||||
|
glm::vec3 viewerPosition = nodeData->getPosition();
|
||||||
|
auto bytes = otherAvatar.toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, distanceAdjust, viewerPosition, &lastSentJointsForOther);
|
||||||
|
numAvatarDataBytes += avatarPacketList->write(bytes);
|
||||||
|
|
||||||
avatarPacketList->endSegment();
|
avatarPacketList->endSegment();
|
||||||
});
|
});
|
||||||
|
|
|
@ -104,6 +104,22 @@ public:
|
||||||
bool getRequestsDomainListData() { return _requestsDomainListData; }
|
bool getRequestsDomainListData() { return _requestsDomainListData; }
|
||||||
void setRequestsDomainListData(bool requesting) { _requestsDomainListData = requesting; }
|
void setRequestsDomainListData(bool requesting) { _requestsDomainListData = requesting; }
|
||||||
|
|
||||||
|
quint64 getLastOtherAvatarEncodeTime(QUuid otherAvatar) {
|
||||||
|
quint64 result = 0;
|
||||||
|
if (_lastOtherAvatarEncodeTime.find(otherAvatar) != _lastOtherAvatarEncodeTime.end()) {
|
||||||
|
result = _lastOtherAvatarEncodeTime[otherAvatar];
|
||||||
|
}
|
||||||
|
_lastOtherAvatarEncodeTime[otherAvatar] = usecTimestampNow();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector<JointData>& getLastOtherAvatarSentJoints(QUuid otherAvatar) {
|
||||||
|
_lastOtherAvatarSentJoints[otherAvatar].resize(_avatar->getJointCount());
|
||||||
|
return _lastOtherAvatarSentJoints[otherAvatar];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AvatarSharedPointer _avatar { new AvatarData() };
|
AvatarSharedPointer _avatar { new AvatarData() };
|
||||||
|
|
||||||
|
@ -111,6 +127,11 @@ private:
|
||||||
std::unordered_map<QUuid, uint16_t> _lastBroadcastSequenceNumbers;
|
std::unordered_map<QUuid, uint16_t> _lastBroadcastSequenceNumbers;
|
||||||
std::unordered_set<QUuid> _hasReceivedFirstPacketsFrom;
|
std::unordered_set<QUuid> _hasReceivedFirstPacketsFrom;
|
||||||
|
|
||||||
|
// this is a map of the last time we encoded an "other" avatar for
|
||||||
|
// sending to "this" node
|
||||||
|
std::unordered_map<QUuid, quint64> _lastOtherAvatarEncodeTime;
|
||||||
|
std::unordered_map<QUuid, QVector<JointData>> _lastOtherAvatarSentJoints;
|
||||||
|
|
||||||
HRCTime _identityChangeTimestamp;
|
HRCTime _identityChangeTimestamp;
|
||||||
bool _avatarSessionDisplayNameMustChange{ false };
|
bool _avatarSessionDisplayNameMustChange{ false };
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,13 @@
|
||||||
#include <GLMHelpers.h>
|
#include <GLMHelpers.h>
|
||||||
#include "ScriptableAvatar.h"
|
#include "ScriptableAvatar.h"
|
||||||
|
|
||||||
|
QByteArray ScriptableAvatar::toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector<JointData>& lastSentJointData,
|
||||||
|
bool distanceAdjust, glm::vec3 viewerPosition, QVector<JointData>* sentJointDataOut) {
|
||||||
|
_globalPosition = getPosition();
|
||||||
|
return AvatarData::toByteArray(dataDetail, lastSentTime, lastSentJointData, distanceAdjust, viewerPosition, sentJointDataOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// hold and priority unused but kept so that client side JS can run.
|
// hold and priority unused but kept so that client side JS can run.
|
||||||
void ScriptableAvatar::startAnimation(const QString& url, float fps, float priority,
|
void ScriptableAvatar::startAnimation(const QString& url, float fps, float priority,
|
||||||
bool loop, bool hold, float firstFrame, float lastFrame, const QStringList& maskedJoints) {
|
bool loop, bool hold, float firstFrame, float lastFrame, const QStringList& maskedJoints) {
|
||||||
|
|
|
@ -28,6 +28,10 @@ public:
|
||||||
Q_INVOKABLE AnimationDetails getAnimationDetails();
|
Q_INVOKABLE AnimationDetails getAnimationDetails();
|
||||||
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override;
|
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override;
|
||||||
|
|
||||||
|
virtual QByteArray toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector<JointData>& lastSentJointData,
|
||||||
|
bool distanceAdjust = false, glm::vec3 viewerPosition = glm::vec3(0), QVector<JointData>* sentJointDataOut = nullptr) override;
|
||||||
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void update(float deltatime);
|
void update(float deltatime);
|
||||||
|
|
||||||
|
|
|
@ -9,9 +9,12 @@
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#include <QtCore/QEventLoop>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <EntityTree.h>
|
#include <EntityTree.h>
|
||||||
#include <SimpleEntitySimulation.h>
|
#include <SimpleEntitySimulation.h>
|
||||||
|
#include <ResourceCache.h>
|
||||||
|
#include <ScriptCache.h>
|
||||||
|
|
||||||
#include "EntityServer.h"
|
#include "EntityServer.h"
|
||||||
#include "EntityServerConsts.h"
|
#include "EntityServerConsts.h"
|
||||||
|
@ -26,6 +29,10 @@ EntityServer::EntityServer(ReceivedMessage& message) :
|
||||||
OctreeServer(message),
|
OctreeServer(message),
|
||||||
_entitySimulation(NULL)
|
_entitySimulation(NULL)
|
||||||
{
|
{
|
||||||
|
ResourceManager::init();
|
||||||
|
DependencyManager::set<ResourceCacheSharedItems>();
|
||||||
|
DependencyManager::set<ScriptCache>();
|
||||||
|
|
||||||
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
||||||
packetReceiver.registerListenerForTypes({ PacketType::EntityAdd, PacketType::EntityEdit, PacketType::EntityErase },
|
packetReceiver.registerListenerForTypes({ PacketType::EntityAdd, PacketType::EntityEdit, PacketType::EntityErase },
|
||||||
this, "handleEntityPacket");
|
this, "handleEntityPacket");
|
||||||
|
@ -285,6 +292,97 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio
|
||||||
} else {
|
} else {
|
||||||
tree->setEntityScriptSourceWhitelist("");
|
tree->setEntityScriptSourceWhitelist("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (readOptionString("entityEditFilter", settingsSectionObject, _entityEditFilter) && !_entityEditFilter.isEmpty()) {
|
||||||
|
// Fetch script from file synchronously. We don't want the server processing edits while a restarting entity server is fetching from a DOS'd source.
|
||||||
|
QUrl scriptURL(_entityEditFilter);
|
||||||
|
|
||||||
|
// The following should be abstracted out for use in Agent.cpp (and maybe later AvatarMixer.cpp)
|
||||||
|
if (scriptURL.scheme().isEmpty() || (scriptURL.scheme() == URL_SCHEME_FILE)) {
|
||||||
|
qWarning() << "Cannot load script from local filesystem, because assignment may be on a different computer.";
|
||||||
|
scriptRequestFinished();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto scriptRequest = ResourceManager::createResourceRequest(this, scriptURL);
|
||||||
|
if (!scriptRequest) {
|
||||||
|
qWarning() << "Could not create ResourceRequest for Agent script at" << scriptURL.toString();
|
||||||
|
scriptRequestFinished();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Agent.cpp sets up a timeout here, but that is unnecessary, as ResourceRequest has its own.
|
||||||
|
connect(scriptRequest, &ResourceRequest::finished, this, &EntityServer::scriptRequestFinished);
|
||||||
|
// FIXME: handle atp rquests setup here. See Agent::requestScript()
|
||||||
|
qInfo() << "Requesting script at URL" << qPrintable(scriptRequest->getUrl().toString());
|
||||||
|
scriptRequest->send();
|
||||||
|
_scriptRequestLoop.exec(); // Block here, but allow the request to be processed and its signals to be handled.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copied from ScriptEngine.cpp. We should make this a class method for reuse.
|
||||||
|
// Note: I've deliberately stopped short of using ScriptEngine instead of QScriptEngine, as that is out of project scope at this point.
|
||||||
|
static bool hasCorrectSyntax(const QScriptProgram& program) {
|
||||||
|
const auto syntaxCheck = QScriptEngine::checkSyntax(program.sourceCode());
|
||||||
|
if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) {
|
||||||
|
const auto error = syntaxCheck.errorMessage();
|
||||||
|
const auto line = QString::number(syntaxCheck.errorLineNumber());
|
||||||
|
const auto column = QString::number(syntaxCheck.errorColumnNumber());
|
||||||
|
const auto message = QString("[SyntaxError] %1 in %2:%3(%4)").arg(error, program.fileName(), line, column);
|
||||||
|
qCritical() << qPrintable(message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
static bool hadUncaughtExceptions(QScriptEngine& engine, const QString& fileName) {
|
||||||
|
if (engine.hasUncaughtException()) {
|
||||||
|
const auto backtrace = engine.uncaughtExceptionBacktrace();
|
||||||
|
const auto exception = engine.uncaughtException().toString();
|
||||||
|
const auto line = QString::number(engine.uncaughtExceptionLineNumber());
|
||||||
|
engine.clearExceptions();
|
||||||
|
|
||||||
|
static const QString SCRIPT_EXCEPTION_FORMAT = "[UncaughtException] %1 in %2:%3";
|
||||||
|
auto message = QString(SCRIPT_EXCEPTION_FORMAT).arg(exception, fileName, line);
|
||||||
|
if (!backtrace.empty()) {
|
||||||
|
static const auto lineSeparator = "\n ";
|
||||||
|
message += QString("\n[Backtrace]%1%2").arg(lineSeparator, backtrace.join(lineSeparator));
|
||||||
|
}
|
||||||
|
qCritical() << qPrintable(message);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
void EntityServer::scriptRequestFinished() {
|
||||||
|
auto scriptRequest = qobject_cast<ResourceRequest*>(sender());
|
||||||
|
const QString urlString = scriptRequest->getUrl().toString();
|
||||||
|
if (scriptRequest && scriptRequest->getResult() == ResourceRequest::Success) {
|
||||||
|
auto scriptContents = scriptRequest->getData();
|
||||||
|
qInfo() << "Downloaded script:" << scriptContents;
|
||||||
|
QScriptProgram program(scriptContents, urlString);
|
||||||
|
if (hasCorrectSyntax(program)) {
|
||||||
|
_entityEditFilterEngine.evaluate(scriptContents);
|
||||||
|
if (!hadUncaughtExceptions(_entityEditFilterEngine, urlString)) {
|
||||||
|
std::static_pointer_cast<EntityTree>(_tree)->initEntityEditFilterEngine(&_entityEditFilterEngine, [this]() {
|
||||||
|
return hadUncaughtExceptions(_entityEditFilterEngine, _entityEditFilter);
|
||||||
|
});
|
||||||
|
scriptRequest->deleteLater();
|
||||||
|
if (_scriptRequestLoop.isRunning()) {
|
||||||
|
_scriptRequestLoop.quit();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (scriptRequest) {
|
||||||
|
qCritical() << "Failed to download script at" << urlString;
|
||||||
|
// See HTTPResourceRequest::onRequestFinished for interpretation of codes. For example, a 404 is code 6 and 403 is 3. A timeout is 2. Go figure.
|
||||||
|
qCritical() << "ResourceRequest error was" << scriptRequest->getResult();
|
||||||
|
} else {
|
||||||
|
qCritical() << "Failed to create script request.";
|
||||||
|
}
|
||||||
|
// Hard stop of the assignment client on failure. We don't want anyone to think they have a filter in place when they don't.
|
||||||
|
// Alas, only indications will be the above logging with assignment client restarting repeatedly, and clients will not see any entities.
|
||||||
|
stop();
|
||||||
|
if (_scriptRequestLoop.isRunning()) {
|
||||||
|
_scriptRequestLoop.quit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityServer::nodeAdded(SharedNodePointer node) {
|
void EntityServer::nodeAdded(SharedNodePointer node) {
|
||||||
|
|
|
@ -69,6 +69,7 @@ protected:
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void handleEntityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
void handleEntityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||||
|
void scriptRequestFinished();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SimpleEntitySimulationPointer _entitySimulation;
|
SimpleEntitySimulationPointer _entitySimulation;
|
||||||
|
@ -76,6 +77,10 @@ private:
|
||||||
|
|
||||||
QReadWriteLock _viewerSendingStatsLock;
|
QReadWriteLock _viewerSendingStatsLock;
|
||||||
QMap<QUuid, QMap<QUuid, ViewerSendingStats>> _viewerSendingStats;
|
QMap<QUuid, QMap<QUuid, ViewerSendingStats>> _viewerSendingStats;
|
||||||
|
|
||||||
|
QString _entityEditFilter{};
|
||||||
|
QScriptEngine _entityEditFilterEngine{};
|
||||||
|
QEventLoop _scriptRequestLoop{};
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_EntityServer_h
|
#endif // hifi_EntityServer_h
|
||||||
|
|
|
@ -660,6 +660,7 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
||||||
quint64 averageUpdateTime = _tree->getAverageUpdateTime();
|
quint64 averageUpdateTime = _tree->getAverageUpdateTime();
|
||||||
quint64 averageCreateTime = _tree->getAverageCreateTime();
|
quint64 averageCreateTime = _tree->getAverageCreateTime();
|
||||||
quint64 averageLoggingTime = _tree->getAverageLoggingTime();
|
quint64 averageLoggingTime = _tree->getAverageLoggingTime();
|
||||||
|
quint64 averageFilterTime = _tree->getAverageFilterTime();
|
||||||
|
|
||||||
int FLOAT_PRECISION = 3;
|
int FLOAT_PRECISION = 3;
|
||||||
|
|
||||||
|
@ -699,6 +700,8 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
||||||
.arg(locale.toString((uint)averageCreateTime).rightJustified(COLUMN_WIDTH, ' '));
|
.arg(locale.toString((uint)averageCreateTime).rightJustified(COLUMN_WIDTH, ' '));
|
||||||
statsString += QString(" Average Logging Time: %1 usecs\r\n")
|
statsString += QString(" Average Logging Time: %1 usecs\r\n")
|
||||||
.arg(locale.toString((uint)averageLoggingTime).rightJustified(COLUMN_WIDTH, ' '));
|
.arg(locale.toString((uint)averageLoggingTime).rightJustified(COLUMN_WIDTH, ' '));
|
||||||
|
statsString += QString(" Average Filter Time: %1 usecs\r\n")
|
||||||
|
.arg(locale.toString((uint)averageFilterTime).rightJustified(COLUMN_WIDTH, ' '));
|
||||||
|
|
||||||
|
|
||||||
int senderNumber = 0;
|
int senderNumber = 0;
|
||||||
|
|
|
@ -1290,6 +1290,14 @@
|
||||||
"default": "",
|
"default": "",
|
||||||
"advanced": true
|
"advanced": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "entityEditFilter",
|
||||||
|
"label": "Filter Entity Edits",
|
||||||
|
"help": "Check all entity edits against this filter function.",
|
||||||
|
"placeholder": "url whose content is like: function filter(properties) { return properties; }",
|
||||||
|
"default": "",
|
||||||
|
"advanced": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "persistFilePath",
|
"name": "persistFilePath",
|
||||||
"label": "Entities File Path",
|
"label": "Entities File Path",
|
||||||
|
|
|
@ -5175,6 +5175,7 @@ void Application::nodeAdded(SharedNodePointer node) const {
|
||||||
if (node->getType() == NodeType::AvatarMixer) {
|
if (node->getType() == NodeType::AvatarMixer) {
|
||||||
// new avatar mixer, send off our identity packet right away
|
// new avatar mixer, send off our identity packet right away
|
||||||
getMyAvatar()->sendIdentityPacket();
|
getMyAvatar()->sendIdentityPacket();
|
||||||
|
getMyAvatar()->resetLastSent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -132,6 +132,11 @@ void AvatarManager::updateMyAvatar(float deltaTime) {
|
||||||
|
|
||||||
Q_LOGGING_CATEGORY(trace_simulation_avatar, "trace.simulation.avatar");
|
Q_LOGGING_CATEGORY(trace_simulation_avatar, "trace.simulation.avatar");
|
||||||
|
|
||||||
|
float AvatarManager::getAvatarDataRate(const QUuid& sessionID, const QString& rateName) {
|
||||||
|
auto avatar = getAvatarBySessionID(sessionID);
|
||||||
|
return avatar->getDataRate(rateName);
|
||||||
|
}
|
||||||
|
|
||||||
class AvatarPriority {
|
class AvatarPriority {
|
||||||
public:
|
public:
|
||||||
AvatarPriority(AvatarSharedPointer a, float p) : avatar(a), priority(p) {}
|
AvatarPriority(AvatarSharedPointer a, float p) : avatar(a), priority(p) {}
|
||||||
|
|
|
@ -69,6 +69,7 @@ public:
|
||||||
void handleOutgoingChanges(const VectorOfMotionStates& motionStates);
|
void handleOutgoingChanges(const VectorOfMotionStates& motionStates);
|
||||||
void handleCollisionEvents(const CollisionEvents& collisionEvents);
|
void handleCollisionEvents(const CollisionEvents& collisionEvents);
|
||||||
|
|
||||||
|
Q_INVOKABLE float getAvatarDataRate(const QUuid& sessionID, const QString& rateName = QString(""));
|
||||||
Q_INVOKABLE RayToAvatarIntersectionResult findRayIntersection(const PickRay& ray,
|
Q_INVOKABLE RayToAvatarIntersectionResult findRayIntersection(const PickRay& ray,
|
||||||
const QScriptValue& avatarIdsToInclude = QScriptValue(),
|
const QScriptValue& avatarIdsToInclude = QScriptValue(),
|
||||||
const QScriptValue& avatarIdsToDiscard = QScriptValue());
|
const QScriptValue& avatarIdsToDiscard = QScriptValue());
|
||||||
|
|
|
@ -226,23 +226,24 @@ void MyAvatar::simulateAttachments(float deltaTime) {
|
||||||
// don't update attachments here, do it in harvestResultsFromPhysicsSimulation()
|
// don't update attachments here, do it in harvestResultsFromPhysicsSimulation()
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray MyAvatar::toByteArray(AvatarDataDetail dataDetail) {
|
QByteArray MyAvatar::toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector<JointData>& lastSentJointData,
|
||||||
|
bool distanceAdjust, glm::vec3 viewerPosition, QVector<JointData>* sentJointDataOut) {
|
||||||
CameraMode mode = qApp->getCamera()->getMode();
|
CameraMode mode = qApp->getCamera()->getMode();
|
||||||
_globalPosition = getPosition();
|
_globalPosition = getPosition();
|
||||||
_globalBoundingBoxCorner.x = _characterController.getCapsuleRadius();
|
_globalBoundingBoxDimensions.x = _characterController.getCapsuleRadius();
|
||||||
_globalBoundingBoxCorner.y = _characterController.getCapsuleHalfHeight();
|
_globalBoundingBoxDimensions.y = _characterController.getCapsuleHalfHeight();
|
||||||
_globalBoundingBoxCorner.z = _characterController.getCapsuleRadius();
|
_globalBoundingBoxDimensions.z = _characterController.getCapsuleRadius();
|
||||||
_globalBoundingBoxCorner += _characterController.getCapsuleLocalOffset();
|
_globalBoundingBoxOffset = _characterController.getCapsuleLocalOffset();
|
||||||
if (mode == CAMERA_MODE_THIRD_PERSON || mode == CAMERA_MODE_INDEPENDENT) {
|
if (mode == CAMERA_MODE_THIRD_PERSON || mode == CAMERA_MODE_INDEPENDENT) {
|
||||||
// fake the avatar position that is sent up to the AvatarMixer
|
// fake the avatar position that is sent up to the AvatarMixer
|
||||||
glm::vec3 oldPosition = getPosition();
|
glm::vec3 oldPosition = getPosition();
|
||||||
setPosition(getSkeletonPosition());
|
setPosition(getSkeletonPosition());
|
||||||
QByteArray array = AvatarData::toByteArray(dataDetail);
|
QByteArray array = AvatarData::toByteArray(dataDetail, lastSentTime, lastSentJointData, distanceAdjust, viewerPosition, sentJointDataOut);
|
||||||
// copy the correct position back
|
// copy the correct position back
|
||||||
setPosition(oldPosition);
|
setPosition(oldPosition);
|
||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
return AvatarData::toByteArray(dataDetail);
|
return AvatarData::toByteArray(dataDetail, lastSentTime, lastSentJointData, distanceAdjust, viewerPosition, sentJointDataOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyAvatar::centerBody() {
|
void MyAvatar::centerBody() {
|
||||||
|
|
|
@ -333,7 +333,11 @@ private:
|
||||||
|
|
||||||
glm::vec3 getWorldBodyPosition() const;
|
glm::vec3 getWorldBodyPosition() const;
|
||||||
glm::quat getWorldBodyOrientation() const;
|
glm::quat getWorldBodyOrientation() const;
|
||||||
QByteArray toByteArray(AvatarDataDetail dataDetail) override;
|
|
||||||
|
|
||||||
|
virtual QByteArray toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector<JointData>& lastSentJointData,
|
||||||
|
bool distanceAdjust = false, glm::vec3 viewerPosition = glm::vec3(0), QVector<JointData>* sentJointDataOut = nullptr) override;
|
||||||
|
|
||||||
void simulate(float deltaTime);
|
void simulate(float deltaTime);
|
||||||
void updateFromTrackers(float deltaTime);
|
void updateFromTrackers(float deltaTime);
|
||||||
virtual void render(RenderArgs* renderArgs, const glm::vec3& cameraPositio) override;
|
virtual void render(RenderArgs* renderArgs, const glm::vec3& cameraPositio) override;
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
#include <UUID.h>
|
#include <UUID.h>
|
||||||
#include <shared/JSONHelpers.h>
|
#include <shared/JSONHelpers.h>
|
||||||
#include <ShapeInfo.h>
|
#include <ShapeInfo.h>
|
||||||
|
#include <AudioHelpers.h>
|
||||||
|
|
||||||
#include "AvatarLogging.h"
|
#include "AvatarLogging.h"
|
||||||
|
|
||||||
|
@ -49,67 +50,9 @@ const glm::vec3 DEFAULT_LOCAL_AABOX_SCALE(1.0f);
|
||||||
|
|
||||||
const QString AvatarData::FRAME_NAME = "com.highfidelity.recording.AvatarData";
|
const QString AvatarData::FRAME_NAME = "com.highfidelity.recording.AvatarData";
|
||||||
|
|
||||||
namespace AvatarDataPacket {
|
|
||||||
|
|
||||||
// NOTE: AvatarDataPackets start with a uint16_t sequence number that is not reflected in the Header structure.
|
|
||||||
|
|
||||||
PACKED_BEGIN struct Header {
|
|
||||||
uint8_t packetStateFlags; // state flags, currently used to indicate if the packet is a minimal or fuller packet
|
|
||||||
} PACKED_END;
|
|
||||||
const size_t HEADER_SIZE = 1;
|
|
||||||
|
|
||||||
PACKED_BEGIN struct MinimalAvatarInfo {
|
|
||||||
float globalPosition[3]; // avatar's position
|
|
||||||
} PACKED_END;
|
|
||||||
const size_t MINIMAL_AVATAR_INFO_SIZE = 12;
|
|
||||||
|
|
||||||
PACKED_BEGIN struct AvatarInfo {
|
|
||||||
float position[3]; // skeletal model's position
|
|
||||||
float globalPosition[3]; // avatar's position
|
|
||||||
float globalBoundingBoxCorner[3]; // global position of the lowest corner of the avatar's bounding box
|
|
||||||
uint16_t localOrientation[3]; // avatar's local euler angles (degrees, compressed) relative to the thing it's attached to
|
|
||||||
uint16_t scale; // (compressed) 'ratio' encoding uses sign bit as flag.
|
|
||||||
float lookAtPosition[3]; // world space position that eyes are focusing on.
|
|
||||||
float audioLoudness; // current loundess of microphone
|
|
||||||
uint8_t sensorToWorldQuat[6]; // 6 byte compressed quaternion part of sensor to world matrix
|
|
||||||
uint16_t sensorToWorldScale; // uniform scale of sensor to world matrix
|
|
||||||
float sensorToWorldTrans[3]; // fourth column of sensor to world matrix
|
|
||||||
uint8_t flags;
|
|
||||||
} PACKED_END;
|
|
||||||
const size_t AVATAR_INFO_SIZE = 81;
|
|
||||||
|
|
||||||
// only present if HAS_REFERENTIAL flag is set in AvatarInfo.flags
|
|
||||||
PACKED_BEGIN struct ParentInfo {
|
|
||||||
uint8_t parentUUID[16]; // rfc 4122 encoded
|
|
||||||
uint16_t parentJointIndex;
|
|
||||||
} PACKED_END;
|
|
||||||
const size_t PARENT_INFO_SIZE = 18;
|
|
||||||
|
|
||||||
// only present if IS_FACESHIFT_CONNECTED flag is set in AvatarInfo.flags
|
|
||||||
PACKED_BEGIN struct FaceTrackerInfo {
|
|
||||||
float leftEyeBlink;
|
|
||||||
float rightEyeBlink;
|
|
||||||
float averageLoudness;
|
|
||||||
float browAudioLift;
|
|
||||||
uint8_t numBlendshapeCoefficients;
|
|
||||||
// float blendshapeCoefficients[numBlendshapeCoefficients];
|
|
||||||
} PACKED_END;
|
|
||||||
const size_t FACE_TRACKER_INFO_SIZE = 17;
|
|
||||||
|
|
||||||
// variable length structure follows
|
|
||||||
/*
|
|
||||||
struct JointData {
|
|
||||||
uint8_t numJoints;
|
|
||||||
uint8_t rotationValidityBits[ceil(numJoints / 8)]; // one bit per joint, if true then a compressed rotation follows.
|
|
||||||
SixByteQuat rotation[numValidRotations]; // encodeded and compressed by packOrientationQuatToSixBytes()
|
|
||||||
uint8_t translationValidityBits[ceil(numJoints / 8)]; // one bit per joint, if true then a compressed translation follows.
|
|
||||||
SixByteTrans translation[numValidTranslations]; // encodeded and compressed by packFloatVec3ToSignedTwoByteFixed()
|
|
||||||
};
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
static const int TRANSLATION_COMPRESSION_RADIX = 12;
|
static const int TRANSLATION_COMPRESSION_RADIX = 12;
|
||||||
static const int SENSOR_TO_WORLD_SCALE_RADIX = 10;
|
static const int SENSOR_TO_WORLD_SCALE_RADIX = 10;
|
||||||
|
static const float AUDIO_LOUDNESS_SCALE = 1024.0f;
|
||||||
|
|
||||||
#define ASSERT(COND) do { if (!(COND)) { abort(); } } while(0)
|
#define ASSERT(COND) do { if (!(COND)) { abort(); } } while(0)
|
||||||
|
|
||||||
|
@ -134,8 +77,15 @@ AvatarData::AvatarData() :
|
||||||
setBodyRoll(0.0f);
|
setBodyRoll(0.0f);
|
||||||
|
|
||||||
ASSERT(sizeof(AvatarDataPacket::Header) == AvatarDataPacket::HEADER_SIZE);
|
ASSERT(sizeof(AvatarDataPacket::Header) == AvatarDataPacket::HEADER_SIZE);
|
||||||
ASSERT(sizeof(AvatarDataPacket::MinimalAvatarInfo) == AvatarDataPacket::MINIMAL_AVATAR_INFO_SIZE);
|
ASSERT(sizeof(AvatarDataPacket::AvatarGlobalPosition) == AvatarDataPacket::AVATAR_GLOBAL_POSITION_SIZE);
|
||||||
ASSERT(sizeof(AvatarDataPacket::AvatarInfo) == AvatarDataPacket::AVATAR_INFO_SIZE);
|
ASSERT(sizeof(AvatarDataPacket::AvatarLocalPosition) == AvatarDataPacket::AVATAR_LOCAL_POSITION_SIZE);
|
||||||
|
ASSERT(sizeof(AvatarDataPacket::AvatarBoundingBox) == AvatarDataPacket::AVATAR_BOUNDING_BOX_SIZE);
|
||||||
|
ASSERT(sizeof(AvatarDataPacket::AvatarOrientation) == AvatarDataPacket::AVATAR_ORIENTATION_SIZE);
|
||||||
|
ASSERT(sizeof(AvatarDataPacket::AvatarScale) == AvatarDataPacket::AVATAR_SCALE_SIZE);
|
||||||
|
ASSERT(sizeof(AvatarDataPacket::LookAtPosition) == AvatarDataPacket::LOOK_AT_POSITION_SIZE);
|
||||||
|
ASSERT(sizeof(AvatarDataPacket::AudioLoudness) == AvatarDataPacket::AUDIO_LOUDNESS_SIZE);
|
||||||
|
ASSERT(sizeof(AvatarDataPacket::SensorToWorldMatrix) == AvatarDataPacket::SENSOR_TO_WORLD_SIZE);
|
||||||
|
ASSERT(sizeof(AvatarDataPacket::AdditionalFlags) == AvatarDataPacket::ADDITIONAL_FLAGS_SIZE);
|
||||||
ASSERT(sizeof(AvatarDataPacket::ParentInfo) == AvatarDataPacket::PARENT_INFO_SIZE);
|
ASSERT(sizeof(AvatarDataPacket::ParentInfo) == AvatarDataPacket::PARENT_INFO_SIZE);
|
||||||
ASSERT(sizeof(AvatarDataPacket::FaceTrackerInfo) == AvatarDataPacket::FACE_TRACKER_INFO_SIZE);
|
ASSERT(sizeof(AvatarDataPacket::FaceTrackerInfo) == AvatarDataPacket::FACE_TRACKER_INFO_SIZE);
|
||||||
}
|
}
|
||||||
|
@ -176,7 +126,11 @@ float AvatarData::getTargetScale() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarData::setTargetScale(float targetScale) {
|
void AvatarData::setTargetScale(float targetScale) {
|
||||||
_targetScale = glm::clamp(targetScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE);
|
auto newValue = glm::clamp(targetScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE);
|
||||||
|
if (_targetScale != newValue) {
|
||||||
|
_targetScale = newValue;
|
||||||
|
_scaleChanged = usecTimestampNow();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::vec3 AvatarData::getHandPosition() const {
|
glm::vec3 AvatarData::getHandPosition() const {
|
||||||
|
@ -188,15 +142,7 @@ void AvatarData::setHandPosition(const glm::vec3& handPosition) {
|
||||||
_handPosition = glm::inverse(getOrientation()) * (handPosition - getPosition());
|
_handPosition = glm::inverse(getOrientation()) * (handPosition - getPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AvatarData::lazyInitHeadData() {
|
||||||
QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail) {
|
|
||||||
bool cullSmallChanges = (dataDetail == CullSmallData);
|
|
||||||
bool sendAll = (dataDetail == SendAllData);
|
|
||||||
bool sendMinimum = (dataDetail == MinimumData);
|
|
||||||
// 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
|
|
||||||
|
|
||||||
// lazily allocate memory for HeadData in case we're not an Avatar instance
|
// lazily allocate memory for HeadData in case we're not an Avatar instance
|
||||||
if (!_headData) {
|
if (!_headData) {
|
||||||
_headData = new HeadData(this);
|
_headData = new HeadData(this);
|
||||||
|
@ -204,77 +150,238 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail) {
|
||||||
if (_forceFaceTrackerConnected) {
|
if (_forceFaceTrackerConnected) {
|
||||||
_headData->_isFaceTrackerConnected = true;
|
_headData->_isFaceTrackerConnected = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool AvatarData::avatarBoundingBoxChangedSince(quint64 time) {
|
||||||
|
return _avatarBoundingBoxChanged >= time;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AvatarData::avatarScaleChangedSince(quint64 time) {
|
||||||
|
return _avatarScaleChanged >= time;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AvatarData::lookAtPositionChangedSince(quint64 time) {
|
||||||
|
return _headData->lookAtPositionChangedSince(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AvatarData::audioLoudnessChangedSince(quint64 time) {
|
||||||
|
return _headData->audioLoudnessChangedSince(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AvatarData::sensorToWorldMatrixChangedSince(quint64 time) {
|
||||||
|
return _sensorToWorldMatrixChanged >= time;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AvatarData::additionalFlagsChangedSince(quint64 time) {
|
||||||
|
return _additionalFlagsChanged >= time;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AvatarData::parentInfoChangedSince(quint64 time) {
|
||||||
|
return _parentChanged >= time;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AvatarData::faceTrackerInfoChangedSince(quint64 time) {
|
||||||
|
return true; // FIXME!
|
||||||
|
}
|
||||||
|
|
||||||
|
float AvatarData::getDistanceBasedMinRotationDOT(glm::vec3 viewerPosition) {
|
||||||
|
auto distance = glm::distance(_globalPosition, viewerPosition);
|
||||||
|
float result = ROTATION_CHANGE_179D; // assume worst
|
||||||
|
if (distance < AVATAR_DISTANCE_LEVEL_1) {
|
||||||
|
result = AVATAR_MIN_ROTATION_DOT;
|
||||||
|
} else if (distance < AVATAR_DISTANCE_LEVEL_2) {
|
||||||
|
result = ROTATION_CHANGE_15D;
|
||||||
|
} else if (distance < AVATAR_DISTANCE_LEVEL_3) {
|
||||||
|
result = ROTATION_CHANGE_45D;
|
||||||
|
} else if (distance < AVATAR_DISTANCE_LEVEL_4) {
|
||||||
|
result = ROTATION_CHANGE_90D;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
float AvatarData::getDistanceBasedMinTranslationDistance(glm::vec3 viewerPosition) {
|
||||||
|
return AVATAR_MIN_TRANSLATION; // Eventually make this distance sensitive as well
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector<JointData>& lastSentJointData,
|
||||||
|
bool distanceAdjust, glm::vec3 viewerPosition, QVector<JointData>* sentJointDataOut) {
|
||||||
|
|
||||||
|
// if no timestamp was included, then assume the avatarData is single instance
|
||||||
|
// and is tracking its own last encoding time.
|
||||||
|
if (lastSentTime == 0) {
|
||||||
|
lastSentTime = _lastToByteArray;
|
||||||
|
_lastToByteArray = usecTimestampNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool cullSmallChanges = (dataDetail == CullSmallData);
|
||||||
|
bool sendAll = (dataDetail == SendAllData);
|
||||||
|
bool sendMinimum = (dataDetail == MinimumData);
|
||||||
|
|
||||||
|
lazyInitHeadData();
|
||||||
|
|
||||||
QByteArray avatarDataByteArray(udt::MAX_PACKET_SIZE, 0);
|
QByteArray avatarDataByteArray(udt::MAX_PACKET_SIZE, 0);
|
||||||
|
|
||||||
unsigned char* destinationBuffer = reinterpret_cast<unsigned char*>(avatarDataByteArray.data());
|
unsigned char* destinationBuffer = reinterpret_cast<unsigned char*>(avatarDataByteArray.data());
|
||||||
unsigned char* startPosition = destinationBuffer;
|
unsigned char* startPosition = destinationBuffer;
|
||||||
|
|
||||||
|
// FIXME -
|
||||||
|
//
|
||||||
|
// BUG -- if you enter a space bubble, and then back away, the avatar has wrong orientation until "send all" happens...
|
||||||
|
// this is an iFrame issue... what to do about that?
|
||||||
|
//
|
||||||
|
// BUG -- Resizing avatar seems to "take too long"... the avatar doesn't redraw at smaller size right away
|
||||||
|
//
|
||||||
|
// TODO consider these additional optimizations in the future
|
||||||
|
// 1) SensorToWorld - should we only send this for avatars with attachments?? - 20 bytes - 7.20 kbps
|
||||||
|
// 2) GUIID for the session change to 2byte index (savings) - 14 bytes - 5.04 kbps
|
||||||
|
// 3) Improve Joints -- currently we use rotational tolerances, but if we had skeleton/bone length data
|
||||||
|
// we could do a better job of determining if the change in joints actually translates to visible
|
||||||
|
// changes at distance.
|
||||||
|
//
|
||||||
|
// Potential savings:
|
||||||
|
// 63 rotations * 6 bytes = 136kbps
|
||||||
|
// 3 translations * 6 bytes = 6.48kbps
|
||||||
|
//
|
||||||
|
|
||||||
|
auto parentID = getParentID();
|
||||||
|
|
||||||
|
bool hasAvatarGlobalPosition = true; // always include global position
|
||||||
|
bool hasAvatarOrientation = sendAll || rotationChangedSince(lastSentTime);
|
||||||
|
bool hasAvatarBoundingBox = sendAll || avatarBoundingBoxChangedSince(lastSentTime);
|
||||||
|
bool hasAvatarScale = sendAll || avatarScaleChangedSince(lastSentTime);
|
||||||
|
bool hasLookAtPosition = sendAll || lookAtPositionChangedSince(lastSentTime);
|
||||||
|
bool hasAudioLoudness = sendAll || audioLoudnessChangedSince(lastSentTime);
|
||||||
|
bool hasSensorToWorldMatrix = sendAll || sensorToWorldMatrixChangedSince(lastSentTime);
|
||||||
|
bool hasAdditionalFlags = sendAll || additionalFlagsChangedSince(lastSentTime);
|
||||||
|
|
||||||
|
// local position, and parent info only apply to avatars that are parented. The local position
|
||||||
|
// and the parent info can change independently though, so we track their "changed since"
|
||||||
|
// separately
|
||||||
|
bool hasParentInfo = hasParent() && (sendAll || parentInfoChangedSince(lastSentTime));
|
||||||
|
bool hasAvatarLocalPosition = hasParent() && (sendAll || tranlationChangedSince(lastSentTime));
|
||||||
|
|
||||||
|
bool hasFaceTrackerInfo = hasFaceTracker() && (sendAll || faceTrackerInfoChangedSince(lastSentTime));
|
||||||
|
bool hasJointData = sendAll || !sendMinimum;
|
||||||
|
|
||||||
// Leading flags, to indicate how much data is actually included in the packet...
|
// Leading flags, to indicate how much data is actually included in the packet...
|
||||||
uint8_t packetStateFlags = 0;
|
AvatarDataPacket::HasFlags packetStateFlags =
|
||||||
if (sendMinimum) {
|
(hasAvatarGlobalPosition ? AvatarDataPacket::PACKET_HAS_AVATAR_GLOBAL_POSITION : 0)
|
||||||
setAtBit(packetStateFlags, AVATARDATA_FLAGS_MINIMUM);
|
| (hasAvatarBoundingBox ? AvatarDataPacket::PACKET_HAS_AVATAR_BOUNDING_BOX : 0)
|
||||||
}
|
| (hasAvatarOrientation ? AvatarDataPacket::PACKET_HAS_AVATAR_ORIENTATION : 0)
|
||||||
|
| (hasAvatarScale ? AvatarDataPacket::PACKET_HAS_AVATAR_SCALE : 0)
|
||||||
|
| (hasLookAtPosition ? AvatarDataPacket::PACKET_HAS_LOOK_AT_POSITION : 0)
|
||||||
|
| (hasAudioLoudness ? AvatarDataPacket::PACKET_HAS_AUDIO_LOUDNESS : 0)
|
||||||
|
| (hasSensorToWorldMatrix ? AvatarDataPacket::PACKET_HAS_SENSOR_TO_WORLD_MATRIX : 0)
|
||||||
|
| (hasAdditionalFlags ? AvatarDataPacket::PACKET_HAS_ADDITIONAL_FLAGS : 0)
|
||||||
|
| (hasParentInfo ? AvatarDataPacket::PACKET_HAS_PARENT_INFO : 0)
|
||||||
|
| (hasAvatarLocalPosition ? AvatarDataPacket::PACKET_HAS_AVATAR_LOCAL_POSITION : 0)
|
||||||
|
| (hasFaceTrackerInfo ? AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO : 0)
|
||||||
|
| (hasJointData ? AvatarDataPacket::PACKET_HAS_JOINT_DATA : 0);
|
||||||
|
|
||||||
memcpy(destinationBuffer, &packetStateFlags, sizeof(packetStateFlags));
|
memcpy(destinationBuffer, &packetStateFlags, sizeof(packetStateFlags));
|
||||||
destinationBuffer += sizeof(packetStateFlags);
|
destinationBuffer += sizeof(packetStateFlags);
|
||||||
|
|
||||||
if (sendMinimum) {
|
if (hasAvatarGlobalPosition) {
|
||||||
memcpy(destinationBuffer, &_globalPosition, sizeof(_globalPosition));
|
auto data = reinterpret_cast<AvatarDataPacket::AvatarGlobalPosition*>(destinationBuffer);
|
||||||
destinationBuffer += sizeof(_globalPosition);
|
data->globalPosition[0] = _globalPosition.x;
|
||||||
} else {
|
data->globalPosition[1] = _globalPosition.y;
|
||||||
auto avatarInfo = reinterpret_cast<AvatarDataPacket::AvatarInfo*>(destinationBuffer);
|
data->globalPosition[2] = _globalPosition.z;
|
||||||
avatarInfo->position[0] = getLocalPosition().x;
|
destinationBuffer += sizeof(AvatarDataPacket::AvatarGlobalPosition);
|
||||||
avatarInfo->position[1] = getLocalPosition().y;
|
}
|
||||||
avatarInfo->position[2] = getLocalPosition().z;
|
|
||||||
avatarInfo->globalPosition[0] = _globalPosition.x;
|
|
||||||
avatarInfo->globalPosition[1] = _globalPosition.y;
|
|
||||||
avatarInfo->globalPosition[2] = _globalPosition.z;
|
|
||||||
avatarInfo->globalBoundingBoxCorner[0] = getPosition().x - _globalBoundingBoxCorner.x;
|
|
||||||
avatarInfo->globalBoundingBoxCorner[1] = getPosition().y - _globalBoundingBoxCorner.y;
|
|
||||||
avatarInfo->globalBoundingBoxCorner[2] = getPosition().z - _globalBoundingBoxCorner.z;
|
|
||||||
|
|
||||||
glm::vec3 bodyEulerAngles = glm::degrees(safeEulerAngles(getLocalOrientation()));
|
if (hasAvatarBoundingBox) {
|
||||||
packFloatAngleToTwoByte((uint8_t*)(avatarInfo->localOrientation + 0), bodyEulerAngles.y);
|
auto data = reinterpret_cast<AvatarDataPacket::AvatarBoundingBox*>(destinationBuffer);
|
||||||
packFloatAngleToTwoByte((uint8_t*)(avatarInfo->localOrientation + 1), bodyEulerAngles.x);
|
|
||||||
packFloatAngleToTwoByte((uint8_t*)(avatarInfo->localOrientation + 2), bodyEulerAngles.z);
|
|
||||||
packFloatRatioToTwoByte((uint8_t*)(&avatarInfo->scale), getDomainLimitedScale());
|
|
||||||
avatarInfo->lookAtPosition[0] = _headData->_lookAtPosition.x;
|
|
||||||
avatarInfo->lookAtPosition[1] = _headData->_lookAtPosition.y;
|
|
||||||
avatarInfo->lookAtPosition[2] = _headData->_lookAtPosition.z;
|
|
||||||
avatarInfo->audioLoudness = _headData->_audioLoudness;
|
|
||||||
|
|
||||||
|
data->avatarDimensions[0] = _globalBoundingBoxDimensions.x;
|
||||||
|
data->avatarDimensions[1] = _globalBoundingBoxDimensions.y;
|
||||||
|
data->avatarDimensions[2] = _globalBoundingBoxDimensions.z;
|
||||||
|
|
||||||
|
data->boundOriginOffset[0] = _globalBoundingBoxOffset.x;
|
||||||
|
data->boundOriginOffset[1] = _globalBoundingBoxOffset.y;
|
||||||
|
data->boundOriginOffset[2] = _globalBoundingBoxOffset.z;
|
||||||
|
|
||||||
|
destinationBuffer += sizeof(AvatarDataPacket::AvatarBoundingBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasAvatarOrientation) {
|
||||||
|
auto localOrientation = getLocalOrientation();
|
||||||
|
destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, localOrientation);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasAvatarScale) {
|
||||||
|
auto data = reinterpret_cast<AvatarDataPacket::AvatarScale*>(destinationBuffer);
|
||||||
|
auto scale = getDomainLimitedScale();
|
||||||
|
packFloatRatioToTwoByte((uint8_t*)(&data->scale), scale);
|
||||||
|
destinationBuffer += sizeof(AvatarDataPacket::AvatarScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasLookAtPosition) {
|
||||||
|
auto data = reinterpret_cast<AvatarDataPacket::LookAtPosition*>(destinationBuffer);
|
||||||
|
auto lookAt = _headData->getLookAtPosition();
|
||||||
|
data->lookAtPosition[0] = lookAt.x;
|
||||||
|
data->lookAtPosition[1] = lookAt.y;
|
||||||
|
data->lookAtPosition[2] = lookAt.z;
|
||||||
|
destinationBuffer += sizeof(AvatarDataPacket::LookAtPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasAudioLoudness) {
|
||||||
|
auto data = reinterpret_cast<AvatarDataPacket::AudioLoudness*>(destinationBuffer);
|
||||||
|
data->audioLoudness = packFloatGainToByte(_headData->getAudioLoudness() / AUDIO_LOUDNESS_SCALE);
|
||||||
|
destinationBuffer += sizeof(AvatarDataPacket::AudioLoudness);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasSensorToWorldMatrix) {
|
||||||
|
auto data = reinterpret_cast<AvatarDataPacket::SensorToWorldMatrix*>(destinationBuffer);
|
||||||
glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix();
|
glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix();
|
||||||
packOrientationQuatToSixBytes(avatarInfo->sensorToWorldQuat, glmExtractRotation(sensorToWorldMatrix));
|
packOrientationQuatToSixBytes(data->sensorToWorldQuat, glmExtractRotation(sensorToWorldMatrix));
|
||||||
glm::vec3 scale = extractScale(sensorToWorldMatrix);
|
glm::vec3 scale = extractScale(sensorToWorldMatrix);
|
||||||
packFloatScalarToSignedTwoByteFixed((uint8_t*)&avatarInfo->sensorToWorldScale, scale.x, SENSOR_TO_WORLD_SCALE_RADIX);
|
packFloatScalarToSignedTwoByteFixed((uint8_t*)&data->sensorToWorldScale, scale.x, SENSOR_TO_WORLD_SCALE_RADIX);
|
||||||
avatarInfo->sensorToWorldTrans[0] = sensorToWorldMatrix[3][0];
|
data->sensorToWorldTrans[0] = sensorToWorldMatrix[3][0];
|
||||||
avatarInfo->sensorToWorldTrans[1] = sensorToWorldMatrix[3][1];
|
data->sensorToWorldTrans[1] = sensorToWorldMatrix[3][1];
|
||||||
avatarInfo->sensorToWorldTrans[2] = sensorToWorldMatrix[3][2];
|
data->sensorToWorldTrans[2] = sensorToWorldMatrix[3][2];
|
||||||
|
destinationBuffer += sizeof(AvatarDataPacket::SensorToWorldMatrix);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasAdditionalFlags) {
|
||||||
|
auto data = reinterpret_cast<AvatarDataPacket::AdditionalFlags*>(destinationBuffer);
|
||||||
|
|
||||||
|
uint8_t flags { 0 };
|
||||||
|
|
||||||
|
setSemiNibbleAt(flags, KEY_STATE_START_BIT, _keyState);
|
||||||
|
|
||||||
setSemiNibbleAt(avatarInfo->flags, KEY_STATE_START_BIT, _keyState);
|
|
||||||
// hand state
|
// hand state
|
||||||
bool isFingerPointing = _handState & IS_FINGER_POINTING_FLAG;
|
bool isFingerPointing = _handState & IS_FINGER_POINTING_FLAG;
|
||||||
setSemiNibbleAt(avatarInfo->flags, HAND_STATE_START_BIT, _handState & ~IS_FINGER_POINTING_FLAG);
|
setSemiNibbleAt(flags, HAND_STATE_START_BIT, _handState & ~IS_FINGER_POINTING_FLAG);
|
||||||
if (isFingerPointing) {
|
if (isFingerPointing) {
|
||||||
setAtBit(avatarInfo->flags, HAND_STATE_FINGER_POINTING_BIT);
|
setAtBit(flags, HAND_STATE_FINGER_POINTING_BIT);
|
||||||
}
|
}
|
||||||
// faceshift state
|
// faceshift state
|
||||||
if (_headData->_isFaceTrackerConnected) {
|
if (_headData->_isFaceTrackerConnected) {
|
||||||
setAtBit(avatarInfo->flags, IS_FACESHIFT_CONNECTED);
|
setAtBit(flags, IS_FACESHIFT_CONNECTED);
|
||||||
}
|
}
|
||||||
// eye tracker state
|
// eye tracker state
|
||||||
if (_headData->_isEyeTrackerConnected) {
|
if (_headData->_isEyeTrackerConnected) {
|
||||||
setAtBit(avatarInfo->flags, IS_EYE_TRACKER_CONNECTED);
|
setAtBit(flags, IS_EYE_TRACKER_CONNECTED);
|
||||||
}
|
}
|
||||||
// referential state
|
// referential state
|
||||||
QUuid parentID = getParentID();
|
|
||||||
if (!parentID.isNull()) {
|
if (!parentID.isNull()) {
|
||||||
setAtBit(avatarInfo->flags, HAS_REFERENTIAL);
|
setAtBit(flags, HAS_REFERENTIAL);
|
||||||
|
}
|
||||||
|
data->flags = flags;
|
||||||
|
destinationBuffer += sizeof(AvatarDataPacket::AdditionalFlags);
|
||||||
}
|
}
|
||||||
destinationBuffer += sizeof(AvatarDataPacket::AvatarInfo);
|
|
||||||
|
|
||||||
if (!parentID.isNull()) {
|
if (hasAvatarLocalPosition) {
|
||||||
|
auto data = reinterpret_cast<AvatarDataPacket::AvatarLocalPosition*>(destinationBuffer);
|
||||||
|
auto localPosition = getLocalPosition();
|
||||||
|
data->localPosition[0] = localPosition.x;
|
||||||
|
data->localPosition[1] = localPosition.y;
|
||||||
|
data->localPosition[2] = localPosition.z;
|
||||||
|
destinationBuffer += sizeof(AvatarDataPacket::AvatarLocalPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasParentInfo) {
|
||||||
auto parentInfo = reinterpret_cast<AvatarDataPacket::ParentInfo*>(destinationBuffer);
|
auto parentInfo = reinterpret_cast<AvatarDataPacket::ParentInfo*>(destinationBuffer);
|
||||||
QByteArray referentialAsBytes = parentID.toRfc4122();
|
QByteArray referentialAsBytes = parentID.toRfc4122();
|
||||||
memcpy(parentInfo->parentUUID, referentialAsBytes.data(), referentialAsBytes.size());
|
memcpy(parentInfo->parentUUID, referentialAsBytes.data(), referentialAsBytes.size());
|
||||||
|
@ -283,7 +390,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it is connected, pack up the data
|
// If it is connected, pack up the data
|
||||||
if (_headData->_isFaceTrackerConnected) {
|
if (hasFaceTrackerInfo) {
|
||||||
auto faceTrackerInfo = reinterpret_cast<AvatarDataPacket::FaceTrackerInfo*>(destinationBuffer);
|
auto faceTrackerInfo = reinterpret_cast<AvatarDataPacket::FaceTrackerInfo*>(destinationBuffer);
|
||||||
|
|
||||||
faceTrackerInfo->leftEyeBlink = _headData->_leftEyeBlink;
|
faceTrackerInfo->leftEyeBlink = _headData->_leftEyeBlink;
|
||||||
|
@ -298,32 +405,47 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail) {
|
||||||
destinationBuffer += _headData->_blendshapeCoefficients.size() * sizeof(float);
|
destinationBuffer += _headData->_blendshapeCoefficients.size() * sizeof(float);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If it is connected, pack up the data
|
||||||
|
if (hasJointData) {
|
||||||
QReadLocker readLock(&_jointDataLock);
|
QReadLocker readLock(&_jointDataLock);
|
||||||
|
|
||||||
// joint rotation data
|
// joint rotation data
|
||||||
*destinationBuffer++ = _jointData.size();
|
int numJoints = _jointData.size();
|
||||||
|
*destinationBuffer++ = (uint8_t)numJoints;
|
||||||
|
|
||||||
unsigned char* validityPosition = destinationBuffer;
|
unsigned char* validityPosition = destinationBuffer;
|
||||||
unsigned char validity = 0;
|
unsigned char validity = 0;
|
||||||
int validityBit = 0;
|
int validityBit = 0;
|
||||||
|
|
||||||
#ifdef WANT_DEBUG
|
#ifdef WANT_DEBUG
|
||||||
int rotationSentCount = 0;
|
int rotationSentCount = 0;
|
||||||
unsigned char* beforeRotations = destinationBuffer;
|
unsigned char* beforeRotations = destinationBuffer;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
_lastSentJointData.resize(_jointData.size());
|
if (sentJointDataOut) {
|
||||||
|
sentJointDataOut->resize(_jointData.size()); // Make sure the destination is resized before using it
|
||||||
|
}
|
||||||
|
float minRotationDOT = !distanceAdjust ? AVATAR_MIN_ROTATION_DOT : getDistanceBasedMinRotationDOT(viewerPosition);
|
||||||
|
|
||||||
for (int i=0; i < _jointData.size(); i++) {
|
for (int i = 0; i < _jointData.size(); i++) {
|
||||||
const JointData& data = _jointData[i];
|
const JointData& data = _jointData[i];
|
||||||
if (sendAll || _lastSentJointData[i].rotation != data.rotation) {
|
|
||||||
if (sendAll ||
|
// The dot product for smaller rotations is a smaller number.
|
||||||
!cullSmallChanges ||
|
// So if the dot() is less than the value, then the rotation is a larger angle of rotation
|
||||||
fabsf(glm::dot(data.rotation, _lastSentJointData[i].rotation)) <= AVATAR_MIN_ROTATION_DOT) {
|
bool largeEnoughRotation = fabsf(glm::dot(data.rotation, lastSentJointData[i].rotation)) < minRotationDOT;
|
||||||
|
|
||||||
|
if (sendAll || lastSentJointData[i].rotation != data.rotation) {
|
||||||
|
if (sendAll || !cullSmallChanges || largeEnoughRotation) {
|
||||||
if (data.rotationSet) {
|
if (data.rotationSet) {
|
||||||
validity |= (1 << validityBit);
|
validity |= (1 << validityBit);
|
||||||
#ifdef WANT_DEBUG
|
#ifdef WANT_DEBUG
|
||||||
rotationSentCount++;
|
rotationSentCount++;
|
||||||
#endif
|
#endif
|
||||||
|
if (sentJointDataOut) {
|
||||||
|
auto jointDataOut = *sentJointDataOut;
|
||||||
|
jointDataOut[i].rotation = data.rotation;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -338,7 +460,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail) {
|
||||||
|
|
||||||
validityBit = 0;
|
validityBit = 0;
|
||||||
validity = *validityPosition++;
|
validity = *validityPosition++;
|
||||||
for (int i = 0; i < _jointData.size(); i ++) {
|
for (int i = 0; i < _jointData.size(); i++) {
|
||||||
const JointData& data = _jointData[i];
|
const JointData& data = _jointData[i];
|
||||||
if (validity & (1 << validityBit)) {
|
if (validity & (1 << validityBit)) {
|
||||||
destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, data.rotation);
|
destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, data.rotation);
|
||||||
|
@ -355,18 +477,20 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail) {
|
||||||
validity = 0;
|
validity = 0;
|
||||||
validityBit = 0;
|
validityBit = 0;
|
||||||
|
|
||||||
#ifdef WANT_DEBUG
|
#ifdef WANT_DEBUG
|
||||||
int translationSentCount = 0;
|
int translationSentCount = 0;
|
||||||
unsigned char* beforeTranslations = destinationBuffer;
|
unsigned char* beforeTranslations = destinationBuffer;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
float minTranslation = !distanceAdjust ? AVATAR_MIN_TRANSLATION : getDistanceBasedMinTranslationDistance(viewerPosition);
|
||||||
|
|
||||||
float maxTranslationDimension = 0.0;
|
float maxTranslationDimension = 0.0;
|
||||||
for (int i=0; i < _jointData.size(); i++) {
|
for (int i = 0; i < _jointData.size(); i++) {
|
||||||
const JointData& data = _jointData[i];
|
const JointData& data = _jointData[i];
|
||||||
if (sendAll || _lastSentJointData[i].translation != data.translation) {
|
if (sendAll || lastSentJointData[i].translation != data.translation) {
|
||||||
if (sendAll ||
|
if (sendAll ||
|
||||||
!cullSmallChanges ||
|
!cullSmallChanges ||
|
||||||
glm::distance(data.translation, _lastSentJointData[i].translation) > AVATAR_MIN_TRANSLATION) {
|
glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation) {
|
||||||
if (data.translationSet) {
|
if (data.translationSet) {
|
||||||
validity |= (1 << validityBit);
|
validity |= (1 << validityBit);
|
||||||
#ifdef WANT_DEBUG
|
#ifdef WANT_DEBUG
|
||||||
|
@ -375,6 +499,12 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail) {
|
||||||
maxTranslationDimension = glm::max(fabsf(data.translation.x), maxTranslationDimension);
|
maxTranslationDimension = glm::max(fabsf(data.translation.x), maxTranslationDimension);
|
||||||
maxTranslationDimension = glm::max(fabsf(data.translation.y), maxTranslationDimension);
|
maxTranslationDimension = glm::max(fabsf(data.translation.y), maxTranslationDimension);
|
||||||
maxTranslationDimension = glm::max(fabsf(data.translation.z), maxTranslationDimension);
|
maxTranslationDimension = glm::max(fabsf(data.translation.z), maxTranslationDimension);
|
||||||
|
|
||||||
|
if (sentJointDataOut) {
|
||||||
|
auto jointDataOut = *sentJointDataOut;
|
||||||
|
jointDataOut[i].translation = data.translation;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -390,7 +520,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail) {
|
||||||
|
|
||||||
validityBit = 0;
|
validityBit = 0;
|
||||||
validity = *validityPosition++;
|
validity = *validityPosition++;
|
||||||
for (int i = 0; i < _jointData.size(); i ++) {
|
for (int i = 0; i < _jointData.size(); i++) {
|
||||||
const JointData& data = _jointData[i];
|
const JointData& data = _jointData[i];
|
||||||
if (validity & (1 << validityBit)) {
|
if (validity & (1 << validityBit)) {
|
||||||
destinationBuffer +=
|
destinationBuffer +=
|
||||||
|
@ -412,7 +542,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail) {
|
||||||
destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerRightHandTransform.getTranslation(),
|
destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerRightHandTransform.getTranslation(),
|
||||||
TRANSLATION_COMPRESSION_RADIX);
|
TRANSLATION_COMPRESSION_RADIX);
|
||||||
|
|
||||||
#ifdef WANT_DEBUG
|
#ifdef WANT_DEBUG
|
||||||
if (sendAll) {
|
if (sendAll) {
|
||||||
qCDebug(avatars) << "AvatarData::toByteArray" << cullSmallChanges << sendAll
|
qCDebug(avatars) << "AvatarData::toByteArray" << cullSmallChanges << sendAll
|
||||||
<< "rotations:" << rotationSentCount << "translations:" << translationSentCount
|
<< "rotations:" << rotationSentCount << "translations:" << translationSentCount
|
||||||
|
@ -423,12 +553,14 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail) {
|
||||||
<< (destinationBuffer - beforeTranslations) << "="
|
<< (destinationBuffer - beforeTranslations) << "="
|
||||||
<< (destinationBuffer - startPosition);
|
<< (destinationBuffer - startPosition);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
return avatarDataByteArray.left(destinationBuffer - startPosition);
|
int avatarDataSize = destinationBuffer - startPosition;
|
||||||
|
return avatarDataByteArray.left(avatarDataSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: This is never used in a "distanceAdjust" mode, so it's ok that it doesn't use a variable minimum rotation/translation
|
||||||
void AvatarData::doneEncoding(bool cullSmallChanges) {
|
void AvatarData::doneEncoding(bool cullSmallChanges) {
|
||||||
// The server has finished sending this version of the joint-data to other nodes. Update _lastSentJointData.
|
// The server has finished sending this version of the joint-data to other nodes. Update _lastSentJointData.
|
||||||
QReadLocker readLock(&_jointDataLock);
|
QReadLocker readLock(&_jointDataLock);
|
||||||
|
@ -495,64 +627,101 @@ const unsigned char* unpackFauxJoint(const unsigned char* sourceBuffer, ThreadSa
|
||||||
// read data in packet starting at byte offset and return number of bytes parsed
|
// read data in packet starting at byte offset and return number of bytes parsed
|
||||||
int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
||||||
// lazily allocate memory for HeadData in case we're not an Avatar instance
|
// lazily allocate memory for HeadData in case we're not an Avatar instance
|
||||||
if (!_headData) {
|
lazyInitHeadData();
|
||||||
_headData = new HeadData(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t packetStateFlags = buffer.at(0);
|
AvatarDataPacket::HasFlags packetStateFlags;
|
||||||
bool minimumSent = oneAtBit(packetStateFlags, AVATARDATA_FLAGS_MINIMUM);
|
|
||||||
|
|
||||||
const unsigned char* startPosition = reinterpret_cast<const unsigned char*>(buffer.data());
|
const unsigned char* startPosition = reinterpret_cast<const unsigned char*>(buffer.data());
|
||||||
const unsigned char* endPosition = startPosition + buffer.size();
|
const unsigned char* endPosition = startPosition + buffer.size();
|
||||||
const unsigned char* sourceBuffer = startPosition + sizeof(packetStateFlags); // skip the flags!!
|
const unsigned char* sourceBuffer = startPosition;
|
||||||
|
|
||||||
|
// read the packet flags
|
||||||
|
memcpy(&packetStateFlags, sourceBuffer, sizeof(packetStateFlags));
|
||||||
|
sourceBuffer += sizeof(packetStateFlags);
|
||||||
|
|
||||||
|
#define HAS_FLAG(B,F) ((B & F) == F)
|
||||||
|
|
||||||
|
bool hasAvatarGlobalPosition = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_GLOBAL_POSITION);
|
||||||
|
bool hasAvatarBoundingBox = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_BOUNDING_BOX);
|
||||||
|
bool hasAvatarOrientation = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_ORIENTATION);
|
||||||
|
bool hasAvatarScale = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_SCALE);
|
||||||
|
bool hasLookAtPosition = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_LOOK_AT_POSITION);
|
||||||
|
bool hasAudioLoudness = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AUDIO_LOUDNESS);
|
||||||
|
bool hasSensorToWorldMatrix = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_SENSOR_TO_WORLD_MATRIX);
|
||||||
|
bool hasAdditionalFlags = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_ADDITIONAL_FLAGS);
|
||||||
|
bool hasParentInfo = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_PARENT_INFO);
|
||||||
|
bool hasAvatarLocalPosition = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_LOCAL_POSITION);
|
||||||
|
bool hasFaceTrackerInfo = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO);
|
||||||
|
bool hasJointData = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_JOINT_DATA);
|
||||||
|
|
||||||
// if this is the minimum, then it only includes the flags
|
|
||||||
if (minimumSent) {
|
|
||||||
memcpy(&_globalPosition, sourceBuffer, sizeof(_globalPosition));
|
|
||||||
sourceBuffer += sizeof(_globalPosition);
|
|
||||||
int numBytesRead = (sourceBuffer - startPosition);
|
|
||||||
_averageBytesReceived.updateAverage(numBytesRead);
|
|
||||||
return numBytesRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
quint64 now = usecTimestampNow();
|
quint64 now = usecTimestampNow();
|
||||||
|
|
||||||
PACKET_READ_CHECK(AvatarInfo, sizeof(AvatarDataPacket::AvatarInfo));
|
if (hasAvatarGlobalPosition) {
|
||||||
auto avatarInfo = reinterpret_cast<const AvatarDataPacket::AvatarInfo*>(sourceBuffer);
|
auto startSection = sourceBuffer;
|
||||||
sourceBuffer += sizeof(AvatarDataPacket::AvatarInfo);
|
|
||||||
|
|
||||||
glm::vec3 position = glm::vec3(avatarInfo->position[0], avatarInfo->position[1], avatarInfo->position[2]);
|
PACKET_READ_CHECK(AvatarGlobalPosition, sizeof(AvatarDataPacket::AvatarGlobalPosition));
|
||||||
_globalPosition = glm::vec3(avatarInfo->globalPosition[0], avatarInfo->globalPosition[1], avatarInfo->globalPosition[2]);
|
auto data = reinterpret_cast<const AvatarDataPacket::AvatarGlobalPosition*>(sourceBuffer);
|
||||||
_globalBoundingBoxCorner = glm::vec3(avatarInfo->globalBoundingBoxCorner[0], avatarInfo->globalBoundingBoxCorner[1], avatarInfo->globalBoundingBoxCorner[2]);
|
auto newValue = glm::vec3(data->globalPosition[0], data->globalPosition[1], data->globalPosition[2]);
|
||||||
if (isNaN(position)) {
|
if (_globalPosition != newValue) {
|
||||||
if (shouldLogError(now)) {
|
_globalPosition = newValue;
|
||||||
qCWarning(avatars) << "Discard AvatarData packet: position NaN, uuid " << getSessionUUID();
|
_globalPositionChanged = usecTimestampNow();
|
||||||
}
|
}
|
||||||
return buffer.size();
|
sourceBuffer += sizeof(AvatarDataPacket::AvatarGlobalPosition);
|
||||||
}
|
int numBytesRead = sourceBuffer - startSection;
|
||||||
setLocalPosition(position);
|
_globalPositionRate.increment(numBytesRead);
|
||||||
|
|
||||||
float pitch, yaw, roll;
|
// if we don't have a parent, make sure to also set our local position
|
||||||
unpackFloatAngleFromTwoByte(avatarInfo->localOrientation + 0, &yaw);
|
if (!hasParent()) {
|
||||||
unpackFloatAngleFromTwoByte(avatarInfo->localOrientation + 1, &pitch);
|
setLocalPosition(newValue);
|
||||||
unpackFloatAngleFromTwoByte(avatarInfo->localOrientation + 2, &roll);
|
|
||||||
if (isNaN(yaw) || isNaN(pitch) || isNaN(roll)) {
|
|
||||||
if (shouldLogError(now)) {
|
|
||||||
qCWarning(avatars) << "Discard AvatarData packet: localOriention is NaN, uuid " << getSessionUUID();
|
|
||||||
}
|
}
|
||||||
return buffer.size();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasAvatarBoundingBox) {
|
||||||
|
auto startSection = sourceBuffer;
|
||||||
|
|
||||||
|
PACKET_READ_CHECK(AvatarBoundingBox, sizeof(AvatarDataPacket::AvatarBoundingBox));
|
||||||
|
auto data = reinterpret_cast<const AvatarDataPacket::AvatarBoundingBox*>(sourceBuffer);
|
||||||
|
auto newDimensions = glm::vec3(data->avatarDimensions[0], data->avatarDimensions[1], data->avatarDimensions[2]);
|
||||||
|
auto newOffset = glm::vec3(data->boundOriginOffset[0], data->boundOriginOffset[1], data->boundOriginOffset[2]);
|
||||||
|
|
||||||
|
|
||||||
|
if (_globalBoundingBoxDimensions != newDimensions) {
|
||||||
|
_globalBoundingBoxDimensions = newDimensions;
|
||||||
|
_avatarBoundingBoxChanged = usecTimestampNow();
|
||||||
|
}
|
||||||
|
if (_globalBoundingBoxOffset != newOffset) {
|
||||||
|
_globalBoundingBoxOffset = newOffset;
|
||||||
|
_avatarBoundingBoxChanged = usecTimestampNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceBuffer += sizeof(AvatarDataPacket::AvatarBoundingBox);
|
||||||
|
int numBytesRead = sourceBuffer - startSection;
|
||||||
|
_avatarBoundingBoxRate.increment(numBytesRead);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasAvatarOrientation) {
|
||||||
|
auto startSection = sourceBuffer;
|
||||||
|
PACKET_READ_CHECK(AvatarOrientation, sizeof(AvatarDataPacket::AvatarOrientation));
|
||||||
|
glm::quat newOrientation;
|
||||||
|
sourceBuffer += unpackOrientationQuatFromSixBytes(sourceBuffer, newOrientation);
|
||||||
glm::quat currentOrientation = getLocalOrientation();
|
glm::quat currentOrientation = getLocalOrientation();
|
||||||
glm::vec3 newEulerAngles(pitch, yaw, roll);
|
|
||||||
glm::quat newOrientation = glm::quat(glm::radians(newEulerAngles));
|
|
||||||
if (currentOrientation != newOrientation) {
|
if (currentOrientation != newOrientation) {
|
||||||
_hasNewJointData = true;
|
_hasNewJointData = true;
|
||||||
setLocalOrientation(newOrientation);
|
setLocalOrientation(newOrientation);
|
||||||
}
|
}
|
||||||
|
int numBytesRead = sourceBuffer - startSection;
|
||||||
|
_avatarOrientationRate.increment(numBytesRead);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasAvatarScale) {
|
||||||
|
auto startSection = sourceBuffer;
|
||||||
|
|
||||||
|
PACKET_READ_CHECK(AvatarScale, sizeof(AvatarDataPacket::AvatarScale));
|
||||||
|
auto data = reinterpret_cast<const AvatarDataPacket::AvatarScale*>(sourceBuffer);
|
||||||
float scale;
|
float scale;
|
||||||
unpackFloatRatioFromTwoByte((uint8_t*)&avatarInfo->scale, scale);
|
unpackFloatRatioFromTwoByte((uint8_t*)&data->scale, scale);
|
||||||
if (isNaN(scale)) {
|
if (isNaN(scale)) {
|
||||||
if (shouldLogError(now)) {
|
if (shouldLogError(now)) {
|
||||||
qCWarning(avatars) << "Discard AvatarData packet: scale NaN, uuid " << getSessionUUID();
|
qCWarning(avatars) << "Discard AvatarData packet: scale NaN, uuid " << getSessionUUID();
|
||||||
|
@ -560,39 +729,78 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
||||||
return buffer.size();
|
return buffer.size();
|
||||||
}
|
}
|
||||||
setTargetScale(scale);
|
setTargetScale(scale);
|
||||||
|
sourceBuffer += sizeof(AvatarDataPacket::AvatarScale);
|
||||||
|
int numBytesRead = sourceBuffer - startSection;
|
||||||
|
_avatarScaleRate.increment(numBytesRead);
|
||||||
|
}
|
||||||
|
|
||||||
glm::vec3 lookAt = glm::vec3(avatarInfo->lookAtPosition[0], avatarInfo->lookAtPosition[1], avatarInfo->lookAtPosition[2]);
|
if (hasLookAtPosition) {
|
||||||
|
auto startSection = sourceBuffer;
|
||||||
|
|
||||||
|
PACKET_READ_CHECK(LookAtPosition, sizeof(AvatarDataPacket::LookAtPosition));
|
||||||
|
auto data = reinterpret_cast<const AvatarDataPacket::LookAtPosition*>(sourceBuffer);
|
||||||
|
glm::vec3 lookAt = glm::vec3(data->lookAtPosition[0], data->lookAtPosition[1], data->lookAtPosition[2]);
|
||||||
if (isNaN(lookAt)) {
|
if (isNaN(lookAt)) {
|
||||||
if (shouldLogError(now)) {
|
if (shouldLogError(now)) {
|
||||||
qCWarning(avatars) << "Discard AvatarData packet: lookAtPosition is NaN, uuid " << getSessionUUID();
|
qCWarning(avatars) << "Discard AvatarData packet: lookAtPosition is NaN, uuid " << getSessionUUID();
|
||||||
}
|
}
|
||||||
return buffer.size();
|
return buffer.size();
|
||||||
}
|
}
|
||||||
_headData->_lookAtPosition = lookAt;
|
_headData->setLookAtPosition(lookAt);
|
||||||
|
sourceBuffer += sizeof(AvatarDataPacket::LookAtPosition);
|
||||||
|
int numBytesRead = sourceBuffer - startSection;
|
||||||
|
_lookAtPositionRate.increment(numBytesRead);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasAudioLoudness) {
|
||||||
|
auto startSection = sourceBuffer;
|
||||||
|
|
||||||
|
PACKET_READ_CHECK(AudioLoudness, sizeof(AvatarDataPacket::AudioLoudness));
|
||||||
|
auto data = reinterpret_cast<const AvatarDataPacket::AudioLoudness*>(sourceBuffer);
|
||||||
|
float audioLoudness;
|
||||||
|
audioLoudness = unpackFloatGainFromByte(data->audioLoudness) * AUDIO_LOUDNESS_SCALE;
|
||||||
|
sourceBuffer += sizeof(AvatarDataPacket::AudioLoudness);
|
||||||
|
|
||||||
float audioLoudness = avatarInfo->audioLoudness;
|
|
||||||
if (isNaN(audioLoudness)) {
|
if (isNaN(audioLoudness)) {
|
||||||
if (shouldLogError(now)) {
|
if (shouldLogError(now)) {
|
||||||
qCWarning(avatars) << "Discard AvatarData packet: audioLoudness is NaN, uuid " << getSessionUUID();
|
qCWarning(avatars) << "Discard AvatarData packet: audioLoudness is NaN, uuid " << getSessionUUID();
|
||||||
}
|
}
|
||||||
return buffer.size();
|
return buffer.size();
|
||||||
}
|
}
|
||||||
_headData->_audioLoudness = audioLoudness;
|
_headData->setAudioLoudness(audioLoudness);
|
||||||
|
int numBytesRead = sourceBuffer - startSection;
|
||||||
|
_audioLoudnessRate.increment(numBytesRead);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasSensorToWorldMatrix) {
|
||||||
|
auto startSection = sourceBuffer;
|
||||||
|
|
||||||
|
PACKET_READ_CHECK(SensorToWorldMatrix, sizeof(AvatarDataPacket::SensorToWorldMatrix));
|
||||||
|
auto data = reinterpret_cast<const AvatarDataPacket::SensorToWorldMatrix*>(sourceBuffer);
|
||||||
glm::quat sensorToWorldQuat;
|
glm::quat sensorToWorldQuat;
|
||||||
unpackOrientationQuatFromSixBytes(avatarInfo->sensorToWorldQuat, sensorToWorldQuat);
|
unpackOrientationQuatFromSixBytes(data->sensorToWorldQuat, sensorToWorldQuat);
|
||||||
float sensorToWorldScale;
|
float sensorToWorldScale;
|
||||||
unpackFloatScalarFromSignedTwoByteFixed((int16_t*)&avatarInfo->sensorToWorldScale, &sensorToWorldScale, SENSOR_TO_WORLD_SCALE_RADIX);
|
unpackFloatScalarFromSignedTwoByteFixed((int16_t*)&data->sensorToWorldScale, &sensorToWorldScale, SENSOR_TO_WORLD_SCALE_RADIX);
|
||||||
glm::vec3 sensorToWorldTrans(avatarInfo->sensorToWorldTrans[0], avatarInfo->sensorToWorldTrans[1], avatarInfo->sensorToWorldTrans[2]);
|
glm::vec3 sensorToWorldTrans(data->sensorToWorldTrans[0], data->sensorToWorldTrans[1], data->sensorToWorldTrans[2]);
|
||||||
glm::mat4 sensorToWorldMatrix = createMatFromScaleQuatAndPos(glm::vec3(sensorToWorldScale), sensorToWorldQuat, sensorToWorldTrans);
|
glm::mat4 sensorToWorldMatrix = createMatFromScaleQuatAndPos(glm::vec3(sensorToWorldScale), sensorToWorldQuat, sensorToWorldTrans);
|
||||||
|
if (_sensorToWorldMatrixCache.get() != sensorToWorldMatrix) {
|
||||||
_sensorToWorldMatrixCache.set(sensorToWorldMatrix);
|
_sensorToWorldMatrixCache.set(sensorToWorldMatrix);
|
||||||
|
_sensorToWorldMatrixChanged = usecTimestampNow();
|
||||||
|
}
|
||||||
|
sourceBuffer += sizeof(AvatarDataPacket::SensorToWorldMatrix);
|
||||||
|
int numBytesRead = sourceBuffer - startSection;
|
||||||
|
_sensorToWorldRate.increment(numBytesRead);
|
||||||
|
}
|
||||||
|
|
||||||
{ // bitFlags and face data
|
if (hasAdditionalFlags) {
|
||||||
uint8_t bitItems = avatarInfo->flags;
|
auto startSection = sourceBuffer;
|
||||||
|
|
||||||
|
PACKET_READ_CHECK(AdditionalFlags, sizeof(AvatarDataPacket::AdditionalFlags));
|
||||||
|
auto data = reinterpret_cast<const AvatarDataPacket::AdditionalFlags*>(sourceBuffer);
|
||||||
|
uint8_t bitItems = data->flags;
|
||||||
|
|
||||||
// key state, stored as a semi-nibble in the bitItems
|
// key state, stored as a semi-nibble in the bitItems
|
||||||
_keyState = (KeyState)getSemiNibbleAt(bitItems, KEY_STATE_START_BIT);
|
auto newKeyState = (KeyState)getSemiNibbleAt(bitItems, KEY_STATE_START_BIT);
|
||||||
|
|
||||||
// hand state, stored as a semi-nibble plus a bit in the bitItems
|
// hand state, stored as a semi-nibble plus a bit in the bitItems
|
||||||
// we store the hand state as well as other items in a shared bitset. The hand state is an octal, but is split
|
// we store the hand state as well as other items in a shared bitset. The hand state is an octal, but is split
|
||||||
|
@ -601,26 +809,79 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
||||||
// |x,x|H0,H1|x,x,x|H2|
|
// |x,x|H0,H1|x,x,x|H2|
|
||||||
// +---+-----+-----+--+
|
// +---+-----+-----+--+
|
||||||
// Hand state - H0,H1,H2 is found in the 3rd, 4th, and 8th bits
|
// Hand state - H0,H1,H2 is found in the 3rd, 4th, and 8th bits
|
||||||
_handState = getSemiNibbleAt(bitItems, HAND_STATE_START_BIT)
|
auto newHandState = getSemiNibbleAt(bitItems, HAND_STATE_START_BIT)
|
||||||
+ (oneAtBit(bitItems, HAND_STATE_FINGER_POINTING_BIT) ? IS_FINGER_POINTING_FLAG : 0);
|
+ (oneAtBit(bitItems, HAND_STATE_FINGER_POINTING_BIT) ? IS_FINGER_POINTING_FLAG : 0);
|
||||||
|
|
||||||
_headData->_isFaceTrackerConnected = oneAtBit(bitItems, IS_FACESHIFT_CONNECTED);
|
auto newFaceTrackerConnected = oneAtBit(bitItems, IS_FACESHIFT_CONNECTED);
|
||||||
_headData->_isEyeTrackerConnected = oneAtBit(bitItems, IS_EYE_TRACKER_CONNECTED);
|
auto newEyeTrackerConnected = oneAtBit(bitItems, IS_EYE_TRACKER_CONNECTED);
|
||||||
bool hasReferential = oneAtBit(bitItems, HAS_REFERENTIAL);
|
|
||||||
|
|
||||||
if (hasReferential) {
|
bool keyStateChanged = (_keyState != newKeyState);
|
||||||
|
bool handStateChanged = (_handState != newHandState);
|
||||||
|
bool faceStateChanged = (_headData->_isFaceTrackerConnected != newFaceTrackerConnected);
|
||||||
|
bool eyeStateChanged = (_headData->_isEyeTrackerConnected != newEyeTrackerConnected);
|
||||||
|
bool somethingChanged = keyStateChanged || handStateChanged || faceStateChanged || eyeStateChanged;
|
||||||
|
|
||||||
|
_keyState = newKeyState;
|
||||||
|
_handState = newHandState;
|
||||||
|
_headData->_isFaceTrackerConnected = newFaceTrackerConnected;
|
||||||
|
_headData->_isEyeTrackerConnected = newEyeTrackerConnected;
|
||||||
|
|
||||||
|
sourceBuffer += sizeof(AvatarDataPacket::AdditionalFlags);
|
||||||
|
|
||||||
|
if (somethingChanged) {
|
||||||
|
_additionalFlagsChanged = usecTimestampNow();
|
||||||
|
}
|
||||||
|
int numBytesRead = sourceBuffer - startSection;
|
||||||
|
_additionalFlagsRate.increment(numBytesRead);
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME -- make sure to handle the existance of a parent vs a change in the parent...
|
||||||
|
//bool hasReferential = oneAtBit(bitItems, HAS_REFERENTIAL);
|
||||||
|
if (hasParentInfo) {
|
||||||
|
auto startSection = sourceBuffer;
|
||||||
PACKET_READ_CHECK(ParentInfo, sizeof(AvatarDataPacket::ParentInfo));
|
PACKET_READ_CHECK(ParentInfo, sizeof(AvatarDataPacket::ParentInfo));
|
||||||
auto parentInfo = reinterpret_cast<const AvatarDataPacket::ParentInfo*>(sourceBuffer);
|
auto parentInfo = reinterpret_cast<const AvatarDataPacket::ParentInfo*>(sourceBuffer);
|
||||||
sourceBuffer += sizeof(AvatarDataPacket::ParentInfo);
|
sourceBuffer += sizeof(AvatarDataPacket::ParentInfo);
|
||||||
|
|
||||||
QByteArray byteArray((const char*)parentInfo->parentUUID, NUM_BYTES_RFC4122_UUID);
|
QByteArray byteArray((const char*)parentInfo->parentUUID, NUM_BYTES_RFC4122_UUID);
|
||||||
_parentID = QUuid::fromRfc4122(byteArray);
|
|
||||||
|
auto newParentID = QUuid::fromRfc4122(byteArray);
|
||||||
|
|
||||||
|
if ((_parentID != newParentID) || (_parentJointIndex = parentInfo->parentJointIndex)) {
|
||||||
|
_parentID = newParentID;
|
||||||
_parentJointIndex = parentInfo->parentJointIndex;
|
_parentJointIndex = parentInfo->parentJointIndex;
|
||||||
|
_parentChanged = usecTimestampNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
int numBytesRead = sourceBuffer - startSection;
|
||||||
|
_parentInfoRate.increment(numBytesRead);
|
||||||
} else {
|
} else {
|
||||||
|
// FIXME - this aint totally right, for switching to parent/no-parent
|
||||||
_parentID = QUuid();
|
_parentID = QUuid();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_headData->_isFaceTrackerConnected) {
|
if (hasAvatarLocalPosition) {
|
||||||
|
assert(hasParent()); // we shouldn't have local position unless we have a parent
|
||||||
|
auto startSection = sourceBuffer;
|
||||||
|
|
||||||
|
PACKET_READ_CHECK(AvatarLocalPosition, sizeof(AvatarDataPacket::AvatarLocalPosition));
|
||||||
|
auto data = reinterpret_cast<const AvatarDataPacket::AvatarLocalPosition*>(sourceBuffer);
|
||||||
|
glm::vec3 position = glm::vec3(data->localPosition[0], data->localPosition[1], data->localPosition[2]);
|
||||||
|
if (isNaN(position)) {
|
||||||
|
if (shouldLogError(now)) {
|
||||||
|
qCWarning(avatars) << "Discard AvatarData packet: position NaN, uuid " << getSessionUUID();
|
||||||
|
}
|
||||||
|
return buffer.size();
|
||||||
|
}
|
||||||
|
setLocalPosition(position);
|
||||||
|
sourceBuffer += sizeof(AvatarDataPacket::AvatarLocalPosition);
|
||||||
|
int numBytesRead = sourceBuffer - startSection;
|
||||||
|
_localPositionRate.increment(numBytesRead);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasFaceTrackerInfo) {
|
||||||
|
auto startSection = sourceBuffer;
|
||||||
|
|
||||||
PACKET_READ_CHECK(FaceTrackerInfo, sizeof(AvatarDataPacket::FaceTrackerInfo));
|
PACKET_READ_CHECK(FaceTrackerInfo, sizeof(AvatarDataPacket::FaceTrackerInfo));
|
||||||
auto faceTrackerInfo = reinterpret_cast<const AvatarDataPacket::FaceTrackerInfo*>(sourceBuffer);
|
auto faceTrackerInfo = reinterpret_cast<const AvatarDataPacket::FaceTrackerInfo*>(sourceBuffer);
|
||||||
sourceBuffer += sizeof(AvatarDataPacket::FaceTrackerInfo);
|
sourceBuffer += sizeof(AvatarDataPacket::FaceTrackerInfo);
|
||||||
|
@ -636,12 +897,15 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
||||||
_headData->_blendshapeCoefficients.resize(numCoefficients); // make sure there's room for the copy!
|
_headData->_blendshapeCoefficients.resize(numCoefficients); // make sure there's room for the copy!
|
||||||
memcpy(_headData->_blendshapeCoefficients.data(), sourceBuffer, coefficientsSize);
|
memcpy(_headData->_blendshapeCoefficients.data(), sourceBuffer, coefficientsSize);
|
||||||
sourceBuffer += coefficientsSize;
|
sourceBuffer += coefficientsSize;
|
||||||
|
int numBytesRead = sourceBuffer - startSection;
|
||||||
|
_faceTrackerRate.increment(numBytesRead);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
if (hasJointData) {
|
||||||
|
auto startSection = sourceBuffer;
|
||||||
|
|
||||||
PACKET_READ_CHECK(NumJoints, sizeof(uint8_t));
|
PACKET_READ_CHECK(NumJoints, sizeof(uint8_t));
|
||||||
int numJoints = *sourceBuffer++;
|
int numJoints = *sourceBuffer++;
|
||||||
|
|
||||||
const int bytesOfValidity = (int)ceil((float)numJoints / (float)BITS_IN_BYTE);
|
const int bytesOfValidity = (int)ceil((float)numJoints / (float)BITS_IN_BYTE);
|
||||||
PACKET_READ_CHECK(JointRotationValidityBits, bytesOfValidity);
|
PACKET_READ_CHECK(JointRotationValidityBits, bytesOfValidity);
|
||||||
|
|
||||||
|
@ -714,23 +978,61 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef WANT_DEBUG
|
#ifdef WANT_DEBUG
|
||||||
if (numValidJointRotations > 15) {
|
if (numValidJointRotations > 15) {
|
||||||
qCDebug(avatars) << "RECEIVING -- rotations:" << numValidJointRotations
|
qCDebug(avatars) << "RECEIVING -- rotations:" << numValidJointRotations
|
||||||
<< "translations:" << numValidJointTranslations
|
<< "translations:" << numValidJointTranslations
|
||||||
<< "size:" << (int)(sourceBuffer - startPosition);
|
<< "size:" << (int)(sourceBuffer - startPosition);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// faux joints
|
// faux joints
|
||||||
sourceBuffer = unpackFauxJoint(sourceBuffer, _controllerLeftHandMatrixCache);
|
sourceBuffer = unpackFauxJoint(sourceBuffer, _controllerLeftHandMatrixCache);
|
||||||
sourceBuffer = unpackFauxJoint(sourceBuffer, _controllerRightHandMatrixCache);
|
sourceBuffer = unpackFauxJoint(sourceBuffer, _controllerRightHandMatrixCache);
|
||||||
|
|
||||||
|
int numBytesRead = sourceBuffer - startSection;
|
||||||
|
_jointDataRate.increment(numBytesRead);
|
||||||
|
}
|
||||||
|
|
||||||
int numBytesRead = sourceBuffer - startPosition;
|
int numBytesRead = sourceBuffer - startPosition;
|
||||||
_averageBytesReceived.updateAverage(numBytesRead);
|
_averageBytesReceived.updateAverage(numBytesRead);
|
||||||
|
|
||||||
|
_parseBufferRate.increment(numBytesRead);
|
||||||
|
|
||||||
return numBytesRead;
|
return numBytesRead;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float AvatarData::getDataRate(const QString& rateName) {
|
||||||
|
if (rateName == "") {
|
||||||
|
return _parseBufferRate.rate() / BYTES_PER_KILOBIT;
|
||||||
|
} else if (rateName == "globalPosition") {
|
||||||
|
return _globalPositionRate.rate() / BYTES_PER_KILOBIT;
|
||||||
|
} else if (rateName == "localPosition") {
|
||||||
|
return _localPositionRate.rate() / BYTES_PER_KILOBIT;
|
||||||
|
} else if (rateName == "avatarBoundingBox") {
|
||||||
|
return _avatarBoundingBoxRate.rate() / BYTES_PER_KILOBIT;
|
||||||
|
} else if (rateName == "avatarOrientation") {
|
||||||
|
return _avatarOrientationRate.rate() / BYTES_PER_KILOBIT;
|
||||||
|
} else if (rateName == "avatarScale") {
|
||||||
|
return _avatarScaleRate.rate() / BYTES_PER_KILOBIT;
|
||||||
|
} else if (rateName == "lookAtPosition") {
|
||||||
|
return _lookAtPositionRate.rate() / BYTES_PER_KILOBIT;
|
||||||
|
} else if (rateName == "audioLoudness") {
|
||||||
|
return _audioLoudnessRate.rate() / BYTES_PER_KILOBIT;
|
||||||
|
} else if (rateName == "sensorToWorkMatrix") {
|
||||||
|
return _sensorToWorldRate.rate() / BYTES_PER_KILOBIT;
|
||||||
|
} else if (rateName == "additionalFlags") {
|
||||||
|
return _additionalFlagsRate.rate() / BYTES_PER_KILOBIT;
|
||||||
|
} else if (rateName == "parentInfo") {
|
||||||
|
return _parentInfoRate.rate() / BYTES_PER_KILOBIT;
|
||||||
|
} else if (rateName == "faceTracker") {
|
||||||
|
return _faceTrackerRate.rate() / BYTES_PER_KILOBIT;
|
||||||
|
} else if (rateName == "jointData") {
|
||||||
|
return _jointDataRate.rate() / BYTES_PER_KILOBIT;
|
||||||
|
}
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
int AvatarData::getAverageBytesReceivedPerSecond() const {
|
int AvatarData::getAverageBytesReceivedPerSecond() const {
|
||||||
return lrint(_averageBytesReceived.getAverageSampleValuePerSecond());
|
return lrint(_averageBytesReceived.getAverageSampleValuePerSecond());
|
||||||
}
|
}
|
||||||
|
@ -1178,6 +1480,7 @@ void AvatarData::detachAll(const QString& modelURL, const QString& jointName) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarData::setJointMappingsFromNetworkReply() {
|
void AvatarData::setJointMappingsFromNetworkReply() {
|
||||||
|
|
||||||
QNetworkReply* networkReply = static_cast<QNetworkReply*>(sender());
|
QNetworkReply* networkReply = static_cast<QNetworkReply*>(sender());
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -1225,9 +1528,17 @@ void AvatarData::sendAvatarDataPacket() {
|
||||||
|
|
||||||
// about 2% of the time, we send a full update (meaning, we transmit all the joint data), even if nothing has changed.
|
// about 2% of the time, we send a full update (meaning, we transmit all the joint data), even if nothing has changed.
|
||||||
// this is to guard against a joint moving once, the packet getting lost, and the joint never moving again.
|
// this is to guard against a joint moving once, the packet getting lost, and the joint never moving again.
|
||||||
QByteArray avatarByteArray = toByteArray((randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO) ? SendAllData : CullSmallData);
|
|
||||||
|
|
||||||
doneEncoding(true); // FIXME - doneEncoding() takes a bool for culling small changes, that's janky!
|
bool cullSmallData = (randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO);
|
||||||
|
auto dataDetail = cullSmallData ? SendAllData : CullSmallData;
|
||||||
|
QVector<JointData> lastSentJointData;
|
||||||
|
{
|
||||||
|
QReadLocker readLock(&_jointDataLock);
|
||||||
|
_lastSentJointData.resize(_jointData.size());
|
||||||
|
lastSentJointData = _lastSentJointData;
|
||||||
|
}
|
||||||
|
QByteArray avatarByteArray = toByteArray(dataDetail, 0, lastSentJointData);
|
||||||
|
doneEncoding(cullSmallData);
|
||||||
|
|
||||||
static AvatarDataSequenceNumber sequenceNumber = 0;
|
static AvatarDataSequenceNumber sequenceNumber = 0;
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,7 @@ typedef unsigned long long quint64;
|
||||||
#include <Packed.h>
|
#include <Packed.h>
|
||||||
#include <ThreadSafeValueCache.h>
|
#include <ThreadSafeValueCache.h>
|
||||||
#include <SharedUtil.h>
|
#include <SharedUtil.h>
|
||||||
|
#include <shared/RateCounter.h>
|
||||||
|
|
||||||
#include "AABox.h"
|
#include "AABox.h"
|
||||||
#include "HeadData.h"
|
#include "HeadData.h"
|
||||||
|
@ -99,6 +100,7 @@ const int IS_EYE_TRACKER_CONNECTED = 5; // 6th bit (was CHAT_CIRCLING)
|
||||||
const int HAS_REFERENTIAL = 6; // 7th bit
|
const int HAS_REFERENTIAL = 6; // 7th bit
|
||||||
const int HAND_STATE_FINGER_POINTING_BIT = 7; // 8th bit
|
const int HAND_STATE_FINGER_POINTING_BIT = 7; // 8th bit
|
||||||
|
|
||||||
|
|
||||||
const char HAND_STATE_NULL = 0;
|
const char HAND_STATE_NULL = 0;
|
||||||
const char LEFT_HAND_POINTING_FLAG = 1;
|
const char LEFT_HAND_POINTING_FLAG = 1;
|
||||||
const char RIGHT_HAND_POINTING_FLAG = 2;
|
const char RIGHT_HAND_POINTING_FLAG = 2;
|
||||||
|
@ -108,6 +110,131 @@ const char IS_FINGER_POINTING_FLAG = 4;
|
||||||
// before the "header" structure
|
// before the "header" structure
|
||||||
const char AVATARDATA_FLAGS_MINIMUM = 0;
|
const char AVATARDATA_FLAGS_MINIMUM = 0;
|
||||||
|
|
||||||
|
using SmallFloat = uint16_t; // a compressed float with less precision, user defined radix
|
||||||
|
|
||||||
|
namespace AvatarDataPacket {
|
||||||
|
|
||||||
|
// NOTE: every time AvatarData is sent from mixer to client, it also includes the GUIID for the session
|
||||||
|
// this is 16bytes of data at 45hz that's 5.76kbps
|
||||||
|
// it might be nice to use a dictionary to compress that
|
||||||
|
|
||||||
|
// Packet State Flags - we store the details about the existence of other records in this bitset:
|
||||||
|
// AvatarGlobalPosition, Avatar Faceshift, eye tracking, and existence of
|
||||||
|
using HasFlags = uint16_t;
|
||||||
|
const HasFlags PACKET_HAS_AVATAR_GLOBAL_POSITION = 1U << 0;
|
||||||
|
const HasFlags PACKET_HAS_AVATAR_BOUNDING_BOX = 1U << 1;
|
||||||
|
const HasFlags PACKET_HAS_AVATAR_ORIENTATION = 1U << 2;
|
||||||
|
const HasFlags PACKET_HAS_AVATAR_SCALE = 1U << 3;
|
||||||
|
const HasFlags PACKET_HAS_LOOK_AT_POSITION = 1U << 4;
|
||||||
|
const HasFlags PACKET_HAS_AUDIO_LOUDNESS = 1U << 5;
|
||||||
|
const HasFlags PACKET_HAS_SENSOR_TO_WORLD_MATRIX = 1U << 6;
|
||||||
|
const HasFlags PACKET_HAS_ADDITIONAL_FLAGS = 1U << 7;
|
||||||
|
const HasFlags PACKET_HAS_PARENT_INFO = 1U << 8;
|
||||||
|
const HasFlags PACKET_HAS_AVATAR_LOCAL_POSITION = 1U << 9;
|
||||||
|
const HasFlags PACKET_HAS_FACE_TRACKER_INFO = 1U << 10;
|
||||||
|
const HasFlags PACKET_HAS_JOINT_DATA = 1U << 11;
|
||||||
|
|
||||||
|
// NOTE: AvatarDataPackets start with a uint16_t sequence number that is not reflected in the Header structure.
|
||||||
|
|
||||||
|
PACKED_BEGIN struct Header {
|
||||||
|
HasFlags packetHasFlags; // state flags, indicated which additional records are included in the packet
|
||||||
|
} PACKED_END;
|
||||||
|
const size_t HEADER_SIZE = 2;
|
||||||
|
|
||||||
|
PACKED_BEGIN struct AvatarGlobalPosition {
|
||||||
|
float globalPosition[3]; // avatar's position
|
||||||
|
} PACKED_END;
|
||||||
|
const size_t AVATAR_GLOBAL_POSITION_SIZE = 12;
|
||||||
|
|
||||||
|
PACKED_BEGIN struct AvatarBoundingBox {
|
||||||
|
float avatarDimensions[3]; // avatar's bounding box in world space units, but relative to the position.
|
||||||
|
float boundOriginOffset[3]; // offset from the position of the avatar to the origin of the bounding box
|
||||||
|
} PACKED_END;
|
||||||
|
const size_t AVATAR_BOUNDING_BOX_SIZE = 24;
|
||||||
|
|
||||||
|
|
||||||
|
using SixByteQuat = uint8_t[6];
|
||||||
|
PACKED_BEGIN struct AvatarOrientation {
|
||||||
|
SixByteQuat avatarOrientation; // encodeded and compressed by packOrientationQuatToSixBytes()
|
||||||
|
} PACKED_END;
|
||||||
|
const size_t AVATAR_ORIENTATION_SIZE = 6;
|
||||||
|
|
||||||
|
PACKED_BEGIN struct AvatarScale {
|
||||||
|
SmallFloat scale; // avatar's scale, compressed by packFloatRatioToTwoByte()
|
||||||
|
} PACKED_END;
|
||||||
|
const size_t AVATAR_SCALE_SIZE = 2;
|
||||||
|
|
||||||
|
PACKED_BEGIN struct LookAtPosition {
|
||||||
|
float lookAtPosition[3]; // world space position that eyes are focusing on.
|
||||||
|
// FIXME - unless the person has an eye tracker, this is simulated...
|
||||||
|
// a) maybe we can just have the client calculate this
|
||||||
|
// b) at distance this will be hard to discern and can likely be
|
||||||
|
// descimated or dropped completely
|
||||||
|
//
|
||||||
|
// POTENTIAL SAVINGS - 12 bytes
|
||||||
|
} PACKED_END;
|
||||||
|
const size_t LOOK_AT_POSITION_SIZE = 12;
|
||||||
|
|
||||||
|
PACKED_BEGIN struct AudioLoudness {
|
||||||
|
uint8_t audioLoudness; // current loudness of microphone compressed with packFloatGainToByte()
|
||||||
|
} PACKED_END;
|
||||||
|
const size_t AUDIO_LOUDNESS_SIZE = 1;
|
||||||
|
|
||||||
|
PACKED_BEGIN struct SensorToWorldMatrix {
|
||||||
|
// FIXME - these 20 bytes are only used by viewers if my avatar has "attachments"
|
||||||
|
// we could save these bytes if no attachments are active.
|
||||||
|
//
|
||||||
|
// POTENTIAL SAVINGS - 20 bytes
|
||||||
|
|
||||||
|
SixByteQuat sensorToWorldQuat; // 6 byte compressed quaternion part of sensor to world matrix
|
||||||
|
uint16_t sensorToWorldScale; // uniform scale of sensor to world matrix
|
||||||
|
float sensorToWorldTrans[3]; // fourth column of sensor to world matrix
|
||||||
|
// FIXME - sensorToWorldTrans might be able to be better compressed if it was
|
||||||
|
// relative to the avatar position.
|
||||||
|
} PACKED_END;
|
||||||
|
const size_t SENSOR_TO_WORLD_SIZE = 20;
|
||||||
|
|
||||||
|
PACKED_BEGIN struct AdditionalFlags {
|
||||||
|
uint8_t flags; // additional flags: hand state, key state, eye tracking
|
||||||
|
} PACKED_END;
|
||||||
|
const size_t ADDITIONAL_FLAGS_SIZE = 1;
|
||||||
|
|
||||||
|
// only present if HAS_REFERENTIAL flag is set in AvatarInfo.flags
|
||||||
|
PACKED_BEGIN struct ParentInfo {
|
||||||
|
uint8_t parentUUID[16]; // rfc 4122 encoded
|
||||||
|
uint16_t parentJointIndex;
|
||||||
|
} PACKED_END;
|
||||||
|
const size_t PARENT_INFO_SIZE = 18;
|
||||||
|
|
||||||
|
// will only ever be included if the avatar has a parent but can change independent of changes to parent info
|
||||||
|
// and so we keep it a separate record
|
||||||
|
PACKED_BEGIN struct AvatarLocalPosition {
|
||||||
|
float localPosition[3]; // parent frame translation of the avatar
|
||||||
|
} PACKED_END;
|
||||||
|
const size_t AVATAR_LOCAL_POSITION_SIZE = 12;
|
||||||
|
|
||||||
|
// only present if IS_FACESHIFT_CONNECTED flag is set in AvatarInfo.flags
|
||||||
|
PACKED_BEGIN struct FaceTrackerInfo {
|
||||||
|
float leftEyeBlink;
|
||||||
|
float rightEyeBlink;
|
||||||
|
float averageLoudness;
|
||||||
|
float browAudioLift;
|
||||||
|
uint8_t numBlendshapeCoefficients;
|
||||||
|
// float blendshapeCoefficients[numBlendshapeCoefficients];
|
||||||
|
} PACKED_END;
|
||||||
|
const size_t FACE_TRACKER_INFO_SIZE = 17;
|
||||||
|
|
||||||
|
// variable length structure follows
|
||||||
|
/*
|
||||||
|
struct JointData {
|
||||||
|
uint8_t numJoints;
|
||||||
|
uint8_t rotationValidityBits[ceil(numJoints / 8)]; // one bit per joint, if true then a compressed rotation follows.
|
||||||
|
SixByteQuat rotation[numValidRotations]; // encodeded and compressed by packOrientationQuatToSixBytes()
|
||||||
|
uint8_t translationValidityBits[ceil(numJoints / 8)]; // one bit per joint, if true then a compressed translation follows.
|
||||||
|
SixByteTrans translation[numValidTranslations]; // encodeded and compressed by packFloatVec3ToSignedTwoByteFixed()
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
static const float MAX_AVATAR_SCALE = 1000.0f;
|
static const float MAX_AVATAR_SCALE = 1000.0f;
|
||||||
static const float MIN_AVATAR_SCALE = .005f;
|
static const float MIN_AVATAR_SCALE = .005f;
|
||||||
|
@ -125,6 +252,16 @@ const float AVATAR_SEND_FULL_UPDATE_RATIO = 0.02f;
|
||||||
const float AVATAR_MIN_ROTATION_DOT = 0.9999999f;
|
const float AVATAR_MIN_ROTATION_DOT = 0.9999999f;
|
||||||
const float AVATAR_MIN_TRANSLATION = 0.0001f;
|
const float AVATAR_MIN_TRANSLATION = 0.0001f;
|
||||||
|
|
||||||
|
const float ROTATION_CHANGE_15D = 0.9914449f;
|
||||||
|
const float ROTATION_CHANGE_45D = 0.9238795f;
|
||||||
|
const float ROTATION_CHANGE_90D = 0.7071068f;
|
||||||
|
const float ROTATION_CHANGE_179D = 0.0087266f;
|
||||||
|
|
||||||
|
const float AVATAR_DISTANCE_LEVEL_1 = 10.0f;
|
||||||
|
const float AVATAR_DISTANCE_LEVEL_2 = 100.0f;
|
||||||
|
const float AVATAR_DISTANCE_LEVEL_3 = 1000.0f;
|
||||||
|
const float AVATAR_DISTANCE_LEVEL_4 = 10000.0f;
|
||||||
|
|
||||||
|
|
||||||
// Where one's own Avatar begins in the world (will be overwritten if avatar data file is found).
|
// Where one's own Avatar begins in the world (will be overwritten if avatar data file is found).
|
||||||
// This is the start location in the Sandbox (xyz: 6270, 211, 6000).
|
// This is the start location in the Sandbox (xyz: 6270, 211, 6000).
|
||||||
|
@ -214,7 +351,9 @@ public:
|
||||||
SendAllData
|
SendAllData
|
||||||
} AvatarDataDetail;
|
} AvatarDataDetail;
|
||||||
|
|
||||||
virtual QByteArray toByteArray(AvatarDataDetail dataDetail);
|
virtual QByteArray toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector<JointData>& lastSentJointData,
|
||||||
|
bool distanceAdjust = false, glm::vec3 viewerPosition = glm::vec3(0), QVector<JointData>* sentJointDataOut = nullptr);
|
||||||
|
|
||||||
virtual void doneEncoding(bool cullSmallChanges);
|
virtual void doneEncoding(bool cullSmallChanges);
|
||||||
|
|
||||||
/// \return true if an error should be logged
|
/// \return true if an error should be logged
|
||||||
|
@ -265,10 +404,11 @@ public:
|
||||||
virtual void setTargetScale(float targetScale);
|
virtual void setTargetScale(float targetScale);
|
||||||
|
|
||||||
float getDomainLimitedScale() const { return glm::clamp(_targetScale, _domainMinimumScale, _domainMaximumScale); }
|
float getDomainLimitedScale() const { return glm::clamp(_targetScale, _domainMinimumScale, _domainMaximumScale); }
|
||||||
|
|
||||||
void setDomainMinimumScale(float domainMinimumScale)
|
void setDomainMinimumScale(float domainMinimumScale)
|
||||||
{ _domainMinimumScale = glm::clamp(domainMinimumScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); }
|
{ _domainMinimumScale = glm::clamp(domainMinimumScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); _scaleChanged = usecTimestampNow(); }
|
||||||
void setDomainMaximumScale(float domainMaximumScale)
|
void setDomainMaximumScale(float domainMaximumScale)
|
||||||
{ _domainMaximumScale = glm::clamp(domainMaximumScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); }
|
{ _domainMaximumScale = glm::clamp(domainMaximumScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); _scaleChanged = usecTimestampNow(); }
|
||||||
|
|
||||||
// Hand State
|
// Hand State
|
||||||
Q_INVOKABLE void setHandState(char s) { _handState = s; }
|
Q_INVOKABLE void setHandState(char s) { _handState = s; }
|
||||||
|
@ -375,7 +515,7 @@ public:
|
||||||
void fromJson(const QJsonObject& json);
|
void fromJson(const QJsonObject& json);
|
||||||
|
|
||||||
glm::vec3 getClientGlobalPosition() { return _globalPosition; }
|
glm::vec3 getClientGlobalPosition() { return _globalPosition; }
|
||||||
glm::vec3 getGlobalBoundingBoxCorner() { return _globalBoundingBoxCorner; }
|
glm::vec3 getGlobalBoundingBoxCorner() { return _globalPosition + _globalBoundingBoxOffset - _globalBoundingBoxDimensions; }
|
||||||
|
|
||||||
Q_INVOKABLE AvatarEntityMap getAvatarEntityData() const;
|
Q_INVOKABLE AvatarEntityMap getAvatarEntityData() const;
|
||||||
Q_INVOKABLE void setAvatarEntityData(const AvatarEntityMap& avatarEntityData);
|
Q_INVOKABLE void setAvatarEntityData(const AvatarEntityMap& avatarEntityData);
|
||||||
|
@ -387,6 +527,17 @@ public:
|
||||||
Q_INVOKABLE glm::mat4 getControllerLeftHandMatrix() const;
|
Q_INVOKABLE glm::mat4 getControllerLeftHandMatrix() const;
|
||||||
Q_INVOKABLE glm::mat4 getControllerRightHandMatrix() const;
|
Q_INVOKABLE glm::mat4 getControllerRightHandMatrix() const;
|
||||||
|
|
||||||
|
float getDataRate(const QString& rateName = QString(""));
|
||||||
|
|
||||||
|
int getJointCount() { return _jointData.size(); }
|
||||||
|
|
||||||
|
QVector<JointData> getLastSentJointData() {
|
||||||
|
QReadLocker readLock(&_jointDataLock);
|
||||||
|
_lastSentJointData.resize(_jointData.size());
|
||||||
|
return _lastSentJointData;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void sendAvatarDataPacket();
|
void sendAvatarDataPacket();
|
||||||
void sendIdentityPacket();
|
void sendIdentityPacket();
|
||||||
|
@ -401,7 +552,27 @@ public slots:
|
||||||
|
|
||||||
float getTargetScale() { return _targetScale; }
|
float getTargetScale() { return _targetScale; }
|
||||||
|
|
||||||
|
void resetLastSent() { _lastToByteArray = 0; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
void lazyInitHeadData();
|
||||||
|
|
||||||
|
float getDistanceBasedMinRotationDOT(glm::vec3 viewerPosition);
|
||||||
|
float getDistanceBasedMinTranslationDistance(glm::vec3 viewerPosition);
|
||||||
|
|
||||||
|
bool avatarBoundingBoxChangedSince(quint64 time);
|
||||||
|
bool avatarScaleChangedSince(quint64 time);
|
||||||
|
bool lookAtPositionChangedSince(quint64 time);
|
||||||
|
bool audioLoudnessChangedSince(quint64 time);
|
||||||
|
bool sensorToWorldMatrixChangedSince(quint64 time);
|
||||||
|
bool additionalFlagsChangedSince(quint64 time);
|
||||||
|
|
||||||
|
bool hasParent() { return !getParentID().isNull(); }
|
||||||
|
bool parentInfoChangedSince(quint64 time);
|
||||||
|
|
||||||
|
bool hasFaceTracker() { return _headData ? _headData->_isFaceTrackerConnected : false; }
|
||||||
|
bool faceTrackerInfoChangedSince(quint64 time);
|
||||||
|
|
||||||
glm::vec3 _handPosition;
|
glm::vec3 _handPosition;
|
||||||
virtual const QString& getSessionDisplayNameForTransport() const { return _sessionDisplayName; }
|
virtual const QString& getSessionDisplayNameForTransport() const { return _sessionDisplayName; }
|
||||||
virtual void maybeUpdateSessionDisplayNameFromTransport(const QString& sessionDisplayName) { } // No-op in AvatarMixer
|
virtual void maybeUpdateSessionDisplayNameFromTransport(const QString& sessionDisplayName) { } // No-op in AvatarMixer
|
||||||
|
@ -460,8 +631,35 @@ protected:
|
||||||
// _globalPosition is sent along with localPosition + parent because the avatar-mixer doesn't know
|
// _globalPosition is sent along with localPosition + parent because the avatar-mixer doesn't know
|
||||||
// where Entities are located. This is currently only used by the mixer to decide how often to send
|
// where Entities are located. This is currently only used by the mixer to decide how often to send
|
||||||
// updates about one avatar to another.
|
// updates about one avatar to another.
|
||||||
glm::vec3 _globalPosition;
|
glm::vec3 _globalPosition { 0, 0, 0 };
|
||||||
glm::vec3 _globalBoundingBoxCorner;
|
|
||||||
|
|
||||||
|
quint64 _globalPositionChanged { 0 };
|
||||||
|
quint64 _avatarBoundingBoxChanged { 0 };
|
||||||
|
quint64 _avatarScaleChanged { 0 };
|
||||||
|
quint64 _sensorToWorldMatrixChanged { 0 };
|
||||||
|
quint64 _additionalFlagsChanged { 0 };
|
||||||
|
quint64 _parentChanged { 0 };
|
||||||
|
|
||||||
|
quint64 _lastToByteArray { 0 }; // tracks the last time we did a toByteArray
|
||||||
|
|
||||||
|
// Some rate data for incoming data
|
||||||
|
RateCounter<> _parseBufferRate;
|
||||||
|
RateCounter<> _globalPositionRate;
|
||||||
|
RateCounter<> _localPositionRate;
|
||||||
|
RateCounter<> _avatarBoundingBoxRate;
|
||||||
|
RateCounter<> _avatarOrientationRate;
|
||||||
|
RateCounter<> _avatarScaleRate;
|
||||||
|
RateCounter<> _lookAtPositionRate;
|
||||||
|
RateCounter<> _audioLoudnessRate;
|
||||||
|
RateCounter<> _sensorToWorldRate;
|
||||||
|
RateCounter<> _additionalFlagsRate;
|
||||||
|
RateCounter<> _parentInfoRate;
|
||||||
|
RateCounter<> _faceTrackerRate;
|
||||||
|
RateCounter<> _jointDataRate;
|
||||||
|
|
||||||
|
glm::vec3 _globalBoundingBoxDimensions;
|
||||||
|
glm::vec3 _globalBoundingBoxOffset;
|
||||||
|
|
||||||
mutable ReadWriteLockable _avatarEntitiesLock;
|
mutable ReadWriteLockable _avatarEntitiesLock;
|
||||||
AvatarEntityIDs _avatarEntityDetached; // recently detached from this avatar
|
AvatarEntityIDs _avatarEntityDetached; // recently detached from this avatar
|
||||||
|
|
|
@ -19,6 +19,8 @@
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
#include <glm/gtc/quaternion.hpp>
|
#include <glm/gtc/quaternion.hpp>
|
||||||
|
|
||||||
|
#include <SharedUtil.h>
|
||||||
|
|
||||||
// degrees
|
// degrees
|
||||||
const float MIN_HEAD_YAW = -180.0f;
|
const float MIN_HEAD_YAW = -180.0f;
|
||||||
const float MAX_HEAD_YAW = 180.0f;
|
const float MAX_HEAD_YAW = 180.0f;
|
||||||
|
@ -56,7 +58,13 @@ public:
|
||||||
void setOrientation(const glm::quat& orientation);
|
void setOrientation(const glm::quat& orientation);
|
||||||
|
|
||||||
float getAudioLoudness() const { return _audioLoudness; }
|
float getAudioLoudness() const { return _audioLoudness; }
|
||||||
void setAudioLoudness(float audioLoudness) { _audioLoudness = audioLoudness; }
|
void setAudioLoudness(float audioLoudness) {
|
||||||
|
if (audioLoudness != _audioLoudness) {
|
||||||
|
_audioLoudnessChanged = usecTimestampNow();
|
||||||
|
}
|
||||||
|
_audioLoudness = audioLoudness;
|
||||||
|
}
|
||||||
|
bool audioLoudnessChangedSince(quint64 time) { return _audioLoudnessChanged >= time; }
|
||||||
|
|
||||||
float getAudioAverageLoudness() const { return _audioAverageLoudness; }
|
float getAudioAverageLoudness() const { return _audioAverageLoudness; }
|
||||||
void setAudioAverageLoudness(float audioAverageLoudness) { _audioAverageLoudness = audioAverageLoudness; }
|
void setAudioAverageLoudness(float audioAverageLoudness) { _audioAverageLoudness = audioAverageLoudness; }
|
||||||
|
@ -66,7 +74,13 @@ public:
|
||||||
void setBlendshapeCoefficients(const QVector<float>& blendshapeCoefficients) { _blendshapeCoefficients = blendshapeCoefficients; }
|
void setBlendshapeCoefficients(const QVector<float>& blendshapeCoefficients) { _blendshapeCoefficients = blendshapeCoefficients; }
|
||||||
|
|
||||||
const glm::vec3& getLookAtPosition() const { return _lookAtPosition; }
|
const glm::vec3& getLookAtPosition() const { return _lookAtPosition; }
|
||||||
void setLookAtPosition(const glm::vec3& lookAtPosition) { _lookAtPosition = lookAtPosition; }
|
void setLookAtPosition(const glm::vec3& lookAtPosition) {
|
||||||
|
if (_lookAtPosition != lookAtPosition) {
|
||||||
|
_lookAtPositionChanged = usecTimestampNow();
|
||||||
|
}
|
||||||
|
_lookAtPosition = lookAtPosition;
|
||||||
|
}
|
||||||
|
bool lookAtPositionChangedSince(quint64 time) { return _lookAtPositionChanged >= time; }
|
||||||
|
|
||||||
friend class AvatarData;
|
friend class AvatarData;
|
||||||
|
|
||||||
|
@ -80,7 +94,11 @@ protected:
|
||||||
float _baseRoll;
|
float _baseRoll;
|
||||||
|
|
||||||
glm::vec3 _lookAtPosition;
|
glm::vec3 _lookAtPosition;
|
||||||
|
quint64 _lookAtPositionChanged { 0 };
|
||||||
|
|
||||||
float _audioLoudness;
|
float _audioLoudness;
|
||||||
|
quint64 _audioLoudnessChanged { 0 };
|
||||||
|
|
||||||
bool _isFaceTrackerConnected;
|
bool _isFaceTrackerConnected;
|
||||||
bool _isEyeTrackerConnected;
|
bool _isEyeTrackerConnected;
|
||||||
float _leftEyeBlink;
|
float _leftEyeBlink;
|
||||||
|
|
|
@ -351,7 +351,6 @@ int EntityItem::expectedBytes() {
|
||||||
return MINIMUM_HEADER_BYTES;
|
return MINIMUM_HEADER_BYTES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// clients use this method to unpack FULL updates from entity-server
|
// clients use this method to unpack FULL updates from entity-server
|
||||||
int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) {
|
int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) {
|
||||||
if (args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_SPLIT_MTU) {
|
if (args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_SPLIT_MTU) {
|
||||||
|
@ -669,6 +668,9 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
||||||
// entity-server is trying to clear our ownership (probably at our own request)
|
// 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
|
// but we actually want to own it, therefore we ignore this clear event
|
||||||
// and pretend that we own it (we assume we'll recover it soon)
|
// and pretend that we own it (we assume we'll recover it 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)) {
|
} else if (_simulationOwner.set(newSimOwner)) {
|
||||||
_dirtyFlags |= Simulation::DIRTY_SIMULATOR_ID;
|
_dirtyFlags |= Simulation::DIRTY_SIMULATOR_ID;
|
||||||
somethingChanged = true;
|
somethingChanged = true;
|
||||||
|
|
|
@ -347,11 +347,15 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
||||||
return changedProperties;
|
return changedProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool skipDefaults) const {
|
QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool skipDefaults, bool allowUnknownCreateTime, bool strictSemantics) const {
|
||||||
|
// If strictSemantics is true and skipDefaults is false, then all and only those properties are copied for which the property flag
|
||||||
|
// is included in _desiredProperties, or is one of the specially enumerated ALWAYS properties below.
|
||||||
|
// (There may be exceptions, but if so, they are bugs.)
|
||||||
|
// In all other cases, you are welcome to inspect the code and try to figure out what was intended. I wish you luck. -HRS 1/18/17
|
||||||
QScriptValue properties = engine->newObject();
|
QScriptValue properties = engine->newObject();
|
||||||
EntityItemProperties defaultEntityProperties;
|
EntityItemProperties defaultEntityProperties;
|
||||||
|
|
||||||
if (_created == UNKNOWN_CREATED_TIME) {
|
if (_created == UNKNOWN_CREATED_TIME && !allowUnknownCreateTime) {
|
||||||
// No entity properties can have been set so return without setting any default, zero property values.
|
// No entity properties can have been set so return without setting any default, zero property values.
|
||||||
return properties;
|
return properties;
|
||||||
}
|
}
|
||||||
|
@ -365,7 +369,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
|
||||||
created.setTimeSpec(Qt::OffsetFromUTC);
|
created.setTimeSpec(Qt::OffsetFromUTC);
|
||||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_ALWAYS(created, created.toString(Qt::ISODate));
|
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_ALWAYS(created, created.toString(Qt::ISODate));
|
||||||
|
|
||||||
if (!skipDefaults || _lifetime != defaultEntityProperties._lifetime) {
|
if ((!skipDefaults || _lifetime != defaultEntityProperties._lifetime) && !strictSemantics) {
|
||||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(age, getAge()); // gettable, but not settable
|
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(age, getAge()); // gettable, but not settable
|
||||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(ageAsText, formatSecondsElapsed(getAge())); // gettable, but not settable
|
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(ageAsText, formatSecondsElapsed(getAge())); // gettable, but not settable
|
||||||
}
|
}
|
||||||
|
@ -541,7 +545,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sitting properties support
|
// Sitting properties support
|
||||||
if (!skipDefaults) {
|
if (!skipDefaults && !strictSemantics) {
|
||||||
QScriptValue sittingPoints = engine->newObject();
|
QScriptValue sittingPoints = engine->newObject();
|
||||||
for (int i = 0; i < _sittingPoints.size(); ++i) {
|
for (int i = 0; i < _sittingPoints.size(); ++i) {
|
||||||
QScriptValue sittingPoint = engine->newObject();
|
QScriptValue sittingPoint = engine->newObject();
|
||||||
|
@ -554,7 +558,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
|
||||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_ALWAYS(sittingPoints, sittingPoints); // gettable, but not settable
|
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_ALWAYS(sittingPoints, sittingPoints); // gettable, but not settable
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!skipDefaults) {
|
if (!skipDefaults && !strictSemantics) {
|
||||||
AABox aaBox = getAABox();
|
AABox aaBox = getAABox();
|
||||||
QScriptValue boundingBox = engine->newObject();
|
QScriptValue boundingBox = engine->newObject();
|
||||||
QScriptValue bottomRightNear = vec3toScriptValue(engine, aaBox.getCorner());
|
QScriptValue bottomRightNear = vec3toScriptValue(engine, aaBox.getCorner());
|
||||||
|
@ -569,7 +573,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
|
||||||
}
|
}
|
||||||
|
|
||||||
QString textureNamesStr = QJsonDocument::fromVariant(_textureNames).toJson();
|
QString textureNamesStr = QJsonDocument::fromVariant(_textureNames).toJson();
|
||||||
if (!skipDefaults) {
|
if (!skipDefaults && !strictSemantics) {
|
||||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(originalTextures, textureNamesStr); // gettable, but not settable
|
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(originalTextures, textureNamesStr); // gettable, but not settable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -586,7 +590,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
|
||||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_OWNING_AVATAR_ID, owningAvatarID);
|
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_OWNING_AVATAR_ID, owningAvatarID);
|
||||||
|
|
||||||
// Rendering info
|
// Rendering info
|
||||||
if (!skipDefaults) {
|
if (!skipDefaults && !strictSemantics) {
|
||||||
QScriptValue renderInfo = engine->newObject();
|
QScriptValue renderInfo = engine->newObject();
|
||||||
|
|
||||||
// currently only supported by models
|
// currently only supported by models
|
||||||
|
|
|
@ -73,7 +73,7 @@ public:
|
||||||
EntityTypes::EntityType getType() const { return _type; }
|
EntityTypes::EntityType getType() const { return _type; }
|
||||||
void setType(EntityTypes::EntityType type) { _type = type; }
|
void setType(EntityTypes::EntityType type) { _type = type; }
|
||||||
|
|
||||||
virtual QScriptValue copyToScriptValue(QScriptEngine* engine, bool skipDefaults) const;
|
virtual QScriptValue copyToScriptValue(QScriptEngine* engine, bool skipDefaults, bool allowUnknownCreateTime = false, bool strictSemantics = false) const;
|
||||||
virtual void copyFromScriptValue(const QScriptValue& object, bool honorReadOnly);
|
virtual void copyFromScriptValue(const QScriptValue& object, bool honorReadOnly);
|
||||||
|
|
||||||
static QScriptValue entityPropertyFlagsToScriptValue(QScriptEngine* engine, const EntityPropertyFlags& flags);
|
static QScriptValue entityPropertyFlagsToScriptValue(QScriptEngine* engine, const EntityPropertyFlags& flags);
|
||||||
|
@ -93,6 +93,8 @@ public:
|
||||||
|
|
||||||
void debugDump() const;
|
void debugDump() const;
|
||||||
void setLastEdited(quint64 usecTime);
|
void setLastEdited(quint64 usecTime);
|
||||||
|
EntityPropertyFlags getDesiredProperties() { return _desiredProperties; }
|
||||||
|
void setDesiredProperties(EntityPropertyFlags properties) { _desiredProperties = properties; }
|
||||||
|
|
||||||
// Note: DEFINE_PROPERTY(PROP_FOO, Foo, foo, type, value) creates the following methods and variables:
|
// Note: DEFINE_PROPERTY(PROP_FOO, Foo, foo, type, value) creates the following methods and variables:
|
||||||
// type getFoo() const;
|
// type getFoo() const;
|
||||||
|
@ -462,10 +464,6 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) {
|
||||||
|
|
||||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, LastEditedBy, lastEditedBy, "");
|
DEBUG_PROPERTY_IF_CHANGED(debug, properties, LastEditedBy, lastEditedBy, "");
|
||||||
|
|
||||||
properties.getAnimation().debugDump();
|
|
||||||
properties.getSkybox().debugDump();
|
|
||||||
properties.getStage().debugDump();
|
|
||||||
|
|
||||||
debug << " last edited:" << properties.getLastEdited() << "\n";
|
debug << " last edited:" << properties.getLastEdited() << "\n";
|
||||||
debug << " edited ago:" << properties.getEditedAgo() << "\n";
|
debug << " edited ago:" << properties.getEditedAgo() << "\n";
|
||||||
debug << "]";
|
debug << "]";
|
||||||
|
|
|
@ -922,6 +922,56 @@ void EntityTree::fixupTerseEditLogging(EntityItemProperties& properties, QList<Q
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EntityTree::initEntityEditFilterEngine(QScriptEngine* engine, std::function<bool()> entityEditFilterHadUncaughtExceptions) {
|
||||||
|
_entityEditFilterEngine = engine;
|
||||||
|
_entityEditFilterHadUncaughtExceptions = entityEditFilterHadUncaughtExceptions;
|
||||||
|
auto global = _entityEditFilterEngine->globalObject();
|
||||||
|
_entityEditFilterFunction = global.property("filter");
|
||||||
|
_hasEntityEditFilter = _entityEditFilterFunction.isFunction();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EntityTree::filterProperties(EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged) {
|
||||||
|
if (!_hasEntityEditFilter || !_entityEditFilterEngine) {
|
||||||
|
propertiesOut = propertiesIn;
|
||||||
|
wasChanged = false; // not changed
|
||||||
|
return true; // allowed
|
||||||
|
}
|
||||||
|
auto oldProperties = propertiesIn.getDesiredProperties();
|
||||||
|
auto specifiedProperties = propertiesIn.getChangedProperties();
|
||||||
|
propertiesIn.setDesiredProperties(specifiedProperties);
|
||||||
|
QScriptValue inputValues = propertiesIn.copyToScriptValue(_entityEditFilterEngine, false, true, true);
|
||||||
|
propertiesIn.setDesiredProperties(oldProperties);
|
||||||
|
|
||||||
|
auto in = QJsonValue::fromVariant(inputValues.toVariant()); // grab json copy now, because the inputValues might be side effected by the filter.
|
||||||
|
QScriptValueList args;
|
||||||
|
args << inputValues;
|
||||||
|
|
||||||
|
QScriptValue result = _entityEditFilterFunction.call(_nullObjectForFilter, args);
|
||||||
|
if (_entityEditFilterHadUncaughtExceptions()) {
|
||||||
|
result = QScriptValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool accepted = result.isObject(); // filters should return null or false to completely reject edit or add
|
||||||
|
if (accepted) {
|
||||||
|
propertiesOut.copyFromScriptValue(result, false);
|
||||||
|
// Javascript objects are == only if they are the same object. To compare arbitrary values, we need to use JSON.
|
||||||
|
auto out = QJsonValue::fromVariant(result.toVariant());
|
||||||
|
wasChanged = in != out;
|
||||||
|
}
|
||||||
|
|
||||||
|
return accepted;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EntityTree::bumpTimestamp(EntityItemProperties& properties) { //fixme put class/header
|
||||||
|
const quint64 LAST_EDITED_SERVERSIDE_BUMP = 1; // usec
|
||||||
|
// also bump up the lastEdited time of the properties so that the interface that created this edit
|
||||||
|
// will accept our adjustment to lifetime back into its own entity-tree.
|
||||||
|
if (properties.getLastEdited() == UNKNOWN_CREATED_TIME) {
|
||||||
|
properties.setLastEdited(usecTimestampNow());
|
||||||
|
}
|
||||||
|
properties.setLastEdited(properties.getLastEdited() + LAST_EDITED_SERVERSIDE_BUMP);
|
||||||
|
}
|
||||||
|
|
||||||
int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength,
|
int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength,
|
||||||
const SharedNodePointer& senderNode) {
|
const SharedNodePointer& senderNode) {
|
||||||
|
|
||||||
|
@ -945,9 +995,9 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
||||||
quint64 startLookup = 0, endLookup = 0;
|
quint64 startLookup = 0, endLookup = 0;
|
||||||
quint64 startUpdate = 0, endUpdate = 0;
|
quint64 startUpdate = 0, endUpdate = 0;
|
||||||
quint64 startCreate = 0, endCreate = 0;
|
quint64 startCreate = 0, endCreate = 0;
|
||||||
|
quint64 startFilter = 0, endFilter = 0;
|
||||||
quint64 startLogging = 0, endLogging = 0;
|
quint64 startLogging = 0, endLogging = 0;
|
||||||
|
|
||||||
const quint64 LAST_EDITED_SERVERSIDE_BUMP = 1; // usec
|
|
||||||
bool suppressDisallowedScript = false;
|
bool suppressDisallowedScript = false;
|
||||||
|
|
||||||
_totalEditMessages++;
|
_totalEditMessages++;
|
||||||
|
@ -999,18 +1049,28 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
||||||
if (properties.getLifetime() == ENTITY_ITEM_IMMORTAL_LIFETIME ||
|
if (properties.getLifetime() == ENTITY_ITEM_IMMORTAL_LIFETIME ||
|
||||||
properties.getLifetime() > _maxTmpEntityLifetime) {
|
properties.getLifetime() > _maxTmpEntityLifetime) {
|
||||||
properties.setLifetime(_maxTmpEntityLifetime);
|
properties.setLifetime(_maxTmpEntityLifetime);
|
||||||
// also bump up the lastEdited time of the properties so that the interface that created this edit
|
bumpTimestamp(properties);
|
||||||
// will accept our adjustment to lifetime back into its own entity-tree.
|
|
||||||
if (properties.getLastEdited() == UNKNOWN_CREATED_TIME) {
|
|
||||||
properties.setLastEdited(usecTimestampNow());
|
|
||||||
}
|
|
||||||
properties.setLastEdited(properties.getLastEdited() + LAST_EDITED_SERVERSIDE_BUMP);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we got a valid edit packet, then it could be a new entity or it could be an update to
|
// If we got a valid edit packet, then it could be a new entity or it could be an update to
|
||||||
// an existing entity... handle appropriately
|
// an existing entity... handle appropriately
|
||||||
if (validEditPacket) {
|
if (validEditPacket) {
|
||||||
|
|
||||||
|
startFilter = usecTimestampNow();
|
||||||
|
bool wasChanged = false;
|
||||||
|
// Having (un)lock rights bypasses the filter.
|
||||||
|
bool allowed = senderNode->isAllowedEditor() || filterProperties(properties, properties, wasChanged);
|
||||||
|
if (!allowed) {
|
||||||
|
properties = EntityItemProperties();
|
||||||
|
}
|
||||||
|
if (!allowed || wasChanged) {
|
||||||
|
bumpTimestamp(properties);
|
||||||
|
// For now, free ownership on any modification.
|
||||||
|
properties.clearSimulationOwner();
|
||||||
|
}
|
||||||
|
endFilter = usecTimestampNow();
|
||||||
|
|
||||||
// search for the entity by EntityItemID
|
// search for the entity by EntityItemID
|
||||||
startLookup = usecTimestampNow();
|
startLookup = usecTimestampNow();
|
||||||
EntityItemPointer existingEntity = findEntityByEntityItemID(entityItemID);
|
EntityItemPointer existingEntity = findEntityByEntityItemID(entityItemID);
|
||||||
|
@ -1018,7 +1078,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
||||||
if (existingEntity && message.getType() == PacketType::EntityEdit) {
|
if (existingEntity && message.getType() == PacketType::EntityEdit) {
|
||||||
|
|
||||||
if (suppressDisallowedScript) {
|
if (suppressDisallowedScript) {
|
||||||
properties.setLastEdited(properties.getLastEdited() + LAST_EDITED_SERVERSIDE_BUMP);
|
bumpTimestamp(properties);
|
||||||
properties.setScript(existingEntity->getScript());
|
properties.setScript(existingEntity->getScript());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1088,6 +1148,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
||||||
_totalUpdateTime += endUpdate - startUpdate;
|
_totalUpdateTime += endUpdate - startUpdate;
|
||||||
_totalCreateTime += endCreate - startCreate;
|
_totalCreateTime += endCreate - startCreate;
|
||||||
_totalLoggingTime += endLogging - startLogging;
|
_totalLoggingTime += endLogging - startLogging;
|
||||||
|
_totalFilterTime += endFilter - startFilter;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -239,6 +239,7 @@ public:
|
||||||
virtual quint64 getAverageUpdateTime() const override { return _totalUpdates == 0 ? 0 : _totalUpdateTime / _totalUpdates; }
|
virtual quint64 getAverageUpdateTime() const override { return _totalUpdates == 0 ? 0 : _totalUpdateTime / _totalUpdates; }
|
||||||
virtual quint64 getAverageCreateTime() const override { return _totalCreates == 0 ? 0 : _totalCreateTime / _totalCreates; }
|
virtual quint64 getAverageCreateTime() const override { return _totalCreates == 0 ? 0 : _totalCreateTime / _totalCreates; }
|
||||||
virtual quint64 getAverageLoggingTime() const override { return _totalEditMessages == 0 ? 0 : _totalLoggingTime / _totalEditMessages; }
|
virtual quint64 getAverageLoggingTime() const override { return _totalEditMessages == 0 ? 0 : _totalLoggingTime / _totalEditMessages; }
|
||||||
|
virtual quint64 getAverageFilterTime() const override { return _totalEditMessages == 0 ? 0 : _totalFilterTime / _totalEditMessages; }
|
||||||
|
|
||||||
void trackIncomingEntityLastEdited(quint64 lastEditedTime, int bytesRead);
|
void trackIncomingEntityLastEdited(quint64 lastEditedTime, int bytesRead);
|
||||||
quint64 getAverageEditDeltas() const
|
quint64 getAverageEditDeltas() const
|
||||||
|
@ -265,6 +266,8 @@ public:
|
||||||
|
|
||||||
void notifyNewCollisionSoundURL(const QString& newCollisionSoundURL, const EntityItemID& entityID);
|
void notifyNewCollisionSoundURL(const QString& newCollisionSoundURL, const EntityItemID& entityID);
|
||||||
|
|
||||||
|
void initEntityEditFilterEngine(QScriptEngine* engine, std::function<bool()> entityEditFilterHadUncaughtExceptions);
|
||||||
|
|
||||||
static const float DEFAULT_MAX_TMP_ENTITY_LIFETIME;
|
static const float DEFAULT_MAX_TMP_ENTITY_LIFETIME;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
@ -290,6 +293,7 @@ protected:
|
||||||
static bool findInBoxOperation(OctreeElementPointer element, void* extraData);
|
static bool findInBoxOperation(OctreeElementPointer element, void* extraData);
|
||||||
static bool findInFrustumOperation(OctreeElementPointer element, void* extraData);
|
static bool findInFrustumOperation(OctreeElementPointer element, void* extraData);
|
||||||
static bool sendEntitiesOperation(OctreeElementPointer element, void* extraData);
|
static bool sendEntitiesOperation(OctreeElementPointer element, void* extraData);
|
||||||
|
static void bumpTimestamp(EntityItemProperties& properties);
|
||||||
|
|
||||||
void notifyNewlyCreatedEntity(const EntityItem& newEntity, const SharedNodePointer& senderNode);
|
void notifyNewlyCreatedEntity(const EntityItem& newEntity, const SharedNodePointer& senderNode);
|
||||||
|
|
||||||
|
@ -332,6 +336,7 @@ protected:
|
||||||
quint64 _totalUpdateTime = 0;
|
quint64 _totalUpdateTime = 0;
|
||||||
quint64 _totalCreateTime = 0;
|
quint64 _totalCreateTime = 0;
|
||||||
quint64 _totalLoggingTime = 0;
|
quint64 _totalLoggingTime = 0;
|
||||||
|
quint64 _totalFilterTime = 0;
|
||||||
|
|
||||||
// these performance statistics are only used in the client
|
// these performance statistics are only used in the client
|
||||||
void resetClientEditStats();
|
void resetClientEditStats();
|
||||||
|
@ -351,6 +356,13 @@ protected:
|
||||||
|
|
||||||
float _maxTmpEntityLifetime { DEFAULT_MAX_TMP_ENTITY_LIFETIME };
|
float _maxTmpEntityLifetime { DEFAULT_MAX_TMP_ENTITY_LIFETIME };
|
||||||
|
|
||||||
|
bool filterProperties(EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged);
|
||||||
|
bool _hasEntityEditFilter{ false };
|
||||||
|
QScriptEngine* _entityEditFilterEngine{};
|
||||||
|
QScriptValue _entityEditFilterFunction{};
|
||||||
|
QScriptValue _nullObjectForFilter{};
|
||||||
|
std::function<bool()> _entityEditFilterHadUncaughtExceptions;
|
||||||
|
|
||||||
QStringList _entityScriptSourceWhitelist;
|
QStringList _entityScriptSourceWhitelist;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
||||||
case PacketType::AvatarData:
|
case PacketType::AvatarData:
|
||||||
case PacketType::BulkAvatarData:
|
case PacketType::BulkAvatarData:
|
||||||
case PacketType::KillAvatar:
|
case PacketType::KillAvatar:
|
||||||
return static_cast<PacketVersion>(AvatarMixerPacketVersion::Unignore);
|
return static_cast<PacketVersion>(AvatarMixerPacketVersion::VariableAvatarData);
|
||||||
case PacketType::ICEServerHeartbeat:
|
case PacketType::ICEServerHeartbeat:
|
||||||
return 18; // ICE Server Heartbeat signing
|
return 18; // ICE Server Heartbeat signing
|
||||||
case PacketType::AssetGetInfo:
|
case PacketType::AssetGetInfo:
|
||||||
|
|
|
@ -220,7 +220,8 @@ enum class AvatarMixerPacketVersion : PacketVersion {
|
||||||
HasKillAvatarReason,
|
HasKillAvatarReason,
|
||||||
SessionDisplayName,
|
SessionDisplayName,
|
||||||
Unignore,
|
Unignore,
|
||||||
ImmediateSessionDisplayNameUpdates
|
ImmediateSessionDisplayNameUpdates,
|
||||||
|
VariableAvatarData
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class DomainConnectRequestVersion : PacketVersion {
|
enum class DomainConnectRequestVersion : PacketVersion {
|
||||||
|
|
|
@ -356,6 +356,7 @@ public:
|
||||||
virtual quint64 getAverageUpdateTime() const { return 0; }
|
virtual quint64 getAverageUpdateTime() const { return 0; }
|
||||||
virtual quint64 getAverageCreateTime() const { return 0; }
|
virtual quint64 getAverageCreateTime() const { return 0; }
|
||||||
virtual quint64 getAverageLoggingTime() const { return 0; }
|
virtual quint64 getAverageLoggingTime() const { return 0; }
|
||||||
|
virtual quint64 getAverageFilterTime() const { return 0; }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void importSize(float x, float y, float z);
|
void importSize(float x, float y, float z);
|
||||||
|
|
|
@ -26,6 +26,9 @@ SpatiallyNestable::SpatiallyNestable(NestableType nestableType, QUuid id) :
|
||||||
// set flags in _transform
|
// set flags in _transform
|
||||||
_transform.setTranslation(glm::vec3(0.0f));
|
_transform.setTranslation(glm::vec3(0.0f));
|
||||||
_transform.setRotation(glm::quat());
|
_transform.setRotation(glm::quat());
|
||||||
|
_scaleChanged = usecTimestampNow();
|
||||||
|
_translationChanged = usecTimestampNow();
|
||||||
|
_rotationChanged = usecTimestampNow();
|
||||||
}
|
}
|
||||||
|
|
||||||
SpatiallyNestable::~SpatiallyNestable() {
|
SpatiallyNestable::~SpatiallyNestable() {
|
||||||
|
@ -399,6 +402,7 @@ void SpatiallyNestable::setPosition(const glm::vec3& position, bool& success, bo
|
||||||
changed = true;
|
changed = true;
|
||||||
myWorldTransform.setTranslation(position);
|
myWorldTransform.setTranslation(position);
|
||||||
Transform::inverseMult(_transform, parentTransform, myWorldTransform);
|
Transform::inverseMult(_transform, parentTransform, myWorldTransform);
|
||||||
|
_translationChanged = usecTimestampNow();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (success && changed) {
|
if (success && changed) {
|
||||||
|
@ -451,6 +455,7 @@ void SpatiallyNestable::setOrientation(const glm::quat& orientation, bool& succe
|
||||||
changed = true;
|
changed = true;
|
||||||
myWorldTransform.setRotation(orientation);
|
myWorldTransform.setRotation(orientation);
|
||||||
Transform::inverseMult(_transform, parentTransform, myWorldTransform);
|
Transform::inverseMult(_transform, parentTransform, myWorldTransform);
|
||||||
|
_rotationChanged = usecTimestampNow();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (success && changed) {
|
if (success && changed) {
|
||||||
|
@ -649,6 +654,8 @@ void SpatiallyNestable::setTransform(const Transform& transform, bool& success)
|
||||||
Transform::inverseMult(_transform, parentTransform, transform);
|
Transform::inverseMult(_transform, parentTransform, transform);
|
||||||
if (_transform != beforeTransform) {
|
if (_transform != beforeTransform) {
|
||||||
changed = true;
|
changed = true;
|
||||||
|
_translationChanged = usecTimestampNow();
|
||||||
|
_rotationChanged = usecTimestampNow();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (success && changed) {
|
if (success && changed) {
|
||||||
|
@ -689,6 +696,7 @@ void SpatiallyNestable::setScale(const glm::vec3& scale) {
|
||||||
if (_transform.getScale() != scale) {
|
if (_transform.getScale() != scale) {
|
||||||
_transform.setScale(scale);
|
_transform.setScale(scale);
|
||||||
changed = true;
|
changed = true;
|
||||||
|
_scaleChanged = usecTimestampNow();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (changed) {
|
if (changed) {
|
||||||
|
@ -710,6 +718,7 @@ void SpatiallyNestable::setScale(float value) {
|
||||||
_transform.setScale(value);
|
_transform.setScale(value);
|
||||||
if (_transform.getScale() != beforeScale) {
|
if (_transform.getScale() != beforeScale) {
|
||||||
changed = true;
|
changed = true;
|
||||||
|
_scaleChanged = usecTimestampNow();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -738,6 +747,9 @@ void SpatiallyNestable::setLocalTransform(const Transform& transform) {
|
||||||
if (_transform != transform) {
|
if (_transform != transform) {
|
||||||
_transform = transform;
|
_transform = transform;
|
||||||
changed = true;
|
changed = true;
|
||||||
|
_scaleChanged = usecTimestampNow();
|
||||||
|
_translationChanged = usecTimestampNow();
|
||||||
|
_rotationChanged = usecTimestampNow();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -765,6 +777,7 @@ void SpatiallyNestable::setLocalPosition(const glm::vec3& position, bool tellPhy
|
||||||
if (_transform.getTranslation() != position) {
|
if (_transform.getTranslation() != position) {
|
||||||
_transform.setTranslation(position);
|
_transform.setTranslation(position);
|
||||||
changed = true;
|
changed = true;
|
||||||
|
_translationChanged = usecTimestampNow();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (changed) {
|
if (changed) {
|
||||||
|
@ -791,6 +804,7 @@ void SpatiallyNestable::setLocalOrientation(const glm::quat& orientation) {
|
||||||
if (_transform.getRotation() != orientation) {
|
if (_transform.getRotation() != orientation) {
|
||||||
_transform.setRotation(orientation);
|
_transform.setRotation(orientation);
|
||||||
changed = true;
|
changed = true;
|
||||||
|
_rotationChanged = usecTimestampNow();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (changed) {
|
if (changed) {
|
||||||
|
@ -848,9 +862,12 @@ void SpatiallyNestable::setLocalScale(const glm::vec3& scale) {
|
||||||
if (_transform.getScale() != scale) {
|
if (_transform.getScale() != scale) {
|
||||||
_transform.setScale(scale);
|
_transform.setScale(scale);
|
||||||
changed = true;
|
changed = true;
|
||||||
|
_scaleChanged = usecTimestampNow();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if (changed) {
|
||||||
dimensionsChanged();
|
dimensionsChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<SpatiallyNestablePointer> SpatiallyNestable::getChildren() const {
|
QList<SpatiallyNestablePointer> SpatiallyNestable::getChildren() const {
|
||||||
|
@ -1073,6 +1090,9 @@ void SpatiallyNestable::setLocalTransformAndVelocities(
|
||||||
if (_transform != localTransform) {
|
if (_transform != localTransform) {
|
||||||
_transform = localTransform;
|
_transform = localTransform;
|
||||||
changed = true;
|
changed = true;
|
||||||
|
_scaleChanged = usecTimestampNow();
|
||||||
|
_translationChanged = usecTimestampNow();
|
||||||
|
_rotationChanged = usecTimestampNow();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// linear velocity
|
// linear velocity
|
||||||
|
|
|
@ -179,6 +179,10 @@ public:
|
||||||
const glm::vec3& localVelocity,
|
const glm::vec3& localVelocity,
|
||||||
const glm::vec3& localAngularVelocity);
|
const glm::vec3& localAngularVelocity);
|
||||||
|
|
||||||
|
bool scaleChangedSince(quint64 time) { return _scaleChanged > time; }
|
||||||
|
bool tranlationChangedSince(quint64 time) { return _translationChanged > time; }
|
||||||
|
bool rotationChangedSince(quint64 time) { return _rotationChanged > time; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
const NestableType _nestableType; // EntityItem or an AvatarData
|
const NestableType _nestableType; // EntityItem or an AvatarData
|
||||||
QUuid _id;
|
QUuid _id;
|
||||||
|
@ -202,6 +206,9 @@ protected:
|
||||||
mutable bool _queryAACubeSet { false };
|
mutable bool _queryAACubeSet { false };
|
||||||
|
|
||||||
bool _missingAncestor { false };
|
bool _missingAncestor { false };
|
||||||
|
quint64 _scaleChanged { 0 };
|
||||||
|
quint64 _translationChanged { 0 };
|
||||||
|
quint64 _rotationChanged { 0 };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
mutable ReadWriteLockable _transformLock;
|
mutable ReadWriteLockable _transformLock;
|
||||||
|
|
63
script-archive/acScripts/simpleBot.js
Normal file
63
script-archive/acScripts/simpleBot.js
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
//
|
||||||
|
// simpleBot.js
|
||||||
|
// examples
|
||||||
|
//
|
||||||
|
// Created by Brad Hefta-Gaub on 12/23/16.
|
||||||
|
// Copyright 2016 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
|
||||||
|
//
|
||||||
|
|
||||||
|
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
||||||
|
|
||||||
|
function getRandomFloat(min, max) {
|
||||||
|
return Math.random() * (max - min) + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRandomInt (min, max) {
|
||||||
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
function printVector(string, vector) {
|
||||||
|
print(string + " " + vector.x + ", " + vector.y + ", " + vector.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
var timePassed = 0.0;
|
||||||
|
var updateSpeed = 3.0;
|
||||||
|
|
||||||
|
var X_MIN = 5.0;
|
||||||
|
var X_MAX = 15.0;
|
||||||
|
var Z_MIN = 5.0;
|
||||||
|
var Z_MAX = 15.0;
|
||||||
|
var Y_PELVIS = 1.0;
|
||||||
|
|
||||||
|
Agent.isAvatar = true;
|
||||||
|
|
||||||
|
// change the avatar's position to the random one
|
||||||
|
Avatar.position = {x:0,y:1.1,z:0}; // { x: getRandomFloat(X_MIN, X_MAX), y: Y_PELVIS, z: getRandomFloat(Z_MIN, Z_MAX) };;
|
||||||
|
printVector("New bot, position = ", Avatar.position);
|
||||||
|
|
||||||
|
var animationData = {url: "file:///D:/Development/HiFi/hifi/interface/resources/avatar/animations/walk_fwd.fbx", lastFrame: 35};
|
||||||
|
//Avatar.startAnimation(animationData.url, animationData.fps || 30, 1, true, false, animationData.firstFrame || 0, animationData.lastFrame);
|
||||||
|
//Avatar.skeletonModelURL = "file:///D:/Development/HiFi/hifi/interface/resources/meshes/being_of_light/being_of_light.fbx";
|
||||||
|
|
||||||
|
var millisecondsToWaitBeforeStarting = 4 * 1000;
|
||||||
|
Script.setTimeout(function () {
|
||||||
|
print("Starting at", JSON.stringify(Avatar.position));
|
||||||
|
Avatar.startAnimation(animationData.url, animationData.fps || 30, 1, true, false, animationData.firstFrame || 0, animationData.lastFrame);
|
||||||
|
}, millisecondsToWaitBeforeStarting);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function update(deltaTime) {
|
||||||
|
timePassed += deltaTime;
|
||||||
|
if (timePassed > updateSpeed) {
|
||||||
|
timePassed = 0;
|
||||||
|
var newPosition = Vec3.sum(Avatar.position, { x: getRandomFloat(-0.1, 0.1), y: 0, z: getRandomFloat(-0.1, 0.1) });
|
||||||
|
Avatar.position = newPosition;
|
||||||
|
Vec3.print("new:", newPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Script.update.connect(update);
|
57
script-archive/entity-server-filter-example.js
Normal file
57
script-archive/entity-server-filter-example.js
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
function filter(p) {
|
||||||
|
/* block comments are ok, but not double-slash end-of-line-comments */
|
||||||
|
|
||||||
|
/* Simple example: if someone specifies name, add an 'x' to it. Note that print is ok to use. */
|
||||||
|
if (p.name) {p.name += 'x'; print('fixme name', p. name);}
|
||||||
|
|
||||||
|
/* This example clamps y. A better filter would probably zero y component of velocity and acceleration. */
|
||||||
|
if (p.position) {p.position.y = Math.min(1, p.position.y); print('fixme p.y', p.position.y);}
|
||||||
|
|
||||||
|
/* Can also reject altogether */
|
||||||
|
if (p.userData) { return false; }
|
||||||
|
|
||||||
|
/* Reject if modifications made to Model properties */
|
||||||
|
if (p.modelURL || p.compoundShapeURL || p.shape || p.shapeType || p.url || p.fps || p.currentFrame || p.running || p.loop || p.firstFrame || p.lastFrame || p.hold || p.textures || p.xTextureURL || p.yTextureURL || p.zTextureURL) { return false; }
|
||||||
|
|
||||||
|
/* Clamp velocity to maxVelocity units/second. Zeroing each component of acceleration keeps us from slamming.*/
|
||||||
|
var maxVelocity = 5;
|
||||||
|
if (p.velocity) {
|
||||||
|
if (Math.abs(p.velocity.x) > maxVelocity) {
|
||||||
|
p.velocity.x = Math.sign(p.velocity.x) * maxVelocity;
|
||||||
|
p.acceleration.x = 0;
|
||||||
|
}
|
||||||
|
if (Math.abs(p.velocity.y) > maxVelocity) {
|
||||||
|
p.velocity.y = Math.sign(p.velocity.y) * maxVelocity;
|
||||||
|
p.acceleration.y = 0;
|
||||||
|
}
|
||||||
|
if (Math.abs(p.velocity.z) > maxVelocity) {
|
||||||
|
p.velocity.z = Math.sign(p.velocity.z) * maxVelocity;
|
||||||
|
p.acceleration.z = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Define an axis-aligned zone in which entities are not allowed to enter. */
|
||||||
|
/* This example zone corresponds to an area to the right of the spawnpoint
|
||||||
|
in your Sandbox. It's an area near the big rock to the right. If an entity
|
||||||
|
enters the zone, it'll move behind the rock.*/
|
||||||
|
var boxMin = {x: 25.5, y: -0.48, z: -9.9};
|
||||||
|
var boxMax = {x: 31.1, y: 4, z: -3.79};
|
||||||
|
var zero = {x: 0.0, y: 0.0, z: 0.0};
|
||||||
|
|
||||||
|
if (p.position) {
|
||||||
|
var x = p.position.x;
|
||||||
|
var y = p.position.y;
|
||||||
|
var z = p.position.z;
|
||||||
|
if ((x > boxMin.x && x < boxMax.x) &&
|
||||||
|
(y > boxMin.y && y < boxMax.y) &&
|
||||||
|
(z > boxMin.z && z < boxMax.z)) {
|
||||||
|
/* Move it to the origin of the zone */
|
||||||
|
p.position = boxMin;
|
||||||
|
p.velocity = zero;
|
||||||
|
p.acceleration = zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we make it this far, return the (possibly modified) properties. */
|
||||||
|
return p;
|
||||||
|
}
|
130
scripts/developer/debugging/debugAvatarMixer.js
Normal file
130
scripts/developer/debugging/debugAvatarMixer.js
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
//
|
||||||
|
// debugAvatarMixer.js
|
||||||
|
// scripts/developer/debugging
|
||||||
|
//
|
||||||
|
// Created by Brad Hefta-Gaub on 01/09/2017
|
||||||
|
// Copyright 2017 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
|
||||||
|
//
|
||||||
|
/* global Toolbars, Script, Users, Overlays, AvatarList, Controller, Camera, getControllerWorldLocation */
|
||||||
|
|
||||||
|
|
||||||
|
(function() { // BEGIN LOCAL_SCOPE
|
||||||
|
|
||||||
|
Script.include("/~/system/libraries/controllers.js");
|
||||||
|
|
||||||
|
var isShowingOverlays = true;
|
||||||
|
var debugOverlays = {};
|
||||||
|
|
||||||
|
function removeOverlays() {
|
||||||
|
// enumerate the overlays and remove them
|
||||||
|
var overlayKeys = Object.keys(debugOverlays);
|
||||||
|
|
||||||
|
for (var i = 0; i < overlayKeys.length; ++i) {
|
||||||
|
var avatarID = overlayKeys[i];
|
||||||
|
for (var j = 0; j < debugOverlays[avatarID].length; ++j) {
|
||||||
|
Overlays.deleteOverlay(debugOverlays[avatarID][j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debugOverlays = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateOverlays() {
|
||||||
|
if (isShowingOverlays) {
|
||||||
|
|
||||||
|
var identifiers = AvatarList.getAvatarIdentifiers();
|
||||||
|
|
||||||
|
for (var i = 0; i < identifiers.length; ++i) {
|
||||||
|
var avatarID = identifiers[i];
|
||||||
|
|
||||||
|
if (avatarID === null) {
|
||||||
|
// this is our avatar, skip it
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the position for this avatar
|
||||||
|
var avatar = AvatarList.getAvatar(avatarID);
|
||||||
|
var avatarPosition = avatar && avatar.position;
|
||||||
|
|
||||||
|
if (!avatarPosition) {
|
||||||
|
// we don't have a valid position for this avatar, skip it
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup a position for the overlay that is just above this avatar's head
|
||||||
|
var overlayPosition = avatar.getJointPosition("Head");
|
||||||
|
overlayPosition.y += 1.05;
|
||||||
|
|
||||||
|
var text = " All: " + AvatarManager.getAvatarDataRate(avatarID).toFixed(2) + "\n"
|
||||||
|
+" GP: " + AvatarManager.getAvatarDataRate(avatarID,"globalPosition").toFixed(2) + "\n"
|
||||||
|
+" LP: " + AvatarManager.getAvatarDataRate(avatarID,"localPosition").toFixed(2) + "\n"
|
||||||
|
+" BB: " + AvatarManager.getAvatarDataRate(avatarID,"avatarBoundingBox").toFixed(2) + "\n"
|
||||||
|
+" AO: " + AvatarManager.getAvatarDataRate(avatarID,"avatarOrientation").toFixed(2) + "\n"
|
||||||
|
+" AS: " + AvatarManager.getAvatarDataRate(avatarID,"avatarScale").toFixed(2) + "\n"
|
||||||
|
+" LA: " + AvatarManager.getAvatarDataRate(avatarID,"lookAtPosition").toFixed(2) + "\n"
|
||||||
|
+" AL: " + AvatarManager.getAvatarDataRate(avatarID,"audioLoudness").toFixed(2) + "\n"
|
||||||
|
+" SW: " + AvatarManager.getAvatarDataRate(avatarID,"sensorToWorkMatrix").toFixed(2) + "\n"
|
||||||
|
+" AF: " + AvatarManager.getAvatarDataRate(avatarID,"additionalFlags").toFixed(2) + "\n"
|
||||||
|
+" PI: " + AvatarManager.getAvatarDataRate(avatarID,"parentInfo").toFixed(2) + "\n"
|
||||||
|
+" FT: " + AvatarManager.getAvatarDataRate(avatarID,"faceTracker").toFixed(2) + "\n"
|
||||||
|
+" JD: " + AvatarManager.getAvatarDataRate(avatarID,"jointData").toFixed(2);
|
||||||
|
|
||||||
|
if (avatarID in debugOverlays) {
|
||||||
|
// keep the overlay above the current position of this avatar
|
||||||
|
Overlays.editOverlay(debugOverlays[avatarID][0], {
|
||||||
|
position: overlayPosition,
|
||||||
|
text: text
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// add the overlay above this avatar
|
||||||
|
var newOverlay = Overlays.addOverlay("text3d", {
|
||||||
|
position: overlayPosition,
|
||||||
|
dimensions: {
|
||||||
|
x: 1,
|
||||||
|
y: 13 * 0.13
|
||||||
|
},
|
||||||
|
lineHeight: 0.1,
|
||||||
|
font:{size:0.1},
|
||||||
|
text: text,
|
||||||
|
size: 1,
|
||||||
|
scale: 0.4,
|
||||||
|
color: { red: 255, green: 255, blue: 255},
|
||||||
|
alpha: 1,
|
||||||
|
solid: true,
|
||||||
|
isFacingAvatar: true,
|
||||||
|
drawInFront: true
|
||||||
|
});
|
||||||
|
|
||||||
|
debugOverlays[avatarID]=[newOverlay];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Script.update.connect(updateOverlays);
|
||||||
|
|
||||||
|
AvatarList.avatarRemovedEvent.connect(function(avatarID){
|
||||||
|
if (isShowingOverlays) {
|
||||||
|
// we are currently showing overlays and an avatar just went away
|
||||||
|
|
||||||
|
// first remove the rendered overlays
|
||||||
|
for (var j = 0; j < debugOverlays[avatarID].length; ++j) {
|
||||||
|
Overlays.deleteOverlay(debugOverlays[avatarID][j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete the saved ID of the overlay from our mod overlays object
|
||||||
|
delete debugOverlays[avatarID];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// cleanup the toolbar button and overlays when script is stopped
|
||||||
|
Script.scriptEnding.connect(function() {
|
||||||
|
removeOverlays();
|
||||||
|
});
|
||||||
|
|
||||||
|
}()); // END LOCAL_SCOPE
|
Loading…
Reference in a new issue