Merge pull request #7032 from hyperlogic/tony/standing-jump

MyAvatar: Standing Takeoff and In-Air Animations
This commit is contained in:
Andrew Meadows 2016-02-05 13:45:52 -08:00
commit 09bdccb18c
9 changed files with 302 additions and 87 deletions

View file

@ -248,8 +248,10 @@
{ "var": "isTurningLeft", "state": "turnLeft" },
{ "var": "isAway", "state": "awayIntro" },
{ "var": "isFlying", "state": "fly" },
{ "var": "isTakeoff", "state": "takeoff" },
{ "var": "isInAir", "state": "inAir" }
{ "var": "isTakeoffStand", "state": "takeoffStand" },
{ "var": "isTakeoffRun", "state": "takeoffRun" },
{ "var": "isInAirStand", "state": "inAirStand" },
{ "var": "isInAirRun", "state": "inAirRun" }
]
},
{
@ -266,8 +268,10 @@
{ "var": "isTurningLeft", "state": "turnLeft" },
{ "var": "isAway", "state": "awayIntro" },
{ "var": "isFlying", "state": "fly" },
{ "var": "isTakeoff", "state": "takeoff" },
{ "var": "isInAir", "state": "inAir" }
{ "var": "isTakeoffStand", "state": "takeoffStand" },
{ "var": "isTakeoffRun", "state": "takeoffRun" },
{ "var": "isInAirStand", "state": "inAirStand" },
{ "var": "isInAirRun", "state": "inAirRun" }
]
},
{
@ -283,8 +287,10 @@
{ "var": "isTurningLeft", "state": "turnLeft" },
{ "var": "isAway", "state": "awayIntro" },
{ "var": "isFlying", "state": "fly" },
{ "var": "isTakeoff", "state": "takeoff" },
{ "var": "isInAir", "state": "inAir" }
{ "var": "isTakeoffStand", "state": "takeoffStand" },
{ "var": "isTakeoffRun", "state": "takeoffRun" },
{ "var": "isInAirStand", "state": "inAirStand" },
{ "var": "isInAirRun", "state": "inAirRun" }
]
},
{
@ -300,8 +306,10 @@
{ "var": "isTurningLeft", "state": "turnLeft" },
{ "var": "isAway", "state": "awayIntro" },
{ "var": "isFlying", "state": "fly" },
{ "var": "isTakeoff", "state": "takeoff" },
{ "var": "isInAir", "state": "inAir" }
{ "var": "isTakeoffStand", "state": "takeoffStand" },
{ "var": "isTakeoffRun", "state": "takeoffRun" },
{ "var": "isInAirStand", "state": "inAirStand" },
{ "var": "isInAirRun", "state": "inAirRun" }
]
},
{
@ -317,8 +325,10 @@
{ "var": "isTurningLeft", "state": "turnLeft" },
{ "var": "isAway", "state": "awayIntro" },
{ "var": "isFlying", "state": "fly" },
{ "var": "isTakeoff", "state": "takeoff" },
{ "var": "isInAir", "state": "inAir" }
{ "var": "isTakeoffStand", "state": "takeoffStand" },
{ "var": "isTakeoffRun", "state": "takeoffRun" },
{ "var": "isInAirStand", "state": "inAirStand" },
{ "var": "isInAirRun", "state": "inAirRun" }
]
},
{
@ -334,8 +344,10 @@
{ "var": "isTurningLeft", "state": "turnLeft" },
{ "var": "isAway", "state": "awayIntro" },
{ "var": "isFlying", "state": "fly" },
{ "var": "isTakeoff", "state": "takeoff" },
{ "var": "isInAir", "state": "inAir" }
{ "var": "isTakeoffStand", "state": "takeoffStand" },
{ "var": "isTakeoffRun", "state": "takeoffRun" },
{ "var": "isInAirStand", "state": "inAirStand" },
{ "var": "isInAirRun", "state": "inAirRun" }
]
},
{
@ -351,8 +363,10 @@
{ "var": "isTurningLeft", "state": "turnLeft" },
{ "var": "isAway", "state": "awayIntro" },
{ "var": "isFlying", "state": "fly" },
{ "var": "isTakeoff", "state": "takeoff" },
{ "var": "isInAir", "state": "inAir" }
{ "var": "isTakeoffStand", "state": "takeoffStand" },
{ "var": "isTakeoffRun", "state": "takeoffRun" },
{ "var": "isInAirStand", "state": "inAirStand" },
{ "var": "isInAirRun", "state": "inAirRun" }
]
},
{
@ -368,8 +382,10 @@
{ "var": "isTurningRight", "state": "turnRight" },
{ "var": "isAway", "state": "awayIntro" },
{ "var": "isFlying", "state": "fly" },
{ "var": "isTakeoff", "state": "takeoff" },
{ "var": "isInAir", "state": "inAir" }
{ "var": "isTakeoffStand", "state": "takeoffStand" },
{ "var": "isTakeoffRun", "state": "takeoffRun" },
{ "var": "isInAirStand", "state": "inAirStand" },
{ "var": "isInAirRun", "state": "inAirRun" }
]
},
{
@ -407,18 +423,38 @@
]
},
{
"id": "takeoff",
"id": "takeoffStand",
"interpTarget": 0,
"interpDuration": 6,
"transitions": [
{ "var": "isAway", "state": "awayIntro" },
{ "var": "isNotTakeoff", "state": "inAir" }
{ "var": "isNotTakeoff", "state": "inAirStand" }
]
},
{
"id": "inAir",
"id": "takeoffRun",
"interpTarget": 0,
"interpDuration": 6,
"transitions": [
{ "var": "isAway", "state": "awayIntro" },
{ "var": "isNotTakeoff", "state": "inAirRun" }
]
},
{
"id": "inAirStand",
"interpTarget": 0,
"interpDuration": 6,
"interpType": "snapshotPrev",
"transitions": [
{ "var": "isAway", "state": "awayIntro" },
{ "var": "isNotInAir", "state": "idle" }
]
},
{
"id": "inAirRun",
"interpTarget": 0,
"interpDuration": 6,
"interpType": "snapshotPrev",
"transitions": [
{ "var": "isAway", "state": "awayIntro" },
{ "var": "isNotInAir", "state": "idle" }
@ -723,19 +759,31 @@
"children": []
},
{
"id": "takeoff",
"id": "takeoffStand",
"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,
"url": "http://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/jump_standing_takeoff.fbx",
"startFrame": 17.0,
"endFrame": 25.0,
"timeScale": 1.0,
"loopFlag": false
},
"children": []
},
{
"id": "inAir",
"id": "takeoffRun",
"type": "clip",
"data": {
"url": "http://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/jump_takeoff.fbx",
"startFrame": 1.0,
"endFrame": 2.5,
"timeScale": 0.01,
"loopFlag": false
},
"children": []
},
{
"id": "inAirStand",
"type": "blendLinear",
"data": {
"alpha": 0.0,
@ -743,10 +791,10 @@
},
"children": [
{
"id": "inAirPreApex",
"id": "inAirStandPreApex",
"type": "clip",
"data": {
"url": "https://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/jump_in_air.fbx",
"url": "http://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/jump_standing_apex.fbx",
"startFrame": 0.0,
"endFrame": 0.0,
"timeScale": 0.0,
@ -755,10 +803,56 @@
"children": []
},
{
"id": "inAirApex",
"id": "inAirStandApex",
"type": "clip",
"data": {
"url": "https://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/jump_in_air.fbx",
"url": "http://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/jump_standing_apex.fbx",
"startFrame": 1.0,
"endFrame": 1.0,
"timeScale": 1.0,
"loopFlag": false
},
"children": []
},
{
"id": "inAirStandPostApex",
"type": "clip",
"data": {
"url": "http://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/jump_standing_apex.fbx",
"startFrame": 2.0,
"endFrame": 2.0,
"timeScale": 1.0,
"loopFlag": false
},
"children": []
}
]
},
{
"id": "inAirRun",
"type": "blendLinear",
"data": {
"alpha": 0.0,
"alphaVar": "inAirAlpha"
},
"children": [
{
"id": "inAirRunPreApex",
"type": "clip",
"data": {
"url": "http://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": "inAirRunApex",
"type": "clip",
"data": {
"url": "http://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/jump_in_air.fbx",
"startFrame": 6.0,
"endFrame": 6.0,
"timeScale": 1.0,
@ -767,10 +861,10 @@
"children": []
},
{
"id": "inAirPostApex",
"id": "inAirRunPostApex",
"type": "clip",
"data": {
"url": "https://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/jump_in_air.fbx",
"url": "http://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/jump_in_air.fbx",
"startFrame": 11.0,
"endFrame": 11.0,
"timeScale": 1.0,

View file

@ -590,17 +590,24 @@ void AnimInverseKinematics::initConstraints() {
stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot);
stConstraint->setTwistLimits(-PI / 4.0f, PI / 4.0f);
// these directions are approximate swing limits in root-frame
// NOTE: they don't need to be normalized
std::vector<glm::vec3> swungDirections;
swungDirections.push_back(glm::vec3(mirror * 0.25f, 0.0f, 1.0f));
swungDirections.push_back(glm::vec3(mirror * -0.5f, 0.0f, 1.0f));
swungDirections.push_back(glm::vec3(mirror * -1.0f, 0.0f, 1.0f));
swungDirections.push_back(glm::vec3(mirror * -1.0f, 0.0f, 0.0f));
swungDirections.push_back(glm::vec3(mirror * -0.5f, -0.5f, -1.0f));
swungDirections.push_back(glm::vec3(mirror * 0.0f, -0.75f, -1.0f));
swungDirections.push_back(glm::vec3(mirror * 0.25f, -1.0f, 0.0f));
swungDirections.push_back(glm::vec3(mirror * 0.25f, -1.0f, 1.0f));
float deltaTheta = PI / 4.0f;
float theta = 0.0f;
swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.25f, sinf(theta)));
theta += deltaTheta;
swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.0f, sinf(theta)));
theta += deltaTheta;
swungDirections.push_back(glm::vec3(mirror * cosf(theta), -0.25f, sinf(theta))); // posterior
theta += deltaTheta;
swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.0f, sinf(theta)));
theta += deltaTheta;
swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.25f, sinf(theta)));
theta += deltaTheta;
swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.5f, sinf(theta)));
theta += deltaTheta;
swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.5f, sinf(theta))); // anterior
theta += deltaTheta;
swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.5f, sinf(theta)));
// rotate directions into joint-frame
glm::quat invAbsoluteRotation = glm::inverse(absolutePoses[i].rot);
@ -755,7 +762,7 @@ void AnimInverseKinematics::initConstraints() {
// we determine the max/min angles by rotating the swing limit lines from parent- to child-frame
// then measure the angles to swing the yAxis into alignment
const float MIN_KNEE_ANGLE = 0.0f;
const float MAX_KNEE_ANGLE = 3.0f * PI / 4.0f;
const float MAX_KNEE_ANGLE = 7.0f * PI / 8.0f;
glm::quat invReferenceRotation = glm::inverse(referenceRotation);
glm::vec3 minSwingAxis = invReferenceRotation * glm::angleAxis(MIN_KNEE_ANGLE, hingeAxis) * Vectors::UNIT_Y;
glm::vec3 maxSwingAxis = invReferenceRotation * glm::angleAxis(MAX_KNEE_ANGLE, hingeAxis) * Vectors::UNIT_Y;

View file

@ -67,6 +67,16 @@ static AnimNode::Type stringToAnimNodeType(const QString& str) {
return AnimNode::Type::NumTypes;
}
static AnimStateMachine::InterpType stringToInterpType(const QString& str) {
if (str == "snapshotBoth") {
return AnimStateMachine::InterpType::SnapshotBoth;
} else if (str == "snapshotPrev") {
return AnimStateMachine::InterpType::SnapshotPrev;
} else {
return AnimStateMachine::InterpType::NumTypes;
}
}
static const char* animManipulatorJointVarTypeToString(AnimManipulator::JointVar::Type type) {
switch (type) {
case AnimManipulator::JointVar::Type::AbsoluteRotation: return "absoluteRotation";
@ -465,9 +475,11 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj,
READ_STRING(id, stateObj, nodeId, jsonUrl, false);
READ_FLOAT(interpTarget, stateObj, nodeId, jsonUrl, false);
READ_FLOAT(interpDuration, stateObj, nodeId, jsonUrl, false);
READ_OPTIONAL_STRING(interpType, stateObj);
READ_OPTIONAL_STRING(interpTargetVar, stateObj);
READ_OPTIONAL_STRING(interpDurationVar, stateObj);
READ_OPTIONAL_STRING(interpTypeVar, stateObj);
auto iter = childMap.find(id);
if (iter == childMap.end()) {
@ -475,7 +487,16 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj,
return false;
}
auto statePtr = std::make_shared<AnimStateMachine::State>(id, iter->second, interpTarget, interpDuration);
AnimStateMachine::InterpType interpTypeEnum = AnimStateMachine::InterpType::SnapshotBoth; // default value
if (!interpType.isEmpty()) {
interpTypeEnum = stringToInterpType(interpType);
if (interpTypeEnum == AnimStateMachine::InterpType::NumTypes) {
qCCritical(animation) << "AnimNodeLoader, bad interpType on stateMachine state, nodeId = " << nodeId << "stateId =" << id << "url = " << jsonUrl.toDisplayString();
return false;
}
}
auto statePtr = std::make_shared<AnimStateMachine::State>(id, iter->second, interpTarget, interpDuration, interpTypeEnum);
assert(statePtr);
if (!interpTargetVar.isEmpty()) {
@ -484,6 +505,9 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj,
if (!interpDurationVar.isEmpty()) {
statePtr->setInterpDurationVar(interpDurationVar);
}
if (!interpTypeVar.isEmpty()) {
statePtr->setInterpTypeVar(interpTypeVar);
}
smNode->addState(statePtr);
stateMap.insert(StateMap::value_type(statePtr->getID(), statePtr));

View file

@ -52,8 +52,25 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, fl
if (_duringInterp) {
_alpha += _alphaVel * dt;
if (_alpha < 1.0f) {
if (_poses.size() > 0 && _nextPoses.size() > 0 && _prevPoses.size() > 0) {
::blend(_poses.size(), &_prevPoses[0], &_nextPoses[0], _alpha, &_poses[0]);
AnimPoseVec* nextPoses = nullptr;
AnimPoseVec* prevPoses = nullptr;
AnimPoseVec localNextPoses;
if (_interpType == InterpType::SnapshotBoth) {
// interp between both snapshots
prevPoses = &_prevPoses;
nextPoses = &_nextPoses;
} else if (_interpType == InterpType::SnapshotPrev) {
// interp between the prev snapshot and evaluated next target.
// this is useful for interping into a blend
localNextPoses = currentStateNode->evaluate(animVars, dt, triggersOut);
prevPoses = &_prevPoses;
nextPoses = &localNextPoses;
} else {
assert(false);
}
if (_poses.size() > 0 && nextPoses && prevPoses && nextPoses->size() > 0 && prevPoses->size() > 0) {
::blend(_poses.size(), &(prevPoses->at(0)), &(nextPoses->at(0)), _alpha, &_poses[0]);
}
} else {
_duringInterp = false;
@ -86,16 +103,32 @@ void AnimStateMachine::switchState(const AnimVariantMap& animVars, State::Pointe
_alpha = 0.0f;
float duration = std::max(0.001f, animVars.lookup(desiredState->_interpDurationVar, desiredState->_interpDuration));
_alphaVel = FRAMES_PER_SECOND / duration;
_prevPoses = _poses;
nextStateNode->setCurrentFrame(desiredState->_interpTarget);
_interpType = (InterpType)animVars.lookup(desiredState->_interpTypeVar, (int)desiredState->_interpType);
// because dt is 0, we should not encounter any triggers
const float dt = 0.0f;
Triggers triggers;
_nextPoses = nextStateNode->evaluate(animVars, dt, triggers);
if (_interpType == InterpType::SnapshotBoth) {
// snapshot previous pose.
_prevPoses = _poses;
// snapshot next pose at the target frame.
nextStateNode->setCurrentFrame(desiredState->_interpTarget);
_nextPoses = nextStateNode->evaluate(animVars, dt, triggers);
} else if (_interpType == InterpType::SnapshotPrev) {
// snapshot previoius pose
_prevPoses = _poses;
// no need to evaluate _nextPoses we will do it dynamically during the interp,
// however we need to set the current frame.
nextStateNode->setCurrentFrame(desiredState->_interpTarget - duration);
} else {
assert(false);
}
#if WANT_DEBUG
qCDebug(animation) << "AnimStateMachine::switchState:" << _currentState->getID() << "->" << desiredState->getID() << "duration =" << duration << "targetFrame =" << desiredState->_interpTarget;
qCDebug(animation) << "AnimStateMachine::switchState:" << _currentState->getID() << "->" << desiredState->getID() << "duration =" << duration << "targetFrame =" << desiredState->_interpTarget << "interpType = " << (int)_interpType;
#endif
_currentState = desiredState;
}

View file

@ -31,13 +31,27 @@
// visible after interpolation is complete.
// * interpDuration - (frames) The total length of time it will take to interp between the current pose and the
// interpTarget frame.
// * interpType - How the interpolation is performed.
// * SnapshotBoth: Stores two snapshots, the previous animation before interpolation begins and the target state at the
// interTarget frame. Then during the interpolation period the two snapshots are interpolated to produce smooth motion between them.
// * SnapshotPrev: Stores a snapshot of the previous animation before interpolation begins. However the target state is
// evaluated dynamically. During the interpolation period the previous snapshot is interpolated with the target pose
// to produce smooth motion between them. This mode is useful for interping into a blended animation where the actual
// blend factor is not known at the start of the interp or is might change dramatically during the interp.
class AnimStateMachine : public AnimNode {
public:
friend class AnimNodeLoader;
friend bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl);
enum class InterpType {
SnapshotBoth = 0,
SnapshotPrev,
NumTypes
};
protected:
class State {
public:
friend AnimStateMachine;
@ -55,14 +69,16 @@ protected:
State::Pointer _state;
};
State(const QString& id, int childIndex, float interpTarget, float interpDuration) :
State(const QString& id, int childIndex, float interpTarget, float interpDuration, InterpType interpType) :
_id(id),
_childIndex(childIndex),
_interpTarget(interpTarget),
_interpDuration(interpDuration) {}
_interpDuration(interpDuration),
_interpType(interpType) {}
void setInterpTargetVar(const QString& interpTargetVar) { _interpTargetVar = interpTargetVar; }
void setInterpDurationVar(const QString& interpDurationVar) { _interpDurationVar = interpDurationVar; }
void setInterpTypeVar(const QString& interpTypeVar) { _interpTypeVar = interpTypeVar; }
int getChildIndex() const { return _childIndex; }
const QString& getID() const { return _id; }
@ -78,9 +94,11 @@ protected:
int _childIndex;
float _interpTarget; // frames
float _interpDuration; // frames
InterpType _interpType;
QString _interpTargetVar;
QString _interpDurationVar;
QString _interpTypeVar;
std::vector<Transition> _transitions;
@ -115,6 +133,7 @@ protected:
// interpolation state
bool _duringInterp = false;
InterpType _interpType { InterpType::SnapshotBoth };
float _alphaVel = 0.0f;
float _alpha = 0.0f;
AnimPoseVec _prevPoses;

View file

@ -627,6 +627,8 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos
// Skip hystersis timer for jump transitions.
if (_desiredState == RigRole::Takeoff) {
_desiredStateAge = STATE_CHANGE_HYSTERESIS_TIMER;
} else if (_state == RigRole::Takeoff && _desiredState == RigRole::InAir) {
_desiredStateAge = STATE_CHANGE_HYSTERESIS_TIMER;
} else if (_state == RigRole::InAir && _desiredState != RigRole::InAir) {
_desiredStateAge = STATE_CHANGE_HYSTERESIS_TIMER;
}
@ -679,9 +681,11 @@ 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("isTakeoffStand", false);
_animVars.set("isTakeoffRun", false);
_animVars.set("isNotTakeoff", true);
_animVars.set("isInAir", false);
_animVars.set("isInAirStand", false);
_animVars.set("isInAirRun", false);
_animVars.set("isNotInAir", true);
}
} else if (_state == RigRole::Turn) {
@ -703,9 +707,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("isTakeoffStand", false);
_animVars.set("isTakeoffRun", false);
_animVars.set("isNotTakeoff", true);
_animVars.set("isInAir", false);
_animVars.set("isInAirStand", false);
_animVars.set("isInAirRun", false);
_animVars.set("isNotInAir", true);
} else if (_state == RigRole::Idle ) {
@ -720,9 +726,11 @@ 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("isTakeoffStand", false);
_animVars.set("isTakeoffRun", false);
_animVars.set("isNotTakeoff", true);
_animVars.set("isInAir", false);
_animVars.set("isInAirStand", false);
_animVars.set("isInAirRun", false);
_animVars.set("isNotInAir", true);
} else if (_state == RigRole::Hover) {
@ -737,9 +745,11 @@ 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("isTakeoffStand", false);
_animVars.set("isTakeoffRun", false);
_animVars.set("isNotTakeoff", true);
_animVars.set("isInAir", false);
_animVars.set("isInAirStand", false);
_animVars.set("isInAirRun", false);
_animVars.set("isNotInAir", true);
} else if (_state == RigRole::Takeoff) {
@ -754,9 +764,19 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos
_animVars.set("isNotTurning", true);
_animVars.set("isFlying", false);
_animVars.set("isNotFlying", true);
_animVars.set("isTakeoff", true);
bool takeOffRun = forwardSpeed > 0.1f;
if (takeOffRun) {
_animVars.set("isTakeoffStand", false);
_animVars.set("isTakeoffRun", true);
} else {
_animVars.set("isTakeoffStand", true);
_animVars.set("isTakeoffRun", false);
}
_animVars.set("isNotTakeoff", false);
_animVars.set("isInAir", true);
_animVars.set("isInAirStand", false);
_animVars.set("isInAirRun", false);
_animVars.set("isNotInAir", false);
} else if (_state == RigRole::InAir) {
@ -771,9 +791,18 @@ 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("isTakeoffStand", false);
_animVars.set("isTakeoffRun", false);
_animVars.set("isNotTakeoff", true);
_animVars.set("isInAir", true);
bool inAirRun = forwardSpeed > 0.1f;
if (inAirRun) {
_animVars.set("isInAirStand", false);
_animVars.set("isInAirRun", true);
} else {
_animVars.set("isInAirStand", true);
_animVars.set("isInAirRun", false);
}
_animVars.set("isNotInAir", false);
// compute blend based on velocity

View file

@ -301,7 +301,7 @@ public:
std::map<QString, AnimNode::Pointer> _origRoleAnimations;
std::vector<AnimNode::Pointer> _prefetchedAnimations;
bool _lastEnableInverseKinematics { false };
bool _lastEnableInverseKinematics { true };
bool _enableInverseKinematics { true };
private:

View file

@ -60,9 +60,10 @@ CharacterController::CharacterController() {
_jumpSpeed = JUMP_SPEED;
_state = State::Hover;
_isPushingUp = false;
_jumpButtonDownStart = 0;
_rayHitStartTime = 0;
_takeoffToInAirStartTime = 0;
_jumpButtonDownStartTime = 0;
_jumpButtonDownCount = 0;
_takeoffToInAirStart = 0;
_followTime = 0.0f;
_followLinearDisplacement = btVector3(0, 0, 0);
_followAngularDisplacement = btQuaternion::getIdentity();
@ -417,6 +418,8 @@ glm::vec3 CharacterController::getLinearVelocity() const {
void CharacterController::preSimulation() {
if (_enabled && _dynamicsWorld) {
quint64 now = usecTimestampNow();
// slam body to where it is supposed to be
_rigidBody->setWorldTransform(_characterBodyTransform);
btVector3 velocity = _rigidBody->getLinearVelocity();
@ -432,27 +435,30 @@ void CharacterController::preSimulation() {
btScalar rayLength = _radius + MAX_FALL_HEIGHT;
btVector3 rayEnd = rayStart - rayLength * _currentUp;
const btScalar JUMP_PROXIMITY_THRESHOLD = 0.1f * _radius;
const quint64 TAKE_OFF_TO_IN_AIR_PERIOD = 250 * MSECS_PER_SECOND;
const btScalar MIN_HOVER_HEIGHT = 2.5f;
const quint64 JUMP_TO_HOVER_PERIOD = 1100 * MSECS_PER_SECOND;
const btScalar MAX_WALKING_SPEED = 2.5f;
const quint64 RAY_HIT_START_PERIOD = 500 * MSECS_PER_SECOND;
ClosestNotMe rayCallback(_rigidBody);
rayCallback.m_closestHitFraction = 1.0f;
_dynamicsWorld->rayTest(rayStart, rayEnd, rayCallback);
if (rayCallback.hasHit()) {
bool rayHasHit = rayCallback.hasHit();
if (rayHasHit) {
_rayHitStartTime = now;
_floorDistance = rayLength * rayCallback.m_closestHitFraction - _radius;
} else if ((now - _rayHitStartTime) < RAY_HIT_START_PERIOD) {
rayHasHit = true;
} else {
_floorDistance = FLT_MAX;
}
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;
_jumpButtonDownStartTime = now;
_jumpButtonDownCount++;
}
}
@ -462,19 +468,21 @@ void CharacterController::preSimulation() {
switch (_state) {
case State::Ground:
if (!rayCallback.hasHit() && !_hasSupport) {
SET_STATE(State::Hover, "no ground");
} else if (_pendingFlags & PENDING_FLAG_JUMP) {
_takeOffJumpButtonID = _jumpButtonDownCount;
if (!rayHasHit && !_hasSupport) {
SET_STATE(State::Hover, "no ground detected");
} else if (_pendingFlags & PENDING_FLAG_JUMP && _jumpButtonDownCount != _takeoffJumpButtonID) {
_takeoffJumpButtonID = _jumpButtonDownCount;
_takeoffToInAirStartTime = now;
SET_STATE(State::Takeoff, "jump pressed");
} else if (rayHasHit && !_hasSupport && _floorDistance > JUMP_PROXIMITY_THRESHOLD) {
SET_STATE(State::InAir, "falling");
}
break;
case State::Takeoff:
if (!rayCallback.hasHit() && !_hasSupport) {
if (!rayHasHit && !_hasSupport) {
SET_STATE(State::Hover, "no ground");
} else if ((now - _takeoffToInAirStart) > TAKE_OFF_TO_IN_AIR_PERIOD) {
} else if ((now - _takeoffToInAirStartTime) > 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);
}
@ -482,9 +490,9 @@ void CharacterController::preSimulation() {
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)) {
} else if (jumpButtonHeld && (_takeoffJumpButtonID != _jumpButtonDownCount)) {
SET_STATE(State::Hover, "double jump button");
} else if (jumpButtonHeld && (now - _jumpButtonDownStart) > JUMP_TO_HOVER_PERIOD) {
} else if (jumpButtonHeld && (now - _jumpButtonDownStartTime) > JUMP_TO_HOVER_PERIOD) {
SET_STATE(State::Hover, "jump button held");
}
break;

View file

@ -114,10 +114,11 @@ protected:
glm::vec3 _boxScale; // used to compute capsule shape
quint64 _takeoffToInAirStart;
quint64 _jumpButtonDownStart;
quint64 _rayHitStartTime;
quint64 _takeoffToInAirStartTime;
quint64 _jumpButtonDownStartTime;
quint32 _jumpButtonDownCount;
quint32 _takeOffJumpButtonID;
quint32 _takeoffJumpButtonID;
btScalar _halfHeight;
btScalar _radius;