VR fixes for: couldn't sit on the floor, wrong walk directions.

- Divided the option "Avatar leaning behavior" into two options that work more usefully: "Allow my avatar to stand" and "Allow my avatar to lean" (PreferencesDialog.cpp).  Made the necessary fixes so that the avatar can be set to stand only when the user is standing (more details below).
- The logic controlling the direction of MyAvatar's action motor is now centralised in calculateScaledDirection (was previously split between there and updateMotors).  calculateScaledDirection now returns a velocity in world space.
- CharacterController::FollowHelper now uses separate follow timers for rotation, horizontal and vertical (previously followed all three based on the longest of their follow times).  Where appropriate, FollowHelper can now snap immediately to the desired rotation/horizontal/vertical independently (see FOLLOW_TIME_IMMEDIATE_SNAP).
- FollowHelper::FollowType has therefore moved to CharacterController::FollowType.
- MyAvatar::FollowHelper::postPhysicsUpdate: If MyAvatar is not allowed to stand when the user is sitting, this now avoids recentring the body based on the head height.
- Removed Q_PROPERTY(MyAvatar::SitStandModelType, as the sitting/standing/leaning model uses different enums now (see setAllowAvatarStandingPreference, setAllowAvatarLeaningPreference).
- Removed Q_PROPERTY(bool isSitStandStateLocked which is no longer used, because we now always track the user's real-world sit/stand state, regardless of what we're doing with it.
- MyAvatar::FollowHelper::shouldActivateHorizontal: If MyAvatar is not allowed to lean, this now returns true to recentre the footing if the head is outside the base of support.
- MyAvatar::FollowHelper::shouldActivateHorizontalCG: If MyAvatar is not allowed to lean, this now always returns true to recentre the footing.  Rearranged to avoid computing values that weren't used depending on the conditions.  Resolved some duplicated code.
- MyAvatar::setUserRecenterModel previously set HMDLeanRecenterEnabled based on the chosen mode, but it got reset when getting out of a sit.  Now HMDLeanRecenterEnabled is only controlled by the scripts.
- Added Rig::getUnscaledHipsHeight (like getUnscaledEyeHeight).  Refactored a little to avoid duplicated code.  Added DEFAULT_AVATAR_HIPS_HEIGHT which is the value that Rig::getUnscaledHipsHeight returns when using the default avatar.
- Fix for recentring not behaving as requested by the user after getting up from click-to-sit (always behaving like 'Auto') : MyAvatar::endSit now passes false to centerBody for 'forceFollowYPos'.
- Fix for incorrect vertical position of the avatar and viewpoint after changing lean recentre mode while not standing in the real world: MyAvatar::setAllowAvatarStandingPreference now calls centerBody with false for 'forceFollowYPos'.
- computeHipsInSensorFrame: The code now matches the comments in that it only skips the dampening of the hips rotation if the centre-of-gravity model is being used.
This commit is contained in:
Phil Palmer 2020-12-22 14:22:27 -05:00
parent f1576aba78
commit 2179c153de
12 changed files with 691 additions and 441 deletions

File diff suppressed because it is too large Load diff

View file

@ -282,16 +282,11 @@ class MyAvatar : public Avatar {
* <p><strong>Warning:</strong> Setting this value also sets the value of <code>analogPlusSprintSpeed</code> to twice * <p><strong>Warning:</strong> Setting this value also sets the value of <code>analogPlusSprintSpeed</code> to twice
* 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 {number} isInSittingState - <code>true</code> if the user wearing the HMD is determined to be sitting;
* @property {number} isInSittingState - <code>true</code> if the user wearing the HMD is determined to be sitting * <code>false</code> if the user wearing the HMD is determined to be standing. This can affect whether the avatar
* (avatar leaning is disabled, recentering is enabled), <code>false</code> if the user wearing the HMD is * is allowed to stand, lean or recenter its footing, depending on user preferences.
* determined to be standing (avatar leaning is enabled, and avatar recenters if it leans too far). * The property value automatically updates as the user sits or stands. Setting the property value overrides the current
* If <code>userRecenterModel == 2</code> (i.e., "auto") the property value automatically updates as the user sits * sitting / standing state, which is updated when the user next sits or stands.
* or stands, unless <code>isSitStandStateLocked == true</code>. Setting the property value overrides the current
* sitting / standing state, which is updated when the user next sits or stands unless
* <code>isSitStandStateLocked == true</code>.
* @property {boolean} isSitStandStateLocked - <code>true</code> to lock the avatar sitting/standing state, i.e., use this
* to disable automatically changing state.
* @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 +408,6 @@ 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(bool isSitStandStateLocked READ getIsSitStandStateLocked WRITE setIsSitStandStateLocked);
Q_PROPERTY(bool allowTeleporting READ getAllowTeleporting) Q_PROPERTY(bool allowTeleporting READ getAllowTeleporting)
const QString DOMINANT_LEFT_HAND = "left"; const QString DOMINANT_LEFT_HAND = "left";
@ -549,6 +542,31 @@ 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();
@ -576,7 +594,7 @@ public:
* the HMD. * the HMD.
* @function MyAvatar.centerBody * @function MyAvatar.centerBody
*/ */
Q_INVOKABLE void centerBody(); // thread-safe Q_INVOKABLE void centerBody(const bool forceFollowYPos); // thread-safe
/**jsdoc /**jsdoc
@ -1417,7 +1435,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;
@ -1709,7 +1726,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 +1770,10 @@ 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 setAllowAvatarStandingPreference(const AllowAvatarStandingPreference preference);
MyAvatar::SitStandModelType getUserRecenterModel() const; AllowAvatarStandingPreference getAllowAvatarStandingPreference() const;
void setIsSitStandStateLocked(bool isLocked); void setAllowAvatarLeaningPreference(const AllowAvatarLeaningPreference preference);
bool getIsSitStandStateLocked() const; 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 +2006,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;
bool areHipsTracked() const;
public slots: public slots:
@ -2841,23 +2862,15 @@ 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, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const;
bool shouldActivateHorizontalCG(MyAvatar& myAvatar) const; bool shouldActivateHorizontalCG(MyAvatar& myAvatar) const;
@ -2880,7 +2893,7 @@ private:
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 +2935,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 +2969,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,7 +2984,12 @@ 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 _squatTimer { 0.0f };
float _tippingPoint { _userHeight.get() }; float _tippingPoint { _userHeight.get() };
@ -3012,7 +3032,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

View file

@ -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;

View file

@ -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;

View file

@ -65,13 +65,20 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) {
return result; return result;
} }
const bool useCenterOfGravityModel =
myAvatar->getCenterOfGravityModelEnabled() && !isFlying && !myAvatar->getIsInWalkingState() &&
!myAvatar->getIsInSittingState() && myAvatar->getHMDLeanRecenterEnabled() &&
(myAvatar->getAllowAvatarLeaningPreference() != MyAvatar::AllowAvatarLeaningPreference::AlwaysNoRecenter) &&
myAvatar->getHMDCrouchRecenterEnabled();
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 +89,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);
} }

View file

@ -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();

View file

@ -422,40 +422,40 @@ void setupPreferences() {
preferences->addPreference(preference); preferences->addPreference(preference);
} }
{ {
auto getter = [myAvatar]()->int { const 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) { const 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);
}
{
const IntPreference::Getter getter = [myAvatar]() -> int {
return static_cast<int>(myAvatar->getAllowAvatarLeaningPreference());
};
const 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);
} }

View file

@ -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.f, GEOMETRY_GROUND_Y, 0.f));
// 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.
const float scaleFactor = GetScaleFactorGeometryToUnscaledRig();
const 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);

View file

@ -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)

View file

@ -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;
@ -351,66 +351,108 @@ void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar
computeNewVelocity(dt, velocity); computeNewVelocity(dt, velocity);
const float MINIMUM_TIME_REMAINING = 0.005f; const 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, 0, 0);
{
linearDisplacement.setZero();
const float horizontalTime = _followTimeRemainingPerType[static_cast<uint>(FollowType::Horizontal)];
const 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) const 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));
} }
_followTime += dt; _followTime += dt;
if (_steppingUp) { if (_steppingUp) {
@ -606,8 +648,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));
} }

View file

@ -53,7 +53,21 @@ 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 +113,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 +159,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 +193,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;

View file

@ -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;