diff --git a/interface/resources/qml/hifi/AvatarApp.qml b/interface/resources/qml/hifi/AvatarApp.qml index 57e4db062a..bfa37385a5 100644 --- a/interface/resources/qml/hifi/AvatarApp.qml +++ b/interface/resources/qml/hifi/AvatarApp.qml @@ -254,7 +254,8 @@ Rectangle { onSaveClicked: function() { var avatarSettings = { dominantHand : settings.dominantHandIsLeft ? 'left' : 'right', - collisionsEnabled : settings.avatarCollisionsOn, + collisionsEnabled : settings.environmentCollisionsOn, + otherAvatarsCollisionsEnabled : settings.otherAvatarsCollisionsOn, animGraphOverrideUrl : settings.avatarAnimationOverrideJSON, collisionSoundUrl : settings.avatarCollisionSoundUrl }; diff --git a/interface/resources/qml/hifi/avatarapp/Settings.qml b/interface/resources/qml/hifi/avatarapp/Settings.qml index cd892c17b1..d212186c5e 100644 --- a/interface/resources/qml/hifi/avatarapp/Settings.qml +++ b/interface/resources/qml/hifi/avatarapp/Settings.qml @@ -35,7 +35,8 @@ Rectangle { property real scaleValue: scaleSlider.value / 10 property alias dominantHandIsLeft: leftHandRadioButton.checked - property alias avatarCollisionsOn: collisionsEnabledRadiobutton.checked + property alias otherAvatarsCollisionsOn: otherAvatarsCollisionsEnabledCheckBox.checked + property alias environmentCollisionsOn: environmentCollisionsEnabledCheckBox.checked property alias avatarAnimationOverrideJSON: avatarAnimationUrlInputText.text property alias avatarAnimationJSON: avatarAnimationUrlInputText.placeholderText property alias avatarCollisionSoundUrl: avatarCollisionSoundUrlInputText.text @@ -54,11 +55,11 @@ Rectangle { } else { rightHandRadioButton.checked = true; } - + if (settings.otherAvatarsCollisionsEnabled) { + otherAvatarsCollisionsEnabledCheckBox.checked = true; + } if (settings.collisionsEnabled) { - collisionsEnabledRadiobutton.checked = true; - } else { - collisionsDisabledRadioButton.checked = true; + environmentCollisionsEnabledCheckBox.checked = true; } avatarAnimationJSON = settings.animGraphUrl; @@ -255,55 +256,43 @@ Rectangle { text: "Right" boxSize: 20 } + + HifiConstants { + id: hifi + } // TextStyle9 RalewaySemiBold { size: 17; Layout.row: 1 Layout.column: 0 - - text: "Avatar Collisions" + text: "Avatar collides with other avatars" } - ButtonGroup { - id: onOff - } - - HifiControlsUit.RadioButton { - id: collisionsEnabledRadiobutton - - Layout.row: 1 - Layout.column: 1 - Layout.leftMargin: -40 - ButtonGroup.group: onOff - - colorScheme: hifi.colorSchemes.light - fontSize: 17 - letterSpacing: 1.4 - checked: true - - text: "ON" - boxSize: 20 - } - - HifiConstants { - id: hifi - } - - HifiControlsUit.RadioButton { - id: collisionsDisabledRadioButton - + HifiControlsUit.CheckBox { + id: otherAvatarsCollisionsEnabledCheckBox; + boxSize: 20; Layout.row: 1 Layout.column: 2 - Layout.rightMargin: 20 - - ButtonGroup.group: onOff + Layout.leftMargin: 60 colorScheme: hifi.colorSchemes.light - fontSize: 17 - letterSpacing: 1.4 + } - text: "OFF" - boxSize: 20 + // TextStyle9 + RalewaySemiBold { + size: 17; + Layout.row: 2 + Layout.column: 0 + text: "Avatar collides with environment" + } + + HifiControlsUit.CheckBox { + id: environmentCollisionsEnabledCheckBox; + boxSize: 20; + Layout.row: 2 + Layout.column: 2 + Layout.leftMargin: 60 + colorScheme: hifi.colorSchemes.light } } diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 21e59c06d8..1508581e42 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -268,6 +268,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { if (avatar->getSkeletonModel()->isLoaded()) { // remove the orb if it is there avatar->removeOrb(); + avatar->updateCollisionGroup(_myAvatar->getOtherAvatarsCollisionsEnabled()); if (avatar->needsPhysicsUpdate()) { _avatarsToChangeInPhysics.insert(avatar); } diff --git a/interface/src/avatar/AvatarMotionState.cpp b/interface/src/avatar/AvatarMotionState.cpp index ca67f634c8..3fa59ea967 100644 --- a/interface/src/avatar/AvatarMotionState.cpp +++ b/interface/src/avatar/AvatarMotionState.cpp @@ -19,6 +19,7 @@ AvatarMotionState::AvatarMotionState(OtherAvatarPointer avatar, const btCollisionShape* shape) : ObjectMotionState(shape), _avatar(avatar) { assert(_avatar); _type = MOTIONSTATE_TYPE_AVATAR; + _collisionGroup = BULLET_COLLISION_GROUP_OTHER_AVATAR; cacheShapeDiameter(); } @@ -170,8 +171,8 @@ QUuid AvatarMotionState::getSimulatorID() const { // virtual void AvatarMotionState::computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const { - group = BULLET_COLLISION_GROUP_OTHER_AVATAR; - mask = Physics::getDefaultCollisionMask(group); + group = _collisionGroup; + mask = _collisionGroup == BULLET_COLLISION_GROUP_COLLISIONLESS ? 0 : Physics::getDefaultCollisionMask(group); } // virtual diff --git a/interface/src/avatar/AvatarMotionState.h b/interface/src/avatar/AvatarMotionState.h index 2533c11d56..9d9a6fba2f 100644 --- a/interface/src/avatar/AvatarMotionState.h +++ b/interface/src/avatar/AvatarMotionState.h @@ -66,6 +66,9 @@ public: void addDirtyFlags(uint32_t flags) { _dirtyFlags |= flags; } + void setCollisionGroup(int32_t group) { _collisionGroup = group; } + int32_t getCollisionGroup() { return _collisionGroup; } + virtual void computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const override; virtual float getMass() const override; @@ -87,7 +90,7 @@ protected: OtherAvatarPointer _avatar; float _diameter { 0.0f }; - + uint32_t _collisionGroup; uint32_t _dirtyFlags; }; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 1f63a904bb..70970e245f 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -3326,7 +3326,6 @@ void MyAvatar::setCollisionsEnabled(bool enabled) { QMetaObject::invokeMethod(this, "setCollisionsEnabled", Q_ARG(bool, enabled)); return; } - _characterController.setCollisionless(!enabled); emit collisionsEnabledChanged(enabled); } @@ -3337,6 +3336,20 @@ bool MyAvatar::getCollisionsEnabled() { return _characterController.computeCollisionGroup() != BULLET_COLLISION_GROUP_COLLISIONLESS; } +void MyAvatar::setOtherAvatarsCollisionsEnabled(bool enabled) { + + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setOtherAvatarsCollisionsEnabled", Q_ARG(bool, enabled)); + return; + } + _collideWithOtherAvatars = enabled; + emit otherAvatarsCollisionsEnabledChanged(enabled); +} + +bool MyAvatar::getOtherAvatarsCollisionsEnabled() { + return _collideWithOtherAvatars; +} + void MyAvatar::updateCollisionCapsuleCache() { glm::vec3 start, end; float radius; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 92fe6d4f4a..845f6398a3 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -225,6 +225,7 @@ class MyAvatar : public Avatar { Q_PROPERTY(bool centerOfGravityModelEnabled READ getCenterOfGravityModelEnabled WRITE setCenterOfGravityModelEnabled) Q_PROPERTY(bool hmdLeanRecenterEnabled READ getHMDLeanRecenterEnabled WRITE setHMDLeanRecenterEnabled) Q_PROPERTY(bool collisionsEnabled READ getCollisionsEnabled WRITE setCollisionsEnabled) + Q_PROPERTY(bool otherAvatarsCollisionsEnabled READ getOtherAvatarsCollisionsEnabled WRITE setOtherAvatarsCollisionsEnabled) Q_PROPERTY(bool characterControllerEnabled READ getCharacterControllerEnabled WRITE setCharacterControllerEnabled) Q_PROPERTY(bool useAdvancedMovementControls READ useAdvancedMovementControls WRITE setUseAdvancedMovementControls) Q_PROPERTY(bool showPlayArea READ getShowPlayArea WRITE setShowPlayArea) @@ -1062,6 +1063,18 @@ public: */ Q_INVOKABLE bool getCollisionsEnabled(); + /**jsdoc + * @function MyAvatar.setOtherAvatarsCollisionsEnabled + * @param {boolean} enabled + */ + Q_INVOKABLE void setOtherAvatarsCollisionsEnabled(bool enabled); + + /**jsdoc + * @function MyAvatar.getOtherAvatarsCollisionsEnabled + * @returns {boolean} + */ + Q_INVOKABLE bool getOtherAvatarsCollisionsEnabled(); + /**jsdoc * @function MyAvatar.getCollisionCapsule * @returns {object} @@ -1489,6 +1502,14 @@ signals: */ void collisionsEnabledChanged(bool enabled); + /**jsdoc + * Triggered when collisions with other avatars enabled or disabled + * @function MyAvatar.otherAvatarsCollisionsEnabledChanged + * @param {boolean} enabled + * @returns {Signal} + */ + void otherAvatarsCollisionsEnabledChanged(bool enabled); + /**jsdoc * Triggered when avatar's animation url changes * @function MyAvatar.animGraphUrlChanged diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index c2687fd525..a71d2478ad 100644 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -120,7 +120,7 @@ bool OtherAvatar::shouldBeInPhysicsSimulation() const { } bool OtherAvatar::needsPhysicsUpdate() const { - constexpr uint32_t FLAGS_OF_INTEREST = Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS | Simulation::DIRTY_POSITION; + constexpr uint32_t FLAGS_OF_INTEREST = Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS | Simulation::DIRTY_POSITION | Simulation::DIRTY_COLLISION_GROUP; return (_motionState && (bool)(_motionState->getIncomingDirtyFlags() & FLAGS_OF_INTEREST)); } @@ -129,3 +129,17 @@ void OtherAvatar::rebuildCollisionShape() { _motionState->addDirtyFlags(Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS); } } + +void OtherAvatar::updateCollisionGroup(bool myAvatarCollide) { + if (_motionState) { + bool collides = _motionState->getCollisionGroup() == BULLET_COLLISION_GROUP_OTHER_AVATAR && myAvatarCollide; + 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); + } + } +} \ No newline at end of file diff --git a/interface/src/avatar/OtherAvatar.h b/interface/src/avatar/OtherAvatar.h index 5b72815757..48402fe55c 100644 --- a/interface/src/avatar/OtherAvatar.h +++ b/interface/src/avatar/OtherAvatar.h @@ -45,6 +45,8 @@ public: bool shouldBeInPhysicsSimulation() const; bool needsPhysicsUpdate() const; + void updateCollisionGroup(bool myAvatarCollide); + friend AvatarManager; protected: diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index a54a74fb93..dffaadf862 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -552,6 +552,7 @@ protected: glm::vec3 getBodyRightDirection() const { return getWorldOrientation() * IDENTITY_RIGHT; } glm::vec3 getBodyUpDirection() const { return getWorldOrientation() * IDENTITY_UP; } void measureMotionDerivatives(float deltaTime); + bool getCollideWithOtherAvatars() const { return _collideWithOtherAvatars; } float getSkeletonHeight() const; float getHeadHeight() const; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index ae72725e2e..e72fa3a6eb 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -540,6 +540,10 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (_headData->getHasProceduralBlinkFaceMovement()) { setAtBit16(flags, PROCEDURAL_BLINK_FACE_MOVEMENT); } + // avatar collisions enabled + if (_collideWithOtherAvatars) { + setAtBit16(flags, COLLIDE_WITH_OTHER_AVATARS); + } data->flags = flags; destinationBuffer += sizeof(AvatarDataPacket::AdditionalFlags); @@ -1116,7 +1120,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { auto newHasAudioEnabledFaceMovement = oneAtBit16(bitItems, AUDIO_ENABLED_FACE_MOVEMENT); auto newHasProceduralEyeFaceMovement = oneAtBit16(bitItems, PROCEDURAL_EYE_FACE_MOVEMENT); auto newHasProceduralBlinkFaceMovement = oneAtBit16(bitItems, PROCEDURAL_BLINK_FACE_MOVEMENT); - + auto newCollideWithOtherAvatars = oneAtBit16(bitItems, COLLIDE_WITH_OTHER_AVATARS); bool keyStateChanged = (_keyState != newKeyState); bool handStateChanged = (_handState != newHandState); @@ -1125,7 +1129,9 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { bool audioEnableFaceMovementChanged = (_headData->getHasAudioEnabledFaceMovement() != newHasAudioEnabledFaceMovement); bool proceduralEyeFaceMovementChanged = (_headData->getHasProceduralEyeFaceMovement() != newHasProceduralEyeFaceMovement); bool proceduralBlinkFaceMovementChanged = (_headData->getHasProceduralBlinkFaceMovement() != newHasProceduralBlinkFaceMovement); - bool somethingChanged = keyStateChanged || handStateChanged || faceStateChanged || eyeStateChanged || audioEnableFaceMovementChanged || proceduralEyeFaceMovementChanged || proceduralBlinkFaceMovementChanged; + bool collideWithOtherAvatarsChanged = (_collideWithOtherAvatars != newCollideWithOtherAvatars); + bool somethingChanged = keyStateChanged || handStateChanged || faceStateChanged || eyeStateChanged || audioEnableFaceMovementChanged || + proceduralEyeFaceMovementChanged || proceduralBlinkFaceMovementChanged || collideWithOtherAvatarsChanged; _keyState = newKeyState; _handState = newHandState; @@ -1134,6 +1140,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { _headData->setHasAudioEnabledFaceMovement(newHasAudioEnabledFaceMovement); _headData->setHasProceduralEyeFaceMovement(newHasProceduralEyeFaceMovement); _headData->setHasProceduralBlinkFaceMovement(newHasProceduralBlinkFaceMovement); + _collideWithOtherAvatars = newCollideWithOtherAvatars; sourceBuffer += sizeof(AvatarDataPacket::AdditionalFlags); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 64b8814149..b42c387f61 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -110,6 +110,7 @@ const int HAND_STATE_FINGER_POINTING_BIT = 7; // 8th bit const int AUDIO_ENABLED_FACE_MOVEMENT = 8; // 9th bit const int PROCEDURAL_EYE_FACE_MOVEMENT = 9; // 10th bit const int PROCEDURAL_BLINK_FACE_MOVEMENT = 10; // 11th bit +const int COLLIDE_WITH_OTHER_AVATARS = 11; // 12th bit const char HAND_STATE_NULL = 0; @@ -1495,6 +1496,7 @@ protected: int _replicaIndex { 0 }; bool _isNewAvatar { true }; bool _isClientAvatar { false }; + bool _collideWithOtherAvatars { true }; // null unless MyAvatar or ScriptableAvatar sending traits data to mixer std::unique_ptr _clientTraitsHandler; diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 2cc5804d10..736bf6ae8e 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -38,6 +38,7 @@ PacketVersion versionForPacketType(PacketType packetType) { return static_cast(EntityQueryPacketVersion::ConicalFrustums); case PacketType::AvatarIdentity: case PacketType::AvatarData: + return static_cast(AvatarMixerPacketVersion::CollisionFlag); case PacketType::BulkAvatarData: case PacketType::KillAvatar: return static_cast(AvatarMixerPacketVersion::GrabTraits); diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 62549a7562..2ca8f0240a 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -305,7 +305,8 @@ enum class AvatarMixerPacketVersion : PacketVersion { MigrateAvatarEntitiesToTraits, FarGrabJointsRedux, JointTransScaled, - GrabTraits + GrabTraits, + CollisionFlag }; enum class DomainConnectRequestVersion : PacketVersion { diff --git a/scripts/system/avatarapp.js b/scripts/system/avatarapp.js index 65abf791a5..2b9a738202 100644 --- a/scripts/system/avatarapp.js +++ b/scripts/system/avatarapp.js @@ -62,7 +62,8 @@ function getMyAvatar() { function getMyAvatarSettings() { return { dominantHand: MyAvatar.getDominantHand(), - collisionsEnabled : MyAvatar.getCollisionsEnabled(), + collisionsEnabled: MyAvatar.getCollisionsEnabled(), + otherAvatarsCollisionsEnabled: MyAvatar.getOtherAvatarsCollisionsEnabled(), collisionSoundUrl : MyAvatar.collisionSoundURL, animGraphUrl: MyAvatar.getAnimGraphUrl(), animGraphOverrideUrl : MyAvatar.getAnimGraphOverrideUrl(), @@ -135,6 +136,13 @@ function onCollisionsEnabledChanged(enabled) { } } +function onOtherAvatarsCollisionsEnabledChanged(enabled) { + if (currentAvatarSettings.otherAvatarsCollisionsEnabled !== enabled) { + currentAvatarSettings.otherAvatarsCollisionsEnabled = enabled; + sendToQml({ 'method': 'settingChanged', 'name': 'otherAvatarsCollisionsEnabled', 'value': enabled }) + } +} + function onNewCollisionSoundUrl(url) { if(currentAvatarSettings.collisionSoundUrl !== url) { currentAvatarSettings.collisionSoundUrl = url; @@ -323,6 +331,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See currentAvatar.avatarScale = message.avatarScale; MyAvatar.setDominantHand(message.settings.dominantHand); + MyAvatar.setOtherAvatarsCollisionsEnabled(message.settings.otherAvatarsCollisionsEnabled); MyAvatar.setCollisionsEnabled(message.settings.collisionsEnabled); MyAvatar.collisionSoundURL = message.settings.collisionSoundUrl; MyAvatar.setAnimGraphOverrideUrl(message.settings.animGraphOverrideUrl); @@ -513,6 +522,7 @@ function off() { MyAvatar.skeletonModelURLChanged.disconnect(onSkeletonModelURLChanged); MyAvatar.dominantHandChanged.disconnect(onDominantHandChanged); MyAvatar.collisionsEnabledChanged.disconnect(onCollisionsEnabledChanged); + MyAvatar.otherAvatarsCollisionsEnabledChanged.disconnect(onOtherAvatarsCollisionsEnabledChanged); MyAvatar.newCollisionSoundURL.disconnect(onNewCollisionSoundUrl); MyAvatar.animGraphUrlChanged.disconnect(onAnimGraphUrlChanged); MyAvatar.targetScaleChanged.disconnect(onTargetScaleChanged); @@ -533,6 +543,7 @@ function on() { MyAvatar.skeletonModelURLChanged.connect(onSkeletonModelURLChanged); MyAvatar.dominantHandChanged.connect(onDominantHandChanged); MyAvatar.collisionsEnabledChanged.connect(onCollisionsEnabledChanged); + MyAvatar.otherAvatarsCollisionsEnabledChanged.connect(onOtherAvatarsCollisionsEnabledChanged); MyAvatar.newCollisionSoundURL.connect(onNewCollisionSoundUrl); MyAvatar.animGraphUrlChanged.connect(onAnimGraphUrlChanged); MyAvatar.targetScaleChanged.connect(onTargetScaleChanged);