WIP commit, added AnimController node.

This commit is contained in:
Anthony J. Thibault 2015-09-08 19:21:56 -07:00 committed by Anthony J. Thibault
parent ae8c3a19ed
commit 75ecf0020d
8 changed files with 420 additions and 195 deletions

View file

@ -1241,8 +1241,8 @@ void MyAvatar::initHeadBones() {
void MyAvatar::initAnimGraph() {
// https://gist.github.com/hyperlogic/7d6a0892a7319c69e2b9
// python2 -m SimpleHTTPServer&
//auto graphUrl = QUrl("http://localhost:8000/avatar.json");
auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/7d6a0892a7319c69e2b9/raw/e2cb37aee601b6fba31d60eac3f6ae3ef72d4a66/avatar.json");
auto graphUrl = QUrl("http://localhost:8000/avatar.json");
//auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/7d6a0892a7319c69e2b9/raw/e2cb37aee601b6fba31d60eac3f6ae3ef72d4a66/avatar.json");
_skeletonModel.initAnimGraph(graphUrl, _skeletonModel.getGeometry()->getFBXGeometry());
}

View file

@ -0,0 +1,92 @@
//
// AnimController.cpp
//
// Created by Anthony J. Thibault on 9/8/15.
// Copyright (c) 2015 High Fidelity, Inc. All rights reserved.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "AnimController.h"
#include "AnimationLogging.h"
AnimController::AnimController(const std::string& id, float alpha) :
AnimNode(AnimNode::Type::Controller, id),
_alpha(alpha) {
}
AnimController::~AnimController() {
}
const AnimPoseVec& AnimController::evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) {
_alpha = animVars.lookup(_alphaVar, _alpha);
for (auto& jointVar : _jointVars) {
if (!jointVar.hasPerformedJointLookup) {
QString qJointName = QString::fromStdString(jointVar.jointName);
jointVar.jointIndex = _skeleton->nameToJointIndex(qJointName);
if (jointVar.jointIndex < 0) {
qCWarning(animation) << "AnimController could not find jointName" << jointVar.jointIndex << "in skeleton";
}
jointVar.hasPerformedJointLookup = true;
}
if (jointVar.jointIndex >= 0) {
AnimPose pose(animVars.lookup(jointVar.var, glm::mat4()));
_poses[jointVar.jointIndex] = pose;
}
}
return _poses;
}
const AnimPoseVec& AnimController::overlay(const AnimVariantMap& animVars, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) {
_alpha = animVars.lookup(_alphaVar, _alpha);
for (auto& jointVar : _jointVars) {
if (!jointVar.hasPerformedJointLookup) {
QString qJointName = QString::fromStdString(jointVar.jointName);
jointVar.jointIndex = _skeleton->nameToJointIndex(qJointName);
if (jointVar.jointIndex < 0) {
qCWarning(animation) << "AnimController could not find jointName" << jointVar.jointIndex << "in skeleton";
}
jointVar.hasPerformedJointLookup = true;
}
if (jointVar.jointIndex >= 0) {
AnimPose pose(animVars.lookup(jointVar.var, underPoses[jointVar.jointIndex]));
_poses[jointVar.jointIndex] = pose;
}
}
return _poses;
}
void AnimController::setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) {
AnimNode::setSkeletonInternal(skeleton);
// invalidate all jointVar indices
for (auto& jointVar : _jointVars) {
jointVar.jointIndex = -1;
jointVar.hasPerformedJointLookup = false;
}
// potentially allocate new joints.
_poses.resize(skeleton->getNumJoints());
// set all joints to identity
for (auto& pose : _poses) {
pose = AnimPose::identity;
}
}
// for AnimDebugDraw rendering
const AnimPoseVec& AnimController::getPosesInternal() const {
return _poses;
}
void AnimController::addJointVar(const JointVar& jointVar) {
_jointVars.push_back(jointVar);
}

View file

@ -0,0 +1,58 @@
//
// AnimController.h
//
// Created by Anthony J. Thibault on 9/8/15.
// Copyright (c) 2015 High Fidelity, Inc. All rights reserved.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_AnimController_h
#define hifi_AnimController_h
#include "AnimNode.h"
// Allows procedural control over a set of joints.
class AnimController : public AnimNode {
public:
friend class AnimTests;
AnimController(const std::string& id, float alpha);
virtual ~AnimController() override;
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) override;
virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) override;
void setAlphaVar(const std::string& alphaVar) { _alphaVar = alphaVar; }
virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override;
struct JointVar {
JointVar(const std::string& varIn, const std::string& jointNameIn) : var(varIn), jointName(jointNameIn), jointIndex(-1), hasPerformedJointLookup(false) {}
std::string var = "";
std::string jointName = "";
int jointIndex = -1;
bool hasPerformedJointLookup = false;
};
void addJointVar(const JointVar& jointVar);
protected:
// for AnimDebugDraw rendering
virtual const AnimPoseVec& getPosesInternal() const override;
AnimPoseVec _poses;
float _alpha;
std::string _alphaVar;
std::vector<JointVar> _jointVars;
// no copies
AnimController(const AnimController&) = delete;
AnimController& operator=(const AnimController&) = delete;
};
#endif // hifi_AnimController_h

View file

@ -40,6 +40,7 @@ public:
BlendLinear,
Overlay,
StateMachine,
Controller,
NumTypes
};
using Pointer = std::shared_ptr<AnimNode>;

View file

@ -20,21 +20,25 @@
#include "AnimOverlay.h"
#include "AnimNodeLoader.h"
#include "AnimStateMachine.h"
#include "AnimController.h"
using NodeLoaderFunc = AnimNode::Pointer (*)(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
using NodeProcessFunc = bool (*)(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
using NodeProcessFunc = AnimNode::Pointer (*)(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
// factory functions
static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
static AnimNode::Pointer loadBlendLinearNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
static AnimNode::Pointer loadOverlayNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
static AnimNode::Pointer loadStateMachineNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
static AnimNode::Pointer loadControllerNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
// called after children have been loaded
static bool processClipNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; }
static bool processBlendLinearNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; }
static bool processOverlayNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; }
bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
// returns node on success, nullptr on failure.
static AnimNode::Pointer processClipNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return node; }
static AnimNode::Pointer processBlendLinearNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return node; }
static AnimNode::Pointer processOverlayNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return node; }
AnimNode::Pointer processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
static AnimNode::Pointer processControllerNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return node; }
static const char* animNodeTypeToString(AnimNode::Type type) {
switch (type) {
@ -42,6 +46,7 @@ static const char* animNodeTypeToString(AnimNode::Type type) {
case AnimNode::Type::BlendLinear: return "blendLinear";
case AnimNode::Type::Overlay: return "overlay";
case AnimNode::Type::StateMachine: return "stateMachine";
case AnimNode::Type::Controller: return "controller";
case AnimNode::Type::NumTypes: return nullptr;
};
return nullptr;
@ -53,6 +58,7 @@ static NodeLoaderFunc animNodeTypeToLoaderFunc(AnimNode::Type type) {
case AnimNode::Type::BlendLinear: return loadBlendLinearNode;
case AnimNode::Type::Overlay: return loadOverlayNode;
case AnimNode::Type::StateMachine: return loadStateMachineNode;
case AnimNode::Type::Controller: return loadControllerNode;
case AnimNode::Type::NumTypes: return nullptr;
};
return nullptr;
@ -64,6 +70,7 @@ static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) {
case AnimNode::Type::BlendLinear: return processBlendLinearNode;
case AnimNode::Type::Overlay: return processOverlayNode;
case AnimNode::Type::StateMachine: return processStateMachineNode;
case AnimNode::Type::Controller: return processControllerNode;
case AnimNode::Type::NumTypes: return nullptr;
};
return nullptr;
@ -268,13 +275,47 @@ static AnimNode::Pointer loadStateMachineNode(const QJsonObject& jsonObj, const
return node;
}
static AnimNode::Pointer loadControllerNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) {
READ_FLOAT(alpha, jsonObj, id, jsonUrl);
auto node = std::make_shared<AnimController>(id.toStdString(), alpha);
READ_OPTIONAL_STRING(alphaVar, jsonObj);
if (!alphaVar.isEmpty()) {
node->setAlphaVar(alphaVar.toStdString());
}
auto jointsValue = jsonObj.value("joints");
if (!jointsValue.isArray()) {
qCCritical(animation) << "AnimNodeLoader, bad array \"joints\" in controller node, id =" << id << ", url =" << jsonUrl.toDisplayString();
return nullptr;
}
auto jointsArray = jointsValue.toArray();
for (const auto& jointValue : jointsArray) {
if (!jointValue.isObject()) {
qCCritical(animation) << "AnimNodeLoader, bad state object in \"joints\", id =" << id << ", url =" << jsonUrl.toDisplayString();
return nullptr;
}
auto jointObj = jointValue.toObject();
READ_STRING(var, jointObj, id, jsonUrl);
READ_STRING(jointName, jointObj, id, jsonUrl);
AnimController::JointVar jointVar(var.toStdString(), jointName.toStdString());
node->addJointVar(jointVar);
};
return node;
}
void buildChildMap(std::map<std::string, AnimNode::Pointer>& map, AnimNode::Pointer node) {
for ( auto child : node->_children ) {
map.insert(std::pair<std::string, AnimNode::Pointer>(child->_id, child));
}
}
bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl) {
AnimNode::Pointer processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl) {
auto smNode = std::static_pointer_cast<AnimStateMachine>(node);
assert(smNode);
@ -373,7 +414,7 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj,
}
smNode->setCurrentState(iter->second);
return true;
return node;
}
AnimNodeLoader::AnimNodeLoader(const QUrl& url) :

View file

@ -35,13 +35,13 @@
class AnimStateMachine : public AnimNode {
public:
friend class AnimNodeLoader;
friend bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl);
friend AnimNode::Pointer processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl);
protected:
class State {
public:
friend AnimStateMachine;
friend bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl);
friend AnimNode::Pointer processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl);
using Pointer = std::shared_ptr<State>;
using ConstPointer = std::shared_ptr<const State>;

View file

@ -952,18 +952,30 @@ void Rig::updateFromHeadParameters(const HeadParameters& params) {
void Rig::updateLeanJoint(int index, float leanSideways, float leanForward, float torsoTwist) {
if (index >= 0 && _jointStates[index].getParentIndex() >= 0) {
auto& parentState = _jointStates[_jointStates[index].getParentIndex()];
if (_enableAnimGraph && _animSkeleton) {
glm::vec3 xAxis(1.0f, 0.0f, 0.0f);
glm::vec3 yAxis(0.0f, 1.0f, 0.0f);
glm::vec3 zAxis(0.0f, 0.0f, 1.0f);
glm::quat rot = (glm::angleAxis(-RADIANS_PER_DEGREE * leanSideways, zAxis) *
glm::angleAxis(-RADIANS_PER_DEGREE * leanForward, xAxis) *
glm::angleAxis(RADIANS_PER_DEGREE * torsoTwist, yAxis));
AnimPose pose = _animSkeleton->getRelativeBindPose(index);
pose.rot = rot;
_animVars.set("lean", static_cast<glm::mat4>(pose));
} else {
auto& parentState = _jointStates[_jointStates[index].getParentIndex()];
// get the rotation axes in joint space and use them to adjust the rotation
glm::vec3 xAxis(1.0f, 0.0f, 0.0f);
glm::vec3 yAxis(0.0f, 1.0f, 0.0f);
glm::vec3 zAxis(0.0f, 0.0f, 1.0f);
glm::quat inverse = glm::inverse(parentState.getRotation() * getJointDefaultRotationInParentFrame(index));
setJointRotationInConstrainedFrame(index,
glm::angleAxis(- RADIANS_PER_DEGREE * leanSideways, inverse * zAxis) *
glm::angleAxis(- RADIANS_PER_DEGREE * leanForward, inverse * xAxis) *
glm::angleAxis(RADIANS_PER_DEGREE * torsoTwist, inverse * yAxis) *
getJointState(index).getDefaultRotation(), DEFAULT_PRIORITY);
// get the rotation axes in joint space and use them to adjust the rotation
glm::vec3 xAxis(1.0f, 0.0f, 0.0f);
glm::vec3 yAxis(0.0f, 1.0f, 0.0f);
glm::vec3 zAxis(0.0f, 0.0f, 1.0f);
glm::quat inverse = glm::inverse(parentState.getRotation() * getJointDefaultRotationInParentFrame(index));
setJointRotationInConstrainedFrame(index,
glm::angleAxis(- RADIANS_PER_DEGREE * leanSideways, inverse * zAxis) *
glm::angleAxis(- RADIANS_PER_DEGREE * leanForward, inverse * xAxis) *
glm::angleAxis(RADIANS_PER_DEGREE * torsoTwist, inverse * yAxis) *
getJointState(index).getDefaultRotation(), DEFAULT_PRIORITY);
}
}
}
@ -1026,7 +1038,7 @@ void Rig::initAnimGraph(const QUrl& url, const FBXGeometry& fbxGeometry) {
_animNode = nodeIn;
_animNode->setSkeleton(_animSkeleton);
});
connect(_animLoader.get(), &AnimNodeLoader::error, [this, url](int error, QString str) {
connect(_animLoader.get(), &AnimNodeLoader::error, [url](int error, QString str) {
qCCritical(animation) << "Error loading" << url.toDisplayString() << "code = " << error << "str =" << str;
});
}

View file

@ -2,189 +2,210 @@
"version": "1.0",
"root": {
"id": "root",
"type": "stateMachine",
"type": "overlay",
"data": {
"currentState": "idle",
"states": [
{
"id": "idle",
"interpTarget": 6,
"interpDuration": 6,
"transitions": [
{ "var": "isMovingForward", "state": "walkFwd" },
{ "var": "isMovingBackward", "state": "walkBwd" },
{ "var": "isMovingRight", "state": "strafeRight" },
{ "var": "isMovingLeft", "state": "strafeLeft" },
{ "var": "isTurningRight", "state": "turnRight" },
{ "var": "isTurningLeft", "state": "turnLeft" }
]
},
{
"id": "walkFwd",
"interpTarget": 6,
"interpDuration": 6,
"transitions": [
{ "var": "isNotMoving", "state": "idle" },
{ "var": "isMovingBackward", "state": "walkBwd" },
{ "var": "isMovingRight", "state": "strafeRight" },
{ "var": "isMovingLeft", "state": "strafeLeft" },
{ "var": "isTurningRight", "state": "turnRight" },
{ "var": "isTurningLeft", "state": "turnLeft" }
]
},
{
"id": "walkBwd",
"interpTarget": 6,
"interpDuration": 6,
"transitions": [
{ "var": "isNotMoving", "state": "idle" },
{ "var": "isMovingForward", "state": "walkFwd" },
{ "var": "isMovingRight", "state": "strafeRight" },
{ "var": "isMovingLeft", "state": "strafeLeft" },
{ "var": "isTurningRight", "state": "turnRight" },
{ "var": "isTurningLeft", "state": "turnLeft" }
]
},
{
"id": "strafeRight",
"interpTarget": 6,
"interpDuration": 6,
"transitions": [
{ "var": "isNotMoving", "state": "idle" },
{ "var": "isMovingForward", "state": "walkFwd" },
{ "var": "isMovingBackward", "state": "walkBwd" },
{ "var": "isMovingLeft", "state": "strafeLeft" },
{ "var": "isTurningRight", "state": "turnRight" },
{ "var": "isTurningLeft", "state": "turnLeft" }
]
},
{
"id": "strafeLeft",
"interpTarget": 6,
"interpDuration": 6,
"transitions": [
{ "var": "isNotMoving", "state": "idle" },
{ "var": "isMovingForward", "state": "walkFwd" },
{ "var": "isMovingBackward", "state": "walkBwd" },
{ "var": "isMovingRight", "state": "strafeRight" },
{ "var": "isTurningRight", "state": "turnRight" },
{ "var": "isTurningLeft", "state": "turnLeft" }
]
},
{
"id": "turnRight",
"interpTarget": 6,
"interpDuration": 6,
"transitions": [
{ "var": "isNotTurning", "state": "idle" },
{ "var": "isMovingForward", "state": "walkFwd" },
{ "var": "isMovingBackward", "state": "walkBwd" },
{ "var": "isMovingRight", "state": "strafeRight" },
{ "var": "isMovingLeft", "state": "strafeLeft" },
{ "var": "isTurningLeft", "state": "turnLeft" }
]
},
{
"id": "turnLeft",
"interpTarget": 6,
"interpDuration": 6,
"transitions": [
{ "var": "isNotTurning", "state": "idle" },
{ "var": "isMovingForward", "state": "walkFwd" },
{ "var": "isMovingBackward", "state": "walkBwd" },
{ "var": "isMovingRight", "state": "strafeRight" },
{ "var": "isMovingLeft", "state": "strafeLeft" },
{ "var": "isTurningRight", "state": "turnRight" }
]
}
]
"alpha": 1.0,
"boneSet": "spineOnly"
},
"children": [
{
"id": "idle",
"type": "clip",
"id": "spineLean",
"type": "controller",
"data": {
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/idle.fbx",
"startFrame": 0.0,
"endFrame": 90.0,
"timeScale": 1.0,
"loopFlag": true
"alpha": 1.0,
"joints": [
{ "var": "lean", "jointName": "Spine" }
]
},
"children": []
},
{
"id": "walkFwd",
"type": "clip",
"id": "mainStateMachine",
"type": "stateMachine",
"data": {
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_fwd.fbx",
"startFrame": 0.0,
"endFrame": 35.0,
"timeScale": 1.0,
"loopFlag": true,
"timeScaleVar": "walkTimeScale"
"currentState": "idle",
"states": [
{
"id": "idle",
"interpTarget": 6,
"interpDuration": 6,
"transitions": [
{ "var": "isMovingForward", "state": "walkFwd" },
{ "var": "isMovingBackward", "state": "walkBwd" },
{ "var": "isMovingRight", "state": "strafeRight" },
{ "var": "isMovingLeft", "state": "strafeLeft" },
{ "var": "isTurningRight", "state": "turnRight" },
{ "var": "isTurningLeft", "state": "turnLeft" }
]
},
{
"id": "walkFwd",
"interpTarget": 6,
"interpDuration": 6,
"transitions": [
{ "var": "isNotMoving", "state": "idle" },
{ "var": "isMovingBackward", "state": "walkBwd" },
{ "var": "isMovingRight", "state": "strafeRight" },
{ "var": "isMovingLeft", "state": "strafeLeft" },
{ "var": "isTurningRight", "state": "turnRight" },
{ "var": "isTurningLeft", "state": "turnLeft" }
]
},
{
"id": "walkBwd",
"interpTarget": 6,
"interpDuration": 6,
"transitions": [
{ "var": "isNotMoving", "state": "idle" },
{ "var": "isMovingForward", "state": "walkFwd" },
{ "var": "isMovingRight", "state": "strafeRight" },
{ "var": "isMovingLeft", "state": "strafeLeft" },
{ "var": "isTurningRight", "state": "turnRight" },
{ "var": "isTurningLeft", "state": "turnLeft" }
]
},
{
"id": "strafeRight",
"interpTarget": 6,
"interpDuration": 6,
"transitions": [
{ "var": "isNotMoving", "state": "idle" },
{ "var": "isMovingForward", "state": "walkFwd" },
{ "var": "isMovingBackward", "state": "walkBwd" },
{ "var": "isMovingLeft", "state": "strafeLeft" },
{ "var": "isTurningRight", "state": "turnRight" },
{ "var": "isTurningLeft", "state": "turnLeft" }
]
},
{
"id": "strafeLeft",
"interpTarget": 6,
"interpDuration": 6,
"transitions": [
{ "var": "isNotMoving", "state": "idle" },
{ "var": "isMovingForward", "state": "walkFwd" },
{ "var": "isMovingBackward", "state": "walkBwd" },
{ "var": "isMovingRight", "state": "strafeRight" },
{ "var": "isTurningRight", "state": "turnRight" },
{ "var": "isTurningLeft", "state": "turnLeft" }
]
},
{
"id": "turnRight",
"interpTarget": 6,
"interpDuration": 6,
"transitions": [
{ "var": "isNotTurning", "state": "idle" },
{ "var": "isMovingForward", "state": "walkFwd" },
{ "var": "isMovingBackward", "state": "walkBwd" },
{ "var": "isMovingRight", "state": "strafeRight" },
{ "var": "isMovingLeft", "state": "strafeLeft" },
{ "var": "isTurningLeft", "state": "turnLeft" }
]
},
{
"id": "turnLeft",
"interpTarget": 6,
"interpDuration": 6,
"transitions": [
{ "var": "isNotTurning", "state": "idle" },
{ "var": "isMovingForward", "state": "walkFwd" },
{ "var": "isMovingBackward", "state": "walkBwd" },
{ "var": "isMovingRight", "state": "strafeRight" },
{ "var": "isMovingLeft", "state": "strafeLeft" },
{ "var": "isTurningRight", "state": "turnRight" }
]
}
]
},
"children": []
},
{
"id": "walkBwd",
"type": "clip",
"data": {
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_bwd.fbx",
"startFrame": 0.0,
"endFrame": 37.0,
"timeScale": 1.0,
"loopFlag": true,
"timeScaleVar": "walkTimeScale"
},
"children": []
},
{
"id": "turnLeft",
"type": "clip",
"data": {
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/turn_left.fbx",
"startFrame": 0.0,
"endFrame": 28.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
},
{
"id": "turnRight",
"type": "clip",
"data": {
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/turn_right.fbx",
"startFrame": 0.0,
"endFrame": 30.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
},
{
"id": "strafeLeft",
"type": "clip",
"data": {
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/strafe_left.fbx",
"startFrame": 0.0,
"endFrame": 31.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
},
{
"id": "strafeRight",
"type": "clip",
"data": {
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/strafe_right.fbx",
"startFrame": 0.0,
"endFrame": 31.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
"children": [
{
"id": "idle",
"type": "clip",
"data": {
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/idle.fbx",
"startFrame": 0.0,
"endFrame": 90.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
},
{
"id": "walkFwd",
"type": "clip",
"data": {
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_fwd.fbx",
"startFrame": 0.0,
"endFrame": 35.0,
"timeScale": 1.0,
"loopFlag": true,
"timeScaleVar": "walkTimeScale"
},
"children": []
},
{
"id": "walkBwd",
"type": "clip",
"data": {
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_bwd.fbx",
"startFrame": 0.0,
"endFrame": 37.0,
"timeScale": 1.0,
"loopFlag": true,
"timeScaleVar": "walkTimeScale"
},
"children": []
},
{
"id": "turnLeft",
"type": "clip",
"data": {
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/turn_left.fbx",
"startFrame": 0.0,
"endFrame": 28.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
},
{
"id": "turnRight",
"type": "clip",
"data": {
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/turn_right.fbx",
"startFrame": 0.0,
"endFrame": 30.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
},
{
"id": "strafeLeft",
"type": "clip",
"data": {
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/strafe_left.fbx",
"startFrame": 0.0,
"endFrame": 31.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
},
{
"id": "strafeRight",
"type": "clip",
"data": {
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/strafe_right.fbx",
"startFrame": 0.0,
"endFrame": 31.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
}
]
}
]
}