mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-13 13:13:02 +02:00
Merge pull request #7032 from hyperlogic/tony/standing-jump
MyAvatar: Standing Takeoff and In-Air Animations
This commit is contained in:
commit
09bdccb18c
9 changed files with 302 additions and 87 deletions
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue