mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-07 14:22:23 +02:00
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:
parent
f1576aba78
commit
2179c153de
12 changed files with 691 additions and 441 deletions
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||
* the value.</p>
|
||||
* @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
|
||||
* (avatar leaning is disabled, recentering is enabled), <code>false</code> if the user wearing the HMD is
|
||||
* determined to be standing (avatar leaning is enabled, and avatar recenters if it leans too far).
|
||||
* If <code>userRecenterModel == 2</code> (i.e., "auto") the property value automatically updates as the user sits
|
||||
* 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 {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
|
||||
* is allowed to stand, lean or recenter its footing, depending on user preferences.
|
||||
* 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.
|
||||
* @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>
|
||||
*
|
||||
|
@ -413,8 +408,6 @@ class MyAvatar : public Avatar {
|
|||
Q_PROPERTY(float walkBackwardSpeed READ getWalkBackwardSpeed WRITE setWalkBackwardSpeed NOTIFY walkBackwardSpeedChanged);
|
||||
Q_PROPERTY(float sprintSpeed READ getSprintSpeed WRITE setSprintSpeed NOTIFY sprintSpeedChanged);
|
||||
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)
|
||||
|
||||
const QString DOMINANT_LEFT_HAND = "left";
|
||||
|
@ -549,6 +542,31 @@ public:
|
|||
};
|
||||
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);
|
||||
virtual ~MyAvatar();
|
||||
|
||||
|
@ -576,7 +594,7 @@ public:
|
|||
* the HMD.
|
||||
* @function MyAvatar.centerBody
|
||||
*/
|
||||
Q_INVOKABLE void centerBody(); // thread-safe
|
||||
Q_INVOKABLE void centerBody(const bool forceFollowYPos); // thread-safe
|
||||
|
||||
|
||||
/**jsdoc
|
||||
|
@ -1417,7 +1435,6 @@ public:
|
|||
controller::Pose getControllerPoseInSensorFrame(controller::Action action) const;
|
||||
controller::Pose getControllerPoseInWorldFrame(controller::Action action) const;
|
||||
controller::Pose getControllerPoseInAvatarFrame(controller::Action action) const;
|
||||
glm::quat getOffHandRotation() const;
|
||||
|
||||
bool hasDriveInput() const;
|
||||
|
||||
|
@ -1709,7 +1726,7 @@ public:
|
|||
|
||||
// derive avatar body position and orientation from the current HMD Sensor location.
|
||||
// results are in sensor frame (-z forward)
|
||||
glm::mat4 deriveBodyFromHMDSensor() const;
|
||||
glm::mat4 deriveBodyFromHMDSensor(const bool forceFollowYPos = false) const;
|
||||
|
||||
glm::mat4 getSpine2RotationRigSpace() const;
|
||||
|
||||
|
@ -1753,10 +1770,10 @@ public:
|
|||
bool getIsInWalkingState() const;
|
||||
void setIsInSittingState(bool isSitting);
|
||||
bool getIsInSittingState() const;
|
||||
void setUserRecenterModel(MyAvatar::SitStandModelType modelName);
|
||||
MyAvatar::SitStandModelType getUserRecenterModel() const;
|
||||
void setIsSitStandStateLocked(bool isLocked);
|
||||
bool getIsSitStandStateLocked() const;
|
||||
void setAllowAvatarStandingPreference(const AllowAvatarStandingPreference preference);
|
||||
AllowAvatarStandingPreference getAllowAvatarStandingPreference() const;
|
||||
void setAllowAvatarLeaningPreference(const AllowAvatarLeaningPreference preference);
|
||||
AllowAvatarLeaningPreference getAllowAvatarLeaningPreference() const;
|
||||
void setWalkSpeed(float value);
|
||||
float getWalkSpeed() const;
|
||||
void setWalkBackwardSpeed(float value);
|
||||
|
@ -1989,6 +2006,10 @@ public:
|
|||
glm::vec3 getLookAtPivotPoint();
|
||||
glm::vec3 getCameraEyesPosition(float deltaTime);
|
||||
bool isJumping();
|
||||
bool getHMDCrouchRecenterEnabled() const;
|
||||
bool isAllowedToLean() const;
|
||||
bool areFeetTracked() const;
|
||||
bool areHipsTracked() const;
|
||||
|
||||
public slots:
|
||||
|
||||
|
@ -2841,23 +2862,15 @@ private:
|
|||
struct FollowHelper {
|
||||
FollowHelper();
|
||||
|
||||
enum FollowType {
|
||||
Rotation = 0,
|
||||
Horizontal,
|
||||
Vertical,
|
||||
NumFollowTypes
|
||||
};
|
||||
float _timeRemaining[NumFollowTypes];
|
||||
CharacterController::FollowTimePerType _timeRemaining;
|
||||
|
||||
void deactivate();
|
||||
void deactivate(FollowType type);
|
||||
void activate();
|
||||
void activate(FollowType type);
|
||||
void deactivate(CharacterController::FollowType type);
|
||||
void activate(CharacterController::FollowType type, const bool snapFollow);
|
||||
bool isActive() const;
|
||||
bool isActive(FollowType followType) const;
|
||||
float getMaxTimeRemaining() const;
|
||||
bool isActive(CharacterController::FollowType followType) const;
|
||||
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 shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const;
|
||||
bool shouldActivateHorizontalCG(MyAvatar& myAvatar) const;
|
||||
|
@ -2880,7 +2893,7 @@ private:
|
|||
|
||||
FollowHelper _follow;
|
||||
|
||||
bool isFollowActive(FollowHelper::FollowType followType) const;
|
||||
bool isFollowActive(CharacterController::FollowType followType) const;
|
||||
|
||||
bool _goToPending { false };
|
||||
bool _physicsSafetyPending { false };
|
||||
|
@ -2922,6 +2935,9 @@ private:
|
|||
|
||||
bool _centerOfGravityModelEnabled { 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 };
|
||||
|
||||
AnimPose _prePhysicsRoomPose;
|
||||
|
@ -2953,7 +2969,6 @@ private:
|
|||
ThreadSafeValueCache<float> _userHeight { DEFAULT_AVATAR_HEIGHT };
|
||||
float _averageUserHeightSensorSpace { _userHeight.get() };
|
||||
bool _sitStandStateChange { false };
|
||||
ThreadSafeValueCache<bool> _lockSitStandState { false };
|
||||
|
||||
// max unscaled forward movement speed
|
||||
ThreadSafeValueCache<float> _defaultWalkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED };
|
||||
|
@ -2969,7 +2984,12 @@ private:
|
|||
float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR };
|
||||
bool _isInWalkingState { 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 _squatTimer { 0.0f };
|
||||
float _tippingPoint { _userHeight.get() };
|
||||
|
@ -3012,7 +3032,8 @@ private:
|
|||
Setting::Handle<int> _controlSchemeIndexSetting;
|
||||
std::vector<Setting::Handle<QUuid>> _avatarEntityIDSettings;
|
||||
std::vector<Setting::Handle<QByteArray>> _avatarEntityDataSettings;
|
||||
Setting::Handle<QString> _userRecenterModelSetting;
|
||||
Setting::Handle<QString> _allowAvatarStandingPreferenceSetting;
|
||||
Setting::Handle<QString> _allowAvatarLeaningPreferenceSetting;
|
||||
|
||||
// AvatarEntities stuff:
|
||||
// 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;
|
||||
}
|
||||
|
||||
MyCharacterController::MyCharacterController(std::shared_ptr<MyAvatar> avatar) {
|
||||
MyCharacterController::MyCharacterController(std::shared_ptr<MyAvatar> avatar,
|
||||
const FollowTimePerType& followTimeRemainingPerType) :
|
||||
CharacterController(followTimeRemainingPerType) {
|
||||
|
||||
assert(avatar);
|
||||
_avatar = avatar;
|
||||
|
|
|
@ -23,7 +23,7 @@ class DetailedMotionState;
|
|||
|
||||
class MyCharacterController : public CharacterController {
|
||||
public:
|
||||
explicit MyCharacterController(std::shared_ptr<MyAvatar> avatar);
|
||||
explicit MyCharacterController(std::shared_ptr<MyAvatar> avatar, const FollowTimePerType& followTimeRemainingPerType);
|
||||
~MyCharacterController ();
|
||||
|
||||
void addToWorld() override;
|
||||
|
|
|
@ -65,13 +65,20 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) {
|
|||
return result;
|
||||
}
|
||||
|
||||
const bool useCenterOfGravityModel =
|
||||
myAvatar->getCenterOfGravityModelEnabled() && !isFlying && !myAvatar->getIsInWalkingState() &&
|
||||
!myAvatar->getIsInSittingState() && myAvatar->getHMDLeanRecenterEnabled() &&
|
||||
(myAvatar->getAllowAvatarLeaningPreference() != MyAvatar::AllowAvatarLeaningPreference::AlwaysNoRecenter) &&
|
||||
myAvatar->getHMDCrouchRecenterEnabled();
|
||||
|
||||
glm::mat4 hipsMat;
|
||||
if (myAvatar->getCenterOfGravityModelEnabled() && !isFlying && !(myAvatar->getIsInWalkingState()) && !(myAvatar->getIsInSittingState()) && myAvatar->getHMDLeanRecenterEnabled()) {
|
||||
if (useCenterOfGravityModel) {
|
||||
// then we use center of gravity model
|
||||
hipsMat = myAvatar->deriveBodyUsingCgModel();
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// otherwise use the default of putting the hips under the head
|
||||
hipsMat = myAvatar->deriveBodyFromHMDSensor();
|
||||
hipsMat = myAvatar->deriveBodyFromHMDSensor(true);
|
||||
}
|
||||
glm::vec3 hipsPos = extractTranslation(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
|
||||
// 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;
|
||||
hipsRot = safeLerp(glmExtractRotation(avatarToSensorMat), hipsRot, MIX_RATIO);
|
||||
}
|
||||
|
|
|
@ -67,13 +67,13 @@ void AnimStats::updateStats(bool force) {
|
|||
|
||||
// print if we are recentering or not.
|
||||
_recenterText = "Recenter: ";
|
||||
if (myAvatar->isFollowActive(MyAvatar::FollowHelper::Rotation)) {
|
||||
if (myAvatar->isFollowActive(CharacterController::FollowType::Rotation)) {
|
||||
_recenterText += "Rotation ";
|
||||
}
|
||||
if (myAvatar->isFollowActive(MyAvatar::FollowHelper::Horizontal)) {
|
||||
if (myAvatar->isFollowActive(CharacterController::FollowType::Horizontal)) {
|
||||
_recenterText += "Horizontal ";
|
||||
}
|
||||
if (myAvatar->isFollowActive(MyAvatar::FollowHelper::Vertical)) {
|
||||
if (myAvatar->isFollowActive(CharacterController::FollowType::Vertical)) {
|
||||
_recenterText += "Vertical ";
|
||||
}
|
||||
emit recenterTextChanged();
|
||||
|
|
|
@ -422,40 +422,40 @@ void setupPreferences() {
|
|||
preferences->addPreference(preference);
|
||||
}
|
||||
{
|
||||
auto getter = [myAvatar]()->int {
|
||||
switch (myAvatar->getUserRecenterModel()) {
|
||||
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;
|
||||
}
|
||||
const IntPreference::Getter getter = [myAvatar]() -> int {
|
||||
return static_cast<int>(myAvatar->getAllowAvatarStandingPreference());
|
||||
};
|
||||
auto setter = [myAvatar](int value) {
|
||||
switch (value) {
|
||||
case 0:
|
||||
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;
|
||||
}
|
||||
|
||||
const IntPreference::Setter setter = [myAvatar](const int& value) {
|
||||
myAvatar->setAllowAvatarStandingPreference(static_cast<MyAvatar::AllowAvatarStandingPreference>(value));
|
||||
};
|
||||
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;
|
||||
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]";
|
||||
preference->setHeading("Avatar leaning behavior");
|
||||
items << "When I'm standing"
|
||||
<< "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);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
|
|
|
@ -1855,6 +1855,16 @@ glm::vec3 Rig::deflectHandFromTorso(const glm::vec3& handPosition, const HFMJoin
|
|||
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,
|
||||
bool leftArmEnabled, bool rightArmEnabled, bool headEnabled, float dt,
|
||||
const AnimPose& leftHandPose, const AnimPose& rightHandPose,
|
||||
|
@ -2703,10 +2713,10 @@ void Rig::computeAvatarBoundingCapsule(
|
|||
Extents totalExtents;
|
||||
totalExtents.reset();
|
||||
|
||||
// HACK by convention our Avatars are always modeled such that y=0 is the ground plane.
|
||||
// add the zero point so that our avatars will always have bounding volumes that are flush with the ground
|
||||
// HACK by convention our Avatars are always modeled such that y=0 (GEOMETRY_GROUND_Y) is the ground plane.
|
||||
// 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)
|
||||
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
|
||||
// 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 {
|
||||
// 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();
|
||||
|
||||
// This factor can be used 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 = geomToRigWithoutAvatarScale.scale().x; // in practice this always a uniform scale factor.
|
||||
// Factor to scale distances in the geometry frame into the unscaled rig frame.
|
||||
float scaleFactor = GetScaleFactorGeometryToUnscaledRig();
|
||||
|
||||
int headTopJoint = indexOfJoint("HeadTop_End");
|
||||
int headJoint = indexOfJoint("Head");
|
||||
int eyeJoint = indexOfJoint("LeftEye") != -1 ? indexOfJoint("LeftEye") : indexOfJoint("RightEye");
|
||||
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.
|
||||
auto skeleton = getAnimSkeleton();
|
||||
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;
|
||||
return scaleFactor * eyeHeight;
|
||||
} else if (eyeJoint >= 0) {
|
||||
// Measure Eye joint to y = 0 plane.
|
||||
float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - GROUND_Y;
|
||||
// Measure Eye joint to ground plane.
|
||||
float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - GEOMETRY_GROUND_Y;
|
||||
return scaleFactor * eyeHeight;
|
||||
} else if (headTopJoint >= 0 && toeJoint >= 0) {
|
||||
// Measure from ToeBase joint to HeadTop_End joint, then remove forehead distance.
|
||||
|
@ -2783,19 +2789,36 @@ float Rig::getUnscaledEyeHeight() const {
|
|||
} else if (headTopJoint >= 0) {
|
||||
// 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;
|
||||
float headHeight = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - GROUND_Y;
|
||||
float headHeight = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - GEOMETRY_GROUND_Y;
|
||||
return scaleFactor * (headHeight - headHeight * ratio);
|
||||
} else if (headJoint >= 0) {
|
||||
// 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 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);
|
||||
} else {
|
||||
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) {
|
||||
_animVars.set(targetName, blendingTarget);
|
||||
_animVars.set(alphaName, alpha);
|
||||
|
|
|
@ -251,6 +251,7 @@ public:
|
|||
Flow& getFlow() { return _internalFlow; }
|
||||
|
||||
float getUnscaledEyeHeight() const;
|
||||
float getUnscaledHipsHeight() const;
|
||||
void buildAbsoluteRigPoses(const AnimPoseVec& relativePoses, AnimPoseVec& absolutePosesOut) const;
|
||||
|
||||
int getOverrideJointCount() const;
|
||||
|
@ -287,6 +288,11 @@ protected:
|
|||
glm::vec3 deflectHandFromTorso(const glm::vec3& handPosition, const HFMJointShapeInfo& hipsShapeInfo, const HFMJointShapeInfo& spineShapeInfo,
|
||||
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 _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 };
|
||||
|
||||
CharacterController::CharacterController() {
|
||||
CharacterController::CharacterController(const FollowTimePerType& followTimeRemainingPerType) :
|
||||
_followTimeRemainingPerType(followTimeRemainingPerType) {
|
||||
_floorDistance = _scaleFactor * DEFAULT_AVATAR_FALL_HEIGHT;
|
||||
|
||||
_targetVelocity.setValue(0.0f, 0.0f, 0.0f);
|
||||
_followDesiredBodyTransform.setIdentity();
|
||||
_followTimeRemaining = 0.0f;
|
||||
_state = State::Hover;
|
||||
_isPushingUp = false;
|
||||
_rayHitStartTime = 0;
|
||||
|
@ -351,66 +351,108 @@ void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar
|
|||
computeNewVelocity(dt, velocity);
|
||||
|
||||
const float MINIMUM_TIME_REMAINING = 0.005f;
|
||||
const float MAX_DISPLACEMENT = 0.5f * _radius;
|
||||
_followTimeRemaining -= dt;
|
||||
if (_followTimeRemaining >= MINIMUM_TIME_REMAINING) {
|
||||
btTransform bodyTransform = _rigidBody->getWorldTransform();
|
||||
static_assert(FOLLOW_TIME_IMMEDIATE_SNAP > MINIMUM_TIME_REMAINING, "The code below assumes this condition is true.");
|
||||
|
||||
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 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;
|
||||
|
||||
// resolve the simple linearDisplacement
|
||||
_followLinearDisplacement += linearDisplacement;
|
||||
|
||||
// now for the rotational part...
|
||||
|
||||
btQuaternion startRot = bodyTransform.getRotation();
|
||||
btQuaternion desiredRot = _followDesiredBodyTransform.getRotation();
|
||||
|
||||
// startRot as default rotation
|
||||
btQuaternion endRot = startRot;
|
||||
|
||||
// the dot product between two quaternions is equal to +/- cos(angle/2)
|
||||
// where 'angle' is that of the rotation between them
|
||||
float qDot = desiredRot.dot(startRot);
|
||||
const float rotationTime = _followTimeRemainingPerType[static_cast<uint>(FollowType::Rotation)];
|
||||
if (rotationTime > MINIMUM_TIME_REMAINING) {
|
||||
btQuaternion desiredRot = _followDesiredBodyTransform.getRotation();
|
||||
|
||||
// when the abs() value of the dot product is approximately 1.0
|
||||
// then the two rotations are effectively adjacent
|
||||
const float MIN_DOT_PRODUCT_OF_ADJACENT_QUATERNIONS = 0.99999f; // corresponds to approx 0.5 degrees
|
||||
if (fabsf(qDot) < MIN_DOT_PRODUCT_OF_ADJACENT_QUATERNIONS) {
|
||||
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;
|
||||
// the dot product between two quaternions is equal to +/- cos(angle/2)
|
||||
// where 'angle' is that of the rotation between them
|
||||
float qDot = desiredRot.dot(startRot);
|
||||
|
||||
// when the abs() value of the dot product is approximately 1.0
|
||||
// then the two rotations are effectively adjacent
|
||||
const float MIN_DOT_PRODUCT_OF_ADJACENT_QUATERNIONS = 0.99999f; // corresponds to approx 0.5 degrees
|
||||
if (fabsf(qDot) < MIN_DOT_PRODUCT_OF_ADJACENT_QUATERNIONS) {
|
||||
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));
|
||||
}
|
||||
|
||||
_followTime += dt;
|
||||
|
||||
if (_steppingUp) {
|
||||
|
@ -606,8 +648,7 @@ void CharacterController::setParentVelocity(const glm::vec3& velocity) {
|
|||
_parentVelocity = glmToBullet(velocity);
|
||||
}
|
||||
|
||||
void CharacterController::setFollowParameters(const glm::mat4& desiredWorldBodyMatrix, float timeRemaining) {
|
||||
_followTimeRemaining = timeRemaining;
|
||||
void CharacterController::setFollowParameters(const glm::mat4& desiredWorldBodyMatrix) {
|
||||
_followDesiredBodyTransform = glmToBullet(desiredWorldBodyMatrix) * btTransform(btQuaternion::getIdentity(), glmToBullet(_shapeLocalOffset));
|
||||
}
|
||||
|
||||
|
|
|
@ -53,7 +53,21 @@ const btScalar MIN_CHARACTER_MOTOR_TIMESCALE = 0.05f;
|
|||
class CharacterController : public btCharacterControllerInterface {
|
||||
|
||||
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();
|
||||
bool needsRemoval() const;
|
||||
bool needsAddition() const;
|
||||
|
@ -99,7 +113,8 @@ public:
|
|||
void getPositionAndOrientation(glm::vec3& position, glm::quat& rotation) const;
|
||||
|
||||
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; }
|
||||
glm::vec3 getFollowLinearDisplacement() const;
|
||||
glm::quat getFollowAngularDisplacement() const;
|
||||
|
@ -144,7 +159,7 @@ public:
|
|||
|
||||
void setPendingFlagsUpdateCollisionMask(){ _pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_MASK; }
|
||||
void setSeated(bool isSeated) { _isSeated = isSeated; }
|
||||
bool getSeated() { return _isSeated; }
|
||||
bool getSeated() const { return _isSeated; }
|
||||
|
||||
void resetStuckCounter() { _numStuckSubsteps = 0; }
|
||||
|
||||
|
@ -178,7 +193,7 @@ protected:
|
|||
btVector3 _preSimulationVelocity;
|
||||
btVector3 _velocityChange;
|
||||
btTransform _followDesiredBodyTransform;
|
||||
btScalar _followTimeRemaining;
|
||||
const FollowTimePerType& _followTimeRemainingPerType;
|
||||
btTransform _characterBodyTransform;
|
||||
btVector3 _position;
|
||||
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_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_HIPS_HEIGHT = 1.01327407f; // meters
|
||||
const float DEFAULT_SPINE2_SPLINE_PROPORTION = 0.71f;
|
||||
const float DEFAULT_AVATAR_SUPPORT_BASE_LEFT = -0.25f;
|
||||
const float DEFAULT_AVATAR_SUPPORT_BASE_RIGHT = 0.25f;
|
||||
|
|
Loading…
Reference in a new issue