mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-06-07 01:21:22 +02:00
Merge pull request #928 from Phil-Palmer/fix/vr-recenter
VR fixes for: couldn't sit on the floor, wrong walk directions.
This commit is contained in:
commit
06b1a8e17c
12 changed files with 837 additions and 498 deletions
File diff suppressed because it is too large
Load diff
|
@ -283,15 +283,16 @@ class MyAvatar : public Avatar {
|
||||||
* the value.</p>
|
* the value.</p>
|
||||||
* @property {number} analogPlusSprintSpeed - The sprint (run) speed of your avatar for the "AnalogPlus" control scheme.
|
* @property {number} analogPlusSprintSpeed - The sprint (run) speed of your avatar for the "AnalogPlus" control scheme.
|
||||||
* @property {MyAvatar.SitStandModelType} userRecenterModel - Controls avatar leaning and recentering behavior.
|
* @property {MyAvatar.SitStandModelType} userRecenterModel - Controls avatar leaning and recentering behavior.
|
||||||
* @property {number} isInSittingState - <code>true</code> if the user wearing the HMD is determined to be sitting
|
* <p class="important">Deprecated: This property is deprecated and will be removed.</p>
|
||||||
* (avatar leaning is disabled, recentering is enabled), <code>false</code> if the user wearing the HMD is
|
* @property {boolean} isInSittingState - <code>true</code> if the user wearing the HMD is determined to be sitting;
|
||||||
* determined to be standing (avatar leaning is enabled, and avatar recenters if it leans too far).
|
* <code>false</code> if the user wearing the HMD is determined to be standing. This can affect whether the avatar
|
||||||
* If <code>userRecenterModel == 2</code> (i.e., "auto") the property value automatically updates as the user sits
|
* is allowed to stand, lean or recenter its footing, depending on user preferences.
|
||||||
* or stands, unless <code>isSitStandStateLocked == true</code>. Setting the property value overrides the current
|
* The property value automatically updates as the user sits or stands. Setting the property value overrides the current
|
||||||
* sitting / standing state, which is updated when the user next sits or stands unless
|
* sitting / standing state, which is updated when the user next sits or stands.
|
||||||
* <code>isSitStandStateLocked == true</code>.
|
|
||||||
* @property {boolean} isSitStandStateLocked - <code>true</code> to lock the avatar sitting/standing state, i.e., use this
|
* @property {boolean} isSitStandStateLocked - <code>true</code> to lock the avatar sitting/standing state, i.e., use this
|
||||||
* to disable automatically changing state.
|
* to disable automatically changing state.
|
||||||
|
* <p class="important">Deprecated: This property is deprecated and will be removed.
|
||||||
|
* See also: <code>getUserRecenterModel</code> and <code>setUserRecenterModel</code>.</p>
|
||||||
* @property {boolean} allowTeleporting - <code>true</code> if teleporting is enabled in the Interface settings,
|
* @property {boolean} allowTeleporting - <code>true</code> if teleporting is enabled in the Interface settings,
|
||||||
* <code>false</code> if it isn't. <em>Read-only.</em>
|
* <code>false</code> if it isn't. <em>Read-only.</em>
|
||||||
*
|
*
|
||||||
|
@ -413,8 +414,8 @@ class MyAvatar : public Avatar {
|
||||||
Q_PROPERTY(float walkBackwardSpeed READ getWalkBackwardSpeed WRITE setWalkBackwardSpeed NOTIFY walkBackwardSpeedChanged);
|
Q_PROPERTY(float walkBackwardSpeed READ getWalkBackwardSpeed WRITE setWalkBackwardSpeed NOTIFY walkBackwardSpeedChanged);
|
||||||
Q_PROPERTY(float sprintSpeed READ getSprintSpeed WRITE setSprintSpeed NOTIFY sprintSpeedChanged);
|
Q_PROPERTY(float sprintSpeed READ getSprintSpeed WRITE setSprintSpeed NOTIFY sprintSpeedChanged);
|
||||||
Q_PROPERTY(bool isInSittingState READ getIsInSittingState WRITE setIsInSittingState);
|
Q_PROPERTY(bool isInSittingState READ getIsInSittingState WRITE setIsInSittingState);
|
||||||
Q_PROPERTY(MyAvatar::SitStandModelType userRecenterModel READ getUserRecenterModel WRITE setUserRecenterModel);
|
Q_PROPERTY(MyAvatar::SitStandModelType userRecenterModel READ getUserRecenterModel WRITE setUserRecenterModel); // Deprecated
|
||||||
Q_PROPERTY(bool isSitStandStateLocked READ getIsSitStandStateLocked WRITE setIsSitStandStateLocked);
|
Q_PROPERTY(bool isSitStandStateLocked READ getIsSitStandStateLocked WRITE setIsSitStandStateLocked); // Deprecated
|
||||||
Q_PROPERTY(bool allowTeleporting READ getAllowTeleporting)
|
Q_PROPERTY(bool allowTeleporting READ getAllowTeleporting)
|
||||||
|
|
||||||
const QString DOMINANT_LEFT_HAND = "left";
|
const QString DOMINANT_LEFT_HAND = "left";
|
||||||
|
@ -519,6 +520,7 @@ public:
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* <p>Specifies different avatar leaning and recentering behaviors.</p>
|
* <p>Specifies different avatar leaning and recentering behaviors.</p>
|
||||||
|
* <p class="important">Deprecated: This type is deprecated and will be removed.</p>
|
||||||
* <table>
|
* <table>
|
||||||
* <thead>
|
* <thead>
|
||||||
* <tr><th>Value</th><th>Name</th><th>Description</th></tr>
|
* <tr><th>Value</th><th>Name</th><th>Description</th></tr>
|
||||||
|
@ -549,6 +551,29 @@ public:
|
||||||
};
|
};
|
||||||
Q_ENUM(SitStandModelType)
|
Q_ENUM(SitStandModelType)
|
||||||
|
|
||||||
|
// Note: The option strings in setupPreferences (PreferencesDialog.cpp) must match this order.
|
||||||
|
enum class AllowAvatarStandingPreference : uint {
|
||||||
|
WhenUserIsStanding,
|
||||||
|
Always,
|
||||||
|
Count,
|
||||||
|
Default = Always
|
||||||
|
};
|
||||||
|
Q_ENUM(AllowAvatarStandingPreference)
|
||||||
|
|
||||||
|
// Note: The option strings in setupPreferences (PreferencesDialog.cpp) must match this order.
|
||||||
|
enum class AllowAvatarLeaningPreference : uint {
|
||||||
|
WhenUserIsStanding,
|
||||||
|
Always,
|
||||||
|
Never,
|
||||||
|
AlwaysNoRecenter, // experimental
|
||||||
|
Count,
|
||||||
|
Default = WhenUserIsStanding
|
||||||
|
};
|
||||||
|
Q_ENUM(AllowAvatarLeaningPreference)
|
||||||
|
|
||||||
|
static const std::array<QString, (uint)AllowAvatarStandingPreference::Count> allowAvatarStandingPreferenceStrings;
|
||||||
|
static const std::array<QString, (uint)AllowAvatarLeaningPreference::Count> allowAvatarLeaningPreferenceStrings;
|
||||||
|
|
||||||
explicit MyAvatar(QThread* thread);
|
explicit MyAvatar(QThread* thread);
|
||||||
virtual ~MyAvatar();
|
virtual ~MyAvatar();
|
||||||
|
|
||||||
|
@ -1417,7 +1442,6 @@ public:
|
||||||
controller::Pose getControllerPoseInSensorFrame(controller::Action action) const;
|
controller::Pose getControllerPoseInSensorFrame(controller::Action action) const;
|
||||||
controller::Pose getControllerPoseInWorldFrame(controller::Action action) const;
|
controller::Pose getControllerPoseInWorldFrame(controller::Action action) const;
|
||||||
controller::Pose getControllerPoseInAvatarFrame(controller::Action action) const;
|
controller::Pose getControllerPoseInAvatarFrame(controller::Action action) const;
|
||||||
glm::quat getOffHandRotation() const;
|
|
||||||
|
|
||||||
bool hasDriveInput() const;
|
bool hasDriveInput() const;
|
||||||
|
|
||||||
|
@ -1596,7 +1620,7 @@ public:
|
||||||
* @function MyAvatar.getAvatarScale
|
* @function MyAvatar.getAvatarScale
|
||||||
* @returns {number} The target scale for the avatar, range <code>0.005</code> – <code>1000.0</code>.
|
* @returns {number} The target scale for the avatar, range <code>0.005</code> – <code>1000.0</code>.
|
||||||
*/
|
*/
|
||||||
Q_INVOKABLE float getAvatarScale();
|
Q_INVOKABLE float getAvatarScale() const;
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* Sets the target scale of the avatar. The target scale is the desired scale of the avatar without any restrictions on
|
* Sets the target scale of the avatar. The target scale is the desired scale of the avatar without any restrictions on
|
||||||
|
@ -1709,7 +1733,7 @@ public:
|
||||||
|
|
||||||
// derive avatar body position and orientation from the current HMD Sensor location.
|
// derive avatar body position and orientation from the current HMD Sensor location.
|
||||||
// results are in sensor frame (-z forward)
|
// results are in sensor frame (-z forward)
|
||||||
glm::mat4 deriveBodyFromHMDSensor() const;
|
glm::mat4 deriveBodyFromHMDSensor(const bool forceFollowYPos = false) const;
|
||||||
|
|
||||||
glm::mat4 getSpine2RotationRigSpace() const;
|
glm::mat4 getSpine2RotationRigSpace() const;
|
||||||
|
|
||||||
|
@ -1753,10 +1777,14 @@ public:
|
||||||
bool getIsInWalkingState() const;
|
bool getIsInWalkingState() const;
|
||||||
void setIsInSittingState(bool isSitting);
|
void setIsInSittingState(bool isSitting);
|
||||||
bool getIsInSittingState() const;
|
bool getIsInSittingState() const;
|
||||||
void setUserRecenterModel(MyAvatar::SitStandModelType modelName);
|
void setUserRecenterModel(MyAvatar::SitStandModelType modelName); // Deprecated, will be removed.
|
||||||
MyAvatar::SitStandModelType getUserRecenterModel() const;
|
MyAvatar::SitStandModelType getUserRecenterModel() const; // Deprecated, will be removed.
|
||||||
void setIsSitStandStateLocked(bool isLocked);
|
void setIsSitStandStateLocked(bool isLocked); // Deprecated, will be removed.
|
||||||
bool getIsSitStandStateLocked() const;
|
bool getIsSitStandStateLocked() const; // Deprecated, will be removed.
|
||||||
|
void setAllowAvatarStandingPreference(const AllowAvatarStandingPreference preference);
|
||||||
|
AllowAvatarStandingPreference getAllowAvatarStandingPreference() const;
|
||||||
|
void setAllowAvatarLeaningPreference(const AllowAvatarLeaningPreference preference);
|
||||||
|
AllowAvatarLeaningPreference getAllowAvatarLeaningPreference() const;
|
||||||
void setWalkSpeed(float value);
|
void setWalkSpeed(float value);
|
||||||
float getWalkSpeed() const;
|
float getWalkSpeed() const;
|
||||||
void setWalkBackwardSpeed(float value);
|
void setWalkBackwardSpeed(float value);
|
||||||
|
@ -1989,6 +2017,10 @@ public:
|
||||||
glm::vec3 getLookAtPivotPoint();
|
glm::vec3 getLookAtPivotPoint();
|
||||||
glm::vec3 getCameraEyesPosition(float deltaTime);
|
glm::vec3 getCameraEyesPosition(float deltaTime);
|
||||||
bool isJumping();
|
bool isJumping();
|
||||||
|
bool getHMDCrouchRecenterEnabled() const;
|
||||||
|
bool isAllowedToLean() const;
|
||||||
|
bool areFeetTracked() const { return _isBodyPartTracked._feet; }; // Determine if the feet are under direct control.
|
||||||
|
bool areHipsTracked() const { return _isBodyPartTracked._hips; }; // Determine if the hips are under direct control.
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
|
||||||
|
@ -2709,6 +2741,16 @@ private:
|
||||||
bool _isBraking { false };
|
bool _isBraking { false };
|
||||||
bool _isAway { false };
|
bool _isAway { false };
|
||||||
|
|
||||||
|
// Indicates which parts of the body are under direct control (tracked).
|
||||||
|
struct {
|
||||||
|
bool _feet { false }; // Left or right foot.
|
||||||
|
bool _feetPreviousUpdate{ false };// Value of _feet on the previous update.
|
||||||
|
bool _hips{ false };
|
||||||
|
bool _leftHand{ false };
|
||||||
|
bool _rightHand{ false };
|
||||||
|
bool _head{ false };
|
||||||
|
} _isBodyPartTracked;
|
||||||
|
|
||||||
float _boomLength { ZOOM_DEFAULT };
|
float _boomLength { ZOOM_DEFAULT };
|
||||||
float _yawSpeed; // degrees/sec
|
float _yawSpeed; // degrees/sec
|
||||||
float _pitchSpeed; // degrees/sec
|
float _pitchSpeed; // degrees/sec
|
||||||
|
@ -2791,6 +2833,7 @@ private:
|
||||||
void resetLookAtRotation(const glm::vec3& avatarPosition, const glm::quat& avatarOrientation);
|
void resetLookAtRotation(const glm::vec3& avatarPosition, const glm::quat& avatarOrientation);
|
||||||
void resetPointAt();
|
void resetPointAt();
|
||||||
static glm::vec3 aimToBlendValues(const glm::vec3& aimVector, const glm::quat& frameOrientation);
|
static glm::vec3 aimToBlendValues(const glm::vec3& aimVector, const glm::quat& frameOrientation);
|
||||||
|
void centerBodyInternal(const bool forceFollowYPos = false);
|
||||||
|
|
||||||
// Avatar Preferences
|
// Avatar Preferences
|
||||||
QUrl _fullAvatarURLFromPreferences;
|
QUrl _fullAvatarURLFromPreferences;
|
||||||
|
@ -2841,26 +2884,21 @@ private:
|
||||||
struct FollowHelper {
|
struct FollowHelper {
|
||||||
FollowHelper();
|
FollowHelper();
|
||||||
|
|
||||||
enum FollowType {
|
CharacterController::FollowTimePerType _timeRemaining;
|
||||||
Rotation = 0,
|
|
||||||
Horizontal,
|
|
||||||
Vertical,
|
|
||||||
NumFollowTypes
|
|
||||||
};
|
|
||||||
float _timeRemaining[NumFollowTypes];
|
|
||||||
|
|
||||||
void deactivate();
|
void deactivate();
|
||||||
void deactivate(FollowType type);
|
void deactivate(CharacterController::FollowType type);
|
||||||
void activate();
|
void activate(CharacterController::FollowType type, const bool snapFollow);
|
||||||
void activate(FollowType type);
|
|
||||||
bool isActive() const;
|
bool isActive() const;
|
||||||
bool isActive(FollowType followType) const;
|
bool isActive(CharacterController::FollowType followType) const;
|
||||||
float getMaxTimeRemaining() const;
|
|
||||||
void decrementTimeRemaining(float dt);
|
void decrementTimeRemaining(float dt);
|
||||||
bool shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const;
|
bool shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix, bool& shouldSnapOut) const;
|
||||||
bool shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const;
|
bool shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const;
|
||||||
bool shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const;
|
bool shouldActivateHorizontal(const MyAvatar& myAvatar,
|
||||||
bool shouldActivateHorizontalCG(MyAvatar& myAvatar) const;
|
const glm::mat4& desiredBodyMatrix,
|
||||||
|
const glm::mat4& currentBodyMatrix,
|
||||||
|
bool& resetModeOut,
|
||||||
|
bool& goToWalkingStateOut) const;
|
||||||
void prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& bodySensorMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput);
|
void prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& bodySensorMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput);
|
||||||
glm::mat4 postPhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix);
|
glm::mat4 postPhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix);
|
||||||
bool getForceActivateRotation() const;
|
bool getForceActivateRotation() const;
|
||||||
|
@ -2871,16 +2909,23 @@ private:
|
||||||
void setForceActivateHorizontal(bool val);
|
void setForceActivateHorizontal(bool val);
|
||||||
bool getToggleHipsFollowing() const;
|
bool getToggleHipsFollowing() const;
|
||||||
void setToggleHipsFollowing(bool followHead);
|
void setToggleHipsFollowing(bool followHead);
|
||||||
bool _squatDetected { false };
|
|
||||||
std::atomic<bool> _forceActivateRotation { false };
|
std::atomic<bool> _forceActivateRotation { false };
|
||||||
std::atomic<bool> _forceActivateVertical { false };
|
std::atomic<bool> _forceActivateVertical { false };
|
||||||
std::atomic<bool> _forceActivateHorizontal { false };
|
std::atomic<bool> _forceActivateHorizontal { false };
|
||||||
std::atomic<bool> _toggleHipsFollowing { true };
|
std::atomic<bool> _toggleHipsFollowing { true };
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool shouldActivateHorizontal_userSitting(const MyAvatar& myAvatar,
|
||||||
|
const glm::mat4& desiredBodyMatrix,
|
||||||
|
const glm::mat4& currentBodyMatrix) const;
|
||||||
|
bool shouldActivateHorizontal_userStanding(const MyAvatar& myAvatar,
|
||||||
|
bool& resetModeOut,
|
||||||
|
bool& goToWalkingStateOut) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
FollowHelper _follow;
|
FollowHelper _follow;
|
||||||
|
|
||||||
bool isFollowActive(FollowHelper::FollowType followType) const;
|
bool isFollowActive(CharacterController::FollowType followType) const;
|
||||||
|
|
||||||
bool _goToPending { false };
|
bool _goToPending { false };
|
||||||
bool _physicsSafetyPending { false };
|
bool _physicsSafetyPending { false };
|
||||||
|
@ -2922,6 +2967,9 @@ private:
|
||||||
|
|
||||||
bool _centerOfGravityModelEnabled { true };
|
bool _centerOfGravityModelEnabled { true };
|
||||||
bool _hmdLeanRecenterEnabled { true };
|
bool _hmdLeanRecenterEnabled { true };
|
||||||
|
bool _hmdCrouchRecenterEnabled {
|
||||||
|
true
|
||||||
|
}; // Is MyAvatar allowed to recenter vertically (stand) when the user is sitting in the real world.
|
||||||
bool _sprint { false };
|
bool _sprint { false };
|
||||||
|
|
||||||
AnimPose _prePhysicsRoomPose;
|
AnimPose _prePhysicsRoomPose;
|
||||||
|
@ -2953,7 +3001,6 @@ private:
|
||||||
ThreadSafeValueCache<float> _userHeight { DEFAULT_AVATAR_HEIGHT };
|
ThreadSafeValueCache<float> _userHeight { DEFAULT_AVATAR_HEIGHT };
|
||||||
float _averageUserHeightSensorSpace { _userHeight.get() };
|
float _averageUserHeightSensorSpace { _userHeight.get() };
|
||||||
bool _sitStandStateChange { false };
|
bool _sitStandStateChange { false };
|
||||||
ThreadSafeValueCache<bool> _lockSitStandState { false };
|
|
||||||
|
|
||||||
// max unscaled forward movement speed
|
// max unscaled forward movement speed
|
||||||
ThreadSafeValueCache<float> _defaultWalkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED };
|
ThreadSafeValueCache<float> _defaultWalkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED };
|
||||||
|
@ -2969,9 +3016,13 @@ private:
|
||||||
float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR };
|
float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR };
|
||||||
bool _isInWalkingState { false };
|
bool _isInWalkingState { false };
|
||||||
ThreadSafeValueCache<bool> _isInSittingState { false };
|
ThreadSafeValueCache<bool> _isInSittingState { false };
|
||||||
ThreadSafeValueCache<MyAvatar::SitStandModelType> _userRecenterModel { MyAvatar::SitStandModelType::Auto };
|
ThreadSafeValueCache<MyAvatar::AllowAvatarStandingPreference> _allowAvatarStandingPreference{
|
||||||
|
MyAvatar::AllowAvatarStandingPreference::Default
|
||||||
|
}; // The user preference of when MyAvatar may stand.
|
||||||
|
ThreadSafeValueCache<MyAvatar::AllowAvatarLeaningPreference> _allowAvatarLeaningPreference{
|
||||||
|
MyAvatar::AllowAvatarLeaningPreference::Default
|
||||||
|
}; // The user preference of when MyAvatar may lean.
|
||||||
float _sitStandStateTimer { 0.0f };
|
float _sitStandStateTimer { 0.0f };
|
||||||
float _squatTimer { 0.0f };
|
|
||||||
float _tippingPoint { _userHeight.get() };
|
float _tippingPoint { _userHeight.get() };
|
||||||
|
|
||||||
// load avatar scripts once when rig is ready
|
// load avatar scripts once when rig is ready
|
||||||
|
@ -3012,7 +3063,8 @@ private:
|
||||||
Setting::Handle<int> _controlSchemeIndexSetting;
|
Setting::Handle<int> _controlSchemeIndexSetting;
|
||||||
std::vector<Setting::Handle<QUuid>> _avatarEntityIDSettings;
|
std::vector<Setting::Handle<QUuid>> _avatarEntityIDSettings;
|
||||||
std::vector<Setting::Handle<QByteArray>> _avatarEntityDataSettings;
|
std::vector<Setting::Handle<QByteArray>> _avatarEntityDataSettings;
|
||||||
Setting::Handle<QString> _userRecenterModelSetting;
|
Setting::Handle<QString> _allowAvatarStandingPreferenceSetting;
|
||||||
|
Setting::Handle<QString> _allowAvatarLeaningPreferenceSetting;
|
||||||
|
|
||||||
// AvatarEntities stuff:
|
// AvatarEntities stuff:
|
||||||
// We cache the "map of unfortunately-formatted-binary-blobs" because they are expensive to compute
|
// We cache the "map of unfortunately-formatted-binary-blobs" because they are expensive to compute
|
||||||
|
|
|
@ -26,7 +26,9 @@ void MyCharacterController::RayShotgunResult::reset() {
|
||||||
walkable = true;
|
walkable = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
MyCharacterController::MyCharacterController(std::shared_ptr<MyAvatar> avatar) {
|
MyCharacterController::MyCharacterController(std::shared_ptr<MyAvatar> avatar,
|
||||||
|
const FollowTimePerType& followTimeRemainingPerType) :
|
||||||
|
CharacterController(followTimeRemainingPerType) {
|
||||||
|
|
||||||
assert(avatar);
|
assert(avatar);
|
||||||
_avatar = avatar;
|
_avatar = avatar;
|
||||||
|
|
|
@ -23,7 +23,7 @@ class DetailedMotionState;
|
||||||
|
|
||||||
class MyCharacterController : public CharacterController {
|
class MyCharacterController : public CharacterController {
|
||||||
public:
|
public:
|
||||||
explicit MyCharacterController(std::shared_ptr<MyAvatar> avatar);
|
explicit MyCharacterController(std::shared_ptr<MyAvatar> avatar, const FollowTimePerType& followTimeRemainingPerType);
|
||||||
~MyCharacterController ();
|
~MyCharacterController ();
|
||||||
|
|
||||||
void addToWorld() override;
|
void addToWorld() override;
|
||||||
|
|
|
@ -65,13 +65,21 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use the center-of-gravity model if the user and the avatar are standing, unless flying or walking.
|
||||||
|
// If artificial standing is disabled, use center-of-gravity regardless of the user's sit/stand state.
|
||||||
|
bool useCenterOfGravityModel =
|
||||||
|
myAvatar->getCenterOfGravityModelEnabled() && !isFlying && !myAvatar->getIsInWalkingState() &&
|
||||||
|
(!myAvatar->getHMDCrouchRecenterEnabled() || !myAvatar->getIsInSittingState()) &&
|
||||||
|
myAvatar->getHMDLeanRecenterEnabled() &&
|
||||||
|
(myAvatar->getAllowAvatarLeaningPreference() != MyAvatar::AllowAvatarLeaningPreference::AlwaysNoRecenter);
|
||||||
|
|
||||||
glm::mat4 hipsMat;
|
glm::mat4 hipsMat;
|
||||||
if (myAvatar->getCenterOfGravityModelEnabled() && !isFlying && !(myAvatar->getIsInWalkingState()) && !(myAvatar->getIsInSittingState()) && myAvatar->getHMDLeanRecenterEnabled()) {
|
if (useCenterOfGravityModel) {
|
||||||
// then we use center of gravity model
|
// then we use center of gravity model
|
||||||
hipsMat = myAvatar->deriveBodyUsingCgModel();
|
hipsMat = myAvatar->deriveBodyUsingCgModel();
|
||||||
} else {
|
} else {
|
||||||
// otherwise use the default of putting the hips under the head
|
// otherwise use the default of putting the hips under the head
|
||||||
hipsMat = myAvatar->deriveBodyFromHMDSensor();
|
hipsMat = myAvatar->deriveBodyFromHMDSensor(true);
|
||||||
}
|
}
|
||||||
glm::vec3 hipsPos = extractTranslation(hipsMat);
|
glm::vec3 hipsPos = extractTranslation(hipsMat);
|
||||||
glm::quat hipsRot = glmExtractRotation(hipsMat);
|
glm::quat hipsRot = glmExtractRotation(hipsMat);
|
||||||
|
@ -82,7 +90,7 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) {
|
||||||
|
|
||||||
// dampen hips rotation, by mixing it with the avatar orientation in sensor space
|
// dampen hips rotation, by mixing it with the avatar orientation in sensor space
|
||||||
// turning this off for center of gravity model because it is already mixed in there
|
// turning this off for center of gravity model because it is already mixed in there
|
||||||
if (!(myAvatar->getCenterOfGravityModelEnabled())) {
|
if (!useCenterOfGravityModel) {
|
||||||
const float MIX_RATIO = 0.5f;
|
const float MIX_RATIO = 0.5f;
|
||||||
hipsRot = safeLerp(glmExtractRotation(avatarToSensorMat), hipsRot, MIX_RATIO);
|
hipsRot = safeLerp(glmExtractRotation(avatarToSensorMat), hipsRot, MIX_RATIO);
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,13 +67,13 @@ void AnimStats::updateStats(bool force) {
|
||||||
|
|
||||||
// print if we are recentering or not.
|
// print if we are recentering or not.
|
||||||
_recenterText = "Recenter: ";
|
_recenterText = "Recenter: ";
|
||||||
if (myAvatar->isFollowActive(MyAvatar::FollowHelper::Rotation)) {
|
if (myAvatar->isFollowActive(CharacterController::FollowType::Rotation)) {
|
||||||
_recenterText += "Rotation ";
|
_recenterText += "Rotation ";
|
||||||
}
|
}
|
||||||
if (myAvatar->isFollowActive(MyAvatar::FollowHelper::Horizontal)) {
|
if (myAvatar->isFollowActive(CharacterController::FollowType::Horizontal)) {
|
||||||
_recenterText += "Horizontal ";
|
_recenterText += "Horizontal ";
|
||||||
}
|
}
|
||||||
if (myAvatar->isFollowActive(MyAvatar::FollowHelper::Vertical)) {
|
if (myAvatar->isFollowActive(CharacterController::FollowType::Vertical)) {
|
||||||
_recenterText += "Vertical ";
|
_recenterText += "Vertical ";
|
||||||
}
|
}
|
||||||
emit recenterTextChanged();
|
emit recenterTextChanged();
|
||||||
|
|
|
@ -422,40 +422,40 @@ void setupPreferences() {
|
||||||
preferences->addPreference(preference);
|
preferences->addPreference(preference);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
auto getter = [myAvatar]()->int {
|
IntPreference::Getter getter = [myAvatar]() -> int {
|
||||||
switch (myAvatar->getUserRecenterModel()) {
|
return static_cast<int>(myAvatar->getAllowAvatarStandingPreference());
|
||||||
case MyAvatar::SitStandModelType::Auto:
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
case MyAvatar::SitStandModelType::ForceSit:
|
|
||||||
return 1;
|
|
||||||
case MyAvatar::SitStandModelType::ForceStand:
|
|
||||||
return 2;
|
|
||||||
case MyAvatar::SitStandModelType::DisableHMDLean:
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
auto setter = [myAvatar](int value) {
|
|
||||||
switch (value) {
|
IntPreference::Setter setter = [myAvatar](const int& value) {
|
||||||
case 0:
|
myAvatar->setAllowAvatarStandingPreference(static_cast<MyAvatar::AllowAvatarStandingPreference>(value));
|
||||||
default:
|
|
||||||
myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::Auto);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::ForceSit);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::ForceStand);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::DisableHMDLean);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
auto preference = new RadioButtonsPreference(VR_MOVEMENT, "Auto / Force Sit / Force Stand / Disable Recenter", getter, setter);
|
|
||||||
|
auto preference = new RadioButtonsPreference(VR_MOVEMENT, "Allow my avatar to stand", getter, setter);
|
||||||
QStringList items;
|
QStringList items;
|
||||||
items << "Auto - turns on avatar leaning when standing in real world" << "Seated - disables all avatar leaning while sitting in real world" << "Standing - enables avatar leaning while sitting in real world" << "Disabled - allows avatar sitting on the floor [Experimental]";
|
items << "When I'm standing"
|
||||||
preference->setHeading("Avatar leaning behavior");
|
<< "Always"; // Must match the order in MyAvatar::AllowAvatarStandingPreference.
|
||||||
|
assert(items.size() == static_cast<uint>(MyAvatar::AllowAvatarStandingPreference::Count));
|
||||||
|
preference->setHeading("Allow my avatar to stand:");
|
||||||
|
preference->setItems(items);
|
||||||
|
preferences->addPreference(preference);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
IntPreference::Getter getter = [myAvatar]() -> int {
|
||||||
|
return static_cast<int>(myAvatar->getAllowAvatarLeaningPreference());
|
||||||
|
};
|
||||||
|
|
||||||
|
IntPreference::Setter setter = [myAvatar](const int& value) {
|
||||||
|
myAvatar->setAllowAvatarLeaningPreference(static_cast<MyAvatar::AllowAvatarLeaningPreference>(value));
|
||||||
|
};
|
||||||
|
|
||||||
|
auto preference = new RadioButtonsPreference(VR_MOVEMENT, "Allow my avatar to lean", getter, setter);
|
||||||
|
QStringList items;
|
||||||
|
items << "When I'm standing"
|
||||||
|
<< "Always"
|
||||||
|
<< "Never"
|
||||||
|
<< "Always, no recenter (Experimental)"; // Must match the order in MyAvatar::AllowAvatarLeaningPreference.
|
||||||
|
assert(items.size() == static_cast<uint>(MyAvatar::AllowAvatarLeaningPreference::Count));
|
||||||
|
preference->setHeading("Allow my avatar to lean:");
|
||||||
preference->setItems(items);
|
preference->setItems(items);
|
||||||
preferences->addPreference(preference);
|
preferences->addPreference(preference);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1855,6 +1855,16 @@ glm::vec3 Rig::deflectHandFromTorso(const glm::vec3& handPosition, const HFMJoin
|
||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the scale factor to convert distances in the geometry frame into the unscaled rig frame.
|
||||||
|
// Typically it will be the unit conversion from cm to m.
|
||||||
|
float Rig::GetScaleFactorGeometryToUnscaledRig() const {
|
||||||
|
// Normally the model offset transform will contain the avatar scale factor; we explicitly remove it here.
|
||||||
|
AnimPose modelOffsetWithoutAvatarScale(glm::vec3(1.0f), getModelOffsetPose().rot(), getModelOffsetPose().trans());
|
||||||
|
AnimPose geomToRigWithoutAvatarScale = modelOffsetWithoutAvatarScale * getGeometryOffsetPose();
|
||||||
|
|
||||||
|
return geomToRigWithoutAvatarScale.scale().x; // in practice this is always a uniform scale factor.
|
||||||
|
}
|
||||||
|
|
||||||
void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, bool hipsEstimated,
|
void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, bool hipsEstimated,
|
||||||
bool leftArmEnabled, bool rightArmEnabled, bool headEnabled, float dt,
|
bool leftArmEnabled, bool rightArmEnabled, bool headEnabled, float dt,
|
||||||
const AnimPose& leftHandPose, const AnimPose& rightHandPose,
|
const AnimPose& leftHandPose, const AnimPose& rightHandPose,
|
||||||
|
@ -2703,10 +2713,10 @@ void Rig::computeAvatarBoundingCapsule(
|
||||||
Extents totalExtents;
|
Extents totalExtents;
|
||||||
totalExtents.reset();
|
totalExtents.reset();
|
||||||
|
|
||||||
// HACK by convention our Avatars are always modeled such that y=0 is the ground plane.
|
// HACK by convention our Avatars are always modeled such that y=0 (GEOMETRY_GROUND_Y) is the ground plane.
|
||||||
// add the zero point so that our avatars will always have bounding volumes that are flush with the ground
|
// add the ground point so that our avatars will always have bounding volumes that are flush with the ground
|
||||||
// even if they do not have legs (default robot)
|
// even if they do not have legs (default robot)
|
||||||
totalExtents.addPoint(glm::vec3(0.0f));
|
totalExtents.addPoint(glm::vec3(0.0f, GEOMETRY_GROUND_Y, 0.0f));
|
||||||
|
|
||||||
// To reduce the radius of the bounding capsule to be tight with the torso, we only consider joints
|
// To reduce the radius of the bounding capsule to be tight with the torso, we only consider joints
|
||||||
// from the head to the hips when computing the rest of the bounding capsule.
|
// from the head to the hips when computing the rest of the bounding capsule.
|
||||||
|
@ -2747,24 +2757,20 @@ void Rig::initFlow(bool isActive) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the vertical position of eye joints, in the rig coordinate frame, ignoring the avatar scale.
|
||||||
float Rig::getUnscaledEyeHeight() const {
|
float Rig::getUnscaledEyeHeight() const {
|
||||||
// Normally the model offset transform will contain the avatar scale factor, we explicitly remove it here.
|
// Normally the model offset transform will contain the avatar scale factor, we explicitly remove it here.
|
||||||
AnimPose modelOffsetWithoutAvatarScale(glm::vec3(1.0f), getModelOffsetPose().rot(), getModelOffsetPose().trans());
|
AnimPose modelOffsetWithoutAvatarScale(glm::vec3(1.0f), getModelOffsetPose().rot(), getModelOffsetPose().trans());
|
||||||
AnimPose geomToRigWithoutAvatarScale = modelOffsetWithoutAvatarScale * getGeometryOffsetPose();
|
AnimPose geomToRigWithoutAvatarScale = modelOffsetWithoutAvatarScale * getGeometryOffsetPose();
|
||||||
|
|
||||||
// This factor can be used to scale distances in the geometry frame into the unscaled rig frame.
|
// Factor to scale distances in the geometry frame into the unscaled rig frame.
|
||||||
// Typically it will be the unit conversion from cm to m.
|
float scaleFactor = GetScaleFactorGeometryToUnscaledRig();
|
||||||
float scaleFactor = geomToRigWithoutAvatarScale.scale().x; // in practice this always a uniform scale factor.
|
|
||||||
|
|
||||||
int headTopJoint = indexOfJoint("HeadTop_End");
|
int headTopJoint = indexOfJoint("HeadTop_End");
|
||||||
int headJoint = indexOfJoint("Head");
|
int headJoint = indexOfJoint("Head");
|
||||||
int eyeJoint = indexOfJoint("LeftEye") != -1 ? indexOfJoint("LeftEye") : indexOfJoint("RightEye");
|
int eyeJoint = indexOfJoint("LeftEye") != -1 ? indexOfJoint("LeftEye") : indexOfJoint("RightEye");
|
||||||
int toeJoint = indexOfJoint("LeftToeBase") != -1 ? indexOfJoint("LeftToeBase") : indexOfJoint("RightToeBase");
|
int toeJoint = indexOfJoint("LeftToeBase") != -1 ? indexOfJoint("LeftToeBase") : indexOfJoint("RightToeBase");
|
||||||
|
|
||||||
// Makes assumption that the y = 0 plane in geometry is the ground plane.
|
|
||||||
// We also make that assumption in Rig::computeAvatarBoundingCapsule()
|
|
||||||
const float GROUND_Y = 0.0f;
|
|
||||||
|
|
||||||
// Values from the skeleton are in the geometry coordinate frame.
|
// Values from the skeleton are in the geometry coordinate frame.
|
||||||
auto skeleton = getAnimSkeleton();
|
auto skeleton = getAnimSkeleton();
|
||||||
if (eyeJoint >= 0 && toeJoint >= 0) {
|
if (eyeJoint >= 0 && toeJoint >= 0) {
|
||||||
|
@ -2772,8 +2778,8 @@ float Rig::getUnscaledEyeHeight() const {
|
||||||
float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - skeleton->getAbsoluteDefaultPose(toeJoint).trans().y;
|
float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - skeleton->getAbsoluteDefaultPose(toeJoint).trans().y;
|
||||||
return scaleFactor * eyeHeight;
|
return scaleFactor * eyeHeight;
|
||||||
} else if (eyeJoint >= 0) {
|
} else if (eyeJoint >= 0) {
|
||||||
// Measure Eye joint to y = 0 plane.
|
// Measure Eye joint to ground plane.
|
||||||
float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - GROUND_Y;
|
float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - GEOMETRY_GROUND_Y;
|
||||||
return scaleFactor * eyeHeight;
|
return scaleFactor * eyeHeight;
|
||||||
} else if (headTopJoint >= 0 && toeJoint >= 0) {
|
} else if (headTopJoint >= 0 && toeJoint >= 0) {
|
||||||
// Measure from ToeBase joint to HeadTop_End joint, then remove forehead distance.
|
// Measure from ToeBase joint to HeadTop_End joint, then remove forehead distance.
|
||||||
|
@ -2783,19 +2789,36 @@ float Rig::getUnscaledEyeHeight() const {
|
||||||
} else if (headTopJoint >= 0) {
|
} else if (headTopJoint >= 0) {
|
||||||
// Measure from HeadTop_End joint to the ground, then remove forehead distance.
|
// Measure from HeadTop_End joint to the ground, then remove forehead distance.
|
||||||
const float ratio = DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD / DEFAULT_AVATAR_HEIGHT;
|
const float ratio = DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD / DEFAULT_AVATAR_HEIGHT;
|
||||||
float headHeight = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - GROUND_Y;
|
float headHeight = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - GEOMETRY_GROUND_Y;
|
||||||
return scaleFactor * (headHeight - headHeight * ratio);
|
return scaleFactor * (headHeight - headHeight * ratio);
|
||||||
} else if (headJoint >= 0) {
|
} else if (headJoint >= 0) {
|
||||||
// Measure Head joint to the ground, then add in distance from neck to eye.
|
// Measure Head joint to the ground, then add in distance from neck to eye.
|
||||||
const float DEFAULT_AVATAR_NECK_TO_EYE = DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD - DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD;
|
const float DEFAULT_AVATAR_NECK_TO_EYE = DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD - DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD;
|
||||||
const float ratio = DEFAULT_AVATAR_NECK_TO_EYE / DEFAULT_AVATAR_NECK_HEIGHT;
|
const float ratio = DEFAULT_AVATAR_NECK_TO_EYE / DEFAULT_AVATAR_NECK_HEIGHT;
|
||||||
float neckHeight = skeleton->getAbsoluteDefaultPose(headJoint).trans().y - GROUND_Y;
|
float neckHeight = skeleton->getAbsoluteDefaultPose(headJoint).trans().y - GEOMETRY_GROUND_Y;
|
||||||
return scaleFactor * (neckHeight + neckHeight * ratio);
|
return scaleFactor * (neckHeight + neckHeight * ratio);
|
||||||
} else {
|
} else {
|
||||||
return DEFAULT_AVATAR_EYE_HEIGHT;
|
return DEFAULT_AVATAR_EYE_HEIGHT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the vertical position of the hips joint, in the rig coordinate frame, ignoring the avatar scale.
|
||||||
|
float Rig::getUnscaledHipsHeight() const {
|
||||||
|
// This factor can be used to scale distances in the geometry frame into the unscaled rig frame.
|
||||||
|
float scaleFactor = GetScaleFactorGeometryToUnscaledRig();
|
||||||
|
|
||||||
|
int hipsJoint = indexOfJoint("Hips");
|
||||||
|
|
||||||
|
// Values from the skeleton are in the geometry coordinate frame.
|
||||||
|
if (hipsJoint >= 0) {
|
||||||
|
// Measure hip joint to ground plane.
|
||||||
|
float hipsHeight = getAnimSkeleton()->getAbsoluteDefaultPose(hipsJoint).trans().y - GEOMETRY_GROUND_Y;
|
||||||
|
return scaleFactor * hipsHeight;
|
||||||
|
} else {
|
||||||
|
return DEFAULT_AVATAR_HIPS_HEIGHT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Rig::setDirectionalBlending(const QString& targetName, const glm::vec3& blendingTarget, const QString& alphaName, float alpha) {
|
void Rig::setDirectionalBlending(const QString& targetName, const glm::vec3& blendingTarget, const QString& alphaName, float alpha) {
|
||||||
_animVars.set(targetName, blendingTarget);
|
_animVars.set(targetName, blendingTarget);
|
||||||
_animVars.set(alphaName, alpha);
|
_animVars.set(alphaName, alpha);
|
||||||
|
|
|
@ -251,6 +251,7 @@ public:
|
||||||
Flow& getFlow() { return _internalFlow; }
|
Flow& getFlow() { return _internalFlow; }
|
||||||
|
|
||||||
float getUnscaledEyeHeight() const;
|
float getUnscaledEyeHeight() const;
|
||||||
|
float getUnscaledHipsHeight() const;
|
||||||
void buildAbsoluteRigPoses(const AnimPoseVec& relativePoses, AnimPoseVec& absolutePosesOut) const;
|
void buildAbsoluteRigPoses(const AnimPoseVec& relativePoses, AnimPoseVec& absolutePosesOut) const;
|
||||||
|
|
||||||
int getOverrideJointCount() const;
|
int getOverrideJointCount() const;
|
||||||
|
@ -287,6 +288,11 @@ protected:
|
||||||
glm::vec3 deflectHandFromTorso(const glm::vec3& handPosition, const HFMJointShapeInfo& hipsShapeInfo, const HFMJointShapeInfo& spineShapeInfo,
|
glm::vec3 deflectHandFromTorso(const glm::vec3& handPosition, const HFMJointShapeInfo& hipsShapeInfo, const HFMJointShapeInfo& spineShapeInfo,
|
||||||
const HFMJointShapeInfo& spine1ShapeInfo, const HFMJointShapeInfo& spine2ShapeInfo) const;
|
const HFMJointShapeInfo& spine1ShapeInfo, const HFMJointShapeInfo& spine2ShapeInfo) const;
|
||||||
|
|
||||||
|
// Get the scale factor to convert distances in the geometry frame into the unscaled rig frame.
|
||||||
|
float GetScaleFactorGeometryToUnscaledRig() const;
|
||||||
|
|
||||||
|
// The ground plane Y position in geometry space.
|
||||||
|
static constexpr float GEOMETRY_GROUND_Y = 0.0f;
|
||||||
|
|
||||||
AnimPose _modelOffset; // model to rig space
|
AnimPose _modelOffset; // model to rig space
|
||||||
AnimPose _geometryOffset; // geometry to model space (includes unit offset & fst offsets)
|
AnimPose _geometryOffset; // geometry to model space (includes unit offset & fst offsets)
|
||||||
|
|
|
@ -107,12 +107,12 @@ CharacterController::CharacterMotor::CharacterMotor(const glm::vec3& vel, const
|
||||||
|
|
||||||
static uint32_t _numCharacterControllers { 0 };
|
static uint32_t _numCharacterControllers { 0 };
|
||||||
|
|
||||||
CharacterController::CharacterController() {
|
CharacterController::CharacterController(const FollowTimePerType& followTimeRemainingPerType) :
|
||||||
|
_followTimeRemainingPerType(followTimeRemainingPerType) {
|
||||||
_floorDistance = _scaleFactor * DEFAULT_AVATAR_FALL_HEIGHT;
|
_floorDistance = _scaleFactor * DEFAULT_AVATAR_FALL_HEIGHT;
|
||||||
|
|
||||||
_targetVelocity.setValue(0.0f, 0.0f, 0.0f);
|
_targetVelocity.setValue(0.0f, 0.0f, 0.0f);
|
||||||
_followDesiredBodyTransform.setIdentity();
|
_followDesiredBodyTransform.setIdentity();
|
||||||
_followTimeRemaining = 0.0f;
|
|
||||||
_state = State::Hover;
|
_state = State::Hover;
|
||||||
_isPushingUp = false;
|
_isPushingUp = false;
|
||||||
_rayHitStartTime = 0;
|
_rayHitStartTime = 0;
|
||||||
|
@ -350,64 +350,103 @@ void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar
|
||||||
btVector3 velocity = _rigidBody->getLinearVelocity() - _parentVelocity;
|
btVector3 velocity = _rigidBody->getLinearVelocity() - _parentVelocity;
|
||||||
computeNewVelocity(dt, velocity);
|
computeNewVelocity(dt, velocity);
|
||||||
|
|
||||||
const float MINIMUM_TIME_REMAINING = 0.005f;
|
constexpr float MINIMUM_TIME_REMAINING = 0.005f;
|
||||||
const float MAX_DISPLACEMENT = 0.5f * _radius;
|
static_assert(FOLLOW_TIME_IMMEDIATE_SNAP > MINIMUM_TIME_REMAINING, "The code below assumes this condition is true.");
|
||||||
_followTimeRemaining -= dt;
|
|
||||||
if (_followTimeRemaining >= MINIMUM_TIME_REMAINING) {
|
|
||||||
btTransform bodyTransform = _rigidBody->getWorldTransform();
|
|
||||||
|
|
||||||
|
bool hasFollowTimeRemaining = false;
|
||||||
|
for (float followTime : _followTimeRemainingPerType) {
|
||||||
|
if (followTime > MINIMUM_TIME_REMAINING) {
|
||||||
|
hasFollowTimeRemaining = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasFollowTimeRemaining) {
|
||||||
|
const float MAX_DISPLACEMENT = 0.5f * _radius;
|
||||||
|
|
||||||
|
btTransform bodyTransform = _rigidBody->getWorldTransform();
|
||||||
btVector3 startPos = bodyTransform.getOrigin();
|
btVector3 startPos = bodyTransform.getOrigin();
|
||||||
btVector3 deltaPos = _followDesiredBodyTransform.getOrigin() - startPos;
|
btVector3 deltaPos = _followDesiredBodyTransform.getOrigin() - startPos;
|
||||||
btVector3 vel = deltaPos / _followTimeRemaining;
|
|
||||||
btVector3 linearDisplacement = clampLength(vel * dt, MAX_DISPLACEMENT); // clamp displacement to prevent tunneling.
|
btVector3 linearDisplacement(0.0f, 0.0f, 0.0f);
|
||||||
|
{
|
||||||
|
float horizontalTime = _followTimeRemainingPerType[static_cast<uint>(FollowType::Horizontal)];
|
||||||
|
float verticalTime = _followTimeRemainingPerType[static_cast<uint>(FollowType::Vertical)];
|
||||||
|
|
||||||
|
if (horizontalTime == FOLLOW_TIME_IMMEDIATE_SNAP) {
|
||||||
|
linearDisplacement.setX(deltaPos.x());
|
||||||
|
linearDisplacement.setZ(deltaPos.z());
|
||||||
|
} else if (horizontalTime > MINIMUM_TIME_REMAINING) {
|
||||||
|
linearDisplacement.setX((deltaPos.x() * dt) / horizontalTime);
|
||||||
|
linearDisplacement.setZ((deltaPos.z() * dt) / horizontalTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verticalTime == FOLLOW_TIME_IMMEDIATE_SNAP) {
|
||||||
|
linearDisplacement.setY(deltaPos.y());
|
||||||
|
} else if (verticalTime > MINIMUM_TIME_REMAINING) {
|
||||||
|
linearDisplacement.setY((deltaPos.y() * dt) / verticalTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
linearDisplacement = clampLength(linearDisplacement, MAX_DISPLACEMENT); // clamp displacement to prevent tunneling.
|
||||||
|
}
|
||||||
|
|
||||||
btVector3 endPos = startPos + linearDisplacement;
|
btVector3 endPos = startPos + linearDisplacement;
|
||||||
|
|
||||||
// resolve the simple linearDisplacement
|
// resolve the simple linearDisplacement
|
||||||
_followLinearDisplacement += linearDisplacement;
|
_followLinearDisplacement += linearDisplacement;
|
||||||
|
|
||||||
// now for the rotational part...
|
// now for the rotational part...
|
||||||
|
|
||||||
btQuaternion startRot = bodyTransform.getRotation();
|
btQuaternion startRot = bodyTransform.getRotation();
|
||||||
btQuaternion desiredRot = _followDesiredBodyTransform.getRotation();
|
|
||||||
|
|
||||||
// startRot as default rotation
|
// startRot as default rotation
|
||||||
btQuaternion endRot = startRot;
|
btQuaternion endRot = startRot;
|
||||||
|
|
||||||
// the dot product between two quaternions is equal to +/- cos(angle/2)
|
float rotationTime = _followTimeRemainingPerType[static_cast<uint>(FollowType::Rotation)];
|
||||||
// where 'angle' is that of the rotation between them
|
if (rotationTime > MINIMUM_TIME_REMAINING) {
|
||||||
float qDot = desiredRot.dot(startRot);
|
btQuaternion desiredRot = _followDesiredBodyTransform.getRotation();
|
||||||
|
|
||||||
// when the abs() value of the dot product is approximately 1.0
|
// the dot product between two quaternions is equal to +/- cos(angle/2)
|
||||||
// then the two rotations are effectively adjacent
|
// where 'angle' is that of the rotation between them
|
||||||
const float MIN_DOT_PRODUCT_OF_ADJACENT_QUATERNIONS = 0.99999f; // corresponds to approx 0.5 degrees
|
float qDot = desiredRot.dot(startRot);
|
||||||
if (fabsf(qDot) < MIN_DOT_PRODUCT_OF_ADJACENT_QUATERNIONS) {
|
|
||||||
if (qDot < 0.0f) {
|
// when the abs() value of the dot product is approximately 1.0
|
||||||
// the quaternions are actually on opposite hyperhemispheres
|
// then the two rotations are effectively adjacent
|
||||||
// so we move one to agree with the other and negate qDot
|
const float MIN_DOT_PRODUCT_OF_ADJACENT_QUATERNIONS = 0.99999f; // corresponds to approx 0.5 degrees
|
||||||
desiredRot = -desiredRot;
|
if (fabsf(qDot) < MIN_DOT_PRODUCT_OF_ADJACENT_QUATERNIONS) {
|
||||||
qDot = -qDot;
|
if (qDot < 0.0f) {
|
||||||
|
// the quaternions are actually on opposite hyperhemispheres
|
||||||
|
// so we move one to agree with the other and negate qDot
|
||||||
|
desiredRot = -desiredRot;
|
||||||
|
qDot = -qDot;
|
||||||
|
}
|
||||||
|
btQuaternion deltaRot = desiredRot * startRot.inverse();
|
||||||
|
|
||||||
|
// the axis is the imaginary part, but scaled by sin(angle/2)
|
||||||
|
btVector3 axis(deltaRot.getX(), deltaRot.getY(), deltaRot.getZ());
|
||||||
|
axis /= sqrtf(1.0f - qDot * qDot);
|
||||||
|
|
||||||
|
// compute the angle we will resolve for this dt, but don't overshoot
|
||||||
|
float angle = 2.0f * acosf(qDot);
|
||||||
|
|
||||||
|
if (rotationTime != FOLLOW_TIME_IMMEDIATE_SNAP) {
|
||||||
|
if (dt < rotationTime) {
|
||||||
|
angle *= dt / rotationTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// accumulate rotation
|
||||||
|
deltaRot = btQuaternion(axis, angle);
|
||||||
|
_followAngularDisplacement = (deltaRot * _followAngularDisplacement).normalize();
|
||||||
|
|
||||||
|
// in order to accumulate displacement of avatar position, we need to take _shapeLocalOffset into account.
|
||||||
|
btVector3 shapeLocalOffset = glmToBullet(_shapeLocalOffset);
|
||||||
|
|
||||||
|
endRot = deltaRot * startRot;
|
||||||
|
btVector3 swingDisplacement =
|
||||||
|
rotateVector(endRot, -shapeLocalOffset) - rotateVector(startRot, -shapeLocalOffset);
|
||||||
|
_followLinearDisplacement += swingDisplacement;
|
||||||
}
|
}
|
||||||
btQuaternion deltaRot = desiredRot * startRot.inverse();
|
|
||||||
|
|
||||||
// the axis is the imaginary part, but scaled by sin(angle/2)
|
|
||||||
btVector3 axis(deltaRot.getX(), deltaRot.getY(), deltaRot.getZ());
|
|
||||||
axis /= sqrtf(1.0f - qDot * qDot);
|
|
||||||
|
|
||||||
// compute the angle we will resolve for this dt, but don't overshoot
|
|
||||||
float angle = 2.0f * acosf(qDot);
|
|
||||||
if (dt < _followTimeRemaining) {
|
|
||||||
angle *= dt / _followTimeRemaining;
|
|
||||||
}
|
|
||||||
|
|
||||||
// accumulate rotation
|
|
||||||
deltaRot = btQuaternion(axis, angle);
|
|
||||||
_followAngularDisplacement = (deltaRot * _followAngularDisplacement).normalize();
|
|
||||||
|
|
||||||
// in order to accumulate displacement of avatar position, we need to take _shapeLocalOffset into account.
|
|
||||||
btVector3 shapeLocalOffset = glmToBullet(_shapeLocalOffset);
|
|
||||||
|
|
||||||
endRot = deltaRot * startRot;
|
|
||||||
btVector3 swingDisplacement = rotateVector(endRot, -shapeLocalOffset) - rotateVector(startRot, -shapeLocalOffset);
|
|
||||||
_followLinearDisplacement += swingDisplacement;
|
|
||||||
}
|
}
|
||||||
_rigidBody->setWorldTransform(btTransform(endRot, endPos));
|
_rigidBody->setWorldTransform(btTransform(endRot, endPos));
|
||||||
}
|
}
|
||||||
|
@ -606,8 +645,7 @@ void CharacterController::setParentVelocity(const glm::vec3& velocity) {
|
||||||
_parentVelocity = glmToBullet(velocity);
|
_parentVelocity = glmToBullet(velocity);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterController::setFollowParameters(const glm::mat4& desiredWorldBodyMatrix, float timeRemaining) {
|
void CharacterController::setFollowParameters(const glm::mat4& desiredWorldBodyMatrix) {
|
||||||
_followTimeRemaining = timeRemaining;
|
|
||||||
_followDesiredBodyTransform = glmToBullet(desiredWorldBodyMatrix) * btTransform(btQuaternion::getIdentity(), glmToBullet(_shapeLocalOffset));
|
_followDesiredBodyTransform = glmToBullet(desiredWorldBodyMatrix) * btTransform(btQuaternion::getIdentity(), glmToBullet(_shapeLocalOffset));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,20 @@ const btScalar MIN_CHARACTER_MOTOR_TIMESCALE = 0.05f;
|
||||||
class CharacterController : public btCharacterControllerInterface {
|
class CharacterController : public btCharacterControllerInterface {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CharacterController();
|
enum class FollowType : uint8_t {
|
||||||
|
Rotation,
|
||||||
|
Horizontal,
|
||||||
|
Vertical,
|
||||||
|
Count
|
||||||
|
};
|
||||||
|
|
||||||
|
// Remaining follow time for each FollowType
|
||||||
|
typedef std::array<float, static_cast<size_t>(FollowType::Count)> FollowTimePerType;
|
||||||
|
|
||||||
|
// Follow time value meaning that we should snap immediately to the target.
|
||||||
|
static constexpr float FOLLOW_TIME_IMMEDIATE_SNAP = FLT_MAX;
|
||||||
|
|
||||||
|
CharacterController(const FollowTimePerType& followTimeRemainingPerType);
|
||||||
virtual ~CharacterController();
|
virtual ~CharacterController();
|
||||||
bool needsRemoval() const;
|
bool needsRemoval() const;
|
||||||
bool needsAddition() const;
|
bool needsAddition() const;
|
||||||
|
@ -99,7 +112,8 @@ public:
|
||||||
void getPositionAndOrientation(glm::vec3& position, glm::quat& rotation) const;
|
void getPositionAndOrientation(glm::vec3& position, glm::quat& rotation) const;
|
||||||
|
|
||||||
void setParentVelocity(const glm::vec3& parentVelocity);
|
void setParentVelocity(const glm::vec3& parentVelocity);
|
||||||
void setFollowParameters(const glm::mat4& desiredWorldMatrix, float timeRemaining);
|
|
||||||
|
void setFollowParameters(const glm::mat4& desiredWorldMatrix);
|
||||||
float getFollowTime() const { return _followTime; }
|
float getFollowTime() const { return _followTime; }
|
||||||
glm::vec3 getFollowLinearDisplacement() const;
|
glm::vec3 getFollowLinearDisplacement() const;
|
||||||
glm::quat getFollowAngularDisplacement() const;
|
glm::quat getFollowAngularDisplacement() const;
|
||||||
|
@ -144,7 +158,7 @@ public:
|
||||||
|
|
||||||
void setPendingFlagsUpdateCollisionMask(){ _pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_MASK; }
|
void setPendingFlagsUpdateCollisionMask(){ _pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_MASK; }
|
||||||
void setSeated(bool isSeated) { _isSeated = isSeated; }
|
void setSeated(bool isSeated) { _isSeated = isSeated; }
|
||||||
bool getSeated() { return _isSeated; }
|
bool getSeated() const { return _isSeated; }
|
||||||
|
|
||||||
void resetStuckCounter() { _numStuckSubsteps = 0; }
|
void resetStuckCounter() { _numStuckSubsteps = 0; }
|
||||||
|
|
||||||
|
@ -178,7 +192,7 @@ protected:
|
||||||
btVector3 _preSimulationVelocity;
|
btVector3 _preSimulationVelocity;
|
||||||
btVector3 _velocityChange;
|
btVector3 _velocityChange;
|
||||||
btTransform _followDesiredBodyTransform;
|
btTransform _followDesiredBodyTransform;
|
||||||
btScalar _followTimeRemaining;
|
const FollowTimePerType& _followTimeRemainingPerType;
|
||||||
btTransform _characterBodyTransform;
|
btTransform _characterBodyTransform;
|
||||||
btVector3 _position;
|
btVector3 _position;
|
||||||
btQuaternion _rotation;
|
btQuaternion _rotation;
|
||||||
|
|
|
@ -20,6 +20,7 @@ const float DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD = 0.11f; // meters
|
||||||
const float DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD = 0.185f; // meters
|
const float DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD = 0.185f; // meters
|
||||||
const float DEFAULT_AVATAR_NECK_HEIGHT = DEFAULT_AVATAR_HEIGHT - DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD;
|
const float DEFAULT_AVATAR_NECK_HEIGHT = DEFAULT_AVATAR_HEIGHT - DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD;
|
||||||
const float DEFAULT_AVATAR_EYE_HEIGHT = DEFAULT_AVATAR_HEIGHT - DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD;
|
const float DEFAULT_AVATAR_EYE_HEIGHT = DEFAULT_AVATAR_HEIGHT - DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD;
|
||||||
|
const float DEFAULT_AVATAR_HIPS_HEIGHT = 1.01327407f; // meters
|
||||||
const float DEFAULT_SPINE2_SPLINE_PROPORTION = 0.71f;
|
const float DEFAULT_SPINE2_SPLINE_PROPORTION = 0.71f;
|
||||||
const float DEFAULT_AVATAR_SUPPORT_BASE_LEFT = -0.25f;
|
const float DEFAULT_AVATAR_SUPPORT_BASE_LEFT = -0.25f;
|
||||||
const float DEFAULT_AVATAR_SUPPORT_BASE_RIGHT = 0.25f;
|
const float DEFAULT_AVATAR_SUPPORT_BASE_RIGHT = 0.25f;
|
||||||
|
|
Loading…
Reference in a new issue