mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-25 17:14:59 +02:00
Merge pull request #7017 from hyperlogic/tony/fly
MyAvatar: Improved Jump / InAir / Fly behavior
This commit is contained in:
commit
861082964e
9 changed files with 403 additions and 144 deletions
|
@ -247,7 +247,9 @@
|
|||
{ "var": "isTurningRight", "state": "turnRight" },
|
||||
{ "var": "isTurningLeft", "state": "turnLeft" },
|
||||
{ "var": "isAway", "state": "awayIntro" },
|
||||
{ "var": "isFlying", "state": "fly" }
|
||||
{ "var": "isFlying", "state": "fly" },
|
||||
{ "var": "isTakeoff", "state": "takeoff" },
|
||||
{ "var": "isInAir", "state": "inAir" }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -263,7 +265,9 @@
|
|||
{ "var": "isTurningRight", "state": "turnRight" },
|
||||
{ "var": "isTurningLeft", "state": "turnLeft" },
|
||||
{ "var": "isAway", "state": "awayIntro" },
|
||||
{ "var": "isFlying", "state": "fly" }
|
||||
{ "var": "isFlying", "state": "fly" },
|
||||
{ "var": "isTakeoff", "state": "takeoff" },
|
||||
{ "var": "isInAir", "state": "inAir" }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -278,7 +282,9 @@
|
|||
{ "var": "isTurningRight", "state": "turnRight" },
|
||||
{ "var": "isTurningLeft", "state": "turnLeft" },
|
||||
{ "var": "isAway", "state": "awayIntro" },
|
||||
{ "var": "isFlying", "state": "fly" }
|
||||
{ "var": "isFlying", "state": "fly" },
|
||||
{ "var": "isTakeoff", "state": "takeoff" },
|
||||
{ "var": "isInAir", "state": "inAir" }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -293,7 +299,9 @@
|
|||
{ "var": "isTurningRight", "state": "turnRight" },
|
||||
{ "var": "isTurningLeft", "state": "turnLeft" },
|
||||
{ "var": "isAway", "state": "awayIntro" },
|
||||
{ "var": "isFlying", "state": "fly" }
|
||||
{ "var": "isFlying", "state": "fly" },
|
||||
{ "var": "isTakeoff", "state": "takeoff" },
|
||||
{ "var": "isInAir", "state": "inAir" }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -308,7 +316,9 @@
|
|||
{ "var": "isTurningRight", "state": "turnRight" },
|
||||
{ "var": "isTurningLeft", "state": "turnLeft" },
|
||||
{ "var": "isAway", "state": "awayIntro" },
|
||||
{ "var": "isFlying", "state": "fly" }
|
||||
{ "var": "isFlying", "state": "fly" },
|
||||
{ "var": "isTakeoff", "state": "takeoff" },
|
||||
{ "var": "isInAir", "state": "inAir" }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -323,7 +333,9 @@
|
|||
{ "var": "isTurningRight", "state": "turnRight" },
|
||||
{ "var": "isTurningLeft", "state": "turnLeft" },
|
||||
{ "var": "isAway", "state": "awayIntro" },
|
||||
{ "var": "isFlying", "state": "fly" }
|
||||
{ "var": "isFlying", "state": "fly" },
|
||||
{ "var": "isTakeoff", "state": "takeoff" },
|
||||
{ "var": "isInAir", "state": "inAir" }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -338,7 +350,9 @@
|
|||
{ "var": "isMovingLeft", "state": "strafeLeft" },
|
||||
{ "var": "isTurningLeft", "state": "turnLeft" },
|
||||
{ "var": "isAway", "state": "awayIntro" },
|
||||
{ "var": "isFlying", "state": "fly" }
|
||||
{ "var": "isFlying", "state": "fly" },
|
||||
{ "var": "isTakeoff", "state": "takeoff" },
|
||||
{ "var": "isInAir", "state": "inAir" }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -353,7 +367,9 @@
|
|||
{ "var": "isMovingLeft", "state": "strafeLeft" },
|
||||
{ "var": "isTurningRight", "state": "turnRight" },
|
||||
{ "var": "isAway", "state": "awayIntro" },
|
||||
{ "var": "isFlying", "state": "fly" }
|
||||
{ "var": "isFlying", "state": "fly" },
|
||||
{ "var": "isTakeoff", "state": "takeoff" },
|
||||
{ "var": "isInAir", "state": "inAir" }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -361,6 +377,7 @@
|
|||
"interpTarget": 30,
|
||||
"interpDuration": 30,
|
||||
"transitions": [
|
||||
{ "var": "isNotAway", "state": "awayOutro" },
|
||||
{ "var": "awayIntroOnDone", "state": "away"}
|
||||
]
|
||||
},
|
||||
|
@ -385,8 +402,27 @@
|
|||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "isAway", "state": "awayIntro" },
|
||||
{ "var": "isNotFlying", "state": "idle" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "takeoff",
|
||||
"interpTarget": 0,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "isAway", "state": "awayIntro" },
|
||||
{ "var": "isNotTakeoff", "state": "inAir" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "inAir",
|
||||
"interpTarget": 0,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "isAway", "state": "awayIntro" },
|
||||
{ "var": "isNotInAir", "state": "idle" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -685,6 +721,64 @@
|
|||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "takeoff",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "https://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/jump_takeoff.fbx",
|
||||
"startFrame": 1.0,
|
||||
"endFrame": 2.5,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": false
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "inAir",
|
||||
"type": "blendLinear",
|
||||
"data": {
|
||||
"alpha": 0.0,
|
||||
"alphaVar": "inAirAlpha"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "inAirPreApex",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "https://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/jump_in_air.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 0.0,
|
||||
"timeScale": 0.0,
|
||||
"loopFlag": false
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "inAirApex",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "https://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/jump_in_air.fbx",
|
||||
"startFrame": 6.0,
|
||||
"endFrame": 6.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": false
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "inAirPostApex",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "https://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/jump_in_air.fbx",
|
||||
"startFrame": 11.0,
|
||||
"endFrame": 11.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": false
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1311,21 +1311,22 @@ void MyAvatar::preRender(RenderArgs* renderArgs) {
|
|||
_prevShouldDrawHead = shouldDrawHead;
|
||||
}
|
||||
|
||||
const float RENDER_HEAD_CUTOFF_DISTANCE = 0.50f;
|
||||
const float RENDER_HEAD_CUTOFF_DISTANCE = 0.5f;
|
||||
|
||||
bool MyAvatar::cameraInsideHead() const {
|
||||
const Head* head = getHead();
|
||||
const glm::vec3 cameraPosition = qApp->getCamera()->getPosition();
|
||||
return glm::length(cameraPosition - head->getEyePosition()) < (RENDER_HEAD_CUTOFF_DISTANCE * getUniformScale());
|
||||
return glm::length(cameraPosition - getDefaultEyePosition()) < (RENDER_HEAD_CUTOFF_DISTANCE * getUniformScale());
|
||||
}
|
||||
|
||||
bool MyAvatar::shouldRenderHead(const RenderArgs* renderArgs) const {
|
||||
return ((renderArgs->_renderMode != RenderArgs::DEFAULT_RENDER_MODE) ||
|
||||
(qApp->getCamera()->getMode() != CAMERA_MODE_FIRST_PERSON) ||
|
||||
!cameraInsideHead());
|
||||
bool defaultMode = renderArgs->_renderMode == RenderArgs::DEFAULT_RENDER_MODE;
|
||||
bool firstPerson = qApp->getCamera()->getMode() == CAMERA_MODE_FIRST_PERSON;
|
||||
bool insideHead = cameraInsideHead();
|
||||
return !defaultMode || !firstPerson || !insideHead;
|
||||
}
|
||||
|
||||
void MyAvatar::updateOrientation(float deltaTime) {
|
||||
|
||||
// Smoothly rotate body with arrow keys
|
||||
float targetSpeed = _driveKeys[YAW] * _yawSpeed;
|
||||
if (targetSpeed != 0.0f) {
|
||||
|
@ -1510,7 +1511,8 @@ void MyAvatar::updatePosition(float deltaTime) {
|
|||
// rotate velocity into camera frame
|
||||
glm::quat rotation = getHead()->getCameraOrientation();
|
||||
glm::vec3 localVelocity = glm::inverse(rotation) * _targetVelocity;
|
||||
glm::vec3 newLocalVelocity = applyKeyboardMotor(deltaTime, localVelocity, isHovering());
|
||||
bool isHovering = _characterController.getState() == CharacterController::State::Hover;
|
||||
glm::vec3 newLocalVelocity = applyKeyboardMotor(deltaTime, localVelocity, isHovering);
|
||||
newLocalVelocity = applyScriptedMotor(deltaTime, newLocalVelocity);
|
||||
|
||||
// rotate back into world-frame
|
||||
|
@ -1579,10 +1581,6 @@ bool findAvatarAvatarPenetration(const glm::vec3 positionA, float radiusA, float
|
|||
return false;
|
||||
}
|
||||
|
||||
bool MyAvatar::isHovering() const {
|
||||
return _characterController.isHovering();
|
||||
}
|
||||
|
||||
void MyAvatar::increaseSize() {
|
||||
if ((1.0f + SCALING_RATIO) * _targetScale < MAX_AVATAR_SCALE) {
|
||||
_targetScale *= (1.0f + SCALING_RATIO);
|
||||
|
|
|
@ -239,8 +239,6 @@ public:
|
|||
glm::quat getCustomListenOrientation() { return _customListenOrientation; }
|
||||
void setCustomListenOrientation(glm::quat customListenOrientation) { _customListenOrientation = customListenOrientation; }
|
||||
|
||||
bool isHovering() const;
|
||||
|
||||
public slots:
|
||||
void increaseSize();
|
||||
void decreaseSize();
|
||||
|
|
|
@ -67,7 +67,7 @@ void MyCharacterController::updateShapeIfNecessary() {
|
|||
_rigidBody->setAngularFactor(0.0f);
|
||||
_rigidBody->setWorldTransform(btTransform(glmToBullet(_avatar->getOrientation()),
|
||||
glmToBullet(_avatar->getPosition())));
|
||||
if (_isHovering) {
|
||||
if (_state == State::Hover) {
|
||||
_rigidBody->setGravity(btVector3(0.0f, 0.0f, 0.0f));
|
||||
} else {
|
||||
_rigidBody->setGravity(DEFAULT_CHARACTER_GRAVITY * _currentUp);
|
||||
|
|
|
@ -72,6 +72,20 @@ void SkeletonModel::initJointStates() {
|
|||
emit skeletonLoaded();
|
||||
}
|
||||
|
||||
Rig::CharacterControllerState convertCharacterControllerState(CharacterController::State state) {
|
||||
switch (state) {
|
||||
default:
|
||||
case CharacterController::State::Ground:
|
||||
return Rig::CharacterControllerState::Ground;
|
||||
case CharacterController::State::Takeoff:
|
||||
return Rig::CharacterControllerState::Takeoff;
|
||||
case CharacterController::State::InAir:
|
||||
return Rig::CharacterControllerState::InAir;
|
||||
case CharacterController::State::Hover:
|
||||
return Rig::CharacterControllerState::Hover;
|
||||
};
|
||||
}
|
||||
|
||||
// Called within Model::simulate call, below.
|
||||
void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
||||
Head* head = _owningAvatar->getHead();
|
||||
|
@ -133,7 +147,8 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
|
||||
_rig->updateFromHandParameters(handParams, deltaTime);
|
||||
|
||||
_rig->computeMotionAnimationState(deltaTime, _owningAvatar->getPosition(), _owningAvatar->getVelocity(), _owningAvatar->getOrientation(), myAvatar->isHovering());
|
||||
Rig::CharacterControllerState ccState = convertCharacterControllerState(myAvatar->getCharacterController()->getState());
|
||||
_rig->computeMotionAnimationState(deltaTime, _owningAvatar->getPosition(), _owningAvatar->getVelocity(), _owningAvatar->getOrientation(), ccState);
|
||||
|
||||
// evaluate AnimGraph animation and update jointStates.
|
||||
Model::updateRig(deltaTime, parentTransform);
|
||||
|
|
|
@ -504,7 +504,7 @@ static const std::vector<float> FORWARD_SPEEDS = { 0.4f, 1.4f, 4.5f }; // m/s
|
|||
static const std::vector<float> BACKWARD_SPEEDS = { 0.6f, 1.45f }; // m/s
|
||||
static const std::vector<float> LATERAL_SPEEDS = { 0.2f, 0.65f }; // m/s
|
||||
|
||||
void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation, bool isHovering) {
|
||||
void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation, CharacterControllerState ccState) {
|
||||
|
||||
glm::vec3 front = worldRotation * IDENTITY_FRONT;
|
||||
|
||||
|
@ -572,11 +572,21 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos
|
|||
const float TURN_ENTER_SPEED_THRESHOLD = 0.5f; // rad/sec
|
||||
const float TURN_EXIT_SPEED_THRESHOLD = 0.2f; // rad/sec
|
||||
|
||||
if (isHovering) {
|
||||
if (ccState == CharacterControllerState::Hover) {
|
||||
if (_desiredState != RigRole::Hover) {
|
||||
_desiredStateAge = 0.0f;
|
||||
}
|
||||
_desiredState = RigRole::Hover;
|
||||
} else if (ccState == CharacterControllerState::InAir) {
|
||||
if (_desiredState != RigRole::InAir) {
|
||||
_desiredStateAge = 0.0f;
|
||||
}
|
||||
_desiredState = RigRole::InAir;
|
||||
} else if (ccState == CharacterControllerState::Takeoff) {
|
||||
if (_desiredState != RigRole::Takeoff) {
|
||||
_desiredStateAge = 0.0f;
|
||||
}
|
||||
_desiredState = RigRole::Takeoff;
|
||||
} else {
|
||||
float moveThresh;
|
||||
if (_state != RigRole::Move) {
|
||||
|
@ -614,6 +624,13 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos
|
|||
|
||||
const float STATE_CHANGE_HYSTERESIS_TIMER = 0.1f;
|
||||
|
||||
// Skip hystersis timer for jump transitions.
|
||||
if (_desiredState == RigRole::Takeoff) {
|
||||
_desiredStateAge = STATE_CHANGE_HYSTERESIS_TIMER;
|
||||
} else if (_state == RigRole::InAir && _desiredState != RigRole::InAir) {
|
||||
_desiredStateAge = STATE_CHANGE_HYSTERESIS_TIMER;
|
||||
}
|
||||
|
||||
if ((_desiredStateAge >= STATE_CHANGE_HYSTERESIS_TIMER) && _desiredState != _state) {
|
||||
_state = _desiredState;
|
||||
_desiredStateAge = 0.0f;
|
||||
|
@ -662,6 +679,10 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos
|
|||
_animVars.set("isNotTurning", true);
|
||||
_animVars.set("isFlying", false);
|
||||
_animVars.set("isNotFlying", true);
|
||||
_animVars.set("isTakeoff", false);
|
||||
_animVars.set("isNotTakeoff", true);
|
||||
_animVars.set("isInAir", false);
|
||||
_animVars.set("isNotInAir", true);
|
||||
}
|
||||
} else if (_state == RigRole::Turn) {
|
||||
if (turningSpeed > 0.0f) {
|
||||
|
@ -682,6 +703,11 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos
|
|||
_animVars.set("isNotMoving", true);
|
||||
_animVars.set("isFlying", false);
|
||||
_animVars.set("isNotFlying", true);
|
||||
_animVars.set("isTakeoff", false);
|
||||
_animVars.set("isNotTakeoff", true);
|
||||
_animVars.set("isInAir", false);
|
||||
_animVars.set("isNotInAir", true);
|
||||
|
||||
} else if (_state == RigRole::Idle ) {
|
||||
// default anim vars to notMoving and notTurning
|
||||
_animVars.set("isMovingForward", false);
|
||||
|
@ -694,7 +720,12 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos
|
|||
_animVars.set("isNotTurning", true);
|
||||
_animVars.set("isFlying", false);
|
||||
_animVars.set("isNotFlying", true);
|
||||
} else {
|
||||
_animVars.set("isTakeoff", false);
|
||||
_animVars.set("isNotTakeoff", true);
|
||||
_animVars.set("isInAir", false);
|
||||
_animVars.set("isNotInAir", true);
|
||||
|
||||
} else if (_state == RigRole::Hover) {
|
||||
// flying.
|
||||
_animVars.set("isMovingForward", false);
|
||||
_animVars.set("isMovingBackward", false);
|
||||
|
@ -706,15 +737,61 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos
|
|||
_animVars.set("isNotTurning", true);
|
||||
_animVars.set("isFlying", true);
|
||||
_animVars.set("isNotFlying", false);
|
||||
_animVars.set("isTakeoff", false);
|
||||
_animVars.set("isNotTakeoff", true);
|
||||
_animVars.set("isInAir", false);
|
||||
_animVars.set("isNotInAir", true);
|
||||
|
||||
} else if (_state == RigRole::Takeoff) {
|
||||
// jumping in-air
|
||||
_animVars.set("isMovingForward", false);
|
||||
_animVars.set("isMovingBackward", false);
|
||||
_animVars.set("isMovingLeft", false);
|
||||
_animVars.set("isMovingRight", false);
|
||||
_animVars.set("isNotMoving", true);
|
||||
_animVars.set("isTurningLeft", false);
|
||||
_animVars.set("isTurningRight", false);
|
||||
_animVars.set("isNotTurning", true);
|
||||
_animVars.set("isFlying", false);
|
||||
_animVars.set("isNotFlying", true);
|
||||
_animVars.set("isTakeoff", true);
|
||||
_animVars.set("isNotTakeoff", false);
|
||||
_animVars.set("isInAir", true);
|
||||
_animVars.set("isNotInAir", false);
|
||||
|
||||
} else if (_state == RigRole::InAir) {
|
||||
// jumping in-air
|
||||
_animVars.set("isMovingForward", false);
|
||||
_animVars.set("isMovingBackward", false);
|
||||
_animVars.set("isMovingLeft", false);
|
||||
_animVars.set("isMovingRight", false);
|
||||
_animVars.set("isNotMoving", true);
|
||||
_animVars.set("isTurningLeft", false);
|
||||
_animVars.set("isTurningRight", false);
|
||||
_animVars.set("isNotTurning", true);
|
||||
_animVars.set("isFlying", false);
|
||||
_animVars.set("isNotFlying", true);
|
||||
_animVars.set("isTakeoff", false);
|
||||
_animVars.set("isNotTakeoff", true);
|
||||
_animVars.set("isInAir", true);
|
||||
_animVars.set("isNotInAir", false);
|
||||
|
||||
// compute blend based on velocity
|
||||
const float JUMP_SPEED = 3.5f;
|
||||
float alpha = glm::clamp(-worldVelocity.y / JUMP_SPEED, -1.0f, 1.0f) + 1.0f;
|
||||
_animVars.set("inAirAlpha", alpha);
|
||||
}
|
||||
|
||||
t += deltaTime;
|
||||
|
||||
if (_enableInverseKinematics) {
|
||||
_animVars.set("ikOverlayAlpha", 1.0f);
|
||||
} else {
|
||||
_animVars.set("ikOverlayAlpha", 0.0f);
|
||||
if (_enableInverseKinematics != _lastEnableInverseKinematics) {
|
||||
if (_enableInverseKinematics) {
|
||||
_animVars.set("ikOverlayAlpha", 1.0f);
|
||||
} else {
|
||||
_animVars.set("ikOverlayAlpha", 0.0f);
|
||||
}
|
||||
}
|
||||
_lastEnableInverseKinematics = _enableInverseKinematics;
|
||||
}
|
||||
|
||||
_lastFront = front;
|
||||
|
|
|
@ -73,6 +73,13 @@ public:
|
|||
glm::quat rightOrientation = glm::quat(); // rig space (z forward)
|
||||
};
|
||||
|
||||
enum class CharacterControllerState {
|
||||
Ground = 0,
|
||||
Takeoff,
|
||||
InAir,
|
||||
Hover
|
||||
};
|
||||
|
||||
virtual ~Rig() {}
|
||||
|
||||
void destroyAnimGraph();
|
||||
|
@ -141,7 +148,7 @@ public:
|
|||
glm::mat4 getJointTransform(int jointIndex) const;
|
||||
|
||||
// Start or stop animations as needed.
|
||||
void computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation, bool isHovering);
|
||||
void computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation, CharacterControllerState ccState);
|
||||
|
||||
// Regardless of who started the animations or how many, update the joints.
|
||||
void updateAnimations(float deltaTime, glm::mat4 rootTransform);
|
||||
|
@ -271,7 +278,9 @@ public:
|
|||
Idle = 0,
|
||||
Turn,
|
||||
Move,
|
||||
Hover
|
||||
Hover,
|
||||
Takeoff,
|
||||
InAir
|
||||
};
|
||||
RigRole _state { RigRole::Idle };
|
||||
RigRole _desiredState { RigRole::Idle };
|
||||
|
@ -292,6 +301,7 @@ public:
|
|||
std::map<QString, AnimNode::Pointer> _origRoleAnimations;
|
||||
std::vector<AnimNode::Pointer> _prefetchedAnimations;
|
||||
|
||||
bool _lastEnableInverseKinematics { false };
|
||||
bool _enableInverseKinematics { true };
|
||||
|
||||
private:
|
||||
|
|
|
@ -15,11 +15,18 @@
|
|||
|
||||
#include "PhysicsCollisionGroups.h"
|
||||
#include "ObjectMotionState.h"
|
||||
#include "PhysicsLogging.h"
|
||||
|
||||
const btVector3 LOCAL_UP_AXIS(0.0f, 1.0f, 0.0f);
|
||||
const float JUMP_SPEED = 3.5f;
|
||||
const float MAX_FALL_HEIGHT = 20.0f;
|
||||
|
||||
#ifdef DEBUG_STATE_CHANGE
|
||||
#define SET_STATE(desiredState, reason) setState(desiredState, reason)
|
||||
#else
|
||||
#define SET_STATE(desiredState, reason) setState(desiredState)
|
||||
#endif
|
||||
|
||||
// helper class for simple ray-traces from character
|
||||
class ClosestNotMe : public btCollisionWorld::ClosestRayResultCallback {
|
||||
public:
|
||||
|
@ -51,19 +58,17 @@ CharacterController::CharacterController() {
|
|||
_followDesiredBodyTransform.setIdentity();
|
||||
_followTimeRemaining = 0.0f;
|
||||
_jumpSpeed = JUMP_SPEED;
|
||||
_isOnGround = false;
|
||||
_isJumping = false;
|
||||
_isFalling = false;
|
||||
_isHovering = true;
|
||||
_state = State::Hover;
|
||||
_isPushingUp = false;
|
||||
_jumpToHoverStart = 0;
|
||||
_jumpButtonDownStart = 0;
|
||||
_jumpButtonDownCount = 0;
|
||||
_takeoffToInAirStart = 0;
|
||||
_followTime = 0.0f;
|
||||
_followLinearDisplacement = btVector3(0, 0, 0);
|
||||
_followAngularDisplacement = btQuaternion::getIdentity();
|
||||
_hasSupport = false;
|
||||
|
||||
_pendingFlags = PENDING_FLAG_UPDATE_SHAPE;
|
||||
|
||||
}
|
||||
|
||||
bool CharacterController::needsRemoval() const {
|
||||
|
@ -107,6 +112,8 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) {
|
|||
}
|
||||
}
|
||||
|
||||
static const float COS_PI_OVER_THREE = cosf(PI / 3.0f);
|
||||
|
||||
bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) const {
|
||||
int numManifolds = collisionWorld->getDispatcher()->getNumManifolds();
|
||||
for (int i = 0; i < numManifolds; i++) {
|
||||
|
@ -119,8 +126,10 @@ bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) cons
|
|||
btManifoldPoint& pt = contactManifold->getContactPoint(j);
|
||||
|
||||
// check to see if contact point is touching the bottom sphere of the capsule.
|
||||
// and the contact normal is not slanted too much.
|
||||
float contactPointY = (obA == _rigidBody) ? pt.m_localPointA.getY() : pt.m_localPointB.getY();
|
||||
if (contactPointY < -_halfHeight) {
|
||||
btVector3 normal = (obA == _rigidBody) ? pt.m_normalWorldOnB : -pt.m_normalWorldOnB;
|
||||
if (contactPointY < -_halfHeight && normal.dot(_currentUp) > COS_PI_OVER_THREE) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -153,72 +162,61 @@ void CharacterController::preStep(btCollisionWorld* collisionWorld) {
|
|||
}
|
||||
|
||||
void CharacterController::playerStep(btCollisionWorld* dynaWorld, btScalar dt) {
|
||||
btVector3 actualVelocity = _rigidBody->getLinearVelocity();
|
||||
btScalar actualSpeed = actualVelocity.length();
|
||||
|
||||
btVector3 desiredVelocity = _walkVelocity;
|
||||
btScalar desiredSpeed = desiredVelocity.length();
|
||||
|
||||
const btScalar MIN_UP_PUSH = 0.1f;
|
||||
if (desiredVelocity.dot(_currentUp) < MIN_UP_PUSH) {
|
||||
_isPushingUp = false;
|
||||
}
|
||||
|
||||
const btScalar MIN_SPEED = 0.001f;
|
||||
if (_isHovering) {
|
||||
if (desiredSpeed < MIN_SPEED) {
|
||||
if (actualSpeed < MIN_SPEED) {
|
||||
_rigidBody->setLinearVelocity(btVector3(0.0f, 0.0f, 0.0f));
|
||||
} else {
|
||||
const btScalar HOVER_BRAKING_TIMESCALE = 0.1f;
|
||||
btScalar tau = glm::max(dt / HOVER_BRAKING_TIMESCALE, 1.0f);
|
||||
_rigidBody->setLinearVelocity((1.0f - tau) * actualVelocity);
|
||||
}
|
||||
} else {
|
||||
const btScalar HOVER_ACCELERATION_TIMESCALE = 0.1f;
|
||||
btScalar tau = dt / HOVER_ACCELERATION_TIMESCALE;
|
||||
_rigidBody->setLinearVelocity(actualVelocity - tau * (actualVelocity - desiredVelocity));
|
||||
|
||||
btVector3 actualVelocity = _rigidBody->getLinearVelocity();
|
||||
if (actualVelocity.length() < MIN_SPEED) {
|
||||
actualVelocity = btVector3(0.0f, 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
btVector3 desiredVelocity = _walkVelocity;
|
||||
if (desiredVelocity.length() < MIN_SPEED) {
|
||||
desiredVelocity = btVector3(0.0f, 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
// decompose into horizontal and vertical components.
|
||||
btVector3 actualVertVelocity = actualVelocity.dot(_currentUp) * _currentUp;
|
||||
btVector3 actualHorizVelocity = actualVelocity - actualVertVelocity;
|
||||
btVector3 desiredVertVelocity = desiredVelocity.dot(_currentUp) * _currentUp;
|
||||
btVector3 desiredHorizVelocity = desiredVelocity - desiredVertVelocity;
|
||||
|
||||
btVector3 finalVelocity;
|
||||
|
||||
switch (_state) {
|
||||
case State::Ground:
|
||||
case State::Takeoff:
|
||||
{
|
||||
// horizontal ground control
|
||||
const btScalar WALK_ACCELERATION_TIMESCALE = 0.1f;
|
||||
btScalar tau = dt / WALK_ACCELERATION_TIMESCALE;
|
||||
finalVelocity = tau * desiredHorizVelocity + (1.0f - tau) * actualHorizVelocity + actualVertVelocity;
|
||||
}
|
||||
} else {
|
||||
if (onGround()) {
|
||||
// walking on ground
|
||||
if (desiredSpeed < MIN_SPEED) {
|
||||
if (actualSpeed < MIN_SPEED) {
|
||||
_rigidBody->setLinearVelocity(btVector3(0.0f, 0.0f, 0.0f));
|
||||
} else {
|
||||
const btScalar HOVER_BRAKING_TIMESCALE = 0.1f;
|
||||
btScalar tau = dt / HOVER_BRAKING_TIMESCALE;
|
||||
_rigidBody->setLinearVelocity((1.0f - tau) * actualVelocity);
|
||||
}
|
||||
} else {
|
||||
// TODO: modify desiredVelocity using floor normal
|
||||
const btScalar WALK_ACCELERATION_TIMESCALE = 0.1f;
|
||||
btScalar tau = dt / WALK_ACCELERATION_TIMESCALE;
|
||||
btVector3 velocityCorrection = tau * (desiredVelocity - actualVelocity);
|
||||
// subtract vertical component
|
||||
velocityCorrection -= velocityCorrection.dot(_currentUp) * _currentUp;
|
||||
_rigidBody->setLinearVelocity(actualVelocity + velocityCorrection);
|
||||
}
|
||||
} else {
|
||||
// transitioning to flying
|
||||
btVector3 velocityCorrection = desiredVelocity - actualVelocity;
|
||||
break;
|
||||
case State::InAir:
|
||||
{
|
||||
// horizontal air control
|
||||
const btScalar IN_AIR_ACCELERATION_TIMESCALE = 2.0f;
|
||||
btScalar tau = dt / IN_AIR_ACCELERATION_TIMESCALE;
|
||||
finalVelocity = tau * desiredHorizVelocity + (1.0f - tau) * actualHorizVelocity + actualVertVelocity;
|
||||
}
|
||||
break;
|
||||
case State::Hover:
|
||||
{
|
||||
// vertical and horizontal air control
|
||||
const btScalar FLY_ACCELERATION_TIMESCALE = 0.2f;
|
||||
btScalar tau = dt / FLY_ACCELERATION_TIMESCALE;
|
||||
if (!_isPushingUp) {
|
||||
// actually falling --> compute a different velocity attenuation factor
|
||||
const btScalar FALL_ACCELERATION_TIMESCALE = 2.0f;
|
||||
tau = dt / FALL_ACCELERATION_TIMESCALE;
|
||||
// zero vertical component
|
||||
velocityCorrection -= velocityCorrection.dot(_currentUp) * _currentUp;
|
||||
}
|
||||
_rigidBody->setLinearVelocity(actualVelocity + tau * velocityCorrection);
|
||||
finalVelocity = tau * desiredVelocity + (1.0f - tau) * actualVelocity;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
_rigidBody->setLinearVelocity(finalVelocity);
|
||||
|
||||
// Dynamicaly compute a follow velocity to move this body toward the _followDesiredBodyTransform.
|
||||
// Rather then add this velocity to velocity the RigidBody, we explicitly teleport the RigidBody towards its goal.
|
||||
// This mirrors the computation done in MyAvatar::FollowHelper::postPhysicsUpdate().
|
||||
// These two computations must be kept in sync.
|
||||
|
||||
const float MINIMUM_TIME_REMAINING = 0.005f;
|
||||
const float MAX_DISPLACEMENT = 0.5f * _radius;
|
||||
_followTimeRemaining -= dt;
|
||||
|
@ -254,21 +252,7 @@ void CharacterController::playerStep(btCollisionWorld* dynaWorld, btScalar dt) {
|
|||
}
|
||||
|
||||
void CharacterController::jump() {
|
||||
// check for case where user is holding down "jump" key...
|
||||
// we'll eventually tansition to "hover"
|
||||
if (!_isJumping) {
|
||||
if (!_isHovering) {
|
||||
_jumpToHoverStart = usecTimestampNow();
|
||||
_pendingFlags |= PENDING_FLAG_JUMP;
|
||||
}
|
||||
} else {
|
||||
quint64 now = usecTimestampNow();
|
||||
const quint64 JUMP_TO_HOVER_PERIOD = 75 * (USECS_PER_SECOND / 100);
|
||||
if (now - _jumpToHoverStart > JUMP_TO_HOVER_PERIOD) {
|
||||
_isPushingUp = true;
|
||||
setHovering(true);
|
||||
}
|
||||
}
|
||||
_pendingFlags |= PENDING_FLAG_JUMP;
|
||||
}
|
||||
|
||||
bool CharacterController::onGround() const {
|
||||
|
@ -276,18 +260,44 @@ bool CharacterController::onGround() const {
|
|||
return _floorDistance < FLOOR_PROXIMITY_THRESHOLD || _hasSupport;
|
||||
}
|
||||
|
||||
void CharacterController::setHovering(bool hover) {
|
||||
if (hover != _isHovering) {
|
||||
_isHovering = hover;
|
||||
_isJumping = false;
|
||||
#ifdef DEBUG_STATE_CHANGE
|
||||
static const char* stateToStr(CharacterController::State state) {
|
||||
switch (state) {
|
||||
case CharacterController::State::Ground:
|
||||
return "Ground";
|
||||
case CharacterController::State::Takeoff:
|
||||
return "Takeoff";
|
||||
case CharacterController::State::InAir:
|
||||
return "InAir";
|
||||
case CharacterController::State::Hover:
|
||||
return "Hover";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
#endif // #ifdef DEBUG_STATE_CHANGE
|
||||
|
||||
if (_rigidBody) {
|
||||
if (hover) {
|
||||
#ifdef DEBUG_STATE_CHANGE
|
||||
void CharacterController::setState(State desiredState, const char* reason) {
|
||||
#else
|
||||
void CharacterController::setState(State desiredState) {
|
||||
#endif
|
||||
if (desiredState != _state) {
|
||||
#ifdef DEBUG_STATE_CHANGE
|
||||
qCDebug(physics) << "CharacterController::setState" << stateToStr(desiredState) << "from" << stateToStr(_state) << "," << reason;
|
||||
#endif
|
||||
if (desiredState == State::Hover && _state != State::Hover) {
|
||||
// hover enter
|
||||
if (_rigidBody) {
|
||||
_rigidBody->setGravity(btVector3(0.0f, 0.0f, 0.0f));
|
||||
} else {
|
||||
}
|
||||
} else if (_state == State::Hover && desiredState != State::Hover) {
|
||||
// hover exit
|
||||
if (_rigidBody) {
|
||||
_rigidBody->setGravity(DEFAULT_CHARACTER_GRAVITY * _currentUp);
|
||||
}
|
||||
}
|
||||
_state = desiredState;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -335,9 +345,8 @@ void CharacterController::setEnabled(bool enabled) {
|
|||
_pendingFlags |= PENDING_FLAG_REMOVE_FROM_SIMULATION;
|
||||
}
|
||||
_pendingFlags &= ~ PENDING_FLAG_ADD_TO_SIMULATION;
|
||||
_isOnGround = false;
|
||||
}
|
||||
setHovering(true);
|
||||
SET_STATE(State::Hover, "setEnabled");
|
||||
_enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
@ -345,7 +354,7 @@ void CharacterController::setEnabled(bool enabled) {
|
|||
void CharacterController::updateUpAxis(const glm::quat& rotation) {
|
||||
btVector3 oldUp = _currentUp;
|
||||
_currentUp = quatRotate(glmToBullet(rotation), LOCAL_UP_AXIS);
|
||||
if (!_isHovering) {
|
||||
if (_state != State::Hover) {
|
||||
const btScalar MIN_UP_ERROR = 0.01f;
|
||||
if (oldUp.distance(_currentUp) > MIN_UP_ERROR) {
|
||||
_rigidBody->setGravity(DEFAULT_CHARACTER_GRAVITY * _currentUp);
|
||||
|
@ -410,6 +419,10 @@ void CharacterController::preSimulation() {
|
|||
if (_enabled && _dynamicsWorld) {
|
||||
// slam body to where it is supposed to be
|
||||
_rigidBody->setWorldTransform(_characterBodyTransform);
|
||||
btVector3 velocity = _rigidBody->getLinearVelocity();
|
||||
|
||||
btVector3 actualVertVelocity = velocity.dot(_currentUp) * _currentUp;
|
||||
btVector3 actualHorizVelocity = velocity - actualVertVelocity;
|
||||
|
||||
// scan for distant floor
|
||||
// rayStart is at center of bottom sphere
|
||||
|
@ -424,32 +437,72 @@ void CharacterController::preSimulation() {
|
|||
_dynamicsWorld->rayTest(rayStart, rayEnd, rayCallback);
|
||||
if (rayCallback.hasHit()) {
|
||||
_floorDistance = rayLength * rayCallback.m_closestHitFraction - _radius;
|
||||
const btScalar MIN_HOVER_HEIGHT = 3.0f;
|
||||
if (_isHovering && _floorDistance < MIN_HOVER_HEIGHT && !_isPushingUp) {
|
||||
setHovering(false);
|
||||
}
|
||||
// TODO: use collision events rather than ray-trace test to disable jumping
|
||||
const btScalar JUMP_PROXIMITY_THRESHOLD = 0.1f * _radius;
|
||||
if (_floorDistance < JUMP_PROXIMITY_THRESHOLD) {
|
||||
_isJumping = false;
|
||||
}
|
||||
} else if (!_hasSupport) {
|
||||
} else {
|
||||
_floorDistance = FLT_MAX;
|
||||
setHovering(true);
|
||||
}
|
||||
|
||||
if (_pendingFlags & PENDING_FLAG_JUMP) {
|
||||
_pendingFlags &= ~ PENDING_FLAG_JUMP;
|
||||
if (onGround()) {
|
||||
_isJumping = true;
|
||||
btVector3 velocity = _rigidBody->getLinearVelocity();
|
||||
const btScalar JUMP_PROXIMITY_THRESHOLD = 0.1f * _radius;
|
||||
const quint64 TAKE_OFF_TO_IN_AIR_PERIOD = 200 * MSECS_PER_SECOND;
|
||||
const btScalar MIN_HOVER_HEIGHT = 2.5f;
|
||||
const quint64 JUMP_TO_HOVER_PERIOD = 750 * MSECS_PER_SECOND;
|
||||
const btScalar MAX_WALKING_SPEED = 2.5f;
|
||||
|
||||
quint64 now = usecTimestampNow();
|
||||
|
||||
// record a time stamp when the jump button was first pressed.
|
||||
if ((_previousFlags & PENDING_FLAG_JUMP) != (_pendingFlags & PENDING_FLAG_JUMP)) {
|
||||
if (_pendingFlags & PENDING_FLAG_JUMP) {
|
||||
_jumpButtonDownStart = now;
|
||||
_jumpButtonDownCount++;
|
||||
}
|
||||
}
|
||||
|
||||
bool jumpButtonHeld = _pendingFlags & PENDING_FLAG_JUMP;
|
||||
bool flyingFast = _state == State::Hover && actualHorizVelocity.length() > (MAX_WALKING_SPEED * 0.75f);
|
||||
|
||||
switch (_state) {
|
||||
case State::Ground:
|
||||
if (!rayCallback.hasHit() && !_hasSupport) {
|
||||
SET_STATE(State::Hover, "no ground");
|
||||
} else if (_pendingFlags & PENDING_FLAG_JUMP) {
|
||||
_takeOffJumpButtonID = _jumpButtonDownCount;
|
||||
SET_STATE(State::Takeoff, "jump pressed");
|
||||
}
|
||||
break;
|
||||
case State::Takeoff:
|
||||
if (!rayCallback.hasHit() && !_hasSupport) {
|
||||
SET_STATE(State::Hover, "no ground");
|
||||
} else if ((now - _takeoffToInAirStart) > TAKE_OFF_TO_IN_AIR_PERIOD) {
|
||||
SET_STATE(State::InAir, "takeoff done");
|
||||
_takeoffToInAirStart = now + USECS_PER_SECOND * 86500.0f;
|
||||
velocity += _jumpSpeed * _currentUp;
|
||||
_rigidBody->setLinearVelocity(velocity);
|
||||
}
|
||||
break;
|
||||
case State::InAir: {
|
||||
if ((velocity.dot(_currentUp) <= (JUMP_SPEED / 2.0f)) && ((_floorDistance < JUMP_PROXIMITY_THRESHOLD) || _hasSupport)) {
|
||||
SET_STATE(State::Ground, "hit ground");
|
||||
} else if (jumpButtonHeld && (_takeOffJumpButtonID != _jumpButtonDownCount)) {
|
||||
SET_STATE(State::Hover, "double jump button");
|
||||
} else if (jumpButtonHeld && (now - _jumpButtonDownStart) > JUMP_TO_HOVER_PERIOD) {
|
||||
SET_STATE(State::Hover, "jump button held");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case State::Hover:
|
||||
if ((_floorDistance < MIN_HOVER_HEIGHT) && !jumpButtonHeld && !flyingFast) {
|
||||
SET_STATE(State::InAir, "near ground");
|
||||
} else if (((_floorDistance < JUMP_PROXIMITY_THRESHOLD) || _hasSupport) && !flyingFast) {
|
||||
SET_STATE(State::Ground, "touching ground");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
_followTime = 0.0f;
|
||||
|
||||
_previousFlags = _pendingFlags;
|
||||
_pendingFlags &= ~PENDING_FLAG_JUMP;
|
||||
|
||||
_followTime = 0.0f;
|
||||
_followLinearDisplacement = btVector3(0, 0, 0);
|
||||
_followAngularDisplacement = btQuaternion::getIdentity();
|
||||
}
|
||||
|
|
|
@ -31,6 +31,8 @@ class btRigidBody;
|
|||
class btCollisionWorld;
|
||||
class btDynamicsWorld;
|
||||
|
||||
//#define DEBUG_STATE_CHANGE
|
||||
|
||||
class CharacterController : public btCharacterControllerInterface {
|
||||
public:
|
||||
CharacterController();
|
||||
|
@ -75,8 +77,14 @@ public:
|
|||
|
||||
glm::vec3 getLinearVelocity() const;
|
||||
|
||||
bool isHovering() const { return _isHovering; }
|
||||
void setHovering(bool enabled);
|
||||
enum class State {
|
||||
Ground = 0,
|
||||
Takeoff,
|
||||
InAir,
|
||||
Hover
|
||||
};
|
||||
|
||||
State getState() const { return _state; }
|
||||
|
||||
void setLocalBoundingBox(const glm::vec3& corner, const glm::vec3& scale);
|
||||
|
||||
|
@ -86,6 +94,12 @@ public:
|
|||
bool getRigidBodyLocation(glm::vec3& avatarRigidBodyPosition, glm::quat& avatarRigidBodyRotation);
|
||||
|
||||
protected:
|
||||
#ifdef DEBUG_STATE_CHANGE
|
||||
void setState(State state, const char* reason);
|
||||
#else
|
||||
void setState(State state);
|
||||
#endif
|
||||
|
||||
void updateUpAxis(const glm::quat& rotation);
|
||||
bool checkForSupport(btCollisionWorld* collisionWorld) const;
|
||||
|
||||
|
@ -100,7 +114,10 @@ protected:
|
|||
|
||||
glm::vec3 _boxScale; // used to compute capsule shape
|
||||
|
||||
quint64 _jumpToHoverStart;
|
||||
quint64 _takeoffToInAirStart;
|
||||
quint64 _jumpButtonDownStart;
|
||||
quint32 _jumpButtonDownCount;
|
||||
quint32 _takeOffJumpButtonID;
|
||||
|
||||
btScalar _halfHeight;
|
||||
btScalar _radius;
|
||||
|
@ -116,16 +133,13 @@ protected:
|
|||
btQuaternion _followAngularDisplacement;
|
||||
|
||||
bool _enabled;
|
||||
bool _isOnGround;
|
||||
bool _isJumping;
|
||||
bool _isFalling;
|
||||
bool _isHovering;
|
||||
State _state;
|
||||
bool _isPushingUp;
|
||||
|
||||
btDynamicsWorld* _dynamicsWorld { nullptr };
|
||||
btRigidBody* _rigidBody { nullptr };
|
||||
uint32_t _pendingFlags { 0 };
|
||||
|
||||
uint32_t _previousFlags { 0 };
|
||||
};
|
||||
|
||||
#endif // hifi_CharacterControllerInterface_h
|
||||
|
|
Loading…
Reference in a new issue