Merge branch 'master' of https://github.com/highfidelity/hifi into QmlMarketplace

This commit is contained in:
Roxanne Skelly 2019-01-25 13:45:04 -08:00
commit 6123c4ec96
29 changed files with 238 additions and 166 deletions

View file

@ -68,6 +68,13 @@ AudioMixer::AudioMixer(ReceivedMessage& message) :
// hash the available codecs (on the mixer) // hash the available codecs (on the mixer)
_availableCodecs.clear(); // Make sure struct is clean _availableCodecs.clear(); // Make sure struct is clean
auto pluginManager = DependencyManager::set<PluginManager>(); auto pluginManager = DependencyManager::set<PluginManager>();
// Only load codec plugins; for now assume codec plugins have 'codec' in their name.
auto codecPluginFilter = [](const QJsonObject& metaData) {
QJsonValue nameValue = metaData["MetaData"]["name"];
return nameValue.toString().contains("codec", Qt::CaseInsensitive);
};
pluginManager->setPluginFilter(codecPluginFilter);
auto codecPlugins = pluginManager->getCodecPlugins(); auto codecPlugins = pluginManager->getCodecPlugins();
for_each(codecPlugins.cbegin(), codecPlugins.cend(), for_each(codecPlugins.cbegin(), codecPlugins.cend(),
[&](const CodecPluginPointer& codec) { [&](const CodecPluginPointer& codec) {

View file

@ -54,7 +54,8 @@ FocusScope {
Image { Image {
z: -10 z: -10
id: loginDialogBackground id: loginDialogBackground
source: "LoginDialog/images/background.jpg" fillMode: Image.PreserveAspectCrop
source: "LoginDialog/images/background.png"
anchors.fill: parent anchors.fill: parent
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 960 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 KiB

View file

@ -55,7 +55,8 @@ FocusScope {
Image { Image {
z: -10 z: -10
id: loginDialogBackground id: loginDialogBackground
source: "LoginDialog/images/background.jpg" fillMode: Image.PreserveAspectCrop
source: "LoginDialog/images/background.png"
anchors.fill: parent anchors.fill: parent
} }

View file

@ -95,7 +95,8 @@ FocusScope {
Image { Image {
z: -10 z: -10
id: loginDialogBackground id: loginDialogBackground
source: "../LoginDialog/images/background_tablet.jpg" fillMode: Image.PreserveAspectCrop
source: "../LoginDialog/images/background_tablet.png"
anchors.fill: parent anchors.fill: parent
} }

View file

@ -148,7 +148,7 @@ Windows.ScrollingWindow {
} }
function canAddToWorld(path) { function canAddToWorld(path) {
var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i, /\.jpg\b/i, /\.png\b/i]; var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i, /\.jpg\b/i, /\.png\b/i, /\.gltf\b/i];
if (selectedItemCount > 1) { if (selectedItemCount > 1) {
return false; return false;

View file

@ -148,7 +148,7 @@ Rectangle {
} }
function canAddToWorld(path) { function canAddToWorld(path) {
var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i, /\.jpg\b/i, /\.png\b/i]; var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i, /\.jpg\b/i, /\.png\b/i, /\.gltf\b/i];
if (selectedItemCount > 1) { if (selectedItemCount > 1) {
return false; return false;

2
interface/src/avatar/AvatarManager.cpp Normal file → Executable file
View file

@ -270,7 +270,6 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
if (avatar->getSkeletonModel()->isLoaded()) { if (avatar->getSkeletonModel()->isLoaded()) {
// remove the orb if it is there // remove the orb if it is there
avatar->removeOrb(); avatar->removeOrb();
avatar->updateCollisionGroup(_myAvatar->getOtherAvatarsCollisionsEnabled());
if (avatar->needsPhysicsUpdate()) { if (avatar->needsPhysicsUpdate()) {
_avatarsToChangeInPhysics.insert(avatar); _avatarsToChangeInPhysics.insert(avatar);
} }
@ -376,7 +375,6 @@ void AvatarManager::simulateAvatarFades(float deltaTime) {
} }
AvatarSharedPointer AvatarManager::newSharedAvatar(const QUuid& sessionUUID) { AvatarSharedPointer AvatarManager::newSharedAvatar(const QUuid& sessionUUID) {
auto otherAvatar = new OtherAvatar(qApp->thread()); auto otherAvatar = new OtherAvatar(qApp->thread());
otherAvatar->setSessionUUID(sessionUUID); otherAvatar->setSessionUUID(sessionUUID);
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();

6
interface/src/avatar/AvatarMotionState.cpp Normal file → Executable file
View file

@ -15,7 +15,6 @@
#include <PhysicsEngine.h> #include <PhysicsEngine.h>
#include <PhysicsHelpers.h> #include <PhysicsHelpers.h>
AvatarMotionState::AvatarMotionState(OtherAvatarPointer avatar, const btCollisionShape* shape) : ObjectMotionState(shape), _avatar(avatar) { AvatarMotionState::AvatarMotionState(OtherAvatarPointer avatar, const btCollisionShape* shape) : ObjectMotionState(shape), _avatar(avatar) {
assert(_avatar); assert(_avatar);
_type = MOTIONSTATE_TYPE_AVATAR; _type = MOTIONSTATE_TYPE_AVATAR;
@ -172,7 +171,10 @@ QUuid AvatarMotionState::getSimulatorID() const {
// virtual // virtual
void AvatarMotionState::computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const { void AvatarMotionState::computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const {
group = _collisionGroup; group = _collisionGroup;
mask = _collisionGroup == BULLET_COLLISION_GROUP_COLLISIONLESS ? 0 : Physics::getDefaultCollisionMask(group); mask = Physics::getDefaultCollisionMask(group);
if (!_avatar->getCollideWithOtherAvatars()) {
mask &= ~(BULLET_COLLISION_GROUP_MY_AVATAR | BULLET_COLLISION_GROUP_OTHER_AVATAR);
}
} }
// virtual // virtual

View file

@ -205,12 +205,12 @@ MyAvatar::MyAvatar(QThread* thread) :
if (recordingInterface->getPlayFromCurrentLocation()) { if (recordingInterface->getPlayFromCurrentLocation()) {
setRecordingBasis(); setRecordingBasis();
} }
_previousCollisionGroup = _characterController.computeCollisionGroup(); _previousCollisionMask = _characterController.computeCollisionMask();
_characterController.setCollisionless(true); _characterController.setCollisionless(true);
} else { } else {
clearRecordingBasis(); clearRecordingBasis();
useFullAvatarURL(_fullAvatarURLFromPreferences, _fullAvatarModelName); useFullAvatarURL(_fullAvatarURLFromPreferences, _fullAvatarModelName);
if (_previousCollisionGroup != BULLET_COLLISION_GROUP_COLLISIONLESS) { if (_previousCollisionMask != BULLET_COLLISION_MASK_COLLISIONLESS) {
_characterController.setCollisionless(false); _characterController.setCollisionless(false);
} }
} }
@ -2534,7 +2534,7 @@ void MyAvatar::updateMotors() {
float verticalMotorTimescale; float verticalMotorTimescale;
if (_characterController.getState() == CharacterController::State::Hover || if (_characterController.getState() == CharacterController::State::Hover ||
_characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) { _characterController.computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS) {
horizontalMotorTimescale = FLYING_MOTOR_TIMESCALE; horizontalMotorTimescale = FLYING_MOTOR_TIMESCALE;
verticalMotorTimescale = FLYING_MOTOR_TIMESCALE; verticalMotorTimescale = FLYING_MOTOR_TIMESCALE;
} else { } else {
@ -2544,7 +2544,7 @@ void MyAvatar::updateMotors() {
if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) { if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) {
if (_characterController.getState() == CharacterController::State::Hover || if (_characterController.getState() == CharacterController::State::Hover ||
_characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) { _characterController.computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS) {
motorRotation = getMyHead()->getHeadOrientation(); motorRotation = getMyHead()->getHeadOrientation();
} else { } else {
// non-hovering = walking: follow camera twist about vertical but not lift // non-hovering = walking: follow camera twist about vertical but not lift
@ -2599,7 +2599,7 @@ void MyAvatar::prepareForPhysicsSimulation() {
qDebug() << "Warning: getParentVelocity failed" << getID(); qDebug() << "Warning: getParentVelocity failed" << getID();
parentVelocity = glm::vec3(); parentVelocity = glm::vec3();
} }
_characterController.handleChangedCollisionGroup(); _characterController.handleChangedCollisionMask();
_characterController.setParentVelocity(parentVelocity); _characterController.setParentVelocity(parentVelocity);
_characterController.setScaleFactor(getSensorToWorldScale()); _characterController.setScaleFactor(getSensorToWorldScale());
@ -3279,7 +3279,7 @@ void MyAvatar::updateOrientation(float deltaTime) {
head->setBaseRoll(ROLL(euler)); head->setBaseRoll(ROLL(euler));
} else { } else {
head->setBaseYaw(0.0f); head->setBaseYaw(0.0f);
head->setBasePitch(getHead()->getBasePitch() + getDriveKey(PITCH) * _pitchSpeed * deltaTime head->setBasePitch(getHead()->getBasePitch() + getDriveKey(PITCH) * _pitchSpeed * deltaTime
+ getDriveKey(DELTA_PITCH) * _pitchSpeed / PITCH_SPEED_DEFAULT); + getDriveKey(DELTA_PITCH) * _pitchSpeed / PITCH_SPEED_DEFAULT);
head->setBaseRoll(0.0f); head->setBaseRoll(0.0f);
} }
@ -3325,7 +3325,7 @@ void MyAvatar::updateActionMotor(float deltaTime) {
glm::vec3 direction = forward + right; glm::vec3 direction = forward + right;
if (state == CharacterController::State::Hover || if (state == CharacterController::State::Hover ||
_characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) { _characterController.computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS) {
glm::vec3 up = (getDriveKey(TRANSLATE_Y)) * IDENTITY_UP; glm::vec3 up = (getDriveKey(TRANSLATE_Y)) * IDENTITY_UP;
direction += up; direction += up;
} }
@ -3881,7 +3881,7 @@ void MyAvatar::setCollisionsEnabled(bool enabled) {
bool MyAvatar::getCollisionsEnabled() { bool MyAvatar::getCollisionsEnabled() {
// may return 'false' even though the collisionless option was requested // may return 'false' even though the collisionless option was requested
// because the zone may disallow collisionless avatars // because the zone may disallow collisionless avatars
return _characterController.computeCollisionGroup() != BULLET_COLLISION_GROUP_COLLISIONLESS; return _characterController.computeCollisionMask() != BULLET_COLLISION_MASK_COLLISIONLESS;
} }
void MyAvatar::setOtherAvatarsCollisionsEnabled(bool enabled) { void MyAvatar::setOtherAvatarsCollisionsEnabled(bool enabled) {
@ -3890,7 +3890,11 @@ void MyAvatar::setOtherAvatarsCollisionsEnabled(bool enabled) {
QMetaObject::invokeMethod(this, "setOtherAvatarsCollisionsEnabled", Q_ARG(bool, enabled)); QMetaObject::invokeMethod(this, "setOtherAvatarsCollisionsEnabled", Q_ARG(bool, enabled));
return; return;
} }
bool change = _collideWithOtherAvatars != enabled;
_collideWithOtherAvatars = enabled; _collideWithOtherAvatars = enabled;
if (change) {
setCollisionWithOtherAvatarsFlags();
}
emit otherAvatarsCollisionsEnabledChanged(enabled); emit otherAvatarsCollisionsEnabledChanged(enabled);
} }
@ -3898,6 +3902,11 @@ bool MyAvatar::getOtherAvatarsCollisionsEnabled() {
return _collideWithOtherAvatars; return _collideWithOtherAvatars;
} }
void MyAvatar::setCollisionWithOtherAvatarsFlags() {
_characterController.setCollideWithOtherAvatars(_collideWithOtherAvatars);
_characterController.setPendingFlagsUpdateCollisionMask();
}
void MyAvatar::updateCollisionCapsuleCache() { void MyAvatar::updateCollisionCapsuleCache() {
glm::vec3 start, end; glm::vec3 start, end;
float radius; float radius;

4
interface/src/avatar/MyAvatar.h Normal file → Executable file
View file

@ -297,6 +297,8 @@ public:
void reset(bool andRecenter = false, bool andReload = true, bool andHead = true); void reset(bool andRecenter = false, bool andReload = true, bool andHead = true);
void setCollisionWithOtherAvatarsFlags() override;
/**jsdoc /**jsdoc
* @function MyAvatar.resetSensorsAndBody * @function MyAvatar.resetSensorsAndBody
*/ */
@ -1732,7 +1734,7 @@ private:
SharedSoundPointer _collisionSound; SharedSoundPointer _collisionSound;
MyCharacterController _characterController; MyCharacterController _characterController;
int32_t _previousCollisionGroup { BULLET_COLLISION_GROUP_MY_AVATAR }; int32_t _previousCollisionMask { BULLET_COLLISION_MASK_MY_AVATAR };
AvatarWeakPointer _lookAtTargetAvatar; AvatarWeakPointer _lookAtTargetAvatar;
glm::vec3 _targetAvatarPosition; glm::vec3 _targetAvatarPosition;

View file

@ -202,6 +202,29 @@ bool MyCharacterController::testRayShotgun(const glm::vec3& position, const glm:
return result.hitFraction < 1.0f; return result.hitFraction < 1.0f;
} }
int32_t MyCharacterController::computeCollisionMask() const {
int32_t collisionMask = BULLET_COLLISION_MASK_MY_AVATAR;
if (_collisionless && _collisionlessAllowed) {
collisionMask = BULLET_COLLISION_MASK_COLLISIONLESS;
} else if (!_collideWithOtherAvatars) {
collisionMask &= ~BULLET_COLLISION_GROUP_OTHER_AVATAR;
}
return collisionMask;
}
void MyCharacterController::handleChangedCollisionMask() {
if (_pendingFlags & PENDING_FLAG_UPDATE_COLLISION_MASK) {
// ATM the easiest way to update collision groups/masks is to remove/re-add the RigidBody
if (_dynamicsWorld) {
_dynamicsWorld->removeRigidBody(_rigidBody);
int32_t collisionMask = computeCollisionMask();
_dynamicsWorld->addRigidBody(_rigidBody, BULLET_COLLISION_GROUP_MY_AVATAR, collisionMask);
}
_pendingFlags &= ~PENDING_FLAG_UPDATE_COLLISION_MASK;
updateCurrentGravity();
}
}
btConvexHullShape* MyCharacterController::computeShape() const { btConvexHullShape* MyCharacterController::computeShape() const {
// HACK: the avatar collides using convex hull with a collision margin equal to // HACK: the avatar collides using convex hull with a collision margin equal to
// the old capsule radius. Two points define a capsule and additional points are // the old capsule radius. Two points define a capsule and additional points are

6
interface/src/avatar/MyCharacterController.h Normal file → Executable file
View file

@ -42,6 +42,12 @@ public:
void setDensity(btScalar density) { _density = density; } void setDensity(btScalar density) { _density = density; }
int32_t computeCollisionMask() const override;
void handleChangedCollisionMask() override;
bool _collideWithOtherAvatars{ true };
void setCollideWithOtherAvatars(bool collideWithOtherAvatars) { _collideWithOtherAvatars = collideWithOtherAvatars; }
protected: protected:
void initRayShotgun(const btCollisionWorld* world); void initRayShotgun(const btCollisionWorld* world);
void updateMassProperties() override; void updateMassProperties() override;

4
interface/src/avatar/MySkeletonModel.cpp Normal file → Executable file
View file

@ -187,7 +187,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
} }
} }
bool isFlying = (myAvatar->getCharacterController()->getState() == CharacterController::State::Hover || myAvatar->getCharacterController()->computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS); bool isFlying = (myAvatar->getCharacterController()->getState() == CharacterController::State::Hover || myAvatar->getCharacterController()->computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS);
if (isFlying != _prevIsFlying) { if (isFlying != _prevIsFlying) {
const float FLY_TO_IDLE_HIPS_TRANSITION_TIME = 0.5f; const float FLY_TO_IDLE_HIPS_TRANSITION_TIME = 0.5f;
_flyIdleTimer = FLY_TO_IDLE_HIPS_TRANSITION_TIME; _flyIdleTimer = FLY_TO_IDLE_HIPS_TRANSITION_TIME;
@ -198,7 +198,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
// if hips are not under direct control, estimate the hips position. // if hips are not under direct control, estimate the hips position.
if (avatarHeadPose.isValid() && !(params.primaryControllerFlags[Rig::PrimaryControllerType_Hips] & (uint8_t)Rig::ControllerFlags::Enabled)) { if (avatarHeadPose.isValid() && !(params.primaryControllerFlags[Rig::PrimaryControllerType_Hips] & (uint8_t)Rig::ControllerFlags::Enabled)) {
bool isFlying = (myAvatar->getCharacterController()->getState() == CharacterController::State::Hover || myAvatar->getCharacterController()->computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS); bool isFlying = (myAvatar->getCharacterController()->getState() == CharacterController::State::Hover || myAvatar->getCharacterController()->computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS);
// timescale in seconds // timescale in seconds
const float TRANS_HORIZ_TIMESCALE = 0.15f; const float TRANS_HORIZ_TIMESCALE = 0.15f;

12
interface/src/avatar/OtherAvatar.cpp Normal file → Executable file
View file

@ -135,17 +135,9 @@ void OtherAvatar::rebuildCollisionShape() {
} }
} }
void OtherAvatar::updateCollisionGroup(bool myAvatarCollide) { void OtherAvatar::setCollisionWithOtherAvatarsFlags() {
if (_motionState) { if (_motionState) {
bool collides = _motionState->getCollisionGroup() == BULLET_COLLISION_GROUP_OTHER_AVATAR && myAvatarCollide; _motionState->addDirtyFlags(Simulation::DIRTY_COLLISION_GROUP);
if (_collideWithOtherAvatars != collides) {
if (!myAvatarCollide) {
_collideWithOtherAvatars = false;
}
auto newCollisionGroup = _collideWithOtherAvatars ? BULLET_COLLISION_GROUP_OTHER_AVATAR : BULLET_COLLISION_GROUP_COLLISIONLESS;
_motionState->setCollisionGroup(newCollisionGroup);
_motionState->addDirtyFlags(Simulation::DIRTY_COLLISION_GROUP);
}
} }
} }

4
interface/src/avatar/OtherAvatar.h Normal file → Executable file
View file

@ -46,7 +46,9 @@ public:
bool shouldBeInPhysicsSimulation() const; bool shouldBeInPhysicsSimulation() const;
bool needsPhysicsUpdate() const; bool needsPhysicsUpdate() const;
void updateCollisionGroup(bool myAvatarCollide); bool getCollideWithOtherAvatars() const { return _collideWithOtherAvatars; }
void setCollisionWithOtherAvatarsFlags() override;
void simulate(float deltaTime, bool inView) override; void simulate(float deltaTime, bool inView) override;

View file

@ -237,8 +237,17 @@ void AnimSkeleton::buildSkeletonFromJoints(const std::vector<HFMJoint>& joints,
_relativeDefaultPoses = _absoluteDefaultPoses; _relativeDefaultPoses = _absoluteDefaultPoses;
convertAbsolutePosesToRelative(_relativeDefaultPoses); convertAbsolutePosesToRelative(_relativeDefaultPoses);
// build _jointIndicesByName hash
for (int i = 0; i < _jointsSize; i++) { for (int i = 0; i < _jointsSize; i++) {
_jointIndicesByName[_joints[i].name] = i; auto iter = _jointIndicesByName.find(_joints[i].name);
if (iter != _jointIndicesByName.end()) {
// prefer joints over meshes if there is a name collision.
if (_joints[i].isSkeletonJoint && !_joints[iter.value()].isSkeletonJoint) {
iter.value() = i;
}
} else {
_jointIndicesByName.insert(_joints[i].name, i);
}
} }
// build mirror map. // build mirror map.

19
libraries/avatars/src/AvatarData.cpp Normal file → Executable file
View file

@ -639,7 +639,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
// compute maxTranslationDimension before we send any joint data. // compute maxTranslationDimension before we send any joint data.
float maxTranslationDimension = 0.001f; float maxTranslationDimension = 0.001f;
for (int i = sendStatus.rotationsSent; i < numJoints; ++i) { for (int i = sendStatus.translationsSent; i < numJoints; ++i) {
const JointData& data = jointData[i]; const JointData& data = jointData[i];
if (!data.translationIsDefaultPose) { if (!data.translationIsDefaultPose) {
maxTranslationDimension = glm::max(fabsf(data.translation.x), maxTranslationDimension); maxTranslationDimension = glm::max(fabsf(data.translation.x), maxTranslationDimension);
@ -1172,6 +1172,9 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
sourceBuffer += sizeof(AvatarDataPacket::AdditionalFlags); sourceBuffer += sizeof(AvatarDataPacket::AdditionalFlags);
if (collideWithOtherAvatarsChanged) {
setCollisionWithOtherAvatarsFlags();
}
if (somethingChanged) { if (somethingChanged) {
_additionalFlagsChanged = now; _additionalFlagsChanged = now;
} }
@ -2428,7 +2431,8 @@ static const QString JSON_AVATAR_VERSION = QStringLiteral("version");
enum class JsonAvatarFrameVersion : int { enum class JsonAvatarFrameVersion : int {
JointRotationsInRelativeFrame = 0, JointRotationsInRelativeFrame = 0,
JointRotationsInAbsoluteFrame, JointRotationsInAbsoluteFrame,
JointDefaultPoseBits JointDefaultPoseBits,
JointUnscaledTranslations,
}; };
QJsonValue toJsonValue(const JointData& joint) { QJsonValue toJsonValue(const JointData& joint) {
@ -2445,7 +2449,16 @@ JointData jointDataFromJsonValue(int version, const QJsonValue& json) {
if (json.isArray()) { if (json.isArray()) {
QJsonArray array = json.toArray(); QJsonArray array = json.toArray();
result.rotation = quatFromJsonValue(array[0]); result.rotation = quatFromJsonValue(array[0]);
result.translation = vec3FromJsonValue(array[1]); result.translation = vec3FromJsonValue(array[1]);
// In old recordings, translations are scaled by _geometryOffset. Undo that scaling.
if (version < (int)JsonAvatarFrameVersion::JointUnscaledTranslations) {
// because we don't have access to the actual _geometryOffset used. we have to guess.
// most avatar FBX files were authored in centimeters.
const float METERS_TO_CENTIMETERS = 100.0f;
result.translation *= METERS_TO_CENTIMETERS;
}
if (version >= (int)JsonAvatarFrameVersion::JointDefaultPoseBits) { if (version >= (int)JsonAvatarFrameVersion::JointDefaultPoseBits) {
result.rotationIsDefaultPose = array[2].toBool(); result.rotationIsDefaultPose = array[2].toBool();
result.translationIsDefaultPose = array[3].toBool(); result.translationIsDefaultPose = array[3].toBool();
@ -2464,7 +2477,7 @@ void AvatarData::avatarEntityDataToJson(QJsonObject& root) const {
QJsonObject AvatarData::toJson() const { QJsonObject AvatarData::toJson() const {
QJsonObject root; QJsonObject root;
root[JSON_AVATAR_VERSION] = (int)JsonAvatarFrameVersion::JointDefaultPoseBits; root[JSON_AVATAR_VERSION] = (int)JsonAvatarFrameVersion::JointUnscaledTranslations;
if (!getSkeletonModelURL().isEmpty()) { if (!getSkeletonModelURL().isEmpty()) {
root[JSON_AVATAR_BODY_MODEL] = getSkeletonModelURL().toString(); root[JSON_AVATAR_BODY_MODEL] = getSkeletonModelURL().toString();

2
libraries/avatars/src/AvatarData.h Normal file → Executable file
View file

@ -496,6 +496,8 @@ public:
/// \return number of bytes parsed /// \return number of bytes parsed
virtual int parseDataFromBuffer(const QByteArray& buffer); virtual int parseDataFromBuffer(const QByteArray& buffer);
virtual void setCollisionWithOtherAvatarsFlags() {};
// Body Rotation (degrees) // Body Rotation (degrees)
float getBodyYaw() const; float getBodyYaw() const;
void setBodyYaw(float bodyYaw); void setBodyYaw(float bodyYaw);

View file

@ -131,6 +131,7 @@ public:
glm::vec3 geometricTranslation; glm::vec3 geometricTranslation;
glm::quat geometricRotation; glm::quat geometricRotation;
glm::vec3 geometricScaling; glm::vec3 geometricScaling;
bool isLimbNode; // is this FBXModel transform is a "LimbNode" i.e. a joint
}; };
glm::mat4 getGlobalTransform(const QMultiMap<QString, QString>& _connectionParentMap, glm::mat4 getGlobalTransform(const QMultiMap<QString, QString>& _connectionParentMap,
@ -559,9 +560,11 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
glm::vec3 geometricRotation; glm::vec3 geometricRotation;
glm::vec3 rotationMin, rotationMax; glm::vec3 rotationMin, rotationMax;
bool isLimbNode = object.properties.size() >= 3 && object.properties.at(2) == "LimbNode";
FBXModel fbxModel = { name, -1, glm::vec3(), glm::mat4(), glm::quat(), glm::quat(), glm::quat(), FBXModel fbxModel = { name, -1, glm::vec3(), glm::mat4(), glm::quat(), glm::quat(), glm::quat(),
glm::mat4(), glm::vec3(), glm::vec3(), glm::mat4(), glm::vec3(), glm::vec3(),
false, glm::vec3(), glm::quat(), glm::vec3(1.0f) }; false, glm::vec3(), glm::quat(), glm::vec3(1.0f), isLimbNode };
ExtractedMesh* mesh = NULL; ExtractedMesh* mesh = NULL;
QVector<ExtractedBlendshape> blendshapes; QVector<ExtractedBlendshape> blendshapes;
foreach (const FBXNode& subobject, object.children) { foreach (const FBXNode& subobject, object.children) {
@ -1258,6 +1261,7 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
// convert the models to joints // convert the models to joints
QVariantList freeJoints = mapping.values("freeJoint"); QVariantList freeJoints = mapping.values("freeJoint");
hfmModel.hasSkeletonJoints = false; hfmModel.hasSkeletonJoints = false;
foreach (const QString& modelID, modelIDs) { foreach (const QString& modelID, modelIDs) {
const FBXModel& fbxModel = fbxModels[modelID]; const FBXModel& fbxModel = fbxModels[modelID];
HFMJoint joint; HFMJoint joint;
@ -1288,6 +1292,8 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
joint.geometricTranslation = fbxModel.geometricTranslation; joint.geometricTranslation = fbxModel.geometricTranslation;
joint.geometricRotation = fbxModel.geometricRotation; joint.geometricRotation = fbxModel.geometricRotation;
joint.geometricScaling = fbxModel.geometricScaling; joint.geometricScaling = fbxModel.geometricScaling;
joint.isSkeletonJoint = fbxModel.isLimbNode;
hfmModel.hasSkeletonJoints = (hfmModel.hasSkeletonJoints || joint.isSkeletonJoint);
glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation; glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation;
@ -1311,14 +1317,6 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
joint.name = hfmModel.hfmToHifiJointNameMapping.key(fbxModel.name); joint.name = hfmModel.hfmToHifiJointNameMapping.key(fbxModel.name);
} }
foreach (const QString& childID, _connectionChildMap.values(modelID)) {
QString type = typeFlags.value(childID);
if (!type.isEmpty()) {
hfmModel.hasSkeletonJoints |= (joint.isSkeletonJoint = type.toLower().contains("Skeleton"));
break;
}
}
joint.bindTransformFoundInCluster = false; joint.bindTransformFoundInCluster = false;
hfmModel.joints.append(joint); hfmModel.joints.append(joint);

View file

@ -109,7 +109,8 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) {
} }
_dynamicsWorld = nullptr; _dynamicsWorld = nullptr;
} }
int32_t collisionGroup = computeCollisionGroup(); int32_t collisionMask = computeCollisionMask();
int32_t collisionGroup = BULLET_COLLISION_GROUP_MY_AVATAR;
if (_rigidBody) { if (_rigidBody) {
updateMassProperties(); updateMassProperties();
} }
@ -117,7 +118,7 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) {
// add to new world // add to new world
_dynamicsWorld = world; _dynamicsWorld = world;
_pendingFlags &= ~PENDING_FLAG_JUMP; _pendingFlags &= ~PENDING_FLAG_JUMP;
_dynamicsWorld->addRigidBody(_rigidBody, collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR); _dynamicsWorld->addRigidBody(_rigidBody, collisionGroup, collisionMask);
_dynamicsWorld->addAction(this); _dynamicsWorld->addAction(this);
// restore gravity settings because adding an object to the world overwrites its gravity setting // restore gravity settings because adding an object to the world overwrites its gravity setting
_rigidBody->setGravity(_currentGravity * _currentUp); _rigidBody->setGravity(_currentGravity * _currentUp);
@ -127,7 +128,7 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) {
assert(shape && shape->getShapeType() == CONVEX_HULL_SHAPE_PROXYTYPE); assert(shape && shape->getShapeType() == CONVEX_HULL_SHAPE_PROXYTYPE);
_ghost.setCharacterShape(static_cast<btConvexHullShape*>(shape)); _ghost.setCharacterShape(static_cast<btConvexHullShape*>(shape));
} }
_ghost.setCollisionGroupAndMask(collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR & (~ collisionGroup)); _ghost.setCollisionGroupAndMask(collisionGroup, collisionMask & (~ collisionGroup));
_ghost.setCollisionWorld(_dynamicsWorld); _ghost.setCollisionWorld(_dynamicsWorld);
_ghost.setRadiusAndHalfHeight(_radius, _halfHeight); _ghost.setRadiusAndHalfHeight(_radius, _halfHeight);
if (_rigidBody) { if (_rigidBody) {
@ -384,8 +385,8 @@ static const char* stateToStr(CharacterController::State state) {
#endif // #ifdef DEBUG_STATE_CHANGE #endif // #ifdef DEBUG_STATE_CHANGE
void CharacterController::updateCurrentGravity() { void CharacterController::updateCurrentGravity() {
int32_t collisionGroup = computeCollisionGroup(); int32_t collisionMask = computeCollisionMask();
if (_state == State::Hover || collisionGroup == BULLET_COLLISION_GROUP_COLLISIONLESS) { if (_state == State::Hover || collisionMask == BULLET_COLLISION_MASK_COLLISIONLESS) {
_currentGravity = 0.0f; _currentGravity = 0.0f;
} else { } else {
_currentGravity = _gravity; _currentGravity = _gravity;
@ -458,28 +459,7 @@ void CharacterController::setLocalBoundingBox(const glm::vec3& minCorner, const
void CharacterController::setCollisionless(bool collisionless) { void CharacterController::setCollisionless(bool collisionless) {
if (collisionless != _collisionless) { if (collisionless != _collisionless) {
_collisionless = collisionless; _collisionless = collisionless;
_pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_GROUP; _pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_MASK;
}
}
int32_t CharacterController::computeCollisionGroup() const {
if (_collisionless) {
return _collisionlessAllowed ? BULLET_COLLISION_GROUP_COLLISIONLESS : BULLET_COLLISION_GROUP_MY_AVATAR;
} else {
return BULLET_COLLISION_GROUP_MY_AVATAR;
}
}
void CharacterController::handleChangedCollisionGroup() {
if (_pendingFlags & PENDING_FLAG_UPDATE_COLLISION_GROUP) {
// ATM the easiest way to update collision groups is to remove/re-add the RigidBody
if (_dynamicsWorld) {
_dynamicsWorld->removeRigidBody(_rigidBody);
int32_t collisionGroup = computeCollisionGroup();
_dynamicsWorld->addRigidBody(_rigidBody, collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR);
}
_pendingFlags &= ~PENDING_FLAG_UPDATE_COLLISION_GROUP;
updateCurrentGravity();
} }
} }
@ -567,8 +547,8 @@ void CharacterController::applyMotor(int index, btScalar dt, btVector3& worldVel
btScalar angle = motor.rotation.getAngle(); btScalar angle = motor.rotation.getAngle();
btVector3 velocity = worldVelocity.rotate(axis, -angle); btVector3 velocity = worldVelocity.rotate(axis, -angle);
int32_t collisionGroup = computeCollisionGroup(); int32_t collisionMask = computeCollisionMask();
if (collisionGroup == BULLET_COLLISION_GROUP_COLLISIONLESS || if (collisionMask == BULLET_COLLISION_MASK_COLLISIONLESS ||
_state == State::Hover || motor.hTimescale == motor.vTimescale) { _state == State::Hover || motor.hTimescale == motor.vTimescale) {
// modify velocity // modify velocity
btScalar tau = dt / motor.hTimescale; btScalar tau = dt / motor.hTimescale;
@ -708,11 +688,11 @@ void CharacterController::updateState() {
btVector3 rayStart = _position; btVector3 rayStart = _position;
btScalar rayLength = _radius; btScalar rayLength = _radius;
int32_t collisionGroup = computeCollisionGroup(); int32_t collisionMask = computeCollisionMask();
if (collisionGroup == BULLET_COLLISION_GROUP_MY_AVATAR) { if (collisionMask == BULLET_COLLISION_MASK_COLLISIONLESS) {
rayLength += _scaleFactor * DEFAULT_AVATAR_FALL_HEIGHT;
} else {
rayLength += MIN_HOVER_HEIGHT; rayLength += MIN_HOVER_HEIGHT;
} else {
rayLength += _scaleFactor * DEFAULT_AVATAR_FALL_HEIGHT;
} }
btVector3 rayEnd = rayStart - rayLength * _currentUp; btVector3 rayEnd = rayStart - rayLength * _currentUp;
@ -746,69 +726,7 @@ void CharacterController::updateState() {
// disable normal state transitions while collisionless // disable normal state transitions while collisionless
const btScalar MAX_WALKING_SPEED = 2.65f; const btScalar MAX_WALKING_SPEED = 2.65f;
if (collisionGroup == BULLET_COLLISION_GROUP_MY_AVATAR) { if (collisionMask == BULLET_COLLISION_MASK_COLLISIONLESS) {
switch (_state) {
case State::Ground:
if (!rayHasHit && !_hasSupport) {
SET_STATE(State::Hover, "no ground detected");
} else if (_pendingFlags & PENDING_FLAG_JUMP && _jumpButtonDownCount != _takeoffJumpButtonID) {
_takeoffJumpButtonID = _jumpButtonDownCount;
_takeoffToInAirStartTime = now;
SET_STATE(State::Takeoff, "jump pressed");
} else if (rayHasHit && !_hasSupport && _floorDistance > GROUND_TO_FLY_THRESHOLD) {
SET_STATE(State::InAir, "falling");
}
break;
case State::Takeoff:
if (!rayHasHit && !_hasSupport) {
SET_STATE(State::Hover, "no ground");
} else if ((now - _takeoffToInAirStartTime) > TAKE_OFF_TO_IN_AIR_PERIOD) {
SET_STATE(State::InAir, "takeoff done");
// compute jumpSpeed based on the scaled jump height for the default avatar in default gravity.
const float jumpHeight = std::max(_scaleFactor * DEFAULT_AVATAR_JUMP_HEIGHT, DEFAULT_AVATAR_MIN_JUMP_HEIGHT);
const float jumpSpeed = sqrtf(2.0f * -DEFAULT_AVATAR_GRAVITY * jumpHeight);
velocity += jumpSpeed * _currentUp;
_rigidBody->setLinearVelocity(velocity);
}
break;
case State::InAir: {
const float jumpHeight = std::max(_scaleFactor * DEFAULT_AVATAR_JUMP_HEIGHT, DEFAULT_AVATAR_MIN_JUMP_HEIGHT);
const float jumpSpeed = sqrtf(2.0f * -DEFAULT_AVATAR_GRAVITY * jumpHeight);
if ((velocity.dot(_currentUp) <= (jumpSpeed / 2.0f)) && ((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport)) {
SET_STATE(State::Ground, "hit ground");
} else if (_flyingAllowed) {
btVector3 desiredVelocity = _targetVelocity;
if (desiredVelocity.length2() < MIN_TARGET_SPEED_SQUARED) {
desiredVelocity = btVector3(0.0f, 0.0f, 0.0f);
}
bool vertTargetSpeedIsNonZero = desiredVelocity.dot(_currentUp) > MIN_TARGET_SPEED;
if ((jumpButtonHeld || vertTargetSpeedIsNonZero) && (_takeoffJumpButtonID != _jumpButtonDownCount)) {
SET_STATE(State::Hover, "double jump button");
} else if ((jumpButtonHeld || vertTargetSpeedIsNonZero) && (now - _jumpButtonDownStartTime) > JUMP_TO_HOVER_PERIOD) {
SET_STATE(State::Hover, "jump button held");
} else if (_floorDistance > _scaleFactor * DEFAULT_AVATAR_FALL_HEIGHT) {
// Transition to hover if we are above the fall threshold
SET_STATE(State::Hover, "above fall threshold");
}
} else if (!rayHasHit && !_hasSupport) {
SET_STATE(State::Hover, "no ground detected");
}
break;
}
case State::Hover:
btScalar horizontalSpeed = (velocity - velocity.dot(_currentUp) * _currentUp).length();
bool flyingFast = horizontalSpeed > (MAX_WALKING_SPEED * 0.75f);
if (!_flyingAllowed && rayHasHit) {
SET_STATE(State::InAir, "flying not allowed");
} else if ((_floorDistance < MIN_HOVER_HEIGHT) && !jumpButtonHeld && !flyingFast) {
SET_STATE(State::InAir, "near ground");
} else if (((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport) && !flyingFast) {
SET_STATE(State::Ground, "touching ground");
}
break;
}
} else {
// when collisionless: only switch between State::Ground and State::Hover // when collisionless: only switch between State::Ground and State::Hover
// and bypass state debugging // and bypass state debugging
if (rayHasHit) { if (rayHasHit) {
@ -820,6 +738,68 @@ void CharacterController::updateState() {
} else { } else {
_state = State::Hover; _state = State::Hover;
} }
} else {
switch (_state) {
case State::Ground:
if (!rayHasHit && !_hasSupport) {
SET_STATE(State::Hover, "no ground detected");
} else if (_pendingFlags & PENDING_FLAG_JUMP && _jumpButtonDownCount != _takeoffJumpButtonID) {
_takeoffJumpButtonID = _jumpButtonDownCount;
_takeoffToInAirStartTime = now;
SET_STATE(State::Takeoff, "jump pressed");
} else if (rayHasHit && !_hasSupport && _floorDistance > GROUND_TO_FLY_THRESHOLD) {
SET_STATE(State::InAir, "falling");
}
break;
case State::Takeoff:
if (!rayHasHit && !_hasSupport) {
SET_STATE(State::Hover, "no ground");
} else if ((now - _takeoffToInAirStartTime) > TAKE_OFF_TO_IN_AIR_PERIOD) {
SET_STATE(State::InAir, "takeoff done");
// compute jumpSpeed based on the scaled jump height for the default avatar in default gravity.
const float jumpHeight = std::max(_scaleFactor * DEFAULT_AVATAR_JUMP_HEIGHT, DEFAULT_AVATAR_MIN_JUMP_HEIGHT);
const float jumpSpeed = sqrtf(2.0f * -DEFAULT_AVATAR_GRAVITY * jumpHeight);
velocity += jumpSpeed * _currentUp;
_rigidBody->setLinearVelocity(velocity);
}
break;
case State::InAir: {
const float jumpHeight = std::max(_scaleFactor * DEFAULT_AVATAR_JUMP_HEIGHT, DEFAULT_AVATAR_MIN_JUMP_HEIGHT);
const float jumpSpeed = sqrtf(2.0f * -DEFAULT_AVATAR_GRAVITY * jumpHeight);
if ((velocity.dot(_currentUp) <= (jumpSpeed / 2.0f)) && ((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport)) {
SET_STATE(State::Ground, "hit ground");
} else if (_flyingAllowed) {
btVector3 desiredVelocity = _targetVelocity;
if (desiredVelocity.length2() < MIN_TARGET_SPEED_SQUARED) {
desiredVelocity = btVector3(0.0f, 0.0f, 0.0f);
}
bool vertTargetSpeedIsNonZero = desiredVelocity.dot(_currentUp) > MIN_TARGET_SPEED;
if ((jumpButtonHeld || vertTargetSpeedIsNonZero) && (_takeoffJumpButtonID != _jumpButtonDownCount)) {
SET_STATE(State::Hover, "double jump button");
} else if ((jumpButtonHeld || vertTargetSpeedIsNonZero) && (now - _jumpButtonDownStartTime) > JUMP_TO_HOVER_PERIOD) {
SET_STATE(State::Hover, "jump button held");
} else if (_floorDistance > _scaleFactor * DEFAULT_AVATAR_FALL_HEIGHT) {
// Transition to hover if we are above the fall threshold
SET_STATE(State::Hover, "above fall threshold");
}
} else if (!rayHasHit && !_hasSupport) {
SET_STATE(State::Hover, "no ground detected");
}
break;
}
case State::Hover:
btScalar horizontalSpeed = (velocity - velocity.dot(_currentUp) * _currentUp).length();
bool flyingFast = horizontalSpeed > (MAX_WALKING_SPEED * 0.75f);
if (!_flyingAllowed && rayHasHit) {
SET_STATE(State::InAir, "flying not allowed");
} else if ((_floorDistance < MIN_HOVER_HEIGHT) && !jumpButtonHeld && !flyingFast) {
SET_STATE(State::InAir, "near ground");
} else if (((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport) && !flyingFast) {
SET_STATE(State::Ground, "touching ground");
}
break;
}
} }
} }
@ -866,6 +846,6 @@ void CharacterController::setFlyingAllowed(bool value) {
void CharacterController::setCollisionlessAllowed(bool value) { void CharacterController::setCollisionlessAllowed(bool value) {
if (value != _collisionlessAllowed) { if (value != _collisionlessAllowed) {
_collisionlessAllowed = value; _collisionlessAllowed = value;
_pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_GROUP; _pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_MASK;
} }
} }

8
libraries/physics/src/CharacterController.h Normal file → Executable file
View file

@ -30,7 +30,7 @@ const uint32_t PENDING_FLAG_ADD_TO_SIMULATION = 1U << 0;
const uint32_t PENDING_FLAG_REMOVE_FROM_SIMULATION = 1U << 1; const uint32_t PENDING_FLAG_REMOVE_FROM_SIMULATION = 1U << 1;
const uint32_t PENDING_FLAG_UPDATE_SHAPE = 1U << 2; const uint32_t PENDING_FLAG_UPDATE_SHAPE = 1U << 2;
const uint32_t PENDING_FLAG_JUMP = 1U << 3; const uint32_t PENDING_FLAG_JUMP = 1U << 3;
const uint32_t PENDING_FLAG_UPDATE_COLLISION_GROUP = 1U << 4; const uint32_t PENDING_FLAG_UPDATE_COLLISION_MASK = 1U << 4;
const uint32_t PENDING_FLAG_RECOMPUTE_FLYING = 1U << 5; const uint32_t PENDING_FLAG_RECOMPUTE_FLYING = 1U << 5;
const float DEFAULT_MIN_FLOOR_NORMAL_DOT_UP = cosf(PI / 3.0f); const float DEFAULT_MIN_FLOOR_NORMAL_DOT_UP = cosf(PI / 3.0f);
@ -120,14 +120,16 @@ public:
bool isStuck() const { return _isStuck; } bool isStuck() const { return _isStuck; }
void setCollisionless(bool collisionless); void setCollisionless(bool collisionless);
int32_t computeCollisionGroup() const;
void handleChangedCollisionGroup(); virtual int32_t computeCollisionMask() const = 0;
virtual void handleChangedCollisionMask() = 0;
bool getRigidBodyLocation(glm::vec3& avatarRigidBodyPosition, glm::quat& avatarRigidBodyRotation); bool getRigidBodyLocation(glm::vec3& avatarRigidBodyPosition, glm::quat& avatarRigidBodyRotation);
void setFlyingAllowed(bool value); void setFlyingAllowed(bool value);
void setCollisionlessAllowed(bool value); void setCollisionlessAllowed(bool value);
void setPendingFlagsUpdateCollisionMask(){ _pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_MASK; }
protected: protected:
#ifdef DEBUG_STATE_CHANGE #ifdef DEBUG_STATE_CHANGE

View file

@ -14,6 +14,11 @@
#include <QtCore/QDebug> #include <QtCore/QDebug>
#include <QtCore/QPluginLoader> #include <QtCore/QPluginLoader>
//#define HIFI_PLUGINMANAGER_DEBUG
#if defined(HIFI_PLUGINMANAGER_DEBUG)
#include <QJsonDocument>
#endif
#include <DependencyManager.h> #include <DependencyManager.h>
#include <UserActivityLogger.h> #include <UserActivityLogger.h>
@ -79,10 +84,7 @@ bool isDisabled(QJsonObject metaData) {
return false; return false;
} }
using Loader = QSharedPointer<QPluginLoader>; auto PluginManager::getLoadedPlugins() const -> const LoaderList& {
using LoaderList = QList<Loader>;
const LoaderList& getLoadedPlugins() {
static std::once_flag once; static std::once_flag once;
static LoaderList loadedPlugins; static LoaderList loadedPlugins;
std::call_once(once, [&] { std::call_once(once, [&] {
@ -106,15 +108,25 @@ const LoaderList& getLoadedPlugins() {
for (auto plugin : candidates) { for (auto plugin : candidates) {
qCDebug(plugins) << "Attempting plugin" << qPrintable(plugin); qCDebug(plugins) << "Attempting plugin" << qPrintable(plugin);
QSharedPointer<QPluginLoader> loader(new QPluginLoader(pluginPath + plugin)); QSharedPointer<QPluginLoader> loader(new QPluginLoader(pluginPath + plugin));
const QJsonObject pluginMetaData = loader->metaData();
if (isDisabled(loader->metaData())) { #if defined(HIFI_PLUGINMANAGER_DEBUG)
QJsonDocument metaDataDoc(pluginMetaData);
qCInfo(plugins) << "Metadata for " << qPrintable(plugin) << ": " << QString(metaDataDoc.toJson());
#endif
if (isDisabled(pluginMetaData)) {
qCWarning(plugins) << "Plugin" << qPrintable(plugin) << "is disabled"; qCWarning(plugins) << "Plugin" << qPrintable(plugin) << "is disabled";
// Skip this one, it's disabled // Skip this one, it's disabled
continue; continue;
} }
if (getPluginInterfaceVersionFromMetaData(loader->metaData()) != HIFI_PLUGIN_INTERFACE_VERSION) {
if (!_pluginFilter(pluginMetaData)) {
qCDebug(plugins) << "Plugin" << qPrintable(plugin) << "doesn't pass provided filter";
continue;
}
if (getPluginInterfaceVersionFromMetaData(pluginMetaData) != HIFI_PLUGIN_INTERFACE_VERSION) {
qCWarning(plugins) << "Plugin" << qPrintable(plugin) << "interface version doesn't match, not loading:" qCWarning(plugins) << "Plugin" << qPrintable(plugin) << "interface version doesn't match, not loading:"
<< getPluginInterfaceVersionFromMetaData(loader->metaData()) << getPluginInterfaceVersionFromMetaData(pluginMetaData)
<< "doesn't match" << HIFI_PLUGIN_INTERFACE_VERSION; << "doesn't match" << HIFI_PLUGIN_INTERFACE_VERSION;
continue; continue;
} }

View file

@ -13,8 +13,7 @@
#include "Forward.h" #include "Forward.h"
class QPluginLoader;
class PluginManager;
using PluginManagerPointer = QSharedPointer<PluginManager>; using PluginManagerPointer = QSharedPointer<PluginManager>;
class PluginManager : public QObject, public Dependency { class PluginManager : public QObject, public Dependency {
@ -47,6 +46,9 @@ public:
void setInputPluginSettingsPersister(const InputPluginSettingsPersister& persister); void setInputPluginSettingsPersister(const InputPluginSettingsPersister& persister);
QStringList getRunningInputDeviceNames() const; QStringList getRunningInputDeviceNames() const;
using PluginFilter = std::function<bool(const QJsonObject&)>;
void setPluginFilter(PluginFilter pluginFilter) { _pluginFilter = pluginFilter; }
signals: signals:
void inputDeviceRunningChanged(const QString& pluginName, bool isRunning, const QStringList& runningDevices); void inputDeviceRunningChanged(const QString& pluginName, bool isRunning, const QStringList& runningDevices);
@ -60,6 +62,12 @@ private:
PluginContainer* _container { nullptr }; PluginContainer* _container { nullptr };
DisplayPluginList _displayPlugins; DisplayPluginList _displayPlugins;
InputPluginList _inputPlugins; InputPluginList _inputPlugins;
PluginFilter _pluginFilter { [](const QJsonObject&) { return true; } };
using Loader = QSharedPointer<QPluginLoader>;
using LoaderList = QList<Loader>;
const LoaderList& getLoadedPlugins() const;
}; };
// TODO: we should define this value in CMake, and then use CMake // TODO: we should define this value in CMake, and then use CMake

View file

@ -3313,7 +3313,8 @@ function loaded() {
} }
} }
let doSelectElement = lastEntityID === '"' + selectedEntityProperties.id + '"'; let hasSelectedEntityChanged = lastEntityID !== '"' + selectedEntityProperties.id + '"';
let doSelectElement = !hasSelectedEntityChanged;
// the event bridge and json parsing handle our avatar id string differently. // the event bridge and json parsing handle our avatar id string differently.
lastEntityID = '"' + selectedEntityProperties.id + '"'; lastEntityID = '"' + selectedEntityProperties.id + '"';
@ -3433,7 +3434,7 @@ function loaded() {
property.elColorPicker.style.backgroundColor = "rgb(" + propertyValue.red + "," + property.elColorPicker.style.backgroundColor = "rgb(" + propertyValue.red + "," +
propertyValue.green + "," + propertyValue.green + "," +
propertyValue.blue + ")"; propertyValue.blue + ")";
if ($(property.elColorPicker).attr('active') === 'true') { if (hasSelectedEntityChanged && $(property.elColorPicker).attr('active') === 'true') {
// Set the color picker inactive before setting the color, // Set the color picker inactive before setting the color,
// otherwise an update will be sent directly after setting it here. // otherwise an update will be sent directly after setting it here.
$(property.elColorPicker).attr('active', 'false'); $(property.elColorPicker).attr('active', 'false');

View file

@ -13,36 +13,39 @@ Nitpick has 5 functions, separated into separate tabs:
1. Web interface 1. Web interface
## Build (for developers) ## Build (for developers)
Nitpick is built as part of the High Fidelity build. Nitpick is built as part of the High Fidelity build.
XXXX refers to the version number - replace as necessary. For example, replace with 3.2.11
### Creating installers ### Creating installers
#### Windows #### Windows
1. Perform Release build
1. Verify that 7Zip is installed. 1. Verify that 7Zip is installed.
1. cd to the `build\tools\nitpick\Release` directory 1. cd to the `build\tools\nitpick\Release` directory
1. Delete any existing installers (named nitpick-installer-###.exe) 1. Delete any existing installers (named nitpick-installer-###.exe)
1. Select all, right-click and select 7-Zip->Add to archive... 1. Select all, right-click and select 7-Zip->Add to archive...
1. Set Archive format to 7z 1. Set Archive format to 7z
1. Check "Create SFX archive 1. Check "Create SFX archive
1. Enter installer name (i.e. `nitpick-installer-v1.2.exe`) 1. Enter installer name (i.e. `nitpick-installer-vXXXX.exe`)
1. Click "OK" 1. Click "OK"
1. Copy created installer to https://hifi-qa.s3.amazonaws.com/nitpick/Windows/nitpick-installer-v1.2.exe: aws s3 cp nitpick-installer-v1.2.exe s3://hifi-qa/nitpick/Mac/nitpick-installer-v1.2.exe 1. Copy created installer to https://hifi-qa.s3.amazonaws.com/nitpick/Windows/nitpick-installer-vXXXX.exe: aws s3 cp nitpick-installer-vXXXX.exe s3://hifi-qa/nitpick/Mac/nitpick-installer-vXXXX.exe
#### Mac #### Mac
These steps assume the hifi repository has been cloned to `~/hifi`. These steps assume the hifi repository has been cloned to `~/hifi`.
1. (first time) Install brew 1. (first time) Install brew
In a terminal: `/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)` In a terminal: `/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)`
1. (First time) install create-dmg: 1. (First time) install create-dmg:
In a terminal: `brew install create-dmg` In a terminal: `brew install create-dmg`
1. Perform Release build
1. In a terminal: cd to the `build/tools/nitpick/Release` folder 1. In a terminal: cd to the `build/tools/nitpick/Release` folder
1. Copy the quazip dynamic library (note final period): 1. Copy the quazip dynamic library (note final period):
In a terminal: `cp ~/hifi/build/ext/Xcode/quazip/project/lib/libquazip5.1.dylib .` In a terminal: `cp ~/hifi/build/ext/Xcode/quazip/project/lib/libquazip5.1.dylib .`
1. Change the loader instruction to find the dynamic library locally 1. Change the loader instruction to find the dynamic library locally
In a terminal: `install_name_tool -change ~/hifi/build/ext/Xcode/quazip/project/lib/libquazip5.1.dylib libquazip5.1.dylib nitpick` In a terminal: `install_name_tool -change ~/hifi/build/ext/Xcode/quazip/project/lib/libquazip5.1.dylib libquazip5.1.dylib nitpick`
1. Delete any existing disk images. In a terminal: `rm *.dmg` 1. Delete any existing disk images. In a terminal: `rm *.dmg`
1. Create installer (note final period).In a terminal: `create-dmg --volname nitpick-installer-v1.2 nitpick-installer-v1.2.dmg .` 1. Create installer (note final period).In a terminal: `create-dmg --volname nitpick-installer-vXXXX nitpick-installer-vXXXX.dmg .`
Make sure to wait for completion. Make sure to wait for completion.
1. Copy created installer to AWS: `~/Library/Python/3.7/bin/aws s3 cp nitpick-installer-v1.2.dmg s3://hifi-qa/nitpick/Mac/nitpick-installer-v1.2.dmg` 1. Copy created installer to AWS: `~/Library/Python/3.7/bin/aws s3 cp nitpick-installer-vXXXX.dmg s3://hifi-qa/nitpick/Mac/nitpick-installer-vXXXX.dmg`
### Installation ### Installation
#### Windows #### Windows
1. (First time) download and install vc_redist.x64.exe (available at https://hifi-qa.s3.amazonaws.com/nitpick/Windows/nitpick-installer-v1.2.exe) 1. (First time) download and install vc_redist.x64.exe (available at https://hifi-qa.s3.amazonaws.com/nitpick/Windows/nitpick-installer-vXXXX.exe)
1. (First time) download and install Python 3 from https://hifi-qa.s3.amazonaws.com/nitpick/Windows/python-3.7.0-amd64.exe (also located at https://www.python.org/downloads/) 1. (First time) download and install Python 3 from https://hifi-qa.s3.amazonaws.com/nitpick/Windows/python-3.7.0-amd64.exe (also located at https://www.python.org/downloads/)
1. After installation - create an environment variable called PYTHON_PATH and set it to the folder containing the Python executable. 1. After installation - create an environment variable called PYTHON_PATH and set it to the folder containing the Python executable.
1. (First time) download and install AWS CLI from https://hifi-qa.s3.amazonaws.com/nitpick/Windows/AWSCLI64PY3.msi (also available at https://aws.amazon.com/cli/ 1. (First time) download and install AWS CLI from https://hifi-qa.s3.amazonaws.com/nitpick/Windows/AWSCLI64PY3.msi (also available at https://aws.amazon.com/cli/
@ -52,7 +55,7 @@ These steps assume the hifi repository has been cloned to `~/hifi`.
1. Leave region name and ouput format as default [None] 1. Leave region name and ouput format as default [None]
1. Install the latest release of Boto3 via pip: `pip install boto3` 1. Install the latest release of Boto3 via pip: `pip install boto3`
1. Download the installer by browsing to [here](<https://hifi-qa.s3.amazonaws.com/nitpick/Windows/nitpick-installer-v1.2.exe>) 1. Download the installer by browsing to [here](<https://hifi-qa.s3.amazonaws.com/nitpick/Windows/nitpick-installer-vXXXX.exe>)
1. Double click on the installer and install to a convenient location 1. Double click on the installer and install to a convenient location
![](./setup_7z.PNG) ![](./setup_7z.PNG)
@ -76,14 +79,14 @@ In a terminal: `python3 get-pip.py --user`
1. Enter the secret key 1. Enter the secret key
1. Leave region name and ouput format as default [None] 1. Leave region name and ouput format as default [None]
1. Install the latest release of Boto3 via pip: pip3 install boto3 1. Install the latest release of Boto3 via pip: pip3 install boto3
1. Download the installer by browsing to [here](<https://hifi-qa.s3.amazonaws.com/nitpick/Mac/nitpick-installer-v1.2.dmg>). 1. Download the installer by browsing to [here](<https://hifi-qa.s3.amazonaws.com/nitpick/Mac/nitpick-installer-vXXXX.dmg>).
1. Double-click on the downloaded image to mount it 1. Double-click on the downloaded image to mount it
1. Create a folder for the nitpick files (e.g. ~/nitpick) 1. Create a folder for the nitpick files (e.g. ~/nitpick)
If this folder exists then delete all it's contents. If this folder exists then delete all it's contents.
1. Copy the downloaded files to the folder 1. Copy the downloaded files to the folder
In a terminal: In a terminal:
`cd ~/nitpick` `cd ~/nitpick`
`cp -r /Volumes/nitpick-installer-v1.2/* .` `cp -r /Volumes/nitpick-installer-vXXXX/* .`
1. __To run nitpick, cd to the folder that you copied to and run `./nitpick`__ 1. __To run nitpick, cd to the folder that you copied to and run `./nitpick`__
# Usage # Usage